compressed-context 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/package.json +64 -64
  2. package/src/compression/active-summary-cache.ts +50 -50
  3. package/src/compression/adopt.ts +57 -57
  4. package/src/compression/auto.ts +372 -372
  5. package/src/compression/babel.ts +85 -85
  6. package/src/compression/block.ts +25 -25
  7. package/src/compression/cache-history.ts +75 -75
  8. package/src/compression/compaction.ts +189 -189
  9. package/src/compression/decompress-tool.ts +74 -74
  10. package/src/compression/decompress.ts +292 -292
  11. package/src/compression/deleted-sessions.ts +24 -24
  12. package/src/compression/distill.ts +220 -220
  13. package/src/compression/events.ts +275 -275
  14. package/src/compression/markdown.ts +53 -53
  15. package/src/compression/normalize.ts +114 -114
  16. package/src/compression/render.ts +70 -70
  17. package/src/compression/schema.ts +84 -84
  18. package/src/compression/select.ts +123 -123
  19. package/src/compression/session-message.ts +112 -112
  20. package/src/compression/settings-rpc.ts +111 -111
  21. package/src/compression/settings.ts +175 -175
  22. package/src/compression/state.ts +248 -248
  23. package/src/compression/summarize.ts +595 -595
  24. package/src/compression/summary.ts +73 -73
  25. package/src/compression/tool.ts +142 -142
  26. package/src/compression/transcript-store.ts +258 -258
  27. package/src/compression/transcript.ts +63 -63
  28. package/src/compression/validate.ts +81 -81
  29. package/src/i18n/en.ts +93 -93
  30. package/src/i18n/index.ts +28 -28
  31. package/src/i18n/types.ts +89 -89
  32. package/src/i18n/zh.ts +93 -93
  33. package/src/index.ts +108 -108
  34. package/src/shared/blocks.ts +17 -17
  35. package/src/shared/cache.ts +199 -199
  36. package/src/shared/format.ts +23 -23
  37. package/src/shared/media.ts +184 -184
  38. package/src/shared/parse.ts +25 -25
  39. package/src/shared/parts.ts +72 -72
  40. package/src/shared/rpc.ts +145 -145
  41. package/src/shared/scan.ts +32 -32
  42. package/src/shared/settings.ts +471 -471
  43. package/src/shared/stats.ts +142 -142
  44. package/src/shared/tokens.ts +259 -259
  45. package/src/tui/chart/cache-chart.ts +237 -237
  46. package/src/tui/chart/color.ts +58 -58
  47. package/src/tui/index.tsx +111 -111
  48. package/src/tui/panel/cache.tsx +203 -203
  49. package/src/tui/panel/data.ts +105 -105
  50. package/src/tui/panel/index.tsx +176 -176
  51. package/src/tui/panel/summaries.tsx +192 -192
  52. package/src/tui/settings/dialogs.tsx +215 -215
  53. package/src/tui/settings/options.ts +238 -238
  54. package/src/tui/settings.tsx +418 -418
  55. package/src/tui/types.ts +4 -4
