@esso0428/pi-subagents 0.15.5 → 0.15.7

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 (234) hide show
  1. package/CHANGELOG.md +10 -19
  2. package/README.md +5 -5
  3. package/dist/agent-history-list.d.ts +19 -0
  4. package/dist/agent-history-list.d.ts.map +1 -0
  5. package/dist/agent-history-list.js +69 -0
  6. package/dist/agent-history-list.js.map +1 -0
  7. package/dist/agent-history.d.ts +2 -1
  8. package/dist/agent-history.d.ts.map +1 -0
  9. package/dist/agent-history.js +32 -34
  10. package/dist/agent-history.js.map +1 -0
  11. package/dist/agent-manager.d.ts +24 -8
  12. package/dist/agent-manager.d.ts.map +1 -0
  13. package/dist/agent-manager.js +171 -93
  14. package/dist/agent-manager.js.map +1 -0
  15. package/dist/agent-runner.d.ts +1 -0
  16. package/dist/agent-runner.d.ts.map +1 -0
  17. package/dist/agent-runner.js +75 -86
  18. package/dist/agent-runner.js.map +1 -0
  19. package/dist/agent-types.d.ts +1 -0
  20. package/dist/agent-types.d.ts.map +1 -0
  21. package/dist/agent-types.js +28 -45
  22. package/dist/agent-types.js.map +1 -0
  23. package/dist/context.d.ts +1 -0
  24. package/dist/context.d.ts.map +1 -0
  25. package/dist/context.js +3 -6
  26. package/dist/context.js.map +1 -0
  27. package/dist/cross-extension-rpc.d.ts +1 -0
  28. package/dist/cross-extension-rpc.d.ts.map +1 -0
  29. package/dist/cross-extension-rpc.js +6 -9
  30. package/dist/cross-extension-rpc.js.map +1 -0
  31. package/dist/custom-agents.d.ts +1 -0
  32. package/dist/custom-agents.d.ts.map +1 -0
  33. package/dist/custom-agents.js +16 -18
  34. package/dist/custom-agents.js.map +1 -0
  35. package/dist/default-agents.d.ts +1 -0
  36. package/dist/default-agents.d.ts.map +1 -0
  37. package/dist/default-agents.js +2 -4
  38. package/dist/default-agents.js.map +1 -0
  39. package/dist/enabled-models.d.ts +1 -0
  40. package/dist/enabled-models.d.ts.map +1 -0
  41. package/dist/enabled-models.js +12 -16
  42. package/dist/enabled-models.js.map +1 -0
  43. package/dist/env.d.ts +1 -0
  44. package/dist/env.d.ts.map +1 -0
  45. package/dist/env.js +2 -4
  46. package/dist/env.js.map +1 -0
  47. package/dist/group-join.d.ts +1 -0
  48. package/dist/group-join.d.ts.map +1 -0
  49. package/dist/group-join.js +2 -5
  50. package/dist/group-join.js.map +1 -0
  51. package/dist/index.d.ts +1 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +306 -292
  54. package/dist/index.js.map +1 -0
  55. package/dist/invocation-config.d.ts +1 -0
  56. package/dist/invocation-config.d.ts.map +1 -0
  57. package/dist/invocation-config.js +3 -6
  58. package/dist/invocation-config.js.map +1 -0
  59. package/dist/memory.d.ts +1 -0
  60. package/dist/memory.d.ts.map +1 -0
  61. package/dist/memory.js +25 -34
  62. package/dist/memory.js.map +1 -0
  63. package/dist/model-resolver.d.ts +1 -0
  64. package/dist/model-resolver.d.ts.map +1 -0
  65. package/dist/model-resolver.js +2 -4
  66. package/dist/model-resolver.js.map +1 -0
  67. package/dist/nico-overrides.d.ts +1 -0
  68. package/dist/nico-overrides.d.ts.map +1 -0
  69. package/dist/nico-overrides.js +15 -20
  70. package/dist/nico-overrides.js.map +1 -0
  71. package/dist/output-file.d.ts +1 -0
  72. package/dist/output-file.d.ts.map +1 -0
  73. package/dist/output-file.js +17 -22
  74. package/dist/output-file.js.map +1 -0
  75. package/dist/prompts.d.ts +1 -0
  76. package/dist/prompts.d.ts.map +1 -0
  77. package/dist/prompts.js +2 -4
  78. package/dist/prompts.js.map +1 -0
  79. package/dist/schedule-store.d.ts +1 -0
  80. package/dist/schedule-store.d.ts.map +1 -0
  81. package/dist/schedule-store.js +17 -21
  82. package/dist/schedule-store.js.map +1 -0
  83. package/dist/schedule.d.ts +1 -0
  84. package/dist/schedule.d.ts.map +1 -0
  85. package/dist/schedule.js +9 -12
  86. package/dist/schedule.js.map +1 -0
  87. package/dist/settings.d.ts +1 -0
  88. package/dist/settings.d.ts.map +1 -0
  89. package/dist/settings.js +16 -23
  90. package/dist/settings.js.map +1 -0
  91. package/dist/skill-loader.d.ts +1 -0
  92. package/dist/skill-loader.d.ts.map +1 -0
  93. package/dist/skill-loader.js +21 -23
  94. package/dist/skill-loader.js.map +1 -0
  95. package/dist/status-note.d.ts +1 -0
  96. package/dist/status-note.d.ts.map +1 -0
  97. package/dist/status-note.js +2 -4
  98. package/dist/status-note.js.map +1 -0
  99. package/dist/types.d.ts +5 -1
  100. package/dist/types.d.ts.map +1 -0
  101. package/dist/types.js +2 -4
  102. package/dist/types.js.map +1 -0
  103. package/dist/ui/agent-widget.d.ts +2 -5
  104. package/dist/ui/agent-widget.d.ts.map +1 -0
  105. package/dist/ui/agent-widget.js +29 -51
  106. package/dist/ui/agent-widget.js.map +1 -0
  107. package/dist/ui/ccstyle/diff/ansi-utils.d.ts +11 -0
  108. package/dist/ui/ccstyle/diff/ansi-utils.d.ts.map +1 -0
  109. package/dist/ui/ccstyle/diff/ansi-utils.js +145 -0
  110. package/dist/ui/ccstyle/diff/ansi-utils.js.map +1 -0
  111. package/dist/ui/ccstyle/diff/diff-presentation.d.ts +12 -0
  112. package/dist/ui/ccstyle/diff/diff-presentation.d.ts.map +1 -0
  113. package/dist/ui/ccstyle/diff/diff-presentation.js +49 -0
  114. package/dist/ui/ccstyle/diff/diff-presentation.js.map +1 -0
  115. package/dist/ui/ccstyle/diff/diff-renderer.d.ts +46 -0
  116. package/dist/ui/ccstyle/diff/diff-renderer.d.ts.map +1 -0
  117. package/dist/ui/ccstyle/diff/diff-renderer.js +2044 -0
  118. package/dist/ui/ccstyle/diff/diff-renderer.js.map +1 -0
  119. package/dist/ui/ccstyle/diff/line-width-safety.d.ts +12 -0
  120. package/dist/ui/ccstyle/diff/line-width-safety.d.ts.map +1 -0
  121. package/dist/ui/ccstyle/diff/line-width-safety.js +59 -0
  122. package/dist/ui/ccstyle/diff/line-width-safety.js.map +1 -0
  123. package/dist/ui/ccstyle/diff/render-utils.d.ts +6 -0
  124. package/dist/ui/ccstyle/diff/render-utils.d.ts.map +1 -0
  125. package/dist/ui/ccstyle/diff/render-utils.js +25 -0
  126. package/dist/ui/ccstyle/diff/render-utils.js.map +1 -0
  127. package/dist/ui/ccstyle/diff/types.d.ts +22 -0
  128. package/dist/ui/ccstyle/diff/types.d.ts.map +1 -0
  129. package/dist/ui/ccstyle/diff/types.js +10 -0
  130. package/dist/ui/ccstyle/diff/types.js.map +1 -0
  131. package/dist/ui/ccstyle/diff/write-display-utils.d.ts +2 -0
  132. package/dist/ui/ccstyle/diff/write-display-utils.d.ts.map +1 -0
  133. package/dist/ui/ccstyle/diff/write-display-utils.js +12 -0
  134. package/dist/ui/ccstyle/diff/write-display-utils.js.map +1 -0
  135. package/dist/ui/ccstyle/tool-renderer.d.ts +17 -0
  136. package/dist/ui/ccstyle/tool-renderer.d.ts.map +1 -0
  137. package/dist/ui/ccstyle/tool-renderer.js +58 -0
  138. package/dist/ui/ccstyle/tool-renderer.js.map +1 -0
  139. package/dist/ui/ccstyle/tool-result.d.ts +24 -0
  140. package/dist/ui/ccstyle/tool-result.d.ts.map +1 -0
  141. package/dist/ui/ccstyle/tool-result.js +193 -0
  142. package/dist/ui/ccstyle/tool-result.js.map +1 -0
  143. package/dist/ui/conversation-blocks.d.ts +46 -0
  144. package/dist/ui/conversation-blocks.d.ts.map +1 -0
  145. package/dist/ui/conversation-blocks.js +306 -0
  146. package/dist/ui/conversation-blocks.js.map +1 -0
  147. package/dist/ui/conversation-nvim.d.ts +8 -0
  148. package/dist/ui/conversation-nvim.d.ts.map +1 -0
  149. package/dist/ui/conversation-nvim.js +117 -0
  150. package/dist/ui/conversation-nvim.js.map +1 -0
  151. package/dist/ui/conversation-role.d.ts +13 -0
  152. package/dist/ui/conversation-role.d.ts.map +1 -0
  153. package/dist/ui/conversation-role.js +39 -0
  154. package/dist/ui/conversation-role.js.map +1 -0
  155. package/dist/ui/conversation-search.d.ts +39 -0
  156. package/dist/ui/conversation-search.d.ts.map +1 -0
  157. package/dist/ui/conversation-search.js +124 -0
  158. package/dist/ui/conversation-search.js.map +1 -0
  159. package/dist/ui/conversation-timeline.d.ts +62 -0
  160. package/dist/ui/conversation-timeline.d.ts.map +1 -0
  161. package/dist/ui/conversation-timeline.js +263 -0
  162. package/dist/ui/conversation-timeline.js.map +1 -0
  163. package/dist/ui/conversation-viewer.d.ts +62 -20
  164. package/dist/ui/conversation-viewer.d.ts.map +1 -0
  165. package/dist/ui/conversation-viewer.js +449 -224
  166. package/dist/ui/conversation-viewer.js.map +1 -0
  167. package/dist/ui/fleet-list.d.ts +24 -11
  168. package/dist/ui/fleet-list.d.ts.map +1 -0
  169. package/dist/ui/fleet-list.js +86 -78
  170. package/dist/ui/fleet-list.js.map +1 -0
  171. package/dist/ui/schedule-menu.d.ts +1 -0
  172. package/dist/ui/schedule-menu.d.ts.map +1 -0
  173. package/dist/ui/schedule-menu.js +2 -4
  174. package/dist/ui/schedule-menu.js.map +1 -0
  175. package/dist/ui/viewer-keys.d.ts +1 -0
  176. package/dist/ui/viewer-keys.d.ts.map +1 -0
  177. package/dist/ui/viewer-keys.js +8 -10
  178. package/dist/ui/viewer-keys.js.map +1 -0
  179. package/dist/usage.d.ts +1 -0
  180. package/dist/usage.d.ts.map +1 -0
  181. package/dist/usage.js +5 -10
  182. package/dist/usage.js.map +1 -0
  183. package/dist/worktree.d.ts +1 -0
  184. package/dist/worktree.d.ts.map +1 -0
  185. package/dist/worktree.js +27 -31
  186. package/dist/worktree.js.map +1 -0
  187. package/dist/write-execution.d.ts +42 -0
  188. package/dist/write-execution.d.ts.map +1 -0
  189. package/dist/write-execution.js +138 -0
  190. package/dist/write-execution.js.map +1 -0
  191. package/package.json +1 -1
  192. package/src/agent-history-list.ts +91 -0
  193. package/src/agent-history.ts +12 -17
  194. package/src/agent-manager.ts +169 -81
  195. package/src/agent-runner.ts +12 -13
  196. package/src/index.ts +117 -98
  197. package/src/output-file.ts +1 -1
  198. package/src/types.ts +4 -1
  199. package/src/ui/agent-widget.ts +3 -9
  200. package/src/ui/ccstyle/ATTRIBUTION.md +37 -0
  201. package/src/ui/ccstyle/diff/ansi-utils.ts +168 -0
  202. package/src/ui/ccstyle/diff/diff-presentation.ts +70 -0
  203. package/src/ui/ccstyle/diff/diff-renderer.ts +3148 -0
  204. package/src/ui/ccstyle/diff/line-width-safety.ts +92 -0
  205. package/src/ui/ccstyle/diff/render-utils.ts +32 -0
  206. package/src/ui/ccstyle/diff/types.ts +33 -0
  207. package/src/ui/ccstyle/diff/write-display-utils.ts +12 -0
  208. package/src/ui/ccstyle/tool-renderer.ts +71 -0
  209. package/src/ui/ccstyle/tool-result.ts +238 -0
  210. package/src/ui/conversation-blocks.ts +404 -0
  211. package/src/ui/conversation-nvim.ts +127 -0
  212. package/src/ui/conversation-role.ts +45 -0
  213. package/src/ui/conversation-search.ts +161 -0
  214. package/src/ui/conversation-timeline.ts +315 -0
  215. package/src/ui/conversation-viewer.ts +418 -210
  216. package/src/ui/fleet-list.ts +73 -52
  217. package/src/write-execution.ts +180 -0
  218. package/test/agent-history-list.test.ts +121 -0
  219. package/test/agent-manager-history.test.ts +59 -58
  220. package/test/agent-menu-history-navigation.test.ts +86 -0
  221. package/test/output-file-history.test.ts +50 -0
  222. package/test/ui/conversation-blocks.test.ts +103 -0
  223. package/test/ui/conversation-diff.test.ts +91 -0
  224. package/test/ui/conversation-search.test.ts +55 -0
  225. package/test/ui/conversation-viewer.test.ts +182 -0
  226. package/test/ui/fleet-list.test.ts +131 -0
  227. package/test/write-execution.test.ts +83 -0
  228. package/tsconfig.json +16 -0
  229. package/vitest.temp.config.mts +6 -0
  230. package/dist/ui/markdown-result.d.ts +0 -3
  231. package/dist/ui/markdown-result.js +0 -53
  232. package/src/ui/markdown-result.ts +0 -56
  233. package/test/agent-history.test.ts +0 -63
  234. package/test/ui/markdown-result.test.ts +0 -45
