pi-subagents 0.24.4 → 0.25.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 +8 -0
- package/README.md +16 -10
- package/package.json +1 -1
- package/prompts/review-loop.md +1 -1
- package/skills/pi-subagents/SKILL.md +46 -10
- package/src/extension/fanout-child.ts +170 -0
- package/src/extension/index.ts +6 -2
- package/src/intercom/result-intercom.ts +108 -0
- package/src/runs/background/async-execution.ts +101 -4
- package/src/runs/background/async-job-tracker.ts +41 -6
- package/src/runs/background/async-resume.ts +28 -15
- package/src/runs/background/async-status.ts +60 -30
- package/src/runs/background/result-watcher.ts +111 -54
- package/src/runs/background/run-id-resolver.ts +83 -0
- package/src/runs/background/run-status.ts +79 -3
- package/src/runs/background/stale-run-reconciler.ts +46 -1
- package/src/runs/background/subagent-runner.ts +48 -11
- package/src/runs/foreground/chain-execution.ts +6 -0
- package/src/runs/foreground/execution.ts +4 -0
- package/src/runs/foreground/subagent-executor.ts +310 -14
- package/src/runs/shared/nested-events.ts +819 -0
- package/src/runs/shared/nested-path.ts +52 -0
- package/src/runs/shared/nested-render.ts +115 -0
- package/src/runs/shared/pi-args.ts +62 -5
- package/src/runs/shared/subagent-prompt-runtime.ts +25 -5
- package/src/shared/types.ts +95 -0
- package/src/tui/render.ts +107 -10
package/src/tui/render.ts
CHANGED
|
@@ -12,12 +12,15 @@ import {
|
|
|
12
12
|
type AsyncJobStep,
|
|
13
13
|
type AsyncParallelGroupStatus,
|
|
14
14
|
type Details,
|
|
15
|
+
type NestedRunSummary,
|
|
16
|
+
type NestedStepSummary,
|
|
15
17
|
MAX_WIDGET_JOBS,
|
|
16
18
|
WIDGET_KEY,
|
|
17
19
|
} from "../shared/types.ts";
|
|
18
20
|
import { formatTokens, formatUsage, formatDuration, formatModelThinking, formatToolCall, shortenPath } from "../shared/formatters.ts";
|
|
19
21
|
import { getDisplayItems, getSingleResultOutput } from "../shared/utils.ts";
|
|
20
22
|
import { flatToLogicalStepIndex } from "../runs/background/parallel-groups.ts";
|
|
23
|
+
import { formatNestedAggregate } from "../runs/shared/nested-render.ts";
|
|
21
24
|
import { aggregateStepStatus, formatActivityLabel, formatAgentRunningLabel, formatParallelOutcome } from "../shared/status-format.ts";
|
|
22
25
|
|
|
23
26
|
type Theme = ExtensionContext["ui"]["theme"];
|
|
@@ -257,6 +260,7 @@ export function widgetRenderKey(job: AsyncJobState): string {
|
|
|
257
260
|
chainStepCount: job.chainStepCount,
|
|
258
261
|
parallelGroups: job.parallelGroups,
|
|
259
262
|
steps: job.steps,
|
|
263
|
+
nestedChildren: job.nestedChildren,
|
|
260
264
|
stepsTotal: job.stepsTotal,
|
|
261
265
|
runningSteps: job.runningSteps,
|
|
262
266
|
completedSteps: job.completedSteps,
|
|
@@ -399,18 +403,21 @@ function widgetChainDetails(job: AsyncJobState, theme: Theme, expanded = false,
|
|
|
399
403
|
return lines;
|
|
400
404
|
}
|
|
401
405
|
|
|
402
|
-
function widgetParallelAgentDetails(job: AsyncJobState, theme: Theme): string[] {
|
|
406
|
+
function widgetParallelAgentDetails(job: AsyncJobState, theme: Theme, expanded = false, width = getTermWidth()): string[] {
|
|
403
407
|
if (!job.steps?.length) return [];
|
|
404
408
|
if (job.mode !== "parallel" && job.mode !== "chain") return [];
|
|
405
|
-
if (job.mode === "chain" && !job.activeParallelGroup && job.parallelGroups?.length) return widgetChainDetails(job, theme);
|
|
409
|
+
if (job.mode === "chain" && !job.activeParallelGroup && job.parallelGroups?.length) return widgetChainDetails(job, theme, expanded, width);
|
|
406
410
|
const total = job.stepsTotal ?? job.steps.length;
|
|
407
|
-
|
|
408
|
-
|
|
411
|
+
const lines: string[] = [];
|
|
412
|
+
for (const [index, step] of job.steps.entries()) {
|
|
413
|
+
const marker = index === job.steps.length - 1 ? "└" : "├";
|
|
409
414
|
const activity = widgetStepActivity(step, job.updatedAt);
|
|
410
415
|
const itemTitle = job.mode === "parallel" || job.activeParallelGroup ? "Agent" : "Step";
|
|
411
416
|
const modelDisplay = modelThinkingBadge(theme, step.model, step.thinking);
|
|
412
|
-
|
|
413
|
-
|
|
417
|
+
lines.push(` ${theme.fg("dim", `${marker} ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index))} ${itemTitle} ${index + 1}/${total}: ${step.agent} · ${widgetStepStatus(step.status, theme)}${modelDisplay}${activity ? ` · ${activity}` : ""}`)}`);
|
|
418
|
+
for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, expanded, job.updatedAt, expanded ? 8 : 1)) lines.push(` ${nestedLine}`);
|
|
419
|
+
}
|
|
420
|
+
return lines;
|
|
414
421
|
}
|
|
415
422
|
|
|
416
423
|
function parseParallelGroupAgentCount(label: string | undefined): number | undefined {
|
|
@@ -646,6 +653,84 @@ function widgetOutputPath(job: AsyncJobState, step: NonNullable<AsyncJobState["s
|
|
|
646
653
|
return path.join(job.asyncDir, `output-${step.index}.log`);
|
|
647
654
|
}
|
|
648
655
|
|
|
656
|
+
function nestedRunName(run: NestedRunSummary): string {
|
|
657
|
+
if (run.agent) return run.agent;
|
|
658
|
+
if (run.agents?.length) return formatWidgetAgents(run.agents);
|
|
659
|
+
return run.id;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function nestedStatusGlyph(state: NestedRunSummary["state"] | NestedStepSummary["status"], theme: Theme, seed?: number): string {
|
|
663
|
+
if (state === "running") return theme.fg("accent", runningGlyph(seed));
|
|
664
|
+
if (state === "complete" || state === "completed") return theme.fg("success", "✓");
|
|
665
|
+
if (state === "failed") return theme.fg("error", "✗");
|
|
666
|
+
if (state === "paused") return theme.fg("warning", "■");
|
|
667
|
+
return theme.fg("muted", "◦");
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function nestedRunSeed(run: NestedRunSummary): number | undefined {
|
|
671
|
+
return runningSeed(run.lastUpdate, run.lastActivityAt, run.currentStep, run.toolCount, run.turnCount, run.totalTokens?.total, run.currentToolStartedAt);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function nestedActivity(input: Pick<NestedRunSummary | NestedStepSummary, "activityState" | "lastActivityAt" | "currentTool" | "currentToolStartedAt" | "currentPath" | "turnCount" | "toolCount">, state: NestedRunSummary["state"] | NestedStepSummary["status"], snapshotNow?: number): string {
|
|
675
|
+
const facts: string[] = [];
|
|
676
|
+
if (input.currentTool && input.currentToolStartedAt !== undefined && snapshotNow !== undefined) facts.push(`${input.currentTool} ${formatDuration(Math.max(0, snapshotNow - input.currentToolStartedAt))}`);
|
|
677
|
+
else if (input.currentTool) facts.push(input.currentTool);
|
|
678
|
+
if (input.currentPath) facts.push(shortenPath(input.currentPath));
|
|
679
|
+
if (input.turnCount !== undefined) facts.push(`${input.turnCount} turns`);
|
|
680
|
+
if (input.toolCount !== undefined) facts.push(`${input.toolCount} tools`);
|
|
681
|
+
const activity = buildLiveStatusLine(input, snapshotNow);
|
|
682
|
+
if (activity && facts.length) return `${activity} · ${facts.join(" · ")}`;
|
|
683
|
+
if (activity) return activity;
|
|
684
|
+
if (facts.length) return facts.join(" · ");
|
|
685
|
+
if (state === "running") return "thinking…";
|
|
686
|
+
if (state === "queued" || state === "pending") return "queued…";
|
|
687
|
+
if (state === "paused") return "Paused";
|
|
688
|
+
if (state === "failed") return "Failed";
|
|
689
|
+
return "Done";
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
function formatNestedWidgetLines(children: NestedRunSummary[] | undefined, theme: Theme, width: number, expanded: boolean, snapshotNow?: number, lineBudget = expanded ? 12 : 1): string[] {
|
|
693
|
+
if (!children?.length || lineBudget <= 0) return [];
|
|
694
|
+
if (!expanded) {
|
|
695
|
+
const aggregate = formatNestedAggregate(children);
|
|
696
|
+
return aggregate ? [theme.fg("dim", `↳ ${aggregate}`)] : [];
|
|
697
|
+
}
|
|
698
|
+
const lines: string[] = [];
|
|
699
|
+
const maxDepth = 2;
|
|
700
|
+
const append = (items: NestedRunSummary[] | undefined, depth: number, prefix: string): void => {
|
|
701
|
+
if (!items?.length || lines.length >= lineBudget) return;
|
|
702
|
+
if (depth > maxDepth) {
|
|
703
|
+
const aggregate = formatNestedAggregate(items);
|
|
704
|
+
if (aggregate && lines.length < lineBudget) lines.push(theme.fg("dim", `${prefix}↳ ${aggregate}`));
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
for (let index = 0; index < items.length; index++) {
|
|
708
|
+
const child = items[index]!;
|
|
709
|
+
if (lines.length >= lineBudget) {
|
|
710
|
+
const aggregate = formatNestedAggregate(items.slice(index));
|
|
711
|
+
if (aggregate) lines[lines.length - 1] = theme.fg("dim", `${prefix}↳ ${aggregate}`);
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
const activity = nestedActivity(child, child.state, snapshotNow ?? child.lastUpdate);
|
|
715
|
+
const error = child.error ? ` · ${child.error}` : "";
|
|
716
|
+
lines.push(theme.fg("dim", `${prefix}↳ ${nestedStatusGlyph(child.state, theme, nestedRunSeed(child))} ${nestedRunName(child)} · ${child.state} · ${activity}${error}`));
|
|
717
|
+
if (depth === maxDepth) {
|
|
718
|
+
const aggregate = formatNestedAggregate([...(child.steps?.flatMap((step) => step.children ?? []) ?? []), ...(child.children ?? [])]);
|
|
719
|
+
if (aggregate && lines.length < lineBudget) lines.push(theme.fg("dim", `${prefix} ↳ ${aggregate}`));
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
for (const step of child.steps ?? []) {
|
|
723
|
+
if (lines.length >= lineBudget) return;
|
|
724
|
+
lines.push(theme.fg("dim", `${prefix} ↳ ${nestedStatusGlyph(step.status, theme)} ${step.agent} · ${step.status} · ${nestedActivity(step, step.status, snapshotNow ?? child.lastUpdate)}`));
|
|
725
|
+
append(step.children, depth + 1, `${prefix} `);
|
|
726
|
+
}
|
|
727
|
+
append(child.children, depth + 1, `${prefix} `);
|
|
728
|
+
}
|
|
729
|
+
};
|
|
730
|
+
append(children, 0, "");
|
|
731
|
+
return lines.map((line) => truncLine(line, width));
|
|
732
|
+
}
|
|
733
|
+
|
|
649
734
|
function foregroundStyleWidgetStepLines(
|
|
650
735
|
job: AsyncJobState,
|
|
651
736
|
theme: Theme,
|
|
@@ -662,6 +747,9 @@ function foregroundStyleWidgetStepLines(
|
|
|
662
747
|
const lines = [` ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index - 1))} ${itemTitle} ${index}/${total}: ${themeBold(theme, step.agent)} ${theme.fg("dim", "·")} ${status}${modelDisplay}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`];
|
|
663
748
|
const activity = widgetStepActivityLine(step, width, expanded, job.updatedAt);
|
|
664
749
|
if (activity) lines.push(` ${theme.fg("dim", `⎿ ${activity}`)}`);
|
|
750
|
+
for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, expanded, job.updatedAt)) {
|
|
751
|
+
lines.push(` ${nestedLine}`);
|
|
752
|
+
}
|
|
665
753
|
if (step.status === "running") {
|
|
666
754
|
if (!expanded) lines.push(` ${theme.fg("accent", "Press Ctrl+O for live detail")}`);
|
|
667
755
|
const output = widgetOutputPath(job, step);
|
|
@@ -683,7 +771,10 @@ function foregroundStyleWidgetStepLines(
|
|
|
683
771
|
}
|
|
684
772
|
|
|
685
773
|
function foregroundStyleWidgetDetails(job: AsyncJobState, theme: Theme, expanded: boolean, width: number): string[] {
|
|
686
|
-
if (!job.steps?.length) return [
|
|
774
|
+
if (!job.steps?.length) return [
|
|
775
|
+
` ${theme.fg("dim", `⎿ ${widgetActivity(job)}`)}`,
|
|
776
|
+
...formatNestedWidgetLines(job.nestedChildren, theme, width, expanded, job.updatedAt).map((line) => ` ${line}`),
|
|
777
|
+
];
|
|
687
778
|
if (job.mode === "chain" && !job.activeParallelGroup && job.parallelGroups?.length) return widgetChainDetails(job, theme, expanded, width);
|
|
688
779
|
const total = job.stepsTotal ?? job.steps.length;
|
|
689
780
|
const itemTitle = job.mode === "parallel" || job.activeParallelGroup ? "Agent" : "Step";
|
|
@@ -691,6 +782,11 @@ function foregroundStyleWidgetDetails(job: AsyncJobState, theme: Theme, expanded
|
|
|
691
782
|
for (const [index, step] of job.steps.entries()) {
|
|
692
783
|
lines.push(...foregroundStyleWidgetStepLines(job, theme, step, itemTitle, index + 1, total, expanded, width));
|
|
693
784
|
}
|
|
785
|
+
const attached = new Set(job.steps.flatMap((step) => step.children?.map((child) => child.id) ?? []));
|
|
786
|
+
const unattached = job.nestedChildren?.filter((child) => !attached.has(child.id)) ?? [];
|
|
787
|
+
for (const nestedLine of formatNestedWidgetLines(unattached, theme, width, expanded, job.updatedAt)) {
|
|
788
|
+
lines.push(` ${nestedLine}`);
|
|
789
|
+
}
|
|
694
790
|
return lines;
|
|
695
791
|
}
|
|
696
792
|
|
|
@@ -720,6 +816,7 @@ function compactSingleWidgetLines(job: AsyncJobState, theme: Theme, width: numbe
|
|
|
720
816
|
const activitySuffix = activity ? ` ${theme.fg("dim", "·")} ${theme.fg("dim", activity)}` : "";
|
|
721
817
|
const modelDisplay = modelThinkingBadge(theme, step.model, step.thinking);
|
|
722
818
|
lines.push(` ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index))} ${itemTitle} ${index + 1}/${total}: ${themeBold(theme, step.agent)} ${theme.fg("dim", "·")} ${status}${modelDisplay}${activitySuffix}${stepStats ? ` ${theme.fg("dim", "·")} ${stepStats}` : ""}`);
|
|
819
|
+
for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, false, job.updatedAt)) lines.push(` ${nestedLine}`);
|
|
723
820
|
}
|
|
724
821
|
if (job.steps.some((step) => step.status === "running")) lines.push(theme.fg("accent", " Press Ctrl+O for live detail"));
|
|
725
822
|
return lines.map((line) => truncLine(line, width));
|
|
@@ -777,7 +874,7 @@ export function buildWidgetLines(jobs: AsyncJobState[], theme: Theme, width = ge
|
|
|
777
874
|
items.push([
|
|
778
875
|
`${widgetStatusGlyph(job, theme)} ${themeBold(theme, widgetJobName(job))}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`,
|
|
779
876
|
` ${theme.fg("dim", `⎿ ${widgetActivity(job)}`)}`,
|
|
780
|
-
...widgetParallelAgentDetails(job, theme),
|
|
877
|
+
...widgetParallelAgentDetails(job, theme, expanded, width),
|
|
781
878
|
]);
|
|
782
879
|
slots--;
|
|
783
880
|
}
|
|
@@ -794,7 +891,7 @@ export function buildWidgetLines(jobs: AsyncJobState[], theme: Theme, width = ge
|
|
|
794
891
|
items.push([
|
|
795
892
|
`${widgetStatusGlyph(job, theme)} ${themeBold(theme, widgetJobName(job))}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`,
|
|
796
893
|
` ${theme.fg("dim", `⎿ ${widgetActivity(job)}`)}`,
|
|
797
|
-
...widgetParallelAgentDetails(job, theme),
|
|
894
|
+
...widgetParallelAgentDetails(job, theme, expanded, width),
|
|
798
895
|
]);
|
|
799
896
|
slots--;
|
|
800
897
|
}
|
|
@@ -841,7 +938,7 @@ function renderSingleCompact(d: Details, r: Details["results"][number], theme: T
|
|
|
841
938
|
const isRunning = r.progress?.status === "running";
|
|
842
939
|
const contextBadge = d.context === "fork" ? theme.fg("warning", " [fork]") : "";
|
|
843
940
|
const stats = statJoin(theme, [
|
|
844
|
-
r.usage?.turns ?
|
|
941
|
+
r.usage?.turns ? `⟳ ${r.usage.turns}` : "",
|
|
845
942
|
formatProgressStats(theme, progress),
|
|
846
943
|
]);
|
|
847
944
|
const c = new Container();
|