Skip to content

Commit 25b1854

Browse files
author
SqlRush
committed
Unwrap sidechain lifecycle content
1 parent 5753f16 commit 25b1854

5 files changed

Lines changed: 78 additions & 0 deletions

File tree

docs/cc-100-roadmap.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ M6 补充:remote history 现在也接受 HTTP `Link` header 中 `rel="previous
102102

103103
M6 补充:sidechain/subagent state loader 现在接受 `subagent_start`/`agent_start`/`task_start``sidechain_end`/`subagent_finish`/`agent_finish`/`task_summary` 等 subtype 别名,并归一化 `active`/`success`/`canceled`/`error` 等运行状态别名,同时读取 `subagentId`/`agentID``subagentType``finalSummary` 等 content 字段。
104104

105+
M6 补充:sidechain/subagent lifecycle content 读取现在会递归解包 `payload`/`data`/`body`/`result`/`response`/`metadata` 等 wrapper,嵌套的 subagent ID、status/outcome、summary、agent type、workspace path 和 task description 都可参与 state/list/resume 恢复。
106+
105107
M6 补充:sidechain runtime finish 现在会在写入 summary 前把 `success`/`error`/`canceled` 等状态别名归一为 `completed`/`failed`/`cancelled`,让 sidechain transcript 与主 transcript 的 lifecycle 输出保持 canonical。
106108

107109
M6 补充:sidechain agent metadata sidecar 读取现在接受 `type`/`subagentType`/`agentName`/`name``workspacePath`/`workspace`/`path`/`directory``taskDescription`/`prompt`/`input`/`command`/`title` 等字段别名,兼容历史或第三方生成的 subagent metadata。
@@ -438,6 +440,8 @@ M7 补充:prompt history `LogEntry` 读取现在接受 `sessionID`/`session`/`
438440

439441
本轮补充:sidechain/subagent state loader 接受 legacy/fork 命名的 start/finish subtype、ID/type/summary 字段别名和常见状态别名,提升旧 subagent transcript resume/list 的恢复率。
440442

443+
本轮补充:sidechain/subagent lifecycle content 读取递归解包常见 wrapper,并从嵌套 start/summary event 中恢复 subagent ID、status、summary、agent type、workspace path 和 task description,减少 fork/第三方 transcript 需要手工扁平化字段的情况。
444+
441445
本轮补充:sidechain runtime 现在会拒绝同一 sidechain ID 的 running 状态重复 start;已完成后重新 start 会被视为新 lifecycle,state loader 会清空上一轮 summary/endedAt 并使用新的 startedAt。
442446

443447
本轮补充:conversation runner 现在会在用户消息入队后计算 compact token warning state,并在触发 warning/error/auto-compact/blocking 阈值时发出 `token_warning` event;warning state 接入 blocking-limit override,auto-compact threshold 判断接入 `CLAUDE_AUTOCOMPACT_PCT_OVERRIDE`,使 runtime warning 和自动压缩使用同一套 window 输入。

docs/claude-code-go-rewrite-plan.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ test/parity/ # golden tests against TS/official behavior
183183

