Skip to content

Commit 138f908

Browse files
author
SqlRush
committed
Accept numeric sidechain lifecycle IDs
1 parent 25b1854 commit 138f908

5 files changed

Lines changed: 96 additions & 3 deletions

File tree

docs/cc-100-roadmap.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ M6 补充:sidechain/subagent state loader 现在接受 `subagent_start`/`agent
104104

105105
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 恢复。
106106

107+
M6 补充:sidechain/subagent lifecycle 的 ID 等 string-like 字段现在接受 JSON number 和 Go 数字标量,numeric subagent ID 会保留为字符串并可用于 state/list/resume 查找。
108+
107109
M6 补充:sidechain runtime finish 现在会在写入 summary 前把 `success`/`error`/`canceled` 等状态别名归一为 `completed`/`failed`/`cancelled`,让 sidechain transcript 与主 transcript 的 lifecycle 输出保持 canonical。
108110

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

443445
本轮补充:sidechain/subagent lifecycle content 读取递归解包常见 wrapper,并从嵌套 start/summary event 中恢复 subagent ID、status、summary、agent type、workspace path 和 task description,减少 fork/第三方 transcript 需要手工扁平化字段的情况。
444446

447+
本轮补充:sidechain/subagent lifecycle content 的 ID 等 string-like 字段现在接受 JSON number/数字标量,numeric subagent ID 会保留为字符串并能通过 resume fallback 找回对应 sidechain。
448+
445449
本轮补充:sidechain runtime 现在会拒绝同一 sidechain ID 的 running 状态重复 start;已完成后重新 start 会被视为新 lifecycle,state loader 会清空上一轮 summary/endedAt 并使用新的 startedAt。
446450

447451
本轮补充: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
@@ -184,6 +184,7 @@ test/parity/ # golden tests against TS/official behavior
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 路径或任务描述。
186186
- 本轮补充: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 恢复。
187+
- 本轮补充:sidechain/subagent lifecycle 的 ID 等 string-like 字段接受 JSON number 和 Go 数字标量,numeric subagent ID 会保留为字符串并可用于 state/list/resume 查找。
187188
- 本轮补充:transcript metadata loader 为 file-history snapshot 和 attribution snapshot 建立 `messageId` 索引,并接受 `message_id`/`messageUuid`/`id` 等字段别名,保留 raw list 的同时支持按消息恢复 snapshot。
188189
- 本轮补充:transcript message/index/session list 读取 `gitBranch`,兼容 `git_branch`/`branch` 别名,并让 session search 可以按分支名命中,贴近官方 lite metadata 的 branch 展示和检索行为。
189190
- 本轮补充:full transcript `TitleFromTranscript` 的标题优先级和 indexed/lite 路径对齐,按 custom title、AI title、首个用户 prompt、last-prompt、summary 顺序兜底。
@@ -337,6 +338,7 @@ test/parity/ # golden tests against TS/official behavior
337338
- 本轮补充:sidechain runtime finish 在写入 summary 前会把 `success`/`error`/`canceled` 等状态别名归一为 `completed`/`failed`/`cancelled`,sidechain transcript 和主 transcript 的 lifecycle 输出保持 canonical。
338339
- 本轮补充:sidechain runtime 现在会拒绝同一 sidechain ID 的 running 状态重复 start;已完成后重新 start 会被视为新 lifecycle,state loader 会清空上一轮 summary/endedAt 并使用新的 startedAt。
339340
- 本轮补充:sidechain/subagent lifecycle content 读取现在递归解包 `payload`/`data`/`body`/`result`/`response`/`metadata` 等 wrapper,并从嵌套 start event 中恢复 agent type、workspace path 和 task description。
341+
- 本轮补充:sidechain/subagent lifecycle content 的 ID 等 string-like 字段现在接受 JSON number/数字标量,numeric subagent ID 会保留为字符串并能通过 resume fallback 找回对应 sidechain。
340342
- 本轮补充: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` 等字段别名。
341343
- 本轮补充: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`
342344
- 本轮补充: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
@@ -169,6 +169,7 @@ M6 progress now includes:
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`.
171171
- `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.
172+
- `internal/session`: sidechain lifecycle scalar fields now accept JSON number values for IDs and other string-like fields, preserving numeric subagent IDs as strings across state/list/resume recovery.
172173
- `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.
173174
- `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.
174175
- `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: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package session
22

