You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The cloud session mirror reads the page's turns, not a page dump (#1225)
claude.ai renders no <article> elements, so the mirror never found a message and fell to its
plan B: the whole conversation region as one 8000-character tail — the run's prompt, the
navigation labels and the exchange somewhere at the end, with no boundaries.
The page does mark its turns: one `transcript-row` per turn in a `role="feed"`, each naming its
position (`data-index`) and kind (`data-perf-row`: human / assistant / marker). The content
script now reads those: one entry per user or session turn under the page's own position,
markers skipped, text head-capped per turn (only the prompt turn is ever that long), streaming
replies re-sent in place. The page-dump fallback is gone — a page with no turn rows mirrors
nothing and says so, rather than mirroring whatever is on screen. The same row is now the
"opening message" the #1568 decoy exclusion scopes by, which on a live page it never found.
The dashboard renders by role: the user's turns as one "you ›" line (the opening turn is the
whole prompt), the session's in full, the newest kept in view. Extension 0.10.0, daemon lockstep
bumped; check.mjs fixtures move to the real row shape and cover the mirror.
Addresses #1225 (item 1; item 2, the front cut, goes with it).
Copy file name to clipboardExpand all lines: FEATURES-SPEC.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -162,6 +162,7 @@ happens while nobody is at the keyboard.
162
162
- Run on a fresh GitHub Actions runner
163
163
- Run on a Claude Code cloud session
164
164
- Chrome extension bridging claude.ai questions back to the dashboard
165
+
- A cloud session's conversation mirrored into the run view, turn by turn, as it is written
165
166
- Answer a cloud agent's question from the dashboard (typed back into claude.ai) — the same gate panel a local agent gets, multi-select and stop options included, listed with every other open question
166
167
- Browser-bridge token setting
167
168
- Web runs trust the project for Claude Code automatically — no manual trust step
Copy file name to clipboardExpand all lines: packages/chrome-extension/SPEC.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -38,7 +38,7 @@ See `## User story`, first and second items.
38
38
39
39
#### Business logic
40
40
41
-
On every claude.ai session page, the content script watches the DOM and extracts the choice the session rendered per the await protocol — a JSON block with a title, its options and their detail text, an optional recommended label, whether several may be picked at once, which start ticked, and which end the session — keyed by the cloud session id parsed from the page URL, which is what the daemon joins back to the agent's record. The service worker posts it to the daemon (`POST /_bridge/question`), deduplicating repeats. The dashboard shows the question as the gate it is, in the same panel a local agent's question gets, so answering it takes the single click a local gate takes. The daemon then composes the text that will be typed — the wording that continues the session with the picked options, or a hand-over line when a picked option ends it — and queues it as the answer. The worker polls `GET /_bridge/answer` on a fast beat, hands a queued answer to the content script in that session's tab, and the content script types that text into the composer and submits it; the worker reports the outcome (`POST /_bridge/answered`), and only a delivery the extension confirmed makes the daemon treat the question as resolved. Alongside questions, the content script mirrors the session's transcript to the daemon (`POST /_bridge/events`) and sends a self-report of what the injected script is and sees (`POST /_bridge/hello`), so the dashboard can show what the session did and diagnosis never needs a screenshot. A daemon with the bridge switched off answers no bridge route at all — turning it on is an explicit choice, since it is the one daemon surface meant to be reached from another origin.
41
+
On every claude.ai session page, the content script watches the DOM and extracts the choice the session rendered per the await protocol — a JSON block with a title, its options and their detail text, an optional recommended label, whether several may be picked at once, which start ticked, and which end the session — keyed by the cloud session id parsed from the page URL, which is what the daemon joins back to the agent's record. The service worker posts it to the daemon (`POST /_bridge/question`), deduplicating repeats. The dashboard shows the question as the gate it is, in the same panel a local agent's question gets, so answering it takes the single click a local gate takes. The daemon then composes the text that will be typed — the wording that continues the session with the picked options, or a hand-over line when a picked option ends it — and queues it as the answer. The worker polls `GET /_bridge/answer` on a fast beat, hands a queued answer to the content script in that session's tab, and the content script types that text into the composer and submits it; the worker reports the outcome (`POST /_bridge/answered`), and only a delivery the extension confirmed makes the daemon treat the question as resolved. Alongside questions, the content script mirrors the session's transcript to the daemon turn by turn, as it is written (`POST /_bridge/events`) and sends a self-report of what the injected script is and sees (`POST /_bridge/hello`), so the dashboard can show what the session did and diagnosis never needs a screenshot. A daemon with the bridge switched off answers no bridge route at all — turning it on is an explicit choice, since it is the one daemon surface meant to be reached from another origin.
Copy file name to clipboardExpand all lines: packages/chrome-extension/check.SPEC.md
+7-1Lines changed: 7 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,13 +8,19 @@ Finding the parked question, across every shape the block has been seen in:
8
8
- A page with no question block reports no question.
9
9
- The await protocol's own spec block, which renders on the page as part of the agent's prompt, never counts as a question — on its own, and when a real question follows it, in which case the real question wins.
10
10
- The protocol's two literal worked examples — the browser-handoff pair and the "Ship this?" approval pair — never count as questions.
11
-
-When the page marks its messages, everything inside the opening message is the rendered prompt: decoys there are ignored while a real question in a later message still wins, and a question-shaped block that exists only inside the opening message is never reported.
11
+
-Everything inside the opening turn is the rendered prompt: decoys there are ignored while a real question in a later turn still wins, and a question-shaped block that exists only inside the opening turn is never reported.
12
12
- Every one of those cases also checks that the composer was located and that the panel shows the question's actual title.
13
13
14
14
What is reported to the daemon:
15
15
16
16
- The question reaches the daemon in the shape the session asked it: whether several answers may be picked at once, which options start ticked, and which option ends the session, alongside the labels and their detail text — while keys the daemon does not know are dropped rather than forwarded.
17
17
18
+
Mirroring the transcript:
19
+
20
+
- The mirror is one entry per conversation turn, under the position the page gives the turn: the user's turns as the user's, the session's as the session's, markers such as "Initialized session" left out, interface glyphs and blank lines removed, and the opening turn — the run's prompt — cut to its first 8000 characters.
21
+
- When only the recent part of the transcript is rendered, positions still come from the page, not from counting what is on screen.
22
+
- A page that marks no turns mirrors nothing, and the panel says that no transcript rows were found.
23
+
18
24
Typing the dashboard's answer back into the session:
19
25
20
26
- The composer is filled and the page's send button is clicked.
console.log(`${ok ? 'PASS' : 'FAIL'} no turn rows means nothing mirrored and the panel says so (entries=${got?.length}, named=${/notranscriptrowsfound/.test(panel)})`)
0 commit comments