Conversation
ɵcreateDevtoolsEmitter(adapter) returns a per-agent emitter that brackets
each protocol event (or a pseudo-event such as run:start) and dispatches
one `threadplane:devtools` CustomEvent on window naming the signals the
event wrote: { v: 1, agent, adapter, seq, eventType, wrote, tMs }. Names
and timing only; signal values are never seen. Brackets nest by joining
the outer report, names outside the adapter's closed vocabulary are
dropped, a report with no names is not sent, and dispatch failures are
swallowed.
It returns null unless `(typeof ngDevMode === 'undefined' || ngDevMode)
&& isDevMode()`, outside a browser, or when the page set
`window.__THREADPLANE_DEVTOOLS_DISABLED__ = true`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… devtools hook In development, agent() wraps every subject in the bag so a write reports its name (the key without `$`) without reading the value, and the bridge brackets processEvent with the stream event's type. Writes outside an event are labelled run:start, run:end, history, reset, queue, submit or branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ols hook In development, toAgent() wraps set/update on the store's vocabulary signals and interruptSession so a write reports its name without reading the value, and brackets each onEvent call with the event's type. Writes outside an event are labelled run:start, run:end, history, reset or submit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ent it verify-devtools-bundle.mjs bundles the built chat, langgraph and ag-ui packages with esbuild as an application would, once with ngDevMode=false and once without, and asserts the production bundle contains neither `threadplane:devtools` nor the opt-out flag while the development bundle (positive control) contains both. Runs in CI after the library build as `nx run chat:test-devtools-bundle`. The langgraph and ag-ui READMEs describe what the hook reports, the dev-only gate and the opt-out; the parity inventory records the new private seam. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
blove
added a commit
to blove/ag-ui-chrome-extension
that referenced
this pull request
Oct 1, 2026
… Threadplane dev app (#57) * docs: Signals view design and plans (§14.3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(signals): the Threadplane devtools report contract (G2, G4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(signals): capture Threadplane devtools reports (G3, G5) The MAIN world listens for threadplane:devtools on its own window (every frame the capture runs in), validates, copies and re-checks the detail, and posts a connectionless signals arm. The relay rebuilds it with cloneReport; the worker keeps a bounded per-tab ring (5,000, eviction counted), cleared with the tab's buffer, its tail mirrored to session storage, and delivered on snapshot and append. E2E: only the valid reports reach the worker, in order, including one from a subframe. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(panel): the Signals tab (G6, G7) A matrix per Threadplane agent instance: the adapter's signal names as rows (vocabulary order), the hook's events as columns (seq order, eventType headers), a lit cell where the event wrote the signal. Capped at the last 500 columns per block with a visible note. A column click finds the wire frame (core/signals/match.ts: event name, then order within the agent, then nearest tMs within 1 s) and selects it in Timeline; with no frame the tab says so quietly. G7's empty state verbatim. PanelState.signals folds from snapshot/append (bounded, eviction counted, empty for imports per G8); report-only appends are coalesced per ~16 ms on the panel side. Visual gate: checkSignals seeds a live snapshot through a test-only harness shim. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(signals): review fixes - match: compare a report's eventType with the frame's name read the way its adapter reads it, verbatim (Threadplane reports namespaced LangGraph names like messages|research:t1 unchanged), cut at 128 like the hook - cloneReport: bound the copy by the contract, not a re-read length (a Proxy array could hang the page's dispatch) - protocol: state exactly what the page can learn from the signals arm and why the connectionless exemption does not weaken the relay - tests: cross-realm detail, growing length, Threadplane-shaped reports (cacheplane/threadplane#1203) through the validator Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(panel): Signals column headers show full event names Monospace headers sized for 20 characters, with an abbreviation that keeps the distinguishing tail for longer names; the full name stays in title and the accessible name. The visual gate asserts unclipped full names, and the seed uses the wrote lists Threadplane actually reports. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(harness): opt-in Threadplane acceptance (acceptance:threadplane) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: Signals view status, privacy note and listing line (§14.3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The devtools bundle check (libs/chat/scripts/) imports esbuild, which is tooling that never ships; @nx/dependency-checks flagged it and failed nx lint chat. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hange Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
In development, each LangGraph and AG-UI agent reports which of its signals each event wrote. The AG-UI DevTools Chrome extension reads these reports to fill its Signals tab: a matrix with signals as rows and events as columns. The emitter lives in
@threadplane/chatas the privateɵcreateDevtoolsEmitter. Both adapters already depend on that package, so this needs no new package or release change.The contract
After handling an event, the agent dispatches this on
window:StreamSubjectskeys without$:status values messages error interrupt interrupts branch history isThreadLoading toolProgress toolCalls messageMetadata subagents queue custom.ReducerStorefields plus the interrupt-session signal:messages status isLoading error toolCalls state interrupt customEvents activities interruptSession.usageandpendingClientToolCallIdsare not in the contract and are not reported.eventType: the LangGraphStreamEvent.typeor the AG-UIevent.type. Writes that no protocol event caused carry a pseudo-event label instead:run:start run:end history reset submit queue branch.The extension validates exactly this shape: own properties only, the exact key set, the vocabulary for each adapter, and the length limits. The emitter enforces the same rules, so it can never send a report the extension would reject.
Privacy model
BehaviorSubject.next(LangGraph) andWritableSignal.set/update(AG-UI) and passes each argument through untouched. It never reads the written value or a signal's current value, and it never runs anupdatecallback itself. Nothing from the conversation is in a report; the extension already has the wire traffic.nullunless(typeof ngDevMode === 'undefined' || ngDevMode) && isDevMode(), which is the gate the telemetry runtime already uses. It also returnsnulloutside a browser. When the emitter isnull, nothing is wrapped and the adapters behave exactly as before. A production build definesngDevMode = false, which removes the whole hook from the bundle (see the bundle check below).CustomEventgets no reply, so the page cannot tell whether anything is listening, and the extension's presence stays hidden. An event that wrote nothing produces no report. If dispatch throws, the error is swallowed.window.__THREADPLANE_DEVTOOLS_DISABLED__ = true(only the valuetruecounts) turns the hook off. It is checked when the agent is created, so no wrappers are installed, and again before each dispatch, so setting it later also works.How writes are attributed
processEventand AG-UI bracketsonEvent. Every write made while the bracket is open belongs to that event, including the adapter's own follow-up work: subagent settling, interrupt-session publishing, rollback.setBranch, and retry/regenerate/submit preambles are bracketed under the pseudo-event labels.clientTools.resolve/settlewritingtoolCalls).Tests
libs/chat/src/lib/devtools/devtools-emitter.spec.ts(14 tests):seqper agent, no report for zero names, nested brackets, the vocabulary filter, the 128-character limitisDevMode()false,ngDevMode = false, opt-out before creation and later, onlytruecountslibs/langgraph/src/lib/devtools.spec.ts(8 tests, throughagent()withMockAgentTransport). Representativewrotelists:messagestuple →['messages', 'messageMetadata', 'subagents', 'toolCalls']valueswith messages →['values', 'messages', 'subagents', 'toolCalls']updates→['values'];custom→['custom'];error→['subagents', 'error', 'status']run:start ['status', 'error', 'custom', 'toolProgress', 'messages']→values ['values']→run:end ['subagents']→run:end ['status']setBranch→branch ['branch'];switchThread→ oneresetreport;retry→submit ['error'], thenrun:start; history refresh →historyreportsseqsequences, and every report stays inside the contractlibs/ag-ui/src/lib/devtools.spec.ts(7 tests, throughtoAgent()with a scriptedFakeAgent). Representativewrotelists:RUN_STARTED→['status', 'isLoading', 'error', 'interrupt', 'customEvents', 'activities']TEXT_MESSAGE_CONTENT→['messages']STATE_SNAPSHOTandSTATE_DELTA→['state', 'messages']TOOL_CALL_START→['toolCalls', 'messages'];TOOL_CALL_ARGS→['toolCalls'];CUSTOM→['customEvents']RUN_FINISHED→['messages', 'status', 'isLoading', 'interruptSession', 'interrupt']RUN_ERROR→['messages', 'status', 'isLoading', 'error', 'state']stop()→run:end ['state', 'messages', 'status', 'isLoading', 'error'];regenerate()→submit ['messages']Proxywhose every trap throws). The LangGraph test writes it through a subject whosevalueandgetValue()throw. The AG-UI test writes it through a signal whose getter throws, and callsupdatewith a callback that throws if it is run. In both, the write must succeed, the value must reach the subject or signal as the same object, andwrotemust receive only the name.Bundle check
libs/chat/scripts/verify-devtools-bundle.mjs(run withnx run chat:test-devtools-bundle; added to the Library CI job after the production build):@threadplane/chat,@threadplane/langgraphand@threadplane/ag-uiwith esbuild (minified, other imports external), with both adapters'provideAgentas the entry.ngDevMode=false): asserts the bundle contains neitherthreadplane:devtoolsnor__THREADPLANE_DEVTOOLS_DISABLED__.ngDevModegate from the built chat bundle and ran the check; it failed with "the production bundle still contains threadplane:devtools".Verification run locally
nx run-many -t lint,test,type-tests --projects=chat,langgraph,ag-uinx run-many -t build --projects=chat,langgraph,ag-ui --configuration=productionnx run chat:test-devtools-bundlenx run langgraph:runtime-quality,langgraph:runtime-type-tests,ag-ui:runtime-quality,ag-ui:runtime-type-testsscripts/react-parity/inventory.mjs --check(baseline refreshed; dispositions added for the new private seam),verify-boundaries.mjs, and the react-parity script specscheck-dx-coverage.mjsgenerate-api-docs: no diff, because the new exports are@internal🤖 Generated with Claude Code