pi-cc-extensions 0.9.8 → 0.9.9
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 +1 -1
- package/README.md +1 -1
- package/extensions/config/config.ts +10 -4
- package/extensions/config/panel.ts +3 -2
- package/extensions/feature/shell/startup-header.ts +82 -6
- package/extensions/feature/shell/working-message.ts +21 -6
- package/extensions/index.ts +2 -1
- package/extensions/renderer/compact-mode.ts +49 -5
- package/extensions/renderer/tool/grouping.ts +6 -3
- package/extensions/renderer/tool/names.ts +9 -4
- package/extensions/renderer/tool/result.ts +7 -1
- package/extensions/utils/patch-keys.ts +2 -0
- package/package.json +7 -4
package/README.en.md
CHANGED
|
@@ -60,7 +60,7 @@ Renderer snapshots for `on` / `compact` modes: [default](./docs/tool-render-exam
|
|
|
60
60
|
"expandedInputMaxLines": 5, // expanded tool Input lines; overflow shows a footer hint
|
|
61
61
|
"expandedOutputMaxLines": 10, // expanded tool Output lines; overflow shows a footer hint
|
|
62
62
|
"expandedPreviewMaxLines": 40, // max lines for expanded TaskList bodies (expanded diffs always show all)
|
|
63
|
-
"inputClip":
|
|
63
|
+
"inputClip": 0, // tool summary path/command clip length; 0 = fit width
|
|
64
64
|
"showStartupHeader": true, // startup header (logo + tips) toggle
|
|
65
65
|
"scrollStepLines": 3, // fullscreen wheel scroll step
|
|
66
66
|
|
package/README.md
CHANGED
|
@@ -60,7 +60,7 @@ pi install git:github.com/minuque/pi-cc-extensions
|
|
|
60
60
|
"expandedInputMaxLines": 5, // 展开工具卡 Input 可见行数,超出在末行显示展开提示
|
|
61
61
|
"expandedOutputMaxLines": 10, // 展开工具卡 Output 可见行数,超出在末行显示展开提示
|
|
62
62
|
"expandedPreviewMaxLines": 40, // 展开 TaskList 正文最大行数(展开 diff 始终全量)
|
|
63
|
-
"inputClip":
|
|
63
|
+
"inputClip": 0, // 工具摘要 path/command 折叠字符数;0 = 按可用宽度
|
|
64
64
|
"showStartupHeader": true, // 启动头(logo + tips)开关
|
|
65
65
|
"scrollStepLines": 3, // fullscreen 滚轮步进
|
|
66
66
|
|
|
@@ -92,8 +92,8 @@ export const EXPANDED_PREVIEW_MAX_LINES_VALUES = ["40", "60", "80", "120", "200"
|
|
|
92
92
|
export const EXPANDED_INPUT_MAX_LINES_VALUES = ["5", "10", "20", "40", "80"];
|
|
93
93
|
/** 展开工具卡 Output 可见行数预设。 */
|
|
94
94
|
export const EXPANDED_OUTPUT_MAX_LINES_VALUES = ["10", "20", "40", "80", "120"];
|
|
95
|
-
/** 工具摘要里 path/command
|
|
96
|
-
export const INPUT_CLIP_VALUES = ["40", "60", "80", "100", "120", "160"];
|
|
95
|
+
/** 工具摘要里 path/command 等输入的折叠字符数;0 = 只按可用宽度截断。 */
|
|
96
|
+
export const INPUT_CLIP_VALUES = ["0", "40", "60", "80", "100", "120", "160"];
|
|
97
97
|
export const THINKING_PREVIEW_LINES_VALUES = ["0", "1", "3", "5", "10"];
|
|
98
98
|
export const THINKING_ANIMATION_INTERVAL_VALUES = ["40", "60", "90", "120", "180"];
|
|
99
99
|
/** fullscreen 滚轮步进行数预设。 */
|
|
@@ -124,7 +124,7 @@ export const DEFAULT_CONFIG: Config = {
|
|
|
124
124
|
expandedPreviewMaxLines: DEFAULT_TOOL_DISPLAY_CONFIG.expandedPreviewMaxLines,
|
|
125
125
|
expandedInputMaxLines: 5,
|
|
126
126
|
expandedOutputMaxLines: 10,
|
|
127
|
-
inputClip:
|
|
127
|
+
inputClip: 0,
|
|
128
128
|
useSummaryTitlesAsThinkingTitle: true,
|
|
129
129
|
previewLines: 3,
|
|
130
130
|
animationIntervalMs: 90,
|
|
@@ -154,6 +154,12 @@ export function pickPositiveInt(value: unknown, fallback: number, min = 1, max =
|
|
|
154
154
|
return Math.min(max, Math.max(min, Math.floor(n)));
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
/** inputClip:0 表示按宽度自适应,其余夹到 8..500。 */
|
|
158
|
+
export function pickInputClip(value: unknown): number {
|
|
159
|
+
const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
|
|
160
|
+
return n === 0 ? 0 : pickPositiveInt(value, DEFAULT_CONFIG.inputClip, 8, 500);
|
|
161
|
+
}
|
|
162
|
+
|
|
157
163
|
export function pickPositiveNumber(value: unknown, fallback: number, min = 1): number {
|
|
158
164
|
const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
|
|
159
165
|
return Number.isFinite(n) ? Math.max(min, n) : fallback;
|
|
@@ -217,7 +223,7 @@ export function normalizeConfig(input: unknown): Config {
|
|
|
217
223
|
1,
|
|
218
224
|
5_000,
|
|
219
225
|
),
|
|
220
|
-
inputClip:
|
|
226
|
+
inputClip: pickInputClip(source.inputClip),
|
|
221
227
|
useSummaryTitlesAsThinkingTitle: source.useSummaryTitlesAsThinkingTitle !== false,
|
|
222
228
|
previewLines: pickPositiveInt(
|
|
223
229
|
source.previewLines,
|
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
EXPANDED_PREVIEW_MAX_LINES_VALUES,
|
|
39
39
|
formatExcludeRenderers,
|
|
40
40
|
getCompactThinkingConfig,
|
|
41
|
+
pickInputClip,
|
|
41
42
|
pickPositiveInt,
|
|
42
43
|
pickPositiveNumber,
|
|
43
44
|
SCROLL_STEP_LINES_VALUES,
|
|
@@ -403,7 +404,7 @@ export async function showCcstylePanel(
|
|
|
403
404
|
id: "inputClip",
|
|
404
405
|
label: "Input clip",
|
|
405
406
|
description:
|
|
406
|
-
"Max characters for path/command/name in single and grouped tool summaries. Enter to type a custom value.",
|
|
407
|
+
"Max characters for path/command/name in single and grouped tool summaries; 0 fits available width. Enter to type a custom value.",
|
|
407
408
|
currentValue: String(config.inputClip),
|
|
408
409
|
values: [...INPUT_CLIP_VALUES],
|
|
409
410
|
submenu: (_current: string, closeSubmenu: (selected?: string) => void) =>
|
|
@@ -667,7 +668,7 @@ export async function showCcstylePanel(
|
|
|
667
668
|
switch (id) {
|
|
668
669
|
case "inputClip":
|
|
669
670
|
updateConfig({
|
|
670
|
-
inputClip:
|
|
671
|
+
inputClip: pickInputClip(value),
|
|
671
672
|
});
|
|
672
673
|
inputClipSetting.currentValue = String(config.inputClip);
|
|
673
674
|
break;
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import { VERSION, type AppKeybinding } from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import { InteractiveMode, VERSION, type AppKeybinding } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { getKeybindings } from "@earendil-works/pi-tui";
|
|
3
3
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { config } from "../../config/config.ts";
|
|
5
5
|
import { ansi16ToRgb, ansi256ToRgb } from "../../utils/ansi-color.ts";
|
|
6
6
|
import { stripAnsi } from "../../utils/ansi-text.ts";
|
|
7
|
+
import {
|
|
8
|
+
EARLY_STARTUP_HEADER_PATCH,
|
|
9
|
+
EARLY_STARTUP_HEADER_SWAP_KEY,
|
|
10
|
+
patchRegistry,
|
|
11
|
+
} from "../../utils/patch-keys.ts";
|
|
7
12
|
type Rgb = [number, number, number];
|
|
8
13
|
type StyledPart = {
|
|
9
14
|
raw: string;
|
|
@@ -279,17 +284,88 @@ export function renderHeaderLines(
|
|
|
279
284
|
);
|
|
280
285
|
}
|
|
281
286
|
|
|
287
|
+
/** 官方 setHeader 的组件工厂:theme 由宿主注入。 */
|
|
288
|
+
function headerFactory(): (
|
|
289
|
+
tui: unknown,
|
|
290
|
+
theme: any,
|
|
291
|
+
) => {
|
|
292
|
+
render(width: number): string[];
|
|
293
|
+
invalidate(): void;
|
|
294
|
+
} {
|
|
295
|
+
return (_tui: unknown, theme: any) => ({
|
|
296
|
+
render(width: number): string[] {
|
|
297
|
+
return renderHeaderLines(width, theme);
|
|
298
|
+
},
|
|
299
|
+
invalidate() {},
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
282
303
|
/** 按配置安装启动头。禁用时不碰槽位,避免清掉其他扩展的 header。 */
|
|
283
304
|
export function applyStartupHeader(ctx: any): void {
|
|
284
305
|
if (!ctx?.hasUI || typeof ctx.ui?.setHeader !== "function" || !config.showStartupHeader) {
|
|
285
306
|
return;
|
|
286
307
|
}
|
|
287
|
-
ctx.ui.setHeader((
|
|
288
|
-
|
|
289
|
-
|
|
308
|
+
ctx.ui.setHeader(headerFactory());
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
type EarlyHeaderPatch = {
|
|
312
|
+
active: boolean;
|
|
313
|
+
original: (...args: any[]) => unknown;
|
|
314
|
+
installed: (...args: any[]) => unknown;
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
type HeaderContainerSwap = {
|
|
318
|
+
original: (child: any) => unknown;
|
|
319
|
+
installed: (child: any) => unknown;
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Pi 在 session_start 之前就把原生启动头画上去了:init() 先建 header 再 requestRender,
|
|
324
|
+
* 之后才 await 工具检查、加载扩展,所以扩展最早只能到 session_start 换头,启动时会闪一下原生头。
|
|
325
|
+
* 这里补 init,在原生 header 刚进容器的瞬间换成 ccstyle 的,首次绘制就已经是我们的。
|
|
326
|
+
* 判定用 child === mode.builtInHeader,不依赖类名;配置关掉时不碰槽位,保留原生头。
|
|
327
|
+
*/
|
|
328
|
+
export function installEarlyStartupHeader(): void {
|
|
329
|
+
const prototype = (InteractiveMode as any)?.prototype;
|
|
330
|
+
if (!prototype || typeof prototype.init !== "function") return;
|
|
331
|
+
const previous = patchRegistry.get<EarlyHeaderPatch>(EARLY_STARTUP_HEADER_PATCH);
|
|
332
|
+
if (previous) previous.active = false;
|
|
333
|
+
const original =
|
|
334
|
+
previous && prototype.init === previous.installed ? previous.original : prototype.init;
|
|
335
|
+
const patch: EarlyHeaderPatch = {
|
|
336
|
+
active: true,
|
|
337
|
+
original,
|
|
338
|
+
installed: async function (this: any, ...args: any[]) {
|
|
339
|
+
installHeaderContainerSwap(this, patch);
|
|
340
|
+
return original.apply(this, args);
|
|
290
341
|
},
|
|
291
|
-
|
|
292
|
-
|
|
342
|
+
};
|
|
343
|
+
prototype.init = patch.installed;
|
|
344
|
+
patchRegistry.install(EARLY_STARTUP_HEADER_PATCH, patch);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** 只包一次 headerContainer.addChild;/reload 后按所有权接管旧包装。 */
|
|
348
|
+
function installHeaderContainerSwap(mode: any, patch: EarlyHeaderPatch): void {
|
|
349
|
+
const container = mode?.headerContainer;
|
|
350
|
+
if (!container || typeof container.addChild !== "function") return;
|
|
351
|
+
const previous = container[EARLY_STARTUP_HEADER_SWAP_KEY] as HeaderContainerSwap | undefined;
|
|
352
|
+
const original =
|
|
353
|
+
previous && container.addChild === previous.installed
|
|
354
|
+
? previous.original
|
|
355
|
+
: container.addChild.bind(container);
|
|
356
|
+
const installed = function (child: any) {
|
|
357
|
+
const result = original(child);
|
|
358
|
+
if (!patch.active || !config.showStartupHeader || child !== mode.builtInHeader) return result;
|
|
359
|
+
if (typeof mode.setExtensionHeader !== "function") return result;
|
|
360
|
+
try {
|
|
361
|
+
mode.setExtensionHeader(headerFactory());
|
|
362
|
+
} catch {
|
|
363
|
+
// 换头失败就退回原生 header,不影响启动
|
|
364
|
+
}
|
|
365
|
+
return result;
|
|
366
|
+
};
|
|
367
|
+
container.addChild = installed;
|
|
368
|
+
container[EARLY_STARTUP_HEADER_SWAP_KEY] = { original, installed };
|
|
293
369
|
}
|
|
294
370
|
|
|
295
371
|
/** 用户从 /ccstyle 关掉本扩展启动头时,恢复官方 header。 */
|
|
@@ -115,18 +115,30 @@ export default function (pi: ExtensionAPI): void {
|
|
|
115
115
|
return parts.length ? `Working... (${parts.join(" · ")})` : "";
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
function workingUiAvailable(): boolean {
|
|
119
|
+
try {
|
|
120
|
+
return activeCtx?.hasUI === true;
|
|
121
|
+
} catch {
|
|
122
|
+
// 会话替换/reload 后捕获的 ctx 失效,getter 抛错;停止驱动 footer。
|
|
123
|
+
turnActive = false;
|
|
124
|
+
activeCtx = null;
|
|
125
|
+
stopRefreshLoop();
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
118
130
|
function restoreDefaultWorkingMessage(): void {
|
|
119
131
|
lastMessage = null;
|
|
120
|
-
if (!
|
|
132
|
+
if (!workingUiAvailable()) return;
|
|
121
133
|
try {
|
|
122
|
-
activeCtx
|
|
134
|
+
activeCtx?.ui?.setWorkingMessage();
|
|
123
135
|
} catch {
|
|
124
136
|
// Noop when the TUI is unavailable.
|
|
125
137
|
}
|
|
126
138
|
}
|
|
127
139
|
|
|
128
140
|
function syncWorkingMessage(force = false): void {
|
|
129
|
-
if (!
|
|
141
|
+
if (!workingUiAvailable()) return;
|
|
130
142
|
const next = buildWorkingMessage();
|
|
131
143
|
if (!next) {
|
|
132
144
|
if (force) restoreDefaultWorkingMessage();
|
|
@@ -135,7 +147,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
135
147
|
if (!force && next === lastMessage) return;
|
|
136
148
|
lastMessage = next;
|
|
137
149
|
try {
|
|
138
|
-
activeCtx
|
|
150
|
+
activeCtx?.ui?.setWorkingMessage(next);
|
|
139
151
|
} catch {
|
|
140
152
|
// Noop when the TUI is unavailable.
|
|
141
153
|
}
|
|
@@ -147,9 +159,12 @@ export default function (pi: ExtensionAPI): void {
|
|
|
147
159
|
refreshTimer = null;
|
|
148
160
|
try {
|
|
149
161
|
syncWorkingMessage();
|
|
150
|
-
}
|
|
151
|
-
|
|
162
|
+
} catch {
|
|
163
|
+
// 定时器内的异常会成为 uncaughtException 终止 Pi;装饰性刷新直接停止。
|
|
164
|
+
turnActive = false;
|
|
165
|
+
return;
|
|
152
166
|
}
|
|
167
|
+
scheduleRefreshTick();
|
|
153
168
|
}, REFRESH_INTERVAL_MS);
|
|
154
169
|
refreshTimer.unref?.();
|
|
155
170
|
}
|
package/extensions/index.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { config } from "./config/config.ts";
|
|
|
5
5
|
import piAliases from "./feature/shell/aliases.ts";
|
|
6
6
|
import { installFlushDockedBash } from "./feature/shell/flush-docked-bash.ts";
|
|
7
7
|
import customFooter from "./feature/shell/footer.ts";
|
|
8
|
-
import piStartupHeader from "./feature/shell/startup-header.ts";
|
|
8
|
+
import piStartupHeader, { installEarlyStartupHeader } from "./feature/shell/startup-header.ts";
|
|
9
9
|
import workingMessage from "./feature/shell/working-message.ts";
|
|
10
10
|
|
|
11
11
|
// feature
|
|
@@ -23,6 +23,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
23
23
|
// shell chrome
|
|
24
24
|
if (config.enableAliases) piAliases(pi);
|
|
25
25
|
installFlushDockedBash();
|
|
26
|
+
installEarlyStartupHeader();
|
|
26
27
|
piStartupHeader(pi);
|
|
27
28
|
if (config.enableWorkingMessage) workingMessage(pi);
|
|
28
29
|
customFooter(pi);
|
|
@@ -1167,6 +1167,7 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
1167
1167
|
stopRoundTick();
|
|
1168
1168
|
return;
|
|
1169
1169
|
}
|
|
1170
|
+
settleStrandedRounds();
|
|
1170
1171
|
try {
|
|
1171
1172
|
// 非 force:保留 fullscreen 布局缓存和差分绘制。
|
|
1172
1173
|
uiRef?.requestRender?.();
|
|
@@ -1320,12 +1321,36 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
1320
1321
|
return found;
|
|
1321
1322
|
};
|
|
1322
1323
|
|
|
1324
|
+
/** 组件在树里的直属父容器。TUI 切换/reparent 后缓存的 container 会过期。 */
|
|
1325
|
+
const containerOfHost = (host: any): any => {
|
|
1326
|
+
let found: any;
|
|
1327
|
+
walkComponentTree(getToolMouseTui(), (value: any) => {
|
|
1328
|
+
if (found) return false;
|
|
1329
|
+
if (Array.isArray(value?.children) && value.children.includes(host)) {
|
|
1330
|
+
found = value;
|
|
1331
|
+
return false;
|
|
1332
|
+
}
|
|
1333
|
+
return undefined;
|
|
1334
|
+
});
|
|
1335
|
+
return found;
|
|
1336
|
+
};
|
|
1337
|
+
|
|
1338
|
+
/** 尾行实际所在容器;缓存的 container 过期时按树找回来。 */
|
|
1339
|
+
const tailHostContainer = (tail: MountedRoundTail): any =>
|
|
1340
|
+
containerOfHost(tail.host) ??
|
|
1341
|
+
(tail.container?.children?.includes?.(tail.host) === true ? tail.container : undefined);
|
|
1342
|
+
|
|
1343
|
+
/** 摘尾行:按实际所在容器摘,避免缓存过期时摘了个空、行留在树上。 */
|
|
1344
|
+
const removeTailHost = (tail: MountedRoundTail): void => {
|
|
1345
|
+
tailHostContainer(tail)?.removeChild?.(tail.host);
|
|
1346
|
+
};
|
|
1347
|
+
|
|
1323
1348
|
const ensureTailHost = (round: CompactRound): MountedRoundTail => {
|
|
1324
1349
|
if (round.tail) return round.tail;
|
|
1325
1350
|
// 同一 anchor 上残留的旧回合尾行(reset/重组后建新回合)先摘除,避免双摘要。
|
|
1326
1351
|
const stale = tailByAnchor.get(round.anchor);
|
|
1327
1352
|
if (stale) {
|
|
1328
|
-
|
|
1353
|
+
removeTailHost(stale);
|
|
1329
1354
|
mountedTails.delete(stale);
|
|
1330
1355
|
tailByAnchor.delete(round.anchor);
|
|
1331
1356
|
}
|
|
@@ -1410,7 +1435,7 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
1410
1435
|
const tail = round.tail;
|
|
1411
1436
|
if (!tail) return;
|
|
1412
1437
|
round.tail = undefined;
|
|
1413
|
-
|
|
1438
|
+
removeTailHost(tail);
|
|
1414
1439
|
mountedTails.delete(tail);
|
|
1415
1440
|
if (tailByAnchor.get(round.anchor) === tail) tailByAnchor.delete(round.anchor);
|
|
1416
1441
|
};
|
|
@@ -1442,7 +1467,11 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
1442
1467
|
const stale = tail.round as CompactRound | undefined;
|
|
1443
1468
|
if (
|
|
1444
1469
|
stale &&
|
|
1445
|
-
|
|
1470
|
+
// 先认组件:重放时消息对象未必是同一个(会话重读/重建),
|
|
1471
|
+
// 只认消息身份会漏掉本该接回的回合,摘要行就此掉队。
|
|
1472
|
+
(stale.anchor === round.anchor ||
|
|
1473
|
+
stale.messages.has(round.anchor) ||
|
|
1474
|
+
stale.detachedMessages.includes(message))
|
|
1446
1475
|
) {
|
|
1447
1476
|
prior = stale;
|
|
1448
1477
|
break;
|
|
@@ -1815,12 +1844,14 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
1815
1844
|
// 一致;同 anchor 重建回合时 ensureTailHost 去重摘除),非 compact 摘掉,
|
|
1816
1845
|
// 别把紧凑摘要带进 default 渲染。
|
|
1817
1846
|
for (const tail of [...mountedTails]) {
|
|
1818
|
-
|
|
1847
|
+
const container = tailHostContainer(tail);
|
|
1848
|
+
if (!container) {
|
|
1819
1849
|
mountedTails.delete(tail);
|
|
1820
1850
|
continue;
|
|
1821
1851
|
}
|
|
1852
|
+
tail.container = container;
|
|
1822
1853
|
if (config.mode === "compact") continue;
|
|
1823
|
-
|
|
1854
|
+
container.removeChild(tail.host);
|
|
1824
1855
|
mountedTails.delete(tail);
|
|
1825
1856
|
if (tail.round?.tail === tail) tail.round.tail = undefined;
|
|
1826
1857
|
if (tail.round?.anchor && tailByAnchor.get(tail.round.anchor) === tail) {
|
|
@@ -1831,6 +1862,19 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
1831
1862
|
stopRoundTick();
|
|
1832
1863
|
};
|
|
1833
1864
|
|
|
1865
|
+
/**
|
|
1866
|
+
* 兜底:resetRounds 会丢掉回合索引但保留已挂的尾行,重放没能接回的回合会一直
|
|
1867
|
+
* active——尾行永远显示 Running...(时长还在涨)却不再计入新工具。既不是活动
|
|
1868
|
+
* 回合也不在收尾静默期的活回合就地收尾,不让它永远挂在屏幕上。
|
|
1869
|
+
*/
|
|
1870
|
+
const settleStrandedRounds = (): void => {
|
|
1871
|
+
for (const tail of [...mountedTails]) {
|
|
1872
|
+
const round = tail.round as CompactRound | undefined;
|
|
1873
|
+
if (!round?.active || round === activeRound || pendingFoldRounds.has(round)) continue;
|
|
1874
|
+
endRound(round, true);
|
|
1875
|
+
}
|
|
1876
|
+
};
|
|
1877
|
+
|
|
1834
1878
|
patch.assistantInstalled = function (this: any, message: any, isStreaming?: boolean) {
|
|
1835
1879
|
const self = this as any;
|
|
1836
1880
|
// 同 compact-thinking:isStreaming 丢失 → mermaid 流式误渲染来回闪。
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
toolCallSummary,
|
|
20
20
|
type ToolCallSummary,
|
|
21
21
|
} from "./names.ts";
|
|
22
|
+
import { toolViewportWidth } from "./result.ts";
|
|
22
23
|
import {
|
|
23
24
|
patchRegistry,
|
|
24
25
|
TOOL_GROUPING_GENERATION_KEY as GENERATION_KEY,
|
|
@@ -378,7 +379,7 @@ export class ToolGroupComponent extends Container {
|
|
|
378
379
|
"",
|
|
379
380
|
truncateToWidth(
|
|
380
381
|
` ${fg(overallColor, "●")} ${label}: ${countText}${nameList} ${hint}`,
|
|
381
|
-
width,
|
|
382
|
+
toolViewportWidth(width),
|
|
382
383
|
"…",
|
|
383
384
|
),
|
|
384
385
|
];
|
|
@@ -411,11 +412,13 @@ export class ToolGroupComponent extends Container {
|
|
|
411
412
|
const summary = toolSummary(tool);
|
|
412
413
|
const prefix = ` ${fg("dim", branch)} ${fg(color, statusIcon(toolStatus))} `;
|
|
413
414
|
const detail = fg("dim", summary.detail);
|
|
414
|
-
|
|
415
|
+
// 与单工具卡标题同宽,宽屏右侧留白一致
|
|
416
|
+
const rowWidth = toolViewportWidth(width);
|
|
417
|
+
const mainWidth = Math.max(0, rowWidth - visibleWidth(prefix) - visibleWidth(detail));
|
|
415
418
|
lines.push(
|
|
416
419
|
truncateToWidth(
|
|
417
420
|
`${prefix}${renderToolSummary(summary, mainWidth, fg)}${detail}`,
|
|
418
|
-
|
|
421
|
+
rowWidth,
|
|
419
422
|
"",
|
|
420
423
|
),
|
|
421
424
|
);
|
|
@@ -5,8 +5,13 @@ import { config } from "../../config/config.ts";
|
|
|
5
5
|
import { oneLine } from "../../utils/format.ts";
|
|
6
6
|
import { headTruncateToWidth } from "./result.ts";
|
|
7
7
|
|
|
8
|
+
/** inputClip=0 时不设字符上限,交给渲染时的实际宽度截断。 */
|
|
9
|
+
function clipLimit(): number {
|
|
10
|
+
return config.inputClip > 0 ? config.inputClip : Number.POSITIVE_INFINITY;
|
|
11
|
+
}
|
|
12
|
+
|
|
8
13
|
function clip(value: unknown): string {
|
|
9
|
-
return oneLine(value,
|
|
14
|
+
return oneLine(value, clipLimit());
|
|
10
15
|
}
|
|
11
16
|
|
|
12
17
|
/** 载荷(入参 JSON / 脚本代码)至少留出这么多宽度才显示,否则整段省略(避免只剩一个孤零零的 " {…")。 */
|
|
@@ -125,7 +130,7 @@ export function truncatePathToWidth(path: string, width: number): string {
|
|
|
125
130
|
|
|
126
131
|
/** 路径展示的统一入口:相对化后按配置和当前可用宽度截断。 */
|
|
127
132
|
export function formatDisplayPath(value: unknown, cwd: string | undefined, width: number): string {
|
|
128
|
-
return truncatePathToWidth(displayPath(value, cwd), Math.min(width,
|
|
133
|
+
return truncatePathToWidth(displayPath(value, cwd), Math.min(width, clipLimit()));
|
|
129
134
|
}
|
|
130
135
|
|
|
131
136
|
/** 按最终终端宽度渲染摘要;路径摘要不会再被整行头部截断。 */
|
|
@@ -134,7 +139,7 @@ export function fitToolCallSummary(summary: ToolCallSummary, width: number): str
|
|
|
134
139
|
const prefix = summary.path.prefix;
|
|
135
140
|
const pathWidth = Math.max(0, width - visibleWidth(prefix) - 1);
|
|
136
141
|
if (pathWidth <= 0) return headToWidth(prefix, width, "…");
|
|
137
|
-
return `${prefix} ${truncatePathToWidth(summary.path.value, Math.min(pathWidth,
|
|
142
|
+
return `${prefix} ${truncatePathToWidth(summary.path.value, Math.min(pathWidth, clipLimit()))}`;
|
|
138
143
|
}
|
|
139
144
|
|
|
140
145
|
function pathSummary(
|
|
@@ -145,7 +150,7 @@ function pathSummary(
|
|
|
145
150
|
): ToolCallSummary {
|
|
146
151
|
const path = displayPath(value, cwd);
|
|
147
152
|
return {
|
|
148
|
-
main: `${prefix} ${truncatePathToWidth(path,
|
|
153
|
+
main: `${prefix} ${truncatePathToWidth(path, clipLimit())}`,
|
|
149
154
|
detail,
|
|
150
155
|
path: { prefix, value: path },
|
|
151
156
|
};
|
|
@@ -7,9 +7,15 @@ import { getToolMouseTui } from "../mouse/scroll.ts";
|
|
|
7
7
|
import { sanitizeToolResultText } from "../../utils/tool-result-sanitize.ts";
|
|
8
8
|
|
|
9
9
|
const TOOL_VIEWPORT_WIDTH_RATIO = 0.8;
|
|
10
|
+
/** 宽屏右侧留白上限:比例留白超过这么多列时改用固定留白。 */
|
|
11
|
+
const TOOL_VIEWPORT_MAX_GUTTER = 24;
|
|
10
12
|
|
|
11
13
|
export function toolViewportWidth(width: number): number {
|
|
12
|
-
return Math.max(
|
|
14
|
+
return Math.max(
|
|
15
|
+
1,
|
|
16
|
+
Math.floor(width * TOOL_VIEWPORT_WIDTH_RATIO),
|
|
17
|
+
Math.floor(width) - TOOL_VIEWPORT_MAX_GUTTER,
|
|
18
|
+
);
|
|
13
19
|
}
|
|
14
20
|
|
|
15
21
|
/** 与默认工具结果相同的一级缩进包装。子组件只扣始终加上的 1 列;↳ 行多出的 2 列由 truncateToWidth 吃掉。 */
|
|
@@ -49,6 +49,8 @@ export const TOOL_MOUSE_TUI_SLOT = Symbol.for("pi.ccstyle.tool-mouse-tui");
|
|
|
49
49
|
export const SCROLL_BUTTON_STATE_SLOT = Symbol.for("pi.ccstyle.scroll-button-state");
|
|
50
50
|
export const OFFICIAL_SCROLL_TO_END_KEY = Symbol.for("pi.ccstyle.official-scroll-to-end");
|
|
51
51
|
export const FLUSH_DOCKED_BASH_PATCH = Symbol.for("pi.ccstyle.flush-docked-bash-patch");
|
|
52
|
+
export const EARLY_STARTUP_HEADER_PATCH = Symbol.for("pi.ccstyle.early-startup-header-patch");
|
|
53
|
+
export const EARLY_STARTUP_HEADER_SWAP_KEY = Symbol.for("pi.ccstyle.early-startup-header-swap");
|
|
52
54
|
export const TOOL_HOVER_STATE_KEY = Symbol.for("pi.ccstyle.tool-hover-state");
|
|
53
55
|
|
|
54
56
|
// ── rich diff 组件标记 ──
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-cc-extensions",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.9",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Claude Code-style TUI output with some personal touches and handy utilities.",
|
|
6
6
|
"type": "module",
|
|
@@ -56,15 +56,18 @@
|
|
|
56
56
|
},
|
|
57
57
|
"bundledDependencies": [],
|
|
58
58
|
"peerDependencies": {
|
|
59
|
-
"@earendil-works/pi-ai": "
|
|
60
|
-
"@earendil-works/pi-coding-agent": "
|
|
61
|
-
"@earendil-works/pi-tui": "
|
|
59
|
+
"@earendil-works/pi-ai": "*",
|
|
60
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
61
|
+
"@earendil-works/pi-tui": "*"
|
|
62
62
|
},
|
|
63
63
|
"engines": {
|
|
64
64
|
"node": ">=22.19.0"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"@biomejs/biome": "2.5.5",
|
|
68
|
+
"@earendil-works/pi-ai": "0.99.1",
|
|
69
|
+
"@earendil-works/pi-coding-agent": "0.99.1",
|
|
70
|
+
"@earendil-works/pi-tui": "0.99.1",
|
|
68
71
|
"@types/node": "^26.1.1",
|
|
69
72
|
"typescript": "^5.9.3"
|
|
70
73
|
}
|