184184
- 已落地 JSONL/session 基础、resume/search/title 支撑、prompt history lock/buffered flush/field aliases、official subagent transcript layout、agent metadata sidecar/field aliases、sidechain runtime start/append/finish/cancel/fail 和 parent-chain append/finish 初版、sidechain manager orchestration 初版、sidechain manifest 聚合、sidechain state/list/resume/content-field aliases 初版、sidechain resume context builder、sidechain conversation/content-replacement reconstruction、transcript tail/window/metadata/index loaders、byte-budget transcript tail loader、agent-scoped content replacement metadata/record field-alias loading、session-scoped metadata reappend including AI-title/last-prompt/task-summary、transcript line offset index/window/byte-budget-window/parent-chain/resume/tail/byte-budget-tail loaders、extended transcript metadata entries/type/field aliases、transcript message/session UUID field aliases including top-level `messageUuid`/`messageId`/`id` record IDs and `role`/`entry_type`/`messageType`/`createdAt` timestamp aliases、transcript tombstone metadata delete/relink、transcript resume conversation builder、index 文本预览和 AI-title/last-prompt/task metadata 字段、流式 transcript 搜索、session list pagination、remote history token refresh、remote history 全量分页抓取/page-field/event-list/records/entries/eventList/sessionEvents/last-id/cursor/event-id/has-next alias/wrapped-data/links/paging/bare-array/connection/eventConnection/sessionEventsConnection wrapper response/edge-cursor fallback/max-pages 截断状态/before_id 续抓、remote event transcript materialization/fallback field fill/去重追加/duplicate parent guard、remote history 一步 sync 到 transcript、CLAUDE.md/memdir 扫描、team-memory secret guard、compact runner/boundary plan、conversation auto-compact、失败熔断、microcompact/cache 初版、persistent cached microcompact 初版、cache digest structural/rich-content metadata 覆盖、cache version/TTL/prune、in-memory micro cache prune、memory-cache write-through 到磁盘、atomic cache write、坏缓存默认 fail-open、session memory summary/frontmatter aliases/recall 初版、model-ranked recall session-id selection 和 invalid-selection fallback 初版、recall agent alternate/camel response keys/fenced-prose JSON extraction/scalar id parsing/nested/wrapped/collection-alias selection parsing、resume context model-assisted recall 接入、session memory rollup/prune compaction、rollup archive exclusion/merge、rune-safe rollup truncation、resume context + session memory recall、conversation recall 注入、deterministic/model-backed memory extraction 初版、extraction agent fenced-prose JSON/wrapped facts/alternate/structured field/nested source object/nested response/fact kind alias parsing 和 turn-end memory extraction 落盘。
185185
- 本轮补充:sidechain agent metadata sidecar 读取接受 `type`/`subagentType`/`agentName`/`name``workspacePath`/`workspace`/`path`/`directory``taskDescription`/`prompt`/`input`/`command`/`title` 等字段别名,避免历史或第三方 subagent sidecar 在 resume/list 时丢失 agent 类型、worktree 路径或任务描述。
186+
- 本轮补充:sidechain/subagent lifecycle content 读取会递归解包 `payload`/`data`/`body`/`result`/`response`/`metadata` 等 wrapper,嵌套的 subagent ID、status/outcome、summary、agent type、workspace path 和 task description 都可参与 state/list/resume 恢复。
186187
- 本轮补充:transcript metadata loader 为 file-history snapshot 和 attribution snapshot 建立 `messageId` 索引,并接受 `message_id`/`messageUuid`/`id` 等字段别名,保留 raw list 的同时支持按消息恢复 snapshot。
187188
- 本轮补充:transcript message/index/session list 读取 `gitBranch`,兼容 `git_branch`/`branch` 别名,并让 session search 可以按分支名命中,贴近官方 lite metadata 的 branch 展示和检索行为。
188189
- 本轮补充:full transcript `TitleFromTranscript` 的标题优先级和 indexed/lite 路径对齐,按 custom title、AI title、首个用户 prompt、last-prompt、summary 顺序兜底。
@@ -335,6 +336,7 @@ test/parity/ # golden tests against TS/official behavior
335336
- 本轮补充:sidechain/subagent state loader 接受 `subagent_start`/`agent_start`/`task_start``sidechain_end`/`subagent_finish`/`agent_finish`/`task_summary` 等 subtype 别名,并归一化 `active`/`success`/`canceled`/`error` 等状态别名。
336337
- 本轮补充:sidechain runtime finish 在写入 summary 前会把 `success`/`error`/`canceled` 等状态别名归一为 `completed`/`failed`/`cancelled`,sidechain transcript 和主 transcript 的 lifecycle 输出保持 canonical。
337338
- 本轮补充:sidechain runtime 现在会拒绝同一 sidechain ID 的 running 状态重复 start;已完成后重新 start 会被视为新 lifecycle,state loader 会清空上一轮 summary/endedAt 并使用新的 startedAt。
339+
- 本轮补充:sidechain/subagent lifecycle content 读取现在递归解包 `payload`/`data`/`body`/`result`/`response`/`metadata` 等 wrapper,并从嵌套 start event 中恢复 agent type、workspace path 和 task description。
338340
- 本轮补充:interaction script 的 `message` step 接受 `type`/`speaker` role 别名和 `content`/`body`/`message` text 别名;`image` step 和 iTerm2 image hint 接受 `fileName`/`file_name`/`name`、`mimeType`/`mime_type`/`contentType`、source path/URL 和 `data`/`base64` 内容别名;permission request step 接受 request/permission/tool-use ID、path、description 和 action 字段别名,并允许 `actions` 使用单字符串;`expectPrompt` 接受 `value`/`input`/`content`/`message`、`expandedText`/`fullText`、`cursorIndex`/`cursorPosition`、`isEmpty`/`blank` 等字段别名,且 `pastedContents` 断言接受 `pastedId`/`pastedContentId`、`kind`/`pastedType`、`value`/`data`/`base64`、`contentType`/`mimeType`、`fileName`/`name` 和 `contains` 等字段别名;`expectVim` 接受 `vimEnabled`/`isEnabled`、`vimMode`/`modeName`/`currentMode`、`vimRegister`/`registerValue`/`yankRegister`、`registerLinewise`/`linewise` 等字段别名;`expectTasks` 接受 `taskCount`/`total`/`size`/`length` 和 `statusCounts`/`countsByState` 等字段别名;`expectScreen`/`expectViewport` 接受 `columns`/`rows`、`screenWidth`/`screenHeight`、`scrollOffset`/`viewportOffset`、`visibleRows`/`lineCount` 等字段别名;`expectReverseSearch` 接受 `isActive`/`visible`/`open`、`search`/`term`/`pattern`、`cursorIndex`、`currentResult`、`matchCount`/`matches`、`noMatches` 等字段别名;`expectDialog` 可断言 body contains/not-contains、actions/action contains/not-contains、action count 和 focused action,runtime-aware scripts 会在步骤间保留 dialog focused action,且接受 `isActive`/`visible`、`dialogId`/`dialogID`、`dialogKind`、`heading`/`header`、`content`/`text`/`message` 等字段别名;`expectEvent`/`expectDialogResult` 接受 `eventType`/`event`/`name`、`payload`/`text`/`message`、`dialogId`/`dialogID`/`dialogKind`、`actionValue`/`resultStatus`/`exists`/`isStale` 等字段别名。
339341
- 本轮补充:interaction script step 接受 `resize`/`terminalSize`/`screenSize` 对象或 `[width,height]` 数组、顶层 `columns`/`rows` resize 别名、`focus`/`focused`/`blur`/`focusIn`/`focusOut` focus event 别名、`snapshot`/`snapshotId`/`snapshotLabel` capture 名称别名,以及 runtime-aware mutation 别名如 `permission`/`permissionRequest``task`/`taskStatus``removeTask`/`deleteTask``cancelPermission``cancelTasks`/`cancelReason``openTasks`/`showTasks`
340342
- 本轮补充:interaction script step 可通过 `status`/`setStatus`/`statusLine`/`baseStatus` 设置状态行;runtime-aware scripts 会把它作为 base status,并继续叠加 permission/task 计数,方便复用带 status line 的 ANSI/interaction fixture。

