pi-subagents 0.47.0 → 0.48.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.
- package/CHANGELOG.md +37 -0
- package/README.md +2 -0
- package/agents/reviewer.md +3 -4
- package/docs/configuration.md +46 -2
- package/docs/observability.md +1 -1
- package/docs/tool-reference.md +1 -1
- package/package.json +1 -1
- package/src/agents/agents.ts +9 -3
- package/src/extension/config.ts +16 -0
- package/src/extension/doctor.ts +40 -0
- package/src/extension/index.ts +30 -11
- package/src/extension/public-execution.ts +5 -0
- package/src/extension/rpc.ts +7 -9
- package/src/extension/schemas.ts +4 -4
- package/src/intercom/intercom-bridge.ts +4 -1
- package/src/intercom/native-supervisor-channel.ts +102 -4
- package/src/missions/lifecycle.ts +4 -7
- package/src/missions/store.ts +4 -4
- package/src/missions/workflow-state.ts +6 -2
- package/src/runs/background/active-async-capacity.ts +374 -0
- package/src/runs/background/active-run-index.ts +46 -0
- package/src/runs/background/async-execution.ts +117 -29
- package/src/runs/background/async-job-tracker.ts +279 -134
- package/src/runs/background/async-resume.ts +7 -1
- package/src/runs/background/async-status.ts +33 -4
- package/src/runs/background/chain-append.ts +33 -15
- package/src/runs/background/control-channel.ts +55 -17
- package/src/runs/background/owned-process-tree.ts +104 -0
- package/src/runs/background/process-terminal.ts +17 -3
- package/src/runs/background/result-watcher.ts +87 -6
- package/src/runs/background/run-status.ts +23 -1
- package/src/runs/background/scheduled-runs.ts +23 -0
- package/src/runs/background/stale-run-reconciler.ts +10 -3
- package/src/runs/background/subagent-runner.ts +75 -33
- package/src/runs/foreground/async-dismiss-action.ts +85 -0
- package/src/runs/foreground/async-steering-action.ts +6 -7
- package/src/runs/foreground/chain-execution.ts +37 -2
- package/src/runs/foreground/execution.ts +90 -19
- package/src/runs/foreground/foreground-control.ts +12 -0
- package/src/runs/foreground/prompt-audit.ts +171 -0
- package/src/runs/foreground/subagent-executor.ts +648 -195
- package/src/runs/shared/acceptance.ts +13 -4
- package/src/runs/shared/llm-intent-arbiter.ts +286 -0
- package/src/runs/shared/parallel-utils.ts +2 -0
- package/src/runs/shared/pi-args.ts +44 -1
- package/src/runs/shared/run-fanout-budget.ts +280 -0
- package/src/runs/shared/single-output.ts +4 -2
- package/src/runs/shared/subagent-prompt-runtime.ts +24 -5
- package/src/runs/shared/task-intent.ts +19 -3
- package/src/runs/shared/worktree.ts +17 -5
- package/src/shared/artifacts.ts +1 -1
- package/src/shared/file-coalescer.ts +9 -0
- package/src/shared/types.ts +97 -1
- package/src/shared/utils.ts +3 -1
- package/src/tui/fleet-status.ts +7 -5
- package/src/tui/fleet.ts +225 -12
- package/src/workflows/scripted-workflow.ts +26 -6
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
|
|
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);
|
|
@@ -517,7 +560,7 @@ function itemStats(item: FleetItem): string[] {
|
|
|
517
560
|
].filter((value): value is string => Boolean(value));
|
|
518
561
|
}
|
|
519
562
|
|
|
520
|
-
function structuredHeader(item: FleetItem, width: number, theme: Theme, conversationState: string): string[] {
|
|
563
|
+
function structuredHeader(item: FleetItem, width: number, theme: Theme, conversationState: string, promptSummary?: string): string[] {
|
|
521
564
|
const lines: string[] = [];
|
|
522
565
|
lines.push(rightAligned(` ${statusGlyph(item, theme)} ${theme.bold(item.agent)}`, theme.fg("dim", item.state), width));
|
|
523
566
|
const child = item.index !== undefined ? ` · child ${item.index + 1}` : "";
|
|
@@ -526,10 +569,7 @@ function structuredHeader(item: FleetItem, width: number, theme: Theme, conversa
|
|
|
526
569
|
lines.push(` ${theme.fg("dim", identity)}`);
|
|
527
570
|
const stats = itemStats(item);
|
|
528
571
|
if (stats.length) lines.push(` ${theme.fg("muted", stats.join(" · "))}`);
|
|
529
|
-
if (
|
|
530
|
-
const task = item.description.replace(/\s+/g, " ").trim();
|
|
531
|
-
lines.push(` ${theme.fg("dim", "Task")} ${task}`);
|
|
532
|
-
}
|
|
572
|
+
if (promptSummary) lines.push(` ${theme.fg("muted", `Task: ${promptSummary}`)}`);
|
|
533
573
|
lines.push(`${theme.fg("accent", "Conversation")} ${theme.fg("dim", `· ${conversationState}`)}`);
|
|
534
574
|
return lines.map((line) => truncateToWidth(line, width));
|
|
535
575
|
}
|
|
@@ -578,8 +618,11 @@ export class SubagentFleetComponent implements Component {
|
|
|
578
618
|
private detailViewportHeight = 8;
|
|
579
619
|
private bodyHeight = 8;
|
|
580
620
|
private expandedTools = false;
|
|
621
|
+
private promptAuditOpen = false;
|
|
622
|
+
private promptAuditView: PromptAuditView = "authored";
|
|
581
623
|
private actionNotice: FleetActionResult | undefined;
|
|
582
624
|
private steerDraft: string | undefined;
|
|
625
|
+
private redoGuidanceDraft: string | undefined;
|
|
583
626
|
private steerMode: SteerDeliveryMode = "steer";
|
|
584
627
|
private stopConfirming = false;
|
|
585
628
|
private actionBusy = false;
|
|
@@ -635,8 +678,45 @@ export class SubagentFleetComponent implements Component {
|
|
|
635
678
|
this.tui.requestRender();
|
|
636
679
|
}
|
|
637
680
|
|
|
681
|
+
private selectedPromptAudit(): LivePromptAudit | undefined {
|
|
682
|
+
const item = this.snapshot.items[this.selected];
|
|
683
|
+
if (item?.kind !== "foreground-active") return undefined;
|
|
684
|
+
if (!this.state.currentSessionId || item.control.sessionId !== this.state.currentSessionId) return undefined;
|
|
685
|
+
return getLivePromptAudit(item.control, item.index ?? 0);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
private promptAuditItems(): Array<{ item: Extract<FleetItem, { kind: "foreground-active" }>; prompt: LivePromptAudit }> {
|
|
689
|
+
const selected = this.snapshot.items[this.selected];
|
|
690
|
+
if (selected?.kind !== "foreground-active" || !this.state.currentSessionId || selected.control.sessionId !== this.state.currentSessionId) return [];
|
|
691
|
+
return this.snapshot.items.flatMap((item) => {
|
|
692
|
+
if (item.kind !== "foreground-active" || item.control !== selected.control) return [];
|
|
693
|
+
const prompt = getLivePromptAudit(item.control, item.index ?? 0);
|
|
694
|
+
return prompt ? [{ item, prompt }] : [];
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
private selectedPromptText(): string | undefined {
|
|
699
|
+
const prompt = this.selectedPromptAudit();
|
|
700
|
+
return prompt ? promptAuditText(prompt, this.promptAuditView) : undefined;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
private movePromptSelection(delta: number): void {
|
|
704
|
+
const items = this.promptAuditItems();
|
|
705
|
+
if (items.length === 0) return;
|
|
706
|
+
const currentKey = this.snapshot.items[this.selected]?.key;
|
|
707
|
+
const current = Math.max(0, items.findIndex(({ item }) => item.key === currentKey));
|
|
708
|
+
const target = items[Math.max(0, Math.min(items.length - 1, current + delta))]?.item;
|
|
709
|
+
if (!target) return;
|
|
710
|
+
this.selected = this.snapshot.items.findIndex((item) => item.key === target.key);
|
|
711
|
+
this.selectedKey = target.key;
|
|
712
|
+
this.detailScroll = 0;
|
|
713
|
+
this.detailAutoFollow = false;
|
|
714
|
+
this.tui.requestRender();
|
|
715
|
+
}
|
|
716
|
+
|
|
638
717
|
private resetActionInput(): void {
|
|
639
718
|
this.steerDraft = undefined;
|
|
719
|
+
this.redoGuidanceDraft = undefined;
|
|
640
720
|
this.steerMode = "steer";
|
|
641
721
|
this.stopConfirming = false;
|
|
642
722
|
}
|
|
@@ -668,6 +748,9 @@ export class SubagentFleetComponent implements Component {
|
|
|
668
748
|
if (this.steerDraft !== undefined) {
|
|
669
749
|
lines.push(this.theme.fg("accent", `Steer message (${this.steerMode}): ${this.steerDraft}${this.theme.fg("dim", "▌")}`));
|
|
670
750
|
lines.push(this.theme.fg("dim", "Enter sends · Tab changes mode · Esc cancels · Backspace edits"));
|
|
751
|
+
} else if (this.redoGuidanceDraft !== undefined) {
|
|
752
|
+
lines.push(this.theme.fg("accent", `Redo guidance: ${this.redoGuidanceDraft}${this.theme.fg("dim", "▌")}`));
|
|
753
|
+
lines.push(this.theme.fg("dim", "Enter rewrites and reruns · Esc cancels · Backspace edits"));
|
|
671
754
|
} else if (this.stopConfirming) {
|
|
672
755
|
const selected = this.snapshot.items[this.selected];
|
|
673
756
|
lines.push(this.theme.fg("warning", `Confirm stop for async run ${selected?.runId ?? "selected run"}?`));
|
|
@@ -714,6 +797,82 @@ export class SubagentFleetComponent implements Component {
|
|
|
714
797
|
}
|
|
715
798
|
|
|
716
799
|
handleInput(data: string): void {
|
|
800
|
+
if (this.promptAuditOpen && this.redoGuidanceDraft !== undefined) {
|
|
801
|
+
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
|
|
802
|
+
this.resetActionInput();
|
|
803
|
+
this.tui.requestRender();
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
if (matchesKey(data, "return") || data === "\r" || data === "\n") {
|
|
807
|
+
const guidance = this.redoGuidanceDraft.trim();
|
|
808
|
+
if (!guidance) {
|
|
809
|
+
this.setActionNotice({ text: "Redo guidance cannot be empty.", isError: true });
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
const selected = this.snapshot.items[this.selected];
|
|
813
|
+
if (selected?.kind !== "foreground-active" || !this.options.actions?.redoPrompt) {
|
|
814
|
+
this.setActionNotice({ text: "Redo is unavailable for this prompt.", isError: true });
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
this.runAction(() => this.options.actions!.redoPrompt!({ runId: selected.runId, index: selected.index ?? 0, guidance, control: selected.control }));
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
if (matchesKey(data, "backspace") || data === "\x7f") {
|
|
821
|
+
this.redoGuidanceDraft = this.redoGuidanceDraft.slice(0, -1);
|
|
822
|
+
this.tui.requestRender();
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
if (data.length === 1 && data >= " " && data !== "\x7f") {
|
|
826
|
+
this.redoGuidanceDraft += data;
|
|
827
|
+
this.tui.requestRender();
|
|
828
|
+
}
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
if (this.promptAuditOpen) {
|
|
832
|
+
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
|
|
833
|
+
this.promptAuditOpen = false;
|
|
834
|
+
this.detailAutoFollow = true;
|
|
835
|
+
this.tui.requestRender();
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
if (data === "j") return this.movePromptSelection(1);
|
|
839
|
+
if (data === "k") return this.movePromptSelection(-1);
|
|
840
|
+
if (data === "1" || data === "2" || data === "3") {
|
|
841
|
+
this.promptAuditView = data === "1" ? "authored" : data === "2" ? "runtime" : "effective";
|
|
842
|
+
this.detailScroll = 0;
|
|
843
|
+
this.tui.requestRender();
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
if (data === "g") {
|
|
847
|
+
const prompt = this.selectedPromptAudit();
|
|
848
|
+
if (!prompt) this.setActionNotice({ text: "Prompt Audit is available only for a live child owned by this session.", isError: true });
|
|
849
|
+
else if (!prompt.rerun) this.setActionNotice({ text: "Redo is not safe for this prompt in this slice.", isError: true });
|
|
850
|
+
else if (!this.options.actions?.redoPrompt) this.setActionNotice({ text: "Redo controls are unavailable in this context.", isError: true });
|
|
851
|
+
else {
|
|
852
|
+
this.actionNotice = undefined;
|
|
853
|
+
this.redoGuidanceDraft = "";
|
|
854
|
+
this.detailAutoFollow = false;
|
|
855
|
+
this.detailScroll = 0;
|
|
856
|
+
this.tui.requestRender();
|
|
857
|
+
}
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
if (data === "c") {
|
|
861
|
+
const text = this.selectedPromptText();
|
|
862
|
+
if (!text) this.setActionNotice({ text: "No prompt view is available to copy.", isError: true });
|
|
863
|
+
else if (!this.options.copyText) this.setActionNotice({ text: "Clipboard is unavailable in this context.", isError: true });
|
|
864
|
+
else this.runAction(async () => {
|
|
865
|
+
await this.options.copyText!(text);
|
|
866
|
+
return { text: "Copied visible prompt view." };
|
|
867
|
+
});
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
if (matchesFleetAction(data, this.keybindings, "scrollUp")) return this.scrollDetail(-1);
|
|
871
|
+
if (matchesFleetAction(data, this.keybindings, "scrollDown")) return this.scrollDetail(1);
|
|
872
|
+
if (matchesFleetAction(data, this.keybindings, "pageUp")) return this.scrollDetail(-this.detailViewportHeight);
|
|
873
|
+
if (matchesFleetAction(data, this.keybindings, "pageDown")) return this.scrollDetail(this.detailViewportHeight);
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
717
876
|
if (this.steerDraft !== undefined) {
|
|
718
877
|
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
|
|
719
878
|
this.resetActionInput();
|
|
@@ -785,6 +944,18 @@ export class SubagentFleetComponent implements Component {
|
|
|
785
944
|
this.tui.requestRender();
|
|
786
945
|
return;
|
|
787
946
|
}
|
|
947
|
+
if (data === "p") {
|
|
948
|
+
if (!this.selectedPromptAudit()) this.setActionNotice({ text: "Prompt Audit is available only for a live child owned by this session.", isError: true });
|
|
949
|
+
else {
|
|
950
|
+
this.promptAuditOpen = true;
|
|
951
|
+
this.promptAuditView = "authored";
|
|
952
|
+
this.detailScroll = 0;
|
|
953
|
+
this.detailAutoFollow = false;
|
|
954
|
+
this.actionNotice = undefined;
|
|
955
|
+
this.tui.requestRender();
|
|
956
|
+
}
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
788
959
|
if (matchesFleetAction(data, this.keybindings, "steer")) {
|
|
789
960
|
const target = this.selectedAsyncAction();
|
|
790
961
|
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 +1001,7 @@ export class SubagentFleetComponent implements Component {
|
|
|
830
1001
|
const marker = index === this.selected ? this.theme.fg("accent", "›") : " ";
|
|
831
1002
|
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
1003
|
const agent = index === this.selected ? this.theme.bold(item.agent) : item.agent;
|
|
833
|
-
const identity = item.
|
|
1004
|
+
const identity = item.runId.slice(0, 8);
|
|
834
1005
|
const left = `${marker} ${statusGlyph(item, this.theme)} ${agent}${context} ${this.theme.fg("dim", `· ${identity}`)}`;
|
|
835
1006
|
return rightAligned(left, this.theme.fg("dim", item.state), width);
|
|
836
1007
|
});
|
|
@@ -853,7 +1024,35 @@ export class SubagentFleetComponent implements Component {
|
|
|
853
1024
|
return { transcript, body: [...body] };
|
|
854
1025
|
}
|
|
855
1026
|
|
|
1027
|
+
private promptAuditDetail(width: number): FleetDetailSections {
|
|
1028
|
+
const selected = this.snapshot.items[this.selected];
|
|
1029
|
+
const prompt = this.selectedPromptAudit();
|
|
1030
|
+
const items = this.promptAuditItems();
|
|
1031
|
+
const selectedPosition = selected ? items.findIndex(({ item }) => item.key === selected.key) : -1;
|
|
1032
|
+
const live = selected?.kind === "foreground-active" ? selected.activeChild ?? selected.control : undefined;
|
|
1033
|
+
const viewLabel = promptAuditViewLabel(this.promptAuditView);
|
|
1034
|
+
const promptText = this.selectedPromptText()
|
|
1035
|
+
?? this.theme.fg("muted", "Selected prompt unavailable.");
|
|
1036
|
+
const raw = [
|
|
1037
|
+
this.theme.bold("Prompt Audit"),
|
|
1038
|
+
this.theme.fg("dim", "Retention: live memory only · no storage"),
|
|
1039
|
+
selected?.kind === "foreground-active" ? `Run: ${selected.runId}${selected.index !== undefined ? ` · Child: ${selected.index}` : ""} · Agent: ${selected.agent}` : "Selected prompt unavailable",
|
|
1040
|
+
live ? `Started: ${new Date(live.startedAt).toISOString()}` : undefined,
|
|
1041
|
+
live ? `Model: ${formatModelThinking(live.model, live.thinking) || "default"}` : undefined,
|
|
1042
|
+
prompt?.cwd ? `Cwd: ${prompt.cwd}` : undefined,
|
|
1043
|
+
prompt?.outputPath ? `Output: ${prompt.outputPath}` : undefined,
|
|
1044
|
+
this.theme.fg("dim", `Live children: ${items.map(({ item }) => item.agent).join(", ") || "none"}`),
|
|
1045
|
+
this.theme.fg("dim", selectedPosition >= 0 ? `Selected: ${selectedPosition + 1}/${items.length}` : "Selected prompt unavailable"),
|
|
1046
|
+
"",
|
|
1047
|
+
this.theme.fg("accent", viewLabel),
|
|
1048
|
+
promptText,
|
|
1049
|
+
];
|
|
1050
|
+
const body = raw.filter((line): line is string => line !== undefined).flatMap((line) => wrapTextWithAnsi(line, Math.max(1, width)));
|
|
1051
|
+
return { header: [], body: this.withActionLines(body) };
|
|
1052
|
+
}
|
|
1053
|
+
|
|
856
1054
|
private wrappedDetail(width: number): FleetDetailSections {
|
|
1055
|
+
if (this.promptAuditOpen) return this.promptAuditDetail(width);
|
|
857
1056
|
const selected = this.snapshot.items[this.selected];
|
|
858
1057
|
let transcriptWarning: string | undefined;
|
|
859
1058
|
if (selected) {
|
|
@@ -871,7 +1070,8 @@ export class SubagentFleetComponent implements Component {
|
|
|
871
1070
|
: latest?.kind === "tool"
|
|
872
1071
|
? `${latest.name} · ${latest.status}`
|
|
873
1072
|
: "activity";
|
|
874
|
-
|
|
1073
|
+
const promptSummary = selected.kind === "foreground-active" ? foregroundAuthoredPromptSummary(selected, this.state) : undefined;
|
|
1074
|
+
return { header: structuredHeader(selected, width, this.theme, conversationState, promptSummary), body: this.withActionLines(body) };
|
|
875
1075
|
}
|
|
876
1076
|
}
|
|
877
1077
|
}
|
|
@@ -880,7 +1080,7 @@ export class SubagentFleetComponent implements Component {
|
|
|
880
1080
|
if (transcriptWarning) raw.unshift(`Transcript preview warning: ${transcriptWarning}`, "");
|
|
881
1081
|
const lines: string[] = [];
|
|
882
1082
|
for (const line of raw) {
|
|
883
|
-
const styled = /^(Run|State|Mode|Source|Child|Agent|Model):/.test(line)
|
|
1083
|
+
const styled = /^(Run|State|Mode|Source|Child|Agent|Model|Task):/.test(line)
|
|
884
1084
|
? this.theme.bold(line)
|
|
885
1085
|
: /^(Transcript|Result transcript tail)/.test(line)
|
|
886
1086
|
? this.theme.fg("accent", line)
|
|
@@ -933,7 +1133,9 @@ export class SubagentFleetComponent implements Component {
|
|
|
933
1133
|
}
|
|
934
1134
|
lines.push(this.theme.fg("border", `├${"─".repeat(rosterWidth)}┴${"─".repeat(detailWidth)}┤`));
|
|
935
1135
|
const position = this.snapshot.items.length ? `${this.selected + 1}/${this.snapshot.items.length}` : "0/0";
|
|
936
|
-
const footer =
|
|
1136
|
+
const footer = this.promptAuditOpen
|
|
1137
|
+
? ` j/k child · 1/2/3 view · g redo with guidance · c copy · Esc close Prompt Audit · ${position}`
|
|
1138
|
+
: ` ${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
1139
|
lines.push(this.theme.fg("border", "│") + fit(this.theme.fg("dim", footer), innerWidth) + this.theme.fg("border", "│"));
|
|
938
1140
|
lines.push(this.theme.fg("border", `╰${"─".repeat(innerWidth)}╯`));
|
|
939
1141
|
return lines.map((line) => truncateToWidth(line, width));
|
|
@@ -954,6 +1156,12 @@ export async function openSubagentFleet(ctx: ExtensionContext, state: SubagentSt
|
|
|
954
1156
|
const wasOpen = state.fleetInspectorOpen === true;
|
|
955
1157
|
state.fleetInspectorOpen = true;
|
|
956
1158
|
if (typeof ctx.ui.setWidget === "function") ctx.ui.setWidget(FLEET_STATUS_WIDGET_KEY, undefined);
|
|
1159
|
+
const copyText = options.copyText ?? (async (text: string) => {
|
|
1160
|
+
const module = await import("@earendil-works/pi-coding-agent");
|
|
1161
|
+
const copyToClipboard = (module as { copyToClipboard?: (value: string) => Promise<void> | void }).copyToClipboard;
|
|
1162
|
+
if (!copyToClipboard) throw new Error("Clipboard is unavailable in this Pi version.");
|
|
1163
|
+
await copyToClipboard(text);
|
|
1164
|
+
});
|
|
957
1165
|
const actions = options.actions ?? {
|
|
958
1166
|
steer: async (input: { runId: string; asyncDir: string; index?: number; message: string; mode: SteerDeliveryMode }) => firstToolResultText(await steerAsyncRun({
|
|
959
1167
|
state,
|
|
@@ -974,10 +1182,15 @@ export async function openSubagentFleet(ctx: ExtensionContext, state: SubagentSt
|
|
|
974
1182
|
...(state.authorityPolicy ? { authorityPolicy: state.authorityPolicy } : {}),
|
|
975
1183
|
...(state.missionStoreConfig ? { missions: state.missionStoreConfig } : {}),
|
|
976
1184
|
}), `Failed to open Herdr inspector for async run ${input.runId}.`),
|
|
1185
|
+
redoPrompt: async (input: { runId: string; index: number; guidance: string; control?: ForegroundRunControl }) => {
|
|
1186
|
+
const control = input.control ?? state.foregroundControls.get(input.runId);
|
|
1187
|
+
if (!control?.promptAuditRedo) return { text: "Redo is not available for this live child.", isError: true };
|
|
1188
|
+
return control.promptAuditRedo(input.index, input.guidance);
|
|
1189
|
+
},
|
|
977
1190
|
} satisfies FleetActionHandlers;
|
|
978
1191
|
try {
|
|
979
1192
|
await ctx.ui.custom<undefined>(
|
|
980
|
-
(tui, theme, _keybindings, done) => new SubagentFleetComponent(tui, theme, state, done, { ...options, actions }),
|
|
1193
|
+
(tui, theme, _keybindings, done) => new SubagentFleetComponent(tui, theme, state, done, { ...options, actions, copyText }),
|
|
981
1194
|
{
|
|
982
1195
|
overlay: true,
|
|
983
1196
|
overlayOptions: { anchor: "center", width: "95%", minWidth: 60, maxHeight: "85%", margin: 1 },
|
|
@@ -131,11 +131,11 @@ function hostCall(method, args, observation) {
|
|
|
131
131
|
: promise;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
function runHostCall(key, params, collectFailure) {
|
|
134
|
+
function runHostCall(key, params, collectFailure, batch) {
|
|
135
135
|
const callId = ++nextCallId;
|
|
136
136
|
const promise = new Promise((resolve, reject) => {
|
|
137
137
|
pending.set(callId, { resolve, reject });
|
|
138
|
-
parentPort.postMessage({ type: "call", callId, method: "run", args: { key, params, ...(collectFailure ? { collectFailure: true } : {}) } });
|
|
138
|
+
parentPort.postMessage({ type: "call", callId, method: "run", args: { key, params, ...(collectFailure ? { collectFailure: true } : {}), ...(batch ? { batch } : {}) } });
|
|
139
139
|
});
|
|
140
140
|
return { key, callId, promise };
|
|
141
141
|
}
|
|
@@ -188,7 +188,8 @@ const runs = Object.freeze({
|
|
|
188
188
|
calls.push({ key, params });
|
|
189
189
|
}
|
|
190
190
|
for (const { key, params } of calls) runFingerprints.set(key, stableRunJson(params));
|
|
191
|
-
const
|
|
191
|
+
const batch = { id: "batch-" + (++nextCallId), calls };
|
|
192
|
+
const launched = calls.map(({ key, params }) => runHostCall(key, params, true, batch));
|
|
192
193
|
return trackRunObservation(launched.map(({ key, callId }) => ({ key, callId })), Promise.all(launched.map(({ promise }) => promise)));
|
|
193
194
|
},
|
|
194
195
|
status(keyOrRunId) { return hostCall("status", { keyOrRunId }); },
|
|
@@ -363,7 +364,8 @@ export interface RunWorkflowScriptOptions {
|
|
|
363
364
|
script: string;
|
|
364
365
|
timeoutMs?: number;
|
|
365
366
|
signal?: AbortSignal;
|
|
366
|
-
|
|
367
|
+
admit?: (calls: Array<{ key: string; params: Record<string, unknown> }>) => void | Promise<void>;
|
|
368
|
+
launch: (key: string, params: Record<string, unknown>, signal: AbortSignal, admission: { admitted: boolean }) => Promise<WorkflowScriptChildResult>;
|
|
367
369
|
status: (keyOrRunId: string, signal: AbortSignal) => Promise<WorkflowScriptChildResult>;
|
|
368
370
|
state?: {
|
|
369
371
|
get: (key: string) => unknown | Promise<unknown>;
|
|
@@ -469,10 +471,13 @@ function validateKey(value: unknown, owner = "runs.run"): string {
|
|
|
469
471
|
return value;
|
|
470
472
|
}
|
|
471
473
|
|
|
472
|
-
function workflowStringMetadata(params: Record<string, unknown>): Pick<WorkflowScriptTraceEntry, "phase" | "label"> {
|
|
474
|
+
function workflowStringMetadata(params: Record<string, unknown>): Pick<WorkflowScriptTraceEntry, "phase" | "label" | "agent"> {
|
|
473
475
|
return {
|
|
474
476
|
...(typeof params.phase === "string" && params.phase.trim() ? { phase: params.phase.trim() } : {}),
|
|
475
477
|
...(typeof params.label === "string" && params.label.trim() ? { label: params.label.trim() } : {}),
|
|
478
|
+
// Requested agent name, so a child is identifiable while it runs. Launch
|
|
479
|
+
// resolution overwrites this with the canonical name on the terminal entry.
|
|
480
|
+
...(typeof params.agent === "string" && params.agent.trim() ? { agent: params.agent.trim() } : {}),
|
|
476
481
|
};
|
|
477
482
|
}
|
|
478
483
|
|
|
@@ -487,6 +492,7 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
487
492
|
const children = new Map<string, WorkflowScriptChildResult>();
|
|
488
493
|
const childOrder: string[] = [];
|
|
489
494
|
const launches = new Map<string, { fingerprint: string; promise: Promise<WorkflowScriptChildResult>; observed: boolean }>();
|
|
495
|
+
const batchAdmissions = new Map<string, Promise<void>>();
|
|
490
496
|
const observedRunCalls = new Set<number>();
|
|
491
497
|
const childController = new AbortController();
|
|
492
498
|
let settled = false;
|
|
@@ -677,7 +683,21 @@ export async function runWorkflowScript(options: RunWorkflowScriptOptions): Prom
|
|
|
677
683
|
childOrder.push(key);
|
|
678
684
|
trace.push({ operation: "run", key, state: "started", ...workflowStringMetadata(params) });
|
|
679
685
|
traceChanged();
|
|
680
|
-
const
|
|
686
|
+
const batch = isRecord(message.args.batch) && typeof message.args.batch.id === "string" && Array.isArray(message.args.batch.calls)
|
|
687
|
+
? { id: message.args.batch.id, calls: message.args.batch.calls.filter((call): call is { key: string; params: Record<string, unknown> } => isRecord(call) && typeof call.key === "string" && isRecord(call.params)) }
|
|
688
|
+
: undefined;
|
|
689
|
+
let admission = batch ? batchAdmissions.get(batch.id) : undefined;
|
|
690
|
+
if (!admission) {
|
|
691
|
+
const seenKeys = new Set<string>();
|
|
692
|
+
const calls = (batch?.calls ?? [{ key, params }]).filter((call) => {
|
|
693
|
+
if (seenKeys.has(call.key) || launches.has(call.key) || call.params.resume !== undefined) return false;
|
|
694
|
+
seenKeys.add(call.key);
|
|
695
|
+
return true;
|
|
696
|
+
});
|
|
697
|
+
admission = Promise.resolve().then(() => options.admit?.(calls));
|
|
698
|
+
if (batch) batchAdmissions.set(batch.id, admission);
|
|
699
|
+
}
|
|
700
|
+
const promise = admission.then(() => options.launch(key, { ...params, async: params.async ?? false }, childController.signal, { admitted: true })).then((result) => {
|
|
681
701
|
const normalized = !result.ok && !result.error ? { ...result, error: result.output } : result;
|
|
682
702
|
children.set(key, normalized);
|
|
683
703
|
trace.push({ operation: "run", key, state: normalized.ok ? "completed" : "failed", durationMs: Date.now() - startedAt, ...workflowStringMetadata(params), ...(normalized.agent ? { agent: normalized.agent } : {}), ...(normalized.runId ? { runId: normalized.runId } : {}), ...(!normalized.ok ? { error: normalized.error ?? normalized.output } : {}) });
|