pi-cc-extensions 0.8.56 → 0.8.59

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 (47) hide show
  1. package/README.en.md +1 -4
  2. package/README.md +1 -4
  3. package/extensions/config/config.ts +40 -9
  4. package/extensions/config/panel.ts +32 -36
  5. package/extensions/feature/compact-thinking.ts +12 -12
  6. package/extensions/feature/context.ts +10 -56
  7. package/extensions/feature/reference/index.ts +17 -7
  8. package/extensions/feature/shell/startup-header.ts +1 -1
  9. package/extensions/feature/shell/working-message.ts +28 -21
  10. package/extensions/renderer/compact-mode.ts +23 -50
  11. package/extensions/renderer/default-mode.ts +36 -152
  12. package/extensions/renderer/index.ts +5 -9
  13. package/extensions/renderer/markdown-enhance.ts +15 -6
  14. package/extensions/renderer/mouse/hover.ts +2 -4
  15. package/extensions/renderer/mouse/interaction.ts +6 -6
  16. package/extensions/renderer/mouse/packets.ts +16 -66
  17. package/extensions/renderer/mouse/scroll.ts +13 -7
  18. package/extensions/renderer/tool/diff/diff-component.ts +107 -0
  19. package/extensions/renderer/tool/diff/diff-edit-render.ts +198 -0
  20. package/extensions/renderer/tool/diff/diff-header.ts +136 -0
  21. package/extensions/renderer/tool/diff/diff-highlight.ts +115 -0
  22. package/extensions/renderer/tool/diff/diff-inline.ts +312 -0
  23. package/extensions/renderer/tool/diff/diff-layout.ts +730 -0
  24. package/extensions/renderer/tool/diff/diff-limits.ts +145 -0
  25. package/extensions/renderer/tool/diff/diff-palette.ts +410 -0
  26. package/extensions/renderer/tool/diff/diff-parse.ts +512 -0
  27. package/extensions/renderer/tool/diff/diff-presentation.ts +2 -2
  28. package/extensions/renderer/tool/diff/diff-renderer.ts +21 -3046
  29. package/extensions/renderer/tool/diff/diff-text.ts +45 -0
  30. package/extensions/renderer/tool/diff/diff-write-render.ts +498 -0
  31. package/extensions/renderer/tool/diff/index.ts +2 -2
  32. package/extensions/renderer/tool/diff/line-width-safety.ts +2 -2
  33. package/extensions/renderer/tool/grouping.ts +19 -143
  34. package/extensions/renderer/{message-display.ts → tool/message-display.ts} +9 -30
  35. package/extensions/renderer/tool/names.ts +168 -0
  36. package/extensions/renderer/tool/result.ts +3 -6
  37. package/extensions/renderer/transcript-refresh.ts +8 -26
  38. package/extensions/utils/ansi-text.ts +47 -0
  39. package/extensions/utils/component-tree.ts +34 -0
  40. package/extensions/utils/format.ts +7 -0
  41. package/extensions/utils/patch-keys.ts +121 -0
  42. package/extensions/utils/sgr-mouse.ts +59 -0
  43. package/package.json +3 -2
  44. package/extensions/renderer/tool/diff/render-utils.ts +0 -5
  45. package/extensions/renderer/tool/diff/types.ts +0 -26
  46. package/extensions/renderer/tool/diff/write-display-utils.ts +0 -12
  47. /package/extensions/renderer/{show-more-hint.ts → tool/show-more-hint.ts} +0 -0
@@ -24,8 +24,8 @@ import { config, getToolDisplayConfig } from "../config/config.ts";
24
24
  import { toolLoadingIcon } from "../utils/tool-loading-icon.ts";
25
25
  import { sanitizeToolResultText } from "../utils/tool-result-sanitize.ts";
26
26
  import { refreshTranscriptComponent } from "./transcript-refresh.ts";
