pi-subagents 0.63.0 → 0.65.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 +57 -1
- package/README.md +2 -2
- package/agents/reviewer.md +1 -1
- package/agents/scout.md +1 -1
- package/docs/agents.md +18 -16
- package/docs/configuration.md +7 -15
- package/docs/extension-api.md +15 -6
- package/docs/missions.md +2 -0
- package/docs/observability.md +10 -12
- package/docs/tool-reference.md +7 -6
- package/docs/watchdog.md +92 -114
- package/docs/workflows.md +5 -3
- package/package.json +3 -4
- package/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
- package/skills/pi-subagents/references/execution-controls.md +6 -5
- package/src/agents/agent-management.ts +47 -15
- package/src/api/capability-ceiling.ts +0 -1
- package/src/api/{pi-args.ts → child-tool-plan.ts} +1 -1
- package/src/api/preflight.ts +2 -3
- package/src/extension/doctor.ts +2 -10
- package/src/extension/fanout-child.ts +9 -11
- package/src/extension/index.ts +27 -5
- package/src/extension/public-execution.ts +14 -0
- package/src/extension/rpc.ts +3 -2
- package/src/extension/schemas.ts +2 -1
- package/src/extension/tool-description.ts +9 -8
- package/src/intercom/native-supervisor-channel.ts +138 -60
- package/src/intercom/supervisor-ui.ts +243 -0
- package/src/runs/background/async-execution.ts +46 -24
- package/src/runs/background/async-job-tracker.ts +11 -0
- package/src/runs/background/async-resume.ts +11 -2
- package/src/runs/background/control-channel.ts +2 -204
- package/src/runs/background/notify.ts +41 -2
- package/src/runs/background/process-terminal.ts +1 -1
- package/src/runs/background/run-child-session.ts +613 -0
- package/src/runs/background/run-status.ts +0 -1
- package/src/runs/background/runner-aliases.ts +125 -0
- package/src/runs/background/runner-child-sessions.ts +31 -0
- package/src/runs/background/scheduled-runs.ts +18 -4
- package/src/runs/background/subagent-runner.ts +203 -898
- package/src/runs/foreground/async-steering-action.ts +1 -17
- package/src/runs/foreground/execution.ts +196 -378
- package/src/runs/foreground/foreground-control.ts +4 -0
- package/src/runs/foreground/subagent-executor.ts +121 -58
- package/src/runs/foreground/workflow-foreground-steering.ts +24 -98
- package/src/runs/shared/abort-recovery.ts +3 -3
- package/src/runs/shared/acceptance.ts +10 -0
- package/src/runs/shared/async-status-projection.ts +11 -43
- package/src/runs/shared/capability-ceiling.ts +1 -2
- package/src/runs/shared/child-hooks.ts +25 -0
- package/src/runs/shared/child-identity.ts +13 -2
- package/src/runs/shared/child-launch.ts +314 -0
- package/src/runs/shared/child-lifecycle.ts +25 -0
- package/src/runs/shared/child-runtime-config.ts +126 -0
- package/src/runs/shared/child-session.ts +342 -0
- package/src/runs/shared/child-tool-plan.ts +530 -0
- package/src/runs/shared/claude-code-adapter.ts +5 -1
- package/src/runs/shared/completion-guard.ts +1 -1
- package/src/runs/shared/external-cli-preflight.ts +16 -0
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +5 -4
- package/src/runs/shared/model-exclusions.ts +82 -14
- package/src/runs/shared/model-fallback.ts +47 -4
- package/src/runs/shared/nested-events.ts +29 -45
- package/src/runs/shared/nested-path.ts +0 -14
- package/src/runs/shared/orca-progress-tabs.ts +12 -7
- package/src/runs/shared/parallel-utils.ts +0 -2
- package/src/runs/shared/permissions.ts +0 -13
- package/src/runs/shared/process-signal.ts +4 -1
- package/src/runs/shared/run-fanout-budget.ts +0 -13
- package/src/runs/shared/runtime-acknowledged-extensions.ts +0 -27
- package/src/runs/shared/structured-output.ts +17 -4
- package/src/runs/shared/subagent-control.ts +6 -2
- package/src/runs/shared/subagent-prompt-runtime.ts +87 -384
- package/src/runs/shared/tool-availability.ts +18 -62
- package/src/runs/shared/tool-budget.ts +0 -14
- package/src/runs/shared/worktree-cleanup-plan.ts +25 -6
- package/src/runs/shared/worktree.ts +117 -30
- package/src/shared/child-session-name.ts +1 -1
- package/src/shared/jsonl-writer.ts +11 -0
- package/src/shared/thinking-ceiling.ts +0 -6
- package/src/shared/types.ts +64 -30
- package/src/shared/utils.ts +3 -4
- package/src/slash/slash-commands.ts +0 -6
- package/src/tui/fleet.ts +0 -1
- package/src/tui/render.ts +238 -31
- package/src/watchdog/child-status.ts +54 -34
- package/src/watchdog/diff-tool.ts +77 -0
- package/src/watchdog/emission-guard.ts +5 -3
- package/src/watchdog/guidance.ts +20 -0
- package/src/watchdog/register-child.ts +28 -25
- package/src/watchdog/register-main.ts +10 -9
- package/src/watchdog/render.ts +4 -5
- package/src/watchdog/review.ts +15 -4
- package/src/watchdog/rules.ts +70 -0
- package/src/watchdog/runtime.ts +75 -92
- package/src/watchdog/scope.ts +0 -11
- package/src/watchdog/settings.ts +48 -104
- package/src/watchdog/types.ts +18 -32
- package/src/watchdog/warning-format.ts +0 -1
- package/src/workflows/chat-progress.ts +3 -2
- package/src/workflows/scripted-workflow.ts +48 -1
- package/src/workflows/workflow-checklist.ts +10 -12
- package/src/workflows/workflow-preflight.ts +28 -1
- package/src/runs/shared/child-protocol.ts +0 -415
- package/src/runs/shared/pi-args.ts +0 -1062
- package/src/runs/shared/subagent-startup-retry.ts +0 -116
- package/src/shared/post-exit-stdio-guard.ts +0 -85
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
* Core execution logic for running subagents
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import { existsSync } from "node:fs";
|
|
5
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
7
6
|
import * as path from "node:path";
|
|
8
7
|
import type { Message } from "@earendil-works/pi-ai";
|
|
9
8
|
import { discoverAgents, formatUnknownAgentError, unknownAgentDiagnosticContext, type AgentConfig } from "../../agents/agents.ts";
|
|
@@ -31,7 +30,6 @@ import {
|
|
|
31
30
|
type AcceptanceLedger,
|
|
32
31
|
type ResolvedAcceptanceConfig,
|
|
33
32
|
truncateOutput,
|
|
34
|
-
getSubagentDepthEnv,
|
|
35
33
|
} from "../../shared/types.ts";
|
|
36
34
|
import {
|
|
37
35
|
DEFAULT_CONTROL_CONFIG,
|
|
@@ -61,21 +59,18 @@ import { evaluateCompletionMutationGuard, expectsImplementationMutation, hasMuta
|
|
|
61
59
|
import { planCompletionEvidence } from "../shared/completion-evidence.ts";
|
|
62
60
|
import { planAbortRecovery } from "../shared/abort-recovery.ts";
|
|
63
61
|
import { arbitrateCompletionGuardRescue } from "../shared/llm-intent-arbiter.ts";
|
|
64
|
-
import { getPiSpawnCommand } from "../shared/pi-spawn.ts";
|
|
65
62
|
import { preflightLaunchCwd } from "../shared/launch-cwd.ts";
|
|
66
63
|
import { createJsonlWriter } from "../../shared/jsonl-writer.ts";
|
|
67
64
|
import { createOrcaProgressTab, type OrcaProgressTab } from "../shared/orca-progress-tabs.ts";
|
|
68
|
-
import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
|
|
69
65
|
import { resolvePermissionRules } from "../shared/permissions.ts";
|
|
70
|
-
import { applyThinkingSuffix,
|
|
66
|
+
import { applyThinkingSuffix, deriveForkPromptCacheKey } from "../shared/child-tool-plan.ts";
|
|
71
67
|
import { deriveChildSessionName } from "../../shared/child-session-name.ts";
|
|
72
|
-
import {
|
|
73
|
-
import { assertAgentAllowedByCapabilityCeiling, decodeSubagentCapabilityCeiling, intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV } from "../shared/capability-ceiling.ts";
|
|
68
|
+
import { assertAgentAllowedByCapabilityCeiling, intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
74
69
|
import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
75
|
-
import { assertThinkingWithinCeiling,
|
|
76
|
-
import {
|
|
77
|
-
import { formatMidToolExitError,
|
|
78
|
-
import {
|
|
70
|
+
import { assertThinkingWithinCeiling, intersectThinkingCeilings } from "../../shared/thinking-ceiling.ts";
|
|
71
|
+
import { MISSING_STRUCTURED_ACCEPTANCE_REPORT_ERROR, MISSING_STRUCTURED_OUTPUT_CALL_ERROR } from "../shared/structured-output.ts";
|
|
72
|
+
import { formatMidToolExitError, isOrdinaryToolForMidToolExit } from "../shared/process-signal.ts";
|
|
73
|
+
import { formatChildToolDiagnostic } from "../shared/tool-availability.ts";
|
|
79
74
|
import { buildTimeoutRecoverySummary, collectTrackedMutationEvidence, snapshotTrackedMutations } from "../shared/mutation-evidence.ts";
|
|
80
75
|
import { captureSingleOutputSnapshot, extractChildWrittenOutput, finalizeSingleOutput, formatSavedOutputReference, hasSingleOutputChangedSinceSnapshot, injectOutputPathSystemPrompt, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from "../shared/single-output.ts";
|
|
81
76
|
import {
|
|
@@ -86,14 +81,6 @@ import {
|
|
|
86
81
|
isRetryableModelFailureAttempt,
|
|
87
82
|
recordRetryableModelFailure,
|
|
88
83
|
} from "../shared/model-fallback.ts";
|
|
89
|
-
import {
|
|
90
|
-
SUBAGENT_STARTUP_RETRY_DELAYS_MS,
|
|
91
|
-
formatSubagentExtensionConflictError,
|
|
92
|
-
formatSubagentStartupRetryExhaustedError,
|
|
93
|
-
formatSubagentStartupRetryNote,
|
|
94
|
-
isRetryableSubagentStartupFailure,
|
|
95
|
-
waitForSubagentStartupRetry,
|
|
96
|
-
} from "../shared/subagent-startup-retry.ts";
|
|
97
84
|
import {
|
|
98
85
|
createMutatingFailureState,
|
|
99
86
|
didMutatingToolFail,
|
|
@@ -112,14 +99,18 @@ import { initialToolBudgetState, toolBudgetState } from "../shared/tool-budget.t
|
|
|
112
99
|
import { resolveWatchdogConfig } from "../../watchdog/settings.ts";
|
|
113
100
|
import { agentDefinitionDigest, launchBindingDigest } from "../../shared/launch-contract.ts";
|
|
114
101
|
import { consumeWorkflowChildPermit } from "../../shared/workflow-child-permit.ts";
|
|
115
|
-
import {
|
|
102
|
+
import { projectChildLifecycle, type ChildLifecycleAction, type ChildLifecycleState } from "../shared/child-lifecycle.ts";
|
|
116
103
|
import {
|
|
117
104
|
acceptChildWatchdogEvent,
|
|
105
|
+
applyChildWatchdogMessage,
|
|
118
106
|
childWatchdogIsActive,
|
|
119
107
|
isChildWatchdogStatusEvent,
|
|
120
108
|
resolveChildWatchdogConfig,
|
|
121
109
|
type ChildWatchdogStateSnapshot,
|
|
110
|
+
type ChildWatchdogStatusEvent,
|
|
122
111
|
} from "../../watchdog/child-status.ts";
|
|
112
|
+
import { buildInProcessChildLaunch } from "../shared/child-launch.ts";
|
|
113
|
+
import { childSessionFactory, projectChildSessionEventForJson, type ChildSession, type ChildSessionEvent } from "../shared/child-session.ts";
|
|
123
114
|
|
|
124
115
|
const artifactOutputByResult = new WeakMap<SingleResult, string>();
|
|
125
116
|
const acceptanceOutputByResult = new WeakMap<SingleResult, string>();
|
|
@@ -287,8 +278,8 @@ function snapshotResult(result: SingleResult, progress: AgentProgress): SingleRe
|
|
|
287
278
|
/**
|
|
288
279
|
* Streaming variant of snapshotResult for `onUpdate` progress events: it drops the
|
|
289
280
|
* unbounded `messages` transcript in favour of compact tool-call summaries so a
|
|
290
|
-
* single `tool_execution_update`
|
|
291
|
-
*
|
|
281
|
+
* single `tool_execution_update` event stays small; the parent records every
|
|
282
|
+
* one in its transcript and `events.jsonl`. Non-streaming consumers such as
|
|
292
283
|
* foreground detach receipts and terminal consumers use snapshotResult directly.
|
|
293
284
|
*/
|
|
294
285
|
function snapshotStreamResult(result: SingleResult, progress: AgentProgress): SingleResult {
|
|
@@ -350,6 +341,9 @@ function structuredDelegationProgressChanged(
|
|
|
350
341
|
const AFTER_COMPACTION_SETTLEMENT = Symbol("afterCompactionSettlement");
|
|
351
342
|
type AbortRecoverySingleResult = SingleResult & { [AFTER_COMPACTION_SETTLEMENT]?: true };
|
|
352
343
|
|
|
344
|
+
const STOPPED_BEFORE_COMPLETION_ERROR = "Subagent stopped before completion.";
|
|
345
|
+
|
|
346
|
+
|
|
353
347
|
async function runSingleAttempt(
|
|
354
348
|
runtimeCwd: string,
|
|
355
349
|
agent: AgentConfig,
|
|
@@ -368,7 +362,6 @@ async function runSingleAttempt(
|
|
|
368
362
|
attemptNotes: string[];
|
|
369
363
|
outputSnapshot?: SingleOutputSnapshot;
|
|
370
364
|
originalTask?: string;
|
|
371
|
-
taskDelivery?: SubagentTaskDelivery;
|
|
372
365
|
orcaProgressTab?: OrcaProgressTab;
|
|
373
366
|
launchWarnings: { emitted: boolean };
|
|
374
367
|
verifyModel: boolean;
|
|
@@ -379,9 +372,9 @@ async function runSingleAttempt(
|
|
|
379
372
|
assertThinkingWithinCeiling({ model: modelArg, configThinking: effectiveThinking, ceiling: options.thinkingCeiling, agent: agent.name, runId: options.runId });
|
|
380
373
|
const expectedModelForVerification = shared.verifyModel ? modelArg : undefined;
|
|
381
374
|
const resolvedThinking = resolveEffectiveThinking(modelArg, effectiveThinking);
|
|
382
|
-
// Display name for the child session: applied inside the child
|
|
383
|
-
//
|
|
384
|
-
//
|
|
375
|
+
// Display name for the child session: applied inside the child through its
|
|
376
|
+
// runtime config and echoed back on the result payload so hosts can label
|
|
377
|
+
// this run without reading the child's session file.
|
|
385
378
|
const childSessionName = deriveChildSessionName({ agent: agent.name, task: shared.originalTask ?? task });
|
|
386
379
|
const watchdogConfig = resolveWatchdogConfig(options.cwd ?? runtimeCwd);
|
|
387
380
|
const childWatchdog = watchdogConfig.ok
|
|
@@ -396,15 +389,12 @@ async function runSingleAttempt(
|
|
|
396
389
|
const permissionAuditPath = permissionRules && options.artifactsDir
|
|
397
390
|
? path.join(options.artifactsDir, "permission-audit", `${options.runId}-${options.index ?? 0}.jsonl`)
|
|
398
391
|
: undefined;
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
task,
|
|
402
|
-
taskDelivery: shared.taskDelivery,
|
|
392
|
+
let onWatchdogStatus: ((event: ChildWatchdogStatusEvent) => void) | undefined;
|
|
393
|
+
const launch = buildInProcessChildLaunch({
|
|
403
394
|
sessionEnabled: shared.sessionEnabled,
|
|
404
395
|
sessionDir: options.sessionDir,
|
|
405
396
|
sessionFile: options.sessionFile,
|
|
406
397
|
model: modelArg,
|
|
407
|
-
thinking: effectiveThinking,
|
|
408
398
|
systemPromptMode: agent.systemPromptMode,
|
|
409
399
|
inheritProjectContext: agent.inheritProjectContext,
|
|
410
400
|
inheritGlobalContext: agent.inheritGlobalContext,
|
|
@@ -418,63 +408,40 @@ async function runSingleAttempt(
|
|
|
418
408
|
systemPrompt: shared.systemPrompt,
|
|
419
409
|
mcpDirectTools: agent.mcpDirectTools,
|
|
420
410
|
cwd: options.cwd ?? runtimeCwd,
|
|
421
|
-
promptFileStem: agent.name,
|
|
422
411
|
intercomSessionName: options.intercomSessionName,
|
|
423
412
|
sessionName: childSessionName,
|
|
424
413
|
orchestratorIntercomTarget: options.orchestratorIntercomTarget,
|
|
425
414
|
runId: options.runId,
|
|
426
415
|
childAgentName: agent.name,
|
|
427
416
|
childIndex: options.index ?? 0,
|
|
428
|
-
|
|
429
|
-
parentControlInbox: options.nestedRoute?.controlInbox,
|
|
430
|
-
parentRootRunId: options.nestedRoute?.rootRunId,
|
|
431
|
-
parentCapabilityToken: options.nestedRoute?.capabilityToken,
|
|
417
|
+
nestedRoute: options.nestedRoute,
|
|
432
418
|
runFanoutBudget: options.runFanoutBudget,
|
|
433
419
|
parentSessionId: options.parentSessionId,
|
|
434
420
|
forkCacheKey: options.context === "fork" ? deriveForkPromptCacheKey(options.parentSessionId) : undefined,
|
|
435
|
-
steerInboxDir: options.steerInboxDir,
|
|
436
|
-
steerCapabilityPath: options.steerCapabilityPath,
|
|
437
|
-
steerAckDir: options.steerAckDir,
|
|
438
421
|
structuredOutput: options.structuredOutput,
|
|
439
422
|
fast: options.fast ?? agent.fast,
|
|
440
423
|
modelCandidates: shared.modelCandidates,
|
|
441
424
|
toolBudget: options.toolBudget,
|
|
442
|
-
allowZeroToolBudget: options.allowZeroToolBudget,
|
|
443
425
|
permissionRules,
|
|
444
426
|
permissionAuditPath,
|
|
445
427
|
childWatchdog,
|
|
428
|
+
watchdogStatus: (event) => onWatchdogStatus?.(event),
|
|
446
429
|
waitToolEnabled: options.waitToolEnabled,
|
|
447
430
|
waitToolDefaultTimeoutMs: options.waitToolDefaultTimeoutMs,
|
|
448
431
|
capabilityCeiling: options.capabilityCeiling,
|
|
449
432
|
thinkingCeiling: options.thinkingCeiling,
|
|
450
|
-
|
|
433
|
+
maxSubagentDepth: options.maxSubagentDepth,
|
|
451
434
|
runtimeSnapshotHost: options.runtimeSnapshotHost,
|
|
435
|
+
inherited: options.childRuntime,
|
|
436
|
+
host: "parent",
|
|
452
437
|
});
|
|
438
|
+
const { toolPlan, capabilityAudit, warnings, launchResolvedExtensions, capture } = launch;
|
|
453
439
|
if (!shared.launchWarnings.emitted && warnings.length > 0) {
|
|
454
440
|
for (const warning of warnings) console.warn(`[pi-subagents] ${warning}`);
|
|
455
441
|
shared.launchWarnings.emitted = true;
|
|
456
442
|
}
|
|
457
443
|
|
|
458
444
|
const effectiveSystemPrompt = shared.systemPrompt;
|
|
459
|
-
const toolPlan = resolvePiLaunchToolPlan({
|
|
460
|
-
tools: agent.tools,
|
|
461
|
-
allowNestedSubagents: agent.allowNestedSubagents,
|
|
462
|
-
extensions: agent.extensions,
|
|
463
|
-
subagentOnlyExtensions: agent.subagentOnlyExtensions,
|
|
464
|
-
mcpDirectTools: agent.mcpDirectTools,
|
|
465
|
-
cwd: options.cwd ?? runtimeCwd,
|
|
466
|
-
requireReadTool: Boolean(shared.resolvedSkillNames?.length),
|
|
467
|
-
structuredOutput: Boolean(options.structuredOutput),
|
|
468
|
-
excludeTools: agent.excludeTools,
|
|
469
|
-
fast: options.fast ?? agent.fast,
|
|
470
|
-
model: modelArg,
|
|
471
|
-
modelCandidates: shared.modelCandidates,
|
|
472
|
-
capabilityCeiling: options.capabilityCeiling,
|
|
473
|
-
inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
|
|
474
|
-
agentName: agent.name,
|
|
475
|
-
permissionRules,
|
|
476
|
-
runtimeSnapshotHost: options.runtimeSnapshotHost,
|
|
477
|
-
});
|
|
478
445
|
const contractTools = toolPlan.explicitToolAllowlist ? toolPlan.effectiveToolAllowlist : undefined;
|
|
479
446
|
const contractError = validateImplementationToolContract({
|
|
480
447
|
agent: agent.name,
|
|
@@ -487,7 +454,6 @@ async function runSingleAttempt(
|
|
|
487
454
|
completionGuard: agent.completionGuard,
|
|
488
455
|
});
|
|
489
456
|
if (contractError) {
|
|
490
|
-
cleanupTempDir(tempDir);
|
|
491
457
|
return {
|
|
492
458
|
index: options.index ?? 0,
|
|
493
459
|
agent: agent.name,
|
|
@@ -506,7 +472,6 @@ async function runSingleAttempt(
|
|
|
506
472
|
...(toolPlan.capabilityAudit ? { capabilityAudit: toolPlan.capabilityAudit } : {}),
|
|
507
473
|
};
|
|
508
474
|
}
|
|
509
|
-
const launchResolvedExtensions = projectLaunchResolvedChildExtensions(toolPlan);
|
|
510
475
|
const launchContractDigest = launchBindingDigest({
|
|
511
476
|
definitionDigest: agentDefinitionDigest(agent),
|
|
512
477
|
task: shared.originalTask ?? task,
|
|
@@ -553,17 +518,6 @@ async function runSingleAttempt(
|
|
|
553
518
|
...(capabilityAudit ? { capabilityAudit } : {}),
|
|
554
519
|
}, options.context);
|
|
555
520
|
const startTime = Date.now();
|
|
556
|
-
if (options.structuredOutput) {
|
|
557
|
-
const cleanupError = clearStructuredOutputCaptures(options.structuredOutput);
|
|
558
|
-
if (cleanupError) {
|
|
559
|
-
cleanupTempDir(tempDir);
|
|
560
|
-
result.exitCode = 1;
|
|
561
|
-
result.error = cleanupError;
|
|
562
|
-
result.finalOutput = cleanupError;
|
|
563
|
-
result.progressSummary = { toolCount: 0, tokens: 0, durationMs: Date.now() - startTime };
|
|
564
|
-
return result;
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
521
|
const controlConfig = options.controlConfig ?? DEFAULT_CONTROL_CONFIG;
|
|
568
522
|
let interruptedByControl = false;
|
|
569
523
|
const allControlEvents: ControlEvent[] = [];
|
|
@@ -598,7 +552,6 @@ async function runSingleAttempt(
|
|
|
598
552
|
result.progress = progress;
|
|
599
553
|
const attemptTimeout = resolveAttemptTimeout(options);
|
|
600
554
|
if (attemptTimeout?.remainingMs === 0) {
|
|
601
|
-
cleanupTempDir(tempDir);
|
|
602
555
|
result.exitCode = 1;
|
|
603
556
|
result.timedOut = true;
|
|
604
557
|
result.error = attemptTimeout.message;
|
|
@@ -612,7 +565,6 @@ async function runSingleAttempt(
|
|
|
612
565
|
};
|
|
613
566
|
return result;
|
|
614
567
|
}
|
|
615
|
-
const spawnEnv = { ...process.env, ...sharedEnv, ...getSubagentDepthEnv(options.maxSubagentDepth) };
|
|
616
568
|
const mutationSnapshot = snapshotTrackedMutations(options.cwd ?? runtimeCwd);
|
|
617
569
|
let observedMutationAttempt = false;
|
|
618
570
|
let structuredOutputToolInvoked = false;
|
|
@@ -620,7 +572,6 @@ async function runSingleAttempt(
|
|
|
620
572
|
let toolAvailabilityError: string | undefined;
|
|
621
573
|
let abortedBySignal = options.signal?.aborted === true;
|
|
622
574
|
|
|
623
|
-
const spawnSpec = getPiSpawnCommand(args);
|
|
624
575
|
if (options.workflowChildPermitLaunch) {
|
|
625
576
|
const permitError = consumeWorkflowChildPermit(options.workflowChildPermitLaunch.permit, {
|
|
626
577
|
workflowRunId: options.workflowChildPermitLaunch.workflowRunId,
|
|
@@ -631,7 +582,6 @@ async function runSingleAttempt(
|
|
|
631
582
|
runner: "pi",
|
|
632
583
|
});
|
|
633
584
|
if (permitError) {
|
|
634
|
-
cleanupTempDir(tempDir);
|
|
635
585
|
result.exitCode = 1;
|
|
636
586
|
result.error = permitError;
|
|
637
587
|
result.finalOutput = permitError;
|
|
@@ -640,16 +590,13 @@ async function runSingleAttempt(
|
|
|
640
590
|
return result;
|
|
641
591
|
}
|
|
642
592
|
}
|
|
593
|
+
const childSessions = options.childSessionFactory ?? childSessionFactory();
|
|
643
594
|
let afterCompactionSettlement = false;
|
|
644
595
|
const exitCode = await new Promise<number>((resolve) => {
|
|
645
|
-
const
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
windowsHide: true,
|
|
650
|
-
});
|
|
651
|
-
const jsonlWriter = createJsonlWriter(shared.jsonlPath, proc.stdout);
|
|
652
|
-
let processClosed = false;
|
|
596
|
+
const jsonlWriter = createJsonlWriter(shared.jsonlPath, { pause() {}, resume() {} });
|
|
597
|
+
let session: ChildSession | undefined;
|
|
598
|
+
let unsubscribe: (() => void) | undefined;
|
|
599
|
+
let sessionSettled = false;
|
|
653
600
|
let lifecycleFinished = false;
|
|
654
601
|
let detached = false;
|
|
655
602
|
let intercomStarted = false;
|
|
@@ -658,26 +605,26 @@ async function runSingleAttempt(
|
|
|
658
605
|
let removeInterruptListener: (() => void) | undefined;
|
|
659
606
|
let activityTimer: NodeJS.Timeout | undefined;
|
|
660
607
|
let timeoutTimer: NodeJS.Timeout | undefined;
|
|
661
|
-
let
|
|
662
|
-
let timeoutHardKillTimer: NodeJS.Timeout | undefined;
|
|
663
|
-
let protocolHardKillTimer: NodeJS.Timeout | undefined;
|
|
608
|
+
let timeoutHardFinishTimer: NodeJS.Timeout | undefined;
|
|
664
609
|
const clearTimeoutTimers = () => {
|
|
665
610
|
if (timeoutTimer) {
|
|
666
611
|
clearTimeout(timeoutTimer);
|
|
667
612
|
timeoutTimer = undefined;
|
|
668
613
|
}
|
|
669
|
-
if (
|
|
670
|
-
clearTimeout(
|
|
671
|
-
|
|
672
|
-
}
|
|
673
|
-
if (timeoutHardKillTimer) {
|
|
674
|
-
clearTimeout(timeoutHardKillTimer);
|
|
675
|
-
timeoutHardKillTimer = undefined;
|
|
614
|
+
if (timeoutHardFinishTimer) {
|
|
615
|
+
clearTimeout(timeoutHardFinishTimer);
|
|
616
|
+
timeoutHardFinishTimer = undefined;
|
|
676
617
|
}
|
|
677
618
|
};
|
|
619
|
+
const abortChild = (): void => {
|
|
620
|
+
if (!session || sessionSettled || lifecycleFinished) return;
|
|
621
|
+
void session.abort().catch(() => {
|
|
622
|
+
// The session settles through its prompt promise; abort failures are not separately actionable.
|
|
623
|
+
});
|
|
624
|
+
};
|
|
678
625
|
|
|
679
626
|
const detachForeground = (reason: string): boolean => {
|
|
680
|
-
if (detached ||
|
|
627
|
+
if (detached || sessionSettled || lifecycleFinished || options.signal?.aborted) return false;
|
|
681
628
|
const receiptProgress = snapshotProgress(progress);
|
|
682
629
|
receiptProgress.status = "detached";
|
|
683
630
|
receiptProgress.durationMs = Date.now() - startTime;
|
|
@@ -712,20 +659,21 @@ async function runSingleAttempt(
|
|
|
712
659
|
}
|
|
713
660
|
if (!accepted) return false;
|
|
714
661
|
detached = true;
|
|
662
|
+
if (session) session.detached = true;
|
|
715
663
|
return true;
|
|
716
664
|
};
|
|
717
665
|
|
|
718
|
-
// If the child emits a terminal assistant stop but never
|
|
719
|
-
//
|
|
666
|
+
// If the child emits a terminal assistant stop but its run never settles
|
|
667
|
+
// (a hook is stuck), abort it after a short grace period and then finish
|
|
668
|
+
// without it.
|
|
720
669
|
const FINAL_STOP_GRACE_MS = 1000;
|
|
721
|
-
const
|
|
722
|
-
let
|
|
723
|
-
let forcedTerminationSignal = false;
|
|
670
|
+
const HARD_FINISH_MS = 3000;
|
|
671
|
+
let forcedTermination = false;
|
|
724
672
|
let cleanTerminalAssistantStopReceived = false;
|
|
725
673
|
let agentSettledReceived = false;
|
|
726
674
|
let compactionStartedReceived = false;
|
|
727
675
|
let finalDrainTimer: NodeJS.Timeout | undefined;
|
|
728
|
-
let
|
|
676
|
+
let finalHardFinishTimer: NodeJS.Timeout | undefined;
|
|
729
677
|
let watchdogTailTimer: NodeJS.Timeout | undefined;
|
|
730
678
|
let childWatchdogState: ChildWatchdogStateSnapshot | undefined;
|
|
731
679
|
const updateChildWatchdogState = (snapshot: ChildWatchdogStateSnapshot): void => {
|
|
@@ -744,9 +692,9 @@ async function runSingleAttempt(
|
|
|
744
692
|
clearTimeout(finalDrainTimer);
|
|
745
693
|
finalDrainTimer = undefined;
|
|
746
694
|
}
|
|
747
|
-
if (
|
|
748
|
-
clearTimeout(
|
|
749
|
-
|
|
695
|
+
if (finalHardFinishTimer) {
|
|
696
|
+
clearTimeout(finalHardFinishTimer);
|
|
697
|
+
finalHardFinishTimer = undefined;
|
|
750
698
|
}
|
|
751
699
|
};
|
|
752
700
|
const startFinalDrain = () => {
|
|
@@ -754,32 +702,30 @@ async function runSingleAttempt(
|
|
|
754
702
|
armWatchdogTail();
|
|
755
703
|
return;
|
|
756
704
|
}
|
|
757
|
-
if (
|
|
705
|
+
if (sessionSettled || finalDrainTimer || lifecycleFinished) return;
|
|
758
706
|
finalDrainTimer = setTimeout(() => {
|
|
759
|
-
if (lifecycleFinished ||
|
|
760
|
-
|
|
761
|
-
if (!termSent) return;
|
|
762
|
-
forcedTerminationSignal = true;
|
|
707
|
+
if (lifecycleFinished || sessionSettled) return;
|
|
708
|
+
forcedTermination = true;
|
|
763
709
|
if (!cleanTerminalAssistantStopReceived && !agentSettledReceived && !assistantError) {
|
|
764
|
-
result.error = result.error ?? `Subagent
|
|
710
|
+
result.error = result.error ?? `Subagent session did not settle within ${FINAL_STOP_GRACE_MS}ms after its terminal event. Aborting it.`;
|
|
765
711
|
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
712
|
+
abortChild();
|
|
713
|
+
finalHardFinishTimer = setTimeout(() => {
|
|
714
|
+
if (lifecycleFinished || sessionSettled) return;
|
|
715
|
+
settle(undefined, true);
|
|
716
|
+
}, HARD_FINISH_MS);
|
|
717
|
+
finalHardFinishTimer.unref?.();
|
|
771
718
|
}, FINAL_STOP_GRACE_MS);
|
|
772
719
|
finalDrainTimer.unref?.();
|
|
773
720
|
};
|
|
774
721
|
function armWatchdogTail(): void {
|
|
775
|
-
if ((!cleanTerminalAssistantStopReceived && !agentSettledReceived) || watchdogTailTimer || lifecycleFinished ||
|
|
722
|
+
if ((!cleanTerminalAssistantStopReceived && !agentSettledReceived) || watchdogTailTimer || lifecycleFinished || sessionSettled) return;
|
|
776
723
|
watchdogTailTimer = setTimeout(() => {
|
|
777
724
|
watchdogTailTimer = undefined;
|
|
778
725
|
updateChildWatchdogState({
|
|
779
726
|
phase: "stale",
|
|
780
727
|
seq: (childWatchdogState?.seq ?? 0) + 1,
|
|
781
728
|
lastUpdate: Date.now(),
|
|
782
|
-
followUpPending: false,
|
|
783
729
|
reason: "child watchdog tail timeout",
|
|
784
730
|
timedOut: true,
|
|
785
731
|
});
|
|
@@ -799,7 +745,7 @@ async function runSingleAttempt(
|
|
|
799
745
|
const childLifecycleState: ChildLifecycleState = { compactionRetryActive: false };
|
|
800
746
|
|
|
801
747
|
const unsubscribeIntercomDetach = options.intercomEvents?.on?.(INTERCOM_DETACH_REQUEST_EVENT, (payload) => {
|
|
802
|
-
if (!options.allowIntercomDetach ||
|
|
748
|
+
if (!options.allowIntercomDetach || sessionSettled) return;
|
|
803
749
|
if (!payload || typeof payload !== "object") return;
|
|
804
750
|
const event = payload as { requestId?: unknown; runId?: unknown; agent?: unknown; childIndex?: unknown };
|
|
805
751
|
const requestId = event.requestId;
|
|
@@ -819,13 +765,8 @@ async function runSingleAttempt(
|
|
|
819
765
|
lifecycleFinished = true;
|
|
820
766
|
clearFinalDrainTimers();
|
|
821
767
|
clearWatchdogTailTimer();
|
|
822
|
-
clearStdioGuard();
|
|
823
768
|
clearTimeoutTimers();
|
|
824
769
|
clearAllToolTimeouts();
|
|
825
|
-
if (protocolHardKillTimer) {
|
|
826
|
-
clearTimeout(protocolHardKillTimer);
|
|
827
|
-
protocolHardKillTimer = undefined;
|
|
828
|
-
}
|
|
829
770
|
if (activityTimer) {
|
|
830
771
|
clearInterval(activityTimer);
|
|
831
772
|
activityTimer = undefined;
|
|
@@ -833,7 +774,13 @@ async function runSingleAttempt(
|
|
|
833
774
|
unsubscribeIntercomDetach?.();
|
|
834
775
|
removeAbortListener?.();
|
|
835
776
|
removeInterruptListener?.();
|
|
836
|
-
|
|
777
|
+
unsubscribe?.();
|
|
778
|
+
onWatchdogStatus = undefined;
|
|
779
|
+
void jsonlWriter.close().catch(() => {
|
|
780
|
+
// JSONL artifact flush is best effort.
|
|
781
|
+
});
|
|
782
|
+
// Report the run only after the child's extensions have shut down.
|
|
783
|
+
void Promise.resolve().then(() => session?.dispose()).catch(() => undefined).then(() => resolve(code));
|
|
837
784
|
};
|
|
838
785
|
|
|
839
786
|
const drainPendingControlEvents = (): ControlEvent[] | undefined => {
|
|
@@ -965,6 +912,7 @@ async function runSingleAttempt(
|
|
|
965
912
|
config: controlConfig,
|
|
966
913
|
startedAt: startTime,
|
|
967
914
|
lastActivityAt: progress.lastActivityAt,
|
|
915
|
+
turnCount: progress.turnCount,
|
|
968
916
|
currentTool: progress.currentTool,
|
|
969
917
|
thinking: resolvedThinking,
|
|
970
918
|
now,
|
|
@@ -995,7 +943,7 @@ async function runSingleAttempt(
|
|
|
995
943
|
|
|
996
944
|
let lastStructuredDelegationProgressState: StructuredDelegationProgressState | undefined;
|
|
997
945
|
const emitUpdateSnapshot = (text: string) => {
|
|
998
|
-
if (!options.onUpdate ||
|
|
946
|
+
if (!options.onUpdate || sessionSettled) return;
|
|
999
947
|
const progressSnapshot = snapshotProgress(progress);
|
|
1000
948
|
const resultSnapshot = snapshotStreamResult(result, progressSnapshot);
|
|
1001
949
|
const controlEvents = drainPendingControlEvents();
|
|
@@ -1011,7 +959,7 @@ async function runSingleAttempt(
|
|
|
1011
959
|
};
|
|
1012
960
|
|
|
1013
961
|
const fireUpdate = () => {
|
|
1014
|
-
if (!options.onUpdate ||
|
|
962
|
+
if (!options.onUpdate || sessionSettled) return;
|
|
1015
963
|
progress.durationMs = Date.now() - startTime;
|
|
1016
964
|
if (options.suppressUnchangedDelegationUpdates) {
|
|
1017
965
|
if (lastStructuredDelegationProgressState && !structuredDelegationProgressChanged(lastStructuredDelegationProgressState, progress, result)) return;
|
|
@@ -1021,20 +969,9 @@ async function runSingleAttempt(
|
|
|
1021
969
|
emitUpdateSnapshot(output || "(running...)");
|
|
1022
970
|
};
|
|
1023
971
|
|
|
1024
|
-
const
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
jsonlWriter.writeLine(line);
|
|
1028
|
-
let evt: { type?: string; message?: Message; toolName?: string; toolCallId?: string; args?: unknown; willRetry?: unknown };
|
|
1029
|
-
try {
|
|
1030
|
-
evt = JSON.parse(line) as { type?: string; message?: Message; toolName?: string; toolCallId?: string; args?: unknown; willRetry?: unknown };
|
|
1031
|
-
} catch {
|
|
1032
|
-
rawStdoutTail.push(`${line}\n`);
|
|
1033
|
-
shared.transcriptWriter?.writeStdoutLine(line);
|
|
1034
|
-
shared.orcaProgressTab?.append(`${line}\n`);
|
|
1035
|
-
// Non-JSON stdout lines are expected; only structured events are parsed.
|
|
1036
|
-
return;
|
|
1037
|
-
}
|
|
972
|
+
const processEvent = (evt: ChildSessionEvent & { message?: Message; toolName?: string; toolCallId?: string; args?: unknown; willRetry?: unknown }) => {
|
|
973
|
+
if (lifecycleFinished) return;
|
|
974
|
+
jsonlWriter.writeLine(JSON.stringify(projectChildSessionEventForJson(evt)));
|
|
1038
975
|
shared.transcriptWriter?.writeChildEvent(evt);
|
|
1039
976
|
shared.orcaProgressTab?.event(evt);
|
|
1040
977
|
if (evt.type === "compaction_start") compactionStartedReceived = true;
|
|
@@ -1119,6 +1056,10 @@ async function runSingleAttempt(
|
|
|
1119
1056
|
|
|
1120
1057
|
if (evt.type === "message_end" && evt.message) {
|
|
1121
1058
|
result.messages!.push(evt.message);
|
|
1059
|
+
if (childWatchdog) {
|
|
1060
|
+
const next = applyChildWatchdogMessage(childWatchdogState, evt.message);
|
|
1061
|
+
if (next) updateChildWatchdogState(next);
|
|
1062
|
+
}
|
|
1122
1063
|
if (evt.message.role === "assistant") {
|
|
1123
1064
|
result.usage.turns++;
|
|
1124
1065
|
progress.turnCount = result.usage.turns;
|
|
@@ -1152,7 +1093,7 @@ async function runSingleAttempt(
|
|
|
1152
1093
|
if (evt.message.errorMessage) assistantError = evt.message.errorMessage;
|
|
1153
1094
|
const assistantText = extractTextFromContent(evt.message.content);
|
|
1154
1095
|
appendRecentOutput(progress, assistantText.split("\n").slice(-10));
|
|
1155
|
-
// Final assistant message: start the
|
|
1096
|
+
// Final assistant message: start the settle drain window.
|
|
1156
1097
|
if (terminalAssistantStop) {
|
|
1157
1098
|
if (!evt.message.errorMessage && assistantText.trim()) assistantError = undefined;
|
|
1158
1099
|
cleanTerminalAssistantStopReceived ||= !evt.message.errorMessage;
|
|
@@ -1214,11 +1155,12 @@ async function runSingleAttempt(
|
|
|
1214
1155
|
fireUpdate();
|
|
1215
1156
|
}
|
|
1216
1157
|
};
|
|
1158
|
+
onWatchdogStatus = (event) => processEvent(event as unknown as Parameters<typeof processEvent>[0]);
|
|
1217
1159
|
|
|
1218
1160
|
fireUpdate();
|
|
1219
1161
|
if (controlConfig.enabled || options.onUpdate) {
|
|
1220
1162
|
activityTimer = setInterval(() => {
|
|
1221
|
-
if (
|
|
1163
|
+
if (sessionSettled || lifecycleFinished) {
|
|
1222
1164
|
return;
|
|
1223
1165
|
}
|
|
1224
1166
|
updateActivityState(Date.now());
|
|
@@ -1229,7 +1171,7 @@ async function runSingleAttempt(
|
|
|
1229
1171
|
|
|
1230
1172
|
if (attemptTimeout) {
|
|
1231
1173
|
timeoutTimer = setTimeout(() => {
|
|
1232
|
-
if (
|
|
1174
|
+
if (sessionSettled || lifecycleFinished || interruptedByControl) return;
|
|
1233
1175
|
result.timedOut = true;
|
|
1234
1176
|
clearAllToolTimeouts();
|
|
1235
1177
|
result.error = attemptTimeout.message;
|
|
@@ -1238,17 +1180,12 @@ async function runSingleAttempt(
|
|
|
1238
1180
|
progress.error = attemptTimeout.message;
|
|
1239
1181
|
progress.durationMs = Date.now() - startTime;
|
|
1240
1182
|
fireUpdate();
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
if (
|
|
1244
|
-
|
|
1245
|
-
}, 1000);
|
|
1246
|
-
timeoutTerminationTimer.unref?.();
|
|
1247
|
-
timeoutHardKillTimer = setTimeout(() => {
|
|
1248
|
-
if (processClosed || lifecycleFinished) return;
|
|
1249
|
-
trySignalChild(proc, "SIGKILL");
|
|
1183
|
+
abortChild();
|
|
1184
|
+
timeoutHardFinishTimer = setTimeout(() => {
|
|
1185
|
+
if (sessionSettled || lifecycleFinished) return;
|
|
1186
|
+
settle(undefined, true);
|
|
1250
1187
|
}, 4000);
|
|
1251
|
-
|
|
1188
|
+
timeoutHardFinishTimer.unref?.();
|
|
1252
1189
|
}, attemptTimeout.remainingMs);
|
|
1253
1190
|
timeoutTimer.unref?.();
|
|
1254
1191
|
}
|
|
@@ -1256,8 +1193,7 @@ async function runSingleAttempt(
|
|
|
1256
1193
|
let toolTimeoutSequence = 0;
|
|
1257
1194
|
const activeToolTimeouts = new Map<string, { toolName: string; timer: ReturnType<typeof setTimeout> }>();
|
|
1258
1195
|
const activeToolTimeoutKeysByName = new Map<string, string[]>();
|
|
1259
|
-
let
|
|
1260
|
-
let toolTimeoutHardKillTimer: ReturnType<typeof setTimeout> | undefined;
|
|
1196
|
+
let toolTimeoutHardFinishTimer: ReturnType<typeof setTimeout> | undefined;
|
|
1261
1197
|
const removeToolTimeoutKey = (key: string): void => {
|
|
1262
1198
|
const active = activeToolTimeouts.get(key);
|
|
1263
1199
|
if (!active) return;
|
|
@@ -1279,17 +1215,13 @@ async function runSingleAttempt(
|
|
|
1279
1215
|
};
|
|
1280
1216
|
const clearAllToolTimeouts = (): void => {
|
|
1281
1217
|
for (const key of [...activeToolTimeouts.keys()]) removeToolTimeoutKey(key);
|
|
1282
|
-
if (
|
|
1283
|
-
clearTimeout(
|
|
1284
|
-
|
|
1285
|
-
}
|
|
1286
|
-
if (toolTimeoutHardKillTimer) {
|
|
1287
|
-
clearTimeout(toolTimeoutHardKillTimer);
|
|
1288
|
-
toolTimeoutHardKillTimer = undefined;
|
|
1218
|
+
if (toolTimeoutHardFinishTimer) {
|
|
1219
|
+
clearTimeout(toolTimeoutHardFinishTimer);
|
|
1220
|
+
toolTimeoutHardFinishTimer = undefined;
|
|
1289
1221
|
}
|
|
1290
1222
|
};
|
|
1291
1223
|
const terminateForToolTimeout = (message: string): void => {
|
|
1292
|
-
if (
|
|
1224
|
+
if (sessionSettled || lifecycleFinished || interruptedByControl) return;
|
|
1293
1225
|
result.timedOut = true;
|
|
1294
1226
|
result.error = message;
|
|
1295
1227
|
result.finalOutput = message;
|
|
@@ -1297,17 +1229,12 @@ async function runSingleAttempt(
|
|
|
1297
1229
|
progress.error = message;
|
|
1298
1230
|
progress.durationMs = Date.now() - startTime;
|
|
1299
1231
|
fireUpdate();
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
if (
|
|
1303
|
-
|
|
1304
|
-
}, 1000);
|
|
1305
|
-
toolTimeoutTerminationTimer.unref?.();
|
|
1306
|
-
toolTimeoutHardKillTimer = setTimeout(() => {
|
|
1307
|
-
if (processClosed || lifecycleFinished) return;
|
|
1308
|
-
trySignalChild(proc, "SIGKILL");
|
|
1232
|
+
abortChild();
|
|
1233
|
+
toolTimeoutHardFinishTimer = setTimeout(() => {
|
|
1234
|
+
if (sessionSettled || lifecycleFinished) return;
|
|
1235
|
+
settle(undefined, true);
|
|
1309
1236
|
}, 4000);
|
|
1310
|
-
|
|
1237
|
+
toolTimeoutHardFinishTimer.unref?.();
|
|
1311
1238
|
};
|
|
1312
1239
|
const armToolTimeout = (event: { toolCallId?: unknown; toolName: string }): void => {
|
|
1313
1240
|
const timeoutForTool = effectiveToolTimeoutMs(event.toolName, options.toolTimeoutMs);
|
|
@@ -1328,111 +1255,42 @@ async function runSingleAttempt(
|
|
|
1328
1255
|
activeToolTimeoutKeysByName.set(toolName, keys);
|
|
1329
1256
|
};
|
|
1330
1257
|
|
|
1331
|
-
|
|
1332
|
-
const
|
|
1333
|
-
if (
|
|
1334
|
-
|
|
1335
|
-
result.error = formatProtocolOutputLimit(limit);
|
|
1336
|
-
progress.status = "failed";
|
|
1337
|
-
progress.error = result.error;
|
|
1338
|
-
progress.durationMs = Date.now() - startTime;
|
|
1339
|
-
fireUpdate();
|
|
1340
|
-
if (!childExited) {
|
|
1341
|
-
trySignalChild(proc, "SIGTERM");
|
|
1342
|
-
protocolHardKillTimer = setTimeout(() => {
|
|
1343
|
-
if (!childExited) trySignalChild(proc, "SIGKILL");
|
|
1344
|
-
}, 3000);
|
|
1345
|
-
protocolHardKillTimer.unref?.();
|
|
1346
|
-
}
|
|
1347
|
-
};
|
|
1348
|
-
const stdoutReader = createBoundedLineReader({
|
|
1349
|
-
oversizedLineProjector: PI_AGGREGATE_EVENT_PROJECTOR,
|
|
1350
|
-
onLine: processLine,
|
|
1351
|
-
onLimit: failProtocol,
|
|
1352
|
-
});
|
|
1353
|
-
const stderrReader = createBoundedLineReader({
|
|
1354
|
-
stream: "stderr",
|
|
1355
|
-
maxPendingLineBytes: MAX_CHILD_STDERR_BYTES,
|
|
1356
|
-
onLine: (line) => shared.transcriptWriter?.writeStderrLine(line),
|
|
1357
|
-
onLimit: (limit) => shared.transcriptWriter?.writeStderrLine(formatProtocolOutputLimit(limit)),
|
|
1358
|
-
});
|
|
1359
|
-
|
|
1360
|
-
const clearStdioGuard = attachPostExitStdioGuard(proc, { idleMs: 2000, hardMs: 8000 });
|
|
1361
|
-
proc.stdout.on("data", (chunk: Buffer) => stdoutReader.push(chunk));
|
|
1362
|
-
proc.stderr.on("data", (chunk: Buffer) => {
|
|
1363
|
-
stderrTail.push(chunk);
|
|
1364
|
-
stderrReader.push(chunk);
|
|
1365
|
-
shared.orcaProgressTab?.append(chunk.toString("utf-8"));
|
|
1366
|
-
});
|
|
1367
|
-
proc.on("exit", () => {
|
|
1368
|
-
childExited = true;
|
|
1369
|
-
clearFinalDrainTimers();
|
|
1370
|
-
});
|
|
1371
|
-
proc.on("close", (code, signal) => {
|
|
1372
|
-
if (lifecycleFinished) return;
|
|
1373
|
-
processClosed = true;
|
|
1258
|
+
/** The child run ended (or was forced to end); fold in the captures and finish. */
|
|
1259
|
+
const settle = (promptError: unknown, forced = false): void => {
|
|
1260
|
+
if (lifecycleFinished || sessionSettled) return;
|
|
1261
|
+
sessionSettled = true;
|
|
1374
1262
|
clearFinalDrainTimers();
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
// JSONL artifact flush is best effort.
|
|
1378
|
-
});
|
|
1379
|
-
const toolDiagnosticError = readChildToolDiagnosticError(toolDiagnosticPath);
|
|
1263
|
+
const diagnostic = capture.toolDiagnostic();
|
|
1264
|
+
const toolDiagnosticError = diagnostic ? formatChildToolDiagnostic(diagnostic, { host: "parent" }) : undefined;
|
|
1380
1265
|
toolAvailabilityError = toolDiagnosticError;
|
|
1381
|
-
result.runtimeAcknowledgedExtensions =
|
|
1382
|
-
cleanupTempDir(tempDir);
|
|
1383
|
-
stdoutReader.end();
|
|
1384
|
-
stderrReader.end();
|
|
1385
|
-
const stderr = stderrTail.text();
|
|
1386
|
-
const rawStdout = rawStdoutTail.text();
|
|
1266
|
+
result.runtimeAcknowledgedExtensions = capture.runtimeAcknowledgedExtensions();
|
|
1387
1267
|
let closeError = result.error ?? toolDiagnosticError ?? assistantError;
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
if (signal) result.processSignal = signal;
|
|
1391
|
-
if (!closeError && forcedDrainAfterEmptyTerminal && stderr.trim()) {
|
|
1392
|
-
closeError = stderr.trim();
|
|
1393
|
-
}
|
|
1394
|
-
if (!closeError && isUnexplainedProcessSignal({
|
|
1395
|
-
processSignal: signal,
|
|
1396
|
-
interrupted: result.interrupted,
|
|
1397
|
-
timedOut: result.timedOut,
|
|
1398
|
-
stopped: result.stopped,
|
|
1399
|
-
forcedDrainAfterFinalSuccess: forcedDrainAfterFinalSuccess && !forcedDrainAfterEmptyTerminal,
|
|
1400
|
-
})) {
|
|
1401
|
-
closeError = formatProcessSignalError(signal!);
|
|
1268
|
+
if (!closeError && promptError !== undefined) {
|
|
1269
|
+
closeError = promptError instanceof Error ? promptError.message : String(promptError);
|
|
1402
1270
|
}
|
|
1403
|
-
|
|
1404
|
-
|
|
1271
|
+
const forcedDrainAfterFinalSuccess = (forced || forcedTermination) && (cleanTerminalAssistantStopReceived || agentSettledReceived) && !closeError;
|
|
1272
|
+
const forcedDrainAfterEmptyTerminal = forcedDrainAfterFinalSuccess && hasEmptyTerminalAssistantResponse(result.messages ?? []);
|
|
1273
|
+
if (!closeError && (abortedBySignal || session?.shutDown) && !result.interrupted && !result.timedOut) {
|
|
1274
|
+
closeError = session?.shutDown ? "Subagent stopped because the parent session shut down." : STOPPED_BEFORE_COMPLETION_ERROR;
|
|
1405
1275
|
}
|
|
1406
|
-
if (
|
|
1407
|
-
closeError =
|
|
1276
|
+
if (!closeError && forced && !forcedDrainAfterFinalSuccess) {
|
|
1277
|
+
closeError = "Subagent session did not settle after it was aborted.";
|
|
1408
1278
|
}
|
|
1409
|
-
const finalCode = forcedDrainAfterFinalSuccess && !forcedDrainAfterEmptyTerminal ? 0 :
|
|
1279
|
+
const finalCode = forcedDrainAfterFinalSuccess && !forcedDrainAfterEmptyTerminal ? 0 : closeError || promptError !== undefined ? 1 : 0;
|
|
1410
1280
|
if (!result.error && closeError) result.error = closeError;
|
|
1411
1281
|
finish(finalCode);
|
|
1412
|
-
}
|
|
1413
|
-
proc.on("error", (error) => {
|
|
1414
|
-
if (lifecycleFinished) return;
|
|
1415
|
-
processClosed = true;
|
|
1416
|
-
clearFinalDrainTimers();
|
|
1417
|
-
clearStdioGuard();
|
|
1418
|
-
void jsonlWriter.close().catch(() => {
|
|
1419
|
-
// JSONL artifact flush is best effort.
|
|
1420
|
-
});
|
|
1421
|
-
cleanupTempDir(tempDir);
|
|
1422
|
-
stdoutReader.end();
|
|
1423
|
-
stderrReader.end();
|
|
1424
|
-
if (!result.error) {
|
|
1425
|
-
result.error = error instanceof Error ? error.message : String(error);
|
|
1426
|
-
}
|
|
1427
|
-
finish(1);
|
|
1428
|
-
});
|
|
1282
|
+
};
|
|
1429
1283
|
|
|
1430
1284
|
if (options.signal) {
|
|
1431
1285
|
const kill = () => {
|
|
1432
|
-
if (
|
|
1286
|
+
if (sessionSettled || lifecycleFinished) return;
|
|
1433
1287
|
abortedBySignal = true;
|
|
1434
|
-
|
|
1435
|
-
setTimeout(() =>
|
|
1288
|
+
abortChild();
|
|
1289
|
+
const hardTimer = setTimeout(() => {
|
|
1290
|
+
if (sessionSettled || lifecycleFinished) return;
|
|
1291
|
+
settle(undefined, true);
|
|
1292
|
+
}, 3000);
|
|
1293
|
+
hardTimer.unref?.();
|
|
1436
1294
|
};
|
|
1437
1295
|
if (options.signal.aborted) kill();
|
|
1438
1296
|
else {
|
|
@@ -1443,7 +1301,7 @@ async function runSingleAttempt(
|
|
|
1443
1301
|
|
|
1444
1302
|
if (options.interruptSignal) {
|
|
1445
1303
|
const interrupt = () => {
|
|
1446
|
-
if (
|
|
1304
|
+
if (sessionSettled || lifecycleFinished) return;
|
|
1447
1305
|
if (result.timedOut) return;
|
|
1448
1306
|
interruptedByControl = true;
|
|
1449
1307
|
clearTimeoutTimers();
|
|
@@ -1454,11 +1312,12 @@ async function runSingleAttempt(
|
|
|
1454
1312
|
result.finalOutput = "Interrupted. Waiting for explicit next action.";
|
|
1455
1313
|
progress.activityState = undefined;
|
|
1456
1314
|
fireUpdate();
|
|
1457
|
-
|
|
1458
|
-
setTimeout(() => {
|
|
1459
|
-
if (
|
|
1460
|
-
|
|
1461
|
-
},
|
|
1315
|
+
abortChild();
|
|
1316
|
+
const hardTimer = setTimeout(() => {
|
|
1317
|
+
if (sessionSettled || lifecycleFinished) return;
|
|
1318
|
+
settle(undefined, true);
|
|
1319
|
+
}, 3000);
|
|
1320
|
+
hardTimer.unref?.();
|
|
1462
1321
|
};
|
|
1463
1322
|
if (options.interruptSignal.aborted) interrupt();
|
|
1464
1323
|
else {
|
|
@@ -1473,9 +1332,35 @@ async function runSingleAttempt(
|
|
|
1473
1332
|
options.onDetachReady?.((reason = "user request") => detachForeground(reason));
|
|
1474
1333
|
} catch (error) {
|
|
1475
1334
|
// A consumer callback is advisory. Keep the child attached and observable
|
|
1476
|
-
// rather than rejecting this attempt and orphaning its live
|
|
1335
|
+
// rather than rejecting this attempt and orphaning its live session.
|
|
1477
1336
|
appendRecentOutput(progress, [`Foreground detach callback failed: ${error instanceof Error ? error.message : String(error)}`]);
|
|
1478
1337
|
}
|
|
1338
|
+
|
|
1339
|
+
void (async () => {
|
|
1340
|
+
try {
|
|
1341
|
+
const created = await childSessions.create({
|
|
1342
|
+
...launch.session,
|
|
1343
|
+
onExtensionError: (error) => {
|
|
1344
|
+
shared.transcriptWriter?.writeStderrLine(`Extension error (${error.extensionPath}, ${error.event}): ${error.error instanceof Error ? error.error.message : String(error.error)}`);
|
|
1345
|
+
},
|
|
1346
|
+
});
|
|
1347
|
+
if (lifecycleFinished) {
|
|
1348
|
+
void created.dispose();
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
session = created;
|
|
1352
|
+
created.detached = detached;
|
|
1353
|
+
unsubscribe = created.subscribe((event) => processEvent(event as Parameters<typeof processEvent>[0]));
|
|
1354
|
+
if (abortedBySignal || interruptedByControl || result.timedOut) {
|
|
1355
|
+
abortChild();
|
|
1356
|
+
}
|
|
1357
|
+
options.onChildSession?.({ steer: (text) => created.steer(text), followUp: (text) => created.followUp(text) });
|
|
1358
|
+
await created.prompt(`Task: ${task}`);
|
|
1359
|
+
settle(undefined);
|
|
1360
|
+
} catch (error) {
|
|
1361
|
+
settle(error ?? new Error("Child session failed."));
|
|
1362
|
+
}
|
|
1363
|
+
})();
|
|
1479
1364
|
});
|
|
1480
1365
|
result.exitCode = exitCode;
|
|
1481
1366
|
if (afterCompactionSettlement) {
|
|
@@ -1501,20 +1386,10 @@ async function runSingleAttempt(
|
|
|
1501
1386
|
&& !abortedBySignal
|
|
1502
1387
|
&& !result.timedOut
|
|
1503
1388
|
&& !result.stopped
|
|
1504
|
-
&& !result.protocolError
|
|
1505
1389
|
&& !toolAvailabilityError) {
|
|
1506
|
-
const processExitCode = result.exitCode;
|
|
1507
1390
|
result.exitCode = 1;
|
|
1508
|
-
result.error = formatMidToolExitError({
|
|
1509
|
-
toolName: progress.currentTool,
|
|
1510
|
-
exitCode: processExitCode,
|
|
1511
|
-
processSignal: result.processSignal,
|
|
1512
|
-
});
|
|
1391
|
+
result.error = formatMidToolExitError({ toolName: progress.currentTool });
|
|
1513
1392
|
}
|
|
1514
|
-
result.error = formatSubagentExtensionConflictError(result.error, {
|
|
1515
|
-
agent: agent.name,
|
|
1516
|
-
ambientExtensionsEnabled: !launchResolvedExtensions.disableAmbientExtensions,
|
|
1517
|
-
});
|
|
1518
1393
|
if (result.error && result.exitCode === 0) {
|
|
1519
1394
|
result.exitCode = 1;
|
|
1520
1395
|
}
|
|
@@ -1522,27 +1397,23 @@ async function runSingleAttempt(
|
|
|
1522
1397
|
if (options.structuredOutput && result.exitCode === 0 && !result.error) {
|
|
1523
1398
|
result.structuredOutputSchemaPath = options.structuredOutput.schemaPath;
|
|
1524
1399
|
result.structuredOutputPath = options.structuredOutput.outputPath;
|
|
1525
|
-
|
|
1400
|
+
const structured = capture.structuredOutput();
|
|
1401
|
+
if (!structuredOutputToolInvoked || !structured.called) {
|
|
1526
1402
|
result.exitCode = 1;
|
|
1527
1403
|
result.error = MISSING_STRUCTURED_OUTPUT_CALL_ERROR;
|
|
1528
1404
|
result.structuredOutputFailed = true;
|
|
1529
1405
|
} else {
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
const acceptanceReport = readStructuredOutputAcceptanceReport(options.structuredOutput);
|
|
1542
|
-
(result as SingleResult & { structuredAcceptanceReport?: unknown; structuredAcceptanceReportError?: string }).structuredAcceptanceReport = acceptanceReport.value;
|
|
1543
|
-
(result as SingleResult & { structuredAcceptanceReport?: unknown; structuredAcceptanceReportError?: string }).structuredAcceptanceReportError = acceptanceReport.error;
|
|
1544
|
-
validatedStructuredOutput = true;
|
|
1545
|
-
}
|
|
1406
|
+
result.structuredOutput = structured.value;
|
|
1407
|
+
const acceptanceMode = options.structuredOutput.acceptanceReportPath
|
|
1408
|
+
? options.structuredOutput.acceptanceReportRequired ? "required" : "optional"
|
|
1409
|
+
: undefined;
|
|
1410
|
+
const acceptanceReportError = acceptanceMode === "required" && !structured.acceptanceReportProvided
|
|
1411
|
+
? MISSING_STRUCTURED_ACCEPTANCE_REPORT_ERROR
|
|
1412
|
+
: undefined;
|
|
1413
|
+
(result as SingleResult & { structuredAcceptanceReport?: unknown; structuredAcceptanceReportError?: string }).structuredAcceptanceReport = acceptanceMode ? structured.acceptanceReport : undefined;
|
|
1414
|
+
(result as SingleResult & { structuredAcceptanceReport?: unknown; structuredAcceptanceReportError?: string }).structuredAcceptanceReportError = acceptanceReportError;
|
|
1415
|
+
writeStructuredOutputArtifacts(options.structuredOutput, structured.value, acceptanceMode ? structured.acceptanceReport : undefined);
|
|
1416
|
+
validatedStructuredOutput = true;
|
|
1546
1417
|
}
|
|
1547
1418
|
}
|
|
1548
1419
|
if (result.exitCode === 0 && !result.error) {
|
|
@@ -1595,7 +1466,7 @@ async function runSingleAttempt(
|
|
|
1595
1466
|
const outputChanged = hasSingleOutputChangedSinceSnapshot(options.outputPath, shared.outputSnapshot);
|
|
1596
1467
|
requiredOutputMissing = outputChanged === undefined ? undefined : !outputChanged;
|
|
1597
1468
|
} else if (options.structuredOutput) {
|
|
1598
|
-
requiredOutputMissing = !
|
|
1469
|
+
requiredOutputMissing = !capture.structuredOutput().called;
|
|
1599
1470
|
}
|
|
1600
1471
|
result.timeoutRecovery = buildTimeoutRecoverySummary({
|
|
1601
1472
|
termination: "timed-out",
|
|
@@ -1714,6 +1585,20 @@ async function runSingleAttempt(
|
|
|
1714
1585
|
return result;
|
|
1715
1586
|
}
|
|
1716
1587
|
|
|
1588
|
+
/** Keep the structured output artifact files other consumers read by path. */
|
|
1589
|
+
function writeStructuredOutputArtifacts(runtime: NonNullable<RunSyncOptions["structuredOutput"]>, value: unknown, acceptanceReport: unknown): void {
|
|
1590
|
+
try {
|
|
1591
|
+
mkdirSync(path.dirname(runtime.outputPath), { recursive: true });
|
|
1592
|
+
writeFileSync(runtime.outputPath, JSON.stringify(value), { mode: 0o600 });
|
|
1593
|
+
if (runtime.acceptanceReportPath) {
|
|
1594
|
+
if (acceptanceReport !== undefined) writeFileSync(runtime.acceptanceReportPath, JSON.stringify(acceptanceReport), { mode: 0o600 });
|
|
1595
|
+
else rmSync(runtime.acceptanceReportPath, { force: true });
|
|
1596
|
+
}
|
|
1597
|
+
} catch {
|
|
1598
|
+
// The captured value is already on the result; the file is observability only.
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1717
1602
|
/**
|
|
1718
1603
|
* Run a subagent synchronously (blocking until complete)
|
|
1719
1604
|
*/
|
|
@@ -1739,7 +1624,7 @@ async function runSyncCompletionInner(
|
|
|
1739
1624
|
}
|
|
1740
1625
|
options = {
|
|
1741
1626
|
...options,
|
|
1742
|
-
capabilityCeiling: intersectSubagentCapabilityCeilings(options.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(options.parentSessionId),
|
|
1627
|
+
capabilityCeiling: intersectSubagentCapabilityCeilings(options.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(options.parentSessionId), options.childRuntime?.capabilityCeiling),
|
|
1743
1628
|
};
|
|
1744
1629
|
const childSessionName = deriveChildSessionName({ agent: agentName, task });
|
|
1745
1630
|
const agent = agents.find((a) => a.name === agentName);
|
|
@@ -1761,7 +1646,7 @@ async function runSyncCompletionInner(
|
|
|
1761
1646
|
thinkingCeiling: intersectThinkingCeilings(
|
|
1762
1647
|
options.thinkingCeiling,
|
|
1763
1648
|
agent.maxThinking,
|
|
1764
|
-
|
|
1649
|
+
options.childRuntime?.thinkingCeiling,
|
|
1765
1650
|
),
|
|
1766
1651
|
};
|
|
1767
1652
|
try {
|
|
@@ -1985,14 +1870,12 @@ async function runSyncCompletionInner(
|
|
|
1985
1870
|
};
|
|
1986
1871
|
let lastResult: SingleResult | undefined;
|
|
1987
1872
|
const modelsToTry = candidates.length > 0 ? candidates : [undefined];
|
|
1988
|
-
// Escalated to "file" after an unexplained zero-activity startup failure so
|
|
1989
|
-
// retries keep the task text out of argv (endpoint pre-exec scans may deny it).
|
|
1990
|
-
let taskDeliveryOverride: SubagentTaskDelivery | undefined;
|
|
1991
1873
|
let abortRecoveryAttempted = false;
|
|
1992
1874
|
let nextAttemptTask = taskWithAcceptance;
|
|
1993
1875
|
modelAttemptsLoop: for (let modelIndex = 0; modelIndex < modelsToTry.length; modelIndex++) {
|
|
1994
1876
|
const candidate = modelsToTry[modelIndex];
|
|
1995
|
-
|
|
1877
|
+
// The inner loop re-runs the same candidate at most once, for abort recovery.
|
|
1878
|
+
for (;;) {
|
|
1996
1879
|
const recoveringAbort = abortRecoveryAttempted;
|
|
1997
1880
|
const attemptTask = nextAttemptTask;
|
|
1998
1881
|
const verifyModel = Boolean(candidate) && !(options.modelOverrideFromParent && modelIndex === 0);
|
|
@@ -2011,13 +1894,12 @@ async function runSyncCompletionInner(
|
|
|
2011
1894
|
.filter((modelCandidate): modelCandidate is string => Boolean(modelCandidate)),
|
|
2012
1895
|
outputSnapshot,
|
|
2013
1896
|
originalTask: task,
|
|
2014
|
-
taskDelivery: taskDeliveryOverride,
|
|
2015
1897
|
orcaProgressTab,
|
|
2016
1898
|
launchWarnings,
|
|
2017
1899
|
verifyModel,
|
|
2018
1900
|
});
|
|
2019
1901
|
lastResult = result;
|
|
2020
|
-
if (!recoveringAbort
|
|
1902
|
+
if (!recoveringAbort) {
|
|
2021
1903
|
if (result.model) attemptedModels.push(result.model);
|
|
2022
1904
|
else if (candidate) attemptedModels.push(candidate);
|
|
2023
1905
|
}
|
|
@@ -2067,75 +1949,10 @@ async function runSyncCompletionInner(
|
|
|
2067
1949
|
if (options.workflowChildPermitLaunch && !attemptSucceeded) break modelAttemptsLoop;
|
|
2068
1950
|
// Preserve the legacy intercom handoff contract: once this logical run has
|
|
2069
1951
|
// been handed to a supervisor, terminating that attempt must not launch a
|
|
2070
|
-
//
|
|
1952
|
+
// model fallback. Explicit user detach retains fallback.
|
|
2071
1953
|
if (intercomDetached || result.timedOut) break modelAttemptsLoop;
|
|
2072
1954
|
if (attemptSucceeded) break modelAttemptsLoop;
|
|
2073
1955
|
|
|
2074
|
-
const startupFailure = isRetryableSubagentStartupFailure({
|
|
2075
|
-
exitCode: result.exitCode,
|
|
2076
|
-
error: result.error,
|
|
2077
|
-
finalOutput: result.finalOutput,
|
|
2078
|
-
messageCount: result.messages?.length ?? 0,
|
|
2079
|
-
toolCount: result.progressSummary?.toolCount ?? 0,
|
|
2080
|
-
usage: result.usage,
|
|
2081
|
-
durationMs: result.progressSummary?.durationMs ?? Number.POSITIVE_INFINITY,
|
|
2082
|
-
protocolError: result.protocolError,
|
|
2083
|
-
processSignal: result.processSignal,
|
|
2084
|
-
detached: result.detached,
|
|
2085
|
-
interrupted: result.interrupted,
|
|
2086
|
-
timedOut: result.timedOut,
|
|
2087
|
-
stopped: result.stopped,
|
|
2088
|
-
});
|
|
2089
|
-
const retryDelayMs = SUBAGENT_STARTUP_RETRY_DELAYS_MS[startupAttemptIndex];
|
|
2090
|
-
if (startupFailure && retryDelayMs !== undefined) {
|
|
2091
|
-
const retryNote = formatSubagentStartupRetryNote({
|
|
2092
|
-
model: attempt.model,
|
|
2093
|
-
attempt: startupAttemptIndex + 1,
|
|
2094
|
-
maxAttempts: SUBAGENT_STARTUP_RETRY_DELAYS_MS.length + 1,
|
|
2095
|
-
delayMs: retryDelayMs,
|
|
2096
|
-
});
|
|
2097
|
-
const shouldRetry = await waitForSubagentStartupRetry(retryDelayMs, [options.signal, options.interruptSignal]);
|
|
2098
|
-
if (!shouldRetry) {
|
|
2099
|
-
if (options.interruptSignal?.aborted) {
|
|
2100
|
-
result.exitCode = 0;
|
|
2101
|
-
result.interrupted = true;
|
|
2102
|
-
result.error = undefined;
|
|
2103
|
-
result.finalOutput = "Interrupted. Waiting for explicit next action.";
|
|
2104
|
-
if (result.progress) {
|
|
2105
|
-
result.progress.status = "running";
|
|
2106
|
-
result.progress.error = undefined;
|
|
2107
|
-
}
|
|
2108
|
-
} else {
|
|
2109
|
-
const cancellationError = "Subagent startup retry cancelled before relaunch.";
|
|
2110
|
-
result.error = cancellationError;
|
|
2111
|
-
result.finalOutput = cancellationError;
|
|
2112
|
-
attempt.error = cancellationError;
|
|
2113
|
-
if (result.progress) result.progress.error = cancellationError;
|
|
2114
|
-
}
|
|
2115
|
-
break modelAttemptsLoop;
|
|
2116
|
-
}
|
|
2117
|
-
if (!taskDeliveryOverride && result.processSignal === "SIGKILL") {
|
|
2118
|
-
taskDeliveryOverride = "file";
|
|
2119
|
-
attemptNotes.push("[startup-retry] retrying with file task delivery to keep the task text out of the child process argv.");
|
|
2120
|
-
}
|
|
2121
|
-
attempt.error = retryNote;
|
|
2122
|
-
attemptNotes.push(retryNote);
|
|
2123
|
-
continue;
|
|
2124
|
-
}
|
|
2125
|
-
if (startupFailure) {
|
|
2126
|
-
const startupError = formatSubagentStartupRetryExhaustedError({
|
|
2127
|
-
model: attempt.model,
|
|
2128
|
-
attempts: startupAttemptIndex + 1,
|
|
2129
|
-
});
|
|
2130
|
-
result.error = startupError;
|
|
2131
|
-
result.finalOutput = startupError;
|
|
2132
|
-
if (result.progress) {
|
|
2133
|
-
result.progress.error = startupError;
|
|
2134
|
-
result.progress.status = "failed";
|
|
2135
|
-
}
|
|
2136
|
-
attempt.error = startupError;
|
|
2137
|
-
break modelAttemptsLoop;
|
|
2138
|
-
}
|
|
2139
1956
|
const retryableModelFailure = isRetryableModelFailureAttempt({ error: result.error, messages: result.messages, toolCount: result.progressSummary?.toolCount });
|
|
2140
1957
|
if (retryableModelFailure) recordRetryableModelFailure(result.model ?? candidate, result.error);
|
|
2141
1958
|
if (isContextOverflow(result.error)) {
|
|
@@ -2231,6 +2048,7 @@ async function runSyncCompletionInner(
|
|
|
2231
2048
|
reportOptional: isAgentContractV1(options.agentContract),
|
|
2232
2049
|
artifactsDir: options.artifactsDir,
|
|
2233
2050
|
runId: options.runId,
|
|
2051
|
+
watchdog: result.watchdog,
|
|
2234
2052
|
});
|
|
2235
2053
|
}
|
|
2236
2054
|
} catch (error) {
|