pi-one-ui 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 (101) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +106 -0
  3. package/extensions/app/config/renderer.ts +452 -0
  4. package/extensions/app/config/shell.ts +2484 -0
  5. package/extensions/app/ownership.ts +18 -0
  6. package/extensions/app/panel.ts +533 -0
  7. package/extensions/app/presets.ts +36 -0
  8. package/extensions/features/agent-summary/core.ts +194 -0
  9. package/extensions/features/agent-summary/index.ts +37 -0
  10. package/extensions/features/compact-thinking.ts +1262 -0
  11. package/extensions/features/context.ts +857 -0
  12. package/extensions/features/reference/index.ts +643 -0
  13. package/extensions/features/reference/session.ts +278 -0
  14. package/extensions/features/reference/subagent.ts +218 -0
  15. package/extensions/features/shell/aliases.ts +28 -0
  16. package/extensions/features/shell/flush-docked-bash.ts +39 -0
  17. package/extensions/features/shell/startup-header.ts +357 -0
  18. package/extensions/features/shell/working-message.ts +226 -0
  19. package/extensions/index.ts +52 -0
  20. package/extensions/shell/accent-rail-editor.ts +112 -0
  21. package/extensions/shell/accent-rail-layout-patch.ts +530 -0
  22. package/extensions/shell/completion-menu.ts +186 -0
  23. package/extensions/shell/editor-metadata-format.ts +287 -0
  24. package/extensions/shell/editor-transfer.ts +73 -0
  25. package/extensions/shell/extension-status.ts +102 -0
  26. package/extensions/shell/footer-format.ts +388 -0
  27. package/extensions/shell/footer-layout.ts +144 -0
  28. package/extensions/shell/footer.ts +1035 -0
  29. package/extensions/shell/format.ts +577 -0
  30. package/extensions/shell/git.ts +434 -0
  31. package/extensions/shell/icons.ts +261 -0
  32. package/extensions/shell/index.ts +1659 -0
  33. package/extensions/shell/interaction-summary.ts +1194 -0
  34. package/extensions/shell/live-context.ts +82 -0
  35. package/extensions/shell/minimalist-editor.ts +548 -0
  36. package/extensions/shell/package-version.ts +675 -0
  37. package/extensions/shell/project-refresh.ts +130 -0
  38. package/extensions/shell/project-state.ts +60 -0
  39. package/extensions/shell/prototype-patch-registry.ts +183 -0
  40. package/extensions/shell/runtime.ts +915 -0
  41. package/extensions/shell/selector-border.ts +109 -0
  42. package/extensions/shell/session-lifecycle.ts +60 -0
  43. package/extensions/shell/settings-command.ts +2144 -0
  44. package/extensions/shell/settings-previews.ts +137 -0
  45. package/extensions/shell/state.ts +83 -0
  46. package/extensions/shell/style.ts +480 -0
  47. package/extensions/shell/telemetry.ts +90 -0
  48. package/extensions/shell/ui.ts +1326 -0
  49. package/extensions/shell/user-message-osc.ts +279 -0
  50. package/extensions/shell/user-message-styles.ts +266 -0
  51. package/extensions/shell/user-message.ts +226 -0
  52. package/extensions/shell/working-line-messages.ts +24 -0
  53. package/extensions/shell/working-line-spinners.ts +54 -0
  54. package/extensions/shell/working-line.ts +1603 -0
  55. package/extensions/tools/ansi-color.ts +44 -0
  56. package/extensions/tools/ansi-text.ts +52 -0
  57. package/extensions/tools/component-tree.ts +37 -0
  58. package/extensions/tools/format.ts +28 -0
  59. package/extensions/tools/fullscreen-detect.ts +11 -0
  60. package/extensions/tools/patch-keys.ts +158 -0
  61. package/extensions/tools/sgr-mouse.ts +59 -0
  62. package/extensions/tools/tool-loading-icon.ts +20 -0
  63. package/extensions/tools/tool-result-sanitize.ts +27 -0
  64. package/extensions/transcript/index.ts +65 -0
  65. package/extensions/transcript/renderer/compact-mode.ts +1443 -0
  66. package/extensions/transcript/renderer/default-mode.ts +735 -0
  67. package/extensions/transcript/renderer/index.ts +286 -0
  68. package/extensions/transcript/renderer/markdown-enhance.ts +325 -0
  69. package/extensions/transcript/renderer/mouse/hover.ts +175 -0
  70. package/extensions/transcript/renderer/mouse/interaction.ts +1016 -0
  71. package/extensions/transcript/renderer/mouse/layout.ts +207 -0
  72. package/extensions/transcript/renderer/mouse/packets.ts +96 -0
  73. package/extensions/transcript/renderer/mouse/scroll.ts +211 -0
  74. package/extensions/transcript/renderer/tool/diff/ATTRIBUTION.md +28 -0
  75. package/extensions/transcript/renderer/tool/diff/ansi-utils.ts +128 -0
  76. package/extensions/transcript/renderer/tool/diff/diff-component.ts +112 -0
  77. package/extensions/transcript/renderer/tool/diff/diff-edit-render.ts +255 -0
  78. package/extensions/transcript/renderer/tool/diff/diff-header.ts +184 -0
  79. package/extensions/transcript/renderer/tool/diff/diff-highlight.ts +131 -0
  80. package/extensions/transcript/renderer/tool/diff/diff-inline.ts +350 -0
  81. package/extensions/transcript/renderer/tool/diff/diff-layout.ts +870 -0
  82. package/extensions/transcript/renderer/tool/diff/diff-limits.ts +189 -0
  83. package/extensions/transcript/renderer/tool/diff/diff-palette.ts +436 -0
  84. package/extensions/transcript/renderer/tool/diff/diff-parse.ts +536 -0
  85. package/extensions/transcript/renderer/tool/diff/diff-presentation.ts +79 -0
  86. package/extensions/transcript/renderer/tool/diff/diff-renderer.ts +29 -0
  87. package/extensions/transcript/renderer/tool/diff/diff-text.ts +57 -0
  88. package/extensions/transcript/renderer/tool/diff/diff-write-render.ts +629 -0
  89. package/extensions/transcript/renderer/tool/diff/index.ts +147 -0
  90. package/extensions/transcript/renderer/tool/diff/line-width-safety.ts +100 -0
  91. package/extensions/transcript/renderer/tool/diff/shiki-highlight.ts +104 -0
  92. package/extensions/transcript/renderer/tool/diff/write-execution.ts +124 -0
  93. package/extensions/transcript/renderer/tool/grouping.ts +619 -0
  94. package/extensions/transcript/renderer/tool/message-display.ts +183 -0
  95. package/extensions/transcript/renderer/tool/names.ts +195 -0
  96. package/extensions/transcript/renderer/tool/result.ts +842 -0
  97. package/extensions/transcript/renderer/tool/show-more-hint.ts +42 -0
  98. package/extensions/transcript/renderer/transcript-refresh.ts +79 -0
  99. package/package.json +58 -0
  100. package/themes/cc-dark.json +91 -0
  101. package/themes/cc-light.json +91 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 minuque
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # pi-one-ui
2
+
3
+ `pi-one-ui` 是一个单一 Pi 插件包,将两个 UI 扩展合并为一个可自定义的源码仓库:
4
+
5
+ - **Zentui**:Editor、User message、Working line、Selector border、Footer
6
+ - **CC Style**:Tool renderer、Rich diff、Thinking、Compact transcript、鼠标交互和辅助功能
7
+
8
+ 安装后只需要启用这个包,不需要再单独安装 `pi-zentui` 或 `pi-cc-extensions`。
9
+
10
+ ## 安装
11
+
12
+ ```bash
13
+ pi install /path/to/pi-one-ui
14
+ # 或安装远程仓库
15
+ pi install git:github.com/<your-account>/pi-one-ui
16
+ ```
17
+
18
+ 安装后执行 `/reload`。
19
+
20
+ ## 统一入口
21
+
22
+ ```text
23
+ /oneui
24
+ ```
25
+
26
+ `/oneui` 是唯一的公开管理命令,执行后直接打开融合后的交互面板。面板按 `Tab` / `Shift+Tab` 切换 Shell、Renderer、Features、Presets 分区。Shell 分区会显示 Editor 和 User Message 的实时样式预览;修改会立即写入统一配置,活动会话支持的样式也会自动重绘。
27
+
28
+ ```text
29
+ ~/.pi/agent/pi-one-ui.json
30
+ ```
31
+
32
+ 面板中的详细配置仍可直接编辑 JSON,以便开发自定义字段。
33
+
34
+ ## 配置结构
35
+
36
+ Zentui 的配置保留在 JSON 根级,CC Style 的配置放在 `renderer` 下:
37
+
38
+ ```json
39
+ {
40
+ "version": 1,
41
+ "components": {
42
+ "editor": { "enabled": true, "style": "accent-rail" },
43
+ "userMessages": { "enabled": true, "style": "framed" },
44
+ "workingLine": { "enabled": true },
45
+ "footer": { "style": "starship" }
46
+ },
47
+ "renderer": {
48
+ "mode": "on",
49
+ "diffViewMode": "auto",
50
+ "enableWorkingMessage": false
51
+ }
52
+ }
53
+ ```
54
+
55
+ 完整字段可参考上游快照:
56
+
57
+ - `vendor/pi-zentui/docs/configuration.md`
58
+ - `vendor/pi-cc-extensions/README.md`
59
+
60
+ Working line 的 owner 固定为 Zentui,因此默认关闭 CC Style 的 `enableWorkingMessage`,避免两个扩展争抢 Pi 的无 key working row。
61
+
62
+ ## 来源与目录结构
63
+
64
+ 本项目基于以下上游项目进行融合开发:
65
+
66
+ - `pi-zentui`:提供 Shell chrome、Editor、Footer、Working line 等能力
67
+ - `pi-cc-extensions`:提供 Tool renderer、Diff、Thinking 和 Transcript 能力
68
+
69
+ 上游源码快照保存在 `vendor/` 中,仅用于追踪和同步。`extensions/` 按 `pi-one-ui` 的产品能力组织,不保留上游项目的目录结构:
70
+
71
+ ```text
72
+ extensions/
73
+ index.ts # 唯一公开入口和 /oneui 命令
74
+ app/ # 统一配置、面板、预设和应用装配
75
+ shell/ # Editor、Footer、Working line 等终端外壳能力
76
+ transcript/ # Tool、Diff、Thinking 和消息渲染
77
+ features/ # Context、Session reference、Agent summary 等功能
78
+ tools/ # Patch、ANSI、组件树和其他底层能力
79
+ vendor/ # 上游只读参考快照和版本记录
80
+ ```
81
+
82
+ 公共入口只有 `/oneui`。Shell 和 Transcript 的内部 owner 由应用装配层统一管理,避免不同模块争抢同一个 Pi UI 接缝。
83
+
84
+ ## 开发
85
+
86
+ ```bash
87
+ npm install
88
+ npm run typecheck
89
+ npm test
90
+ npm run pack:check
91
+ ```
92
+
93
+ `npm test` 会同时运行:
94
+
95
+ - `node:test`:Transcript、工具渲染和融合入口测试
96
+ - `Vitest`:Shell 及其生命周期、布局和配置测试
97
+
98
+ 测试文件按产品能力命名为 `tests/shell-*`、`tests/transcript-*` 和 `tests/one-ui-*`。
99
+
100
+ 本地启动:
101
+
102
+ ```bash
103
+ npm run pi:dev
104
+ ```
105
+
106
+ 上游快照版本记录在 `ARCHITECTURE.md`。更新上游时,先在 `vendor/` 中获取新快照,再将对应源码同步到 `extensions/`,不要直接修改 vendor 参考目录。
@@ -0,0 +1,452 @@
1
+ import type { CompactThinkingConfig } from "../../features/compact-thinking.ts";
2
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
+
6
+ export type CompactStyleMode = "on" | "compact" | "off";
7
+
8
+ // ── diff 显示配置:作为配置 schema 的一部分由 config 层拥有 ──
9
+ // renderer(diff)从 config 导入并 re-export,避免 config → renderer 的反向依赖。
10
+
11
+ export type DiffViewMode = "auto" | "split" | "unified";
12
+ export type DiffIndicatorMode = "bars" | "classic" | "none";
13
+
14
+ export interface ToolDisplayConfig {
15
+ diffViewMode: DiffViewMode;
16
+ diffIndicatorMode: DiffIndicatorMode;
17
+ diffSplitMinWidth: number;
18
+ editDiffCollapsedLines: number;
19
+ /** Write-only collapsed body lines. 0 = `↳ created • click to show more`. */
20
+ writeDiffCollapsedLines: number;
21
+ diffWordWrap: boolean;
22
+ expandedPreviewMaxLines: number;
23
+ }
24
+
25
+ export const DEFAULT_TOOL_DISPLAY_CONFIG: ToolDisplayConfig = {
26
+ diffViewMode: "auto",
27
+ diffIndicatorMode: "bars",
28
+ diffSplitMinWidth: 120,
29
+ /** Collapsed edit/diff body: ~half a typical terminal after chrome. */
30
+ editDiffCollapsedLines: 24,
31
+ /**
32
+ * Write create/overwrite collapsed body.
33
+ * 0 = `↳ created • click to show more` (stats stay on the title).
34
+ */
35
+ writeDiffCollapsedLines: 0,
36
+ diffWordWrap: true,
37
+ /**
38
+ * Expanded tool/diff body cap. 40 ≈ one screen of content after title,
39
+ * Input section, editor, and status — keeps the TUI compact.
40
+ * Raise via /ccstyle → Diff → Expanded max lines when reviewing large dumps.
41
+ */
42
+ expandedPreviewMaxLines: 40,
43
+ };
44
+
45
+ export type Config = {
46
+ mode: CompactStyleMode;
47
+ excludeRenderers: string[];
48
+ diffViewMode: DiffViewMode;
49
+ diffIndicatorMode: DiffIndicatorMode;
50
+ diffSplitMinWidth: number;
51
+ editDiffCollapsedLines: number;
52
+ writeDiffCollapsedLines: number;
53
+ diffWordWrap: boolean;
54
+ expandedPreviewMaxLines: number;
55
+ toolInputNameLength: number;
56
+ useSummaryTitlesAsThinkingTitle: boolean;
57
+ previewLines: number;
58
+ animationIntervalMs: number;
59
+ dimThinkingText: boolean;
60
+ showStartupHeader: boolean;
61
+ scrollStepLines: number;
62
+ enableSessionReference: boolean;
63
+ enableSubagentAutocomplete: boolean;
64
+ enableContextCommand: boolean;
65
+ enableAgentSummary: boolean;
66
+ enableWorkingMessage: boolean;
67
+ enableAliases: boolean;
68
+ };
69
+
70
+ const AGENT_DIR =
71
+ process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent");
72
+ const CONFIG_PATH = join(AGENT_DIR, "pi-one-ui.json");
73
+ const LEGACY_UNIFIED_CONFIG_PATH = join(AGENT_DIR, "pi-mine-ui.json");
74
+ const LEGACY_CONFIG_PATH = join(AGENT_DIR, "claude-code-style.json");
75
+ const LEGACY_ZENTUI_CONFIG_PATH = join(AGENT_DIR, "zentui.json");
76
+
77
+ type ConfigRecord = Record<string, unknown>;
78
+
79
+ function readJson(path: string): ConfigRecord | undefined {
80
+ try {
81
+ if (!existsSync(path)) return undefined;
82
+ const value = JSON.parse(readFileSync(path, "utf8"));
83
+ return value && typeof value === "object" && !Array.isArray(value)
84
+ ? (value as ConfigRecord)
85
+ : undefined;
86
+ } catch {
87
+ return undefined;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Keep the two upstream configuration schemas under one portable file. Zentui
93
+ * owns the root fields; the CC renderer owns `renderer`. Existing installations
94
+ * are imported once without deleting their legacy files.
95
+ */
96
+ function readUnifiedConfig(): ConfigRecord {
97
+ const current = readJson(CONFIG_PATH) ?? readJson(LEGACY_UNIFIED_CONFIG_PATH);
98
+ if (current) {
99
+ if (!existsSync(CONFIG_PATH)) {
100
+ try {
101
+ writeFileSync(CONFIG_PATH, JSON.stringify(current, null, 2));
102
+ } catch {
103
+ // Keep using the legacy file in memory if migration cannot be written.
104
+ }
105
+ }
106
+ return current;
107
+ }
108
+
109
+ const shell = readJson(LEGACY_ZENTUI_CONFIG_PATH) ?? {};
110
+ const renderer = readJson(LEGACY_CONFIG_PATH);
111
+ if (!renderer && Object.keys(shell).length === 0) return {};
112
+
113
+ const migrated = { ...shell, version: 1, renderer: renderer ?? {} };
114
+ try {
115
+ writeFileSync(CONFIG_PATH, JSON.stringify(migrated, null, 2));
116
+ } catch {
117
+ // A read-only agent directory still uses the migrated in-memory value.
118
+ }
119
+ return migrated;
120
+ }
121
+
122
+ function rendererConfigFrom(record: ConfigRecord): ConfigRecord {
123
+ const renderer = record.renderer;
124
+ return renderer && typeof renderer === "object" && !Array.isArray(renderer)
125
+ ? (renderer as ConfigRecord)
126
+ : record;
127
+ }
128
+
129
+ export const DIFF_VIEW_MODES: DiffViewMode[] = ["auto", "split", "unified"];
130
+ export const DIFF_INDICATOR_MODES: DiffIndicatorMode[] = [
131
+ "bars",
132
+ "classic",
133
+ "none",
134
+ ];
135
+ export const DIFF_SPLIT_MIN_WIDTH_VALUES = [
136
+ "80",
137
+ "100",
138
+ "120",
139
+ "140",
140
+ "160",
141
+ "180",
142
+ ];
143
+ export const DIFF_COLLAPSED_LINES_VALUES = [
144
+ "12",
145
+ "24",
146
+ "36",
147
+ "48",
148
+ "80",
149
+ "120",
150
+ ];
151
+ /** Write collapsed presets. 0 = stats only (`+N -0` + expand hint). */
152
+ export const WRITE_DIFF_COLLAPSED_LINES_VALUES = [
153
+ "0",
154
+ "4",
155
+ "8",
156
+ "12",
157
+ "24",
158
+ "36",
159
+ ];
160
+ /** Presets for expanded body height — keep low options first so cycling stays TUI-friendly. */
161
+ export const EXPANDED_PREVIEW_MAX_LINES_VALUES = [
162
+ "40",
163
+ "60",
164
+ "80",
165
+ "120",
166
+ "200",
167
+ "500",
168
+ "2000",
169
+ ];
170
+ /** 工具摘要里 path/command 等输入的折叠字符数。 */
171
+ export const TOOL_INPUT_NAME_LENGTH_VALUES = [
172
+ "40",
173
+ "60",
174
+ "80",
175
+ "100",
176
+ "120",
177
+ "160",
178
+ ];
179
+ export const THINKING_PREVIEW_LINES_VALUES = ["0", "1", "3", "5", "10"];
180
+ export const THINKING_ANIMATION_INTERVAL_VALUES = [
181
+ "40",
182
+ "60",
183
+ "90",
184
+ "120",
185
+ "180",
186
+ ];
187
+ /** fullscreen 滚轮步进行数预设。 */
188
+ export const SCROLL_STEP_LINES_VALUES = ["1", "2", "3", "5", "10"];
189
+ /** Tools commonly toggled in excludeRenderers via the settings panel. */
190
+ export const EXCLUDE_RENDERER_CANDIDATES = [
191
+ "bash",
192
+ "read",
193
+ "edit",
194
+ "write",
195
+ "grep",
196
+ "find",
197
+ "ls",
198
+ "webfetch",
199
+ "wait",
200
+ ];
201
+
202
+ export const DEFAULT_CONFIG: Config = {
203
+ mode: "on",
204
+ excludeRenderers: [],
205
+ diffViewMode: DEFAULT_TOOL_DISPLAY_CONFIG.diffViewMode,
206
+ diffIndicatorMode: DEFAULT_TOOL_DISPLAY_CONFIG.diffIndicatorMode,
207
+ diffSplitMinWidth: DEFAULT_TOOL_DISPLAY_CONFIG.diffSplitMinWidth,
208
+ editDiffCollapsedLines: DEFAULT_TOOL_DISPLAY_CONFIG.editDiffCollapsedLines,
209
+ writeDiffCollapsedLines: DEFAULT_TOOL_DISPLAY_CONFIG.writeDiffCollapsedLines,
210
+ diffWordWrap: DEFAULT_TOOL_DISPLAY_CONFIG.diffWordWrap,
211
+ expandedPreviewMaxLines: DEFAULT_TOOL_DISPLAY_CONFIG.expandedPreviewMaxLines,
212
+ toolInputNameLength: 100,
213
+ useSummaryTitlesAsThinkingTitle: true,
214
+ previewLines: 3,
215
+ animationIntervalMs: 90,
216
+ dimThinkingText: false,
217
+ showStartupHeader: true,
218
+ scrollStepLines: 3,
219
+ enableSessionReference: true,
220
+ enableSubagentAutocomplete: true,
221
+ enableContextCommand: true,
222
+ enableAgentSummary: true,
223
+ // Zentui is the sole owner of Pi's unkeyed working row in pi-one-ui.
224
+ enableWorkingMessage: false,
225
+ enableAliases: true,
226
+ };
227
+
228
+ function pickEnum<T extends string>(
229
+ value: unknown,
230
+ allowed: readonly T[],
231
+ fallback: T,
232
+ ): T {
233
+ return typeof value === "string" &&
234
+ (allowed as readonly string[]).includes(value)
235
+ ? (value as T)
236
+ : fallback;
237
+ }
238
+
239
+ export function pickPositiveInt(
240
+ value: unknown,
241
+ fallback: number,
242
+ min = 1,
243
+ max = 100_000,
244
+ ): number {
245
+ const n =
246
+ typeof value === "number"
247
+ ? value
248
+ : typeof value === "string"
249
+ ? Number(value)
250
+ : NaN;
251
+ if (!Number.isFinite(n)) return fallback;
252
+ return Math.min(max, Math.max(min, Math.floor(n)));
253
+ }
254
+
255
+ export function pickPositiveNumber(
256
+ value: unknown,
257
+ fallback: number,
258
+ min = 1,
259
+ ): number {
260
+ const n =
261
+ typeof value === "number"
262
+ ? value
263
+ : typeof value === "string"
264
+ ? Number(value)
265
+ : NaN;
266
+ return Number.isFinite(n) ? Math.max(min, n) : fallback;
267
+ }
268
+
269
+ export function normalizeConfig(input: unknown): Config {
270
+ const source = (input && typeof input === "object" ? input : {}) as Record<
271
+ string,
272
+ unknown
273
+ >;
274
+ const mode = source.mode;
275
+ // 旧 `enabled: boolean` 配置迁移;compact 已恢复为受支持模式,不再回退 on。
276
+ const migratedMode: CompactStyleMode =
277
+ mode === "on" || mode === "compact" || mode === "off"
278
+ ? mode
279
+ : typeof source.enabled === "boolean"
280
+ ? source.enabled
281
+ ? "on"
282
+ : "off"
283
+ : "on";
284
+ const excludeRenderers = Array.isArray(source.excludeRenderers)
285
+ ? [
286
+ ...new Set(
287
+ source.excludeRenderers.filter(
288
+ (name): name is string =>
289
+ typeof name === "string" && name.length > 0,
290
+ ),
291
+ ),
292
+ ]
293
+ : [];
294
+ return {
295
+ mode: migratedMode,
296
+ excludeRenderers,
297
+ diffViewMode: pickEnum(
298
+ source.diffViewMode,
299
+ DIFF_VIEW_MODES,
300
+ DEFAULT_CONFIG.diffViewMode,
301
+ ),
302
+ diffIndicatorMode: pickEnum(
303
+ source.diffIndicatorMode,
304
+ DIFF_INDICATOR_MODES,
305
+ DEFAULT_CONFIG.diffIndicatorMode,
306
+ ),
307
+ diffSplitMinWidth: pickPositiveInt(
308
+ source.diffSplitMinWidth,
309
+ DEFAULT_CONFIG.diffSplitMinWidth,
310
+ 40,
311
+ 300,
312
+ ),
313
+ editDiffCollapsedLines: pickPositiveInt(
314
+ source.editDiffCollapsedLines ?? source.diffCollapsedLines,
315
+ DEFAULT_CONFIG.editDiffCollapsedLines,
316
+ 1,
317
+ 500,
318
+ ),
319
+ writeDiffCollapsedLines: pickPositiveInt(
320
+ source.writeDiffCollapsedLines,
321
+ DEFAULT_CONFIG.writeDiffCollapsedLines,
322
+ 0,
323
+ 500,
324
+ ),
325
+ diffWordWrap: source.diffWordWrap !== false,
326
+ expandedPreviewMaxLines: pickPositiveInt(
327
+ source.expandedPreviewMaxLines,
328
+ DEFAULT_CONFIG.expandedPreviewMaxLines,
329
+ 10,
330
+ 50_000,
331
+ ),
332
+ toolInputNameLength: pickPositiveInt(
333
+ source.toolInputNameLength,
334
+ DEFAULT_CONFIG.toolInputNameLength,
335
+ 8,
336
+ 500,
337
+ ),
338
+ useSummaryTitlesAsThinkingTitle:
339
+ source.useSummaryTitlesAsThinkingTitle !== false,
340
+ previewLines: pickPositiveInt(
341
+ source.previewLines,
342
+ DEFAULT_CONFIG.previewLines,
343
+ 0,
344
+ Number.MAX_SAFE_INTEGER,
345
+ ),
346
+ animationIntervalMs: pickPositiveNumber(
347
+ source.animationIntervalMs,
348
+ DEFAULT_CONFIG.animationIntervalMs,
349
+ ),
350
+ dimThinkingText: source.dimThinkingText === true,
351
+ showStartupHeader: source.showStartupHeader !== false,
352
+ scrollStepLines: pickPositiveInt(
353
+ source.scrollStepLines,
354
+ DEFAULT_CONFIG.scrollStepLines,
355
+ 1,
356
+ 50,
357
+ ),
358
+ enableSessionReference: source.enableSessionReference !== false,
359
+ enableSubagentAutocomplete: source.enableSubagentAutocomplete !== false,
360
+ enableContextCommand: source.enableContextCommand !== false,
361
+ enableAgentSummary: source.enableAgentSummary !== false,
362
+ enableWorkingMessage: source.enableWorkingMessage === true,
363
+ enableAliases: source.enableAliases !== false,
364
+ };
365
+ }
366
+
367
+ export function getCompactThinkingConfig(
368
+ source: Config = config,
369
+ ): CompactThinkingConfig {
370
+ return {
371
+ useSummaryTitlesAsThinkingTitle: source.useSummaryTitlesAsThinkingTitle,
372
+ previewLines: source.previewLines,
373
+ animationIntervalMs: source.animationIntervalMs,
374
+ };
375
+ }
376
+
377
+ export function getToolDisplayConfig(
378
+ source: Config = config,
379
+ ): ToolDisplayConfig {
380
+ return {
381
+ diffViewMode: source.diffViewMode,
382
+ diffIndicatorMode: source.diffIndicatorMode,
383
+ diffSplitMinWidth: source.diffSplitMinWidth,
384
+ editDiffCollapsedLines: source.editDiffCollapsedLines,
385
+ writeDiffCollapsedLines: source.writeDiffCollapsedLines,
386
+ diffWordWrap: source.diffWordWrap,
387
+ expandedPreviewMaxLines: source.expandedPreviewMaxLines,
388
+ };
389
+ }
390
+
391
+ export function formatExcludeRenderers(names: readonly string[]): string {
392
+ return names.length === 0 ? "none" : names.join(", ");
393
+ }
394
+
395
+ export function formatConfigStatus(source: Config = config): string {
396
+ return [
397
+ `mode=${source.mode}`,
398
+ `exclude=[${source.excludeRenderers.join(", ") || "none"}]`,
399
+ `diffView=${source.diffViewMode}`,
400
+ `diffIndicator=${source.diffIndicatorMode}`,
401
+ `diffSplitMin=${source.diffSplitMinWidth}`,
402
+ `editCollapsed=${source.editDiffCollapsedLines}`,
403
+ `writeCollapsed=${source.writeDiffCollapsedLines}`,
404
+ `diffWordWrap=${source.diffWordWrap ? "on" : "off"}`,
405
+ `expandedMax=${source.expandedPreviewMaxLines}`,
406
+ `toolInputName=${source.toolInputNameLength}`,
407
+ `thinkingTitle=${source.useSummaryTitlesAsThinkingTitle ? "summary" : "default"}`,
408
+ `thinkingPreview=${source.previewLines}`,
409
+ `thinkingAnimation=${source.animationIntervalMs}ms`,
410
+ `thinkingDim=${source.dimThinkingText ? "on" : "off"}`,
411
+ `startupHeader=${source.showStartupHeader ? "on" : "off"}`,
412
+ `scrollStep=${source.scrollStepLines}`,
413
+ `sessionRef=${source.enableSessionReference ? "on" : "off"}`,
414
+ `subagentAuto=${source.enableSubagentAutocomplete ? "on" : "off"}`,
415
+ `context=${source.enableContextCommand ? "on" : "off"}`,
416
+ `agentSummary=${source.enableAgentSummary ? "on" : "off"}`,
417
+ `workingMsg=${source.enableWorkingMessage ? "on" : "off"}`,
418
+ `aliases=${source.enableAliases ? "on" : "off"}`,
419
+ ].join(" · ");
420
+ }
421
+
422
+ /** 进程内唯一的活动配置对象。读取直接用 `config.xxx`;写入必须走 `updateConfig`。 */
423
+ export const config: Config = loadConfig();
424
+
425
+ function loadConfig(): Config {
426
+ try {
427
+ const unified = readUnifiedConfig();
428
+ return normalizeConfig(rendererConfigFrom(unified));
429
+ } catch {
430
+ // Ignore bad config and fall back to defaults.
431
+ }
432
+ return { ...DEFAULT_CONFIG };
433
+ }
434
+
435
+ export function saveConfig() {
436
+ const unified = readUnifiedConfig();
437
+ writeFileSync(
438
+ CONFIG_PATH,
439
+ JSON.stringify({ ...unified, version: 1, renderer: config }, null, 2),
440
+ );
441
+ }
442
+
443
+ /** 运行时配置写入的唯一入口:合并 + 规范化 + 持久化。 */
444
+ export function updateConfig(partial: Partial<Config>): void {
445
+ Object.assign(config, normalizeConfig({ ...config, ...partial }));
446
+ saveConfig();
447
+ }
448
+
449
+ /** 整体替换配置(default export 的 configOverride 注入路径;就地覆盖,不持久化)。 */
450
+ export function setConfig(next: Config): void {
451
+ Object.assign(config, next);
452
+ }