oira666_pi-subagent 0.2.19 → 0.2.21
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/LICENSE +21 -21
- package/README.md +456 -456
- package/agents/code-architect.md +22 -22
- package/agents/code-reviwer.md +21 -21
- package/agents/code-writer.md +16 -16
- package/agents.ts +188 -188
- package/index.ts +360 -32
- package/package.json +74 -74
- package/render.ts +471 -463
- package/resume.ts +227 -228
- package/runner.ts +1059 -1028
- package/shared.ts +97 -97
- package/types.ts +311 -311
package/index.ts
CHANGED
|
@@ -15,8 +15,8 @@ import {
|
|
|
15
15
|
} from "@mariozechner/pi-ai";
|
|
16
16
|
import { Type } from "@sinclair/typebox";
|
|
17
17
|
import { type AgentConfig, discoverAgents } from "./agents.js";
|
|
18
|
-
import { renderCall, renderResult } from "./render.js";
|
|
19
|
-
import { runAgentSubprocess, executeParallelSubprocess } from "./runner.js";
|
|
18
|
+
import { renderCall, renderResult, setBroadcastNumberingActive } from "./render.js";
|
|
19
|
+
import { runAgentSubprocess, executeParallelSubprocess, type RunningSubagentHandle } from "./runner.js";
|
|
20
20
|
import {
|
|
21
21
|
SUBAGENT_RESUME_DISABLE_ENV,
|
|
22
22
|
SUBAGENT_RESUME_PROMPT_ENV,
|
|
@@ -45,7 +45,9 @@ import {
|
|
|
45
45
|
DEFAULT_DELEGATION_MODE,
|
|
46
46
|
buildSubagentDetails,
|
|
47
47
|
getFinalOutput,
|
|
48
|
+
getNestedSubagentResults,
|
|
48
49
|
isResultError,
|
|
50
|
+
isSubagentDetails,
|
|
49
51
|
} from "./types.js";
|
|
50
52
|
|
|
51
53
|
// ---------------------------------------------------------------------------
|
|
@@ -76,10 +78,7 @@ const TaskItem = Type.Object({
|
|
|
76
78
|
description:
|
|
77
79
|
"Task description for this delegated run. Include all required context; the subagent receives only this prompt.",
|
|
78
80
|
}),
|
|
79
|
-
|
|
80
|
-
Type.String({ description: "Working directory for this agent's process" }),
|
|
81
|
-
),
|
|
82
|
-
});
|
|
81
|
+
}, { additionalProperties: false });
|
|
83
82
|
|
|
84
83
|
const SubagentParams = Type.Object({
|
|
85
84
|
tasks: Type.Array(TaskItem, {
|
|
@@ -87,7 +86,7 @@ const SubagentParams = Type.Object({
|
|
|
87
86
|
description:
|
|
88
87
|
"Array of {agent, task} objects. One task behaves like a single-agent delegation; multiple tasks run concurrently.",
|
|
89
88
|
}),
|
|
90
|
-
});
|
|
89
|
+
}, { additionalProperties: false });
|
|
91
90
|
|
|
92
91
|
// ---------------------------------------------------------------------------
|
|
93
92
|
// Helpers
|
|
@@ -613,6 +612,286 @@ export default function (pi: ExtensionAPI) {
|
|
|
613
612
|
let pendingResumePlan: ResumableSubagentCall | null = null;
|
|
614
613
|
let modelToRestoreAfterResume: any | undefined;
|
|
615
614
|
const approvedProjectAgentDirsForSession = new Set<string>();
|
|
615
|
+
const activeSubagents = new Map<number, { agent: string; task: string; handle: RunningSubagentHandle }>();
|
|
616
|
+
const latestBroadcastTargets = {
|
|
617
|
+
all: [] as BroadcastTarget[],
|
|
618
|
+
youngest: [] as BroadcastTarget[],
|
|
619
|
+
};
|
|
620
|
+
let nextActiveSubagentId = 1;
|
|
621
|
+
|
|
622
|
+
const BROADCAST_STEER_PREFIX = "__PI_SUBAGENT_BROADCAST_STEER__";
|
|
623
|
+
|
|
624
|
+
interface BroadcastTarget {
|
|
625
|
+
display: string;
|
|
626
|
+
topLevelId: number;
|
|
627
|
+
restPath: number[];
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
interface ParsedBroadcastSelection {
|
|
631
|
+
targets: BroadcastTarget[];
|
|
632
|
+
errors: string[];
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function parseBroadcastPath(raw: string): number[] | null {
|
|
636
|
+
const parts = raw.trim().split(".");
|
|
637
|
+
if (parts.length === 0) return null;
|
|
638
|
+
const path: number[] = [];
|
|
639
|
+
for (const part of parts) {
|
|
640
|
+
if (!/^\d+$/.test(part)) return null;
|
|
641
|
+
const value = Number(part);
|
|
642
|
+
if (!Number.isSafeInteger(value) || value < 1) return null;
|
|
643
|
+
path.push(value);
|
|
644
|
+
}
|
|
645
|
+
return path;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function parseBroadcastSelection(input: string, available: number[]): ParsedBroadcastSelection {
|
|
649
|
+
const normalized = input.trim().toUpperCase();
|
|
650
|
+
if (normalized === "ALL") {
|
|
651
|
+
return {
|
|
652
|
+
targets: available.map((id) => ({ display: String(id), topLevelId: id, restPath: [] })),
|
|
653
|
+
errors: [],
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const errors: string[] = [];
|
|
658
|
+
const targetMap = new Map<string, BroadcastTarget>();
|
|
659
|
+
for (const rawPart of input.split(",")) {
|
|
660
|
+
const part = rawPart.trim();
|
|
661
|
+
if (!part) continue;
|
|
662
|
+
|
|
663
|
+
if (part.includes("-")) {
|
|
664
|
+
const [startRaw, endRaw, extra] = part.split("-").map((p) => p.trim());
|
|
665
|
+
const startPath = parseBroadcastPath(startRaw);
|
|
666
|
+
const endPath = parseBroadcastPath(endRaw);
|
|
667
|
+
if (extra !== undefined || !startPath || !endPath || startPath.length !== 1 || endPath.length !== 1) {
|
|
668
|
+
errors.push(`Invalid range "${part}". Use top-level ranges like 1-3.`);
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
const start = startPath[0];
|
|
672
|
+
const end = endPath[0];
|
|
673
|
+
for (let id = Math.min(start, end); id <= Math.max(start, end); id++) {
|
|
674
|
+
if (!available.includes(id)) {
|
|
675
|
+
errors.push(`Subagent ${id} is not running.`);
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
targetMap.set(String(id), { display: String(id), topLevelId: id, restPath: [] });
|
|
679
|
+
}
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const path = parseBroadcastPath(part);
|
|
684
|
+
if (!path) {
|
|
685
|
+
errors.push(`Invalid target "${part}". Use ALL, numbers, paths, or top-level ranges (e.g. 1, 2, 4.1, 1-3).`);
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
const [topLevelId, ...restPath] = path;
|
|
689
|
+
if (!available.includes(topLevelId)) {
|
|
690
|
+
errors.push(`Subagent ${topLevelId} is not running.`);
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
const display = path.join(".");
|
|
694
|
+
targetMap.set(display, { display, topLevelId, restPath });
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
return {
|
|
698
|
+
targets: Array.from(targetMap.values()).sort((a, b) => a.display.localeCompare(b.display, undefined, { numeric: true })),
|
|
699
|
+
errors,
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function encodeNestedBroadcast(message: string, path: number[]): string {
|
|
704
|
+
return `${BROADCAST_STEER_PREFIX}${JSON.stringify({ path, message })}`;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function decodeNestedBroadcast(text: string): { path: number[]; message: string } | null {
|
|
708
|
+
if (!text.startsWith(BROADCAST_STEER_PREFIX)) return null;
|
|
709
|
+
try {
|
|
710
|
+
const parsed = JSON.parse(text.slice(BROADCAST_STEER_PREFIX.length));
|
|
711
|
+
if (!Array.isArray(parsed?.path) || !parsed.path.every((n: unknown) => Number.isSafeInteger(n) && (n as number) >= 1)) return null;
|
|
712
|
+
if (typeof parsed?.message !== "string") return null;
|
|
713
|
+
return { path: parsed.path, message: parsed.message };
|
|
714
|
+
} catch {
|
|
715
|
+
return null;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function extractPendingSubagentTaskCounts(result: SingleResult): number[] {
|
|
720
|
+
const completedToolCallIds = new Set(getNestedSubagentResults(result.messages).map((nested) => nested.toolCallId));
|
|
721
|
+
const counts: number[] = [];
|
|
722
|
+
for (const message of result.messages as any[]) {
|
|
723
|
+
if (message?.role !== "assistant" || !Array.isArray(message.content)) continue;
|
|
724
|
+
for (const part of message.content) {
|
|
725
|
+
if (part?.type !== "toolCall" || part?.name !== "subagent") continue;
|
|
726
|
+
const toolCallId = typeof part.toolCallId === "string" ? part.toolCallId : typeof part.id === "string" ? part.id : undefined;
|
|
727
|
+
if (toolCallId && completedToolCallIds.has(toolCallId)) continue;
|
|
728
|
+
const tasks = Array.isArray(part.arguments?.tasks) ? part.arguments.tasks : [];
|
|
729
|
+
if (tasks.length > 0) counts.push(tasks.length);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
return counts;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function collectRunningBroadcastTargetsFromResult(
|
|
736
|
+
result: SingleResult,
|
|
737
|
+
path: number[],
|
|
738
|
+
targets: { all: BroadcastTarget[]; youngest: BroadcastTarget[] },
|
|
739
|
+
): boolean {
|
|
740
|
+
const nestedRunningPaths: number[][] = [];
|
|
741
|
+
|
|
742
|
+
for (const nested of getNestedSubagentResults(result.messages)) {
|
|
743
|
+
if (!isSubagentDetails(nested.details)) continue;
|
|
744
|
+
nested.details.results.forEach((child, index) => {
|
|
745
|
+
const childPath = [...path, index + 1];
|
|
746
|
+
if (collectRunningBroadcastTargetsFromResult(child, childPath, targets)) {
|
|
747
|
+
nestedRunningPaths.push(childPath);
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
if (result.exitCode === -1) {
|
|
753
|
+
for (const taskCount of extractPendingSubagentTaskCounts(result)) {
|
|
754
|
+
for (let i = 1; i <= taskCount; i++) {
|
|
755
|
+
const childPath = [...path, i];
|
|
756
|
+
const [topLevelId, ...restPath] = childPath;
|
|
757
|
+
targets.all.push({ display: childPath.join("."), topLevelId, restPath });
|
|
758
|
+
targets.youngest.push({ display: childPath.join("."), topLevelId, restPath });
|
|
759
|
+
nestedRunningPaths.push(childPath);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
const isRunning = result.exitCode === -1;
|
|
765
|
+
if (!isRunning) return nestedRunningPaths.length > 0;
|
|
766
|
+
|
|
767
|
+
const [topLevelId, ...restPath] = path;
|
|
768
|
+
const self = { display: path.join("."), topLevelId, restPath };
|
|
769
|
+
targets.all.push(self);
|
|
770
|
+
if (nestedRunningPaths.length === 0) targets.youngest.push(self);
|
|
771
|
+
return true;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function updateLatestBroadcastTargets(details: SubagentDetails | undefined): void {
|
|
775
|
+
latestBroadcastTargets.all = [];
|
|
776
|
+
latestBroadcastTargets.youngest = [];
|
|
777
|
+
if (!details) {
|
|
778
|
+
for (const id of activeSubagents.keys()) {
|
|
779
|
+
const target = { display: String(id), topLevelId: id, restPath: [] };
|
|
780
|
+
latestBroadcastTargets.all.push(target);
|
|
781
|
+
latestBroadcastTargets.youngest.push(target);
|
|
782
|
+
}
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
details.results.forEach((result, index) => {
|
|
786
|
+
collectRunningBroadcastTargetsFromResult(result, [index + 1], latestBroadcastTargets);
|
|
787
|
+
});
|
|
788
|
+
const dedupe = (targets: BroadcastTarget[]) =>
|
|
789
|
+
Array.from(new Map(targets.map((target) => [target.display, target])).values())
|
|
790
|
+
.filter((target) => activeSubagents.has(target.topLevelId))
|
|
791
|
+
.sort((a, b) => a.display.localeCompare(b.display, undefined, { numeric: true }));
|
|
792
|
+
latestBroadcastTargets.all = dedupe(latestBroadcastTargets.all);
|
|
793
|
+
latestBroadcastTargets.youngest = dedupe(latestBroadcastTargets.youngest);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function getFallbackTopLevelTargets(): BroadcastTarget[] {
|
|
797
|
+
return Array.from(activeSubagents.keys())
|
|
798
|
+
.sort((a, b) => a - b)
|
|
799
|
+
.map((id) => ({ display: String(id), topLevelId: id, restPath: [] }));
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function sendBroadcastToTargets(message: string, targets: BroadcastTarget[], ctx: any): void {
|
|
803
|
+
const delivered: string[] = [];
|
|
804
|
+
const missed: string[] = [];
|
|
805
|
+
for (const target of targets) {
|
|
806
|
+
const item = activeSubagents.get(target.topLevelId);
|
|
807
|
+
if (!item) {
|
|
808
|
+
missed.push(target.display);
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
item.handle.steer(
|
|
812
|
+
target.restPath.length > 0
|
|
813
|
+
? encodeNestedBroadcast(message, target.restPath)
|
|
814
|
+
: message,
|
|
815
|
+
);
|
|
816
|
+
delivered.push(target.display);
|
|
817
|
+
}
|
|
818
|
+
if (delivered.length > 0) {
|
|
819
|
+
ctx.ui.notify(`Broadcasted steering message to subagent(s): ${delivered.join(", ")}`, "info");
|
|
820
|
+
}
|
|
821
|
+
if (missed.length > 0) {
|
|
822
|
+
ctx.ui.notify(`Some selected subagents are no longer running: ${missed.join(", ")}`, "warning");
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
async function askBroadcastForSteering(message: string, ctx: any): Promise<"continue" | "handled"> {
|
|
827
|
+
const nested = decodeNestedBroadcast(message);
|
|
828
|
+
if (nested) {
|
|
829
|
+
const available = Array.from(activeSubagents.keys()).sort((a, b) => a - b);
|
|
830
|
+
if (available.length === 0) return "handled";
|
|
831
|
+
const [nextId, ...restPath] = nested.path;
|
|
832
|
+
if (!available.includes(nextId)) return "handled";
|
|
833
|
+
sendBroadcastToTargets(nested.message, [{ display: nested.path.join("."), topLevelId: nextId, restPath }], ctx);
|
|
834
|
+
return "handled";
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
if (!ctx.hasUI || activeSubagents.size === 0) return "continue";
|
|
838
|
+
|
|
839
|
+
setBroadcastNumberingActive(true);
|
|
840
|
+
try {
|
|
841
|
+
const available = Array.from(activeSubagents.keys()).sort((a, b) => a - b);
|
|
842
|
+
const choice = await ctx.ui.select(
|
|
843
|
+
"Broadcast this steering message to subagents?",
|
|
844
|
+
["No", "All (+nested)", "Youngest", "Numbers (e.g. 1, 2, 4.1)"],
|
|
845
|
+
);
|
|
846
|
+
if (choice === "All (+nested)" || choice === "Youngest") {
|
|
847
|
+
const fallback = getFallbackTopLevelTargets();
|
|
848
|
+
const selected = choice === "Youngest"
|
|
849
|
+
? (latestBroadcastTargets.youngest.length > 0 ? latestBroadcastTargets.youngest : fallback)
|
|
850
|
+
: (latestBroadcastTargets.all.length > 0 ? latestBroadcastTargets.all : fallback);
|
|
851
|
+
const current = selected.filter((target) => activeSubagents.has(target.topLevelId));
|
|
852
|
+
if (current.length === 0) {
|
|
853
|
+
ctx.ui.notify("No selected subagents are still running. Continuing with normal steering.", "warning");
|
|
854
|
+
return "continue";
|
|
855
|
+
}
|
|
856
|
+
sendBroadcastToTargets(message, current, ctx);
|
|
857
|
+
return "handled";
|
|
858
|
+
}
|
|
859
|
+
if (choice !== "Numbers (e.g. 1, 2, 4.1)") return "continue";
|
|
860
|
+
|
|
861
|
+
const answer = await ctx.ui.input(
|
|
862
|
+
"Subagent numbers/ranges to broadcast to (e.g. 1, 2, 4.1)",
|
|
863
|
+
"",
|
|
864
|
+
);
|
|
865
|
+
if (!answer) return "continue";
|
|
866
|
+
const current = Array.from(new Set([
|
|
867
|
+
...available,
|
|
868
|
+
...latestBroadcastTargets.all.map((target) => target.topLevelId),
|
|
869
|
+
])).sort((a, b) => a - b);
|
|
870
|
+
const parsed = parseBroadcastSelection(answer, current);
|
|
871
|
+
const knownDisplays = new Set(latestBroadcastTargets.all.map((target) => target.display));
|
|
872
|
+
const validatedTargets = knownDisplays.size > 0
|
|
873
|
+
? parsed.targets.filter((target) => knownDisplays.has(target.display))
|
|
874
|
+
: parsed.targets;
|
|
875
|
+
const unknownNested = knownDisplays.size > 0
|
|
876
|
+
? parsed.targets.filter((target) => !knownDisplays.has(target.display)).map((target) => target.display)
|
|
877
|
+
: [];
|
|
878
|
+
const errors = [
|
|
879
|
+
...parsed.errors,
|
|
880
|
+
...unknownNested.map((display) => `Subagent ${display} is not running.`),
|
|
881
|
+
];
|
|
882
|
+
if (errors.length > 0) {
|
|
883
|
+
ctx.ui.notify(errors.slice(0, 4).join("\n"), "warning");
|
|
884
|
+
}
|
|
885
|
+
if (validatedTargets.length === 0) {
|
|
886
|
+
ctx.ui.notify("No valid running subagents selected. Continuing with normal steering.", "warning");
|
|
887
|
+
return "continue";
|
|
888
|
+
}
|
|
889
|
+
sendBroadcastToTargets(message, validatedTargets, ctx);
|
|
890
|
+
return "handled";
|
|
891
|
+
} finally {
|
|
892
|
+
setBroadcastNumberingActive(false);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
616
895
|
|
|
617
896
|
async function restoreModelAfterResumeFailure(ctx?: { ui?: { notify?: (message: string, type?: "info" | "warning" | "error") => void } }) {
|
|
618
897
|
const restore = modelToRestoreAfterResume;
|
|
@@ -751,6 +1030,23 @@ export default function (pi: ExtensionAPI) {
|
|
|
751
1030
|
}, RESUME_INTERACTIVE_DELAY_MS);
|
|
752
1031
|
});
|
|
753
1032
|
|
|
1033
|
+
pi.on("input", async (event, ctx) => {
|
|
1034
|
+
try {
|
|
1035
|
+
// Pi emits this before it applies the built-in streaming behavior. When a
|
|
1036
|
+
// subagent tool is running, user input is a normal steering message to the
|
|
1037
|
+
// parent. Give the user a chance to route it to child RPC sessions instead.
|
|
1038
|
+
if (activeSubagents.size === 0) return { action: "continue" as const };
|
|
1039
|
+
if (ctx.isIdle()) return { action: "continue" as const };
|
|
1040
|
+
const result = await askBroadcastForSteering(event.text, ctx);
|
|
1041
|
+
return result === "handled"
|
|
1042
|
+
? { action: "handled" as const }
|
|
1043
|
+
: { action: "continue" as const };
|
|
1044
|
+
} catch (err) {
|
|
1045
|
+
console.error("[pi-subagent] Error while handling steering broadcast:", err);
|
|
1046
|
+
return { action: "continue" as const };
|
|
1047
|
+
}
|
|
1048
|
+
});
|
|
1049
|
+
|
|
754
1050
|
// Inject available agents into the system prompt
|
|
755
1051
|
pi.on("before_agent_start", async (event) => {
|
|
756
1052
|
try {
|
|
@@ -821,6 +1117,9 @@ calls one after another. Do NOT put dependent tasks in the same array.
|
|
|
821
1117
|
|
|
822
1118
|
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
823
1119
|
try {
|
|
1120
|
+
activeSubagents.clear();
|
|
1121
|
+
updateLatestBroadcastTargets(undefined);
|
|
1122
|
+
nextActiveSubagentId = 1;
|
|
824
1123
|
const discovery = discoverAgents(ctx.cwd, "both");
|
|
825
1124
|
const { agents } = discovery;
|
|
826
1125
|
|
|
@@ -843,6 +1142,10 @@ calls one after another. Do NOT put dependent tasks in the same array.
|
|
|
843
1142
|
}
|
|
844
1143
|
|
|
845
1144
|
const executionMode = tasks.length === 1 ? "single" : "parallel";
|
|
1145
|
+
const trackedOnUpdate = (partial: any) => {
|
|
1146
|
+
if (isSubagentDetails(partial?.details)) updateLatestBroadcastTargets(partial.details);
|
|
1147
|
+
onUpdate?.(partial);
|
|
1148
|
+
};
|
|
846
1149
|
|
|
847
1150
|
// Security: guard project-local agents before running
|
|
848
1151
|
const requested = new Set<string>();
|
|
@@ -937,11 +1240,10 @@ This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A)
|
|
|
937
1240
|
return executeSingle(
|
|
938
1241
|
task.agent,
|
|
939
1242
|
task.task,
|
|
940
|
-
task.cwd,
|
|
941
1243
|
agents,
|
|
942
1244
|
ctx.cwd,
|
|
943
1245
|
signal,
|
|
944
|
-
|
|
1246
|
+
trackedOnUpdate,
|
|
945
1247
|
makeDetails,
|
|
946
1248
|
resumePlan?.details?.results[0],
|
|
947
1249
|
getSessionDirForTask(resumePlan?.previousToolCallId ?? toolCallId, 0),
|
|
@@ -950,7 +1252,7 @@ This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A)
|
|
|
950
1252
|
// active model (normal runs). This prevents children from
|
|
951
1253
|
// defaulting to whatever settings.json says at spawn time, which
|
|
952
1254
|
// can change while the parent session is long-running.
|
|
953
|
-
formatModelFlag(modelToRestoreAfterResume ?? lastRestorableModel),
|
|
1255
|
+
formatModelFlag(modelToRestoreAfterResume ?? ctx.model ?? lastRestorableModel),
|
|
954
1256
|
);
|
|
955
1257
|
}
|
|
956
1258
|
|
|
@@ -959,12 +1261,12 @@ This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A)
|
|
|
959
1261
|
agents,
|
|
960
1262
|
ctx.cwd,
|
|
961
1263
|
signal,
|
|
962
|
-
|
|
1264
|
+
trackedOnUpdate,
|
|
963
1265
|
makeDetails,
|
|
964
1266
|
resumePlan?.details?.results,
|
|
965
1267
|
(index) => getSessionDirForTask(resumePlan?.previousToolCallId ?? toolCallId, index),
|
|
966
1268
|
!!resumePlan,
|
|
967
|
-
formatModelFlag(modelToRestoreAfterResume ?? lastRestorableModel),
|
|
1269
|
+
formatModelFlag(modelToRestoreAfterResume ?? ctx.model ?? lastRestorableModel),
|
|
968
1270
|
);
|
|
969
1271
|
} catch (err) {
|
|
970
1272
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -998,7 +1300,6 @@ This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A)
|
|
|
998
1300
|
async function executeSingle(
|
|
999
1301
|
agentName: string,
|
|
1000
1302
|
task: string,
|
|
1001
|
-
cwd: string | undefined,
|
|
1002
1303
|
agents: AgentConfig[],
|
|
1003
1304
|
defaultCwd: string,
|
|
1004
1305
|
signal: AbortSignal | undefined,
|
|
@@ -1021,12 +1322,12 @@ This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A)
|
|
|
1021
1322
|
};
|
|
1022
1323
|
}
|
|
1023
1324
|
|
|
1325
|
+
let activeId: number | undefined;
|
|
1024
1326
|
const result = await runAgentSubprocess({
|
|
1025
1327
|
cwd: defaultCwd,
|
|
1026
1328
|
agents,
|
|
1027
1329
|
agentName,
|
|
1028
1330
|
task,
|
|
1029
|
-
taskCwd: cwd,
|
|
1030
1331
|
parentDepth: currentDepth,
|
|
1031
1332
|
parentAgentStack: ancestorAgentStack,
|
|
1032
1333
|
maxDepth,
|
|
@@ -1039,7 +1340,16 @@ This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A)
|
|
|
1039
1340
|
resumeSession: resumeExistingSession,
|
|
1040
1341
|
initialResult: previousResult,
|
|
1041
1342
|
fallbackModel,
|
|
1343
|
+
onHandle: (handle) => {
|
|
1344
|
+
activeId = 1;
|
|
1345
|
+
activeSubagents.set(activeId, { agent: agentName, task, handle });
|
|
1346
|
+
updateLatestBroadcastTargets(undefined);
|
|
1347
|
+
},
|
|
1042
1348
|
});
|
|
1349
|
+
if (activeId !== undefined) {
|
|
1350
|
+
activeSubagents.delete(activeId);
|
|
1351
|
+
updateLatestBroadcastTargets(undefined);
|
|
1352
|
+
}
|
|
1043
1353
|
|
|
1044
1354
|
if (isResultError(result)) {
|
|
1045
1355
|
const errorMsg =
|
|
@@ -1070,7 +1380,7 @@ This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A)
|
|
|
1070
1380
|
}
|
|
1071
1381
|
|
|
1072
1382
|
async function executeParallel(
|
|
1073
|
-
tasks: Array<{ agent: string; task: string
|
|
1383
|
+
tasks: Array<{ agent: string; task: string }>,
|
|
1074
1384
|
agents: AgentConfig[],
|
|
1075
1385
|
defaultCwd: string,
|
|
1076
1386
|
signal: AbortSignal | undefined,
|
|
@@ -1081,22 +1391,40 @@ This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A)
|
|
|
1081
1391
|
resumeExistingSessions: boolean,
|
|
1082
1392
|
fallbackModel?: string,
|
|
1083
1393
|
) {
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1394
|
+
const taskIds = new Map<number, number>();
|
|
1395
|
+
try {
|
|
1396
|
+
return await executeParallelSubprocess(
|
|
1397
|
+
tasks,
|
|
1398
|
+
agents,
|
|
1399
|
+
defaultCwd,
|
|
1400
|
+
currentDepth,
|
|
1401
|
+
maxDepth,
|
|
1402
|
+
ancestorAgentStack,
|
|
1403
|
+
preventCycles,
|
|
1404
|
+
signal,
|
|
1405
|
+
onUpdate,
|
|
1406
|
+
makeDetails("parallel"),
|
|
1407
|
+
resumeResults,
|
|
1408
|
+
(index) => getSessionDir(index),
|
|
1409
|
+
resumeExistingSessions,
|
|
1410
|
+
currentSubagentSessionRoot,
|
|
1411
|
+
fallbackModel,
|
|
1412
|
+
(index, task, handle) => {
|
|
1413
|
+
const id = index + 1;
|
|
1414
|
+
taskIds.set(index, id);
|
|
1415
|
+
activeSubagents.set(id, { agent: task.agent, task: task.task, handle });
|
|
1416
|
+
updateLatestBroadcastTargets(undefined);
|
|
1417
|
+
},
|
|
1418
|
+
(index) => {
|
|
1419
|
+
const id = taskIds.get(index);
|
|
1420
|
+
if (id !== undefined) {
|
|
1421
|
+
activeSubagents.delete(id);
|
|
1422
|
+
updateLatestBroadcastTargets(undefined);
|
|
1423
|
+
}
|
|
1424
|
+
},
|
|
1425
|
+
);
|
|
1426
|
+
} finally {
|
|
1427
|
+
for (const id of taskIds.values()) activeSubagents.delete(id);
|
|
1428
|
+
}
|
|
1101
1429
|
}
|
|
1102
1430
|
}
|
package/package.json
CHANGED
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "oira666_pi-subagent",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Subagent extension for Pi coding agent. Delegate tasks to specialized agents.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "index.ts",
|
|
7
|
-
"files": [
|
|
8
|
-
"index.ts",
|
|
9
|
-
"agents.ts",
|
|
10
|
-
"runner.ts",
|
|
11
|
-
"resume.ts",
|
|
12
|
-
"shared.ts",
|
|
13
|
-
"render.ts",
|
|
14
|
-
"types.ts",
|
|
15
|
-
"agents/*.md",
|
|
16
|
-
"README.md",
|
|
17
|
-
"LICENSE"
|
|
18
|
-
],
|
|
19
|
-
"pi": {
|
|
20
|
-
"extensions": [
|
|
21
|
-
"./index.ts"
|
|
22
|
-
]
|
|
23
|
-
},
|
|
24
|
-
"keywords": [
|
|
25
|
-
"pi",
|
|
26
|
-
"subagent",
|
|
27
|
-
"delegation",
|
|
28
|
-
"pi-package"
|
|
29
|
-
],
|
|
30
|
-
"repository": {
|
|
31
|
-
"type": "git",
|
|
32
|
-
"url": "git+https://github.com/gee666/pi-subagent.git"
|
|
33
|
-
},
|
|
34
|
-
"bugs": {
|
|
35
|
-
"url": "https://github.com/gee666/pi-subagent/issues"
|
|
36
|
-
},
|
|
37
|
-
"homepage": "https://github.com/gee666/pi-subagent#readme",
|
|
38
|
-
"publishConfig": {
|
|
39
|
-
"access": "public"
|
|
40
|
-
},
|
|
41
|
-
"license": "MIT",
|
|
42
|
-
"scripts": {
|
|
43
|
-
"test": "node --import tsx/esm --test tests/*.test.ts test/*.test.ts"
|
|
44
|
-
},
|
|
45
|
-
"devDependencies": {
|
|
46
|
-
"@types/node": "^25.2.3",
|
|
47
|
-
"tsx": "^4.21.0",
|
|
48
|
-
"typescript": "^5.9.3"
|
|
49
|
-
},
|
|
50
|
-
"peerDependencies": {
|
|
51
|
-
"@mariozechner/pi-agent-core": ">=0.37.0",
|
|
52
|
-
"@mariozechner/pi-ai": ">=0.37.0",
|
|
53
|
-
"@mariozechner/pi-coding-agent": ">=0.37.0",
|
|
54
|
-
"@mariozechner/pi-tui": ">=0.37.0",
|
|
55
|
-
"@sinclair/typebox": ">=0.34.0"
|
|
56
|
-
},
|
|
57
|
-
"peerDependenciesMeta": {
|
|
58
|
-
"@mariozechner/pi-agent-core": {
|
|
59
|
-
"optional": true
|
|
60
|
-
},
|
|
61
|
-
"@mariozechner/pi-coding-agent": {
|
|
62
|
-
"optional": true
|
|
63
|
-
},
|
|
64
|
-
"@mariozechner/pi-tui": {
|
|
65
|
-
"optional": true
|
|
66
|
-
},
|
|
67
|
-
"@mariozechner/pi-ai": {
|
|
68
|
-
"optional": true
|
|
69
|
-
},
|
|
70
|
-
"@sinclair/typebox": {
|
|
71
|
-
"optional": true
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "oira666_pi-subagent",
|
|
3
|
+
"version": "0.2.21",
|
|
4
|
+
"description": "Subagent extension for Pi coding agent. Delegate tasks to specialized agents.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"index.ts",
|
|
9
|
+
"agents.ts",
|
|
10
|
+
"runner.ts",
|
|
11
|
+
"resume.ts",
|
|
12
|
+
"shared.ts",
|
|
13
|
+
"render.ts",
|
|
14
|
+
"types.ts",
|
|
15
|
+
"agents/*.md",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"pi": {
|
|
20
|
+
"extensions": [
|
|
21
|
+
"./index.ts"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"pi",
|
|
26
|
+
"subagent",
|
|
27
|
+
"delegation",
|
|
28
|
+
"pi-package"
|
|
29
|
+
],
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/gee666/pi-subagent.git"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/gee666/pi-subagent/issues"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/gee666/pi-subagent#readme",
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"scripts": {
|
|
43
|
+
"test": "node --import tsx/esm --test tests/*.test.ts test/*.test.ts"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^25.2.3",
|
|
47
|
+
"tsx": "^4.21.0",
|
|
48
|
+
"typescript": "^5.9.3"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"@mariozechner/pi-agent-core": ">=0.37.0",
|
|
52
|
+
"@mariozechner/pi-ai": ">=0.37.0",
|
|
53
|
+
"@mariozechner/pi-coding-agent": ">=0.37.0",
|
|
54
|
+
"@mariozechner/pi-tui": ">=0.37.0",
|
|
55
|
+
"@sinclair/typebox": ">=0.34.0"
|
|
56
|
+
},
|
|
57
|
+
"peerDependenciesMeta": {
|
|
58
|
+
"@mariozechner/pi-agent-core": {
|
|
59
|
+
"optional": true
|
|
60
|
+
},
|
|
61
|
+
"@mariozechner/pi-coding-agent": {
|
|
62
|
+
"optional": true
|
|
63
|
+
},
|
|
64
|
+
"@mariozechner/pi-tui": {
|
|
65
|
+
"optional": true
|
|
66
|
+
},
|
|
67
|
+
"@mariozechner/pi-ai": {
|
|
68
|
+
"optional": true
|
|
69
|
+
},
|
|
70
|
+
"@sinclair/typebox": {
|
|
71
|
+
"optional": true
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|