fix(session): preserve tool metadata across pending→running transition#20198
fix(session): preserve tool metadata across pending→running transition#20198rmk40 wants to merge 1 commit intoanomalyco:devfrom
Conversation
|
The following comment was made by an LLM, it may be inaccurate: Based on my search results, I found one related PR: Related PR Found:
Why it's related: PR #20183 addresses the same issue (preserving metadata during session state transitions) and is explicitly mentioned in the current PR description as a related fix. Both are addressing the problem of metadata being lost when a session transitions from pending to running state, which prevents subagent session navigation from working in the TUI. The current PR (#20198) appears to be a more comprehensive fix using a side-channel approach, while #20183 may be an earlier attempt or partial fix for the same root cause. |
6498eb6 to
367f492
Compare
|
Thanks for updating your PR! It now meets our contributing guidelines. 👍 |
The AI SDK fires tool execute() as a detached promise before the
processor handles the tool-call stream event. When execute() calls
ctx.metadata({sessionId}), the processor's tool-call handler
overwrites the DB with a fresh running state that has no metadata,
making subagent sessions unclickable in the TUI.
- Add synchronous toolMetadata side-channel on processor context
- setToolMetadata() on Handle writes to the map immediately
- tool-call handler reads and merges metadata from the side-channel
- prompt.ts metadata callback calls setToolMetadata() before the
async DB update, ensuring metadata is captured regardless of
event ordering
- Add E2E regression test using fake Anthropic HTTP server through
the real AI SDK streamText pipeline with gate-based assertion of
running-state metadata
Closes anomalyco#20184
367f492 to
2747b5e
Compare
|
Split into two PRs: #20208 (test fixtures) and a follow-up PR (fix + regression test). |
Issue for this PR
Closes #20184
Type of change
What does this PR do?
The AI SDK fires tool
execute()as a detached promise before the processor handles thetool-callstream event. Whenexecute()callsctx.metadata({sessionId}), the processor'stool-callhandler overwrites the DB with a fresh{status: "running", input, time}state that drops the metadata. This makes subagent sessions unclickable in the TUI because the TUI readsstate.metadata.sessionIdfor navigation.The fix adds a synchronous
toolMetadatamap on the processor context.setToolMetadata()on the Handle writes to this map immediately when the metadata callback fires. Thetool-callhandler then reads from the map and mergestitle/metadatainto the running state.prompt.ts's metadata callback callssetToolMetadata()synchronously before its existing async DB update, so metadata is captured regardless of event ordering.This PR also introduces two reusable test fixtures:
test/fixture/anthropic.ts— Fake Anthropic HTTP server (Bun.serve({port:0})) with SSE response helpers (toolResponse,textResponse,waitRequest,deferred). Reusable for any test that needs to exercise the real AI SDKstreamTextpipeline against a fake provider.test/fixture/prompt-layers.ts— FullSessionPromptEffect layer stack with realLLM.defaultLayerand no-op stubs for MCP/LSP/FileTime. Reusable for any test that needs the complete prompt pipeline (tool registration,resolveTools, processor event handling) without mocking the LLM.Related: #20183
How did you verify your code works?
test/session/metadata-race.test.ts) that goes through the full production path:ToolRegistry.register→SessionPrompt.loop→resolveTools→ctx.metadata()→setToolMetadata→ processortool-callhandler. Uses the fake Anthropic server fixture with real AI SDKstreamText. Assertsmetadata.sessionIdis present on the running tool part in the DB.setToolMetadatamissing from Handle), confirming it catches the regression.prompt-effect.test.ts(23 tests) andprocessor-effect.test.ts(10 tests) still pass.Screenshots / recordings
N/A — data layer fix, not UI change.
Checklist