docs/first-second-parity-audit.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ M6 progress now includes:
168168
- `internal/session`: remote-history pagination now also accepts HTTP `Link` header URLs with `previous`/`prev`/`older`/`next` rels as continuation cursor fallbacks.
169169
- `internal/session`: sidechain state loading now accepts legacy subagent/agent/task start and finish subtype aliases, broader sidechain/subagent ID and type fields, summary aliases, and common running/completed/cancelled/failed status aliases.
170170
- `internal/session`: sidechain agent metadata sidecar loading now accepts broader agent type, workspace/worktree path, and task-description field aliases such as `subagentType`, `agentName`, `workspacePath`, `taskDescription`, `prompt`, and `title`.
171+
- `internal/session`: sidechain lifecycle content loading now recurses through wrapper objects such as `payload`, `data`, `result`, `response`, and `metadata`, preserving nested subagent IDs, status, summaries, agent type, worktree path, and task descriptions during state/list/resume recovery.
171172
- `internal/session`: sidechain runtime finish now canonicalizes status aliases such as `success`, `error`, and `canceled` before writing summary records, keeping sidechain and main transcript lifecycle output stable.
172173
- `internal/session`: sidechain runtime now rejects duplicate starts while the same sidechain ID is running, and state loading treats a later start as a fresh lifecycle by clearing stale summary/end metadata.
173174
- `internal/session`: transcript metadata loading now indexes file-history and attribution snapshots by message ID, with aliases such as `message_id`, `messageUuid`, and `id`, while preserving raw snapshot lists.

