pi-subagents 0.3.0 → 0.3.1

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,26 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.1] - 2026-01-24
4
+
5
+ ### Changed
6
+ - **Major code refactor** - Split monolithic index.ts into focused modules:
7
+ - `execution.ts` - Core runSync function for single agent execution
8
+ - `chain-execution.ts` - Chain orchestration (sequential + parallel steps)
9
+ - `async-execution.ts` - Async/background execution support
10
+ - `render.ts` - TUI rendering (widget, tool result display)
11
+ - `schemas.ts` - TypeBox parameter schemas
12
+ - `formatters.ts` - Output formatting utilities
13
+ - `utils.ts` - Shared utility functions
14
+ - `types.ts` - Shared type definitions and constants
15
+
16
+ ### Fixed
17
+ - **Expanded view visibility** - Running chains now properly show:
18
+ - Task preview (truncated to 80 chars) for each step
19
+ - Recent tools fallback when between tool calls
20
+ - Increased recent output from 2 to 3 lines
21
+ - **Progress matching** - Added agent name fallback when index doesn't match
22
+ - **Type safety** - Added defensive `?? []` for `recentOutput` access on union types
23
+
3
24
  ## [0.3.0] - 2026-01-24
4
25
 
5
26
  ### Added
package/agents.ts CHANGED
@@ -160,13 +160,3 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
160
160
 
161
161
  return { agents: Array.from(agentMap.values()), projectAgentsDir };
162
162
  }