package/src/index.ts CHANGED
@@ -24,7 +24,8 @@ import { isModelInScope, readEnabledModels, resolveEnabledModels } from "./enabl
24
24
  import { GroupJoinManager } from "./group-join.js";
25
25
  import { resolveAgentInvocationConfig, resolveJoinMode } from "./invocation-config.js";
26
26
  import { type ModelRegistry, resolveModel } from "./model-resolver.js";
27
- import { agentHistoryLocator, createAgentHistoryPath, ensureSubagentsGitignore, hasAgentHistory, readAgentHistory } from "./agent-history.js";
27
+ import { agentHistoryLocator, createAgentHistoryPath, readAgentHistory } from "./agent-history.js";
28
+ import { buildAgentStatusMenuEntries, canOpenActiveAgent, canOpenAgentHistory, formatAgentHistoryOption, splitAgentRecords } from "./agent-history-list.js";
28
29
  import { createOutputFilePath, streamToOutputFile, writeInitialEntry } from "./output-file.js";
29
30
  import { SubagentScheduler } from "./schedule.js";
30
31
  import { resolveStorePath, ScheduleStore } from "./schedule-store.js";
@@ -49,7 +50,6 @@ import {
49
50
  type UICtx,
50
51
  } from "./ui/agent-widget.js";
