@esso0428/pi-subagents 0.15.8 → 0.15.10
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/.pi/tasks/tasks-01a09263-0ac6-77be-97f5-664b3ffea76b.json +53 -0
- package/.pi/workflows/index.json +5 -0
- package/CHANGELOG.md +17 -0
- package/dist/ui/ccstyle/diff/diff-renderer.d.ts.map +1 -1
- package/dist/ui/ccstyle/diff/diff-renderer.js +4 -2
- package/dist/ui/ccstyle/diff/diff-renderer.js.map +1 -1
- package/dist/ui/ccstyle/tool-renderer.d.ts +1 -1
- package/dist/ui/ccstyle/tool-renderer.d.ts.map +1 -1
- package/dist/ui/ccstyle/tool-renderer.js +12 -2
- package/dist/ui/ccstyle/tool-renderer.js.map +1 -1
- package/dist/ui/ccstyle/tool-result.d.ts.map +1 -1
- package/dist/ui/ccstyle/tool-result.js +14 -3
- package/dist/ui/ccstyle/tool-result.js.map +1 -1
- package/dist/ui/conversation-timeline.d.ts +9 -0
- package/dist/ui/conversation-timeline.d.ts.map +1 -1
- package/dist/ui/conversation-timeline.js +107 -22
- package/dist/ui/conversation-timeline.js.map +1 -1
- package/dist/ui/conversation-viewer.d.ts +4 -0
- package/dist/ui/conversation-viewer.d.ts.map +1 -1
- package/dist/ui/conversation-viewer.js +116 -22
- package/dist/ui/conversation-viewer.js.map +1 -1
- package/dist/write-execution.d.ts +2 -0
- package/dist/write-execution.d.ts.map +1 -1
- package/dist/write-execution.js +5 -1
- package/dist/write-execution.js.map +1 -1
- package/package.json +1 -1
- package/src/ui/ccstyle/diff/diff-renderer.ts +7 -2
- package/src/ui/ccstyle/tool-renderer.ts +18 -2
- package/src/ui/ccstyle/tool-result.ts +14 -3
- package/src/ui/conversation-timeline.ts +132 -21
- package/src/ui/conversation-viewer.ts +113 -23
- package/src/write-execution.ts +7 -1
- package/test/ui/conversation-diff.test.ts +13 -0
- package/test/ui/conversation-viewer.test.ts +113 -9
- package/test/write-execution.test.ts +2 -0
|
@@ -82,9 +82,14 @@ describe("ConversationViewer", () => {
|
|
|
82
82
|
{ pi: {} as any, ctx: {} as any, readOnly: false },
|
|
83
83
|
);
|
|
84
84
|
const liveFooter = live.render(120).join("\n");
|
|
85
|
-
expect(liveFooter).toContain("j/k · J/K · g/G t M o O /n N q");
|
|
86
|
-
expect(liveFooter).toContain("
|
|
85
|
+
expect(liveFooter).toContain("j/k scroll · J/K messages · g/G t M o O /n N q");
|
|
86
|
+
expect(liveFooter).toContain("e steer");
|
|
87
87
|
expect(liveFooter).toContain("x stop");
|
|
88
|
+
live.handleInput("\r");
|
|
89
|
+
expect(live.render(120).join("\n")).not.toContain("Enter send");
|
|
90
|
+
live.handleInput("e");
|
|
91
|
+
expect(live.render(120).join("\n")).toContain("Enter send");
|
|
92
|
+
live.handleInput("\x1b");
|
|
88
93
|
|
|
89
94
|
const history = new ConversationViewer(
|
|
90
95
|
tui(),
|
|
@@ -99,7 +104,7 @@ describe("ConversationViewer", () => {
|
|
|
99
104
|
{ pi: {} as any, ctx: {} as any, readOnly: true },
|
|
100
105
|
);
|
|
101
106
|
const historyFooter = history.render(120).join("\n");
|
|
102
|
-
expect(historyFooter).toContain("j/k · J/K · g/G t M o /n N q");
|
|
107
|
+
expect(historyFooter).toContain("j/k scroll · J/K messages · g/G t M o /n N q");
|
|
103
108
|
expect(historyFooter).not.toContain("M o O");
|
|
104
109
|
});
|
|
105
110
|
|
|
@@ -147,13 +152,71 @@ describe("ConversationViewer", () => {
|
|
|
147
152
|
vi.fn(),
|
|
148
153
|
);
|
|
149
154
|
|
|
150
|
-
|
|
151
|
-
|
|
155
|
+
const initial = viewer.render(120).join("\n");
|
|
156
|
+
expect(initial).not.toContain("six");
|
|
157
|
+
expect(initial).not.toContain("READ");
|
|
152
158
|
viewer.handleInput("\r");
|
|
153
159
|
expect(viewer.render(120).join("\n")).toContain("six");
|
|
154
160
|
});
|
|
155
161
|
|
|
156
|
-
it("
|
|
162
|
+
it("keeps J/K on user and assistant blocks while j/k remain line scrolling", () => {
|
|
163
|
+
const viewer = new ConversationViewer(
|
|
164
|
+
tui(),
|
|
165
|
+
createStaticConversationSource([
|
|
166
|
+
{ role: "assistant", content: [{ type: "text", text: "first" }, { type: "toolCall", name: "read", arguments: { path: "x" } }] },
|
|
167
|
+
{ role: "toolResult", toolName: "read", content: [{ type: "text", text: "tool output" }] },
|
|
168
|
+
{ role: "assistant", content: [{ type: "text", text: "second" }] },
|
|
169
|
+
] as any),
|
|
170
|
+
record(),
|
|
171
|
+
undefined,
|
|
172
|
+
theme,
|
|
173
|
+
vi.fn(),
|
|
174
|
+
);
|
|
175
|
+
viewer.render(120);
|
|
176
|
+
viewer.handleInput("J");
|
|
177
|
+
expect((viewer as any).currentBlock().role).toBe("assistant");
|
|
178
|
+
expect((viewer as any).currentBlock().markdown).toBe("second");
|
|
179
|
+
viewer.handleInput("K");
|
|
180
|
+
expect((viewer as any).currentBlock().markdown).toBe("first");
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("uses a distinct focus rail for tool cards", () => {
|
|
184
|
+
const railTheme = {
|
|
185
|
+
fg: (name: string, text: string) => `<${name}>${text}</${name}>`,
|
|
186
|
+
bold: (text: string) => `<bold>${text}</bold>`,
|
|
187
|
+
};
|
|
188
|
+
const viewer = new ConversationViewer(
|
|
189
|
+
tui(),
|
|
190
|
+
createStaticConversationSource([
|
|
191
|
+
{ role: "assistant", content: [{ type: "toolCall", name: "read", arguments: { path: "x" } }] },
|
|
192
|
+
{ role: "toolResult", toolName: "read", content: [{ type: "text", text: "done" }] },
|
|
193
|
+
] as any),
|
|
194
|
+
record(),
|
|
195
|
+
undefined,
|
|
196
|
+
railTheme,
|
|
197
|
+
vi.fn(),
|
|
198
|
+
);
|
|
199
|
+
expect(viewer.render(120).join("\\n")).toContain("<toolTitle>▌</toolTitle>");
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("scrolls the transcript with a normalized mouse wheel event", () => {
|
|
203
|
+
const longText = Array.from({ length: 80 }, (_, index) => `line-${index}`).join("\n");
|
|
204
|
+
const viewer = new ConversationViewer(
|
|
205
|
+
tui(),
|
|
206
|
+
createStaticConversationSource([{ role: "assistant", content: [{ type: "text", text: longText }] }] as any),
|
|
207
|
+
record(),
|
|
208
|
+
undefined,
|
|
209
|
+
theme,
|
|
210
|
+
vi.fn(),
|
|
211
|
+
);
|
|
212
|
+
viewer.render(120);
|
|
213
|
+
const before = (viewer as any).scrollOffset;
|
|
214
|
+
const event = { type: "wheel", button: "none", x: 2, y: 5, screenX: 2, screenY: 5, width: 120, height: 30, shift: false, alt: false, ctrl: false, wheelDelta: -3 };
|
|
215
|
+
expect(viewer.handleMouse(event as any)?.handled).toBe(true);
|
|
216
|
+
expect((viewer as any).scrollOffset).toBeLessThan(before);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("expands on the first click and collapses only on the second click", () => {
|
|
157
220
|
const viewer = new ConversationViewer(
|
|
158
221
|
tui(),
|
|
159
222
|
createStaticConversationSource([
|
|
@@ -166,11 +229,52 @@ describe("ConversationViewer", () => {
|
|
|
166
229
|
vi.fn(),
|
|
167
230
|
);
|
|
168
231
|
expect(viewer.render(120).join("\n")).not.toContain("six");
|
|
169
|
-
const
|
|
170
|
-
|
|
232
|
+
const span = (viewer as any).timeline.getSnapshot().toolSpans[0];
|
|
233
|
+
expect(span.clickStartLine).toEqual(expect.any(Number));
|
|
234
|
+
const hintY = 4 + span.clickStartLine;
|
|
235
|
+
const mouse = { button: "left", x: 1, screenX: 1, screenY: hintY, width: 120, height: 30, shift: false, alt: false, ctrl: false };
|
|
236
|
+
expect(viewer.handleMouse({ ...mouse, type: "press", y: hintY } as any)?.handled).toBe(true);
|
|
171
237
|
expect(viewer.render(120).join("\n")).not.toContain("six");
|
|
172
|
-
viewer.handleMouse({ ...mouse, type: "click", y:
|
|
238
|
+
viewer.handleMouse({ ...mouse, type: "click", y: hintY, clickCount: 1 } as any);
|
|
173
239
|
expect(viewer.render(120).join("\n")).toContain("six");
|
|
240
|
+
viewer.handleMouse({ ...mouse, type: "click", y: hintY, clickCount: 1 } as any);
|
|
241
|
+
expect(viewer.render(120).join("\n")).toContain("six");
|
|
242
|
+
viewer.handleMouse({ ...mouse, type: "click", y: hintY, clickCount: 2 } as any);
|
|
243
|
+
expect(viewer.render(120).join("\n")).not.toContain("six");
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it("highlights the tool hint on hover and closes from the viewer [esc] button", () => {
|
|
247
|
+
const semanticTheme = {
|
|
248
|
+
fg: (name: string, text: string) => `<${name}>${text}</${name}>`,
|
|
249
|
+
bold: (text: string) => `<bold>${text}</bold>`,
|
|
250
|
+
};
|
|
251
|
+
const done = vi.fn();
|
|
252
|
+
const viewer = new ConversationViewer(
|
|
253
|
+
tui(),
|
|
254
|
+
createStaticConversationSource([
|
|
255
|
+
{ role: "assistant", content: [{ type: "toolCall", name: "read", arguments: { path: "x" } }] },
|
|
256
|
+
{ role: "toolResult", toolName: "read", content: [{ type: "text", text: "one\ntwo\nthree\nfour\nfive\nsix" }] },
|
|
257
|
+
] as any),
|
|
258
|
+
record(),
|
|
259
|
+
undefined,
|
|
260
|
+
semanticTheme,
|
|
261
|
+
done,
|
|
262
|
+
);
|
|
263
|
+
const initial = viewer.render(120).join("\n");
|
|
264
|
+
const span = (viewer as any).timeline.getSnapshot().toolSpans[0];
|
|
265
|
+
expect(span.clickStartLine).toEqual(expect.any(Number));
|
|
266
|
+
const hintY = 4 + span.clickStartLine;
|
|
267
|
+
expect(initial).not.toContain("<text>click to show more</text>");
|
|
268
|
+
viewer.handleMouse({ type: "move", button: "none", x: 2, y: hintY, screenX: 2, screenY: hintY, width: 120, height: 30, shift: false, alt: false, ctrl: false } as any);
|
|
269
|
+
expect(viewer.render(120).join("\n")).toContain("<text>click to show more</text>");
|
|
270
|
+
|
|
271
|
+
expect(viewer.render(120).join("\n")).toContain("[esc]");
|
|
272
|
+
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);
|
|
273
|
+
expect(viewer.render(120).join("\n")).toContain("<text><bold>[esc]</bold></text>");
|
|
274
|
+
const close = { button: "left", x: 114, screenX: 114, screenY: 1, y: 1, width: 120, height: 30, shift: false, alt: false, ctrl: false };
|
|
275
|
+
expect(viewer.handleMouse({ ...close, type: "press" } as any)?.handled).toBe(true);
|
|
276
|
+
expect(viewer.handleMouse({ ...close, type: "click", clickCount: 1 } as any)?.handled).toBe(true);
|
|
277
|
+
expect(done).toHaveBeenCalledTimes(1);
|
|
174
278
|
});
|
|
175
279
|
|
|
176
280
|
it("keeps historical viewers read-only and closes on Escape", () => {
|
|
@@ -31,6 +31,7 @@ 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);
|
|
34
35
|
expect(result.details?.diff).toContain("+1 new");
|
|
35
36
|
expect(result.details?.patch).toContain("+new");
|
|
36
37
|
expect(store.get("call-new")?.diff).toBe(result.details?.diff);
|
|
@@ -48,6 +49,7 @@ describe("executeWriteWithMetadata", () => {
|
|
|
48
49
|
|
|
49
50
|
await writeFile(join(cwd, "file.txt"), "later\n", "utf8");
|
|
50
51
|
|
|
52
|
+
expect(persisted.fileExistedBeforeWrite).toBe(true);
|
|
51
53
|
expect(persisted.diff).toContain("-1 old");
|
|
52
54
|
expect(persisted.diff).toContain("+1 new");
|
|
53
55
|
expect(persisted.patch).toContain("-old");
|