@herbertgao/pi-extensions 2026.10.3 → 2026.10.5
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.md +3 -2
- package/THIRD_PARTY_NOTICES.md +4 -0
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +12 -7
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +12 -7
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +67 -16
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +240 -48
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/footer-currency.ts +29 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/footer.ts +92 -59
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/git-stats.ts +162 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/token-rate.ts +192 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/working-message.ts +51 -65
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +113 -25
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +160 -24
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/index.ts +30 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +26 -24
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/sticky-prompt.ts +402 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/ask-user-question.ts +173 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/codemode.ts +4 -8
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-edit-render.ts +64 -11
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-presentation.ts +41 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +5 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-write-render.ts +35 -9
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +101 -31
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/names.ts +3 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +106 -15
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/format.ts +9 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/patch-keys.ts +5 -0
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +4 -4
- package/node_modules/@herbertgao/pi-cc-extensions/themes/cc-dark.json +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/themes/cc-light.json +1 -1
- package/node_modules/pi-provider-qoder/README.md +140 -0
- package/node_modules/pi-provider-qoder/dist/index.js +10249 -0
- package/node_modules/pi-provider-qoder/package.json +57 -0
- package/package.json +5 -2
|
@@ -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,35 @@ 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
|
+
* 当前宿主只把它留在工具结果上,渲染上下文补丁会把整份结果挂到 `context.result`。
|
|
232
|
+
* 都拿不到再退回自己的时钟——开始时间在 renderCall 记,结束时间在结果定型时定一次。
|
|
233
|
+
* 运行中的实时计时也只能走时钟。
|
|
234
|
+
*/
|
|
235
|
+
function recordedShellDurationMs(context: any): number | undefined {
|
|
236
|
+
for (const value of [context?.durationMs, context?.result?.durationMs]) {
|
|
237
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) return value;
|
|
238
|
+
}
|
|
239
|
+
return undefined;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function shellDurationText(toolName: string, context: any, isPartial: boolean): string {
|
|
243
|
+
if (!SHELL_TOOL_NAMES.has(toolName)) return "";
|
|
244
|
+
const recorded = recordedShellDurationMs(context);
|
|
245
|
+
if (!isPartial && recorded !== undefined) {
|
|
246
|
+
return formatToolDuration(recorded);
|
|
247
|
+
}
|
|
248
|
+
const startedAt = context?.state?.ccstyleShellStartedAt;
|
|
249
|
+
if (typeof startedAt !== "number") return "";
|
|
250
|
+
const endedAt = context?.state?.ccstyleShellEndedAt;
|
|
251
|
+
const end = typeof endedAt === "number" ? endedAt : Date.now();
|
|
252
|
+
// 时钟回拨时别吐负数;快照固定时钟也会走到这。
|
|
253
|
+
return formatToolDuration(Math.max(0, end - startedAt));
|
|
254
|
+
}
|
|
255
|
+
|
|
216
256
|
/** 用 ccstyle call/result 包装任意工具定义。 */
|
|
217
257
|
function createCcstyleTool(
|
|
218
258
|
originalTool: any,
|
|
@@ -232,11 +272,24 @@ function createCcstyleTool(
|
|
|
232
272
|
return renderDefault(originalTool, "renderCall", [args, theme, context], String(toolName));
|
|
233
273
|
}
|
|
234
274
|
|
|
275
|
+
// shell 的耗时从执行开始算;restore 出来的行没有 executionStarted,不参与。
|
|
276
|
+
if (
|
|
277
|
+
SHELL_TOOL_NAMES.has(toolName) &&
|
|
278
|
+
context?.executionStarted &&
|
|
279
|
+
context.state &&
|
|
280
|
+
context.state.ccstyleShellStartedAt === undefined
|
|
281
|
+
) {
|
|
282
|
+
context.state.ccstyleShellStartedAt = Date.now();
|
|
283
|
+
}
|
|
284
|
+
|
|
235
285
|
const visualState = resolveToolVisualState(context);
|
|
236
286
|
const isPending =
|
|
237
287
|
visualState === "pending" ||
|
|
238
288
|
(!visualState && (context?.isPartial || context?.executionStarted));
|
|
239
|
-
|
|
289
|
+
// 等待窗口(声明流式中或等待派发)也要转:Pi 要到 assistant 消息结束
|
|
290
|
+
// 才派发执行,这段只靠 executionStarted 会一直冻在第一帧。
|
|
291
|
+
const animating =
|
|
292
|
+
isPending && (Boolean(context?.executionStarted) || isLiveToolCall(context?.toolCallId));
|
|
240
293
|
if (animating) scheduleAnimation(context, { light: true });
|
|
241
294
|
const settledRawIcon = isPending ? "" : settledIcon(toolName, visualState);
|
|
242
295
|
const settledIconStyled =
|
|
@@ -251,9 +304,23 @@ function createCcstyleTool(
|
|
|
251
304
|
variant: "default",
|
|
252
305
|
cwd: context?.cwd,
|
|
253
306
|
});
|
|
307
|
+
// ask_user_question:问题原文在折叠结果树里逐条列出,标题只留工具名和问题数,
|
|
308
|
+
// 否则第一个问题会在标题和树里各出现一次。
|
|
309
|
+
if (toolName === ASK_USER_QUESTION_TOOL_NAME) {
|
|
310
|
+
// Pi 的渲染上下文不带 result,问题数从这里取。
|
|
311
|
+
const questionCount = askUserQuestionCount(context?.result, args);
|
|
312
|
+
summary.main = title;
|
|
313
|
+
summary.detail = questionCount === undefined ? "" : ` (+${questionCount})`;
|
|
314
|
+
summary.payload = undefined;
|
|
315
|
+
}
|
|
254
316
|
let writeStatsText = "";
|
|
255
317
|
let writeStatsStyled = "";
|
|
256
|
-
|
|
318
|
+
// 折叠 0 行预览时计数由正文那行(↳ diff +A -D · created …)显示,标题不重复。
|
|
319
|
+
const statsInBody = writeStatsInCollapsedBody(
|
|
320
|
+
Boolean(context?.expanded),
|
|
321
|
+
getToolDisplayConfig(),
|
|
322
|
+
);
|
|
323
|
+
if (toolName === "write" && visualState === "success" && !statsInBody) {
|
|
257
324
|
const meta = writeExecutionMetadata.get(context?.toolCallId);
|
|
258
325
|
const stats = countWriteDiffStats(
|
|
259
326
|
typeof args?.content === "string" ? args.content : undefined,
|
|
@@ -336,11 +403,62 @@ function createCcstyleTool(
|
|
|
336
403
|
isHovered: () => isToolCallHovered(toolCallId),
|
|
337
404
|
});
|
|
338
405
|
}
|
|
406
|
+
// ask_user_question:折叠态摊成问答树,展开态按问题逐条列出选择。
|
|
407
|
+
if (toolName === ASK_USER_QUESTION_TOOL_NAME) {
|
|
408
|
+
if (!options?.isPartial) setToolVisualState(context, isError ? "error" : "success");
|
|
409
|
+
if (options?.isPartial) {
|
|
410
|
+
// 折叠态 1 格缩进:与完成后的 ├/└ 对齐标题行的勾。
|
|
411
|
+
const pending = expanded ? "↳ Pending…" : " ↳ Pending…";
|
|
412
|
+
return new Text(theme.fg("muted", pending), 0, 0);
|
|
413
|
+
}
|
|
414
|
+
if (expanded) {
|
|
415
|
+
return renderExpandedToolResult(
|
|
416
|
+
askUserExpandedBody(result),
|
|
417
|
+
theme,
|
|
418
|
+
isError,
|
|
419
|
+
context?.lastComponent,
|
|
420
|
+
context?.args,
|
|
421
|
+
context,
|
|
422
|
+
true,
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
if (context?.state) context.state.ccstyleIoView = undefined;
|
|
426
|
+
const toolCallId = context?.toolCallId;
|
|
427
|
+
return createAskUserResultComponent({
|
|
428
|
+
result,
|
|
429
|
+
theme,
|
|
430
|
+
isError,
|
|
431
|
+
isHovered: () => isToolCallHovered(toolCallId),
|
|
432
|
+
});
|
|
433
|
+
}
|
|
339
434
|
if (options?.isPartial) {
|
|
340
|
-
//
|
|
435
|
+
// 折叠态保持 3 格:与完成后的 ` ↳ …` 同列,工具跑完那行不横跳。
|
|
436
|
+
// 展开态贴左(Box 提供 1 格 pad)。
|
|
341
437
|
const pending = expanded ? "↳ Pending…" : " ↳ Pending…";
|
|
342
|
-
|
|
438
|
+
if (!SHELL_TOOL_NAMES.has(toolName)) {
|
|
439
|
+
return new Text(theme.fg("muted", pending), 0, 0);
|
|
440
|
+
}
|
|
441
|
+
// shell 运行中报已用时间:数字在 render() 内取,动画 tick 才能带上新值。
|
|
442
|
+
return {
|
|
443
|
+
render(width: number) {
|
|
444
|
+
const elapsed = shellDurationText(toolName, context, true);
|
|
445
|
+
const suffix = elapsed ? theme.fg("dim", ` · ${elapsed}`) : "";
|
|
446
|
+
return [truncateToWidth(theme.fg("muted", pending) + suffix, width, "")];
|
|
447
|
+
},
|
|
448
|
+
invalidate(): void {},
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// shell:结果定型时定一次结束时间,这一行之后不再变。
|
|
453
|
+
if (
|
|
454
|
+
SHELL_TOOL_NAMES.has(toolName) &&
|
|
455
|
+
context?.state &&
|
|
456
|
+
context.state.ccstyleShellEndedAt === undefined
|
|
457
|
+
) {
|
|
458
|
+
context.state.ccstyleShellEndedAt = Date.now();
|
|
343
459
|
}
|
|
460
|
+
// 折叠行接在摘要后,展开态接在 Output 正文后(原生也是在结果底部报一行)。
|
|
461
|
+
const duration = shellDurationText(toolName, context, false);
|
|
344
462
|
|
|
345
463
|
setToolVisualState(context, isError ? "error" : "success");
|
|
346
464
|
const toolCallId = context?.toolCallId;
|
|
@@ -371,26 +489,16 @@ function createCcstyleTool(
|
|
|
371
489
|
if (taskResult) return taskResult;
|
|
372
490
|
}
|
|
373
491
|
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
492
|
const rendered = tasks.length
|
|
378
493
|
? taskListSummary(tasks)
|
|
379
|
-
: isError
|
|
380
|
-
? text
|
|
381
|
-
? // 72 与原 result.ts 默认一致,保持错误摘要截断宽度。
|
|
382
|
-
oneLine(text, 72)
|
|
383
|
-
: "Failed"
|
|
384
|
-
: outputLines
|
|
385
|
-
? `${outputLines} ${lineWord} ${action}`
|
|
386
|
-
: "Done";
|
|
494
|
+
: collapsedResultSummary({ result, toolName, text, isError });
|
|
387
495
|
const expandable = !expanded && (tasks.length > 0 || hasExpandableDetail(text, args));
|
|
388
496
|
const hintText = showMoreHintText();
|
|
389
497
|
const hintPrefix = expandable ? theme.fg("dim", " · ") : "";
|
|
390
498
|
const hint = expandable ? hintPrefix + theme.fg("dim", hintText) : "";
|
|
391
499
|
const hoveredHint = expandable ? hintPrefix + theme.fg("text", hintText) : "";
|
|
392
500
|
if (expanded) {
|
|
393
|
-
|
|
501
|
+
const view = renderExpandedToolResult(
|
|
394
502
|
text || "",
|
|
395
503
|
theme,
|
|
396
504
|
Boolean(isError),
|
|
@@ -399,8 +507,14 @@ function createCcstyleTool(
|
|
|
399
507
|
context,
|
|
400
508
|
true, // mode=on:贴左,由外层 Box(1,1) 提供 1 格 padding
|
|
401
509
|
);
|
|
510
|
+
// 尾部附注只能挂在 IoView 上:鼠标层按对象识别它,包一层会断掉命中。
|
|
511
|
+
// 与折叠态保持同一写法:裸值,不写 Took。
|
|
512
|
+
if (isExpandedToolIoView(view)) view.setFooter(duration || undefined);
|
|
513
|
+
return view;
|
|
402
514
|
}
|
|
403
515
|
if (context?.state) context.state.ccstyleIoView = undefined;
|
|
516
|
+
// 折叠态的耗时接在摘要后、展开提示前(提示必须留在行尾供鼠标层识别)。
|
|
517
|
+
const body = duration ? `${rendered}${theme.fg("dim", ` · ${duration}`)}` : rendered;
|
|
404
518
|
let cachedWidth: number | undefined;
|
|
405
519
|
let cachedLine: string | undefined;
|
|
406
520
|
let cachedHoveredLine: string | undefined;
|
|
@@ -410,11 +524,11 @@ function createCcstyleTool(
|
|
|
410
524
|
cachedWidth = width;
|
|
411
525
|
cachedLine = theme.fg(
|
|
412
526
|
isError ? "error" : "muted",
|
|
413
|
-
renderCollapsedToolResultToWidth(
|
|
527
|
+
renderCollapsedToolResultToWidth(body, hint, width),
|
|
414
528
|
);
|
|
415
529
|
cachedHoveredLine = theme.fg(
|
|
416
530
|
isError ? "error" : "muted",
|
|
417
|
-
renderCollapsedToolResultToWidth(
|
|
531
|
+
renderCollapsedToolResultToWidth(body, hoveredHint, width),
|
|
418
532
|
);
|
|
419
533
|
}
|
|
420
534
|
return [isToolCallHovered(toolCallId) ? cachedHoveredLine! : cachedLine];
|
|
@@ -566,6 +680,10 @@ function installGlobalToolRendering(
|
|
|
566
680
|
previous?.originalInvalidate && prototype.invalidate === previous.invalidatePaint
|
|
567
681
|
? previous.originalInvalidate
|
|
568
682
|
: prototype.invalidate;
|
|
683
|
+
const originalGetRenderContext =
|
|
684
|
+
previous?.originalGetRenderContext && prototype.getRenderContext === previous.getRenderContext
|
|
685
|
+
? previous.originalGetRenderContext
|
|
686
|
+
: prototype.getRenderContext;
|
|
569
687
|
if (isOwnershipAwarePatch(previous)) disconnectGlobalToolRenderPatch(previous);
|
|
570
688
|
|
|
571
689
|
const patch: GlobalToolRenderPatch = {
|
|
@@ -581,6 +699,7 @@ function installGlobalToolRendering(
|
|
|
581
699
|
installed: undefined as any,
|
|
582
700
|
originalRender,
|
|
583
701
|
originalInvalidate,
|
|
702
|
+
originalGetRenderContext,
|
|
584
703
|
paintCache: new WeakMap(),
|
|
585
704
|
};
|
|
586
705
|
|
|
@@ -618,6 +737,13 @@ function installGlobalToolRendering(
|
|
|
618
737
|
prototype.getRenderShell = patch.installed.getRenderShell;
|
|
619
738
|
prototype.getCallRenderer = patch.installed.getCallRenderer;
|
|
620
739
|
prototype.getResultRenderer = patch.installed.getResultRenderer;
|
|
740
|
+
// 渲染上下文没有 result。ask_user_question 的标题要报问题数,只能从这里补。
|
|
741
|
+
patch.getRenderContext = function (this: any, ...args: any[]) {
|
|
742
|
+
const context = originalGetRenderContext.apply(this, args);
|
|
743
|
+
if (context && typeof context === "object") context.result = this.result;
|
|
744
|
+
return context;
|
|
745
|
+
};
|
|
746
|
+
prototype.getRenderContext = patch.getRenderContext;
|
|
621
747
|
// fullscreen 滚动/选区每帧整树 render;内容未变时复用上一帧行,避免重做 diff/Box。
|
|
622
748
|
patch.renderPaint = function (this: any, width: number): string[] {
|
|
623
749
|
if (!patch.active || patch.mode() === "off") {
|
|
@@ -626,8 +752,11 @@ function installGlobalToolRendering(
|
|
|
626
752
|
const cache = patch.paintCache;
|
|
627
753
|
// A running partial tool owns a time-varying spinner. Light animation ticks
|
|
628
754
|
// request a repaint without invalidating the component, so settled-content
|
|
629
|
-
// caching must not intercept those paints.
|
|
630
|
-
|
|
755
|
+
// caching must not intercept those paints. The waiting window before the
|
|
756
|
+
// tool is dispatched animates too, so it must bypass the cache as well.
|
|
757
|
+
const animatingRow =
|
|
758
|
+
this.isPartial && (this.executionStarted || isLiveToolCall(this.toolCallId));
|
|
759
|
+
const cacheable = !animatingRow;
|
|
631
760
|
const hit = cacheable ? cache?.get(this) : undefined;
|
|
632
761
|
if (hit && toolPaintMatches(hit, this, width)) return hit.lines;
|
|
633
762
|
const lines = originalRender.call(this, width);
|
|
@@ -683,6 +812,13 @@ function deactivateGlobalToolRendering(patch: GlobalToolRenderPatch): void {
|
|
|
683
812
|
) {
|
|
684
813
|
prototype.invalidate = patch.originalInvalidate;
|
|
685
814
|
}
|
|
815
|
+
if (
|
|
816
|
+
patch.getRenderContext &&
|
|
817
|
+
prototype.getRenderContext === patch.getRenderContext &&
|
|
818
|
+
patch.originalGetRenderContext
|
|
819
|
+
) {
|
|
820
|
+
prototype.getRenderContext = patch.originalGetRenderContext;
|
|
821
|
+
}
|
|
686
822
|
}
|
|
687
823
|
|
|
688
824
|
/** 展开面板背景统一为 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,
|
|
@@ -123,6 +133,8 @@ export default function (
|
|
|
123
133
|
if (staleGlobalPatch) staleGlobalPatch.mode = () => "off";
|
|
124
134
|
// regular 主屏的差分渲染补丁:视口上方变化不再触发清回滚的 fullRender。
|
|
125
135
|
installMainScreenDoRenderPatch();
|
|
136
|
+
// 粘性用户消息头:标记注入 + fullscreen 帧合成。
|
|
137
|
+
installStickyPromptPatches();
|
|
126
138
|
const defaultMode = installDefaultMode(writeExecutionMetadata);
|
|
127
139
|
const toolGrouping = installToolGrouping(() => config.mode === "on");
|
|
128
140
|
const compactMode = installCompactMode({
|
|
@@ -189,6 +201,9 @@ export default function (
|
|
|
189
201
|
});
|
|
190
202
|
|
|
191
203
|
pi.on("message_update", async (event) => {
|
|
204
|
+
// 声明期(assistant 消息结束前)就记住工具 id:这段工具行已在等启动,
|
|
205
|
+
// spinner 该转,但 Pi 还没跑 markExecutionStarted。
|
|
206
|
+
if (event.message?.role === "assistant") noteStreamingToolCalls(event.message);
|
|
192
207
|
// compact-thinking 在 session_start 时于 compact 补丁之上再装一层;
|
|
193
208
|
// 扩展事件先于 interactive-mode 的 updateContent 派发,此处先重新认领。
|
|
194
209
|
if (config.mode === "compact" && event.message?.role === "assistant") {
|
|
@@ -204,6 +219,7 @@ export default function (
|
|
|
204
219
|
});
|
|
205
220
|
|
|
206
221
|
pi.on("tool_execution_end", async (event) => {
|
|
222
|
+
retireLiveToolCall(event?.toolCallId);
|
|
207
223
|
if (config.mode !== "compact") return;
|
|
208
224
|
// Agent 等工具收尾后延迟刷新,让 compact-thinking 先落最终态。
|
|
209
225
|
const toolCallId: string | undefined = event?.toolCallId;
|
|
@@ -232,6 +248,7 @@ export default function (
|
|
|
232
248
|
if (!hooks) return;
|
|
233
249
|
hooks.toolGrouping.setTheme(ctx.ui.theme);
|
|
234
250
|
setMessageDisplayTheme(ctx.ui.theme);
|
|
251
|
+
setStickyPromptTheme(ctx.ui.theme);
|
|
235
252
|
ctx.ui.setStatus("ccstyle", undefined);
|
|
236
253
|
// 先收集 resume transcript,再同步 compact 补丁与全局展开状态。
|
|
237
254
|
syncCompactMode(ctx);
|
|
@@ -249,6 +266,7 @@ export default function (
|
|
|
249
266
|
if (!hooks) return;
|
|
250
267
|
hooks.toolGrouping.setTheme(ctx.ui.theme);
|
|
251
268
|
setMessageDisplayTheme(ctx.ui.theme);
|
|
269
|
+
setStickyPromptTheme(ctx.ui.theme);
|
|
252
270
|
syncCompactMode(ctx);
|
|
253
271
|
scheduleSessionRender(() => {
|
|
254
272
|
syncCompactMode(ctx);
|
|
@@ -268,8 +286,18 @@ export default function (
|
|
|
268
286
|
noteNewTranscriptItem();
|
|
269
287
|
});
|
|
270
288
|
|
|
289
|
+
pi.on("turn_end", async () => {
|
|
290
|
+
clearLiveToolCalls();
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
pi.on("agent_end", async () => {
|
|
294
|
+
clearLiveToolCalls();
|
|
295
|
+
});
|
|
296
|
+
|
|
271
297
|
pi.on("session_shutdown", async () => {
|
|
272
298
|
writeExecutionMetadata.clear();
|
|
299
|
+
// 会话结束就清空,不能等 installation 早退判断;残留的 id 会让下一会话误转。
|
|
300
|
+
clearLiveToolCalls();
|
|
273
301
|
// 鼠标交互独立于渲染层:fullscreen 下 installation 为 undefined,
|
|
274
302
|
// 但 onTerminalInput 监听与 handleViewportInput 包装仍需释放。
|
|
275
303
|
teardownToolMouseInteraction(mouseOwner);
|
|
@@ -281,6 +309,7 @@ export default function (
|
|
|
281
309
|
current.compactMode.shutdown();
|
|
282
310
|
compactModeHooks = undefined;
|
|
283
311
|
current.disposeMessageDisplay();
|
|
312
|
+
disposeStickyPromptPatches();
|
|
284
313
|
clearAllAnimations();
|
|
285
314
|
installation = undefined;
|
|
286
315
|
});
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
setHoveredCompactAssistant,
|
|
14
14
|
} from "../compact-mode.ts";
|
|
15
15
|
import { isMessageDisplayComponent } from "../tool/message-display.ts";
|
|
16
|
-
import { config } from "../../config/config.ts";
|
|
17
16
|
import { isLazyProxyTui } from "../../utils/fullscreen-detect.ts";
|
|
18
17
|
import { setToolTuiFullscreen, isCollapseHintLine } from "../tool/show-more-hint.ts";
|
|
19
18
|
import {
|
|
@@ -76,6 +75,11 @@ import {
|
|
|
76
75
|
setHoveredToolIo,
|
|
77
76
|
type FullscreenHoverTarget,
|
|
78
77
|
} from "./hover.ts";
|
|
78
|
+
import {
|
|
79
|
+
handleStickyPromptClick,
|
|
80
|
+
resetStickyPromptState,
|
|
81
|
+
updateStickyPromptHover,
|
|
82
|
+
} from "./sticky-prompt.ts";
|
|
79
83
|
|
|
80
84
|
type FrameToolRender = {
|
|
81
85
|
component: any;
|
|
@@ -308,10 +312,6 @@ function ensureFullscreenToolMouseMotion(tui: any): void {
|
|
|
308
312
|
releaseFullscreenToolMouseMotion(tui);
|
|
309
313
|
return;
|
|
310
314
|
}
|
|
311
|
-
// 面板改 scrollStepLines 后,下一帧渲染即同步(restore 仍按 original 恢复)。
|
|
312
|
-
if (typeof tui.wheelScrollLines === "number" && tui.wheelScrollLines !== config.scrollStepLines) {
|
|
313
|
-
tui.wheelScrollLines = config.scrollStepLines;
|
|
314
|
-
}
|
|
315
315
|
if (
|
|
316
316
|
!toolMouseInteractionActive() ||
|
|
317
317
|
tui.mouseEnabled === false ||
|
|
@@ -348,7 +348,6 @@ function releaseFullscreenToolMouseMotion(tui?: any): void {
|
|
|
348
348
|
}
|
|
349
349
|
|
|
350
350
|
const FULLSCREEN_VIEWPORT_PATCH = Symbol("ccstyle.fullscreen-viewport-patch");
|
|
351
|
-
const FULLSCREEN_WHEEL_SCROLL_ORIGINAL = Symbol("ccstyle.fullscreen-wheel-scroll-original");
|
|
352
351
|
|
|
353
352
|
/**
|
|
354
353
|
* diff 结果组件自带 remainder 行:声明存在时只有那一行是展开入口(正文里的同名字样不算);
|
|
@@ -445,9 +444,15 @@ function handleFullscreenToolClick(tui: any, packet: SgrMousePacket): boolean {
|
|
|
445
444
|
}
|
|
446
445
|
// 展开 round 卡内工具时,让 compact 的强制折叠放行它(非 round 内工具为空操作)。
|
|
447
446
|
if (isTool) markCompactRoundToolExpanded(component);
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
447
|
+
if (isAssistant) {
|
|
448
|
+
// compact 摘要行自己记展开前的视口(收起时还原跟随),所以它先展开再停跟随:
|
|
449
|
+
// 顺序反了快照会记下「未跟随」,收起时就交不回官方 follow。
|
|
450
|
+
component.setExpanded(true);
|
|
451
|
+
keepViewportOnExpand(tui);
|
|
452
|
+
} else {
|
|
453
|
+
keepViewportOnExpand(tui);
|
|
454
|
+
component.setExpanded(true);
|
|
455
|
+
}
|
|
451
456
|
clearPendingCollapsePress();
|
|
452
457
|
} else {
|
|
453
458
|
// 普通工具截断头 show-more:打开全量预览(不收起)。
|
|
@@ -487,10 +492,6 @@ function handleFullscreenToolClick(tui: any, packet: SgrMousePacket): boolean {
|
|
|
487
492
|
return true;
|
|
488
493
|
}
|
|
489
494
|
|
|
490
|
-
/**
|
|
491
|
-
* fullscreen 鼠标悬停:collapsed 卡 [click to show more] hint、
|
|
492
|
-
* expanded 卡截断头 show-more、回到底部按钮。motion 不 consume,官方链照常。
|
|
493
|
-
*/
|
|
494
495
|
/** 点到的组件和外层卡是否属于同一张 compact 回合面板。 */
|
|
495
496
|
function isSameExpandedRound(other: any, owner: any): boolean {
|
|
496
497
|
if (!owner || !isCompactAssistantComponent(other)) return false;
|
|
@@ -501,6 +502,7 @@ function isSameExpandedRound(other: any, owner: any): boolean {
|
|
|
501
502
|
/**
|
|
502
503
|
* 原地展开时停在当前滚动位置:transcript 跟随底部时,内容变高会把视口拖到底,
|
|
503
504
|
* 点击的行和它上方的外层面板被顶出屏幕。展开前关掉跟随,点击行留在原位。
|
|
505
|
+
* compact 摘要行同样适用:它的展开入口在 setExpanded 之后调用本函数(见调用点)。
|
|
504
506
|
*/
|
|
505
507
|
function keepViewportOnExpand(tui: any): void {
|
|
506
508
|
const view = tui.getPrimaryScrollView?.();
|
|
@@ -510,6 +512,10 @@ function keepViewportOnExpand(tui: any): void {
|
|
|
510
512
|
if (view.isFollowingEnd === false) setScrollButtonVisible(true);
|
|
511
513
|
}
|
|
512
514
|
|
|
515
|
+
/**
|
|
516
|
+
* fullscreen 鼠标悬停:collapsed 卡 [click to show more] hint、
|
|
517
|
+
* expanded 卡截断头 show-more、回到底部按钮。motion 不 consume,官方链照常。
|
|
518
|
+
*/
|
|
513
519
|
function handleFullscreenToolHover(tui: any, packet: SgrMousePacket): void {
|
|
514
520
|
if (!isSgrIdleMotion(packet)) return;
|
|
515
521
|
const layout = tui.currentLayout;
|
|
@@ -589,11 +595,6 @@ function patchFullscreenViewportInput(tui: any): void {
|
|
|
589
595
|
const proto = Object.getPrototypeOf(tui);
|
|
590
596
|
const original = proto?.handleViewportInput;
|
|
591
597
|
if (typeof original !== "function") return;
|
|
592
|
-
// 官方原生 routeWheel 已完整处理嵌套 ScrollView;只调整默认步进(config.scrollStepLines)。
|
|
593
|
-
if (typeof tui.wheelScrollLines === "number") {
|
|
594
|
-
tui[FULLSCREEN_WHEEL_SCROLL_ORIGINAL] = tui.wheelScrollLines;
|
|
595
|
-
tui.wheelScrollLines = config.scrollStepLines;
|
|
596
|
-
}
|
|
597
598
|
tui[FULLSCREEN_VIEWPORT_PATCH] = true;
|
|
598
599
|
tui.handleViewportInput = function (this: any, data: string) {
|
|
599
600
|
if (toolMouseInteractionActive() && tui.mode === "fullscreen") {
|
|
@@ -622,12 +623,17 @@ function patchFullscreenViewportInput(tui: any): void {
|
|
|
622
623
|
}
|
|
623
624
|
} else if (!isSgrLeftPress(packet) && !isSgrIdleMotion(packet))
|
|
624
625
|
clearPendingCollapsePressOnMove(packet);
|
|
626
|
+
if (isSgrLeftPress(packet) && handleStickyPromptClick(tui, packet)) {
|
|
627
|
+
return { consume: true };
|
|
628
|
+
}
|
|
625
629
|
if (isSgrLeftPress(packet) && handleFullscreenToolClick(tui, packet)) {
|
|
626
630
|
return { consume: true };
|
|
627
631
|
}
|
|
628
632
|
// 仅无按键移动走 hover;左键拖动(文本多选)放行官方选区,避免每像素命中+重绘。
|
|
629
633
|
if (isSgrIdleMotion(packet)) {
|
|
630
|
-
|
|
634
|
+
// 粘性行命中时不进工具 hover:同一行只能有一个高亮。
|
|
635
|
+
if (updateStickyPromptHover(tui, packet)) applyFullscreenHover(tui, null);
|
|
636
|
+
else handleFullscreenToolHover(tui, packet);
|
|
631
637
|
}
|
|
632
638
|
}
|
|
633
639
|
}
|
|
@@ -642,11 +648,6 @@ function restoreFullscreenViewportInput(tui: any): void {
|
|
|
642
648
|
if (typeof proto?.handleViewportInput === "function") {
|
|
643
649
|
tui.handleViewportInput = proto.handleViewportInput;
|
|
644
650
|
}
|
|
645
|
-
const originalWheelScrollLines = tui[FULLSCREEN_WHEEL_SCROLL_ORIGINAL];
|
|
646
|
-
if (typeof originalWheelScrollLines === "number") {
|
|
647
|
-
tui.wheelScrollLines = originalWheelScrollLines;
|
|
648
|
-
tui[FULLSCREEN_WHEEL_SCROLL_ORIGINAL] = undefined;
|
|
649
|
-
}
|
|
650
651
|
tui[FULLSCREEN_VIEWPORT_PATCH] = false;
|
|
651
652
|
}
|
|
652
653
|
|
|
@@ -1037,6 +1038,7 @@ export function resetToolHoverState(): void {
|
|
|
1037
1038
|
setHoveredCompactAssistant(null);
|
|
1038
1039
|
setScrollButtonVisible(false);
|
|
1039
1040
|
setScrollButtonHovered(false);
|
|
1041
|
+
resetStickyPromptState();
|
|
1040
1042
|
restoreOfficialScrollToEnd(getToolMouseTui());
|
|
1041
1043
|
releaseFullscreenToolMouseMotion(getToolMouseTui());
|
|
1042
1044
|
}
|