compressed-context 0.0.0-stage → 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 (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +88 -2
  3. package/package.json +62 -4
  4. package/src/compression/active-summary-cache.ts +50 -0
  5. package/src/compression/adopt.ts +57 -0
  6. package/src/compression/auto.ts +372 -0
  7. package/src/compression/babel.ts +85 -0
  8. package/src/compression/block.ts +25 -0
  9. package/src/compression/cache-history.ts +75 -0
  10. package/src/compression/compaction.ts +189 -0
  11. package/src/compression/decompress-tool.ts +74 -0
  12. package/src/compression/decompress.ts +292 -0
  13. package/src/compression/deleted-sessions.ts +24 -0
  14. package/src/compression/distill.ts +220 -0
  15. package/src/compression/events.ts +275 -0
  16. package/src/compression/markdown.ts +53 -0
  17. package/src/compression/normalize.ts +114 -0
  18. package/src/compression/render.ts +70 -0
  19. package/src/compression/schema.ts +84 -0
  20. package/src/compression/select.ts +123 -0
  21. package/src/compression/session-message.ts +112 -0
  22. package/src/compression/settings-rpc.ts +111 -0
  23. package/src/compression/settings.ts +175 -0
  24. package/src/compression/state.ts +248 -0
  25. package/src/compression/summarize.ts +595 -0
  26. package/src/compression/summary.ts +73 -0
  27. package/src/compression/tool.ts +142 -0
  28. package/src/compression/transcript-store.ts +258 -0
  29. package/src/compression/transcript.ts +63 -0
  30. package/src/compression/validate.ts +81 -0
  31. package/src/i18n/en.ts +93 -0
  32. package/src/i18n/index.ts +28 -0
  33. package/src/i18n/types.ts +89 -0
  34. package/src/i18n/zh.ts +93 -0
  35. package/src/index.ts +108 -0
  36. package/src/shared/blocks.ts +17 -0
  37. package/src/shared/cache.ts +199 -0
  38. package/src/shared/format.ts +23 -0
  39. package/src/shared/media.ts +184 -0
  40. package/src/shared/parse.ts +25 -0
  41. package/src/shared/parts.ts +72 -0
  42. package/src/shared/rpc.ts +145 -0
  43. package/src/shared/scan.ts +32 -0
  44. package/src/shared/settings.ts +471 -0
  45. package/src/shared/stats.ts +142 -0
  46. package/src/shared/tokens.ts +259 -0
  47. package/src/tui/chart/cache-chart.ts +237 -0
  48. package/src/tui/chart/color.ts +58 -0
  49. package/src/tui/index.tsx +111 -0
  50. package/src/tui/panel/cache.tsx +203 -0
  51. package/src/tui/panel/data.ts +105 -0
  52. package/src/tui/panel/index.tsx +176 -0
  53. package/src/tui/panel/summaries.tsx +192 -0
  54. package/src/tui/settings/dialogs.tsx +215 -0
  55. package/src/tui/settings/options.ts +238 -0
  56. package/src/tui/settings.tsx +418 -0
  57. package/src/tui/types.ts +4 -0
@@ -0,0 +1,248 @@
1
+ import type { Schema } from "effect"
2
+
3
+ import { toBabelSummary } from "./babel.ts"
4
+ import { isSessionDeleted } from "./deleted-sessions.ts"
5
+ import { type Summary, type SummaryFields } from "./schema.ts"
6
+
7
+ /** 压缩层级:1 = 原文摘要,2/3 = 摘要再蒸馏。 */
8
+ export type Tier = 1 | 2 | 3
9
+
10
+ /** 下一次 compaction 的目的;缺省表示 T1。 */
11
+ export type CompactionPhase = "t2" | "t3"
12
+
13
+ /** 一个已落盘或待落盘的压缩块。 */
14
+ export interface Block {
15
+ readonly id: string
16
+ readonly tier: Tier
17
+ readonly summary: Summary
18
+ readonly fromId: string
19
+ readonly toId: string
20
+ readonly tokensBefore: number
21
+ readonly tokensAfter: number
22
+ readonly at: number
23
+ /** active 链中的顺序;合并块占第一个源块的位置。 */
24
+ readonly order: number
25
+ /** 被本块合并的源块;空表示原始 T1 块。 */
26
+ readonly sourceBlockIds?: readonly string[]
27
+ }
28
+
29
+ /** 每个会话一份的压缩状态。 */
30
+ export interface State {
31
+ readonly blocks: readonly Block[]
32
+ readonly pending?: Block
33
+ /** 下一次 compaction 是二级压缩;T1 时缺省。 */
34
+ readonly phase?: CompactionPhase
35
+ readonly attempts: number
36
+ readonly lastError?: string
37
+ /** 上一次主动压缩提醒时的上下文 token 数;缺省表示还没建立基线。 */
38
+ readonly nudgeBaselineTokens?: number
39
+ /** 已发出的主动压缩提醒次数。 */
40
+ readonly nudgeCount?: number
41
+ /** 全 T3 且无法继续压缩时的终止原因;存在时停止自动压缩。 */
42
+ readonly terminal?: string
43
+ }
44
+
45
+ /** 服务端 storage 的最小接口,便于测试替身。 */
46
+ export interface StorageLike {
47
+ get(key: string): Promise<Schema.Json | undefined>
48
+ set(key: string, value: Schema.Json): Promise<void>
49
+ remove(key: string): Promise<void>
50
+ }
51
+
52
+ export const STATE_PREFIX = "compression:state:"
53
+
54
+ /** 连续压缩失败达到这个次数后暂停自动压缩;同一层内还会做摘要重试。 */
55
+ export const MAX_COMPRESSION_ATTEMPTS = 3
56
+
57
+ export const EMPTY_STATE: State = { blocks: [], attempts: 0 }
58
+
59
+ /** 输入:sessionID;输出:storage key。 */
60
+ export function stateKey(sessionID: string): string {
61
+ return STATE_PREFIX + sessionID
62
+ }
63
+
64
+ /** 输入:块列表;输出:下一个可用的 order。 */
65
+ export function nextOrder(blocks: readonly Block[]): number {
66
+ return blocks.reduce((max, block) => Math.max(max, block.order), 0) + 1
67
+ }
68
+
69
+ /** 输入:已落盘块 + 可选 pending;输出:下一个可用的 bN id。 */
70
+ export function nextBlockId(blocks: readonly Block[], pending?: Block): string {
71
+ let max = 0
72
+ const all = pending === undefined ? blocks : [...blocks, pending]
73
+ for (const block of all) {
74
+ const numeric = Number(block.id.startsWith("b") ? block.id.slice(1) : "")
75
+ if (Number.isInteger(numeric) && numeric > max) max = numeric
76
+ }
77
+ return `b${(max + 1).toString()}`
78
+ }
79
+
80
+ function isStringArray(value: unknown): value is readonly string[] {
81
+ return Array.isArray(value) && value.every((item) => typeof item === "string")
82
+ }
83
+
84
+ /** 输入:存储里的摘要;输出:JSON 摘要字段;形状不合法时 undefined。 */
85
+ function parseFields(value: unknown): SummaryFields | undefined {
86
+ if (typeof value !== "object" || value === null) return undefined
87
+ const fields = value as Record<string, unknown>
88
+ if (typeof fields["title"] !== "string" || typeof fields["goal"] !== "string") return undefined
89
+ if (!isStringArray(fields["constraints"])) return undefined
90
+ if (!isStringArray(fields["decisions"])) return undefined
91
+ if (!isStringArray(fields["open_items"])) return undefined
92
+ if (!isStringArray(fields["files"])) return undefined
93
+ if (!isStringArray(fields["facts"])) return undefined
94
+ const progress = fields["progress"]
95
+ if (typeof progress !== "object" || progress === null) return undefined
96
+ const p = progress as Record<string, unknown>
97
+ if (!isStringArray(p["completed"]) || !isStringArray(p["active"]) || !isStringArray(p["blocked"])) {
98
+ return undefined
99
+ }
100
+ return {
101
+ title: fields["title"],
102
+ goal: fields["goal"],
103
+ constraints: fields["constraints"],
104
+ decisions: fields["decisions"],
105
+ progress: { completed: p["completed"], active: p["active"], blocked: p["blocked"] },
106
+ open_items: fields["open_items"],
107
+ files: fields["files"],
108
+ facts: fields["facts"],
109
+ }
110
+ }
111
+
112
+ /** 输入:存储里的摘要;输出:规范摘要;形状不合法时 undefined。 */
113
+ function parseSummary(value: unknown): Summary | undefined {
114
+ if (typeof value !== "object" || value === null) return undefined
115
+ const record = value as Record<string, unknown>
116
+ if (record["format"] === "markdown") {
117
+ if (typeof record["title"] !== "string" || typeof record["text"] !== "string") return undefined
118
+ return { format: "markdown", title: record["title"], text: record["text"] }
119
+ }
120
+ if (record["format"] === "babel") {
121
+ if (typeof record["text"] !== "string") return undefined
122
+ return toBabelSummary(
123
+ record["text"],
124
+ typeof record["title"] === "string" ? record["title"] : undefined,
125
+ typeof record["goal"] === "string" ? record["goal"] : undefined,
126
+ )
127
+ }
128
+ if ("format" in record) return undefined
129
+ return parseFields(value)
130
+ }
131
+
132
+ /** 输入:存储里的块;输出:规范块;形状不合法时 undefined。 */
133
+ function parseBlock(value: unknown): Block | undefined {
134
+ if (typeof value !== "object" || value === null) return undefined
135
+ const block = value as Record<string, unknown>
136
+ if (typeof block["id"] !== "string") return undefined
137
+ const tier = block["tier"]
138
+ if (tier !== 1 && tier !== 2 && tier !== 3) return undefined
139
+ if (typeof block["fromId"] !== "string" || typeof block["toId"] !== "string") return undefined
140
+ if (typeof block["tokensBefore"] !== "number" || typeof block["tokensAfter"] !== "number") return undefined
141
+ if (typeof block["at"] !== "number" || typeof block["order"] !== "number") return undefined
142
+ const sourceBlockIds = block["sourceBlockIds"]
143
+ if (sourceBlockIds !== undefined && !isStringArray(sourceBlockIds)) return undefined
144
+ const summary = parseSummary(block["summary"])
145
+ if (summary === undefined) return undefined
146
+ return {
147
+ id: block["id"],
148
+ tier,
149
+ summary,
150
+ fromId: block["fromId"],
151
+ toId: block["toId"],
152
+ tokensBefore: block["tokensBefore"],
153
+ tokensAfter: block["tokensAfter"],
154
+ at: block["at"],
155
+ order: block["order"],
156
+ ...(sourceBlockIds === undefined ? {} : { sourceBlockIds }),
157
+ }
158
+ }
159
+
160
+ /**
161
+ * 输入:storage 里的原始值。
162
+ * 输出:合法状态;损坏时返回空状态。
163
+ * 预期:不读版本号、不做迁移;坏块直接丢弃。
164
+ */
165
+ export function parseState(value: unknown): State {
166
+ if (typeof value !== "object" || value === null) return EMPTY_STATE
167
+ const record = value as Record<string, unknown>
168
+ const blocks = Array.isArray(record["blocks"])
169
+ ? record["blocks"].map(parseBlock).filter((block): block is Block => block !== undefined)
170
+ : []
171
+ const pending = parseBlock(record["pending"])
172
+ const attempts = typeof record["attempts"] === "number" ? record["attempts"] : 0
173
+ const lastError = typeof record["lastError"] === "string" ? record["lastError"] : undefined
174
+ const nudgeBaselineTokens =
175
+ typeof record["nudgeBaselineTokens"] === "number" ? record["nudgeBaselineTokens"] : undefined
176
+ const nudgeCount = typeof record["nudgeCount"] === "number" ? record["nudgeCount"] : undefined
177
+ const terminal = typeof record["terminal"] === "string" ? record["terminal"] : undefined
178
+ const rawPhase = record["phase"]
179
+ const phase: CompactionPhase | undefined = rawPhase === "t2" || rawPhase === "t3" ? rawPhase : undefined
180
+ return {
181
+ blocks,
182
+ attempts,
183
+ ...(pending === undefined ? {} : { pending }),
184
+ ...(lastError === undefined ? {} : { lastError }),
185
+ ...(nudgeBaselineTokens === undefined ? {} : { nudgeBaselineTokens }),
186
+ ...(nudgeCount === undefined ? {} : { nudgeCount }),
187
+ ...(terminal === undefined ? {} : { terminal }),
188
+ ...(phase === undefined ? {} : { phase }),
189
+ }
190
+ }
191
+
192
+ /** 输入:storage + sessionID;输出:状态。 */
193
+ export async function loadState(storage: StorageLike, sessionID: string): Promise<State> {
194
+ return parseState(await storage.get(stateKey(sessionID)))
195
+ }
196
+
197
+ /** 输入:storage + sessionID + 状态;输出:无;预期:写入 JSON;已删会话直接跳过。 */
198
+ export async function saveState(storage: StorageLike, sessionID: string, state: State): Promise<void> {
199
+ if (isSessionDeleted(sessionID)) return
200
+ await storage.set(stateKey(sessionID), state as unknown as Schema.Json)
201
+ }
202
+
203
+ /** 输入:状态;输出:pending 转正后的新状态;预期:成功时清 lastError/terminal,保留下一层 phase。 */
204
+ export function commitPending(state: State): State {
205
+ if (!state.pending) return state
206
+ return {
207
+ blocks: [...state.blocks, state.pending],
208
+ attempts: 0,
209
+ ...(state.nudgeCount === undefined ? {} : { nudgeCount: state.nudgeCount }),
210
+ ...(state.phase === undefined ? {} : { phase: state.phase }),
211
+ }
212
+ }
213
+
214
+ /** 输入:状态 + 错误;输出:丢弃 pending 后的新状态;预期:失败时清 pending/phase,保留 terminal。 */
215
+ export function dropPending(state: State, error: string): State {
216
+ return {
217
+ blocks: state.blocks,
218
+ attempts: state.attempts + 1,
219
+ lastError: error,
220
+ ...(state.nudgeBaselineTokens === undefined ? {} : { nudgeBaselineTokens: state.nudgeBaselineTokens }),
221
+ ...(state.nudgeCount === undefined ? {} : { nudgeCount: state.nudgeCount }),
222
+ ...(state.terminal === undefined ? {} : { terminal: state.terminal }),
223
+ }
224
+ }
225
+
226
+ /**
227
+ * 输入:失败后的状态 + 错误。
228
+ * 输出:清 pending,但保留 phase 以便下一次请求重试。
229
+ * 预期:attempts 达到上限后清掉 phase,避免无限重试。
230
+ */
231
+ export function retryPending(state: State, error: string): State {
232
+ const attempts = state.attempts + 1
233
+ const keepPhase = state.phase !== undefined && attempts < MAX_COMPRESSION_ATTEMPTS
234
+ return {
235
+ blocks: state.blocks,
236
+ attempts,
237
+ lastError: error,
238
+ ...(state.nudgeBaselineTokens === undefined ? {} : { nudgeBaselineTokens: state.nudgeBaselineTokens }),
239
+ ...(state.nudgeCount === undefined ? {} : { nudgeCount: state.nudgeCount }),
240
+ ...(keepPhase ? { phase: state.phase } : {}),
241
+ ...(state.terminal === undefined ? {} : { terminal: state.terminal }),
242
+ }
243
+ }
244
+
245
+ /** 输入:状态 + 新块;输出:挂上 pending 的新状态。 */
246
+ export function withPending(state: State, block: Block): State {
247
+ return { ...state, pending: block }
248
+ }