@oh-my-pi/pi-coding-agent 3.21.0 → 3.24.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.
Files changed (66) hide show
  1. package/CHANGELOG.md +40 -1
  2. package/docs/sdk.md +47 -50
  3. package/examples/custom-tools/README.md +0 -15
  4. package/examples/hooks/custom-compaction.ts +1 -3
  5. package/examples/sdk/README.md +6 -10
  6. package/package.json +5 -5
  7. package/src/cli/args.ts +9 -6
  8. package/src/core/agent-session.ts +3 -3
  9. package/src/core/custom-tools/wrapper.ts +0 -1
  10. package/src/core/extensions/index.ts +1 -6
  11. package/src/core/extensions/wrapper.ts +0 -7
  12. package/src/core/file-mentions.ts +5 -8
  13. package/src/core/sdk.ts +41 -111
  14. package/src/core/session-manager.ts +7 -0
  15. package/src/core/system-prompt.ts +22 -33
  16. package/src/core/tools/ask.ts +14 -7
  17. package/src/core/tools/bash-interceptor.ts +4 -4
  18. package/src/core/tools/bash.ts +19 -9
  19. package/src/core/tools/context.ts +7 -0
  20. package/src/core/tools/edit.ts +8 -15
  21. package/src/core/tools/exa/render.ts +4 -16
  22. package/src/core/tools/find.ts +7 -18
  23. package/src/core/tools/git.ts +13 -3
  24. package/src/core/tools/grep.ts +7 -18
  25. package/src/core/tools/index.test.ts +180 -0
  26. package/src/core/tools/index.ts +94 -237
  27. package/src/core/tools/ls.ts +4 -9
  28. package/src/core/tools/lsp/index.ts +32 -29
  29. package/src/core/tools/lsp/render.ts +7 -28
  30. package/src/core/tools/notebook.ts +3 -5
  31. package/src/core/tools/output.ts +5 -17
  32. package/src/core/tools/read.ts +8 -19
  33. package/src/core/tools/review.ts +0 -18
  34. package/src/core/tools/rulebook.ts +8 -2
  35. package/src/core/tools/task/agents.ts +28 -7
  36. package/src/core/tools/task/discovery.ts +0 -6
  37. package/src/core/tools/task/executor.ts +264 -254
  38. package/src/core/tools/task/index.ts +45 -220
  39. package/src/core/tools/task/render.ts +21 -11
  40. package/src/core/tools/task/types.ts +6 -11
  41. package/src/core/tools/task/worker-protocol.ts +17 -0
  42. package/src/core/tools/task/worker.ts +238 -0
  43. package/src/core/tools/web-fetch.ts +4 -36
  44. package/src/core/tools/web-search/index.ts +2 -1
  45. package/src/core/tools/web-search/render.ts +1 -4
  46. package/src/core/tools/write.ts +7 -15
  47. package/src/discovery/helpers.test.ts +1 -1
  48. package/src/index.ts +5 -16
  49. package/src/main.ts +4 -4
  50. package/src/modes/interactive/theme/theme.ts +4 -4
  51. package/src/prompts/task.md +0 -7
  52. package/src/prompts/tools/output.md +2 -2
  53. package/src/prompts/tools/task.md +68 -0
  54. package/examples/custom-tools/question/index.ts +0 -84
  55. package/examples/custom-tools/subagent/README.md +0 -172
  56. package/examples/custom-tools/subagent/agents/planner.md +0 -37
  57. package/examples/custom-tools/subagent/agents/scout.md +0 -50
  58. package/examples/custom-tools/subagent/agents/worker.md +0 -24
  59. package/examples/custom-tools/subagent/agents.ts +0 -156
  60. package/examples/custom-tools/subagent/commands/implement-and-review.md +0 -10
  61. package/examples/custom-tools/subagent/commands/implement.md +0 -10
  62. package/examples/custom-tools/subagent/commands/scout-and-plan.md +0 -9
  63. package/examples/custom-tools/subagent/index.ts +0 -1002
  64. package/examples/sdk/05-tools.ts +0 -94
  65. package/examples/sdk/12-full-control.ts +0 -95
  66. package/src/prompts/browser.md +0 -71
@@ -16,6 +16,7 @@
16
16
  import type { Usage } from "@mariozechner/pi-ai";
