← Commit history
README.md+2
@@ -276,6 +276,8 @@ v1.1.20: /command returns errorCode command_not_found (HTTP 400) with did-you-me  v1.1.21: event-driven tab census + Claude session identity (John 2026-09-03: never poll the iframes again). GET /tabs and the SSE stream GET /tabs/events publish the extension host's own tabGroups census on every change; POST /claude/session-start ingests the Claude SessionStart hook (id + transcript path, before the jsonl is flushed) and GET /claude/sessions lists it; POST /claude/open-session opens an existing conversation by id and confirms the bind (retagging sdk-cli transcripts, which the Claude extension otherwise declines). Capability markers in /health: tabs.list, tabs.events, claude.open-session, claude.session-start, claude.sessions. +v1.1.78: an expired provider token is never "sign in again": the usage widgets say the token is waiting for Claude Code or Codex to refresh it, keep the last good numbers on screen dimmed, and retry every minute; the Claude widget skips the call when the file already says the token is expired.+ v1.1.77: a new AI thread unlocks the active editor group first (VS Code keeps a group locked after its terminal tab closes and then refuses the new tab).  v1.1.76: an expired provider sign-in (HTTP 401 or 403) reads "sign in to Codex again" in the slot instead of the raw HTTP error.
VERSION+1−1
@@ -1 +1 @@-1.1.77+1.1.78
bin/adom-vscode
⋯ 1 unchanged line ⋯
bin/hydrogen-widget-claude-usage+6−1
@@ -2,7 +2,7 @@ """Claude usage in the title bar: 5-hour session, weekly, and the weekly model buckets that apply to your plan, as percent used. Reads the Claude Code sign-in in ~/.claude/.credentials.json and asks the same usage endpoint the Claude Code CLI shows in /usage. Asks the provider at most once every three minutes, pauses while the window is hidden, backs off on errors."""-import json, os, sys+import json, os, sys, time sys.path.insert(0, os.path.dirname(os.path.realpath(__file__))) from importlib.machinery import SourceFileLoader marks = SourceFileLoader('hwmarks', os.path.join(os.path.dirname(os.path.realpath(__file__)), 'hydrogen-widget-marks.py')).load_module()@@ -15,6 +15,11 @@ def data():     oa = c.get('claudeAiOauth') or c     tok = oa.get('accessToken')     if not tok: return {'error': 'not signed in to Claude Code'}+    exp = oa.get('expiresAt') or 0+    if exp and exp < time.time() * 1000:+        # Do not spend a call on a token the file already says is expired; Claude Code refreshes+        # it on its next run and read_json_if_changed picks the new file up.+        e = Exception('token expired'); e.code = 401; raise e     u = lib.http_json('https://api.anthropic.com/api/oauth/usage', {'Authorization': 'Bearer ' + tok, 'anthropic-beta': 'oauth-2025-04-20', 'User-Agent': 'hydrogen-widget-claude-usage'})     bars = []     # The structured list is what Claude Code's own Usage panel draws: the session window, the
bin/hydrogen-widget-codex-usage

  
bin/hydrogen-widget-lib.py+18−6
@@ -23,6 +23,7 @@ html,body{margin:0;height:100%%;background:transparent;color:#e6ebef;font-family .v{font-variant-numeric:tabular-nums;text-align:right} .big{font-weight:600;font-variant-numeric:tabular-nums} .err{color:#f0a060}+#w.stale{opacity:.45} @media (max-height:33px){   .w{font-size:clamp(7px,37vh,12px);line-height:1.2}   .lbl{font-size:clamp(6px,30vh,9px)}.bar{width:clamp(24px,130vh,40px);height:clamp(4px,20vh,6px)}.v{min-width:2.4em}@@ -47,7 +48,9 @@ html,body{margin:0;height:100%%;background:transparent;color:#e6ebef;font-family <script> const R=%(refresh_ms)d; function bar(l,p,sev){const c=(sev==='critical'||p>=90)?'hot':(sev==='warning'||p>=70)?'warn':'';return `<span class="row"><span class="lbl">${l}</span><span class="bar ${c}"><i style="width:${Math.max(0,Math.min(100,p))}%%"></i></span><span class="v">${Math.round(p)}%%</span></span>`;}-function render(d){document.getElementById('w').innerHTML=%(render_js)s;postWidth();}+/* d.stale = the last good data while the provider is unreachable or the token is between refreshes:+   drawn dimmed, the reason in the tooltip, never an error where numbers were. */+function render(d0){const d=d0&&d0.stale?d0.stale:d0;const el=document.getElementById('w');el.classList.toggle('stale',!!(d0&&d0.stale));el.title=(d0&&d0.stale)?String(d0.error||''):'';el.innerHTML=%(render_js)s;postWidth();} /* THE WIDGET DECIDES ITS WIDTH: ask the host for the intrinsic width of what is shown in the    current layout (tall rows are narrow, the compact row is wide). Measured on a hidden clone laid    out at max-content, so the slot's current width never feeds back into the answer (rows stretch to@@ -71,19 +74,28 @@ def serve(title, data_fn, render_js, refresh_ms=60000, mark=None):     port = int(os.environ.get('HYDROGEN_WIDGET_PORT') or (sys.argv[sys.argv.index('--port')+1] if '--port' in sys.argv else 8930))     # One upstream call per interval at most, however often the page asks; on an upstream     # error the interval doubles up to 15 minutes and returns to normal on the next success.-    cache = {'t': 0, 'v': {'error': 'starting'}, 'wait': refresh_ms/1000}+    cache = {'t': 0, 'v': {'error': 'starting'}, 'wait': refresh_ms/1000, 'good': None}     lock = threading.Lock()     def data():         with lock:             if time.time() - cache['t'] >= cache['wait'] - 1:                 try:                     cache['v'] = data_fn(); cache['wait'] = refresh_ms/1000+                    if not cache['v'].get('error'): cache['good'] = cache['v']                 except Exception as e:-                    # A 401/403 from the provider is an expired sign-in, so say that instead of-                    # printing the HTTP error (ConfRoomROG 2026-09-14: "HTTP Error 401: Unauthorized").+                    # A 401/403 is almost always an EXPIRED access token, not a missing sign-in:+                    # Claude Code and Codex refresh their own tokens the next time they run, and+                    # the widget re-reads the file. Never tell a signed-in user to sign in again+                    # (AdomLapper 2026-09-14: "sign in to Claude again" while John was chatting).+                    # Say what is true, keep the last good numbers on screen (dimmed), and retry+                    # every minute instead of backing off; other errors still back off.                     code = getattr(e, 'code', None)-                    msg = ('sign in to %s again' % title.replace(' usage', '')) if code in (401, 403) else str(e)[:120]-                    cache['v'] = {'error': msg}; cache['wait'] = min(900, max(refresh_ms/1000, cache['wait']) * 2)+                    if code in (401, 403):+                        msg = '%s token expired, waiting for %s to refresh it' % (title.replace(' usage', ''), 'Claude Code' if 'Claude' in title else 'Codex')+                        cache['wait'] = 60+                    else:+                        msg = str(e)[:120]; cache['wait'] = min(900, max(refresh_ms/1000, cache['wait']) * 2)+                    cache['v'] = {'error': msg, 'stale': cache['good']} if cache['good'] else {'error': msg}                 cache['t'] = time.time()             return cache['v']     class H(BaseHTTPRequestHandler):
bin/hydrogen-widget-marks.py

  
install.sh

  
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "adom-vscode",-  "version": "1.1.77",+  "version": "1.1.78",   "type": "app",   "description": "CLI + VS Code extension that lets AI assistants and the Hydrogen Desktop frontend drive VS Code from inside an Adom container: open files, live get/set of any setting/theme/font (no reload), native streamed exec, a frontend event queue, container identity (hd/hw), api-key status/inject, extension query/update, modes, paste screenshots, and any VS Code command via a local HTTP API on port 8821 (with port-file fallback discovery).",   "license": "PROPRIETARY",
page.json+2−2
@@ -4,7 +4,7 @@   "slug": "adom-vscode",   "title": "adom-vscode",   "brief": "Let Claude, Codex and Kimi drive VS Code inside your Adom container",-  "version": "1.1.77",+  "version": "1.1.78",   "license": "PROPRIETARY",   "visibility": "public",   "hero": {@@ -141,4 +141,4 @@     "adom-vscode-extensions",     "adom-vscode-modes"   ]-}\ No newline at end of file+}