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.
- package/README.md +59 -51
- 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 +386 -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 +131 -84
- package/src/compression/select.ts +123 -123
- package/src/compression/session-message.ts +112 -112
- package/src/compression/settings-rpc.ts +123 -111
- package/src/compression/settings.ts +175 -175
- package/src/compression/shape.ts +75 -0
- package/src/compression/state.ts +248 -248
- package/src/compression/summarize.ts +534 -595
- package/src/compression/summary.ts +73 -73
- package/src/compression/tool.ts +184 -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 +116 -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 +466 -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 +109 -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 +203 -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,73 +1,73 @@
|
|
|
1
|
-
import { isBabelSummary, isMarkdownSummary, type Summary, type SummaryFields } from "./schema.ts"
|
|
2
|
-
import { markdownGoal } from "./markdown.ts"
|
|
3
|
-
|
|
4
|
-
/** 输入:条目;输出:Markdown 列表;空列表输出 `- (none)`。 */
|
|
5
|
-
function bullets(items: readonly string[]): string {
|
|
6
|
-
if (items.length === 0) return "- (none)"
|
|
7
|
-
return items.map((item) => `- ${item}`).join("\n")
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/** 输入:JSON 摘要字段;输出:Markdown 正文。 */
|
|
11
|
-
export function renderFields(fields: SummaryFields): string {
|
|
12
|
-
return [
|
|
13
|
-
"## Goal",
|
|
14
|
-
fields.goal,
|
|
15
|
-
"",
|
|
16
|
-
"## Constraints",
|
|
17
|
-
bullets(fields.constraints),
|
|
18
|
-
"",
|
|
19
|
-
"## Decisions",
|
|
20
|
-
bullets(fields.decisions),
|
|
21
|
-
"",
|
|
22
|
-
"## Progress",
|
|
23
|
-
"### Completed",
|
|
24
|
-
bullets(fields.progress.completed),
|
|
25
|
-
"### Active",
|
|
26
|
-
bullets(fields.progress.active),
|
|
27
|
-
"### Blocked",
|
|
28
|
-
bullets(fields.progress.blocked),
|
|
29
|
-
"",
|
|
30
|
-
"## Open Items",
|
|
31
|
-
bullets(fields.open_items),
|
|
32
|
-
"",
|
|
33
|
-
"## Files",
|
|
34
|
-
bullets(fields.files),
|
|
35
|
-
"",
|
|
36
|
-
"## Facts",
|
|
37
|
-
bullets(fields.facts),
|
|
38
|
-
].join("\n")
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** 输入:摘要;输出:块标题。 */
|
|
42
|
-
export function summaryTitle(summary: Summary): string {
|
|
43
|
-
return summary.title
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** 输入:摘要;输出:用于候选描述的短目标。 */
|
|
47
|
-
export function summaryGoal(summary: Summary): string {
|
|
48
|
-
if (isBabelSummary(summary)) return summary.goal
|
|
49
|
-
return isMarkdownSummary(summary) ? markdownGoal(summary.text) : summary.goal
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** 输入:摘要;输出:渲染后的正文。 */
|
|
53
|
-
export function summaryBody(summary: Summary): string {
|
|
54
|
-
if (isBabelSummary(summary) || isMarkdownSummary(summary)) return summary.text
|
|
55
|
-
return renderFields(summary)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** 输入:摘要;输出:正文实际字符数。 */
|
|
59
|
-
export function summaryChars(summary: Summary): number {
|
|
60
|
-
if (isBabelSummary(summary) || isMarkdownSummary(summary)) return summary.text.length
|
|
61
|
-
return [
|
|
62
|
-
summary.title,
|
|
63
|
-
summary.goal,
|
|
64
|
-
...summary.constraints,
|
|
65
|
-
...summary.decisions,
|
|
66
|
-
...summary.progress.completed,
|
|
67
|
-
...summary.progress.active,
|
|
68
|
-
...summary.progress.blocked,
|
|
69
|
-
...summary.open_items,
|
|
70
|
-
...summary.files,
|
|
71
|
-
...summary.facts,
|
|
72
|
-
].join("\n").length
|
|
73
|
-
}
|
|
1
|
+
import { isBabelSummary, isMarkdownSummary, type Summary, type SummaryFields } from "./schema.ts"
|
|
2
|
+
import { markdownGoal } from "./markdown.ts"
|
|
3
|
+
|
|
4
|
+
/** 输入:条目;输出:Markdown 列表;空列表输出 `- (none)`。 */
|
|
5
|
+
function bullets(items: readonly string[]): string {
|
|
6
|
+
if (items.length === 0) return "- (none)"
|
|
7
|
+
return items.map((item) => `- ${item}`).join("\n")
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** 输入:JSON 摘要字段;输出:Markdown 正文。 */
|
|
11
|
+
export function renderFields(fields: SummaryFields): string {
|
|
12
|
+
return [
|
|
13
|
+
"## Goal",
|
|
14
|
+
fields.goal,
|
|
15
|
+
"",
|
|
16
|
+
"## Constraints",
|
|
17
|
+
bullets(fields.constraints),
|
|
18
|
+
"",
|
|
19
|
+
"## Decisions",
|
|
20
|
+
bullets(fields.decisions),
|
|
21
|
+
"",
|
|
22
|
+
"## Progress",
|
|
23
|
+
"### Completed",
|
|
24
|
+
bullets(fields.progress.completed),
|
|
25
|
+
"### Active",
|
|
26
|
+
bullets(fields.progress.active),
|
|
27
|
+
"### Blocked",
|
|
28
|
+
bullets(fields.progress.blocked),
|
|
29
|
+
"",
|
|
30
|
+
"## Open Items",
|
|
31
|
+
bullets(fields.open_items),
|
|
32
|
+
"",
|
|
33
|
+
"## Files",
|
|
34
|
+
bullets(fields.files),
|
|
35
|
+
"",
|
|
36
|
+
"## Facts",
|
|
37
|
+
bullets(fields.facts),
|
|
38
|
+
].join("\n")
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** 输入:摘要;输出:块标题。 */
|
|
42
|
+
export function summaryTitle(summary: Summary): string {
|
|
43
|
+
return summary.title
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** 输入:摘要;输出:用于候选描述的短目标。 */
|
|
47
|
+
export function summaryGoal(summary: Summary): string {
|
|
48
|
+
if (isBabelSummary(summary)) return summary.goal
|
|
49
|
+
return isMarkdownSummary(summary) ? markdownGoal(summary.text) : summary.goal
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** 输入:摘要;输出:渲染后的正文。 */
|
|
53
|
+
export function summaryBody(summary: Summary): string {
|
|
54
|
+
if (isBabelSummary(summary) || isMarkdownSummary(summary)) return summary.text
|
|
55
|
+
return renderFields(summary)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** 输入:摘要;输出:正文实际字符数。 */
|
|
59
|
+
export function summaryChars(summary: Summary): number {
|
|
60
|
+
if (isBabelSummary(summary) || isMarkdownSummary(summary)) return summary.text.length
|
|
61
|
+
return [
|
|
62
|
+
summary.title,
|
|
63
|
+
summary.goal,
|
|
64
|
+
...summary.constraints,
|
|
65
|
+
...summary.decisions,
|
|
66
|
+
...summary.progress.completed,
|
|
67
|
+
...summary.progress.active,
|
|
68
|
+
...summary.progress.blocked,
|
|
69
|
+
...summary.open_items,
|
|
70
|
+
...summary.files,
|
|
71
|
+
...summary.facts,
|
|
72
|
+
].join("\n").length
|
|
73
|
+
}
|
package/src/compression/tool.ts
CHANGED
|
@@ -1,142 +1,184 @@
|
|
|
1
|
-
import type { Plugin } from "@opencode/plugin"
|
|
2
|
-
import { Schema } from "effect"
|
|
3
|
-
|
|
4
|
-
import { resolveSummaryLimits } from "../shared/settings.ts"
|
|
5
|
-
import { createT1Block } from "./block.ts"
|
|
6
|
-
import { toSummary } from "./normalize.ts"
|
|
7
|
-
import {
|
|
8
|
-
import { RESOLVED_WINDOW_KEY, loadServerSettings, windowKey } from "./settings.ts"
|
|
9
|
-
import { eligibleMessages, messageRange, type MessageLike } from "./select.ts"
|
|
10
|
-
import { sessionMessagesToMessageLikes } from "./session-message.ts"
|
|
11
|
-
import {
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
} catch
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
1
|
+
import type { Plugin } from "@opencode/plugin"
|
|
2
|
+
import { Schema } from "effect"
|
|
3
|
+
|
|
4
|
+
import { resolveSummaryLimits, type SummaryFormat } from "../shared/settings.ts"
|
|
5
|
+
import { createT1Block } from "./block.ts"
|
|
6
|
+
import { toSummary } from "./normalize.ts"
|
|
7
|
+
import { summaryInputFor } from "./schema.ts"
|
|
8
|
+
import { RESOLVED_WINDOW_KEY, loadServerSettings, windowKey } from "./settings.ts"
|
|
9
|
+
import { eligibleMessages, messageRange, type MessageLike } from "./select.ts"
|
|
10
|
+
import { sessionMessagesToMessageLikes } from "./session-message.ts"
|
|
11
|
+
import { summaryShape } from "./shape.ts"
|
|
12
|
+
import { loadState, saveState, withPending } from "./state.ts"
|
|
13
|
+
import type { Disposable } from "./settings-rpc.ts"
|
|
14
|
+
import { buildTranscript } from "./transcript.ts"
|
|
15
|
+
import { saveTranscript } from "./transcript-store.ts"
|
|
16
|
+
import { MIN_SOURCE_CHARS, validateSummary } from "./validate.ts"
|
|
17
|
+
|
|
18
|
+
const COMMON =
|
|
19
|
+
"Compress the conversation so far into a structured checkpoint. Call this when the context is getting full. " +
|
|
20
|
+
"It covers every message since the last compressed block, up to the current user message, and replaces them with one block. " +
|
|
21
|
+
"Do not call while a compression is already pending. Add a short title if useful."
|
|
22
|
+
|
|
23
|
+
const CONTENT =
|
|
24
|
+
"The block is the only remaining record of those messages, so copy exact paths with line numbers, signatures, the " +
|
|
25
|
+
"code lines that carry the logic, error text, versions, config keys, thresholds, and measured values. " +
|
|
26
|
+
"Give the reason for each decision, keep the objective and any pivots, why important actions were taken, unanswered " +
|
|
27
|
+
"questions, and work the user still expects. Describe the past as past, not as a new instruction. " +
|
|
28
|
+
"Leave out logs, repeated reads, exploration that produced nothing, abandoned attempts (one line on why), and " +
|
|
29
|
+
"repeated status checks. If space is tight, keep objective and constraints first, then decisions and reasons, then " +
|
|
30
|
+
"exact technical facts, then conclusions, then lessons. Write short factual entries, not prose."
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 输入:摘要格式。
|
|
34
|
+
* 输出:该格式的字段使用规则。
|
|
35
|
+
* 预期:只讲这个格式用得上的字段,不把三种格式混在一起讲。
|
|
36
|
+
*/
|
|
37
|
+
function fieldRules(format: SummaryFormat): string {
|
|
38
|
+
if (format === "babel") {
|
|
39
|
+
return (
|
|
40
|
+
"Only `T=<short title>|G=<one-line goal>` is fixed; the body is an adaptive latent index built from anchors you " +
|
|
41
|
+
"invent for this source. Keep shipped work and still-open work in that body, and never invent facts."
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
if (format === "markdown") {
|
|
45
|
+
return (
|
|
46
|
+
"Only include sections with real content; never invent entries to fill one. " +
|
|
47
|
+
"Use real file paths in `## Files`; put config keys and function names in `## Facts`. " +
|
|
48
|
+
"Put work in `### Completed` only when it was actually finished; plans go in `### Active` or `## Open Items`."
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
return (
|
|
52
|
+
"Omit any optional field that has no real content instead of inventing entries. " +
|
|
53
|
+
"Use `files` only for real file paths; put config keys and function names in `facts`. " +
|
|
54
|
+
"Put work in `progress.completed` only when it was actually finished; plans go in `progress.active` or `open_items`."
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** 输入:摘要格式;输出:compress 工具描述;写法部分与压缩提示词共用同一份文案。 */
|
|
59
|
+
export function descriptionFor(format: SummaryFormat): string {
|
|
60
|
+
return [COMMON, summaryShape(format), fieldRules(format), CONTENT].join("\n\n")
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function failure(message: string) {
|
|
64
|
+
return {
|
|
65
|
+
output: { block: "", status: "error" },
|
|
66
|
+
content: `ERROR: ${message}`,
|
|
67
|
+
metadata: { error: true },
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** compress 工具的注册句柄:注销 + 切换摘要格式。 */
|
|
72
|
+
export interface CompressToolHandle extends Disposable {
|
|
73
|
+
/** 输入:新的摘要格式;输出:无;预期:更新工具描述与参数,并让宿主重算工具列表。 */
|
|
74
|
+
setFormat(format: SummaryFormat): Promise<void>
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 输入:服务端上下文。
|
|
79
|
+
* 输出:注册 compress 工具,并返回可切换格式的句柄。
|
|
80
|
+
* 预期:执行时从会话上下文按需取消息,不再把整段历史常驻内存;
|
|
81
|
+
* 描述与参数按当前摘要格式生成,切换后由宿主重新计算工具列表。
|
|
82
|
+
*/
|
|
83
|
+
export async function registerCompressTool(
|
|
84
|
+
ctx: Plugin.Context,
|
|
85
|
+
legacyCache?: ReadonlyMap<string, readonly MessageLike[]>,
|
|
86
|
+
): Promise<CompressToolHandle> {
|
|
87
|
+
const initial = await loadServerSettings(ctx.storage)
|
|
88
|
+
let format: SummaryFormat = initial.summaryFormat
|
|
89
|
+
const registration = await ctx.tool.transform((tools) => {
|
|
90
|
+
tools.add({
|
|
91
|
+
name: "compress",
|
|
92
|
+
description: descriptionFor(format),
|
|
93
|
+
input: summaryInputFor(format),
|
|
94
|
+
output: Schema.Struct({ block: Schema.String, status: Schema.String }),
|
|
95
|
+
execute: async (input, toolCtx) => {
|
|
96
|
+
const sessionID = toolCtx.sessionID
|
|
97
|
+
const state = await loadState(ctx.storage, sessionID)
|
|
98
|
+
if (state.terminal !== undefined) return failure(state.terminal)
|
|
99
|
+
if (state.pending || state.phase !== undefined) return failure("compression already in progress")
|
|
100
|
+
|
|
101
|
+
let eligible: readonly MessageLike[] | undefined
|
|
102
|
+
try {
|
|
103
|
+
const raw = await ctx.session.context({ sessionID })
|
|
104
|
+
eligible = eligibleMessages(sessionMessagesToMessageLikes(raw))
|
|
105
|
+
} catch {
|
|
106
|
+
// 兼容旧测试替身;生产路径不传 legacyCache。
|
|
107
|
+
eligible = legacyCache === undefined ? undefined : (legacyCache.get(sessionID) ?? [])
|
|
108
|
+
}
|
|
109
|
+
if (eligible === undefined) return failure("failed to read session context")
|
|
110
|
+
const transcript = buildTranscript(eligible)
|
|
111
|
+
const sourceChars = transcript.length
|
|
112
|
+
if (eligible.length === 0) return failure("nothing to compress")
|
|
113
|
+
if (sourceChars < MIN_SOURCE_CHARS) {
|
|
114
|
+
return failure(
|
|
115
|
+
`not enough content to compress (${sourceChars.toString()} chars, min ${MIN_SOURCE_CHARS.toString()})`,
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const settings = await loadServerSettings(ctx.storage)
|
|
120
|
+
if (!settings.enableCompressTool) return failure("compress tool is disabled in settings")
|
|
121
|
+
// 优先用当前会话模型自己的窗口;取不到再回退最近一次解析值。
|
|
122
|
+
let windowTokens: number | undefined
|
|
123
|
+
try {
|
|
124
|
+
const session = await ctx.session.get({ sessionID })
|
|
125
|
+
const model = session.model
|
|
126
|
+
const stored =
|
|
127
|
+
model === undefined ? undefined : await ctx.storage.get(windowKey(model.providerID, model.id))
|
|
128
|
+
if (typeof stored === "number" && stored > 0) windowTokens = stored
|
|
129
|
+
} catch {
|
|
130
|
+
// 会话或模型未知:回退到最近一次解析值
|
|
131
|
+
}
|
|
132
|
+
if (windowTokens === undefined) {
|
|
133
|
+
const resolved = await ctx.storage.get(RESOLVED_WINDOW_KEY)
|
|
134
|
+
if (typeof resolved === "number" && resolved > 0) windowTokens = resolved
|
|
135
|
+
}
|
|
136
|
+
const limits = resolveSummaryLimits(settings, windowTokens)
|
|
137
|
+
const summary = toSummary(input, settings.summaryFormat)
|
|
138
|
+
if (summary === undefined) return failure("invalid summary shape")
|
|
139
|
+
const validation = validateSummary(summary, sourceChars, undefined, limits.blockChars)
|
|
140
|
+
if (!validation.ok) return failure(validation.message)
|
|
141
|
+
|
|
142
|
+
const range = messageRange(eligible)
|
|
143
|
+
if (range === undefined) {
|
|
144
|
+
return failure("messages are missing ids")
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const block = createT1Block({
|
|
148
|
+
blocks: state.blocks,
|
|
149
|
+
summary,
|
|
150
|
+
fromId: range.fromId,
|
|
151
|
+
toId: range.toId,
|
|
152
|
+
sourceChars,
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
// transcript 落盘失败不应阻断摘要;解压时会降级为占位符。
|
|
156
|
+
try {
|
|
157
|
+
await saveTranscript(ctx.storage, sessionID, block, eligible)
|
|
158
|
+
} catch {
|
|
159
|
+
// best effort
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
await saveState(ctx.storage, sessionID, withPending(state, block))
|
|
163
|
+
try {
|
|
164
|
+
await ctx.session.compact({ sessionID })
|
|
165
|
+
} catch (error) {
|
|
166
|
+
await saveState(ctx.storage, sessionID, { ...state, lastError: String(error) })
|
|
167
|
+
return failure(`failed to start compaction: ${String(error)}`)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
output: { block: block.id, status: "queued" },
|
|
172
|
+
content: `queued compression ${block.id}`,
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
return {
|
|
178
|
+
dispose: () => registration.dispose(),
|
|
179
|
+
setFormat: async (next) => {
|
|
180
|
+
format = next
|
|
181
|
+
await ctx.tool.reload()
|
|
182
|
+
},
|
|
183
|
+
}
|
|
184
|
+
}
|