pi-subagents 0.47.1 → 0.49.0

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 (66) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +2 -0
  3. package/docs/agents.md +1 -0
  4. package/docs/configuration.md +74 -7
  5. package/docs/missions.md +4 -2
  6. package/docs/observability.md +27 -3
  7. package/docs/tool-reference.md +1 -1
  8. package/package.json +1 -1
  9. package/src/agents/agents.ts +36 -15
  10. package/src/api/preflight.ts +1 -1
  11. package/src/extension/config.ts +26 -0
  12. package/src/extension/doctor.ts +40 -0
  13. package/src/extension/index.ts +39 -8
  14. package/src/extension/public-execution.ts +35 -4
  15. package/src/extension/rpc.ts +3 -9
  16. package/src/extension/schemas.ts +10 -9
  17. package/src/extension/tool-description.ts +10 -8
  18. package/src/inspectors/herdr/actions.ts +11 -2
  19. package/src/inspectors/herdr/inspector-runner.ts +16 -3
  20. package/src/intercom/intercom-bridge.ts +7 -2
  21. package/src/missions/lifecycle.ts +4 -7
  22. package/src/missions/store.ts +12 -7
  23. package/src/missions/workflow-state.ts +6 -2
  24. package/src/runs/background/active-async-capacity.ts +431 -0
  25. package/src/runs/background/active-run-index.ts +9 -5
  26. package/src/runs/background/async-execution.ts +141 -65
  27. package/src/runs/background/async-job-tracker.ts +4 -0
  28. package/src/runs/background/async-resume.ts +18 -2
  29. package/src/runs/background/async-status.ts +11 -5
  30. package/src/runs/background/chain-append.ts +33 -15
  31. package/src/runs/background/fleet-view.ts +18 -5
  32. package/src/runs/background/owned-process-tree.ts +104 -0
  33. package/src/runs/background/process-terminal.ts +17 -3
  34. package/src/runs/background/resume-guidance.ts +27 -7
  35. package/src/runs/background/retained-children.ts +14 -6
  36. package/src/runs/background/run-status.ts +101 -5
  37. package/src/runs/background/stale-run-reconciler.ts +3 -3
  38. package/src/runs/background/subagent-runner.ts +60 -32
  39. package/src/runs/foreground/chain-execution.ts +37 -2
  40. package/src/runs/foreground/execution.ts +90 -19
  41. package/src/runs/foreground/foreground-control.ts +12 -0
  42. package/src/runs/foreground/prompt-audit.ts +172 -0
  43. package/src/runs/foreground/subagent-executor.ts +829 -203
  44. package/src/runs/shared/acceptance.ts +13 -4
  45. package/src/runs/shared/completion-guard.ts +17 -1
  46. package/src/runs/shared/llm-intent-arbiter.ts +302 -0
  47. package/src/runs/shared/parallel-utils.ts +2 -0
  48. package/src/runs/shared/pi-args.ts +44 -1
  49. package/src/runs/shared/run-fanout-budget.ts +280 -0
  50. package/src/runs/shared/single-output.ts +4 -2
  51. package/src/runs/shared/task-intent.ts +19 -3
  52. package/src/runs/shared/worktree.ts +17 -5
  53. package/src/shared/agent-stream-options.ts +5 -0
  54. package/src/shared/artifacts.ts +2 -6
  55. package/src/shared/display-text.ts +50 -0
  56. package/src/shared/node-executable.ts +21 -0
  57. package/src/shared/types.ts +115 -4
  58. package/src/shared/utils.ts +3 -1
  59. package/src/tui/fleet-status.ts +7 -5
  60. package/src/tui/fleet-transcript.ts +1 -48
  61. package/src/tui/fleet.ts +228 -13
  62. package/src/tui/render.ts +86 -39
  63. package/src/watchdog/permission-arbiter.ts +2 -1
  64. package/src/watchdog/review.ts +4 -3
  65. package/src/workflows/chat-progress.ts +8 -2
  66. package/src/workflows/scripted-workflow.ts +66 -10