27
- import { getMessageDisplayTheme } from "./message-display.ts";
28
- import { showMoreHintText } from "./show-more-hint.ts";
27
+ import { getMessageDisplayTheme } from "./tool/message-display.ts";
28
+ import { showMoreHintText } from "./tool/show-more-hint.ts";
29
29
  import {
30
30
  countEditDiffStats,
31
31
  countWriteDiffStats,
@@ -35,7 +35,21 @@ import { renderRichToolResult } from "./tool/diff/index.ts";
35
35
  import type { WriteExecutionMetadataStore } from "./tool/diff/write-execution.ts";
36
36
  import { insetComponent, renderExpandedToolResult, scheduleAnimation } from "./tool/result.ts";
37
37
  import { oneLine } from "../utils/format.ts";
38
- import { paddedBackgroundRow, stripBackgroundAnsi } from "./tool/grouping.ts";
38
+ import { paddedBackgroundRow } from "./tool/grouping.ts";
39
+ import { hasVisibleText, stripBackgroundAnsi } from "../utils/ansi-text.ts";
40
+ import { walkComponentTree } from "../utils/component-tree.ts";
41
+ import {
42
+ ASSISTANT_REENTRY_DESCRIPTION,
43
+ ASSISTANT_REENTRY_KEY,
44
+ ASSISTANT_SET_EXPANDED_DESCRIPTION,
45
+ ASSISTANT_SET_EXPANDED_KEY,
46
+ ASSISTANT_TOGGLE_ROUND_DESCRIPTION,
47
+ ASSISTANT_TOGGLE_ROUND_KEY,
48
+ COMPACT_MODE_PATCH_KEY,
49
+ COMPACT_THINKING_PATCH_KEY,
50
+ patchRegistry,
51
+ PROTOTYPE_ORIGINAL_KEY,
52
+ } from "../utils/patch-keys.ts";
39
53
 
40
54
  /** compact 渲染层对 compact-thinking 的只读查询面(不建第二套计时器)。 */
41
55
  export type CompactThinkingQuery = {
@@ -45,16 +59,6 @@ export type CompactThinkingQuery = {
45
59
  setCompactSummaryActive?(active: boolean): void;
46
60
  };
47
61
 
48
- const COMPACT_MODE_PATCH_KEY = Symbol.for("pi.ccstyle.compact-mode-patch");
49
- const COMPACT_THINKING_PATCH_KEY = Symbol.for("pi.ccstyle.compact-thinking-update");
50
- const PROTOTYPE_ORIGINAL_KEY = Symbol.for("pi.ccstyle.prototype-original");
51
- const ASSISTANT_SET_EXPANDED_DESCRIPTION = "pi.ccstyle.compact-assistant-set-expanded";
52
- const ASSISTANT_TOGGLE_ROUND_DESCRIPTION = "pi.ccstyle.compact-assistant-toggle-round";
53
- const ASSISTANT_REENTRY_DESCRIPTION = "pi.ccstyle.compact-assistant-reentry";
54
- const ASSISTANT_SET_EXPANDED_KEY = Symbol.for(ASSISTANT_SET_EXPANDED_DESCRIPTION);
55
- const ASSISTANT_TOGGLE_ROUND_KEY = Symbol.for(ASSISTANT_TOGGLE_ROUND_DESCRIPTION);
56
- const ASSISTANT_REENTRY_KEY = Symbol.for(ASSISTANT_REENTRY_DESCRIPTION);
57
-
58
62
  const EDIT_WRITE_TOOLS = new Set(["edit", "write"]);
59
63
 
60
64
  type CompactThinkingTheme = Pick<Theme, "fg" | "italic" | "bold">;
@@ -284,16 +288,6 @@ function darkenBgAnsi(theme: any, slot: string): string {
284
288
  return `\x1b[48;2;${r};${g};${b}m`;
285
289
  }
286
290
 
287
- /** 去掉行内所有 CSI/OSC 序列后是否有可见文本(判断工具卡首尾内容行)。 */
288
- function hasVisibleText(line: string): boolean {
289
- return (
290
- line
291
- .replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
292
- .replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "")
293
- .trim().length > 0
294
- );
295
- }
296
-
297
291
  /** 工具卡深色行:左右内缩(左 2 右 3 格,含工具卡自身 padding),背景只到内容区,
298
292
  * 修复行尾 reset 截断。 */
299
293
  function toolCardBgRow(
@@ -314,7 +308,7 @@ function toolCardBgRow(
314
308
  const body = ` ${clipped}${" ".repeat(pad)} `;
315
309
  const stable = body.replace(/\x1b\[(?:0)?m/g, (reset) => reset + bgAnsi);
316
310
  const outerBg = typeof theme?.getBgAnsi === "function" ? String(theme.getBgAnsi(slot)) : "";
317
- const inset = (n) => (outerBg ? `${outerBg}${" ".repeat(n)}\x1b[49m` : " ".repeat(n));
311
+ const inset = (n: number) => (outerBg ? `${outerBg}${" ".repeat(n)}\x1b[49m` : " ".repeat(n));
318
312
  return `${inset(leftInset)}${bgAnsi}${stable}\x1b[49m${inset(rightInset)}`;
319
313
  }
320
314
  /** edit/write 展开卡:保持原样式(userMessageBg Box),不应用工具卡深色/间距改动。 */
@@ -694,16 +688,9 @@ function ensureAssistantSetExpanded(component: any): void {
694
688
 
695
689
  function collectMountedComponents(root: any): void {
696
690
  if (!root || typeof root !== "object") return;
697
- const seen = new Set<any>();
698
691
  const assistants = new Set<any>();
699
692
  const tools = new Set<any>();
700
- const visit = (value: any): void => {
701
- if (!value || typeof value !== "object" || seen.has(value)) return;
702
- seen.add(value);
703
- if (Array.isArray(value)) {
704
- for (const child of value) visit(child);
705
- return;
706
- }
693
+ walkComponentTree(root, (value: any) => {
707
694
  if (isAssistantComponent(value)) {
708
695
  assistants.add(value);
709
696
  // 仅在 compact 模式给实例装 setExpanded,避免 on/off 下 Ctrl+O/mouse 回归。
@@ -711,20 +698,7 @@ function collectMountedComponents(root: any): void {
711
698
  } else if (isToolComponent(value)) {
712
699
  tools.add(value);
713
700
  }
714
- const children = value.children;
715
- if (Array.isArray(children)) {
716
- for (const child of children) visit(child);
717
- }
718
- try {
719
- const mounted = value.getMountedRoots?.();
720
- if (Array.isArray(mounted)) {
721
- for (const root2 of mounted) visit(root2);
722
- }
723
- } catch {
724
- // renderer 切换中的惰性 Proxy 可能暂时没有 mounted roots
725
- }
726
- };
727
- visit(root);
701
+ });
728
702
  // 扫到组件才替换跟踪表。面板/custom UI 打开时 root 往往扫不到 transcript,
729
703
  // 若此时清空会丢掉 live updateContent/updateDisplay 已登记的实例,
730
704
  // /ccstyle 切换就只能靠 /reload 重建。
@@ -739,8 +713,7 @@ function collectMountedComponents(root: any): void {
739
713
  }
740
714
 
741
715
  export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHooks {
742
- const host = globalThis as any;
743
- const previous = host[COMPACT_MODE_PATCH_KEY] as CompactModePatch | undefined;
716
+ const previous = patchRegistry.get<CompactModePatch>(COMPACT_MODE_PATCH_KEY);
744
717
  if (previous) previous.dispose();
745
718
 
746
719
  const assistantPrototype = AssistantMessageComponent.prototype as any;
@@ -1149,7 +1122,7 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
1149
1122
  if (toolPrototype.updateDisplay === patch.toolInstalledUpdateDisplay) {
1150
1123
  toolPrototype.updateDisplay = patch.toolOriginalUpdateDisplay;
1151
1124
  }
1152
- if (host[COMPACT_MODE_PATCH_KEY] === patch) delete host[COMPACT_MODE_PATCH_KEY];
1125
+ patchRegistry.dispose(COMPACT_MODE_PATCH_KEY, patch);
1153
1126
  for (const component of trackedAssistantComponents) detachAssistantExpansion(component);
1154
1127
  trackedAssistantComponents.clear();
1155
1128
  trackedToolComponents.clear();
@@ -1160,7 +1133,7 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
1160
1133
  assistantPrototype.updateContent = patch.assistantInstalled;
1161
1134
  toolPrototype.render = patch.toolInstalledRender;
1162
1135
  toolPrototype.updateDisplay = patch.toolInstalledUpdateDisplay;
1163
- host[COMPACT_MODE_PATCH_KEY] = patch;
1136
+ patchRegistry.install(COMPACT_MODE_PATCH_KEY, patch);
1164
1137
 
1165
1138
  const syncGlobalExpanded = (ctx: any): void => {
1166
1139
  let globalExpanded = false;
@@ -7,6 +7,12 @@
7
7
  import { ToolExecutionComponent } from "@earendil-works/pi-coding-agent";
8
8
  import { Text, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
9
9
  import { config, getToolDisplayConfig, type CompactStyleMode } from "../config/config.ts";
10
+ import {
11
+ COMPONENT_TOOL_RENDER_MODE,
12
+ GLOBAL_TOOL_RENDER_PATCH,
13
+ patchRegistry,
14
+ TOOL_EXPANDED_BACKGROUND_PATCH,
15
+ } from "../utils/patch-keys.ts";
10
16
  import { isToolCallHovered } from "./mouse/hover.ts";
11
17
  import {
12
18
  countLines,
@@ -27,25 +33,15 @@ import {
27
33
  toolViewportWidth,
28
34
  } from "./tool/result.ts";
29
35
  import { oneLine } from "../utils/format.ts";
30
- import { showMoreHintText } from "./show-more-hint.ts";
36
+ import { showMoreHintText } from "./tool/show-more-hint.ts";
31
37
  import { renderRichToolResult, type WriteExecutionMetadataStore } from "./tool/diff/index.ts";
32
- import { getMessageDisplayTheme } from "./message-display.ts";
38
+ import { getMessageDisplayTheme } from "./tool/message-display.ts";
39
+ import { humanizeToolLabel, toolCallSummary } from "./tool/names.ts";
33
40
 
34
41
  // 成功勾:亮绿 truecolor(与 message-display 一致)
35
42
  const BRIGHT_GREEN = "\x1b[38;2;80;220;100m";
36
43
  const ANSI_FG_RESET = "\x1b[39m";
37
44
 
38
- const GLOBAL_TOOL_RENDER_PATCH = Symbol.for("pi.ccstyle.global-tool-render-patch");
39
- const COMPONENT_TOOL_RENDER_MODE = Symbol.for("pi.ccstyle.component-tool-render-mode");
40
- const COMPONENT_TOOL_SELF_SHELL_MODE = Symbol.for("pi.ccstyle.component-tool-self-shell-mode");
41
- const TOOL_EXPANDED_BACKGROUND_PATCH = Symbol.for("pi.ccstyle.tool-expanded-background-patch");
42
-
43
- const AGENT_FAMILY_TOOL_NAMES = new Set([
44
- "Agent",
45
- "Agents",
46
- "get_subagent_result",
47
- "steer_subagent",
48
- ]);
49
45
  // pi-subagents 等扩展为 Agent 提供专用渲染器,ccstyle 必须保留。
50
46
  const DEDICATED_RENDERER_TOOLS = new Set(["Agent"]);
51
47
 
@@ -141,94 +137,6 @@ function renderDefault(tool: any, slot: "renderCall" | "renderResult", args: any
141
137
  return new Text(fallback, 0, 0);
142
138
  }
143
139
 
144
- function humanizeToolLabel(label: string): string {
145
- return label
146
- .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
147
- .replace(/[_-]+/g, " ")
148
- .replace(/\b\w/g, (char) => char.toUpperCase());
149
- }
150
-
151
- function singleToolCallSummary(
152
- toolName: string,
153
- label: string,
154
- args: any,
155
- ): { main: string; detail: string } {
156
- const title = label === toolName ? humanizeToolLabel(label) : label;
157
- if (!args || typeof args !== "object") return { main: title, detail: "" };
158
- const name = toolName.toLowerCase();
159
- const value = (fallback: string, ...keys: string[]) => {
160
- const found = keys.map((key) => args[key]).find((item) => typeof item === "string" && item);
161
- // 与多 tool 摘要对齐:从头截断,上限 96 字符(tool-grouping oneLine 默认值)。
162
- return `${title} ${oneLine(found || fallback, 96)}`;
163
- };
164
- if (AGENT_FAMILY_TOOL_NAMES.has(toolName) && args.agent_id) {
165
- return { main: `${title} ${oneLine(args.agent_id, 96)}`, detail: "" };
166
- }
167
- // Agents 走 ccstyle wrapper;Agent 保留专用渲染器。
168
- if (name === "agents") {
169
- return {
170
- main: value("launch agents", "description", "prompt"),
171
- detail: "",
172
- };
173
- }
174
- if (name === "skill") return { main: value("run skill", "name"), detail: "" };
175
- if (name === "enterplanmode" || name === "enter_plan_mode") {
176
- return { main: `${title} enable read-only planning`, detail: "" };
177
- }
178
- if (name === "exitplanmode" || name === "exit_plan_mode") {
179
- return { main: `${title} present plan`, detail: "" };
180
- }
181
- if (name === "taskcreate") return { main: value("create task", "subject"), detail: "" };
182
- if (name === "tasklist") return { main: `${title} task list`, detail: "" };
183
- if (name === "taskget" || name === "taskupdate") {
184
- return { main: value("task", "taskId", "task_id"), detail: "" };
185
- }
186
- if (name === "taskoutput" || name === "taskstop") {
187
- return { main: value("background task", "task_id", "taskId"), detail: "" };
188
- }
189
- if (name === "taskexecute") {
190
- const ids = Array.isArray(args.task_ids)
191
- ? args.task_ids
192
- : Array.isArray(args.taskIds)
193
- ? args.taskIds
194
- : [];
195
- const summary = ids.length
196
- ? `${ids[0]}${ids.length > 1 ? ` (+${ids.length - 1} tasks)` : ""}`
197
- : "start tasks";
198
- return { main: `${title} ${summary}`, detail: "" };
199
- }
200
- if (toolName === "read") {
201
- const details = [
202
- args.offset !== undefined ? `offset=${args.offset}` : "",
203
- args.limit !== undefined ? `limit=${args.limit}` : "",
204
- ].filter(Boolean);
205
- return {
206
- main: `${title}${args.path ? ` ${oneLine(args.path, 96)}` : ""}`,
207
- detail: details.length ? ` (${details.join(", ")})` : "",
208
- };
209
- }
210
- const preferred =
211
- args.path ??
212
- args.file_path ??
213
- args.command ??
214
- args.query ??
215
- args.question ??
216
- args.pattern ??
217
- args.url ??
218
- args.name ??
219
- args.tool_use_id ??
220
- args.toolCallId ??
221
- args.id ??
222
- args.message;
223
- return {
224
- main:
225
- preferred !== undefined && preferred !== null && typeof preferred !== "object"
226
- ? `${title} ${oneLine(preferred, 96)}`
227
- : title,
228
- detail: "",
229
- };
230
- }
231
-
232
140
  type ParsedTask = { id: string; status: string; subject: string };
233
141
 
234
142
  function parseTaskList(text: string): ParsedTask[] {
@@ -330,7 +238,10 @@ function createCcstyleTool(
330
238
  visualState === "success"
331
239
  ? `${BRIGHT_GREEN}${rawIcon}${ANSI_FG_RESET}`
332
240
  : theme.fg(toolIconColor(context), rawIcon);
333
- const summary = singleToolCallSummary(toolName, label, args);
241
+ const summary = toolCallSummary(toolName, args, {
242
+ title: label === toolName ? humanizeToolLabel(label) : label,
243
+ variant: "default",
244
+ });
334
245
  let cachedWidth: number | undefined;
335
246
  let cachedLine: string | undefined;
336
247
  const expanded = Boolean(context?.expanded);
@@ -485,11 +396,6 @@ function shouldGloballyStyleTool(component: any, patch: GlobalToolRenderPatch):
485
396
  return useCcstyle;
486
397
  }
487
398
 
488
- function shouldUseSelfShell(component: any, _patch: GlobalToolRenderPatch): boolean {
489
- component[COMPONENT_TOOL_SELF_SHELL_MODE] = false;
490
- return false;
491
- }
492
-
493
399
  function getGloballyStyledTool(component: any, patch: GlobalToolRenderPatch): any {
494
400
  const definition = component.toolDefinition ?? component.builtInToolDefinition;
495
401
  if (definition && typeof definition === "object") {
@@ -527,21 +433,6 @@ function isOwnershipAwarePatch(value: any): value is GlobalToolRenderPatch {
527
433
  );
528
434
  }
529
435
 
530
- function isLegacyInstalledWrapper(method: unknown, downstreamField: string): boolean {
531
- if (typeof method !== "function") return false;
532
- try {
533
- const source = Function.prototype.toString.call(method);
534
- return (
535
- source.includes(downstreamField) &&
536
- (source.includes("shouldGloballyStyleTool") ||
537
- source.includes("shouldUseSelfShell") ||
538
- source.includes("getGloballyStyledTool"))
539
- );
540
- } catch {
541
- return false;
542
- }
543
- }
544
-
545
436
  function downstreamForGlobalToolInstall(prototype: any, previous: any): ToolRenderMethods {
546
437
  const current = prototypeToolRenderMethods(prototype);
547
438
  if (!previous || previous.prototype !== prototype) return current;
@@ -566,27 +457,26 @@ function downstreamForGlobalToolInstall(prototype: any, previous: any): ToolRend
566
457
  };
567
458
  }
568
459
 
569
- // pre-v2 Symbol 无 wrapper 引用;能识别则回退,否则保留当前方法为 external。
570
- const legacyDownstream = (method: Function, field: string): Function => {
571
- const saved = previous[field];
572
- return typeof saved === "function" && isLegacyInstalledWrapper(method, field) ? saved : method;
573
- };
460
+ // pre-v2 Symbol:originalX 显式记录真实下游(native 原型方法),无需源码嗅探。
461
+ // 缺失字段回退到当前原型方法(视作 external)。
462
+ const originalOrCurrent = (field: string, method: Function): Function =>
463
+ typeof previous[field] === "function" ? previous[field] : method;
574
464
  return {
575
- hasRendererDefinition: legacyDownstream(
576
- current.hasRendererDefinition,
465
+ hasRendererDefinition: originalOrCurrent(
577
466
  "originalHasRendererDefinition",
467
+ current.hasRendererDefinition,
578
468
  ) as ToolRenderMethods["hasRendererDefinition"],
579
- getRenderShell: legacyDownstream(
580
- current.getRenderShell,
469
+ getRenderShell: originalOrCurrent(
581
470
  "originalGetRenderShell",
471
+ current.getRenderShell,
582
472
  ) as ToolRenderMethods["getRenderShell"],
583
- getCallRenderer: legacyDownstream(
584
- current.getCallRenderer,
473
+ getCallRenderer: originalOrCurrent(
585
474
  "originalGetCallRenderer",
475
+ current.getCallRenderer,
586
476
  ) as ToolRenderMethods["getCallRenderer"],
587
- getResultRenderer: legacyDownstream(
588
- current.getResultRenderer,
477
+ getResultRenderer: originalOrCurrent(
589
478
  "originalGetResultRenderer",
479
+ current.getResultRenderer,
590
480
  ) as ToolRenderMethods["getResultRenderer"],
591
481
  };
592
482
  }
@@ -606,8 +496,7 @@ function installGlobalToolRendering(
606
496
  writeExecutionMetadata: WriteExecutionMetadataStore,
607
497
  ): GlobalToolRenderPatch {
608
498
  const prototype = (ToolExecutionComponent as any).prototype;
609
- const host = globalThis as any;
610
- const previous = host[GLOBAL_TOOL_RENDER_PATCH];
499
+ const previous = patchRegistry.get<GlobalToolRenderPatch>(GLOBAL_TOOL_RENDER_PATCH);
611
500
  const downstream = downstreamForGlobalToolInstall(prototype, previous);
612
501
  // 外部仍持有的旧 wrapper 先变为无回调 pass-through,再挂新安装。
613
502
  disconnectGlobalToolRenderPatch(previous);
@@ -637,14 +526,12 @@ function installGlobalToolRendering(
637
526
  },
638
527
  getRenderShell: function (this: any, ...args: any[]) {
639
528
  if (!patch.active) return patch.downstream.getRenderShell.apply(this, args);
640
- const useSelfShell = shouldUseSelfShell(this, patch);
641
529
  const useCcstyle = shouldGloballyStyleTool(this, patch);
642
- const shell =
643
- useSelfShell || (useCcstyle && !this.expanded)
644
- ? "self"
645
- : useCcstyle
646
- ? "default"
647
- : patch.downstream.getRenderShell.apply(this, args);
530
+ const shell = useCcstyle
531
+ ? this.expanded
532
+ ? "default"
533
+ : "self"
534
+ : patch.downstream.getRenderShell.apply(this, args);
648
535
  syncToolShell(this, shell);
649
536
  return shell;
650
537
  },
@@ -666,7 +553,7 @@ function installGlobalToolRendering(
666
553
  prototype.getRenderShell = patch.installed.getRenderShell;
667
554
  prototype.getCallRenderer = patch.installed.getCallRenderer;
668
555
  prototype.getResultRenderer = patch.installed.getResultRenderer;
669
- host[GLOBAL_TOOL_RENDER_PATCH] = patch;
556
+ patchRegistry.install(GLOBAL_TOOL_RENDER_PATCH, patch);
670
557
  return patch;
671
558
  }
672
559
 
@@ -691,8 +578,7 @@ function deactivateGlobalToolRendering(patch: GlobalToolRenderPatch): void {
691
578
  /** 展开面板背景统一为 user message 背景色;折叠行保持原生状态色。
692
579
  * 必须在 compact-mode 之后安装,shutdown 时先于 compact-mode 释放。 */
693
580
  export function installToolExpandedBackground(): () => void {
694
- const host = globalThis as any;
695
- const previous = host[TOOL_EXPANDED_BACKGROUND_PATCH] as ToolExpandedBackgroundPatch | undefined;
581
+ const previous = patchRegistry.get<ToolExpandedBackgroundPatch>(TOOL_EXPANDED_BACKGROUND_PATCH);
696
582
  if (previous) previous.dispose();
697
583
  const prototype = ToolExecutionComponent.prototype as unknown as { updateDisplay: () => void };
698
584
  const original = prototype.updateDisplay;
@@ -718,13 +604,11 @@ export function installToolExpandedBackground(): () => void {
718
604
  if (prototype.updateDisplay === patch.installed) {
719
605
  prototype.updateDisplay = original;
720
606
  }
721
- if (host[TOOL_EXPANDED_BACKGROUND_PATCH] === patch) {
722
- delete host[TOOL_EXPANDED_BACKGROUND_PATCH];
723
- }
607
+ patchRegistry.dispose(TOOL_EXPANDED_BACKGROUND_PATCH, patch);
724
608
  },
725
609
  };
726
610
  prototype.updateDisplay = patch.installed;
727
- host[TOOL_EXPANDED_BACKGROUND_PATCH] = patch;
611
+ patchRegistry.install(TOOL_EXPANDED_BACKGROUND_PATCH, patch);
728
612
  return patch.dispose;
729
613
  }
730
614
 
@@ -736,7 +620,7 @@ export function installDefaultMode(
736
620
  return {
737
621
  isOwner() {
738
622
  return (
739
- (globalThis as any)[GLOBAL_TOOL_RENDER_PATCH] === globalToolRendering &&
623
+ patchRegistry.owns(GLOBAL_TOOL_RENDER_PATCH, globalToolRendering) &&
740
624
  globalToolRendering.active
741
625
  );
742
626
  },
@@ -14,12 +14,13 @@ import {
14
14
  } from "./default-mode.ts";
15
15
  import { isLazyProxyTui } from "../utils/fullscreen-detect.ts";
16
16
  import { showCcstylePanel } from "../config/panel.ts";
17
+ import { GLOBAL_COMPACTION_RENDER_PATCH, patchRegistry } from "../utils/patch-keys.ts";
17
18
  import {
18
19
  config,
19
20
  formatConfigStatus,
20
21
  normalizeConfig,
21
- saveConfig,
22
22
  setConfig,
23
+ updateConfig,
23
24
  type CompactStyleMode,
24
25
  type Config,
25
26
  } from "../config/config.ts";
@@ -38,7 +39,7 @@ import {
38
39
  installMessageDisplayRendering,
39
40
  refreshMessageDisplays,
40
41
  setMessageDisplayTheme,
41
- } from "./message-display.ts";
42
+ } from "./tool/message-display.ts";
42
43
 
43
44
  /**
44
45
  * Claude Code Style for pi — 装配入口。
@@ -48,8 +49,6 @@ import {
48
49
  * 共用 → tool-grouping / message-display / mouse / write override
49
50
  */
50
51
 
51
- const GLOBAL_COMPACTION_RENDER_PATCH = Symbol.for("pi.ccstyle.compaction-render-patch");
52
-
53
52
  let compactModeHooks: CompactModeHooks | undefined;
54
53
 
55
54
  function refreshCurrentTranscript(ctx?: any, toolGrouping?: ToolGroupingHooks): void {
@@ -68,8 +67,7 @@ function syncCompactMode(ctx: any): void {
68
67
  }
69
68
 
70
69
  function applyStyleMode(mode: CompactStyleMode, ctx: any, toolGrouping?: ToolGroupingHooks): void {
71
- config.mode = mode;
72
- saveConfig();
70
+ updateConfig({ mode });
73
71
  if (mode === "off") {
74
72
  // Native rendering:清 hover/click,关闭鼠标上报以恢复终端默认滚轮。
75
73
  resetToolHoverState();
@@ -101,9 +99,7 @@ type LegacyCompactionRenderPatch = {
101
99
 
102
100
  /** Disable the pre-native compaction monkey patch left alive by /reload. */
103
101
  function deactivateLegacyCompactionRendering() {
104
- const patch = (globalThis as any)[GLOBAL_COMPACTION_RENDER_PATCH] as
105
- | LegacyCompactionRenderPatch
106
- | undefined;
102
+ const patch = patchRegistry.get<LegacyCompactionRenderPatch>(GLOBAL_COMPACTION_RENDER_PATCH);
107
103
  if (patch) patch.enabled = () => false;
108
104
  }
109
105
 
@@ -71,7 +71,7 @@ function renderAdmonition(lines: string[], i: number): { output: string[]; next:
71
71
  while (body.length > 0 && body[body.length - 1].trim() === "") body.pop();
72
72
  // 内容合并为一行,作为引用块正文
73
73
  const content = body.join(" ").trim();
74
- // 保留引用块形态,标签改为加粗图标+类型(pi 渲染为左侧竖线,比表格轻量)
74
+ // 保留引用块形态(pi 渲染为左侧竖线),标签加粗
75
75
  return {
76
76
  output: [`> **${style.icon} ${style.label}**${content ? ` ${content}` : ""}`, ""],
77
77
  next: j,
@@ -82,14 +82,23 @@ function renderAdmonition(lines: string[], i: number): { output: string[]; next:
82
82
  // 裸 URL 转可点击超链接
83
83
  // ============================================================================
84
84
 
85
- const URL_RE = /(?<!<)(?<!\]\()https?:\/\/[^\s<>'"|,。;:!?、」』】]+/g;
86
- const TRIM_URL_RE = /[.,;:!?】」』"'》]+$/;
85
+ const URL_RE = /(?<!<)(?<!\]\()https?:\/\/[^\s<>'"|,。;:!?、」』】()【】《》『』「」]+/g;
86
+ const TRIM_URL_RE = /[.,;:!?】」』"'》)}]+$/;
87
87
 
88
- /** 去掉 URL 尾部标点;括号按平衡保留(如 Wikipedia 链接含括号)。 */
88
+ /**
89
+ * 去掉 URL 尾部标点;括号按平衡保留(如 Wikipedia 链接含括号、
90
+ * IPv6 地址含 [::1]),尾部不成对的 ] 与 ) 会截掉。
91
+ */
89
92
  function trimUrl(url: string): string {
90
93
  let t = url.replace(TRIM_URL_RE, "");
91
- while (t.endsWith(")") && (t.match(/\(/g)?.length ?? 0) < (t.match(/\)/g)?.length ?? 0)) {
92
- t = t.slice(0, -1);
94
+ for (;;) {
95
+ if (t.endsWith(")") && (t.match(/\(/g)?.length ?? 0) < (t.match(/\)/g)?.length ?? 0)) {
96
+ t = t.slice(0, -1);
97
+ } else if (t.endsWith("]") && (t.match(/\[/g)?.length ?? 0) < (t.match(/\]/g)?.length ?? 0)) {
98
+ t = t.slice(0, -1);
99
+ } else {
100
+ break;
101
+ }
93
102
  }
94
103
  return t;
95
104
  }
@@ -1,4 +1,5 @@
1
1
  import { setHoveredCompactAssistant } from "../compact-mode.ts";
2
+ import { patchRegistry, TOOL_HOVER_STATE_KEY } from "../../utils/patch-keys.ts";
2
3
  import {
3
4
  isExpandedToolIoView,
4
5
  invalidateIoView,
@@ -9,8 +10,6 @@ import type { ToolGroupComponent } from "../tool/grouping.ts";
9
10
  import { componentAtLocalRow, type ComponentRowHit } from "./layout.ts";
10
11
  import { setScrollButtonHovered } from "./scroll.ts";
11
12
 
12
- const TOOL_HOVER_STATE_KEY = Symbol.for("pi.ccstyle.tool-hover-state");
13
-
14
13
  type SharedToolHoverState = { toolCallId: string | null };
15
14
 
16
15
  /**
@@ -18,8 +17,7 @@ type SharedToolHoverState = { toolCallId: string | null };
18
17
  * 测试依赖该跨实例语义。
19
18
  */
20
19
  export function sharedToolHoverState(): SharedToolHoverState {
21
- const host = globalThis as any;
22
- return (host[TOOL_HOVER_STATE_KEY] ??= { toolCallId: null });
20
+ return patchRegistry.ensure(TOOL_HOVER_STATE_KEY, () => ({ toolCallId: null }));
23
21
  }
24
22
 
25
23
  // 状态单源在 globalThis 槽;hoveredToolCallId 变量仅为兼容旧 deep import 保留
@@ -1,9 +1,10 @@
1
1
  import { hasActiveTextPreview, showTextPreview } from "../../feature/context.ts";
2
+ import { patchRegistry, TOOL_MOUSE_OWNER_KEY } from "../../utils/patch-keys.ts";
2
3
  import { ToolGroupComponent } from "../tool/grouping.ts";
3
4
  import { isCompactAssistantComponent, setHoveredCompactAssistant } from "../compact-mode.ts";
4
5
  import { config } from "../../config/config.ts";
5
6
  import { isLazyProxyTui } from "../../utils/fullscreen-detect.ts";
6
- import { setToolTuiFullscreen } from "../show-more-hint.ts";
7
+ import { setToolTuiFullscreen } from "../tool/show-more-hint.ts";
7
8
  import {
8
9
  type ExpandedToolIoView,
9
10
  getActiveIoViewFrame,
@@ -81,7 +82,6 @@ const TOOL_MOUSE_WIDGET_KEY = "ccstyle-tool-mouse";
81
82
  const TOOL_MOUSE_MOTION_ENABLE = "\x1b[?1003h\x1b[?1006h";
82
83
  const TOOL_MOUSE_MOTION_DISABLE = "\x1b[?1003l";
83
84
  const FULLSCREEN_MOTION_ENABLED = Symbol("ccstyle.fullscreen-motion-enabled");
84
- const TOOL_MOUSE_OWNER_KEY = Symbol.for("pi.ccstyle.tool-mouse-owner");
85
85
  const DEFAULT_TOOL_MOUSE_OWNER = {};
86
86
  export const TOOL_MOUSE_DISABLE = "\x1b[?1006l\x1b[?1003l\x1b[?1000l";
87
87
 
@@ -768,8 +768,8 @@ function handleToolMouseInput(data: string): { consume: true } | undefined {
768
768
  export function teardownToolMouseInteraction(
769
769
  owner: object = toolMouseInstallationOwner ?? DEFAULT_TOOL_MOUSE_OWNER,
770
770
  ): void {
771
- const host = globalThis as any;
772
- if (host[TOOL_MOUSE_OWNER_KEY] && host[TOOL_MOUSE_OWNER_KEY] !== owner) return;
771
+ const current = patchRegistry.get<object>(TOOL_MOUSE_OWNER_KEY);
772
+ if (current && current !== owner) return;
773
773
  if (sessionRenderTimer) {
774
774
  clearTimeout(sessionRenderTimer);
775
775
  sessionRenderTimer = null;
@@ -796,7 +796,7 @@ export function teardownToolMouseInteraction(
796
796
  resetScrollButtonState();
797
797
  setToolMouseTui(null);
798
798
  toolMouseUi = null;
799
- if (host[TOOL_MOUSE_OWNER_KEY] === owner) delete host[TOOL_MOUSE_OWNER_KEY];
799
+ patchRegistry.dispose(TOOL_MOUSE_OWNER_KEY, owner);
800
800
  if (toolMouseInstallationOwner === owner) toolMouseInstallationOwner = null;
801
801
  }
802
802
 
@@ -819,7 +819,7 @@ export function installToolMouseInteraction(
819
819
  return;
820
820
 
821
821
  toolMouseInstallationOwner = owner;
822
- (globalThis as any)[TOOL_MOUSE_OWNER_KEY] = owner;
822
+ patchRegistry.install(TOOL_MOUSE_OWNER_KEY, owner);
823
823
  setHoveredToolCallId(null);
824
824
  toolMouseUi = ctx.ui;
825
825
  // 0.84+ 的 tui 是惰性 Proxy:regular 保留原生 scrollback;fullscreen