oira666_pi-subagent 0.1.15 → 0.2.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/README.md +7 -13
- package/agents/code-architect.md +0 -2
- package/agents/code-reviwer.md +0 -2
- package/agents/code-writer.md +0 -2
- package/index.ts +6 -109
- package/package.json +1 -1
- package/render.ts +7 -16
- package/runner-sdk.ts +1 -72
- package/runner.ts +1 -67
- package/types.ts +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Pi Subagent
|
|
2
2
|
|
|
3
|
-
Delegate tasks to specialized subagents
|
|
3
|
+
Delegate tasks to specialized subagents.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -39,21 +39,17 @@ Processes are spawned via the operating system and communicate through JSON-line
|
|
|
39
39
|
Each subagent runs as an **in-process `AgentSession`** created via the pi SDK — no new process is spawned.
|
|
40
40
|
|
|
41
41
|
- No spawn overhead — sessions start in milliseconds
|
|
42
|
-
- No temp files for system prompts
|
|
42
|
+
- No temp files for system prompts
|
|
43
43
|
- Concurrency through the Node.js event loop (fine for I/O-bound LLM work)
|
|
44
44
|
- All sessions share the same memory and event loop
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
**`spawn` (default)** — Child receives only the task string. Best for isolated work, lower cost.
|
|
49
|
-
**`fork`** — Child receives a snapshot of the current session context + task. Best for follow-up work.
|
|
50
|
-
|
|
51
|
-
The main agent receives only the **final text output** from subagents (no tool calls, no reasoning).
|
|
46
|
+
Each subagent receives only the task string. The main agent in turn receives
|
|
47
|
+
only the **final text output** from subagents (no tool calls, no reasoning).
|
|
52
48
|
|
|
53
49
|
## Tool Call Shape
|
|
54
50
|
|
|
55
51
|
```json
|
|
56
|
-
{ "tasks": [{ "agent": "code-writer", "task": "Implement the API" }]
|
|
52
|
+
{ "tasks": [{ "agent": "code-writer", "task": "Implement the API" }] }
|
|
57
53
|
```
|
|
58
54
|
|
|
59
55
|
Multiple tasks run in parallel:
|
|
@@ -63,8 +59,7 @@ Multiple tasks run in parallel:
|
|
|
63
59
|
"tasks": [
|
|
64
60
|
{ "agent": "code-writer", "task": "Draft the implementation" },
|
|
65
61
|
{ "agent": "code-reviwer", "task": "Review the plan" }
|
|
66
|
-
]
|
|
67
|
-
"mode": "fork"
|
|
62
|
+
]
|
|
68
63
|
}
|
|
69
64
|
```
|
|
70
65
|
|
|
@@ -136,7 +131,6 @@ PI_SUBAGENTS_MODE=subprocess pi
|
|
|
136
131
|
| Session isolation | Full OS-level | Shared memory/event loop |
|
|
137
132
|
| Startup overhead | ~200–500 ms per agent | ~10–50 ms per agent |
|
|
138
133
|
| Parallelism | True OS parallelism | Event-loop concurrency |
|
|
139
|
-
| Fork mode context | Temp JSONL file | Temp JSONL file (same format) |
|
|
140
134
|
| Depth tracking | Env vars in child process | Closure parameters |
|
|
141
135
|
| `pi` binary required | Yes | No |
|
|
142
136
|
| Crash isolation | Yes — subprocess crash is contained | No — exception bubbles up |
|
|
@@ -236,7 +230,7 @@ tool_result_end
|
|
|
236
230
|
interface SubagentDetails {
|
|
237
231
|
// Execution metadata
|
|
238
232
|
mode: "single" | "parallel"; // one task vs multiple parallel tasks
|
|
239
|
-
delegationMode: "spawn"
|
|
233
|
+
delegationMode: "spawn"; // always "spawn" (kept for backward-compatible serialization)
|
|
240
234
|
projectAgentsDir: string | null; // path to .pi/agents/ dir if used
|
|
241
235
|
|
|
242
236
|
// Individual agent results (one per task)
|
package/agents/code-architect.md
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: code-architect
|
|
3
3
|
description: Technical design agent for shaping implementations, APIs, module boundaries, and tradeoffs before coding. Use this agent for plans and architecture decisions.
|
|
4
|
-
thinking: high
|
|
5
|
-
tools: read,bash,grep,find,ls
|
|
6
4
|
---
|
|
7
5
|
|
|
8
6
|
You are a senior software architect focused on practical design.
|
package/agents/code-reviwer.md
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: code-reviwer
|
|
3
3
|
description: Code review specialist for finding bugs, regressions, edge cases, and maintainability issues. Use this agent to review code, plans, or patches.
|
|
4
|
-
thinking: high
|
|
5
|
-
tools: read,bash,grep,find,ls
|
|
6
4
|
---
|
|
7
5
|
|
|
8
6
|
You are a skeptical, detail-oriented code reviewer.
|
package/agents/code-writer.md
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: code-writer
|
|
3
3
|
description: Focused implementation agent for writing and refactoring code with small, reliable diffs. Use this agent when you want code changes made directly.
|
|
4
|
-
thinking: medium
|
|
5
|
-
tools: read,bash,edit,write
|
|
6
4
|
---
|
|
7
5
|
|
|
8
6
|
You are a pragmatic software engineer focused on implementation.
|
package/index.ts
CHANGED
|
@@ -13,10 +13,6 @@
|
|
|
13
13
|
* The tool always accepts a `tasks` array:
|
|
14
14
|
* - One task: treated as a single-agent delegation.
|
|
15
15
|
* - Multiple tasks: treated as a parallel delegation.
|
|
16
|
-
*
|
|
17
|
-
* And two context modes:
|
|
18
|
-
* - spawn (default): child gets only the task prompt.
|
|
19
|
-
* - fork: child gets a forked snapshot of current session context + task prompt.
|
|
20
16
|
*/
|
|
21
17
|
|
|
22
18
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
@@ -33,7 +29,6 @@ import {
|
|
|
33
29
|
type SubagentDetails,
|
|
34
30
|
DEFAULT_DELEGATION_MODE,
|
|
35
31
|
buildSubagentDetails,
|
|
36
|
-
emptyUsage,
|
|
37
32
|
getFinalOutput,
|
|
38
33
|
isResultError,
|
|
39
34
|
} from "./types.js";
|
|
@@ -65,7 +60,7 @@ const TaskItem = Type.Object({
|
|
|
65
60
|
}),
|
|
66
61
|
task: Type.String({
|
|
67
62
|
description:
|
|
68
|
-
"Task description for this delegated run.
|
|
63
|
+
"Task description for this delegated run. Include all required context; the subagent receives only this prompt.",
|
|
69
64
|
}),
|
|
70
65
|
cwd: Type.Optional(
|
|
71
66
|
Type.String({ description: "Working directory for this agent's process" }),
|
|
@@ -78,13 +73,6 @@ const SubagentParams = Type.Object({
|
|
|
78
73
|
description:
|
|
79
74
|
"Array of {agent, task} objects. One task behaves like a single-agent delegation; multiple tasks run concurrently.",
|
|
80
75
|
}),
|
|
81
|
-
mode: Type.Optional(
|
|
82
|
-
Type.String({
|
|
83
|
-
description:
|
|
84
|
-
"Context mode for delegated runs. 'spawn' (default) sends only the task prompt (best for isolated, reproducible runs with lower token/cost and less context leakage). 'fork' adds a snapshot of current session context plus task prompt (best for follow-up work, but usually higher token/cost and may include sensitive context).",
|
|
85
|
-
default: DEFAULT_DELEGATION_MODE,
|
|
86
|
-
}),
|
|
87
|
-
),
|
|
88
76
|
});
|
|
89
77
|
|
|
90
78
|
// ---------------------------------------------------------------------------
|
|
@@ -99,34 +87,6 @@ interface DelegationDepthConfig {
|
|
|
99
87
|
preventCycles: boolean;
|
|
100
88
|
}
|
|
101
89
|
|
|
102
|
-
interface SessionSnapshotSource {
|
|
103
|
-
getHeader: () => unknown;
|
|
104
|
-
getBranch: () => unknown[];
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function parseDelegationMode(raw: unknown): DelegationMode | null {
|
|
108
|
-
if (raw === undefined) return DEFAULT_DELEGATION_MODE;
|
|
109
|
-
if (typeof raw !== "string") return null;
|
|
110
|
-
const normalized = raw.trim().toLowerCase();
|
|
111
|
-
if (normalized === "spawn" || normalized === "fork") {
|
|
112
|
-
return normalized;
|
|
113
|
-
}
|
|
114
|
-
return null;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function buildForkSessionSnapshotJsonl(
|
|
118
|
-
sessionManager: SessionSnapshotSource,
|
|
119
|
-
): string | null {
|
|
120
|
-
const header = sessionManager.getHeader();
|
|
121
|
-
if (!header || typeof header !== "object") return null;
|
|
122
|
-
|
|
123
|
-
const branchEntries = sessionManager.getBranch();
|
|
124
|
-
const lines = [JSON.stringify(header)];
|
|
125
|
-
for (const entry of branchEntries) lines.push(JSON.stringify(entry));
|
|
126
|
-
return `${lines.join("\n")}\n`;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
|
|
130
90
|
function parseBoolean(raw: unknown): boolean | null {
|
|
131
91
|
if (typeof raw === "boolean") return raw;
|
|
132
92
|
if (typeof raw !== "string") return null;
|
|
@@ -496,22 +456,18 @@ ${agentList}
|
|
|
496
456
|
|
|
497
457
|
Each subagent runs in an **isolated process**.
|
|
498
458
|
|
|
499
|
-
Context behavior is controlled by optional 'mode':
|
|
500
|
-
- 'spawn' (default): child receives only the provided task prompt. Best for isolated, reproducible tasks with lower token/cost and less context leakage.
|
|
501
|
-
- 'fork': child receives a forked snapshot of current session context plus the task prompt. Best for follow-up tasks that rely on prior context; usually higher token/cost and may include sensitive context.
|
|
502
|
-
|
|
503
459
|
The tool always accepts a \`tasks\` array:
|
|
504
460
|
- one item = single-agent delegation
|
|
505
461
|
- multiple items = parallel delegation
|
|
506
462
|
|
|
507
463
|
**Single-task delegation**:
|
|
508
464
|
\`\`\`json
|
|
509
|
-
{ "tasks": [{ "agent": "agent-name", "task": "Detailed task..." }]
|
|
465
|
+
{ "tasks": [{ "agent": "agent-name", "task": "Detailed task..." }] }
|
|
510
466
|
\`\`\`
|
|
511
467
|
|
|
512
468
|
**Multi-task delegation**:
|
|
513
469
|
\`\`\`json
|
|
514
|
-
{ "tasks": [{ "agent": "agent-name", "task": "..." }, { "agent": "other-agent", "task": "..." }]
|
|
470
|
+
{ "tasks": [{ "agent": "agent-name", "task": "..." }, { "agent": "other-agent", "task": "..." }] }
|
|
515
471
|
\`\`\`
|
|
516
472
|
|
|
517
473
|
### Runtime delegation guards
|
|
@@ -541,14 +497,8 @@ The tool always accepts a \`tasks\` array:
|
|
|
541
497
|
" - one task: single-agent delegation",
|
|
542
498
|
" - multiple tasks: parallel delegation",
|
|
543
499
|
"",
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
" Best for isolated/reproducible work; lower token/cost and less context leakage.",
|
|
547
|
-
" mode: \"fork\" -> child gets current session context + your task prompt.",
|
|
548
|
-
" Best for follow-up work that depends on prior context; higher token/cost and may include sensitive context.",
|
|
549
|
-
"",
|
|
550
|
-
'Example single: { tasks: [{ agent: "writer", task: "Rewrite README.md" }], mode: "spawn" }',
|
|
551
|
-
'Example parallel: { tasks: [{ agent: "writer", task: "..." }, { agent: "tester", task: "..." }], mode: "fork" }',
|
|
500
|
+
'Example single: { tasks: [{ agent: "writer", task: "Rewrite README.md" }] }',
|
|
501
|
+
'Example parallel: { tasks: [{ agent: "writer", task: "..." }, { agent: "tester", task: "..." }] }',
|
|
552
502
|
].join("\n"),
|
|
553
503
|
parameters: SubagentParams,
|
|
554
504
|
|
|
@@ -557,48 +507,11 @@ The tool always accepts a \`tasks\` array:
|
|
|
557
507
|
const discovery = discoverAgents(ctx.cwd, "both");
|
|
558
508
|
const { agents } = discovery;
|
|
559
509
|
|
|
560
|
-
const delegationMode = parseDelegationMode(params.mode);
|
|
561
|
-
if (!delegationMode) {
|
|
562
|
-
const fallbackDetails = makeDetailsFactory(
|
|
563
|
-
discovery.projectAgentsDir,
|
|
564
|
-
DEFAULT_DELEGATION_MODE,
|
|
565
|
-
);
|
|
566
|
-
return {
|
|
567
|
-
content: [
|
|
568
|
-
{
|
|
569
|
-
type: "text",
|
|
570
|
-
text: `Invalid mode \"${String(params.mode)}\". Expected \"spawn\" or \"fork\".\nAvailable agents: ${formatAgentNames(agents)}`,
|
|
571
|
-
},
|
|
572
|
-
],
|
|
573
|
-
details: fallbackDetails("single")([]),
|
|
574
|
-
isError: true,
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
|
|
578
510
|
const makeDetails = makeDetailsFactory(
|
|
579
511
|
discovery.projectAgentsDir,
|
|
580
|
-
|
|
512
|
+
DEFAULT_DELEGATION_MODE,
|
|
581
513
|
);
|
|
582
514
|
|
|
583
|
-
let forkSessionSnapshotJsonl: string | undefined;
|
|
584
|
-
if (delegationMode === "fork") {
|
|
585
|
-
forkSessionSnapshotJsonl = buildForkSessionSnapshotJsonl(
|
|
586
|
-
ctx.sessionManager,
|
|
587
|
-
);
|
|
588
|
-
if (!forkSessionSnapshotJsonl) {
|
|
589
|
-
return {
|
|
590
|
-
content: [
|
|
591
|
-
{
|
|
592
|
-
type: "text",
|
|
593
|
-
text: "Cannot use mode=\"fork\": failed to snapshot current session context.",
|
|
594
|
-
},
|
|
595
|
-
],
|
|
596
|
-
details: makeDetails("single")([]),
|
|
597
|
-
isError: true,
|
|
598
|
-
};
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
|
|
602
515
|
const tasks = params.tasks ?? [];
|
|
603
516
|
if (tasks.length === 0) {
|
|
604
517
|
return {
|
|
@@ -700,8 +613,6 @@ This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A)
|
|
|
700
613
|
task.agent,
|
|
701
614
|
task.task,
|
|
702
615
|
task.cwd,
|
|
703
|
-
delegationMode,
|
|
704
|
-
forkSessionSnapshotJsonl,
|
|
705
616
|
agents,
|
|
706
617
|
ctx.cwd,
|
|
707
618
|
signal,
|
|
@@ -714,8 +625,6 @@ This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A)
|
|
|
714
625
|
|
|
715
626
|
return await executeParallel(
|
|
716
627
|
tasks,
|
|
717
|
-
delegationMode,
|
|
718
|
-
forkSessionSnapshotJsonl,
|
|
719
628
|
agents,
|
|
720
629
|
ctx.cwd,
|
|
721
630
|
signal,
|
|
@@ -750,8 +659,6 @@ This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A)
|
|
|
750
659
|
agentName: string,
|
|
751
660
|
task: string,
|
|
752
661
|
cwd: string | undefined,
|
|
753
|
-
delegationMode: DelegationMode,
|
|
754
|
-
forkSessionSnapshotJsonl: string | undefined,
|
|
755
662
|
agents: AgentConfig[],
|
|
756
663
|
defaultCwd: string,
|
|
757
664
|
signal: AbortSignal | undefined,
|
|
@@ -768,8 +675,6 @@ This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A)
|
|
|
768
675
|
agentName,
|
|
769
676
|
task,
|
|
770
677
|
taskCwd: cwd,
|
|
771
|
-
delegationMode,
|
|
772
|
-
forkSessionSnapshotJsonl,
|
|
773
678
|
parentDepth: currentDepth,
|
|
774
679
|
parentAgentStack: ancestorAgentStack,
|
|
775
680
|
maxDepth,
|
|
@@ -786,8 +691,6 @@ This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A)
|
|
|
786
691
|
agentName,
|
|
787
692
|
task,
|
|
788
693
|
taskCwd: cwd,
|
|
789
|
-
delegationMode,
|
|
790
|
-
forkSessionSnapshotJsonl,
|
|
791
694
|
parentDepth: currentDepth,
|
|
792
695
|
parentAgentStack: ancestorAgentStack,
|
|
793
696
|
maxDepth,
|
|
@@ -827,8 +730,6 @@ This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A)
|
|
|
827
730
|
|
|
828
731
|
async function executeParallel(
|
|
829
732
|
tasks: Array<{ agent: string; task: string; cwd?: string }>,
|
|
830
|
-
delegationMode: DelegationMode,
|
|
831
|
-
forkSessionSnapshotJsonl: string | undefined,
|
|
832
733
|
agents: AgentConfig[],
|
|
833
734
|
defaultCwd: string,
|
|
834
735
|
signal: AbortSignal | undefined,
|
|
@@ -840,8 +741,6 @@ This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A)
|
|
|
840
741
|
if (subagentMode === "sdk") {
|
|
841
742
|
return executeParallelSameProcess(
|
|
842
743
|
tasks,
|
|
843
|
-
delegationMode,
|
|
844
|
-
forkSessionSnapshotJsonl,
|
|
845
744
|
agents,
|
|
846
745
|
defaultCwd,
|
|
847
746
|
currentDepth,
|
|
@@ -857,8 +756,6 @@ This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A)
|
|
|
857
756
|
}
|
|
858
757
|
return executeParallelSubprocess(
|
|
859
758
|
tasks,
|
|
860
|
-
delegationMode,
|
|
861
|
-
forkSessionSnapshotJsonl,
|
|
862
759
|
agents,
|
|
863
760
|
defaultCwd,
|
|
864
761
|
currentDepth,
|
package/package.json
CHANGED
package/render.ts
CHANGED
|
@@ -5,13 +5,11 @@
|
|
|
5
5
|
import * as os from "node:os";
|
|
6
6
|
import { Container, Spacer, Text } from "@mariozechner/pi-tui";
|
|
7
7
|
import {
|
|
8
|
-
type DelegationMode,
|
|
9
8
|
type LiveLogEntry,
|
|
10
9
|
type NestedSubagentResult,
|
|
11
10
|
type SingleResult,
|
|
12
11
|
type SubagentDetails,
|
|
13
12
|
type UsageStats,
|
|
14
|
-
DEFAULT_DELEGATION_MODE,
|
|
15
13
|
aggregateUsage,
|
|
16
14
|
getDisplayItems,
|
|
17
15
|
getFinalOutput,
|
|
@@ -35,6 +33,7 @@ interface TreeNode {
|
|
|
35
33
|
children: TreeNode[];
|
|
36
34
|
}
|
|
37
35
|
|
|
36
|
+
|
|
38
37
|
interface TreeCounts {
|
|
39
38
|
total: number;
|
|
40
39
|
running: number;
|
|
@@ -45,7 +44,6 @@ interface TreeCounts {
|
|
|
45
44
|
|
|
46
45
|
interface PendingSubagentCall {
|
|
47
46
|
toolCallId: string;
|
|
48
|
-
mode: DelegationMode;
|
|
49
47
|
tasks: Array<{ agent: string; task?: string }>;
|
|
50
48
|
}
|
|
51
49
|
|
|
@@ -83,10 +81,6 @@ function truncate(text: string, maxLen: number): string {
|
|
|
83
81
|
return text.length > maxLen ? `${text.slice(0, maxLen)}...` : text;
|
|
84
82
|
}
|
|
85
83
|
|
|
86
|
-
function normalizeDelegationMode(raw: unknown): DelegationMode {
|
|
87
|
-
return raw === "fork" ? "fork" : DEFAULT_DELEGATION_MODE;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
84
|
function splitOutputLines(text: string): string[] {
|
|
91
85
|
const lines = text.replace(/\r\n?/g, "\n").split("\n");
|
|
92
86
|
if (lines.length > 1 && lines[lines.length - 1] === "") lines.pop();
|
|
@@ -166,7 +160,6 @@ function extractPendingSubagentCalls(messages: SingleResult["messages"]): Pendin
|
|
|
166
160
|
: typeof part.id === "string"
|
|
167
161
|
? part.id
|
|
168
162
|
: `${messageIndex}:${partIndex}`,
|
|
169
|
-
mode: normalizeDelegationMode((args as any).mode),
|
|
170
163
|
tasks,
|
|
171
164
|
});
|
|
172
165
|
}
|
|
@@ -178,14 +171,13 @@ function buildPendingNodes(call: PendingSubagentCall): TreeNode[] {
|
|
|
178
171
|
return call.tasks.map((task) => ({
|
|
179
172
|
label: task.agent,
|
|
180
173
|
status: "running",
|
|
181
|
-
meta: call.mode === "fork" ? "fork" : "spawn",
|
|
182
174
|
task: task.task,
|
|
183
175
|
children: [],
|
|
184
176
|
}));
|
|
185
177
|
}
|
|
186
178
|
|
|
187
179
|
function buildNodesFromNestedResult(nested: NestedSubagentResult): TreeNode[] {
|
|
188
|
-
return nested.details.results.map((result) => buildResultNode(result
|
|
180
|
+
return nested.details.results.map((result) => buildResultNode(result));
|
|
189
181
|
}
|
|
190
182
|
|
|
191
183
|
function buildNestedChildren(result: SingleResult): TreeNode[] {
|
|
@@ -292,10 +284,10 @@ function buildLeafPreview(result: SingleResult): string[] | undefined {
|
|
|
292
284
|
return unique.length > 0 ? unique.slice(-OUTPUT_PREVIEW_LINE_COUNT) : undefined;
|
|
293
285
|
}
|
|
294
286
|
|
|
295
|
-
function buildResultNode(result: SingleResult
|
|
287
|
+
function buildResultNode(result: SingleResult): TreeNode {
|
|
296
288
|
const status = statusFromResult(result);
|
|
297
289
|
const usage = formatUsage(result.usage, result.model);
|
|
298
|
-
const metaParts: string[] = [result.agentSource
|
|
290
|
+
const metaParts: string[] = [result.agentSource];
|
|
299
291
|
if (usage) metaParts.push(usage);
|
|
300
292
|
if (status === "error") {
|
|
301
293
|
const errorText = result.errorMessage || result.stderr || result.stopReason;
|
|
@@ -316,7 +308,7 @@ function buildResultNode(result: SingleResult, delegationMode: DelegationMode):
|
|
|
316
308
|
}
|
|
317
309
|
|
|
318
310
|
function buildTopLevelNodes(details: SubagentDetails): TreeNode[] {
|
|
319
|
-
return details.results.map((result) => buildResultNode(result
|
|
311
|
+
return details.results.map((result) => buildResultNode(result));
|
|
320
312
|
}
|
|
321
313
|
|
|
322
314
|
function renderTreeLines(
|
|
@@ -374,10 +366,9 @@ function topLevelSummary(details: SubagentDetails, counts: TreeCounts): string {
|
|
|
374
366
|
// ---------------------------------------------------------------------------
|
|
375
367
|
|
|
376
368
|
export function renderCall(args: Record<string, any>, theme: { fg: ThemeFg; bold: (s: string) => string }): Text {
|
|
377
|
-
const delegationMode = normalizeDelegationMode(args.mode);
|
|
378
369
|
const tasks = Array.isArray(args.tasks) ? args.tasks : [];
|
|
379
370
|
const count = tasks.length;
|
|
380
|
-
let text = `${theme.fg("toolTitle", theme.bold("subagent "))}${theme.fg("
|
|
371
|
+
let text = `${theme.fg("toolTitle", theme.bold("subagent "))}${theme.fg("accent", `${count} task${count === 1 ? "" : "s"}`)}`;
|
|
381
372
|
for (const task of tasks.slice(0, 6)) {
|
|
382
373
|
const agent = typeof task?.agent === "string" ? task.agent : "...";
|
|
383
374
|
const preview = typeof task?.task === "string" ? ` ${truncate(task.task, 56)}` : "";
|
|
@@ -414,7 +405,7 @@ export function renderResult(
|
|
|
414
405
|
const container = new Container();
|
|
415
406
|
container.addChild(
|
|
416
407
|
new Text(
|
|
417
|
-
`${icon} ${theme.fg("toolTitle", theme.bold("subagent tree "))}${theme.fg("
|
|
408
|
+
`${icon} ${theme.fg("toolTitle", theme.bold("subagent tree "))}${theme.fg("dim", topLevelSummary(details, counts))}`,
|
|
418
409
|
0,
|
|
419
410
|
0,
|
|
420
411
|
),
|
package/runner-sdk.ts
CHANGED
|
@@ -8,8 +8,6 @@
|
|
|
8
8
|
* read at the root level exactly as before — they're just propagated to nested
|
|
9
9
|
* sessions via closure parameters rather than via subprocess env inheritance.
|
|
10
10
|
*
|
|
11
|
-
* Fork mode still writes a JSONL snapshot to a temp file; the child session
|
|
12
|
-
* opens it with SessionManager.open() instead of receiving it via --session.
|
|
13
11
|
*/
|
|
14
12
|
|
|
15
13
|
import {
|
|
@@ -26,12 +24,8 @@ import {
|
|
|
26
24
|
createLsTool,
|
|
27
25
|
type ToolDefinition,
|
|
28
26
|
} from "@mariozechner/pi-coding-agent";
|
|
29
|
-
import * as fs from "node:fs";
|
|
30
|
-
import * as os from "node:os";
|
|
31
|
-
import * as path from "node:path";
|
|
32
27
|
import type { AgentConfig } from "./agents.js";
|
|
33
28
|
import {
|
|
34
|
-
type DelegationMode,
|
|
35
29
|
type LiveLogEntry,
|
|
36
30
|
type SingleResult,
|
|
37
31
|
type SubagentDetails,
|
|
@@ -60,26 +54,6 @@ import {
|
|
|
60
54
|
// ---------------------------------------------------------------------------
|
|
61
55
|
|
|
62
56
|
|
|
63
|
-
function writeForkSessionToTempFile(
|
|
64
|
-
agentName: string,
|
|
65
|
-
sessionJsonl: string,
|
|
66
|
-
): { dir: string; filePath: string } {
|
|
67
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
|
|
68
|
-
const safeName = agentName.replace(/[^\w.-]+/g, "_");
|
|
69
|
-
const filePath = path.join(tmpDir, `fork-${safeName}.jsonl`);
|
|
70
|
-
fs.writeFileSync(filePath, sessionJsonl, { encoding: "utf-8", mode: 0o600 });
|
|
71
|
-
return { dir: tmpDir, filePath };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function cleanupTempDir(dir: string | null): void {
|
|
75
|
-
if (!dir) return;
|
|
76
|
-
try {
|
|
77
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
78
|
-
} catch {
|
|
79
|
-
/* ignore */
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
57
|
/**
|
|
84
58
|
* Build the tool list for a child agent.
|
|
85
59
|
* When the agent config restricts tools, only those are included.
|
|
@@ -127,9 +101,6 @@ export interface RunAgentSameProcessOptions {
|
|
|
127
101
|
agentName: string;
|
|
128
102
|
task: string;
|
|
129
103
|
taskCwd?: string;
|
|
130
|
-
delegationMode: DelegationMode;
|
|
131
|
-
/** JSONL session snapshot for fork mode — same format as subprocess runner */
|
|
132
|
-
forkSessionSnapshotJsonl?: string;
|
|
133
104
|
parentDepth: number;
|
|
134
105
|
parentAgentStack: string[];
|
|
135
106
|
maxDepth: number;
|
|
@@ -163,8 +134,6 @@ export async function runAgentSameProcess(
|
|
|
163
134
|
agentName,
|
|
164
135
|
task,
|
|
165
136
|
taskCwd,
|
|
166
|
-
delegationMode,
|
|
167
|
-
forkSessionSnapshotJsonl,
|
|
168
137
|
parentDepth,
|
|
169
138
|
parentAgentStack,
|
|
170
139
|
maxDepth,
|
|
@@ -194,28 +163,6 @@ export async function runAgentSameProcess(
|
|
|
194
163
|
};
|
|
195
164
|
}
|
|
196
165
|
|
|
197
|
-
if (
|
|
198
|
-
delegationMode === "fork" &&
|
|
199
|
-
(!forkSessionSnapshotJsonl || !forkSessionSnapshotJsonl.trim())
|
|
200
|
-
) {
|
|
201
|
-
return {
|
|
202
|
-
agent: agentName,
|
|
203
|
-
agentSource: agent.source,
|
|
204
|
-
task,
|
|
205
|
-
exitCode: 1,
|
|
206
|
-
messages: [],
|
|
207
|
-
stderr: "Cannot run in fork mode: missing parent session snapshot context.",
|
|
208
|
-
usage: emptyUsage(),
|
|
209
|
-
toolCalls: {},
|
|
210
|
-
model: agent.model,
|
|
211
|
-
stopReason: "error",
|
|
212
|
-
errorMessage: "Cannot run in fork mode: missing parent session snapshot context.",
|
|
213
|
-
completedTurns: 0,
|
|
214
|
-
turnInProgress: false,
|
|
215
|
-
liveLog: [],
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
|
|
219
166
|
const childDepth = Math.max(0, Math.floor(parentDepth)) + 1;
|
|
220
167
|
const childStack = [...parentAgentStack, agentName];
|
|
221
168
|
const effectiveCwd = taskCwd ?? cwd;
|
|
@@ -243,16 +190,6 @@ export async function runAgentSameProcess(
|
|
|
243
190
|
|
|
244
191
|
emitUpdate();
|
|
245
192
|
|
|
246
|
-
// Fork mode: write JSONL to a temp file; opened via SessionManager.open()
|
|
247
|
-
// (same serialisation format as the subprocess runner — backward-compatible)
|
|
248
|
-
let forkTmpDir: string | null = null;
|
|
249
|
-
let forkTmpPath: string | null = null;
|
|
250
|
-
if (delegationMode === "fork" && forkSessionSnapshotJsonl) {
|
|
251
|
-
const tmp = writeForkSessionToTempFile(agentName, forkSessionSnapshotJsonl);
|
|
252
|
-
forkTmpDir = tmp.dir;
|
|
253
|
-
forkTmpPath = tmp.filePath;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
193
|
const childCtx: SubagentSessionContext = {
|
|
257
194
|
depth: childDepth,
|
|
258
195
|
maxDepth,
|
|
@@ -284,9 +221,7 @@ export async function runAgentSameProcess(
|
|
|
284
221
|
// Thinking level from agent config (same string values as --thinking flag)
|
|
285
222
|
const thinkingLevel = agent.thinking as any | undefined;
|
|
286
223
|
|
|
287
|
-
const sessionManager =
|
|
288
|
-
? SessionManager.open(forkTmpPath)
|
|
289
|
-
: SessionManager.inMemory();
|
|
224
|
+
const sessionManager = SessionManager.inMemory();
|
|
290
225
|
|
|
291
226
|
const { session } = await subagentContext.run(childCtx, () =>
|
|
292
227
|
createAgentSession({
|
|
@@ -407,8 +342,6 @@ export async function runAgentSameProcess(
|
|
|
407
342
|
result.errorMessage = result.errorMessage ?? msg;
|
|
408
343
|
if (!result.stderr.trim()) result.stderr = msg;
|
|
409
344
|
return result;
|
|
410
|
-
} finally {
|
|
411
|
-
cleanupTempDir(forkTmpDir);
|
|
412
345
|
}
|
|
413
346
|
}
|
|
414
347
|
|
|
@@ -418,8 +351,6 @@ export async function runAgentSameProcess(
|
|
|
418
351
|
|
|
419
352
|
export async function executeParallelSameProcess(
|
|
420
353
|
tasks: Array<{ agent: string; task: string; cwd?: string }>,
|
|
421
|
-
delegationMode: DelegationMode,
|
|
422
|
-
forkSessionSnapshotJsonl: string | undefined,
|
|
423
354
|
agents: AgentConfig[],
|
|
424
355
|
defaultCwd: string,
|
|
425
356
|
depth: number,
|
|
@@ -509,8 +440,6 @@ export async function executeParallelSameProcess(
|
|
|
509
440
|
agentName: t.agent,
|
|
510
441
|
task: t.task,
|
|
511
442
|
taskCwd: t.cwd,
|
|
512
|
-
delegationMode,
|
|
513
|
-
forkSessionSnapshotJsonl,
|
|
514
443
|
parentDepth: depth,
|
|
515
444
|
parentAgentStack: stack,
|
|
516
445
|
maxDepth,
|
package/runner.ts
CHANGED
|
@@ -12,12 +12,10 @@ import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
|
|
12
12
|
import type { Message } from "@mariozechner/pi-ai";
|
|
13
13
|
import type { AgentConfig } from "./agents.js";
|
|
14
14
|
import {
|
|
15
|
-
type DelegationMode,
|
|
16
15
|
type LiveLogEntry,
|
|
17
16
|
type SingleResult,
|
|
18
17
|
type SubagentDetails,
|
|
19
18
|
MAX_LIVE_LOG_ENTRIES,
|
|
20
|
-
buildSubagentDetails,
|
|
21
19
|
emptyUsage,
|
|
22
20
|
extractToolCalls,
|
|
23
21
|
getFinalOutput,
|
|
@@ -74,17 +72,6 @@ function writePromptToTempFile(
|
|
|
74
72
|
return { dir: tmpDir, filePath };
|
|
75
73
|
}
|
|
76
74
|
|
|
77
|
-
function writeForkSessionToTempFile(
|
|
78
|
-
agentName: string,
|
|
79
|
-
sessionJsonl: string,
|
|
80
|
-
): { dir: string; filePath: string } {
|
|
81
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
|
|
82
|
-
const safeName = agentName.replace(/[^\w.-]+/g, "_");
|
|
83
|
-
const filePath = path.join(tmpDir, `fork-${safeName}.jsonl`);
|
|
84
|
-
fs.writeFileSync(filePath, sessionJsonl, { encoding: "utf-8", mode: 0o600 });
|
|
85
|
-
return { dir: tmpDir, filePath };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
75
|
function cleanupTempDir(dir: string | null): void {
|
|
89
76
|
if (!dir) return;
|
|
90
77
|
try {
|
|
@@ -363,8 +350,6 @@ function buildPiArgs(
|
|
|
363
350
|
agent: AgentConfig,
|
|
364
351
|
systemPromptPath: string | null,
|
|
365
352
|
task: string,
|
|
366
|
-
delegationMode: DelegationMode,
|
|
367
|
-
forkSessionPath: string | null,
|
|
368
353
|
): string[] {
|
|
369
354
|
const args: string[] = [
|
|
370
355
|
"--mode",
|
|
@@ -372,14 +357,9 @@ function buildPiArgs(
|
|
|
372
357
|
..._inheritedCliArgs.extensionArgs,
|
|
373
358
|
..._inheritedCliArgs.alwaysProxy,
|
|
374
359
|
"-p",
|
|
360
|
+
"--no-session",
|
|
375
361
|
];
|
|
376
362
|
|
|
377
|
-
if (delegationMode === "spawn") {
|
|
378
|
-
args.push("--no-session");
|
|
379
|
-
} else if (forkSessionPath) {
|
|
380
|
-
args.push("--session", forkSessionPath);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
363
|
// Agent config takes priority; fall back to parent CLI value
|
|
384
364
|
const model = agent.model ?? _inheritedCliArgs.fallbackModel;
|
|
385
365
|
if (model) args.push("--model", model);
|
|
@@ -425,10 +405,6 @@ export interface RunAgentOptions {
|
|
|
425
405
|
task: string;
|
|
426
406
|
/** Optional override working directory. */
|
|
427
407
|
taskCwd?: string;
|
|
428
|
-
/** Context mode: spawn (fresh) or fork (session snapshot + task). */
|
|
429
|
-
delegationMode: DelegationMode;
|
|
430
|
-
/** Serialized parent session snapshot used when delegationMode is "fork". */
|
|
431
|
-
forkSessionSnapshotJsonl?: string;
|
|
432
408
|
/** Current delegation depth of the caller process. */
|
|
433
409
|
parentDepth: number;
|
|
434
410
|
/** Delegation stack from the caller process (ancestor agent names). */
|
|
@@ -457,8 +433,6 @@ export async function runAgentSubprocess(opts: RunAgentOptions): Promise<SingleR
|
|
|
457
433
|
agentName,
|
|
458
434
|
task,
|
|
459
435
|
taskCwd,
|
|
460
|
-
delegationMode,
|
|
461
|
-
forkSessionSnapshotJsonl,
|
|
462
436
|
parentDepth,
|
|
463
437
|
parentAgentStack,
|
|
464
438
|
maxDepth,
|
|
@@ -486,30 +460,6 @@ export async function runAgentSubprocess(opts: RunAgentOptions): Promise<SingleR
|
|
|
486
460
|
};
|
|
487
461
|
}
|
|
488
462
|
|
|
489
|
-
if (
|
|
490
|
-
delegationMode === "fork" &&
|
|
491
|
-
(!forkSessionSnapshotJsonl || !forkSessionSnapshotJsonl.trim())
|
|
492
|
-
) {
|
|
493
|
-
return {
|
|
494
|
-
agent: agentName,
|
|
495
|
-
agentSource: agent.source,
|
|
496
|
-
task,
|
|
497
|
-
exitCode: 1,
|
|
498
|
-
messages: [],
|
|
499
|
-
stderr:
|
|
500
|
-
"Cannot run in fork mode: missing parent session snapshot context.",
|
|
501
|
-
usage: emptyUsage(),
|
|
502
|
-
toolCalls: {},
|
|
503
|
-
model: agent.model,
|
|
504
|
-
stopReason: "error",
|
|
505
|
-
errorMessage:
|
|
506
|
-
"Cannot run in fork mode: missing parent session snapshot context.",
|
|
507
|
-
completedTurns: 0,
|
|
508
|
-
turnInProgress: false,
|
|
509
|
-
liveLog: [],
|
|
510
|
-
};
|
|
511
|
-
}
|
|
512
|
-
|
|
513
463
|
const result: SingleResult = {
|
|
514
464
|
agent: agentName,
|
|
515
465
|
agentSource: agent.source,
|
|
@@ -548,22 +498,11 @@ export async function runAgentSubprocess(opts: RunAgentOptions): Promise<SingleR
|
|
|
548
498
|
promptTmpPath = tmp.filePath;
|
|
549
499
|
}
|
|
550
500
|
|
|
551
|
-
// Write forked session snapshot if needed
|
|
552
|
-
let forkSessionTmpDir: string | null = null;
|
|
553
|
-
let forkSessionTmpPath: string | null = null;
|
|
554
|
-
if (delegationMode === "fork" && forkSessionSnapshotJsonl) {
|
|
555
|
-
const tmp = writeForkSessionToTempFile(agent.name, forkSessionSnapshotJsonl);
|
|
556
|
-
forkSessionTmpDir = tmp.dir;
|
|
557
|
-
forkSessionTmpPath = tmp.filePath;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
501
|
try {
|
|
561
502
|
const piArgs = buildPiArgs(
|
|
562
503
|
agent,
|
|
563
504
|
promptTmpPath,
|
|
564
505
|
task,
|
|
565
|
-
delegationMode,
|
|
566
|
-
forkSessionTmpPath,
|
|
567
506
|
);
|
|
568
507
|
let wasAborted = false;
|
|
569
508
|
|
|
@@ -761,7 +700,6 @@ export async function runAgentSubprocess(opts: RunAgentOptions): Promise<SingleR
|
|
|
761
700
|
return result;
|
|
762
701
|
} finally {
|
|
763
702
|
cleanupTempDir(promptTmpDir);
|
|
764
|
-
cleanupTempDir(forkSessionTmpDir);
|
|
765
703
|
}
|
|
766
704
|
}
|
|
767
705
|
|
|
@@ -774,8 +712,6 @@ export async function runAgentSubprocess(opts: RunAgentOptions): Promise<SingleR
|
|
|
774
712
|
|
|
775
713
|
export async function executeParallelSubprocess(
|
|
776
714
|
tasks: Array<{ agent: string; task: string; cwd?: string }>,
|
|
777
|
-
delegationMode: DelegationMode,
|
|
778
|
-
forkSessionSnapshotJsonl: string | undefined,
|
|
779
715
|
agents: AgentConfig[],
|
|
780
716
|
defaultCwd: string,
|
|
781
717
|
parentDepth: number,
|
|
@@ -865,8 +801,6 @@ export async function executeParallelSubprocess(
|
|
|
865
801
|
agentName: t.agent,
|
|
866
802
|
task: t.task,
|
|
867
803
|
taskCwd: t.cwd,
|
|
868
|
-
delegationMode,
|
|
869
|
-
forkSessionSnapshotJsonl,
|
|
870
804
|
parentDepth,
|
|
871
805
|
parentAgentStack,
|
|
872
806
|
maxDepth,
|
package/types.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import type { Message } from "@mariozechner/pi-ai";
|
|
6
6
|
|
|
7
7
|
/** Context mode for delegated runs. */
|
|
8
|
-
export type DelegationMode = "spawn"
|
|
8
|
+
export type DelegationMode = "spawn";
|
|
9
9
|
|
|
10
10
|
/** Default context mode for delegated runs. */
|
|
11
11
|
export const DEFAULT_DELEGATION_MODE: DelegationMode = "spawn";
|
|
@@ -224,7 +224,7 @@ export function isSubagentDetails(value: unknown): value is SubagentDetails {
|
|
|
224
224
|
if (!value || typeof value !== "object") return false;
|
|
225
225
|
const maybe = value as Partial<SubagentDetails>;
|
|
226
226
|
return (maybe.mode === "single" || maybe.mode === "parallel") &&
|
|
227
|
-
|
|
227
|
+
maybe.delegationMode === "spawn" &&
|
|
228
228
|
Array.isArray(maybe.results);
|
|
229
229
|
}
|
|
230
230
|
|