pi-cc-extensions 0.8.52 → 0.8.53
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.
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
formatThoughtDuration,
|
|
19
19
|
styleCompactThinkingText,
|
|
20
20
|
} from "../renderer/compact-mode.ts";
|
|
21
|
+
import { refreshMountedTranscript } from "../renderer/transcript-refresh.ts";
|
|
21
22
|
// 保持导出兼容:渲染函数已并入 renderer/compact-mode.ts,这里 re-export。
|
|
22
23
|
export { animateCompactThinkingText, formatThoughtDuration, styleCompactThinkingText };
|
|
23
24
|
|
|
@@ -286,7 +287,6 @@ function compactThinking(pi: ExtensionAPI) {
|
|
|
286
287
|
};
|
|
287
288
|
|
|
288
289
|
const completedDurations = new Map<number, Map<number, number>>();
|
|
289
|
-
const renderedComponents = new Set<AssistantMessageComponentLike>();
|
|
290
290
|
const streamingComponents = new Set<AssistantMessageComponentLike>();
|
|
291
291
|
let activeThinking: ActiveThinking | undefined;
|
|
292
292
|
let activeTheme: Theme | undefined;
|
|
@@ -298,14 +298,6 @@ function compactThinking(pi: ExtensionAPI) {
|
|
|
298
298
|
let compactSummaryActive = false;
|
|
299
299
|
let patchInstalled = true;
|
|
300
300
|
|
|
301
|
-
function refreshRenderedComponents() {
|
|
302
|
-
for (const component of renderedComponents) {
|
|
303
|
-
const self = component as unknown as AssistantInternals;
|
|
304
|
-
if (self.lastMessage) self.updateContent(self.lastMessage);
|
|
305
|
-
}
|
|
306
|
-
activeTui?.requestRender(true);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
301
|
function thinkingStyle(text: string) {
|
|
310
302
|
return styleCompactThinkingText(text, activeTheme);
|
|
311
303
|
}
|
|
@@ -346,7 +338,6 @@ function compactThinking(pi: ExtensionAPI) {
|
|
|
346
338
|
const component = this as AssistantMessageComponentLike;
|
|
347
339
|
const self = this as unknown as AssistantInternals;
|
|
348
340
|
self.lastMessage = message;
|
|
349
|
-
renderedComponents.add(component);
|
|
350
341
|
latestComponent = component;
|
|
351
342
|
latestComponentTimestamp = message.timestamp;
|
|
352
343
|
|
|
@@ -455,8 +446,10 @@ function compactThinking(pi: ExtensionAPI) {
|
|
|
455
446
|
(durationText ? thinkingStyle(` · ${durationText}`) : "");
|
|
456
447
|
} else {
|
|
457
448
|
// Completed compact blocks use one provider-independent status line.
|
|
449
|
+
// 无时长记录(中断流/旧会话/entry 缺失或索引错位)时不回退到加载文案
|
|
450
|
+
// "Thinking...":改用摘要标题或中性 "Thought"。
|
|
458
451
|
heading = thinkingStyle(
|
|
459
|
-
durationText ? `Thought for ${durationText}` :
|
|
452
|
+
durationText ? `Thought for ${durationText}` : (latestSummary?.title ?? "Thought"),
|
|
460
453
|
);
|
|
461
454
|
}
|
|
462
455
|
self.contentContainer.addChild(new Text(heading, self.outputPad, 0));
|
|
@@ -696,20 +689,24 @@ function compactThinking(pi: ExtensionAPI) {
|
|
|
696
689
|
|
|
697
690
|
// An empty widget gives the animation loop access to requestRender without
|
|
698
691
|
// enabling terminal mouse reporting or intercepting native scrollback input.
|
|
692
|
+
// setWidget 的 factory 同步执行,此刻补丁已安装。
|
|
699
693
|
ctx.ui.setWidget(WIDGET_ID, (tui) => {
|
|
700
694
|
activeTui = tui;
|
|
701
695
|
return { render: () => [], invalidate() {} };
|
|
702
696
|
});
|
|
703
697
|
|
|
704
|
-
//
|
|
705
|
-
//
|
|
706
|
-
//
|
|
707
|
-
|
|
698
|
+
// pi 在 reload/resume 时先于 session_start 用原始原型重建聊天组件
|
|
699
|
+
// (rebuildChatFromMessages / renderCurrentSessionState)。由共享的
|
|
700
|
+
// refreshMountedTranscript 扫描挂载树重绘这些组件,恢复 compact 渲染、
|
|
701
|
+
// 持久化时长与工具调用显示。
|
|
702
|
+
refreshMountedTranscript(activeTui);
|
|
703
|
+
activeTui?.requestRender(true);
|
|
708
704
|
});
|
|
709
705
|
|
|
710
706
|
pi.on("session_tree", (_event, ctx) => {
|
|
711
707
|
restoreDurationEntries(ctx.sessionManager.getBranch(), completedDurations);
|
|
712
|
-
|
|
708
|
+
refreshMountedTranscript(activeTui);
|
|
709
|
+
activeTui?.requestRender(true);
|
|
713
710
|
});
|
|
714
711
|
|
|
715
712
|
pi.on("session_shutdown", (_event, ctx) => {
|
|
@@ -726,7 +723,6 @@ function compactThinking(pi: ExtensionAPI) {
|
|
|
726
723
|
latestComponent = undefined;
|
|
727
724
|
latestComponentTimestamp = undefined;
|
|
728
725
|
completedDurations.clear();
|
|
729
|
-
renderedComponents.clear();
|
|
730
726
|
streamingComponents.clear();
|
|
731
727
|
if (ctx.mode === "tui") ctx.ui.setWidget(WIDGET_ID, undefined);
|
|
732
728
|
|
|
@@ -129,6 +129,18 @@ function linkifyUrls(markdown: string): string {
|
|
|
129
129
|
return out.join("\n");
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
// ============================================================================
|
|
133
|
+
// 圈数字 → 半角括号
|
|
134
|
+
// ============================================================================
|
|
135
|
+
|
|
136
|
+
// Nerd Font 补丁字形(U+2460-U+2473)ink 超界,DirectWrite 下渲染会压住相邻字符;
|
|
137
|
+
// 转成 ASCII "(n)" 后任何字体下宽度一致。U+2460-U+2473 连续,差值即序号。
|
|
138
|
+
const CIRCLED_RE = /[①-⑳]/g;
|
|
139
|
+
|
|
140
|
+
function deCircled(markdown: string): string {
|
|
141
|
+
return markdown.replace(CIRCLED_RE, (ch) => `(${ch.codePointAt(0)! - 0x2460 + 1})`);
|
|
142
|
+
}
|
|
143
|
+
|
|
132
144
|
// ============================================================================
|
|
133
145
|
// 注册
|
|
134
146
|
// ============================================================================
|
|
@@ -140,6 +152,8 @@ export default function (pi: ExtensionAPI): void {
|
|
|
140
152
|
const { messageType, isStreaming = false } = context ?? {};
|
|
141
153
|
// 与官方推荐一致:thinking 块与流式中间态不转换
|
|
142
154
|
if (messageType === "assistant-thinking" || isStreaming) return markdown;
|
|
155
|
+
// 0. 圈数字转半角括号(Nerd Font 补丁字形缺陷规避)
|
|
156
|
+
markdown = deCircled(markdown);
|
|
143
157
|
// 1. Mermaid 方言渲染
|
|
144
158
|
markdown = renderDiagrams(markdown, context);
|
|
145
159
|
// 2. GitHub 风格提示框
|
|
@@ -22,6 +22,7 @@ import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
|
22
22
|
import { config, getToolDisplayConfig } from "../config/config.ts";
|
|
23
23
|
import { toolLoadingIcon } from "../utils/tool-loading-icon.ts";
|
|
24
24
|
import { sanitizeToolResultText } from "../utils/tool-result-sanitize.ts";
|
|
25
|
+
import { refreshTranscriptComponent } from "./transcript-refresh.ts";
|
|
25
26
|
import { getMessageDisplayTheme } from "./message-display.ts";
|
|
26
27
|
import { showMoreHintText } from "./show-more-hint.ts";
|
|
27
28
|
import {
|
|
@@ -927,15 +928,15 @@ function refreshTrackedComponents(): void {
|
|
|
927
928
|
for (const component of [...trackedAssistantComponents]) {
|
|
928
929
|
try {
|
|
929
930
|
if (config.mode !== "compact") detachAssistantExpansion(component);
|
|
930
|
-
|
|
931
|
-
else component.invalidate?.();
|
|
931
|
+
refreshTranscriptComponent(component);
|
|
932
932
|
} catch {
|
|
933
933
|
trackedAssistantComponents.delete(component);
|
|
934
934
|
}
|
|
935
935
|
}
|
|
936
936
|
for (const component of [...trackedToolComponents]) {
|
|
937
937
|
try {
|
|
938
|
-
|
|
938
|
+
// 共享实现负责 updateDisplay;invalidate 属于 compact-mode 的跟踪语义。
|
|
939
|
+
refreshTranscriptComponent(component);
|
|
939
940
|
component.invalidate?.();
|
|
940
941
|
} catch {
|
|
941
942
|
trackedToolComponents.delete(component);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
2
|
+
import { AssistantMessageComponent, ToolExecutionComponent } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
|
|
4
|
+
// pi-coding-agent 类型声明中 AssistantMessageComponent 仅能以 value 形式使用,
|
|
5
|
+
// updateContent/lastMessage 用结构化类型访问。
|
|
6
|
+
type TranscriptComponentInternals = {
|
|
7
|
+
lastMessage?: AssistantMessage;
|
|
8
|
+
updateContent(message: AssistantMessage): void;
|
|
9
|
+
invalidate?(): void;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 重绘单个已构造的组件:assistant 用最新消息重渲染(compact thinking),
|
|
14
|
+
* tool 强制 updateDisplay(其内容可能仍在旧 shell 容器中)。
|
|
15
|
+
* 不吞异常——由调用方决定如何处理失败。
|
|
16
|
+
*/
|
|
17
|
+
export function refreshTranscriptComponent(value: unknown): boolean {
|
|
18
|
+
if (!value || typeof value !== "object") return false;
|
|
19
|
+
if (value instanceof AssistantMessageComponent) {
|
|
20
|
+
const self = value as unknown as TranscriptComponentInternals;
|
|
21
|
+
if (self.lastMessage) self.updateContent(self.lastMessage);
|
|
22
|
+
else self.invalidate?.();
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
if (value instanceof ToolExecutionComponent) {
|
|
26
|
+
// updateDisplay 是私有方法,但它是 resume 后让内容回到正确
|
|
27
|
+
// shell 容器的唯一入口(原型链已含全局工具渲染补丁)。
|
|
28
|
+
(value as unknown as { updateDisplay(): void }).updateDisplay();
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 扫描挂载树,重绘所有已构造的 assistant/tool 组件。
|
|
36
|
+
* 用于 reload/resume/compaction 后"pi 用原始原型重建、补丁事后才装"的场景。
|
|
37
|
+
* 遍历 value.children 与 value.getMountedRoots?.()(seen 集合防环),
|
|
38
|
+
* 单个组件失败用 try/catch 隔离,不影响其余组件。
|
|
39
|
+
*/
|
|
40
|
+
// compact-mode 把补丁暴露在此 Symbol 上(见 compact-mode.ts),避免循环依赖。
|
|
41
|
+
const COMPACT_MODE_PATCH_KEY = Symbol.for("pi.ccstyle.compact-mode-patch");
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 补丁链序不变量:compact 模式下 compact-mode 必须位于 compact-thinking
|
|
45
|
+
* 外层(round 摘要才能聚合工具统计)。resume/reload 后若无新消息,
|
|
46
|
+
* message_update 触发的重新认领不会执行,这里在任何重绘前先断言一次。
|
|
47
|
+
* 断言幂等:链序已正确时无操作;mode=on 时也安全(外层走 pass-through)。
|
|
48
|
+
*/
|
|
49
|
+
function assertCompactModeOutermost(): void {
|
|
50
|
+
try {
|
|
51
|
+
const patch = (globalThis as any)[COMPACT_MODE_PATCH_KEY];
|
|
52
|
+
if (typeof patch?.assertAssistantOwnership === "function") {
|
|
53
|
+
patch.assertAssistantOwnership();
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
// 无 compact-mode 补丁或断言失败时跳过,扫描仍会执行。
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function refreshMountedTranscript(tui?: unknown): void {
|
|
61
|
+
if (!tui || typeof (tui as any).getMountedRoots !== "function") return;
|
|
62
|
+
// 先修正链序再重绘:扫描触发 updateContent 时 compact-mode 已在外层。
|
|
63
|
+
assertCompactModeOutermost();
|
|
64
|
+
const seen = new Set<object>();
|
|
65
|
+
const visit = (value: any): void => {
|
|
66
|
+
if (!value || typeof value !== "object" || seen.has(value)) return;
|
|
67
|
+
seen.add(value);
|
|
68
|
+
if (Array.isArray(value)) {
|
|
69
|
+
for (const child of value) visit(child);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
refreshTranscriptComponent(value);
|
|
74
|
+
} catch {
|
|
75
|
+
// 单个组件失败不阻断其余组件;后续 session_tree/重绘再试。
|
|
76
|
+
}
|
|
77
|
+
const children = value.children;
|
|
78
|
+
if (Array.isArray(children)) {
|
|
79
|
+
for (const child of children) visit(child);
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const mounted = value.getMountedRoots?.();
|
|
83
|
+
if (Array.isArray(mounted)) {
|
|
84
|
+
for (const root of mounted) visit(root);
|
|
85
|
+
}
|
|
86
|
+
} catch {
|
|
87
|
+
// 惰性 Proxy / 未挂载容器没有 mounted roots。
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
visit(tui);
|
|
91
|
+
}
|
|
@@ -107,8 +107,8 @@ function summaryParts(data: AgentSummaryData): string[] {
|
|
|
107
107
|
const parts: string[] = [];
|
|
108
108
|
if (data.reads) parts.push(`read ${data.reads} file${plural(data.reads)}`);
|
|
109
109
|
if (data.edits) parts.push(`edited ${data.edits} file${plural(data.edits)}`);
|
|
110
|
-
if (data.commands) parts.push(`ran ${data.commands} command${plural(data.commands)}`);
|
|
111
110
|
if (data.others) parts.push(`${data.others} other tool${plural(data.others)}`);
|
|
111
|
+
if (data.commands) parts.push(`ran ${data.commands} command${plural(data.commands)}`);
|
|
112
112
|
if (data.failed) parts.push(`${data.failed} failed`);
|
|
113
113
|
return parts;
|
|
114
114
|
}
|