pi-subagents 0.24.3 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -5
- package/README.md +19 -11
- package/package.json +4 -8
- package/prompts/review-loop.md +1 -1
- package/skills/pi-subagents/SKILL.md +46 -10
- package/src/agents/agent-management.ts +5 -0
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +30 -6
- package/src/agents/skills.ts +25 -23
- package/src/extension/config.ts +16 -0
- package/src/extension/fanout-child.ts +170 -0
- package/src/extension/index.ts +13 -25
- package/src/intercom/intercom-bridge.ts +2 -1
- package/src/intercom/result-intercom.ts +108 -0
- package/src/runs/background/async-execution.ts +107 -7
- package/src/runs/background/async-job-tracker.ts +57 -14
- package/src/runs/background/async-resume.ts +28 -15
- package/src/runs/background/async-status.ts +60 -30
- package/src/runs/background/result-watcher.ts +111 -54
- package/src/runs/background/run-id-resolver.ts +83 -0
- package/src/runs/background/run-status.ts +79 -3
- package/src/runs/background/stale-run-reconciler.ts +46 -1
- package/src/runs/background/subagent-runner.ts +66 -18
- package/src/runs/foreground/chain-execution.ts +6 -0
- package/src/runs/foreground/execution.ts +21 -5
- package/src/runs/foreground/subagent-executor.ts +314 -18
- package/src/runs/shared/completion-guard.ts +23 -1
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +365 -0
- package/src/runs/shared/nested-events.ts +819 -0
- package/src/runs/shared/nested-path.ts +52 -0
- package/src/runs/shared/nested-render.ts +115 -0
- package/src/runs/shared/parallel-utils.ts +1 -0
- package/src/runs/shared/pi-args.ts +67 -5
- package/src/runs/shared/run-history.ts +12 -7
- package/src/runs/shared/single-output.ts +12 -2
- package/src/runs/shared/subagent-prompt-runtime.ts +25 -5
- package/src/shared/artifacts.ts +2 -2
- package/src/shared/types.ts +95 -0
- package/src/shared/utils.ts +11 -1
- package/src/tui/render.ts +254 -153
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
type AsyncParallelGroupStatus,
|
|
15
15
|
type AsyncStatus,
|
|
16
16
|
type ModelAttempt,
|
|
17
|
+
type NestedRouteInfo,
|
|
17
18
|
type ResolvedControlConfig,
|
|
18
19
|
type SubagentRunMode,
|
|
19
20
|
type Usage,
|
|
@@ -40,6 +41,7 @@ import {
|
|
|
40
41
|
MAX_PARALLEL_CONCURRENCY,
|
|
41
42
|
} from "../shared/parallel-utils.ts";
|
|
42
43
|
import { buildPiArgs, cleanupTempDir } from "../shared/pi-args.ts";
|
|
44
|
+
import { nestedSummaryFromAsyncStatus, writeNestedEvent } from "../shared/nested-events.ts";
|
|
43
45
|
import { formatModelAttemptNote, isRetryableModelFailure } from "../shared/model-fallback.ts";
|
|
44
46
|
import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
|
|
45
47
|
import { detectSubagentError, extractTextFromContent, extractToolArgsPreview, getFinalOutput } from "../../shared/utils.ts";
|
|
@@ -92,6 +94,8 @@ interface SubagentRunConfig {
|
|
|
92
94
|
controlIntercomTarget?: string;
|
|
93
95
|
childIntercomTargets?: Array<string | undefined>;
|
|
94
96
|
resultMode?: SubagentRunMode;
|
|
97
|
+
nestedRoute?: NestedRouteInfo;
|
|
98
|
+
nestedSelf?: { parentRunId: string; parentStepIndex?: number; depth: number; path?: Array<{ runId: string; stepIndex?: number; agent?: string }> };
|
|
95
99
|
}
|
|
96
100
|
|
|
97
101
|
interface StepResult {
|
|
@@ -222,7 +226,12 @@ function runPiStreaming(
|
|
|
222
226
|
...(piPackageRoot ? { piPackageRoot } : {}),
|
|
223
227
|
...(piArgv1 ? { argv1: piArgv1 } : {}),
|
|
224
228
|
});
|
|
225
|
-
const child = spawn(spawnSpec.command, spawnSpec.args, {
|
|
229
|
+
const child = spawn(spawnSpec.command, spawnSpec.args, {
|
|
230
|
+
cwd,
|
|
231
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
232
|
+
env: spawnEnv,
|
|
233
|
+
windowsHide: true,
|
|
234
|
+
});
|
|
226
235
|
let stderr = "";
|
|
227
236
|
let stdoutBuf = "";
|
|
228
237
|
let stderrBuf = "";
|
|
@@ -230,6 +239,7 @@ function runPiStreaming(
|
|
|
230
239
|
const usage = emptyUsage();
|
|
231
240
|
let model: string | undefined;
|
|
232
241
|
let error: string | undefined;
|
|
242
|
+
let assistantError: string | undefined;
|
|
233
243
|
let interrupted = false;
|
|
234
244
|
let observedMutationAttempt = false;
|
|
235
245
|
const rawStdoutLines: string[] = [];
|
|
@@ -290,7 +300,7 @@ function runPiStreaming(
|
|
|
290
300
|
|
|
291
301
|
if (event.type !== "message_end" || event.message.role !== "assistant") return;
|
|
292
302
|
if (event.message.model) model = event.message.model;
|
|
293
|
-
if (event.message.errorMessage)
|
|
303
|
+
if (event.message.errorMessage) assistantError = event.message.errorMessage;
|
|
294
304
|
const eventUsage = event.message.usage;
|
|
295
305
|
if (eventUsage) {
|
|
296
306
|
usage.turns++;
|
|
@@ -304,6 +314,7 @@ function runPiStreaming(
|
|
|
304
314
|
const hasToolCall = Array.isArray(event.message.content)
|
|
305
315
|
&& event.message.content.some((part) => (part as { type?: string }).type === "toolCall");
|
|
306
316
|
if (stopReason === "stop" && !hasToolCall) {
|
|
317
|
+
if (!event.message.errorMessage && extractTextFromContent(event.message.content).trim()) assistantError = undefined;
|
|
307
318
|
cleanTerminalAssistantStopReceived ||= !event.message.errorMessage;
|
|
308
319
|
startFinalDrain();
|
|
309
320
|
}
|
|
@@ -371,7 +382,7 @@ function runPiStreaming(
|
|
|
371
382
|
const termSent = trySignalChild(child, "SIGTERM");
|
|
372
383
|
if (!termSent) return;
|
|
373
384
|
forcedTerminationSignal = true;
|
|
374
|
-
if (!cleanTerminalAssistantStopReceived && !error) {
|
|
385
|
+
if (!cleanTerminalAssistantStopReceived && !error && !assistantError) {
|
|
375
386
|
error = `Subagent process did not exit within ${FINAL_STOP_GRACE_MS}ms after its final message. Forcing termination.`;
|
|
376
387
|
}
|
|
377
388
|
finalHardKillTimer = setTimeout(() => {
|
|
@@ -395,14 +406,15 @@ function runPiStreaming(
|
|
|
395
406
|
if (stderrBuf.trim()) appendChildLine("subagent.child.stderr", stderrBuf);
|
|
396
407
|
outputStream.end();
|
|
397
408
|
const finalOutput = getFinalOutput(messages) || rawStdoutLines.join("\n").trim();
|
|
398
|
-
const
|
|
409
|
+
const finalError = error ?? assistantError;
|
|
410
|
+
const forcedDrainAfterFinalSuccess = forcedTerminationSignal && cleanTerminalAssistantStopReceived && !finalError;
|
|
399
411
|
resolve({
|
|
400
412
|
stderr,
|
|
401
413
|
exitCode: interrupted || forcedDrainAfterFinalSuccess ? 0 : forcedTerminationSignal || signal ? (exitCode ?? 1) : exitCode,
|
|
402
414
|
messages,
|
|
403
415
|
usage,
|
|
404
416
|
model,
|
|
405
|
-
error: interrupted || forcedDrainAfterFinalSuccess ? undefined :
|
|
417
|
+
error: interrupted || forcedDrainAfterFinalSuccess ? undefined : finalError,
|
|
406
418
|
finalOutput,
|
|
407
419
|
interrupted,
|
|
408
420
|
observedMutationAttempt,
|
|
@@ -417,7 +429,7 @@ function runPiStreaming(
|
|
|
417
429
|
outputStream.end();
|
|
418
430
|
const finalOutput = getFinalOutput(messages) || rawStdoutLines.join("\n").trim();
|
|
419
431
|
const spawnErrorMessage = spawnError instanceof Error ? spawnError.message : String(spawnError);
|
|
420
|
-
resolve({ stderr, exitCode: 1, messages, usage, model, error: error ?? spawnErrorMessage, finalOutput, observedMutationAttempt });
|
|
432
|
+
resolve({ stderr, exitCode: 1, messages, usage, model, error: error ?? assistantError ?? spawnErrorMessage, finalOutput, observedMutationAttempt });
|
|
421
433
|
});
|
|
422
434
|
});
|
|
423
435
|
}
|
|
@@ -546,6 +558,7 @@ interface SingleStepContext {
|
|
|
546
558
|
registerInterrupt?: (interrupt: (() => void) | undefined) => void;
|
|
547
559
|
childIntercomTarget?: string;
|
|
548
560
|
orchestratorIntercomTarget?: string;
|
|
561
|
+
nestedRoute?: NestedRouteInfo;
|
|
549
562
|
onAttemptStart?: (attempt: { model?: string; thinking?: string }) => void;
|
|
550
563
|
onChildEvent?: (event: ChildEvent) => void;
|
|
551
564
|
}
|
|
@@ -614,12 +627,17 @@ async function runSingleStep(
|
|
|
614
627
|
systemPrompt: step.systemPrompt,
|
|
615
628
|
systemPromptMode: step.systemPromptMode,
|
|
616
629
|
mcpDirectTools: step.mcpDirectTools,
|
|
630
|
+
cwd: step.cwd ?? ctx.cwd,
|
|
617
631
|
promptFileStem: step.agent,
|
|
618
632
|
intercomSessionName: ctx.childIntercomTarget,
|
|
619
633
|
orchestratorIntercomTarget: ctx.orchestratorIntercomTarget,
|
|
620
634
|
runId: ctx.id,
|
|
621
635
|
childAgentName: step.agent,
|
|
622
636
|
childIndex: ctx.flatIndex,
|
|
637
|
+
parentEventSink: ctx.nestedRoute?.eventSink,
|
|
638
|
+
parentControlInbox: ctx.nestedRoute?.controlInbox,
|
|
639
|
+
parentRootRunId: ctx.nestedRoute?.rootRunId,
|
|
640
|
+
parentCapabilityToken: ctx.nestedRoute?.capabilityToken,
|
|
623
641
|
});
|
|
624
642
|
const run = await runPiStreaming(
|
|
625
643
|
args,
|
|
@@ -636,11 +654,13 @@ async function runSingleStep(
|
|
|
636
654
|
cleanupTempDir(tempDir);
|
|
637
655
|
|
|
638
656
|
const hiddenError = run.exitCode === 0 && !run.error ? detectSubagentError(run.messages) : null;
|
|
639
|
-
const completionGuard = run.exitCode === 0 && !run.error && !hiddenError?.hasError
|
|
657
|
+
const completionGuard = run.exitCode === 0 && !run.error && !hiddenError?.hasError && step.completionGuard !== false
|
|
640
658
|
? evaluateCompletionMutationGuard({
|
|
641
659
|
agent: step.agent,
|
|
642
660
|
task,
|
|
643
661
|
messages: run.messages,
|
|
662
|
+
tools: step.tools,
|
|
663
|
+
mcpDirectTools: step.mcpDirectTools,
|
|
644
664
|
})
|
|
645
665
|
: undefined;
|
|
646
666
|
const completionGuardTriggered = completionGuard?.triggered === true && !run.observedMutationAttempt;
|
|
@@ -927,6 +947,32 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
927
947
|
|
|
928
948
|
fs.mkdirSync(asyncDir, { recursive: true });
|
|
929
949
|
writeAtomicJson(statusPath, statusPayload);
|
|
950
|
+
const emitNestedSelfEvent = (type: "subagent.nested.updated" | "subagent.nested.completed"): void => {
|
|
951
|
+
if (!config.nestedRoute || !config.nestedSelf) return;
|
|
952
|
+
try {
|
|
953
|
+
writeNestedEvent(config.nestedRoute, {
|
|
954
|
+
type,
|
|
955
|
+
ts: Date.now(),
|
|
956
|
+
parentRunId: config.nestedSelf.parentRunId,
|
|
957
|
+
parentStepIndex: config.nestedSelf.parentStepIndex,
|
|
958
|
+
child: nestedSummaryFromAsyncStatus(statusPayload, asyncDir, {
|
|
959
|
+
id,
|
|
960
|
+
parentRunId: config.nestedSelf.parentRunId,
|
|
961
|
+
parentStepIndex: config.nestedSelf.parentStepIndex,
|
|
962
|
+
depth: config.nestedSelf.depth,
|
|
963
|
+
path: config.nestedSelf.path,
|
|
964
|
+
mode: statusPayload.mode,
|
|
965
|
+
ts: Date.now(),
|
|
966
|
+
}),
|
|
967
|
+
});
|
|
968
|
+
} catch (error) {
|
|
969
|
+
console.error("Failed to emit nested async status event:", error);
|
|
970
|
+
}
|
|
971
|
+
};
|
|
972
|
+
const writeStatusPayload = (): void => {
|
|
973
|
+
writeAtomicJson(statusPath, statusPayload);
|
|
974
|
+
emitNestedSelfEvent(statusPayload.state === "running" || statusPayload.state === "queued" ? "subagent.nested.updated" : "subagent.nested.completed");
|
|
975
|
+
};
|
|
930
976
|
|
|
931
977
|
const stepOutputActivityAt = (index: number): number => {
|
|
932
978
|
const step = statusPayload.steps[index];
|
|
@@ -1017,7 +1063,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1017
1063
|
step.model = model;
|
|
1018
1064
|
step.thinking = thinking;
|
|
1019
1065
|
statusPayload.lastUpdate = now;
|
|
1020
|
-
|
|
1066
|
+
writeStatusPayload();
|
|
1021
1067
|
};
|
|
1022
1068
|
const updateStepFromChildEvent = (flatIndex: number, event: ChildEvent): void => {
|
|
1023
1069
|
const step = statusPayload.steps[flatIndex];
|
|
@@ -1105,7 +1151,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1105
1151
|
statusPayload.lastActivityAt = now;
|
|
1106
1152
|
statusPayload.lastUpdate = now;
|
|
1107
1153
|
maybeEmitActiveLongRunning(flatIndex, now);
|
|
1108
|
-
|
|
1154
|
+
writeStatusPayload();
|
|
1109
1155
|
};
|
|
1110
1156
|
const updateRunnerActivityState = (now: number): boolean => {
|
|
1111
1157
|
if (!controlConfig.enabled) return false;
|
|
@@ -1160,7 +1206,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1160
1206
|
changed = true;
|
|
1161
1207
|
}
|
|
1162
1208
|
statusPayload.lastUpdate = now;
|
|
1163
|
-
if (changed)
|
|
1209
|
+
if (changed) writeStatusPayload();
|
|
1164
1210
|
return changed;
|
|
1165
1211
|
};
|
|
1166
1212
|
if (controlConfig.enabled) {
|
|
@@ -1189,7 +1235,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1189
1235
|
step.lastActivityAt = now;
|
|
1190
1236
|
}
|
|
1191
1237
|
}
|
|
1192
|
-
|
|
1238
|
+
writeStatusPayload();
|
|
1193
1239
|
appendJsonl(eventsPath, JSON.stringify({
|
|
1194
1240
|
type: "subagent.run.paused",
|
|
1195
1241
|
ts: now,
|
|
@@ -1300,7 +1346,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1300
1346
|
statusPayload.steps[fi].exitCode = -1;
|
|
1301
1347
|
statusPayload.steps[fi].activityState = undefined;
|
|
1302
1348
|
statusPayload.lastUpdate = skippedAt;
|
|
1303
|
-
|
|
1349
|
+
writeStatusPayload();
|
|
1304
1350
|
appendJsonl(eventsPath, JSON.stringify({
|
|
1305
1351
|
type: "subagent.step.failed", ts: skippedAt, runId: id, stepIndex: fi, agent: task.agent, exitCode: -1, durationMs: 0,
|
|
1306
1352
|
}));
|
|
@@ -1320,7 +1366,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1320
1366
|
statusPayload.outputFile = path.join(asyncDir, `output-${fi}.log`);
|
|
1321
1367
|
statusPayload.lastActivityAt = taskStartTime;
|
|
1322
1368
|
statusPayload.lastUpdate = taskStartTime;
|
|
1323
|
-
|
|
1369
|
+
writeStatusPayload();
|
|
1324
1370
|
|
|
1325
1371
|
appendJsonl(eventsPath, JSON.stringify({
|
|
1326
1372
|
type: "subagent.step.started", ts: taskStartTime, runId: id, stepIndex: fi, agent: task.agent,
|
|
@@ -1341,6 +1387,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1341
1387
|
piArgv1: config.piArgv1,
|
|
1342
1388
|
childIntercomTarget: config.childIntercomTargets?.[fi],
|
|
1343
1389
|
orchestratorIntercomTarget: config.controlIntercomTarget,
|
|
1390
|
+
nestedRoute: config.nestedRoute,
|
|
1344
1391
|
registerInterrupt: (interrupt) => {
|
|
1345
1392
|
activeChildInterrupt = interrupt;
|
|
1346
1393
|
},
|
|
@@ -1364,7 +1411,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1364
1411
|
statusPayload.steps[fi].modelAttempts = singleResult.modelAttempts;
|
|
1365
1412
|
statusPayload.steps[fi].error = singleResult.error;
|
|
1366
1413
|
statusPayload.lastUpdate = taskEndTime;
|
|
1367
|
-
|
|
1414
|
+
writeStatusPayload();
|
|
1368
1415
|
|
|
1369
1416
|
appendJsonl(eventsPath, JSON.stringify({
|
|
1370
1417
|
type: singleResult.exitCode === 0 ? "subagent.step.completed" : "subagent.step.failed",
|
|
@@ -1408,7 +1455,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1408
1455
|
}
|
|
1409
1456
|
statusPayload.totalTokens = { ...previousCumulativeTokens };
|
|
1410
1457
|
statusPayload.lastUpdate = Date.now();
|
|
1411
|
-
|
|
1458
|
+
writeStatusPayload();
|
|
1412
1459
|
|
|
1413
1460
|
for (const pr of parallelResults) {
|
|
1414
1461
|
results.push({
|
|
@@ -1466,7 +1513,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1466
1513
|
statusPayload.lastActivityAt = stepStartTime;
|
|
1467
1514
|
statusPayload.lastUpdate = stepStartTime;
|
|
1468
1515
|
statusPayload.outputFile = path.join(asyncDir, `output-${flatIndex}.log`);
|
|
1469
|
-
|
|
1516
|
+
writeStatusPayload();
|
|
1470
1517
|
|
|
1471
1518
|
appendJsonl(eventsPath, JSON.stringify({
|
|
1472
1519
|
type: "subagent.step.started",
|
|
@@ -1486,6 +1533,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1486
1533
|
piArgv1: config.piArgv1,
|
|
1487
1534
|
childIntercomTarget: config.childIntercomTargets?.[flatIndex],
|
|
1488
1535
|
orchestratorIntercomTarget: config.controlIntercomTarget,
|
|
1536
|
+
nestedRoute: config.nestedRoute,
|
|
1489
1537
|
registerInterrupt: (interrupt) => {
|
|
1490
1538
|
activeChildInterrupt = interrupt;
|
|
1491
1539
|
},
|
|
@@ -1546,7 +1594,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1546
1594
|
statusPayload.totalTokens = { ...previousCumulativeTokens };
|
|
1547
1595
|
}
|
|
1548
1596
|
statusPayload.lastUpdate = stepEndTime;
|
|
1549
|
-
|
|
1597
|
+
writeStatusPayload();
|
|
1550
1598
|
|
|
1551
1599
|
appendJsonl(eventsPath, JSON.stringify({
|
|
1552
1600
|
type: singleResult.exitCode === 0 ? "subagent.step.completed" : "subagent.step.failed",
|
|
@@ -1648,7 +1696,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1648
1696
|
statusPayload.error = `Step failed: ${failedStep.agent}`;
|
|
1649
1697
|
}
|
|
1650
1698
|
}
|
|
1651
|
-
|
|
1699
|
+
writeStatusPayload();
|
|
1652
1700
|
appendJsonl(
|
|
1653
1701
|
eventsPath,
|
|
1654
1702
|
JSON.stringify({
|
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
type ControlEvent,
|
|
52
52
|
type Details,
|
|
53
53
|
type IntercomEventBus,
|
|
54
|
+
type NestedRouteInfo,
|
|
54
55
|
type ResolvedControlConfig,
|
|
55
56
|
type SingleResult,
|
|
56
57
|
MAX_CONCURRENCY,
|
|
@@ -112,6 +113,7 @@ interface ParallelChainRunInput {
|
|
|
112
113
|
totalSteps: number;
|
|
113
114
|
worktreeSetup?: WorktreeSetup;
|
|
114
115
|
maxSubagentDepth: number;
|
|
116
|
+
nestedRoute?: NestedRouteInfo;
|
|
115
117
|
}
|
|
116
118
|
|
|
117
119
|
function buildChainExecutionDetails(input: ChainExecutionDetailsInput): Details {
|
|
@@ -244,6 +246,7 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
244
246
|
onControlEvent: input.onControlEvent,
|
|
245
247
|
intercomSessionName: input.childIntercomTarget?.(task.agent, input.globalTaskIndex + taskIndex),
|
|
246
248
|
orchestratorIntercomTarget: input.orchestratorIntercomTarget,
|
|
249
|
+
nestedRoute: input.nestedRoute,
|
|
247
250
|
modelOverride: effectiveModel,
|
|
248
251
|
availableModels: input.availableModels,
|
|
249
252
|
preferredModelProvider: input.ctx.model?.provider,
|
|
@@ -331,6 +334,7 @@ interface ChainExecutionParams {
|
|
|
331
334
|
chainSkills?: string[];
|
|
332
335
|
chainDir?: string;
|
|
333
336
|
maxSubagentDepth: number;
|
|
337
|
+
nestedRoute?: NestedRouteInfo;
|
|
334
338
|
worktreeSetupHook?: string;
|
|
335
339
|
worktreeSetupHookTimeoutMs?: number;
|
|
336
340
|
}
|
|
@@ -591,6 +595,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
591
595
|
childIntercomTarget,
|
|
592
596
|
orchestratorIntercomTarget,
|
|
593
597
|
foregroundControl,
|
|
598
|
+
nestedRoute: params.nestedRoute,
|
|
594
599
|
worktreeSetup,
|
|
595
600
|
maxSubagentDepth: params.maxSubagentDepth,
|
|
596
601
|
});
|
|
@@ -793,6 +798,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
793
798
|
onControlEvent,
|
|
794
799
|
intercomSessionName: childIntercomTarget?.(seqStep.agent, globalTaskIndex),
|
|
795
800
|
orchestratorIntercomTarget,
|
|
801
|
+
nestedRoute: params.nestedRoute,
|
|
796
802
|
modelOverride: effectiveModel,
|
|
797
803
|
availableModels,
|
|
798
804
|
preferredModelProvider: ctx.model?.provider,
|
|
@@ -151,12 +151,17 @@ async function runSingleAttempt(
|
|
|
151
151
|
extensions: agent.extensions,
|
|
152
152
|
systemPrompt: shared.systemPrompt,
|
|
153
153
|
mcpDirectTools: agent.mcpDirectTools,
|
|
154
|
+
cwd: options.cwd ?? runtimeCwd,
|
|
154
155
|
promptFileStem: agent.name,
|
|
155
156
|
intercomSessionName: options.intercomSessionName,
|
|
156
157
|
orchestratorIntercomTarget: options.orchestratorIntercomTarget,
|
|
157
158
|
runId: options.runId,
|
|
158
159
|
childAgentName: agent.name,
|
|
159
160
|
childIndex: options.index ?? 0,
|
|
161
|
+
parentEventSink: options.nestedRoute?.eventSink,
|
|
162
|
+
parentControlInbox: options.nestedRoute?.controlInbox,
|
|
163
|
+
parentRootRunId: options.nestedRoute?.rootRunId,
|
|
164
|
+
parentCapabilityToken: options.nestedRoute?.capabilityToken,
|
|
160
165
|
});
|
|
161
166
|
|
|
162
167
|
const result: SingleResult = {
|
|
@@ -207,6 +212,7 @@ async function runSingleAttempt(
|
|
|
207
212
|
cwd: options.cwd ?? runtimeCwd,
|
|
208
213
|
env: spawnEnv,
|
|
209
214
|
stdio: ["ignore", "pipe", "pipe"],
|
|
215
|
+
windowsHide: true,
|
|
210
216
|
});
|
|
211
217
|
const jsonlWriter = createJsonlWriter(shared.jsonlPath, proc.stdout);
|
|
212
218
|
let buf = "";
|
|
@@ -214,6 +220,7 @@ async function runSingleAttempt(
|
|
|
214
220
|
let settled = false;
|
|
215
221
|
let detached = false;
|
|
216
222
|
let intercomStarted = false;
|
|
223
|
+
let assistantError: string | undefined;
|
|
217
224
|
let removeAbortListener: (() => void) | undefined;
|
|
218
225
|
let removeInterruptListener: (() => void) | undefined;
|
|
219
226
|
let activityTimer: NodeJS.Timeout | undefined;
|
|
@@ -259,7 +266,7 @@ async function runSingleAttempt(
|
|
|
259
266
|
const termSent = trySignalChild(proc, "SIGTERM");
|
|
260
267
|
if (!termSent) return;
|
|
261
268
|
forcedTerminationSignal = true;
|
|
262
|
-
if (!cleanTerminalAssistantStopReceived) {
|
|
269
|
+
if (!cleanTerminalAssistantStopReceived && !assistantError) {
|
|
263
270
|
result.error = result.error ?? `Subagent process did not exit within ${FINAL_STOP_GRACE_MS}ms after its final message. Forcing termination.`;
|
|
264
271
|
}
|
|
265
272
|
finalHardKillTimer = setTimeout(() => {
|
|
@@ -465,13 +472,15 @@ async function runSingleAttempt(
|
|
|
465
472
|
progress.tokens = result.usage.input + result.usage.output;
|
|
466
473
|
}
|
|
467
474
|
if (!result.model && evt.message.model) result.model = evt.message.model;
|
|
468
|
-
if (evt.message.errorMessage)
|
|
469
|
-
|
|
475
|
+
if (evt.message.errorMessage) assistantError = evt.message.errorMessage;
|
|
476
|
+
const assistantText = extractTextFromContent(evt.message.content);
|
|
477
|
+
appendRecentOutput(progress, assistantText.split("\n").slice(-10));
|
|
470
478
|
// Final assistant message: start the exit drain window.
|
|
471
479
|
const stopReason = (evt.message as { stopReason?: string }).stopReason;
|
|
472
480
|
const hasToolCall = Array.isArray(evt.message.content)
|
|
473
481
|
&& evt.message.content.some((part) => (part as { type?: string }).type === "toolCall");
|
|
474
482
|
if (stopReason === "stop" && !hasToolCall) {
|
|
483
|
+
if (!evt.message.errorMessage && assistantText.trim()) assistantError = undefined;
|
|
475
484
|
cleanTerminalAssistantStopReceived ||= !evt.message.errorMessage;
|
|
476
485
|
startFinalDrain();
|
|
477
486
|
}
|
|
@@ -551,6 +560,7 @@ async function runSingleAttempt(
|
|
|
551
560
|
}
|
|
552
561
|
processClosed = true;
|
|
553
562
|
if (buf.trim()) processLine(buf);
|
|
563
|
+
if (!result.error && assistantError) result.error = assistantError;
|
|
554
564
|
const forcedDrainAfterFinalSuccess = forcedTerminationSignal && cleanTerminalAssistantStopReceived && !result.error;
|
|
555
565
|
if (code !== 0 && stderrBuf.trim() && !result.error && !forcedDrainAfterFinalSuccess) {
|
|
556
566
|
result.error = stderrBuf.trim();
|
|
@@ -662,8 +672,14 @@ async function runSingleAttempt(
|
|
|
662
672
|
};
|
|
663
673
|
|
|
664
674
|
let fullOutput = getFinalOutput(result.messages);
|
|
665
|
-
const completionGuard = result.exitCode === 0 && !result.error
|
|
666
|
-
? evaluateCompletionMutationGuard({
|
|
675
|
+
const completionGuard = result.exitCode === 0 && !result.error && agent.completionGuard !== false
|
|
676
|
+
? evaluateCompletionMutationGuard({
|
|
677
|
+
agent: agent.name,
|
|
678
|
+
task,
|
|
679
|
+
messages: result.messages,
|
|
680
|
+
tools: agent.tools,
|
|
681
|
+
mcpDirectTools: agent.mcpDirectTools,
|
|
682
|
+
})
|
|
667
683
|
: undefined;
|
|
668
684
|
if (completionGuard?.triggered && !observedMutationAttempt) {
|
|
669
685
|
result.exitCode = 1;
|