Claude agent: Phase 13 — session restoration#316343
Merged
Merged
Conversation
Implement IAgent.getSessionMessages for the Claude provider so the workbench can reload an existing Claude session's full transcript across agent-host restarts. Unblocks self-hosting. - claudeAgentSdkService.ts: add getSessionMessages binding - claudeReplayMapper.ts (NEW): SessionMessage[] -> readonly Turn[] per CONTEXT M7. Pure function; no by-products, no persistence. Splits SDK shape detection (parseSessionMessage adapter) from the stateful reducer (ReplayBuilder). - claudeAgent.ts: replace getSessionMessages stub with subagent URI dispatch + provisional check + SDK fetch + replay, all wrapped with the listSessions-style warn-log-and-return-[] resilience. - claudeReplayMapper.test.ts: 10 fixtures covering M7 grouping rules (text/tool_result/system, tail-Turn state, subagent markers, CLI-echo synthetic-message drop). - claudeAgent.test.ts: 4 Phase 13 integration tests (happy path, subagent URI, provisional session, SDK throw resilience). - CONTEXT.md: relax M7 notification gate to admit all priorities pending real-world data on priority: 'low' content. - phase13-plan.md (NEW) + roadmap.md: capture decisions, drift, council-review fixes, and mark Phase 13 done.
Contributor
Contributor
There was a problem hiding this comment.
Pull request overview
Implements Claude session restoration by adding SDK transcript fetching and replaying Claude SessionMessage[] into agent-host Turn[], allowing restored Claude sessions to reload transcript history after agent-host restarts.
Changes:
- Adds
IClaudeAgentSdkService.getSessionMessagesand wiresClaudeAgent.getSessionMessages. - Introduces
claudeReplayMapper.tsplus fixture coverage for text, tool calls, system notifications, subagent markers, and CLI echo filtering. - Updates Phase 13 planning/context docs and related test fakes for the widened SDK interface.
Show a summary per file
| File | Description |
|---|---|
src/vs/platform/agentHost/node/claude/claudeAgent.ts |
Fetches SDK transcripts and maps them to restored turns. |
src/vs/platform/agentHost/node/claude/claudeAgentSdkService.ts |
Adds the SDK binding for session transcript reads. |
src/vs/platform/agentHost/node/claude/claudeReplayMapper.ts |
New replay mapper from Claude transcript messages to protocol turns. |
src/vs/platform/agentHost/node/claude/CONTEXT.md |
Updates system notification replay guidance. |
src/vs/platform/agentHost/node/claude/phase13-plan.md |
Documents Phase 13 scope, decisions, tests, and deferred work. |
src/vs/platform/agentHost/node/claude/roadmap.md |
Marks Phase 13 complete and updates related roadmap decisions. |
src/vs/platform/agentHost/test/node/claudeAgent.test.ts |
Adds agent-level restoration tests and fake SDK support. |
src/vs/platform/agentHost/test/node/claudeAgent.integrationTest.ts |
Updates integration fake to satisfy the new SDK service interface. |
src/vs/platform/agentHost/test/node/claudeReplayMapper.test.ts |
Adds replay mapper fixture tests. |
Copilot's findings
- Files reviewed: 9/9 changed files
- Comments generated: 1
Copilot review caught that on-disk JSONL system entries put `subtype`
(and `content` for compact_boundary, `text` for notification) at the
top level of the envelope alongside `type`, NOT nested inside
`message`. The SDK's `SessionMessage` type only declares
`{ type, uuid, session_id, message, parent_tool_use_id }` so the
extra envelope fields aren't typed — but the production session
parser fixtures (extensions/copilot/.../claudeSessionParser.spec.ts)
confirm the on-disk shape.
Net effect of the bug: real `compact_boundary` and `notification`
entries were silently dropped even with `includeSystemMessages: true`,
defeating the whole point of asking for them.
- claudeReplayMapper.ts: parseSystemMessage now reads from the
envelope via a single narrow cast; new readSystemEnvelopeText
prefers `text` (notification) then `content` (compact_boundary).
- claudeReplayMapper.test.ts: makeSystem helper now builds
envelope-shaped fixtures; Fixture 4 asserts the actual text
surfaces.
This reverts f77f3f0. Listening to the SDK contract over the production parser's raw-JSONL fixtures: the SDK's `SessionMessage` type declares `{ type, uuid, session_id, message: unknown, parent_tool_use_id }` for ALL three discriminants. For user/assistant we already read the discriminant-specific payload (role, content, blocks) from `message.*`; doing the same for system (`message.subtype`, `message.text`) is the consistent pattern. The production session parser fixtures parse raw on-disk JSONL — a lower layer than `getSessionMessages()`, which normalizes those entries into the documented `SessionMessage` shape with payload inside `message`. The original implementation was correct.
roblourens
approved these changes
May 14, 2026
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.
Implements
IAgent.getSessionMessagesfor the Claude provider so the workbench can reload an existing Claude session's full transcript across agent-host restarts. Unblocks self-hosting.Full plan + decisions + drift notes live in
phase13-plan.md.What changed
claudeAgentSdkService.tsgetSessionMessagesto interface + bindings + implclaudeReplayMapper.ts(new)SessionMessage[]→readonly Turn[]per CONTEXT M7. Pure function; splits SDK shape detection (parseSessionMessageadapter) from the stateful reducer (ReplayBuilder).claudeAgent.tsgetSessionMessagesstub with subagent-URI dispatch + provisional check + SDK fetch + replay, all wrapped inlistSessions-style warn-log-and-return-[]resilienceclaudeReplayMapper.test.ts(new)claudeAgent.test.tsTODO: Phase 12, provisional session returns[], SDK throw →[])CONTEXT.mdnotificationat all priorities pending real-world data onpriority: 'low'contentphase13-plan.md(new),roadmap.mdTests
compile-check-ts-nativecleanOut of scope (intentional)
(turnId, assistantEnvelopeUuid)mapping. Originally planned as live ingest + replay backfill, then as a mapper return-value by-product. Both reverted: fork (Phase 6.5) is the only consumer, fork is rare, and walking JSONL on demand at fork time is cheaper than threading state forever to optimize a cold path. Phase 6.5, if/when it lands, readsSessionMessage.uuiditself.TODO: Phase 12.IAgent.truncateSession— explicitly not implemented; clients fork instead.