Skip to content

Commit 4767b9e

Browse files
author
SqlRush
committed
Accept wrapped sidechain metadata aliases
1 parent 8121209 commit 4767b9e

4 files changed

Lines changed: 41 additions & 22 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ test/parity/ # golden tests against TS/official behavior
182182
当前进度:
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/provider-style response wrapper/alternate/structured field/nested source object/nested response/fact kind alias parsing 和 turn-end memory extraction 落盘。
185-
- 本轮补充:sidechain agent metadata sidecar 读取接受 `type`/`subagentType`/`agentName`/`name``workspaceRoot`/`workspacePath`/`workspace`/`path`/`directory``taskDescription`/`operationName`/`commandName`/`displayTitle`/`prompt`/`input`/`command`/`title` 等字段别名,避免历史或第三方 subagent sidecar 在 resume/list 时丢失 agent 类型、worktree 路径或任务描述。
185+
- 本轮补充:sidechain agent metadata sidecar 读取接受 `type`/`subagentType`/`agentName`/`name``workspaceRoot`/`workspacePath`/`workspace`/`path`/`directory``taskDescription`/`operationName`/`commandName`/`displayTitle`/`prompt`/`input`/`command`/`title` 等字段别名,支持 JSON:API/resource-style wrapper,并避免把 `resource.type` 误当成 agent 类型,避免历史或第三方 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 恢复。
187187
- 本轮补充:sidechain/subagent lifecycle content 读取现在也接受 JSON:API/resource-style 的 `resource`/`attributes`/`properties` wrapper;外层 resource `id` 可作为 sidechain ID fallback,内层 metadata、status/outcome 和 summary 仍会参与 state/list/resume 恢复。
188188
- 本轮补充:sidechain/subagent lifecycle content 读取现在也递归解包 GraphQL/JSON:API 风格的 `edge`/`node`/`attrs` wrapper,wrapped start/summary event 可继续恢复 ID、status、summary 和 agent metadata。

