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,192 +1,203 @@
1
- import type { Context } from "@opencode/plugin/tui/context"
2
- import { SyntaxStyle, type RGBA } from "@opentui/core"
3
- import { createEffect, createMemo, createSignal, For, onCleanup, Show } from "solid-js"
4
-
5
- import type { Messages } from "../../i18n/index.ts"
6
- import type { SummaryBlock } from "../../shared/blocks.ts"
7
- import { formatTimestamp, formatTokens } from "../../shared/format.ts"
8
- import type { Settings } from "../../shared/settings.ts"
9
- import type { UpdateSettings } from "../types.ts"
10
-
11
- /** 输入:摘要等级;输出:对应主题色;T1 蓝、T2 黄、T3 红。 */
12
- function tierColor(tier: SummaryBlock["tier"], theme: Context["theme"]): RGBA {
13
- if (tier === 1) return theme.text.feedback.info.base
14
- if (tier === 2) return theme.text.feedback.warning.base
15
- return theme.text.feedback.error.base
16
- }
17
-
18
- /** 输入:无;输出:共享的 SyntaxStyle;预期:markdown 渲染器需要它,懒加载避免重复创建。 */
19
- let sharedSyntaxStyle: SyntaxStyle | undefined
20
- function getSyntaxStyle(): SyntaxStyle {
21
- sharedSyntaxStyle ??= SyntaxStyle.create()
22
- return sharedSyntaxStyle
23
- }
24
-
25
- /**
26
- * 输入:TUI 上下文 + 主题 + 块。
27
- * 输出:占满可用高度的阅读弹窗。
28
- * 预期:标题保持 `[T1] b1 : title`;Babel 按纯文本显示,其余交给 markdown 渲染器,esc 关闭。
29
- */
30
- export function SummaryDialog(props: {
31
- readonly context: Context
32
- readonly theme: Context["theme"]
33
- readonly block: SummaryBlock
34
- }) {
35
- const height = createMemo(() => Math.max(8, Math.min(30, props.context.renderer.height - 8)))
36
- return (
37
- <box paddingLeft={3} paddingRight={3} paddingBottom={1} gap={1}>
38
- <box flexDirection="row" justifyContent="space-between" gap={1}>
39
- <text fg={props.theme.text.base} wrapMode="word">
40
- <b>{`[T${props.block.tier.toString()}] ${props.block.id} : ${props.block.title}`}</b>
41
- </text>
42
- <text
43
- fg={props.theme.text.muted}
44
- onMouseUp={() => {
45
- props.context.ui.dialog.clear()
46
- }}
47
- >
48
- esc
49
- </text>
50
- </box>
51
- <scrollbox height={height()} scrollY={true} focused={true} width="100%">
52
- <Show
53
- when={props.block.format === "babel"}
54
- fallback={
55
- <markdown
56
- content={props.block.body}
57
- syntaxStyle={getSyntaxStyle()}
58
- fg={props.theme.text.base}
59
- conceal={true}
60
- tableOptions={{ widthMode: "full", style: "columns" }}
61
- />
62
- }
63
- >
64
- <text fg={props.theme.text.base} wrapMode="word">
65
- {props.block.body}
66
- </text>
67
- </Show>
68
- </scrollbox>
69
- </box>
70
- )
71
- }
72
-
73
- /**
74
- * 输入:主题 + 文案 + 设置 + 写入函数 + 摘要块。
75
- * 输出:可折叠的摘要块列表;点击标题打开 markdown 阅读弹窗。
76
- * 预期:只展示服务端返回的 active 链;hover 只让箭头动,标题和背景保持稳定。
77
- */
78
- export function SummarySection(props: {
79
- readonly context: Context
80
- readonly theme: Context["theme"]
81
- readonly copy: Messages
82
- readonly settings: Settings
83
- readonly updateSettings: UpdateSettings
84
- readonly blocks: readonly SummaryBlock[]
85
- }) {
86
- const [hovered, setHovered] = createSignal<string | undefined>(undefined)
87
- const [frame, setFrame] = createSignal(0)
88
-
89
- // 只有 hover 时跑两帧;鼠标移出或销毁时清理,避免后台定时器。
90
- createEffect(() => {
91
- if (hovered() === undefined) {
92
- setFrame(0)
93
- return
94
- }
95
- const timer = setInterval(() => setFrame((value) => (value + 1) % 2), 180)
96
- onCleanup(() => {
97
- clearInterval(timer)
98
- })
99
- })
100
-
101
- /** 输入:无;输出:无;预期:翻转摘要区折叠状态。 */
102
- const toggleFold = (): void => {
103
- void props.updateSettings((draft) => {
104
- draft.summariesExpanded = !draft.summariesExpanded
105
- })
106
- }
107
-
108
- /** 输入:块;输出:无;预期:打开该块的 markdown 阅读弹窗。 */
109
- const openSummary = (block: SummaryBlock): void => {
110
- props.context.ui.dialog.show(() => (
111
- <SummaryDialog context={props.context} theme={props.theme} block={block} />
112
- ))
113
- props.context.ui.dialog.set({ size: "large", centered: true })
114
- }
115
-
116
- return (
117
- <Show when={props.settings.showSummaries}>
118
- <box width="100%" flexDirection="column">
119
- <box flexDirection="row" height={1} onMouseDown={toggleFold}>
120
- <box flexDirection="row" gap={1}>
121
- <text fg={props.theme.text.base}>{props.settings.summariesExpanded ? "▼" : "▶"}</text>
122
- <text fg={props.theme.text.base}>
123
- <b>{props.copy.summariesTitle}</b>
124
- </text>
125
- <text fg={props.theme.text.muted}>{`(${props.blocks.length.toString()})`}</text>
126
- </box>
127
- </box>
128
- <Show when={props.settings.summariesExpanded}>
129
- <Show
130
- when={props.blocks.length > 0}
131
- fallback={<text fg={props.theme.text.muted}>{props.copy.noData}</text>}
132
- >
133
- <box paddingLeft={2} flexDirection="column" width="100%">
134
- <For each={props.blocks}>
135
- {(block) => {
136
- const meta = (): string => {
137
- const parts: string[] = []
138
- if (props.settings.showTimeline) parts.push(formatTimestamp(block.at))
139
- if (block.sourceBlockIds !== undefined && block.sourceBlockIds.length > 0) {
140
- parts.push(`merged ${block.sourceBlockIds.map((id) => `#${id}`).join(" + ")}`)
141
- }
142
- return parts.join(" ")
143
- }
144
- return (
145
- <box flexDirection="column" width="100%">
146
- <box
147
- flexDirection="row"
148
- gap={1}
149
- onMouseOver={() => {
150
- setHovered(block.id)
151
- }}
152
- onMouseOut={() => {
153
- setHovered((current) => (current === block.id ? undefined : current))
154
- }}
155
- onMouseUp={() => {
156
- openSummary(block)
157
- }}
158
- >
159
- <text width={2} fg={tierColor(block.tier, props.theme)}>
160
- {hovered() === block.id ? (frame() === 0 ? "▸ " : " ▸") : " "}
161
- </text>
162
- <text fg={props.theme.text.base} wrapMode="word" flexGrow={1}>
163
- <span style={{ fg: tierColor(block.tier, props.theme) }}>
164
- <b>{`[T${block.tier.toString()}]`}</b>
165
- </span>
166
- <span style={{ fg: props.theme.text.muted }}>
167
- {` ${block.id} : `}
168
- </span>
169
- <span style={{ fg: props.theme.text.base }}>{block.title}</span>
170
- <Show when={props.settings.showBlockTokens}>
171
- <span style={{ fg: props.theme.text.muted }}>
172
- {` ≈${formatTokens(block.tokensAfter)}`}
173
- </span>
174
- </Show>
175
- </text>
176
- </box>
177
- <Show when={meta() !== ""}>
178
- <text fg={props.theme.text.muted} wrapMode="word">
179
- {` ${meta()}`}
180
- </text>
181
- </Show>
182
- </box>
183
- )
184
- }}
185
- </For>
186
- </box>
187
- </Show>
188
- </Show>
189
- </box>
190
- </Show>
191
- )
192
- }
1
+ import type { Context } from "@opencode/plugin/tui/context"
2
+ import { SyntaxStyle, type RGBA } from "@opentui/core"
3
+ import { createEffect, createMemo, createSignal, For, onCleanup, Show } from "solid-js"
4
+
5
+ import type { Messages } from "../../i18n/index.ts"
6
+ import type { SummaryBlock } from "../../shared/blocks.ts"
7
+ import { formatTimestamp, formatTokens } from "../../shared/format.ts"
8
+ import type { Settings } from "../../shared/settings.ts"
9
+ import type { UpdateSettings } from "../types.ts"
10
+
11
+ /** 输入:摘要等级;输出:对应主题色;T1 蓝、T2 黄、T3 红。 */
12
+ function tierColor(tier: SummaryBlock["tier"], theme: Context["theme"]): RGBA {
13
+ if (tier === 1) return theme.text.feedback.info.base
14
+ if (tier === 2) return theme.text.feedback.warning.base
15
+ return theme.text.feedback.error.base
16
+ }
17
+
18
+ /** 输入:TUI renderer;输出:共享的 SyntaxStyle,拿不到 resource owner 时返回 undefined。 */
19
+ let sharedSyntaxStyle: SyntaxStyle | undefined
20
+ function getSyntaxStyle(renderer: Context["renderer"]): SyntaxStyle | undefined {
21
+ const owner = (renderer as { readonly nativeScene?: unknown }).nativeScene
22
+ if (owner === undefined) return undefined
23
+ sharedSyntaxStyle ??= SyntaxStyle.create(owner as Parameters<typeof SyntaxStyle.create>[0])
24
+ return sharedSyntaxStyle
25
+ }
26
+
27
+ /**
28
+ * 输入:TUI 上下文 + 主题 + 块。
29
+ * 输出:占满可用高度的阅读弹窗。
30
+ * 预期:标题保持 `[T1] b1 : title`;Babel 按纯文本显示,其余交给 markdown 渲染器,esc 关闭。
31
+ */
32
+ export function SummaryDialog(props: {
33
+ readonly context: Context
34
+ readonly theme: Context["theme"]
35
+ readonly block: SummaryBlock
36
+ }) {
37
+ const height = createMemo(() => Math.max(8, Math.min(30, props.context.renderer.height - 8)))
38
+ return (
39
+ <box paddingLeft={3} paddingRight={3} paddingBottom={1} gap={1}>
40
+ <box flexDirection="row" justifyContent="space-between" gap={1}>
41
+ <text fg={props.theme.text.base} wrapMode="word">
42
+ <b>{`[T${props.block.tier.toString()}] ${props.block.id} : ${props.block.title}`}</b>
43
+ </text>
44
+ <text
45
+ fg={props.theme.text.muted}
46
+ onMouseUp={() => {
47
+ props.context.ui.dialog.clear()
48
+ }}
49
+ >
50
+ esc
51
+ </text>
52
+ </box>
53
+ <scrollbox height={height()} scrollY={true} focused={true} width="100%">
54
+ <Show when={props.block.format !== "babel"}>
55
+ <Show
56
+ when={getSyntaxStyle(props.context.renderer)}
57
+ fallback={
58
+ <text fg={props.theme.text.base} wrapMode="word">
59
+ {props.block.body}
60
+ </text>
61
+ }
62
+ >
63
+ {(style) => (
64
+ <markdown
65
+ content={props.block.body}
66
+ syntaxStyle={style()}
67
+ fg={props.theme.text.base}
68
+ conceal={true}
69
+ tableOptions={{ widthMode: "full", style: "columns" }}
70
+ />
71
+ )}
72
+ </Show>
73
+ </Show>
74
+ <Show when={props.block.format === "babel"}>
75
+ <text fg={props.theme.text.base} wrapMode="word">
76
+ {props.block.body}
77
+ </text>
78
+ </Show>
79
+ </scrollbox>
80
+ </box>
81
+ )
82
+ }
83
+
84
+ /**
85
+ * 输入:主题 + 文案 + 设置 + 写入函数 + 摘要块。
86
+ * 输出:可折叠的摘要块列表;点击标题打开 markdown 阅读弹窗。
87
+ * 预期:只展示服务端返回的 active 链;hover 只让箭头动,标题和背景保持稳定。
88
+ */
89
+ export function SummarySection(props: {
90
+ readonly context: Context
91
+ readonly theme: Context["theme"]
92
+ readonly copy: Messages
93
+ readonly settings: Settings
94
+ readonly updateSettings: UpdateSettings
95
+ readonly blocks: readonly SummaryBlock[]
96
+ }) {
97
+ const [hovered, setHovered] = createSignal<string | undefined>(undefined)
98
+ const [frame, setFrame] = createSignal(0)
99
+
100
+ // 只有 hover 时跑两帧;鼠标移出或销毁时清理,避免后台定时器。
101
+ createEffect(() => {
102
+ if (hovered() === undefined) {
103
+ setFrame(0)
104
+ return
105
+ }
106
+ const timer = setInterval(() => setFrame((value) => (value + 1) % 2), 180)
107
+ onCleanup(() => {
108
+ clearInterval(timer)
109
+ })
110
+ })
111
+
112
+ /** 输入:无;输出:无;预期:翻转摘要区折叠状态。 */
113
+ const toggleFold = (): void => {
114
+ void props.updateSettings((draft) => {
115
+ draft.summariesExpanded = !draft.summariesExpanded
116
+ })
117
+ }
118
+
119
+ /** 输入:块;输出:无;预期:打开该块的 markdown 阅读弹窗。 */
120
+ const openSummary = (block: SummaryBlock): void => {
121
+ props.context.ui.dialog.show(() => (
122
+ <SummaryDialog context={props.context} theme={props.theme} block={block} />
123
+ ))
124
+ props.context.ui.dialog.set({ size: "large", centered: true })
125
+ }
126
+
127
+ return (
128
+ <Show when={props.settings.showSummaries}>
129
+ <box width="100%" flexDirection="column">
130
+ <box flexDirection="row" height={1} onMouseDown={toggleFold}>
131
+ <box flexDirection="row" gap={1}>
132
+ <text fg={props.theme.text.base}>{props.settings.summariesExpanded ? "▼" : "▶"}</text>
133
+ <text fg={props.theme.text.base}>
134
+ <b>{props.copy.summariesTitle}</b>
135
+ </text>
136
+ <text fg={props.theme.text.muted}>{`(${props.blocks.length.toString()})`}</text>
137
+ </box>
138
+ </box>
139
+ <Show when={props.settings.summariesExpanded}>
140
+ <Show
141
+ when={props.blocks.length > 0}
142
+ fallback={<text fg={props.theme.text.muted}>{props.copy.noData}</text>}
143
+ >
144
+ <box paddingLeft={2} flexDirection="column" width="100%">
145
+ <For each={props.blocks}>
146
+ {(block) => {
147
+ const meta = (): string => {
148
+ const parts: string[] = []
149
+ if (props.settings.showTimeline) parts.push(formatTimestamp(block.at))
150
+ if (block.sourceBlockIds !== undefined && block.sourceBlockIds.length > 0) {
151
+ parts.push(`merged ${block.sourceBlockIds.map((id) => `#${id}`).join(" + ")}`)
152
+ }
153
+ return parts.join(" ")
154
+ }
155
+ return (
156
+ <box flexDirection="column" width="100%">
157
+ <box
158
+ flexDirection="row"
159
+ gap={1}
160
+ onMouseOver={() => {
161
+ setHovered(block.id)
162
+ }}
163
+ onMouseOut={() => {
164
+ setHovered((current) => (current === block.id ? undefined : current))
165
+ }}
166
+ onMouseUp={() => {
167
+ openSummary(block)
168
+ }}
169
+ >
170
+ <text width={2} fg={tierColor(block.tier, props.theme)}>
171
+ {hovered() === block.id ? (frame() === 0 ? "▸ " : " ▸") : " "}
172
+ </text>
173
+ <text fg={props.theme.text.base} wrapMode="word" flexGrow={1}>
174
+ <span style={{ fg: tierColor(block.tier, props.theme) }}>
175
+ <b>{`[T${block.tier.toString()}]`}</b>
176
+ </span>
177
+ <span style={{ fg: props.theme.text.muted }}>
178
+ {` ${block.id} : `}
179
+ </span>
180
+ <span style={{ fg: props.theme.text.base }}>{block.title}</span>
181
+ <Show when={props.settings.showBlockTokens}>
182
+ <span style={{ fg: props.theme.text.muted }}>
183
+ {` ≈${formatTokens(block.tokensAfter)}`}
184
+ </span>
185
+ </Show>
186
+ </text>
187
+ </box>
188
+ <Show when={meta() !== ""}>
189
+ <text fg={props.theme.text.muted} wrapMode="word">
190
+ {` ${meta()}`}
191
+ </text>
192
+ </Show>
193
+ </box>
194
+ )
195
+ }}
196
+ </For>
197
+ </box>
198
+ </Show>
199
+ </Show>
200
+ </box>
201
+ </Show>
202
+ )
203
+ }