pi-subagents 0.47.1 → 0.48.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 +23 -0
- package/README.md +2 -0
- package/docs/configuration.md +46 -2
- package/docs/observability.md +1 -1
- package/docs/tool-reference.md +1 -1
- package/package.json +1 -1
- package/src/agents/agents.ts +9 -3
- package/src/extension/config.ts +6 -0
- package/src/extension/doctor.ts +40 -0
- package/src/extension/index.ts +21 -0
- package/src/extension/public-execution.ts +5 -0
- package/src/extension/rpc.ts +3 -9
- package/src/extension/schemas.ts +2 -2
- package/src/intercom/intercom-bridge.ts +4 -1
- package/src/missions/lifecycle.ts +4 -7
- package/src/missions/store.ts +4 -4
- package/src/missions/workflow-state.ts +6 -2
- package/src/runs/background/active-async-capacity.ts +374 -0
- package/src/runs/background/active-run-index.ts +9 -5
- package/src/runs/background/async-execution.ts +117 -29
- package/src/runs/background/async-resume.ts +7 -1
- package/src/runs/background/async-status.ts +11 -5
- package/src/runs/background/chain-append.ts +33 -15
- package/src/runs/background/owned-process-tree.ts +104 -0
- package/src/runs/background/process-terminal.ts +17 -3
- package/src/runs/background/run-status.ts +5 -1
- package/src/runs/background/stale-run-reconciler.ts +3 -3
- package/src/runs/background/subagent-runner.ts +56 -31
- package/src/runs/foreground/chain-execution.ts +37 -2
- package/src/runs/foreground/execution.ts +90 -19
- package/src/runs/foreground/foreground-control.ts +12 -0
- package/src/runs/foreground/prompt-audit.ts +171 -0
- package/src/runs/foreground/subagent-executor.ts +614 -187
- package/src/runs/shared/acceptance.ts +13 -4
- package/src/runs/shared/llm-intent-arbiter.ts +286 -0
- package/src/runs/shared/parallel-utils.ts +2 -0
- package/src/runs/shared/pi-args.ts +44 -1
- package/src/runs/shared/run-fanout-budget.ts +280 -0
- package/src/runs/shared/single-output.ts +4 -2
- package/src/runs/shared/task-intent.ts +19 -3
- package/src/runs/shared/worktree.ts +17 -5
- package/src/shared/types.ts +92 -0
- package/src/shared/utils.ts +3 -1
- package/src/tui/fleet-status.ts +7 -5
- package/src/tui/fleet.ts +225 -12
- package/src/workflows/scripted-workflow.ts +22 -5
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
settleForegroundSchedulingOwner,
|
|
18
18
|
updateForegroundChild,
|
|
19
19
|
} from "./foreground-control.ts";
|
|
20
|
+
import { getLivePromptAudit, rewritePromptWithGuidance, updateLiveEffectivePrompt } from "./prompt-audit.ts";
|
|
20
21
|
import { persistForegroundRunHistory, MAX_REMEMBERED_FOREGROUND_RUNS } from "./foreground-history.ts";
|
|
21
22
|
import { resolveExecutionAgentScope } from "../../agents/agent-scope.ts";
|
|
22
23
|
import { handleManagementAction } from "../../agents/agent-management.ts";
|
|
@@ -54,6 +55,7 @@ import {
|
|
|
54
55
|
import { discoverAvailableSkills, normalizeSkillInput } from "../../agents/skills.ts";
|
|
55
56
|
import { buildAsyncRunnerSteps, DEFAULT_ASYNC_TIMEOUT_MS, executeAsyncChain, executeAsyncSingle, formatAsyncStartedMessage, isAsyncAvailable, workflowAwaitedAsyncResultPath } from "../background/async-execution.ts";
|
|
56
57
|
import { updateActiveRunIndex } from "../background/active-run-index.ts";
|
|
58
|
+
import { acquireActiveAsyncCapacity, ActiveAsyncCapacityError, getActiveAsyncCapacitySnapshot, resolveMaxActiveAsyncRunsPerSession, transferActiveAsyncCapacity, type ActiveAsyncCapacityHandle } from "../background/active-async-capacity.ts";
|
|
57
59
|
import { isScheduledRunAction, type ScheduledRunAction } from "../background/scheduled-runs.ts";
|
|
58
60
|
import { enqueueChainAppendRequest, readPendingChainAppendRequests, runnerStepOutputNames } from "../background/chain-append.ts";
|
|
59
61
|
import { ChainOutputValidationError, validateChainOutputBindingsWithContext } from "../shared/chain-outputs.ts";
|
|
@@ -64,6 +66,7 @@ import { applyIntercomBridgeToAgent, INTERCOM_BRIDGE_MARKER, resolveIntercomBrid
|
|
|
64
66
|
import { formatControlIntercomMessage, formatControlNoticeMessage, resolveControlConfig, shouldNotifyControlEvent } from "../shared/subagent-control.ts";
|
|
65
67
|
import { resolveTurnBudgetConfig } from "../shared/turn-budget.ts";
|
|
66
68
|
import { formatSpawnBudget, getSpawnBudgetSnapshot, grantSpawnBudget, preflightSpawnBudget, preflightSpawnBudgetGrant, reserveSpawnBudget } from "../shared/spawn-budget.ts";
|
|
69
|
+
import { claimRunFanoutBatch, claimRunFanoutBatchWithCommit, createRunFanoutBudget, decodeRunFanoutBudgetDescriptor, formatRunFanoutBudget, getRunFanoutBudgetSnapshot, readRunFanoutBudgetDescriptor, RunFanoutLimitError, RUN_FANOUT_BUDGET_ENV, writeRunFanoutBudgetDescriptor } from "../shared/run-fanout-budget.ts";
|
|
67
70
|
import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
|
|
68
71
|
import { usageBudgetExceededMessage, usageBudgetState, validateUsageBudgetConfig } from "../shared/usage-budget.ts";
|
|
69
72
|
import { intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
@@ -71,6 +74,7 @@ import { isAgentContractV1 } from "../shared/agent-contract.ts";
|
|
|
71
74
|
import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
72
75
|
import { cleanupStructuredOutputRuntime, createStructuredOutputRuntime } from "../shared/structured-output.ts";
|
|
73
76
|
import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, readStatus, resolveChildCwd, sumResultsCost, sumResultsUsage } from "../../shared/utils.ts";
|
|
77
|
+
import { createTaskMutationArbiter } from "../shared/llm-intent-arbiter.ts";
|
|
74
78
|
import { DEFAULT_GLOBAL_CONCURRENCY_LIMIT, Semaphore } from "../shared/parallel-utils.ts";
|
|
75
79
|
import { discardPreservedWorktrees, formatParallelHandoffError, formatParallelHandoffReference, parallelHandoffPath, writeParallelHandoffGroup, writePendingParallelHandoff } from "../shared/parallel-handoff.ts";
|
|
76
80
|
import { summarizeContextModes, type ContextMode, type ContextSummary } from "../shared/context-mode.ts";
|
|
@@ -143,6 +147,7 @@ import {
|
|
|
143
147
|
type ResolvedControlConfig,
|
|
144
148
|
type ResolvedTurnBudget,
|
|
145
149
|
type ResolvedToolBudget,
|
|
150
|
+
type RunFanoutBudgetDescriptor,
|
|
146
151
|
type SingleResult,
|
|
147
152
|
type ToolBudgetConfig,
|
|
148
153
|
type TurnBudgetConfig,
|
|
@@ -161,6 +166,7 @@ import {
|
|
|
161
166
|
resolveTopLevelParallelMaxTasks,
|
|
162
167
|
resolveChildMaxSubagentDepth,
|
|
163
168
|
resolveCurrentMaxSubagentDepth,
|
|
169
|
+
resolveMaxSubagentSpawnsPerRun,
|
|
164
170
|
wrapForkTask,
|
|
165
171
|
} from "../../shared/types.ts";
|
|
166
172
|
|
|
@@ -284,6 +290,10 @@ export interface SubagentParamsLike {
|
|
|
284
290
|
workflowKey?: string;
|
|
285
291
|
workflowChildAsyncId?: string;
|
|
286
292
|
suppressRoutineResultIntercom?: boolean;
|
|
293
|
+
/** Internal inherited cumulative run-tree budget. */
|
|
294
|
+
runFanoutBudget?: RunFanoutBudgetDescriptor;
|
|
295
|
+
/** Internal workflow host admission proof. */
|
|
296
|
+
runFanoutAdmitted?: boolean;
|
|
287
297
|
/** Internal durable-run compatibility fields. Public callers must use workflowScript. */
|
|
288
298
|
chain?: ChainStep[];
|
|
289
299
|
tasks?: TaskParam[];
|
|
@@ -388,6 +398,7 @@ interface ExecutionContextData {
|
|
|
388
398
|
artifactsDir: string;
|
|
389
399
|
backgroundRequestedWhileClarifying: boolean;
|
|
390
400
|
effectiveAsync: boolean;
|
|
401
|
+
asyncRunId: string;
|
|
391
402
|
controlConfig: ResolvedControlConfig;
|
|
392
403
|
intercomBridge: IntercomBridgeState;
|
|
393
404
|
nestedRoute?: NestedRouteInfo;
|
|
@@ -404,6 +415,9 @@ interface ExecutionContextData {
|
|
|
404
415
|
parentSessionId: string | null;
|
|
405
416
|
parentPiSessionId?: string;
|
|
406
417
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
418
|
+
runFanoutBudget: RunFanoutBudgetDescriptor;
|
|
419
|
+
topLevelAsyncCapacityEligible: boolean;
|
|
420
|
+
activeAsyncCapacity?: ActiveAsyncCapacityHandle;
|
|
407
421
|
}
|
|
408
422
|
|
|
409
423
|
function resolveRequestedCwd(runtimeCwd: string, requestedCwd: string | undefined): string {
|
|
@@ -432,6 +446,17 @@ function getForegroundControl(state: SubagentState, runId: string | undefined) {
|
|
|
432
446
|
return newest;
|
|
433
447
|
}
|
|
434
448
|
|
|
449
|
+
function promptAuditRedoParams(value: unknown, rewrittenTask: string): SubagentParamsLike {
|
|
450
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Prompt redo is missing a safe live launch contract.");
|
|
451
|
+
const params = { ...(value as SubagentParamsLike), task: rewrittenTask, async: false };
|
|
452
|
+
delete params.workflowParentRunId;
|
|
453
|
+
delete params.workflowKey;
|
|
454
|
+
delete params.workflowChildAsyncId;
|
|
455
|
+
delete params.suppressRoutineResultIntercom;
|
|
456
|
+
if (params.worktree === true && Array.isArray(params.tasks)) delete params.cwd;
|
|
457
|
+
return params;
|
|
458
|
+
}
|
|
459
|
+
|
|
435
460
|
function formatForegroundActivity(control: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never): string | undefined {
|
|
436
461
|
const facts: string[] = [];
|
|
437
462
|
if (control.currentTool && control.currentToolStartedAt) facts.push(`tool ${control.currentTool} for ${Math.floor(Math.max(0, Date.now() - control.currentToolStartedAt) / 1000)}s`);
|
|
@@ -483,12 +508,18 @@ function withSpawnBudgetStatus(
|
|
|
483
508
|
sessionId: string | null,
|
|
484
509
|
): AgentToolResult<Details> {
|
|
485
510
|
const spawnBudget = getSpawnBudgetSnapshot(state, config, sessionId);
|
|
511
|
+
const activeAsyncCapacity = sessionId
|
|
512
|
+
? getActiveAsyncCapacitySnapshot(sessionId, resolveMaxActiveAsyncRunsPerSession(config.maxActiveAsyncRunsPerSession), {
|
|
513
|
+
liveWorkflowRunIds: new Set(state.workflowControllers?.keys() ?? []),
|
|
514
|
+
})
|
|
515
|
+
: { used: 0, limit: resolveMaxActiveAsyncRunsPerSession(config.maxActiveAsyncRunsPerSession) ?? 0 };
|
|
516
|
+
state.activeAsyncCapacity = activeAsyncCapacity;
|
|
486
517
|
return {
|
|
487
518
|
...result,
|
|
488
519
|
content: result.content.map((item, index) => index === 0 && item.type === "text"
|
|
489
|
-
? { ...item, text: `${formatSpawnBudget(spawnBudget)}\n${item.text}` }
|
|
520
|
+
? { ...item, text: `${formatSpawnBudget(spawnBudget)}\nActive async capacity: ${activeAsyncCapacity.used}/${activeAsyncCapacity.limit || "unlimited"} used\n${item.text}` }
|
|
490
521
|
: item),
|
|
491
|
-
details: { ...result.details, spawnBudget },
|
|
522
|
+
details: { ...result.details, spawnBudget, activeAsyncCapacity },
|
|
492
523
|
};
|
|
493
524
|
}
|
|
494
525
|
|
|
@@ -509,6 +540,31 @@ function countRequestedSubagentSpawns(params: SubagentParamsLike, config: Extens
|
|
|
509
540
|
return params.agent ? 1 : 0;
|
|
510
541
|
}
|
|
511
542
|
|
|
543
|
+
function staticRunFanoutPaths(params: SubagentParamsLike): string[] {
|
|
544
|
+
if (params.tasks) return params.tasks.map((_, index) => `tasks[${index}]`);
|
|
545
|
+
if (params.chain) return params.chain.flatMap((step, stepIndex) => {
|
|
546
|
+
if (isDynamicParallelStep(step) || "checkpoint" in step) return [];
|
|
547
|
+
if (isParallelStep(step)) return step.parallel.map((_, itemIndex) => `chain[${stepIndex}].parallel[${itemIndex}]`);
|
|
548
|
+
return [`chain[${stepIndex}]`];
|
|
549
|
+
});
|
|
550
|
+
return params.agent ? ["single"] : [];
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function runFanoutErrorResult(error: RunFanoutLimitError, mode: "single" | "parallel" | "chain"): AgentToolResult<Details> {
|
|
554
|
+
return { content: [{ type: "text", text: error.message }], isError: true, details: { mode, results: [], runFanoutBudget: error.snapshot, runFanoutRejection: error.rejection } };
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function withRunFanoutBudget(result: AgentToolResult<Details>, descriptor: RunFanoutBudgetDescriptor, options: { annotateContent?: boolean } = {}): AgentToolResult<Details> {
|
|
558
|
+
const runFanoutBudget = getRunFanoutBudgetSnapshot(descriptor);
|
|
559
|
+
return {
|
|
560
|
+
...result,
|
|
561
|
+
content: options.annotateContent === false
|
|
562
|
+
? result.content
|
|
563
|
+
: result.content.map((item, index) => index === 0 && item.type === "text" ? { ...item, text: `${formatRunFanoutBudget(runFanoutBudget)}\n${item.text}` } : item),
|
|
564
|
+
details: { ...result.details, runFanoutBudget },
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
|
|
512
568
|
function foregroundStatusResult(control: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never): AgentToolResult<Details> {
|
|
513
569
|
let nestedWarning: string | undefined;
|
|
514
570
|
try {
|
|
@@ -1141,18 +1197,30 @@ function appendStepToAsyncChain(input: {
|
|
|
1141
1197
|
}
|
|
1142
1198
|
|
|
1143
1199
|
try {
|
|
1200
|
+
const asyncDir = resolved.location.asyncDir;
|
|
1201
|
+
if (!asyncDir) throw new Error(`Run '${resolved.id}' is missing its async directory.`);
|
|
1202
|
+
const runFanoutBudget = readRunFanoutBudgetDescriptor(asyncDir);
|
|
1203
|
+
if (!runFanoutBudget) throw new Error(`Run '${resolved.id}' is missing its run fan-out budget identity.`);
|
|
1204
|
+
const startIndex = (status.chainStepCount ?? status.steps?.length ?? 0) + pendingAppendRequests.reduce((total, request) => total + request.steps.length, 0);
|
|
1205
|
+
const appendPaths = chain.flatMap((step, localIndex) => {
|
|
1206
|
+
const absoluteIndex = startIndex + localIndex;
|
|
1207
|
+
if (isDynamicParallelStep(step) || "checkpoint" in step) return [];
|
|
1208
|
+
if (isParallelStep(step)) return step.parallel.map((_, itemIndex) => `chain[${absoluteIndex}].parallel[${itemIndex}]`);
|
|
1209
|
+
return [`chain[${absoluteIndex}]`];
|
|
1210
|
+
});
|
|
1144
1211
|
const result = enqueueChainAppendRequest({
|
|
1145
|
-
asyncDir
|
|
1212
|
+
asyncDir,
|
|
1146
1213
|
runId: resolved.id,
|
|
1147
1214
|
steps: built.steps,
|
|
1215
|
+
admit: (persist) => claimRunFanoutBatchWithCommit(runFanoutBudget, appendPaths, persist),
|
|
1148
1216
|
});
|
|
1149
1217
|
const stepText = built.steps.length === 1 ? "step" : "steps";
|
|
1150
1218
|
return {
|
|
1151
1219
|
content: [{
|
|
1152
1220
|
type: "text",
|
|
1153
|
-
text: `Append queued for chain run ${resolved.id}: ${built.steps.length} ${stepText}. It becomes eligible after the chain's already-queued steps finish. Pending appends: ${result.pendingCount}
|
|
1221
|
+
text: `Append queued for chain run ${resolved.id}: ${built.steps.length} ${stepText}. It becomes eligible after the chain's already-queued steps finish. Pending appends: ${result.pendingCount}.${result.bookkeepingError ? ` Bookkeeping warning: ${result.bookkeepingError}` : ""}`,
|
|
1154
1222
|
}],
|
|
1155
|
-
details: { mode: "management", results: [], asyncId: resolved.id, asyncDir
|
|
1223
|
+
details: { mode: "management", results: [], asyncId: resolved.id, asyncDir },
|
|
1156
1224
|
};
|
|
1157
1225
|
} catch (error) {
|
|
1158
1226
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1487,6 +1555,20 @@ async function resumeAsyncRun(input: {
|
|
|
1487
1555
|
};
|
|
1488
1556
|
}
|
|
1489
1557
|
const runId = randomUUID().slice(0, 8);
|
|
1558
|
+
const topLevelResume = depth === 0 && !resolveInheritedNestedRouteFromEnv() && !input.params.workflowParentRunId;
|
|
1559
|
+
let activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined;
|
|
1560
|
+
try {
|
|
1561
|
+
activeAsyncCapacity = topLevelResume ? acquireActiveAsyncCapacity({
|
|
1562
|
+
sessionId: input.deps.state.currentSessionId!,
|
|
1563
|
+
limit: resolveMaxActiveAsyncRunsPerSession(input.deps.config.maxActiveAsyncRunsPerSession),
|
|
1564
|
+
runId,
|
|
1565
|
+
kind: "runner",
|
|
1566
|
+
asyncDir: path.join(DIRS.async, runId),
|
|
1567
|
+
}, { liveWorkflowRunIds: new Set(input.deps.state.workflowControllers?.keys() ?? []) }) : undefined;
|
|
1568
|
+
} catch (error) {
|
|
1569
|
+
if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "chain", results: [], activeAsyncCapacity: error.snapshot } };
|
|
1570
|
+
throw error;
|
|
1571
|
+
}
|
|
1490
1572
|
const artifactConfig: ArtifactConfig = omitUndefinedProperties({ ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir });
|
|
1491
1573
|
const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
1492
1574
|
const contextPolicy = resolveExplicitContextPolicy(input.params);
|
|
@@ -1538,9 +1620,14 @@ async function resumeAsyncRun(input: {
|
|
|
1538
1620
|
controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
|
|
1539
1621
|
childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
1540
1622
|
globalConcurrencyLimit: input.deps.config.globalConcurrencyLimit,
|
|
1623
|
+
runFanoutBudget: createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
|
|
1541
1624
|
capabilityCeiling: intersectSubagentCapabilityCeilings("capabilityCeiling" in target ? target.capabilityCeiling : undefined, resolveCurrentSubagentCapabilityCeiling(input.deps.state.currentSessionId)),
|
|
1625
|
+
activeAsyncCapacity,
|
|
1542
1626
|
}));
|
|
1543
|
-
if (result.isError)
|
|
1627
|
+
if (result.isError) {
|
|
1628
|
+
activeAsyncCapacity?.rollback();
|
|
1629
|
+
return result;
|
|
1630
|
+
}
|
|
1544
1631
|
const attachedId = result.details.asyncId ?? runId;
|
|
1545
1632
|
const lines = [
|
|
1546
1633
|
`Attached async subagent ${target.runId} as the first step of a new chain.`,
|
|
@@ -1559,12 +1646,38 @@ async function resumeAsyncRun(input: {
|
|
|
1559
1646
|
if (!revivalSessionFile) {
|
|
1560
1647
|
return { content: [{ type: "text", text: `Async child '${target.runId}' has no persisted session file to resume.` }], isError: true, details: { mode: "management", results: [] } };
|
|
1561
1648
|
}
|
|
1649
|
+
if (target.source === "async" && !recoveryDescriptor) {
|
|
1650
|
+
return { content: [{ type: "text", text: `Async child '${target.runId}' is missing its required run fan-out recovery identity. Start a new run instead.` }], isError: true, details: { mode: "management", results: [] } };
|
|
1651
|
+
}
|
|
1562
1652
|
const runId = randomUUID().slice(0, 8);
|
|
1653
|
+
const topLevelResume = depth === 0 && !resolveInheritedNestedRouteFromEnv() && !input.params.workflowParentRunId;
|
|
1654
|
+
let activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined;
|
|
1655
|
+
try {
|
|
1656
|
+
activeAsyncCapacity = !topLevelResume ? undefined : target.source === "async"
|
|
1657
|
+
? transferActiveAsyncCapacity({
|
|
1658
|
+
sessionId: input.deps.state.currentSessionId!,
|
|
1659
|
+
limit: resolveMaxActiveAsyncRunsPerSession(input.deps.config.maxActiveAsyncRunsPerSession),
|
|
1660
|
+
sourceRunId: target.runId,
|
|
1661
|
+
runId,
|
|
1662
|
+
asyncDir: path.join(DIRS.async, runId),
|
|
1663
|
+
})
|
|
1664
|
+
: acquireActiveAsyncCapacity({
|
|
1665
|
+
sessionId: input.deps.state.currentSessionId!,
|
|
1666
|
+
limit: resolveMaxActiveAsyncRunsPerSession(input.deps.config.maxActiveAsyncRunsPerSession),
|
|
1667
|
+
runId,
|
|
1668
|
+
kind: "runner",
|
|
1669
|
+
asyncDir: path.join(DIRS.async, runId),
|
|
1670
|
+
});
|
|
1671
|
+
} catch (error) {
|
|
1672
|
+
if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "single", results: [], activeAsyncCapacity: error.snapshot } };
|
|
1673
|
+
return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "single", results: [] } };
|
|
1674
|
+
}
|
|
1563
1675
|
const recoveryAgentConfig = recoveryDescriptor ? applySteeringRecoveryAgentConfig(agentConfig, recoveryDescriptor) : agentConfig;
|
|
1564
1676
|
const artifactConfig: ArtifactConfig = recoveryDescriptor?.artifactConfig ?? omitUndefinedProperties({ ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir });
|
|
1565
1677
|
const artifactsDir = recoveryDescriptor?.artifactsDir ?? getArtifactsDir(parentSessionFile, effectiveCwd, artifactConfig.dir);
|
|
1566
1678
|
const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
1567
1679
|
const parentModel = input.parentModel;
|
|
1680
|
+
const revivalAsyncDir = path.join(DIRS.async, runId);
|
|
1568
1681
|
const result = executeAsyncSingle(runId, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
|
|
1569
1682
|
agent: target.agent,
|
|
1570
1683
|
task: buildRevivedAsyncTask(target as Parameters<typeof buildRevivedAsyncTask>[0], effectiveFollowUp),
|
|
@@ -1618,10 +1731,28 @@ async function resumeAsyncRun(input: {
|
|
|
1618
1731
|
...(input.params.turnBudget !== undefined ? { turnBudget: input.params.turnBudget } : {}),
|
|
1619
1732
|
...(input.params.toolBudget !== undefined ? { toolBudget: input.params.toolBudget } : {}),
|
|
1620
1733
|
capabilityCeiling: intersectSubagentCapabilityCeilings("capabilityCeiling" in target ? target.capabilityCeiling : undefined, recoveryDescriptor?.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(input.deps.state.currentSessionId)),
|
|
1734
|
+
runFanoutBudget: recoveryDescriptor?.runFanoutBudget ?? createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
|
|
1621
1735
|
parentWorkflowRunId: input.params.workflowParentRunId,
|
|
1622
1736
|
workflowKey: input.params.workflowKey,
|
|
1737
|
+
activeAsyncCapacity,
|
|
1623
1738
|
}));
|
|
1624
|
-
if (result.isError)
|
|
1739
|
+
if (result.isError) {
|
|
1740
|
+
const startedStatus = readStatus(revivalAsyncDir);
|
|
1741
|
+
if (input.params.workflowParentRunId !== undefined && startedStatus?.runId === runId && startedStatus.processTerminal?.runnerProcessInstanceId) {
|
|
1742
|
+
return {
|
|
1743
|
+
...result,
|
|
1744
|
+
details: {
|
|
1745
|
+
...result.details,
|
|
1746
|
+
runId,
|
|
1747
|
+
asyncId: runId,
|
|
1748
|
+
asyncDir: revivalAsyncDir,
|
|
1749
|
+
...(target.launchContractDigest ? { sourceLaunchContractDigest: target.launchContractDigest } : {}),
|
|
1750
|
+
},
|
|
1751
|
+
};
|
|
1752
|
+
}
|
|
1753
|
+
activeAsyncCapacity?.rollback();
|
|
1754
|
+
return result;
|
|
1755
|
+
}
|
|
1625
1756
|
for (const brief of queuedBriefs) fs.rmSync(brief.path, { force: true });
|
|
1626
1757
|
if (queuedBriefs.length > 0 && sourceAsyncDir) {
|
|
1627
1758
|
const sourceStatus = readStatus(sourceAsyncDir);
|
|
@@ -2117,6 +2248,27 @@ export const DEFAULT_FOREGROUND_TIMEOUT_MS = 30 * 60 * 1000;
|
|
|
2117
2248
|
// stay unbounded as a whole, while each runner child has its own deadline.
|
|
2118
2249
|
export { DEFAULT_ASYNC_TIMEOUT_MS };
|
|
2119
2250
|
|
|
2251
|
+
/**
|
|
2252
|
+
* Maximum delay a Node.js timer accepts. Values above the 32-bit signed integer
|
|
2253
|
+
* ceiling overflow `setTimeout`, which silently clamps the delay to ~1ms and
|
|
2254
|
+
* fires almost immediately — so a run configured with a larger deadline would
|
|
2255
|
+
* terminate right away while reporting the long duration. Any timeout destined
|
|
2256
|
+
* for a timer must stay within this bound.
|
|
2257
|
+
*/
|
|
2258
|
+
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
2259
|
+
|
|
2260
|
+
/**
|
|
2261
|
+
* Resolve the optional global default runtime deadline from extension config
|
|
2262
|
+
* (`config.timeoutMs`). Returns undefined for unset or invalid values so callers
|
|
2263
|
+
* fall back to the built-in defaults. "Invalid" covers non-positive-integer
|
|
2264
|
+
* values and values above `MAX_TIMER_DELAY_MS`; the latter would overflow the
|
|
2265
|
+
* Node.js timer and expire the run almost immediately instead of running long.
|
|
2266
|
+
*/
|
|
2267
|
+
export function resolveConfigDefaultTimeoutMs(raw: unknown): number | undefined {
|
|
2268
|
+
if (typeof raw !== "number" || !Number.isInteger(raw) || raw <= 0 || raw > MAX_TIMER_DELAY_MS) return undefined;
|
|
2269
|
+
return raw;
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2120
2272
|
export function resolveForegroundTimeout(params: SubagentParamsLike, defaultTimeoutMs?: number): { timeoutMs?: number; error?: string } {
|
|
2121
2273
|
const rawTimeout = params.timeoutMs;
|
|
2122
2274
|
const rawMaxRuntime = params.maxRuntimeMs;
|
|
@@ -2140,14 +2292,18 @@ export function resolveForegroundTimeout(params: SubagentParamsLike, defaultTime
|
|
|
2140
2292
|
* Resolve the effective launch timeout for a single-agent run, applying the
|
|
2141
2293
|
* async/foreground default when neither the caller nor the agent set one.
|
|
2142
2294
|
*
|
|
2143
|
-
*
|
|
2144
|
-
*
|
|
2145
|
-
*
|
|
2146
|
-
*
|
|
2295
|
+
* A global config default (`config.timeoutMs`, passed as `configDefaultTimeoutMs`)
|
|
2296
|
+
* replaces the built-in 30-minute backstop wherever a concrete default is applied.
|
|
2297
|
+
* The async default is deliberately applied only to plain single-agent launches.
|
|
2298
|
+
* Composite launches keep their top-level execution unbounded when no timeout is
|
|
2299
|
+
* set — even with a config default — while their runner children resolve separate
|
|
2300
|
+
* deadlines. Exported so the executor wiring is directly testable.
|
|
2147
2301
|
*/
|
|
2148
|
-
export function resolveSingleAgentLaunchTimeout(params: SubagentParamsLike, async: boolean): { timeoutMs?: number; error?: string } {
|
|
2302
|
+
export function resolveSingleAgentLaunchTimeout(params: SubagentParamsLike, async: boolean, configDefaultTimeoutMs?: number): { timeoutMs?: number; error?: string } {
|
|
2149
2303
|
const isComposite = (params.chain?.length ?? 0) > 0 || (params.tasks?.length ?? 0) > 0 || params.workflowScript !== undefined;
|
|
2150
|
-
const
|
|
2304
|
+
const foregroundDefault = configDefaultTimeoutMs ?? DEFAULT_FOREGROUND_TIMEOUT_MS;
|
|
2305
|
+
const asyncSingleDefault = configDefaultTimeoutMs ?? DEFAULT_ASYNC_TIMEOUT_MS;
|
|
2306
|
+
const defaultTimeoutMs = !async ? foregroundDefault : isComposite ? undefined : asyncSingleDefault;
|
|
2151
2307
|
return resolveForegroundTimeout(params, defaultTimeoutMs);
|
|
2152
2308
|
}
|
|
2153
2309
|
|
|
@@ -2560,8 +2716,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2560
2716
|
details: { mode: "single" as const, results: [] },
|
|
2561
2717
|
};
|
|
2562
2718
|
}
|
|
2563
|
-
const
|
|
2564
|
-
const id = requestedWorkflowChildAsyncId && path.basename(requestedWorkflowChildAsyncId) === requestedWorkflowChildAsyncId ? requestedWorkflowChildAsyncId : randomUUID();
|
|
2719
|
+
const id = data.asyncRunId;
|
|
2565
2720
|
const parentModel = data.parentModel;
|
|
2566
2721
|
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
2567
2722
|
pi: deps.pi,
|
|
@@ -2615,6 +2770,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2615
2770
|
shareEnabled,
|
|
2616
2771
|
sessionRoot,
|
|
2617
2772
|
chainSkills: [],
|
|
2773
|
+
activeAsyncCapacity: data.activeAsyncCapacity,
|
|
2618
2774
|
sessionFilesByFlatIndex: params.tasks.map((task, index) => sessionFileForTask(task.agent, index, task.model)),
|
|
2619
2775
|
thinkingOverridesByFlatIndex: params.tasks.map((task, index) => thinkingOverrideForTask(task.agent, index, task.model)),
|
|
2620
2776
|
contextForAgent: contextPolicy.contextForAgent,
|
|
@@ -2634,6 +2790,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2634
2790
|
usageBudget: data.usageBudget,
|
|
2635
2791
|
configToolBudget: data.configToolBudget,
|
|
2636
2792
|
capabilityCeiling: data.capabilityCeiling,
|
|
2793
|
+
runFanoutBudget: data.runFanoutBudget,
|
|
2637
2794
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2638
2795
|
parentWorkflowRunId: params.workflowParentRunId,
|
|
2639
2796
|
workflowKey: params.workflowKey,
|
|
@@ -2659,6 +2816,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2659
2816
|
shareEnabled,
|
|
2660
2817
|
sessionRoot,
|
|
2661
2818
|
chainSkills,
|
|
2819
|
+
activeAsyncCapacity: data.activeAsyncCapacity,
|
|
2662
2820
|
sessionFilesByFlatIndex: collectChainSessionFiles(chain, sessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
2663
2821
|
thinkingOverridesByFlatIndex: collectChainThinkingOverrides(chain, thinkingOverrideForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
2664
2822
|
contextForAgent: contextPolicy.contextForAgent,
|
|
@@ -2679,6 +2837,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2679
2837
|
usageBudget: data.usageBudget,
|
|
2680
2838
|
configToolBudget: data.configToolBudget,
|
|
2681
2839
|
capabilityCeiling: data.capabilityCeiling,
|
|
2840
|
+
runFanoutBudget: data.runFanoutBudget,
|
|
2682
2841
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2683
2842
|
parentWorkflowRunId: params.workflowParentRunId,
|
|
2684
2843
|
workflowKey: params.workflowKey,
|
|
@@ -2700,7 +2859,12 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2700
2859
|
const normalizedSkills = normalizeSkillInput(params.skill);
|
|
2701
2860
|
const skills = normalizedSkills === false ? [] : normalizedSkills;
|
|
2702
2861
|
const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, a.maxSubagentDepth);
|
|
2703
|
-
const
|
|
2862
|
+
const externalRunnerWithoutExplicitModel = a.runner?.type === "external-cli"
|
|
2863
|
+
&& params.model === undefined
|
|
2864
|
+
&& (a.model === undefined || (a.modelSource?.type === "subagents.defaultModel" && a.model === a.modelSource.model));
|
|
2865
|
+
const modelOverride = externalRunnerWithoutExplicitModel
|
|
2866
|
+
? undefined
|
|
2867
|
+
: resolveEffectiveSubagentModel(params.model as string | undefined, a.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope });
|
|
2704
2868
|
return executeAsyncSingle(id, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
|
|
2705
2869
|
agent: params.agent!,
|
|
2706
2870
|
task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
|
|
@@ -2713,6 +2877,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2713
2877
|
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
2714
2878
|
artifactConfig,
|
|
2715
2879
|
shareEnabled,
|
|
2880
|
+
activeAsyncCapacity: data.activeAsyncCapacity,
|
|
2716
2881
|
sessionRoot,
|
|
2717
2882
|
sessionFile: sessionFileForTask(params.agent!, 0, modelOverride),
|
|
2718
2883
|
context: contextPolicy.contextForAgent(params.agent!),
|
|
@@ -2722,7 +2887,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2722
2887
|
...(params.reads !== undefined ? { reads: params.reads } : {}),
|
|
2723
2888
|
outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
|
|
2724
2889
|
modelOverride,
|
|
2725
|
-
thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride),
|
|
2890
|
+
thinkingOverride: externalRunnerWithoutExplicitModel ? undefined : thinkingOverrideForTask(params.agent!, 0, modelOverride),
|
|
2726
2891
|
maxSubagentDepth,
|
|
2727
2892
|
waitToolEnabled: deps.waitToolEnabled,
|
|
2728
2893
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
@@ -2741,6 +2906,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2741
2906
|
usageBudget: data.usageBudget,
|
|
2742
2907
|
configToolBudget: data.configToolBudget,
|
|
2743
2908
|
capabilityCeiling: data.capabilityCeiling,
|
|
2909
|
+
runFanoutBudget: data.runFanoutBudget,
|
|
2744
2910
|
parentWorkflowRunId: params.workflowParentRunId,
|
|
2745
2911
|
workflowKey: params.workflowKey,
|
|
2746
2912
|
}));
|
|
@@ -2831,6 +2997,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2831
2997
|
permissions: deps.config.permissions,
|
|
2832
2998
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2833
2999
|
capabilityCeiling: data.capabilityCeiling,
|
|
3000
|
+
runFanoutBudget: data.runFanoutBudget,
|
|
2834
3001
|
}));
|
|
2835
3002
|
|
|
2836
3003
|
if (chainResult.requestedAsync) {
|
|
@@ -2841,7 +3008,9 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2841
3008
|
details: { mode: "chain" as const, results: [] },
|
|
2842
3009
|
};
|
|
2843
3010
|
}
|
|
2844
|
-
const id =
|
|
3011
|
+
const id = data.asyncRunId;
|
|
3012
|
+
const capacity = acquireClarifiedBackgroundCapacity(data, deps, "chain", id);
|
|
3013
|
+
if (capacity.errorResult) return capacity.errorResult;
|
|
2845
3014
|
const parentModel = data.parentModel;
|
|
2846
3015
|
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
2847
3016
|
pi: deps.pi,
|
|
@@ -2857,42 +3026,48 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2857
3026
|
const rawAsyncChain = chainResult.requestedAsync.chain;
|
|
2858
3027
|
const asyncChain = wrapChainTasksForFork(rawAsyncChain, contextPolicy);
|
|
2859
3028
|
const firstAgent = firstChainAgent(rawAsyncChain);
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
3029
|
+
try {
|
|
3030
|
+
return executeAsyncChain(id, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
|
|
3031
|
+
chain: asyncChain,
|
|
3032
|
+
task: params.task,
|
|
3033
|
+
goal: resolveAsyncEventGoal(params.task, rawAsyncChain, firstAgent ? shouldForkAgent(contextPolicy, firstAgent) : false),
|
|
3034
|
+
agents,
|
|
3035
|
+
ctx: asyncCtx,
|
|
3036
|
+
availableModels: ctx.modelRegistry.getAvailable().map(toModelInfo),
|
|
3037
|
+
cwd: effectiveCwd,
|
|
3038
|
+
maxOutput: params.maxOutput,
|
|
3039
|
+
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
3040
|
+
artifactConfig,
|
|
3041
|
+
shareEnabled,
|
|
3042
|
+
sessionRoot,
|
|
3043
|
+
chainSkills: chainResult.requestedAsync.chainSkills,
|
|
3044
|
+
activeAsyncCapacity: capacity.activeAsyncCapacity,
|
|
3045
|
+
sessionFilesByFlatIndex: collectChainSessionFiles(asyncChain, sessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
3046
|
+
thinkingOverridesByFlatIndex: collectChainThinkingOverrides(asyncChain, thinkingOverrideForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
3047
|
+
contextForAgent: contextPolicy.contextForAgent,
|
|
3048
|
+
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
3049
|
+
maxSubagentDepth: currentMaxSubagentDepth,
|
|
3050
|
+
waitToolEnabled: deps.waitToolEnabled,
|
|
3051
|
+
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
3052
|
+
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
3053
|
+
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
3054
|
+
controlConfig,
|
|
3055
|
+
agentContract: params.agentContract,
|
|
3056
|
+
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
3057
|
+
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
3058
|
+
nestedRoute: data.nestedRoute,
|
|
3059
|
+
timeoutMs: data.timeoutMs,
|
|
3060
|
+
turnBudget: data.turnBudget,
|
|
3061
|
+
toolBudget: data.toolBudget,
|
|
3062
|
+
usageBudget: data.usageBudget,
|
|
3063
|
+
configToolBudget: data.configToolBudget,
|
|
3064
|
+
capabilityCeiling: data.capabilityCeiling,
|
|
3065
|
+
runFanoutBudget: data.runFanoutBudget,
|
|
3066
|
+
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
3067
|
+
}));
|
|
3068
|
+
} finally {
|
|
3069
|
+
finalizeClarifiedBackgroundCapacity(data, deps, capacity.activeAsyncCapacity);
|
|
3070
|
+
}
|
|
2896
3071
|
}
|
|
2897
3072
|
|
|
2898
3073
|
const rawChainDetails = chainResult.details ? compactOptional<Details>({ ...chainResult.details, runId, timeoutMs: data.timeoutMs }) : undefined;
|
|
@@ -2937,6 +3112,7 @@ interface ForegroundParallelRunInput {
|
|
|
2937
3112
|
intercomEvents: IntercomEventBus;
|
|
2938
3113
|
parentSessionId: string | null;
|
|
2939
3114
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
3115
|
+
runFanoutBudget: RunFanoutBudgetDescriptor;
|
|
2940
3116
|
signal: AbortSignal;
|
|
2941
3117
|
runId: string;
|
|
2942
3118
|
sessionDirForIndex: (idx?: number) => string | undefined;
|
|
@@ -2986,6 +3162,48 @@ function buildParallelModeError(message: string): AgentToolResult<Details> {
|
|
|
2986
3162
|
};
|
|
2987
3163
|
}
|
|
2988
3164
|
|
|
3165
|
+
function acquireClarifiedBackgroundCapacity(
|
|
3166
|
+
data: ExecutionContextData,
|
|
3167
|
+
deps: ExecutorDeps,
|
|
3168
|
+
mode: "single" | "parallel" | "chain",
|
|
3169
|
+
asyncRunId: string,
|
|
3170
|
+
): { activeAsyncCapacity?: ActiveAsyncCapacityHandle; errorResult?: AgentToolResult<Details> } {
|
|
3171
|
+
if (!data.topLevelAsyncCapacityEligible || !data.parentSessionId) return {};
|
|
3172
|
+
const liveWorkflowRunIds = new Set(deps.state.workflowControllers?.keys() ?? []);
|
|
3173
|
+
const activeLimit = resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession);
|
|
3174
|
+
try {
|
|
3175
|
+
const activeAsyncCapacity = acquireActiveAsyncCapacity({
|
|
3176
|
+
sessionId: data.parentSessionId,
|
|
3177
|
+
limit: activeLimit,
|
|
3178
|
+
runId: asyncRunId,
|
|
3179
|
+
kind: "runner",
|
|
3180
|
+
asyncDir: path.join(DIRS.async, asyncRunId),
|
|
3181
|
+
}, { liveWorkflowRunIds });
|
|
3182
|
+
deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(data.parentSessionId, activeLimit, { liveWorkflowRunIds });
|
|
3183
|
+
return { activeAsyncCapacity };
|
|
3184
|
+
} catch (error) {
|
|
3185
|
+
if (error instanceof ActiveAsyncCapacityError) {
|
|
3186
|
+
deps.state.activeAsyncCapacity = error.snapshot;
|
|
3187
|
+
return { errorResult: { content: [{ type: "text", text: error.message }], isError: true, details: { mode, results: [], activeAsyncCapacity: error.snapshot } } };
|
|
3188
|
+
}
|
|
3189
|
+
throw error;
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
|
|
3193
|
+
function finalizeClarifiedBackgroundCapacity(
|
|
3194
|
+
data: ExecutionContextData,
|
|
3195
|
+
deps: ExecutorDeps,
|
|
3196
|
+
activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined,
|
|
3197
|
+
): void {
|
|
3198
|
+
if (!activeAsyncCapacity || !data.parentSessionId) return;
|
|
3199
|
+
if (!activeAsyncCapacity.owner.runnerStartedAt) activeAsyncCapacity.rollback();
|
|
3200
|
+
deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(
|
|
3201
|
+
data.parentSessionId,
|
|
3202
|
+
resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession),
|
|
3203
|
+
{ liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) },
|
|
3204
|
+
);
|
|
3205
|
+
}
|
|
3206
|
+
|
|
2989
3207
|
function createParallelWorktreeSetup(
|
|
2990
3208
|
enabled: boolean | undefined,
|
|
2991
3209
|
cwd: string,
|
|
@@ -3174,7 +3392,33 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
3174
3392
|
beginForegroundChild(input.foregroundControl, {
|
|
3175
3393
|
index,
|
|
3176
3394
|
agent: task.agent,
|
|
3177
|
-
|
|
3395
|
+
authoredTask: input.taskDescriptions[index] ?? "",
|
|
3396
|
+
effectivePrompt: taskText,
|
|
3397
|
+
cwd: taskCwd,
|
|
3398
|
+
...(outputPath ? { outputPath } : {}),
|
|
3399
|
+
rerun: { params: compactOptional<Record<string, unknown>>({
|
|
3400
|
+
agent: task.agent,
|
|
3401
|
+
task: input.taskDescriptions[index] ?? "",
|
|
3402
|
+
async: false,
|
|
3403
|
+
cwd: input.worktreeSetup ? undefined : taskCwd,
|
|
3404
|
+
worktree: input.worktreeSetup ? true : false,
|
|
3405
|
+
context: input.contextPolicy.contextForAgent(task.agent),
|
|
3406
|
+
share: input.shareEnabled || undefined,
|
|
3407
|
+
maxOutput: input.maxOutput,
|
|
3408
|
+
timeoutMs: input.timeoutMs,
|
|
3409
|
+
turnBudget: input.turnBudget,
|
|
3410
|
+
...(model ? { model } : {}),
|
|
3411
|
+
...(effectiveSkills !== undefined ? { skill: effectiveSkills } : {}),
|
|
3412
|
+
...(outputPath ? { output: outputPath } : {}),
|
|
3413
|
+
...(behavior?.outputMode !== undefined ? { outputMode: behavior.outputMode } : {}),
|
|
3414
|
+
...(behavior?.reads !== undefined ? { reads: behavior.reads } : {}),
|
|
3415
|
+
...(behavior?.progress !== undefined ? { progress: behavior.progress } : {}),
|
|
3416
|
+
...(task.toolBudget !== undefined ? { toolBudget: task.toolBudget } : {}),
|
|
3417
|
+
...(task.outputSchema !== undefined ? { outputSchema: task.outputSchema } : {}),
|
|
3418
|
+
...(task.acceptance !== undefined ? { acceptance: task.acceptance } : {}),
|
|
3419
|
+
...(task.agentContract !== undefined || input.agentContract !== undefined ? { agentContract: task.agentContract ?? input.agentContract } : {}),
|
|
3420
|
+
}) },
|
|
3421
|
+
description: `${task.agent} child`,
|
|
3178
3422
|
...(model ? { model } : {}),
|
|
3179
3423
|
...(thinking ? { thinking } : {}),
|
|
3180
3424
|
interrupt: () => {
|
|
@@ -3191,8 +3435,10 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
3191
3435
|
const result = await runSync(input.ctx.cwd, input.agents, task.agent, taskText, compactOptional<Parameters<typeof runSync>[4]>({
|
|
3192
3436
|
permissions: input.permissions,
|
|
3193
3437
|
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
3438
|
+
llmIntentArbiter: createTaskMutationArbiter(input.ctx),
|
|
3194
3439
|
...workflowForegroundSteeringLaunchOptions(input.foregroundControl, index),
|
|
3195
3440
|
context: input.contextPolicy.contextForAgent(task.agent),
|
|
3441
|
+
runFanoutBudget: { ...input.runFanoutBudget, parentPath: `${input.runFanoutBudget.parentPath ? `${input.runFanoutBudget.parentPath}/` : ""}tasks[${index}]` },
|
|
3196
3442
|
cwd: taskCwd,
|
|
3197
3443
|
signal: input.signal,
|
|
3198
3444
|
interruptSignal: interruptController.signal,
|
|
@@ -3237,6 +3483,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
3237
3483
|
agentContract: task.agentContract ?? input.agentContract,
|
|
3238
3484
|
acceptance: task.acceptance,
|
|
3239
3485
|
acceptanceContext: { mode: "parallel" },
|
|
3486
|
+
onEffectivePrompt: input.foregroundControl ? (prompt) => updateLiveEffectivePrompt(input.foregroundControl!, index, prompt) : undefined,
|
|
3240
3487
|
timeoutMs: input.timeoutMs,
|
|
3241
3488
|
deadlineAt: input.deadlineAt,
|
|
3242
3489
|
turnBudget: input.turnBudget,
|
|
@@ -3420,7 +3667,9 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3420
3667
|
details: { mode: "parallel" as const, results: [] },
|
|
3421
3668
|
};
|
|
3422
3669
|
}
|
|
3423
|
-
const id =
|
|
3670
|
+
const id = data.asyncRunId;
|
|
3671
|
+
const capacity = acquireClarifiedBackgroundCapacity(data, deps, "parallel", id);
|
|
3672
|
+
if (capacity.errorResult) return capacity.errorResult;
|
|
3424
3673
|
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
3425
3674
|
pi: deps.pi,
|
|
3426
3675
|
cwd: ctx.cwd,
|
|
@@ -3451,40 +3700,46 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3451
3700
|
...(t.agentContract !== undefined ? { agentContract: t.agentContract } : {}),
|
|
3452
3701
|
});
|
|
3453
3702
|
});
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3703
|
+
try {
|
|
3704
|
+
return executeAsyncChain(id, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
|
|
3705
|
+
chain: [compactOptional<ParallelStep>({ parallel: parallelTasks, concurrency: parallelConcurrency, worktree: params.worktree })],
|
|
3706
|
+
resultMode: "parallel",
|
|
3707
|
+
goal: taskTexts[0] ?? "",
|
|
3708
|
+
agents,
|
|
3709
|
+
ctx: asyncCtx,
|
|
3710
|
+
availableModels,
|
|
3711
|
+
cwd: effectiveCwd,
|
|
3712
|
+
maxOutput: params.maxOutput,
|
|
3713
|
+
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
3714
|
+
artifactConfig,
|
|
3715
|
+
shareEnabled,
|
|
3716
|
+
sessionRoot,
|
|
3717
|
+
chainSkills: [],
|
|
3718
|
+
activeAsyncCapacity: capacity.activeAsyncCapacity,
|
|
3719
|
+
sessionFilesByFlatIndex: tasks.map((task, index) => sessionFileForTask(task.agent, index, modelOverrides[index])),
|
|
3720
|
+
thinkingOverridesByFlatIndex: tasks.map((task, index) => thinkingOverrideForTask(task.agent, index, modelOverrides[index])),
|
|
3721
|
+
contextForAgent: contextPolicy.contextForAgent,
|
|
3722
|
+
maxSubagentDepth: currentMaxSubagentDepth,
|
|
3723
|
+
waitToolEnabled: deps.waitToolEnabled,
|
|
3724
|
+
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
3725
|
+
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
3726
|
+
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
3727
|
+
controlConfig,
|
|
3728
|
+
agentContract: params.agentContract,
|
|
3729
|
+
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
3730
|
+
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
3731
|
+
nestedRoute: data.nestedRoute,
|
|
3732
|
+
timeoutMs: data.timeoutMs,
|
|
3733
|
+
turnBudget: data.turnBudget,
|
|
3734
|
+
usageBudget: data.usageBudget,
|
|
3735
|
+
toolBudget: data.toolBudget,
|
|
3736
|
+
configToolBudget: data.configToolBudget,
|
|
3737
|
+
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
3738
|
+
runFanoutBudget: data.runFanoutBudget,
|
|
3739
|
+
}));
|
|
3740
|
+
} finally {
|
|
3741
|
+
finalizeClarifiedBackgroundCapacity(data, deps, capacity.activeAsyncCapacity);
|
|
3742
|
+
}
|
|
3488
3743
|
}
|
|
3489
3744
|
}
|
|
3490
3745
|
|
|
@@ -3563,6 +3818,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3563
3818
|
intercomEvents: deps.pi.events,
|
|
3564
3819
|
parentSessionId: data.parentSessionId,
|
|
3565
3820
|
capabilityCeiling: data.capabilityCeiling,
|
|
3821
|
+
runFanoutBudget: data.runFanoutBudget,
|
|
3566
3822
|
signal,
|
|
3567
3823
|
runId,
|
|
3568
3824
|
sessionDirForIndex,
|
|
@@ -3797,7 +4053,9 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3797
4053
|
details: { mode: "single" as const, results: [] },
|
|
3798
4054
|
};
|
|
3799
4055
|
}
|
|
3800
|
-
const id =
|
|
4056
|
+
const id = data.asyncRunId;
|
|
4057
|
+
const capacity = acquireClarifiedBackgroundCapacity(data, deps, "single", id);
|
|
4058
|
+
if (capacity.errorResult) return capacity.errorResult;
|
|
3801
4059
|
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
3802
4060
|
pi: deps.pi,
|
|
3803
4061
|
cwd: ctx.cwd,
|
|
@@ -3809,49 +4067,56 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3809
4067
|
interactive: ctx.hasUI,
|
|
3810
4068
|
permissions: deps.config.permissions,
|
|
3811
4069
|
});
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
4070
|
+
try {
|
|
4071
|
+
return executeAsyncSingle(id, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
|
|
4072
|
+
agent: params.agent!,
|
|
4073
|
+
task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(task) : task,
|
|
4074
|
+
goal: task,
|
|
4075
|
+
agentConfig,
|
|
4076
|
+
ctx: asyncCtx,
|
|
4077
|
+
availableModels,
|
|
4078
|
+
cwd: effectiveCwd,
|
|
4079
|
+
maxOutput: params.maxOutput,
|
|
4080
|
+
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
4081
|
+
artifactConfig,
|
|
4082
|
+
shareEnabled,
|
|
4083
|
+
activeAsyncCapacity: capacity.activeAsyncCapacity,
|
|
4084
|
+
sessionRoot,
|
|
4085
|
+
sessionFile: sessionFileForTask(params.agent!, 0, modelOverride),
|
|
4086
|
+
context: contextPolicy.contextForAgent(params.agent!),
|
|
4087
|
+
skills: skillOverride === false ? [] : skillOverride,
|
|
4088
|
+
output: effectiveOutput,
|
|
4089
|
+
outputMode: effectiveOutputMode,
|
|
4090
|
+
...(readsOverride !== undefined ? { reads: readsOverride } : {}),
|
|
4091
|
+
outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
|
|
4092
|
+
modelOverride,
|
|
4093
|
+
thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride),
|
|
4094
|
+
maxSubagentDepth,
|
|
4095
|
+
waitToolEnabled: deps.waitToolEnabled,
|
|
4096
|
+
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
4097
|
+
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
4098
|
+
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
4099
|
+
controlConfig,
|
|
4100
|
+
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
4101
|
+
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
4102
|
+
nestedRoute: data.nestedRoute,
|
|
4103
|
+
agentContract: params.agentContract,
|
|
4104
|
+
structuredOutputSchema: params.outputSchema,
|
|
4105
|
+
acceptance: params.acceptance,
|
|
4106
|
+
timeoutMs: data.timeoutMs,
|
|
4107
|
+
turnBudget: data.turnBudget,
|
|
4108
|
+
toolBudget: effectiveToolBudget.toolBudget,
|
|
4109
|
+
usageBudget: data.usageBudget,
|
|
4110
|
+
allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
|
|
4111
|
+
runFanoutBudget: data.runFanoutBudget,
|
|
4112
|
+
}));
|
|
4113
|
+
} finally {
|
|
4114
|
+
finalizeClarifiedBackgroundCapacity(data, deps, capacity.activeAsyncCapacity);
|
|
4115
|
+
}
|
|
3852
4116
|
}
|
|
3853
4117
|
}
|
|
3854
4118
|
|
|
4119
|
+
const authoredTask = task;
|
|
3855
4120
|
if (shouldForkAgent(contextPolicy, params.agent!)) {
|
|
3856
4121
|
task = wrapForkTask(task);
|
|
3857
4122
|
}
|
|
@@ -3888,6 +4153,11 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3888
4153
|
beginForegroundChild(foregroundControl, omitUndefinedProperties({
|
|
3889
4154
|
index: 0,
|
|
3890
4155
|
agent: params.agent!,
|
|
4156
|
+
authoredTask,
|
|
4157
|
+
effectivePrompt: task,
|
|
4158
|
+
cwd: effectiveCwd,
|
|
4159
|
+
outputPath,
|
|
4160
|
+
rerun: { params: { ...params, task: authoredTask, async: params.async ?? false } },
|
|
3891
4161
|
description: foregroundControl.description,
|
|
3892
4162
|
...(modelOverride ? { model: modelOverride } : {}),
|
|
3893
4163
|
...(thinking ? { thinking } : {}),
|
|
@@ -3913,8 +4183,10 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3913
4183
|
r = await runSync(ctx.cwd, agents, params.agent!, task, compactOptional<Parameters<typeof runSync>[4]>({
|
|
3914
4184
|
permissions: deps.config.permissions,
|
|
3915
4185
|
parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
|
|
4186
|
+
llmIntentArbiter: createTaskMutationArbiter(ctx),
|
|
3916
4187
|
...workflowForegroundSteeringLaunchOptions(foregroundControl, 0),
|
|
3917
4188
|
context: data.contextPolicy.contextForAgent(params.agent!),
|
|
4189
|
+
runFanoutBudget: params.runFanoutAdmitted ? data.runFanoutBudget : { ...data.runFanoutBudget, parentPath: `${data.runFanoutBudget.parentPath ? `${data.runFanoutBudget.parentPath}/` : ""}single` },
|
|
3918
4190
|
cwd: effectiveCwd,
|
|
3919
4191
|
signal,
|
|
3920
4192
|
interruptSignal: interruptController.signal,
|
|
@@ -3948,6 +4220,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3948
4220
|
agentContract: params.agentContract,
|
|
3949
4221
|
acceptance: params.acceptance,
|
|
3950
4222
|
acceptanceContext: { mode: "single" },
|
|
4223
|
+
onEffectivePrompt: foregroundControl ? (prompt) => updateLiveEffectivePrompt(foregroundControl, 0, prompt) : undefined,
|
|
3951
4224
|
onDetachReady: (detach) => {
|
|
3952
4225
|
detachForeground = detach;
|
|
3953
4226
|
},
|
|
@@ -3996,12 +4269,14 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3996
4269
|
if (r.artifactPaths) allArtifactPaths.push(r.artifactPaths);
|
|
3997
4270
|
|
|
3998
4271
|
const fullOutput = getSingleResultOutput(r);
|
|
4272
|
+
const preserveRejectedSavedOutput = r.acceptance?.explicit && r.acceptance.status === "rejected" && r.savedOutputPath !== undefined;
|
|
3999
4273
|
const finalizedOutput = finalizeSingleOutput(omitUndefinedProperties({
|
|
4000
4274
|
fullOutput,
|
|
4001
4275
|
truncatedOutput: r.truncation?.text,
|
|
4002
4276
|
outputPath,
|
|
4003
4277
|
outputMode: r.outputMode,
|
|
4004
4278
|
exitCode: r.exitCode,
|
|
4279
|
+
preserveSavedOutput: preserveRejectedSavedOutput,
|
|
4005
4280
|
savedPath: r.savedOutputPath,
|
|
4006
4281
|
outputReference: r.outputReference,
|
|
4007
4282
|
saveError: r.outputSaveError,
|
|
@@ -4092,7 +4367,7 @@ function duplicateSubagentCallResult(params: SubagentParamsLike): AgentToolResul
|
|
|
4092
4367
|
};
|
|
4093
4368
|
}
|
|
4094
4369
|
|
|
4095
|
-
const workflowLaunchObservers = new WeakMap<object, (launch: { agent: string; sessionFile?: string }) => void>();
|
|
4370
|
+
const workflowLaunchObservers = new WeakMap<object, (launch: { agent: string; sessionFile?: string; async: boolean; runId?: string }) => void>();
|
|
4096
4371
|
|
|
4097
4372
|
function recordMissionWorkflowChild(
|
|
4098
4373
|
binding: MissionLaunchBinding | undefined,
|
|
@@ -4101,8 +4376,9 @@ function recordMissionWorkflowChild(
|
|
|
4101
4376
|
update: Omit<MissionWorkflowChildUpdate, "workflowRunId" | "key">,
|
|
4102
4377
|
): void {
|
|
4103
4378
|
if (!binding) return;
|
|
4379
|
+
const { task: _task, ...durableUpdate } = update;
|
|
4104
4380
|
try {
|
|
4105
|
-
updateMission(binding.location, binding.missionId, { upsertWorkflowChildren: [{ workflowRunId, key, ...
|
|
4381
|
+
updateMission(binding.location, binding.missionId, { upsertWorkflowChildren: [{ workflowRunId, key, ...durableUpdate }] });
|
|
4106
4382
|
} catch (error) {
|
|
4107
4383
|
console.warn(`[pi-subagents] Failed to record mission workflow child '${key}': ${error instanceof Error ? error.message : String(error)}`);
|
|
4108
4384
|
}
|
|
@@ -4184,7 +4460,7 @@ export function prepareWorkflowLaunchParams(
|
|
|
4184
4460
|
childParams: Record<string, unknown>,
|
|
4185
4461
|
parentWorkflowRunId: string,
|
|
4186
4462
|
workflowKey: string,
|
|
4187
|
-
options: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean } = {},
|
|
4463
|
+
options: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor } = {},
|
|
4188
4464
|
): SubagentParamsLike {
|
|
4189
4465
|
if (typeof childParams.resume === "string") {
|
|
4190
4466
|
if (childParams.gate !== undefined || workflowDefaults.gate !== undefined) {
|
|
@@ -4212,6 +4488,7 @@ export function prepareWorkflowLaunchParams(
|
|
|
4212
4488
|
...(options.missionDetached ? { mission: false } : {}),
|
|
4213
4489
|
workflowParentRunId: parentWorkflowRunId,
|
|
4214
4490
|
workflowKey,
|
|
4491
|
+
...(options.runFanoutBudget ? { runFanoutBudget: { ...options.runFanoutBudget, parentPath: `${options.runFanoutBudget.parentPath ? `${options.runFanoutBudget.parentPath}/` : ""}workflow[${workflowKey}]` } } : {}),
|
|
4215
4492
|
...(options.suppressRoutineResultIntercom ? { suppressRoutineResultIntercom: true } : {}),
|
|
4216
4493
|
} as SubagentParamsLike;
|
|
4217
4494
|
const normalizedGate = normalizeGateParams(launchParams);
|
|
@@ -4359,6 +4636,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4359
4636
|
} {
|
|
4360
4637
|
const delegatedThinkingOverrides = new WeakMap<object, AgentConfig["thinking"]>();
|
|
4361
4638
|
const delegatedZeroToolBudgets = new WeakSet<object>();
|
|
4639
|
+
const delegatedExecutions = new WeakSet<object>();
|
|
4362
4640
|
const warnedArtifactPackageDirs = new Set<string>();
|
|
4363
4641
|
const scheduledOwnerExecutors = new Map<string, ReturnType<typeof createSubagentExecutor>>();
|
|
4364
4642
|
const execute = async (
|
|
@@ -4372,6 +4650,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4372
4650
|
const workflowLaunchObserver = workflowLaunchObservers.get(params);
|
|
4373
4651
|
const delegatedThinkingOverride = delegatedThinkingOverrides.get(params);
|
|
4374
4652
|
const allowZeroToolBudget = delegatedZeroToolBudgets.has(params);
|
|
4653
|
+
const delegatedExecution = delegatedExecutions.has(params);
|
|
4375
4654
|
if (!preserveActiveSession) deps.state.baseCwd = ctx.cwd;
|
|
4376
4655
|
deps.state.foregroundRuns ??= new Map();
|
|
4377
4656
|
deps.state.foregroundControls ??= new Map();
|
|
@@ -4382,7 +4661,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4382
4661
|
const normalizedAction = typeof requestParams.action === "string" ? requestParams.action.trim() : requestParams.action;
|
|
4383
4662
|
if (requestParams.workflowScript !== undefined && normalizedAction === undefined) {
|
|
4384
4663
|
const parentCwd = ctx.cwd;
|
|
4385
|
-
const timeout = requestParams.timeoutMs ?? requestParams.maxRuntimeMs ?? (requestParams.async === false ? DEFAULT_FOREGROUND_TIMEOUT_MS : undefined);
|
|
4664
|
+
const timeout = requestParams.timeoutMs ?? requestParams.maxRuntimeMs ?? (requestParams.async === false ? resolveConfigDefaultTimeoutMs(deps.config.timeoutMs) ?? DEFAULT_FOREGROUND_TIMEOUT_MS : undefined);
|
|
4386
4665
|
const workflowUsageBudget = validateUsageBudgetConfig(requestParams.usageBudget ?? deps.config.usageBudget, requestParams.usageBudget ? "usageBudget" : "config.usageBudget");
|
|
4387
4666
|
if (workflowUsageBudget.error) return buildRequestedModeError(requestParams, workflowUsageBudget.error);
|
|
4388
4667
|
const workflowCwd = resolveRequestedCwd(parentCwd, requestParams.cwd);
|
|
@@ -4393,11 +4672,45 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4393
4672
|
const explicitMission = requestParams.missionId !== undefined || requestParams.mission !== undefined;
|
|
4394
4673
|
const autoMission = !explicitMission;
|
|
4395
4674
|
const workflowPreview = autoMission ? previewSimpleWorkflowRun(requestParams.workflowScript) : undefined;
|
|
4396
|
-
const previewTask = workflowPreview?.task?.trim() || undefined;
|
|
4397
4675
|
const previewAgent = workflowPreview?.agent?.trim() || undefined;
|
|
4398
4676
|
const scriptFirstLine = requestParams.workflowScript.split(/\r?\n/).map((line) => line.trim()).find(Boolean) || "Workflow";
|
|
4399
4677
|
const boundedScriptPreview = scriptFirstLine.length > 100 ? `${scriptFirstLine.slice(0, 97)}...` : scriptFirstLine;
|
|
4400
|
-
const derivedObjective =
|
|
4678
|
+
const derivedObjective = previewAgent ? `Workflow: ${previewAgent}` : boundedScriptPreview;
|
|
4679
|
+
const workflowDepth = checkSubagentDepth(deps.config.maxSubagentDepth).depth;
|
|
4680
|
+
const asyncWorkflow = requestParams.async !== false;
|
|
4681
|
+
const topLevelAsyncWorkflow = asyncWorkflow
|
|
4682
|
+
&& workflowDepth === 0
|
|
4683
|
+
&& !resolveInheritedNestedRouteFromEnv()
|
|
4684
|
+
&& !requestParams.workflowParentRunId;
|
|
4685
|
+
const workflowRunId = asyncWorkflow ? randomUUID() : undefined;
|
|
4686
|
+
let workflowCapacity: ActiveAsyncCapacityHandle | undefined;
|
|
4687
|
+
if (workflowRunId && topLevelAsyncWorkflow) {
|
|
4688
|
+
const currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
4689
|
+
try {
|
|
4690
|
+
workflowCapacity = acquireActiveAsyncCapacity({
|
|
4691
|
+
sessionId: currentSessionId,
|
|
4692
|
+
limit: resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession),
|
|
4693
|
+
runId: workflowRunId,
|
|
4694
|
+
kind: "workflow",
|
|
4695
|
+
asyncDir: path.join(DIRS.async, workflowRunId),
|
|
4696
|
+
}, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) });
|
|
4697
|
+
} catch (error) {
|
|
4698
|
+
if (error instanceof ActiveAsyncCapacityError) {
|
|
4699
|
+
deps.state.activeAsyncCapacity = error.snapshot;
|
|
4700
|
+
return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "workflow", results: [], activeAsyncCapacity: error.snapshot } };
|
|
4701
|
+
}
|
|
4702
|
+
throw error;
|
|
4703
|
+
}
|
|
4704
|
+
}
|
|
4705
|
+
let workflowFanoutBudget: RunFanoutBudgetDescriptor;
|
|
4706
|
+
try {
|
|
4707
|
+
workflowFanoutBudget = requestParams.runFanoutBudget
|
|
4708
|
+
?? decodeRunFanoutBudgetDescriptor(process.env[RUN_FANOUT_BUDGET_ENV])
|
|
4709
|
+
?? createRunFanoutBudget(_id, resolveMaxSubagentSpawnsPerRun(deps.config.maxSubagentSpawnsPerRun));
|
|
4710
|
+
} catch (error) {
|
|
4711
|
+
workflowCapacity?.rollback();
|
|
4712
|
+
return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "workflow", results: [] } };
|
|
4713
|
+
}
|
|
4401
4714
|
let missionBinding: MissionLaunchBinding | undefined;
|
|
4402
4715
|
let missionWarning: string | undefined;
|
|
4403
4716
|
try {
|
|
@@ -4408,22 +4721,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4408
4721
|
ownerSessionId: resolveCurrentSessionId(ctx.sessionManager),
|
|
4409
4722
|
});
|
|
4410
4723
|
} catch (error) {
|
|
4411
|
-
if (explicitMission)
|
|
4724
|
+
if (explicitMission) {
|
|
4725
|
+
workflowCapacity?.rollback();
|
|
4726
|
+
return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "workflow", results: [] } };
|
|
4727
|
+
}
|
|
4412
4728
|
missionWarning = `Mission tracking unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
4413
4729
|
}
|
|
4414
|
-
let shouldPatchMissionObjective = autoMission && previewTask === undefined && missionBinding !== undefined;
|
|
4415
|
-
const patchMissionObjective = (task: unknown): void => {
|
|
4416
|
-
if (!shouldPatchMissionObjective || !missionBinding || typeof task !== "string" || !task.trim()) return;
|
|
4417
|
-
shouldPatchMissionObjective = false;
|
|
4418
|
-
const objective = task.trim();
|
|
4419
|
-
const firstLine = objective.split(/\r?\n/, 1)[0]?.trim() || objective;
|
|
4420
|
-
const title = firstLine.length > 100 ? `${firstLine.slice(0, 97)}...` : firstLine;
|
|
4421
|
-
try {
|
|
4422
|
-
updateMission(missionBinding.location, missionBinding.missionId, { title, objective });
|
|
4423
|
-
} catch (error) {
|
|
4424
|
-
console.warn(`[pi-subagents] Failed to update automatic mission objective: ${error instanceof Error ? error.message : String(error)}`);
|
|
4425
|
-
}
|
|
4426
|
-
};
|
|
4427
4730
|
const detachWorkflowChildMissions = autoMission || missionBinding !== undefined || requestParams.mission === false;
|
|
4428
4731
|
const workflowState = missionBinding ? createMissionWorkflowState(missionBinding.location, missionBinding.missionId) : undefined;
|
|
4429
4732
|
const attachWorkflowMission = (result: AgentToolResult<Details>): AgentToolResult<Details> => {
|
|
@@ -4437,20 +4740,27 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4437
4740
|
: { ...result, details: { ...result.details, missionWarning: warning } };
|
|
4438
4741
|
}
|
|
4439
4742
|
};
|
|
4440
|
-
if (
|
|
4743
|
+
if (workflowRunId) {
|
|
4441
4744
|
const toolCallId = _id;
|
|
4442
|
-
const workflowRunId = randomUUID();
|
|
4443
4745
|
const asyncDir = path.join(DIRS.async, workflowRunId);
|
|
4444
4746
|
const resultPath = path.join(DIRS.results, `${workflowRunId}.json`);
|
|
4445
4747
|
const statusPath = path.join(asyncDir, "status.json");
|
|
4446
4748
|
const eventsPath = path.join(asyncDir, "events.jsonl");
|
|
4447
4749
|
const startedAt = Date.now();
|
|
4448
4750
|
const currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
4449
|
-
|
|
4450
|
-
|
|
4751
|
+
try {
|
|
4752
|
+
fs.mkdirSync(asyncDir, { recursive: true });
|
|
4753
|
+
writeRunFanoutBudgetDescriptor(asyncDir, workflowFanoutBudget);
|
|
4754
|
+
fs.mkdirSync(DIRS.results, { recursive: true });
|
|
4755
|
+
} catch (error) {
|
|
4756
|
+
workflowCapacity?.rollback();
|
|
4757
|
+
return { content: [{ type: "text", text: `Failed to create async workflow storage: ${error instanceof Error ? error.message : String(error)}` }], isError: true, details: { mode: "workflow", results: [] } };
|
|
4758
|
+
}
|
|
4451
4759
|
const controller = new AbortController();
|
|
4452
4760
|
deps.state.workflowControllers ??= new Map();
|
|
4453
4761
|
deps.state.workflowControllers.set(workflowRunId, controller);
|
|
4762
|
+
workflowCapacity?.markWorkflowStarted();
|
|
4763
|
+
if (workflowCapacity) deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers.keys()) });
|
|
4454
4764
|
let status: AsyncStatus = {
|
|
4455
4765
|
runId: workflowRunId,
|
|
4456
4766
|
toolCallId,
|
|
@@ -4464,6 +4774,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4464
4774
|
pid: process.pid,
|
|
4465
4775
|
steps: [],
|
|
4466
4776
|
workflow: { trace: [], emits: [], console: [] },
|
|
4777
|
+
runFanoutBudget: getRunFanoutBudgetSnapshot(workflowFanoutBudget),
|
|
4467
4778
|
};
|
|
4468
4779
|
const appendWorkflowEvent = (event: Record<string, unknown>) => fs.appendFileSync(eventsPath, `${JSON.stringify({ ts: Date.now(), runId: workflowRunId, ...event })}\n`, "utf-8");
|
|
4469
4780
|
let indexedState: AsyncStatus["state"] | undefined;
|
|
@@ -4550,12 +4861,23 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4550
4861
|
if (existing) {
|
|
4551
4862
|
existing.status = mapped;
|
|
4552
4863
|
if (entry.agent) existing.agent = entry.agent;
|
|
4864
|
+
if (entry.runId) existing.runId = entry.runId;
|
|
4865
|
+
if (entry.state === "failed" && !entry.runId && existing.async === undefined) existing.async = false;
|
|
4553
4866
|
if (entry.error === undefined) delete existing.error;
|
|
4554
4867
|
else existing.error = entry.error;
|
|
4555
4868
|
if (entry.durationMs === undefined) delete existing.durationMs;
|
|
4556
4869
|
else existing.durationMs = entry.durationMs;
|
|
4557
4870
|
} else {
|
|
4558
|
-
const step: NonNullable<AsyncStatus["steps"]>[number] = {
|
|
4871
|
+
const step: NonNullable<AsyncStatus["steps"]>[number] = {
|
|
4872
|
+
agent: entry.agent ?? entry.key,
|
|
4873
|
+
label: entry.key,
|
|
4874
|
+
workflowKey: entry.key,
|
|
4875
|
+
parentWorkflowRunId: workflowRunId,
|
|
4876
|
+
status: mapped,
|
|
4877
|
+
startedAt: Date.now(),
|
|
4878
|
+
...(entry.runId ? { runId: entry.runId } : {}),
|
|
4879
|
+
...(entry.state === "failed" && !entry.runId ? { async: false } : {}),
|
|
4880
|
+
};
|
|
4559
4881
|
status.steps?.push(step);
|
|
4560
4882
|
workflowSteps.set(entry.key, step);
|
|
4561
4883
|
}
|
|
@@ -4574,30 +4896,32 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4574
4896
|
prompts: workflowPrompts,
|
|
4575
4897
|
...(workflowState ? { state: workflowState } : {}),
|
|
4576
4898
|
onTrace: updateTrace,
|
|
4899
|
+
admit: (calls) => {
|
|
4900
|
+
status.runFanoutBudget = claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
|
|
4901
|
+
persist();
|
|
4902
|
+
},
|
|
4577
4903
|
onEmit: (emits) => {
|
|
4578
4904
|
// Each emit is validated at the host boundary in runWorkflowScript before onEmit fires.
|
|
4579
4905
|
status.workflow = { ...(status.workflow ?? { trace: [], console: [] }), emits };
|
|
4580
4906
|
persist();
|
|
4581
4907
|
appendWorkflowEvent({ type: "subagent.workflow.emit", value: emits.at(-1) });
|
|
4582
4908
|
},
|
|
4583
|
-
launch: async (key, childParams, workflowSignal) => {
|
|
4909
|
+
launch: async (key, childParams, workflowSignal, admission) => {
|
|
4584
4910
|
if (workflowUsageBudget.budget && childParams.async === true) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, "workflow usageBudget does not support async runs.run launches."));
|
|
4585
4911
|
const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
|
|
4586
4912
|
if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)));
|
|
4587
|
-
patchMissionObjective(childParams.task);
|
|
4588
4913
|
const childPhase = typeof childParams.phase === "string" && childParams.phase.trim() ? childParams.phase.trim() : undefined;
|
|
4589
4914
|
const childLabel = typeof childParams.label === "string" && childParams.label.trim() ? childParams.label.trim() : undefined;
|
|
4590
4915
|
recordMissionWorkflowChild(missionBinding, workflowRunId, key, {
|
|
4591
4916
|
status: "running",
|
|
4592
4917
|
...(typeof childParams.agent === "string" && childParams.agent.trim() ? { agent: childParams.agent.trim() } : {}),
|
|
4593
|
-
...(typeof childParams.task === "string" && childParams.task.trim() ? { task: childParams.task.trim() } : {}),
|
|
4594
4918
|
...(childLabel ? { label: childLabel } : {}),
|
|
4595
4919
|
...(childPhase ? { phase: childPhase } : {}),
|
|
4596
4920
|
heartbeat: { status: "running", ...(childPhase ? { phase: childPhase } : {}) },
|
|
4597
4921
|
});
|
|
4598
4922
|
const result = await runMissionWorkflowChild(missionBinding, workflowRunId, key, childPhase, () => {
|
|
4599
4923
|
const childRequest = bindMissionWorkflowChildAsyncLaunch(
|
|
4600
|
-
prepareWorkflowLaunchParams(workflowChildDefaults, childParams, workflowRunId, key, { missionDetached: detachWorkflowChildMissions }),
|
|
4924
|
+
{ ...prepareWorkflowLaunchParams(workflowChildDefaults, childParams, workflowRunId, key, { missionDetached: detachWorkflowChildMissions, runFanoutBudget: workflowFanoutBudget }), runFanoutAdmitted: admission.admitted },
|
|
4601
4925
|
missionBinding,
|
|
4602
4926
|
deps.asyncByDefault,
|
|
4603
4927
|
);
|
|
@@ -4606,6 +4930,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4606
4930
|
if (step) {
|
|
4607
4931
|
step.agent = launch.agent;
|
|
4608
4932
|
step.sessionFile = launch.sessionFile;
|
|
4933
|
+
step.async = launch.async;
|
|
4934
|
+
if (launch.runId) step.runId = launch.runId;
|
|
4609
4935
|
persist();
|
|
4610
4936
|
}
|
|
4611
4937
|
recordMissionWorkflowChild(missionBinding, workflowRunId, key, { status: "running", agent: launch.agent, ...(launch.sessionFile ? { sessionPath: launch.sessionFile } : {}) });
|
|
@@ -4637,8 +4963,13 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4637
4963
|
}, ctx, preserveActiveSession);
|
|
4638
4964
|
});
|
|
4639
4965
|
workflowResults.push(...result.details.results);
|
|
4640
|
-
if (result.details.asyncDir && missionBinding) writeMissionAsyncBinding(result.details.asyncDir, missionBinding);
|
|
4641
4966
|
const child = workflowChildResult(key, result);
|
|
4967
|
+
const step = status.steps?.find((candidate) => candidate.workflowKey === key);
|
|
4968
|
+
if (step) {
|
|
4969
|
+
step.async = Boolean(result.details.asyncId || result.details.asyncDir);
|
|
4970
|
+
if (child.runId) step.runId = child.runId;
|
|
4971
|
+
}
|
|
4972
|
+
if (result.details.asyncDir && missionBinding) writeMissionAsyncBinding(result.details.asyncDir, missionBinding);
|
|
4642
4973
|
const childStatus = missionWorkflowChildStatus(result);
|
|
4643
4974
|
recordMissionWorkflowChild(missionBinding, workflowRunId, key, {
|
|
4644
4975
|
status: childStatus,
|
|
@@ -4674,12 +5005,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4674
5005
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, error: status.error });
|
|
4675
5006
|
} finally {
|
|
4676
5007
|
deps.state.workflowControllers?.delete(workflowRunId);
|
|
5008
|
+
deps.state.activeAsyncCapacity = workflowCapacity?.reconcile(new Set(deps.state.workflowControllers?.keys() ?? []))
|
|
5009
|
+
?? deps.state.activeAsyncCapacity;
|
|
4677
5010
|
}
|
|
4678
5011
|
});
|
|
4679
|
-
return attachWorkflowMission({
|
|
5012
|
+
return attachWorkflowMission(withRunFanoutBudget({
|
|
4680
5013
|
content: [{ type: "text", text: formatAsyncStartedMessage(`Async workflow [${workflowRunId}]`, ctx.hasUI === true) }],
|
|
4681
|
-
details: { mode: "workflow", runId: workflowRunId, toolCallId, asyncId: workflowRunId, asyncDir, results: [], chatProgress },
|
|
4682
|
-
});
|
|
5014
|
+
details: { mode: "workflow", runId: workflowRunId, toolCallId, asyncId: workflowRunId, asyncDir, results: [], chatProgress, ...(deps.state.activeAsyncCapacity ? { activeAsyncCapacity: deps.state.activeAsyncCapacity } : {}) },
|
|
5015
|
+
}, workflowFanoutBudget));
|
|
4683
5016
|
}
|
|
4684
5017
|
const { workflowScript: _workflowScript, action: _action, agent: _agent, task: _task, resume: _resume, tasks: _tasks, chain: _chain, concurrency: _concurrency, async: _async, foregroundOnly: _foregroundOnly, clarify: _clarify, timeoutMs: _timeoutMs, maxRuntimeMs: _maxRuntimeMs, usageBudget: _usageBudget, chatProgress: _chatProgress, missionId: _missionId, mission: _mission, ...workflowChildDefaults } = requestParams;
|
|
4685
5018
|
const workflowResults: SingleResult[] = [];
|
|
@@ -4699,28 +5032,29 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4699
5032
|
liveWorkflow = { ...liveWorkflow, trace };
|
|
4700
5033
|
sendWorkflowProgress();
|
|
4701
5034
|
},
|
|
5035
|
+
admit: (calls) => {
|
|
5036
|
+
claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
|
|
5037
|
+
},
|
|
4702
5038
|
onEmit: (emits) => {
|
|
4703
5039
|
liveWorkflow = { ...liveWorkflow, emits };
|
|
4704
5040
|
sendWorkflowProgress();
|
|
4705
5041
|
},
|
|
4706
|
-
launch: async (key, childParams, workflowSignal) => {
|
|
5042
|
+
launch: async (key, childParams, workflowSignal, admission) => {
|
|
4707
5043
|
if (workflowUsageBudget.budget && childParams.async === true) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, "workflow usageBudget does not support async runs.run launches."));
|
|
4708
5044
|
const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
|
|
4709
5045
|
if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)));
|
|
4710
|
-
patchMissionObjective(childParams.task);
|
|
4711
5046
|
const childPhase = typeof childParams.phase === "string" && childParams.phase.trim() ? childParams.phase.trim() : undefined;
|
|
4712
5047
|
const childLabel = typeof childParams.label === "string" && childParams.label.trim() ? childParams.label.trim() : undefined;
|
|
4713
5048
|
recordMissionWorkflowChild(missionBinding, _id, key, {
|
|
4714
5049
|
status: "running",
|
|
4715
5050
|
...(typeof childParams.agent === "string" && childParams.agent.trim() ? { agent: childParams.agent.trim() } : {}),
|
|
4716
|
-
...(typeof childParams.task === "string" && childParams.task.trim() ? { task: childParams.task.trim() } : {}),
|
|
4717
5051
|
...(childLabel ? { label: childLabel } : {}),
|
|
4718
5052
|
...(childPhase ? { phase: childPhase } : {}),
|
|
4719
5053
|
heartbeat: { status: "running", ...(childPhase ? { phase: childPhase } : {}) },
|
|
4720
5054
|
});
|
|
4721
5055
|
const result = await runMissionWorkflowChild(missionBinding, _id, key, childPhase, () => {
|
|
4722
5056
|
const childRequest = bindMissionWorkflowChildAsyncLaunch(
|
|
4723
|
-
prepareWorkflowLaunchParams(workflowChildDefaults, childParams, _id, key, { missionDetached: detachWorkflowChildMissions, suppressRoutineResultIntercom: chatProgress.mode === "live-card" }),
|
|
5057
|
+
{ ...prepareWorkflowLaunchParams(workflowChildDefaults, childParams, _id, key, { missionDetached: detachWorkflowChildMissions, suppressRoutineResultIntercom: chatProgress.mode === "live-card", runFanoutBudget: workflowFanoutBudget }), runFanoutAdmitted: admission.admitted },
|
|
4724
5058
|
missionBinding,
|
|
4725
5059
|
deps.asyncByDefault,
|
|
4726
5060
|
);
|
|
@@ -4761,10 +5095,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4761
5095
|
if (workflow.emits.length > 0) sections.push(`Emitted:\n${workflow.emits.map(formatWorkflowValue).join("\n")}`);
|
|
4762
5096
|
if (workflow.console.length > 0) sections.push(`Console:\n${workflow.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
|
|
4763
5097
|
if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
|
|
4764
|
-
return attachWorkflowMission({
|
|
5098
|
+
return attachWorkflowMission(withRunFanoutBudget({
|
|
4765
5099
|
content: [{ type: "text", text: sections.join("\n\n") }],
|
|
4766
5100
|
details: compactOptional<Details>({ mode: "workflow", runId: _id, results: workflow.children.flatMap((child) => (child.results ?? []) as SingleResult[]), totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { value: workflow.value, trace: workflow.trace, emits: workflow.emits, console: workflow.console }, chatProgress }),
|
|
4767
|
-
});
|
|
5101
|
+
}, workflowFanoutBudget));
|
|
4768
5102
|
} catch (error) {
|
|
4769
5103
|
const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
|
|
4770
5104
|
const text = error instanceof Error ? error.message : String(error);
|
|
@@ -4773,11 +5107,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4773
5107
|
if (partial.emits.length > 0) sections.push(`Emitted:\n${partial.emits.map(formatWorkflowValue).join("\n")}`);
|
|
4774
5108
|
if (partial.console.length > 0) sections.push(`Console:\n${partial.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
|
|
4775
5109
|
if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
|
|
4776
|
-
return attachWorkflowMission({
|
|
5110
|
+
return attachWorkflowMission(withRunFanoutBudget({
|
|
4777
5111
|
content: [{ type: "text", text: sections.join("\n\n") }],
|
|
4778
5112
|
isError: true,
|
|
4779
5113
|
details: compactOptional<Details>({ mode: "workflow", runId: _id, results: partial.children.flatMap((child) => (child.results ?? []) as SingleResult[]), totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { trace: partial.trace, emits: partial.emits, console: partial.console }, chatProgress }),
|
|
4780
|
-
});
|
|
5114
|
+
}, workflowFanoutBudget));
|
|
4781
5115
|
}
|
|
4782
5116
|
}
|
|
4783
5117
|
const directParams = prepareWorkflowChildParams(requestParams);
|
|
@@ -5015,6 +5349,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5015
5349
|
if (!sessionError) sessionError = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
5016
5350
|
}
|
|
5017
5351
|
const spawnBudget = getSpawnBudgetSnapshot(deps.state, deps.config, currentSessionId);
|
|
5352
|
+
const activeAsyncCapacity = currentSessionId
|
|
5353
|
+
? getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) })
|
|
5354
|
+
: { used: 0, limit: resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession) ?? 0 };
|
|
5355
|
+
deps.state.activeAsyncCapacity = activeAsyncCapacity;
|
|
5018
5356
|
return {
|
|
5019
5357
|
content: [{
|
|
5020
5358
|
type: "text",
|
|
@@ -5031,7 +5369,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5031
5369
|
expandTilde: deps.expandTilde,
|
|
5032
5370
|
})),
|
|
5033
5371
|
}],
|
|
5034
|
-
details: { mode: "management", results: [], spawnBudget },
|
|
5372
|
+
details: { mode: "management", results: [], spawnBudget, activeAsyncCapacity },
|
|
5035
5373
|
};
|
|
5036
5374
|
}
|
|
5037
5375
|
if (action === "status") {
|
|
@@ -5448,7 +5786,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5448
5786
|
const runId = randomUUID().slice(0, 8);
|
|
5449
5787
|
const inheritedNestedRoute = resolveInheritedNestedRouteFromEnv();
|
|
5450
5788
|
const nestedParentAddress = inheritedNestedRoute ? resolveNestedParentAddressFromEnv() : undefined;
|
|
5451
|
-
const nestedRoute = inheritedNestedRoute ?? createNestedRoute(runId);
|
|
5452
5789
|
const shareEnabled = effectiveParams.share === true;
|
|
5453
5790
|
const hasChain = (effectiveParams.chain?.length ?? 0) > 0;
|
|
5454
5791
|
const hasTasks = (effectiveParams.tasks?.length ?? 0) > 0;
|
|
@@ -5532,9 +5869,50 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5532
5869
|
const foregroundTimeout = resolveSingleAgentLaunchTimeout(
|
|
5533
5870
|
effectiveParams,
|
|
5534
5871
|
effectiveAsync,
|
|
5872
|
+
resolveConfigDefaultTimeoutMs(deps.config.timeoutMs),
|
|
5535
5873
|
);
|
|
5536
5874
|
if (foregroundTimeout.error) return buildRequestedModeError(effectiveParams, foregroundTimeout.error);
|
|
5537
5875
|
const controlConfig = resolveControlConfig(deps.config.control, effectiveParams.control);
|
|
5876
|
+
const requestedWorkflowChildAsyncId = typeof effectiveParams.workflowChildAsyncId === "string" ? effectiveParams.workflowChildAsyncId.trim() : "";
|
|
5877
|
+
const asyncRunId = requestedWorkflowChildAsyncId && path.basename(requestedWorkflowChildAsyncId) === requestedWorkflowChildAsyncId
|
|
5878
|
+
? requestedWorkflowChildAsyncId
|
|
5879
|
+
: randomUUID();
|
|
5880
|
+
const topLevelAsyncCapacityEligible = depth === 0 && !inheritedNestedRoute && !effectiveParams.workflowParentRunId;
|
|
5881
|
+
const topLevelAsync = effectiveAsync && topLevelAsyncCapacityEligible;
|
|
5882
|
+
let activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined;
|
|
5883
|
+
if (topLevelAsync) {
|
|
5884
|
+
try {
|
|
5885
|
+
const activeLimit = resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession);
|
|
5886
|
+
activeAsyncCapacity = acquireActiveAsyncCapacity({
|
|
5887
|
+
sessionId: requestSessionId,
|
|
5888
|
+
limit: activeLimit,
|
|
5889
|
+
runId: asyncRunId,
|
|
5890
|
+
kind: "runner",
|
|
5891
|
+
asyncDir: path.join(DIRS.async, asyncRunId),
|
|
5892
|
+
}, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) });
|
|
5893
|
+
deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(requestSessionId, activeLimit, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) });
|
|
5894
|
+
} catch (error) {
|
|
5895
|
+
if (error instanceof ActiveAsyncCapacityError) {
|
|
5896
|
+
deps.state.activeAsyncCapacity = error.snapshot;
|
|
5897
|
+
return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: foregroundMode, results: [], activeAsyncCapacity: error.snapshot } };
|
|
5898
|
+
}
|
|
5899
|
+
throw error;
|
|
5900
|
+
}
|
|
5901
|
+
}
|
|
5902
|
+
|
|
5903
|
+
let runFanoutBudget: RunFanoutBudgetDescriptor;
|
|
5904
|
+
try {
|
|
5905
|
+
const inheritedRunFanoutBudget = effectiveParams.runFanoutBudget ? undefined : decodeRunFanoutBudgetDescriptor(process.env[RUN_FANOUT_BUDGET_ENV]);
|
|
5906
|
+
runFanoutBudget = effectiveParams.runFanoutBudget
|
|
5907
|
+
?? (inheritedRunFanoutBudget ? { ...inheritedRunFanoutBudget, parentPath: `${inheritedRunFanoutBudget.parentPath ? `${inheritedRunFanoutBudget.parentPath}/` : ""}${runId}` } : undefined)
|
|
5908
|
+
?? createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(deps.config.maxSubagentSpawnsPerRun));
|
|
5909
|
+
if (!effectiveParams.runFanoutAdmitted) claimRunFanoutBatch(runFanoutBudget, staticRunFanoutPaths(effectiveParams));
|
|
5910
|
+
} catch (error) {
|
|
5911
|
+
activeAsyncCapacity?.rollback();
|
|
5912
|
+
if (error instanceof RunFanoutLimitError) return runFanoutErrorResult(error, foregroundMode);
|
|
5913
|
+
return buildRequestedModeError(effectiveParams, error instanceof Error ? error.message : String(error));
|
|
5914
|
+
}
|
|
5915
|
+
const nestedRoute = inheritedNestedRoute ?? createNestedRoute(runId);
|
|
5538
5916
|
|
|
5539
5917
|
const artifactConfig: ArtifactConfig = omitUndefinedProperties({
|
|
5540
5918
|
...DEFAULT_ARTIFACT_CONFIG,
|
|
@@ -5560,6 +5938,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5560
5938
|
try {
|
|
5561
5939
|
fs.mkdirSync(sessionRoot, { recursive: true });
|
|
5562
5940
|
} catch (error) {
|
|
5941
|
+
activeAsyncCapacity?.rollback();
|
|
5563
5942
|
const message = error instanceof Error ? error.message : String(error);
|
|
5564
5943
|
return toExecutionErrorResult(
|
|
5565
5944
|
effectiveParams,
|
|
@@ -5590,10 +5969,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5590
5969
|
preflightForkSessionsForStaticTasks(effectiveParams, contextPolicy, forkSessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems);
|
|
5591
5970
|
}
|
|
5592
5971
|
} catch (error) {
|
|
5972
|
+
activeAsyncCapacity?.rollback();
|
|
5593
5973
|
return toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary);
|
|
5594
5974
|
}
|
|
5595
5975
|
const chainBindingsError = validateExecutionChainBindings(effectiveParams, deps.config.chain?.dynamicFanout?.maxItems);
|
|
5596
|
-
if (chainBindingsError)
|
|
5976
|
+
if (chainBindingsError) {
|
|
5977
|
+
activeAsyncCapacity?.rollback();
|
|
5978
|
+
return withResolvedContext(chainBindingsError, contextPolicy.contextSummary);
|
|
5979
|
+
}
|
|
5597
5980
|
|
|
5598
5981
|
const onUpdateWithContext = onUpdate
|
|
5599
5982
|
? (r: AgentToolResult<Details>) => onUpdate(withResolvedContext({
|
|
@@ -5613,14 +5996,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5613
5996
|
ownerSessionId: requestSessionId,
|
|
5614
5997
|
});
|
|
5615
5998
|
} catch (error) {
|
|
5616
|
-
if (explicitMission)
|
|
5999
|
+
if (explicitMission) {
|
|
6000
|
+
activeAsyncCapacity?.rollback();
|
|
6001
|
+
return toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary);
|
|
6002
|
+
}
|
|
5617
6003
|
missionWarning = `Mission tracking unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
5618
6004
|
}
|
|
5619
6005
|
|
|
5620
6006
|
const attachMission = (result: AgentToolResult<Details>): AgentToolResult<Details> => {
|
|
5621
6007
|
if (!missionBinding) return missionWarning ? { ...result, details: { ...result.details, missionWarning } } : result;
|
|
5622
6008
|
try {
|
|
5623
|
-
return attachMissionToLaunchResult({ binding: missionBinding, result });
|
|
6009
|
+
return attachMissionToLaunchResult({ binding: delegatedExecution ? { ...missionBinding, announceInContent: false } : missionBinding, result });
|
|
5624
6010
|
} catch (error) {
|
|
5625
6011
|
const warning = `Mission tracking unavailable after launch: ${error instanceof Error ? error.message : String(error)}`;
|
|
5626
6012
|
if (explicitMission) {
|
|
@@ -5641,7 +6027,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5641
6027
|
requestSessionId,
|
|
5642
6028
|
requestedSpawns,
|
|
5643
6029
|
);
|
|
5644
|
-
if (reservation.error)
|
|
6030
|
+
if (reservation.error) {
|
|
6031
|
+
activeAsyncCapacity?.rollback();
|
|
6032
|
+
return attachMission(spawnBudgetErrorResult(reservation.error, foregroundMode));
|
|
6033
|
+
}
|
|
5645
6034
|
|
|
5646
6035
|
const execData: ExecutionContextData = omitUndefinedProperties({
|
|
5647
6036
|
params: effectiveParams,
|
|
@@ -5661,6 +6050,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5661
6050
|
artifactsDir,
|
|
5662
6051
|
backgroundRequestedWhileClarifying,
|
|
5663
6052
|
effectiveAsync,
|
|
6053
|
+
asyncRunId,
|
|
5664
6054
|
controlConfig,
|
|
5665
6055
|
intercomBridge,
|
|
5666
6056
|
nestedRoute,
|
|
@@ -5676,11 +6066,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5676
6066
|
parentSessionId: requestSessionId,
|
|
5677
6067
|
parentPiSessionId: requestPiSessionId,
|
|
5678
6068
|
capabilityCeiling: resolveCurrentSubagentCapabilityCeiling(requestSessionId),
|
|
6069
|
+
runFanoutBudget,
|
|
6070
|
+
topLevelAsyncCapacityEligible,
|
|
6071
|
+
activeAsyncCapacity,
|
|
5679
6072
|
});
|
|
5680
6073
|
|
|
5681
|
-
const foregroundDescription =
|
|
5682
|
-
|
|
5683
|
-
|
|
6074
|
+
const foregroundDescription = selectedAgentNames.length === 1
|
|
6075
|
+
? `${selectedAgentNames[0]} child`
|
|
6076
|
+
: `${selectedAgentNames.length} live children`;
|
|
5684
6077
|
const parentWorkflowStatus = effectiveParams.workflowParentRunId
|
|
5685
6078
|
? readStatus(path.join(DIRS.async, effectiveParams.workflowParentRunId))
|
|
5686
6079
|
: null;
|
|
@@ -5715,6 +6108,37 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5715
6108
|
interrupt: undefined,
|
|
5716
6109
|
});
|
|
5717
6110
|
if (foregroundControl) {
|
|
6111
|
+
foregroundControl.promptAuditRedo = async (index, guidance) => {
|
|
6112
|
+
const audit = getLivePromptAudit(foregroundControl, index);
|
|
6113
|
+
if (!audit?.rerun) return { text: "Redo is not safe for this prompt in this slice.", isError: true };
|
|
6114
|
+
const rewrittenTask = await rewritePromptWithGuidance({
|
|
6115
|
+
ctx,
|
|
6116
|
+
authoredTask: audit.authoredTask,
|
|
6117
|
+
runtimeAdditions: audit.runtimeAdditions,
|
|
6118
|
+
finalEffectivePrompt: audit.finalEffectivePrompt,
|
|
6119
|
+
guidance,
|
|
6120
|
+
signal,
|
|
6121
|
+
});
|
|
6122
|
+
const redoParams = promptAuditRedoParams(audit.rerun.params, rewrittenTask);
|
|
6123
|
+
const previousForegroundId = deps.state.lastForegroundControlId;
|
|
6124
|
+
const launch = execute(randomUUID(), redoParams, signal, undefined, ctx, true);
|
|
6125
|
+
const newRunId = deps.state.lastForegroundControlId && deps.state.lastForegroundControlId !== previousForegroundId
|
|
6126
|
+
? deps.state.lastForegroundControlId
|
|
6127
|
+
: undefined;
|
|
6128
|
+
if (!newRunId) {
|
|
6129
|
+
const result = await launch;
|
|
6130
|
+
return { text: result.content.find((item) => item.type === "text")?.text ?? "Prompt redo could not start.", isError: true };
|
|
6131
|
+
}
|
|
6132
|
+
foregroundControl.supersededByRunId = newRunId;
|
|
6133
|
+
const replacement = deps.state.foregroundControls.get(newRunId);
|
|
6134
|
+
if (replacement) replacement.sourceRunId = foregroundControl.runId;
|
|
6135
|
+
void launch.then((result) => {
|
|
6136
|
+
if (result.isError) console.warn(`[pi-subagents] Prompt redo ${newRunId} failed: ${result.content.find((item) => item.type === "text")?.text ?? "unknown error"}`);
|
|
6137
|
+
}).catch((error) => {
|
|
6138
|
+
console.warn(`[pi-subagents] Prompt redo ${newRunId} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
6139
|
+
});
|
|
6140
|
+
return { text: `Prompt redo started ${newRunId}.` };
|
|
6141
|
+
};
|
|
5718
6142
|
deps.state.foregroundControls.set(runId, foregroundControl);
|
|
5719
6143
|
deps.state.lastForegroundControlId = runId;
|
|
5720
6144
|
}
|
|
@@ -5809,9 +6233,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5809
6233
|
if (workflowLaunchObserver) {
|
|
5810
6234
|
const singleTask = hasTasks && effectiveParams.tasks?.length === 1 ? effectiveParams.tasks[0] : undefined;
|
|
5811
6235
|
const launch = hasSingle
|
|
5812
|
-
? { agent: effectiveParams.agent!, sessionFile: childSessionFileForTask(effectiveParams.agent!, 0, effectiveParams.model) }
|
|
6236
|
+
? { agent: effectiveParams.agent!, sessionFile: childSessionFileForTask(effectiveParams.agent!, 0, effectiveParams.model), async: effectiveAsync, ...(effectiveAsync ? { runId: asyncRunId } : {}) }
|
|
5813
6237
|
: singleTask
|
|
5814
|
-
? { agent: singleTask.agent, sessionFile: childSessionFileForTask(singleTask.agent, 0, singleTask.model) }
|
|
6238
|
+
? { agent: singleTask.agent, sessionFile: childSessionFileForTask(singleTask.agent, 0, singleTask.model), async: effectiveAsync, ...(effectiveAsync ? { runId: asyncRunId } : {}) }
|
|
5815
6239
|
: undefined;
|
|
5816
6240
|
if (launch) {
|
|
5817
6241
|
workflowLaunchObservers.delete(params);
|
|
@@ -5819,31 +6243,33 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5819
6243
|
}
|
|
5820
6244
|
}
|
|
5821
6245
|
const asyncResult = runAsyncPath(execData, deps);
|
|
5822
|
-
if (asyncResult) return attachMission(withResolvedContext(withForkThinkingNotes(asyncResult, forkThinkingDowngrades), contextPolicy.contextSummary));
|
|
6246
|
+
if (asyncResult) return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(asyncResult, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget));
|
|
5823
6247
|
if (foregroundControl) {
|
|
5824
6248
|
writeNestedForegroundEvent("subagent.nested.started");
|
|
5825
6249
|
nestedForegroundStarted = true;
|
|
5826
6250
|
}
|
|
6251
|
+
const runFanoutAnnotateContent = !delegatedExecution;
|
|
5827
6252
|
if (hasChain && effectiveParams.chain) {
|
|
5828
6253
|
const result = await runChainPath(execData, deps);
|
|
5829
6254
|
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
5830
|
-
return attachMission(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary));
|
|
6255
|
+
return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget, { annotateContent: runFanoutAnnotateContent }));
|
|
5831
6256
|
}
|
|
5832
6257
|
if (hasTasks && effectiveParams.tasks) {
|
|
5833
6258
|
const result = await runParallelPath(execData, deps);
|
|
5834
6259
|
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
5835
|
-
return attachMission(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary));
|
|
6260
|
+
return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget, { annotateContent: runFanoutAnnotateContent }));
|
|
5836
6261
|
}
|
|
5837
6262
|
if (hasSingle) {
|
|
5838
6263
|
const result = await runSinglePath(execData, deps);
|
|
5839
6264
|
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
5840
|
-
return attachMission(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary));
|
|
6265
|
+
return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget, { annotateContent: runFanoutAnnotateContent }));
|
|
5841
6266
|
}
|
|
5842
6267
|
} catch (error) {
|
|
5843
6268
|
const errorResult = withForkThinkingNotes(toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary), forkThinkingDowngrades);
|
|
5844
6269
|
if (nestedForegroundStarted) writeNestedForegroundEvent("subagent.nested.completed", errorResult);
|
|
5845
6270
|
return attachMission(errorResult);
|
|
5846
6271
|
} finally {
|
|
6272
|
+
if (activeAsyncCapacity && !activeAsyncCapacity.owner.runnerStartedAt) activeAsyncCapacity.rollback();
|
|
5847
6273
|
if (foregroundControl) {
|
|
5848
6274
|
settleForegroundSchedulingOwner(foregroundControl);
|
|
5849
6275
|
removeForegroundControlIfIdle(deps.state, runId);
|
|
@@ -5912,6 +6338,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5912
6338
|
delete privateParams.delegatedAllowZeroToolBudget;
|
|
5913
6339
|
if (thinkingOverride !== undefined) delegatedThinkingOverrides.set(delegatedParams, thinkingOverride);
|
|
5914
6340
|
if (allowZeroToolBudget) delegatedZeroToolBudgets.add(delegatedParams);
|
|
6341
|
+
delegatedExecutions.add(delegatedParams);
|
|
5915
6342
|
return execute(id, delegatedParams, signal, onUpdate, ctx);
|
|
5916
6343
|
};
|
|
5917
6344
|
|