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