pi-claude-code-ui 1.0.82 → 1.0.84
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/CHANGELOG.md +6 -0
- package/extensions/index.ts +388 -57
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
> [!IMPORTANT]
|
|
4
4
|
> **1.0.69 — package rename (permanent).** Canonical npm name is now [`pi-claude-code-ui`](https://www.npmjs.com/package/pi-claude-code-ui). `pi-claude-style-tools` is legacy and will not receive further releases. Install with `pi install npm:pi-claude-code-ui` or `npm i pi-claude-code-ui`.
|
|
5
5
|
|
|
6
|
+
## 1.0.83 — 2026-09-07
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- **Support Pi 0.85+ MouseRegion-wrapped thinking blocks** — unwrap `MouseRegion` containers in `replaceHiddenThinkingPlaceholders` and the assistant message Markdown post-processor. Resolves an issue in newer Pi versions where thinking blocks were rendered in raw italics and collapsed thoughts remained stuck on "Thinking…" instead of transitioning to "Thought for Xs". Preserves native Pi 0.85 mouse click-to-expand and click-to-collapse functionality.
|
|
11
|
+
|
|
6
12
|
## 1.0.80 — 2026-08-24
|
|
7
13
|
|
|
8
14
|
### Fixed
|
package/extensions/index.ts
CHANGED
|
@@ -41,6 +41,24 @@ import {
|
|
|
41
41
|
visibleWidth,
|
|
42
42
|
wrapTextWithAnsi,
|
|
43
43
|
} from "@earendil-works/pi-tui";
|
|
44
|
+
import * as PiTui from "@earendil-works/pi-tui";
|
|
45
|
+
|
|
46
|
+
// Pi 0.85+ fullscreen mouse types. The 0.79 peer typings do not export them yet.
|
|
47
|
+
interface TuiMouseEvent {
|
|
48
|
+
type: string;
|
|
49
|
+
button: string;
|
|
50
|
+
x: number;
|
|
51
|
+
y: number;
|
|
52
|
+
width: number;
|
|
53
|
+
height: number;
|
|
54
|
+
}
|
|
55
|
+
interface TuiMouseEventResult {
|
|
56
|
+
handled?: boolean;
|
|
57
|
+
capture?: boolean;
|
|
58
|
+
focus?: boolean;
|
|
59
|
+
render?: boolean;
|
|
60
|
+
}
|
|
61
|
+
type MouseLayoutEntry = { component?: any; height: number; top: number };
|
|
44
62
|
|
|
45
63
|
import * as Diff from "diff";
|
|
46
64
|
import type { BundledLanguage, BundledTheme } from "shiki";
|
|
@@ -71,6 +89,8 @@ const TOOL_RENDER_CACHE = Symbol.for("pi-claude-style-tools:tool-render-cache");
|
|
|
71
89
|
const COMPONENT_PARENT = Symbol.for("pi-claude-style-tools:component-parent");
|
|
72
90
|
const PARENT_TRACKING_PATCH_FLAG = Symbol.for("pi-claude-style-tools:patched-parent-tracking");
|
|
73
91
|
const TOOL_CACHE_PATCH_FLAG = Symbol.for("pi-claude-style-tools:patched-tool-cache-invalidation");
|
|
92
|
+
const TOOL_MOUSE_PATCH_FLAG = Symbol.for("pi-claude-style-tools:patched-tool-fullscreen-mouse");
|
|
93
|
+
const FULLSCREEN_HOVER_PATCH_FLAG = Symbol.for("pi-claude-style-tools:patched-fullscreen-hover");
|
|
74
94
|
const TOOL_IMAGE_EXPAND_PATCH_FLAG = Symbol.for("pi-claude-style-tools:patched-read-image-expansion");
|
|
75
95
|
const CUSTOM_MESSAGE_PATCH_FLAG = Symbol.for("pi-claude-style-tools:patched-custom-message-render");
|
|
76
96
|
const USER_MESSAGE_PATCH_FLAG = Symbol.for("pi-claude-style-tools:patched-user-message-render");
|
|
@@ -880,7 +900,8 @@ function isGroupableTool(value: unknown): value is InstanceType<typeof ToolExecu
|
|
|
880
900
|
|
|
881
901
|
class ToolGroupComponent extends Container {
|
|
882
902
|
private tools: any[] = [];
|
|
883
|
-
|
|
903
|
+
expanded = false;
|
|
904
|
+
private mouseLayout?: { width: number; children: MouseLayoutEntry[] };
|
|
884
905
|
// Memoize full group output. Grouped history is the long-chat bottleneck:
|
|
885
906
|
// each warm frame used to re-render every child tool, re-branch lines, and
|
|
886
907
|
// re-clamp every row even when nothing changed.
|
|
@@ -933,6 +954,8 @@ class ToolGroupComponent extends Container {
|
|
|
933
954
|
addTool(tool: any): void {
|
|
934
955
|
ACTIVE_TOOL_GROUPS.add(this);
|
|
935
956
|
this.tools.push(tool);
|
|
957
|
+
setToolHover(tool, false);
|
|
958
|
+
if (hoveredClickTarget?.owner === tool) hoveredClickTarget = undefined;
|
|
936
959
|
tool[COMPONENT_PARENT] = this;
|
|
937
960
|
// Don't cascade invalidate into every child — only drop our own cache.
|
|
938
961
|
// Child tools already rebuild via their own updateDisplay path.
|
|
@@ -948,7 +971,7 @@ class ToolGroupComponent extends Container {
|
|
|
948
971
|
}
|
|
949
972
|
|
|
950
973
|
setExpanded(expanded: boolean): void {
|
|
951
|
-
if (this.expanded === expanded) return;
|
|
974
|
+
if (this.expanded === expanded && this.tools.every((tool) => tool.expanded === expanded)) return;
|
|
952
975
|
this.expanded = expanded;
|
|
953
976
|
for (const tool of this.tools) tool.setExpanded?.(expanded);
|
|
954
977
|
this.clearRenderCache();
|
|
@@ -962,8 +985,27 @@ class ToolGroupComponent extends Container {
|
|
|
962
985
|
this.clearRenderCache();
|
|
963
986
|
}
|
|
964
987
|
|
|
988
|
+
handleMouse(event: TuiMouseEvent): TuiMouseEventResult | undefined {
|
|
989
|
+
const target = mouseLayoutTarget(this.mouseLayout, event);
|
|
990
|
+
if (event.type === "move") {
|
|
991
|
+
const row = target?.top ?? -1;
|
|
992
|
+
claimClickHover(this, () => { if (setGroupHover(this, -1)) requestComponentRender(this); });
|
|
993
|
+
return setGroupHover(this, row) ? { handled: true, render: true } : undefined;
|
|
994
|
+
}
|
|
995
|
+
if (isPrimaryClick(event) && target) {
|
|
996
|
+
const targets = target.component
|
|
997
|
+
? Array.isArray(target.component) ? target.component : [target.component]
|
|
998
|
+
: this.tools;
|
|
999
|
+
return toggleGroupedTools(this, targets);
|
|
1000
|
+
}
|
|
1001
|
+
return undefined;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
965
1004
|
render(width: number): string[] {
|
|
966
|
-
if (this.tools.length === 0)
|
|
1005
|
+
if (this.tools.length === 0) {
|
|
1006
|
+
rememberMouseLayout(this, width, []);
|
|
1007
|
+
return [];
|
|
1008
|
+
}
|
|
967
1009
|
const safeWidth = Number.isFinite(width) ? Math.max(1, Math.floor(width)) : 1;
|
|
968
1010
|
// Fast path: settled groups with a valid memo skip ALL child walks.
|
|
969
1011
|
// Child mutations mark dirty via clearToolRenderCache → invalidate().
|
|
@@ -975,7 +1017,7 @@ class ToolGroupComponent extends Container {
|
|
|
975
1017
|
&& this.cachedMode === toolBackgroundMode
|
|
976
1018
|
&& this.cachedExpanded === this.expanded
|
|
977
1019
|
) {
|
|
978
|
-
return this.cachedLines;
|
|
1020
|
+
return applyGroupHover(this.cachedLines, this.mouseLayout, (this as any)[GROUP_HOVER_KEY]);
|
|
979
1021
|
}
|
|
980
1022
|
|
|
981
1023
|
const status = this.statusSnapshot();
|
|
@@ -988,6 +1030,12 @@ class ToolGroupComponent extends Container {
|
|
|
988
1030
|
const label = getToolGroupLabel(this.tools);
|
|
989
1031
|
const names = groupedName ? "" : formatToolNameList(this.tools);
|
|
990
1032
|
const overall: ToolStatus = status.error > 0 ? "error" : status.pending > 0 ? "pending" : "success";
|
|
1033
|
+
const mouseChildren: MouseLayoutEntry[] = [];
|
|
1034
|
+
let mouseY = 0;
|
|
1035
|
+
const recordLines = (rowLines: string[], target?: any) => {
|
|
1036
|
+
mouseChildren.push({ component: target, height: rowLines.length, top: mouseY });
|
|
1037
|
+
mouseY += rowLines.length;
|
|
1038
|
+
};
|
|
991
1039
|
// Group header breathes only when every pending member is Agent-family;
|
|
992
1040
|
// mixed groups keep the ordinary on/off light.
|
|
993
1041
|
const pendingTools = this.tools.filter((tool) => getToolStatusForGroup(tool) === "pending");
|
|
@@ -1006,13 +1054,17 @@ class ToolGroupComponent extends Container {
|
|
|
1006
1054
|
if (collapseToSingleRow) {
|
|
1007
1055
|
const entry = { tools: this.tools, name: groupedName, subject };
|
|
1008
1056
|
const entryLine = getCollapsedToolEntryLine(entry, Math.max(1, safeWidth - 3));
|
|
1009
|
-
|
|
1057
|
+
const row = [clampLineWidth(
|
|
1010
1058
|
` ${light} ${entryLine}${toolOutputDetailHint(undefined as any, false, true)}`,
|
|
1011
1059
|
safeWidth,
|
|
1012
|
-
)
|
|
1060
|
+
)];
|
|
1061
|
+
recordLines(row, undefined);
|
|
1062
|
+
lines.push(...row);
|
|
1013
1063
|
} else {
|
|
1014
1064
|
const summary = ` ${light} ${summaryLabel} ${countsText}${names ? ` ${TRANSPARENT_RESET}• ${names}` : ""}${toolOutputDetailHint(undefined as any, this.expanded, true)}`;
|
|
1015
|
-
|
|
1065
|
+
const header = [" ".repeat(safeWidth), clampLineWidth(summary, safeWidth)];
|
|
1066
|
+
recordLines(header, undefined);
|
|
1067
|
+
lines.push(...header);
|
|
1016
1068
|
const childWidth = Math.max(1, safeWidth - 6);
|
|
1017
1069
|
if (this.expanded) {
|
|
1018
1070
|
for (let index = 0; index < total; index++) {
|
|
@@ -1025,7 +1077,9 @@ class ToolGroupComponent extends Container {
|
|
|
1025
1077
|
getToolStatusForGroup(tool),
|
|
1026
1078
|
{ agentBreathe: isAgentFamilyToolName(getToolName(tool)) },
|
|
1027
1079
|
);
|
|
1028
|
-
|
|
1080
|
+
const row = branched.map((line) => clampLineWidth(line, safeWidth));
|
|
1081
|
+
recordLines(row, tool);
|
|
1082
|
+
lines.push(...row);
|
|
1029
1083
|
}
|
|
1030
1084
|
} else {
|
|
1031
1085
|
const entries = collapseRepeatedToolEntries(this.tools);
|
|
@@ -1039,10 +1093,13 @@ class ToolGroupComponent extends Container {
|
|
|
1039
1093
|
getToolGroupOverallStatus(entry.tools),
|
|
1040
1094
|
{ agentBreathe: entry.tools.every((tool) => isAgentFamilyToolName(getToolName(tool))) },
|
|
1041
1095
|
);
|
|
1042
|
-
|
|
1096
|
+
const row = branched.map((line) => clampLineWidth(line, safeWidth));
|
|
1097
|
+
recordLines(row, entry.tools.length === 1 ? entry.tools[0] : entry.tools);
|
|
1098
|
+
lines.push(...row);
|
|
1043
1099
|
}
|
|
1044
1100
|
}
|
|
1045
1101
|
}
|
|
1102
|
+
rememberMouseLayout(this, safeWidth, mouseChildren);
|
|
1046
1103
|
|
|
1047
1104
|
// Final clamp already applied per-line above; avoid a second full pass.
|
|
1048
1105
|
if (canCache) {
|
|
@@ -1058,10 +1115,20 @@ class ToolGroupComponent extends Container {
|
|
|
1058
1115
|
} else {
|
|
1059
1116
|
this.clearRenderCache();
|
|
1060
1117
|
}
|
|
1061
|
-
return lines;
|
|
1118
|
+
return applyGroupHover(lines, this.mouseLayout, (this as any)[GROUP_HOVER_KEY]);
|
|
1062
1119
|
}
|
|
1063
1120
|
}
|
|
1064
1121
|
|
|
1122
|
+
function applyGroupHover(lines: string[], layout: { children: MouseLayoutEntry[] } | undefined, hoverStart: unknown): string[] {
|
|
1123
|
+
if (typeof hoverStart !== "number" || hoverStart < 0 || !layout) return lines;
|
|
1124
|
+
const entry = layout.children.find((candidate) => candidate.top === hoverStart);
|
|
1125
|
+
if (!entry) return lines;
|
|
1126
|
+
const rendered = lines.slice();
|
|
1127
|
+
const line = entry.top + (entry.component ? 0 : Math.max(0, entry.height - 1));
|
|
1128
|
+
if (line < rendered.length) rendered[line] = brightenRenderedLine(rendered[line]);
|
|
1129
|
+
return rendered;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1065
1132
|
function isToolGroupComponent(value: unknown): value is ToolGroupComponent {
|
|
1066
1133
|
return value instanceof ToolGroupComponent;
|
|
1067
1134
|
}
|
|
@@ -1189,7 +1256,7 @@ function maybeGroupToolComponent(parent: any, component: any): void {
|
|
|
1189
1256
|
}
|
|
1190
1257
|
if (isGroupableTool(previous)) {
|
|
1191
1258
|
const group = new ToolGroupComponent();
|
|
1192
|
-
group.
|
|
1259
|
+
group.expanded = (previous as any).expanded === true || (component as any).expanded === true;
|
|
1193
1260
|
group.addTool(previous);
|
|
1194
1261
|
group.addTool(component);
|
|
1195
1262
|
(group as any)[COMPONENT_PARENT] = parent;
|
|
@@ -1265,17 +1332,21 @@ function patchGlobalToolBorders(): void {
|
|
|
1265
1332
|
&& cached?.branchKey === branchKey
|
|
1266
1333
|
&& cached?.branchEpoch === _toolBranchVisualEpoch
|
|
1267
1334
|
) {
|
|
1335
|
+
// A cache hit skips Container.render, which is also where fullscreen
|
|
1336
|
+
// mouse targets are recorded. Restore the layout from that render.
|
|
1337
|
+
if (cached.mouseLayout) (this as { mouseLayout?: unknown }).mouseLayout = cached.mouseLayout;
|
|
1268
1338
|
return cached.lines;
|
|
1269
1339
|
}
|
|
1270
1340
|
}
|
|
1271
1341
|
|
|
1272
1342
|
const rendered = originalRender.call(this, width);
|
|
1343
|
+
const mouseLayout = (this as { mouseLayout?: unknown }).mouseLayout;
|
|
1273
1344
|
if (!Array.isArray(rendered) || rendered.length === 0) return rendered;
|
|
1274
1345
|
const todoOverlay = formatTodoOverlayLines(rendered, width);
|
|
1275
1346
|
if (!isToolExecutionLike(this)) return todoOverlay;
|
|
1276
1347
|
const branchCache = { branchKey: toolBranchRenderCacheKey(), branchEpoch: _toolBranchVisualEpoch };
|
|
1277
1348
|
if (toolBackgroundMode === "default") {
|
|
1278
|
-
(this as any)[TOOL_RENDER_CACHE] = { width, mode: toolBackgroundMode, lines: rendered, ...branchCache };
|
|
1349
|
+
(this as any)[TOOL_RENDER_CACHE] = { width, mode: toolBackgroundMode, lines: rendered, mouseLayout, ...branchCache };
|
|
1279
1350
|
return rendered;
|
|
1280
1351
|
}
|
|
1281
1352
|
|
|
@@ -1287,7 +1358,7 @@ function patchGlobalToolBorders(): void {
|
|
|
1287
1358
|
|
|
1288
1359
|
const { textLines, imageLines } = splitRenderedImageBlock(rendered.slice(start, end + 1));
|
|
1289
1360
|
if (imageLines.length > 0) {
|
|
1290
|
-
(this as any)[TOOL_RENDER_CACHE] = { width, mode: toolBackgroundMode, lines: rendered, ...branchCache };
|
|
1361
|
+
(this as any)[TOOL_RENDER_CACHE] = { width, mode: toolBackgroundMode, lines: rendered, mouseLayout, ...branchCache };
|
|
1291
1362
|
return rendered;
|
|
1292
1363
|
}
|
|
1293
1364
|
// Agent-family tools stay column-aligned with every other tool row — no extra
|
|
@@ -1307,7 +1378,7 @@ function patchGlobalToolBorders(): void {
|
|
|
1307
1378
|
result = [spacerLine, ...core, ...imageLines];
|
|
1308
1379
|
}
|
|
1309
1380
|
|
|
1310
|
-
(this as any)[TOOL_RENDER_CACHE] = { width, mode: toolBackgroundMode, lines: result, ...branchCache };
|
|
1381
|
+
(this as any)[TOOL_RENDER_CACHE] = { width, mode: toolBackgroundMode, lines: result, mouseLayout, ...branchCache };
|
|
1311
1382
|
return result;
|
|
1312
1383
|
};
|
|
1313
1384
|
|
|
@@ -1379,6 +1450,96 @@ function clearToolRenderCache(value: unknown): void {
|
|
|
1379
1450
|
if (isToolGroupComponent(parent)) parent.invalidate();
|
|
1380
1451
|
}
|
|
1381
1452
|
|
|
1453
|
+
/** Fullscreen hit-testing reads the layout recorded by the last render. */
|
|
1454
|
+
function rememberMouseLayout(component: object, width: number, children: MouseLayoutEntry[]): void {
|
|
1455
|
+
(component as { mouseLayout?: { width: number; children: MouseLayoutEntry[] } }).mouseLayout = { width, children };
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
function isPrimaryClick(event: TuiMouseEvent): boolean {
|
|
1459
|
+
return event.type === "click" && event.button === "left";
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
function toggleExpandedComponent(component: any): boolean {
|
|
1463
|
+
if (!component || typeof component.setExpanded !== "function") return false;
|
|
1464
|
+
component.setExpanded(component.expanded !== true);
|
|
1465
|
+
return true;
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
function requestComponentRender(component: any): void {
|
|
1469
|
+
const ui = component?.ui;
|
|
1470
|
+
if (typeof ui?.requestRender === "function") ui.requestRender();
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
const GROUP_HOVER_KEY = Symbol.for("pi-claude-style-tools:group-hover");
|
|
1474
|
+
|
|
1475
|
+
function setGroupHover(group: any, rowStart: number): boolean {
|
|
1476
|
+
if (group[GROUP_HOVER_KEY] === rowStart) return false;
|
|
1477
|
+
group[GROUP_HOVER_KEY] = rowStart;
|
|
1478
|
+
return true;
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
function mouseLayoutTarget(
|
|
1482
|
+
layout: { width: number; children: MouseLayoutEntry[] } | undefined,
|
|
1483
|
+
event: TuiMouseEvent,
|
|
1484
|
+
): MouseLayoutEntry | undefined {
|
|
1485
|
+
if (!layout || layout.width !== event.width || event.y < 0 || event.y >= event.height) return undefined;
|
|
1486
|
+
return layout.children.find((entry) => event.y >= entry.top && event.y < entry.top + entry.height);
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
function toggleGroupedTools(group: any, tools: any[]): { handled: true } | undefined {
|
|
1490
|
+
const targets = tools.filter((tool) => typeof tool?.setExpanded === "function");
|
|
1491
|
+
if (targets.length === 0) return undefined;
|
|
1492
|
+
const expanded = targets.some((tool) => tool.expanded !== true);
|
|
1493
|
+
for (const tool of targets) tool.setExpanded(expanded);
|
|
1494
|
+
if (Array.isArray(group?.tools)) group.expanded = group.tools.some((tool: any) => tool?.expanded === true);
|
|
1495
|
+
group.invalidate?.();
|
|
1496
|
+
const ui = targets.find((tool) => typeof tool?.ui?.requestRender === "function")?.ui;
|
|
1497
|
+
ui?.requestRender?.();
|
|
1498
|
+
return { handled: true };
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
let hoveredClickTarget: { owner: object; clear: () => void } | undefined;
|
|
1502
|
+
let hoverClaimedThisMove = false;
|
|
1503
|
+
|
|
1504
|
+
function claimClickHover(owner: object, clear: () => void): void {
|
|
1505
|
+
hoverClaimedThisMove = true;
|
|
1506
|
+
if (hoveredClickTarget?.owner === owner) return;
|
|
1507
|
+
const previous = hoveredClickTarget;
|
|
1508
|
+
hoveredClickTarget = { owner, clear };
|
|
1509
|
+
previous?.clear();
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
function patchFullscreenHoverExit(): void {
|
|
1513
|
+
const proto = (PiTui as any).TuiAltScreen?.prototype;
|
|
1514
|
+
if (!proto || proto[FULLSCREEN_HOVER_PATCH_FLAG] || typeof proto.handleMouseEvent !== "function") return;
|
|
1515
|
+
const originalHandleMouse = proto.handleMouseEvent;
|
|
1516
|
+
proto.handleMouseEvent = function patchedFullscreenMouseEvent(raw: { button: number }) {
|
|
1517
|
+
const isMove = (raw.button & 35) === 35;
|
|
1518
|
+
if (isMove) hoverClaimedThisMove = false;
|
|
1519
|
+
const result = originalHandleMouse.call(this, raw);
|
|
1520
|
+
if (isMove && !hoverClaimedThisMove && hoveredClickTarget) {
|
|
1521
|
+
const previous = hoveredClickTarget;
|
|
1522
|
+
hoveredClickTarget = undefined;
|
|
1523
|
+
previous.clear();
|
|
1524
|
+
this.requestRender();
|
|
1525
|
+
}
|
|
1526
|
+
return result;
|
|
1527
|
+
};
|
|
1528
|
+
if (typeof proto.handleViewportInput === "function") {
|
|
1529
|
+
const originalInput = proto.handleViewportInput;
|
|
1530
|
+
proto.handleViewportInput = function patchedFullscreenInput(data: string) {
|
|
1531
|
+
if (data === "\x1b[O" && hoveredClickTarget) {
|
|
1532
|
+
const previous = hoveredClickTarget;
|
|
1533
|
+
hoveredClickTarget = undefined;
|
|
1534
|
+
previous.clear();
|
|
1535
|
+
this.requestRender();
|
|
1536
|
+
}
|
|
1537
|
+
return originalInput.call(this, data);
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
proto[FULLSCREEN_HOVER_PATCH_FLAG] = true;
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1382
1543
|
function unrefTimer(timer: ReturnType<typeof setTimeout> | null | undefined): void {
|
|
1383
1544
|
(timer as any)?.unref?.();
|
|
1384
1545
|
}
|
|
@@ -1393,6 +1554,7 @@ function safeInvalidate(ctx: any): void {
|
|
|
1393
1554
|
|
|
1394
1555
|
const ASSISTANT_PATCH_FLAG = Symbol.for("pi-claude-style-tools:patched-assistant-message");
|
|
1395
1556
|
const ASSISTANT_RENDER_PATCH_FLAG = Symbol.for("pi-claude-style-tools:patched-assistant-message-render");
|
|
1557
|
+
const ASSISTANT_MOUSE_PATCH_FLAG = Symbol.for("pi-claude-style-tools:patched-assistant-fullscreen-mouse");
|
|
1396
1558
|
const TOOL_EXECUTION_PATCH_FLAG = Symbol.for("pi-claude-style-tools:patched-tool-execution");
|
|
1397
1559
|
|
|
1398
1560
|
// Rendered-output cache for assistant/user/custom message components.
|
|
@@ -1450,9 +1612,8 @@ const WORKED_TURNS_KEY = "_piClaudeStyleWorkedTurns";
|
|
|
1450
1612
|
const WORKED_DURATION_MARKER = "Turn took";
|
|
1451
1613
|
const THINKING_DURATION_KEY = "_piClaudeStyleThinkingDurationMs";
|
|
1452
1614
|
const THINKING_ACTIVE_KEY = "_piClaudeStyleThinkingActive";
|
|
1453
|
-
const
|
|
1615
|
+
const MAX_REASONABLE_THINKING_DURATION_MS = 24 * 60 * 60 * 1000;
|
|
1454
1616
|
|
|
1455
|
-
let lastThinkingBlockDurationMs: number | undefined;
|
|
1456
1617
|
let thinkingBlockStartMs = 0;
|
|
1457
1618
|
/** True from thinking_start until thinking_end on the current assistant stream. */
|
|
1458
1619
|
let thinkingBlockInFlight = false;
|
|
@@ -1518,9 +1679,25 @@ function pluralizeTurns(n: number): string {
|
|
|
1518
1679
|
return `${n} turn${n === 1 ? "" : "s"}`;
|
|
1519
1680
|
}
|
|
1520
1681
|
|
|
1521
|
-
function thinkingSummaryStyledText(body: string): string {
|
|
1682
|
+
function thinkingSummaryStyledText(body: string, hovered = false): string {
|
|
1522
1683
|
// Preserve the visible thinking text column while omitting ∴ when collapsed.
|
|
1523
|
-
|
|
1684
|
+
const color = hovered ? brightenAnsi(WORKED_LINE_FG) : WORKED_LINE_FG;
|
|
1685
|
+
return ` ${color}${body}${RESET}`;
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
/** Brighten an existing color without changing its hue. */
|
|
1689
|
+
function brightenAnsi(baseAnsi: string): string {
|
|
1690
|
+
const rgb = parseAnsiRgb(baseAnsi);
|
|
1691
|
+
if (!rgb) return "\x1b[97m";
|
|
1692
|
+
const maximum = Math.max(rgb.r, rgb.g, rgb.b);
|
|
1693
|
+
if (maximum === 0) return baseAnsi;
|
|
1694
|
+
const scale = Math.min(1.12, 255 / maximum);
|
|
1695
|
+
const lift = (channel: number) => Math.round(channel * scale);
|
|
1696
|
+
return `\x1b[38;2;${lift(rgb.r)};${lift(rgb.g)};${lift(rgb.b)}m`;
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
function brightenRenderedLine(line: string): string {
|
|
1700
|
+
return line.replace(/\x1b\[38;2;(\d+);(\d+);(\d+)m/g, (_match, r, g, b) => brightenAnsi(`\x1b[38;2;${r};${g};${b}m`));
|
|
1524
1701
|
}
|
|
1525
1702
|
|
|
1526
1703
|
function thinkingActiveSummaryText(): string {
|
|
@@ -1533,34 +1710,47 @@ function thoughtDurationSummaryText(ms: number): string {
|
|
|
1533
1710
|
|
|
1534
1711
|
/** Single-line hidden thinking row — no Text paddingX or thinking symbol. */
|
|
1535
1712
|
class HiddenThinkingSummary {
|
|
1536
|
-
private
|
|
1713
|
+
private body: string;
|
|
1714
|
+
private hovered = false;
|
|
1537
1715
|
private cachedWidth?: number;
|
|
1716
|
+
private cachedHovered?: boolean;
|
|
1538
1717
|
private cachedLines?: string[];
|
|
1539
1718
|
|
|
1540
|
-
constructor(
|
|
1541
|
-
this.
|
|
1719
|
+
constructor(body: string) {
|
|
1720
|
+
this.body = stripAnsi(body).trim();
|
|
1542
1721
|
}
|
|
1543
1722
|
|
|
1544
1723
|
setSummary(summaryText: string): void {
|
|
1545
|
-
|
|
1724
|
+
const body = stripAnsi(summaryText).trim();
|
|
1725
|
+
if (this.body === body) return;
|
|
1726
|
+
this.body = body;
|
|
1727
|
+
this.invalidate();
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
setHovered(hovered: boolean): void {
|
|
1731
|
+
if (this.hovered === hovered) return;
|
|
1732
|
+
this.hovered = hovered;
|
|
1546
1733
|
this.invalidate();
|
|
1547
1734
|
}
|
|
1548
1735
|
|
|
1549
1736
|
invalidate(): void {
|
|
1550
1737
|
this.cachedWidth = undefined;
|
|
1738
|
+
this.cachedHovered = undefined;
|
|
1551
1739
|
this.cachedLines = undefined;
|
|
1552
1740
|
}
|
|
1553
1741
|
|
|
1554
1742
|
render(width: number): string[] {
|
|
1555
|
-
if (this.cachedLines && this.cachedWidth === width) return this.cachedLines;
|
|
1743
|
+
if (this.cachedLines && this.cachedWidth === width && this.cachedHovered === this.hovered) return this.cachedLines;
|
|
1556
1744
|
const safeWidth = Number.isFinite(width) ? Math.max(0, Math.floor(width)) : 0;
|
|
1557
1745
|
if (safeWidth <= 0) {
|
|
1558
1746
|
this.cachedWidth = width;
|
|
1747
|
+
this.cachedHovered = this.hovered;
|
|
1559
1748
|
this.cachedLines = [""];
|
|
1560
1749
|
return this.cachedLines;
|
|
1561
1750
|
}
|
|
1562
|
-
const line = padRenderedLineToWidth(this.
|
|
1751
|
+
const line = padRenderedLineToWidth(thinkingSummaryStyledText(this.body, this.hovered), safeWidth);
|
|
1563
1752
|
this.cachedWidth = width;
|
|
1753
|
+
this.cachedHovered = this.hovered;
|
|
1564
1754
|
this.cachedLines = [line];
|
|
1565
1755
|
return this.cachedLines;
|
|
1566
1756
|
}
|
|
@@ -1568,13 +1758,8 @@ class HiddenThinkingSummary {
|
|
|
1568
1758
|
|
|
1569
1759
|
function getMessageThinkingDurationMs(message: any): number {
|
|
1570
1760
|
const stored = (message as any)?.[THINKING_DURATION_KEY];
|
|
1571
|
-
if (typeof stored === "number" && stored > 0) return stored;
|
|
1572
|
-
if (typeof
|
|
1573
|
-
return lastThinkingBlockDurationMs;
|
|
1574
|
-
}
|
|
1575
|
-
if (typeof (message as any)?.[WORKED_DURATION_KEY] === "number" && (message as any)[WORKED_DURATION_KEY] > 0) {
|
|
1576
|
-
return (message as any)[WORKED_DURATION_KEY];
|
|
1577
|
-
}
|
|
1761
|
+
if (typeof stored === "number" && Number.isFinite(stored) && stored > 0 && stored <= MAX_REASONABLE_THINKING_DURATION_MS) return stored;
|
|
1762
|
+
if (message && typeof message === "object" && stored !== undefined) delete (message as any)[THINKING_DURATION_KEY];
|
|
1578
1763
|
let totalChars = 0;
|
|
1579
1764
|
if (Array.isArray(message?.content)) {
|
|
1580
1765
|
for (const block of message.content) {
|
|
@@ -2043,9 +2228,10 @@ function replaceHiddenThinkingPlaceholders(container: { children?: any[] }, mess
|
|
|
2043
2228
|
let firstReplaced = false;
|
|
2044
2229
|
for (let i = 0; i < container.children.length; i++) {
|
|
2045
2230
|
const child = container.children[i];
|
|
2046
|
-
|
|
2231
|
+
const inner = (child as any)?.child ?? child;
|
|
2232
|
+
if (inner instanceof HiddenThinkingSummary || (inner as any)?.constructor?.name === "HiddenThinkingSummary") {
|
|
2047
2233
|
if (!firstReplaced) {
|
|
2048
|
-
|
|
2234
|
+
inner.setSummary(summary);
|
|
2049
2235
|
firstReplaced = true;
|
|
2050
2236
|
} else {
|
|
2051
2237
|
container.children.splice(i, 1);
|
|
@@ -2053,9 +2239,14 @@ function replaceHiddenThinkingPlaceholders(container: { children?: any[] }, mess
|
|
|
2053
2239
|
}
|
|
2054
2240
|
continue;
|
|
2055
2241
|
}
|
|
2056
|
-
if (isHiddenThinkingPlaceholderText(
|
|
2242
|
+
if (isHiddenThinkingPlaceholderText(inner)) {
|
|
2057
2243
|
if (!firstReplaced) {
|
|
2058
|
-
|
|
2244
|
+
const summaryComp = new HiddenThinkingSummary(summary);
|
|
2245
|
+
if ((child as any)?.child !== undefined) {
|
|
2246
|
+
(child as any).child = summaryComp;
|
|
2247
|
+
} else {
|
|
2248
|
+
container.children[i] = summaryComp;
|
|
2249
|
+
}
|
|
2059
2250
|
firstReplaced = true;
|
|
2060
2251
|
} else {
|
|
2061
2252
|
container.children.splice(i, 1);
|
|
@@ -2067,10 +2258,18 @@ function replaceHiddenThinkingPlaceholders(container: { children?: any[] }, mess
|
|
|
2067
2258
|
|
|
2068
2259
|
class ThinkingParagraph {
|
|
2069
2260
|
private text: string;
|
|
2261
|
+
private hovered = false;
|
|
2070
2262
|
private cachedWidth?: number;
|
|
2071
2263
|
private cachedLines?: string[];
|
|
2264
|
+
private cachedHovered = false;
|
|
2072
2265
|
private chromeEpoch = -1;
|
|
2073
2266
|
|
|
2267
|
+
setHovered(hovered: boolean): void {
|
|
2268
|
+
if (this.hovered === hovered) return;
|
|
2269
|
+
this.hovered = hovered;
|
|
2270
|
+
this.invalidate();
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2074
2273
|
constructor(
|
|
2075
2274
|
text: string,
|
|
2076
2275
|
_markdownTheme: ConstructorParameters<typeof Markdown>[3],
|
|
@@ -2117,6 +2316,7 @@ class ThinkingParagraph {
|
|
|
2117
2316
|
if (
|
|
2118
2317
|
this.cachedLines
|
|
2119
2318
|
&& this.cachedWidth === width
|
|
2319
|
+
&& this.cachedHovered === this.hovered
|
|
2120
2320
|
&& this.chromeEpoch === _toolBranchVisualEpoch
|
|
2121
2321
|
) {
|
|
2122
2322
|
return this.cachedLines;
|
|
@@ -2138,8 +2338,9 @@ class ThinkingParagraph {
|
|
|
2138
2338
|
return this.cachedLines;
|
|
2139
2339
|
}
|
|
2140
2340
|
const lines = sanitizeRenderedTextBlockLines(md.render(safeWidth - PREFIX_W), safeWidth - PREFIX_W);
|
|
2341
|
+
const displayLines = this.hovered ? lines.map((line) => brightenRenderedLine(line)) : lines;
|
|
2141
2342
|
let symbolPlaced = false;
|
|
2142
|
-
const rendered =
|
|
2343
|
+
const rendered = displayLines.map((line: string) => {
|
|
2143
2344
|
if (!symbolPlaced && stripAnsi(line).trim()) {
|
|
2144
2345
|
symbolPlaced = true;
|
|
2145
2346
|
return ` ${prefix} ${line}`;
|
|
@@ -2147,6 +2348,7 @@ class ThinkingParagraph {
|
|
|
2147
2348
|
return ` ${line}`;
|
|
2148
2349
|
}).map((line) => clampLineWidth(line, safeWidth));
|
|
2149
2350
|
this.cachedWidth = width;
|
|
2351
|
+
this.cachedHovered = this.hovered;
|
|
2150
2352
|
this.cachedLines = rendered;
|
|
2151
2353
|
this.chromeEpoch = _toolBranchVisualEpoch;
|
|
2152
2354
|
return rendered;
|
|
@@ -2438,8 +2640,10 @@ function patchAssistantMessages(): void {
|
|
|
2438
2640
|
// - "full" mode: behaves like stock pi (stays collapsed).
|
|
2439
2641
|
const liveMode = getThinkingMode() === "live";
|
|
2440
2642
|
const thinkingCollapsed = !!(this as any).hideThinkingBlock;
|
|
2441
|
-
const
|
|
2442
|
-
|
|
2643
|
+
const override = (this as any).thinkingVisibilityOverrides?.get?.(0);
|
|
2644
|
+
const showLiveThinking = liveMode && thinkingCollapsed && override !== true && isLiveThinkingMessage(this, message);
|
|
2645
|
+
const hideThinking = override ?? (thinkingCollapsed && !showLiveThinking);
|
|
2646
|
+
if (hideThinking && messageHasThinkingContent(message)) {
|
|
2443
2647
|
// Pi wraps this in theme.italic/fg again — keep plain label for the placeholder pass.
|
|
2444
2648
|
(this as any).hiddenThinkingLabel = "Thinking…";
|
|
2445
2649
|
}
|
|
@@ -2453,21 +2657,32 @@ function patchAssistantMessages(): void {
|
|
|
2453
2657
|
// Replace text-block Markdown children with DottedParagraph wrappers
|
|
2454
2658
|
const container = (this as any).contentContainer;
|
|
2455
2659
|
if (!container?.children) return;
|
|
2456
|
-
if (
|
|
2660
|
+
if (hideThinking && messageHasThinkingContent(message)) {
|
|
2457
2661
|
replaceHiddenThinkingPlaceholders(container, message);
|
|
2458
2662
|
}
|
|
2459
2663
|
const mdTheme = (this as any).markdownTheme;
|
|
2460
2664
|
for (let i = container.children.length - 1; i >= 0; i--) {
|
|
2461
2665
|
const child = container.children[i];
|
|
2462
|
-
|
|
2463
|
-
|
|
2666
|
+
const inner = (child as any)?.child ?? child;
|
|
2667
|
+
if (isMarkdownComponent(inner)) {
|
|
2668
|
+
const text = (inner as any).text;
|
|
2464
2669
|
if (!text) continue;
|
|
2465
|
-
const isThinking = !!(
|
|
2670
|
+
const isThinking = !!(inner as any).defaultTextStyle?.italic;
|
|
2466
2671
|
if (isThinking) {
|
|
2467
|
-
const style = (
|
|
2468
|
-
|
|
2672
|
+
const style = (inner as any).defaultTextStyle;
|
|
2673
|
+
const replacement = new ThinkingParagraph(text, mdTheme, style);
|
|
2674
|
+
if ((child as any)?.child !== undefined) {
|
|
2675
|
+
(child as any).child = replacement;
|
|
2676
|
+
} else {
|
|
2677
|
+
container.children[i] = replacement;
|
|
2678
|
+
}
|
|
2469
2679
|
} else {
|
|
2470
|
-
|
|
2680
|
+
const replacement = new DottedParagraph(text, mdTheme);
|
|
2681
|
+
if ((child as any)?.child !== undefined) {
|
|
2682
|
+
(child as any).child = replacement;
|
|
2683
|
+
} else {
|
|
2684
|
+
container.children[i] = replacement;
|
|
2685
|
+
}
|
|
2471
2686
|
}
|
|
2472
2687
|
}
|
|
2473
2688
|
}
|
|
@@ -2496,8 +2711,120 @@ function patchAssistantMessages(): void {
|
|
|
2496
2711
|
proto[ASSISTANT_PATCH_FLAG] = true;
|
|
2497
2712
|
}
|
|
2498
2713
|
|
|
2714
|
+
const THINKING_HOVER_KEY = Symbol.for("pi-claude-style-tools:thinking-hover");
|
|
2715
|
+
|
|
2716
|
+
function thinkingVisualChild(child: any): any {
|
|
2717
|
+
const inner = child?.child ?? child;
|
|
2718
|
+
if (
|
|
2719
|
+
inner instanceof HiddenThinkingSummary
|
|
2720
|
+
|| inner instanceof ThinkingParagraph
|
|
2721
|
+
|| inner?.constructor?.name === "HiddenThinkingSummary"
|
|
2722
|
+
|| inner?.constructor?.name === "ThinkingParagraph"
|
|
2723
|
+
) return inner;
|
|
2724
|
+
return undefined;
|
|
2725
|
+
}
|
|
2726
|
+
|
|
2727
|
+
function thinkingLayout(component: any, width: number): Array<{ component: any; height: number }> {
|
|
2728
|
+
const container = component?.contentContainer;
|
|
2729
|
+
const children = Array.isArray(container?.children) ? container.children : [];
|
|
2730
|
+
if (container?.mouseLayout?.width === width) return container.mouseLayout.children;
|
|
2731
|
+
return children.map((child: any) => ({ component: child, height: child.render(width).length }));
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2734
|
+
function thinkingRowAt(component: any, event: TuiMouseEvent): any {
|
|
2735
|
+
let childY = 0;
|
|
2736
|
+
for (const entry of thinkingLayout(component, event.width)) {
|
|
2737
|
+
if (event.y >= childY && event.y < childY + entry.height) return thinkingVisualChild(entry.component);
|
|
2738
|
+
childY += entry.height;
|
|
2739
|
+
}
|
|
2740
|
+
return undefined;
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2743
|
+
function setThinkingHover(component: any, hovered: any): boolean {
|
|
2744
|
+
const previous = component[THINKING_HOVER_KEY];
|
|
2745
|
+
if (previous === hovered) return false;
|
|
2746
|
+
previous?.setHovered?.(false);
|
|
2747
|
+
if (hovered) hovered.setHovered(true);
|
|
2748
|
+
component[THINKING_HOVER_KEY] = hovered;
|
|
2749
|
+
clearMessageRenderCache(component);
|
|
2750
|
+
return true;
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2753
|
+
function patchAssistantFullscreenMouse(): void {
|
|
2754
|
+
const proto = AssistantMessageComponent.prototype as any;
|
|
2755
|
+
if (proto[ASSISTANT_MOUSE_PATCH_FLAG]) return;
|
|
2756
|
+
const originalHandleMouse = proto.handleMouse;
|
|
2757
|
+
proto.handleMouse = function patchedAssistantHandleMouse(event: TuiMouseEvent) {
|
|
2758
|
+
const hovered = thinkingRowAt(this, event);
|
|
2759
|
+
if (event.type === "move" && hovered) {
|
|
2760
|
+
claimClickHover(this, () => { if (setThinkingHover(this, undefined)) requestComponentRender(this); });
|
|
2761
|
+
}
|
|
2762
|
+
const hoverChanged = event.type === "move" ? setThinkingHover(this, hovered) : false;
|
|
2763
|
+
if (event.type === "move") return hoverChanged ? { handled: true, render: true } : undefined;
|
|
2764
|
+
// Claim the row before Pi's MouseRegion. Its handler restores the italic
|
|
2765
|
+
// "Thinking…" label while a block is streaming.
|
|
2766
|
+
if (isPrimaryClick(event) && hovered && this.lastMessage) {
|
|
2767
|
+
const overrides = this.thinkingVisibilityOverrides instanceof Map
|
|
2768
|
+
? this.thinkingVisibilityOverrides
|
|
2769
|
+
: new Map();
|
|
2770
|
+
this.thinkingVisibilityOverrides = overrides;
|
|
2771
|
+
overrides.set(0, hovered instanceof ThinkingParagraph);
|
|
2772
|
+
this.updateContent(this.lastMessage);
|
|
2773
|
+
return { handled: true };
|
|
2774
|
+
}
|
|
2775
|
+
return typeof originalHandleMouse === "function" ? originalHandleMouse.call(this, event) : undefined;
|
|
2776
|
+
};
|
|
2777
|
+
proto[ASSISTANT_MOUSE_PATCH_FLAG] = true;
|
|
2778
|
+
}
|
|
2779
|
+
|
|
2499
2780
|
const TOOL_BG_PATCH_FLAG = Symbol.for("pi-claude-style-tools:patched-tool-bg-sync");
|
|
2500
2781
|
|
|
2782
|
+
const TOOL_HOVER_KEY = Symbol.for("pi-claude-style-tools:tool-hover");
|
|
2783
|
+
|
|
2784
|
+
function setToolHover(component: any, hovered: boolean): boolean {
|
|
2785
|
+
if (component[TOOL_HOVER_KEY] === hovered) return false;
|
|
2786
|
+
component[TOOL_HOVER_KEY] = hovered;
|
|
2787
|
+
clearToolRenderCache(component);
|
|
2788
|
+
return true;
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2791
|
+
function patchToolFullscreenMouse(): void {
|
|
2792
|
+
const proto = ToolExecutionComponent.prototype as any;
|
|
2793
|
+
if (proto[TOOL_MOUSE_PATCH_FLAG]) return;
|
|
2794
|
+
const originalHandleMouse = proto.handleMouse;
|
|
2795
|
+
if (typeof originalHandleMouse !== "function") return;
|
|
2796
|
+
const originalRender = proto.render;
|
|
2797
|
+
proto.render = function patchedToolHoverRender(width: number) {
|
|
2798
|
+
const lines = originalRender.call(this, width);
|
|
2799
|
+
if (this[TOOL_HOVER_KEY] !== true || !Array.isArray(lines)) return lines;
|
|
2800
|
+
const titleIndex = lines.findIndex((line: string) => stripAnsi(line).trim().length > 0 && !/^\s*[╭╰─│]+\s*$/.test(stripAnsi(line)));
|
|
2801
|
+
if (titleIndex < 0) return lines;
|
|
2802
|
+
const rendered = lines.slice();
|
|
2803
|
+
rendered[titleIndex] = brightenRenderedLine(rendered[titleIndex]);
|
|
2804
|
+
return rendered;
|
|
2805
|
+
};
|
|
2806
|
+
proto.handleMouse = function patchedToolHandleMouse(event: TuiMouseEvent) {
|
|
2807
|
+
if (event.type === "move") {
|
|
2808
|
+
claimClickHover(this, () => { if (setToolHover(this, false)) requestComponentRender(this); });
|
|
2809
|
+
return setToolHover(this, true) ? { handled: true, render: true } : undefined;
|
|
2810
|
+
}
|
|
2811
|
+
const result = originalHandleMouse.call(this, event);
|
|
2812
|
+
if (result) return result;
|
|
2813
|
+
// Self-rendered rows forward into their own content. Default-shell rows
|
|
2814
|
+
// reach here only when no nested region claimed the click, including the
|
|
2815
|
+
// blank outline padding our container patch paints around the content.
|
|
2816
|
+
if (!isPrimaryClick(event) || !this.result) return undefined;
|
|
2817
|
+
const group = this[COMPONENT_PARENT];
|
|
2818
|
+
if (group?.constructor?.name === "ToolGroupComponent") {
|
|
2819
|
+
return toggleGroupedTools(group, [this]);
|
|
2820
|
+
}
|
|
2821
|
+
if (!toggleExpandedComponent(this)) return undefined;
|
|
2822
|
+
requestComponentRender(this);
|
|
2823
|
+
return { handled: true };
|
|
2824
|
+
};
|
|
2825
|
+
proto[TOOL_MOUSE_PATCH_FLAG] = true;
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2501
2828
|
function patchToolExecutionBackgroundSync(): void {
|
|
2502
2829
|
const proto = ToolExecutionComponent.prototype as any;
|
|
2503
2830
|
if (proto[TOOL_BG_PATCH_FLAG]) return;
|
|
@@ -5257,7 +5584,6 @@ function trackThinkingBlockEvents(event: any, ctx?: any): void {
|
|
|
5257
5584
|
if (evt.type === "thinking_start") {
|
|
5258
5585
|
thinkingBlockInFlight = true;
|
|
5259
5586
|
thinkingBlockStartMs = Date.now();
|
|
5260
|
-
lastThinkingBlockDurationMs = undefined;
|
|
5261
5587
|
if (message?.role === "assistant") {
|
|
5262
5588
|
(message as any)[THINKING_ACTIVE_KEY] = true;
|
|
5263
5589
|
delete (message as any)[THINKING_DURATION_KEY];
|
|
@@ -5267,10 +5593,14 @@ function trackThinkingBlockEvents(event: any, ctx?: any): void {
|
|
|
5267
5593
|
}
|
|
5268
5594
|
if (evt.type === "thinking_end") {
|
|
5269
5595
|
thinkingBlockInFlight = false;
|
|
5270
|
-
const duration =
|
|
5596
|
+
const duration = thinkingBlockStartMs > 0
|
|
5597
|
+
? Math.max(0, Date.now() - thinkingBlockStartMs)
|
|
5598
|
+
: undefined;
|
|
5271
5599
|
if (message?.role === "assistant") delete (message as any)[THINKING_ACTIVE_KEY];
|
|
5272
|
-
|
|
5273
|
-
|
|
5600
|
+
if (typeof duration === "number" && duration <= MAX_REASONABLE_THINKING_DURATION_MS) {
|
|
5601
|
+
if (message?.role === "assistant") (message as any)[THINKING_DURATION_KEY] = duration;
|
|
5602
|
+
}
|
|
5603
|
+
thinkingBlockStartMs = 0;
|
|
5274
5604
|
refreshThinkingChrome();
|
|
5275
5605
|
return;
|
|
5276
5606
|
}
|
|
@@ -5285,10 +5615,10 @@ function trackThinkingBlockEvents(event: any, ctx?: any): void {
|
|
|
5285
5615
|
thinkingBlockInFlight = false;
|
|
5286
5616
|
const duration = thinkingBlockStartMs > 0 ? Math.max(0, Date.now() - thinkingBlockStartMs) : undefined;
|
|
5287
5617
|
if (message?.role === "assistant") delete (message as any)[THINKING_ACTIVE_KEY];
|
|
5288
|
-
if (typeof duration === "number") {
|
|
5289
|
-
lastThinkingBlockDurationMs = duration;
|
|
5618
|
+
if (typeof duration === "number" && duration <= MAX_REASONABLE_THINKING_DURATION_MS) {
|
|
5290
5619
|
if (message?.role === "assistant") (message as any)[THINKING_DURATION_KEY] = duration;
|
|
5291
5620
|
}
|
|
5621
|
+
thinkingBlockStartMs = 0;
|
|
5292
5622
|
refreshThinkingChrome();
|
|
5293
5623
|
}
|
|
5294
5624
|
}
|
|
@@ -5337,6 +5667,7 @@ function registerThinkingLabels(pi: ExtensionAPI): void {
|
|
|
5337
5667
|
// global in-flight flag set and the next message renders a stale
|
|
5338
5668
|
// "Thinking..." row until its own thinking events arrive.
|
|
5339
5669
|
thinkingBlockInFlight = false;
|
|
5670
|
+
thinkingBlockStartMs = 0;
|
|
5340
5671
|
delete (message as any)[THINKING_ACTIVE_KEY];
|
|
5341
5672
|
}
|
|
5342
5673
|
});
|
|
@@ -5353,13 +5684,10 @@ function registerThinkingLabels(pi: ExtensionAPI): void {
|
|
|
5353
5684
|
// at the end of the message instead of sticking into later calls.
|
|
5354
5685
|
thinkingBlockInFlight = false;
|
|
5355
5686
|
delete (message as any)[THINKING_ACTIVE_KEY];
|
|
5356
|
-
if (typeof (message as any)[THINKING_DURATION_KEY] !== "number") {
|
|
5357
|
-
const duration =
|
|
5358
|
-
if (
|
|
5359
|
-
lastThinkingBlockDurationMs = duration;
|
|
5687
|
+
if (typeof (message as any)[THINKING_DURATION_KEY] !== "number" && thinkingBlockStartMs > 0) {
|
|
5688
|
+
const duration = Math.max(0, Date.now() - thinkingBlockStartMs);
|
|
5689
|
+
if (duration <= MAX_REASONABLE_THINKING_DURATION_MS) {
|
|
5360
5690
|
(message as any)[THINKING_DURATION_KEY] = duration;
|
|
5361
|
-
} else if (typeof lastThinkingBlockDurationMs === "number" && lastThinkingBlockDurationMs > 0) {
|
|
5362
|
-
(message as any)[THINKING_DURATION_KEY] = lastThinkingBlockDurationMs;
|
|
5363
5691
|
}
|
|
5364
5692
|
}
|
|
5365
5693
|
thinkingBlockStartMs = 0;
|
|
@@ -6423,11 +6751,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
6423
6751
|
patchToolExecutionBackgroundSync();
|
|
6424
6752
|
patchToolRenderCacheInvalidation();
|
|
6425
6753
|
patchReadImageExpansion();
|
|
6754
|
+
patchFullscreenHoverExit();
|
|
6755
|
+
patchToolFullscreenMouse();
|
|
6426
6756
|
patchContainerParentTracking();
|
|
6427
6757
|
patchGlobalToolBorders();
|
|
6428
6758
|
patchCustomMessageRender();
|
|
6429
6759
|
patchUserMessageRender();
|
|
6430
6760
|
patchAssistantMessages();
|
|
6761
|
+
patchAssistantFullscreenMouse();
|
|
6431
6762
|
patchToolExecutionRenderers();
|
|
6432
6763
|
applyDiffPalette();
|
|
6433
6764
|
registerThinkingLabels(pi);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-claude-code-ui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.84",
|
|
4
4
|
"description": "Claude Code-style grouped tool rows for pi with Ctrl+O/Ctrl+Shift+O previews, math rendering, and consistent built-in, MCP, and custom tool rendering",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
],
|
|
38
38
|
"scripts": {
|
|
39
39
|
"test": "node --experimental-strip-types --test scripts/bash-command.test.ts",
|
|
40
|
+
"test:fullscreen": "node scripts/fullscreen-mouse.test.mjs",
|
|
40
41
|
"typecheck": "tsc --noEmit --target ES2022 --module NodeNext --moduleResolution NodeNext --skipLibCheck extensions/index.ts extensions/spinner.ts",
|
|
41
42
|
"pack:dry-run": "npm pack --dry-run",
|
|
42
43
|
"publish:dry-run": "npm publish --dry-run",
|