@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.
Files changed (35) hide show
  1. package/.pi/tasks/tasks-01a09263-0ac6-77be-97f5-664b3ffea76b.json +53 -0
  2. package/.pi/workflows/index.json +5 -0
  3. package/CHANGELOG.md +17 -0
  4. package/dist/ui/ccstyle/diff/diff-renderer.d.ts.map +1 -1
  5. package/dist/ui/ccstyle/diff/diff-renderer.js +4 -2
  6. package/dist/ui/ccstyle/diff/diff-renderer.js.map +1 -1
  7. package/dist/ui/ccstyle/tool-renderer.d.ts +1 -1
  8. package/dist/ui/ccstyle/tool-renderer.d.ts.map +1 -1
  9. package/dist/ui/ccstyle/tool-renderer.js +12 -2
  10. package/dist/ui/ccstyle/tool-renderer.js.map +1 -1
  11. package/dist/ui/ccstyle/tool-result.d.ts.map +1 -1
  12. package/dist/ui/ccstyle/tool-result.js +14 -3
  13. package/dist/ui/ccstyle/tool-result.js.map +1 -1
  14. package/dist/ui/conversation-timeline.d.ts +9 -0
  15. package/dist/ui/conversation-timeline.d.ts.map +1 -1
  16. package/dist/ui/conversation-timeline.js +107 -22
  17. package/dist/ui/conversation-timeline.js.map +1 -1
  18. package/dist/ui/conversation-viewer.d.ts +4 -0
  19. package/dist/ui/conversation-viewer.d.ts.map +1 -1
  20. package/dist/ui/conversation-viewer.js +116 -22
  21. package/dist/ui/conversation-viewer.js.map +1 -1
  22. package/dist/write-execution.d.ts +2 -0
  23. package/dist/write-execution.d.ts.map +1 -1
  24. package/dist/write-execution.js +5 -1
  25. package/dist/write-execution.js.map +1 -1
  26. package/package.json +1 -1
  27. package/src/ui/ccstyle/diff/diff-renderer.ts +7 -2
  28. package/src/ui/ccstyle/tool-renderer.ts +18 -2
  29. package/src/ui/ccstyle/tool-result.ts +14 -3
  30. package/src/ui/conversation-timeline.ts +132 -21
  31. package/src/ui/conversation-viewer.ts +113 -23
  32. package/src/write-execution.ts +7 -1
  33. package/test/ui/conversation-diff.test.ts +13 -0
  34. package/test/ui/conversation-viewer.test.ts +113 -9
  35. 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("Enter steer");
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
- expect(viewer.render(120).join("\n")).not.toContain("six");
151
- viewer.handleInput("\t");
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("toggles mouse expansion only on the normalized click event", () => {
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 mouse = { button: "left", x: 1, screenX: 1, screenY: 5, width: 120, height: 30, shift: false, alt: false, ctrl: false };
170
- viewer.handleMouse({ ...mouse, type: "press", y: 5 } as any);
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: 5 } as any);
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");