pi-subagents 0.56.0 → 0.58.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 +92 -0
- package/agents/claude-code-writer.md +15 -0
- package/agents/claude-code.md +15 -0
- package/agents/codex-exec-writer.md +15 -0
- package/agents/codex-exec.md +15 -0
- package/agents/cursor-agent-writer.md +14 -0
- package/agents/cursor-agent.md +14 -0
- package/docs/agents.md +124 -21
- package/docs/configuration.md +37 -0
- package/docs/extension-api.md +41 -2
- package/docs/models.md +3 -3
- package/docs/observability.md +8 -7
- package/docs/tool-reference.md +14 -3
- package/docs/workflows.md +44 -0
- package/package.json +1 -1
- package/skills/pi-subagents/SKILL.md +2 -0
- package/skills/pi-subagents/references/execution-controls.md +2 -2
- package/skills/pi-subagents/references/management-authoring-rpc.md +1 -0
- package/skills/pi-subagents/references/prompting-and-roles.md +2 -2
- package/src/agents/agent-management.ts +36 -5
- package/src/agents/agent-refinements.ts +4 -4
- package/src/agents/agent-serializer.ts +5 -0
- package/src/agents/agents.ts +257 -51
- package/src/agents/builtin-names.ts +6 -0
- package/src/agents/runtime-agent-events.ts +70 -0
- package/src/agents/runtime-agent-registry.ts +18 -4
- package/src/api/agents.ts +10 -5
- package/src/api/preflight.ts +28 -3
- package/src/extension/config.ts +70 -0
- package/src/extension/doctor.ts +3 -3
- package/src/extension/index.ts +33 -8
- package/src/extension/public-execution.ts +29 -13
- package/src/extension/rpc.ts +55 -19
- package/src/extension/schemas.ts +6 -5
- package/src/extension/tool-description.ts +18 -12
- package/src/inspectors/herdr/actions.ts +2 -1
- package/src/inspectors/herdr/inspector-runner.ts +2 -10
- package/src/inspectors/herdr/session-roots-codec.ts +42 -0
- package/src/integrations/herdr-status.ts +51 -3
- package/src/runs/background/active-async-capacity.ts +77 -10
- package/src/runs/background/async-execution.ts +127 -19
- package/src/runs/background/async-job-tracker.ts +5 -0
- package/src/runs/background/async-resume.ts +6 -2
- package/src/runs/background/async-retention.ts +20 -3
- package/src/runs/background/async-status.ts +7 -0
- package/src/runs/background/chain-append.ts +2 -0
- package/src/runs/background/chain-root-attachment.ts +15 -1
- package/src/runs/background/fleet-view.ts +16 -10
- package/src/runs/background/inspect-rpc.ts +8 -8
- package/src/runs/background/notify.ts +26 -3
- package/src/runs/background/result-delivery-ownership.ts +45 -0
- package/src/runs/background/result-files.ts +27 -14
- package/src/runs/background/result-watcher.ts +36 -15
- package/src/runs/background/run-status.ts +33 -6
- package/src/runs/background/scheduled-runs.ts +7 -1
- package/src/runs/background/subagent-runner.ts +239 -56
- package/src/runs/background/wait-completions.ts +4 -0
- package/src/runs/foreground/execution.ts +92 -11
- package/src/runs/foreground/foreground-control.ts +6 -0
- package/src/runs/foreground/foreground-history.ts +22 -1
- package/src/runs/foreground/subagent-executor.ts +322 -102
- package/src/runs/foreground/workflow-detach-reconcile.ts +144 -18
- package/src/runs/shared/child-protocol.ts +21 -7
- package/src/runs/shared/claude-code-adapter.ts +129 -0
- package/src/runs/shared/codex-exec-adapter.ts +129 -0
- package/src/runs/shared/completion-guard.ts +4 -3
- package/src/runs/shared/cursor-agent-adapter.ts +114 -0
- package/src/runs/shared/dynamic-fanout.ts +3 -3
- package/src/runs/shared/external-cli-contract.ts +167 -0
- package/src/runs/shared/external-cli-preflight.ts +122 -0
- package/src/runs/shared/external-cli-runner.ts +348 -55
- package/src/runs/shared/fast-mode-extension.ts +5 -5
- package/src/runs/shared/launch-cwd.ts +16 -0
- package/src/runs/shared/long-running-guard.ts +2 -1
- package/src/runs/shared/mcp-config-sources.ts +386 -0
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +155 -42
- package/src/runs/shared/model-exclusions.ts +69 -7
- package/src/runs/shared/model-fallback.ts +39 -5
- package/src/runs/shared/mutation-evidence.ts +7 -2
- package/src/runs/shared/nested-events.ts +3 -1
- package/src/runs/shared/nested-render.ts +2 -2
- package/src/runs/shared/parallel-utils.ts +8 -1
- package/src/runs/shared/pi-args.ts +61 -6
- package/src/runs/shared/process-signal.ts +13 -0
- package/src/runs/shared/run-history.ts +21 -1
- package/src/runs/shared/single-output.ts +17 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +85 -9
- package/src/shared/fork-context.ts +21 -0
- package/src/shared/formatters.ts +13 -1
- package/src/shared/launch-contract.ts +4 -0
- package/src/shared/pruned-fork.ts +450 -0
- package/src/shared/session-file-trust.ts +19 -0
- package/src/shared/session-tokens.ts +14 -3
- package/src/shared/settings.ts +10 -2
- package/src/shared/shortcuts.ts +17 -0
- package/src/shared/types.ts +160 -10
- package/src/shared/utils.ts +6 -29
- package/src/shared/workflow-child-permit.ts +116 -0
- package/src/slash/delegation-adapters.ts +0 -1
- package/src/slash/slash-commands.ts +8 -6
- package/src/slash/subagents-admin.ts +3 -0
- package/src/tui/fleet-status.ts +27 -10
- package/src/tui/fleet-transcript.ts +11 -5
- package/src/tui/fleet.ts +28 -13
- package/src/tui/render.ts +55 -21
- package/src/workflows/scripted-workflow.ts +299 -31
- package/src/workflows/workflow-child-summary.ts +117 -0
- package/src/workflows/workflow-receipt.ts +155 -5
|
@@ -4,9 +4,13 @@ import * as path from "node:path";
|
|
|
4
4
|
import { writePrivateAtomicJson } from "../../shared/atomic-json.ts";
|
|
5
5
|
import { TEMP_ROOT_DIR, type ActiveAsyncCapacitySnapshot, type AsyncStatus } from "../../shared/types.ts";
|
|
6
6
|
import { readStatus } from "../../shared/utils.ts";
|
|
7
|
+
import { checkPidLiveness, type PidLiveness } from "./stale-run-reconciler.ts";
|
|
7
8
|
import { readProcessTerminal } from "./process-terminal.ts";
|
|
8
9
|
|
|
9
10
|
export const ACTIVE_ASYNC_CAPACITY_DIR = path.join(TEMP_ROOT_DIR, "session-active-async-capacity");
|
|
11
|
+
export const DEFAULT_ABANDONED_SLOT_RELEASE_AFTER_MS = 20 * 60 * 1000;
|
|
12
|
+
export const MIN_ABANDONED_SLOT_RELEASE_AFTER_MS = 5 * 60 * 1000;
|
|
13
|
+
export const MAX_ABANDONED_SLOT_RELEASE_AFTER_MS = 24 * 60 * 60 * 1000;
|
|
10
14
|
|
|
11
15
|
export interface ActiveAsyncCapacityOwnerV1 {
|
|
12
16
|
version: 1;
|
|
@@ -36,11 +40,21 @@ interface CapacityOptions {
|
|
|
36
40
|
rootDir?: string;
|
|
37
41
|
now?: () => number;
|
|
38
42
|
token?: () => string;
|
|
43
|
+
abandonedSlotReleaseAfterMs?: number | false;
|
|
44
|
+
pidLiveness?: (pid: number) => PidLiveness;
|
|
39
45
|
afterSlotRename?: (releasedDir: string) => void;
|
|
40
46
|
}
|
|
41
47
|
|
|
48
|
+
export interface ActiveAsyncCapacityReleaseEvidence {
|
|
49
|
+
releasedBy: "abandoned-timeout";
|
|
50
|
+
processProof: "unknown";
|
|
51
|
+
runnerPid: "gone";
|
|
52
|
+
lastActivityAgeMs: number;
|
|
53
|
+
abandonedSlotReleaseAfterMs: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
42
56
|
export type ActiveAsyncCapacityReleaseVerdict =
|
|
43
|
-
| { state: "releasable"; reason: string }
|
|
57
|
+
| { state: "releasable"; reason: string; evidence?: ActiveAsyncCapacityReleaseEvidence }
|
|
44
58
|
| { state: "retained"; reason: string }
|
|
45
59
|
| { state: "not-owned"; reason: string };
|
|
46
60
|
|
|
@@ -66,6 +80,12 @@ export function resolveMaxActiveAsyncRunsPerSession(value: unknown): number | un
|
|
|
66
80
|
return value === 0 ? undefined : value;
|
|
67
81
|
}
|
|
68
82
|
|
|
83
|
+
export function resolveAbandonedSlotReleaseAfterMs(value: unknown): number | false {
|
|
84
|
+
if (value === false) return false;
|
|
85
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 1) return DEFAULT_ABANDONED_SLOT_RELEASE_AFTER_MS;
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
88
|
+
|
|
69
89
|
export function activeAsyncCapacitySessionKey(sessionId: string): string {
|
|
70
90
|
return createHash("sha256").update(sessionId).digest("hex");
|
|
71
91
|
}
|
|
@@ -154,7 +174,7 @@ function withSlotClaim<T>(dir: string, operation: () => T): { acquired: true; va
|
|
|
154
174
|
}
|
|
155
175
|
}
|
|
156
176
|
|
|
157
|
-
function removeOwnedSlot(dir: string, expected: ActiveAsyncCapacityOwnerV1, options: CapacityOptions, requireUnstarted = false): boolean {
|
|
177
|
+
function removeOwnedSlot(dir: string, expected: ActiveAsyncCapacityOwnerV1, options: CapacityOptions, requireUnstarted = false, release?: ActiveAsyncCapacityReleaseVerdict): boolean {
|
|
158
178
|
if (requireUnstarted && (expected.runnerProcessInstanceId || expected.runnerStartedAt)) return false;
|
|
159
179
|
const claimed = withSlotClaim(dir, () => {
|
|
160
180
|
const current = matchingOwner(dir, expected);
|
|
@@ -162,17 +182,36 @@ function removeOwnedSlot(dir: string, expected: ActiveAsyncCapacityOwnerV1, opti
|
|
|
162
182
|
const releasedDir = path.join(path.dirname(dir), `.${path.basename(dir)}.released-${randomUUID()}`);
|
|
163
183
|
fs.renameSync(dir, releasedDir);
|
|
164
184
|
options.afterSlotRename?.(releasedDir);
|
|
185
|
+
if (release?.state === "releasable" && release.evidence) {
|
|
186
|
+
appendAbandonedReleaseEvent(expected.asyncDir, expected, release.evidence, options.now?.() ?? Date.now());
|
|
187
|
+
}
|
|
165
188
|
fs.rmSync(releasedDir, { recursive: true, force: true });
|
|
166
189
|
return true;
|
|
167
190
|
});
|
|
168
191
|
return claimed.acquired && claimed.value;
|
|
169
192
|
}
|
|
170
193
|
|
|
194
|
+
function appendAbandonedReleaseEvent(asyncDir: string, owner: ActiveAsyncCapacityOwnerV1, evidence: ActiveAsyncCapacityReleaseEvidence, now: number): void {
|
|
195
|
+
try {
|
|
196
|
+
const eventsPath = path.join(asyncDir, "events.jsonl");
|
|
197
|
+
fs.mkdirSync(path.dirname(eventsPath), { recursive: true });
|
|
198
|
+
fs.appendFileSync(eventsPath, `${JSON.stringify({
|
|
199
|
+
type: "subagent.capacity.released",
|
|
200
|
+
ts: now,
|
|
201
|
+
runId: owner.runId,
|
|
202
|
+
sessionId: owner.ownerSessionId,
|
|
203
|
+
...evidence,
|
|
204
|
+
})}\n`, "utf-8");
|
|
205
|
+
} catch {
|
|
206
|
+
// Capacity release must not fail because its diagnostic event cannot be written.
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
171
210
|
function terminalState(state: AsyncStatus["state"]): boolean {
|
|
172
211
|
return state !== "queued" && state !== "running" && state !== "paused";
|
|
173
212
|
}
|
|
174
213
|
|
|
175
|
-
function runnerReleaseVerdict(owner: ActiveAsyncCapacityOwnerV1, status: AsyncStatus | null): ActiveAsyncCapacityReleaseVerdict {
|
|
214
|
+
function runnerReleaseVerdict(owner: ActiveAsyncCapacityOwnerV1, status: AsyncStatus | null, options: CapacityOptions): ActiveAsyncCapacityReleaseVerdict {
|
|
176
215
|
if (!status) return { state: "retained", reason: "status file is missing or unreadable" };
|
|
177
216
|
if (!owner.runnerProcessInstanceId) return { state: "retained", reason: "runner process identity has not been recorded" };
|
|
178
217
|
if (status.sessionId !== owner.ownerSessionId) return { state: "retained", reason: `status session ${status.sessionId ?? "unknown"} does not match owner session ${owner.ownerSessionId}` };
|
|
@@ -191,7 +230,34 @@ function runnerReleaseVerdict(owner: ActiveAsyncCapacityOwnerV1, status: AsyncSt
|
|
|
191
230
|
&& proof.runId === owner.runId
|
|
192
231
|
&& proof.runnerProcessInstanceId === owner.runnerProcessInstanceId
|
|
193
232
|
? { state: "releasable", reason: "matching observed process-terminal proof is present" }
|
|
194
|
-
:
|
|
233
|
+
: abandonedRunnerReleaseVerdict(owner, status, proof?.state ?? "missing", options);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function abandonedRunnerReleaseVerdict(owner: ActiveAsyncCapacityOwnerV1, status: AsyncStatus, proofState: string, options: CapacityOptions): ActiveAsyncCapacityReleaseVerdict {
|
|
237
|
+
const proofReason = `process-terminal proof is ${proofState}`;
|
|
238
|
+
const thresholdMs = resolveAbandonedSlotReleaseAfterMs(options.abandonedSlotReleaseAfterMs);
|
|
239
|
+
if (thresholdMs === false) return { state: "retained", reason: `${proofReason}; abandoned-timeout policy is disabled` };
|
|
240
|
+
if (status.state !== "failed") return { state: "retained", reason: `${proofReason}; abandoned-timeout policy requires a failed run, not ${status.state}` };
|
|
241
|
+
if (typeof status.pid !== "number" || !Number.isInteger(status.pid) || status.pid < 1) return { state: "retained", reason: `${proofReason}; runner PID is missing or invalid` };
|
|
242
|
+
const liveness = (options.pidLiveness ?? checkPidLiveness)(status.pid);
|
|
243
|
+
if (liveness !== "dead") return { state: "retained", reason: `${proofReason}; runner PID liveness is ${liveness}` };
|
|
244
|
+
const lastActivityAt = status.lastActivityAt ?? status.lastUpdate ?? status.endedAt;
|
|
245
|
+
if (typeof lastActivityAt !== "number" || !Number.isFinite(lastActivityAt)) return { state: "retained", reason: `${proofReason}; last activity timestamp is missing or invalid` };
|
|
246
|
+
const now = options.now?.() ?? Date.now();
|
|
247
|
+
const lastActivityAgeMs = Math.max(0, now - lastActivityAt);
|
|
248
|
+
if (lastActivityAgeMs <= thresholdMs) return { state: "retained", reason: `${proofReason}; last activity age ${lastActivityAgeMs}ms has not exceeded abandoned-timeout ${thresholdMs}ms` };
|
|
249
|
+
const evidence: ActiveAsyncCapacityReleaseEvidence = {
|
|
250
|
+
releasedBy: "abandoned-timeout",
|
|
251
|
+
processProof: "unknown",
|
|
252
|
+
runnerPid: "gone",
|
|
253
|
+
lastActivityAgeMs,
|
|
254
|
+
abandonedSlotReleaseAfterMs: thresholdMs,
|
|
255
|
+
};
|
|
256
|
+
return {
|
|
257
|
+
state: "releasable",
|
|
258
|
+
reason: `${evidence.releasedBy}: ${proofReason}; runner PID is gone; process proof unknown; last activity age ${lastActivityAgeMs}ms exceeds ${thresholdMs}ms`,
|
|
259
|
+
evidence,
|
|
260
|
+
};
|
|
195
261
|
}
|
|
196
262
|
|
|
197
263
|
function workflowReleaseVerdict(owner: ActiveAsyncCapacityOwnerV1, status: AsyncStatus | null, liveWorkflowRunIds: ReadonlySet<string>): ActiveAsyncCapacityReleaseVerdict {
|
|
@@ -222,10 +288,10 @@ function workflowReleaseVerdict(owner: ActiveAsyncCapacityOwnerV1, status: Async
|
|
|
222
288
|
return { state: "releasable", reason: "workflow is terminal, controller is gone, and async children have observed proof" };
|
|
223
289
|
}
|
|
224
290
|
|
|
225
|
-
function ownerReleaseVerdict(owner: ActiveAsyncCapacityOwnerV1, liveWorkflowRunIds: ReadonlySet<string
|
|
291
|
+
function ownerReleaseVerdict(owner: ActiveAsyncCapacityOwnerV1, liveWorkflowRunIds: ReadonlySet<string>, options: CapacityOptions): ActiveAsyncCapacityReleaseVerdict {
|
|
226
292
|
const status = readStatus(owner.asyncDir);
|
|
227
293
|
return owner.kind === "runner"
|
|
228
|
-
? runnerReleaseVerdict(owner, status)
|
|
294
|
+
? runnerReleaseVerdict(owner, status, options)
|
|
229
295
|
: workflowReleaseVerdict(owner, status, liveWorkflowRunIds);
|
|
230
296
|
}
|
|
231
297
|
|
|
@@ -262,7 +328,7 @@ export function inspectActiveAsyncCapacityOwner(
|
|
|
262
328
|
release: { state: "not-owned", reason: `slot was transferred to ${owner.runId}` },
|
|
263
329
|
};
|
|
264
330
|
}
|
|
265
|
-
return { owner, relation: "current", slotDir: dir, release: ownerReleaseVerdict(owner, liveWorkflowRunIds) };
|
|
331
|
+
return { owner, relation: "current", slotDir: dir, release: ownerReleaseVerdict(owner, liveWorkflowRunIds, options) };
|
|
266
332
|
}
|
|
267
333
|
}
|
|
268
334
|
return { relation: "none", release: { state: "not-owned", reason: "no active-capacity slot records this run" } };
|
|
@@ -281,9 +347,10 @@ export function reconcileActiveAsyncCapacity(
|
|
|
281
347
|
if (!owner
|
|
282
348
|
|| owner.ownerSessionId !== sessionId
|
|
283
349
|
|| owner.ownerSessionKey !== activeAsyncCapacitySessionKey(sessionId)
|
|
284
|
-
|| path.basename(dir) !== `slot-${owner.slot}`
|
|
285
|
-
|
|
286
|
-
|
|
350
|
+
|| path.basename(dir) !== `slot-${owner.slot}`) continue;
|
|
351
|
+
const release = ownerReleaseVerdict(owner, liveWorkflowRunIds, options);
|
|
352
|
+
if (release.state !== "releasable") continue;
|
|
353
|
+
removeOwnedSlot(dir, owner, options, false, release);
|
|
287
354
|
}
|
|
288
355
|
return snapshotFor(sessionId, limit, rootDir);
|
|
289
356
|
}
|
|
@@ -10,7 +10,7 @@ import * as path from "node:path";
|
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
11
|
import { createRequire } from "node:module";
|
|
12
12
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
13
|
-
import type
|
|
13
|
+
import { discoverAgents, formatUnknownAgentError, unknownAgentDiagnosticContext, type AgentConfig, type UnknownAgentDiagnosticContext } from "../../agents/agents.ts";
|
|
14
14
|
import { appendAgentRefinementOverlay } from "../../agents/agent-refinements.ts";
|
|
15
15
|
import { writePrivateAtomicJson } from "../../shared/atomic-json.ts";
|
|
16
16
|
import { currentCompletionOwnerId } from "../../shared/completion-owner.ts";
|
|
@@ -20,6 +20,7 @@ import { buildChainInstructions, isDynamicParallelStep, isParallelStep, resolveC
|
|
|
20
20
|
import type { RunnerStep } from "../shared/parallel-utils.ts";
|
|
21
21
|
import type { ContextMode } from "../shared/context-mode.ts";
|
|
22
22
|
import { resolvePiPackageRoot } from "../shared/pi-spawn.ts";
|
|
23
|
+
import { preflightLaunchCwd } from "../shared/launch-cwd.ts";
|
|
23
24
|
import { resolveNodeExecutable } from "../../shared/node-executable.ts";
|
|
24
25
|
import { buildSkillInjection, normalizeSkillInput, resolveSkillsWithFallback } from "../../agents/skills.ts";
|
|
25
26
|
import { buildAgentMemoryInjection } from "../../agents/agent-memory.ts";
|
|
@@ -27,7 +28,7 @@ import { PI_CODING_AGENT_PACKAGE_ROOT_ENV, PROMPT_REDACTED, resolveChildCwd } fr
|
|
|
27
28
|
import { buildModelCandidates, inheritsParentModel, resolveEffectiveSubagentModel, resolveModelCandidate, resolveSubagentModelOverride, type AvailableModelInfo, type ParentModel } from "../shared/model-fallback.ts";
|
|
28
29
|
import { resolveToolTimeoutMs, toolTimeoutFromEnv } from "../shared/tool-timeout.ts";
|
|
29
30
|
import { resolveModelScopesForAgent, type ModelScopeConfig } from "../shared/model-scope.ts";
|
|
30
|
-
import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
31
|
+
import { findModelInfo, resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
31
32
|
import { assertThinkingWithinCeiling, decodeThinkingCeiling, intersectThinkingCeilings, SUBAGENT_THINKING_CEILING_ENV, type ThinkingLevel } from "../../shared/thinking-ceiling.ts";
|
|
32
33
|
import { resolveExpectedWorktreeAgentCwd } from "../shared/worktree.ts";
|
|
33
34
|
import { buildWorkflowGraphSnapshot } from "../shared/workflow-graph.ts";
|
|
@@ -39,6 +40,7 @@ import { validateImplementationToolContract } from "../shared/completion-guard.t
|
|
|
39
40
|
import {
|
|
40
41
|
type AcceptanceInput,
|
|
41
42
|
type AgentContract,
|
|
43
|
+
type AsyncParallelGroupStatus,
|
|
42
44
|
type AsyncStatus,
|
|
43
45
|
type ArtifactConfig,
|
|
44
46
|
type Details,
|
|
@@ -70,6 +72,7 @@ import type { ImportedAsyncRoot } from "./chain-root-attachment.ts";
|
|
|
70
72
|
import type { SessionLeaseRequest } from "../shared/session-lease.ts";
|
|
71
73
|
import { finalizeProcessTerminal, readProcessTerminal } from "./process-terminal.ts";
|
|
72
74
|
import type { ActiveAsyncCapacityHandle } from "./active-async-capacity.ts";
|
|
75
|
+
import { statusStepDescription } from "./chain-append.ts";
|
|
73
76
|
import { SUBAGENT_PROCESS_TERMINAL_EVENT } from "../../shared/types.ts";
|
|
74
77
|
import { assertAgentAllowedByCapabilityCeiling, decodeSubagentCapabilityCeiling, intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
75
78
|
import { agentDefinitionDigest, launchBindingDigest } from "../../shared/launch-contract.ts";
|
|
@@ -151,6 +154,8 @@ interface AsyncChainParams {
|
|
|
151
154
|
attachRoot?: ImportedAsyncRoot & { agent: string; outputName?: string; label?: string };
|
|
152
155
|
resultMode?: SubagentRunMode;
|
|
153
156
|
agents: AgentConfig[];
|
|
157
|
+
/** Original discovery provenance, retained by normal callers for unknown-agent diagnostics. */
|
|
158
|
+
unknownAgentDiagnosticContext?: UnknownAgentDiagnosticContext;
|
|
154
159
|
ctx: AsyncExecutionContext;
|
|
155
160
|
availableModels?: AvailableModelInfo[];
|
|
156
161
|
cwd?: string;
|
|
@@ -208,6 +213,7 @@ interface AsyncSingleParams {
|
|
|
208
213
|
recoveryAgentConfig?: AgentConfig;
|
|
209
214
|
ctx: AsyncExecutionContext;
|
|
210
215
|
cwd?: string;
|
|
216
|
+
requestedCwd?: string;
|
|
211
217
|
maxOutput?: MaxOutputConfig;
|
|
212
218
|
artifactsDir?: string;
|
|
213
219
|
artifactConfig: ArtifactConfig;
|
|
@@ -283,6 +289,8 @@ export interface AsyncRunnerStepBuildParams {
|
|
|
283
289
|
attachRoot?: ImportedAsyncRoot & { agent: string; outputName?: string; label?: string };
|
|
284
290
|
resultMode?: SubagentRunMode;
|
|
285
291
|
agents: AgentConfig[];
|
|
292
|
+
/** Exact discovery provenance for failed resolution; omission triggers defensive fallback discovery. */
|
|
293
|
+
unknownAgentDiagnosticContext?: UnknownAgentDiagnosticContext;
|
|
286
294
|
ctx: AsyncExecutionContext;
|
|
287
295
|
availableModels?: AvailableModelInfo[];
|
|
288
296
|
cwd?: string;
|
|
@@ -493,24 +501,33 @@ interface SpawnRunnerResult {
|
|
|
493
501
|
startupDidNotProceed?: boolean;
|
|
494
502
|
}
|
|
495
503
|
|
|
496
|
-
function
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
}
|
|
504
|
+
function isStaleExtensionContextError(error: unknown): boolean {
|
|
505
|
+
return error instanceof Error && /extension ctx is stale|stale after session replacement or reload/i.test(error.message);
|
|
506
|
+
}
|
|
500
507
|
|
|
508
|
+
export function emitProcessTerminalEvent(ctx: AsyncExecutionContext, proof: unknown): void {
|
|
501
509
|
try {
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
}
|
|
507
|
-
|
|
510
|
+
ctx.pi.events.emit(SUBAGENT_PROCESS_TERMINAL_EVENT, proof);
|
|
511
|
+
} catch (error) {
|
|
512
|
+
if (isStaleExtensionContextError(error)) return;
|
|
513
|
+
console.error("Failed to emit subagent process-terminal event:", error);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function spawnRunner(cfg: object, suffix: string, cwd: string, initialStatus: Omit<AsyncStatus, "pid" | "processTerminal">, initialStatusPath: string, onProcessTerminal?: (proof: unknown) => void, requestedCwd = cwd): SpawnRunnerResult {
|
|
518
|
+
const cwdError = preflightLaunchCwd(requestedCwd, cwd);
|
|
519
|
+
if (cwdError) return { error: cwdError };
|
|
520
|
+
|
|
521
|
+
if (!jitiCliPath) {
|
|
522
|
+
return { error: "upstream jiti for TypeScript execution could not be found; ensure package dependencies are installed" };
|
|
508
523
|
}
|
|
509
524
|
|
|
510
525
|
fs.mkdirSync(TEMP_ROOT_DIR, { recursive: true });
|
|
511
526
|
const cfgPath = getAsyncConfigPath(suffix);
|
|
512
527
|
const runnerProcessInstanceId = randomUUID();
|
|
513
|
-
const
|
|
528
|
+
const hasRevivalLease = typeof (cfg as { revivalLease?: unknown }).revivalLease === "object";
|
|
529
|
+
const launchBarrierToken = hasRevivalLease ? undefined : runnerProcessInstanceId;
|
|
530
|
+
const launchConfig = { ...cfg, runnerProcessInstanceId, ...(launchBarrierToken ? { launchBarrierToken } : {}) };
|
|
514
531
|
writePrivateAtomicJson(cfgPath, launchConfig);
|
|
515
532
|
const runner = path.join(path.dirname(fileURLToPath(import.meta.url)), "subagent-runner.ts");
|
|
516
533
|
const nodeCommand = resolveNodeExecutable();
|
|
@@ -523,7 +540,9 @@ function spawnRunner(cfg: object, suffix: string, cwd: string, onProcessTerminal
|
|
|
523
540
|
? path.join(launchAsyncDir, "runner-startup.json")
|
|
524
541
|
: undefined;
|
|
525
542
|
const startupAckPath = startupPath ? path.join(path.dirname(startupPath), "runner-startup-ack.json") : undefined;
|
|
526
|
-
const startupProceedPath =
|
|
543
|
+
const startupProceedPath = launchAsyncDir && (startupPath || launchBarrierToken)
|
|
544
|
+
? path.join(launchAsyncDir, "runner-startup-proceed.json")
|
|
545
|
+
: undefined;
|
|
527
546
|
if (startupPath) fs.rmSync(startupPath, { force: true });
|
|
528
547
|
if (startupAckPath) fs.rmSync(startupAckPath, { force: true });
|
|
529
548
|
if (startupProceedPath) fs.rmSync(startupProceedPath, { force: true });
|
|
@@ -605,6 +624,27 @@ function spawnRunner(cfg: object, suffix: string, cwd: string, onProcessTerminal
|
|
|
605
624
|
if (typeof proc.pid !== "number") {
|
|
606
625
|
return { error: `async runner did not produce a pid for cwd: ${cwd}` };
|
|
607
626
|
}
|
|
627
|
+
try {
|
|
628
|
+
writePrivateAtomicJson(initialStatusPath, {
|
|
629
|
+
...initialStatus,
|
|
630
|
+
pid: proc.pid,
|
|
631
|
+
processTerminal: { version: 1, state: "pending", runId: initialStatus.runId, runnerProcessInstanceId },
|
|
632
|
+
});
|
|
633
|
+
} catch (error) {
|
|
634
|
+
const message = `Failed to persist initial async status: ${error instanceof Error ? error.message : String(error)}`;
|
|
635
|
+
const terminationObserved = terminateRunnerBeforeProceed(proc.pid);
|
|
636
|
+
return { pid: proc.pid, runnerProcessInstanceId, error: message, terminationObserved, startupDidNotProceed: true };
|
|
637
|
+
}
|
|
638
|
+
if (launchBarrierToken && startupProceedPath) {
|
|
639
|
+
try {
|
|
640
|
+
writeRunnerStartupControl(startupProceedPath, { action: "proceed", token: launchBarrierToken });
|
|
641
|
+
} catch (error) {
|
|
642
|
+
const message = `Failed to authorize async runner startup: ${error instanceof Error ? error.message : String(error)}`;
|
|
643
|
+
if (launchAsyncDir) persistPreProceedStartupFailure(launchAsyncDir, launchRunId, runnerProcessInstanceId, launchSessionId, launchCompletionOwnerId, message);
|
|
644
|
+
const terminationObserved = terminateRunnerBeforeProceed(proc.pid);
|
|
645
|
+
return { pid: proc.pid, runnerProcessInstanceId, error: message, terminationObserved, startupDidNotProceed: true };
|
|
646
|
+
}
|
|
647
|
+
}
|
|
608
648
|
proc.unref();
|
|
609
649
|
if (startupPath && startupAckPath && startupProceedPath) {
|
|
610
650
|
const persistStartupFailure = (message: string) => {
|
|
@@ -709,6 +749,8 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
709
749
|
}
|
|
710
750
|
const workflowGraph = buildWorkflowGraphSnapshot({ runId: id, mode: resultMode, steps: graphChain });
|
|
711
751
|
|
|
752
|
+
const diagnosticContext = params.unknownAgentDiagnosticContext
|
|
753
|
+
?? unknownAgentDiagnosticContext(discoverAgents(path.resolve(runnerCwd), "both"));
|
|
712
754
|
for (const s of chain) {
|
|
713
755
|
const stepAgents = isParallelStep(s)
|
|
714
756
|
? s.parallel.map((t) => t.agent)
|
|
@@ -716,9 +758,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
716
758
|
? [s.parallel.agent]
|
|
717
759
|
: [(s as SequentialStep).agent];
|
|
718
760
|
for (const agentName of stepAgents) {
|
|
719
|
-
if (!agents.find((x) => x.name === agentName)) {
|
|
720
|
-
return { error: `Unknown agent: ${agentName}` };
|
|
721
|
-
}
|
|
761
|
+
if (!agents.find((x) => x.name === agentName)) return { error: formatUnknownAgentError(agentName, diagnosticContext) };
|
|
722
762
|
}
|
|
723
763
|
}
|
|
724
764
|
|
|
@@ -824,6 +864,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
824
864
|
const thinkingOverride = flatIndex === undefined ? undefined : thinkingOverridesByFlatIndex?.[flatIndex];
|
|
825
865
|
const effectiveThinking = externalRunner ? undefined : thinkingOverride ?? a.thinking;
|
|
826
866
|
const model = externalRunner ? undefined : applyThinkingSuffix(primaryModel, effectiveThinking, thinkingOverride !== undefined);
|
|
867
|
+
const contextLimit = model ? findModelInfo(model, availableModels, a.modelProvider ?? ctx.currentModelProvider)?.contextWindow : undefined;
|
|
827
868
|
const thinkingCeiling = externalRunner ? undefined : intersectThinkingCeilings(
|
|
828
869
|
params.thinkingCeiling,
|
|
829
870
|
a.maxThinking,
|
|
@@ -868,6 +909,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
868
909
|
inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
|
|
869
910
|
agentName: a.name,
|
|
870
911
|
permissionRules,
|
|
912
|
+
runtimeSnapshotHost: ctx.pi,
|
|
871
913
|
});
|
|
872
914
|
const launchResolvedExtensions = externalRunner ? undefined : projectLaunchResolvedChildExtensions(toolPlan);
|
|
873
915
|
if (externalRunner && permissionRules) {
|
|
@@ -902,11 +944,15 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
902
944
|
outputName: s.as,
|
|
903
945
|
structured: Boolean(s.outputSchema),
|
|
904
946
|
cwd: stepCwd,
|
|
947
|
+
requestedCwd: s.cwd ?? stepCwd,
|
|
905
948
|
model,
|
|
949
|
+
...(contextLimit !== undefined ? { contextLimit } : {}),
|
|
906
950
|
...(fast !== undefined ? { fast } : {}),
|
|
907
951
|
thinking: resolveEffectiveThinking(model, effectiveThinking),
|
|
908
952
|
...(thinkingCeiling ? { thinkingCeiling } : {}),
|
|
909
953
|
launchResolvedExtensions,
|
|
954
|
+
...(toolPlan.mcpConfig ? { mcpConfig: toolPlan.mcpConfig } : {}),
|
|
955
|
+
...(toolPlan.runtimeServerNames ? { runtimeServerNames: toolPlan.runtimeServerNames } : {}),
|
|
910
956
|
modelCandidates: externalRunner ? undefined : modelCandidates,
|
|
911
957
|
...(primaryModelFromParent ? { skipPrimaryModelVerification: true } : {}),
|
|
912
958
|
...(availableModels && availableModels.length > 0 ? { modelVerificationRegistry: availableModels } : {}),
|
|
@@ -914,10 +960,12 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
914
960
|
extensions: a.extensions,
|
|
915
961
|
subagentOnlyExtensions: a.subagentOnlyExtensions,
|
|
916
962
|
mcpDirectTools: a.mcpDirectTools,
|
|
963
|
+
mutationTools: a.mutationTools,
|
|
917
964
|
completionGuard: a.completionGuard,
|
|
918
965
|
systemPrompt,
|
|
919
966
|
systemPromptMode: a.systemPromptMode,
|
|
920
967
|
inheritProjectContext: a.inheritProjectContext,
|
|
968
|
+
inheritGlobalContext: a.inheritGlobalContext,
|
|
921
969
|
inheritSkills: a.inheritSkills,
|
|
922
970
|
skills: resolvedSkills.map((r) => r.name),
|
|
923
971
|
outputPath,
|
|
@@ -1054,6 +1102,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
1054
1102
|
index: params.attachRoot.index,
|
|
1055
1103
|
},
|
|
1056
1104
|
inheritProjectContext: false,
|
|
1105
|
+
inheritGlobalContext: false,
|
|
1057
1106
|
inheritSkills: false,
|
|
1058
1107
|
}, ...builtSteps]
|
|
1059
1108
|
: builtSteps;
|
|
@@ -1140,6 +1189,7 @@ export function executeAsyncChain(
|
|
|
1140
1189
|
attachRoot: params.attachRoot,
|
|
1141
1190
|
resultMode,
|
|
1142
1191
|
agents,
|
|
1192
|
+
unknownAgentDiagnosticContext: params.unknownAgentDiagnosticContext,
|
|
1143
1193
|
ctx,
|
|
1144
1194
|
availableModels: params.availableModels,
|
|
1145
1195
|
cwd,
|
|
@@ -1191,6 +1241,21 @@ export function executeAsyncChain(
|
|
|
1191
1241
|
}
|
|
1192
1242
|
return "agent" in step ? [childIntercomTarget(step.agent, childTargetIndex++)] : [undefined];
|
|
1193
1243
|
}) : undefined;
|
|
1244
|
+
const initialStatusSteps = eventChain.flatMap((step) => isParallelStep(step)
|
|
1245
|
+
? step.parallel.map((task) => ({ agent: task.agent, ...(statusStepDescription(task.task) ? { description: statusStepDescription(task.task) } : {}), ...(task.label ? { label: task.label } : {}), ...(task.as ? { outputName: task.as } : {}), status: "pending" as const }))
|
|
1246
|
+
: isDynamicParallelStep(step)
|
|
1247
|
+
? [{ agent: `expand:${step.parallel.agent}`, label: step.label ?? step.parallel.label ?? `Dynamic fanout (${step.collect.as})`, outputName: step.collect.as, status: "pending" as const }]
|
|
1248
|
+
: [{ agent: (step as SequentialStep).agent, ...(statusStepDescription((step as SequentialStep).task) ? { description: statusStepDescription((step as SequentialStep).task) } : {}), ...((step as SequentialStep).label ? { label: (step as SequentialStep).label } : {}), ...((step as SequentialStep).as ? { outputName: (step as SequentialStep).as } : {}), status: "pending" as const }]);
|
|
1249
|
+
const initialParallelGroups: AsyncParallelGroupStatus[] = [];
|
|
1250
|
+
let initialFlatIndex = 0;
|
|
1251
|
+
for (let stepIndex = 0; stepIndex < eventChain.length; stepIndex++) {
|
|
1252
|
+
const step = eventChain[stepIndex]!;
|
|
1253
|
+
if (isParallelStep(step)) initialParallelGroups.push({ start: initialFlatIndex, count: step.parallel.length, stepIndex });
|
|
1254
|
+
else if (isDynamicParallelStep(step)) initialParallelGroups.push({ start: initialFlatIndex, count: 1, stepIndex });
|
|
1255
|
+
initialFlatIndex += isParallelStep(step) ? step.parallel.length : 1;
|
|
1256
|
+
}
|
|
1257
|
+
const initialStatusAt = Date.now();
|
|
1258
|
+
const initialCompletionOwnerId = ctx.completionOwnerId ?? currentCompletionOwnerId();
|
|
1194
1259
|
|
|
1195
1260
|
let spawnResult: SpawnRunnerResult = {};
|
|
1196
1261
|
try {
|
|
@@ -1240,7 +1305,22 @@ export function executeAsyncChain(
|
|
|
1240
1305
|
},
|
|
1241
1306
|
id,
|
|
1242
1307
|
runnerCwd,
|
|
1243
|
-
|
|
1308
|
+
{
|
|
1309
|
+
lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
1310
|
+
runId: id,
|
|
1311
|
+
...(ctx.currentSessionId ? { sessionId: ctx.currentSessionId } : {}),
|
|
1312
|
+
...(initialCompletionOwnerId ? { completionOwnerId: initialCompletionOwnerId } : {}),
|
|
1313
|
+
mode: resultMode,
|
|
1314
|
+
state: "running",
|
|
1315
|
+
startedAt: initialStatusAt,
|
|
1316
|
+
lastUpdate: initialStatusAt,
|
|
1317
|
+
currentStep: 0,
|
|
1318
|
+
chainStepCount: eventChain.length,
|
|
1319
|
+
...(initialParallelGroups.length ? { parallelGroups: initialParallelGroups } : {}),
|
|
1320
|
+
steps: initialStatusSteps,
|
|
1321
|
+
},
|
|
1322
|
+
path.join(asyncDir, "status.json"),
|
|
1323
|
+
(proof) => emitProcessTerminalEvent(ctx, proof),
|
|
1244
1324
|
);
|
|
1245
1325
|
} catch (error) {
|
|
1246
1326
|
params.activeAsyncCapacity?.rollback();
|
|
@@ -1507,6 +1587,7 @@ export function executeAsyncSingle(
|
|
|
1507
1587
|
);
|
|
1508
1588
|
const effectiveThinking = externalRunner ? undefined : params.thinkingOverride ?? agentConfig.thinking;
|
|
1509
1589
|
const model = externalRunner ? undefined : applyThinkingSuffix(primaryModel, effectiveThinking, params.thinkingOverride !== undefined);
|
|
1590
|
+
const contextLimit = model ? findModelInfo(model, availableModels, agentConfig.modelProvider ?? ctx.currentModelProvider)?.contextWindow : undefined;
|
|
1510
1591
|
const thinkingCeiling = externalRunner ? undefined : intersectThinkingCeilings(
|
|
1511
1592
|
params.thinkingCeiling,
|
|
1512
1593
|
agentConfig.maxThinking,
|
|
@@ -1574,6 +1655,7 @@ export function executeAsyncSingle(
|
|
|
1574
1655
|
inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
|
|
1575
1656
|
agentName: agentConfig.name,
|
|
1576
1657
|
permissionRules: resolvePermissionRules(ctx.permissions, agentConfig.permissions),
|
|
1658
|
+
runtimeSnapshotHost: ctx.pi,
|
|
1577
1659
|
});
|
|
1578
1660
|
const launchResolvedExtensions = externalRunner ? undefined : projectLaunchResolvedChildExtensions(toolPlan);
|
|
1579
1661
|
if (!externalRunner) {
|
|
@@ -1601,6 +1683,7 @@ export function executeAsyncSingle(
|
|
|
1601
1683
|
systemPrompt: effectiveSystemPrompt,
|
|
1602
1684
|
systemPromptMode: agentConfig.systemPromptMode,
|
|
1603
1685
|
inheritProjectContext: agentConfig.inheritProjectContext,
|
|
1686
|
+
inheritGlobalContext: agentConfig.inheritGlobalContext,
|
|
1604
1687
|
inheritSkills: agentConfig.inheritSkills,
|
|
1605
1688
|
skills: resolvedSkills.map((skill) => skill.name),
|
|
1606
1689
|
tools: toolPlan.effectiveToolAllowlist,
|
|
@@ -1643,9 +1726,11 @@ export function executeAsyncSingle(
|
|
|
1643
1726
|
...(recoveryAgentConfig.extensions ? { extensions: [...recoveryAgentConfig.extensions] } : {}),
|
|
1644
1727
|
...(recoveryAgentConfig.subagentOnlyExtensions ? { subagentOnlyExtensions: [...recoveryAgentConfig.subagentOnlyExtensions] } : {}),
|
|
1645
1728
|
...(recoveryAgentConfig.mcpDirectTools ? { mcpDirectTools: [...recoveryAgentConfig.mcpDirectTools] } : {}),
|
|
1729
|
+
...(recoveryAgentConfig.mutationTools ? { mutationTools: [...recoveryAgentConfig.mutationTools] } : {}),
|
|
1646
1730
|
...(recoveryAgentConfig.systemPrompt ? { systemPrompt: recoveryAgentConfig.systemPrompt } : {}),
|
|
1647
1731
|
systemPromptMode: recoveryAgentConfig.systemPromptMode,
|
|
1648
1732
|
inheritProjectContext: recoveryAgentConfig.inheritProjectContext,
|
|
1733
|
+
inheritGlobalContext: recoveryAgentConfig.inheritGlobalContext,
|
|
1649
1734
|
inheritSkills: recoveryAgentConfig.inheritSkills,
|
|
1650
1735
|
...(resolvedSkills.length ? { skills: resolvedSkills.map((skill) => skill.name) } : {}),
|
|
1651
1736
|
...(recoveryAgentConfig.skillPath ? { skillPath: [...recoveryAgentConfig.skillPath] } : {}),
|
|
@@ -1678,6 +1763,8 @@ export function executeAsyncSingle(
|
|
|
1678
1763
|
}
|
|
1679
1764
|
}
|
|
1680
1765
|
let spawnResult: SpawnRunnerResult = {};
|
|
1766
|
+
const initialStatusAt = Date.now();
|
|
1767
|
+
const initialCompletionOwnerId = ctx.completionOwnerId ?? currentCompletionOwnerId();
|
|
1681
1768
|
try {
|
|
1682
1769
|
spawnResult = spawnRunner(
|
|
1683
1770
|
{
|
|
@@ -1693,7 +1780,9 @@ export function executeAsyncSingle(
|
|
|
1693
1780
|
...(params.externalJobFollowUp ? { externalJobFollowUp: params.externalJobFollowUp } : {}),
|
|
1694
1781
|
...(params.context ? { context: params.context } : {}),
|
|
1695
1782
|
cwd: runnerCwd,
|
|
1783
|
+
requestedCwd: params.requestedCwd ?? runnerCwd,
|
|
1696
1784
|
model,
|
|
1785
|
+
...(contextLimit !== undefined ? { contextLimit } : {}),
|
|
1697
1786
|
...(params.fast ?? agentConfig.fast ? { fast: params.fast ?? agentConfig.fast } : {}),
|
|
1698
1787
|
thinking: resolveEffectiveThinking(model, effectiveThinking),
|
|
1699
1788
|
...(thinkingCeiling ? { thinkingCeiling } : {}),
|
|
@@ -1704,10 +1793,14 @@ export function executeAsyncSingle(
|
|
|
1704
1793
|
extensions: agentConfig.extensions,
|
|
1705
1794
|
subagentOnlyExtensions: agentConfig.subagentOnlyExtensions,
|
|
1706
1795
|
mcpDirectTools: agentConfig.mcpDirectTools,
|
|
1796
|
+
...(toolPlan.mcpConfig ? { mcpConfig: toolPlan.mcpConfig } : {}),
|
|
1797
|
+
...(toolPlan.runtimeServerNames ? { runtimeServerNames: toolPlan.runtimeServerNames } : {}),
|
|
1798
|
+
mutationTools: agentConfig.mutationTools,
|
|
1707
1799
|
completionGuard: agentConfig.completionGuard,
|
|
1708
1800
|
systemPrompt,
|
|
1709
1801
|
systemPromptMode: agentConfig.systemPromptMode,
|
|
1710
1802
|
inheritProjectContext: agentConfig.inheritProjectContext,
|
|
1803
|
+
inheritGlobalContext: agentConfig.inheritGlobalContext,
|
|
1711
1804
|
inheritSkills: agentConfig.inheritSkills,
|
|
1712
1805
|
skills: resolvedSkills.map((r) => r.name),
|
|
1713
1806
|
outputPath,
|
|
@@ -1773,7 +1866,22 @@ export function executeAsyncSingle(
|
|
|
1773
1866
|
},
|
|
1774
1867
|
id,
|
|
1775
1868
|
runnerCwd,
|
|
1776
|
-
|
|
1869
|
+
{
|
|
1870
|
+
lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
1871
|
+
runId: id,
|
|
1872
|
+
...(ctx.currentSessionId ? { sessionId: ctx.currentSessionId } : {}),
|
|
1873
|
+
...(initialCompletionOwnerId ? { completionOwnerId: initialCompletionOwnerId } : {}),
|
|
1874
|
+
mode: "single",
|
|
1875
|
+
state: "running",
|
|
1876
|
+
startedAt: initialStatusAt,
|
|
1877
|
+
lastUpdate: initialStatusAt,
|
|
1878
|
+
currentStep: 0,
|
|
1879
|
+
chainStepCount: 1,
|
|
1880
|
+
steps: [{ agent, status: "pending", ...(model ? { model } : {}), ...(contextLimit !== undefined ? { contextLimit } : {}) }],
|
|
1881
|
+
},
|
|
1882
|
+
path.join(asyncDir, "status.json"),
|
|
1883
|
+
(proof) => emitProcessTerminalEvent(ctx, proof),
|
|
1884
|
+
params.requestedCwd ?? runnerCwd,
|
|
1777
1885
|
);
|
|
1778
1886
|
} catch (error) {
|
|
1779
1887
|
params.activeAsyncCapacity?.rollback();
|
|
@@ -24,6 +24,7 @@ import { findNestedRouteForRootId, hasLiveNestedDescendants, updateAsyncJobNeste
|
|
|
24
24
|
import { listAsyncRuns, type AsyncRunSummary } from "./async-status.ts";
|
|
25
25
|
import { EXTERNAL_JOB_BRIDGE_REQUEST_DIR, serviceExternalJobBridgeRequests } from "../shared/external-job-bridge.ts";
|
|
26
26
|
import { shouldUseNativeFsWatch } from "../../shared/watch-strategy.ts";
|
|
27
|
+
import { parseWorkflowChildSummary } from "../../workflows/workflow-child-summary.ts";
|
|
27
28
|
|
|
28
29
|
interface AsyncJobTrackerOptions {
|
|
29
30
|
completionRetentionMs?: number;
|
|
@@ -171,6 +172,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
171
172
|
parentWorkflowRunId: run.parentWorkflowRunId,
|
|
172
173
|
workflowKey: run.workflowKey,
|
|
173
174
|
workflow: run.workflow,
|
|
175
|
+
workflowChildren: parseWorkflowChildSummary(run.workflowChildren),
|
|
174
176
|
};
|
|
175
177
|
};
|
|
176
178
|
const cancelCleanup = (asyncId: string) => {
|
|
@@ -398,6 +400,9 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
398
400
|
job.parentWorkflowRunId = status.parentWorkflowRunId ?? job.parentWorkflowRunId;
|
|
399
401
|
job.workflowKey = status.workflowKey ?? job.workflowKey;
|
|
400
402
|
job.workflow = status.workflow ?? job.workflow;
|
|
403
|
+
const workflowChildren = parseWorkflowChildSummary(status.workflowChildren);
|
|
404
|
+
if (workflowChildren && workflowChildren.workflowRunId !== status.runId) throw new Error("workflowChildren.workflowRunId does not match async status runId.");
|
|
405
|
+
job.workflowChildren = workflowChildren ?? job.workflowChildren;
|
|
401
406
|
job.currentStep = status.currentStep ?? job.currentStep;
|
|
402
407
|
job.chainStepCount = status.chainStepCount ?? job.chainStepCount;
|
|
403
408
|
job.startedAt = status.startedAt ?? job.startedAt;
|
|
@@ -325,7 +325,7 @@ export function readAsyncRecoveryDescriptor(asyncDir: string | undefined): Steer
|
|
|
325
325
|
const parsed = value as Record<string, unknown>;
|
|
326
326
|
const allowedFields = new Set([
|
|
327
327
|
"version", "launchContractDigest", "sourceRunId", "agentContract", "agent", "sessionFile", "cwd", "model", "modelProvider", "modelOverrideFromParent", "fallbackModels", "thinking", "thinkingCeiling", "tools", "extensions",
|
|
328
|
-
"subagentOnlyExtensions", "mcpDirectTools", "systemPrompt", "systemPromptMode", "inheritProjectContext", "inheritSkills", "skills",
|
|
328
|
+
"subagentOnlyExtensions", "mcpDirectTools", "mutationTools", "systemPrompt", "systemPromptMode", "inheritProjectContext", "inheritGlobalContext", "inheritSkills", "skills",
|
|
329
329
|
"skillPath", "agentFilePath", "completionGuard", "memory", "outputPath", "outputMode", "structuredOutputSchema", "acceptance", "sessionDir", "artifactConfig",
|
|
330
330
|
"artifactsDir", "maxOutput", "controlConfig", "context", "intercomBridge", "absoluteDeadlineAt", "initialTurnBudget", "initialToolBudget", "maxSubagentDepth", "share", "capabilityCeiling",
|
|
331
331
|
"launchResolvedExtensions", "runFanoutBudget",
|
|
@@ -359,8 +359,10 @@ export function readAsyncRecoveryDescriptor(asyncDir: string | undefined): Steer
|
|
|
359
359
|
for (const field of ["inheritProjectContext", "inheritSkills", "share"] as const) {
|
|
360
360
|
if (typeof parsed[field] !== "boolean") throw new Error(`Invalid async recovery descriptor '${descriptorPath}': ${field} must be a boolean.`);
|
|
361
361
|
}
|
|
362
|
+
if (parsed.inheritGlobalContext === undefined) parsed.inheritGlobalContext = parsed.inheritProjectContext;
|
|
363
|
+
else if (typeof parsed.inheritGlobalContext !== "boolean") throw new Error(`Invalid async recovery descriptor '${descriptorPath}': inheritGlobalContext must be a boolean.`);
|
|
362
364
|
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.`);
|
|
363
|
-
for (const field of ["fallbackModels", "tools", "extensions", "subagentOnlyExtensions", "mcpDirectTools", "skills", "skillPath"] as const) {
|
|
365
|
+
for (const field of ["fallbackModels", "tools", "extensions", "subagentOnlyExtensions", "mcpDirectTools", "mutationTools", "skills", "skillPath"] as const) {
|
|
364
366
|
const item = parsed[field];
|
|
365
367
|
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.`);
|
|
366
368
|
}
|
|
@@ -592,9 +594,11 @@ export function applySteeringRecoveryAgentConfig(agentConfig: AgentConfig, descr
|
|
|
592
594
|
extensions: descriptor.extensions ? [...descriptor.extensions] : undefined,
|
|
593
595
|
subagentOnlyExtensions: descriptor.subagentOnlyExtensions ? [...descriptor.subagentOnlyExtensions] : undefined,
|
|
594
596
|
mcpDirectTools: descriptor.mcpDirectTools ? [...descriptor.mcpDirectTools] : undefined,
|
|
597
|
+
mutationTools: descriptor.mutationTools ? [...descriptor.mutationTools] : undefined,
|
|
595
598
|
systemPrompt: descriptor.systemPrompt ?? agentConfig.systemPrompt,
|
|
596
599
|
systemPromptMode: descriptor.systemPromptMode,
|
|
597
600
|
inheritProjectContext: descriptor.inheritProjectContext,
|
|
601
|
+
inheritGlobalContext: descriptor.inheritGlobalContext,
|
|
598
602
|
inheritSkills: descriptor.inheritSkills,
|
|
599
603
|
skills: descriptor.skills ? [...descriptor.skills] : undefined,
|
|
600
604
|
skillPath: descriptor.skillPath ? [...descriptor.skillPath] : undefined,
|