@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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import type { TuiMouseEvent } from "@earendil-works/pi-tui";
|
|
2
3
|
import { ConversationViewer, createStaticConversationSource } from "../../src/ui/conversation-viewer.js";
|
|
3
4
|
|
|
4
5
|
const theme = {
|
|
@@ -28,6 +29,28 @@ function tui() {
|
|
|
28
29
|
} as any;
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
function mouse(type: TuiMouseEvent["type"], y: number, overrides: Partial<TuiMouseEvent> = {}): TuiMouseEvent {
|
|
33
|
+
return {
|
|
34
|
+
type,
|
|
35
|
+
button: "left",
|
|
36
|
+
x: 2,
|
|
37
|
+
y,
|
|
38
|
+
screenX: 2,
|
|
39
|
+
screenY: y,
|
|
40
|
+
width: 120,
|
|
41
|
+
height: 30,
|
|
42
|
+
shift: false,
|
|
43
|
+
alt: false,
|
|
44
|
+
ctrl: false,
|
|
45
|
+
...overrides,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// The test record has the default invocation row, so timeline line 0 is viewer row 4.
|
|
50
|
+
function viewerY(timelineLine: number): number {
|
|
51
|
+
return 4 + timelineLine;
|
|
52
|
+
}
|
|
53
|
+
|
|
31
54
|
describe("ConversationViewer", () => {
|
|
32
55
|
it("renders assistant fenced code through the Markdown renderer", () => {
|
|
33
56
|
const ui = tui();
|
|
@@ -85,8 +108,6 @@ describe("ConversationViewer", () => {
|
|
|
85
108
|
expect(liveFooter).toContain("j/k scroll · J/K messages · [/] tools · w preview · g/G t M o O /n N q");
|
|
86
109
|
expect(liveFooter).toContain("e steer");
|
|
87
110
|
expect(liveFooter).toContain("x stop");
|
|
88
|
-
live.handleInput("\r");
|
|
89
|
-
expect(live.render(120).join("\n")).not.toContain("Enter send");
|
|
90
111
|
live.handleInput("e");
|
|
91
112
|
expect(live.render(120).join("\n")).toContain("Enter send");
|
|
92
113
|
live.handleInput("\x1b");
|
|
@@ -152,78 +173,62 @@ describe("ConversationViewer", () => {
|
|
|
152
173
|
vi.fn(),
|
|
153
174
|
);
|
|
154
175
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
expect(initial).not.toContain("READ");
|
|
176
|
+
expect(viewer.render(120).join("\n")).not.toContain("six");
|
|
177
|
+
viewer.handleInput("\t");
|
|
158
178
|
viewer.handleInput("\r");
|
|
159
179
|
expect(viewer.render(120).join("\n")).toContain("six");
|
|
160
180
|
});
|
|
161
181
|
|
|
162
|
-
it("
|
|
182
|
+
it("toggles mouse expansion only on the normalized click event", () => {
|
|
163
183
|
const viewer = new ConversationViewer(
|
|
164
184
|
tui(),
|
|
165
185
|
createStaticConversationSource([
|
|
166
|
-
{ role: "assistant", content: [{ type: "
|
|
167
|
-
{ role: "toolResult", toolName: "read", content: [{ type: "text", text: "
|
|
168
|
-
{ role: "assistant", content: [{ type: "text", text: "second" }] },
|
|
186
|
+
{ role: "assistant", content: [{ type: "toolCall", name: "read", arguments: { path: "x" } }] },
|
|
187
|
+
{ role: "toolResult", toolName: "read", content: [{ type: "text", text: "one\ntwo\nthree\nfour\nfive\nsix" }] },
|
|
169
188
|
] as any),
|
|
170
189
|
record(),
|
|
171
190
|
undefined,
|
|
172
191
|
theme,
|
|
173
192
|
vi.fn(),
|
|
174
193
|
);
|
|
175
|
-
viewer.render(120);
|
|
176
|
-
viewer.
|
|
177
|
-
expect(
|
|
178
|
-
|
|
179
|
-
viewer.
|
|
180
|
-
expect(
|
|
194
|
+
expect(viewer.render(120).join("\n")).not.toContain("six");
|
|
195
|
+
const span = (viewer as any).timeline.getSnapshot().toolSpans[0];
|
|
196
|
+
expect(span.clickStartLine).toEqual(expect.any(Number));
|
|
197
|
+
const hintY = viewerY(span.clickStartLine);
|
|
198
|
+
expect(viewer.handleMouse(mouse("press", hintY))?.handled).toBe(true);
|
|
199
|
+
expect(viewer.render(120).join("\n")).not.toContain("six");
|
|
200
|
+
viewer.handleMouse(mouse("click", hintY, { clickCount: 1 }));
|
|
201
|
+
expect(viewer.render(120).join("\n")).toContain("six");
|
|
181
202
|
});
|
|
182
203
|
|
|
183
|
-
it("focuses
|
|
204
|
+
it("focuses USER and ASSISTANT blocks from their rendered spans", () => {
|
|
184
205
|
const viewer = new ConversationViewer(
|
|
185
206
|
tui(),
|
|
186
207
|
createStaticConversationSource([
|
|
187
|
-
{ role: "
|
|
188
|
-
{ role: "
|
|
189
|
-
{ role: "assistant", content: [{ type: "text", text: "between" }, { type: "toolCall", name: "read", arguments: { path: "second" } }] },
|
|
190
|
-
{ role: "toolResult", toolName: "read", content: [{ type: "text", text: "second output" }] },
|
|
208
|
+
{ role: "user", content: "user text" },
|
|
209
|
+
{ role: "assistant", content: [{ type: "text", text: "assistant text" }] },
|
|
191
210
|
] as any),
|
|
192
211
|
record(),
|
|
193
212
|
undefined,
|
|
194
213
|
theme,
|
|
195
214
|
vi.fn(),
|
|
196
215
|
);
|
|
197
|
-
viewer.render(120);
|
|
198
|
-
const timeline = (viewer as any).timeline;
|
|
199
|
-
const ids = timeline.getSnapshot().interactiveToolIds;
|
|
200
|
-
expect(ids).toHaveLength(2);
|
|
201
216
|
|
|
202
|
-
viewer.handleInput("]");
|
|
203
|
-
expect(timeline.getFocusedToolCallId()).toBe(ids[0]);
|
|
204
217
|
viewer.render(120);
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
expect(
|
|
208
|
-
viewer.
|
|
209
|
-
expect((viewer as any).
|
|
210
|
-
|
|
211
|
-
|
|
218
|
+
const timeline = (viewer as any).timeline;
|
|
219
|
+
const spans = timeline.getSnapshot().blockSpans;
|
|
220
|
+
expect(spans).toHaveLength(2);
|
|
221
|
+
viewer.handleMouse(mouse("click", viewerY(spans[1].startLine), { clickCount: 1 }));
|
|
222
|
+
expect((viewer as any).selectedMessageIdx).toBe(1);
|
|
223
|
+
expect(timeline.getFocusedToolCallId()).toBeUndefined();
|
|
224
|
+
viewer.handleMouse(mouse("click", viewerY(spans[0].startLine), { clickCount: 1 }));
|
|
225
|
+
expect((viewer as any).selectedMessageIdx).toBe(0);
|
|
226
|
+
expect(timeline.getFocusedToolCallId()).toBeUndefined();
|
|
212
227
|
});
|
|
213
228
|
|
|
214
|
-
it("
|
|
215
|
-
const ui = tui();
|
|
216
|
-
const custom = vi.fn((factory: any) => {
|
|
217
|
-
const preview = factory(ui, theme, undefined, vi.fn());
|
|
218
|
-
const rendered = preview.render(100).join("\\n");
|
|
219
|
-
expect(rendered).toContain("Input");
|
|
220
|
-
expect(rendered).toContain("Output");
|
|
221
|
-
expect(rendered).toContain("one");
|
|
222
|
-
expect(rendered).toContain("six");
|
|
223
|
-
return Promise.resolve(undefined);
|
|
224
|
-
});
|
|
229
|
+
it("focuses normal tool rows without expanding, while hint clicks expand", () => {
|
|
225
230
|
const viewer = new ConversationViewer(
|
|
226
|
-
|
|
231
|
+
tui(),
|
|
227
232
|
createStaticConversationSource([
|
|
228
233
|
{ role: "assistant", content: [{ type: "toolCall", name: "read", arguments: { path: "x" } }] },
|
|
229
234
|
{ role: "toolResult", toolName: "read", content: [{ type: "text", text: "one\ntwo\nthree\nfour\nfive\nsix" }] },
|
|
@@ -232,81 +237,92 @@ describe("ConversationViewer", () => {
|
|
|
232
237
|
undefined,
|
|
233
238
|
theme,
|
|
234
239
|
vi.fn(),
|
|
235
|
-
undefined,
|
|
236
|
-
undefined,
|
|
237
|
-
undefined,
|
|
238
|
-
{ pi: {} as any, ctx: { ui: { custom } } as any, readOnly: true },
|
|
239
240
|
);
|
|
240
|
-
|
|
241
|
-
viewer.
|
|
242
|
-
viewer.
|
|
243
|
-
|
|
241
|
+
|
|
242
|
+
expect(viewer.render(120).join("\n")).not.toContain("six");
|
|
243
|
+
const timeline = (viewer as any).timeline;
|
|
244
|
+
const initialSpan = timeline.getSnapshot().toolSpans[0];
|
|
245
|
+
const toolId = initialSpan.toolCallId;
|
|
246
|
+
const hintLine = initialSpan.clickStartLine as number;
|
|
247
|
+
const normalOutputLine = hintLine - 1;
|
|
248
|
+
|
|
249
|
+
const press = viewer.handleMouse(mouse("press", viewerY(hintLine)));
|
|
250
|
+
expect(press).toMatchObject({ handled: true, capture: true });
|
|
251
|
+
expect(timeline.getFocusedToolCallId()).toBeUndefined();
|
|
252
|
+
expect(viewer.render(120).join("\n")).not.toContain("six");
|
|
253
|
+
|
|
254
|
+
viewer.handleMouse(mouse("click", viewerY(normalOutputLine), { clickCount: 1 }));
|
|
255
|
+
expect(timeline.getFocusedToolCallId()).toBe(toolId);
|
|
256
|
+
expect((viewer as any).selectedMessageIdx).toBe(initialSpan.blockIndex);
|
|
257
|
+
expect(viewer.render(120).join("\n")).not.toContain("six");
|
|
258
|
+
|
|
259
|
+
const collapsedSpan = timeline.getSnapshot().toolSpans[0];
|
|
260
|
+
viewer.handleMouse(mouse("click", viewerY(collapsedSpan.clickStartLine as number), { clickCount: 1 }));
|
|
261
|
+
expect(timeline.getFocusedToolCallId()).toBe(toolId);
|
|
262
|
+
expect(viewer.render(120).join("\n")).toContain("six");
|
|
244
263
|
});
|
|
245
264
|
|
|
246
|
-
it("
|
|
247
|
-
const railTheme = {
|
|
248
|
-
fg: (name: string, text: string) => `<${name}>${text}</${name}>`,
|
|
249
|
-
bold: (text: string) => `<bold>${text}</bold>`,
|
|
250
|
-
};
|
|
265
|
+
it("synchronizes J/K message focus and anchors [/] around the selected message", () => {
|
|
251
266
|
const viewer = new ConversationViewer(
|
|
252
267
|
tui(),
|
|
253
268
|
createStaticConversationSource([
|
|
254
|
-
{ role: "
|
|
255
|
-
{ role: "
|
|
269
|
+
{ role: "user", content: "user one" },
|
|
270
|
+
{ role: "assistant", content: [{ type: "text", text: "assistant one" }] },
|
|
271
|
+
{ role: "assistant", content: [{ type: "toolCall", id: "tool-1", name: "read", arguments: { path: "one" } }] },
|
|
272
|
+
{ role: "toolResult", toolCallId: "tool-1", toolName: "read", content: [{ type: "text", text: "one" }] },
|
|
273
|
+
{ role: "assistant", content: [{ type: "text", text: "assistant two" }] },
|
|
274
|
+
{ role: "assistant", content: [{ type: "toolCall", id: "tool-2", name: "read", arguments: { path: "two" } }] },
|
|
275
|
+
{ role: "toolResult", toolCallId: "tool-2", toolName: "read", content: [{ type: "text", text: "two" }] },
|
|
256
276
|
] as any),
|
|
257
277
|
record(),
|
|
258
278
|
undefined,
|
|
259
|
-
railTheme,
|
|
260
|
-
vi.fn(),
|
|
261
|
-
);
|
|
262
|
-
expect(viewer.render(120).join("\\n")).toContain("<toolTitle>▌</toolTitle>");
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
it("scrolls the transcript with a normalized mouse wheel event", () => {
|
|
266
|
-
const longText = Array.from({ length: 80 }, (_, index) => `line-${index}`).join("\n");
|
|
267
|
-
const viewer = new ConversationViewer(
|
|
268
|
-
tui(),
|
|
269
|
-
createStaticConversationSource([{ role: "assistant", content: [{ type: "text", text: longText }] }] as any),
|
|
270
|
-
record(),
|
|
271
|
-
undefined,
|
|
272
279
|
theme,
|
|
273
280
|
vi.fn(),
|
|
274
281
|
);
|
|
282
|
+
|
|
275
283
|
viewer.render(120);
|
|
276
|
-
const
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
expect((viewer as any).
|
|
284
|
+
const timeline = (viewer as any).timeline;
|
|
285
|
+
const snapshot = timeline.getSnapshot();
|
|
286
|
+
viewer.handleInput("]");
|
|
287
|
+
expect((viewer as any).selectedMessageIdx).toBe(snapshot.toolSpans[0].blockIndex);
|
|
288
|
+
expect(timeline.getFocusedToolCallId()).toBe("tool-1");
|
|
289
|
+
|
|
290
|
+
viewer.handleInput("J");
|
|
291
|
+
expect((viewer as any).selectedMessageIdx).toBe(3);
|
|
292
|
+
expect(timeline.getFocusedToolCallId()).toBeUndefined();
|
|
293
|
+
viewer.handleInput("]");
|
|
294
|
+
expect((viewer as any).selectedMessageIdx).toBe(snapshot.toolSpans[1].blockIndex);
|
|
295
|
+
expect(timeline.getFocusedToolCallId()).toBe("tool-2");
|
|
296
|
+
|
|
297
|
+
viewer.handleInput("K");
|
|
298
|
+
expect((viewer as any).selectedMessageIdx).toBe(3);
|
|
299
|
+
expect(timeline.getFocusedToolCallId()).toBeUndefined();
|
|
300
|
+
viewer.handleInput("[");
|
|
301
|
+
expect((viewer as any).selectedMessageIdx).toBe(snapshot.toolSpans[0].blockIndex);
|
|
302
|
+
expect(timeline.getFocusedToolCallId()).toBe("tool-1");
|
|
280
303
|
});
|
|
281
304
|
|
|
282
|
-
it("
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
);
|
|
294
|
-
expect(
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
expect(
|
|
300
|
-
expect(viewer.render(120).join("\n")).not.toContain("six");
|
|
301
|
-
viewer.handleMouse({ ...mouse, type: "click", y: hintY, clickCount: 1 } as any);
|
|
302
|
-
expect(viewer.render(120).join("\n")).toContain("six");
|
|
303
|
-
viewer.handleMouse({ ...mouse, type: "click", y: hintY, clickCount: 1 } as any);
|
|
304
|
-
expect(viewer.render(120).join("\n")).toContain("six");
|
|
305
|
-
viewer.handleMouse({ ...mouse, type: "click", y: hintY, clickCount: 2 } as any);
|
|
306
|
-
expect(viewer.render(120).join("\n")).not.toContain("six");
|
|
305
|
+
it("uses the first or last tool for initial [/], before an explicit focus exists", () => {
|
|
306
|
+
const source = createStaticConversationSource([
|
|
307
|
+
{ role: "assistant", content: [{ type: "toolCall", id: "tool-1", name: "read", arguments: { path: "one" } }] },
|
|
308
|
+
{ role: "toolResult", toolCallId: "tool-1", toolName: "read", content: [{ type: "text", text: "one" }] },
|
|
309
|
+
{ role: "assistant", content: [{ type: "toolCall", id: "tool-2", name: "read", arguments: { path: "two" } }] },
|
|
310
|
+
{ role: "toolResult", toolCallId: "tool-2", toolName: "read", content: [{ type: "text", text: "two" }] },
|
|
311
|
+
] as any);
|
|
312
|
+
const makeViewer = () => new ConversationViewer(tui(), source, record(), undefined, theme, vi.fn());
|
|
313
|
+
|
|
314
|
+
const left = makeViewer();
|
|
315
|
+
left.render(120);
|
|
316
|
+
left.handleInput("[");
|
|
317
|
+
expect((left as any).timeline.getFocusedToolCallId()).toBe("tool-2");
|
|
318
|
+
|
|
319
|
+
const right = makeViewer();
|
|
320
|
+
right.render(120);
|
|
321
|
+
right.handleInput("]");
|
|
322
|
+
expect((right as any).timeline.getFocusedToolCallId()).toBe("tool-1");
|
|
307
323
|
});
|
|
308
324
|
|
|
309
|
-
it("highlights the tool hint
|
|
325
|
+
it("highlights the collapsed tool hint and the viewer close control independently", () => {
|
|
310
326
|
const semanticTheme = {
|
|
311
327
|
fg: (name: string, text: string) => `<${name}>${text}</${name}>`,
|
|
312
328
|
bold: (text: string) => `<bold>${text}</bold>`,
|
|
@@ -325,21 +341,74 @@ describe("ConversationViewer", () => {
|
|
|
325
341
|
);
|
|
326
342
|
const initial = viewer.render(120).join("\n");
|
|
327
343
|
const span = (viewer as any).timeline.getSnapshot().toolSpans[0];
|
|
328
|
-
|
|
329
|
-
const hintY = 4 + span.clickStartLine;
|
|
344
|
+
const hintY = viewerY(span.clickStartLine);
|
|
330
345
|
expect(initial).not.toContain("<text>click to show more</text>");
|
|
331
|
-
viewer.handleMouse(
|
|
346
|
+
viewer.handleMouse(mouse("move", hintY, { button: "none" }));
|
|
332
347
|
expect(viewer.render(120).join("\n")).toContain("<text>click to show more</text>");
|
|
333
|
-
|
|
334
|
-
expect(viewer.render(120).join("\n")).toContain("[esc]");
|
|
335
|
-
viewer.handleMouse({ type: "move", button: "none", x: 114, y: 1, screenX: 114, screenY: 1, width: 120, height: 30, shift: false, alt: false, ctrl: false } as any);
|
|
348
|
+
viewer.handleMouse(mouse("move", 1, { button: "none", x: 114, screenX: 114, screenY: 1 }));
|
|
336
349
|
expect(viewer.render(120).join("\n")).toContain("<text><bold>[esc]</bold></text>");
|
|
337
|
-
|
|
338
|
-
expect(viewer.handleMouse({
|
|
339
|
-
expect(viewer.handleMouse({ ...close, type: "click", clickCount: 1 } as any)?.handled).toBe(true);
|
|
350
|
+
expect(viewer.handleMouse(mouse("press", 1, { x: 114, screenX: 114 }))?.handled).toBe(true);
|
|
351
|
+
expect(viewer.handleMouse(mouse("click", 1, { x: 114, screenX: 114, clickCount: 1 }))?.handled).toBe(true);
|
|
340
352
|
expect(done).toHaveBeenCalledTimes(1);
|
|
341
353
|
});
|
|
342
354
|
|
|
355
|
+
it("focuses nearby tools with [ and ] and opens a complete read-only preview with w", () => {
|
|
356
|
+
const ui = tui();
|
|
357
|
+
let preview: any;
|
|
358
|
+
const previewDone = vi.fn();
|
|
359
|
+
const custom = vi.fn((factory: any) => {
|
|
360
|
+
preview = factory(ui, theme, undefined, previewDone);
|
|
361
|
+
const initial = preview.render(100).join("\\n");
|
|
362
|
+
expect(initial).toContain("Input");
|
|
363
|
+
expect(initial).toContain("Output");
|
|
364
|
+
expect(initial).toContain("line 1");
|
|
365
|
+
expect(initial).not.toContain("line 40");
|
|
366
|
+
preview.handleInput("G");
|
|
367
|
+
expect(preview.render(100).join("\\n")).toContain("line 40");
|
|
368
|
+
preview.handleInput("k");
|
|
369
|
+
preview.handleInput("j");
|
|
370
|
+
preview.handleInput("g");
|
|
371
|
+
preview.handleInput("g");
|
|
372
|
+
expect(preview.render(100).join("\\n")).toContain("line 1");
|
|
373
|
+
preview.handleInput("q");
|
|
374
|
+
expect(previewDone).toHaveBeenCalledTimes(1);
|
|
375
|
+
return Promise.resolve(undefined);
|
|
376
|
+
});
|
|
377
|
+
const viewer = new ConversationViewer(
|
|
378
|
+
ui,
|
|
379
|
+
createStaticConversationSource([
|
|
380
|
+
{ role: "assistant", content: [{ type: "toolCall", name: "read", arguments: { path: "first" } }] },
|
|
381
|
+
{ role: "toolResult", toolName: "read", content: [{ type: "text", text: Array.from({ length: 40 }, (_, index) => `line ${index + 1}`).join("\n") }] },
|
|
382
|
+
{ role: "assistant", content: [{ type: "toolCall", name: "read", arguments: { path: "second" } }] },
|
|
383
|
+
{ role: "toolResult", toolName: "read", content: [{ type: "text", text: "second output" }] },
|
|
384
|
+
] as any),
|
|
385
|
+
record(),
|
|
386
|
+
undefined,
|
|
387
|
+
theme,
|
|
388
|
+
vi.fn(),
|
|
389
|
+
undefined,
|
|
390
|
+
undefined,
|
|
391
|
+
undefined,
|
|
392
|
+
{ pi: {} as any, ctx: { ui: { custom } } as any, readOnly: true },
|
|
393
|
+
);
|
|
394
|
+
viewer.render(120);
|
|
395
|
+
const timeline = (viewer as any).timeline;
|
|
396
|
+
const ids = timeline.getSnapshot().interactiveToolIds;
|
|
397
|
+
viewer.handleInput("]");
|
|
398
|
+
expect(timeline.getFocusedToolCallId()).toBe(ids[0]);
|
|
399
|
+
viewer.render(120);
|
|
400
|
+
viewer.handleInput("w");
|
|
401
|
+
expect(custom).toHaveBeenCalledWith(expect.any(Function), expect.objectContaining({ overlay: true }));
|
|
402
|
+
preview.handleInput("G");
|
|
403
|
+
preview.handleInput("g");
|
|
404
|
+
preview.handleInput("g");
|
|
405
|
+
preview.handleInput("q");
|
|
406
|
+
viewer.handleInput("]");
|
|
407
|
+
expect(timeline.getFocusedToolCallId()).toBe(ids[1]);
|
|
408
|
+
viewer.handleInput("[");
|
|
409
|
+
expect(timeline.getFocusedToolCallId()).toBe(ids[0]);
|
|
410
|
+
});
|
|
411
|
+
|
|
343
412
|
it("keeps historical viewers read-only and closes on Escape", () => {
|
|
344
413
|
const ui = tui();
|
|
345
414
|
const steer = vi.fn();
|
|
@@ -31,7 +31,6 @@ describe("executeWriteWithMetadata", () => {
|
|
|
31
31
|
|
|
32
32
|
expect(await readFile(join(cwd, "nested/file.txt"), "utf8")).toBe("new\n");
|
|
33
33
|
expect(result.content[0]?.text).toContain("Successfully wrote to nested/file.txt");
|
|
34
|
-
expect(result.details?.fileExistedBeforeWrite).toBe(false);
|
|
35
34
|
expect(result.details?.diff).toContain("+1 new");
|
|
36
35
|
expect(result.details?.patch).toContain("+new");
|
|
37
36
|
expect(store.get("call-new")?.diff).toBe(result.details?.diff);
|
|
@@ -49,7 +48,6 @@ describe("executeWriteWithMetadata", () => {
|
|
|
49
48
|
|
|
50
49
|
await writeFile(join(cwd, "file.txt"), "later\n", "utf8");
|
|
51
50
|
|
|
52
|
-
expect(persisted.fileExistedBeforeWrite).toBe(true);
|
|
53
51
|
expect(persisted.diff).toContain("-1 old");
|
|
54
52
|
expect(persisted.diff).toContain("+1 new");
|
|
55
53
|
expect(persisted.patch).toContain("-old");
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"nextId": 4,
|
|
3
|
-
"tasks": [
|
|
4
|
-
{
|
|
5
|
-
"id": "1",
|
|
6
|
-
"subject": "Analyze viewer interaction boundaries",
|
|
7
|
-
"description": "Inspect current ConversationViewer/ConversationTimeline/tool renderer and Pi mouse semantics. Identify the smallest safe design for press/release/click, viewer-local hover target styling, clickable [esc], and complete expanded output. Read-only; do not edit.",
|
|
8
|
-
"status": "in_progress",
|
|
9
|
-
"activeForm": "Analyzing viewer interaction boundaries",
|
|
10
|
-
"metadata": {
|
|
11
|
-
"agentType": "oracle"
|
|
12
|
-
},
|
|
13
|
-
"blocks": [],
|
|
14
|
-
"blockedBy": [],
|
|
15
|
-
"createdAt": 1789162453256,
|
|
16
|
-
"updatedAt": 1789162461551,
|
|
17
|
-
"owner": "orchestrator"
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
"id": "2",
|
|
21
|
-
"subject": "Implement viewer mouse behavior",
|
|
22
|
-
"description": "After inspecting current source, implement the requested ConversationViewer behavior in source and focused regression tests: press/release never toggle, click-target expansion/collapse, hover styling, clickable top-right [esc], Esc key close, complete expanded output with usable scrolling, preserving Enter and J/K/j/k. Modify source/tests only; do not run git/npm install/npm publish.",
|
|
23
|
-
"status": "in_progress",
|
|
24
|
-
"activeForm": "Implementing viewer mouse behavior",
|
|
25
|
-
"metadata": {
|
|
26
|
-
"agentType": "fixer"
|
|
27
|
-
},
|
|
28
|
-
"blocks": [
|
|
29
|
-
"3"
|
|
30
|
-
],
|
|
31
|
-
"blockedBy": [],
|
|
32
|
-
"createdAt": 1789162453263,
|
|
33
|
-
"updatedAt": 1789162461564,
|
|
34
|
-
"owner": "orchestrator"
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
"id": "3",
|
|
38
|
-
"subject": "Validate viewer regressions",
|
|
39
|
-
"description": "Run the focused ConversationViewer test command after implementation, inspect changed source/tests for correctness and scope, and report failures or evidence. Do not run git/npm install/npm publish.",
|
|
40
|
-
"status": "pending",
|
|
41
|
-
"activeForm": "Validating viewer regressions",
|
|
42
|
-
"metadata": {
|
|
43
|
-
"agentType": "build"
|
|
44
|
-
},
|
|
45
|
-
"blocks": [],
|
|
46
|
-
"blockedBy": [
|
|
47
|
-
"2"
|
|
48
|
-
],
|
|
49
|
-
"createdAt": 1789162453268,
|
|
50
|
-
"updatedAt": 1789162461564
|
|
51
|
-
}
|
|
52
|
-
]
|
|
53
|
-
}
|