@@ -1,114 +1,114 @@
1
- import { Schema } from "effect"
2
-
3
- import type { SummaryFormat } from "../shared/settings.ts"
4
- import { toBabelSummary } from "./babel.ts"
5
- import { toMarkdownSummary } from "./markdown.ts"
6
- import { SummaryFields, type Summary } from "./schema.ts"
7
-
8
- /** 输入:任意模型字段;输出:字符串数组;预期:字符串包成单元素数组,其他丢弃。 */
9
- export function stringArray(value: unknown): string[] {
10
- if (Array.isArray(value)) {
11
- return value.filter((item): item is string => typeof item === "string" && item.trim().length > 0)
12
- }
13
- if (typeof value === "string" && value.trim().length > 0) return [value]
14
- return []
15
- }
16
-
17
- /**
18
- * 输入:模型返回的原始 JSON 对象。
19
- * 输出:补齐缺失可选字段后的对象。
20
- * 预期:真实模型常漏 progress/files 或把数组写成字符串;只补空值,不编造内容。
21
- */
22
- export function normalizeSummary(value: unknown): unknown {
23
- if (typeof value !== "object" || value === null) return value
24
- const record = value as Record<string, unknown>
25
- const goal = typeof record["goal"] === "string" ? record["goal"] : ""
26
- const title = (
27
- typeof record["title"] === "string" && record["title"].trim().length > 0
28
- ? record["title"]
29
- : goal.trim() || "untitled"
30
- ).slice(0, 80)
31
- const progress =
32
- typeof record["progress"] === "object" && record["progress"] !== null
33
- ? (record["progress"] as Record<string, unknown>)
34
- : {}
35
- return {
36
- ...record,
37
- title,
38
- goal,
39
- constraints: stringArray(record["constraints"]),
40
- decisions: stringArray(record["decisions"]),
41
- progress: {
42
- completed: stringArray(progress["completed"]),
43
- active: stringArray(progress["active"]),
44
- blocked: stringArray(progress["blocked"]),
45
- },
46
- open_items: stringArray(record["open_items"]),
47
- files: stringArray(record["files"]),
48
- facts: stringArray(record["facts"]),
49
- }
50
- }
51
-
52
- /** 输入:模型返回的 Markdown 载体;输出:正文;预期:支持 string / markdown / text / summary。 */
53
- function markdownText(value: unknown): string | undefined {
54
- if (typeof value === "string") return value
55
- if (typeof value !== "object" || value === null) return undefined
56
- const record = value as Record<string, unknown>
57
- for (const key of ["markdown", "text", "summary", "content"]) {
58
- const text = record[key]
59
- if (typeof text === "string") return text
60
- }
61
- return undefined
62
- }
63
-
64
- /** 输入:模型返回的 Markdown 载体;输出:可选标题。 */
65
- function markdownTitle(value: unknown): string | undefined {
66
- if (typeof value !== "object" || value === null) return undefined
67
- const record = value as Record<string, unknown>
68
- const title = record["title"] ?? record["topic"]
69
- return typeof title === "string" ? title : undefined
70
- }
71
-
72
- /** 输入:Babel 载体;输出:正文文本和可选 title/goal。 */
73
- function babelParts(
74
- value: unknown,
75
- ): { readonly text: string; readonly title?: string; readonly goal?: string } | undefined {
76
- if (typeof value === "string") return { text: value }
77
- if (typeof value !== "object" || value === null) return undefined
78
- const record = value as Record<string, unknown>
79
- for (const key of ["babel", "markdown", "text", "summary", "content"]) {
80
- const text = record[key]
81
- if (typeof text !== "string") continue
82
- const title = typeof record["title"] === "string" ? record["title"] : undefined
83
- const goal = typeof record["goal"] === "string" ? record["goal"] : undefined
84
- return {
85
- text,
86
- ...(title === undefined ? {} : { title }),
87
- ...(goal === undefined ? {} : { goal }),
88
- }
89
- }
90
- return undefined
91
- }
92
-
93
- /**
94
- * 输入:模型返回的原始值 + 摘要格式。
95
- * 输出:规范摘要;形状不合法时返回 undefined。
96
- * 预期:Markdown 直接保留原文;JSON 先补齐可选字段再由 SummaryFields 解码。
97
- */
98
- export function toSummary(value: unknown, format: SummaryFormat = "markdown"): Summary | undefined {
99
- if (format === "babel") {
100
- const parts = babelParts(value)
101
- if (parts === undefined) return undefined
102
- return toBabelSummary(parts.text, parts.title, parts.goal)
103
- }
104
- if (format === "markdown") {
105
- const text = markdownText(value)
106
- if (text !== undefined) return toMarkdownSummary(text, markdownTitle(value))
107
- // Markdown 模式下仍收到 JSON 字段时回退到结构化解析,避免工具调用直接失败。
108
- }
109
- try {
110
- return Schema.decodeUnknownSync(SummaryFields)(normalizeSummary(value))
111
- } catch {
112
- return undefined
113
- }
114
- }
1
+ import { Schema } from "effect"
2
+
3
+ import type { SummaryFormat } from "../shared/settings.ts"
4
+ import { toBabelSummary } from "./babel.ts"
5
+ import { toMarkdownSummary } from "./markdown.ts"
6
+ import { SummaryFields, type Summary } from "./schema.ts"
7
+
8
+ /** 输入:任意模型字段;输出:字符串数组;预期:字符串包成单元素数组,其他丢弃。 */
9
+ export function stringArray(value: unknown): string[] {
10
+ if (Array.isArray(value)) {
11
+ return value.filter((item): item is string => typeof item === "string" && item.trim().length > 0)
12
+ }
13
+ if (typeof value === "string" && value.trim().length > 0) return [value]
14
+ return []
15
+ }
16
+
17
+ /**
18
+ * 输入:模型返回的原始 JSON 对象。
19
+ * 输出:补齐缺失可选字段后的对象。
20
+ * 预期:真实模型常漏 progress/files 或把数组写成字符串;只补空值,不编造内容。
21
+ */
22
+ export function normalizeSummary(value: unknown): unknown {
23
+ if (typeof value !== "object" || value === null) return value
24
+ const record = value as Record<string, unknown>
25
+ const goal = typeof record["goal"] === "string" ? record["goal"] : ""
26
+ const title = (
27
+ typeof record["title"] === "string" && record["title"].trim().length > 0
28
+ ? record["title"]
29
+ : goal.trim() || "untitled"
30
+ ).slice(0, 80)
31
+ const progress =
32
+ typeof record["progress"] === "object" && record["progress"] !== null
33
+ ? (record["progress"] as Record<string, unknown>)
34
+ : {}
35
+ return {
36
+ ...record,
37
+ title,
38
+ goal,
39
+ constraints: stringArray(record["constraints"]),
40
+ decisions: stringArray(record["decisions"]),
41
+ progress: {
42
+ completed: stringArray(progress["completed"]),
43
+ active: stringArray(progress["active"]),
44
+ blocked: stringArray(progress["blocked"]),
45
+ },
46
+ open_items: stringArray(record["open_items"]),
47
+ files: stringArray(record["files"]),
48
+ facts: stringArray(record["facts"]),
49
+ }
50
+ }
51
+
52
+ /** 输入:模型返回的 Markdown 载体;输出:正文;预期:支持 string / markdown / text / summary。 */
53
+ function markdownText(value: unknown): string | undefined {
54
+ if (typeof value === "string") return value
55
+ if (typeof value !== "object" || value === null) return undefined
56
+ const record = value as Record<string, unknown>
57
+ for (const key of ["markdown", "text", "summary", "content"]) {
58
+ const text = record[key]
59
+ if (typeof text === "string") return text
60
+ }
61
+ return undefined
62
+ }
63
+
64
+ /** 输入:模型返回的 Markdown 载体;输出:可选标题。 */
65
+ function markdownTitle(value: unknown): string | undefined {
66
+ if (typeof value !== "object" || value === null) return undefined
67
+ const record = value as Record<string, unknown>
68
+ const title = record["title"] ?? record["topic"]
69
+ return typeof title === "string" ? title : undefined
70
+ }
71
+
72
+ /** 输入:Babel 载体;输出:正文文本和可选 title/goal。 */
73
+ function babelParts(
74
+ value: unknown,
75
+ ): { readonly text: string; readonly title?: string; readonly goal?: string } | undefined {
76
+ if (typeof value === "string") return { text: value }
77
+ if (typeof value !== "object" || value === null) return undefined
78
+ const record = value as Record<string, unknown>
79
+ for (const key of ["babel", "markdown", "text", "summary", "content"]) {
80
+ const text = record[key]
81
+ if (typeof text !== "string") continue
82
+ const title = typeof record["title"] === "string" ? record["title"] : undefined
83
+ const goal = typeof record["goal"] === "string" ? record["goal"] : undefined
84
+ return {
85
+ text,
86
+ ...(title === undefined ? {} : { title }),
87
+ ...(goal === undefined ? {} : { goal }),
88
+ }
89
+ }
90
+ return undefined
91
+ }
92
+
93
+ /**
94
+ * 输入:模型返回的原始值 + 摘要格式。
95
+ * 输出:规范摘要;形状不合法时返回 undefined。
96
+ * 预期:Markdown 直接保留原文;JSON 先补齐可选字段再由 SummaryFields 解码。
97
+ */
98
+ export function toSummary(value: unknown, format: SummaryFormat = "markdown"): Summary | undefined {
99
+ if (format === "babel") {
100
+ const parts = babelParts(value)
101
+ if (parts === undefined) return undefined
102
+ return toBabelSummary(parts.text, parts.title, parts.goal)
103
+ }
104
+ if (format === "markdown") {
105
+ const text = markdownText(value)
106
+ if (text !== undefined) return toMarkdownSummary(text, markdownTitle(value))
107
+ // Markdown 模式下仍收到 JSON 字段时回退到结构化解析,避免工具调用直接失败。
108
+ }
109
+ try {
110
+ return Schema.decodeUnknownSync(SummaryFields)(normalizeSummary(value))
111
+ } catch {
112
+ return undefined
113
+ }
114
+ }
@@ -1,70 +1,70 @@
1
- import type { Block } from "./state.ts"
2
- import type { Summary } from "./schema.ts"
3
- import { summaryBody, summaryTitle } from "./summary.ts"
4
- import { formatTimestamp } from "../shared/format.ts"
5
- import { countText } from "../shared/tokens.ts"
6
-
7
- /** 输入:块 id;输出:块头标记;预期:用于渲染和对账。 */
8
- export function blockMarker(id: string): string {
9
- return `[Compressed conversation section #${id}]`
10
- }
11
-
12
- /** 块头附加信息开关;默认都不显示,避免多占 token。 */
13
- export interface RenderOptions {
14
- readonly showTimeline?: boolean
15
- readonly showBlockTokens?: boolean
16
- }
17
-
18
- /** 输入:结构化字段;输出:Markdown 摘要正文。 */
19
- export function renderSummary(summary: Summary): string {
20
- return summaryBody(summary)
21
- }
22
-
23
- /**
24
- * 输入:一个块 + 可选渲染开关。
25
- * 输出:带块头、层级/合并关系、可选时间与 token 估算的完整文本。
26
- * 预期:marker 前缀保持原样,对账逻辑只依赖 `blockMarker(id)`。
27
- */
28
- export function renderBlock(block: Block, options: RenderOptions = {}): string {
29
- const body = renderSummary(block.summary)
30
- const meta: string[] = [`T${block.tier.toString()}`]
31
- if (block.sourceBlockIds !== undefined && block.sourceBlockIds.length > 0) {
32
- meta.push(`merged ${block.sourceBlockIds.map((id) => `#${id}`).join(" + ")}`)
33
- }
34
- if (options.showBlockTokens === true) {
35
- meta.push(`≈${countText(body).toString()} tok`)
36
- }
37
- if (options.showTimeline === true) {
38
- meta.push(formatTimestamp(block.at))
39
- }
40
- const title = summaryTitle(block.summary).trim()
41
- const header = `${blockMarker(block.id)} [${meta.join(" · ")}]${title ? ` ${title}` : ""}`
42
- return `${header}\n\n${body}`
43
- }
44
-
45
- /**
46
- * 输入:已落盘块 + 可选 pending 块 + 渲染开关。
47
- * 输出:active 顶层块,按 order 排序。
48
- * 预期:被任何块 sourceBlockIds 引用的块不显示;合并块占据第一个源块的位置。
49
- */
50
- export function activeBlocks(blocks: readonly Block[], pending?: Block): readonly Block[] {
51
- const all = pending === undefined ? blocks : [...blocks, pending]
52
- const consumed = new Set<string>()
53
- for (const block of all) {
54
- for (const sourceId of block.sourceBlockIds ?? []) consumed.add(sourceId)
55
- }
56
- return all
57
- .filter((block) => !consumed.has(block.id))
58
- .sort((a, b) => a.order - b.order || a.at - b.at || a.id.localeCompare(b.id))
59
- }
60
-
61
- /** 输入:块 + 可选 pending + 渲染开关;输出:拼接后的 checkpoint 文本。 */
62
- export function renderAll(
63
- blocks: readonly Block[],
64
- pending?: Block,
65
- options: RenderOptions = {},
66
- ): string {
67
- return activeBlocks(blocks, pending)
68
- .map((block) => renderBlock(block, options))
69
- .join("\n\n")
70
- }
1
+ import type { Block } from "./state.ts"
2
+ import type { Summary } from "./schema.ts"
3
+ import { summaryBody, summaryTitle } from "./summary.ts"
4
+ import { formatTimestamp } from "../shared/format.ts"
5
+ import { countText } from "../shared/tokens.ts"
6
+
7
+ /** 输入:块 id;输出:块头标记;预期:用于渲染和对账。 */
8
+ export function blockMarker(id: string): string {
9
+ return `[Compressed conversation section #${id}]`
10
+ }
11
+
12
+ /** 块头附加信息开关;默认都不显示,避免多占 token。 */
13
+ export interface RenderOptions {
14
+ readonly showTimeline?: boolean
15
+ readonly showBlockTokens?: boolean
16
+ }
17
+
18
+ /** 输入:结构化字段;输出:Markdown 摘要正文。 */
19
+ export function renderSummary(summary: Summary): string {
20
+ return summaryBody(summary)
21
+ }
22
+
23
+ /**
24
+ * 输入:一个块 + 可选渲染开关。
25
+ * 输出:带块头、层级/合并关系、可选时间与 token 估算的完整文本。
26
+ * 预期:marker 前缀保持原样,对账逻辑只依赖 `blockMarker(id)`。
27
+ */
28
+ export function renderBlock(block: Block, options: RenderOptions = {}): string {
29
+ const body = renderSummary(block.summary)
30
+ const meta: string[] = [`T${block.tier.toString()}`]
31
+ if (block.sourceBlockIds !== undefined && block.sourceBlockIds.length > 0) {
32
+ meta.push(`merged ${block.sourceBlockIds.map((id) => `#${id}`).join(" + ")}`)
33
+ }
34
+ if (options.showBlockTokens === true) {
35
+ meta.push(`≈${countText(body).toString()} tok`)
36
+ }
37
+ if (options.showTimeline === true) {
38
+ meta.push(formatTimestamp(block.at))
39
+ }
40
+ const title = summaryTitle(block.summary).trim()
41
+ const header = `${blockMarker(block.id)} [${meta.join(" · ")}]${title ? ` ${title}` : ""}`
42
+ return `${header}\n\n${body}`
43
+ }
44
+
45
+ /**
46
+ * 输入:已落盘块 + 可选 pending 块 + 渲染开关。
47
+ * 输出:active 顶层块,按 order 排序。
48
+ * 预期:被任何块 sourceBlockIds 引用的块不显示;合并块占据第一个源块的位置。
49
+ */
50
+ export function activeBlocks(blocks: readonly Block[], pending?: Block): readonly Block[] {
51
+ const all = pending === undefined ? blocks : [...blocks, pending]
52
+ const consumed = new Set<string>()
53
+ for (const block of all) {
54
+ for (const sourceId of block.sourceBlockIds ?? []) consumed.add(sourceId)
55
+ }
56
+ return all
57
+ .filter((block) => !consumed.has(block.id))
58
+ .sort((a, b) => a.order - b.order || a.at - b.at || a.id.localeCompare(b.id))
59
+ }
60
+
61
+ /** 输入:块 + 可选 pending + 渲染开关;输出:拼接后的 checkpoint 文本。 */
62
+ export function renderAll(
63
+ blocks: readonly Block[],
64
+ pending?: Block,
65
+ options: RenderOptions = {},
66
+ ): string {
67
+ return activeBlocks(blocks, pending)
68
+ .map((block) => renderBlock(block, options))
69
+ .join("\n\n")
70
+ }
@@ -1,84 +1,84 @@
1
- import { Schema } from "effect"
2
-
3
- /**
4
- * 摘要的规范 JSON 形状:写入存储、渲染、校验都用这一份,所有字段必填。
5
- *
6
- * 模型侧输入用 SummaryInput:JSON 模式只要求 goal,Markdown 模式要求 markdown;
7
- * 进入内部前由 normalize 补齐,避免逼模型编造内容。
8
- */
9
- export const SummaryFields = Schema.Struct({
10
- title: Schema.String,
11
- goal: Schema.String,
12
- constraints: Schema.Array(Schema.String),
13
- decisions: Schema.Array(Schema.String),
14
- progress: Schema.Struct({
15
- completed: Schema.Array(Schema.String),
16
- active: Schema.Array(Schema.String),
17
- blocked: Schema.Array(Schema.String),
18
- }),
19
- open_items: Schema.Array(Schema.String),
20
- files: Schema.Array(Schema.String),
21
- facts: Schema.Array(Schema.String),
22
- })
23
-
24
- export type SummaryFields = Schema.Schema.Type<typeof SummaryFields>
25
-
26
- /** Markdown 摘要:正文是唯一权威内容,title 只是派生元数据。 */
27
- export interface MarkdownSummary {
28
- readonly format: "markdown"
29
- readonly title: string
30
- readonly text: string
31
- }
32
-
33
- /**
34
- * Babel 摘要:正文是模型间高密度文本,title/goal 从首行 `T=...|G=...` 解析。
35
- * text 保留首行,方便 T2/T3 合并时继续看到目标。
36
- */
37
- export interface BabelSummary {
38
- readonly format: "babel"
39
- readonly title: string
40
- readonly goal: string
41
- readonly text: string
42
- }
43
-
44
- /** 块摘要:JSON 保持原形状,Markdown/Babel 用 format 标记;旧 JSON 块天然兼容。 */
45
- export type Summary = SummaryFields | MarkdownSummary | BabelSummary
46
-
47
- /** 输入:任意摘要;输出:是否 Markdown 摘要。 */
48
- export function isMarkdownSummary(summary: Summary): summary is MarkdownSummary {
49
- return "format" in summary && summary.format === "markdown"
50
- }
51
-
52
- /** 输入:任意摘要;输出:是否 Babel 摘要。 */
53
- export function isBabelSummary(summary: Summary): summary is BabelSummary {
54
- return "format" in summary && summary.format === "babel"
55
- }
56
-
57
- /**
58
- * 模型侧摘要输入:JSON 字段都可选,Markdown 用 markdown 字段。
59
- *
60
- * 预期:JSON 模式在归一化时要求 goal;Markdown 模式要求 markdown。
61
- */
62
- export const SummaryInput = Schema.Struct({
63
- format: Schema.optional(
64
- Schema.Union([Schema.Literal("json"), Schema.Literal("markdown"), Schema.Literal("babel")]),
65
- ),
66
- markdown: Schema.optional(Schema.String),
67
- babel: Schema.optional(Schema.String),
68
- title: Schema.optional(Schema.String),
69
- goal: Schema.optional(Schema.String),
70
- constraints: Schema.optional(Schema.Array(Schema.String)),
71
- decisions: Schema.optional(Schema.Array(Schema.String)),
72
- progress: Schema.optional(
73
- Schema.Struct({
74
- completed: Schema.optional(Schema.Array(Schema.String)),
75
- active: Schema.optional(Schema.Array(Schema.String)),
76
- blocked: Schema.optional(Schema.Array(Schema.String)),
77
- }),
78
- ),
79
- open_items: Schema.optional(Schema.Array(Schema.String)),
80
- files: Schema.optional(Schema.Array(Schema.String)),
81
- facts: Schema.optional(Schema.Array(Schema.String)),
82
- })
83
-
84
- export type SummaryInput = Schema.Schema.Type<typeof SummaryInput>
1
+ import { Schema } from "effect"
2
+
3
+ /**
4
+ * 摘要的规范 JSON 形状:写入存储、渲染、校验都用这一份,所有字段必填。
5
+ *
6
+ * 模型侧输入用 SummaryInput:JSON 模式只要求 goal,Markdown 模式要求 markdown;
7
+ * 进入内部前由 normalize 补齐,避免逼模型编造内容。
8
+ */
9
+ export const SummaryFields = Schema.Struct({
10
+ title: Schema.String,
11
+ goal: Schema.String,
12
+ constraints: Schema.Array(Schema.String),
13
+ decisions: Schema.Array(Schema.String),
14
+ progress: Schema.Struct({
15
+ completed: Schema.Array(Schema.String),
16
+ active: Schema.Array(Schema.String),
17
+ blocked: Schema.Array(Schema.String),
18
+ }),
19
+ open_items: Schema.Array(Schema.String),
20
+ files: Schema.Array(Schema.String),
21
+ facts: Schema.Array(Schema.String),
22
+ })
23
+
24
+ export type SummaryFields = Schema.Schema.Type<typeof SummaryFields>
25
+
26
+ /** Markdown 摘要:正文是唯一权威内容,title 只是派生元数据。 */
27
+ export interface MarkdownSummary {
28
+ readonly format: "markdown"
29
+ readonly title: string
30
+ readonly text: string
31
+ }
32
+
33
+ /**
34
+ * Babel 摘要:正文是模型间高密度文本,title/goal 从首行 `T=...|G=...` 解析。
35
+ * text 保留首行,方便 T2/T3 合并时继续看到目标。
36
+ */
37
+ export interface BabelSummary {
38
+ readonly format: "babel"
39
+ readonly title: string
40
+ readonly goal: string
41
+ readonly text: string
42
+ }
43
+
44
+ /** 块摘要:JSON 保持原形状,Markdown/Babel 用 format 标记;旧 JSON 块天然兼容。 */
45
+ export type Summary = SummaryFields | MarkdownSummary | BabelSummary
46
+
47
+ /** 输入:任意摘要;输出:是否 Markdown 摘要。 */
48
+ export function isMarkdownSummary(summary: Summary): summary is MarkdownSummary {
49
+ return "format" in summary && summary.format === "markdown"
50
+ }
51
+
52
+ /** 输入:任意摘要;输出:是否 Babel 摘要。 */
53
+ export function isBabelSummary(summary: Summary): summary is BabelSummary {
54
+ return "format" in summary && summary.format === "babel"
55
+ }
56
+
57
+ /**
58
+ * 模型侧摘要输入:JSON 字段都可选,Markdown 用 markdown 字段。
59
+ *
60
+ * 预期:JSON 模式在归一化时要求 goal;Markdown 模式要求 markdown。
61
+ */
62
+ export const SummaryInput = Schema.Struct({
63
+ format: Schema.optional(
64
+ Schema.Union([Schema.Literal("json"), Schema.Literal("markdown"), Schema.Literal("babel")]),
65
+ ),
66
+ markdown: Schema.optional(Schema.String),
67
+ babel: Schema.optional(Schema.String),
68
+ title: Schema.optional(Schema.String),
69
+ goal: Schema.optional(Schema.String),
70
+ constraints: Schema.optional(Schema.Array(Schema.String)),
71
+ decisions: Schema.optional(Schema.Array(Schema.String)),
72
+ progress: Schema.optional(
73
+ Schema.Struct({
74
+ completed: Schema.optional(Schema.Array(Schema.String)),
75
+ active: Schema.optional(Schema.Array(Schema.String)),
76
+ blocked: Schema.optional(Schema.Array(Schema.String)),
77
+ }),
78
+ ),
79
+ open_items: Schema.optional(Schema.Array(Schema.String)),
80
+ files: Schema.optional(Schema.Array(Schema.String)),
81
+ facts: Schema.optional(Schema.Array(Schema.String)),
82
+ })
83
+
84
+ export type SummaryInput = Schema.Schema.Type<typeof SummaryInput>