51
52
  import { FleetList, type FleetUICtx } from "./ui/fleet-list.js";
52
- import { createMarkdownResult } from "./ui/markdown-result.js";
53
53
  import { showSchedulesMenu } from "./ui/schedule-menu.js";
54
54
  import { addUsage, getLifetimeTotal, getSessionContextPercent, type LifetimeUsage } from "./usage.js";
55
55
 
@@ -242,9 +242,6 @@ function buildDetails(
242
242
  };
243
243
  }
244
244
 
245
- /** Maximum expanded notification body retained in message details. */
246
- const MAX_NOTIFICATION_RESULT_TEXT_LENGTH = 20_000;
247
-
248
245
  /** Build notification details for the custom message renderer. */
249
246
  function buildNotificationDetails(record: AgentRecord, resultMaxLen: number, activity?: AgentActivity): NotificationDetails {
250
247
  const totalTokens = getLifetimeTotal(record.lifetimeUsage);
@@ -260,11 +257,6 @@ function buildNotificationDetails(record: AgentRecord, resultMaxLen: number, act
260
257
  durationMs: record.completedAt ? record.completedAt - record.startedAt : 0,
261
258
  outputFile: record.outputFile,
262
259
  error: record.error,
263
- resultText: record.result
264
- ? record.result.length > MAX_NOTIFICATION_RESULT_TEXT_LENGTH
265
- ? record.result.slice(0, MAX_NOTIFICATION_RESULT_TEXT_LENGTH) + "…"
266
- : record.result
267
- : undefined,
268
260
  resultPreview: record.result
269
261
  ? record.result.length > resultMaxLen
270
262
  ? record.result.slice(0, resultMaxLen) + "…"
@@ -281,7 +273,7 @@ export default function (pi: ExtensionAPI) {
281
273
  const d = message.details;
282
274
  if (!d) return undefined;
283
275
 
284
- function renderOne(d: NotificationDetails): string | Container {
276
+ function renderOne(d: NotificationDetails): string {
285
277
  const isError = d.status === "error" || d.status === "stopped" || d.status === "aborted";
286
278
  const icon = isError ? theme.fg("error", "✗") : theme.fg("success", "✓");
287
279
  const statusText = isError ? d.status
@@ -301,34 +293,25 @@ export default function (pi: ExtensionAPI) {
301
293
  line += "\n " + parts.map(p => theme.fg("dim", p)).join(" " + theme.fg("dim", "·") + " ");
302
294
  }
303
295
 
304
- if (!expanded) {
305
- // Collapsed previews intentionally remain compact plain text.
296
+ // Line 3: result preview (collapsed) or full (expanded)
297
+ if (expanded) {
298
+ const lines = d.resultPreview.split("\n").slice(0, 30);
299
+ for (const l of lines) line += "\n" + theme.fg("dim", ` ${l}`);
300
+ } else {
306
301
  const preview = d.resultPreview.split("\n")[0]?.slice(0, 80) ?? "";
307
302
  line += "\n " + theme.fg("dim", `⎿ ${preview}`);
308
- if (d.outputFile) {
309
- line += "\n " + theme.fg("muted", `transcript: ${d.outputFile}`);
310
- }
311
- return line;
312
303
  }
313
304
 
314
- const container = new Container();
315
- container.addChild(new Text(line, 0, 0));
316
- container.addChild(createMarkdownResult(d.resultText ?? d.resultPreview, theme, 30));
305
+ // Line 4: output file link (if present)
317
306
  if (d.outputFile) {
318
- container.addChild(new Text(theme.fg("muted", ` transcript: ${d.outputFile}`), 0, 0));
307
+ line += "\n " + theme.fg("muted", `transcript: ${d.outputFile}`);
319
308
  }
320
- return container;
309
+
310
+ return line;
321
311
  }
322
312
 
323
313
  const all = [d, ...(d.others ?? [])];
324
- if (!expanded) return new Text(all.map(renderOne).join("\n"), 0, 0);
325
-
326
- const container = new Container();
327
- all.forEach((item, index) => {
328
- if (index > 0) container.addChild(new Spacer(1));
329
- container.addChild(renderOne(item) as Container);
330
- });
331
- return container;
314
+ return new Text(all.map(renderOne).join("\n"), 0, 0);
332
315
  }
333
316
  );
334
317
 
@@ -463,13 +446,14 @@ export default function (pi: ExtensionAPI) {
463
446
  pi.events.emit("subagents:completed", eventData);
464
447
  }
465
448
 
466
- // Persist final record for cross-extension history reconstruction. Keep
467
- // the transcript body in a project-local file rather than inflating the
468
- // parent session JSONL; only its project-relative locator is persisted.
449
+ // Persist final record for cross-extension history reconstruction
469
450
  pi.appendEntry("subagents:record", {
470
451
  id: record.id, type: record.type, description: record.description,
471
452
  status: record.status, result: record.result, error: record.error,
472
453
  startedAt: record.startedAt, completedAt: record.completedAt,
454
+ toolUses: record.toolUses,
455
+ lifetimeUsage: record.lifetimeUsage,
456
+ invocation: record.invocation,
473
457
  transcriptPath: record.transcriptPath,
474
458
  });
475
459
 
@@ -574,22 +558,11 @@ export default function (pi: ExtensionAPI) {
574
558
  // bound session_start, so a filtered-out activation never advertises (#142).
575
559
  pi.on("session_start", async (_event, ctx) => {
576
560
  currentCtx = ctx;
577
- try {
578
- ensureSubagentsGitignore(ctx.cwd);
579
- } catch (err) {
580
- console.warn("[pi-subagents] Failed to protect project-local history:", err);
581
- }
582
561
  manager.clearCompleted(true);
583
- const historicalRecords = ctx.sessionManager
584
- .getBranch()
585
- .filter((entry: any) => entry?.type === "custom" && entry.customType === "subagents:record")
586
- .map((entry: any) => entry.data);
587
- manager.restoreCompleted(historicalRecords);
588
- // Bind the UI here as well as on tool execution so rehydrated terminal
589
- // records are immediately visible and openable from FleetView after reload.
590
- widget.setUICtx(ctx.ui as UICtx);
591
- fleet.setUICtx(ctx.ui as unknown as FleetUICtx);
592
- fleet.update();
562
+ const branch = ctx.sessionManager?.getBranch?.() ?? [];
563
+ manager.restoreCompleted(branch
564
+ .filter((entry: any) => entry?.type === "custom" && entry?.customType === "subagents:record")
565
+ .map((entry: any) => entry.data));
593
566
  // Guard mirrors the `!scheduler.isActive()` pattern below: session_start
594
567
  // fires once per activation, but a double-bind must not leak listeners.
595
568
  if (!rpcHandle) {
@@ -640,11 +613,11 @@ export default function (pi: ExtensionAPI) {
640
613
  // everything else; "off" = hide the widget entirely. Read live at render time.
641
614
  let widgetMode: WidgetMode = "background";
642
615
  function getWidgetMode(): WidgetMode { return widgetMode; }
643
- const widget = new AgentWidget(manager, agentActivity, getWidgetMode, () => currentCtx?.cwd);
616
+ const widget = new AgentWidget(manager, agentActivity, getWidgetMode);
644
617
  function setWidgetMode(m: WidgetMode): void { widgetMode = m; widget.update(); }
645
618
 
646
619
  // Claude Code-style FleetView: navigable list of main + subagents below the editor.
647
- const fleet = new FleetList(manager, agentActivity, () => currentCtx?.cwd);
620
+ const fleet = new FleetList(manager, agentActivity, () => currentCtx?.cwd, pi, () => currentCtx);
648
621
  let fleetViewEnabled = true;
649
622
  function isFleetViewEnabled(): boolean { return fleetViewEnabled; }
650
623
  function setFleetViewEnabled(b: boolean): void { fleetViewEnabled = b; fleet.setEnabled(b); }
@@ -1061,15 +1034,15 @@ Terse command-style prompts produce shallow, generic work.
1061
1034
 
1062
1035
  if (expanded) {
1063
1036
  const resultText = result.content[0]?.type === "text" ? result.content[0].text : "";
1064
- const container = new Container();
1065
- container.addChild(new Text(line, 0, 0));
1066
1037
  if (resultText) {
1067
- container.addChild(createMarkdownResult(resultText, theme, 50));
1038
+ const lines = resultText.split("\n").slice(0, 50);
1039
+ for (const l of lines) {
1040
+ line += "\n" + theme.fg("dim", ` ${l}`);
1041
+ }
1068
1042
  if (resultText.split("\n").length > 50) {
1069
- container.addChild(new Text(theme.fg("muted", " ... (use get_subagent_result with verbose for full output)"), 0, 0));
1043
+ line += "\n" + theme.fg("muted", " ... (use get_subagent_result with verbose for full output)");
1070
1044
  }
1071
1045
  }
1072
- return container;
1073
1046
  } else {
1074
1047
  const doneText = isSteered ? "Wrapped up (turn limit)" : "Done";
1075
1048
  line += "\n" + theme.fg("dim", ` ⎿ ${doneText}`);
@@ -1176,17 +1149,17 @@ Terse command-style prompts produce shallow, generic work.
1176
1149
  rec.outputFile = createOutputFilePath(ctx.cwd, agentId, ctx.sessionManager.getSessionId());
1177
1150
  writeInitialEntry(rec.outputFile, agentId, params.prompt, ctx.cwd);
1178
1151
 
1179
- // Keep the existing /tmp output for compatibility, while also writing a
1180
- // durable project-local copy for reload-safe, read-only history viewing.
1181
1152
  try {
1182
- rec.historyFile = createAgentHistoryPath(ctx.cwd, agentId, subagentType);
1153
+ rec.historyFile = createAgentHistoryPath(ctx.cwd, agentId);
1183
1154
  rec.transcriptPath = agentHistoryLocator(ctx.cwd, rec.historyFile);
1184
1155
  writeInitialEntry(rec.historyFile, agentId, params.prompt, ctx.cwd);
1185
1156
  } catch (err) {
1186
- // History is best effort; never disable the existing output transcript.
1187
1157
  rec.historyFile = undefined;
1188
1158
  rec.transcriptPath = undefined;
1189
- console.warn("[pi-subagents] Failed to create durable transcript:", err);
1159
+ ctx.ui.notify(
1160
+ `Could not create durable transcript for agent ${agentId}: ${err instanceof Error ? err.message : String(err)}`,
1161
+ "warning",
1162
+ );
1190
1163
  }
1191
1164
  };
1192
1165
 
@@ -1198,7 +1171,9 @@ Terse command-style prompts produce shallow, generic work.
1198
1171
  const effectiveMaxTurns = normalizeMaxTurns(resolvedConfig.maxTurns ?? getDefaultMaxTurns());
1199
1172
  const agentInvocation: AgentInvocation = {
1200
1173
  modelName,
1174
+ effectiveModelName: model?.name ?? model?.id ?? ctx.model?.name ?? ctx.model?.id,
1201
1175
  thinking,
1176
+ effectiveThinking: thinking,
1202
1177
  // Explicit value only — the default fallback would just add noise.
1203
1178
  // Normalize so `0` (unlimited) doesn't surface as a misleading "max turns: 0".
1204
1179
  maxTurns: normalizeMaxTurns(resolvedConfig.maxTurns),
@@ -1667,17 +1642,10 @@ Terse command-style prompts produce shallow, generic work.
1667
1642
  // Build select options
1668
1643
  const options: string[] = [];
1669
1644
 
1670
- // Only advertise agents that can actually be opened. Reload-restored
1671
- // metadata without a live session or durable transcript is intentionally
1672
- // invisible here, matching FleetView and showRunningAgents below.
1673
- const agents = manager.listAgents().filter((record) =>
1674
- record.session !== undefined || hasAgentHistory(ctx.cwd, record.transcriptPath),
1675
- );
1676
- if (agents.length > 0) {
1677
- const running = agents.filter(a => a.status === "running" || a.status === "queued").length;
1678
- const done = agents.filter(a => a.status === "completed" || a.status === "steered").length;
1679
- options.push(`Running agents (${agents.length}) — ${running} running, ${done} done`);
1680
- }
1645
+ // Keep active agents and terminal history in separate menu entries.
1646
+ const records = manager.listAgents();
1647
+ const { active, history } = splitAgentRecords(records, ctx.cwd);
1648
+ options.push(...buildAgentStatusMenuEntries(records, ctx.cwd));
1681
1649
 
1682
1650
  // Agent types list
1683
1651
  if (allNames.length > 0) {
@@ -1694,7 +1662,7 @@ Terse command-style prompts produce shallow, generic work.
1694
1662
  options.push("Create new agent");
1695
1663
  options.push("Settings");
1696
1664
 
1697
- const noAgentsMsg = allNames.length === 0 && agents.length === 0
1665
+ const noAgentsMsg = allNames.length === 0 && active.length === 0 && history.length === 0
1698
1666
  ? "No agents found. Create specialized subagents that can be delegated to.\n\n" +
1699
1667
  "Each subagent has its own context window, custom system prompt, and specific tools.\n\n" +
1700
1668
  "Try creating: Code Reviewer, Security Auditor, Test Writer, or Documentation Writer.\n\n"
@@ -1710,6 +1678,9 @@ Terse command-style prompts produce shallow, generic work.
1710
1678
  if (choice.startsWith("Running agents (")) {
1711
1679
  await showRunningAgents(ctx);
1712
1680
  await showAgentsMenu(ctx);
1681
+ } else if (choice.startsWith("Agent history (")) {
1682
+ await showAgentHistory(ctx);
1683
+ await showAgentsMenu(ctx);
1713
1684
  } else if (choice.startsWith("Agent types (")) {
1714
1685
  await showAllAgentsList(ctx);
1715
1686
  await showAgentsMenu(ctx);
@@ -1792,54 +1763,101 @@ Terse command-style prompts produce shallow, generic work.
1792
1763
  }
1793
1764
  }
1794
1765
 
1766
+ function makeUniqueAgentOptionLabels(pairs: Array<{ record: AgentRecord; label: string }>): string[] {
1767
+ const counts = new Map<string, number>();
1768
+ for (const pair of pairs) counts.set(pair.label, (counts.get(pair.label) ?? 0) + 1);
1769
+ const used = new Set<string>();
1770
+ return pairs.map((pair) => {
1771
+ const { record, label } = pair;
1772
+ if ((counts.get(label) ?? 0) === 1) {
1773
+ used.add(label);
1774
+ return label;
1775
+ }
1776
+ const suffix = ` · #${record.id.slice(-8)}`;
1777
+ let candidate = `${label}${suffix}`;
1778
+ let n = 2;
1779
+ while (used.has(candidate)) candidate = `${label}${suffix}-${n++}`;
1780
+ used.add(candidate);
1781
+ pair.label = candidate;
1782
+ return candidate;
1783
+ });
1784
+ }
1785
+
1795
1786
  async function showRunningAgents(ctx: ExtensionCommandContext) {
1796
- const agents = manager.listAgents().filter((record) =>
1797
- record.session !== undefined || hasAgentHistory(ctx.cwd, record.transcriptPath),
1798
- );
1787
+ const { active: agents } = splitAgentRecords(manager.listAgents(), ctx.cwd);
1799
1788
  if (agents.length === 0) {
1800
1789
  ctx.ui.notify("No agents.", "info");
1801
1790
  return;
1802
1791
  }
1803
1792
 
1804
- const options = agents.map(a => {
1805
- const dn = getDisplayName(a.type);
1806
- const dur = formatDuration(a.startedAt, a.completedAt);
1807
- return `${dn} (${a.description}) · ${a.toolUses} tools · ${a.status} · ${dur}`;
1793
+ const pairs = agents.map((record) => {
1794
+ const dn = getDisplayName(record.type);
1795
+ const dur = formatDuration(record.startedAt, record.completedAt);
1796
+ return { record, label: `${dn} (${record.description}) · ${record.toolUses} tools · ${record.status} · ${dur}` };
1808
1797
  });
1798
+ const options = makeUniqueAgentOptionLabels(pairs);
1809
1799
 
1810
1800
  const choice = await ctx.ui.select("Running agents", options);
1811
1801
  if (!choice) return;
1802
+ const record = pairs.find((pair) => pair.label === choice)?.record;
1803
+ if (!record) return;
1812
1804
 
1813
- // Find the selected agent by matching the option index
1814
- const idx = options.indexOf(choice);
1815
- if (idx < 0) return;
1816
- const record = agents[idx];
1817
-
1818
- await viewAgentConversation(ctx, record);
1805
+ await viewAgentConversation(ctx, record, "live");
1819
1806
  // Back-navigation: re-show the list
1820
1807
  await showRunningAgents(ctx);
1821
1808
  }
1822
1809
 
1823
- async function viewAgentConversation(ctx: ExtensionCommandContext, record: AgentRecord) {
1824
- if (!record.session) {
1825
- ctx.ui.notify(`Agent is ${record.status === "queued" ? "queued" : "expired"} — no session available.`, "info");
1810
+ async function showAgentHistory(ctx: ExtensionCommandContext) {
1811
+ const { history } = splitAgentRecords(manager.listAgents(), ctx.cwd);
1812
+ if (history.length === 0) {
1813
+ ctx.ui.notify("No agent history.", "info");
1814
+ return;
1815
+ }
1816
+
1817
+ const pairs = history.map((record) => ({ record, label: formatAgentHistoryOption(record, Date.now()) }));
1818
+ const options = makeUniqueAgentOptionLabels(pairs);
1819
+ const choice = await ctx.ui.select("Agent history", options);
1820
+ if (!choice) return;
1821
+ const record = pairs.find((pair) => pair.label === choice)?.record;
1822
+ if (!record) return;
1823
+
1824
+ await viewAgentConversation(ctx, record, "history");
1825
+ }
1826
+
1827
+ async function viewAgentConversation(
1828
+ ctx: ExtensionCommandContext,
1829
+ record: AgentRecord,
1830
+ mode: "live" | "history",
1831
+ ) {
1832
+ if (mode === "live" && !canOpenActiveAgent(record)) {
1833
+ ctx.ui.notify(`Agent is ${record.status === "queued" ? "queued" : "expired"} — no history available.`, "info");
1834
+ return;
1835
+ }
1836
+ if (mode === "history" && !canOpenAgentHistory(record, ctx.cwd)) {
1837
+ ctx.ui.notify("No agent history.", "info");
1826
1838
  return;
1827
1839
  }
1828
1840
 
1829
1841
  const { ConversationViewer, VIEWPORT_HEIGHT_PCT, createStaticConversationSource } = await import("./ui/conversation-viewer.js");
1830
- const session = record.session ?? (record.transcriptPath
1831
- ? (() => {
1832
- const messages = readAgentHistory(ctx.cwd, record.transcriptPath!);
1833
- return messages ? createStaticConversationSource(messages) : undefined;
1834
- })()
1835
- : undefined);
1842
+ const session = mode === "live"
1843
+ ? record.session
1844
+ : (() => {
1845
+ const messages = record.transcriptPath
1846
+ ? readAgentHistory(ctx.cwd, record.transcriptPath)
1847
+ : undefined;
1848
+ return messages
1849
+ ? createStaticConversationSource(messages)
1850
+ : record.session
1851
+ ? createStaticConversationSource(record.session.messages)
1852
+ : undefined;
1853
+ })();
1836
1854
  if (!session) {
1837
- ctx.ui.notify(`Agent is ${record.status === "queued" ? "queued" : "expired"} — no history available.`, "info");
1855
+ ctx.ui.notify("No agent history.", "info");
1838
1856
  return;
1839
1857
  }
1840
- const activity = agentActivity.get(record.id);
1841
- const isLive = record.session !== undefined;
1842
1858
 
1859
+ const activity = agentActivity.get(record.id);
1860
+ const isLive = mode === "live";
1843
1861
  await ctx.ui.custom<undefined>(
1844
1862
  (tui, theme, keybindings, done) => {
1845
1863
  return new ConversationViewer(tui, session, record, activity, theme, done,
@@ -1849,7 +1867,8 @@ Terse command-style prompts produce shallow, generic work.
1849
1867
  }
1850
1868
  } : undefined,
1851
1869
  keybindings,
1852
- isLive ? (message: string) => manager.steer(record.id, message) : undefined);
1870
+ isLive ? (message: string) => manager.steer(record.id, message) : undefined,
1871
+ mode === "history" ? { pi, ctx, readOnly: true } : { pi, ctx });
1853
1872
  },
1854
1873
  {
1855
1874
  overlay: true,
@@ -65,8 +65,8 @@ export function streamToOutputFile(
65
65
  cwd: string,
66
66
  historyPath?: string,
67
67
  ): () => void {
68
- const outputPaths = historyPath && historyPath !== path ? [path, historyPath] : [path];
69
68
  let writtenCount = 1; // initial user prompt already written
69
+ const outputPaths = historyPath && historyPath !== path ? [path, historyPath] : [path];
70
70
 
71
71
  const flush = () => {
72
72
  const messages = session.messages;
package/src/types.ts CHANGED
@@ -137,6 +137,10 @@ export interface AgentRecord {
137
137
  export interface AgentInvocation {
138
138
  /** Short display name, e.g. "haiku" — only set when different from parent. */
139
139
  modelName?: string;
140
+ /** Effective model captured from the child session, including inherited models. */
141
+ effectiveModelName?: string;
142
+ /** Requested/ effective thinking level. */
143
+ effectiveThinking?: AgentSession["thinkingLevel"];
140
144
  thinking?: ThinkingLevel;
141
145
  maxTurns?: number;
142
146
  isolated?: boolean;
@@ -158,7 +162,6 @@ export interface NotificationDetails {
158
162
  outputFile?: string;
159
163
  error?: string;
160
164
  resultPreview: string;
161
- resultText?: string;
162
165
  /** Additional agents in a group notification. */
163
166
  others?: NotificationDetails[];
164
167
  }
@@ -6,7 +6,6 @@
6
6
  */
7
7
 
8
8
  import { truncateToWidth } from "@earendil-works/pi-tui";
9
- import { hasAgentHistory } from "../agent-history.js";
10
9
  import type { AgentManager } from "../agent-manager.js";
11
10
  import { getConfig } from "../agent-types.js";
12
11
  import type { AgentInvocation, SubagentType, WidgetMode } from "../types.js";
@@ -167,7 +166,8 @@ export function buildInvocationTags(
167
166
  ): { modelName?: string; tags: string[] } {
168
167
  const tags: string[] = [];
169
168
  if (!invocation) return { tags };
170
- if (invocation.thinking) tags.push(`thinking: ${invocation.thinking}`);
169
+ const thinking = invocation.effectiveThinking ?? invocation.thinking;
170
+ if (thinking) tags.push(`thinking: ${thinking}`);
171
171
  if (invocation.isolated) tags.push("isolated");
172
172
  if (invocation.isolation === "worktree") tags.push("worktree");
173
173
  if (invocation.inheritContext) tags.push("inherit context");
@@ -238,8 +238,6 @@ export class AgentWidget {
238
238
  * extension supplies one defaulting to `"background"`.
239
239
  */
240
240
  private mode: () => WidgetMode = () => "all",
241
- /** Current project cwd used to validate reload-restored transcripts. */
242
- private getCwd: () => string | undefined = () => undefined,
243
241
  ) {}
244
242
 
245
243
  /**
@@ -254,11 +252,7 @@ export class AgentWidget {
254
252
  * - `all`: every agent.
255
253
  */
256
254
  private widgetAgents() {
257
- const all = this.manager.listAgents().filter((record) => {
258
- if (record.status === "running" || record.status === "queued") return true;
259
- const cwd = this.getCwd();
260
- return cwd !== undefined && hasAgentHistory(cwd, record.transcriptPath);
261
- });
255
+ const all = this.manager.listAgents();
262
256
  switch (this.mode()) {
263
257
  case "off": return [];
264
258
  case "background": return all.filter(a => a.isBackground !== false);
@@ -0,0 +1,37 @@
1
+ # Attribution
2
+
3
+ The viewer-local ccstyle tool-card and diff renderer are adapted from
4
+ `@herbertgao/pi-cc-extensions` version `0.9.0`, specifically its
5
+ `extensions/renderer/tool/result.ts` and
6
+ `extensions/renderer/tool/diff/` modules.
7
+
8
+ The adaptation is local to this package and changes the configuration boundary,
9
+ write payload handling, width guards, and syntax highlighting fallback. It does
10
+ not install `@shikijs/cli`, patch Pi prototypes, or register global terminal
11
+ handlers. The local copy uses Pi's public `highlightCode` API and plain text
12
+ when highlighting is unavailable.
13
+
14
+ The source package is distributed under the MIT License. The relevant license
15
+ text is reproduced below:
16
+
17
+ > MIT License
18
+ >
19
+ > Copyright (c) 2026 Herbert Gao and pi-cc-extensions contributors
20
+ >
21
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
22
+ > of this software and associated documentation files (the "Software"), to deal
23
+ > in the Software without restriction, including without limitation the rights
24
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
25
+ > copies of the Software, and to permit persons to whom the Software is
26
+ > furnished to do so, subject to the following conditions:
27
+ >
28
+ > The above copyright notice and this permission notice shall be included in all
29
+ > copies or substantial portions of the Software.
30
+ >
31
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
32
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
33
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
34
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
35
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
36
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
37
+ > SOFTWARE.
@@ -0,0 +1,168 @@
1
+ const ANSI_SGR_PATTERN = /\x1b\[([0-9;]*)m/g;
2
+ const ANSI_SGR_WITH_COLON_PATTERN = /\x1b\[([0-9;:]*)m/g;
3
+ const STYLE_RESET_PARAMS = [39, 22, 23, 24, 25, 27, 28, 29, 59] as const;
4
+
5
+ export { ANSI_SGR_PATTERN, STYLE_RESET_PARAMS };
6
+
7
+ export function expandSgrReset(param: number): readonly number[] | undefined {
8
+ return param === 0 ? STYLE_RESET_PARAMS : undefined;
9
+ }
10
+
11
+ export function toSgrParams(rawParams: string): number[] {
12
+ if (!rawParams.trim()) {
13
+ return [0];
14
+ }
15
+
16
+ const parsed = rawParams
17
+ .split(";")
18
+ .map((token) => Number.parseInt(token, 10))
19
+ .filter((value) => Number.isFinite(value));
20
+
21
+ return parsed.length > 0 ? parsed : [];
22
+ }
23
+
24
+ export function isFiniteSgrParam(value: number | undefined): value is number {
25
+ return typeof value === "number" && Number.isFinite(value);
26
+ }
27
+
28
+ export function readSgrColorSequence(params: number[], index: number): number[] | undefined {
29
+ const param = params[index];
30
+ if (param !== 38 && param !== 48) {
31
+ return undefined;
32
+ }
33
+
34
+ const colorMode = params[index + 1];
35
+ if (colorMode === 5) {
36
+ const colorValue = params[index + 2];
37
+ return isFiniteSgrParam(colorValue) ? [param, colorMode, colorValue] : undefined;
38
+ }
39
+
40
+ if (colorMode === 2) {
41
+ const red = params[index + 2];
42
+ const green = params[index + 3];
43
+ const blue = params[index + 4];
44
+ return isFiniteSgrParam(red) && isFiniteSgrParam(green) && isFiniteSgrParam(blue)
45
+ ? [param, colorMode, red, green, blue]
46
+ : undefined;
47
+ }
48
+
49
+ return undefined;
50
+ }
51
+
52
+ export function stripBackgroundSgrParams(params: readonly number[]): number[] {
53
+ const sanitized: number[] = [];
54
+
55
+ for (let index = 0; index < params.length; index++) {
56
+ const param = params[index] ?? 0;
57
+
58
+ if (param === 0) {
59
+ sanitized.push(...expandSgrReset(param)!);
60
+ continue;
61
+ }
62
+
63
+ if (param === 49 || (param >= 40 && param <= 47) || (param >= 100 && param <= 107)) {
64
+ continue;
65
+ }
66
+
67
+ if (param === 38 || param === 48) {
68
+ const sequence = readSgrColorSequence(params as number[], index);
69
+ if (sequence) {
70
+ if (param === 38) {
71
+ sanitized.push(...sequence);
72
+ }
73
+ index += sequence.length - 1;
74
+ continue;
75
+ }
76
+ const advance = params[index + 1] === 5 ? 2 : params[index + 1] === 2 ? 4 : 0;
77
+ if (advance > 0) {
78
+ index += advance;
79
+ if (param === 38) {
80
+ sanitized.push(param);
81
+ }
82
+ continue;
83
+ }
84
+ }
85
+
86
+ sanitized.push(param);
87
+ }
88
+
89
+ return sanitized;
90
+ }
91
+
92
+ export function filterSgrSequences(text: string, filter: (params: number[]) => number[]): string {
93
+ if (!text || !text.includes("\x1b[")) {
94
+ return text;
95
+ }
96
+
97
+ return text.replace(ANSI_SGR_PATTERN, (_sequence, rawParams: string) => {
98
+ const parsed = toSgrParams(rawParams);
99
+ if (parsed.length === 0) {
100
+ return "";
101
+ }
102
+
103
+ const sanitized = filter(parsed);
104
+ if (sanitized.length === 0) {
105
+ return "";
106
+ }
107
+
108
+ return `\x1b[${sanitized.join(";")}m`;
109
+ });
110
+ }
111
+
112
+ function isValidColonForeground(segment: string): boolean {
113
+ const params = segment.split(":");
114
+ const isByte = (value: string | undefined) =>
115
+ value !== undefined && /^\d+$/.test(value) && Number(value) <= 255;
116
+ if (params[0] !== "38") return false;
117
+ if (params[1] === "5") return params.length === 3 && isByte(params[2]);
118
+ return (
119
+ params[1] === "2" &&
120
+ params.length === 6 &&
121
+ (params[2] === "" || /^\d+$/.test(params[2] ?? "")) &&
122
+ params.slice(3).every(isByte)
123
+ );
124
+ }
125
+
126
+ function sanitizeColonSgr(rawParams: string): string {
127
+ const segments = rawParams.split(";");
128
+ const sanitized: string[] = [];
129
+ for (let index = 0; index < segments.length; index++) {
130
+ const segment = segments[index] ?? "";
131
+ if (segment.includes(":")) {
132
+ const code = Number.parseInt(segment, 10);
133
+ if (code !== 48 && (code !== 38 || isValidColonForeground(segment))) {
134
+ sanitized.push(segment);
135
+ }
136
+ continue;
137
+ }
138
+ const param = Number.parseInt(segment, 10);
139
+ if (!Number.isFinite(param)) continue;
140
+ if (param === 0) {
141
+ sanitized.push(...STYLE_RESET_PARAMS.map(String));
142
+ continue;
143
+ }
144
+ if (param === 49 || (param >= 40 && param <= 47) || (param >= 100 && param <= 107)) {
145
+ continue;
146
+ }
147
+ if (param === 38 || param === 48) {
148
+ const mode = Number.parseInt(segments[index + 1] ?? "", 10);
149
+ const advance = mode === 5 ? 2 : mode === 2 ? 4 : 0;
150
+ if (advance > 0) {
151
+ if (param === 38) sanitized.push(...segments.slice(index, index + advance + 1));
152
+ index += advance;
153
+ continue;
154
+ }
155
+ }
156
+ sanitized.push(segment);
157
+ }
158
+ return sanitized.length > 0 ? `\x1b[${sanitized.join(";")}m` : "";
159
+ }
160
+
161
+ export function sanitizeAnsiForThemedOutput(text: string): string {
162
+ if (!text || !text.includes("\x1b[")) return text;
163
+ return text.replace(ANSI_SGR_WITH_COLON_PATTERN, (sequence, rawParams: string) =>
164
+ rawParams.includes(":")
165
+ ? sanitizeColonSgr(rawParams)
166
+ : filterSgrSequences(sequence, stripBackgroundSgrParams),
167
+ );
168
+ }