docs/first-second-parity-audit.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ M6 progress now includes:
205205
- `internal/session`: sidechain lifecycle content loading now also recurses through GraphQL/JSON:API `edge`/`node`/`attrs` wrappers, preserving wrapped start and summary event fields during state/list/resume recovery.
206206
- `internal/session`: sidechain lifecycle field extraction now also traverses collection wrappers such as `edges`, `nodes`, and `included`, plus array elements, so GraphQL connection or JSON:API included start/summary payloads can still recover state/list/resume fields.
207207
- `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.
208-
- `internal/session`: sidechain lifecycle and metadata sidecar loading now accept adjacent runtime aliases such as `jobId`, `threadId`, `workflowId`, `operationId`, `requestId`, `workerType`, `taskType`, `workspaceRoot`, `projectPath`, `instructions`, `operationName`, `commandName`, `displayTitle`, `jobStatus`, `resultState`, `outputText`, and millisecond start/end time fields under `runtime`/`context`/`state` wrappers.
208+
- `internal/session`: sidechain lifecycle and metadata sidecar loading now accept adjacent runtime aliases such as `jobId`, `threadId`, `workflowId`, `operationId`, `requestId`, `workerType`, `taskType`, `workspaceRoot`, `projectPath`, `instructions`, `operationName`, `commandName`, `displayTitle`, `jobStatus`, `resultState`, `outputText`, and millisecond start/end time fields under `runtime`/`context`/`state` or JSON:API/resource wrappers.
209209
- `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.
210210
- `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.
211211
- `internal/session`: sidechain lifecycle state loading now accepts adjacent subtype aliases such as `subagent_started`, `agentStarted`, `task_failed`, and `sidechainCompleted`, with broader task/worker/run ID fields plus `resultText`/`finalMessage` summaries and subtype-derived failed/cancelled defaults.

internal/session/sidechain_metadata.go

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package session
22

33
import (
4+
"bytes"
45
"encoding/json"
56
"os"
67
"path/filepath"
@@ -13,34 +14,29 @@ type SidechainMetadata struct {
1314
}
1415

1516
func (m *SidechainMetadata) UnmarshalJSON(data []byte) error {
16-
type sidechainMetadataJSON SidechainMetadata
17-
var canonical sidechainMetadataJSON
18-
if err := json.Unmarshal(data, &canonical); err != nil {
17+
decoder := json.NewDecoder(bytes.NewReader(data))
18+
decoder.UseNumber()
19+
var fields any
20+
if err := decoder.Decode(&fields); err != nil {
1921
return err
2022
}
21-
var fields map[string]any
22-
if err := json.Unmarshal(data, &fields); err != nil {
23-
return err
24-
}
25-
metadata := SidechainMetadata(canonical)
23+
metadata := SidechainMetadata{}
24+
metadata.AgentType = firstStringField(fields, sidechainLifecycleAgentTypeFields...)
2625
if metadata.AgentType == "" {
27-
metadata.AgentType = firstStringField(fields, appendSidechainMetadataFallbackFields(sidechainLifecycleAgentTypeFields, "type")...)
28-
}
29-
if metadata.WorktreePath == "" {
30-
metadata.WorktreePath = firstStringField(fields, sidechainLifecycleWorktreeFields...)
31-
}
32-
if metadata.Description == "" {
33-
metadata.Description = firstStringField(fields, sidechainLifecycleDescriptionFields...)
26+
metadata.AgentType = topLevelStringField(fields, "type")
3427
}
28+
metadata.WorktreePath = firstStringField(fields, sidechainLifecycleWorktreeFields...)
29+
metadata.Description = firstStringField(fields, sidechainLifecycleDescriptionFields...)
3530
*m = metadata
3631
return nil
3732
}
3833

39-
func appendSidechainMetadataFallbackFields(fields []string, fallback ...string) []string {
40-
out := make([]string, 0, len(fields)+len(fallback))
41-
out = append(out, fields...)
42-
out = append(out, fallback...)
43-
return out
34+
func topLevelStringField(value any, key string) string {
35+
fields, ok := value.(map[string]any)
36+
if !ok {
37+
return ""
38+
}
39+
return scalarStringField(fields[key])
4440
}
4541

4642
func (m SidechainMetadata) Empty() bool {

internal/session/sidechain_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,29 @@ func TestLoadSidechainStateAcceptsExtendedMetadataAliases(t *testing.T) {
810810
}
811811
}
812812

813+
func TestLoadSidechainStateAcceptsWrappedMetadataSidecarAliases(t *testing.T) {
814+
sessionPath := filepath.Join(t.TempDir(), "session.jsonl")
815+
sessionID := contracts.ID("sess_1")
816+
if err := AppendSidechainMessage(sessionPath, sessionID, "wrapped_meta", TranscriptMessage{
817+
Type: "assistant",
818+
UUID: "msg_1",
819+
}); err != nil {
820+
t.Fatal(err)
821+
}
822+
metadataPath := SidechainMetadataPath(sessionPath, sessionID, "wrapped_meta")
823+
if err := os.WriteFile(metadataPath, []byte(`{"resource":{"id":"meta_1","type":"sidechain-metadata","attributes":{"agentName":"researcher","workspaceRoot":"/tmp/research-worktree","displayTitle":"research wrapped sidecar"}}}`), 0o644); err != nil {
824+
t.Fatal(err)
825+
}
826+
827+
state, err := FindSidechainState(sessionPath, sessionID, "wrapped_meta")
828+
if err != nil {
829+
t.Fatal(err)
830+
}
831+
if state.Metadata.AgentType != "researcher" || state.Metadata.WorktreePath != "/tmp/research-worktree" || state.Metadata.Description != "research wrapped sidecar" {
832+
t.Fatalf("metadata = %#v", state.Metadata)
833+
}
834+
}
835+
813836
func TestSidechainManagerOrchestratesRunningSidechains(t *testing.T) {
814837
sessionPath := filepath.Join(t.TempDir(), "session.jsonl")
815838
sessionID := contracts.ID("sess_1")

0 commit comments

Comments
 (0)