internal/session/sidechain_status.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ func LoadSidechainState(info SidechainInfo) (SidechainState, error) {
7777
if agentType := firstStringField(msg.Content, "agentType", "agent_type", "subagentType", "subagent_type", "agentKind", "agent_kind", "agent"); agentType != "" && state.Metadata.AgentType == "" {
7878
state.Metadata.AgentType = agentType
7979
}
80+
if worktreePath := firstStringField(msg.Content, "worktreePath", "worktree_path", "worktree", "worktreeDir", "worktree_dir", "workingDirectory", "working_directory", "cwd", "workspacePath", "workspace_path", "workspace", "path", "directory"); worktreePath != "" && state.Metadata.WorktreePath == "" {
81+
state.Metadata.WorktreePath = worktreePath
82+
}
83+
if description := firstStringField(msg.Content, "description", "description_text", "descriptionText", "desc", "summary", "task", "taskDescription", "task_description", "prompt", "input", "command", "title"); description != "" && state.Metadata.Description == "" {
84+
state.Metadata.Description = description
85+
}
8086
if status := sidechainStatusField(msg.Content, "status", "state", "phase", "lifecycle", "lifecycle_state", "lifecycleState"); status != "" {
8187
state.Status = status
8288
} else {
@@ -230,6 +236,13 @@ func stringField(value any, key string) string {
230236
}
231237

232238
func firstStringField(value any, keys ...string) string {
239+
return firstStringFieldDepth(value, keys, 0)
240+
}
241+
242+
func firstStringFieldDepth(value any, keys []string, depth int) string {
243+
if depth > 4 {
244+
return ""
245+
}
233246
switch fields := value.(type) {
234247
case map[string]any:
235248
for _, key := range keys {
@@ -238,6 +251,13 @@ func firstStringField(value any, keys ...string) string {
238251
return raw
239252
}
240253
}
254+
for _, key := range []string{"payload", "data", "body", "content", "result", "response", "record", "entry", "item", "event", "metadata", "details"} {
255+
if raw, ok := fields[key]; ok {
256+
if value := firstStringFieldDepth(raw, keys, depth+1); value != "" {
257+
return value
258+
}
259+
}
260+
}
241261
case map[string]string:
242262
for _, key := range keys {
243263
if raw := fields[key]; raw != "" {

internal/session/sidechain_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,57 @@ func TestLoadSidechainStateAcceptsSubtypeAndStatusAliases(t *testing.T) {
221221
}
222222
}
223223

224+
func TestLoadSidechainStateAcceptsWrappedLifecycleContent(t *testing.T) {
225+
sessionPath := filepath.Join(t.TempDir(), "session.jsonl")
226+
sessionID := contracts.ID("sess_1")
227+
if err := AppendSidechainMessage(sessionPath, sessionID, "wrapped", TranscriptMessage{
228+
Type: "system",
229+
UUID: "start_1",
230+
Timestamp: "2026-01-01T00:00:01Z",
231+
Subtype: "agent_start",
232+
Content: map[string]any{
233+
"payload": map[string]any{
234+
"subagentID": "subagent_nested",
235+
"agentKind": "planner",
236+
"workspacePath": "/tmp/planner-worktree",
237+
"lifecycle": "in_progress",
238+
"taskDescription": "plan the rollout",
239+
},
240+
},
241+
}); err != nil {
242+
t.Fatal(err)
243+
}
244+
if err := AppendSidechainMessage(sessionPath, sessionID, "wrapped", TranscriptMessage{
245+
Type: "system",
246+
UUID: "summary_1",
247+
Timestamp: "2026-01-01T00:00:02Z",
248+
Subtype: "task_summary",
249+
Content: map[string]any{
250+
"result": map[string]any{
251+
"outcome": "failed_error",
252+
"resultSummary": "nested task failed",
253+
},
254+
},
255+
}); err != nil {
256+
t.Fatal(err)
257+
}
258+
259+
states, err := ListSidechainStates(sessionPath, sessionID)
260+
if err != nil {
261+
t.Fatal(err)
262+
}
263+
if len(states) != 1 {
264+
t.Fatalf("states = %#v", states)
265+
}
266+
state := states[0]
267+
if state.ID != "subagent_nested" || state.Status != SidechainStatusFailed || state.Summary != "nested task failed" || state.Metadata.AgentType != "planner" {
268+
t.Fatalf("state = %#v", state)
269+
}
270+
if state.Metadata.WorktreePath != "/tmp/planner-worktree" || state.Metadata.Description != "plan the rollout" {
271+
t.Fatalf("metadata = %#v", state.Metadata)
272+
}
273+
}
274+
224275
func TestLoadSidechainStateAcceptsMetadataFieldAliases(t *testing.T) {
225276
sessionPath := filepath.Join(t.TempDir(), "session.jsonl")
226277
sessionID := contracts.ID("sess_1")

0 commit comments

Comments
 (0)