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.
- package/package.json +64 -64
- package/src/compression/active-summary-cache.ts +50 -50
- package/src/compression/adopt.ts +57 -57
- package/src/compression/auto.ts +372 -372
- package/src/compression/babel.ts +85 -85
- package/src/compression/block.ts +25 -25
- package/src/compression/cache-history.ts +75 -75
- package/src/compression/compaction.ts +189 -189
- package/src/compression/decompress-tool.ts +74 -74
- package/src/compression/decompress.ts +292 -292
- package/src/compression/deleted-sessions.ts +24 -24
- package/src/compression/distill.ts +220 -220
- package/src/compression/events.ts +275 -275
- package/src/compression/markdown.ts +53 -53
- package/src/compression/normalize.ts +114 -114
- package/src/compression/render.ts +70 -70
- package/src/compression/schema.ts +84 -84
- package/src/compression/select.ts +123 -123
- package/src/compression/session-message.ts +112 -112
- package/src/compression/settings-rpc.ts +111 -111
- package/src/compression/settings.ts +175 -175
- package/src/compression/state.ts +248 -248
- package/src/compression/summarize.ts +595 -595
- package/src/compression/summary.ts +73 -73
- package/src/compression/tool.ts +142 -142
- package/src/compression/transcript-store.ts +258 -258
- package/src/compression/transcript.ts +63 -63
- package/src/compression/validate.ts +81 -81
- package/src/i18n/en.ts +93 -93
- package/src/i18n/index.ts +28 -28
- package/src/i18n/types.ts +89 -89
- package/src/i18n/zh.ts +93 -93
- package/src/index.ts +108 -108
- package/src/shared/blocks.ts +17 -17
- package/src/shared/cache.ts +199 -199
- package/src/shared/format.ts +23 -23
- package/src/shared/media.ts +184 -184
- package/src/shared/parse.ts +25 -25
- package/src/shared/parts.ts +72 -72
- package/src/shared/rpc.ts +145 -145
- package/src/shared/scan.ts +32 -32
- package/src/shared/settings.ts +471 -471
- package/src/shared/stats.ts +142 -142
- package/src/shared/tokens.ts +259 -259
- package/src/tui/chart/cache-chart.ts +237 -237
- package/src/tui/chart/color.ts +58 -58
- package/src/tui/index.tsx +111 -111
- package/src/tui/panel/cache.tsx +203 -203
- package/src/tui/panel/data.ts +105 -105
- package/src/tui/panel/index.tsx +176 -176
- package/src/tui/panel/summaries.tsx +192 -192
- package/src/tui/settings/dialogs.tsx +215 -215
- package/src/tui/settings/options.ts +238 -238
- package/src/tui/settings.tsx +418 -418
- 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>
|