pi-cc-extensions 0.9.12 → 1.0.0
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 +2 -1
- package/README.md +2 -1
- package/extensions/config/config.ts +5 -0
- package/extensions/config/panel.ts +21 -6
- package/extensions/renderer/compact-mode.ts +108 -25
- package/extensions/renderer/default-mode.ts +152 -24
- package/extensions/renderer/index.ts +30 -1
- package/extensions/renderer/mouse/interaction.ts +22 -4
- package/extensions/renderer/mouse/sticky-prompt.ts +398 -0
- package/extensions/renderer/tool/ask-user-question.ts +173 -0
- package/extensions/renderer/tool/codemode.ts +4 -8
- package/extensions/renderer/tool/diff/diff-edit-render.ts +6 -6
- package/extensions/renderer/tool/diff/diff-presentation.ts +41 -0
- package/extensions/renderer/tool/diff/diff-renderer.ts +5 -1
- package/extensions/renderer/tool/diff/diff-write-render.ts +35 -9
- package/extensions/renderer/tool/grouping.ts +115 -24
- package/extensions/renderer/tool/names.ts +1 -1
- package/extensions/renderer/tool/result.ts +106 -15
- package/extensions/utils/patch-keys.ts +5 -0
- package/package.json +1 -4
- package/themes/cc-dark.json +1 -1
- package/themes/cc-light.json +1 -1
|
@@ -15,11 +15,13 @@ import {
|
|
|
15
15
|
} from "../utils/patch-keys.ts";
|
|
16
16
|
import { isToolCallHovered } from "./mouse/hover.ts";
|
|
17
17
|
import {
|
|
18
|
-
|
|
18
|
+
collapsedResultSummary,
|
|
19
|
+
formatToolDuration,
|
|
19
20
|
hasExpandableDetail,
|
|
20
21
|
insetComponent,
|
|
22
|
+
isExpandedToolIoView,
|
|
23
|
+
isLiveToolCall,
|
|
21
24
|
isToolExpanded,
|
|
22
|
-
outputLineCount,
|
|
23
25
|
pendingIcon,
|
|
24
26
|
renderCollapsedToolResultToWidth,
|
|
25
27
|
renderExpandedToolResult,
|
|
@@ -31,20 +33,26 @@ import {
|
|
|
31
33
|
toolIconColor,
|
|
32
34
|
toolViewportWidth,
|
|
33
35
|
} from "./tool/result.ts";
|
|
34
|
-
import { oneLine } from "../utils/format.ts";
|
|
35
36
|
import { showMoreHintText } from "./tool/show-more-hint.ts";
|
|
37
|
+
import {
|
|
38
|
+
ASK_USER_QUESTION_TOOL_NAME,
|
|
39
|
+
askUserExpandedBody,
|
|
40
|
+
askUserQuestionCount,
|
|
41
|
+
createAskUserResultComponent,
|
|
42
|
+
} from "./tool/ask-user-question.ts";
|
|
36
43
|
import {
|
|
37
44
|
CODEMODE_TOOL_NAME,
|
|
38
45
|
codemodeExpandedBody,
|
|
39
46
|
createCodemodeResultComponent,
|
|
40
47
|
} from "./tool/codemode.ts";
|
|
41
|
-
import { countWriteDiffStats } from "./tool/diff/diff-renderer.ts";
|
|
48
|
+
import { countWriteDiffStats, writeStatsInCollapsedBody } from "./tool/diff/diff-renderer.ts";
|
|
42
49
|
import { renderRichToolResult, type WriteExecutionMetadataStore } from "./tool/diff/index.ts";
|
|
43
50
|
import { getMessageDisplayTheme } from "./tool/message-display.ts";
|
|
44
51
|
import { mcpToolTitle } from "./tool/mcp-title.ts";
|
|
45
52
|
import {
|
|
46
53
|
fitToolCallSummary,
|
|
47
54
|
humanizeToolLabel,
|
|
55
|
+
SHELL_TOOL_NAMES,
|
|
48
56
|
renderToolSummary,
|
|
49
57
|
toolCallSummary,
|
|
50
58
|
} from "./tool/names.ts";
|
|
@@ -95,6 +103,9 @@ type GlobalToolRenderPatch = {
|
|
|
95
103
|
renderPaint?: (width: number) => string[];
|
|
96
104
|
invalidatePaint?: () => void;
|
|
97
105
|
paintCache?: WeakMap<object, ToolPaintHit>;
|
|
106
|
+
/** 给渲染上下文补 result;卸下时还原。 */
|
|
107
|
+
originalGetRenderContext?: (...args: any[]) => any;
|
|
108
|
+
getRenderContext?: (this: any, ...args: any[]) => any;
|
|
98
109
|
};
|
|
99
110
|
|
|
100
111
|
type ToolExpandedBackgroundPatch = {
|
|
@@ -213,6 +224,27 @@ function renderExpandedTaskResult(
|
|
|
213
224
|
return formatted ? new Text(`↳ ${formatted}`, 0, 0) : undefined;
|
|
214
225
|
}
|
|
215
226
|
|
|
227
|
+
/**
|
|
228
|
+
* shell 的耗时文本。Pi 原生 shell 渲染器会在结果底部报一行,ccstyle 的通用折叠行把它盖掉了。
|
|
229
|
+
*
|
|
230
|
+
* 两条路都要:新 Pi 在渲染上下文里给 `durationMs`(单调,restore 出来的行也带得到),优先用它;
|
|
231
|
+
* 拿不到(旧 Pi、无耗时的结果)再退回自己的时钟——开始时间在 renderCall 记,结束时间在结果定型时定一次。
|
|
232
|
+
* 运行中的实时计时也只能走时钟。
|
|
233
|
+
*/
|
|
234
|
+
function shellDurationText(toolName: string, context: any, isPartial: boolean): string {
|
|
235
|
+
if (!SHELL_TOOL_NAMES.has(toolName)) return "";
|
|
236
|
+
const recorded = context?.durationMs;
|
|
237
|
+
if (!isPartial && typeof recorded === "number" && Number.isFinite(recorded) && recorded >= 0) {
|
|
238
|
+
return formatToolDuration(recorded);
|
|
239
|
+
}
|
|
240
|
+
const startedAt = context?.state?.ccstyleShellStartedAt;
|
|
241
|
+
if (typeof startedAt !== "number") return "";
|
|
242
|
+
const endedAt = context?.state?.ccstyleShellEndedAt;
|
|
243
|
+
const end = typeof endedAt === "number" ? endedAt : Date.now();
|
|
244
|
+
// 时钟回拨时别吐负数;快照固定时钟也会走到这。
|
|
245
|
+
return formatToolDuration(Math.max(0, end - startedAt));
|
|
246
|
+
}
|
|
247
|
+
|
|
216
248
|
/** 用 ccstyle call/result 包装任意工具定义。 */
|
|
217
249
|
function createCcstyleTool(
|
|
218
250
|
originalTool: any,
|
|
@@ -232,11 +264,24 @@ function createCcstyleTool(
|
|
|
232
264
|
return renderDefault(originalTool, "renderCall", [args, theme, context], String(toolName));
|
|
233
265
|
}
|
|
234
266
|
|
|
267
|
+
// shell 的耗时从执行开始算;restore 出来的行没有 executionStarted,不参与。
|
|
268
|
+
if (
|
|
269
|
+
SHELL_TOOL_NAMES.has(toolName) &&
|
|
270
|
+
context?.executionStarted &&
|
|
271
|
+
context.state &&
|
|
272
|
+
context.state.ccstyleShellStartedAt === undefined
|
|
273
|
+
) {
|
|
274
|
+
context.state.ccstyleShellStartedAt = Date.now();
|
|
275
|
+
}
|
|
276
|
+
|
|
235
277
|
const visualState = resolveToolVisualState(context);
|
|
236
278
|
const isPending =
|
|
237
279
|
visualState === "pending" ||
|
|
238
280
|
(!visualState && (context?.isPartial || context?.executionStarted));
|
|
239
|
-
|
|
281
|
+
// 等待窗口(声明流式中或等待派发)也要转:Pi 要到 assistant 消息结束
|
|
282
|
+
// 才派发执行,这段只靠 executionStarted 会一直冻在第一帧。
|
|
283
|
+
const animating =
|
|
284
|
+
isPending && (Boolean(context?.executionStarted) || isLiveToolCall(context?.toolCallId));
|
|
240
285
|
if (animating) scheduleAnimation(context, { light: true });
|
|
241
286
|
const settledRawIcon = isPending ? "" : settledIcon(toolName, visualState);
|
|
242
287
|
const settledIconStyled =
|
|
@@ -251,9 +296,23 @@ function createCcstyleTool(
|
|
|
251
296
|
variant: "default",
|
|
252
297
|
cwd: context?.cwd,
|
|
253
298
|
});
|
|
299
|
+
// ask_user_question:问题原文在折叠结果树里逐条列出,标题只留工具名和问题数,
|
|
300
|
+
// 否则第一个问题会在标题和树里各出现一次。
|
|
301
|
+
if (toolName === ASK_USER_QUESTION_TOOL_NAME) {
|
|
302
|
+
// Pi 的渲染上下文不带 result,问题数从这里取。
|
|
303
|
+
const questionCount = askUserQuestionCount(context?.result, args);
|
|
304
|
+
summary.main = title;
|
|
305
|
+
summary.detail = questionCount === undefined ? "" : ` (+${questionCount})`;
|
|
306
|
+
summary.payload = undefined;
|
|
307
|
+
}
|
|
254
308
|
let writeStatsText = "";
|
|
255
309
|
let writeStatsStyled = "";
|
|
256
|
-
|
|
310
|
+
// 折叠 0 行预览时计数由正文那行(↳ diff +A -D · created …)显示,标题不重复。
|
|
311
|
+
const statsInBody = writeStatsInCollapsedBody(
|
|
312
|
+
Boolean(context?.expanded),
|
|
313
|
+
getToolDisplayConfig(),
|
|
314
|
+
);
|
|
315
|
+
if (toolName === "write" && visualState === "success" && !statsInBody) {
|
|
257
316
|
const meta = writeExecutionMetadata.get(context?.toolCallId);
|
|
258
317
|
const stats = countWriteDiffStats(
|
|
259
318
|
typeof args?.content === "string" ? args.content : undefined,
|
|
@@ -336,11 +395,62 @@ function createCcstyleTool(
|
|
|
336
395
|
isHovered: () => isToolCallHovered(toolCallId),
|
|
337
396
|
});
|
|
338
397
|
}
|
|
398
|
+
// ask_user_question:折叠态摊成问答树,展开态按问题逐条列出选择。
|
|
399
|
+
if (toolName === ASK_USER_QUESTION_TOOL_NAME) {
|
|
400
|
+
if (!options?.isPartial) setToolVisualState(context, isError ? "error" : "success");
|
|
401
|
+
if (options?.isPartial) {
|
|
402
|
+
// 折叠态 1 格缩进:与完成后的 ├/└ 对齐标题行的勾。
|
|
403
|
+
const pending = expanded ? "↳ Pending…" : " ↳ Pending…";
|
|
404
|
+
return new Text(theme.fg("muted", pending), 0, 0);
|
|
405
|
+
}
|
|
406
|
+
if (expanded) {
|
|
407
|
+
return renderExpandedToolResult(
|
|
408
|
+
askUserExpandedBody(result),
|
|
409
|
+
theme,
|
|
410
|
+
isError,
|
|
411
|
+
context?.lastComponent,
|
|
412
|
+
context?.args,
|
|
413
|
+
context,
|
|
414
|
+
true,
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
if (context?.state) context.state.ccstyleIoView = undefined;
|
|
418
|
+
const toolCallId = context?.toolCallId;
|
|
419
|
+
return createAskUserResultComponent({
|
|
420
|
+
result,
|
|
421
|
+
theme,
|
|
422
|
+
isError,
|
|
423
|
+
isHovered: () => isToolCallHovered(toolCallId),
|
|
424
|
+
});
|
|
425
|
+
}
|
|
339
426
|
if (options?.isPartial) {
|
|
340
|
-
//
|
|
427
|
+
// 折叠态保持 3 格:与完成后的 ` ↳ …` 同列,工具跑完那行不横跳。
|
|
428
|
+
// 展开态贴左(Box 提供 1 格 pad)。
|
|
341
429
|
const pending = expanded ? "↳ Pending…" : " ↳ Pending…";
|
|
342
|
-
|
|
430
|
+
if (!SHELL_TOOL_NAMES.has(toolName)) {
|
|
431
|
+
return new Text(theme.fg("muted", pending), 0, 0);
|
|
432
|
+
}
|
|
433
|
+
// shell 运行中报已用时间:数字在 render() 内取,动画 tick 才能带上新值。
|
|
434
|
+
return {
|
|
435
|
+
render(width: number) {
|
|
436
|
+
const elapsed = shellDurationText(toolName, context, true);
|
|
437
|
+
const suffix = elapsed ? theme.fg("dim", ` · ${elapsed}`) : "";
|
|
438
|
+
return [truncateToWidth(theme.fg("muted", pending) + suffix, width, "")];
|
|
439
|
+
},
|
|
440
|
+
invalidate(): void {},
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// shell:结果定型时定一次结束时间,这一行之后不再变。
|
|
445
|
+
if (
|
|
446
|
+
SHELL_TOOL_NAMES.has(toolName) &&
|
|
447
|
+
context?.state &&
|
|
448
|
+
context.state.ccstyleShellEndedAt === undefined
|
|
449
|
+
) {
|
|
450
|
+
context.state.ccstyleShellEndedAt = Date.now();
|
|
343
451
|
}
|
|
452
|
+
// 折叠行接在摘要后,展开态接在 Output 正文后(原生也是在结果底部报一行)。
|
|
453
|
+
const duration = shellDurationText(toolName, context, false);
|
|
344
454
|
|
|
345
455
|
setToolVisualState(context, isError ? "error" : "success");
|
|
346
456
|
const toolCallId = context?.toolCallId;
|
|
@@ -371,26 +481,16 @@ function createCcstyleTool(
|
|
|
371
481
|
if (taskResult) return taskResult;
|
|
372
482
|
}
|
|
373
483
|
const tasks = !isError && toolName === "TaskList" ? parseTaskList(text) : [];
|
|
374
|
-
const outputLines = outputLineCount(result) || countLines(text);
|
|
375
|
-
const lineWord = outputLines === 1 ? "line" : "lines";
|
|
376
|
-
const action = toolName === "read" ? "loaded" : "returned";
|
|
377
484
|
const rendered = tasks.length
|
|
378
485
|
? taskListSummary(tasks)
|
|
379
|
-
: isError
|
|
380
|
-
? text
|
|
381
|
-
? // 72 与原 result.ts 默认一致,保持错误摘要截断宽度。
|
|
382
|
-
oneLine(text, 72)
|
|
383
|
-
: "Failed"
|
|
384
|
-
: outputLines
|
|
385
|
-
? `${outputLines} ${lineWord} ${action}`
|
|
386
|
-
: "Done";
|
|
486
|
+
: collapsedResultSummary({ result, toolName, text, isError });
|
|
387
487
|
const expandable = !expanded && (tasks.length > 0 || hasExpandableDetail(text, args));
|
|
388
488
|
const hintText = showMoreHintText();
|
|
389
489
|
const hintPrefix = expandable ? theme.fg("dim", " · ") : "";
|
|
390
490
|
const hint = expandable ? hintPrefix + theme.fg("dim", hintText) : "";
|
|
391
491
|
const hoveredHint = expandable ? hintPrefix + theme.fg("text", hintText) : "";
|
|
392
492
|
if (expanded) {
|
|
393
|
-
|
|
493
|
+
const view = renderExpandedToolResult(
|
|
394
494
|
text || "",
|
|
395
495
|
theme,
|
|
396
496
|
Boolean(isError),
|
|
@@ -399,8 +499,14 @@ function createCcstyleTool(
|
|
|
399
499
|
context,
|
|
400
500
|
true, // mode=on:贴左,由外层 Box(1,1) 提供 1 格 padding
|
|
401
501
|
);
|
|
502
|
+
// 尾部附注只能挂在 IoView 上:鼠标层按对象识别它,包一层会断掉命中。
|
|
503
|
+
// 与折叠态保持同一写法:裸值,不写 Took。
|
|
504
|
+
if (isExpandedToolIoView(view)) view.setFooter(duration || undefined);
|
|
505
|
+
return view;
|
|
402
506
|
}
|
|
403
507
|
if (context?.state) context.state.ccstyleIoView = undefined;
|
|
508
|
+
// 折叠态的耗时接在摘要后、展开提示前(提示必须留在行尾供鼠标层识别)。
|
|
509
|
+
const body = duration ? `${rendered}${theme.fg("dim", ` · ${duration}`)}` : rendered;
|
|
404
510
|
let cachedWidth: number | undefined;
|
|
405
511
|
let cachedLine: string | undefined;
|
|
406
512
|
let cachedHoveredLine: string | undefined;
|
|
@@ -410,11 +516,11 @@ function createCcstyleTool(
|
|
|
410
516
|
cachedWidth = width;
|
|
411
517
|
cachedLine = theme.fg(
|
|
412
518
|
isError ? "error" : "muted",
|
|
413
|
-
renderCollapsedToolResultToWidth(
|
|
519
|
+
renderCollapsedToolResultToWidth(body, hint, width),
|
|
414
520
|
);
|
|
415
521
|
cachedHoveredLine = theme.fg(
|
|
416
522
|
isError ? "error" : "muted",
|
|
417
|
-
renderCollapsedToolResultToWidth(
|
|
523
|
+
renderCollapsedToolResultToWidth(body, hoveredHint, width),
|
|
418
524
|
);
|
|
419
525
|
}
|
|
420
526
|
return [isToolCallHovered(toolCallId) ? cachedHoveredLine! : cachedLine];
|
|
@@ -566,6 +672,10 @@ function installGlobalToolRendering(
|
|
|
566
672
|
previous?.originalInvalidate && prototype.invalidate === previous.invalidatePaint
|
|
567
673
|
? previous.originalInvalidate
|
|
568
674
|
: prototype.invalidate;
|
|
675
|
+
const originalGetRenderContext =
|
|
676
|
+
previous?.originalGetRenderContext && prototype.getRenderContext === previous.getRenderContext
|
|
677
|
+
? previous.originalGetRenderContext
|
|
678
|
+
: prototype.getRenderContext;
|
|
569
679
|
if (isOwnershipAwarePatch(previous)) disconnectGlobalToolRenderPatch(previous);
|
|
570
680
|
|
|
571
681
|
const patch: GlobalToolRenderPatch = {
|
|
@@ -581,6 +691,7 @@ function installGlobalToolRendering(
|
|
|
581
691
|
installed: undefined as any,
|
|
582
692
|
originalRender,
|
|
583
693
|
originalInvalidate,
|
|
694
|
+
originalGetRenderContext,
|
|
584
695
|
paintCache: new WeakMap(),
|
|
585
696
|
};
|
|
586
697
|
|
|
@@ -618,6 +729,13 @@ function installGlobalToolRendering(
|
|
|
618
729
|
prototype.getRenderShell = patch.installed.getRenderShell;
|
|
619
730
|
prototype.getCallRenderer = patch.installed.getCallRenderer;
|
|
620
731
|
prototype.getResultRenderer = patch.installed.getResultRenderer;
|
|
732
|
+
// 渲染上下文没有 result。ask_user_question 的标题要报问题数,只能从这里补。
|
|
733
|
+
patch.getRenderContext = function (this: any, ...args: any[]) {
|
|
734
|
+
const context = originalGetRenderContext.apply(this, args);
|
|
735
|
+
if (context && typeof context === "object") context.result = this.result;
|
|
736
|
+
return context;
|
|
737
|
+
};
|
|
738
|
+
prototype.getRenderContext = patch.getRenderContext;
|
|
621
739
|
// fullscreen 滚动/选区每帧整树 render;内容未变时复用上一帧行,避免重做 diff/Box。
|
|
622
740
|
patch.renderPaint = function (this: any, width: number): string[] {
|
|
623
741
|
if (!patch.active || patch.mode() === "off") {
|
|
@@ -626,8 +744,11 @@ function installGlobalToolRendering(
|
|
|
626
744
|
const cache = patch.paintCache;
|
|
627
745
|
// A running partial tool owns a time-varying spinner. Light animation ticks
|
|
628
746
|
// request a repaint without invalidating the component, so settled-content
|
|
629
|
-
// caching must not intercept those paints.
|
|
630
|
-
|
|
747
|
+
// caching must not intercept those paints. The waiting window before the
|
|
748
|
+
// tool is dispatched animates too, so it must bypass the cache as well.
|
|
749
|
+
const animatingRow =
|
|
750
|
+
this.isPartial && (this.executionStarted || isLiveToolCall(this.toolCallId));
|
|
751
|
+
const cacheable = !animatingRow;
|
|
631
752
|
const hit = cacheable ? cache?.get(this) : undefined;
|
|
632
753
|
if (hit && toolPaintMatches(hit, this, width)) return hit.lines;
|
|
633
754
|
const lines = originalRender.call(this, width);
|
|
@@ -683,6 +804,13 @@ function deactivateGlobalToolRendering(patch: GlobalToolRenderPatch): void {
|
|
|
683
804
|
) {
|
|
684
805
|
prototype.invalidate = patch.originalInvalidate;
|
|
685
806
|
}
|
|
807
|
+
if (
|
|
808
|
+
patch.getRenderContext &&
|
|
809
|
+
prototype.getRenderContext === patch.getRenderContext &&
|
|
810
|
+
patch.originalGetRenderContext
|
|
811
|
+
) {
|
|
812
|
+
prototype.getRenderContext = patch.originalGetRenderContext;
|
|
813
|
+
}
|
|
686
814
|
}
|
|
687
815
|
|
|
688
816
|
/** 展开面板背景统一为 user message 背景色;折叠行保持原生状态色。
|
|
@@ -31,8 +31,18 @@ import {
|
|
|
31
31
|
TOOL_MOUSE_DISABLE,
|
|
32
32
|
} from "./mouse/interaction.ts";
|
|
33
33
|
import { getToolMouseTui, noteNewTranscriptItem } from "./mouse/scroll.ts";
|
|
34
|
+
import {
|
|
35
|
+
disposeStickyPromptPatches,
|
|
36
|
+
installStickyPromptPatches,
|
|
37
|
+
setStickyPromptTheme,
|
|
38
|
+
} from "./mouse/sticky-prompt.ts";
|
|
34
39
|
import { setHoveredToolGroup, setHoveredToolIo } from "./mouse/hover.ts";
|
|
35
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
clearAllAnimations,
|
|
42
|
+
clearLiveToolCalls,
|
|
43
|
+
noteStreamingToolCalls,
|
|
44
|
+
retireLiveToolCall,
|
|
45
|
+
} from "./tool/result.ts";
|
|
36
46
|
import { installWriteOverride, WriteExecutionMetadataStore } from "./tool/diff/index.ts";
|
|
37
47
|
import {
|
|
38
48
|
installMessageDisplayRendering,
|
|
@@ -118,6 +128,8 @@ export default function (
|
|
|
118
128
|
if (installation) return installation;
|
|
119
129
|
// regular 主屏的差分渲染补丁:视口上方变化不再触发清回滚的 fullRender。
|
|
120
130
|
installMainScreenDoRenderPatch();
|
|
131
|
+
// 粘性用户消息头:标记注入 + fullscreen 帧合成。
|
|
132
|
+
installStickyPromptPatches();
|
|
121
133
|
const defaultMode = installDefaultMode(writeExecutionMetadata);
|
|
122
134
|
const toolGrouping = installToolGrouping(() => config.mode === "on");
|
|
123
135
|
const compactMode = installCompactMode({
|
|
@@ -184,6 +196,9 @@ export default function (
|
|
|
184
196
|
});
|
|
185
197
|
|
|
186
198
|
pi.on("message_update", async (event) => {
|
|
199
|
+
// 声明期(assistant 消息结束前)就记住工具 id:这段工具行已在等启动,
|
|
200
|
+
// spinner 该转,但 Pi 还没跑 markExecutionStarted。
|
|
201
|
+
if (event.message?.role === "assistant") noteStreamingToolCalls(event.message);
|
|
187
202
|
// compact-thinking 在 session_start 时于 compact 补丁之上再装一层;
|
|
188
203
|
// 扩展事件先于 interactive-mode 的 updateContent 派发,此处先重新认领。
|
|
189
204
|
if (config.mode === "compact" && event.message?.role === "assistant") {
|
|
@@ -199,6 +214,7 @@ export default function (
|
|
|
199
214
|
});
|
|
200
215
|
|
|
201
216
|
pi.on("tool_execution_end", async (event) => {
|
|
217
|
+
retireLiveToolCall(event?.toolCallId);
|
|
202
218
|
if (config.mode !== "compact") return;
|
|
203
219
|
// Agent 等工具收尾后延迟刷新,让 compact-thinking 先落最终态。
|
|
204
220
|
const toolCallId: string | undefined = event?.toolCallId;
|
|
@@ -227,6 +243,7 @@ export default function (
|
|
|
227
243
|
if (!hooks) return;
|
|
228
244
|
hooks.toolGrouping.setTheme(ctx.ui.theme);
|
|
229
245
|
setMessageDisplayTheme(ctx.ui.theme);
|
|
246
|
+
setStickyPromptTheme(ctx.ui.theme);
|
|
230
247
|
ctx.ui.setStatus("ccstyle", undefined);
|
|
231
248
|
// 先收集 resume transcript,再同步 compact 补丁与全局展开状态。
|
|
232
249
|
syncCompactMode(ctx);
|
|
@@ -244,6 +261,7 @@ export default function (
|
|
|
244
261
|
if (!hooks) return;
|
|
245
262
|
hooks.toolGrouping.setTheme(ctx.ui.theme);
|
|
246
263
|
setMessageDisplayTheme(ctx.ui.theme);
|
|
264
|
+
setStickyPromptTheme(ctx.ui.theme);
|
|
247
265
|
syncCompactMode(ctx);
|
|
248
266
|
scheduleSessionRender(() => {
|
|
249
267
|
syncCompactMode(ctx);
|
|
@@ -263,8 +281,18 @@ export default function (
|
|
|
263
281
|
noteNewTranscriptItem();
|
|
264
282
|
});
|
|
265
283
|
|
|
284
|
+
pi.on("turn_end", async () => {
|
|
285
|
+
clearLiveToolCalls();
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
pi.on("agent_end", async () => {
|
|
289
|
+
clearLiveToolCalls();
|
|
290
|
+
});
|
|
291
|
+
|
|
266
292
|
pi.on("session_shutdown", async () => {
|
|
267
293
|
writeExecutionMetadata.clear();
|
|
294
|
+
// 会话结束就清空,不能等 installation 早退判断;残留的 id 会让下一会话误转。
|
|
295
|
+
clearLiveToolCalls();
|
|
268
296
|
// 鼠标交互独立于渲染层:fullscreen 下 installation 为 undefined,
|
|
269
297
|
// 但 onTerminalInput 监听与 handleViewportInput 包装仍需释放。
|
|
270
298
|
teardownToolMouseInteraction(mouseOwner);
|
|
@@ -276,6 +304,7 @@ export default function (
|
|
|
276
304
|
current.compactMode.shutdown();
|
|
277
305
|
compactModeHooks = undefined;
|
|
278
306
|
current.disposeMessageDisplay();
|
|
307
|
+
disposeStickyPromptPatches();
|
|
279
308
|
clearAllAnimations();
|
|
280
309
|
installation = undefined;
|
|
281
310
|
});
|
|
@@ -75,6 +75,11 @@ import {
|
|
|
75
75
|
setHoveredToolIo,
|
|
76
76
|
type FullscreenHoverTarget,
|
|
77
77
|
} from "./hover.ts";
|
|
78
|
+
import {
|
|
79
|
+
handleStickyPromptClick,
|
|
80
|
+
resetStickyPromptState,
|
|
81
|
+
updateStickyPromptHover,
|
|
82
|
+
} from "./sticky-prompt.ts";
|
|
78
83
|
|
|
79
84
|
type FrameToolRender = {
|
|
80
85
|
component: any;
|
|
@@ -439,9 +444,15 @@ function handleFullscreenToolClick(tui: any, packet: SgrMousePacket): boolean {
|
|
|
439
444
|
}
|
|
440
445
|
// 展开 round 卡内工具时,让 compact 的强制折叠放行它(非 round 内工具为空操作)。
|
|
441
446
|
if (isTool) markCompactRoundToolExpanded(component);
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
447
|
+
if (isAssistant) {
|
|
448
|
+
// compact 摘要行自己记展开前的视口(收起时还原跟随),所以它先展开再停跟随:
|
|
449
|
+
// 顺序反了快照会记下「未跟随」,收起时就交不回官方 follow。
|
|
450
|
+
component.setExpanded(true);
|
|
451
|
+
keepViewportOnExpand(tui);
|
|
452
|
+
} else {
|
|
453
|
+
keepViewportOnExpand(tui);
|
|
454
|
+
component.setExpanded(true);
|
|
455
|
+
}
|
|
445
456
|
clearPendingCollapsePress();
|
|
446
457
|
} else {
|
|
447
458
|
// 普通工具截断头 show-more:打开全量预览(不收起)。
|
|
@@ -491,6 +502,7 @@ function isSameExpandedRound(other: any, owner: any): boolean {
|
|
|
491
502
|
/**
|
|
492
503
|
* 原地展开时停在当前滚动位置:transcript 跟随底部时,内容变高会把视口拖到底,
|
|
493
504
|
* 点击的行和它上方的外层面板被顶出屏幕。展开前关掉跟随,点击行留在原位。
|
|
505
|
+
* compact 摘要行同样适用:它的展开入口在 setExpanded 之后调用本函数(见调用点)。
|
|
494
506
|
*/
|
|
495
507
|
function keepViewportOnExpand(tui: any): void {
|
|
496
508
|
const view = tui.getPrimaryScrollView?.();
|
|
@@ -611,12 +623,17 @@ function patchFullscreenViewportInput(tui: any): void {
|
|
|
611
623
|
}
|
|
612
624
|
} else if (!isSgrLeftPress(packet) && !isSgrIdleMotion(packet))
|
|
613
625
|
clearPendingCollapsePressOnMove(packet);
|
|
626
|
+
if (isSgrLeftPress(packet) && handleStickyPromptClick(tui, packet)) {
|
|
627
|
+
return { consume: true };
|
|
628
|
+
}
|
|
614
629
|
if (isSgrLeftPress(packet) && handleFullscreenToolClick(tui, packet)) {
|
|
615
630
|
return { consume: true };
|
|
616
631
|
}
|
|
617
632
|
// 仅无按键移动走 hover;左键拖动(文本多选)放行官方选区,避免每像素命中+重绘。
|
|
618
633
|
if (isSgrIdleMotion(packet)) {
|
|
619
|
-
|
|
634
|
+
// 粘性行命中时不进工具 hover:同一行只能有一个高亮。
|
|
635
|
+
if (updateStickyPromptHover(tui, packet)) applyFullscreenHover(tui, null);
|
|
636
|
+
else handleFullscreenToolHover(tui, packet);
|
|
620
637
|
}
|
|
621
638
|
}
|
|
622
639
|
}
|
|
@@ -1021,6 +1038,7 @@ export function resetToolHoverState(): void {
|
|
|
1021
1038
|
setHoveredCompactAssistant(null);
|
|
1022
1039
|
setScrollButtonVisible(false);
|
|
1023
1040
|
setScrollButtonHovered(false);
|
|
1041
|
+
resetStickyPromptState();
|
|
1024
1042
|
restoreOfficialScrollToEnd(getToolMouseTui());
|
|
1025
1043
|
releaseFullscreenToolMouseMotion(getToolMouseTui());
|
|
1026
1044
|
}
|