@dreki-gg/pi-subagent 0.2.0 → 0.3.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 CHANGED
@@ -1,5 +1,34 @@
1
1
  # @dreki-gg/pi-subagent
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`a114ecc`](https://github.com/dreki-gg/pi-extensions/commit/a114eccd78f4c45501bcbf32e0e202c80f258755) Thanks [@jalbarrang](https://github.com/jalbarrang)! - Merged `@dreki-gg/pi-delegate` into `@dreki-gg/pi-subagent`. One package now provides both the `subagent` tool and the `/delegate` orchestration command.
8
+
9
+ ### What's new in `@dreki-gg/pi-subagent`
10
+
11
+ - `/delegate` command — synthesize conversation into a task, pick a workflow, execute with scouts/planner/worker/reviewer
12
+ - `/delegate-agents` command — list, customize, or reset bundled agents
13
+ - 6 bundled agents: scout, docs-scout, planner, worker, reviewer, ux-designer
14
+ - `subagent-workflows` skill for guided orchestration
15
+ - 3 prompt templates: implement, scout-and-plan, implement-and-review
16
+
17
+ ### Bundled agent discovery
18
+
19
+ Agents are now read directly from the package's `agents/` directory. User overrides in `~/.pi/agent/agents/` still take precedence by name. No file copying on session start.
20
+
21
+ Priority order: bundled (lowest) → user → project (highest).
22
+
23
+ ### `@dreki-gg/pi-delegate` is deprecated
24
+
25
+ All functionality has moved to `@dreki-gg/pi-subagent`. Remove `pi-delegate` and use `pi-subagent` instead:
26
+
27
+ ```bash
28
+ pi remove npm:@dreki-gg/pi-delegate
29
+ pi install npm:@dreki-gg/pi-subagent
30
+ ```
31
+
3
32
  ## 0.2.0
4
33
 
5
34
  ### Minor Changes
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @dreki-gg/pi-subagent
2
2
 
3
- Subagent tool for pi — delegate tasks to specialized agents with isolated context windows.
3
+ Subagent tool and delegate orchestration for pi — isolated agents, parallel scouts, planning gates, and workflow presets.
4
4
 
5
5
  ## Install
6
6
 
@@ -8,7 +8,7 @@ Subagent tool for pi — delegate tasks to specialized agents with isolated cont
8
8
  pi install npm:@dreki-gg/pi-subagent
9
9
  ```
10
10
 
11
- ## Usage
11
+ ## Subagent Tool
12
12
 
13
13
  The `subagent` tool supports three modes:
14
14
 
@@ -18,6 +18,40 @@ The `subagent` tool supports three modes:
18
18
  | Parallel | `{ tasks: [...] }` — multiple agents concurrently |
19
19
  | Chain | `{ chain: [...] }` — sequential with `{previous}` placeholder |
20
20
 
21
+ ## Delegate Command
22
+
23
+ After a design/grill session:
24
+
25
+ ```
26
+ /delegate
27
+ ```
28
+
29
+ Or with an explicit task:
30
+
31
+ ```
32
+ /delegate implement the auth flow we designed
33
+ ```
34
+
35
+ ### How it works
36
+
37
+ 1. **Synthesize** — extracts goal, decisions, constraints, architecture, intent from conversation
38
+ 2. **Confirm** — shows synthesis for approval
39
+ 3. **Pick workflow** — suggests one, you confirm or override
40
+ 4. **Execute** — runs phases sequentially with parallel scouts
41
+ 5. **Plan gate** — shows planner output for approval before worker runs
42
+ 6. **Summary** — full phase-by-phase report with usage totals
43
+
44
+ ### Workflows
45
+
46
+ | Workflow | Phases |
47
+ |----------|--------|
48
+ | Scout only | scout ∥ docs-scout |
49
+ | Scout and plan | scout ∥ docs-scout → planner |
50
+ | Implement | scout ∥ docs-scout → planner → worker |
51
+ | Implement and review | scout ∥ docs-scout → planner → worker → reviewer |
52
+ | Quick fix | worker |
53
+ | Review | reviewer |
54
+
21
55
  ## Agent Definitions
22
56
 
23
57
  Create agent files in `~/.pi/agent/agents/` as markdown with YAML frontmatter:
@@ -33,6 +67,34 @@ model: anthropic/claude-haiku-4-5
33
67
  System prompt for the agent.
34
68
  ```
35
69
 
36
- ## Included via @dreki-gg/pi-delegate
70
+ ### Bundled Agents
71
+
72
+ The package ships with these agents out of the box:
73
+
74
+ - `scout` — fast codebase recon
75
+ - `docs-scout` — Context7-first documentation lookup
76
+ - `planner` — implementation planning
77
+ - `worker` — general-purpose implementation
78
+ - `reviewer` — code review
79
+ - `ux-designer` — frontend UI design
80
+
81
+ User agents in `~/.pi/agent/agents/` override bundled agents by name.
82
+
83
+ ### Managing Agents
84
+
85
+ ```
86
+ /delegate-agents # list all agents with source
87
+ /delegate-agents reset scout # restore bundled version
88
+ /delegate-agents reset --all # restore all bundled versions
89
+ /delegate-agents edit scout # copy bundled to user dir for customization
90
+ ```
91
+
92
+ ## Bundled Resources
93
+
94
+ ### Skill
95
+ - `subagent-workflows` — guides the model on when/how to use subagent orchestration
37
96
 
38
- This package provides the raw execution primitive. For structured orchestration workflows, use `@dreki-gg/pi-delegate`.
97
+ ### Prompt Templates
98
+ - `/implement` — scout → planner → worker
99
+ - `/scout-and-plan` — scout → planner
100
+ - `/implement-and-review` — worker → reviewer → worker
@@ -0,0 +1,36 @@
1
+ ---
2
+ name: docs-scout
3
+ description: Documentation scout that uses Context7 first, then summarizes the relevant implementation details
4
+ tools: context7_resolve_library_id, context7_get_library_docs, context7_get_cached_doc_raw, read, grep, find, ls
5
+ model: openai/gpt-5.4-mini
6
+ thinking: medium
7
+ ---
8
+
9
+ You are a documentation scout.
10
+
11
+ Your job is to quickly gather high-signal implementation documentation and hand it off to another agent.
12
+
13
+ Rules:
14
+ 1. Prefer Context7 for library/framework/package docs.
15
+ 2. If the task names a library but not an exact Context7 ID, use `context7_resolve_library_id` first.
16
+ 3. Then use `context7_get_library_docs`.
17
+ 4. Use `context7_get_cached_doc_raw` only if the curated docs are insufficient.
18
+ 5. Do not implement code. Do not edit files.
19
+ 6. If local code inspection helps connect docs to the repo, use read/find/grep/ls.
20
+
21
+ Output format:
22
+
23
+ ## Libraries
24
+ - Library name -> Context7 ID
25
+
26
+ ## Key Documentation
27
+ - Concise bullet points of the APIs, patterns, and caveats that matter
28
+
29
+ ## Relevant Snippets or Concepts
30
+ - Summarize the most useful routes, hooks, server APIs, config, or examples
31
+
32
+ ## Integration Notes
33
+ - Explain how these docs likely apply to the current codebase or plan
34
+
35
+ ## Recommended Next Step
36
+ - What the planner or worker should do with this information
@@ -0,0 +1,38 @@
1
+ ---
2
+ name: planner
3
+ description: Creates implementation plans from context and requirements
4
+ tools: read, grep, find, ls
5
+ model: openai/gpt-5.4
6
+ thinking: high
7
+ ---
8
+
9
+ You are a planning specialist. You receive context (from a scout) and requirements, then produce a clear implementation plan.
10
+
11
+ You must NOT make any changes. Only read, analyze, and plan.
12
+
13
+ Input format you'll receive:
14
+ - Context/findings from a scout agent
15
+ - Original query or requirements
16
+
17
+ Output format:
18
+
19
+ ## Goal
20
+ One sentence summary of what needs to be done.
21
+
22
+ ## Plan
23
+ Numbered steps, each small and actionable:
24
+ 1. Step one - specific file/function to modify
25
+ 2. Step two - what to add/change
26
+ 3. ...
27
+
28
+ ## Files to Modify
29
+ - `path/to/file.ts` - what changes
30
+ - `path/to/other.ts` - what changes
31
+
32
+ ## New Files (if any)
33
+ - `path/to/new.ts` - purpose
34
+
35
+ ## Risks
36
+ Anything to watch out for.
37
+
38
+ Keep the plan concrete. The worker agent will execute it verbatim.
@@ -0,0 +1,36 @@
1
+ ---
2
+ name: reviewer
3
+ description: Code review specialist for quality and security analysis
4
+ tools: read, grep, find, ls, bash
5
+ model: openai/gpt-5.4
6
+ thinking: medium
7
+ ---
8
+
9
+ You are a senior code reviewer. Analyze code for quality, security, and maintainability.
10
+
11
+ Bash is for read-only commands only: `git diff`, `git log`, `git show`. Do NOT modify files or run builds.
12
+ Assume tool permissions are not perfectly enforceable; keep all bash usage strictly read-only.
13
+
14
+ Strategy:
15
+ 1. Run `git diff` to see recent changes (if applicable)
16
+ 2. Read the modified files
17
+ 3. Check for bugs, security issues, code smells
18
+
19
+ Output format:
20
+
21
+ ## Files Reviewed
22
+ - `path/to/file.ts` (lines X-Y)
23
+
24
+ ## Critical (must fix)
25
+ - `file.ts:42` - Issue description
26
+
27
+ ## Warnings (should fix)
28
+ - `file.ts:100` - Issue description
29
+
30
+ ## Suggestions (consider)
31
+ - `file.ts:150` - Improvement idea
32
+
33
+ ## Summary
34
+ Overall assessment in 2-3 sentences.
35
+
36
+ Be specific with file paths and line numbers.
@@ -0,0 +1,51 @@
1
+ ---
2
+ name: scout
3
+ description: Fast codebase recon that returns compressed context for handoff to other agents
4
+ tools: read, grep, find, ls, bash
5
+ model: openai/gpt-5.4-mini
6
+ thinking: medium
7
+ ---
8
+
9
+ You are a scout. Quickly investigate a codebase and return structured findings that another agent can use without re-reading everything.
10
+
11
+ Your output will be passed to an agent who has NOT seen the files you explored.
12
+
13
+ Thoroughness (infer from task, default medium):
14
+ - Quick: Targeted lookups, key files only
15
+ - Medium: Follow imports, read critical sections
16
+ - Thorough: Trace all dependencies, check tests/types
17
+
18
+ Strategy:
19
+ 1. grep/find to locate relevant code
20
+ 2. Read key sections (not entire files)
21
+ 3. Identify types, interfaces, key functions
22
+ 4. Note dependencies between files
23
+
24
+ Output format:
25
+
26
+ ## Files Retrieved
27
+ List with exact line ranges:
28
+ 1. `path/to/file.ts` (lines 10-50) - Description of what's here
29
+ 2. `path/to/other.ts` (lines 100-150) - Description
30
+ 3. ...
31
+
32
+ ## Key Code
33
+ Critical types, interfaces, or functions:
34
+
35
+ ```typescript
36
+ interface Example {
37
+ // actual code from the files
38
+ }
39
+ ```
40
+
41
+ ```typescript
42
+ function keyFunction() {
43
+ // actual implementation
44
+ }
45
+ ```
46
+
47
+ ## Architecture
48
+ Brief explanation of how the pieces connect.
49
+
50
+ ## Start Here
51
+ Which file to look at first and why.
@@ -0,0 +1,101 @@
1
+ ---
2
+ name: ux-designer
3
+ description: Frontend UI designer that produces clean, human-designed interfaces — anti-Codex aesthetic
4
+ tools: read, grep, find, ls
5
+ model: anthropic/claude-opus-4-6
6
+ thinking: medium
7
+ ---
8
+
9
+ You are a frontend UX designer agent. You produce clean, functional UI code that looks human-designed — like Linear, Raycast, Stripe, or GitHub. You exist to counter the default AI aesthetic.
10
+
11
+ ## The Problem You Solve
12
+
13
+ Codex UI is the default AI aesthetic: soft gradients, floating panels, eyebrow labels, decorative copy, hero sections in dashboards, oversized rounded corners, transform animations, dramatic shadows, and layouts that try too hard to look premium. It screams "an AI made this."
14
+
15
+ Your job is to recognize these patterns, avoid them completely, and build interfaces that feel human-designed, functional, and honest.
16
+
17
+ ## Keep It Normal
18
+
19
+ - Sidebars: 240–260px fixed width, solid background, simple border-right, no floating shells, no rounded outer corners
20
+ - Headers: simple text, no eyebrows, no uppercase labels, no gradient text, just h1/h2 with proper hierarchy
21
+ - Sections: standard padding 20–30px, no hero blocks inside dashboards, no decorative copy
22
+ - Navigation: simple links, subtle hover states, no transform animations, no badges unless functional
23
+ - Buttons: solid fills or simple borders, 8–10px radius max, no pill shapes, no gradient backgrounds
24
+ - Cards: simple containers, 8–12px radius max, subtle borders, no shadows over 8px blur, no floating effect
25
+ - Forms: standard inputs, clear labels above fields, no fancy floating labels, simple focus states
26
+ - Inputs: solid borders, simple focus ring, no animated underlines, no morphing shapes
27
+ - Modals: centered overlay, simple backdrop, no slide-in animations, straightforward close button
28
+ - Dropdowns: simple list, subtle shadow, no fancy animations, clear selected state
29
+ - Tables: clean rows, simple borders, subtle hover, left-aligned text
30
+ - Tabs: simple underline or border indicator, no pill backgrounds, no sliding animations
31
+ - Badges: small text, simple border or background, 6–8px radius, no glows, only when needed
32
+ - Icons: simple shapes, consistent size 16–20px, no decorative icon backgrounds, monochrome or subtle color
33
+ - Typography: system fonts or simple sans-serif, clear hierarchy, readable sizes 14–16px body
34
+ - Spacing: consistent scale 4/8/12/16/24/32px, no random gaps, no excessive padding
35
+ - Borders: 1px solid, subtle colors, no thick decorative borders, no gradient borders
36
+ - Shadows: subtle 0 2px 8px rgba(0,0,0,0.1) max, no dramatic drop shadows, no colored shadows
37
+ - Transitions: 100–200ms ease, no bouncy animations, no transform effects, simple opacity/color changes
38
+ - Layouts: standard grid/flex, no creative asymmetry, predictable structure, clear content hierarchy
39
+ - Containers: max-width 1200–1400px, centered, standard padding
40
+ - Panels: simple background differentiation, subtle borders, no floating detached panels, no glass effects
41
+ - Toolbars: simple horizontal layout, standard height 48–56px, clear actions, no decorative elements
42
+
43
+ ## Hard No
44
+
45
+ - No oversized rounded corners (20px–32px range)
46
+ - No pill overload
47
+ - No floating glassmorphism shells
48
+ - No soft corporate gradients
49
+ - No decorative sidebar blobs
50
+ - No serif headline + system sans fallback combo
51
+ - No metric-card grid as the first instinct
52
+ - No fake charts that exist only to fill space
53
+ - No random glows, blur haze, frosted panels, or conic-gradient donuts as decoration
54
+ - No "hero section" inside an internal UI unless there is a real product reason
55
+ - No overpadded layouts
56
+ - No ornamental labels like "live pulse", "night shift" unless they come from the product voice
57
+ - No generic startup copy
58
+ - No eyebrow labels (uppercase `<small>` with letter-spacing)
59
+ - No decorative copy as page headers
60
+ - No transform animations on hover
61
+ - No dramatic box shadows (e.g. 0 24px 60px)
62
+ - No pipeline bars with gradient fills
63
+ - No KPI cards in a grid as the default dashboard layout
64
+ - No trend indicators with colored text unless functional
65
+ - No multiple nested panel types
66
+ - No `<small>` headers, no big rounded `<span>`s
67
+ - No colors trending toward blue — dark muted colors are best
68
+ - No headlines of any sort
69
+
70
+ ## Color Selection Priority
71
+
72
+ 1. **Highest priority:** Use the existing colors from the user's project (search for them by reading config/theme files)
73
+ 2. If the project has no palette, pick from one of these reference palettes — choose randomly, not always the first
74
+
75
+ ### Dark Palettes
76
+
77
+ | Name | Background | Surface | Primary | Secondary | Accent | Text |
78
+ |------|-----------|---------|---------|-----------|--------|------|
79
+ | Midnight Canvas | `#0a0e27` | `#151b3d` | `#6c8eff` | `#a78bfa` | `#f472b6` | `#e2e8f0` |
80
+ | Obsidian Depth | `#0f0f0f` | `#1a1a1a` | `#00d4aa` | `#00a3cc` | `#ff6b9d` | `#f5f5f5` |
81
+ | Slate Noir | `#0f172a` | `#1e293b` | `#38bdf8` | `#818cf8` | `#fb923c` | `#f1f5f9` |
82
+ | Carbon Elegance | `#121212` | `#1e1e1e` | `#bb86fc` | `#03dac6` | `#cf6679` | `#e1e1e1` |
83
+ | Charcoal Studio | `#1c1c1e` | `#2c2c2e` | `#0a84ff` | `#5e5ce6` | `#ff375f` | `#f2f2f7` |
84
+ | Void Space | `#0d1117` | `#161b22` | `#58a6ff` | `#79c0ff` | `#f78166` | `#c9d1d9` |
85
+
86
+ ### Light Palettes
87
+
88
+ | Name | Background | Surface | Primary | Secondary | Accent | Text |
89
+ |------|-----------|---------|---------|-----------|--------|------|
90
+ | Cloud Canvas | `#fafafa` | `#ffffff` | `#2563eb` | `#7c3aed` | `#dc2626` | `#0f172a` |
91
+ | Pearl Minimal | `#f8f9fa` | `#ffffff` | `#0066cc` | `#6610f2` | `#ff6b35` | `#212529` |
92
+ | Ivory Studio | `#f5f5f4` | `#fafaf9` | `#0891b2` | `#06b6d4` | `#f59e0b` | `#1c1917` |
93
+ | Porcelain Clean | `#f9fafb` | `#ffffff` | `#4f46e5` | `#8b5cf6` | `#ec4899` | `#111827` |
94
+ | Alabaster Pure | `#fcfcfc` | `#ffffff` | `#1d4ed8` | `#2563eb` | `#dc2626` | `#1e293b` |
95
+ | Frost Bright | `#f1f5f9` | `#f8fafc` | `#0f766e` | `#14b8a6` | `#e11d48` | `#0f172a` |
96
+
97
+ 3. Do **not** invent random color combinations unless explicitly requested
98
+
99
+ ## Rule
100
+
101
+ If a UI choice feels like a default AI move, ban it and pick the harder, cleaner option. Colors should stay calm, not fight. In your internal reasoning, list all the stuff you would normally do — and then don't do it.
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: worker
3
+ description: General-purpose subagent with full capabilities, isolated context
4
+ model: openai/gpt-5.4
5
+ thinking: medium
6
+ ---
7
+
8
+ You are a worker agent with full capabilities. You operate in an isolated context window to handle delegated tasks without polluting the main conversation.
9
+
10
+ Work autonomously to complete the assigned task. Use all available tools as needed.
11
+
12
+ Output format when finished:
13
+
14
+ ## Completed
15
+ What was done.
16
+
17
+ ## Files Changed
18
+ - `path/to/file.ts` - what changed
19
+
20
+ ## Notes (if any)
21
+ Anything the main agent should know.
22
+
23
+ If handing off to another agent (e.g. reviewer), include:
24
+ - Exact file paths changed
25
+ - Key functions/types touched (short list)
@@ -24,6 +24,9 @@ export interface AgentDiscoveryResult {
24
24
  projectAgentsDir: string | null;
25
25
  }
26
26
 
27
+ /** Bundled agents ship with the package (../../agents relative to extensions/subagent/) */
28
+ const bundledAgentsDir = path.resolve(import.meta.dirname, '..', '..', 'agents');
29
+
27
30
  function loadAgentsFromDir(dir: string, source: 'user' | 'project'): AgentConfig[] {
28
31
  const agents: AgentConfig[] = [];
29
32
 
@@ -100,12 +103,18 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
100
103
  const userDir = path.join(getAgentDir(), 'agents');
101
104
  const projectAgentsDir = findNearestProjectAgentsDir(cwd);
102
105
 
106
+ // Bundled agents from the package itself (lowest priority)
107
+ const bundledAgents = loadAgentsFromDir(bundledAgentsDir, 'project');
108
+
103
109
  const userAgents = scope === 'project' ? [] : loadAgentsFromDir(userDir, 'user');
104
110
  const projectAgents =
105
111
  scope === 'user' || !projectAgentsDir ? [] : loadAgentsFromDir(projectAgentsDir, 'project');
106
112
 
113
+ // Priority: bundled (lowest) → user → project (highest, overrides by name)
107
114
  const agentMap = new Map<string, AgentConfig>();
108
115
 
116
+ for (const agent of bundledAgents) agentMap.set(agent.name, agent);
117
+
109
118
  if (scope === 'both') {
110
119
  for (const agent of userAgents) agentMap.set(agent.name, agent);
111
120
  for (const agent of projectAgents) agentMap.set(agent.name, agent);
@@ -118,6 +127,8 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
118
127
  return { agents: Array.from(agentMap.values()), projectAgentsDir };
119
128
  }
120
129
 
130
+ export { bundledAgentsDir };
131
+
121
132
  export function formatAgentList(
122
133
  agents: AgentConfig[],
123
134
  maxItems: number,
@@ -0,0 +1,200 @@
1
+ import { spawn } from 'node:child_process';
2
+ import * as fs from 'node:fs';
3
+ import * as os from 'node:os';
4
+ import * as path from 'node:path';
5
+ import { withFileMutationQueue } from '@mariozechner/pi-coding-agent';
6
+ import type { Message } from '@mariozechner/pi-ai';
7
+ import { discoverAgents } from './agents';
8
+ import type { AgentResult, UsageStats } from './delegate-types';
9
+
10
+ function emptyUsage(): UsageStats {
11
+ return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 };
12
+ }
13
+
14
+ function getPiInvocation(args: string[]): { command: string; args: string[] } {
15
+ const currentScript = process.argv[1];
16
+ if (currentScript && fs.existsSync(currentScript)) {
17
+ return { command: process.execPath, args: [currentScript, ...args] };
18
+ }
19
+
20
+ const execName = path.basename(process.execPath).toLowerCase();
21
+ const isGenericRuntime = /^(node|bun)(\.exe)?$/.test(execName);
22
+ if (!isGenericRuntime) return { command: process.execPath, args };
23
+
24
+ return { command: 'pi', args };
25
+ }
26
+
27
+ async function writePromptToTempFile(
28
+ agentName: string,
29
+ prompt: string,
30
+ ): Promise<{ dir: string; filePath: string }> {
31
+ const tmpDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'pi-subagent-'));
32
+ const safeName = agentName.replace(/[^\w.-]+/g, '_');
33
+ const filePath = path.join(tmpDir, `prompt-${safeName}.md`);
34
+ await withFileMutationQueue(filePath, async () => {
35
+ await fs.promises.writeFile(filePath, prompt, { encoding: 'utf-8', mode: 0o600 });
36
+ });
37
+ return { dir: tmpDir, filePath };
38
+ }
39
+
40
+ export type OnPhaseUpdate = (phaseName: string, agentName: string, result: AgentResult) => void;
41
+
42
+ export async function runAgent(
43
+ cwd: string,
44
+ agentName: string,
45
+ task: string,
46
+ onUpdate?: OnPhaseUpdate,
47
+ phaseName?: string,
48
+ ): Promise<AgentResult> {
49
+ const { agents } = discoverAgents(cwd, 'user');
50
+ const agent = agents.find((a) => a.name === agentName);
51
+
52
+ if (!agent) {
53
+ const available = agents.map((a) => a.name).join(', ') || 'none';
54
+ return {
55
+ agent: agentName,
56
+ task,
57
+ exitCode: 1,
58
+ messages: [],
59
+ stderr: `Unknown agent: "${agentName}". Available: ${available}.`,
60
+ usage: emptyUsage(),
61
+ };
62
+ }
63
+
64
+ const args: string[] = ['--mode', 'json', '-p', '--no-session'];
65
+ if (agent.model) args.push('--model', agent.model);
66
+ if (agent.thinking) args.push('--thinking', agent.thinking);
67
+ if (agent.tools && agent.tools.length > 0) args.push('--tools', agent.tools.join(','));
68
+
69
+ let tmpPromptDir: string | null = null;
70
+ let tmpPromptPath: string | null = null;
71
+
72
+ const result: AgentResult = {
73
+ agent: agentName,
74
+ task,
75
+ exitCode: 0,
76
+ messages: [],
77
+ stderr: '',
78
+ usage: emptyUsage(),
79
+ model: agent.model,
80
+ };
81
+
82
+ try {
83
+ if (agent.systemPrompt.trim()) {
84
+ const tmp = await writePromptToTempFile(agent.name, agent.systemPrompt);
85
+ tmpPromptDir = tmp.dir;
86
+ tmpPromptPath = tmp.filePath;
87
+ args.push('--append-system-prompt', tmpPromptPath);
88
+ }
89
+
90
+ args.push(`Task: ${task}`);
91
+
92
+ const exitCode = await new Promise<number>((resolve) => {
93
+ const invocation = getPiInvocation(args);
94
+ const proc = spawn(invocation.command, invocation.args, {
95
+ cwd,
96
+ shell: false,
97
+ stdio: ['ignore', 'pipe', 'pipe'],
98
+ });
99
+
100
+ let buffer = '';
101
+
102
+ const processLine = (line: string) => {
103
+ if (!line.trim()) return;
104
+ let event: any;
105
+ try {
106
+ event = JSON.parse(line);
107
+ } catch {
108
+ return;
109
+ }
110
+
111
+ if (event.type === 'message_end' && event.message) {
112
+ const msg = event.message as Message;
113
+ result.messages.push(msg);
114
+
115
+ if (msg.role === 'assistant') {
116
+ result.usage.turns++;
117
+ const usage = msg.usage;
118
+ if (usage) {
119
+ result.usage.input += usage.input || 0;
120
+ result.usage.output += usage.output || 0;
121
+ result.usage.cacheRead += usage.cacheRead || 0;
122
+ result.usage.cacheWrite += usage.cacheWrite || 0;
123
+ result.usage.cost += usage.cost?.total || 0;
124
+ result.usage.contextTokens = usage.totalTokens || 0;
125
+ }
126
+ if (!result.model && msg.model) result.model = msg.model;
127
+ if (msg.stopReason) result.stopReason = msg.stopReason;
128
+ if (msg.errorMessage) result.errorMessage = msg.errorMessage;
129
+ }
130
+
131
+ if (onUpdate) onUpdate(phaseName ?? 'unknown', agentName, { ...result });
132
+ }
133
+
134
+ if (event.type === 'tool_result_end' && event.message) {
135
+ result.messages.push(event.message as Message);
136
+ if (onUpdate) onUpdate(phaseName ?? 'unknown', agentName, { ...result });
137
+ }
138
+ };
139
+
140
+ proc.stdout.on('data', (data: Buffer) => {
141
+ buffer += data.toString();
142
+ const lines = buffer.split('\n');
143
+ buffer = lines.pop() || '';
144
+ for (const line of lines) processLine(line);
145
+ });
146
+
147
+ proc.stderr.on('data', (data: Buffer) => {
148
+ result.stderr += data.toString();
149
+ });
150
+
151
+ proc.on('close', (code: number | null) => {
152
+ if (buffer.trim()) processLine(buffer);
153
+ resolve(code ?? 0);
154
+ });
155
+
156
+ proc.on('error', () => resolve(1));
157
+ });
158
+
159
+ result.exitCode = exitCode;
160
+ return result;
161
+ } finally {
162
+ if (tmpPromptPath)
163
+ try {
164
+ fs.unlinkSync(tmpPromptPath);
165
+ } catch {
166
+ /* ignore */
167
+ }
168
+ if (tmpPromptDir)
169
+ try {
170
+ fs.rmdirSync(tmpPromptDir);
171
+ } catch {
172
+ /* ignore */
173
+ }
174
+ }
175
+ }
176
+
177
+ export async function runParallel(
178
+ cwd: string,
179
+ agentNames: string[],
180
+ task: string,
181
+ onUpdate?: OnPhaseUpdate,
182
+ phaseName?: string,
183
+ ): Promise<AgentResult[]> {
184
+ const MAX_CONCURRENCY = 4;
185
+ const results: AgentResult[] = new Array(agentNames.length);
186
+ let nextIndex = 0;
187
+
188
+ const workers = new Array(Math.min(MAX_CONCURRENCY, agentNames.length))
189
+ .fill(null)
190
+ .map(async () => {
191
+ while (true) {
192
+ const current = nextIndex++;
193
+ if (current >= agentNames.length) return;
194
+ results[current] = await runAgent(cwd, agentNames[current], task, onUpdate, phaseName);
195
+ }
196
+ });
197
+
198
+ await Promise.all(workers);
199
+ return results;
200
+ }