17
17
  import type { AgentTool } from "@oh-my-pi/pi-agent-core";
18
18
  import type { Theme } from "../../../modes/interactive/theme/theme";
19
+ import taskDescriptionTemplate from "../../../prompts/tools/task.md" with { type: "text" };
19
20
  import { formatDuration } from "../render-utils";
20
21
  import { cleanupTempDir, createTempArtifactsDir, getArtifactsDir } from "./artifacts";
21
22
  import { discoverAgents, getAgent } from "./discovery";
@@ -27,15 +28,13 @@ import {
27
28
  MAX_AGENTS_IN_DESCRIPTION,
28
29
  MAX_CONCURRENCY,
29
30
  MAX_PARALLEL_TASKS,
30
- OMP_BLOCKED_AGENT_ENV,
31
- OMP_NO_SUBAGENTS_ENV,
32
- OMP_SPAWNS_ENV,
33
31
  type TaskToolDetails,
34
32
  taskSchema,
35
33
  } from "./types";
36
34
 
37
- // Import review tools for side effects (registers subprocess tool handlers)
35
+ // Import review tools for side effects (registers subagent tool handlers)
38
36
  import "../review";
37
+ import type { ToolSession } from "..";
39
38
 
40
39
  /** Format byte count for display */
41
40
  function formatBytes(bytes: number): string {
@@ -83,51 +82,6 @@ function addUsageTotals(target: Usage, usage: Partial<Usage>): void {
83
82
  target.cost.total += cost.total;
84
83
  }
85
84
 
86
- function parseSubagentUsage(events: string[] | undefined): Usage | undefined {
87
- if (!events || events.length === 0) return undefined;
88
-
89
- const totals = createUsageTotals();
90
- let hasUsage = false;
91
-
92
- for (const line of events) {
93
- let event: unknown;
94
- try {
95
- event = JSON.parse(line);
96
- } catch {
97
- continue;
98
- }
99
-
100
- if (!event || typeof event !== "object") continue;
101
- const record = event as Record<string, unknown>;
102
- if (record.type !== "message_end") continue;
103
-
104
- const message = record.message;
105
- if (!message || typeof message !== "object") continue;
106
- const msgRecord = message as Record<string, unknown>;
107
- if (msgRecord.role !== "assistant") continue;
108
- if (msgRecord.stopReason === "aborted" || msgRecord.stopReason === "error") continue;
109
-
110
- const usage = msgRecord.usage;
111
- if (!usage || typeof usage !== "object") continue;
112
-
113
- addUsageTotals(totals, usage as Partial<Usage>);
114
- hasUsage = true;
115
- }
116
-
117
- return hasUsage ? totals : undefined;
118
- }
119
-
120
- /** Session context interface */
121
- interface SessionContext {
122
- getSessionFile: () => string | null;
123
- }
124
-
125
- /** Task tool options */
126
- interface TaskToolOptions {
127
- /** Set of available tool names (for cross-tool awareness) */
128
- availableTools?: Set<string>;
129
- }
130
-
131
85
  // Re-export types and utilities
132
86
  export { loadBundledAgents as BUNDLED_AGENTS } from "./agents";
133
87
  export { discoverCommands, expandCommand, getCommand } from "./commands";
@@ -141,166 +95,45 @@ export { taskSchema } from "./types";
141
95
  async function buildDescription(cwd: string): Promise<string> {
142
96
  const { agents } = await discoverAgents(cwd);
143
97
 
144
- const lines: string[] = [];
145
-
146
- lines.push("Launch a new agent to handle complex, multi-step tasks autonomously.");
147
- lines.push("");
148
- lines.push(
149
- "The Task tool launches specialized agents (subprocesses) that autonomously handle complex tasks. Each agent type has specific capabilities and tools available to it.",
150
- );
151
- lines.push("");
152
- lines.push("Available agent types and the tools they have access to:");
153
-
98
+ // Build agents list
99
+ const agentLines: string[] = [];
154
100
  for (const agent of agents.slice(0, MAX_AGENTS_IN_DESCRIPTION)) {
155
101
  const tools = agent.tools?.join(", ") || "All tools";
156
- lines.push(`- ${agent.name}: ${agent.description} (Tools: ${tools})`);
102
+ agentLines.push(`- ${agent.name}: ${agent.description} (Tools: ${tools})`);
157
103
  }
158
104
  if (agents.length > MAX_AGENTS_IN_DESCRIPTION) {
159
- lines.push(` ...and ${agents.length - MAX_AGENTS_IN_DESCRIPTION} more agents`);
105
+ agentLines.push(` ...and ${agents.length - MAX_AGENTS_IN_DESCRIPTION} more agents`);
160
106
  }
161
107
 
162
- lines.push("");
163
- lines.push("When NOT to use the Task tool:");
164
- lines.push(
165
- "- If you want to read a specific file path, use the Read or Glob tool instead of the Task tool, to find the match more quickly",
166
- );
167
- lines.push(
168
- '- If you are searching for a specific class definition like "class Foo", use the Glob tool instead, to find the match more quickly',
169
- );
170
- lines.push(
171
- "- If you are searching for code within a specific file or set of 2-3 files, use the Read tool instead of the Task tool, to find the match more quickly",
172
- );
173
- lines.push("- Other tasks that are not related to the agent descriptions above");
174
- lines.push("");
175
- lines.push("");
176
- lines.push("Usage notes:");
177
- lines.push("- Always include a short description of the task in the task parameter");
178
- lines.push(
179
- "- Prefer plan-then-execute: put shared constraints in context, keep each task focused, and specify output format and acceptance criteria",
180
- );
181
- lines.push(
182
- "- Minimize tool chatter: avoid repeating large context and use the Output tool with output ids for full logs",
183
- );
184
- lines.push("- Launch multiple agents concurrently whenever possible, to maximize performance");
185
- lines.push(
186
- "- When the agent is done, it will return a single message back to you. The result returned by the agent is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result.",
187
- );
188
- lines.push(
189
- "- Each agent invocation is stateless. You will not be able to send additional messages to the agent, nor will the agent be able to communicate with you outside of its final report. Therefore, your task should contain a highly detailed task description for the agent to perform autonomously and you should specify exactly what information the agent should return back to you in its final and only message to you.",
190
- );
191
- lines.push(
192
- "- IMPORTANT: Agent results are intermediate data, not task completions. Use the agent's findings to continue executing the user's request. Do not treat agent reports as 'task complete' signals - they provide context for you to perform the actual work.",
193
- );
194
- lines.push("- The agent's outputs should generally be trusted");
195
- lines.push(
196
- "- Clearly tell the agent whether you expect it to write code or just to do research (search, file reads, web fetches, etc.), since it is not aware of the user's intent",
197
- );
198
- lines.push(
199
- "- If the agent description mentions that it should be used proactively, then you should try your best to use it without the user having to ask for it first. Use your judgement.",
200
- );
201
- lines.push("");
202
- lines.push("Parameters:");
203
- lines.push(
204
- `- tasks: Array of {agent, task, description?, model?} - tasks to run in parallel (max ${MAX_PARALLEL_TASKS}, ${MAX_CONCURRENCY} concurrent)`,
205
- );
206
- lines.push(
207
- ' - model: (optional) Override the agent\'s default model with fuzzy matching (e.g., "sonnet", "codex", "5.2"). Supports comma-separated fallbacks: "gpt, opus" tries gpt first, then opus. Use "default" for omp\'s default model',
208
- );
209
- lines.push(
210
- "- context: (optional) Shared context string prepended to all task prompts - use this to avoid repeating instructions",
211
- );
212
- lines.push("");
213
- lines.push("Results are always written to {tempdir}/omp-task-{runId}/task_{agent}_{index}.md");
214
- lines.push("");
215
- lines.push("Example usage:");
216
- lines.push("");
217
- lines.push("<example_agent_descriptions>");
218
- lines.push('"code-reviewer": use this agent after you are done writing a significant piece of code');
219
- lines.push('"explore": use this agent for fast codebase exploration and research');
220
- lines.push("</example_agent_descriptions>");
221
- lines.push("");
222
- lines.push("<example>");
223
- lines.push('user: "Please write a function that checks if a number is prime"');
224
- lines.push("assistant: Sure let me write a function that checks if a number is prime");
225
- lines.push("assistant: I'm going to use the Write tool to write the following code:");
226
- lines.push("<code>");
227
- lines.push("function isPrime(n) {");
228
- lines.push(" if (n <= 1) return false");
229
- lines.push(" for (let i = 2; i * i <= n; i++) {");
230
- lines.push(" if (n % i === 0) return false");
231
- lines.push(" }");
232
- lines.push(" return true");
233
- lines.push("}");
234
- lines.push("</code>");
235
- lines.push("<commentary>");
236
- lines.push(
237
- "Since a significant piece of code was written and the task was completed, now use the code-reviewer agent to review the code",
238
- );
239
- lines.push("</commentary>");
240
- lines.push("assistant: Now let me use the code-reviewer agent to review the code");
241
- lines.push(
242
- 'assistant: Uses the Task tool: { tasks: [{ agent: "code-reviewer", task: "Review the isPrime function" }] }',
243
- );
244
- lines.push("</example>");
245
- lines.push("");
246
- lines.push("<example>");
247
- lines.push('user: "Find all TODO comments in the codebase"');
248
- lines.push("assistant: I'll use multiple explore agents to search different directories in parallel");
249
- lines.push("assistant: Uses the Task tool:");
250
- lines.push("{");
251
- lines.push(' "context": "Find all TODO comments. Return file:line:content format.",');
252
- lines.push(' "tasks": [');
253
- lines.push(' { "agent": "explore", "task": "Search in src/" },');
254
- lines.push(' { "agent": "explore", "task": "Search in lib/" },');
255
- lines.push(' { "agent": "explore", "task": "Search in tests/" }');
256
- lines.push(" ]");
257
- lines.push("}");
258
- lines.push("Results → {tempdir}/omp-task-{runId}/task_explore_*.md");
259
- lines.push("</example>");
260
-
261
- return lines.join("\n");
108
+ // Fill template placeholders
109
+ return taskDescriptionTemplate
110
+ .replace("{{AGENTS_LIST}}", agentLines.join("\n"))
111
+ .replace("{{MAX_PARALLEL_TASKS}}", String(MAX_PARALLEL_TASKS))
112
+ .replace("{{MAX_CONCURRENCY}}", String(MAX_CONCURRENCY));
262
113
  }
263
114
 
264
115
  /**
265
- * Create the task tool configured for a specific working directory.
116
+ * Create the task tool configured for a specific session.
266
117
  */
267
118
  export async function createTaskTool(
268
- cwd: string,
269
- sessionContext?: SessionContext,
270
- options?: TaskToolOptions,
119
+ session: ToolSession,
271
120
  ): Promise<AgentTool<typeof taskSchema, TaskToolDetails, Theme>> {
272
- const hasOutputTool = options?.availableTools?.has("output") ?? false;
273
- // Check if subagents are completely inhibited (legacy recursion prevention)
274
- if (process.env[OMP_NO_SUBAGENTS_ENV]) {
275
- return {
276
- name: "task",
277
- label: "Task",
278
- description: "Sub-agents disabled (recursion prevention)",
279
- parameters: taskSchema,
280
- execute: async () => ({
281
- content: [{ type: "text", text: "Sub-agents are disabled for this agent (recursion prevention)." }],
282
- details: {
283
- projectAgentsDir: null,
284
- results: [],
285
- totalDurationMs: 0,
286
- },
287
- }),
288
- };
289
- }
290
-
291
121
  // Check for same-agent blocking (allows other agent types)
292
- const blockedAgent = process.env[OMP_BLOCKED_AGENT_ENV];
122
+ const blockedAgent = process.env.OMP_BLOCKED_AGENT;
123
+
124
+ // Build description upfront
125
+ const description = await buildDescription(session.cwd);
293
126
 
294
127
  return {
295
128
  name: "task",
296
129
  label: "Task",
297
- description: await buildDescription(cwd),
130
+ description,
298
131
  parameters: taskSchema,
299
132
  renderCall,
300
133
  renderResult,
301
134
  execute: async (_toolCallId, params, signal, onUpdate) => {
302
135
  const startTime = Date.now();
303
- const { agents, projectAgentsDir } = await discoverAgents(cwd);
136
+ const { agents, projectAgentsDir } = await discoverAgents(session.cwd);
304
137
  const context = params.context;
305
138
 
306
139
  // Handle empty or missing tasks
@@ -339,7 +172,7 @@ export async function createTaskTool(
339
172
  }
340
173
 
341
174
  // Derive artifacts directory
342
- const sessionFile = sessionContext?.getSessionFile() ?? null;
175
+ const sessionFile = session.getSessionFile();
343
176
  const artifactsDir = sessionFile ? getArtifactsDir(sessionFile) : null;
344
177
  const tempArtifactsDir = artifactsDir ? null : createTempArtifactsDir();
345
178
  const effectiveArtifactsDir = artifactsDir || tempArtifactsDir!;
@@ -404,13 +237,12 @@ export async function createTaskTool(
404
237
  }
405
238
 
406
239
  // Check spawn restrictions from parent
407
- const parentSpawns = process.env[OMP_SPAWNS_ENV];
240
+ const parentSpawns = session.getSessionSpawns() ?? "*";
241
+ const allowedSpawns = parentSpawns.split(",").map((s) => s.trim());
408
242
  const isSpawnAllowed = (agentName: string): boolean => {
409
- if (parentSpawns === undefined) return true; // Root = allow all
410
243
  if (parentSpawns === "") return false; // Empty = deny all
411
244
  if (parentSpawns === "*") return true; // Wildcard = allow all
412
- const allowed = new Set(parentSpawns.split(",").map((s) => s.trim()));
413
- return allowed.has(agentName);
245
+ return allowedSpawns.includes(agentName);
414
246
  };
415
247
 
416
248
  for (const task of tasks) {
@@ -459,7 +291,7 @@ export async function createTaskTool(
459
291
  const results = await mapWithConcurrencyLimit(tasksWithContext, MAX_CONCURRENCY, async (task, index) => {
460
292
  const agent = getAgent(agents, task.agent)!;
461
293
  return runSubprocess({
462
- cwd,
294
+ cwd: session.cwd,
463
295
  agent,
464
296
  task: task.task,
465
297
  description: task.description,
@@ -470,6 +302,7 @@ export async function createTaskTool(
470
302
  persistArtifacts: !!artifactsDir,
471
303
  artifactsDir: effectiveArtifactsDir,
472
304
  signal,
305
+ eventBus: undefined,
473
306
  onProgress: (progress) => {
474
307
  progressMap.set(index, structuredClone(progress));
475
308
  emitProgress();
@@ -477,55 +310,48 @@ export async function createTaskTool(
477
310
  });
478
311
  });
479
312
 
313
+ // Aggregate usage from executor results (already accumulated incrementally)
480
314
  const aggregatedUsage = createUsageTotals();
481
315
  let hasAggregatedUsage = false;
482
- const resultsWithUsage = results.map((result) => {
483
- const usage = parseSubagentUsage(result.jsonlEvents);
484
- if (usage) {
485
- addUsageTotals(aggregatedUsage, usage);
316
+ for (const result of results) {
317
+ if (result.usage) {
318
+ addUsageTotals(aggregatedUsage, result.usage);
486
319
  hasAggregatedUsage = true;
487
- return { ...result, usage };
488
320
  }
489
- return result;
490
- });
321
+ }
491
322
 
492
323
  // Collect output paths (artifacts already written by executor in real-time)
493
324
  const outputPaths: string[] = [];
494
- for (const result of resultsWithUsage) {
325
+ for (const result of results) {
495
326
  if (result.artifactPaths) {
496
327
  outputPaths.push(result.artifactPaths.outputPath);
497
328
  }
498
329
  }
499
330
 
500
331
  // Build final output - match plugin format
501
- const successCount = resultsWithUsage.filter((r) => r.exitCode === 0).length;
332
+ const successCount = results.filter((r) => r.exitCode === 0).length;
502
333
  const totalDuration = Date.now() - startTime;
503
334
 
504
- const summaries = resultsWithUsage.map((r) => {
335
+ const summaries = results.map((r) => {
505
336
  const status = r.exitCode === 0 ? "completed" : `failed (exit ${r.exitCode})`;
506
337
  const output = r.output.trim() || r.stderr.trim() || "(no output)";
507
338
  const preview = output.split("\n").slice(0, 5).join("\n");
508
- // Include output metadata and ID; include path only if Output tool unavailable (for Read fallback)
339
+ // Include output metadata and ID
509
340
  const outputId = `${r.agent}_${r.index}`;
510
341
  const meta = r.outputMeta
511
342
  ? ` [${r.outputMeta.lineCount} lines, ${formatBytes(r.outputMeta.charCount)}]`
512
343
  : "";
513
- const pathInfo = !hasOutputTool && r.artifactPaths?.outputPath ? ` (${r.artifactPaths.outputPath})` : "";
514
- return `[${r.agent}] ${status}${meta} ${outputId}${pathInfo}\n${preview}`;
344
+ return `[${r.agent}] ${status}${meta} ${outputId}\n${preview}`;
515
345
  });
516
346
 
517
347
  const skippedNote =
518
348
  skippedSelfRecursion > 0
519
- ? ` (${skippedSelfRecursion} ${blockedAgent} task${
520
- skippedSelfRecursion > 1 ? "s" : ""
521
- } skipped - self-recursion blocked)`
349
+ ? ` (${skippedSelfRecursion} ${blockedAgent} task${skippedSelfRecursion > 1 ? "s" : ""} skipped - self-recursion blocked)`
522
350
  : "";
523
- const outputIds = resultsWithUsage.map((r) => `${r.agent}_${r.index}`);
351
+ const outputIds = results.map((r) => `${r.agent}_${r.index}`);
524
352
  const outputHint =
525
- hasOutputTool && outputIds.length > 0
526
- ? `\n\nUse output tool for full logs: output ids ${outputIds.join(", ")}`
527
- : "";
528
- const summary = `${successCount}/${resultsWithUsage.length} succeeded${skippedNote} [${formatDuration(
353
+ outputIds.length > 0 ? `\n\nUse output tool for full logs: output ids ${outputIds.join(", ")}` : "";
354
+ const summary = `${successCount}/${results.length} succeeded${skippedNote} [${formatDuration(
529
355
  totalDuration,
530
356
  )}]\n\n${summaries.join("\n\n---\n\n")}${outputHint}`;
531
357
 
@@ -538,7 +364,7 @@ export async function createTaskTool(
538
364
  content: [{ type: "text", text: summary }],
539
365
  details: {
540
366
  projectAgentsDir,
541
- results: resultsWithUsage,
367
+ results: results,
542
368
  totalDurationMs: totalDuration,
543
369
  usage: hasAggregatedUsage ? aggregatedUsage : undefined,
544
370
  outputPaths,
@@ -563,16 +389,15 @@ export async function createTaskTool(
563
389
  };
564
390
  }
565
391
 
566
- // Default task tool using process.cwd() - returns a placeholder sync tool
567
- // Real implementations should use createTaskTool() which properly initializes the tool
392
+ // Default task tool - returns a placeholder tool
393
+ // Real implementations should use createTaskTool(session) to initialize the tool
568
394
  export const taskTool: AgentTool<typeof taskSchema, TaskToolDetails, Theme> = {
569
395
  name: "task",
570
396
  label: "Task",
571
- description:
572
- "Launch a new agent to handle complex, multi-step tasks autonomously. (Agent discovery pending - use createTaskTool for full functionality)",
397
+ description: "Launch a new agent to handle complex, multi-step tasks autonomously.",
573
398
  parameters: taskSchema,
574
399
  execute: async () => ({
575
- content: [{ type: "text", text: "Task tool not properly initialized. Use createTaskTool(cwd) instead." }],
400
+ content: [{ type: "text", text: "Task tool not properly initialized. Use createTaskTool(session) instead." }],
576
401
  details: {
577
402
  projectAgentsDir: null,
578
403
  results: [],
@@ -181,19 +181,29 @@ function renderAgentProgress(
181
181
 
182
182
  lines.push(statusLine);
183
183
 
184
- // Current tool (if running)
185
- if (progress.status === "running" && progress.currentTool) {
186
- let toolLine = `${continuePrefix}${theme.tree.hook} ${theme.fg("muted", progress.currentTool)}`;
187
- if (progress.currentToolArgs) {
188
- toolLine += `: ${theme.fg("dim", truncate(progress.currentToolArgs, 40, theme.format.ellipsis))}`;
189
- }
190
- if (progress.currentToolStartMs) {
191
- const elapsed = Date.now() - progress.currentToolStartMs;
192
- if (elapsed > 5000) {
193
- toolLine += `${theme.sep.dot}${theme.fg("warning", formatDuration(elapsed))}`;
184
+ // Current tool (if running) or most recent completed tool
185
+ if (progress.status === "running") {
186
+ if (progress.currentTool) {
187
+ let toolLine = `${continuePrefix}${theme.tree.hook} ${theme.fg("muted", progress.currentTool)}`;
188
+ if (progress.currentToolArgs) {
189
+ toolLine += `: ${theme.fg("dim", truncate(progress.currentToolArgs, 40, theme.format.ellipsis))}`;
190
+ }
191
+ if (progress.currentToolStartMs) {
192
+ const elapsed = Date.now() - progress.currentToolStartMs;
193
+ if (elapsed > 5000) {
194
+ toolLine += `${theme.sep.dot}${theme.fg("warning", formatDuration(elapsed))}`;
195
+ }
196
+ }
197
+ lines.push(toolLine);
198
+ } else if (progress.recentTools.length > 0) {
199
+ // Show most recent completed tool when idle between tools
200
+ const recent = progress.recentTools[0];
201
+ let toolLine = `${continuePrefix}${theme.tree.hook} ${theme.fg("dim", recent.tool)}`;
202
+ if (recent.args) {
203
+ toolLine += `: ${theme.fg("dim", truncate(recent.args, 40, theme.format.ellipsis))}`;
194
204
  }
205
+ lines.push(toolLine);
195
206
  }
196
- lines.push(toolLine);
197
207
  }
198
208
 
199
209
  // Render extracted tool data inline (e.g., review findings)
@@ -19,14 +19,11 @@ export const MAX_OUTPUT_LINES = 5000;
19
19
  /** Maximum agents to show in description */
20
20
  export const MAX_AGENTS_IN_DESCRIPTION = 10;
21
21
 
22
- /** Environment variable to inhibit subagent spawning (legacy, still checked for backwards compat) */
23
- export const OMP_NO_SUBAGENTS_ENV = "OMP_NO_SUBAGENTS";
22
+ /** EventBus channel for raw subagent events */
23
+ export const TASK_SUBAGENT_EVENT_CHANNEL = "task:subagent:event";
24
24
 
25
- /** Environment variable containing blocked agent name (self-recursion prevention) */
26
- export const OMP_BLOCKED_AGENT_ENV = "OMP_BLOCKED_AGENT";
27
-
28
- /** Environment variable containing allowed spawn list (propagated to subprocesses) */
29
- export const OMP_SPAWNS_ENV = "OMP_SPAWNS";
25
+ /** EventBus channel for aggregated subagent progress */
26
+ export const TASK_SUBAGENT_PROGRESS_CHANNEL = "task:subagent:progress";
30
27
 
31
28
  /** Single task item for parallel execution */
32
29
  export const taskItemSchema = Type.Object({
@@ -81,7 +78,6 @@ export interface AgentDefinition {
81
78
  tools?: string[];
82
79
  spawns?: string[] | "*";
83
80
  model?: string;
84
- recursive?: boolean;
85
81
  source: AgentSource;
86
82
  filePath?: string;
87
83
  }
@@ -123,10 +119,9 @@ export interface SingleResult {
123
119
  modelOverride?: string;
124
120
  error?: string;
125
121
  aborted?: boolean;
126
- jsonlEvents?: string[];
127
- artifactPaths?: { inputPath: string; outputPath: string; jsonlPath?: string };
128
- /** Aggregated usage from the subprocess, if available. */
122
+ /** Aggregated usage from the subprocess, accumulated incrementally from message_end events. */
129
123
  usage?: Usage;
124
+ artifactPaths?: { inputPath: string; outputPath: string; jsonlPath?: string };
130
125
  /** Data extracted by registered subprocess tool handlers (keyed by tool name) */
131
126
  extractedToolData?: Record<string, unknown[]>;
132
127
  /** Output metadata for Output tool integration */
@@ -0,0 +1,17 @@
1
+ import type { AgentEvent } from "@oh-my-pi/pi-agent-core";
2
+
3
+ export interface SubagentWorkerStartPayload {
4
+ cwd: string;
5
+ task: string;
6
+ systemPrompt: string;
7
+ model?: string;
8
+ toolNames?: string[];
9
+ sessionFile?: string | null;
10
+ spawnsEnv?: string;
11
+ }
12
+
13
+ export type SubagentWorkerRequest = { type: "start"; payload: SubagentWorkerStartPayload } | { type: "abort" };
14
+
15
+ export type SubagentWorkerResponse =
16
+ | { type: "event"; event: AgentEvent }
17
+ | { type: "done"; exitCode: number; durationMs: number; error?: string; aborted?: boolean };