pi-cc-extensions 0.8.56 → 0.8.58
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/extensions/config/config.ts +40 -9
- package/extensions/config/panel.ts +32 -36
- package/extensions/feature/compact-thinking.ts +12 -12
- package/extensions/feature/context.ts +10 -56
- package/extensions/feature/reference/index.ts +4 -2
- package/extensions/feature/shell/startup-header.ts +1 -1
- package/extensions/feature/shell/working-message.ts +28 -21
- package/extensions/renderer/compact-mode.ts +23 -50
- package/extensions/renderer/default-mode.ts +36 -152
- package/extensions/renderer/index.ts +5 -9
- package/extensions/renderer/markdown-enhance.ts +1 -1
- package/extensions/renderer/mouse/hover.ts +2 -4
- package/extensions/renderer/mouse/interaction.ts +6 -6
- package/extensions/renderer/mouse/packets.ts +16 -66
- package/extensions/renderer/mouse/scroll.ts +13 -7
- package/extensions/renderer/tool/diff/diff-component.ts +107 -0
- package/extensions/renderer/tool/diff/diff-edit-render.ts +198 -0
- package/extensions/renderer/tool/diff/diff-header.ts +136 -0
- package/extensions/renderer/tool/diff/diff-highlight.ts +115 -0
- package/extensions/renderer/tool/diff/diff-inline.ts +312 -0
- package/extensions/renderer/tool/diff/diff-layout.ts +730 -0
- package/extensions/renderer/tool/diff/diff-limits.ts +145 -0
- package/extensions/renderer/tool/diff/diff-palette.ts +410 -0
- package/extensions/renderer/tool/diff/diff-parse.ts +512 -0
- package/extensions/renderer/tool/diff/diff-presentation.ts +2 -2
- package/extensions/renderer/tool/diff/diff-renderer.ts +21 -3046
- package/extensions/renderer/tool/diff/diff-text.ts +45 -0
- package/extensions/renderer/tool/diff/diff-write-render.ts +498 -0
- package/extensions/renderer/tool/diff/index.ts +2 -2
- package/extensions/renderer/tool/diff/line-width-safety.ts +2 -2
- package/extensions/renderer/tool/grouping.ts +19 -143
- package/extensions/renderer/{message-display.ts → tool/message-display.ts} +9 -30
- package/extensions/renderer/tool/names.ts +168 -0
- package/extensions/renderer/tool/result.ts +3 -6
- package/extensions/renderer/transcript-refresh.ts +8 -26
- package/extensions/utils/ansi-text.ts +47 -0
- package/extensions/utils/component-tree.ts +34 -0
- package/extensions/utils/format.ts +7 -0
- package/extensions/utils/patch-keys.ts +121 -0
- package/extensions/utils/sgr-mouse.ts +59 -0
- package/package.json +1 -1
- package/extensions/renderer/tool/diff/render-utils.ts +0 -5
- package/extensions/renderer/tool/diff/types.ts +0 -26
- package/extensions/renderer/tool/diff/write-display-utils.ts +0 -12
- /package/extensions/renderer/{show-more-hint.ts → tool/show-more-hint.ts} +0 -0
|
@@ -1,16 +1,40 @@
|
|
|
1
1
|
import type { CompactThinkingConfig } from "../feature/compact-thinking.ts";
|
|
2
|
-
import {
|
|
3
|
-
DEFAULT_TOOL_DISPLAY_CONFIG,
|
|
4
|
-
type DiffIndicatorMode,
|
|
5
|
-
type DiffViewMode,
|
|
6
|
-
type ToolDisplayConfig,
|
|
7
|
-
} from "../renderer/tool/diff/index.ts";
|
|
8
2
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
9
3
|
import { homedir } from "node:os";
|
|
10
4
|
import { join } from "node:path";
|
|
11
5
|
|
|
12
6
|
export type CompactStyleMode = "on" | "compact" | "off";
|
|
13
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
|
+
diffCollapsedLines: number;
|
|
19
|
+
diffWordWrap: boolean;
|
|
20
|
+
expandedPreviewMaxLines: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const DEFAULT_TOOL_DISPLAY_CONFIG: ToolDisplayConfig = {
|
|
24
|
+
diffViewMode: "auto",
|
|
25
|
+
diffIndicatorMode: "bars",
|
|
26
|
+
diffSplitMinWidth: 120,
|
|
27
|
+
/** Collapsed tool/diff body: ~half a typical terminal after chrome. */
|
|
28
|
+
diffCollapsedLines: 24,
|
|
29
|
+
diffWordWrap: true,
|
|
30
|
+
/**
|
|
31
|
+
* Expanded tool/diff body cap. 40 ≈ one screen of content after title,
|
|
32
|
+
* Input section, editor, and status — keeps the TUI compact.
|
|
33
|
+
* Raise via /ccstyle → Diff → Expanded max lines when reviewing large dumps.
|
|
34
|
+
*/
|
|
35
|
+
expandedPreviewMaxLines: 40,
|
|
36
|
+
};
|
|
37
|
+
|
|
14
38
|
export type Config = {
|
|
15
39
|
mode: CompactStyleMode;
|
|
16
40
|
excludeRenderers: string[];
|
|
@@ -216,7 +240,8 @@ export function formatConfigStatus(source: Config = config): string {
|
|
|
216
240
|
].join(" · ");
|
|
217
241
|
}
|
|
218
242
|
|
|
219
|
-
|
|
243
|
+
/** 进程内唯一的活动配置对象。读取直接用 `config.xxx`;写入必须走 `updateConfig`。 */
|
|
244
|
+
export const config: Config = loadConfig();
|
|
220
245
|
|
|
221
246
|
function loadConfig(): Config {
|
|
222
247
|
try {
|
|
@@ -247,7 +272,13 @@ export function saveConfig() {
|
|
|
247
272
|
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
248
273
|
}
|
|
249
274
|
|
|
250
|
-
/**
|
|
275
|
+
/** 运行时配置写入的唯一入口:合并 + 规范化 + 持久化。 */
|
|
276
|
+
export function updateConfig(partial: Partial<Config>): void {
|
|
277
|
+
Object.assign(config, normalizeConfig({ ...config, ...partial }));
|
|
278
|
+
saveConfig();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** 整体替换配置(default export 的 configOverride 注入路径;就地覆盖,不持久化)。 */
|
|
251
282
|
export function setConfig(next: Config): void {
|
|
252
|
-
config
|
|
283
|
+
Object.assign(config, next);
|
|
253
284
|
}
|
|
@@ -9,7 +9,6 @@ import { Input, SettingsList, matchesKey, truncateToWidth } from "@earendil-work
|
|
|
9
9
|
import type { CompactThinkingController } from "../feature/compact-thinking.ts";
|
|
10
10
|
import { applyStartupHeader } from "../feature/shell/startup-header.ts";
|
|
11
11
|
import type { ToolGroupingHooks } from "../renderer/tool/grouping.ts";
|
|
12
|
-
import type { DiffIndicatorMode, DiffViewMode } from "../renderer/tool/diff/index.ts";
|
|
13
12
|
import {
|
|
14
13
|
config,
|
|
15
14
|
DEFAULT_CONFIG,
|
|
@@ -23,11 +22,14 @@ import {
|
|
|
23
22
|
getCompactThinkingConfig,
|
|
24
23
|
pickPositiveInt,
|
|
25
24
|
pickPositiveNumber,
|
|
26
|
-
saveConfig,
|
|
27
25
|
SCROLL_STEP_LINES_VALUES,
|
|
28
26
|
THINKING_ANIMATION_INTERVAL_VALUES,
|
|
29
27
|
THINKING_PREVIEW_LINES_VALUES,
|
|
28
|
+
updateConfig,
|
|
30
29
|
type CompactStyleMode,
|
|
30
|
+
type Config,
|
|
31
|
+
type DiffIndicatorMode,
|
|
32
|
+
type DiffViewMode,
|
|
31
33
|
} from "./config.ts";
|
|
32
34
|
|
|
33
35
|
/** renderer 注入的渲染副作用,面板自身不触碰渲染状态。 */
|
|
@@ -117,8 +119,7 @@ function buildExcludeRenderersSubmenu(
|
|
|
117
119
|
const excluded = new Set(config.excludeRenderers);
|
|
118
120
|
if (value === "exclude") excluded.add(id);
|
|
119
121
|
else excluded.delete(id);
|
|
120
|
-
|
|
121
|
-
saveConfig();
|
|
122
|
+
updateConfig({ excludeRenderers: [...excluded].sort((a, b) => a.localeCompare(b)) });
|
|
122
123
|
onLiveChange();
|
|
123
124
|
},
|
|
124
125
|
() => onClose(),
|
|
@@ -414,9 +415,8 @@ export async function showCcstylePanel(
|
|
|
414
415
|
// 额外功能开关:字段名与配置布尔字段一一对应,切换后重启生效。
|
|
415
416
|
const featureToggle = featureToggles[id];
|
|
416
417
|
if (featureToggle) {
|
|
417
|
-
(
|
|
418
|
+
updateConfig({ [id]: value === "on" } as Partial<Config>);
|
|
418
419
|
featureToggle.apply(value === "on");
|
|
419
|
-
saveConfig();
|
|
420
420
|
ctx.ui.notify(`Updated ${id}: ${value} (next restart)`, "info");
|
|
421
421
|
return;
|
|
422
422
|
}
|
|
@@ -434,62 +434,57 @@ export async function showCcstylePanel(
|
|
|
434
434
|
excludeSetting.description = excludeRenderersDescription(config.excludeRenderers);
|
|
435
435
|
return;
|
|
436
436
|
case "diffViewMode":
|
|
437
|
-
|
|
437
|
+
updateConfig({ diffViewMode: value as DiffViewMode });
|
|
438
438
|
diffViewSetting.description = diffViewModeDescription(config.diffViewMode);
|
|
439
439
|
break;
|
|
440
440
|
case "diffIndicatorMode":
|
|
441
|
-
|
|
441
|
+
updateConfig({ diffIndicatorMode: value as DiffIndicatorMode });
|
|
442
442
|
diffIndicatorSetting.description = diffIndicatorDescription(config.diffIndicatorMode);
|
|
443
443
|
break;
|
|
444
444
|
case "diffSplitMinWidth":
|
|
445
|
-
|
|
446
|
-
value,
|
|
447
|
-
|
|
448
|
-
40,
|
|
449
|
-
300,
|
|
450
|
-
);
|
|
445
|
+
updateConfig({
|
|
446
|
+
diffSplitMinWidth: pickPositiveInt(value, DEFAULT_CONFIG.diffSplitMinWidth, 40, 300),
|
|
447
|
+
});
|
|
451
448
|
diffSplitSetting.currentValue = String(config.diffSplitMinWidth);
|
|
452
449
|
break;
|
|
453
450
|
case "diffCollapsedLines":
|
|
454
|
-
|
|
455
|
-
value,
|
|
456
|
-
|
|
457
|
-
1,
|
|
458
|
-
500,
|
|
459
|
-
);
|
|
451
|
+
updateConfig({
|
|
452
|
+
diffCollapsedLines: pickPositiveInt(value, DEFAULT_CONFIG.diffCollapsedLines, 1, 500),
|
|
453
|
+
});
|
|
460
454
|
diffCollapsedSetting.currentValue = String(config.diffCollapsedLines);
|
|
461
455
|
break;
|
|
462
456
|
case "diffWordWrap":
|
|
463
|
-
|
|
457
|
+
updateConfig({ diffWordWrap: value === "on" });
|
|
464
458
|
diffWordWrapSetting.description = config.diffWordWrap
|
|
465
459
|
? "Long diff lines wrap within the panel width."
|
|
466
460
|
: "Long diff lines are truncated to the panel width.";
|
|
467
461
|
break;
|
|
468
462
|
case "expandedPreviewMaxLines":
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
463
|
+
updateConfig({
|
|
464
|
+
expandedPreviewMaxLines: pickPositiveInt(
|
|
465
|
+
value,
|
|
466
|
+
DEFAULT_CONFIG.expandedPreviewMaxLines,
|
|
467
|
+
10,
|
|
468
|
+
50_000,
|
|
469
|
+
),
|
|
470
|
+
});
|
|
475
471
|
expandedMaxSetting.currentValue = String(config.expandedPreviewMaxLines);
|
|
476
472
|
break;
|
|
477
473
|
case "useSummaryTitlesAsThinkingTitle":
|
|
478
|
-
|
|
474
|
+
updateConfig({ useSummaryTitlesAsThinkingTitle: value === "on" });
|
|
479
475
|
break;
|
|
480
476
|
case "previewLines":
|
|
481
|
-
|
|
477
|
+
updateConfig({ previewLines: pickPositiveInt(value, DEFAULT_CONFIG.previewLines, 0) });
|
|
482
478
|
thinkingPreviewSetting.currentValue = String(config.previewLines);
|
|
483
479
|
break;
|
|
484
480
|
case "animationIntervalMs":
|
|
485
|
-
|
|
486
|
-
value,
|
|
487
|
-
|
|
488
|
-
);
|
|
481
|
+
updateConfig({
|
|
482
|
+
animationIntervalMs: pickPositiveNumber(value, DEFAULT_CONFIG.animationIntervalMs),
|
|
483
|
+
});
|
|
489
484
|
thinkingAnimationSetting.currentValue = String(config.animationIntervalMs);
|
|
490
485
|
break;
|
|
491
486
|
case "showStartupHeader":
|
|
492
|
-
|
|
487
|
+
updateConfig({ showStartupHeader: value === "on" });
|
|
493
488
|
startupHeaderSetting.description = config.showStartupHeader
|
|
494
489
|
? "Show the custom startup header (logo + tips) on new sessions."
|
|
495
490
|
: "Use Pi's native startup header instead.";
|
|
@@ -497,13 +492,14 @@ export async function showCcstylePanel(
|
|
|
497
492
|
applyStartupHeader(ctx);
|
|
498
493
|
break;
|
|
499
494
|
case "scrollStepLines":
|
|
500
|
-
|
|
495
|
+
updateConfig({
|
|
496
|
+
scrollStepLines: pickPositiveInt(value, DEFAULT_CONFIG.scrollStepLines, 1, 50),
|
|
497
|
+
});
|
|
501
498
|
scrollStepSetting.currentValue = String(config.scrollStepLines);
|
|
502
499
|
break;
|
|
503
500
|
default:
|
|
504
501
|
return;
|
|
505
502
|
}
|
|
506
|
-
saveConfig();
|
|
507
503
|
compactThinking?.updateConfig(getCompactThinkingConfig());
|
|
508
504
|
hooks.refreshCurrentTranscript(ctx);
|
|
509
505
|
ctx.ui.notify(`Updated ${id}: ${value}`, "info");
|
|
@@ -19,6 +19,12 @@ import {
|
|
|
19
19
|
styleCompactThinkingText,
|
|
20
20
|
} from "../renderer/compact-mode.ts";
|
|
21
21
|
import { refreshMountedTranscript } from "../renderer/transcript-refresh.ts";
|
|
22
|
+
import {
|
|
23
|
+
COMPACT_THINKING_OWNER,
|
|
24
|
+
COMPACT_THINKING_PATCH_KEY,
|
|
25
|
+
patchRegistry,
|
|
26
|
+
PROTOTYPE_ORIGINAL_KEY,
|
|
27
|
+
} from "../utils/patch-keys.ts";
|
|
22
28
|
// 保持导出兼容:渲染函数已并入 renderer/compact-mode.ts,这里 re-export。
|
|
23
29
|
export { animateCompactThinkingText, formatThoughtDuration, styleCompactThinkingText };
|
|
24
30
|
|
|
@@ -106,8 +112,6 @@ const config: CompactThinkingConfig = {
|
|
|
106
112
|
};
|
|
107
113
|
|
|
108
114
|
const DURATION_ENTRY_TYPE = "compact-thinking-duration";
|
|
109
|
-
const COMPACT_THINKING_PATCH_KEY = Symbol.for("pi.ccstyle.compact-thinking-update");
|
|
110
|
-
const PROTOTYPE_ORIGINAL_KEY = Symbol.for("pi.ccstyle.prototype-original");
|
|
111
115
|
|
|
112
116
|
/** 当前激活 session 的只读查询委托(compact 渲染层使用)。 */
|
|
113
117
|
type ThinkingDurationQuery = (messageTimestamp: number) => number | undefined;
|
|
@@ -747,8 +751,6 @@ type CompactThinkingOwner = {
|
|
|
747
751
|
stop(event?: any, ctx?: any): void;
|
|
748
752
|
};
|
|
749
753
|
|
|
750
|
-
const COMPACT_THINKING_OWNER = Symbol.for("pi.ccstyle.compact-thinking-owner");
|
|
751
|
-
|
|
752
754
|
type UpstreamHandler = (event: any, ctx: any) => void;
|
|
753
755
|
|
|
754
756
|
export function installCompactThinking(
|
|
@@ -756,10 +758,6 @@ export function installCompactThinking(
|
|
|
756
758
|
initialConfig: CompactThinkingConfig,
|
|
757
759
|
): CompactThinkingController {
|
|
758
760
|
const owner = {};
|
|
759
|
-
const host = globalThis as typeof globalThis & {
|
|
760
|
-
[COMPACT_THINKING_OWNER]?: CompactThinkingOwner;
|
|
761
|
-
};
|
|
762
|
-
|
|
763
761
|
let session: { event: any; ctx: any } | undefined;
|
|
764
762
|
let active = false;
|
|
765
763
|
// Stable pi.on wrappers delegate here so activate/reload never double-binds.
|
|
@@ -795,7 +793,8 @@ export function installCompactThinking(
|
|
|
795
793
|
const shutdown = delegates.get("session_shutdown");
|
|
796
794
|
delegates.clear();
|
|
797
795
|
shutdown?.(event ?? session?.event ?? {}, ctx ?? session?.ctx ?? { mode: "rpc", ui: {} });
|
|
798
|
-
if (
|
|
796
|
+
if (patchRegistry.get<CompactThinkingOwner>(COMPACT_THINKING_OWNER)?.owner === owner)
|
|
797
|
+
patchRegistry.delete(COMPACT_THINKING_OWNER);
|
|
799
798
|
};
|
|
800
799
|
|
|
801
800
|
const activate = (event: any, ctx: any) => {
|
|
@@ -803,7 +802,7 @@ export function installCompactThinking(
|
|
|
803
802
|
// TUI prototype patch or kill its thinking ticker.
|
|
804
803
|
if (ctx?.mode !== "tui") return;
|
|
805
804
|
|
|
806
|
-
|
|
805
|
+
patchRegistry.get<CompactThinkingOwner>(COMPACT_THINKING_OWNER)?.stop(event, ctx);
|
|
807
806
|
session = { event, ctx };
|
|
808
807
|
|
|
809
808
|
// 配置统一由 claude-code-style 管控,加载时覆盖库默认值。
|
|
@@ -828,7 +827,7 @@ export function installCompactThinking(
|
|
|
828
827
|
} as unknown as ExtensionAPI);
|
|
829
828
|
|
|
830
829
|
active = true;
|
|
831
|
-
|
|
830
|
+
patchRegistry.install(COMPACT_THINKING_OWNER, { owner, stop });
|
|
832
831
|
};
|
|
833
832
|
|
|
834
833
|
pi.on("session_start", (event, ctx) => {
|
|
@@ -836,7 +835,8 @@ export function installCompactThinking(
|
|
|
836
835
|
activate(event, ctx);
|
|
837
836
|
});
|
|
838
837
|
pi.on("session_shutdown", (event, ctx) => {
|
|
839
|
-
if (
|
|
838
|
+
if (patchRegistry.get<CompactThinkingOwner>(COMPACT_THINKING_OWNER)?.owner === owner)
|
|
839
|
+
stop(event, ctx);
|
|
840
840
|
session = undefined;
|
|
841
841
|
});
|
|
842
842
|
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type BuildSystemPromptOptions,
|
|
2
3
|
type ExtensionAPI,
|
|
3
4
|
type ExtensionCommandContext,
|
|
5
|
+
type ToolInfo,
|
|
4
6
|
estimateTokens,
|
|
5
7
|
getMarkdownTheme,
|
|
6
8
|
} from "@earendil-works/pi-coding-agent";
|
|
7
|
-
import { Key, Markdown, matchesKey,
|
|
9
|
+
import { Key, Markdown, matchesKey, visibleWidth } from "@earendil-works/pi-tui";
|
|
10
|
+
import { mouseBaseButton, parseSgrMousePacket } from "../utils/sgr-mouse.ts";
|
|
11
|
+
import { padLine } from "../utils/format.ts";
|
|
8
12
|
|
|
9
13
|
export type ContextPart = {
|
|
10
14
|
label: string;
|
|
@@ -189,10 +193,6 @@ export async function showTextPreview(
|
|
|
189
193
|
escHitbox = escCloseHitbox({ left: overlayLeft, top: overlayTop, width });
|
|
190
194
|
const visible = wrapped.slice(scrollOffset, scrollOffset + pageSize);
|
|
191
195
|
const border = (text: string) => theme.fg("border", text);
|
|
192
|
-
const padLine = (text: string, lineWidth = inner): string => {
|
|
193
|
-
const truncated = truncateToWidth(text, lineWidth, "…");
|
|
194
|
-
return truncated + " ".repeat(Math.max(0, lineWidth - visibleWidth(truncated)));
|
|
195
|
-
};
|
|
196
196
|
const scrollable = totalLines > pageSize;
|
|
197
197
|
const thumbSize = scrollable
|
|
198
198
|
? Math.max(1, Math.floor((pageSize * pageSize) / totalLines))
|
|
@@ -231,7 +231,7 @@ export async function showTextPreview(
|
|
|
231
231
|
`${border("├")}${border("─".repeat(inner))}${border("┤")}`,
|
|
232
232
|
...bodyRows,
|
|
233
233
|
`${border("├")}${border("─".repeat(inner))}${border("┤")}`,
|
|
234
|
-
`${border("│")}${padLine(theme.fg("dim", ` ${status}`))}${border("│")}`,
|
|
234
|
+
`${border("│")}${padLine(theme.fg("dim", ` ${status}`), inner)}${border("│")}`,
|
|
235
235
|
border(`╰${"─".repeat(inner)}╯`),
|
|
236
236
|
];
|
|
237
237
|
},
|
|
@@ -253,40 +253,12 @@ export async function showTextPreview(
|
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
255
|
|
|
256
|
-
type SgrMousePacket = {
|
|
257
|
-
code: number;
|
|
258
|
-
col: number;
|
|
259
|
-
row: number;
|
|
260
|
-
final: "M" | "m";
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
function parseSgrMousePacket(data: string): SgrMousePacket | null {
|
|
264
|
-
const match = data.match(/^\x1b\[<(\d+);(\d+);(\d+)([Mm])$/);
|
|
265
|
-
if (!match) return null;
|
|
266
|
-
return {
|
|
267
|
-
code: Number(match[1]),
|
|
268
|
-
col: Number(match[2]),
|
|
269
|
-
row: Number(match[3]),
|
|
270
|
-
final: match[4] as "M" | "m",
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
function mouseBaseButton(code: number): number {
|
|
275
|
-
return code & ~(4 | 8 | 16 | 32);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
256
|
const tokenEstimate = (value: unknown): number => {
|
|
279
257
|
if (!value) return 0;
|
|
280
258
|
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
281
259
|
return Math.max(0, Math.ceil(text.length / 4));
|
|
282
260
|
};
|
|
283
261
|
|
|
284
|
-
type ToolPreviewInfo = {
|
|
285
|
-
name: string;
|
|
286
|
-
description?: string;
|
|
287
|
-
promptGuidelines?: string[];
|
|
288
|
-
};
|
|
289
|
-
|
|
290
262
|
export function scaleParts(parts: ContextPart[], target: number): ContextPart[] {
|
|
291
263
|
const estimated = parts.reduce((sum, part) => sum + part.tokens, 0);
|
|
292
264
|
if (estimated === 0 || target <= 0) return parts;
|
|
@@ -309,22 +281,9 @@ export function formatTokens(tokens: number): string {
|
|
|
309
281
|
return `${Math.round(tokens / 1_000)}k`;
|
|
310
282
|
}
|
|
311
283
|
|
|
312
|
-
type SystemPromptOptions = {
|
|
313
|
-
contextFiles?: Array<{ path: string; content: string }>;
|
|
314
|
-
skills?: Array<{
|
|
315
|
-
name: string;
|
|
316
|
-
description?: string;
|
|
317
|
-
filePath?: string;
|
|
318
|
-
disableModelInvocation?: boolean;
|
|
319
|
-
}>;
|
|
320
|
-
selectedTools?: string[];
|
|
321
|
-
toolSnippets?: Record<string, string>;
|
|
322
|
-
promptGuidelines?: string[];
|
|
323
|
-
};
|
|
324
|
-
|
|
325
284
|
type ContextBreakdown = {
|
|
326
285
|
parts: ContextPart[];
|
|
327
|
-
options:
|
|
286
|
+
options: BuildSystemPromptOptions;
|
|
328
287
|
systemPrompt: string;
|
|
329
288
|
};
|
|
330
289
|
|
|
@@ -333,7 +292,7 @@ type ContextBreakdown = {
|
|
|
333
292
|
* so the /context UI does not re-fetch or re-stringify the same sources.
|
|
334
293
|
*/
|
|
335
294
|
function collectContextBreakdown(ctx: ExtensionCommandContext): ContextBreakdown {
|
|
336
|
-
const options = (ctx.getSystemPromptOptions?.() ?? {}) as
|
|
295
|
+
const options = (ctx.getSystemPromptOptions?.() ?? {}) as BuildSystemPromptOptions;
|
|
337
296
|
const systemPrompt = typeof ctx.getSystemPrompt === "function" ? ctx.getSystemPrompt() : "";
|
|
338
297
|
|
|
339
298
|
const contextFileTokens = (options.contextFiles ?? []).reduce(
|
|
@@ -410,8 +369,8 @@ export default function contextUsageExtension(pi: ExtensionAPI) {
|
|
|
410
369
|
}
|
|
411
370
|
|
|
412
371
|
const options = breakdown.options;
|
|
413
|
-
const toolByName = new Map<string,
|
|
414
|
-
|
|
372
|
+
const toolByName = new Map<string, ToolInfo>(
|
|
373
|
+
pi.getAllTools().map((tool) => [tool.name, tool] as const),
|
|
415
374
|
);
|
|
416
375
|
const toolContent = (options.selectedTools ?? []).map((name) => {
|
|
417
376
|
const tool = toolByName.get(name);
|
|
@@ -490,11 +449,6 @@ export default function contextUsageExtension(pi: ExtensionAPI) {
|
|
|
490
449
|
}> = [];
|
|
491
450
|
let escHitbox: { row: number; startCol: number; endCol: number } | undefined;
|
|
492
451
|
|
|
493
|
-
const padLine = (text: string, width: number): string => {
|
|
494
|
-
const truncated = truncateToWidth(text, width, "…");
|
|
495
|
-
return truncated + " ".repeat(Math.max(0, width - visibleWidth(truncated)));
|
|
496
|
-
};
|
|
497
|
-
|
|
498
452
|
return {
|
|
499
453
|
invalidate() {},
|
|
500
454
|
handleInput(data: string) {
|
|
@@ -109,7 +109,7 @@ function trackSubagentFromEvent(data: unknown): void {
|
|
|
109
109
|
// Started event — create new record
|
|
110
110
|
const agent = typeof event.agent === "string" ? event.agent : "";
|
|
111
111
|
const cwd = typeof event.cwd === "string" ? event.cwd : "";
|
|
112
|
-
const startedAt = typeof event.startedAt === "number" ?
|
|
112
|
+
const startedAt = typeof event.startedAt === "number" ? event.startedAt : Date.now();
|
|
113
113
|
liveSubagentRecords.set(runId, {
|
|
114
114
|
runId,
|
|
115
115
|
sessionId,
|
|
@@ -122,7 +122,9 @@ function trackSubagentFromEvent(data: unknown): void {
|
|
|
122
122
|
|
|
123
123
|
function getSubagentManager(): SubagentManager | undefined {
|
|
124
124
|
// Try the global manager first (future-proof), fall back to local records.
|
|
125
|
-
const manager = (globalThis as
|
|
125
|
+
const manager = (globalThis as Record<PropertyKey, unknown>)[SUBAGENT_MANAGER_KEY] as
|
|
126
|
+
| SubagentManager
|
|
127
|
+
| undefined;
|
|
126
128
|
if (manager && typeof manager.getRecord === "function") return manager;
|
|
127
129
|
// If no global manager, use our local tracking.
|
|
128
130
|
if (liveSubagentRecords.size === 0) return undefined;
|
|
@@ -295,7 +295,7 @@ export function applyStartupHeader(ctx: any): void {
|
|
|
295
295
|
ctx.ui.setHeader(undefined);
|
|
296
296
|
return;
|
|
297
297
|
}
|
|
298
|
-
ctx.ui.setHeader((_tui, theme) => ({
|
|
298
|
+
ctx.ui.setHeader((_tui: unknown, theme: any) => ({
|
|
299
299
|
render(width: number): string[] {
|
|
300
300
|
return renderHeaderLines(width, theme);
|
|
301
301
|
},
|
|
@@ -9,21 +9,24 @@ function formatCount(value: number): string {
|
|
|
9
9
|
return new Intl.NumberFormat("en-US").format(value);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
12
|
+
type ContentBlock = {
|
|
13
|
+
type?: unknown;
|
|
14
|
+
text?: unknown;
|
|
15
|
+
thinkingSignature?: { body?: unknown };
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type StreamMessage = {
|
|
19
|
+
content?: unknown;
|
|
20
|
+
usage?: { output?: unknown };
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/** 每个 content index 的可见文本/思考长度;无对应块的 index 保持稀疏洞。 */
|
|
24
|
+
function textBlockLengths(message: StreamMessage): number[] {
|
|
25
|
+
const content = message.content;
|
|
26
|
+
if (!Array.isArray(content)) return [];
|
|
24
27
|
const lengths: number[] = [];
|
|
25
|
-
for (let index = 0; index <
|
|
26
|
-
const block =
|
|
28
|
+
for (let index = 0; index < content.length; index++) {
|
|
29
|
+
const block = content[index] as ContentBlock;
|
|
27
30
|
if (block?.type === "text" && typeof block.text === "string") {
|
|
28
31
|
lengths[index] = block.text.length;
|
|
29
32
|
} else if (block?.type === "thinking" && block.thinkingSignature?.body) {
|
|
@@ -33,11 +36,15 @@ function textBlockLengths(message: any): number[] {
|
|
|
33
36
|
return lengths;
|
|
34
37
|
}
|
|
35
38
|
|
|
36
|
-
function outputUsage(message:
|
|
39
|
+
function outputUsage(message: StreamMessage): number {
|
|
37
40
|
const value = Number(message?.usage?.output);
|
|
38
41
|
return Number.isFinite(value) && value > 0 ? Math.round(value) : 0;
|
|
39
42
|
}
|
|
40
43
|
|
|
44
|
+
type WorkingUi = {
|
|
45
|
+
setWorkingMessage(message?: string): void;
|
|
46
|
+
};
|
|
47
|
+
|
|
41
48
|
/**
|
|
42
49
|
* Extend Pi's footer working row while preserving its spinner and "Working...":
|
|
43
50
|
* `⠋ Working... (↓ 1,234 tokens · 12s)`
|
|
@@ -54,7 +61,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
54
61
|
let providerOutputTokens = 0;
|
|
55
62
|
let refreshTimer: ReturnType<typeof setTimeout> | null = null;
|
|
56
63
|
let lastMessage: string | null = null;
|
|
57
|
-
let activeCtx: { ui:
|
|
64
|
+
let activeCtx: { ui: WorkingUi | undefined; hasUI: boolean } | null = null;
|
|
58
65
|
|
|
59
66
|
function tokenCount(): number {
|
|
60
67
|
return providerOutputTokens || Math.max(0, Math.round(responseLength / 4));
|
|
@@ -66,13 +73,13 @@ export default function (pi: ExtensionAPI): void {
|
|
|
66
73
|
responseLength = Math.max(0, responseLength + responseTextBlockLengths[index] - previous);
|
|
67
74
|
}
|
|
68
75
|
|
|
69
|
-
function resetResponseTracking(message?:
|
|
76
|
+
function resetResponseTracking(message?: StreamMessage): void {
|
|
70
77
|
responseTextBlockLengths = message ? textBlockLengths(message) : [];
|
|
71
|
-
responseLength =
|
|
78
|
+
responseLength = responseTextBlockLengths.reduce((sum, length) => sum + length, 0);
|
|
72
79
|
providerOutputTokens = message ? outputUsage(message) : 0;
|
|
73
80
|
}
|
|
74
81
|
|
|
75
|
-
function updateProviderUsage(message:
|
|
82
|
+
function updateProviderUsage(message: StreamMessage): void {
|
|
76
83
|
const output = outputUsage(message);
|
|
77
84
|
if (output > 0) providerOutputTokens = output;
|
|
78
85
|
}
|
|
@@ -93,7 +100,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
93
100
|
lastMessage = null;
|
|
94
101
|
if (!activeCtx?.hasUI) return;
|
|
95
102
|
try {
|
|
96
|
-
activeCtx.ui
|
|
103
|
+
activeCtx.ui?.setWorkingMessage();
|
|
97
104
|
} catch {
|
|
98
105
|
// Noop when the TUI is unavailable.
|
|
99
106
|
}
|
|
@@ -109,7 +116,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
109
116
|
if (!force && next === lastMessage) return;
|
|
110
117
|
lastMessage = next;
|
|
111
118
|
try {
|
|
112
|
-
activeCtx.ui
|
|
119
|
+
activeCtx.ui?.setWorkingMessage(next);
|
|
113
120
|
} catch {
|
|
114
121
|
// Noop when the TUI is unavailable.
|
|
115
122
|
}
|