Skip to content

Commit a763656

Browse files
author
SqlRush
committed
Accept message microcompact summaries
1 parent 719c8c8 commit a763656

5 files changed

Lines changed: 105 additions & 6 deletions

File tree

docs/cc-100-roadmap.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ M6 补充:microcompact disk cache loader 现在接受 JSON:API/resource-style
4444

4545
M6 补充:microcompact disk cache loader 的 summary-like payload 现在接受 text content-block object、text content-block array 和 string array,会把可见 text block 合并为 summary,兼容官方/SDK 响应内容块形态的 cached microcompact 文件。
4646

47+
M6 补充:microcompact disk cache loader 的 summary-like payload 现在也接受完整 contract message object,并会递归解包 `message`/`assistantMessage`/`resultMessage`/`outputMessage`/`completionMessage` wrapper,从 message content 中恢复 visible text summary。
48+
4749
M6 补充:sidechain/subagent lifecycle state loader 现在接受 `subagent_started``agentStarted``task_failed``sidechainCompleted` 等相邻 subtype aliases,支持 `taskID`/`workerId`/`runId``agentName`/`kind``resultText`/`finalMessage` 等字段,并在 failed/cancelled subtype 没有显式 status 时自动归一状态。
4850

4951
M6 补充:sidechain/subagent lifecycle content 读取现在接受 JSON:API/resource-style `resource`/`attributes`/`properties` wrapper,外层 resource `id` 可作为 sidechain ID fallback,内层 agent metadata、status/outcome 和 summary 字段仍能恢复到 state/list/resume。
@@ -490,6 +492,8 @@ M7 补充:prompt history `LogEntry` 读取现在接受 `sessionID`/`session`/`
490492

491493
本轮补充:microcompact disk cache loader 的 summary-like payload 现在接受 text content-block object、text content-block array 和 string array,会把可见 text block 合并为 summary,兼容官方/SDK 响应内容块形态的 cached microcompact 文件。
492494

495+
本轮补充:microcompact disk cache loader 的 summary-like payload 现在也接受完整 contract message object,并会递归解包 `message`/`assistantMessage`/`resultMessage`/`outputMessage`/`completionMessage` wrapper,从 message content 中恢复 visible text summary。
496+
493497
本轮补充:contract content block 解码现在接受文本块字段别名 `body`/`message`/`value`/`output`/`contentText`/`content_text`,并在 `text`/`thinking` block 中从字符串 `content` 回填文本,transcript resume 可恢复这些嵌套文本块格式。
494498

495499
本轮补充:contract image source 解码现在接受 `mediaType`/`mimeType`/`contentType``base64`/`payload` 等 source 字段别名,并支持顶层 image block 直接携带媒体类型和 base64 数据,transcript resume 会保留为规范 `ImageSource`

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ test/parity/ # golden tests against TS/official behavior
253253
- 本轮补充:microcompact disk cache loader 现在会从 `metadata`/`meta`/`cacheInfo`/`cacheDetails`/`cacheEntry`/`entry`/`record`/`cache` 等 sidecar object 中补缺失的 digest、version、cache-hit、timestamp、TTL 和 count aliases;主 summary payload 字段仍保持优先。
254254
- 本轮补充:microcompact disk cache loader 现在也接受 JSON:API/resource-style `resource`/`attributes`/`properties` wrapper,summary payload 可放在 attributes/properties 内,外层 resource `id` 可作为 digest fallback。
255255
- 本轮补充:microcompact disk cache loader 的 summary-like payload 现在接受 text content-block object、text content-block array 和 string array,会把可见 text block 合并为 summary,兼容官方/SDK 响应内容块形态的 cached microcompact 文件。
256+
- 本轮补充:microcompact disk cache loader 的 summary-like payload 现在也接受完整 contract message object,并会递归解包 `message`/`assistantMessage`/`resultMessage`/`outputMessage`/`completionMessage` wrapper,从 message content 中恢复 visible text summary。
256257

257258
### M7: TUI renderer 和交互体验
258259

docs/first-second-parity-audit.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ M6 progress now includes:
131131
- `internal/compact`: microcompact disk cache loading now accepts non-strict cache-hit booleans for `cached`/`fromCache`/`cacheHit`/`isCached`, including `yes`/`no`, `on`/`off`, and `1`/`0` JSON number or string values.
132132
- `internal/compact`: microcompact disk cache loading now accepts JSON:API/resource-style `resource`/`attributes`/`properties` wrappers, with summary payloads inside attributes/properties and outer resource IDs used as digest fallback when needed.
133133
- `internal/compact`: microcompact disk cache summary payloads now accept text content-block objects, text content-block arrays, and string arrays, joining visible text blocks into the recovered summary for official/SDK-style cached microcompact files.
134+
- `internal/compact`: microcompact disk cache summary payloads now also accept full contract message objects and unwrap `message`/`assistantMessage`/`resultMessage`/`outputMessage`/`completionMessage` response wrappers before extracting visible text summary.
134135
- `internal/conversation`: optional auto-compact can now run before the main request, fail open with a consecutive-failure circuit breaker, emit runtime `token_warning` events from compact warning state, persist compact boundary metadata to transcript, write a session-memory summary, inject recalled session-memory snippets into API request context when enabled, and optionally extract turn-end memory facts into session memory.
135136
- `internal/memory`: session-memory summaries can now be loaded with frontmatter field aliases, rolled up/pruned into an archive summary with prior rollup archives excluded from candidate compaction and merged across archive IDs, rune-safe rollup truncation, recalled by query with deterministic scoring and recency ordering, ranked through a model-assisted candidate/session-id selection path with fallback when selected IDs are invalid or excluded, alternate/camel response-key parsing, fenced/prose JSON extraction, scalar session-id parsing, nested/wrapped/collection-alias selection parsing, and nested selected-memory item parsing, and injected into resume context through the same optional recall agent path; deterministic and model-backed memory fact extraction can summarize user preferences, requests, decisions, and tool-use facts, including fenced/prose JSON, wrapped facts responses, alternate and structured fact field names, nested source objects, nested fact response shapes, and fact kind aliases.
136137
- `internal/session`: project session listing and pagination, prompt-history lock/buffered flush/field aliases, lightweight transcript index/title/text-preview inference, line-offset transcript indexing/window/byte-budget-window/parent-chain/resume/tail/byte-budget-tail loading, AI-title/last-prompt/task/agent/PR/worktree transcript metadata loading and type/field aliases, transcript message/session UUID field aliases, tombstone metadata loading with target/session/reason aliases and delete/relink replay, agent-scoped content replacement metadata/record field-alias loading, session-scoped metadata reappend including AI-title/last-prompt/task-summary, streaming transcript search snippets for resume/search UI, official `subagents/agent-*.jsonl` transcript layout with legacy sidechain listing, agent metadata sidecar read/write/field aliases, sidechain runtime start/append/finish/cancel/fail summary bridging plus parent-chain append/finish, sidechain manager orchestration for spawn/append/finish/cancel/fail/list/resume, sidechain state/list/resume support with content-field aliases, sidechain resume context construction, sidechain conversation and agent-scoped content-replacement reconstruction, transcript tail/window loading and byte-budget tail loading for bounded-memory resume/UI paths, lightweight transcript metadata loading, remote-history token refresh retry, page-field/event-list/records/entries/last-id/cursor/event-id/has-next aliases plus wrapped-data/links/paging/bare-array/keyed-event-map responses, before_id resume, fallback field fill, and duplicate-aware parent linking during transcript materialization.

internal/compact/compact_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,80 @@ func TestLoadMicroResultAcceptsContentBlockSummaryPayloads(t *testing.T) {
521521
}
522522
}
523523

524+
func TestLoadMicroResultAcceptsMessageSummaryPayloads(t *testing.T) {
525+
cacheDir := filepath.Join(t.TempDir(), "micro")
526+
if err := os.MkdirAll(cacheDir, 0o755); err != nil {
527+
t.Fatal(err)
528+
}
529+
for _, tc := range []struct {
530+
digest string
531+
payload string
532+
want string
533+
}{
534+
{
535+
digest: "summary-message",
536+
payload: `{
537+
"summary": {
538+
"role": "assistant",
539+
"content": [
540+
{"type": "text", "text": "message summary"},
541+
{"type": "thinking", "text": "not part of visible summary"}
542+
]
543+
},
544+
"digest": "summary-message",
545+
"version": "microcompact.v1",
546+
"createdAt": 100
547+
}`,
548+
want: "message summary",
549+
},
550+
{
551+
digest: "message-wrapper",
552+
payload: `{
553+
"message": {
554+
"role": "assistant",
555+
"content": [
556+
{"type": "text", "text": "wrapped message summary"},
557+
"tail line"
558+
]
559+
},
560+
"cacheKey": "message-wrapper",
561+
"cacheVersion": "microcompact.v1",
562+
"cachedAt": 100
563+
}`,
564+
want: "wrapped message summary\ntail line",
565+
},
566+
{
567+
digest: "response-message-wrapper",
568+
payload: `{
569+
"response": {
570+
"message": {
571+
"type": "assistant",
572+
"content": {"type": "text", "body": "response message summary"}
573+
},
574+
"cacheDigest": "response-message-wrapper",
575+
"formatVersion": "microcompact.v1",
576+
"createdAt": 100
577+
}
578+
}`,
579+
want: "response message summary",
580+
},
581+
} {
582+
if err := os.WriteFile(microResultPath(cacheDir, tc.digest), []byte(tc.payload), 0o600); err != nil {
583+
t.Fatal(err)
584+
}
585+
result, ok, err := LoadMicroResult(cacheDir, tc.digest)
586+
if err != nil {
587+
t.Fatalf("%s load error: %v", tc.digest, err)
588+
}
589+
if !ok {
590+
t.Fatalf("%s was not loaded", tc.digest)
591+
}
592+
if result.Summary != tc.want || result.Digest != tc.digest || result.Version != DefaultMicroCacheVersion {
593+
t.Fatalf("%s result = %#v", tc.digest, result)
594+
}
595+
}
596+
}
597+
524598
func TestLoadMicroResultAcceptsMetadataCacheAliases(t *testing.T) {
525599
cacheDir := filepath.Join(t.TempDir(), "micro")
526600
if err := os.MkdirAll(cacheDir, 0o755); err != nil {

internal/compact/micro.go

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ func microResultWrappedJSON(fields map[string]json.RawMessage) (json.RawMessage,
190190
for _, name := range []string{
191191
"result", "data", "cache", "cacheEntry", "cache_entry", "entry", "record", "item", "resource", "payload", "response", "body",
192192
"microcompact", "microCompact", "micro_compact", "micro_result", "microResult", "microcompactResult", "microCompactResult", "micro_compact_result",
193+
"message", "assistantMessage", "assistant_message", "resultMessage", "result_message", "outputMessage", "output_message", "completion", "completionMessage", "completion_message",
193194
"attributes", "properties", "value",
194195
} {
195196
raw, ok := fields[name]
@@ -528,14 +529,17 @@ func microSummaryFromRaw(raw json.RawMessage, field string) (string, bool, error
528529
}
529530
switch trimmed[0] {
530531
case '{':
531-
var block contracts.ContentBlock
532-
if err := json.Unmarshal(trimmed, &block); err != nil {
532+
if text, ok, err := microSummaryContentBlockText(trimmed); err != nil {
533533
return "", false, err
534+
} else if ok {
535+
return text, true, nil
534536
}
535-
if block.Type == contracts.ContentText && block.Text != "" {
536-
return block.Text, true, nil
537+
if text, ok, err := microSummaryMessageText(trimmed); err != nil {
538+
return "", false, err
539+
} else if ok {
540+
return text, true, nil
537541
}
538-
return "", false, fmt.Errorf("invalid summary field %q: expected text content block", field)
542+
return "", false, fmt.Errorf("invalid summary field %q: expected text content block or message", field)
539543
case '[':
540544
var items []json.RawMessage
541545
if err := json.Unmarshal(trimmed, &items); err != nil {
@@ -572,8 +576,12 @@ func microSummaryArrayItemFromRaw(raw json.RawMessage, field string) (string, bo
572576
if trimmed[0] != '{' {
573577
return "", false, fmt.Errorf("invalid summary field %q", field)
574578
}
579+
return microSummaryContentBlockText(trimmed)
580+
}
581+
582+
func microSummaryContentBlockText(raw json.RawMessage) (string, bool, error) {
575583
var block contracts.ContentBlock
576-
if err := json.Unmarshal(trimmed, &block); err != nil {
584+
if err := json.Unmarshal(raw, &block); err != nil {
577585
return "", false, err
578586
}
579587
if block.Type == contracts.ContentText && block.Text != "" {
@@ -582,6 +590,17 @@ func microSummaryArrayItemFromRaw(raw json.RawMessage, field string) (string, bo
582590
return "", false, nil
583591
}
584592

593+
func microSummaryMessageText(raw json.RawMessage) (string, bool, error) {
594+
var message contracts.Message
595+
if err := json.Unmarshal(raw, &message); err != nil {
596+
return "", false, err
597+
}
598+
if text := msgs.TextContent(message); text != "" {
599+
return text, true, nil
600+
}
601+
return "", false, nil
602+
}
603+
585604
func microBoolJSONField(fields map[string]json.RawMessage, names ...string) (bool, bool, error) {
586605
for _, name := range names {
587606
raw, ok := fields[name]

0 commit comments

Comments
 (0)