@esso0428/pi-subagents 0.15.14 → 0.15.15
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 -37
- package/dist/ui/agent-widget.d.ts +8 -0
- package/dist/ui/agent-widget.d.ts.map +1 -1
- package/dist/ui/agent-widget.js +40 -12
- package/dist/ui/agent-widget.js.map +1 -1
- package/dist/ui/ccstyle/diff/diff-renderer.d.ts.map +1 -1
- package/dist/ui/ccstyle/diff/diff-renderer.js +2 -4
- package/dist/ui/ccstyle/diff/diff-renderer.js.map +1 -1
- package/dist/ui/ccstyle/tool-renderer.d.ts.map +1 -1
- package/dist/ui/ccstyle/tool-renderer.js +4 -10
- package/dist/ui/ccstyle/tool-renderer.js.map +1 -1
- package/dist/ui/ccstyle/tool-result.d.ts +9 -16
- package/dist/ui/ccstyle/tool-result.d.ts.map +1 -1
- package/dist/ui/ccstyle/tool-result.js +71 -121
- package/dist/ui/ccstyle/tool-result.js.map +1 -1
- package/dist/ui/conversation-timeline.d.ts +14 -9
- package/dist/ui/conversation-timeline.d.ts.map +1 -1
- package/dist/ui/conversation-timeline.js +60 -72
- package/dist/ui/conversation-timeline.js.map +1 -1
- package/dist/ui/conversation-viewer.d.ts +4 -1
- package/dist/ui/conversation-viewer.d.ts.map +1 -1
- package/dist/ui/conversation-viewer.js +95 -94
- package/dist/ui/conversation-viewer.js.map +1 -1
- package/dist/write-execution.d.ts +0 -2
- package/dist/write-execution.d.ts.map +1 -1
- package/dist/write-execution.js +1 -5
- package/dist/write-execution.js.map +1 -1
- package/package.json +1 -1
- package/src/ui/agent-widget.ts +38 -12
- package/src/ui/ccstyle/diff/diff-renderer.ts +2 -7
- package/src/ui/ccstyle/tool-renderer.ts +9 -22
- package/src/ui/ccstyle/tool-result.ts +82 -186
- package/src/ui/conversation-timeline.ts +77 -73
- package/src/ui/conversation-viewer.ts +102 -108
- package/src/write-execution.ts +1 -7
- package/test/ui/agent-widget.test.ts +123 -0
- package/test/ui/conversation-diff.test.ts +1 -47
- package/test/ui/conversation-viewer.test.ts +181 -112
- package/test/write-execution.test.ts +0 -2
- package/.pi/tasks/tasks-01a09263-0ac6-77be-97f5-664b3ffea76b.json +0 -53
- package/.pi/workflows/index.json +0 -5
|
@@ -29,10 +29,15 @@ export interface TimelineRenderLine {
|
|
|
29
29
|
railable?: boolean;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
export interface
|
|
33
|
-
|
|
32
|
+
export interface TimelineBlockSpan {
|
|
33
|
+
/** Index into the snapshot's messageBlocks array, not the source block array. */
|
|
34
|
+
blockIndex: number;
|
|
34
35
|
startLine: number;
|
|
35
36
|
height: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface TimelineToolSpan extends TimelineBlockSpan {
|
|
40
|
+
toolCallId: string;
|
|
36
41
|
/** The only row that opens a collapsed card with a single click. */
|
|
37
42
|
clickStartLine?: number;
|
|
38
43
|
clickHeight?: number;
|
|
@@ -43,9 +48,17 @@ export interface ConversationTimelineSnapshot {
|
|
|
43
48
|
messageStarts: number[];
|
|
44
49
|
messageBlocks: ConversationBlock[];
|
|
45
50
|
interactiveToolIds: string[];
|
|
51
|
+
blockSpans: TimelineBlockSpan[];
|
|
46
52
|
toolSpans: TimelineToolSpan[];
|
|
47
53
|
}
|
|
48
54
|
|
|
55
|
+
export interface ConversationTimelineMouseResult {
|
|
56
|
+
handled?: boolean;
|
|
57
|
+
capture?: boolean;
|
|
58
|
+
render?: boolean;
|
|
59
|
+
focusedBlockIndex?: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
49
62
|
export interface ConversationTimelineOptions {
|
|
50
63
|
cwd?: string;
|
|
51
64
|
record?: AgentRecord;
|
|
@@ -81,7 +94,7 @@ function fallbackToolLines(block: ConversationBlock, width: number, expanded = f
|
|
|
81
94
|
const clipped = truncateToWidth(line, width, "");
|
|
82
95
|
if (!hovered || expanded || !theme) return clipped;
|
|
83
96
|
const plain = stripAnsi(clipped);
|
|
84
|
-
const marker = plain.includes(showMoreHintText()) ? showMoreHintText() :
|
|
97
|
+
const marker = plain.includes(showMoreHintText()) ? showMoreHintText() : "";
|
|
85
98
|
if (!marker) return clipped;
|
|
86
99
|
const markerIndex = plain.indexOf(marker);
|
|
87
100
|
return theme.fg("dim", plain.slice(0, markerIndex)) + theme.fg("text", marker) + theme.fg("dim", plain.slice(markerIndex + marker.length));
|
|
@@ -145,7 +158,6 @@ export class ConversationTimeline implements Component {
|
|
|
145
158
|
private hoveredToolCallId: string | undefined;
|
|
146
159
|
private hoveredIoView: any;
|
|
147
160
|
private hoveredIoSection: "input" | "output" | null = null;
|
|
148
|
-
/** The last tool clicked; used to make only clickCount=2 collapse it. */
|
|
149
161
|
private lastClickedToolCallId: string | undefined;
|
|
150
162
|
private lastClickedPoint: { x: number; y: number } | undefined;
|
|
151
163
|
private toolComponents = new Map<string, Component & { handleMouse?: (event: TuiMouseEvent) => unknown }>();
|
|
@@ -154,6 +166,7 @@ export class ConversationTimeline implements Component {
|
|
|
154
166
|
messageStarts: [],
|
|
155
167
|
messageBlocks: [],
|
|
156
168
|
interactiveToolIds: [],
|
|
169
|
+
blockSpans: [],
|
|
157
170
|
toolSpans: [],
|
|
158
171
|
};
|
|
159
172
|
private lastWidth = 0;
|
|
@@ -169,15 +182,6 @@ export class ConversationTimeline implements Component {
|
|
|
169
182
|
const ids = new Set(this.blocks.filter((block) => block.kind === "tool" && block.toolCallId).map((block) => block.toolCallId!));
|
|
170
183
|
if (this.focusedToolCallId && !ids.has(this.focusedToolCallId)) this.focusedToolCallId = undefined;
|
|
171
184
|
if (this.hoveredToolCallId && !ids.has(this.hoveredToolCallId)) this.hoveredToolCallId = undefined;
|
|
172
|
-
if (this.hoveredIoView && typeof this.hoveredIoView.setHoveredSection === "function") {
|
|
173
|
-
this.hoveredIoView.setHoveredSection(null);
|
|
174
|
-
this.hoveredIoView = undefined;
|
|
175
|
-
this.hoveredIoSection = null;
|
|
176
|
-
}
|
|
177
|
-
if (this.lastClickedToolCallId && !ids.has(this.lastClickedToolCallId)) {
|
|
178
|
-
this.lastClickedToolCallId = undefined;
|
|
179
|
-
this.lastClickedPoint = undefined;
|
|
180
|
-
}
|
|
181
185
|
for (const id of this.expandedTools) if (!ids.has(id)) this.expandedTools.delete(id);
|
|
182
186
|
for (const id of this.toolComponents.keys()) if (!ids.has(id)) this.toolComponents.delete(id);
|
|
183
187
|
this.invalidate();
|
|
@@ -189,13 +193,6 @@ export class ConversationTimeline implements Component {
|
|
|
189
193
|
if (!show) {
|
|
190
194
|
this.focusedToolCallId = undefined;
|
|
191
195
|
this.hoveredToolCallId = undefined;
|
|
192
|
-
if (this.hoveredIoView && typeof this.hoveredIoView.setHoveredSection === "function") {
|
|
193
|
-
this.hoveredIoView.setHoveredSection(null);
|
|
194
|
-
}
|
|
195
|
-
this.hoveredIoView = undefined;
|
|
196
|
-
this.hoveredIoSection = null;
|
|
197
|
-
this.lastClickedToolCallId = undefined;
|
|
198
|
-
this.lastClickedPoint = undefined;
|
|
199
196
|
}
|
|
200
197
|
this.invalidate();
|
|
201
198
|
}
|
|
@@ -213,10 +210,6 @@ export class ConversationTimeline implements Component {
|
|
|
213
210
|
return this.focusedToolCallId;
|
|
214
211
|
}
|
|
215
212
|
|
|
216
|
-
getHoveredToolCallId(): string | undefined {
|
|
217
|
-
return this.hoveredToolCallId;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
213
|
getSnapshot(): ConversationTimelineSnapshot {
|
|
221
214
|
if (this.lastWidth <= 0) this.render(80);
|
|
222
215
|
return this.snapshot;
|
|
@@ -255,6 +248,7 @@ export class ConversationTimeline implements Component {
|
|
|
255
248
|
const messageStarts: number[] = [];
|
|
256
249
|
const messageBlocks: ConversationBlock[] = [];
|
|
257
250
|
const interactiveToolIds: string[] = [];
|
|
251
|
+
const blockSpans: TimelineBlockSpan[] = [];
|
|
258
252
|
const toolSpans: TimelineToolSpan[] = [];
|
|
259
253
|
const visibleBlocks = this.blocks.filter((block) => this.showTools || block.kind !== "tool");
|
|
260
254
|
|
|
@@ -272,6 +266,7 @@ export class ConversationTimeline implements Component {
|
|
|
272
266
|
|
|
273
267
|
const hasRoleHeader = block.role === "user" || block.role === "assistant";
|
|
274
268
|
const blockStartLine = lines.length;
|
|
269
|
+
const snapshotBlockIndex = messageBlocks.length;
|
|
275
270
|
messageStarts.push(blockStartLine);
|
|
276
271
|
messageBlocks.push(block);
|
|
277
272
|
if (hasRoleHeader) {
|
|
@@ -309,8 +304,6 @@ export class ConversationTimeline implements Component {
|
|
|
309
304
|
this.theme,
|
|
310
305
|
);
|
|
311
306
|
} catch {
|
|
312
|
-
// Pi's interactive theme is a capability, not a viewer dependency;
|
|
313
|
-
// test hosts and older Pi versions can lack its initialized theme.
|
|
314
307
|
rendered = fallbackToolLines(
|
|
315
308
|
block,
|
|
316
309
|
requestedWidth,
|
|
@@ -323,19 +316,23 @@ export class ConversationTimeline implements Component {
|
|
|
323
316
|
lines.push({ text: truncateToWidth(line, requestedWidth), plain: stripAnsi(line), blockIndex, railable: true });
|
|
324
317
|
}
|
|
325
318
|
if (id) {
|
|
326
|
-
const startLine = blockStartLine;
|
|
327
319
|
const hintLine = !this.expandedTools.has(id)
|
|
328
320
|
? rendered.findIndex((line) => {
|
|
329
321
|
const plain = stripAnsi(line);
|
|
330
322
|
return plain.includes(showMoreHintText()) || plain.includes("Enter to expand");
|
|
331
323
|
})
|
|
332
324
|
: -1;
|
|
325
|
+
const blockHeight = lines.length - blockStartLine;
|
|
326
|
+
blockSpans.push({ blockIndex: snapshotBlockIndex, startLine: blockStartLine, height: blockHeight });
|
|
333
327
|
toolSpans.push({
|
|
328
|
+
blockIndex: snapshotBlockIndex,
|
|
334
329
|
toolCallId: id,
|
|
335
|
-
startLine,
|
|
336
|
-
height:
|
|
337
|
-
...(hintLine >= 0 ? { clickStartLine:
|
|
330
|
+
startLine: blockStartLine,
|
|
331
|
+
height: blockHeight,
|
|
332
|
+
...(hintLine >= 0 ? { clickStartLine: blockStartLine + (hasRoleHeader ? 1 : 0) + hintLine, clickHeight: 1 } : {}),
|
|
338
333
|
});
|
|
334
|
+
} else {
|
|
335
|
+
blockSpans.push({ blockIndex: snapshotBlockIndex, startLine: blockStartLine, height: lines.length - blockStartLine });
|
|
339
336
|
}
|
|
340
337
|
continue;
|
|
341
338
|
}
|
|
@@ -358,14 +355,15 @@ export class ConversationTimeline implements Component {
|
|
|
358
355
|
}
|
|
359
356
|
}
|
|
360
357
|
}
|
|
358
|
+
blockSpans.push({ blockIndex: snapshotBlockIndex, startLine: blockStartLine, height: lines.length - blockStartLine });
|
|
361
359
|
}
|
|
362
360
|
|
|
363
361
|
this.lastWidth = requestedWidth;
|
|
364
|
-
this.snapshot = { lines, messageStarts, messageBlocks, interactiveToolIds, toolSpans };
|
|
362
|
+
this.snapshot = { lines, messageStarts, messageBlocks, interactiveToolIds, blockSpans, toolSpans };
|
|
365
363
|
return lines.map((line) => line.text);
|
|
366
364
|
}
|
|
367
365
|
|
|
368
|
-
handleMouse(event: TuiMouseEvent):
|
|
366
|
+
handleMouse(event: TuiMouseEvent): ConversationTimelineMouseResult | undefined {
|
|
369
367
|
if (event.type === "move") {
|
|
370
368
|
const hovered = this.snapshot.toolSpans.find((span) =>
|
|
371
369
|
span.clickStartLine !== undefined &&
|
|
@@ -389,8 +387,7 @@ export class ConversationTimeline implements Component {
|
|
|
389
387
|
}
|
|
390
388
|
}
|
|
391
389
|
}
|
|
392
|
-
const unchanged = hovered === this.hoveredToolCallId &&
|
|
393
|
-
nextIoView === this.hoveredIoView && nextIoSection === this.hoveredIoSection;
|
|
390
|
+
const unchanged = hovered === this.hoveredToolCallId && nextIoView === this.hoveredIoView && nextIoSection === this.hoveredIoSection;
|
|
394
391
|
if (unchanged) return undefined;
|
|
395
392
|
if (this.hoveredIoView && this.hoveredIoView !== nextIoView && typeof this.hoveredIoView.setHoveredSection === "function") {
|
|
396
393
|
this.hoveredIoView.setHoveredSection(null);
|
|
@@ -404,63 +401,70 @@ export class ConversationTimeline implements Component {
|
|
|
404
401
|
return { handled: true, render: true };
|
|
405
402
|
}
|
|
406
403
|
|
|
407
|
-
// The TUI sends a click only when a component claims the preceding press.
|
|
408
|
-
// Claim only the collapsed hint (or the last expanded card's hit area for
|
|
409
|
-
// a possible second click); never change expansion on press or release.
|
|
410
404
|
if (event.type !== "click" && event.type !== "press") return undefined;
|
|
411
405
|
if (event.button !== "left") return undefined;
|
|
412
|
-
|
|
406
|
+
|
|
407
|
+
// Keep the hint ahead of the full tool span. Its row has a distinct action.
|
|
413
408
|
const hintSpan = this.snapshot.toolSpans.find((candidate) =>
|
|
414
409
|
candidate.clickStartLine !== undefined &&
|
|
415
410
|
event.y >= candidate.clickStartLine &&
|
|
416
411
|
event.y < candidate.clickStartLine + (candidate.clickHeight ?? 1),
|
|
417
412
|
);
|
|
418
413
|
const fullSpan = this.snapshot.toolSpans.find((candidate) =>
|
|
419
|
-
event.y >= candidate.startLine &&
|
|
420
|
-
event.y < candidate.startLine + candidate.height,
|
|
414
|
+
event.y >= candidate.startLine && event.y < candidate.startLine + candidate.height,
|
|
421
415
|
);
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
&& event.screenY === this.lastClickedPoint.y;
|
|
428
|
-
const span = event.type === "click" && clickCount === 2 && lastSpan
|
|
429
|
-
? lastSpan
|
|
430
|
-
: hintSpan ?? (sameLastPoint ? lastSpan : undefined);
|
|
431
|
-
if (!span) return undefined;
|
|
416
|
+
const blockSpan = this.snapshot.blockSpans.find((candidate) =>
|
|
417
|
+
event.y >= candidate.startLine && event.y < candidate.startLine + candidate.height,
|
|
418
|
+
);
|
|
419
|
+
const target = hintSpan ?? fullSpan ?? blockSpan;
|
|
420
|
+
if (!target) return undefined;
|
|
432
421
|
if (event.type === "press") return { handled: true, capture: true, render: false };
|
|
433
422
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
this.
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
423
|
+
if (hintSpan || fullSpan) {
|
|
424
|
+
const span = hintSpan ?? fullSpan!;
|
|
425
|
+
const clickCount = event.clickCount ?? 1;
|
|
426
|
+
const previousClickedToolCallId = this.lastClickedToolCallId;
|
|
427
|
+
const lastSpan = this.lastClickedToolCallId
|
|
428
|
+
? this.snapshot.toolSpans.find((candidate) => candidate.toolCallId === this.lastClickedToolCallId)
|
|
429
|
+
: undefined;
|
|
430
|
+
const sameLastPoint = this.lastClickedPoint !== undefined &&
|
|
431
|
+
event.screenX === this.lastClickedPoint.x && event.screenY === this.lastClickedPoint.y;
|
|
432
|
+
const sameLastSpan = sameLastPoint && span.toolCallId === lastSpan?.toolCallId;
|
|
433
|
+
const isExpanded = this.expandedTools.has(span.toolCallId);
|
|
434
|
+
const shouldExpand = !isExpanded && hintSpan?.toolCallId === span.toolCallId;
|
|
435
|
+
const shouldCollapse = isExpanded && clickCount === 2 && sameLastSpan && span.toolCallId === previousClickedToolCallId;
|
|
436
|
+
|
|
437
|
+
this.focusedToolCallId = span.toolCallId;
|
|
438
|
+
this.lastClickedToolCallId = span.toolCallId;
|
|
439
|
+
this.lastClickedPoint = { x: event.screenX, y: event.screenY };
|
|
440
|
+
if (shouldExpand) this.expandedTools.add(span.toolCallId);
|
|
441
|
+
else if (shouldCollapse) this.expandedTools.delete(span.toolCallId);
|
|
442
|
+
|
|
443
|
+
if (shouldExpand || shouldCollapse) {
|
|
444
|
+
const component = this.toolComponents.get(span.toolCallId);
|
|
445
|
+
try {
|
|
446
|
+
component?.handleMouse?.({ ...event, y: event.y - span.startLine, height: span.height });
|
|
447
|
+
} catch {
|
|
448
|
+
// A missing/older Pi mouse implementation must not break the viewer.
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
this.invalidate();
|
|
452
|
+
this.options.onChange?.();
|
|
453
|
+
return { handled: true, render: shouldExpand || shouldCollapse, focusedBlockIndex: span.blockIndex };
|
|
446
454
|
}
|
|
447
455
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
component?.handleMouse?.({ ...event, y: event.y - span.startLine, height: span.height });
|
|
454
|
-
} catch {
|
|
455
|
-
// A missing/older Pi mouse implementation must not break the viewer.
|
|
456
|
-
}
|
|
456
|
+
// A message click intentionally drops any stale tool focus so Enter cannot
|
|
457
|
+
// toggle a tool that is no longer the selected block.
|
|
458
|
+
this.focusedToolCallId = undefined;
|
|
459
|
+
this.lastClickedToolCallId = undefined;
|
|
460
|
+
this.lastClickedPoint = undefined;
|
|
457
461
|
this.invalidate();
|
|
458
462
|
this.options.onChange?.();
|
|
459
|
-
return { handled: true, render: true };
|
|
463
|
+
return { handled: true, render: true, focusedBlockIndex: target.blockIndex };
|
|
460
464
|
}
|
|
461
465
|
|
|
462
466
|
invalidate(): void {
|
|
463
467
|
this.lastWidth = 0;
|
|
464
|
-
this.snapshot = { lines: [], messageStarts: [], messageBlocks: [], interactiveToolIds: [], toolSpans: [] };
|
|
468
|
+
this.snapshot = { lines: [], messageStarts: [], messageBlocks: [], interactiveToolIds: [], blockSpans: [], toolSpans: [] };
|
|
465
469
|
}
|
|
466
470
|
}
|