@leing2021/super-pi 0.22.0 → 0.23.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 (101) hide show
  1. package/README.md +38 -209
  2. package/extensions/ce-core/index.ts +190 -7
  3. package/extensions/ce-core/tools/parallel-subagent.ts +3 -3
  4. package/extensions/ce-core/tools/subagent.ts +2 -2
  5. package/package.json +4 -18
  6. package/rules/common/naming.md +154 -0
  7. package/skills/02-plan/SKILL.md +3 -2
  8. package/skills/03-work/SKILL.md +9 -6
  9. package/skills/04-review/SKILL.md +3 -2
  10. package/skills/06-next/SKILL.md +38 -10
  11. package/skills/06-next/references/recommendation-logic.md +2 -2
  12. package/skills/07-worktree/SKILL.md +9 -8
  13. package/skills/{09-help → 08-help}/SKILL.md +3 -3
  14. package/skills/{09-help → 08-help}/references/workflow-sequence.md +2 -1
  15. package/skills/references/language-detection.md +44 -0
  16. package/extensions/subagent/agent-management.ts +0 -595
  17. package/extensions/subagent/agent-manager-chain-detail.ts +0 -162
  18. package/extensions/subagent/agent-manager-detail.ts +0 -231
  19. package/extensions/subagent/agent-manager-edit.ts +0 -390
  20. package/extensions/subagent/agent-manager-list.ts +0 -278
  21. package/extensions/subagent/agent-manager-parallel.ts +0 -304
  22. package/extensions/subagent/agent-manager.ts +0 -705
  23. package/extensions/subagent/agent-scope.ts +0 -8
  24. package/extensions/subagent/agent-selection.ts +0 -25
  25. package/extensions/subagent/agent-serializer.ts +0 -123
  26. package/extensions/subagent/agent-templates.ts +0 -62
  27. package/extensions/subagent/agents/context-builder.md +0 -37
  28. package/extensions/subagent/agents/delegate.md +0 -9
  29. package/extensions/subagent/agents/oracle.md +0 -73
  30. package/extensions/subagent/agents/planner.md +0 -52
  31. package/extensions/subagent/agents/researcher.md +0 -50
  32. package/extensions/subagent/agents/reviewer.md +0 -38
  33. package/extensions/subagent/agents/scout.md +0 -48
  34. package/extensions/subagent/agents/worker.md +0 -52
  35. package/extensions/subagent/agents.ts +0 -761
  36. package/extensions/subagent/artifacts.ts +0 -100
  37. package/extensions/subagent/async-execution.ts +0 -520
  38. package/extensions/subagent/async-job-tracker.ts +0 -216
  39. package/extensions/subagent/async-status.ts +0 -241
  40. package/extensions/subagent/chain-clarify.ts +0 -1364
  41. package/extensions/subagent/chain-execution.ts +0 -853
  42. package/extensions/subagent/chain-serializer.ts +0 -126
  43. package/extensions/subagent/completion-dedupe.ts +0 -65
  44. package/extensions/subagent/doctor.ts +0 -200
  45. package/extensions/subagent/execution.ts +0 -738
  46. package/extensions/subagent/file-coalescer.ts +0 -42
  47. package/extensions/subagent/fork-context.ts +0 -63
  48. package/extensions/subagent/formatters.ts +0 -122
  49. package/extensions/subagent/frontmatter.ts +0 -31
  50. package/extensions/subagent/index.ts +0 -585
  51. package/extensions/subagent/intercom-bridge.ts +0 -240
  52. package/extensions/subagent/jsonl-writer.ts +0 -83
  53. package/extensions/subagent/model-fallback.ts +0 -108
  54. package/extensions/subagent/notify.ts +0 -110
  55. package/extensions/subagent/parallel-utils.ts +0 -108
  56. package/extensions/subagent/pi-args.ts +0 -138
  57. package/extensions/subagent/pi-spawn.ts +0 -100
  58. package/extensions/subagent/post-exit-stdio-guard.ts +0 -87
  59. package/extensions/subagent/prompt-template-bridge.ts +0 -399
  60. package/extensions/subagent/prompts/gather-context-and-clarify.md +0 -13
  61. package/extensions/subagent/prompts/parallel-cleanup.md +0 -42
  62. package/extensions/subagent/prompts/parallel-research.md +0 -50
  63. package/extensions/subagent/prompts/parallel-review.md +0 -40
  64. package/extensions/subagent/render-helpers.ts +0 -82
  65. package/extensions/subagent/render.ts +0 -836
  66. package/extensions/subagent/result-intercom.ts +0 -237
  67. package/extensions/subagent/result-watcher.ts +0 -171
  68. package/extensions/subagent/run-history.ts +0 -57
  69. package/extensions/subagent/run-status.ts +0 -136
  70. package/extensions/subagent/schemas.ts +0 -164
  71. package/extensions/subagent/session-tokens.ts +0 -50
  72. package/extensions/subagent/settings.ts +0 -367
  73. package/extensions/subagent/single-output.ts +0 -97
  74. package/extensions/subagent/skills.ts +0 -626
  75. package/extensions/subagent/slash-bridge.ts +0 -176
  76. package/extensions/subagent/slash-commands.ts +0 -303
  77. package/extensions/subagent/slash-live-state.ts +0 -294
  78. package/extensions/subagent/subagent-control.ts +0 -150
  79. package/extensions/subagent/subagent-executor.ts +0 -1899
  80. package/extensions/subagent/subagent-prompt-runtime.ts +0 -75
  81. package/extensions/subagent/subagent-runner.ts +0 -1470
  82. package/extensions/subagent/subagents-status.ts +0 -472
  83. package/extensions/subagent/text-editor.ts +0 -272
  84. package/extensions/subagent/top-level-async.ts +0 -15
  85. package/extensions/subagent/types.ts +0 -623
  86. package/extensions/subagent/utils.ts +0 -456
  87. package/extensions/subagent/worktree.ts +0 -579
  88. package/extensions/super-pi-extension/agents/ce-oracle.md +0 -109
  89. package/extensions/super-pi-extension/agents/ce-planner.md +0 -116
  90. package/extensions/super-pi-extension/agents/ce-reviewer.md +0 -99
  91. package/extensions/super-pi-extension/agents/ce-scout.md +0 -80
  92. package/extensions/super-pi-extension/agents/ce-worker.md +0 -78
  93. package/extensions/super-pi-extension/chains/ce-parallel-review.chain.md +0 -34
  94. package/extensions/super-pi-extension/chains/ce-review-only.chain.md +0 -12
  95. package/extensions/super-pi-extension/chains/ce-standard.chain.md +0 -28
  96. package/extensions/super-pi-extension/index.ts +0 -129
  97. package/extensions/super-pi-extension/model-sync.ts +0 -204
  98. package/skills/08-status/SKILL.md +0 -41
  99. package/skills/08-status/references/artifact-locations.md +0 -10
  100. package/skills/10-rules/SKILL.md +0 -76
  101. package/skills/pi-subagents/SKILL.md +0 -566
