pi-subagents 0.67.0 → 0.69.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 +90 -0
- package/README.md +1 -1
- package/docs/agents.md +41 -12
- package/docs/configuration.md +61 -19
- package/docs/extension-api.md +5 -1
- package/docs/missions.md +2 -2
- package/docs/models.md +11 -79
- package/docs/observability.md +18 -8
- package/docs/standalone-background.md +13 -3
- package/docs/tool-reference.md +38 -14
- package/docs/watchdog.md +10 -12
- package/docs/workflows.md +59 -1
- package/index.ts +5 -2
- package/package.json +4 -2
- package/runner-peer-loader.mjs +24 -0
- package/runner-peer-preload.mjs +25 -11
- package/skills/pi-subagents/SKILL.md +18 -21
- package/skills/pi-subagents/references/constraints-and-recipes.md +3 -2
- package/skills/pi-subagents/references/execution-controls.md +6 -4
- package/skills/pi-subagents/references/management-authoring-rpc.md +0 -1
- package/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +16 -12
- package/skills/pi-subagents/references/review-and-validation.md +3 -3
- package/src/agents/agent-management.ts +57 -58
- package/src/agents/agent-serializer.ts +4 -3
- package/src/agents/agents.ts +185 -72
- package/src/agents/chain-serializer.ts +5 -0
- package/src/agents/runtime-agent-registry.ts +7 -6
- package/src/agents/skills.ts +1 -1
- package/src/api/preflight.ts +20 -16
- package/src/api/required-child-extensions.ts +6 -0
- package/src/extension/config.ts +10 -37
- package/src/extension/fanout-child.ts +3 -0
- package/src/extension/herdr-pi-bridge.ts +160 -0
- package/src/extension/index.ts +42 -31
- package/src/extension/public-execution.ts +3 -3
- package/src/extension/schemas.ts +23 -6
- package/src/extension/tool-description.ts +8 -7
- package/src/inspectors/ghostty/plugin.ts +13 -1
- package/src/intercom/native-supervisor-channel.ts +22 -18
- package/src/policy/authority.ts +4 -0
- package/src/profiles/profiles.ts +12 -6
- package/src/runs/background/active-run-index.ts +17 -1
- package/src/runs/background/async-execution.ts +309 -126
- package/src/runs/background/async-job-tracker.ts +8 -6
- package/src/runs/background/async-resume.ts +13 -4
- package/src/runs/background/async-status.ts +15 -4
- package/src/runs/background/auto-drain.ts +20 -10
- package/src/runs/background/binary-bootstrap.ts +5 -0
- package/src/runs/background/chain-append.ts +1 -1
- package/src/runs/background/chain-root-attachment.ts +14 -33
- package/src/runs/background/notify.ts +74 -6
- package/src/runs/background/result-files.ts +8 -4
- package/src/runs/background/result-watcher.ts +19 -2
- package/src/runs/background/run-child-session.ts +20 -29
- package/src/runs/background/runner-aliases.ts +4 -33
- package/src/runs/background/runner-child-launch.ts +4 -1
- package/src/runs/background/runner-child-sessions.ts +2 -2
- package/src/runs/background/runner-http-dispatcher.ts +119 -0
- package/src/runs/background/scheduled-runs.ts +11 -5
- package/src/runs/background/stale-run-reconciler.ts +35 -11
- package/src/runs/background/subagent-runner.ts +413 -276
- package/src/runs/background/subagent-wait.ts +128 -23
- package/src/runs/background/wait-completions.ts +75 -27
- package/src/runs/background/wait-subscriptions.ts +9 -3
- package/src/runs/background/wait-tool.ts +4 -2
- package/src/runs/foreground/async-stop-action.ts +93 -3
- package/src/runs/foreground/execution.ts +115 -219
- package/src/runs/foreground/foreground-history.ts +2 -1
- package/src/runs/foreground/subagent-executor.ts +281 -80
- package/src/runs/shared/acceptance.ts +194 -37
- package/src/runs/shared/async-status-projection.ts +123 -33
- package/src/runs/shared/child-launch-plan.ts +15 -3
- package/src/runs/shared/child-launch.ts +19 -6
- package/src/runs/shared/child-runtime-config.ts +5 -0
- package/src/runs/shared/child-session.ts +94 -50
- package/src/runs/shared/child-tool-plan.ts +28 -16
- package/src/runs/shared/dynamic-fanout.ts +2 -2
- package/src/runs/shared/external-cli-contract.ts +11 -1
- package/src/runs/shared/external-cli-preflight.ts +6 -2
- package/src/runs/shared/herdr-connection.ts +134 -0
- package/src/runs/shared/herdr-external-adapters.ts +169 -0
- package/src/runs/shared/herdr-machine.ts +279 -0
- package/src/runs/shared/herdr-pi-protocol.ts +59 -0
- package/src/runs/shared/herdr-placed-run.ts +263 -0
- package/src/runs/shared/model-resolution-diagnostic.ts +76 -0
- package/src/runs/shared/{model-fallback.ts → model-resolution.ts} +22 -237
- package/src/runs/shared/model-scope.ts +1 -1
- package/src/runs/shared/nested-events.ts +11 -2
- package/src/runs/shared/parallel-utils.ts +7 -2
- package/src/runs/shared/pi-spawn.ts +1 -1
- package/src/runs/shared/subagent-prompt-runtime.ts +4 -2
- package/src/runs/shared/worktree-setup-command.ts +27 -4
- package/src/runs/shared/worktree.ts +30 -8
- package/src/shared/child-cache-retention.ts +43 -0
- package/src/shared/launch-contract.ts +6 -9
- package/src/shared/pruned-fork.ts +1 -1
- package/src/shared/required-child-extensions.ts +81 -0
- package/src/shared/settings.ts +5 -2
- package/src/shared/shortcuts.ts +0 -4
- package/src/shared/types.ts +81 -29
- package/src/slash/slash-commands.ts +0 -6
- package/src/slash/subagents-admin.ts +13 -9
- package/src/tui/render.ts +20 -10
- package/src/watchdog/child-status.ts +28 -36
- package/src/watchdog/lsp-diagnostics.ts +1 -1
- package/src/watchdog/model-selection.ts +1 -1
- package/src/watchdog/register-child.ts +10 -3
- package/src/watchdog/register-main.ts +20 -20
- package/src/watchdog/render.ts +1 -1
- package/src/watchdog/review.ts +14 -30
- package/src/watchdog/rules.ts +1 -1
- package/src/watchdog/runtime.ts +23 -12
- package/src/watchdog/settings.ts +3 -6
- package/src/watchdog/types.ts +3 -5
- package/src/watchdog/warning-format.ts +1 -1
- package/src/workflows/scripted-workflow.ts +68 -7
- package/src/workflows/workflow-receipt.ts +21 -3
- package/src/workflows/workflow-resources.ts +13 -2
- package/src/runs/shared/model-exclusions.ts +0 -374
- package/src/runs/shared/readonly-model-continuation.ts +0 -69
- package/src/runs/shared/readonly-session-evidence.ts +0 -307
|
@@ -2,18 +2,20 @@
|
|
|
2
2
|
* Async execution logic for subagent tool
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { spawn } from "node:child_process";
|
|
5
|
+
import { spawn, type ChildProcess } from "node:child_process";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
import * as fs from "node:fs";
|
|
8
8
|
import * as path from "node:path";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
|
-
import
|
|
10
|
+
import * as nodeModule from "node:module";
|
|
11
11
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
12
12
|
import { discoverAgents, formatUnknownAgentError, unknownAgentDiagnosticContext, type AgentConfig, type UnknownAgentDiagnosticContext } from "../../agents/agents.ts";
|
|
13
13
|
import { createAtomicJsonWriter, writePrivateAtomicJson } from "../../shared/atomic-json.ts";
|
|
14
|
+
import { childCacheRetentionEnv } from "../../shared/child-cache-retention.ts";
|
|
14
15
|
import { buildEffectiveSystemPrompt } from "../shared/effective-system-prompt.ts";
|
|
15
16
|
import { currentCompletionOwnerId } from "../../shared/completion-owner.ts";
|
|
16
|
-
import { planChildLaunch, resolveStepBehavior, suppressProgressForReadOnlyTask, type ResolvedStepBehavior } from "../shared/child-launch-plan.ts";
|
|
17
|
+
import { planChildLaunch, projectChainOutputSchemas, resolveStepBehavior, suppressProgressForReadOnlyTask, type ResolvedStepBehavior } from "../shared/child-launch-plan.ts";
|
|
18
|
+
import { formatHerdrMachineRunnerUnsupported, resolveHerdrMachinePlacement } from "../shared/herdr-machine.ts";
|
|
17
19
|
import { applyThinkingSuffix, getHostBuiltinToolNames, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan } from "../shared/child-tool-plan.ts";
|
|
18
20
|
import { injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
19
21
|
import { applyWatchdogLaunchRules, sendRuleViolationWarning } from "../../watchdog/rules.ts";
|
|
@@ -27,7 +29,7 @@ import { resolveNodeExecutable } from "../../shared/node-executable.ts";
|
|
|
27
29
|
import { backgroundProcessOptions } from "../shared/background-process-options.ts";
|
|
28
30
|
import { normalizeSkillInput, resolveSkillsWithFallback } from "../../agents/skills.ts";
|
|
29
31
|
import { PI_CODING_AGENT_PACKAGE_ROOT_ENV, PROMPT_REDACTED, resolveChildCwd } from "../../shared/utils.ts";
|
|
30
|
-
import {
|
|
32
|
+
import { resolveEffectiveSubagentModel, resolveModelOrigin, resolveModelSelection, resolveSubagentModelOverride, type AvailableModelInfo, type ModelOrigin, type ParentModel } from "../shared/model-resolution.ts";
|
|
31
33
|
import { resolveToolTimeoutMs, toolTimeoutFromEnv } from "../shared/tool-timeout.ts";
|
|
32
34
|
import { resolveModelScopesForAgent, type ModelScopeConfig } from "../shared/model-scope.ts";
|
|
33
35
|
import { findModelInfo, resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
@@ -47,6 +49,7 @@ import {
|
|
|
47
49
|
type ArtifactConfig,
|
|
48
50
|
type Details,
|
|
49
51
|
type IntercomBridgeConfig,
|
|
52
|
+
type HerdrMachineReference,
|
|
50
53
|
type JsonSchemaObject,
|
|
51
54
|
type MaxOutputConfig,
|
|
52
55
|
type NestedRouteInfo,
|
|
@@ -70,6 +73,7 @@ import type { ChildRuntimeConfig } from "../shared/child-runtime-config.ts";
|
|
|
70
73
|
import { childSessionFactoryModule } from "../shared/child-session.ts";
|
|
71
74
|
import { inheritedChildRuntime } from "../shared/child-launch.ts";
|
|
72
75
|
import { resultFilePath } from "./result-files.ts";
|
|
76
|
+
import { updateActiveRunIndex } from "./active-run-index.ts";
|
|
73
77
|
import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
|
|
74
78
|
import { usageBudgetState } from "../shared/usage-budget.ts";
|
|
75
79
|
import type { ImportedAsyncRoot } from "./chain-root-attachment.ts";
|
|
@@ -83,9 +87,25 @@ import { resolveLaunchBinding } from "../../shared/launch-contract.ts";
|
|
|
83
87
|
import { resolvePermissionRules, type PermissionConfig } from "../shared/permissions.ts";
|
|
84
88
|
import { normalizeExtensionBindings, omitExtensionBindingsEnv, type ExtensionBindings } from "../shared/extension-bindings.ts";
|
|
85
89
|
import { assertWorkflowLaneKey, normalizeWorkflowLaneMetadata } from "../shared/lane-metadata.ts";
|
|
90
|
+
import { resolveRequiredChildExtensions, type RequiredChildExtensionSnapshot } from "../../shared/required-child-extensions.ts";
|
|
86
91
|
|
|
87
|
-
const require = createRequire(import.meta.url);
|
|
88
|
-
const piPackageRoot =
|
|
92
|
+
const require = nodeModule.createRequire(import.meta.url);
|
|
93
|
+
const piPackageRoot = resolveAsyncPiPackageRoot();
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The detached runner resolves the same host package the foreground
|
|
97
|
+
* `resolvePiCliScript` path resolves, so an explicit
|
|
98
|
+
* `PI_SUBAGENTS_PI_CODING_AGENT_PACKAGE_ROOT` override must be honored here
|
|
99
|
+
* too. Precedence mirrors the foreground resolver: argv-based discovery wins
|
|
100
|
+
* first (the foreground returns the argv script before any candidate), the
|
|
101
|
+
* environment override is consulted when that discovery cannot identify the
|
|
102
|
+
* host (wrapper installs, non-standard layouts), and the
|
|
103
|
+
* package-manager entry is last. Without the override, such hosts fail
|
|
104
|
+
* closed with "neither is available" while foreground children launch fine.
|
|
105
|
+
*/
|
|
106
|
+
function resolveAsyncPiPackageRoot(env: NodeJS.ProcessEnv = process.env): string | undefined {
|
|
107
|
+
return resolvePiPackageRoot() || env[PI_CODING_AGENT_PACKAGE_ROOT_ENV]?.trim() || resolveInstalledPiPackageRoot();
|
|
108
|
+
}
|
|
89
109
|
|
|
90
110
|
function resolveJitiCliFromPackageJson(packageJsonPath: string): string | undefined {
|
|
91
111
|
if (!fs.existsSync(packageJsonPath)) return undefined;
|
|
@@ -109,12 +129,12 @@ function resolveJitiCliPath(): string | undefined {
|
|
|
109
129
|
const candidates: Array<() => string | undefined> = [
|
|
110
130
|
() => require.resolve("jiti/package.json"),
|
|
111
131
|
() => piPackageRoot
|
|
112
|
-
? createRequire(path.join(piPackageRoot, "package.json")).resolve("jiti/package.json")
|
|
132
|
+
? nodeModule.createRequire(path.join(piPackageRoot, "package.json")).resolve("jiti/package.json")
|
|
113
133
|
: undefined,
|
|
114
134
|
() => {
|
|
115
135
|
if (!process.argv[1]) return undefined;
|
|
116
136
|
const piEntry = fs.realpathSync(process.argv[1]);
|
|
117
|
-
return createRequire(piEntry).resolve("jiti/package.json");
|
|
137
|
+
return nodeModule.createRequire(piEntry).resolve("jiti/package.json");
|
|
118
138
|
},
|
|
119
139
|
() => piPackageRoot ? path.join(piPackageRoot, "node_modules", "jiti", "package.json") : undefined,
|
|
120
140
|
];
|
|
@@ -132,6 +152,17 @@ function resolveJitiCliPath(): string | undefined {
|
|
|
132
152
|
}
|
|
133
153
|
|
|
134
154
|
const jitiCliPath = resolveJitiCliPath();
|
|
155
|
+
const asyncRunnerSourcePath = path.join(
|
|
156
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
157
|
+
`subagent-runner${path.extname(fileURLToPath(import.meta.url))}`,
|
|
158
|
+
);
|
|
159
|
+
const sourceUnderNodeModules = asyncRunnerSourcePath.split(path.sep).some((segment) => segment.toLowerCase() === "node_modules");
|
|
160
|
+
function supportsNativeRunner(nodeExecutable: string): boolean {
|
|
161
|
+
return Boolean(process.features.typescript)
|
|
162
|
+
&& typeof nodeModule.registerHooks === "function"
|
|
163
|
+
&& nodeExecutable === process.execPath
|
|
164
|
+
&& !sourceUnderNodeModules;
|
|
165
|
+
}
|
|
135
166
|
|
|
136
167
|
interface AsyncExecutionContext {
|
|
137
168
|
pi: ExtensionAPI;
|
|
@@ -168,6 +199,9 @@ interface AsyncChainParams {
|
|
|
168
199
|
availableModels?: AvailableModelInfo[];
|
|
169
200
|
cwd?: string;
|
|
170
201
|
maxOutput?: MaxOutputConfig;
|
|
202
|
+
machine?: string;
|
|
203
|
+
/** Launch cwd as typed when a machine is set: a path on that machine, never resolved locally. */
|
|
204
|
+
machineCwd?: string;
|
|
171
205
|
artifactsDir?: string;
|
|
172
206
|
artifactConfig: ArtifactConfig;
|
|
173
207
|
shareEnabled: boolean;
|
|
@@ -223,9 +257,13 @@ interface AsyncSingleParams {
|
|
|
223
257
|
agentConfig: AgentConfig;
|
|
224
258
|
/** Agent contract before per-run bridge injection, used only for recovery persistence. */
|
|
225
259
|
recoveryAgentConfig?: AgentConfig;
|
|
260
|
+
requiredExtensions?: RequiredChildExtensionSnapshot;
|
|
226
261
|
ctx: AsyncExecutionContext;
|
|
227
262
|
cwd?: string;
|
|
228
263
|
requestedCwd?: string;
|
|
264
|
+
machine?: string;
|
|
265
|
+
/** Launch cwd as typed when a machine is set: a path on that machine, never resolved locally. */
|
|
266
|
+
machineCwd?: string;
|
|
229
267
|
maxOutput?: MaxOutputConfig;
|
|
230
268
|
artifactsDir?: string;
|
|
231
269
|
artifactConfig: ArtifactConfig;
|
|
@@ -269,6 +307,8 @@ interface AsyncSingleParams {
|
|
|
269
307
|
absoluteDeadlineAt?: number;
|
|
270
308
|
/** Optional per-call hard toolTimeoutMs override (highest precedence). */
|
|
271
309
|
toolTimeoutMs?: number;
|
|
310
|
+
/** Steer the child to checkpoint and stop this many ms before the run deadline (resolved call param ?? config). */
|
|
311
|
+
checkpointBeforeDeadlineMs?: number;
|
|
272
312
|
toolBudget?: ResolvedToolBudget | ToolBudgetConfig;
|
|
273
313
|
usageBudget?: UsageBudgetConfig;
|
|
274
314
|
configToolBudget?: ResolvedToolBudget;
|
|
@@ -312,6 +352,8 @@ export interface AsyncRunnerStepBuildParams {
|
|
|
312
352
|
ctx: AsyncExecutionContext;
|
|
313
353
|
availableModels?: AvailableModelInfo[];
|
|
314
354
|
cwd?: string;
|
|
355
|
+
machine?: string;
|
|
356
|
+
machineCwd?: string;
|
|
315
357
|
chainSkills?: string[];
|
|
316
358
|
sessionFilesByFlatIndex?: (string | undefined)[];
|
|
317
359
|
thinkingOverridesByFlatIndex?: (AgentConfig["thinking"] | undefined)[];
|
|
@@ -368,11 +410,9 @@ export function formatAsyncStartedMessage(headline: string, interactive: boolean
|
|
|
368
410
|
return [headline, "", ...guidance].join("\n");
|
|
369
411
|
}
|
|
370
412
|
|
|
371
|
-
/**
|
|
372
|
-
* Check if jiti is available for async execution
|
|
373
|
-
*/
|
|
413
|
+
/** Check whether detached async execution has a supported runtime. */
|
|
374
414
|
export function isAsyncAvailable(): boolean {
|
|
375
|
-
return jitiCliPath !== undefined;
|
|
415
|
+
return resolveBunPiExecutable() !== undefined || supportsNativeRunner(resolveNodeExecutable()) || jitiCliPath !== undefined;
|
|
376
416
|
}
|
|
377
417
|
|
|
378
418
|
export function resolveAsyncRunnerLogPaths(cfg: object): { stdoutPath: string; stderrPath: string } | undefined {
|
|
@@ -399,24 +439,14 @@ function closeFd(fd: number | undefined): void {
|
|
|
399
439
|
* Spawn the async runner process
|
|
400
440
|
*/
|
|
401
441
|
const RUNNER_STARTUP_TIMEOUT_MS = 10_000;
|
|
402
|
-
|
|
403
|
-
const RUNNER_STARTUP_WAIT_VIEW = RUNNER_STARTUP_WAIT_BUFFER ? new Int32Array(RUNNER_STARTUP_WAIT_BUFFER) : undefined;
|
|
404
|
-
|
|
405
|
-
type RunnerStartupState = "ready" | "acknowledged";
|
|
442
|
+
type RunnerStartupState = "ready" | "acknowledged" | "confirmed";
|
|
406
443
|
|
|
407
444
|
type RunnerStartupWaitResult =
|
|
408
445
|
| { ok: true; token: string }
|
|
409
446
|
| { ok: false; error: string; startupDidNotProceed?: boolean };
|
|
410
447
|
|
|
411
|
-
function waitForStartupInterval(delayMs = 20): void {
|
|
412
|
-
|
|
413
|
-
Atomics.wait(RUNNER_STARTUP_WAIT_VIEW, 0, 0, delayMs);
|
|
414
|
-
return;
|
|
415
|
-
}
|
|
416
|
-
const waitUntil = Date.now() + delayMs;
|
|
417
|
-
while (Date.now() < waitUntil) {
|
|
418
|
-
// Startup handshakes are synchronous so resume rejects before reporting a run as started.
|
|
419
|
-
}
|
|
448
|
+
function waitForStartupInterval(delayMs = 20): Promise<void> {
|
|
449
|
+
return new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
420
450
|
}
|
|
421
451
|
|
|
422
452
|
function readRunnerStartup(startupPath: string, expectedState: RunnerStartupState, expectedToken?: string): RunnerStartupWaitResult | undefined {
|
|
@@ -434,13 +464,32 @@ function readRunnerStartup(startupPath: string, expectedState: RunnerStartupStat
|
|
|
434
464
|
}
|
|
435
465
|
}
|
|
436
466
|
|
|
437
|
-
function waitForRunnerStartup(startupPath: string, expectedState: RunnerStartupState, timeoutMs: number, expectedToken?: string): RunnerStartupWaitResult {
|
|
467
|
+
async function waitForRunnerStartup(startupPath: string, expectedState: RunnerStartupState, timeoutMs: number, expectedToken?: string, processClosed?: Promise<{ exitCode: number | null; signal: NodeJS.Signals | null }>): Promise<RunnerStartupWaitResult> {
|
|
468
|
+
const confirmOpen = async (result: RunnerStartupWaitResult): Promise<RunnerStartupWaitResult> => {
|
|
469
|
+
if (!processClosed || result.ok === false) return result;
|
|
470
|
+
const outcome = await Promise.race([processClosed.then((exit) => ({ exit })), new Promise<undefined>((resolve) => setImmediate(() => resolve(undefined)))]);
|
|
471
|
+
if (!outcome) return result;
|
|
472
|
+
const finalResult = readRunnerStartup(startupPath, expectedState, expectedToken);
|
|
473
|
+
if (finalResult?.ok === false) return finalResult;
|
|
474
|
+
const { exitCode, signal } = outcome.exit;
|
|
475
|
+
return { ok: false, error: `Async runner exited before startup state '${expectedState}' (exit code ${exitCode ?? "unknown"}, signal ${signal ?? "none"}).`, startupDidNotProceed: true };
|
|
476
|
+
};
|
|
438
477
|
const deadline = Date.now() + timeoutMs;
|
|
439
|
-
|
|
478
|
+
while (Date.now() <= deadline) {
|
|
440
479
|
const result = readRunnerStartup(startupPath, expectedState, expectedToken);
|
|
441
|
-
if (result) return result;
|
|
442
|
-
|
|
443
|
-
|
|
480
|
+
if (result) return await confirmOpen(result);
|
|
481
|
+
const delay = waitForStartupInterval(Math.min(20, Math.max(1, deadline - Date.now())));
|
|
482
|
+
if (processClosed) {
|
|
483
|
+
const outcome = await Promise.race([delay.then(() => undefined), processClosed.then((exit) => ({ exit }))]);
|
|
484
|
+
if (outcome) {
|
|
485
|
+
const finalResult = readRunnerStartup(startupPath, expectedState, expectedToken);
|
|
486
|
+
if (finalResult?.ok === false) return finalResult;
|
|
487
|
+
const { exitCode, signal } = outcome.exit;
|
|
488
|
+
return { ok: false, error: `Async runner exited before startup state '${expectedState}' (exit code ${exitCode ?? "unknown"}, signal ${signal ?? "none"}).`, startupDidNotProceed: true };
|
|
489
|
+
}
|
|
490
|
+
} else {
|
|
491
|
+
await delay;
|
|
492
|
+
}
|
|
444
493
|
}
|
|
445
494
|
const finalResult = readRunnerStartup(startupPath, expectedState, expectedToken);
|
|
446
495
|
if (finalResult) return finalResult;
|
|
@@ -449,7 +498,7 @@ function waitForRunnerStartup(startupPath: string, expectedState: RunnerStartupS
|
|
|
449
498
|
|
|
450
499
|
const writePrivateStartupControlJson = createAtomicJsonWriter({ mode: 0o600, ignoreCleanupErrorAfterSuccess: true });
|
|
451
500
|
|
|
452
|
-
function writeRunnerStartupControl(filePath: string, payload: { action: "ack" | "proceed"; token: string }): void {
|
|
501
|
+
function writeRunnerStartupControl(filePath: string, payload: { action: "ack" | "confirm" | "proceed"; token: string }): void {
|
|
453
502
|
// Delegate to the shared atomic JSON writer (temp file + rename, retrying
|
|
454
503
|
// transient Windows EPERM/EBUSY/EACCES locks and cleaning up the temp file
|
|
455
504
|
// on failure), so the startup handshake gets the same locking resilience as
|
|
@@ -467,6 +516,13 @@ function runnerIsAlive(pid: number): boolean {
|
|
|
467
516
|
}
|
|
468
517
|
}
|
|
469
518
|
|
|
519
|
+
function waitForStartupIntervalSync(delayMs = 20): void {
|
|
520
|
+
const waitUntil = Date.now() + delayMs;
|
|
521
|
+
while (Date.now() < waitUntil) {
|
|
522
|
+
// Pre-handshake failures happen before the runner can be observed asynchronously.
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
470
526
|
function terminateRunnerBeforeProceed(pid: number): boolean {
|
|
471
527
|
for (const signal of ["SIGTERM", "SIGKILL"] as const) {
|
|
472
528
|
if (!runnerIsAlive(pid)) return true;
|
|
@@ -476,11 +532,88 @@ function terminateRunnerBeforeProceed(pid: number): boolean {
|
|
|
476
532
|
if (!runnerIsAlive(pid)) return true;
|
|
477
533
|
}
|
|
478
534
|
const deadline = Date.now() + 1000;
|
|
479
|
-
while (runnerIsAlive(pid) && Date.now() < deadline)
|
|
535
|
+
while (runnerIsAlive(pid) && Date.now() < deadline) waitForStartupIntervalSync();
|
|
480
536
|
}
|
|
481
537
|
return !runnerIsAlive(pid);
|
|
482
538
|
}
|
|
483
539
|
|
|
540
|
+
async function terminateRunnerBeforeProceedAsync(proc: ChildProcess, processClosed: Promise<{ exitCode: number | null; signal: NodeJS.Signals | null }>): Promise<boolean> {
|
|
541
|
+
for (const signal of ["SIGTERM", "SIGKILL"] as const) {
|
|
542
|
+
if (proc.pid === undefined || !runnerIsAlive(proc.pid)) return true;
|
|
543
|
+
try { process.kill(proc.pid, signal); } catch {
|
|
544
|
+
if (proc.pid === undefined || !runnerIsAlive(proc.pid)) return true;
|
|
545
|
+
}
|
|
546
|
+
const exited = await Promise.race([processClosed.then(() => true), waitForStartupInterval(1000).then(() => false)]);
|
|
547
|
+
if (exited || proc.pid === undefined || !runnerIsAlive(proc.pid)) return true;
|
|
548
|
+
}
|
|
549
|
+
return proc.pid === undefined || !runnerIsAlive(proc.pid);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
async function completeRunnerStartupHandshake(
|
|
553
|
+
startupPath: string,
|
|
554
|
+
startupAckPath: string,
|
|
555
|
+
startupProceedPath: string,
|
|
556
|
+
proc: ChildProcess,
|
|
557
|
+
processClosed: Promise<{ exitCode: number | null; signal: NodeJS.Signals | null }>,
|
|
558
|
+
getObservedProcessExit: () => { exitCode: number | null; signal: NodeJS.Signals | null } | undefined,
|
|
559
|
+
runnerProcessInstanceId: string,
|
|
560
|
+
persistStartupFailure: (message: string) => void,
|
|
561
|
+
): Promise<SpawnRunnerResult> {
|
|
562
|
+
try {
|
|
563
|
+
const ready = await waitForRunnerStartup(startupPath, "ready", RUNNER_STARTUP_TIMEOUT_MS, undefined, processClosed);
|
|
564
|
+
if (ready.ok === false) {
|
|
565
|
+
persistStartupFailure(ready.error);
|
|
566
|
+
const terminationObserved = await terminateRunnerBeforeProceedAsync(proc, processClosed);
|
|
567
|
+
return { pid: proc.pid, runnerProcessInstanceId, error: ready.error, terminationObserved, startupDidNotProceed: true };
|
|
568
|
+
}
|
|
569
|
+
try { writeRunnerStartupControl(startupAckPath, { action: "ack", token: ready.token }); } catch (error) {
|
|
570
|
+
const message = `Failed to acknowledge async runner startup: ${error instanceof Error ? error.message : String(error)}`;
|
|
571
|
+
persistStartupFailure(message);
|
|
572
|
+
const terminationObserved = await terminateRunnerBeforeProceedAsync(proc, processClosed);
|
|
573
|
+
return { pid: proc.pid, runnerProcessInstanceId, error: message, terminationObserved, startupDidNotProceed: true };
|
|
574
|
+
}
|
|
575
|
+
const acknowledged = await waitForRunnerStartup(startupPath, "acknowledged", RUNNER_STARTUP_TIMEOUT_MS, ready.token, processClosed);
|
|
576
|
+
if (acknowledged.ok === false) {
|
|
577
|
+
persistStartupFailure(acknowledged.error);
|
|
578
|
+
const terminationObserved = await terminateRunnerBeforeProceedAsync(proc, processClosed);
|
|
579
|
+
return { pid: proc.pid, runnerProcessInstanceId, error: acknowledged.error, terminationObserved, startupDidNotProceed: true };
|
|
580
|
+
}
|
|
581
|
+
try { writeRunnerStartupControl(startupAckPath, { action: "confirm", token: ready.token }); } catch (error) {
|
|
582
|
+
const message = `Failed to confirm async runner startup: ${error instanceof Error ? error.message : String(error)}`;
|
|
583
|
+
persistStartupFailure(message);
|
|
584
|
+
const terminationObserved = await terminateRunnerBeforeProceedAsync(proc, processClosed);
|
|
585
|
+
return { pid: proc.pid, runnerProcessInstanceId, error: message, terminationObserved, startupDidNotProceed: true };
|
|
586
|
+
}
|
|
587
|
+
const confirmed = await waitForRunnerStartup(startupPath, "confirmed", RUNNER_STARTUP_TIMEOUT_MS, ready.token, processClosed);
|
|
588
|
+
if (confirmed.ok === false) {
|
|
589
|
+
persistStartupFailure(confirmed.error);
|
|
590
|
+
const terminationObserved = await terminateRunnerBeforeProceedAsync(proc, processClosed);
|
|
591
|
+
return { pid: proc.pid, runnerProcessInstanceId, error: confirmed.error, terminationObserved, startupDidNotProceed: true };
|
|
592
|
+
}
|
|
593
|
+
const closedAtCommit = await Promise.race([
|
|
594
|
+
processClosed.then((exit) => exit),
|
|
595
|
+
new Promise<undefined>((resolve) => setImmediate(() => resolve(undefined))),
|
|
596
|
+
]);
|
|
597
|
+
const observedExit = closedAtCommit ?? getObservedProcessExit();
|
|
598
|
+
if (observedExit) {
|
|
599
|
+
const message = `Async runner exited after startup state 'acknowledged' before proceed (exit code ${observedExit.exitCode ?? "unknown"}, signal ${observedExit.signal ?? "none"}).`;
|
|
600
|
+
persistStartupFailure(message);
|
|
601
|
+
const terminationObserved = await terminateRunnerBeforeProceedAsync(proc, processClosed);
|
|
602
|
+
return { pid: proc.pid, runnerProcessInstanceId, error: message, terminationObserved, startupDidNotProceed: true };
|
|
603
|
+
}
|
|
604
|
+
try { writeRunnerStartupControl(startupProceedPath, { action: "proceed", token: ready.token }); } catch (error) {
|
|
605
|
+
const message = `Failed to authorize async runner startup: ${error instanceof Error ? error.message : String(error)}`;
|
|
606
|
+
persistStartupFailure(message);
|
|
607
|
+
const terminationObserved = await terminateRunnerBeforeProceedAsync(proc, processClosed);
|
|
608
|
+
return { pid: proc.pid, runnerProcessInstanceId, error: message, terminationObserved, startupDidNotProceed: true };
|
|
609
|
+
}
|
|
610
|
+
try { fs.rmSync(startupPath, { force: true }); } catch {}
|
|
611
|
+
return { pid: proc.pid, runnerProcessInstanceId };
|
|
612
|
+
} finally {
|
|
613
|
+
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
484
617
|
function persistPreProceedStartupFailure(asyncDir: string, runId: string, runnerProcessInstanceId: string, sessionId: string | undefined, completionOwnerId: string | undefined, message: string): void {
|
|
485
618
|
const now = Date.now();
|
|
486
619
|
try {
|
|
@@ -489,6 +622,8 @@ function persistPreProceedStartupFailure(asyncDir: string, runId: string, runner
|
|
|
489
622
|
try {
|
|
490
623
|
status = JSON.parse(fs.readFileSync(statusPath, "utf-8")) as Partial<AsyncStatus>;
|
|
491
624
|
} catch {}
|
|
625
|
+
const existingProcessTerminal = status.processTerminal?.state === "observed" || status.processTerminal?.state === "unknown"
|
|
626
|
+
? status.processTerminal : undefined;
|
|
492
627
|
writePrivateAtomicJson(statusPath, {
|
|
493
628
|
...status,
|
|
494
629
|
runId,
|
|
@@ -497,7 +632,7 @@ function persistPreProceedStartupFailure(asyncDir: string, runId: string, runner
|
|
|
497
632
|
state: "failed",
|
|
498
633
|
lastUpdate: now,
|
|
499
634
|
error: message,
|
|
500
|
-
processTerminal: {
|
|
635
|
+
processTerminal: existingProcessTerminal ?? {
|
|
501
636
|
version: 1,
|
|
502
637
|
state: "not-started",
|
|
503
638
|
runId,
|
|
@@ -537,17 +672,27 @@ export function emitProcessTerminalEvent(ctx: AsyncExecutionContext, proof: unkn
|
|
|
537
672
|
}
|
|
538
673
|
}
|
|
539
674
|
|
|
540
|
-
function spawnRunner(cfg: object, suffix: string, cwd: string, initialStatus: Omit<AsyncStatus, "pid" | "processTerminal">, initialStatusPath: string, onProcessTerminal?: (proof: unknown) => void, onBeforeProceed?: (runnerProcessInstanceId: string) => void, requestedCwd = cwd): SpawnRunnerResult {
|
|
675
|
+
function spawnRunner(cfg: object, suffix: string, cwd: string, initialStatus: Omit<AsyncStatus, "pid" | "processTerminal">, initialStatusPath: string, onProcessTerminal?: (proof: unknown) => void, onBeforeProceed?: (runnerProcessInstanceId: string) => void, requestedCwd = cwd): SpawnRunnerResult | Promise<SpawnRunnerResult> {
|
|
541
676
|
const cwdError = preflightLaunchCwd(requestedCwd, cwd);
|
|
542
677
|
if (cwdError) return { error: cwdError };
|
|
543
678
|
|
|
544
679
|
// The compiled host exposes its SDK only through Pi's extension loader.
|
|
545
680
|
const binaryHost = resolveBunPiExecutable();
|
|
546
|
-
const
|
|
547
|
-
const
|
|
681
|
+
const nodeExecutable = resolveNodeExecutable();
|
|
682
|
+
const configuredExtensions = (cfg as { steps?: Array<{ extensions?: unknown[]; subagentOnlyExtensions?: unknown[]; requiredExtensions?: unknown[] }> }).steps?.some(
|
|
683
|
+
(step) => (step.extensions?.length ?? 0) > 0 || (step.subagentOnlyExtensions?.length ?? 0) > 0 || (step.requiredExtensions?.length ?? 0) > 0,
|
|
684
|
+
) ?? false;
|
|
685
|
+
// Keep provider/tool extensions in Jiti's host-alias boundary; the native preload
|
|
686
|
+
// only certifies the runner's imports, not a separately loaded extension graph.
|
|
687
|
+
const nativeRunnerSupported = supportsNativeRunner(nodeExecutable) && !configuredExtensions;
|
|
688
|
+
const runner = asyncRunnerSourcePath;
|
|
689
|
+
const runnerIsJavaScript = path.extname(runner) === ".js";
|
|
690
|
+
const bootstrap = path.join(path.dirname(runner), `binary-bootstrap${path.extname(fileURLToPath(import.meta.url))}`);
|
|
548
691
|
if (binaryHost && !fs.existsSync(bootstrap)) return { error: `Background runner bootstrap not found: ${bootstrap}` };
|
|
549
|
-
if (!binaryHost && !jitiCliPath) {
|
|
550
|
-
return { error:
|
|
692
|
+
if (!binaryHost && !runnerIsJavaScript && !nativeRunnerSupported && !jitiCliPath) {
|
|
693
|
+
return { error: sourceUnderNodeModules
|
|
694
|
+
? "Background runner source is installed under node_modules, so upstream jiti is required for TypeScript execution."
|
|
695
|
+
: "Background runner requires enabled native TypeScript with synchronous module hooks, or installed upstream jiti for TypeScript execution." };
|
|
551
696
|
}
|
|
552
697
|
if (!binaryHost && !piPackageRoot) {
|
|
553
698
|
return { error: `Background children require a supported standalone Pi host or the installed npm package (${PI_CODING_AGENT_PACKAGE}); neither is available.` };
|
|
@@ -564,7 +709,7 @@ function spawnRunner(cfg: object, suffix: string, cwd: string, initialStatus: Om
|
|
|
564
709
|
const launchBarrierToken = hasRevivalLease ? undefined : runnerProcessInstanceId;
|
|
565
710
|
const launchConfig = { ...cfg, runnerProcessInstanceId, ...(launchBarrierToken ? { launchBarrierToken } : {}) };
|
|
566
711
|
writePrivateAtomicJson(cfgPath, launchConfig);
|
|
567
|
-
const command = binaryHost ??
|
|
712
|
+
const command = binaryHost ?? nodeExecutable;
|
|
568
713
|
const launchForStartup = launchConfig as typeof launchConfig & { asyncDir?: unknown; id?: unknown; sessionId?: unknown; completionOwnerId?: unknown; revivalLease?: unknown };
|
|
569
714
|
const launchAsyncDir = typeof launchForStartup.asyncDir === "string" ? launchForStartup.asyncDir : undefined;
|
|
570
715
|
const launchRunId = typeof launchForStartup.id === "string" ? launchForStartup.id : suffix;
|
|
@@ -595,27 +740,44 @@ function spawnRunner(cfg: object, suffix: string, cwd: string, initialStatus: Om
|
|
|
595
740
|
: [];
|
|
596
741
|
const args = binaryHost
|
|
597
742
|
? ["--no-extensions", "--no-skills", "--no-prompt-templates", "--no-session", "--mode", "rpc", "--extension", bootstrap]
|
|
598
|
-
:
|
|
743
|
+
: runnerIsJavaScript
|
|
744
|
+
? [...preload, runner, cfgPath]
|
|
745
|
+
: nativeRunnerSupported
|
|
746
|
+
? [...preload, "--experimental-strip-types", runner, cfgPath]
|
|
747
|
+
: [...preload, jitiCliPath!, runner, cfgPath];
|
|
599
748
|
const proc = spawn(command, args, {
|
|
600
749
|
cwd,
|
|
601
750
|
...backgroundProcessOptions(),
|
|
602
751
|
stdio: ["ignore", stdoutFd ?? "ignore", stderrFd ?? "ignore"],
|
|
603
752
|
env: {
|
|
604
753
|
...omitExtensionBindingsEnv(process.env),
|
|
754
|
+
// Unset leaves the inherited parent value in place. See childCacheRetention.
|
|
755
|
+
...childCacheRetentionEnv(),
|
|
605
756
|
[PI_CODING_AGENT_PACKAGE_ROOT_ENV]: binaryHost ? undefined : piPackageRoot,
|
|
606
757
|
// npm must override inherited bundled layouts (#2071); binaries retain release assets.
|
|
607
758
|
PI_PACKAGE_DIR: binaryHost ? process.env.PI_PACKAGE_DIR : piPackageRoot,
|
|
608
759
|
[JITI_ALIAS_ENV]: binaryHost ? undefined : JSON.stringify(hostPeerAliases.aliases),
|
|
760
|
+
PI_ASYNC_NATIVE_RUNNER: !binaryHost && (runnerIsJavaScript || nativeRunnerSupported) ? "1" : "0",
|
|
761
|
+
PI_ASYNC_COMPILED_RUNNER: !binaryHost && runnerIsJavaScript ? "1" : "0",
|
|
609
762
|
PI_SUBAGENT_RUNNER_CONFIG: binaryHost ? cfgPath : undefined,
|
|
610
763
|
},
|
|
611
764
|
});
|
|
765
|
+
let observedProcessExit: { exitCode: number | null; signal: NodeJS.Signals | null } | undefined;
|
|
766
|
+
proc.once("exit", (exitCode, signal) => { observedProcessExit = { exitCode, signal }; });
|
|
767
|
+
const processClosed = new Promise<{ exitCode: number | null; signal: NodeJS.Signals | null }>((resolve) => {
|
|
768
|
+
proc.once("close", (exitCode, signal) => {
|
|
769
|
+
observedProcessExit ??= { exitCode, signal };
|
|
770
|
+
resolve({ exitCode, signal });
|
|
771
|
+
});
|
|
772
|
+
});
|
|
612
773
|
closeFd(stdoutFd);
|
|
613
774
|
closeFd(stderrFd);
|
|
614
775
|
proc.on("error", (error) => {
|
|
615
776
|
console.error(`[pi-subagents] async spawn failed: ${error.message}`);
|
|
616
777
|
});
|
|
617
778
|
proc.once("close", (exitCode, signal) => {
|
|
618
|
-
const
|
|
779
|
+
const finalize = () => {
|
|
780
|
+
const launch = launchConfig as { asyncDir?: unknown; id?: unknown; nestedRoute?: NestedRouteInfo; nestedSelf?: { parentRunId: string; parentStepIndex?: number; depth: number; path?: Array<{ runId: string; stepIndex?: number; agent?: string }> } };
|
|
619
781
|
const asyncDir = launch.asyncDir;
|
|
620
782
|
const runId = launch.id;
|
|
621
783
|
if (typeof asyncDir !== "string" || typeof runId !== "string") return;
|
|
@@ -663,6 +825,9 @@ function spawnRunner(cfg: object, suffix: string, cwd: string, initialStatus: Om
|
|
|
663
825
|
}
|
|
664
826
|
}
|
|
665
827
|
onProcessTerminal?.(persisted);
|
|
828
|
+
};
|
|
829
|
+
if (startupPath) setImmediate(finalize);
|
|
830
|
+
else finalize();
|
|
666
831
|
});
|
|
667
832
|
if (typeof proc.pid !== "number") {
|
|
668
833
|
return { error: `async runner did not produce a pid for cwd: ${cwd}` };
|
|
@@ -673,6 +838,8 @@ function spawnRunner(cfg: object, suffix: string, cwd: string, initialStatus: Om
|
|
|
673
838
|
pid: proc.pid,
|
|
674
839
|
processTerminal: { version: 1, state: "pending", runId: initialStatus.runId, runnerProcessInstanceId },
|
|
675
840
|
});
|
|
841
|
+
// Aggregate waits must see the launch before the runner's first status update.
|
|
842
|
+
updateActiveRunIndex(path.dirname(initialStatusPath), initialStatus.state, initialStatus.toolCallId);
|
|
676
843
|
} catch (error) {
|
|
677
844
|
const message = `Failed to persist initial async status: ${error instanceof Error ? error.message : String(error)}`;
|
|
678
845
|
if (launchAsyncDir) persistPreProceedStartupFailure(launchAsyncDir, launchRunId, runnerProcessInstanceId, launchSessionId, launchCompletionOwnerId, message);
|
|
@@ -711,39 +878,7 @@ function spawnRunner(cfg: object, suffix: string, cwd: string, initialStatus: Om
|
|
|
711
878
|
const persistStartupFailure = (message: string) => {
|
|
712
879
|
if (launchAsyncDir) persistPreProceedStartupFailure(launchAsyncDir, launchRunId, runnerProcessInstanceId, launchSessionId, launchCompletionOwnerId, message);
|
|
713
880
|
};
|
|
714
|
-
|
|
715
|
-
if (ready.ok === false) {
|
|
716
|
-
persistStartupFailure(ready.error);
|
|
717
|
-
const terminationObserved = terminateRunnerBeforeProceed(proc.pid);
|
|
718
|
-
return { pid: proc.pid, runnerProcessInstanceId, error: ready.error, terminationObserved, startupDidNotProceed: ready.startupDidNotProceed };
|
|
719
|
-
}
|
|
720
|
-
try {
|
|
721
|
-
writeRunnerStartupControl(startupAckPath, { action: "ack", token: ready.token });
|
|
722
|
-
} catch (error) {
|
|
723
|
-
const message = `Failed to acknowledge async runner startup: ${error instanceof Error ? error.message : String(error)}`;
|
|
724
|
-
persistStartupFailure(message);
|
|
725
|
-
const terminationObserved = terminateRunnerBeforeProceed(proc.pid);
|
|
726
|
-
return { pid: proc.pid, runnerProcessInstanceId, error: message, terminationObserved, startupDidNotProceed: true };
|
|
727
|
-
}
|
|
728
|
-
const acknowledged = waitForRunnerStartup(startupPath, "acknowledged", RUNNER_STARTUP_TIMEOUT_MS, ready.token);
|
|
729
|
-
if (acknowledged.ok === false) {
|
|
730
|
-
persistStartupFailure(acknowledged.error);
|
|
731
|
-
const terminationObserved = terminateRunnerBeforeProceed(proc.pid);
|
|
732
|
-
return { pid: proc.pid, runnerProcessInstanceId, error: acknowledged.error, terminationObserved, startupDidNotProceed: acknowledged.startupDidNotProceed };
|
|
733
|
-
}
|
|
734
|
-
try {
|
|
735
|
-
writeRunnerStartupControl(startupProceedPath, { action: "proceed", token: ready.token });
|
|
736
|
-
} catch (error) {
|
|
737
|
-
const message = `Failed to authorize async runner startup: ${error instanceof Error ? error.message : String(error)}`;
|
|
738
|
-
persistStartupFailure(message);
|
|
739
|
-
const terminationObserved = terminateRunnerBeforeProceed(proc.pid);
|
|
740
|
-
return { pid: proc.pid, runnerProcessInstanceId, error: message, terminationObserved, startupDidNotProceed: true };
|
|
741
|
-
}
|
|
742
|
-
try {
|
|
743
|
-
fs.rmSync(startupPath, { force: true });
|
|
744
|
-
} catch {
|
|
745
|
-
// Proceed is the commit point; handshake cleanup cannot turn a running revival into a start error.
|
|
746
|
-
}
|
|
881
|
+
return completeRunnerStartupHandshake(startupPath, startupAckPath, startupProceedPath, proc, processClosed, () => observedProcessExit ?? (proc.exitCode !== null || proc.signalCode !== null ? { exitCode: proc.exitCode, signal: proc.signalCode } : undefined), runnerProcessInstanceId, persistStartupFailure);
|
|
747
882
|
}
|
|
748
883
|
return { pid: proc.pid, runnerProcessInstanceId };
|
|
749
884
|
} catch (error) {
|
|
@@ -785,6 +920,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
785
920
|
const chainSkills = params.chainSkills ?? [];
|
|
786
921
|
const availableModels = params.availableModels;
|
|
787
922
|
const runnerCwd = resolveChildCwd(ctx.cwd, cwd);
|
|
923
|
+
const launchMachine = params.machine;
|
|
788
924
|
let managedWorktreeProvider: "native" | "worktrunk" | undefined;
|
|
789
925
|
try {
|
|
790
926
|
if (chain.some((step) => "worktree" in step && step.worktree === true)) {
|
|
@@ -819,8 +955,6 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
819
955
|
if (error instanceof ChainOutputValidationError) return { error: error.message };
|
|
820
956
|
throw error;
|
|
821
957
|
}
|
|
822
|
-
const workflowGraph = buildWorkflowGraphSnapshot({ runId: id, mode: resultMode, steps: graphChain });
|
|
823
|
-
|
|
824
958
|
const diagnosticContext = params.unknownAgentDiagnosticContext
|
|
825
959
|
?? unknownAgentDiagnosticContext(discoverAgents(path.resolve(runnerCwd), "both"));
|
|
826
960
|
for (const s of chain) {
|
|
@@ -833,6 +967,8 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
833
967
|
if (!agents.find((x) => x.name === agentName)) return { error: formatUnknownAgentError(agentName, diagnosticContext) };
|
|
834
968
|
}
|
|
835
969
|
}
|
|
970
|
+
const graphSteps = projectChainOutputSchemas(graphChain, agents) as ChainStep[];
|
|
971
|
+
const workflowGraph = buildWorkflowGraphSnapshot({ runId: id, mode: resultMode, steps: graphSteps });
|
|
836
972
|
|
|
837
973
|
let progressInstructionCreated = false;
|
|
838
974
|
const buildStepOverrides = (s: SequentialStep): StepOverrides => {
|
|
@@ -845,16 +981,32 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
845
981
|
...(stepSkillInput !== undefined ? { skills: stepSkillInput } : {}),
|
|
846
982
|
...(s.model !== undefined ? { model: s.model } : {}),
|
|
847
983
|
...(s.fast !== undefined ? { fast: s.fast } : {}),
|
|
984
|
+
...(s.outputSchema !== undefined ? { outputSchema: s.outputSchema } : {}),
|
|
848
985
|
};
|
|
849
986
|
};
|
|
850
987
|
const buildSeqStep = (s: SequentialStep, sessionFile?: string, behaviorCwd?: string, progressPrecreated = false, resolvedBehavior?: ResolvedStepBehavior, flatIndex?: number, parallelOutputNamespace?: { stepIndex: number; taskIndex?: number }, runFanoutPath?: string) => {
|
|
851
988
|
const a = agents.find((x) => x.name === s.agent)!;
|
|
989
|
+
const effectiveBehavior = resolvedBehavior ?? suppressProgressForReadOnlyTask(resolveStepBehavior(a, buildStepOverrides(s), chainSkills), s.task, originalTask);
|
|
990
|
+
const requestedMachine = s.machine ?? launchMachine ?? a.machine;
|
|
852
991
|
const externalRunner = a.runner?.type === "external-cli" || a.runner?.type === "external-job";
|
|
853
992
|
const externalRunnerType = a.runner?.type;
|
|
993
|
+
const machineUnsupported = formatHerdrMachineRunnerUnsupported({ machine: requestedMachine, agentName: a.name, runnerType: a.runner?.type, adapter: a.runner?.type === "external-cli" ? a.runner.adapter : undefined, worktree: s.worktree });
|
|
994
|
+
if (machineUnsupported) throw new AsyncStartValidationError(machineUnsupported);
|
|
995
|
+
let machine: HerdrMachineReference | undefined;
|
|
996
|
+
let machineEnv: Record<string, string> | undefined;
|
|
997
|
+
if (requestedMachine) {
|
|
998
|
+
try {
|
|
999
|
+
const placement = resolveHerdrMachinePlacement({ machine: requestedMachine, cwd: runnerCwd, stepCwd: s.cwd ?? params.machineCwd });
|
|
1000
|
+
machine = placement.machine;
|
|
1001
|
+
machineEnv = placement.env;
|
|
1002
|
+
} catch (error) {
|
|
1003
|
+
throw new AsyncStartValidationError(error instanceof Error ? error.message : String(error));
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
854
1006
|
if (externalRunner) {
|
|
855
1007
|
const unsupported: string[] = [];
|
|
856
1008
|
if (s.model !== undefined) unsupported.push("model override");
|
|
857
|
-
if (
|
|
1009
|
+
if (effectiveBehavior.outputSchema !== undefined) unsupported.push("structured output");
|
|
858
1010
|
if (s.acceptance !== undefined || params.agentContract !== undefined || s.agentContract !== undefined) unsupported.push("acceptance/agent contract");
|
|
859
1011
|
if (s.toolBudget !== undefined || params.toolBudget !== undefined || a.toolBudget !== undefined || params.configToolBudget !== undefined) unsupported.push("tool budget");
|
|
860
1012
|
if ((s.fast ?? params.fast ?? a.fast) === true) unsupported.push("fast mode");
|
|
@@ -885,10 +1037,11 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
885
1037
|
runtimeCwd: ctx.cwd,
|
|
886
1038
|
stepCwdInput: s.cwd,
|
|
887
1039
|
behaviorCwd,
|
|
1040
|
+
...(machine ? { machineCwd: machine.cwd } : {}),
|
|
888
1041
|
chainSkills,
|
|
889
1042
|
outputBaseDir,
|
|
890
1043
|
parallelOutputNamespace,
|
|
891
|
-
resolvedBehavior,
|
|
1044
|
+
resolvedBehavior: effectiveBehavior,
|
|
892
1045
|
});
|
|
893
1046
|
const { stepCwd, instructionCwd, readExistenceCwd, behavior, namespaceOutputPath, outputPath, skillNames } = launchPlan;
|
|
894
1047
|
const { resolved: resolvedSkills, missing: missingSkills } = resolveSkillsWithFallback(
|
|
@@ -944,39 +1097,40 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
944
1097
|
}
|
|
945
1098
|
const agentContract = s.agentContract ?? params.agentContract;
|
|
946
1099
|
const permissionRules = resolvePermissionRules(ctx.permissions, a.permissions);
|
|
947
|
-
let
|
|
1100
|
+
let selectedModel = model;
|
|
1101
|
+
let requestedModel: string | undefined;
|
|
948
1102
|
if (!externalRunner) {
|
|
949
1103
|
try {
|
|
950
|
-
|
|
1104
|
+
const modelEvidence = resolveModelSelection(primaryModel, availableModels, a.modelProvider ?? ctx.currentModelProvider, {
|
|
951
1105
|
scope: modelScopes,
|
|
952
1106
|
primaryModelFromParent,
|
|
953
1107
|
origin: modelOrigin,
|
|
954
|
-
}).flatMap((candidate) => {
|
|
955
|
-
const resolved = applyThinkingSuffix(candidate, effectiveThinking, thinkingOverride !== undefined);
|
|
956
|
-
return resolved ? [resolved] : [];
|
|
957
1108
|
});
|
|
958
|
-
|
|
1109
|
+
requestedModel = modelEvidence.requestedModel;
|
|
1110
|
+
selectedModel = applyThinkingSuffix(modelEvidence.model, effectiveThinking, thinkingOverride !== undefined);
|
|
1111
|
+
assertThinkingWithinCeiling({ model: selectedModel, configThinking: effectiveThinking, ceiling: thinkingCeiling, agent: a.name, runId: id });
|
|
959
1112
|
} catch (error) {
|
|
960
1113
|
throw new AsyncStartValidationError(error instanceof Error ? error.message : String(error));
|
|
961
1114
|
}
|
|
962
1115
|
}
|
|
963
|
-
const launchRuleError = applyWatchdogLaunchRules({ cwd: stepCwd, agent: a.name, model:
|
|
1116
|
+
const launchRuleError = applyWatchdogLaunchRules({ cwd: machine ? runnerCwd : stepCwd, agent: a.name, model: selectedModel, warn: (violation) => sendRuleViolationWarning(ctx.pi, violation) });
|
|
964
1117
|
if (launchRuleError) throw new AsyncStartValidationError(launchRuleError);
|
|
965
1118
|
const fast = s.fast ?? params.fast ?? a.fast;
|
|
966
1119
|
const hostAvailableBuiltins = getHostBuiltinToolNames(ctx.pi);
|
|
1120
|
+
const requiredExtensions = externalRunner ? [] : ctx.childRuntime?.requiredExtensions ?? resolveRequiredChildExtensions(ctx.parentSessionId ?? ctx.currentSessionId ?? undefined);
|
|
967
1121
|
const toolPlan = resolvePiLaunchToolPlan({
|
|
968
1122
|
tools: a.tools,
|
|
969
1123
|
excludeTools: a.excludeTools,
|
|
970
1124
|
allowNestedSubagents: a.allowNestedSubagents,
|
|
971
1125
|
extensions: a.extensions,
|
|
972
1126
|
subagentOnlyExtensions: a.subagentOnlyExtensions,
|
|
1127
|
+
requiredExtensions,
|
|
973
1128
|
mcpDirectTools: a.mcpDirectTools,
|
|
974
1129
|
cwd: stepCwd,
|
|
975
1130
|
requireReadTool: Boolean(resolvedSkills.length),
|
|
976
|
-
structuredOutput: Boolean(
|
|
1131
|
+
structuredOutput: Boolean(behavior.outputSchema),
|
|
977
1132
|
fast,
|
|
978
|
-
model,
|
|
979
|
-
modelCandidates,
|
|
1133
|
+
model: selectedModel,
|
|
980
1134
|
capabilityCeiling: params.capabilityCeiling,
|
|
981
1135
|
inheritedCapabilityCeiling: ctx.childRuntime?.capabilityCeiling,
|
|
982
1136
|
agentName: a.name,
|
|
@@ -1010,21 +1164,23 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
1010
1164
|
agent: s.agent,
|
|
1011
1165
|
task,
|
|
1012
1166
|
...(a.runner ? { runner: a.runner } : {}),
|
|
1167
|
+
...(machine ? { machine } : {}),
|
|
1168
|
+
...(machineEnv ? { machineEnv } : {}),
|
|
1013
1169
|
...(params.contextForAgent ? { context: params.contextForAgent(s.agent) } : {}),
|
|
1014
1170
|
...(agentContract ? { agentContract } : {}),
|
|
1015
1171
|
phase: s.phase,
|
|
1016
1172
|
label: s.label,
|
|
1017
1173
|
outputName: s.as,
|
|
1018
|
-
structured: Boolean(
|
|
1174
|
+
structured: Boolean(behavior.outputSchema),
|
|
1019
1175
|
cwd: stepCwd,
|
|
1020
|
-
requestedCwd: s.cwd ?? stepCwd,
|
|
1021
|
-
model,
|
|
1176
|
+
requestedCwd: machine ? machine.cwd : s.cwd ?? stepCwd,
|
|
1177
|
+
model: selectedModel,
|
|
1022
1178
|
...(contextLimit !== undefined ? { contextLimit } : {}),
|
|
1023
1179
|
...(fast !== undefined ? { fast } : {}),
|
|
1024
|
-
thinking: resolveEffectiveThinking(
|
|
1180
|
+
thinking: resolveEffectiveThinking(selectedModel, effectiveThinking),
|
|
1025
1181
|
...(thinkingCeiling ? { thinkingCeiling } : {}),
|
|
1026
1182
|
launchResolvedExtensions,
|
|
1027
|
-
|
|
1183
|
+
...(requestedModel ? { requestedModel } : {}),
|
|
1028
1184
|
...(primaryModelFromParent ? { skipPrimaryModelVerification: true } : {}),
|
|
1029
1185
|
...(availableModels && availableModels.length > 0 ? { modelVerificationRegistry: availableModels } : {}),
|
|
1030
1186
|
...(ctx.modelResponseAliases ? { modelResponseAliases: ctx.modelResponseAliases } : {}),
|
|
@@ -1033,6 +1189,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
1033
1189
|
allowNestedSubagents: a.allowNestedSubagents,
|
|
1034
1190
|
extensions: a.extensions,
|
|
1035
1191
|
subagentOnlyExtensions: a.subagentOnlyExtensions,
|
|
1192
|
+
...(!externalRunner ? { requiredExtensions } : {}),
|
|
1036
1193
|
mcpDirectTools: a.mcpDirectTools,
|
|
1037
1194
|
mutationTools: a.mutationTools,
|
|
1038
1195
|
completionGuard: a.completionGuard,
|
|
@@ -1064,8 +1221,8 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
1064
1221
|
acceptanceInput: s.acceptance,
|
|
1065
1222
|
acceptanceRole: a.acceptanceRole,
|
|
1066
1223
|
...(s.gateOn ? { gateOn: s.gateOn } : {}),
|
|
1067
|
-
...(
|
|
1068
|
-
...(
|
|
1224
|
+
...(behavior.outputSchema ? { structuredOutputSchema: behavior.outputSchema } : {}),
|
|
1225
|
+
...(behavior.outputSchema ? { structuredOutput: createStructuredOutputRuntime(behavior.outputSchema, path.join(asyncDir, "structured-output"), { acceptanceReport: resolveAcceptanceReportMode(s.acceptance) }) } : {}),
|
|
1069
1226
|
...(resolvedToolBudget.budget ? { toolBudget: resolvedToolBudget.budget } : {}),
|
|
1070
1227
|
...(s.worktree ? { worktree: true } : {}),
|
|
1071
1228
|
};
|
|
@@ -1109,7 +1266,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
1109
1266
|
}
|
|
1110
1267
|
}
|
|
1111
1268
|
const staticStep = nextFlatStep();
|
|
1112
|
-
return buildSeqStep({ ...t, agentContract: t.agentContract ?? s.agentContract, gateOn: t.gateOn ?? s.gateOn }, staticStep.sessionFile, behaviorCwd, progressPrecreated, parallelBehaviors[taskIndex], staticStep.index, { stepIndex, taskIndex }, resultMode === "parallel" ? `tasks[${taskIndex}]` : `chain[${stepIndex}].parallel[${taskIndex}]`);
|
|
1269
|
+
return buildSeqStep({ ...t, machine: t.machine ?? s.machine, worktree: s.worktree, agentContract: t.agentContract ?? s.agentContract, gateOn: t.gateOn ?? s.gateOn }, staticStep.sessionFile, behaviorCwd, progressPrecreated, parallelBehaviors[taskIndex], staticStep.index, { stepIndex, taskIndex }, resultMode === "parallel" ? `tasks[${taskIndex}]` : `chain[${stepIndex}].parallel[${taskIndex}]`);
|
|
1113
1270
|
}),
|
|
1114
1271
|
concurrency: s.concurrency,
|
|
1115
1272
|
failFast: s.failFast,
|
|
@@ -1238,11 +1395,9 @@ export function executeAsyncChain(
|
|
|
1238
1395
|
} = params;
|
|
1239
1396
|
const resultMode = params.resultMode ?? "chain";
|
|
1240
1397
|
const acceptanceErrors = validateExecutionAcceptance({
|
|
1241
|
-
chain: chain
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
return { acceptance: step.acceptance, outputSchema: step.outputSchema };
|
|
1245
|
-
}),
|
|
1398
|
+
chain: projectChainOutputSchemas(chain, agents,
|
|
1399
|
+
(step, outputSchema) => ({ acceptance: step.acceptance, outputSchema }),
|
|
1400
|
+
(step, parallel) => Array.isArray(step.parallel) ? { parallel } : { acceptance: "acceptance" in step ? step.acceptance : undefined, parallel }),
|
|
1246
1401
|
});
|
|
1247
1402
|
if (acceptanceErrors.length > 0) return formatAsyncStartError(resultMode, acceptanceErrors.join(" "));
|
|
1248
1403
|
const capabilityCeiling = params.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(ctx.currentSessionId);
|
|
@@ -1276,6 +1431,8 @@ export function executeAsyncChain(
|
|
|
1276
1431
|
availableModels: params.availableModels,
|
|
1277
1432
|
cwd,
|
|
1278
1433
|
chainSkills: params.chainSkills,
|
|
1434
|
+
machine: params.machine,
|
|
1435
|
+
machineCwd: params.machineCwd,
|
|
1279
1436
|
sessionFilesByFlatIndex,
|
|
1280
1437
|
thinkingOverridesByFlatIndex,
|
|
1281
1438
|
contextForAgent: params.contextForAgent,
|
|
@@ -1410,7 +1567,7 @@ export function executeAsyncChain(
|
|
|
1410
1567
|
path.join(asyncDir, "status.json"),
|
|
1411
1568
|
(proof) => emitProcessTerminalEvent(ctx, proof),
|
|
1412
1569
|
(runnerProcessInstanceId) => params.activeAsyncCapacity?.markStarted(runnerProcessInstanceId),
|
|
1413
|
-
);
|
|
1570
|
+
) as SpawnRunnerResult;
|
|
1414
1571
|
} catch (error) {
|
|
1415
1572
|
params.activeAsyncCapacity?.rollback();
|
|
1416
1573
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1550,7 +1707,7 @@ export function workflowAwaitedAsyncResultPath(asyncDir: string): string {
|
|
|
1550
1707
|
export function executeAsyncSingle(
|
|
1551
1708
|
id: string,
|
|
1552
1709
|
params: AsyncSingleParams,
|
|
1553
|
-
): AsyncExecutionResult {
|
|
1710
|
+
): AsyncExecutionResult | Promise<AsyncExecutionResult> {
|
|
1554
1711
|
const {
|
|
1555
1712
|
agent,
|
|
1556
1713
|
agentConfig,
|
|
@@ -1614,6 +1771,20 @@ export function executeAsyncSingle(
|
|
|
1614
1771
|
return formatAsyncStartError("single", error instanceof Error ? error.message : String(error));
|
|
1615
1772
|
}
|
|
1616
1773
|
const runnerCwd = resolveChildCwd(ctx.cwd, cwd);
|
|
1774
|
+
const requestedMachine = params.machine ?? agentConfig.machine;
|
|
1775
|
+
const machineUnsupported = formatHerdrMachineRunnerUnsupported({ machine: requestedMachine, agentName: agentConfig.name, runnerType: agentConfig.runner?.type, adapter: agentConfig.runner?.type === "external-cli" ? agentConfig.runner.adapter : undefined, worktree: params.worktree });
|
|
1776
|
+
if (machineUnsupported) return formatAsyncStartError("single", machineUnsupported);
|
|
1777
|
+
let machine: HerdrMachineReference | undefined;
|
|
1778
|
+
let machineEnv: Record<string, string> | undefined;
|
|
1779
|
+
if (requestedMachine) {
|
|
1780
|
+
try {
|
|
1781
|
+
const placement = resolveHerdrMachinePlacement({ machine: requestedMachine, cwd: runnerCwd, stepCwd: params.machineCwd });
|
|
1782
|
+
machine = placement.machine;
|
|
1783
|
+
machineEnv = placement.env;
|
|
1784
|
+
} catch (error) {
|
|
1785
|
+
return formatAsyncStartError("single", error instanceof Error ? error.message : String(error));
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1617
1788
|
let managedWorktreeProvider: "native" | "worktrunk" | undefined;
|
|
1618
1789
|
if (params.worktree === true) {
|
|
1619
1790
|
try {
|
|
@@ -1627,7 +1798,7 @@ export function executeAsyncSingle(
|
|
|
1627
1798
|
? WORKTREE_AGENT_CWD_PLACEHOLDER
|
|
1628
1799
|
: params.worktree === true && managedWorktreeProvider === "native"
|
|
1629
1800
|
? resolveExpectedWorktreeAgentCwd(runnerCwd, `${id}-s0`, 0, worktreeBaseDir)
|
|
1630
|
-
: runnerCwd;
|
|
1801
|
+
: machine?.cwd ?? runnerCwd;
|
|
1631
1802
|
const readExistenceCwd = params.worktree === true ? runnerCwd : instructionCwd;
|
|
1632
1803
|
const skillNames = params.skills ?? agentConfig.skills ?? [];
|
|
1633
1804
|
const availableModels = params.availableModels;
|
|
@@ -1670,7 +1841,11 @@ export function executeAsyncSingle(
|
|
|
1670
1841
|
// absolute paths pass through; relative paths resolve against the child cwd.
|
|
1671
1842
|
const reads = params.reads !== undefined ? params.reads : agentConfig.defaultReads ?? false;
|
|
1672
1843
|
const readPaths = Array.isArray(reads)
|
|
1673
|
-
?
|
|
1844
|
+
? machine
|
|
1845
|
+
? externalRunner
|
|
1846
|
+
? reads.map((read) => read === "~" || read.startsWith("~/") || path.posix.isAbsolute(read) ? read : path.posix.resolve(instructionCwd, read))
|
|
1847
|
+
: []
|
|
1848
|
+
: managedWorktreeProvider === "worktrunk"
|
|
1674
1849
|
? resolveExistingReadInstructionPaths(reads, instructionCwd, readExistenceCwd)
|
|
1675
1850
|
: resolveExistingReadPaths(reads, readExistenceCwd)
|
|
1676
1851
|
: [];
|
|
@@ -1736,36 +1911,37 @@ export function executeAsyncSingle(
|
|
|
1736
1911
|
const structuredOutput = params.structuredOutputSchema
|
|
1737
1912
|
? createStructuredOutputRuntime(params.structuredOutputSchema, path.join(asyncDir, "structured-output"), { acceptanceReport: resolveAcceptanceReportMode(params.acceptance) })
|
|
1738
1913
|
: undefined;
|
|
1739
|
-
let
|
|
1914
|
+
let selectedModel = model;
|
|
1915
|
+
let requestedModel: string | undefined;
|
|
1740
1916
|
if (!externalRunner) {
|
|
1741
1917
|
try {
|
|
1742
|
-
|
|
1918
|
+
const modelEvidence = resolveModelSelection(primaryModel, availableModels, agentConfig.modelProvider ?? ctx.currentModelProvider, {
|
|
1743
1919
|
scope: modelScopes,
|
|
1744
1920
|
primaryModelFromParent: modelOrigin === "inherited",
|
|
1745
1921
|
origin: modelOrigin,
|
|
1746
|
-
}).flatMap((candidate) => {
|
|
1747
|
-
const resolved = applyThinkingSuffix(candidate, effectiveThinking, params.thinkingOverride !== undefined);
|
|
1748
|
-
return resolved ? [resolved] : [];
|
|
1749
1922
|
});
|
|
1750
|
-
|
|
1923
|
+
requestedModel = modelEvidence.requestedModel;
|
|
1924
|
+
selectedModel = applyThinkingSuffix(modelEvidence.model, effectiveThinking, params.thinkingOverride !== undefined);
|
|
1925
|
+
assertThinkingWithinCeiling({ model: selectedModel, configThinking: effectiveThinking, ceiling: thinkingCeiling, agent: agentConfig.name, runId: id });
|
|
1751
1926
|
} catch (error) {
|
|
1752
1927
|
return formatAsyncStartError("single", error instanceof Error ? error.message : String(error));
|
|
1753
1928
|
}
|
|
1754
1929
|
}
|
|
1755
1930
|
const hostAvailableBuiltins = getHostBuiltinToolNames(ctx.pi);
|
|
1931
|
+
const requiredExtensions = externalRunner ? [] : params.requiredExtensions ?? ctx.childRuntime?.requiredExtensions ?? resolveRequiredChildExtensions(ctx.parentSessionId ?? ctx.currentSessionId ?? undefined);
|
|
1756
1932
|
const toolPlan = resolvePiLaunchToolPlan({
|
|
1757
1933
|
tools: agentConfig.tools,
|
|
1758
1934
|
excludeTools: agentConfig.excludeTools,
|
|
1759
1935
|
allowNestedSubagents: agentConfig.allowNestedSubagents,
|
|
1760
1936
|
extensions: agentConfig.extensions,
|
|
1761
1937
|
subagentOnlyExtensions: agentConfig.subagentOnlyExtensions,
|
|
1938
|
+
requiredExtensions,
|
|
1762
1939
|
mcpDirectTools: agentConfig.mcpDirectTools,
|
|
1763
1940
|
cwd: runnerCwd,
|
|
1764
1941
|
requireReadTool: Boolean(resolvedSkills.length),
|
|
1765
1942
|
structuredOutput: Boolean(params.structuredOutputSchema),
|
|
1766
1943
|
fast: params.fast ?? agentConfig.fast,
|
|
1767
|
-
model,
|
|
1768
|
-
modelCandidates,
|
|
1944
|
+
model: selectedModel,
|
|
1769
1945
|
capabilityCeiling,
|
|
1770
1946
|
inheritedCapabilityCeiling: ctx.childRuntime?.capabilityCeiling,
|
|
1771
1947
|
agentName: agentConfig.name,
|
|
@@ -1789,11 +1965,11 @@ export function executeAsyncSingle(
|
|
|
1789
1965
|
if (contractError) return formatAsyncStartError("single", contractError);
|
|
1790
1966
|
}
|
|
1791
1967
|
const fast = params.fast ?? agentConfig.fast;
|
|
1792
|
-
const launchThinking = resolveEffectiveThinking(
|
|
1968
|
+
const launchThinking = resolveEffectiveThinking(selectedModel, effectiveThinking);
|
|
1793
1969
|
const { definitionDigest, launchContractDigest } = resolveLaunchBinding({
|
|
1794
1970
|
agent: agentConfig,
|
|
1795
1971
|
task,
|
|
1796
|
-
|
|
1972
|
+
model: selectedModel,
|
|
1797
1973
|
...(fast !== undefined ? { fast } : {}),
|
|
1798
1974
|
...(launchThinking ? { thinking: launchThinking } : {}),
|
|
1799
1975
|
systemPrompt,
|
|
@@ -1820,6 +1996,7 @@ export function executeAsyncSingle(
|
|
|
1820
1996
|
...(lane ? { lane } : {}),
|
|
1821
1997
|
launchContractDigest,
|
|
1822
1998
|
...(extensionBindings ? { extensionBindings } : {}),
|
|
1999
|
+
...(requiredExtensions.length > 0 ? { requiredExtensions } : {}),
|
|
1823
2000
|
runFanoutBudget,
|
|
1824
2001
|
sourceRunId: id,
|
|
1825
2002
|
...(params.agentContract ? { agentContract: params.agentContract } : {}),
|
|
@@ -1827,13 +2004,12 @@ export function executeAsyncSingle(
|
|
|
1827
2004
|
launchResolvedExtensions,
|
|
1828
2005
|
...(sessionFile ? { sessionFile } : {}),
|
|
1829
2006
|
cwd: runnerCwd,
|
|
1830
|
-
...(
|
|
2007
|
+
...(selectedModel ? { model: selectedModel } : {}),
|
|
1831
2008
|
...(params.fast ?? recoveryAgentConfig.fast ? { fast: params.fast ?? recoveryAgentConfig.fast } : {}),
|
|
1832
2009
|
...(recoveryAgentConfig.modelProvider ? { modelProvider: recoveryAgentConfig.modelProvider } : {}),
|
|
1833
2010
|
...(modelOrigin === "inherited" ? { modelOverrideFromParent: true } : {}),
|
|
1834
2011
|
modelOrigin,
|
|
1835
|
-
...(
|
|
1836
|
-
...(effectiveThinking ? { thinking: resolveEffectiveThinking(model, effectiveThinking) } : {}),
|
|
2012
|
+
...(effectiveThinking ? { thinking: resolveEffectiveThinking(selectedModel, effectiveThinking) } : {}),
|
|
1837
2013
|
...(thinkingCeiling ? { thinkingCeiling } : {}),
|
|
1838
2014
|
...(recoveryAgentConfig.tools ? { tools: [...recoveryAgentConfig.tools] } : {}),
|
|
1839
2015
|
...(recoveryAgentConfig.excludeTools ? { excludeTools: [...recoveryAgentConfig.excludeTools] } : {}),
|
|
@@ -1877,11 +2053,11 @@ export function executeAsyncSingle(
|
|
|
1877
2053
|
return formatAsyncStartError("single", `Failed to persist async recovery descriptor for '${id}': ${error instanceof Error ? error.message : String(error)}`);
|
|
1878
2054
|
}
|
|
1879
2055
|
}
|
|
1880
|
-
let
|
|
2056
|
+
let spawnResultOrPromise: SpawnRunnerResult | Promise<SpawnRunnerResult> = {};
|
|
1881
2057
|
const initialStatusAt = Date.now();
|
|
1882
2058
|
const initialCompletionOwnerId = ctx.completionOwnerId ?? currentCompletionOwnerId();
|
|
1883
2059
|
try {
|
|
1884
|
-
|
|
2060
|
+
spawnResultOrPromise = spawnRunner(
|
|
1885
2061
|
{
|
|
1886
2062
|
id,
|
|
1887
2063
|
steps: [
|
|
@@ -1892,16 +2068,19 @@ export function executeAsyncSingle(
|
|
|
1892
2068
|
agent,
|
|
1893
2069
|
task: taskText,
|
|
1894
2070
|
...(agentConfig.runner ? { runner: agentConfig.runner } : {}),
|
|
2071
|
+
...(machine ? { machine } : {}),
|
|
2072
|
+
...(!externalRunner && machine && params.reads !== undefined ? { remoteReads: params.reads } : {}),
|
|
2073
|
+
...(machineEnv ? { machineEnv } : {}),
|
|
1895
2074
|
...(params.externalJobFollowUp ? { externalJobFollowUp: params.externalJobFollowUp } : {}),
|
|
1896
2075
|
...(params.context ? { context: params.context } : {}),
|
|
1897
|
-
cwd: runnerCwd,
|
|
1898
|
-
requestedCwd: params.requestedCwd ?? runnerCwd,
|
|
1899
|
-
model,
|
|
2076
|
+
cwd: machine?.cwd ?? runnerCwd,
|
|
2077
|
+
requestedCwd: machine?.cwd ?? params.requestedCwd ?? runnerCwd,
|
|
2078
|
+
model: selectedModel,
|
|
1900
2079
|
...(contextLimit !== undefined ? { contextLimit } : {}),
|
|
1901
2080
|
...(params.fast ?? agentConfig.fast ? { fast: params.fast ?? agentConfig.fast } : {}),
|
|
1902
|
-
thinking: resolveEffectiveThinking(
|
|
2081
|
+
thinking: resolveEffectiveThinking(selectedModel, effectiveThinking),
|
|
1903
2082
|
...(thinkingCeiling ? { thinkingCeiling } : {}),
|
|
1904
|
-
|
|
2083
|
+
...(requestedModel ? { requestedModel } : {}),
|
|
1905
2084
|
...(modelOrigin === "inherited" ? { skipPrimaryModelVerification: true } : {}),
|
|
1906
2085
|
...(availableModels && availableModels.length > 0 ? { modelVerificationRegistry: availableModels } : {}),
|
|
1907
2086
|
...(ctx.modelResponseAliases ? { modelResponseAliases: ctx.modelResponseAliases } : {}),
|
|
@@ -1910,6 +2089,7 @@ export function executeAsyncSingle(
|
|
|
1910
2089
|
allowNestedSubagents: agentConfig.allowNestedSubagents,
|
|
1911
2090
|
extensions: agentConfig.extensions,
|
|
1912
2091
|
subagentOnlyExtensions: agentConfig.subagentOnlyExtensions,
|
|
2092
|
+
...(!externalRunner ? { requiredExtensions } : {}),
|
|
1913
2093
|
mcpDirectTools: agentConfig.mcpDirectTools,
|
|
1914
2094
|
mutationTools: agentConfig.mutationTools,
|
|
1915
2095
|
completionGuard: agentConfig.completionGuard,
|
|
@@ -1968,6 +2148,7 @@ export function executeAsyncSingle(
|
|
|
1968
2148
|
timeoutMs,
|
|
1969
2149
|
deadlineAt,
|
|
1970
2150
|
toolTimeoutMs,
|
|
2151
|
+
checkpointBeforeDeadlineMs: params.checkpointBeforeDeadlineMs,
|
|
1971
2152
|
toolBudget: params.toolBudget,
|
|
1972
2153
|
usageBudget: params.usageBudget,
|
|
1973
2154
|
controlIntercomTarget,
|
|
@@ -2015,7 +2196,7 @@ export function executeAsyncSingle(
|
|
|
2015
2196
|
const message = error instanceof Error ? error.message : String(error);
|
|
2016
2197
|
return formatAsyncStartError("single", `Failed to start async run '${id}': ${message}`);
|
|
2017
2198
|
}
|
|
2018
|
-
|
|
2199
|
+
const finishSpawnResult = (spawnResult: SpawnRunnerResult): AsyncExecutionResult => {
|
|
2019
2200
|
if (spawnResult.error) {
|
|
2020
2201
|
if (spawnResult.startupDidNotProceed) {
|
|
2021
2202
|
if (!spawnResult.runnerProcessInstanceId || params.activeAsyncCapacity?.rollbackBeforeRunnerProceed(spawnResult.runnerProcessInstanceId) !== true) params.activeAsyncCapacity?.rollback();
|
|
@@ -2092,4 +2273,6 @@ export function executeAsyncSingle(
|
|
|
2092
2273
|
content: [{ type: "text", text: formatAsyncStartedMessage(`Async: ${agent} [${id}]`, ctx.interactive === true) }],
|
|
2093
2274
|
details: { mode: "single", runId: id, results: [], asyncId: id, asyncDir, launchContractDigest, launchResolvedExtensions, ...(capabilityCeiling ? { capabilityCeiling } : {}), ...(params.context ? { context: params.context } : {}), ...(timeoutMs !== undefined ? { timeoutMs, deadlineAt } : {}), ...(params.toolBudget ? { toolBudget: resolvedToolBudget.budget ?? params.toolBudget } : {}), ...(initialUsageBudget ? { usageBudget: initialUsageBudget } : {}) } as Details,
|
|
2094
2275
|
};
|
|
2276
|
+
};
|
|
2277
|
+
return spawnResultOrPromise instanceof Promise ? spawnResultOrPromise.then(finishSpawnResult) : finishSpawnResult(spawnResultOrPromise);
|
|
2095
2278
|
}
|