package/src/tui/fleet.ts CHANGED
@@ -17,12 +17,14 @@ import { contextModeBadge, contextModeLabel } from "../runs/shared/context-mode.
17
17
  import { FLEET_STATUS_WIDGET_KEY } from "./fleet-status.ts";
18
18
  import { readFleetTranscript, renderFleetTranscript, type FleetTranscript } from "./fleet-transcript.ts";
19
19
  import { handleHerdrInspectorAction } from "../inspectors/herdr/actions.ts";
20
+ import { getLivePromptAudit, type LivePromptAudit, type PromptAuditView } from "../runs/foreground/prompt-audit.ts";
20
21
 
21
22
  const REFRESH_MS = 750;
22
23
  const MAX_RECENT_ASYNC_RUNS = 20;
23
24
  const MAX_FLEET_HISTORY_CANDIDATES = 100;
24
25
  const TRANSCRIPT_LINES = 200;
25
26
  const OUTPUT_TAIL_BYTES = 64 * 1024;
27
+ const PROMPT_AUDIT_SUMMARY_WIDTH = 160;
26
28
 
27
29
  export const DEFAULT_FLEET_KEYBINDINGS: Record<FleetKeybindingAction, string[]> = {
28
30
  close: ["escape", "ctrl+c", "q"],
@@ -91,6 +93,7 @@ export interface FleetActionHandlers {
91
93
  steer(input: { runId: string; asyncDir: string; index?: number; message: string; mode: SteerDeliveryMode }): Promise<FleetActionResult>;
92
94
  stop(input: { runId: string; asyncDir: string; index?: number }): Promise<FleetActionResult> | FleetActionResult;
93
95
  inspect?(input: { runId: string; asyncDir: string; index?: number }): Promise<FleetActionResult>;
96
+ redoPrompt?(input: { runId: string; index: number; guidance: string; control?: ForegroundRunControl }): Promise<FleetActionResult>;
94
97
  }
95
98
 
96
99
  export interface FleetViewOptions {
@@ -101,6 +104,7 @@ export interface FleetViewOptions {
101
104
  markdownTheme?: MarkdownTheme;
102
105
  fleetKeybindings?: FleetKeybindingsConfig;
103
106
  actions?: FleetActionHandlers;
107
+ copyText?: (text: string) => Promise<void> | void;
104
108
  }
105
109
 
106
110
  function belongsToCurrentSession(sessionId: string | undefined, currentSessionId: string | null): boolean {
@@ -280,23 +284,62 @@ function statusGlyph(item: FleetItem, theme: Theme): string {
280
284
  return theme.fg("error", "✗");
281
285
  }
282
286
 
283
- function foregroundActiveDetail(item: Extract<FleetItem, { kind: "foreground-active" }>): string[] {
287
+ function foregroundPromptAuditCount(item: Extract<FleetItem, { kind: "foreground-active" }>, state: SubagentState): number {
288
+ if (!state.currentSessionId || item.control.sessionId !== state.currentSessionId) return 0;
289
+ if (!item.control.activeChildren) return getLivePromptAudit(item.control, item.index ?? 0) ? 1 : 0;
290
+ return [...item.control.activeChildren.keys()].filter((index) => getLivePromptAudit(item.control, index)).length;
291
+ }
292
+
293
+ function authoredPromptSummary(text: string): string | undefined {
294
+ const summary = text.replace(/\s+/g, " ").trim();
295
+ return summary ? truncateToWidth(summary, PROMPT_AUDIT_SUMMARY_WIDTH, "…") : undefined;
296
+ }
297
+
298
+ function foregroundAuthoredPromptSummary(item: Extract<FleetItem, { kind: "foreground-active" }>, state: SubagentState): string | undefined {
299
+ if (!state.currentSessionId || item.control.sessionId !== state.currentSessionId) return undefined;
300
+ const prompt = getLivePromptAudit(item.control, item.index ?? 0);
301
+ return prompt ? authoredPromptSummary(prompt.authoredTask) : undefined;
302
+ }
303
+
304
+ function promptAuditText(prompt: LivePromptAudit, view: PromptAuditView): string {
305
+ switch (view) {
306
+ case "authored": return prompt.authoredTask;
307
+ case "runtime": return prompt.runtimeAdditions;
308
+ case "effective": return prompt.finalEffectivePrompt;
309
+ }
310
+ }
311
+
312
+ function promptAuditViewLabel(view: PromptAuditView): string {
313
+ switch (view) {
314
+ case "authored": return "[1] Authored task";
315
+ case "runtime": return "[2] Runtime additions";
316
+ case "effective": return "[3] Final effective prompt";
317
+ }
318
+ }
319
+
320
+ function foregroundActiveDetail(item: Extract<FleetItem, { kind: "foreground-active" }>, state: SubagentState): string[] {
284
321
  const { control } = item;
285
322
  const live = item.activeChild ?? control;
286
323
  const modelThinking = formatModelThinking(live.model, live.thinking);
324
+ const promptAuditCount = foregroundPromptAuditCount(item, state);
325
+ const promptSummary = foregroundAuthoredPromptSummary(item, state);
287
326
  const lines = [
288
327
  `Run: ${item.runId}`,
289
328
  "Source: foreground",
290
329
  `State: running`,
291
330
  `Mode: ${control.mode}`,
292
331
  control.parentWorkflowRunId ? `Workflow child of: ${control.parentWorkflowRunId}${control.workflowKey ? ` (${control.workflowKey})` : ""}` : undefined,
332
+ control.sourceRunId ? `Redo source: ${control.sourceRunId}` : undefined,
333
+ control.supersededByRunId ? `Superseded by: ${control.supersededByRunId}` : undefined,
293
334
  item.index !== undefined ? `Child: ${item.index} (${item.agent})` : `Agent: ${item.agent}`,
294
335
  modelThinking ? `Model: ${modelThinking}` : undefined,
336
+ promptSummary ? `Task: ${promptSummary}` : undefined,
295
337
  `Started: ${new Date(live.startedAt).toISOString()}`,
296
338
  live.currentTool ? `Current tool: ${live.currentTool}${live.currentPath ? ` · ${shortenPath(live.currentPath)}` : ""}` : undefined,
297
339
  live.turnCount !== undefined ? `Turns: ${live.turnCount}` : undefined,
298
340
  live.toolCount !== undefined ? `Tools: ${live.toolCount}` : undefined,
299
341
  live.tokens !== undefined ? `Tokens: ${formatTokens(live.tokens)}` : undefined,
342
+ promptAuditCount > 0 ? `Prompt audit: ${promptAuditCount} live · 3 views · p opens` : undefined,
300
343
  "",
301
344
  "Transcript",
302
345
  "Live foreground output remains in the expanded subagent tool result. Persisted output and session paths appear here after the child settles.",
@@ -404,7 +447,7 @@ function asyncDetail(item: Extract<FleetItem, { kind: "async" }>): string[] {
404
447
  function detailLines(item: FleetItem | undefined, error: string | undefined, state: SubagentState): string[] {
405
448
  if (!item) return [error ? `Fleet scan failed: ${error}` : "No current-session foreground or recent async children.", "", "New runs appear here automatically while this inspector remains open."];
406
449
  const lines = item.kind === "foreground-active"
407
- ? foregroundActiveDetail(item)
450
+ ? foregroundActiveDetail(item, state)
408
451
  : item.kind === "foreground-recent"
409
452
  ? foregroundRecentDetail(item, state)
410
453
  : asyncDetail(item);
@@ -430,7 +473,7 @@ function fleetArtifactsRoot(state: SubagentState, cwd: string): string {
430
473
  return getArtifactsDir(
431
474
  state.parentSessionFile ?? null,
432
475
  cwd,
433
- state.artifactDirPreference ?? "project",
476
+ state.artifactDirPreference,
434
477
  );
435
478
  }
436
479
 
@@ -467,6 +510,7 @@ function transcriptTarget(item: FleetItem, state: SubagentState): { path: string
467
510
  item.run.asyncDir,
468
511
  fleetArtifactsRoot(state, state.baseCwd),
469
512
  trackedJob?.cwd ? fleetArtifactsRoot(state, trackedJob.cwd) : undefined,
513
+ item.run.sessionFile ? getArtifactsDir(item.run.sessionFile, item.run.cwd ?? state.baseCwd, state.artifactDirPreference) : undefined,
470
514
  ]),
471
515
  };
472
516
  }
@@ -517,7 +561,7 @@ function itemStats(item: FleetItem): string[] {
517
561
  ].filter((value): value is string => Boolean(value));
518
562
  }
519
563
 
520
- function structuredHeader(item: FleetItem, width: number, theme: Theme, conversationState: string): string[] {
564
+ function structuredHeader(item: FleetItem, width: number, theme: Theme, conversationState: string, promptSummary?: string): string[] {
521
565
  const lines: string[] = [];
522
566
  lines.push(rightAligned(` ${statusGlyph(item, theme)} ${theme.bold(item.agent)}`, theme.fg("dim", item.state), width));
523
567
  const child = item.index !== undefined ? ` · child ${item.index + 1}` : "";
@@ -526,10 +570,7 @@ function structuredHeader(item: FleetItem, width: number, theme: Theme, conversa
526
570
  lines.push(` ${theme.fg("dim", identity)}`);
527
571
  const stats = itemStats(item);
528
572
  if (stats.length) lines.push(` ${theme.fg("muted", stats.join(" · "))}`);
529
- if (item.description) {
530
- const task = item.description.replace(/\s+/g, " ").trim();
531
- lines.push(` ${theme.fg("dim", "Task")} ${task}`);
532
- }
573
+ if (promptSummary) lines.push(` ${theme.fg("muted", `Task: ${promptSummary}`)}`);
533
574
  lines.push(`${theme.fg("accent", "Conversation")} ${theme.fg("dim", `· ${conversationState}`)}`);
534
575
  return lines.map((line) => truncateToWidth(line, width));
535
576
  }
@@ -578,8 +619,11 @@ export class SubagentFleetComponent implements Component {
578
619
  private detailViewportHeight = 8;
579
620
  private bodyHeight = 8;
580
621
  private expandedTools = false;
622
+ private promptAuditOpen = false;
623
+ private promptAuditView: PromptAuditView = "authored";
581
624
  private actionNotice: FleetActionResult | undefined;
582
625
  private steerDraft: string | undefined;
626
+ private redoGuidanceDraft: string | undefined;
583
627
  private steerMode: SteerDeliveryMode = "steer";
584
628
  private stopConfirming = false;
585
629
  private actionBusy = false;
@@ -635,8 +679,45 @@ export class SubagentFleetComponent implements Component {
635
679
  this.tui.requestRender();
636
680
  }
637
681
 
682
+ private selectedPromptAudit(): LivePromptAudit | undefined {
683
+ const item = this.snapshot.items[this.selected];
684
+ if (item?.kind !== "foreground-active") return undefined;
685
+ if (!this.state.currentSessionId || item.control.sessionId !== this.state.currentSessionId) return undefined;
686
+ return getLivePromptAudit(item.control, item.index ?? 0);
687
+ }
688
+
689
+ private promptAuditItems(): Array<{ item: Extract<FleetItem, { kind: "foreground-active" }>; prompt: LivePromptAudit }> {
690
+ const selected = this.snapshot.items[this.selected];
691
+ if (selected?.kind !== "foreground-active" || !this.state.currentSessionId || selected.control.sessionId !== this.state.currentSessionId) return [];
692
+ return this.snapshot.items.flatMap((item) => {
693
+ if (item.kind !== "foreground-active" || item.control !== selected.control) return [];
694
+ const prompt = getLivePromptAudit(item.control, item.index ?? 0);
695
+ return prompt ? [{ item, prompt }] : [];
696
+ });
697
+ }
698
+
699
+ private selectedPromptText(): string | undefined {
700
+ const prompt = this.selectedPromptAudit();
701
+ return prompt ? promptAuditText(prompt, this.promptAuditView) : undefined;
702
+ }
703
+
704
+ private movePromptSelection(delta: number): void {
705
+ const items = this.promptAuditItems();
706
+ if (items.length === 0) return;
707
+ const currentKey = this.snapshot.items[this.selected]?.key;
708
+ const current = Math.max(0, items.findIndex(({ item }) => item.key === currentKey));
709
+ const target = items[Math.max(0, Math.min(items.length - 1, current + delta))]?.item;
710
+ if (!target) return;
711
+ this.selected = this.snapshot.items.findIndex((item) => item.key === target.key);
712
+ this.selectedKey = target.key;
713
+ this.detailScroll = 0;
714
+ this.detailAutoFollow = false;
715
+ this.tui.requestRender();
716
+ }
717
+
638
718
  private resetActionInput(): void {
639
719
  this.steerDraft = undefined;
720
+ this.redoGuidanceDraft = undefined;
640
721
  this.steerMode = "steer";
641
722
  this.stopConfirming = false;
642
723
  }
@@ -668,6 +749,9 @@ export class SubagentFleetComponent implements Component {
668
749
  if (this.steerDraft !== undefined) {
669
750
  lines.push(this.theme.fg("accent", `Steer message (${this.steerMode}): ${this.steerDraft}${this.theme.fg("dim", "▌")}`));
670
751
  lines.push(this.theme.fg("dim", "Enter sends · Tab changes mode · Esc cancels · Backspace edits"));
752
+ } else if (this.redoGuidanceDraft !== undefined) {
753
+ lines.push(this.theme.fg("accent", `Redo guidance: ${this.redoGuidanceDraft}${this.theme.fg("dim", "▌")}`));
754
+ lines.push(this.theme.fg("dim", "Enter rewrites and reruns · Esc cancels · Backspace edits"));
671
755
  } else if (this.stopConfirming) {
672
756
  const selected = this.snapshot.items[this.selected];
673
757
  lines.push(this.theme.fg("warning", `Confirm stop for async run ${selected?.runId ?? "selected run"}?`));
@@ -714,6 +798,82 @@ export class SubagentFleetComponent implements Component {
714
798
  }
715
799
 
716
800
  handleInput(data: string): void {
801
+ if (this.promptAuditOpen && this.redoGuidanceDraft !== undefined) {
802
+ if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
803
+ this.resetActionInput();
804
+ this.tui.requestRender();
805
+ return;
806
+ }
807
+ if (matchesKey(data, "return") || data === "\r" || data === "\n") {
808
+ const guidance = this.redoGuidanceDraft.trim();
809
+ if (!guidance) {
810
+ this.setActionNotice({ text: "Redo guidance cannot be empty.", isError: true });
811
+ return;
812
+ }
813
+ const selected = this.snapshot.items[this.selected];
814
+ if (selected?.kind !== "foreground-active" || !this.options.actions?.redoPrompt) {
815
+ this.setActionNotice({ text: "Redo is unavailable for this prompt.", isError: true });
816
+ return;
817
+ }
818
+ this.runAction(() => this.options.actions!.redoPrompt!({ runId: selected.runId, index: selected.index ?? 0, guidance, control: selected.control }));
819
+ return;
820
+ }
821
+ if (matchesKey(data, "backspace") || data === "\x7f") {
822
+ this.redoGuidanceDraft = this.redoGuidanceDraft.slice(0, -1);
823
+ this.tui.requestRender();
824
+ return;
825
+ }
826
+ if (data.length === 1 && data >= " " && data !== "\x7f") {
827
+ this.redoGuidanceDraft += data;
828
+ this.tui.requestRender();
829
+ }
830
+ return;
831
+ }
832
+ if (this.promptAuditOpen) {
833
+ if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
834
+ this.promptAuditOpen = false;
835
+ this.detailAutoFollow = true;
836
+ this.tui.requestRender();
837
+ return;
838
+ }
839
+ if (data === "j") return this.movePromptSelection(1);
840
+ if (data === "k") return this.movePromptSelection(-1);
841
+ if (data === "1" || data === "2" || data === "3") {
842
+ this.promptAuditView = data === "1" ? "authored" : data === "2" ? "runtime" : "effective";
843
+ this.detailScroll = 0;
844
+ this.tui.requestRender();
845
+ return;
846
+ }
847
+ if (data === "g") {
848
+ const prompt = this.selectedPromptAudit();
849
+ if (!prompt) this.setActionNotice({ text: "Prompt Audit is available only for a live child owned by this session.", isError: true });
850
+ else if (!prompt.rerun) this.setActionNotice({ text: "Redo is not safe for this prompt in this slice.", isError: true });
851
+ else if (!this.options.actions?.redoPrompt) this.setActionNotice({ text: "Redo controls are unavailable in this context.", isError: true });
852
+ else {
853
+ this.actionNotice = undefined;
854
+ this.redoGuidanceDraft = "";
855
+ this.detailAutoFollow = false;
856
+ this.detailScroll = 0;
857
+ this.tui.requestRender();
858
+ }
859
+ return;
860
+ }
861
+ if (data === "c") {
862
+ const text = this.selectedPromptText();
863
+ if (!text) this.setActionNotice({ text: "No prompt view is available to copy.", isError: true });
864
+ else if (!this.options.copyText) this.setActionNotice({ text: "Clipboard is unavailable in this context.", isError: true });
865
+ else this.runAction(async () => {
866
+ await this.options.copyText!(text);
867
+ return { text: "Copied visible prompt view." };
868
+ });
869
+ return;
870
+ }
871
+ if (matchesFleetAction(data, this.keybindings, "scrollUp")) return this.scrollDetail(-1);
872
+ if (matchesFleetAction(data, this.keybindings, "scrollDown")) return this.scrollDetail(1);
873
+ if (matchesFleetAction(data, this.keybindings, "pageUp")) return this.scrollDetail(-this.detailViewportHeight);
874
+ if (matchesFleetAction(data, this.keybindings, "pageDown")) return this.scrollDetail(this.detailViewportHeight);
875
+ return;
876
+ }
717
877
  if (this.steerDraft !== undefined) {
718
878
  if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
719
879
  this.resetActionInput();
@@ -785,6 +945,18 @@ export class SubagentFleetComponent implements Component {
785
945
  this.tui.requestRender();
786
946
  return;
787
947
  }
948
+ if (data === "p") {
949
+ if (!this.selectedPromptAudit()) this.setActionNotice({ text: "Prompt Audit is available only for a live child owned by this session.", isError: true });
950
+ else {
951
+ this.promptAuditOpen = true;
952
+ this.promptAuditView = "authored";
953
+ this.detailScroll = 0;
954
+ this.detailAutoFollow = false;
955
+ this.actionNotice = undefined;
956
+ this.tui.requestRender();
957
+ }
958
+ return;
959
+ }
788
960
  if (matchesFleetAction(data, this.keybindings, "steer")) {
789
961
  const target = this.selectedAsyncAction();
790
962
  if ("reason" in target || !this.options.actions) this.setActionNotice({ text: "reason" in target ? target.reason : "Fleet controls are unavailable in this context.", isError: true });
@@ -830,7 +1002,7 @@ export class SubagentFleetComponent implements Component {
830
1002
  const marker = index === this.selected ? this.theme.fg("accent", "›") : " ";
831
1003
  const context = item.kind === "async" ? contextModeBadge(this.theme, item.step?.context ?? item.run.context) : item.kind === "foreground-recent" ? contextModeBadge(this.theme, item.child.context) : "";
832
1004
  const agent = index === this.selected ? this.theme.bold(item.agent) : item.agent;
833
- const identity = item.description?.replace(/\s+/g, " ").trim() || item.runId.slice(0, 8);
1005
+ const identity = item.runId.slice(0, 8);
834
1006
  const left = `${marker} ${statusGlyph(item, this.theme)} ${agent}${context} ${this.theme.fg("dim", `· ${identity}`)}`;
835
1007
  return rightAligned(left, this.theme.fg("dim", item.state), width);
836
1008
  });
@@ -853,7 +1025,35 @@ export class SubagentFleetComponent implements Component {
853
1025
  return { transcript, body: [...body] };
854
1026
  }
855
1027
 
1028
+ private promptAuditDetail(width: number): FleetDetailSections {
1029
+ const selected = this.snapshot.items[this.selected];
1030
+ const prompt = this.selectedPromptAudit();
1031
+ const items = this.promptAuditItems();
1032
+ const selectedPosition = selected ? items.findIndex(({ item }) => item.key === selected.key) : -1;
1033
+ const live = selected?.kind === "foreground-active" ? selected.activeChild ?? selected.control : undefined;
1034
+ const viewLabel = promptAuditViewLabel(this.promptAuditView);
1035
+ const promptText = this.selectedPromptText()
1036
+ ?? this.theme.fg("muted", "Selected prompt unavailable.");
1037
+ const raw = [
1038
+ this.theme.bold("Prompt Audit"),
1039
+ this.theme.fg("dim", "Retention: live memory only · no storage"),
1040
+ selected?.kind === "foreground-active" ? `Run: ${selected.runId}${selected.index !== undefined ? ` · Child: ${selected.index}` : ""} · Agent: ${selected.agent}` : "Selected prompt unavailable",
1041
+ live ? `Started: ${new Date(live.startedAt).toISOString()}` : undefined,
1042
+ live ? `Model: ${formatModelThinking(live.model, live.thinking) || "default"}` : undefined,
1043
+ prompt?.cwd ? `Cwd: ${prompt.cwd}` : undefined,
1044
+ prompt?.outputPath ? `Output: ${prompt.outputPath}` : undefined,
1045
+ this.theme.fg("dim", `Live children: ${items.map(({ item }) => item.agent).join(", ") || "none"}`),
1046
+ this.theme.fg("dim", selectedPosition >= 0 ? `Selected: ${selectedPosition + 1}/${items.length}` : "Selected prompt unavailable"),
1047
+ "",
1048
+ this.theme.fg("accent", viewLabel),
1049
+ promptText,
1050
+ ];
1051
+ const body = raw.filter((line): line is string => line !== undefined).flatMap((line) => wrapTextWithAnsi(line, Math.max(1, width)));
1052
+ return { header: [], body: this.withActionLines(body) };
1053
+ }
1054
+
856
1055
  private wrappedDetail(width: number): FleetDetailSections {
1056
+ if (this.promptAuditOpen) return this.promptAuditDetail(width);
857
1057
  const selected = this.snapshot.items[this.selected];
858
1058
  let transcriptWarning: string | undefined;
859
1059
  if (selected) {
@@ -871,7 +1071,8 @@ export class SubagentFleetComponent implements Component {
871
1071
  : latest?.kind === "tool"
872
1072
  ? `${latest.name} · ${latest.status}`
873
1073
  : "activity";
874
- return { header: structuredHeader(selected, width, this.theme, conversationState), body: this.withActionLines(body) };
1074
+ const promptSummary = selected.kind === "foreground-active" ? foregroundAuthoredPromptSummary(selected, this.state) : undefined;
1075
+ return { header: structuredHeader(selected, width, this.theme, conversationState, promptSummary), body: this.withActionLines(body) };
875
1076
  }
876
1077
  }
877
1078
  }
@@ -880,7 +1081,7 @@ export class SubagentFleetComponent implements Component {
880
1081
  if (transcriptWarning) raw.unshift(`Transcript preview warning: ${transcriptWarning}`, "");
881
1082
  const lines: string[] = [];
882
1083
  for (const line of raw) {
883
- const styled = /^(Run|State|Mode|Source|Child|Agent|Model):/.test(line)
1084
+ const styled = /^(Run|State|Mode|Source|Child|Agent|Model|Task):/.test(line)
884
1085
  ? this.theme.bold(line)
885
1086
  : /^(Transcript|Result transcript tail)/.test(line)
886
1087
  ? this.theme.fg("accent", line)
@@ -933,7 +1134,9 @@ export class SubagentFleetComponent implements Component {
933
1134
  }
934
1135
  lines.push(this.theme.fg("border", `├${"─".repeat(rosterWidth)}┴${"─".repeat(detailWidth)}┤`));
935
1136
  const position = this.snapshot.items.length ? `${this.selected + 1}/${this.snapshot.items.length}` : "0/0";
936
- const footer = ` ${bindingLabel(this.keybindings, "selectUp")}/${bindingLabel(this.keybindings, "selectDown")} agent · ${bindingLabel(this.keybindings, "inspect")} Herdr · ${bindingLabel(this.keybindings, "steer")} steer · ${bindingLabel(this.keybindings, "stop")} stop · ${bindingLabel(this.keybindings, "toggleTools")} tools · ${bindingLabel(this.keybindings, "refresh")} refresh · ${bindingLabel(this.keybindings, "close")} close · ${position}`;
1137
+ const footer = this.promptAuditOpen
1138
+ ? ` j/k child · 1/2/3 view · g redo with guidance · c copy · Esc close Prompt Audit · ${position}`
1139
+ : ` ${bindingLabel(this.keybindings, "selectUp")}/${bindingLabel(this.keybindings, "selectDown")} agent · p Prompt Audit · ${bindingLabel(this.keybindings, "inspect")} Herdr · ${bindingLabel(this.keybindings, "steer")} steer · ${bindingLabel(this.keybindings, "stop")} stop · ${bindingLabel(this.keybindings, "toggleTools")} tools · ${bindingLabel(this.keybindings, "refresh")} refresh · ${bindingLabel(this.keybindings, "close")} close · ${position}`;
937
1140
  lines.push(this.theme.fg("border", "│") + fit(this.theme.fg("dim", footer), innerWidth) + this.theme.fg("border", "│"));
938
1141
  lines.push(this.theme.fg("border", `╰${"─".repeat(innerWidth)}╯`));
939
1142
  return lines.map((line) => truncateToWidth(line, width));
@@ -954,6 +1157,12 @@ export async function openSubagentFleet(ctx: ExtensionContext, state: SubagentSt
954
1157
  const wasOpen = state.fleetInspectorOpen === true;
955
1158
  state.fleetInspectorOpen = true;
956
1159
  if (typeof ctx.ui.setWidget === "function") ctx.ui.setWidget(FLEET_STATUS_WIDGET_KEY, undefined);
1160
+ const copyText = options.copyText ?? (async (text: string) => {
1161
+ const module = await import("@earendil-works/pi-coding-agent");
1162
+ const copyToClipboard = (module as { copyToClipboard?: (value: string) => Promise<void> | void }).copyToClipboard;
1163
+ if (!copyToClipboard) throw new Error("Clipboard is unavailable in this Pi version.");
1164
+ await copyToClipboard(text);
1165
+ });
957
1166
  const actions = options.actions ?? {
958
1167
  steer: async (input: { runId: string; asyncDir: string; index?: number; message: string; mode: SteerDeliveryMode }) => firstToolResultText(await steerAsyncRun({
959
1168
  state,
@@ -970,14 +1179,20 @@ export async function openSubagentFleet(ctx: ExtensionContext, state: SubagentSt
970
1179
  ...(input.index !== undefined ? { index: input.index } : {}),
971
1180
  }, {
972
1181
  state,
1182
+ sessionRoots: state.trustedSessionRoots,
973
1183
  cwd: state.baseCwd,
974
1184
  ...(state.authorityPolicy ? { authorityPolicy: state.authorityPolicy } : {}),
975
1185
  ...(state.missionStoreConfig ? { missions: state.missionStoreConfig } : {}),
976
1186
  }), `Failed to open Herdr inspector for async run ${input.runId}.`),
1187
+ redoPrompt: async (input: { runId: string; index: number; guidance: string; control?: ForegroundRunControl }) => {
1188
+ const control = input.control ?? state.foregroundControls.get(input.runId);
1189
+ if (!control?.promptAuditRedo) return { text: "Redo is not available for this live child.", isError: true };
1190
+ return control.promptAuditRedo(input.index, input.guidance);
1191
+ },
977
1192
  } satisfies FleetActionHandlers;
978
1193
  try {
979
1194
  await ctx.ui.custom<undefined>(
980
- (tui, theme, _keybindings, done) => new SubagentFleetComponent(tui, theme, state, done, { ...options, actions }),
1195
+ (tui, theme, _keybindings, done) => new SubagentFleetComponent(tui, theme, state, done, { ...options, actions, copyText }),
981
1196
  {
982
1197
  overlay: true,
983
1198
  overlayOptions: { anchor: "center", width: "95%", minWidth: 60, maxHeight: "85%", margin: 1 },