@@ -1,738 +0,0 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
3
- /**
4
- * Core execution logic for running subagents
5
- */
6
-
7
- import { spawn } from "node:child_process";
8
- import { existsSync } from "node:fs";
9
- import type { Message } from "@mariozechner/pi-ai";
10
- import type { AgentConfig } from "./agents.ts";
11
- import {
12
- ensureArtifactsDir,
13
- getArtifactPaths,
14
- writeArtifact,
15
- writeMetadata,
16
- } from "./artifacts.ts";
17
- import {
18
- type AgentProgress,
19
- type ArtifactPaths,
20
- type ControlEvent,
21
- type ModelAttempt,
22
- type RunSyncOptions,
23
- type SingleResult,
24
- type Usage,
25
- DEFAULT_MAX_OUTPUT,
26
- INTERCOM_DETACH_REQUEST_EVENT,
27
- INTERCOM_DETACH_RESPONSE_EVENT,
28
- truncateOutput,
29
- getSubagentDepthEnv,
30
- } from "./types.ts";
31
- import {
32
- DEFAULT_CONTROL_CONFIG,
33
- buildControlEvent,
34
- claimControlNotification,
35
- deriveActivityState,
36
- shouldEmitControlEvent,
37
- shouldNotifyControlEvent,
38
- } from "./subagent-control.ts";
39
- import {
40
- getFinalOutput,
41
- findLatestSessionFile,
42
- detectSubagentError,
43
- extractToolArgsPreview,
44
- extractTextFromContent,
45
- } from "./utils.ts";
46
- import { buildSkillInjection, resolveSkillsWithFallback } from "./skills.ts";
47
- import { getPiSpawnCommand } from "./pi-spawn.ts";
48
- import { createJsonlWriter } from "./jsonl-writer.ts";
49
- import { attachPostExitStdioGuard, trySignalChild } from "./post-exit-stdio-guard.ts";
50
- import { applyThinkingSuffix, buildPiArgs, cleanupTempDir } from "./pi-args.ts";
51
- import { captureSingleOutputSnapshot, resolveSingleOutput, type SingleOutputSnapshot } from "./single-output.ts";
52
- import {
53
- buildModelCandidates,
54
- formatModelAttemptNote,
55
- isRetryableModelFailure,
56
- } from "./model-fallback.ts";
57
-
58
- function emptyUsage(): Usage {
59
- return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
60
- }
61
-
62
- function sumUsage(target: Usage, source: Usage): void {
63
- target.input += source.input;
64
- target.output += source.output;
65
- target.cacheRead += source.cacheRead;
66
- target.cacheWrite += source.cacheWrite;
67
- target.cost += source.cost;
68
- target.turns += source.turns;
69
- }
70
-
71
- function appendRecentOutput(progress: AgentProgress, lines: string[]): void {
72
- if (lines.length === 0) return;
73
- progress.recentOutput.push(...lines.filter((line) => line.trim()));
74
- if (progress.recentOutput.length > 50) {
75
- progress.recentOutput.splice(0, progress.recentOutput.length - 50);
76
- }
77
- }
78
-
79
- function snapshotProgress(progress: AgentProgress): AgentProgress {
80
- return {
81
- ...progress,
82
- skills: progress.skills ? [...progress.skills] : undefined,
83
- recentTools: progress.recentTools.map((tool) => ({ ...tool })),
84
- recentOutput: [...progress.recentOutput],
85
- };
86
- }
87
-
88
- function snapshotResult(result: SingleResult, progress: AgentProgress): SingleResult {
89
- return {
90
- ...result,
91
- messages: result.messages ? [...result.messages] : undefined,
92
- usage: { ...result.usage },
93
- skills: result.skills ? [...result.skills] : undefined,
94
- attemptedModels: result.attemptedModels ? [...result.attemptedModels] : undefined,
95
- modelAttempts: result.modelAttempts
96
- ? result.modelAttempts.map((attempt) => ({
97
- ...attempt,
98
- usage: attempt.usage ? { ...attempt.usage } : undefined,
99
- }))
100
- : undefined,
101
- controlEvents: result.controlEvents ? result.controlEvents.map((event) => ({ ...event })) : undefined,
102
- progress,
103
- progressSummary: result.progressSummary ? { ...result.progressSummary } : undefined,
104
- artifactPaths: result.artifactPaths ? { ...result.artifactPaths } : undefined,
105
- truncation: result.truncation ? { ...result.truncation } : undefined,
106
- };
107
- }
108
-
109
- async function runSingleAttempt(
110
- runtimeCwd: string,
111
- agent: AgentConfig,
112
- task: string,
113
- model: string | undefined,
114
- options: RunSyncOptions,
115
- shared: {
116
- sessionEnabled: boolean;
117
- systemPrompt: string;
118
- resolvedSkillNames?: string[];
119
- skillsWarning?: string;
120
- jsonlPath?: string;
121
- artifactPaths?: ArtifactPaths;
122
- attemptNotes: string[];
123
- outputSnapshot?: SingleOutputSnapshot;
124
- },
125
- ): Promise<SingleResult> {
126
- const modelArg = applyThinkingSuffix(model, agent.thinking);
127
- const { args, env: sharedEnv, tempDir } = buildPiArgs({
128
- baseArgs: ["--mode", "json", "-p"],
129
- task,
130
- sessionEnabled: shared.sessionEnabled,
131
- sessionDir: options.sessionDir,
132
- sessionFile: options.sessionFile,
133
- model,
134
- thinking: agent.thinking,
135
- systemPromptMode: agent.systemPromptMode,
136
- inheritProjectContext: agent.inheritProjectContext,
137
- inheritSkills: agent.inheritSkills,
138
- tools: agent.tools,
139
- extensions: agent.extensions,
140
- systemPrompt: shared.systemPrompt,
141
- mcpDirectTools: agent.mcpDirectTools,
142
- promptFileStem: agent.name,
143
- intercomSessionName: options.intercomSessionName,
144
- });
145
-
146
- const result: SingleResult = {
147
- agent: agent.name,
148
- task,
149
- exitCode: 0,
150
- messages: [],
151
- usage: emptyUsage(),
152
- model: modelArg,
153
- artifactPaths: shared.artifactPaths,
154
- skills: shared.resolvedSkillNames,
155
- skillsWarning: shared.skillsWarning,
156
- };
157
- const startTime = Date.now();
158
- const controlConfig = options.controlConfig ?? DEFAULT_CONTROL_CONFIG;
159
- let interruptedByControl = false;
160
- const allControlEvents: ControlEvent[] = [];
161
- let pendingControlEvents: ControlEvent[] = [];
162
-
163
- const progress: AgentProgress = {
164
- index: options.index ?? 0,
165
- agent: agent.name,
166
- status: "running",
167
- task,
168
- skills: shared.resolvedSkillNames,
169
- recentTools: [],
170
- recentOutput: [...shared.attemptNotes],
171
- toolCount: 0,
172
- tokens: 0,
173
- durationMs: 0,
174
- lastActivityAt: startTime,
175
- };
176
- result.progress = progress;
177
- const spawnEnv = { ...process.env, ...sharedEnv, ...getSubagentDepthEnv(options.maxSubagentDepth) };
178
-
179
- const exitCode = await new Promise<number>((resolve) => {
180
- const spawnSpec = getPiSpawnCommand(args);
181
- const proc = spawn(spawnSpec.command, spawnSpec.args, {
182
- cwd: options.cwd ?? runtimeCwd,
183
- env: spawnEnv,
184
- stdio: ["ignore", "pipe", "pipe"],
185
- });
186
- const jsonlWriter = createJsonlWriter(shared.jsonlPath, proc.stdout);
187
- let buf = "";
188
- let processClosed = false;
189
- let settled = false;
190
- let detached = false;
191
- let intercomStarted = false;
192
- let removeAbortListener: (() => void) | undefined;
193
- let removeInterruptListener: (() => void) | undefined;
194
- let activityTimer: NodeJS.Timeout | undefined;
195
-
196
- const detachForIntercom = () => {
197
- detached = true;
198
- processClosed = true;
199
- result.detached = true;
200
- result.detachedReason = "intercom coordination";
201
- progress.status = "detached";
202
- progress.durationMs = Date.now() - startTime;
203
- result.progressSummary = {
204
- toolCount: progress.toolCount,
205
- tokens: progress.tokens,
206
- durationMs: progress.durationMs,
207
- };
208
- finish(-2);
209
- };
210
-
211
- // If the child emits its final assistant message but never exits,
212
- // start a bounded drain window and force termination if needed.
213
- const FINAL_DRAIN_MS = 5000;
214
- const HARD_KILL_MS = 3000;
215
- let childExited = false;
216
- let forcedTerminationSignal = false;
217
- let finalDrainTimer: NodeJS.Timeout | undefined;
218
- let finalHardKillTimer: NodeJS.Timeout | undefined;
219
- const clearFinalDrainTimers = () => {
220
- if (finalDrainTimer) {
221
- clearTimeout(finalDrainTimer);
222
- finalDrainTimer = undefined;
223
- }
224
- if (finalHardKillTimer) {
225
- clearTimeout(finalHardKillTimer);
226
- finalHardKillTimer = undefined;
227
- }
228
- };
229
- const startFinalDrain = () => {
230
- if (childExited || finalDrainTimer || settled || processClosed || detached) return;
231
- finalDrainTimer = setTimeout(() => {
232
- if (settled || processClosed || detached) return;
233
- const termSent = trySignalChild(proc, "SIGTERM");
234
- if (!termSent) return;
235
- forcedTerminationSignal = true;
236
- result.error = result.error
237
- ?? `Subagent process did not exit within ${FINAL_DRAIN_MS}ms after its final message. Forcing termination.`;
238
- finalHardKillTimer = setTimeout(() => {
239
- if (settled || processClosed || detached) return;
240
- forcedTerminationSignal = trySignalChild(proc, "SIGKILL") || forcedTerminationSignal;
241
- }, HARD_KILL_MS);
242
- finalHardKillTimer.unref?.();
243
- }, FINAL_DRAIN_MS);
244
- finalDrainTimer.unref?.();
245
- };
246
-
247
- const unsubscribeIntercomDetach = options.intercomEvents?.on?.(INTERCOM_DETACH_REQUEST_EVENT, (payload) => {
248
- if (!options.allowIntercomDetach || detached || processClosed || !intercomStarted) return;
249
- if (!payload || typeof payload !== "object") return;
250
- const requestId = (payload as { requestId?: unknown }).requestId;
251
- if (typeof requestId !== "string" || requestId.length === 0) return;
252
- options.intercomEvents?.emit(INTERCOM_DETACH_RESPONSE_EVENT, { requestId, accepted: true });
253
- detachForIntercom();
254
- });
255
-
256
- const finish = (code: number) => {
257
- if (settled) return;
258
- settled = true;
259
- clearFinalDrainTimers();
260
- clearStdioGuard();
261
- if (activityTimer) {
262
- clearInterval(activityTimer);
263
- activityTimer = undefined;
264
- }
265
- unsubscribeIntercomDetach?.();
266
- removeAbortListener?.();
267
- removeInterruptListener?.();
268
- resolve(code);
269
- };
270
-
271
- const drainPendingControlEvents = (): ControlEvent[] | undefined => {
272
- if (pendingControlEvents.length === 0) return undefined;
273
- const events = pendingControlEvents;
274
- pendingControlEvents = [];
275
- return events;
276
- };
277
-
278
- const emittedControlEventKeys = new Set<string>();
279
- const emitControlEvent = (event: ControlEvent) => {
280
- if (shouldNotifyControlEvent(controlConfig, event) && !claimControlNotification(controlConfig, event, emittedControlEventKeys)) return;
281
- allControlEvents.push(event);
282
- pendingControlEvents.push(event);
283
- options.onControlEvent?.(event);
284
- };
285
-
286
- const updateActivityState = (now: number): boolean => {
287
- const next = deriveActivityState({
288
- config: controlConfig,
289
- startedAt: startTime,
290
- lastActivityAt: progress.lastActivityAt,
291
- now,
292
- });
293
- if (next === progress.activityState) return false;
294
- const previous = progress.activityState;
295
- progress.activityState = next;
296
- if (shouldEmitControlEvent(controlConfig, previous, next)) {
297
- emitControlEvent(buildControlEvent({
298
- from: previous,
299
- to: next,
300
- runId: options.runId,
301
- agent: agent.name,
302
- index: options.index,
303
- ts: now,
304
- lastActivityAt: progress.lastActivityAt,
305
- }));
306
- }
307
- return true;
308
- };
309
-
310
-
311
- const emitUpdateSnapshot = (text: string) => {
312
- if (!options.onUpdate || processClosed) return;
313
- const progressSnapshot = snapshotProgress(progress);
314
- const resultSnapshot = snapshotResult(result, progressSnapshot);
315
- const controlEvents = drainPendingControlEvents();
316
- options.onUpdate({
317
- content: [{ type: "text", text }],
318
- details: {
319
- mode: "single",
320
- results: [resultSnapshot],
321
- progress: [progressSnapshot],
322
- controlEvents,
323
- },
324
- });
325
- };
326
-
327
- const fireUpdate = () => {
328
- if (!options.onUpdate || processClosed) return;
329
- progress.durationMs = Date.now() - startTime;
330
- emitUpdateSnapshot(getFinalOutput(result.messages) || "(running...)");
331
- };
332
-
333
- const processLine = (line: string) => {
334
- if (!line.trim()) return;
335
- jsonlWriter.writeLine(line);
336
- let evt: { type?: string; message?: Message; toolName?: string; args?: unknown };
337
- try {
338
- evt = JSON.parse(line) as { type?: string; message?: Message; toolName?: string; args?: unknown };
339
- } catch {
340
- // Non-JSON stdout lines are expected; only structured events are parsed.
341
- return;
342
- }
343
-
344
- const now = Date.now();
345
- progress.durationMs = now - startTime;
346
- progress.lastActivityAt = now;
347
- updateActivityState(now);
348
-
349
- if (evt.type === "tool_execution_start") {
350
- if (options.allowIntercomDetach && evt.toolName === "intercom") {
351
- intercomStarted = true;
352
- }
353
- progress.toolCount++;
354
- progress.currentTool = evt.toolName;
355
- progress.currentToolArgs = extractToolArgsPreview((evt.args || {}) as Record<string, unknown>);
356
- progress.currentToolStartedAt = now;
357
- fireUpdate();
358
- }
359
-
360
- if (evt.type === "tool_execution_end") {
361
- if (progress.currentTool) {
362
- progress.recentTools.push({
363
- tool: progress.currentTool,
364
- args: progress.currentToolArgs || "",
365
- endMs: now,
366
- });
367
- }
368
- progress.currentTool = undefined;
369
- progress.currentToolArgs = undefined;
370
- progress.currentToolStartedAt = undefined;
371
- fireUpdate();
372
- }
373
-
374
- if (evt.type === "message_end" && evt.message) {
375
- result.messages.push(evt.message);
376
- if (evt.message.role === "assistant") {
377
- result.usage.turns++;
378
- const u = evt.message.usage;
379
- if (u) {
380
- result.usage.input += u.input || 0;
381
- result.usage.output += u.output || 0;
382
- result.usage.cacheRead += u.cacheRead || 0;
383
- result.usage.cacheWrite += u.cacheWrite || 0;
384
- result.usage.cost += u.cost?.total || 0;
385
- progress.tokens = result.usage.input + result.usage.output;
386
- }
387
- if (!result.model && evt.message.model) result.model = evt.message.model;
388
- if (evt.message.errorMessage) result.error = evt.message.errorMessage;
389
- appendRecentOutput(progress, extractTextFromContent(evt.message.content).split("\n").slice(-10));
390
- // Final assistant message: start the exit drain window.
391
- const stopReason = (evt.message as { stopReason?: string }).stopReason;
392
- const hasToolCall = Array.isArray(evt.message.content)
393
- && evt.message.content.some((part) => (part as { type?: string }).type === "toolCall");
394
- if (stopReason === "stop" && !hasToolCall) {
395
- startFinalDrain();
396
- }
397
- }
398
- fireUpdate();
399
- }
400
-
401
- if (evt.type === "tool_result_end" && evt.message) {
402
- result.messages.push(evt.message);
403
- appendRecentOutput(progress, extractTextFromContent(evt.message.content).split("\n").slice(-10));
404
- fireUpdate();
405
- }
406
- };
407
-
408
- if (controlConfig.enabled) {
409
- activityTimer = setInterval(() => {
410
- if (processClosed || settled || detached) return;
411
- const now = Date.now();
412
- if (updateActivityState(now)) {
413
- progress.durationMs = now - startTime;
414
- fireUpdate();
415
- }
416
- }, 1000);
417
- activityTimer.unref?.();
418
- }
419
-
420
- let stderrBuf = "";
421
-
422
- const clearStdioGuard = attachPostExitStdioGuard(proc, { idleMs: 2000, hardMs: 8000 });
423
- proc.stdout.on("data", (d) => {
424
- buf += d.toString();
425
- const lines = buf.split("\n");
426
- buf = lines.pop() || "";
427
- lines.forEach(processLine);
428
- });
429
- proc.stderr.on("data", (d) => {
430
- stderrBuf += d.toString();
431
- });
432
- proc.on("exit", () => {
433
- childExited = true;
434
- clearFinalDrainTimers();
435
- });
436
- proc.on("close", (code, signal) => {
437
- clearFinalDrainTimers();
438
- clearStdioGuard();
439
- void jsonlWriter.close().catch(() => {
440
- // JSONL artifact flush is best effort.
441
- });
442
- cleanupTempDir(tempDir);
443
- if (detached) {
444
- finish(-2);
445
- return;
446
- }
447
- processClosed = true;
448
- if (buf.trim()) processLine(buf);
449
- if (code !== 0 && stderrBuf.trim() && !result.error) {
450
- result.error = stderrBuf.trim();
451
- }
452
- const finalCode = forcedTerminationSignal || signal ? (code ?? 1) : (code ?? 0);
453
- finish(finalCode);
454
- });
455
- proc.on("error", (error) => {
456
- clearFinalDrainTimers();
457
- clearStdioGuard();
458
- void jsonlWriter.close().catch(() => {
459
- // JSONL artifact flush is best effort.
460
- });
461
- cleanupTempDir(tempDir);
462
- if (!result.error) {
463
- result.error = error instanceof Error ? error.message : String(error);
464
- }
465
- finish(1);
466
- });
467
-
468
- if (options.signal) {
469
- const kill = () => {
470
- if (processClosed || detached) return;
471
- if (options.allowIntercomDetach && intercomStarted && !detached) {
472
- detachForIntercom();
473
- return;
474
- }
475
- proc.kill("SIGTERM");
476
- setTimeout(() => !proc.killed && proc.kill("SIGKILL"), 3000);
477
- };
478
- if (options.signal.aborted) kill();
479
- else {
480
- options.signal.addEventListener("abort", kill, { once: true });
481
- removeAbortListener = () => options.signal?.removeEventListener("abort", kill);
482
- }
483
- }
484
-
485
- if (options.interruptSignal) {
486
- const interrupt = () => {
487
- if (processClosed || detached || settled) return;
488
- interruptedByControl = true;
489
- progress.status = "running";
490
- progress.durationMs = Date.now() - startTime;
491
- result.interrupted = true;
492
- result.finalOutput = "Interrupted. Waiting for explicit next action.";
493
- progress.activityState = undefined;
494
- fireUpdate();
495
- trySignalChild(proc, "SIGINT");
496
- setTimeout(() => {
497
- if (settled || processClosed || detached) return;
498
- trySignalChild(proc, "SIGTERM");
499
- }, 1000).unref?.();
500
- };
501
- if (options.interruptSignal.aborted) interrupt();
502
- else {
503
- options.interruptSignal.addEventListener("abort", interrupt, { once: true });
504
- removeInterruptListener = () => options.interruptSignal?.removeEventListener("abort", interrupt);
505
- }
506
- }
507
- });
508
- result.exitCode = exitCode;
509
- if (interruptedByControl) {
510
- result.exitCode = 0;
511
- result.interrupted = true;
512
- result.error = undefined;
513
- result.finalOutput = result.finalOutput || "Interrupted. Waiting for explicit next action.";
514
- result.controlEvents = allControlEvents.length ? allControlEvents : undefined;
515
- progress.activityState = undefined;
516
- progress.durationMs = Date.now() - startTime;
517
- result.progressSummary = {
518
- toolCount: progress.toolCount,
519
- tokens: progress.tokens,
520
- durationMs: progress.durationMs,
521
- };
522
- return result;
523
- }
524
- if (result.detached) {
525
- result.exitCode = 0;
526
- result.finalOutput = "Detached for intercom coordination.";
527
- return result;
528
- }
529
-
530
- if (exitCode === 0 && !result.error) {
531
- const errInfo = detectSubagentError(result.messages);
532
- if (errInfo.hasError) {
533
- result.exitCode = errInfo.exitCode ?? 1;
534
- result.error = errInfo.details
535
- ? `${errInfo.errorType} failed (exit ${errInfo.exitCode}): ${errInfo.details}`
536
- : `${errInfo.errorType} failed with exit code ${errInfo.exitCode}`;
537
- }
538
- }
539
-
540
- progress.status = result.exitCode === 0 ? "completed" : "failed";
541
- progress.durationMs = Date.now() - startTime;
542
- if (result.error) {
543
- progress.error = result.error;
544
- if (progress.currentTool) {
545
- progress.failedTool = progress.currentTool;
546
- }
547
- }
548
-
549
- result.progressSummary = {
550
- toolCount: progress.toolCount,
551
- tokens: progress.tokens,
552
- durationMs: progress.durationMs,
553
- };
554
-
555
- let fullOutput = getFinalOutput(result.messages);
556
- if (options.outputPath && result.exitCode === 0) {
557
- const resolvedOutput = resolveSingleOutput(options.outputPath, fullOutput, shared.outputSnapshot);
558
- fullOutput = resolvedOutput.fullOutput;
559
- result.savedOutputPath = resolvedOutput.savedPath;
560
- result.outputSaveError = resolvedOutput.saveError;
561
- }
562
- result.finalOutput = fullOutput;
563
- if (options.onUpdate) {
564
- const finalText = result.finalOutput || result.error || "(no output)";
565
- const progressSnapshot = snapshotProgress(progress);
566
- const resultSnapshot = snapshotResult(result, progressSnapshot);
567
- options.onUpdate({
568
- content: [{ type: "text", text: finalText }],
569
- details: { mode: "single", results: [resultSnapshot], progress: [progressSnapshot] },
570
- });
571
- }
572
- return result;
573
- }
574
-
575
- /**
576
- * Run a subagent synchronously (blocking until complete)
577
- */
578
- export async function runSync(
579
- runtimeCwd: string,
580
- agents: AgentConfig[],
581
- agentName: string,
582
- task: string,
583
- options: RunSyncOptions,
584
- ): Promise<SingleResult> {
585
- const agent = agents.find((a) => a.name === agentName);
586
- if (!agent) {
587
- return {
588
- agent: agentName,
589
- task,
590
- exitCode: 1,
591
- messages: [],
592
- usage: emptyUsage(),
593
- error: `Unknown agent: ${agentName}`,
594
- };
595
- }
596
-
597
- const shareEnabled = options.share === true;
598
- const sessionEnabled = Boolean(options.sessionFile || options.sessionDir) || shareEnabled;
599
- const outputSnapshot = captureSingleOutputSnapshot(options.outputPath);
600
- const skillNames = options.skills ?? agent.skills ?? [];
601
- const skillCwd = options.cwd ?? runtimeCwd;
602
- const { resolved: resolvedSkills, missing: missingSkills } = resolveSkillsWithFallback(skillNames, skillCwd, runtimeCwd);
603
- let systemPrompt = agent.systemPrompt?.trim() || "";
604
- if (resolvedSkills.length > 0) {
605
- const skillInjection = buildSkillInjection(resolvedSkills);
606
- systemPrompt = systemPrompt ? `${systemPrompt}\n\n${skillInjection}` : skillInjection;
607
- }
608
-
609
- const candidates = buildModelCandidates(
610
- options.modelOverride ?? agent.model,
611
- agent.fallbackModels,
612
- options.availableModels,
613
- options.preferredModelProvider,
614
- );
615
- const attemptedModels: string[] = [];
616
- const modelAttempts: ModelAttempt[] = [];
617
- const aggregateUsage = emptyUsage();
618
- const attemptNotes: string[] = [];
619
- let totalToolCount = 0;
620
- let totalDurationMs = 0;
621
-
622
- let artifactPathsResult: ArtifactPaths | undefined;
623
- let jsonlPath: string | undefined;
624
- if (options.artifactsDir && options.artifactConfig?.enabled !== false) {
625
- artifactPathsResult = getArtifactPaths(options.artifactsDir, options.runId, agentName, options.index);
626
- ensureArtifactsDir(options.artifactsDir);
627
- if (options.artifactConfig?.includeInput !== false) {
628
- writeArtifact(artifactPathsResult.inputPath, `# Task for ${agentName}\n\n${task}`);
629
- }
630
- if (options.artifactConfig?.includeJsonl !== false) {
631
- jsonlPath = artifactPathsResult.jsonlPath;
632
- }
633
- }
634
-
635
- let lastResult: SingleResult | undefined;
636
- const modelsToTry = candidates.length > 0 ? candidates : [undefined];
637
- for (let i = 0; i < modelsToTry.length; i++) {
638
- const candidate = modelsToTry[i];
639
- if (candidate) attemptedModels.push(candidate);
640
- const result = await runSingleAttempt(runtimeCwd, agent, task, candidate, options, {
641
- sessionEnabled,
642
- systemPrompt,
643
- resolvedSkillNames: resolvedSkills.length > 0 ? resolvedSkills.map((skill) => skill.name) : undefined,
644
- skillsWarning: missingSkills.length > 0 ? `Skills not found: ${missingSkills.join(", ")}` : undefined,
645
- jsonlPath,
646
- artifactPaths: artifactPathsResult,
647
- attemptNotes,
648
- outputSnapshot,
649
- });
650
- lastResult = result;
651
- sumUsage(aggregateUsage, result.usage);
652
- totalToolCount += result.progressSummary?.toolCount ?? 0;
653
- totalDurationMs += result.progressSummary?.durationMs ?? 0;
654
- const attempt: ModelAttempt = {
655
- model: candidate ?? result.model ?? agent.model ?? "default",
656
- success: result.exitCode === 0,
657
- exitCode: result.exitCode,
658
- error: result.error,
659
- usage: { ...result.usage },
660
- };
661
- modelAttempts.push(attempt);
662
- if (result.exitCode === 0) {
663
- break;
664
- }
665
- if (!isRetryableModelFailure(result.error) || i === modelsToTry.length - 1) {
666
- break;
667
- }
668
- attemptNotes.push(formatModelAttemptNote(attempt, modelsToTry[i + 1]));
669
- }
670
-
671
- const result = lastResult ?? {
672
- agent: agentName,
673
- task,
674
- exitCode: 1,
675
- messages: [],
676
- usage: emptyUsage(),
677
- error: "Subagent did not produce a result.",
678
- } satisfies SingleResult;
679
-
680
- result.usage = aggregateUsage;
681
- result.attemptedModels = attemptedModels.length > 0 ? attemptedModels : undefined;
682
- result.modelAttempts = modelAttempts.length > 0 ? modelAttempts : undefined;
683
- result.progressSummary = {
684
- toolCount: totalToolCount,
685
- tokens: aggregateUsage.input + aggregateUsage.output,
686
- durationMs: totalDurationMs,
687
- };
688
- if (attemptNotes.length > 0 && result.progress) {
689
- result.progress.recentOutput = [...attemptNotes, ...result.progress.recentOutput];
690
- if (result.progress.recentOutput.length > 50) {
691
- result.progress.recentOutput.splice(50);
692
- }
693
- }
694
-
695
- if (artifactPathsResult && options.artifactConfig?.enabled !== false) {
696
- result.artifactPaths = artifactPathsResult;
697
- if (options.artifactConfig?.includeOutput !== false) {
698
- writeArtifact(artifactPathsResult.outputPath, result.finalOutput ?? "");
699
- }
700
- if (options.artifactConfig?.includeMetadata !== false) {
701
- writeMetadata(artifactPathsResult.metadataPath, {
702
- runId: options.runId,
703
- agent: agentName,
704
- task,
705
- exitCode: result.exitCode,
706
- usage: result.usage,
707
- model: result.model,
708
- attemptedModels: result.attemptedModels,
709
- modelAttempts: result.modelAttempts,
710
- durationMs: result.progressSummary?.durationMs,
711
- toolCount: result.progressSummary?.toolCount,
712
- error: result.error,
713
- skills: result.skills,
714
- skillsWarning: result.skillsWarning,
715
- timestamp: Date.now(),
716
- });
717
- }
718
-
719
- if (options.maxOutput) {
720
- const config = { ...DEFAULT_MAX_OUTPUT, ...options.maxOutput };
721
- const truncationResult = truncateOutput(result.finalOutput ?? "", config, artifactPathsResult.outputPath);
722
- if (truncationResult.truncated) result.truncation = truncationResult;
723
- }
724
- } else if (options.maxOutput) {
725
- const config = { ...DEFAULT_MAX_OUTPUT, ...options.maxOutput };
726
- const truncationResult = truncateOutput(result.finalOutput ?? "", config);
727
- if (truncationResult.truncated) result.truncation = truncationResult;
728
- }
729
-
730
- if (options.sessionFile && (existsSync(options.sessionFile) || result.messages?.length)) {
731
- result.sessionFile = options.sessionFile;
732
- } else if (shareEnabled && options.sessionDir) {
733
- const sessionFile = findLatestSessionFile(options.sessionDir);
734
- if (sessionFile) result.sessionFile = sessionFile;
735
- }
736
-
737
- return result;
738
- }