33
import (
4+
"encoding/json"
45
"fmt"
6+
"math"
57
"sort"
8+
"strconv"
69
"strings"
710

811
"ccgo/internal/contracts"
@@ -246,9 +249,10 @@ func firstStringFieldDepth(value any, keys []string, depth int) string {
246249
switch fields := value.(type) {
247250
case map[string]any:
248251
for _, key := range keys {
249-
raw, _ := fields[key].(string)
250-
if raw != "" {
251-
return raw
252+
if raw, ok := fields[key]; ok {
253+
if value := scalarStringField(raw); value != "" {
254+
return value
255+
}
252256
}
253257
}
254258
for _, key := range []string{"payload", "data", "body", "content", "result", "response", "record", "entry", "item", "event", "metadata", "details"} {
@@ -269,6 +273,48 @@ func firstStringFieldDepth(value any, keys []string, depth int) string {
269273
return ""
270274
}
271275

276+
func scalarStringField(value any) string {
277+
switch raw := value.(type) {
278+
case string:
279+
return raw
280+
case json.Number:
281+
return raw.String()
282+
case int:
283+
return strconv.Itoa(raw)
284+
case int8:
285+
return strconv.FormatInt(int64(raw), 10)
286+
case int16:
287+
return strconv.FormatInt(int64(raw), 10)
288+
case int32:
289+
return strconv.FormatInt(int64(raw), 10)
290+
case int64:
291+
return strconv.FormatInt(raw, 10)
292+
case uint:
293+
return strconv.FormatUint(uint64(raw), 10)
294+
case uint8:
295+
return strconv.FormatUint(uint64(raw), 10)
296+
case uint16:
297+
return strconv.FormatUint(uint64(raw), 10)
298+
case uint32:
299+
return strconv.FormatUint(uint64(raw), 10)
300+
case uint64:
301+
return strconv.FormatUint(raw, 10)
302+
case float32:
303+
return formatScalarFloat(float64(raw), 32)
304+
case float64:
305+
return formatScalarFloat(raw, 64)
306+
default:
307+
return ""
308+
}
309+
}
310+
311+
func formatScalarFloat(value float64, bitSize int) string {
312+
if math.IsNaN(value) || math.IsInf(value, 0) {
313+
return ""
314+
}
315+
return strconv.FormatFloat(value, 'f', -1, bitSize)
316+
}
317+
272318
func replaceJSONLExt(path string, ext string) string {
273319
if path == "" {
274320
return ""

internal/session/sidechain_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,46 @@ func TestLoadSidechainStateAcceptsWrappedLifecycleContent(t *testing.T) {
272272
}
273273
}
274274

275+
func TestLoadSidechainStateAcceptsNumericLifecycleIDs(t *testing.T) {
276+
sessionPath := filepath.Join(t.TempDir(), "session.jsonl")
277+
sessionID := contracts.ID("sess_1")
278+
if err := AppendSidechainMessage(sessionPath, sessionID, "numeric-fallback", TranscriptMessage{
279+
Type: "system",
280+
UUID: "start_1",
281+
Timestamp: "2026-01-01T00:00:01Z",
282+
Subtype: "subagent_start",
283+
Content: map[string]any{
284+
"payload": map[string]any{
285+
"subagentID": 42,
286+
"subagentType": "coder",
287+
"workspacePath": "/tmp/coder-worktree",
288+
"status": "started",
289+
},
290+
},
291+
}); err != nil {
292+
t.Fatal(err)
293+
}
294+
295+
states, err := ListSidechainStates(sessionPath, sessionID)
296+
if err != nil {
297+
t.Fatal(err)
298+
}
299+
if len(states) != 1 {
300+
t.Fatalf("states = %#v", states)
301+
}
302+
state := states[0]
303+
if state.ID != "42" || state.Status != SidechainStatusRunning || state.Metadata.AgentType != "coder" || state.Metadata.WorktreePath != "/tmp/coder-worktree" {
304+
t.Fatalf("state = %#v", state)
305+
}
306+
run, ok, err := ResumeSidechainRun(sessionPath, sessionID, "42")
307+
if err != nil {
308+
t.Fatal(err)
309+
}
310+
if !ok || run.ID != "42" || run.Path != state.Path || run.Status != SidechainStatusRunning {
311+
t.Fatalf("run = %#v ok=%v state=%#v", run, ok, state)
312+
}
313+
}
314+
275315
func TestLoadSidechainStateAcceptsMetadataFieldAliases(t *testing.T) {
276316
sessionPath := filepath.Join(t.TempDir(), "session.jsonl")
277317
sessionID := contracts.ID("sess_1")

0 commit comments

Comments
 (0)