163
-
164
- export function formatAgentList(agents: AgentConfig[], maxItems: number): { text: string; remaining: number } {
165
- if (agents.length === 0) return { text: "none", remaining: 0 };
166
- const listed = agents.slice(0, maxItems);
167
- const remaining = agents.length - listed.length;
168
- return {
169
- text: listed.map((a) => `${a.name} (${a.source}): ${a.description}`).join("; "),
170
- remaining,
171
- };
172
- }
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Async execution logic for subagent tool
3
+ */
4
+
5
+ import { spawn } from "node:child_process";
6
+ import * as fs from "node:fs";
7
+ import * as os from "node:os";
8
+ import * as path from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ import { createRequire } from "node:module";
11
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
12
+ import type { AgentConfig } from "./agents.js";
13
+ import { isParallelStep, type ChainStep, type SequentialStep } from "./settings.js";
14
+ import {
15
+ type ArtifactConfig,
16
+ type Details,
17
+ type MaxOutputConfig,
18
+ ASYNC_DIR,
19
+ RESULTS_DIR,
20
+ } from "./types.js";
21
+
22
+ const require = createRequire(import.meta.url);
23
+ const jitiCliPath: string | undefined = (() => {
24
+ try {
25
+ return path.join(path.dirname(require.resolve("jiti/package.json")), "lib/jiti-cli.mjs");
26
+ } catch {
27
+ return undefined;
28
+ }
29
+ })();
30
+
31
+ export interface AsyncExecutionContext {
32
+ pi: ExtensionAPI;
33
+ cwd: string;
34
+ currentSessionId: string;
35
+ }
36
+
37
+ export interface AsyncChainParams {
38
+ chain: ChainStep[];
39
+ agents: AgentConfig[];
40
+ ctx: AsyncExecutionContext;
41
+ cwd?: string;
42
+ maxOutput?: MaxOutputConfig;
43
+ artifactsDir?: string;
44
+ artifactConfig: ArtifactConfig;
45
+ shareEnabled: boolean;
46
+ sessionRoot?: string;
47
+ }
48
+
49
+ export interface AsyncSingleParams {
50
+ agent: string;
51
+ task: string;
52
+ agentConfig: AgentConfig;
53
+ ctx: AsyncExecutionContext;
54
+ cwd?: string;
55
+ maxOutput?: MaxOutputConfig;
56
+ artifactsDir?: string;
57
+ artifactConfig: ArtifactConfig;
58
+ shareEnabled: boolean;
59
+ sessionRoot?: string;
60
+ }
61
+
62
+ export interface AsyncExecutionResult {
63
+ content: Array<{ type: "text"; text: string }>;
64
+ details: Details;
65
+ isError?: boolean;
66
+ }
67
+
68
+ /**
69
+ * Check if jiti is available for async execution
70
+ */
71
+ export function isAsyncAvailable(): boolean {
72
+ return jitiCliPath !== undefined;
73
+ }
74
+
75
+ /**
76
+ * Spawn the async runner process
77
+ */
78
+ function spawnRunner(cfg: object, suffix: string, cwd: string): number | undefined {
79
+ if (!jitiCliPath) return undefined;
80
+
81
+ const cfgPath = path.join(os.tmpdir(), `pi-async-cfg-${suffix}.json`);
82
+ fs.writeFileSync(cfgPath, JSON.stringify(cfg));
83
+ const runner = path.join(path.dirname(fileURLToPath(import.meta.url)), "subagent-runner.ts");
84
+
85
+ const proc = spawn("node", [jitiCliPath, runner, cfgPath], {
86
+ cwd,
87
+ detached: true,
88
+ stdio: "ignore",
89
+ });
90
+ proc.unref();
91
+ return proc.pid;
92
+ }
93
+
94
+ /**
95
+ * Execute a chain asynchronously
96
+ */
97
+ export function executeAsyncChain(
98
+ id: string,
99
+ params: AsyncChainParams,
100
+ ): AsyncExecutionResult {
101
+ const { chain, agents, ctx, cwd, maxOutput, artifactsDir, artifactConfig, shareEnabled, sessionRoot } = params;
102
+
103
+ // Async mode doesn't support parallel steps (v1 limitation)
104
+ const hasParallelInChain = chain.some(isParallelStep);
105
+ if (hasParallelInChain) {
106
+ return {
107
+ content: [{ type: "text", text: "Async mode doesn't support chains with parallel steps. Use clarify: true (sync mode) for parallel-in-chain." }],
108
+ isError: true,
109
+ details: { mode: "chain" as const, results: [] },
110
+ };
111
+ }
112
+
113
+ // At this point, all steps are sequential
114
+ const seqSteps = chain as SequentialStep[];
115
+
116
+ // Validate all agents exist before building steps
117
+ for (const s of seqSteps) {
118
+ if (!agents.find((x) => x.name === s.agent)) {
119
+ return {
120
+ content: [{ type: "text", text: `Unknown agent: ${s.agent}` }],
121
+ isError: true,
122
+ details: { mode: "chain" as const, results: [] },
123
+ };
124
+ }
125
+ }
126
+
127
+ const asyncDir = path.join(ASYNC_DIR, id);
128
+ try {
129
+ fs.mkdirSync(asyncDir, { recursive: true });
130
+ } catch {}
131
+
132
+ const steps = seqSteps.map((s, i) => {
133
+ const a = agents.find((x) => x.name === s.agent)!;
134
+ return {
135
+ agent: s.agent,
136
+ // First step validated to have task; others default to {previous} (replaced by runner)
137
+ task: s.task ?? "{previous}",
138
+ cwd: s.cwd,
139
+ model: a.model,
140
+ tools: a.tools,
141
+ systemPrompt: a.systemPrompt?.trim() || null,
142
+ };
143
+ });
144
+
145
+ const runnerCwd = cwd ?? ctx.cwd;
146
+ const pid = spawnRunner(
147
+ {
148
+ id,
149
+ steps,
150
+ resultPath: path.join(RESULTS_DIR, `${id}.json`),
151
+ cwd: runnerCwd,
152
+ placeholder: "{previous}",
153
+ maxOutput,
154
+ artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
155
+ artifactConfig,
156
+ share: shareEnabled,
157
+ sessionDir: sessionRoot ? path.join(sessionRoot, `async-${id}`) : undefined,
158
+ asyncDir,
159
+ sessionId: ctx.currentSessionId,
160
+ },
161
+ id,
162
+ runnerCwd,
163
+ );
164
+
165
+ if (pid) {
166
+ const firstAgent = chain[0] as SequentialStep;
167
+ ctx.pi.events.emit("subagent_enhanced:started", {
168
+ id,
169
+ pid,
170
+ agent: firstAgent.agent,
171
+ task: firstAgent.task?.slice(0, 50),
172
+ chain: chain.map((s) => (s as SequentialStep).agent),
173
+ cwd: runnerCwd,
174
+ asyncDir,
175
+ });
176
+ ctx.pi.events.emit("subagent:started", {
177
+ id,
178
+ pid,
179
+ agent: firstAgent.agent,
180
+ task: firstAgent.task?.slice(0, 50),
181
+ chain: chain.map((s) => (s as SequentialStep).agent),
182
+ cwd: runnerCwd,
183
+ asyncDir,
184
+ });
185
+ }
186
+
187
+ return {
188
+ content: [
189
+ { type: "text", text: `Async chain: ${chain.map((s) => (s as SequentialStep).agent).join(" -> ")} [${id}]` },
190
+ ],
191
+ details: { mode: "chain", results: [], asyncId: id, asyncDir },
192
+ };
193
+ }
194
+
195
+ /**
196
+ * Execute a single agent asynchronously
197
+ */
198
+ export function executeAsyncSingle(
199
+ id: string,
200
+ params: AsyncSingleParams,
201
+ ): AsyncExecutionResult {
202
+ const { agent, task, agentConfig, ctx, cwd, maxOutput, artifactsDir, artifactConfig, shareEnabled, sessionRoot } = params;
203
+
204
+ const asyncDir = path.join(ASYNC_DIR, id);
205
+ try {
206
+ fs.mkdirSync(asyncDir, { recursive: true });
207
+ } catch {}
208
+
209
+ const runnerCwd = cwd ?? ctx.cwd;
210
+ const pid = spawnRunner(
211
+ {
212
+ id,
213
+ steps: [
214
+ {
215
+ agent,
216
+ task,
217
+ cwd,
218
+ model: agentConfig.model,
219
+ tools: agentConfig.tools,
220
+ systemPrompt: agentConfig.systemPrompt?.trim() || null,
221
+ },
222
+ ],
223
+ resultPath: path.join(RESULTS_DIR, `${id}.json`),
224
+ cwd: runnerCwd,
225
+ placeholder: "{previous}",
226
+ maxOutput,
227
+ artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
228
+ artifactConfig,
229
+ share: shareEnabled,
230
+ sessionDir: sessionRoot ? path.join(sessionRoot, `async-${id}`) : undefined,
231
+ asyncDir,
232
+ sessionId: ctx.currentSessionId,
233
+ },
234
+ id,
235
+ runnerCwd,
236
+ );
237
+
238
+ if (pid) {
239
+ ctx.pi.events.emit("subagent_enhanced:started", {
240
+ id,
241
+ pid,
242
+ agent,
243
+ task: task?.slice(0, 50),
244
+ cwd: runnerCwd,
245
+ asyncDir,
246
+ });
247
+ ctx.pi.events.emit("subagent:started", {
248
+ id,
249
+ pid,
250
+ agent,
251
+ task: task?.slice(0, 50),
252
+ cwd: runnerCwd,
253
+ asyncDir,
254
+ });
255
+ }
256
+
257
+ return {
258
+ content: [{ type: "text", text: `Async: ${agent} [${id}]` }],
259
+ details: { mode: "single", results: [], asyncId: id, asyncDir },
260
+ };
261
+ }