pi-subagents 0.61.0 → 0.63.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 +52 -1
- package/docs/agents.md +11 -6
- package/docs/configuration.md +31 -5
- package/docs/extension-api.md +2 -2
- package/docs/models.md +5 -5
- package/docs/observability.md +5 -2
- package/docs/tool-reference.md +3 -3
- package/install.mjs +0 -1
- package/package.json +1 -1
- package/skills/pi-subagents/references/execution-controls.md +3 -4
- package/skills/pi-subagents/references/management-authoring-rpc.md +1 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +3 -3
- package/src/agents/agent-management.ts +41 -4
- package/src/agents/agent-serializer.ts +3 -0
- package/src/agents/agents.ts +120 -124
- package/src/agents/runtime-agent-registry.ts +5 -1
- package/src/api/preflight.ts +4 -0
- package/src/api/shared-types.ts +3 -0
- package/src/extension/config.ts +20 -0
- package/src/extension/public-execution.ts +1 -0
- package/src/extension/schemas.ts +6 -2
- package/src/extension/tool-description.ts +1 -1
- package/src/inspectors/herdr/inspector-runner.ts +19 -13
- package/src/runs/background/active-async-capacity.ts +26 -8
- package/src/runs/background/async-execution.ts +100 -23
- package/src/runs/background/async-resume.ts +6 -2
- package/src/runs/background/async-status.ts +18 -2
- package/src/runs/background/notify.ts +13 -1
- package/src/runs/background/process-terminal.ts +16 -0
- package/src/runs/background/run-status.ts +22 -2
- package/src/runs/background/scheduled-runs.ts +63 -6
- package/src/runs/background/steering.ts +4 -1
- package/src/runs/background/subagent-runner.ts +44 -9
- package/src/runs/background/wait-completions.ts +13 -0
- package/src/runs/background/wait-tool.ts +1 -7
- package/src/runs/foreground/execution.ts +14 -7
- package/src/runs/foreground/subagent-executor.ts +38 -5
- package/src/runs/shared/acceptance.ts +85 -18
- package/src/runs/shared/capability-ceiling.ts +1 -0
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/lane-metadata.ts +24 -3
- package/src/runs/shared/parallel-handoff.ts +4 -0
- package/src/runs/shared/parallel-utils.ts +2 -6
- package/src/runs/shared/permissions.ts +1 -1
- package/src/runs/shared/pi-args.ts +32 -14
- package/src/runs/shared/pi-spawn.ts +69 -35
- package/src/runs/shared/structured-output.ts +33 -6
- package/src/runs/shared/subagent-prompt-runtime.ts +20 -3
- package/src/runs/shared/task-intent.ts +21 -7
- package/src/runs/shared/tool-timeout.ts +1 -1
- package/src/runs/shared/worktree.ts +467 -63
- package/src/shared/atomic-json.ts +3 -1
- package/src/shared/fork-context.ts +0 -12
- package/src/shared/fork-session-cwd.ts +27 -0
- package/src/shared/launch-contract.ts +3 -0
- package/src/shared/types.ts +32 -1
- package/src/shared/utils.ts +18 -7
- package/src/slash/slash-commands.ts +1 -1
- package/src/slash/subagents-admin.ts +26 -12
- package/src/tui/fleet-status.ts +61 -2
- package/src/tui/fleet.ts +12 -7
- package/src/tui/render.ts +222 -14
- package/src/workflows/workflow-checklist.ts +441 -0
|
@@ -41,7 +41,8 @@ export function formatInspectorDashboard(input: { status: AsyncStatus; asyncDir:
|
|
|
41
41
|
lines.push("");
|
|
42
42
|
}
|
|
43
43
|
lines.push(formatAsyncRunTranscript(status, asyncDir, { index: input.index, lines: 60, sessionRoots: input.sessionRoots }));
|
|
44
|
-
const
|
|
44
|
+
const acceptsPlainGuidance = input.index !== undefined || status.mode === "single";
|
|
45
|
+
const controls = [input.allowSteer === false || !acceptsPlainGuidance ? undefined : "type guidance", input.allowSteer === false ? undefined : "steer <message>", input.allowStop === false ? undefined : "stop", "status"].filter(Boolean);
|
|
45
46
|
lines.push("", `Controls: ${controls.join(" | ")}`, "Supervisor replies remain in the parent Pi session (subagent_supervisor/intercom).");
|
|
46
47
|
return lines.join("\n");
|
|
47
48
|
}
|
|
@@ -81,6 +82,20 @@ function isTerminal(status: AsyncStatus): boolean {
|
|
|
81
82
|
return status.state !== "queued" && status.state !== "running";
|
|
82
83
|
}
|
|
83
84
|
|
|
85
|
+
function queueInspectorSteer(options: RunnerOptions, status: AsyncStatus, message: string): string {
|
|
86
|
+
if (options.allowSteer === false) throw new Error("Authority policy does not allow steer from this inspector.");
|
|
87
|
+
if (isTerminal(status)) throw new Error(`Run '${options.runId}' is ${status.state} and cannot be steered.`);
|
|
88
|
+
const runningIndexes = (status.steps ?? []).map((step, index) => step.status === "running" ? index : undefined).filter((index): index is number => index !== undefined);
|
|
89
|
+
const targetIndex = options.index ?? (status.mode === "single" ? 0 : undefined);
|
|
90
|
+
if (targetIndex === undefined && runningIndexes.length === 0) throw new Error("No running child is available to steer. Open a child-specific inspector for a pending child.");
|
|
91
|
+
requestAsyncSteer(options.asyncDir, {
|
|
92
|
+
message,
|
|
93
|
+
...(targetIndex !== undefined ? { targetIndex } : { targetIndexes: runningIndexes }),
|
|
94
|
+
source: "herdr-inspector",
|
|
95
|
+
});
|
|
96
|
+
return steeringReceipt(message, `Steering queued for run ${options.runId}.`);
|
|
97
|
+
}
|
|
98
|
+
|
|
84
99
|
export function submitInspectorControl(options: RunnerOptions, line: string): string {
|
|
85
100
|
const command = line.trim();
|
|
86
101
|
if (!command || command === "status") return "Status refreshed.";
|
|
@@ -93,22 +108,13 @@ export function submitInspectorControl(options: RunnerOptions, line: string): st
|
|
|
93
108
|
return `Stop requested for run ${options.runId}.`;
|
|
94
109
|
}
|
|
95
110
|
if (command.startsWith("steer ")) {
|
|
96
|
-
if (options.allowSteer === false) throw new Error("Authority policy does not allow steer from this inspector.");
|
|
97
111
|
const message = command.slice("steer ".length).trim();
|
|
98
112
|
if (!message) throw new Error("steer requires a message.");
|
|
99
|
-
|
|
100
|
-
const runningIndexes = (status.steps ?? []).map((step, index) => step.status === "running" ? index : undefined).filter((index): index is number => index !== undefined);
|
|
101
|
-
const targetIndex = options.index ?? (status.mode === "single" ? 0 : undefined);
|
|
102
|
-
if (targetIndex === undefined && runningIndexes.length === 0) throw new Error("No running child is available to steer. Open a child-specific inspector for a pending child.");
|
|
103
|
-
requestAsyncSteer(options.asyncDir, {
|
|
104
|
-
message,
|
|
105
|
-
...(targetIndex !== undefined ? { targetIndex } : { targetIndexes: runningIndexes }),
|
|
106
|
-
source: "herdr-inspector",
|
|
107
|
-
});
|
|
108
|
-
return steeringReceipt(message, `Steering queued for run ${options.runId}.`);
|
|
113
|
+
return queueInspectorSteer(options, status, message);
|
|
109
114
|
}
|
|
110
115
|
if (command.startsWith("reply ")) throw new Error("Supervisor replies are owned by the parent Pi session; use subagent_supervisor/intercom there.");
|
|
111
|
-
throw new Error("
|
|
116
|
+
if (options.index === undefined && status.mode !== "single") throw new Error("Plain guidance requires a child-specific inspector. Use steer <message> to target all running children from the aggregate inspector.");
|
|
117
|
+
return queueInspectorSteer(options, status, command);
|
|
112
118
|
}
|
|
113
119
|
|
|
114
120
|
export function runInspector(argv = process.argv.slice(2)): void {
|
|
@@ -33,6 +33,7 @@ export interface ActiveAsyncCapacityHandle {
|
|
|
33
33
|
markStarted(runnerProcessInstanceId: string): void;
|
|
34
34
|
markWorkflowStarted(): void;
|
|
35
35
|
rollback(): boolean;
|
|
36
|
+
rollbackBeforeRunnerProceed(runnerProcessInstanceId: string): boolean;
|
|
36
37
|
reconcile(liveWorkflowRunIds?: ReadonlySet<string>): ActiveAsyncCapacitySnapshot;
|
|
37
38
|
}
|
|
38
39
|
|
|
@@ -43,6 +44,7 @@ interface CapacityOptions {
|
|
|
43
44
|
abandonedSlotReleaseAfterMs?: number | false;
|
|
44
45
|
pidLiveness?: (pid: number) => PidLiveness;
|
|
45
46
|
afterSlotRename?: (releasedDir: string) => void;
|
|
47
|
+
writeOwner?: (filePath: string, owner: ActiveAsyncCapacityOwnerV1) => void;
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
export interface ActiveAsyncCapacityReleaseEvidence {
|
|
@@ -269,7 +271,6 @@ function workflowReleaseVerdict(owner: ActiveAsyncCapacityOwnerV1, status: Async
|
|
|
269
271
|
if (liveWorkflowRunIds.has(owner.runId)) return { state: "retained", reason: "workflow controller is still live" };
|
|
270
272
|
for (const step of status.steps ?? []) {
|
|
271
273
|
const label = step.workflowKey ?? step.agent;
|
|
272
|
-
if (step.status === "pending" || step.status === "running" || step.status === "paused") return { state: "retained", reason: `workflow child ${label} is still ${step.status}` };
|
|
273
274
|
if (typeof step.async !== "boolean") return { state: "retained", reason: `workflow child ${label} is missing async classification` };
|
|
274
275
|
if (!step.async) continue;
|
|
275
276
|
if (!step.runId) return { state: "retained", reason: `async workflow child ${label} is missing run id` };
|
|
@@ -380,6 +381,7 @@ function createSlot(poolDir: string, owner: ActiveAsyncCapacityOwnerV1): boolean
|
|
|
380
381
|
|
|
381
382
|
function handleFor(owner: ActiveAsyncCapacityOwnerV1, limit: number, options: CapacityOptions, rollbackOwner?: ActiveAsyncCapacityOwnerV1): ActiveAsyncCapacityHandle {
|
|
382
383
|
const rootDir = options.rootDir ?? ACTIVE_ASYNC_CAPACITY_DIR;
|
|
384
|
+
const writeOwner = options.writeOwner ?? writePrivateAtomicJson;
|
|
383
385
|
const dir = slotDir(sessionDir(owner.ownerSessionId, rootDir), owner.slot);
|
|
384
386
|
return {
|
|
385
387
|
owner,
|
|
@@ -388,14 +390,10 @@ function handleFor(owner: ActiveAsyncCapacityOwnerV1, limit: number, options: Ca
|
|
|
388
390
|
const current = matchingOwner(dir, owner);
|
|
389
391
|
if (!current) return false;
|
|
390
392
|
const next = { ...current, runnerProcessInstanceId, runnerStartedAt: options.now?.() ?? Date.now() };
|
|
391
|
-
// Mark memory first
|
|
392
|
-
//
|
|
393
|
+
// Mark memory first so pre-proceed cleanup can distinguish a failed durable
|
|
394
|
+
// bind from an unrelated unstarted reservation.
|
|
393
395
|
Object.assign(owner, next);
|
|
394
|
-
|
|
395
|
-
writePrivateAtomicJson(path.join(dir, "owner.json"), next);
|
|
396
|
-
} catch (error) {
|
|
397
|
-
console.error(`Failed to bind active async capacity to runner '${runnerProcessInstanceId}'; capacity will remain occupied:`, error);
|
|
398
|
-
}
|
|
396
|
+
writeOwner(path.join(dir, "owner.json"), next);
|
|
399
397
|
return true;
|
|
400
398
|
});
|
|
401
399
|
if (!claimed.acquired || !claimed.value) throw new Error(`Active async capacity ownership changed for run '${owner.runId}'.`);
|
|
@@ -427,6 +425,26 @@ function handleFor(owner: ActiveAsyncCapacityOwnerV1, limit: number, options: Ca
|
|
|
427
425
|
});
|
|
428
426
|
return claimed.acquired && claimed.value;
|
|
429
427
|
},
|
|
428
|
+
rollbackBeforeRunnerProceed(runnerProcessInstanceId) {
|
|
429
|
+
const claimed = withSlotClaim(dir, () => {
|
|
430
|
+
const current = matchingOwner(dir, owner);
|
|
431
|
+
if (!current) return false;
|
|
432
|
+
const boundToRunner = current.runnerProcessInstanceId === runnerProcessInstanceId;
|
|
433
|
+
const bindingFailedBeforeProceed = current.runnerProcessInstanceId === undefined && current.runnerStartedAt === undefined && owner.runnerProcessInstanceId === runnerProcessInstanceId;
|
|
434
|
+
if (!boundToRunner && !bindingFailedBeforeProceed) return false;
|
|
435
|
+
if (rollbackOwner) {
|
|
436
|
+
writePrivateAtomicJson(path.join(dir, "owner.json"), rollbackOwner);
|
|
437
|
+
Object.assign(owner, rollbackOwner);
|
|
438
|
+
return true;
|
|
439
|
+
}
|
|
440
|
+
const releasedDir = path.join(path.dirname(dir), `.${path.basename(dir)}.released-${randomUUID()}`);
|
|
441
|
+
fs.renameSync(dir, releasedDir);
|
|
442
|
+
options.afterSlotRename?.(releasedDir);
|
|
443
|
+
fs.rmSync(releasedDir, { recursive: true, force: true });
|
|
444
|
+
return true;
|
|
445
|
+
});
|
|
446
|
+
return claimed.acquired && claimed.value;
|
|
447
|
+
},
|
|
430
448
|
reconcile(liveWorkflowRunIds) {
|
|
431
449
|
return reconcileActiveAsyncCapacity(owner.ownerSessionId, limit, { ...options, rootDir, liveWorkflowRunIds });
|
|
432
450
|
},
|
|
@@ -11,15 +11,15 @@ import { createRequire } 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 { appendAgentRefinementOverlay } from "../../agents/agent-refinements.ts";
|
|
14
|
-
import { writePrivateAtomicJson } from "../../shared/atomic-json.ts";
|
|
14
|
+
import { createAtomicJsonWriter, writePrivateAtomicJson } from "../../shared/atomic-json.ts";
|
|
15
15
|
import { currentCompletionOwnerId } from "../../shared/completion-owner.ts";
|
|
16
16
|
import { planChildLaunch, resolveStepBehavior, suppressProgressForReadOnlyTask, type ResolvedStepBehavior } from "../shared/child-launch-plan.ts";
|
|
17
17
|
import { applyThinkingSuffix, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan } from "../shared/pi-args.ts";
|
|
18
18
|
import { injectOutputPathSystemPrompt, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
19
|
-
import { buildChainInstructions, isDynamicParallelStep, isParallelStep, resolveExistingReadPaths, writeInitialProgressFile, type ChainStep, type SequentialStep, type StepOverrides } from "../../shared/settings.ts";
|
|
19
|
+
import { buildChainInstructions, isDynamicParallelStep, isParallelStep, resolveExistingReadInstructionPaths, resolveExistingReadPaths, writeInitialProgressFile, type ChainStep, type SequentialStep, type StepOverrides } from "../../shared/settings.ts";
|
|
20
20
|
import type { RunnerStep } from "../shared/parallel-utils.ts";
|
|
21
21
|
import type { ContextMode } from "../shared/context-mode.ts";
|
|
22
|
-
import { resolvePiPackageRoot } from "../shared/pi-spawn.ts";
|
|
22
|
+
import { resolveInstalledPiPackageRoot, resolvePiPackageRoot } from "../shared/pi-spawn.ts";
|
|
23
23
|
import { preflightLaunchCwd } from "../shared/launch-cwd.ts";
|
|
24
24
|
import { resolveNodeExecutable } from "../../shared/node-executable.ts";
|
|
25
25
|
import { backgroundProcessOptions } from "../shared/background-process-options.ts";
|
|
@@ -31,11 +31,11 @@ import { resolveToolTimeoutMs, toolTimeoutFromEnv } from "../shared/tool-timeout
|
|
|
31
31
|
import { resolveModelScopesForAgent, type ModelScopeConfig } from "../shared/model-scope.ts";
|
|
32
32
|
import { findModelInfo, resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
33
33
|
import { assertThinkingWithinCeiling, decodeThinkingCeiling, intersectThinkingCeilings, SUBAGENT_THINKING_CEILING_ENV, type ThinkingLevel } from "../../shared/thinking-ceiling.ts";
|
|
34
|
-
import { resolveExpectedWorktreeAgentCwd } from "../shared/worktree.ts";
|
|
34
|
+
import { resolveExpectedWorktreeAgentCwd, resolveWorktreeProvider, shouldDeferWorktreeCwd, WORKTREE_AGENT_CWD_PLACEHOLDER } from "../shared/worktree.ts";
|
|
35
35
|
import { buildWorkflowGraphSnapshot } from "../shared/workflow-graph.ts";
|
|
36
36
|
import { ChainOutputValidationError, validateChainOutputBindings } from "../shared/chain-outputs.ts";
|
|
37
37
|
import { createStructuredOutputRuntime } from "../shared/structured-output.ts";
|
|
38
|
-
import { resolveEffectiveAcceptance, validateAcceptanceInput, validateExecutionAcceptance } from "../shared/acceptance.ts";
|
|
38
|
+
import { resolveAcceptanceReportMode, resolveEffectiveAcceptance, validateAcceptanceInput, validateExecutionAcceptance } from "../shared/acceptance.ts";
|
|
39
39
|
import { createRunFanoutBudget, writeRunFanoutBudgetDescriptor } from "../shared/run-fanout-budget.ts";
|
|
40
40
|
import { validateImplementationToolContract } from "../shared/completion-guard.ts";
|
|
41
41
|
import {
|
|
@@ -70,7 +70,7 @@ import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
|
|
|
70
70
|
import { usageBudgetState } from "../shared/usage-budget.ts";
|
|
71
71
|
import type { ImportedAsyncRoot } from "./chain-root-attachment.ts";
|
|
72
72
|
import type { SessionLeaseRequest } from "../shared/session-lease.ts";
|
|
73
|
-
import { finalizeProcessTerminal, readProcessTerminal } from "./process-terminal.ts";
|
|
73
|
+
import { finalizeProcessTerminal, initializeProcessTerminal, readProcessTerminal } from "./process-terminal.ts";
|
|
74
74
|
import type { ActiveAsyncCapacityHandle } from "./active-async-capacity.ts";
|
|
75
75
|
import { statusStepDescription } from "./chain-append.ts";
|
|
76
76
|
import { SUBAGENT_PROCESS_TERMINAL_EVENT } from "../../shared/types.ts";
|
|
@@ -81,7 +81,7 @@ import { normalizeExtensionBindings, omitExtensionBindingsEnv, type ExtensionBin
|
|
|
81
81
|
import { assertWorkflowLaneKey, normalizeWorkflowLaneMetadata } from "../shared/lane-metadata.ts";
|
|
82
82
|
|
|
83
83
|
const require = createRequire(import.meta.url);
|
|
84
|
-
const piPackageRoot = resolvePiPackageRoot();
|
|
84
|
+
const piPackageRoot = resolvePiPackageRoot() ?? resolveInstalledPiPackageRoot();
|
|
85
85
|
|
|
86
86
|
function resolveJitiCliFromPackageJson(packageJsonPath: string): string | undefined {
|
|
87
87
|
if (!fs.existsSync(packageJsonPath)) return undefined;
|
|
@@ -178,6 +178,8 @@ interface AsyncChainParams {
|
|
|
178
178
|
worktreeSetupHook?: string;
|
|
179
179
|
worktreeSetupHookTimeoutMs?: number;
|
|
180
180
|
worktreeBaseDir?: string;
|
|
181
|
+
worktreeProvider?: import("../../shared/types.ts").WorktreeProvider;
|
|
182
|
+
worktreeBranchPrefix?: string;
|
|
181
183
|
controlConfig?: ResolvedControlConfig;
|
|
182
184
|
controlIntercomTarget?: string;
|
|
183
185
|
childIntercomTarget?: (agent: string, index: number) => string | undefined;
|
|
@@ -245,6 +247,8 @@ interface AsyncSingleParams {
|
|
|
245
247
|
worktreeSetupHook?: string;
|
|
246
248
|
worktreeSetupHookTimeoutMs?: number;
|
|
247
249
|
worktreeBaseDir?: string;
|
|
250
|
+
worktreeProvider?: import("../../shared/types.ts").WorktreeProvider;
|
|
251
|
+
worktreeBranchPrefix?: string;
|
|
248
252
|
worktree?: boolean;
|
|
249
253
|
controlConfig?: ResolvedControlConfig;
|
|
250
254
|
intercomBridge?: IntercomBridgeConfig;
|
|
@@ -310,6 +314,8 @@ export interface AsyncRunnerStepBuildParams {
|
|
|
310
314
|
waitToolEnabled?: boolean;
|
|
311
315
|
waitToolDefaultTimeoutMs?: number;
|
|
312
316
|
worktreeBaseDir?: string;
|
|
317
|
+
worktreeProvider?: import("../../shared/types.ts").WorktreeProvider;
|
|
318
|
+
worktreeBranchPrefix?: string;
|
|
313
319
|
asyncDir: string;
|
|
314
320
|
outputBaseDir?: string;
|
|
315
321
|
validateOutputBindings?: boolean;
|
|
@@ -432,13 +438,15 @@ function waitForRunnerStartup(startupPath: string, expectedState: RunnerStartupS
|
|
|
432
438
|
return { ok: false, error: `Timed out after ${timeoutMs}ms waiting for the async runner startup state '${expectedState}'.`, startupDidNotProceed: true };
|
|
433
439
|
}
|
|
434
440
|
|
|
441
|
+
const writePrivateStartupControlJson = createAtomicJsonWriter({ mode: 0o600, ignoreCleanupErrorAfterSuccess: true });
|
|
442
|
+
|
|
435
443
|
function writeRunnerStartupControl(filePath: string, payload: { action: "ack" | "proceed"; token: string }): void {
|
|
436
444
|
// Delegate to the shared atomic JSON writer (temp file + rename, retrying
|
|
437
445
|
// transient Windows EPERM/EBUSY/EACCES locks and cleaning up the temp file
|
|
438
446
|
// on failure), so the startup handshake gets the same locking resilience as
|
|
439
447
|
// every other async control/result file. This is exercised by
|
|
440
448
|
// test/unit/atomic-json.test.ts.
|
|
441
|
-
|
|
449
|
+
writePrivateStartupControlJson(filePath, payload);
|
|
442
450
|
}
|
|
443
451
|
|
|
444
452
|
function runnerIsAlive(pid: number): boolean {
|
|
@@ -520,7 +528,7 @@ export function emitProcessTerminalEvent(ctx: AsyncExecutionContext, proof: unkn
|
|
|
520
528
|
}
|
|
521
529
|
}
|
|
522
530
|
|
|
523
|
-
function spawnRunner(cfg: object, suffix: string, cwd: string, initialStatus: Omit<AsyncStatus, "pid" | "processTerminal">, initialStatusPath: string, onProcessTerminal?: (proof: unknown) => void, requestedCwd = cwd): SpawnRunnerResult {
|
|
531
|
+
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 {
|
|
524
532
|
const cwdError = preflightLaunchCwd(requestedCwd, cwd);
|
|
525
533
|
if (cwdError) return { error: cwdError };
|
|
526
534
|
|
|
@@ -637,6 +645,24 @@ function spawnRunner(cfg: object, suffix: string, cwd: string, initialStatus: Om
|
|
|
637
645
|
});
|
|
638
646
|
} catch (error) {
|
|
639
647
|
const message = `Failed to persist initial async status: ${error instanceof Error ? error.message : String(error)}`;
|
|
648
|
+
if (launchAsyncDir) persistPreProceedStartupFailure(launchAsyncDir, launchRunId, runnerProcessInstanceId, launchSessionId, launchCompletionOwnerId, message);
|
|
649
|
+
const terminationObserved = terminateRunnerBeforeProceed(proc.pid);
|
|
650
|
+
return { pid: proc.pid, runnerProcessInstanceId, error: message, terminationObserved, startupDidNotProceed: true };
|
|
651
|
+
}
|
|
652
|
+
try {
|
|
653
|
+
if (!launchAsyncDir) throw new Error("Async runner is missing its lifecycle directory.");
|
|
654
|
+
initializeProcessTerminal(launchAsyncDir, launchRunId, runnerProcessInstanceId);
|
|
655
|
+
} catch (error) {
|
|
656
|
+
const message = `Failed to establish async runner lifecycle sidecar: ${error instanceof Error ? error.message : String(error)}`;
|
|
657
|
+
if (launchAsyncDir) persistPreProceedStartupFailure(launchAsyncDir, launchRunId, runnerProcessInstanceId, launchSessionId, launchCompletionOwnerId, message);
|
|
658
|
+
const terminationObserved = terminateRunnerBeforeProceed(proc.pid);
|
|
659
|
+
return { pid: proc.pid, runnerProcessInstanceId, error: message, terminationObserved, startupDidNotProceed: true };
|
|
660
|
+
}
|
|
661
|
+
try {
|
|
662
|
+
onBeforeProceed?.(runnerProcessInstanceId);
|
|
663
|
+
} catch (error) {
|
|
664
|
+
const message = `Failed to establish async runner capacity ownership: ${error instanceof Error ? error.message : String(error)}`;
|
|
665
|
+
if (launchAsyncDir) persistPreProceedStartupFailure(launchAsyncDir, launchRunId, runnerProcessInstanceId, launchSessionId, launchCompletionOwnerId, message);
|
|
640
666
|
const terminationObserved = terminateRunnerBeforeProceed(proc.pid);
|
|
641
667
|
return { pid: proc.pid, runnerProcessInstanceId, error: message, terminationObserved, startupDidNotProceed: true };
|
|
642
668
|
}
|
|
@@ -720,6 +746,8 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
720
746
|
thinkingOverridesByFlatIndex,
|
|
721
747
|
maxSubagentDepth,
|
|
722
748
|
worktreeBaseDir,
|
|
749
|
+
worktreeProvider,
|
|
750
|
+
worktreeBranchPrefix,
|
|
723
751
|
asyncDir,
|
|
724
752
|
} = params;
|
|
725
753
|
const outputBaseDir = params.outputBaseDir;
|
|
@@ -727,6 +755,15 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
727
755
|
const chainSkills = params.chainSkills ?? [];
|
|
728
756
|
const availableModels = params.availableModels;
|
|
729
757
|
const runnerCwd = resolveChildCwd(ctx.cwd, cwd);
|
|
758
|
+
let managedWorktreeProvider: "native" | "worktrunk" | undefined;
|
|
759
|
+
try {
|
|
760
|
+
if (chain.some((step) => "worktree" in step && step.worktree === true)) {
|
|
761
|
+
const resolved = resolveWorktreeProvider(worktreeProvider, worktreeBaseDir);
|
|
762
|
+
managedWorktreeProvider = shouldDeferWorktreeCwd(worktreeProvider, worktreeBaseDir) ? "worktrunk" : resolved;
|
|
763
|
+
}
|
|
764
|
+
} catch (error) {
|
|
765
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
766
|
+
}
|
|
730
767
|
const progressDir = params.progressDir ?? runnerCwd;
|
|
731
768
|
const graphChain: ChainStep[] = params.attachRoot
|
|
732
769
|
? [{
|
|
@@ -853,7 +890,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
853
890
|
if (validationError) throw new AsyncStartValidationError(validationError);
|
|
854
891
|
let taskTemplate = s.task ?? "{previous}";
|
|
855
892
|
taskTemplate = taskTemplate.replace(/\{task\}/g, originalTask ?? "");
|
|
856
|
-
taskTemplate = taskTemplate.replace(/\{chain_dir\}/g, runnerCwd);
|
|
893
|
+
taskTemplate = taskTemplate.replace(/\{chain_dir\}/g, behaviorCwd ?? runnerCwd);
|
|
857
894
|
const taskText = `${readInstructions.prefix}${taskTemplate}${progressInstructions.suffix}`;
|
|
858
895
|
const task = namespaceOutputPath ? taskText : injectSingleOutputInstruction(taskText, outputPath, a);
|
|
859
896
|
|
|
@@ -905,6 +942,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
905
942
|
const fast = s.fast ?? params.fast ?? a.fast;
|
|
906
943
|
const toolPlan = resolvePiLaunchToolPlan({
|
|
907
944
|
tools: a.tools,
|
|
945
|
+
excludeTools: a.excludeTools,
|
|
908
946
|
allowNestedSubagents: a.allowNestedSubagents,
|
|
909
947
|
extensions: a.extensions,
|
|
910
948
|
subagentOnlyExtensions: a.subagentOnlyExtensions,
|
|
@@ -967,6 +1005,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
967
1005
|
...(primaryModelFromParent ? { skipPrimaryModelVerification: true } : {}),
|
|
968
1006
|
...(availableModels && availableModels.length > 0 ? { modelVerificationRegistry: availableModels } : {}),
|
|
969
1007
|
tools: a.tools,
|
|
1008
|
+
excludeTools: a.excludeTools,
|
|
970
1009
|
allowNestedSubagents: a.allowNestedSubagents,
|
|
971
1010
|
extensions: a.extensions,
|
|
972
1011
|
subagentOnlyExtensions: a.subagentOnlyExtensions,
|
|
@@ -1002,7 +1041,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
1002
1041
|
acceptanceRole: a.acceptanceRole,
|
|
1003
1042
|
...(s.gateOn ? { gateOn: s.gateOn } : {}),
|
|
1004
1043
|
...(s.outputSchema ? { structuredOutputSchema: s.outputSchema } : {}),
|
|
1005
|
-
...(s.outputSchema ? { structuredOutput: createStructuredOutputRuntime(s.outputSchema, path.join(asyncDir, "structured-output"), {
|
|
1044
|
+
...(s.outputSchema ? { structuredOutput: createStructuredOutputRuntime(s.outputSchema, path.join(asyncDir, "structured-output"), { acceptanceReport: resolveAcceptanceReportMode(s.acceptance) }) } : {}),
|
|
1006
1045
|
...(resolvedToolBudget.budget ? { toolBudget: resolvedToolBudget.budget } : {}),
|
|
1007
1046
|
...(s.worktree ? { worktree: true } : {}),
|
|
1008
1047
|
};
|
|
@@ -1036,7 +1075,9 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
1036
1075
|
return {
|
|
1037
1076
|
parallel: s.parallel.map((t, taskIndex) => {
|
|
1038
1077
|
let behaviorCwd: string | undefined;
|
|
1039
|
-
if (s.worktree) {
|
|
1078
|
+
if (s.worktree && managedWorktreeProvider === "worktrunk") {
|
|
1079
|
+
behaviorCwd = WORKTREE_AGENT_CWD_PLACEHOLDER;
|
|
1080
|
+
} else if (s.worktree && managedWorktreeProvider === "native") {
|
|
1040
1081
|
try {
|
|
1041
1082
|
behaviorCwd = resolveExpectedWorktreeAgentCwd(runnerCwd, `${id}-s${stepIndex}`, taskIndex, worktreeBaseDir);
|
|
1042
1083
|
} catch {
|
|
@@ -1091,7 +1132,9 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
1091
1132
|
}
|
|
1092
1133
|
const sequential = s as SequentialStep;
|
|
1093
1134
|
let behaviorCwd: string | undefined;
|
|
1094
|
-
if (sequential.worktree) {
|
|
1135
|
+
if (sequential.worktree && managedWorktreeProvider === "worktrunk") {
|
|
1136
|
+
behaviorCwd = WORKTREE_AGENT_CWD_PLACEHOLDER;
|
|
1137
|
+
} else if (sequential.worktree && managedWorktreeProvider === "native") {
|
|
1095
1138
|
try {
|
|
1096
1139
|
behaviorCwd = resolveExpectedWorktreeAgentCwd(runnerCwd, `${id}-s${stepIndex}`, 0, worktreeBaseDir);
|
|
1097
1140
|
} catch {
|
|
@@ -1161,6 +1204,8 @@ export function executeAsyncChain(
|
|
|
1161
1204
|
worktreeSetupHook,
|
|
1162
1205
|
worktreeSetupHookTimeoutMs,
|
|
1163
1206
|
worktreeBaseDir,
|
|
1207
|
+
worktreeProvider,
|
|
1208
|
+
worktreeBranchPrefix,
|
|
1164
1209
|
controlConfig,
|
|
1165
1210
|
controlIntercomTarget,
|
|
1166
1211
|
childIntercomTarget,
|
|
@@ -1171,7 +1216,7 @@ export function executeAsyncChain(
|
|
|
1171
1216
|
chain: chain.map((step) => {
|
|
1172
1217
|
if (isParallelStep(step)) return { parallel: step.parallel };
|
|
1173
1218
|
if (isDynamicParallelStep(step)) return { acceptance: step.acceptance, parallel: step.parallel };
|
|
1174
|
-
return { acceptance: step.acceptance };
|
|
1219
|
+
return { acceptance: step.acceptance, outputSchema: step.outputSchema };
|
|
1175
1220
|
}),
|
|
1176
1221
|
});
|
|
1177
1222
|
if (acceptanceErrors.length > 0) return formatAsyncStartError(resultMode, acceptanceErrors.join(" "));
|
|
@@ -1217,6 +1262,8 @@ export function executeAsyncChain(
|
|
|
1217
1262
|
waitToolEnabled: params.waitToolEnabled,
|
|
1218
1263
|
waitToolDefaultTimeoutMs: params.waitToolDefaultTimeoutMs,
|
|
1219
1264
|
worktreeBaseDir,
|
|
1265
|
+
worktreeProvider,
|
|
1266
|
+
worktreeBranchPrefix,
|
|
1220
1267
|
asyncDir,
|
|
1221
1268
|
fast: params.fast,
|
|
1222
1269
|
toolBudget: params.toolBudget,
|
|
@@ -1292,6 +1339,8 @@ export function executeAsyncChain(
|
|
|
1292
1339
|
worktreeSetupHook,
|
|
1293
1340
|
worktreeSetupHookTimeoutMs,
|
|
1294
1341
|
worktreeBaseDir,
|
|
1342
|
+
worktreeProvider,
|
|
1343
|
+
worktreeBranchPrefix,
|
|
1295
1344
|
controlConfig,
|
|
1296
1345
|
toolBudget: params.toolBudget,
|
|
1297
1346
|
usageBudget: params.usageBudget,
|
|
@@ -1332,6 +1381,7 @@ export function executeAsyncChain(
|
|
|
1332
1381
|
},
|
|
1333
1382
|
path.join(asyncDir, "status.json"),
|
|
1334
1383
|
(proof) => emitProcessTerminalEvent(ctx, proof),
|
|
1384
|
+
(runnerProcessInstanceId) => params.activeAsyncCapacity?.markStarted(runnerProcessInstanceId),
|
|
1335
1385
|
);
|
|
1336
1386
|
} catch (error) {
|
|
1337
1387
|
params.activeAsyncCapacity?.rollback();
|
|
@@ -1340,7 +1390,10 @@ export function executeAsyncChain(
|
|
|
1340
1390
|
}
|
|
1341
1391
|
|
|
1342
1392
|
if (spawnResult.error) {
|
|
1343
|
-
if (
|
|
1393
|
+
if (spawnResult.startupDidNotProceed) {
|
|
1394
|
+
if (!spawnResult.runnerProcessInstanceId || params.activeAsyncCapacity?.rollbackBeforeRunnerProceed(spawnResult.runnerProcessInstanceId) !== true) params.activeAsyncCapacity?.rollback();
|
|
1395
|
+
}
|
|
1396
|
+
else if (!spawnResult.pid || !spawnResult.runnerProcessInstanceId) params.activeAsyncCapacity?.rollback();
|
|
1344
1397
|
else params.activeAsyncCapacity?.markStarted(spawnResult.runnerProcessInstanceId);
|
|
1345
1398
|
return formatAsyncStartError(resultMode, `Failed to start async ${resultMode} '${id}': ${spawnResult.error}`);
|
|
1346
1399
|
}
|
|
@@ -1348,8 +1401,6 @@ export function executeAsyncChain(
|
|
|
1348
1401
|
params.activeAsyncCapacity?.rollback();
|
|
1349
1402
|
return formatAsyncStartError(resultMode, `Failed to start async ${resultMode} '${id}': runner identity unavailable`);
|
|
1350
1403
|
}
|
|
1351
|
-
params.activeAsyncCapacity?.markStarted(spawnResult.runnerProcessInstanceId);
|
|
1352
|
-
|
|
1353
1404
|
if (spawnResult.pid) {
|
|
1354
1405
|
const eventFirstStep = eventChain[0];
|
|
1355
1406
|
if (!eventFirstStep) {
|
|
@@ -1487,6 +1538,8 @@ export function executeAsyncSingle(
|
|
|
1487
1538
|
worktreeSetupHook,
|
|
1488
1539
|
worktreeSetupHookTimeoutMs,
|
|
1489
1540
|
worktreeBaseDir,
|
|
1541
|
+
worktreeProvider,
|
|
1542
|
+
worktreeBranchPrefix,
|
|
1490
1543
|
controlConfig,
|
|
1491
1544
|
controlIntercomTarget,
|
|
1492
1545
|
childIntercomTarget,
|
|
@@ -1532,7 +1585,18 @@ export function executeAsyncSingle(
|
|
|
1532
1585
|
return formatAsyncStartError("single", error instanceof Error ? error.message : String(error));
|
|
1533
1586
|
}
|
|
1534
1587
|
const runnerCwd = resolveChildCwd(ctx.cwd, cwd);
|
|
1535
|
-
|
|
1588
|
+
let managedWorktreeProvider: "native" | "worktrunk" | undefined;
|
|
1589
|
+
if (params.worktree === true) {
|
|
1590
|
+
try {
|
|
1591
|
+
const resolved = resolveWorktreeProvider(params.worktreeProvider, worktreeBaseDir);
|
|
1592
|
+
managedWorktreeProvider = shouldDeferWorktreeCwd(params.worktreeProvider, worktreeBaseDir) ? "worktrunk" : resolved;
|
|
1593
|
+
} catch (error) {
|
|
1594
|
+
return formatAsyncStartError("single", error instanceof Error ? error.message : String(error));
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
const instructionCwd = params.worktree === true && managedWorktreeProvider === "worktrunk"
|
|
1598
|
+
? WORKTREE_AGENT_CWD_PLACEHOLDER
|
|
1599
|
+
: params.worktree === true && managedWorktreeProvider === "native"
|
|
1536
1600
|
? resolveExpectedWorktreeAgentCwd(runnerCwd, `${id}-s0`, 0, worktreeBaseDir)
|
|
1537
1601
|
: runnerCwd;
|
|
1538
1602
|
const readExistenceCwd = params.worktree === true ? runnerCwd : instructionCwd;
|
|
@@ -1586,7 +1650,11 @@ export function executeAsyncSingle(
|
|
|
1586
1650
|
// Reads: caller override > agent defaultReads > none. `~`/`~/` expand to home;
|
|
1587
1651
|
// absolute paths pass through; relative paths resolve against the child cwd.
|
|
1588
1652
|
const reads = params.reads !== undefined ? params.reads : agentConfig.defaultReads ?? false;
|
|
1589
|
-
const readPaths = Array.isArray(reads)
|
|
1653
|
+
const readPaths = Array.isArray(reads)
|
|
1654
|
+
? managedWorktreeProvider === "worktrunk"
|
|
1655
|
+
? resolveExistingReadInstructionPaths(reads, instructionCwd, readExistenceCwd)
|
|
1656
|
+
: resolveExistingReadPaths(reads, readExistenceCwd)
|
|
1657
|
+
: [];
|
|
1590
1658
|
const readsInstruction = readPaths.length > 0
|
|
1591
1659
|
? `[Read from: ${readPaths.join(", ")}]\n\n`
|
|
1592
1660
|
: "";
|
|
@@ -1647,7 +1715,7 @@ export function executeAsyncSingle(
|
|
|
1647
1715
|
const initialUsageBudget = usageBudgetState(params.usageBudget, undefined);
|
|
1648
1716
|
const resolvedSessionDir = params.sessionDir ?? (sessionRoot ? path.join(sessionRoot, `async-${id}`) : undefined);
|
|
1649
1717
|
const structuredOutput = params.structuredOutputSchema
|
|
1650
|
-
? createStructuredOutputRuntime(params.structuredOutputSchema, path.join(asyncDir, "structured-output"), {
|
|
1718
|
+
? createStructuredOutputRuntime(params.structuredOutputSchema, path.join(asyncDir, "structured-output"), { acceptanceReport: resolveAcceptanceReportMode(params.acceptance) })
|
|
1651
1719
|
: undefined;
|
|
1652
1720
|
let modelCandidates: string[] = [];
|
|
1653
1721
|
if (!externalRunner) {
|
|
@@ -1667,6 +1735,7 @@ export function executeAsyncSingle(
|
|
|
1667
1735
|
}
|
|
1668
1736
|
const toolPlan = resolvePiLaunchToolPlan({
|
|
1669
1737
|
tools: agentConfig.tools,
|
|
1738
|
+
excludeTools: agentConfig.excludeTools,
|
|
1670
1739
|
allowNestedSubagents: agentConfig.allowNestedSubagents,
|
|
1671
1740
|
extensions: agentConfig.extensions,
|
|
1672
1741
|
subagentOnlyExtensions: agentConfig.subagentOnlyExtensions,
|
|
@@ -1713,6 +1782,7 @@ export function executeAsyncSingle(
|
|
|
1713
1782
|
inheritSkills: agentConfig.inheritSkills,
|
|
1714
1783
|
skills: resolvedSkills.map((skill) => skill.name),
|
|
1715
1784
|
tools: toolPlan.effectiveToolAllowlist,
|
|
1785
|
+
...(toolPlan.excludeTools.length > 0 ? { excludeTools: toolPlan.excludeTools } : {}),
|
|
1716
1786
|
extensions: toolPlan.extensionArgs,
|
|
1717
1787
|
mcpDirectTools: toolPlan.effectiveMcpTools,
|
|
1718
1788
|
...(outputPath ? { outputPath } : {}),
|
|
@@ -1751,6 +1821,7 @@ export function executeAsyncSingle(
|
|
|
1751
1821
|
...(effectiveThinking ? { thinking: resolveEffectiveThinking(model, effectiveThinking) } : {}),
|
|
1752
1822
|
...(thinkingCeiling ? { thinkingCeiling } : {}),
|
|
1753
1823
|
...(recoveryAgentConfig.tools ? { tools: [...recoveryAgentConfig.tools] } : {}),
|
|
1824
|
+
...(recoveryAgentConfig.excludeTools ? { excludeTools: [...recoveryAgentConfig.excludeTools] } : {}),
|
|
1754
1825
|
...(recoveryAgentConfig.allowNestedSubagents !== undefined ? { allowNestedSubagents: recoveryAgentConfig.allowNestedSubagents } : {}),
|
|
1755
1826
|
...(recoveryAgentConfig.extensions ? { extensions: [...recoveryAgentConfig.extensions] } : {}),
|
|
1756
1827
|
...(recoveryAgentConfig.subagentOnlyExtensions ? { subagentOnlyExtensions: [...recoveryAgentConfig.subagentOnlyExtensions] } : {}),
|
|
@@ -1818,6 +1889,7 @@ export function executeAsyncSingle(
|
|
|
1818
1889
|
...(modelOrigin === "inherited" ? { skipPrimaryModelVerification: true } : {}),
|
|
1819
1890
|
...(availableModels && availableModels.length > 0 ? { modelVerificationRegistry: availableModels } : {}),
|
|
1820
1891
|
tools: agentConfig.tools,
|
|
1892
|
+
excludeTools: agentConfig.excludeTools,
|
|
1821
1893
|
allowNestedSubagents: agentConfig.allowNestedSubagents,
|
|
1822
1894
|
extensions: agentConfig.extensions,
|
|
1823
1895
|
subagentOnlyExtensions: agentConfig.subagentOnlyExtensions,
|
|
@@ -1846,6 +1918,7 @@ export function executeAsyncSingle(
|
|
|
1846
1918
|
...(extensionBindings ? { extensionBindings } : {}),
|
|
1847
1919
|
launchResolvedExtensions,
|
|
1848
1920
|
effectiveAcceptance: resolvedAcceptance,
|
|
1921
|
+
acceptanceInput: params.acceptance,
|
|
1849
1922
|
...(structuredOutput ? { structuredOutput } : {}),
|
|
1850
1923
|
...(params.structuredOutputSchema ? { structuredOutputSchema: params.structuredOutputSchema } : {}),
|
|
1851
1924
|
...(resolvedToolBudget.budget ? { toolBudget: resolvedToolBudget.budget } : {}),
|
|
@@ -1872,6 +1945,8 @@ export function executeAsyncSingle(
|
|
|
1872
1945
|
worktreeSetupHook,
|
|
1873
1946
|
worktreeSetupHookTimeoutMs,
|
|
1874
1947
|
worktreeBaseDir,
|
|
1948
|
+
worktreeProvider,
|
|
1949
|
+
worktreeBranchPrefix,
|
|
1875
1950
|
controlConfig,
|
|
1876
1951
|
timeoutMs,
|
|
1877
1952
|
deadlineAt,
|
|
@@ -1914,6 +1989,7 @@ export function executeAsyncSingle(
|
|
|
1914
1989
|
},
|
|
1915
1990
|
path.join(asyncDir, "status.json"),
|
|
1916
1991
|
(proof) => emitProcessTerminalEvent(ctx, proof),
|
|
1992
|
+
(runnerProcessInstanceId) => params.activeAsyncCapacity?.markStarted(runnerProcessInstanceId),
|
|
1917
1993
|
params.requestedCwd ?? runnerCwd,
|
|
1918
1994
|
);
|
|
1919
1995
|
} catch (error) {
|
|
@@ -1923,7 +1999,10 @@ export function executeAsyncSingle(
|
|
|
1923
1999
|
}
|
|
1924
2000
|
|
|
1925
2001
|
if (spawnResult.error) {
|
|
1926
|
-
if (
|
|
2002
|
+
if (spawnResult.startupDidNotProceed) {
|
|
2003
|
+
if (!spawnResult.runnerProcessInstanceId || params.activeAsyncCapacity?.rollbackBeforeRunnerProceed(spawnResult.runnerProcessInstanceId) !== true) params.activeAsyncCapacity?.rollback();
|
|
2004
|
+
}
|
|
2005
|
+
else if (!spawnResult.pid || !spawnResult.runnerProcessInstanceId) params.activeAsyncCapacity?.rollback();
|
|
1927
2006
|
else params.activeAsyncCapacity?.markStarted(spawnResult.runnerProcessInstanceId);
|
|
1928
2007
|
return formatAsyncStartError("single", `Failed to start async run '${id}': ${spawnResult.error}`);
|
|
1929
2008
|
}
|
|
@@ -1931,8 +2010,6 @@ export function executeAsyncSingle(
|
|
|
1931
2010
|
params.activeAsyncCapacity?.rollback();
|
|
1932
2011
|
return formatAsyncStartError("single", `Failed to start async run '${id}': runner identity unavailable`);
|
|
1933
2012
|
}
|
|
1934
|
-
params.activeAsyncCapacity?.markStarted(spawnResult.runnerProcessInstanceId);
|
|
1935
|
-
|
|
1936
2013
|
if (spawnResult.pid) {
|
|
1937
2014
|
if (inheritedNestedRoute && nestedAddress) {
|
|
1938
2015
|
const now = Date.now();
|
|
@@ -45,6 +45,8 @@ export type AsyncResumeTarget = {
|
|
|
45
45
|
sessionName?: string;
|
|
46
46
|
index: number;
|
|
47
47
|
cwd?: string;
|
|
48
|
+
/** True when cwd is the retained managed worktree recorded by the handoff. */
|
|
49
|
+
managedWorktree?: boolean;
|
|
48
50
|
sessionFile?: string;
|
|
49
51
|
model?: string;
|
|
50
52
|
thinking?: string;
|
|
@@ -315,7 +317,7 @@ export function readAsyncRecoveryDescriptor(asyncDir: string | undefined): Steer
|
|
|
315
317
|
const parsed = value as Record<string, unknown>;
|
|
316
318
|
const allowedFields = new Set([
|
|
317
319
|
"version", "launchContractDigest", "sourceRunId", "agentContract", "agent", "sessionFile", "cwd", "model", "modelProvider", "modelOverrideFromParent", "modelOrigin", "fallbackModels", "thinking", "thinkingCeiling", "tools", "allowNestedSubagents", "extensions",
|
|
318
|
-
"subagentOnlyExtensions", "mcpDirectTools", "mutationTools", "systemPrompt", "systemPromptMode", "inheritProjectContext", "inheritGlobalContext", "inheritSkills", "skills",
|
|
320
|
+
"subagentOnlyExtensions", "mcpDirectTools", "excludeTools", "mutationTools", "systemPrompt", "systemPromptMode", "inheritProjectContext", "inheritGlobalContext", "inheritSkills", "skills",
|
|
319
321
|
"skillPath", "agentFilePath", "completionGuard", "memory", "outputPath", "outputMode", "structuredOutputSchema", "acceptance", "sessionDir", "artifactConfig",
|
|
320
322
|
"artifactsDir", "maxOutput", "controlConfig", "context", "intercomBridge", "absoluteDeadlineAt", "initialTurnBudget", "initialToolBudget", "maxSubagentDepth", "share", "capabilityCeiling",
|
|
321
323
|
"launchResolvedExtensions", "runFanoutBudget", "lane",
|
|
@@ -356,7 +358,7 @@ export function readAsyncRecoveryDescriptor(asyncDir: string | undefined): Steer
|
|
|
356
358
|
else if (typeof parsed.inheritGlobalContext !== "boolean") throw new Error(`Invalid async recovery descriptor '${descriptorPath}': inheritGlobalContext must be a boolean.`);
|
|
357
359
|
if (parsed.allowNestedSubagents !== undefined && typeof parsed.allowNestedSubagents !== "boolean") throw new Error(`Invalid async recovery descriptor '${descriptorPath}': allowNestedSubagents must be a boolean.`);
|
|
358
360
|
if (!Number.isInteger(parsed.maxSubagentDepth) || (parsed.maxSubagentDepth as number) < 0) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': maxSubagentDepth must be a non-negative integer.`);
|
|
359
|
-
for (const field of ["fallbackModels", "tools", "extensions", "subagentOnlyExtensions", "mcpDirectTools", "mutationTools", "skills", "skillPath"] as const) {
|
|
361
|
+
for (const field of ["fallbackModels", "tools", "excludeTools", "extensions", "subagentOnlyExtensions", "mcpDirectTools", "mutationTools", "skills", "skillPath"] as const) {
|
|
360
362
|
const item = parsed[field];
|
|
361
363
|
if (item !== undefined && (!Array.isArray(item) || item.some((entry) => typeof entry !== "string" || !entry.trim()))) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': ${field} must contain non-empty strings.`);
|
|
362
364
|
}
|
|
@@ -569,6 +571,7 @@ export function resolveAsyncResumeTarget(params: AsyncResumeParams, deps: AsyncR
|
|
|
569
571
|
...(statusSteps[index]?.sessionName ?? resultSteps[index]?.sessionName ? { sessionName: statusSteps[index]?.sessionName ?? resultSteps[index]?.sessionName } : {}),
|
|
570
572
|
index,
|
|
571
573
|
...(resumeCwd ? { cwd: resumeCwd } : {}),
|
|
574
|
+
...(managedWorktreeCwd ? { managedWorktree: true } : {}),
|
|
572
575
|
...(resolvedSessionFile ? { sessionFile: resolvedSessionFile } : {}),
|
|
573
576
|
...(stepModel ? { model: stepModel } : {}),
|
|
574
577
|
...(stepThinking ? { thinking: stepThinking } : {}),
|
|
@@ -590,6 +593,7 @@ export function applySteeringRecoveryAgentConfig(agentConfig: AgentConfig, descr
|
|
|
590
593
|
thinking: descriptor.thinking,
|
|
591
594
|
maxThinking: intersectThinkingCeilings(descriptor.thinkingCeiling, agentConfig.maxThinking),
|
|
592
595
|
tools: descriptor.tools ? [...descriptor.tools] : undefined,
|
|
596
|
+
excludeTools: descriptor.excludeTools ? [...descriptor.excludeTools] : undefined,
|
|
593
597
|
allowNestedSubagents: descriptor.allowNestedSubagents,
|
|
594
598
|
extensions: descriptor.extensions ? [...descriptor.extensions] : undefined,
|
|
595
599
|
subagentOnlyExtensions: descriptor.subagentOnlyExtensions ? [...descriptor.subagentOnlyExtensions] : undefined,
|
|
@@ -3,7 +3,7 @@ import * as path from "node:path";
|
|
|
3
3
|
import { formatDuration, formatModelThinking, formatTokens, shortenPath } from "../../shared/formatters.ts";
|
|
4
4
|
import { previewDisplayText } from "../../shared/display-text.ts";
|
|
5
5
|
import { formatActivityLabel, formatParallelOutcome } from "../../shared/status-format.ts";
|
|
6
|
-
import { type ActivityState, type AsyncJobStep, type AsyncParallelGroupStatus, type AsyncStatus, type CostSummary, type Details, type HostStepNodeV1, type HostStepState, type LaunchResolvedChildExtensionsV1, type RuntimeAcknowledgedChildExtensionsV1, type NestedRunSummary, type SteeringStatus, type SubagentRunMode, type TimeoutRecoveryProjection, type TokenUsage, type TurnBudgetState, type UsageBudgetState, type WorkflowPreflightV1, type WorkflowGraphSnapshot } from "../../shared/types.ts";
|
|
6
|
+
import { type ActivityState, type AsyncJobStep, type AsyncParallelGroupStatus, type AsyncStatus, type CostSummary, type Details, type HostStepNodeV1, type HostStepState, type LaunchResolvedChildExtensionsV1, type RuntimeAcknowledgedChildExtensionsV1, type NestedRunSummary, type SteeringStatus, type SubagentRunMode, type TimeoutRecoveryProjection, type TokenUsage, type TurnBudgetState, type UsageBudgetState, type WorktreeNaming, type WorkflowPreflightV1, type WorkflowGraphSnapshot } from "../../shared/types.ts";
|
|
7
7
|
import type { ResolvedSubagentCapabilityCeiling, SubagentCapabilityAudit } from "../shared/capability-ceiling.ts";
|
|
8
8
|
import { readStatus } from "../../shared/utils.ts";
|
|
9
9
|
import { attachRootChildrenToSteps, buildNestedRouteIndex, findNestedRouteForRootId, type NestedRoute, projectNestedEvents } from "../shared/nested-events.ts";
|
|
@@ -24,6 +24,7 @@ import { validateAsyncStatusLaneMetadata } from "../shared/lane-metadata.ts";
|
|
|
24
24
|
import { formatWorkflowPreflightPlanSummary, formatWorkflowPreflightWarningSummary } from "../../workflows/workflow-preflight.ts";
|
|
25
25
|
import { workflowGraphStageNodes } from "../shared/workflow-graph.ts";
|
|
26
26
|
import { formatTimeoutRecoveryLines, projectTimeoutRecovery } from "../shared/mutation-evidence.ts";
|
|
27
|
+
import { formatWorkflowChecklistText, projectWorkflowChecklist } from "../../workflows/workflow-checklist.ts";
|
|
27
28
|
|
|
28
29
|
interface AsyncRunStepSummary {
|
|
29
30
|
index: number;
|
|
@@ -39,6 +40,8 @@ interface AsyncRunStepSummary {
|
|
|
39
40
|
lane?: AsyncJobStep["lane"];
|
|
40
41
|
worktreePath?: string;
|
|
41
42
|
branch?: string;
|
|
43
|
+
provider?: "native" | "worktrunk";
|
|
44
|
+
naming?: WorktreeNaming;
|
|
42
45
|
runId?: string;
|
|
43
46
|
outputName?: string;
|
|
44
47
|
structured?: boolean;
|
|
@@ -337,6 +340,8 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
|
|
|
337
340
|
...(step.lane ? { lane: step.lane } : {}),
|
|
338
341
|
...(step.worktreePath ? { worktreePath: step.worktreePath } : {}),
|
|
339
342
|
...(step.branch ? { branch: step.branch } : {}),
|
|
343
|
+
...(step.provider ? { provider: step.provider } : {}),
|
|
344
|
+
...(step.naming ? { naming: step.naming } : {}),
|
|
340
345
|
...(step.runId ? { runId: step.runId } : {}),
|
|
341
346
|
...(step.outputName ? { outputName: step.outputName } : {}),
|
|
342
347
|
...(step.structured ? { structured: step.structured } : {}),
|
|
@@ -614,7 +619,7 @@ function formatStepLine(step: AsyncRunStepSummary): string {
|
|
|
614
619
|
if (step.durationMs !== undefined) parts.push(formatDuration(step.durationMs));
|
|
615
620
|
if (step.tokens) parts.push(`${formatTokens(step.tokens.total)} tok`);
|
|
616
621
|
if (step.lane) parts.push(`lane ${step.lane.key}`);
|
|
617
|
-
if (step.worktreePath) parts.push(`worktree ${shortenPath(step.worktreePath)} · branch ${step.branch ?? "unknown"}`);
|
|
622
|
+
if (step.worktreePath) parts.push(`worktree ${shortenPath(step.worktreePath)} · branch ${step.branch ?? "unknown"}${step.provider ? ` · provider ${step.provider}` : ""}`);
|
|
618
623
|
return parts.join(" | ");
|
|
619
624
|
}
|
|
620
625
|
|
|
@@ -707,6 +712,17 @@ export function formatAsyncRunList(runs: AsyncRunSummary[], heading = "Active as
|
|
|
707
712
|
if (run.preflight) lines.push(formatWorkflowPreflightPlanSummary(run.preflight, { indent: " " }));
|
|
708
713
|
const preflightWarning = formatWorkflowPreflightWarningSummary(run.workflow?.preflightWarnings, { indent: " " });
|
|
709
714
|
if (preflightWarning) lines.push(preflightWarning);
|
|
715
|
+
if (run.mode === "workflow") {
|
|
716
|
+
const checklist = projectWorkflowChecklist({
|
|
717
|
+
graph: run.workflowGraph,
|
|
718
|
+
steps: run.steps,
|
|
719
|
+
hostSteps: run.hostSteps,
|
|
720
|
+
preflight: run.preflight,
|
|
721
|
+
trace: run.workflow?.trace,
|
|
722
|
+
now: run.lastUpdate ?? run.endedAt ?? Date.now(),
|
|
723
|
+
});
|
|
724
|
+
lines.push(...formatWorkflowChecklistText(checklist, " ", { includeItems: false }));
|
|
725
|
+
}
|
|
710
726
|
for (const step of run.steps) {
|
|
711
727
|
lines.push(` ${formatStepLine(step)}`);
|
|
712
728
|
lines.push(...formatTimeoutRecoveryLines(step.timeoutRecovery, " "));
|