compressed-context 0.1.0 → 0.2.0

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 (57) hide show
  1. package/README.md +59 -51
  2. package/package.json +64 -64
  3. package/src/compression/active-summary-cache.ts +50 -50
  4. package/src/compression/adopt.ts +57 -57
  5. package/src/compression/auto.ts +386 -372
  6. package/src/compression/babel.ts +85 -85
  7. package/src/compression/block.ts +25 -25
  8. package/src/compression/cache-history.ts +75 -75
  9. package/src/compression/compaction.ts +189 -189
  10. package/src/compression/decompress-tool.ts +74 -74
  11. package/src/compression/decompress.ts +292 -292
  12. package/src/compression/deleted-sessions.ts +24 -24
  13. package/src/compression/distill.ts +220 -220
  14. package/src/compression/events.ts +275 -275
  15. package/src/compression/markdown.ts +53 -53
  16. package/src/compression/normalize.ts +114 -114
  17. package/src/compression/render.ts +70 -70
  18. package/src/compression/schema.ts +131 -84
  19. package/src/compression/select.ts +123 -123
  20. package/src/compression/session-message.ts +112 -112
  21. package/src/compression/settings-rpc.ts +123 -111
  22. package/src/compression/settings.ts +175 -175
  23. package/src/compression/shape.ts +75 -0
  24. package/src/compression/state.ts +248 -248
  25. package/src/compression/summarize.ts +534 -595
  26. package/src/compression/summary.ts +73 -73
  27. package/src/compression/tool.ts +184 -142
  28. package/src/compression/transcript-store.ts +258 -258
  29. package/src/compression/transcript.ts +63 -63
  30. package/src/compression/validate.ts +81 -81
  31. package/src/i18n/en.ts +93 -93
  32. package/src/i18n/index.ts +28 -28
  33. package/src/i18n/types.ts +89 -89
  34. package/src/i18n/zh.ts +93 -93
  35. package/src/index.ts +116 -108
  36. package/src/shared/blocks.ts +17 -17
  37. package/src/shared/cache.ts +199 -199
  38. package/src/shared/format.ts +23 -23
  39. package/src/shared/media.ts +184 -184
  40. package/src/shared/parse.ts +25 -25
  41. package/src/shared/parts.ts +72 -72
  42. package/src/shared/rpc.ts +145 -145
  43. package/src/shared/scan.ts +32 -32
  44. package/src/shared/settings.ts +466 -471
  45. package/src/shared/stats.ts +142 -142
  46. package/src/shared/tokens.ts +259 -259
  47. package/src/tui/chart/cache-chart.ts +237 -237
  48. package/src/tui/chart/color.ts +58 -58
  49. package/src/tui/index.tsx +109 -111
  50. package/src/tui/panel/cache.tsx +203 -203
  51. package/src/tui/panel/data.ts +105 -105
  52. package/src/tui/panel/index.tsx +176 -176
  53. package/src/tui/panel/summaries.tsx +203 -192
  54. package/src/tui/settings/dialogs.tsx +215 -215
  55. package/src/tui/settings/options.ts +238 -238
  56. package/src/tui/settings.tsx +418 -418
  57. 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,131 @@
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
+ import type { SummaryFormat } from "../shared/settings.ts"
4
+
5
+ /**
6
+ * 摘要的规范 JSON 形状:写入存储、渲染、校验都用这一份,所有字段必填。
7
+ *
8
+ * 模型侧输入用 SummaryInput:JSON 模式只要求 goal,Markdown 模式要求 markdown;
9
+ * 进入内部前由 normalize 补齐,避免逼模型编造内容。
10
+ */
11
+ export const SummaryFields = Schema.Struct({
12
+ title: Schema.String,
13
+ goal: Schema.String,
14
+ constraints: Schema.Array(Schema.String),
15
+ decisions: Schema.Array(Schema.String),
16
+ progress: Schema.Struct({
17
+ completed: Schema.Array(Schema.String),
18
+ active: Schema.Array(Schema.String),
19
+ blocked: Schema.Array(Schema.String),
20
+ }),
21
+ open_items: Schema.Array(Schema.String),
22
+ files: Schema.Array(Schema.String),
23
+ facts: Schema.Array(Schema.String),
24
+ })
25
+
26
+ export type SummaryFields = Schema.Schema.Type<typeof SummaryFields>
27
+
28
+ /** Markdown 摘要:正文是唯一权威内容,title 只是派生元数据。 */
29
+ export interface MarkdownSummary {
30
+ readonly format: "markdown"
31
+ readonly title: string
32
+ readonly text: string
33
+ }
34
+
35
+ /**
36
+ * Babel 摘要:正文是模型间高密度文本,title/goal 从首行 `T=...|G=...` 解析。
37
+ * text 保留首行,方便 T2/T3 合并时继续看到目标。
38
+ */
39
+ export interface BabelSummary {
40
+ readonly format: "babel"
41
+ readonly title: string
42
+ readonly goal: string
43
+ readonly text: string
44
+ }
45
+
46
+ /** 块摘要:JSON 保持原形状,Markdown/Babel 用 format 标记;旧 JSON 块天然兼容。 */
47
+ export type Summary = SummaryFields | MarkdownSummary | BabelSummary
48
+
49
+ /** 输入:任意摘要;输出:是否 Markdown 摘要。 */
50
+ export function isMarkdownSummary(summary: Summary): summary is MarkdownSummary {
51
+ return "format" in summary && summary.format === "markdown"
52
+ }
53
+
54
+ /** 输入:任意摘要;输出:是否 Babel 摘要。 */
55
+ export function isBabelSummary(summary: Summary): summary is BabelSummary {
56
+ return "format" in summary && summary.format === "babel"
57
+ }
58
+
59
+ /**
60
+ * 模型侧摘要输入:JSON 字段都可选,Markdown 用 markdown 字段。
61
+ *
62
+ * 预期:JSON 模式在归一化时要求 goal;Markdown 模式要求 markdown。
63
+ */
64
+ export const SummaryInput = Schema.Struct({
65
+ format: Schema.optional(
66
+ Schema.Union([Schema.Literal("json"), Schema.Literal("markdown"), Schema.Literal("babel")]),
67
+ ),
68
+ markdown: Schema.optional(Schema.String),
69
+ babel: Schema.optional(Schema.String),
70
+ title: Schema.optional(Schema.String),
71
+ goal: Schema.optional(Schema.String),
72
+ constraints: Schema.optional(Schema.Array(Schema.String)),
73
+ decisions: Schema.optional(Schema.Array(Schema.String)),
74
+ progress: Schema.optional(
75
+ Schema.Struct({
76
+ completed: Schema.optional(Schema.Array(Schema.String)),
77
+ active: Schema.optional(Schema.Array(Schema.String)),
78
+ blocked: Schema.optional(Schema.Array(Schema.String)),
79
+ }),
80
+ ),
81
+ open_items: Schema.optional(Schema.Array(Schema.String)),
82
+ files: Schema.optional(Schema.Array(Schema.String)),
83
+ facts: Schema.optional(Schema.Array(Schema.String)),
84
+ })
85
+
86
+ export type SummaryInput = Schema.Schema.Type<typeof SummaryInput>
87
+
88
+ /**
89
+ * JSON 模式的模型输入:goal 必填,其余可选。
90
+ *
91
+ * 与 SummaryInput 的差别只有 goal:JSON 模式没有 goal 就没有内容可写。
92
+ */
93
+ export const JsonSummaryInput = Schema.Struct({
94
+ goal: Schema.String,
95
+ title: Schema.optional(Schema.String),
96
+ constraints: Schema.optional(Schema.Array(Schema.String)),
97
+ decisions: Schema.optional(Schema.Array(Schema.String)),
98
+ progress: Schema.optional(
99
+ Schema.Struct({
100
+ completed: Schema.optional(Schema.Array(Schema.String)),
101
+ active: Schema.optional(Schema.Array(Schema.String)),
102
+ blocked: Schema.optional(Schema.Array(Schema.String)),
103
+ }),
104
+ ),
105
+ open_items: Schema.optional(Schema.Array(Schema.String)),
106
+ files: Schema.optional(Schema.Array(Schema.String)),
107
+ facts: Schema.optional(Schema.Array(Schema.String)),
108
+ })
109
+
110
+ /** Markdown 模式的模型输入:正文必填;title 可选,传了就省掉从首行提取。 */
111
+ export const MarkdownSummaryInput = Schema.Struct({
112
+ markdown: Schema.String,
113
+ title: Schema.optional(Schema.String),
114
+ })
115
+
116
+ /** Babel 模式的模型输入:正文必填;title 可选,目标从首行 `G=` 解析。 */
117
+ export const BabelSummaryInput = Schema.Struct({
118
+ babel: Schema.String,
119
+ title: Schema.optional(Schema.String),
120
+ })
121
+
122
+ /**
123
+ * 输入:摘要格式。
124
+ * 输出:该格式的模型输入 schema,只保留这个格式用得上的字段。
125
+ * 预期:切换格式后工具参数真的收窄,模型不会按旧习惯传别的字段。
126
+ */
127
+ export function summaryInputFor(format: SummaryFormat) {
128
+ if (format === "markdown") return MarkdownSummaryInput
129
+ if (format === "babel") return BabelSummaryInput
130
+ return JsonSummaryInput
131
+ }