pi-cc-extensions 1.0.8 → 1.0.10
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.en.md +5 -7
- package/README.md +5 -7
- package/extensions/config/config.ts +1 -0
- package/extensions/config/panel.ts +14 -8
- package/extensions/feature/shell/footer.ts +28 -6
- package/extensions/feature/shell/prompt-editor.ts +33 -12
- package/extensions/renderer/compact-mode.ts +16 -3
- package/extensions/renderer/default-mode.ts +1 -5
- package/extensions/renderer/index.ts +16 -2
- package/extensions/renderer/markdown-enhance.ts +4 -1
- package/extensions/renderer/mouse/interaction.ts +30 -18
- package/extensions/renderer/mouse/layout.ts +12 -3
- package/extensions/renderer/tool/ccstyle-tool.ts +109 -0
- package/extensions/renderer/tool/grouping.ts +196 -111
- package/extensions/renderer/tool/names.ts +12 -5
- package/extensions/renderer/tool/subagent-notification.ts +556 -0
- package/extensions/utils/format.ts +11 -0
- package/extensions/utils/patch-keys.ts +4 -0
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
# pi-cc-extensions
|
|
6
6
|
|
|
7
|
-
> Claude Code-style TUI output with some personal touches and handy utilities.
|
|
7
|
+
> Claude Code-style TUI output with some personal touches and handy utilities (no extra built-in tools).
|
|
8
8
|
|
|
9
9
|
## Preview
|
|
10
10
|
|
|
@@ -34,7 +34,7 @@ Run `/reload` after installation.
|
|
|
34
34
|
| Context inspection | Usage breakdown and previews for the system prompt, memory, skills, tools definition, and messages | `/context` |
|
|
35
35
|
| Session/Subagent references | Search and inject effective context from previous Sessions or existing SubAgents | `@` |
|
|
36
36
|
| Status bar | Shows model, context, cache, cost, and git; also works with `@narumitw/pi-usage` for live quota. Git/cache Nerd Font icons can be turned off | `/ccstyle` |
|
|
37
|
-
| Theme | Bundled CC Dark and CC Light themes | `/
|
|
37
|
+
| Theme | Bundled CC Dark and CC Light themes | `/settings` → Theme |
|
|
38
38
|
|
|
39
39
|
Renderer snapshots for `on` / `compact` modes: [default](./docs/tool-render-examples-default.md) · [compact](./docs/tool-render-examples-compact.md)
|
|
40
40
|
|
|
@@ -55,7 +55,7 @@ Renderer snapshots for `on` / `compact` modes: [default](./docs/tool-render-exam
|
|
|
55
55
|
"enableAgentSummary": true, // per-turn tool summary
|
|
56
56
|
"enableWorkingMessage": true, // Working... bottom token/elapsed
|
|
57
57
|
"enableAliases": true, // /clear, /exit aliases
|
|
58
|
-
"enableMarkdownEnhance": true,
|
|
58
|
+
"enableMarkdownEnhance": true, // markdown pass: Mermaid dialects and circled-number conversion; false hands it to other extensions
|
|
59
59
|
"enableEditorPrefix": true, // editor ❯ prefix
|
|
60
60
|
|
|
61
61
|
// ui
|
|
@@ -97,12 +97,10 @@ Renderer snapshots for `on` / `compact` modes: [default](./docs/tool-render-exam
|
|
|
97
97
|
```
|
|
98
98
|
|
|
99
99
|
> [!TIP]
|
|
100
|
-
> **Fullscreen**:
|
|
101
|
-
>
|
|
102
|
-
> Both `scrollButtonMode` values show the same `Back to bottom` / `N new message(s)` text: `dock` draws it in a dedicated row, `native` in Pi’s floating overlay.
|
|
100
|
+
> **Fullscreen**: expanded diffs always show every line. When expanded Input/Output exceeds the line cap, the footer shows `… +N more lines`; click for the full preview, click again to collapse.
|
|
103
101
|
|
|
104
102
|
> [!NOTE]
|
|
105
|
-
> **Mermaid rendering**: set `markdown.mermaid` to `final` via `~/.pi/agent/settings.json` or
|
|
103
|
+
> **Mermaid rendering**: set `markdown.mermaid` to `final` via `~/.pi/agent/settings.json` or `/settings`. Default `streaming` redraws every frame; `final` renders once.
|
|
106
104
|
|
|
107
105
|
## Local development
|
|
108
106
|
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
# pi-cc-extensions
|
|
6
6
|
|
|
7
|
-
> 类 Claude Code TUI
|
|
7
|
+
> 类 Claude Code TUI 输出风格,并融入了一些个人喜好,和一些实用小功能(无额外内置工具)。
|
|
8
8
|
|
|
9
9
|
## 界面预览
|
|
10
10
|
|
|
@@ -34,7 +34,7 @@ pi install git:github.com/minuque/pi-cc-extensions
|
|
|
34
34
|
| 上下文检查 | 查看上下文占用,并预览 System prompt、Memory、Skills、Tools definition 和消息内容 | `/context` |
|
|
35
35
|
| Session/Subagent 引用 | 搜索并注入历史 Session 或现有 SubAgent 的有效上下文 | `@` |
|
|
36
36
|
| 状态栏 | 显示:模型、上下文、缓存、费用、git并适配 @narumitw/pi-usage 实时显示额度;git/缓存图标可关 Nerd Font | `/ccstyle` |
|
|
37
|
-
| 主题 | 随包提供内置 CC Dark、CC Light 主题 | `/
|
|
37
|
+
| 主题 | 随包提供内置 CC Dark、CC Light 主题 | `/settings` → Theme |
|
|
38
38
|
|
|
39
39
|
`on` / `compact` 两种模式的渲染样例:[默认模式](./docs/tool-render-examples-default.md) · [紧凑模式](./docs/tool-render-examples-compact.md)
|
|
40
40
|
|
|
@@ -55,7 +55,7 @@ pi install git:github.com/minuque/pi-cc-extensions
|
|
|
55
55
|
"enableAgentSummary": true, // 每回合工具摘要
|
|
56
56
|
"enableWorkingMessage": true, // Working... 底部 token/耗时
|
|
57
57
|
"enableAliases": true, // /clear、/exit 别名
|
|
58
|
-
"enableMarkdownEnhance": true,
|
|
58
|
+
"enableMarkdownEnhance": true, // markdown 转换:Mermaid 方言渲染、圈数字转半角括号;false 让位给其他扩展
|
|
59
59
|
"enableEditorPrefix": true, // 输入框 ❯ 前缀
|
|
60
60
|
|
|
61
61
|
// ui
|
|
@@ -97,12 +97,10 @@ pi install git:github.com/minuque/pi-cc-extensions
|
|
|
97
97
|
```
|
|
98
98
|
|
|
99
99
|
> [!TIP]
|
|
100
|
-
>
|
|
101
|
-
>
|
|
102
|
-
> `scrollButtonMode` 两种模式文案相同(`Back to bottom` / `N new message(s)`):`dock` 画在独占行,`native` 画在 Pi 原生浮层。
|
|
100
|
+
> **全屏模式**:展开态 diff 全量显示。Input/Output 超行时末行给 `… +N more lines`,单击看全量,再击收起。
|
|
103
101
|
|
|
104
102
|
> [!NOTE]
|
|
105
|
-
> **Mermaid
|
|
103
|
+
> **Mermaid 渲染**:`markdown.mermaid` 建议设为 `final`(`~/.pi/agent/settings.json` 或 `/settings`)。默认 `streaming` 逐帧重绘,`final` 只渲染最终版。
|
|
106
104
|
|
|
107
105
|
## 本地开发
|
|
108
106
|
|
|
@@ -122,6 +122,7 @@ export const THINKING_PREVIEW_LINES_VALUES = ["0", "1", "3", "5", "10"];
|
|
|
122
122
|
export const THINKING_ANIMATION_INTERVAL_VALUES = ["40", "60", "90", "120", "180"];
|
|
123
123
|
/** Tools commonly toggled in excludeRenderers via the settings panel. */
|
|
124
124
|
export const EXCLUDE_RENDERER_CANDIDATES = [
|
|
125
|
+
"agent",
|
|
125
126
|
"bash",
|
|
126
127
|
"powershell",
|
|
127
128
|
"read",
|
|
@@ -82,8 +82,8 @@ function modeSettingDescription(mode: CompactStyleMode): string {
|
|
|
82
82
|
|
|
83
83
|
function excludeRenderersDescription(names: readonly string[]): string {
|
|
84
84
|
return names.length === 0
|
|
85
|
-
? "No tools excluded.
|
|
86
|
-
: `Native renderer for: ${names.join(", ")}.
|
|
85
|
+
? "No tools excluded. Enter to toggle common tools."
|
|
86
|
+
: `Native renderer for: ${names.join(", ")}. Enter to toggle.`;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
function customFooterDescription(enabled: boolean): string {
|
|
@@ -274,10 +274,7 @@ function buildExcludeRenderersSubmenu(
|
|
|
274
274
|
const items = candidates.map((name) => ({
|
|
275
275
|
id: name,
|
|
276
276
|
label: name,
|
|
277
|
-
description:
|
|
278
|
-
name === "Agent"
|
|
279
|
-
? "Agent always uses its dedicated renderer and cannot be forced through ccstyle."
|
|
280
|
-
: `Use Pi native renderer for ${name} instead of Claude Code styling.`,
|
|
277
|
+
description: `Use Pi native renderer for ${name} instead of Claude Code styling.`,
|
|
281
278
|
currentValue: config.excludeRenderers.includes(name) ? "exclude" : "style",
|
|
282
279
|
values: ["style", "exclude"],
|
|
283
280
|
}));
|
|
@@ -431,6 +428,11 @@ export async function showCcstylePanel(
|
|
|
431
428
|
return;
|
|
432
429
|
}
|
|
433
430
|
|
|
431
|
+
// 编辑器插槽只能在面板关闭后替换:面板自己就挂在 editorContainer 上,面板开着时调
|
|
432
|
+
// setEditorComponent 会把面板踢出渲染树、done() 再也走不到,`ctx.ui.custom()` 永不落定,
|
|
433
|
+
// 整个输入管线随之卡死。
|
|
434
|
+
let editorPrefixChanged = false;
|
|
435
|
+
|
|
434
436
|
await ctx.ui.custom((tui: any, theme: any, _keybindings: any, done: () => void) => {
|
|
435
437
|
const modeSetting = {
|
|
436
438
|
id: "mode",
|
|
@@ -880,8 +882,7 @@ export async function showCcstylePanel(
|
|
|
880
882
|
editorPrefixSetting.description = enabled
|
|
881
883
|
? "Show ❯ before the input line; color follows thinking level / bash mode."
|
|
882
884
|
: "Plain input line, no ❯ glyph.";
|
|
883
|
-
|
|
884
|
-
else clearPromptEditor(ctx);
|
|
885
|
+
editorPrefixChanged = true;
|
|
885
886
|
ctx.ui.notify(`Updated ${id}: ${value}`, "info");
|
|
886
887
|
return;
|
|
887
888
|
}
|
|
@@ -1208,6 +1209,11 @@ export async function showCcstylePanel(
|
|
|
1208
1209
|
},
|
|
1209
1210
|
};
|
|
1210
1211
|
});
|
|
1212
|
+
// 面板已经卸下,插槽此时替换才不会踢掉对话框。
|
|
1213
|
+
if (editorPrefixChanged) {
|
|
1214
|
+
if (config.enableEditorPrefix) applyPromptEditor(ctx);
|
|
1215
|
+
else clearPromptEditor(ctx);
|
|
1216
|
+
}
|
|
1211
1217
|
// 面板卸下后主 transcript 重新挂载;再刷一次,吃掉打开期间扫树失败的切换。
|
|
1212
1218
|
hooks.refreshCurrentTranscript(ctx, toolGrouping);
|
|
1213
1219
|
}
|
|
@@ -385,12 +385,24 @@ const createCustomFooterFactory =
|
|
|
385
385
|
return 0;
|
|
386
386
|
};
|
|
387
387
|
|
|
388
|
-
|
|
388
|
+
// 三个会话统计每帧重算会全量遍历 session,长会话把 pi 钉在 100% CPU(issue #80)。
|
|
389
|
+
// leaf/model/thinking 任一变化立即重算;不变时 1s TTL,流式期间每秒刷新一次。
|
|
390
|
+
const SESSION_STATS_TTL_MS = 1_000;
|
|
391
|
+
let statsMemoKey = "";
|
|
392
|
+
let statsMemoAt = 0;
|
|
393
|
+
let statsMemo:
|
|
394
|
+
| { contextUsage: ReturnType<typeof ctx.getContextUsage>; cost: number; cachePct: number }
|
|
395
|
+
| undefined;
|
|
396
|
+
const sessionStats = () => {
|
|
389
397
|
const model = ctx.model;
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
398
|
+
const key = `${ctx.sessionManager.getLeafId() ?? ""}|${model?.provider}/${model?.id}|${ctx.thinkingLevel ?? ""}`;
|
|
399
|
+
if (
|
|
400
|
+
statsMemo &&
|
|
401
|
+
key === statsMemoKey &&
|
|
402
|
+
performance.now() - statsMemoAt < SESSION_STATS_TTL_MS
|
|
403
|
+
) {
|
|
404
|
+
return statsMemo;
|
|
405
|
+
}
|
|
394
406
|
let cost = 0;
|
|
395
407
|
for (const entry of ctx.sessionManager.getEntries()) {
|
|
396
408
|
if (entry.type === "message") {
|
|
@@ -404,6 +416,17 @@ const createCustomFooterFactory =
|
|
|
404
416
|
cost += entry.usage.cost?.total || 0;
|
|
405
417
|
}
|
|
406
418
|
}
|
|
419
|
+
statsMemo = { contextUsage: ctx.getContextUsage(), cost, cachePct: getCachePct() };
|
|
420
|
+
statsMemoKey = key;
|
|
421
|
+
statsMemoAt = performance.now();
|
|
422
|
+
return statsMemo;
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
const render = (width: number): string[] => {
|
|
426
|
+
const model = ctx.model;
|
|
427
|
+
const { contextUsage, cost, cachePct } = sessionStats();
|
|
428
|
+
const contextWindow = contextUsage?.contextWindow ?? model?.contextWindow ?? 0;
|
|
429
|
+
const percent = contextUsage?.percent;
|
|
407
430
|
|
|
408
431
|
// cwd 默认只显示最底层路径(根目录时回退完整路径)
|
|
409
432
|
const cwdLabel = ctx.cwd.split(/[\\/]/).filter(Boolean).pop() || ctx.cwd;
|
|
@@ -430,7 +453,6 @@ const createCustomFooterFactory =
|
|
|
430
453
|
const dimPlugin = (text: string) => colorUsageChip(theme, text);
|
|
431
454
|
|
|
432
455
|
const pctLabel = percent === null || percent === undefined ? "?" : `${Math.floor(percent)}%`;
|
|
433
|
-
const cachePct = getCachePct();
|
|
434
456
|
const glyphs = footerGlyphs(config.footerNerdIcons);
|
|
435
457
|
const cacheLabel =
|
|
436
458
|
cachePct > 0 ? `${glyphs.cache ? `${glyphs.cache} ` : ""}${Math.floor(cachePct)}%` : "";
|
|
@@ -59,27 +59,48 @@ const promptEditorFactory: EditorFactory = (tui, theme, keybindings) =>
|
|
|
59
59
|
let active = false;
|
|
60
60
|
let previousEditorFactory: EditorFactory | undefined;
|
|
61
61
|
|
|
62
|
-
/**
|
|
62
|
+
/**
|
|
63
|
+
* 撤销本扩展占据的插槽;从未占用就不碰插槽。
|
|
64
|
+
*
|
|
65
|
+
* 插槽可能已经被其他扩展包在外层(pi-inline-skills 会把当前工厂再包一层),
|
|
66
|
+
* 那种结构里无法只拆内层,直接还回占据前的工厂;外层扩展会在下次终端输入时
|
|
67
|
+
* 自己重新包装,前缀不会因此复活。
|
|
68
|
+
*
|
|
69
|
+
* 例外:插槽被 pi 重置回占据前的工厂时(/new、/reload 走 resetExtensionUI),
|
|
70
|
+
* 我们已经不在槽里,再写一次会把 pi 刚重置的结果又覆盖掉;此时只清状态。
|
|
71
|
+
*/
|
|
63
72
|
function releasePromptEditor(ctx: ExtensionContext): void {
|
|
64
73
|
if (!active) return;
|
|
65
|
-
|
|
74
|
+
const previous = previousEditorFactory;
|
|
66
75
|
active = false;
|
|
67
76
|
previousEditorFactory = undefined;
|
|
77
|
+
if (ctx.ui.getEditorComponent?.() === previous) return;
|
|
78
|
+
ctx.ui.setEditorComponent(previous);
|
|
68
79
|
}
|
|
69
80
|
|
|
70
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* 应用输入框前缀;enableEditorPrefix=false 时只撤自己装的那层。
|
|
83
|
+
*
|
|
84
|
+
* 只在没有扩展对话框开着时调用:pi 的 setEditorComponent 会清空 editorContainer 并
|
|
85
|
+
* 抢走焦点,把正在设置面板里的对话框踢出渲染树,对话框的 done() 再也走不到,
|
|
86
|
+
* `ctx.ui.custom()` 的 Promise 永不落定,后续输入全部卡死。设置面板自己不做替换,
|
|
87
|
+
* 等 `ctx.ui.custom()` 返回后再调本函数。
|
|
88
|
+
*
|
|
89
|
+
* 插槽已经装着本扩展的编辑器时直接返回:重复安装只会重建实例,丢掉撤销栈、
|
|
90
|
+
* 历史等编辑器状态,还会打断其他扩展包在外层的编辑器。
|
|
91
|
+
*/
|
|
71
92
|
export function applyPromptEditor(ctx: ExtensionContext): void {
|
|
72
93
|
if (!ctx?.hasUI || typeof ctx.ui?.setEditorComponent !== "function") return;
|
|
94
|
+
if (!config.enableEditorPrefix) {
|
|
95
|
+
releasePromptEditor(ctx);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const current = ctx.ui.getEditorComponent?.();
|
|
99
|
+
if (current === promptEditorFactory) return;
|
|
73
100
|
try {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
if (!active) {
|
|
79
|
-
// pi 的插槽是硬替换、没有栈,先记住是谁占着,撤销时还回去。
|
|
80
|
-
previousEditorFactory = ctx.ui.getEditorComponent?.();
|
|
81
|
-
active = true;
|
|
82
|
-
}
|
|
101
|
+
// pi 的插槽是硬替换、没有栈,先记住是谁占着,撤销时还回去。
|
|
102
|
+
previousEditorFactory = current;
|
|
103
|
+
active = true;
|
|
83
104
|
ctx.ui.setEditorComponent(promptEditorFactory);
|
|
84
105
|
} catch (err) {
|
|
85
106
|
ctx.ui.notify(
|
|
@@ -1057,6 +1057,14 @@ function compactStopStatusLine(status: string, pad = 0): any {
|
|
|
1057
1057
|
/** 展开入口的动作词:我们的 `click to show more`、pi 的 `<key> to expand`(键名可能取不到)。 */
|
|
1058
1058
|
const EXPAND_ACTION = String.raw`(?:click to show more|(?:[\w/+]+\s+)?to (?:show more|expand))`;
|
|
1059
1059
|
|
|
1060
|
+
/**
|
|
1061
|
+
* 展开入口只挂在行尾:正则只看行尾这一段,单行代价与行长无关。
|
|
1062
|
+
* 图片负载(kitty 图形数据的 Base64)一行可达数百 KB,整行过正则会拖住渲染帧。
|
|
1063
|
+
*/
|
|
1064
|
+
const SLOT_HINT_WINDOW = 200;
|
|
1065
|
+
/** 窗口里出现这些词才可能有展开入口,其余行(负载、代码)直接放过正则。 */
|
|
1066
|
+
const SLOT_HINT_MARKERS = ["to expand", "to show more"];
|
|
1067
|
+
|
|
1060
1068
|
const SLOT_HINT_PATTERNS: RegExp[] = [
|
|
1061
1069
|
// 卡在括号里:`(9 more lines, click to show more)` / `(195 earlier lines, ctrl+o to expand)`,
|
|
1062
1070
|
// 只去掉动作词,收尾括号与计数保留。
|
|
@@ -1066,13 +1074,18 @@ const SLOT_HINT_PATTERNS: RegExp[] = [
|
|
|
1066
1074
|
new RegExp(String.raw`\s*(?:[•·]\s*)?${EXPAND_ACTION}\s*$`),
|
|
1067
1075
|
];
|
|
1068
1076
|
|
|
1069
|
-
|
|
1070
|
-
|
|
1077
|
+
/** 导出供单测:窗口边界与键名长度是性能与裁剪行为的关键,需要逐条钉住。 */
|
|
1078
|
+
export function stripSlotHint(line: string): string {
|
|
1079
|
+
// 切点可能落在转义序列中间,残片只当作窗口头部文本;两个模式都锚在行尾,不受影响。
|
|
1080
|
+
const head = line.slice(0, Math.max(0, line.length - SLOT_HINT_WINDOW));
|
|
1081
|
+
const tail = line.slice(head.length);
|
|
1082
|
+
const plain = stripTerminalSequencesPreservingLayout(tail);
|
|
1083
|
+
if (!SLOT_HINT_MARKERS.some((marker) => plain.includes(marker))) return line;
|
|
1071
1084
|
for (const re of SLOT_HINT_PATTERNS) {
|
|
1072
1085
|
const match = re.exec(plain);
|
|
1073
1086
|
if (!match) continue;
|
|
1074
1087
|
// 删除区间而非截断后拼纯文本:收尾括号留在原样式里(dim),不会掉回默认前景色。
|
|
1075
|
-
return removeStyledRange(
|
|
1088
|
+
return head + removeStyledRange(tail, match.index, match.index + match[0].length);
|
|
1076
1089
|
}
|
|
1077
1090
|
return line;
|
|
1078
1091
|
}
|
|
@@ -20,10 +20,7 @@ import { createCcstyleTool } from "./tool/ccstyle-tool.ts";
|
|
|
20
20
|
import type { WriteExecutionMetadataStore } from "./tool/diff/index.ts";
|
|
21
21
|
export { shouldRenderRichDiff } from "./tool/ccstyle-tool.ts";
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
const DEDICATED_RENDERER_TOOLS = new Set(["Agent"]);
|
|
25
|
-
|
|
26
|
-
/** 排除名单内且自带 renderer 的工具保留原渲染。 */
|
|
23
|
+
/** 排除名单内且自带 renderer 的工具保留原渲染;excludeRenderers 里的工具也交给原生渲染器。 */
|
|
27
24
|
export function preservesOriginalRenderer(
|
|
28
25
|
extensionDefinition: any,
|
|
29
26
|
toolName: string,
|
|
@@ -117,7 +114,6 @@ function shouldGloballyStyleTool(component: any, patch: GlobalToolRenderPatch):
|
|
|
117
114
|
// compact 也复用同一套 ccstyle call/result,避免折叠态工具卡回落到 Pi 原生样式。
|
|
118
115
|
const useCcstyle =
|
|
119
116
|
patch.mode() !== "off" &&
|
|
120
|
-
!DEDICATED_RENDERER_TOOLS.has(toolName) &&
|
|
121
117
|
!preservesOriginalRenderer(extensionDefinition, toolName, builtInDefinition);
|
|
122
118
|
component[COMPONENT_TOOL_RENDER_MODE] = useCcstyle;
|
|
123
119
|
return useCcstyle;
|
|
@@ -49,13 +49,17 @@ import {
|
|
|
49
49
|
refreshMessageDisplays,
|
|
50
50
|
setMessageDisplayTheme,
|
|
51
51
|
} from "./tool/message-display.ts";
|
|
52
|
+
import {
|
|
53
|
+
installSubagentNotificationRenderer,
|
|
54
|
+
refreshSubagentNotifications,
|
|
55
|
+
} from "./tool/subagent-notification.ts";
|
|
52
56
|
|
|
53
57
|
/**
|
|
54
58
|
* Claude Code Style for pi — 装配入口。
|
|
55
59
|
*
|
|
56
60
|
* mode=on → default-mode(工具卡样式 + 展开背景)
|
|
57
61
|
* mode=compact → compact-mode(消息折叠摘要)
|
|
58
|
-
* 共用 → tool-grouping / message-display / mouse / write override
|
|
62
|
+
* 共用 → tool-grouping / message-display / subagent-notification / mouse / write override
|
|
59
63
|
*/
|
|
60
64
|
|
|
61
65
|
let compactModeHooks: CompactModeHooks | undefined;
|
|
@@ -64,6 +68,7 @@ function refreshCurrentTranscript(ctx?: any, toolGrouping?: ToolGroupingHooks):
|
|
|
64
68
|
const tui = getToolMouseTui();
|
|
65
69
|
toolGrouping?.refresh(tui);
|
|
66
70
|
refreshMessageDisplays(tui);
|
|
71
|
+
refreshSubagentNotifications(tui);
|
|
67
72
|
refreshCompactModeComponents(tui);
|
|
68
73
|
compactModeHooks?.refresh();
|
|
69
74
|
tui?.requestRender?.(true);
|
|
@@ -117,6 +122,7 @@ export default function (
|
|
|
117
122
|
toolGrouping: ToolGroupingHooks;
|
|
118
123
|
compactMode: CompactModeHooks;
|
|
119
124
|
disposeMessageDisplay: () => void;
|
|
125
|
+
disposeSubagentNotificationRenderer: () => void;
|
|
120
126
|
disposeToolExpandedBackground: () => void;
|
|
121
127
|
}
|
|
122
128
|
| undefined;
|
|
@@ -138,6 +144,8 @@ export default function (
|
|
|
138
144
|
});
|
|
139
145
|
compactModeHooks = compactMode;
|
|
140
146
|
const disposeMessageDisplay = installMessageDisplayRendering();
|
|
147
|
+
// pi-subagents 的结束通知:换查找结果而不注册,加载先后都归本插件,off 还给原生。
|
|
148
|
+
const disposeSubagentNotificationRenderer = installSubagentNotificationRenderer();
|
|
141
149
|
// 展开背景必须在 compact-mode 之后装,shutdown 时先于 compact 释放。
|
|
142
150
|
const disposeToolExpandedBackground = installToolExpandedBackground();
|
|
143
151
|
installation = {
|
|
@@ -145,6 +153,7 @@ export default function (
|
|
|
145
153
|
toolGrouping,
|
|
146
154
|
compactMode,
|
|
147
155
|
disposeMessageDisplay,
|
|
156
|
+
disposeSubagentNotificationRenderer,
|
|
148
157
|
disposeToolExpandedBackground,
|
|
149
158
|
};
|
|
150
159
|
return installation;
|
|
@@ -253,7 +262,11 @@ export default function (
|
|
|
253
262
|
// 历史回放发生在 session_start 之后,延迟到回放结束再收尾最后一个回合,
|
|
254
263
|
// 否则它没有后续消息,摘要行会停在 Running。
|
|
255
264
|
setTimeout(() => compactModeHooks?.settleReplayedRound(), 0);
|
|
256
|
-
scheduleSessionRender(() =>
|
|
265
|
+
scheduleSessionRender(() => {
|
|
266
|
+
hooks.toolGrouping.refresh(getToolMouseTui());
|
|
267
|
+
// /reload、/resume 在 session_start 之前就重建了 transcript:已挂载的通知卡换上本插件的渲染器。
|
|
268
|
+
refreshSubagentNotifications(getToolMouseTui());
|
|
269
|
+
});
|
|
257
270
|
});
|
|
258
271
|
|
|
259
272
|
pi.on("session_compact", async (event, ctx) => {
|
|
@@ -307,6 +320,7 @@ export default function (
|
|
|
307
320
|
current.compactMode.shutdown();
|
|
308
321
|
compactModeHooks = undefined;
|
|
309
322
|
current.disposeMessageDisplay();
|
|
323
|
+
current.disposeSubagentNotificationRenderer();
|
|
310
324
|
disposeStickyPromptPatches();
|
|
311
325
|
clearAllAnimations();
|
|
312
326
|
installation = undefined;
|
|
@@ -102,7 +102,10 @@ function deCircled(markdown: string): string {
|
|
|
102
102
|
// 跨行链接规整
|
|
103
103
|
// ============================================================================
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
// \x1b 排除:Pi 的带颜色 Mermaid 输出把每行包成含 ANSI SGR(\x1b[...m)的 code span,
|
|
106
|
+
// 转义序列里的 `[` 不能当成链接起点,否则匹配会从第一个转义跨行直到后面真正的 `](`,
|
|
107
|
+
// 把图和之后的段落全压成一行(#84)。
|
|
108
|
+
const MULTILINE_LINK_RE = /(?<!\x1b)\[([^\]\n\x1b]*(?:\n[^\]\n\x1b]*)+)\](?=\()/g;
|
|
106
109
|
|
|
107
110
|
/** 防止 Markdown 跨行链接把行尾填充空格纳入 OSC 8 点击区。 */
|
|
108
111
|
function normalizeMultilineLinks(markdown: string): string {
|
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
markCompactRoundToolExpanded,
|
|
13
13
|
setHoveredCompactAssistant,
|
|
14
14
|
} from "../compact-mode.ts";
|
|
15
|
-
import { isMessageDisplayComponent } from "../tool/message-display.ts";
|
|
16
15
|
import { isLazyProxyTui } from "../../utils/fullscreen-detect.ts";
|
|
17
16
|
import { setToolTuiFullscreen, isCollapseHintLine } from "../tool/names.ts";
|
|
18
17
|
import {
|
|
@@ -43,6 +42,7 @@ import {
|
|
|
43
42
|
collapsedHintHitbox,
|
|
44
43
|
fullscreenContentWidth,
|
|
45
44
|
fullscreenLeafAt,
|
|
45
|
+
isMessageCard,
|
|
46
46
|
isScrollbarColumnAt,
|
|
47
47
|
} from "./layout.ts";
|
|
48
48
|
import {
|
|
@@ -122,7 +122,7 @@ let sessionRenderTimer: ReturnType<typeof setTimeout> | null = null;
|
|
|
122
122
|
let latestInteractionFrame: InteractionFrame = { regions: [] };
|
|
123
123
|
|
|
124
124
|
/** Summary markers used by Pi and ccstyle; unlike the trailing hint, these survive truncation. */
|
|
125
|
-
const COLLAPSED_TOOL_SUMMARY = /^\s*(
|
|
125
|
+
const COLLAPSED_TOOL_SUMMARY = /^\s*(?:↳|└|⎿|●|✓|✗|…|\.\.\.)/;
|
|
126
126
|
|
|
127
127
|
function interactionRegionAt(packet: SgrMousePacket): InteractionRegion | null {
|
|
128
128
|
const matches = latestInteractionFrame.regions.filter(
|
|
@@ -347,28 +347,40 @@ function releaseFullscreenToolMouseMotion(tui?: any): void {
|
|
|
347
347
|
const FULLSCREEN_VIEWPORT_PATCH = Symbol("ccstyle.fullscreen-viewport-patch");
|
|
348
348
|
|
|
349
349
|
/**
|
|
350
|
-
*
|
|
351
|
-
*
|
|
350
|
+
* 折叠卡上哪些行声明为展开入口。resultRendererComponent 自带 isCollapsedHintLine
|
|
351
|
+
* 的(diff/ask/codemode)按它声明的判,行号原样转发;不带的原生卡(grep/find/ls/
|
|
352
|
+
* read/bash/fallback)按原生展开提示的收尾特征判——pi 给结果区套整卡 MouseRegion,
|
|
353
|
+
* 正文行(含路径文本)也会被官方 click 展开,这里把入口收到提示那几行。
|
|
354
|
+
* localRow 是组件 render 内的行号(frame marker / 布局 localRow),供声明按行判。
|
|
352
355
|
*/
|
|
353
|
-
function isCollapsedHintRow(component: any, finalLine: string): boolean {
|
|
356
|
+
function isCollapsedHintRow(component: any, finalLine: string, localRow?: number): boolean {
|
|
354
357
|
const result = component?.resultRendererComponent;
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
+
if (typeof result?.isCollapsedHintLine === "function") {
|
|
359
|
+
return result.isCollapsedHintLine(finalLine, localRow);
|
|
360
|
+
}
|
|
361
|
+
return isNativeExpandHintLine(finalLine);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** 原生折叠卡展开提示的收尾特征:`(... N more lines, ctrl+o to expand)` 或行尾的 `to show more`。 */
|
|
365
|
+
function isNativeExpandHintLine(line: string): boolean {
|
|
366
|
+
return /(\([^()]*\/(?: click| to expand)\)|(?:[\w/+]+\s+)?to (?:expand|show more)\)?)\s*$/.test(
|
|
367
|
+
stripTerminalSequencesPreservingLayout(line),
|
|
368
|
+
);
|
|
358
369
|
}
|
|
359
370
|
|
|
360
371
|
/**
|
|
361
372
|
* pi 0.87 给工具结果区套了 MouseRegion:整卡左键 click 都会 setExpanded。
|
|
362
|
-
*
|
|
363
|
-
*
|
|
373
|
+
* 声明了展开入口的组件(diff remainder 等)与原生卡(提示在预览末行)只放行入口
|
|
374
|
+
* 行,其余位置直接吞掉 click,避免官方把正文当展开入口;无提示的单行摘要卡保持
|
|
375
|
+
* 官方整行行为。
|
|
364
376
|
*/
|
|
365
377
|
function blocksOfficialCardToggle(component: any, event: any): boolean {
|
|
366
378
|
if (event?.type !== "click" || event.button !== "left" || component?.expanded) return false;
|
|
367
|
-
const result = component?.resultRendererComponent;
|
|
368
|
-
if (typeof result?.isCollapsedHintLine !== "function") return false;
|
|
369
379
|
const lines = component.render?.(Math.max(1, Math.floor(Number(event.width) || 0)));
|
|
370
|
-
const
|
|
371
|
-
|
|
380
|
+
const row = event.y;
|
|
381
|
+
const line = Array.isArray(lines) ? lines[row] : undefined;
|
|
382
|
+
if (typeof line !== "string") return false;
|
|
383
|
+
return !isCollapsedHintRow(component, line, row);
|
|
372
384
|
}
|
|
373
385
|
|
|
374
386
|
/**
|
|
@@ -410,7 +422,7 @@ function handleFullscreenToolClick(tui: any, packet: SgrMousePacket): boolean {
|
|
|
410
422
|
const isGroup = component instanceof ToolGroupComponent;
|
|
411
423
|
const isAssistant = isCompactAssistantComponent(component);
|
|
412
424
|
const isThinking = component instanceof ThinkingPreviewBlock;
|
|
413
|
-
const isMessage =
|
|
425
|
+
const isMessage = isMessageCard(component);
|
|
414
426
|
if (!isTool && !isGroup && !isAssistant && !isThinking && !isMessage) return false;
|
|
415
427
|
if (!component.expanded) {
|
|
416
428
|
// collapsed 仅按钮文本可展开,不能把同一行正文/留白变成点击区。
|
|
@@ -419,7 +431,7 @@ function handleFullscreenToolClick(tui: any, packet: SgrMousePacket): boolean {
|
|
|
419
431
|
const onHint = Boolean(
|
|
420
432
|
hint &&
|
|
421
433
|
(isThinking || (packet.col >= hint.startCol && packet.col <= hint.endCol)) &&
|
|
422
|
-
isCollapsedHintRow(component, line),
|
|
434
|
+
isCollapsedHintRow(component, line, hit.localRow),
|
|
423
435
|
);
|
|
424
436
|
if (!onHint) {
|
|
425
437
|
// 面板内非提示区(工具卡标题/摘要行、thinking 预览正文):单击收起整块面板。
|
|
@@ -573,7 +585,7 @@ function handleFullscreenToolHover(tui: any, packet: SgrMousePacket): void {
|
|
|
573
585
|
} else if (isCompactAssistantComponent(component)) {
|
|
574
586
|
// compact 摘要行:折叠时仅提示文字高亮,展开卡整体高亮。
|
|
575
587
|
if (component.expanded || overHint) target = { kind: "assistant", component };
|
|
576
|
-
} else if (
|
|
588
|
+
} else if (isMessageCard(component)) {
|
|
577
589
|
if (overHint) target = { kind: "message", component };
|
|
578
590
|
}
|
|
579
591
|
}
|
|
@@ -758,7 +770,7 @@ function buildInteractionFrame(
|
|
|
758
770
|
if (
|
|
759
771
|
box &&
|
|
760
772
|
COLLAPSED_TOOL_SUMMARY.test(stripTerminalSequences(line)) &&
|
|
761
|
-
isCollapsedHintRow(component, line)
|
|
773
|
+
isCollapsedHintRow(component, line, placement.componentRow)
|
|
762
774
|
) {
|
|
763
775
|
regions.push({ kind: "collapsed-hint", row: finalRow, ...box, component });
|
|
764
776
|
}
|
|
@@ -3,6 +3,7 @@ import { ThinkingPreviewBlock } from "../../feature/compact-thinking.ts";
|
|
|
3
3
|
import { isCompactAssistantComponent } from "../compact-mode.ts";
|
|
4
4
|
import { ToolGroupComponent } from "../tool/grouping.ts";
|
|
5
5
|
import { isMessageDisplayComponent } from "../tool/message-display.ts";
|
|
6
|
+
import { isSubagentNotificationView } from "../tool/subagent-notification.ts";
|
|
6
7
|
import { isToolExecutionComponent, stripTerminalSequencesPreservingLayout } from "./packets.ts";
|
|
7
8
|
import { getScrollButtonWidget } from "./scroll.ts";
|
|
8
9
|
|
|
@@ -15,11 +16,19 @@ export type ComponentRowHit = {
|
|
|
15
16
|
owner?: any;
|
|
16
17
|
};
|
|
17
18
|
|
|
19
|
+
/**
|
|
20
|
+
* 消息类卡片:skill/压缩/分支摘要三种消息组件,以及单独出现的 subagent 通知行。
|
|
21
|
+
* 点击、悬停与 single-expand 规则相同。
|
|
22
|
+
*/
|
|
23
|
+
export function isMessageCard(component: any): boolean {
|
|
24
|
+
return isMessageDisplayComponent(component) || isSubagentNotificationView(component);
|
|
25
|
+
}
|
|
26
|
+
|
|
18
27
|
/** 行内 [click to show more] / [↑ Collapse] 提示的命中列区间(1-based,含两端)。 */
|
|
19
28
|
export function collapsedHintHitbox(line: string): { startCol: number; endCol: number } | null {
|
|
20
29
|
const plain = stripTerminalSequencesPreservingLayout(line);
|
|
21
30
|
const match =
|
|
22
|
-
/(\([^()\n]* \/ click\)|click to show more|to show more|↑ Collapse|to collapse)(?=\)?\s*$)/.exec(
|
|
31
|
+
/(\([^()\n]*(?: \/ click|, (?:[\w/+]+\s+)?to (?:expand|show more))\)|click to show more|to show more|↑ Collapse|to collapse)(?=\)?\s*$)/.exec(
|
|
23
32
|
plain,
|
|
24
33
|
);
|
|
25
34
|
if (!match?.[1]) return null;
|
|
@@ -140,7 +149,7 @@ export function componentAtLocalRow(
|
|
|
140
149
|
if (component instanceof ThinkingPreviewBlock) {
|
|
141
150
|
return { component, row: localRow };
|
|
142
151
|
}
|
|
143
|
-
if (
|
|
152
|
+
if (isMessageCard(component)) {
|
|
144
153
|
return { component, row: localRow };
|
|
145
154
|
}
|
|
146
155
|
if (component === getScrollButtonWidget()) {
|
|
@@ -182,7 +191,7 @@ export function collectFullscreenToolCards(
|
|
|
182
191
|
component instanceof ToolGroupComponent ||
|
|
183
192
|
isCompactAssistantComponent(component) ||
|
|
184
193
|
component instanceof ThinkingPreviewBlock ||
|
|
185
|
-
|
|
194
|
+
isMessageCard(component)
|
|
186
195
|
) {
|
|
187
196
|
out.push(component);
|
|
188
197
|
return;
|