@esso0428/pi-subagents 0.15.13 → 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 -31
- 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 +17 -11
- package/dist/ui/ccstyle/tool-renderer.js.map +1 -1
- package/dist/ui/ccstyle/tool-result.d.ts +22 -10
- package/dist/ui/ccstyle/tool-result.d.ts.map +1 -1
- package/dist/ui/ccstyle/tool-result.js +160 -73
- package/dist/ui/ccstyle/tool-result.js.map +1 -1
- package/dist/ui/conversation-timeline.d.ts +16 -9
- package/dist/ui/conversation-timeline.d.ts.map +1 -1
- package/dist/ui/conversation-timeline.js +85 -61
- 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 +107 -110
- 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 +23 -24
- package/src/ui/ccstyle/tool-result.ts +167 -107
- package/src/ui/conversation-timeline.ts +103 -62
- package/src/ui/conversation-viewer.ts +119 -124
- package/src/write-execution.ts +1 -7
- package/test/ui/agent-widget.test.ts +123 -0
- package/test/ui/conversation-diff.test.ts +1 -28
- 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));
|
|
@@ -143,7 +156,8 @@ export class ConversationTimeline implements Component {
|
|
|
143
156
|
private focusedToolCallId: string | undefined;
|
|
144
157
|
private expandedTools = new Set<string>();
|
|
145
158
|
private hoveredToolCallId: string | undefined;
|
|
146
|
-
|
|
159
|
+
private hoveredIoView: any;
|
|
160
|
+
private hoveredIoSection: "input" | "output" | null = null;
|
|
147
161
|
private lastClickedToolCallId: string | undefined;
|
|
148
162
|
private lastClickedPoint: { x: number; y: number } | undefined;
|
|
149
163
|
private toolComponents = new Map<string, Component & { handleMouse?: (event: TuiMouseEvent) => unknown }>();
|
|
@@ -152,6 +166,7 @@ export class ConversationTimeline implements Component {
|
|
|
152
166
|
messageStarts: [],
|
|
153
167
|
messageBlocks: [],
|
|
154
168
|
interactiveToolIds: [],
|
|
169
|
+
blockSpans: [],
|
|
155
170
|
toolSpans: [],
|
|
156
171
|
};
|
|
157
172
|
private lastWidth = 0;
|
|
@@ -167,10 +182,6 @@ export class ConversationTimeline implements Component {
|
|
|
167
182
|
const ids = new Set(this.blocks.filter((block) => block.kind === "tool" && block.toolCallId).map((block) => block.toolCallId!));
|
|
168
183
|
if (this.focusedToolCallId && !ids.has(this.focusedToolCallId)) this.focusedToolCallId = undefined;
|
|
169
184
|
if (this.hoveredToolCallId && !ids.has(this.hoveredToolCallId)) this.hoveredToolCallId = undefined;
|
|
170
|
-
if (this.lastClickedToolCallId && !ids.has(this.lastClickedToolCallId)) {
|
|
171
|
-
this.lastClickedToolCallId = undefined;
|
|
172
|
-
this.lastClickedPoint = undefined;
|
|
173
|
-
}
|
|
174
185
|
for (const id of this.expandedTools) if (!ids.has(id)) this.expandedTools.delete(id);
|
|
175
186
|
for (const id of this.toolComponents.keys()) if (!ids.has(id)) this.toolComponents.delete(id);
|
|
176
187
|
this.invalidate();
|
|
@@ -182,8 +193,6 @@ export class ConversationTimeline implements Component {
|
|
|
182
193
|
if (!show) {
|
|
183
194
|
this.focusedToolCallId = undefined;
|
|
184
195
|
this.hoveredToolCallId = undefined;
|
|
185
|
-
this.lastClickedToolCallId = undefined;
|
|
186
|
-
this.lastClickedPoint = undefined;
|
|
187
196
|
}
|
|
188
197
|
this.invalidate();
|
|
189
198
|
}
|
|
@@ -201,10 +210,6 @@ export class ConversationTimeline implements Component {
|
|
|
201
210
|
return this.focusedToolCallId;
|
|
202
211
|
}
|
|
203
212
|
|
|
204
|
-
getHoveredToolCallId(): string | undefined {
|
|
205
|
-
return this.hoveredToolCallId;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
213
|
getSnapshot(): ConversationTimelineSnapshot {
|
|
209
214
|
if (this.lastWidth <= 0) this.render(80);
|
|
210
215
|
return this.snapshot;
|
|
@@ -243,6 +248,7 @@ export class ConversationTimeline implements Component {
|
|
|
243
248
|
const messageStarts: number[] = [];
|
|
244
249
|
const messageBlocks: ConversationBlock[] = [];
|
|
245
250
|
const interactiveToolIds: string[] = [];
|
|
251
|
+
const blockSpans: TimelineBlockSpan[] = [];
|
|
246
252
|
const toolSpans: TimelineToolSpan[] = [];
|
|
247
253
|
const visibleBlocks = this.blocks.filter((block) => this.showTools || block.kind !== "tool");
|
|
248
254
|
|
|
@@ -260,6 +266,7 @@ export class ConversationTimeline implements Component {
|
|
|
260
266
|
|
|
261
267
|
const hasRoleHeader = block.role === "user" || block.role === "assistant";
|
|
262
268
|
const blockStartLine = lines.length;
|
|
269
|
+
const snapshotBlockIndex = messageBlocks.length;
|
|
263
270
|
messageStarts.push(blockStartLine);
|
|
264
271
|
messageBlocks.push(block);
|
|
265
272
|
if (hasRoleHeader) {
|
|
@@ -297,8 +304,6 @@ export class ConversationTimeline implements Component {
|
|
|
297
304
|
this.theme,
|
|
298
305
|
);
|
|
299
306
|
} catch {
|
|
300
|
-
// Pi's interactive theme is a capability, not a viewer dependency;
|
|
301
|
-
// test hosts and older Pi versions can lack its initialized theme.
|
|
302
307
|
rendered = fallbackToolLines(
|
|
303
308
|
block,
|
|
304
309
|
requestedWidth,
|
|
@@ -311,19 +316,23 @@ export class ConversationTimeline implements Component {
|
|
|
311
316
|
lines.push({ text: truncateToWidth(line, requestedWidth), plain: stripAnsi(line), blockIndex, railable: true });
|
|
312
317
|
}
|
|
313
318
|
if (id) {
|
|
314
|
-
const startLine = blockStartLine;
|
|
315
319
|
const hintLine = !this.expandedTools.has(id)
|
|
316
320
|
? rendered.findIndex((line) => {
|
|
317
321
|
const plain = stripAnsi(line);
|
|
318
322
|
return plain.includes(showMoreHintText()) || plain.includes("Enter to expand");
|
|
319
323
|
})
|
|
320
324
|
: -1;
|
|
325
|
+
const blockHeight = lines.length - blockStartLine;
|
|
326
|
+
blockSpans.push({ blockIndex: snapshotBlockIndex, startLine: blockStartLine, height: blockHeight });
|
|
321
327
|
toolSpans.push({
|
|
328
|
+
blockIndex: snapshotBlockIndex,
|
|
322
329
|
toolCallId: id,
|
|
323
|
-
startLine,
|
|
324
|
-
height:
|
|
325
|
-
...(hintLine >= 0 ? { clickStartLine:
|
|
330
|
+
startLine: blockStartLine,
|
|
331
|
+
height: blockHeight,
|
|
332
|
+
...(hintLine >= 0 ? { clickStartLine: blockStartLine + (hasRoleHeader ? 1 : 0) + hintLine, clickHeight: 1 } : {}),
|
|
326
333
|
});
|
|
334
|
+
} else {
|
|
335
|
+
blockSpans.push({ blockIndex: snapshotBlockIndex, startLine: blockStartLine, height: lines.length - blockStartLine });
|
|
327
336
|
}
|
|
328
337
|
continue;
|
|
329
338
|
}
|
|
@@ -346,84 +355,116 @@ export class ConversationTimeline implements Component {
|
|
|
346
355
|
}
|
|
347
356
|
}
|
|
348
357
|
}
|
|
358
|
+
blockSpans.push({ blockIndex: snapshotBlockIndex, startLine: blockStartLine, height: lines.length - blockStartLine });
|
|
349
359
|
}
|
|
350
360
|
|
|
351
361
|
this.lastWidth = requestedWidth;
|
|
352
|
-
this.snapshot = { lines, messageStarts, messageBlocks, interactiveToolIds, toolSpans };
|
|
362
|
+
this.snapshot = { lines, messageStarts, messageBlocks, interactiveToolIds, blockSpans, toolSpans };
|
|
353
363
|
return lines.map((line) => line.text);
|
|
354
364
|
}
|
|
355
365
|
|
|
356
|
-
handleMouse(event: TuiMouseEvent):
|
|
366
|
+
handleMouse(event: TuiMouseEvent): ConversationTimelineMouseResult | undefined {
|
|
357
367
|
if (event.type === "move") {
|
|
358
368
|
const hovered = this.snapshot.toolSpans.find((span) =>
|
|
359
369
|
span.clickStartLine !== undefined &&
|
|
360
370
|
event.y >= span.clickStartLine &&
|
|
361
371
|
event.y < span.clickStartLine + (span.clickHeight ?? 1),
|
|
362
372
|
)?.toolCallId;
|
|
363
|
-
|
|
373
|
+
const fullSpan = this.snapshot.toolSpans.find((span) =>
|
|
374
|
+
event.y >= span.startLine && event.y < span.startLine + span.height,
|
|
375
|
+
);
|
|
376
|
+
let nextIoView: any;
|
|
377
|
+
let nextIoSection: "input" | "output" | null = null;
|
|
378
|
+
if (fullSpan) {
|
|
379
|
+
const component = this.toolComponents.get(fullSpan.toolCallId) as any;
|
|
380
|
+
const view = component?.resultRendererComponent;
|
|
381
|
+
const localRow = event.y - fullSpan.startLine;
|
|
382
|
+
if (view && typeof view.sectionAtLine === "function" && typeof view.showMoreLine === "function" && typeof view.setHoveredSection === "function") {
|
|
383
|
+
const section = view.sectionAtLine(localRow);
|
|
384
|
+
if (section && view.showMoreLine(section) === localRow) {
|
|
385
|
+
nextIoView = view;
|
|
386
|
+
nextIoSection = section;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
const unchanged = hovered === this.hoveredToolCallId && nextIoView === this.hoveredIoView && nextIoSection === this.hoveredIoSection;
|
|
391
|
+
if (unchanged) return undefined;
|
|
392
|
+
if (this.hoveredIoView && this.hoveredIoView !== nextIoView && typeof this.hoveredIoView.setHoveredSection === "function") {
|
|
393
|
+
this.hoveredIoView.setHoveredSection(null);
|
|
394
|
+
}
|
|
364
395
|
this.hoveredToolCallId = hovered;
|
|
396
|
+
this.hoveredIoView = nextIoView;
|
|
397
|
+
this.hoveredIoSection = nextIoSection;
|
|
398
|
+
if (nextIoView) nextIoView.setHoveredSection(nextIoSection);
|
|
365
399
|
this.invalidate();
|
|
366
400
|
this.options.onChange?.();
|
|
367
401
|
return { handled: true, render: true };
|
|
368
402
|
}
|
|
369
403
|
|
|
370
|
-
// The TUI sends a click only when a component claims the preceding press.
|
|
371
|
-
// Claim only the collapsed hint (or the last expanded card's hit area for
|
|
372
|
-
// a possible second click); never change expansion on press or release.
|
|
373
404
|
if (event.type !== "click" && event.type !== "press") return undefined;
|
|
374
405
|
if (event.button !== "left") return undefined;
|
|
375
|
-
|
|
406
|
+
|
|
407
|
+
// Keep the hint ahead of the full tool span. Its row has a distinct action.
|
|
376
408
|
const hintSpan = this.snapshot.toolSpans.find((candidate) =>
|
|
377
409
|
candidate.clickStartLine !== undefined &&
|
|
378
410
|
event.y >= candidate.clickStartLine &&
|
|
379
411
|
event.y < candidate.clickStartLine + (candidate.clickHeight ?? 1),
|
|
380
412
|
);
|
|
381
413
|
const fullSpan = this.snapshot.toolSpans.find((candidate) =>
|
|
382
|
-
event.y >= candidate.startLine &&
|
|
383
|
-
event.y < candidate.startLine + candidate.height,
|
|
414
|
+
event.y >= candidate.startLine && event.y < candidate.startLine + candidate.height,
|
|
384
415
|
);
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
&& event.screenY === this.lastClickedPoint.y;
|
|
391
|
-
const span = event.type === "click" && clickCount === 2 && lastSpan
|
|
392
|
-
? lastSpan
|
|
393
|
-
: hintSpan ?? (sameLastPoint ? lastSpan : undefined);
|
|
394
|
-
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;
|
|
395
421
|
if (event.type === "press") return { handled: true, capture: true, render: false };
|
|
396
422
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
this.
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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 };
|
|
409
454
|
}
|
|
410
455
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
component?.handleMouse?.({ ...event, y: event.y - span.startLine, height: span.height });
|
|
417
|
-
} catch {
|
|
418
|
-
// A missing/older Pi mouse implementation must not break the viewer.
|
|
419
|
-
}
|
|
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;
|
|
420
461
|
this.invalidate();
|
|
421
462
|
this.options.onChange?.();
|
|
422
|
-
return { handled: true, render: true };
|
|
463
|
+
return { handled: true, render: true, focusedBlockIndex: target.blockIndex };
|
|
423
464
|
}
|
|
424
465
|
|
|
425
466
|
invalidate(): void {
|
|
426
467
|
this.lastWidth = 0;
|
|
427
|
-
this.snapshot = { lines: [], messageStarts: [], messageBlocks: [], interactiveToolIds: [], toolSpans: [] };
|
|
468
|
+
this.snapshot = { lines: [], messageStarts: [], messageBlocks: [], interactiveToolIds: [], blockSpans: [], toolSpans: [] };
|
|
428
469
|
}
|
|
429
470
|
}
|