pi-subagents 0.47.0 → 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 +37 -0
- package/README.md +2 -0
- package/agents/reviewer.md +3 -4
- 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 +16 -0
- package/src/extension/doctor.ts +40 -0
- package/src/extension/index.ts +30 -11
- package/src/extension/public-execution.ts +5 -0
- package/src/extension/rpc.ts +7 -9
- package/src/extension/schemas.ts +4 -4
- package/src/intercom/intercom-bridge.ts +4 -1
- package/src/intercom/native-supervisor-channel.ts +102 -4
- 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 +46 -0
- package/src/runs/background/async-execution.ts +117 -29
- package/src/runs/background/async-job-tracker.ts +279 -134
- package/src/runs/background/async-resume.ts +7 -1
- package/src/runs/background/async-status.ts +33 -4
- package/src/runs/background/chain-append.ts +33 -15
- package/src/runs/background/control-channel.ts +55 -17
- package/src/runs/background/owned-process-tree.ts +104 -0
- package/src/runs/background/process-terminal.ts +17 -3
- package/src/runs/background/result-watcher.ts +87 -6
- package/src/runs/background/run-status.ts +23 -1
- package/src/runs/background/scheduled-runs.ts +23 -0
- package/src/runs/background/stale-run-reconciler.ts +10 -3
- package/src/runs/background/subagent-runner.ts +75 -33
- package/src/runs/foreground/async-dismiss-action.ts +85 -0
- package/src/runs/foreground/async-steering-action.ts +6 -7
- 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 +648 -195
- 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/subagent-prompt-runtime.ts +24 -5
- package/src/runs/shared/task-intent.ts +19 -3
- package/src/runs/shared/worktree.ts +17 -5
- package/src/shared/artifacts.ts +1 -1
- package/src/shared/file-coalescer.ts +9 -0
- package/src/shared/types.ts +97 -1
- 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 +26 -6
|
@@ -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";
|
|
@@ -53,6 +54,8 @@ import {
|
|
|
53
54
|
} from "../../shared/settings.ts";
|
|
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";
|
|
57
|
+
import { updateActiveRunIndex } from "../background/active-run-index.ts";
|
|
58
|
+
import { acquireActiveAsyncCapacity, ActiveAsyncCapacityError, getActiveAsyncCapacitySnapshot, resolveMaxActiveAsyncRunsPerSession, transferActiveAsyncCapacity, type ActiveAsyncCapacityHandle } from "../background/active-async-capacity.ts";
|
|
56
59
|
import { isScheduledRunAction, type ScheduledRunAction } from "../background/scheduled-runs.ts";
|
|
57
60
|
import { enqueueChainAppendRequest, readPendingChainAppendRequests, runnerStepOutputNames } from "../background/chain-append.ts";
|
|
58
61
|
import { ChainOutputValidationError, validateChainOutputBindingsWithContext } from "../shared/chain-outputs.ts";
|
|
@@ -63,6 +66,7 @@ import { applyIntercomBridgeToAgent, INTERCOM_BRIDGE_MARKER, resolveIntercomBrid
|
|
|
63
66
|
import { formatControlIntercomMessage, formatControlNoticeMessage, resolveControlConfig, shouldNotifyControlEvent } from "../shared/subagent-control.ts";
|
|
64
67
|
import { resolveTurnBudgetConfig } from "../shared/turn-budget.ts";
|
|
65
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";
|
|
66
70
|
import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
|
|
67
71
|
import { usageBudgetExceededMessage, usageBudgetState, validateUsageBudgetConfig } from "../shared/usage-budget.ts";
|
|
68
72
|
import { intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
@@ -70,6 +74,7 @@ import { isAgentContractV1 } from "../shared/agent-contract.ts";
|
|
|
70
74
|
import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
71
75
|
import { cleanupStructuredOutputRuntime, createStructuredOutputRuntime } from "../shared/structured-output.ts";
|
|
72
76
|
import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, readStatus, resolveChildCwd, sumResultsCost, sumResultsUsage } from "../../shared/utils.ts";
|
|
77
|
+
import { createTaskMutationArbiter } from "../shared/llm-intent-arbiter.ts";
|
|
73
78
|
import { DEFAULT_GLOBAL_CONCURRENCY_LIMIT, Semaphore } from "../shared/parallel-utils.ts";
|
|
74
79
|
import { discardPreservedWorktrees, formatParallelHandoffError, formatParallelHandoffReference, parallelHandoffPath, writeParallelHandoffGroup, writePendingParallelHandoff } from "../shared/parallel-handoff.ts";
|
|
75
80
|
import { summarizeContextModes, type ContextMode, type ContextSummary } from "../shared/context-mode.ts";
|
|
@@ -93,6 +98,7 @@ import {
|
|
|
93
98
|
workflowForegroundSteeringLaunchOptions,
|
|
94
99
|
} from "./workflow-foreground-steering.ts";
|
|
95
100
|
import { stopAsyncRun } from "./async-stop-action.ts";
|
|
101
|
+
import { dismissRecoveredWorkflow } from "./async-dismiss-action.ts";
|
|
96
102
|
import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
|
|
97
103
|
import { resolveAsyncRootResultPath, waitForImportedAsyncRoot } from "../background/chain-root-attachment.ts";
|
|
98
104
|
import { attachRootChildrenToSteps, createNestedRoute, findNestedControlResult, resolveInheritedNestedRouteFromEnv, resolveNestedAsyncDir, resolveNestedParentAddressFromEnv, snapshotNestedEventFiles, updateForegroundNestedProjection, writeNestedControlRequest, writeNestedEvent, type NestedRunResolutionScope } from "../shared/nested-events.ts";
|
|
@@ -141,6 +147,7 @@ import {
|
|
|
141
147
|
type ResolvedControlConfig,
|
|
142
148
|
type ResolvedTurnBudget,
|
|
143
149
|
type ResolvedToolBudget,
|
|
150
|
+
type RunFanoutBudgetDescriptor,
|
|
144
151
|
type SingleResult,
|
|
145
152
|
type ToolBudgetConfig,
|
|
146
153
|
type TurnBudgetConfig,
|
|
@@ -159,10 +166,11 @@ import {
|
|
|
159
166
|
resolveTopLevelParallelMaxTasks,
|
|
160
167
|
resolveChildMaxSubagentDepth,
|
|
161
168
|
resolveCurrentMaxSubagentDepth,
|
|
169
|
+
resolveMaxSubagentSpawnsPerRun,
|
|
162
170
|
wrapForkTask,
|
|
163
171
|
} from "../../shared/types.ts";
|
|
164
172
|
|
|
165
|
-
const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete", "eject", "disable", "enable", "reset", "grant-spawn-budget", "watchdog.configure", "mission.create", "mission.update", "mission.resolve-decision", "mission.attach-run", "mission.close", "inspector.open", "inspector.close", "project.open", "project.close", "worktree.discard", "refine", "refine.rollback", "schedule.create", "schedule.pause", "schedule.resume", "schedule.run", "schedule.run-due", "schedule.delete"]);
|
|
173
|
+
const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete", "eject", "disable", "enable", "reset", "grant-spawn-budget", "watchdog.configure", "mission.create", "mission.update", "mission.resolve-decision", "mission.attach-run", "mission.close", "inspector.open", "inspector.close", "project.open", "project.close", "worktree.discard", "refine", "refine.rollback", "dismiss", "schedule.create", "schedule.pause", "schedule.resume", "schedule.run", "schedule.run-due", "schedule.delete"]);
|
|
166
174
|
const DESTRUCTIVE_MANAGEMENT_ACTIONS = new Set(["delete", "eject", "disable", "reset", "mission.close", "worktree.discard", "refine.rollback", "inspector.close", "project.close", "stop", "interrupt", "reject-checkpoint", "schedule.delete"]);
|
|
167
175
|
|
|
168
176
|
function editDistance(left: string, right: string): number {
|
|
@@ -282,6 +290,10 @@ export interface SubagentParamsLike {
|
|
|
282
290
|
workflowKey?: string;
|
|
283
291
|
workflowChildAsyncId?: string;
|
|
284
292
|
suppressRoutineResultIntercom?: boolean;
|
|
293
|
+
/** Internal inherited cumulative run-tree budget. */
|
|
294
|
+
runFanoutBudget?: RunFanoutBudgetDescriptor;
|
|
295
|
+
/** Internal workflow host admission proof. */
|
|
296
|
+
runFanoutAdmitted?: boolean;
|
|
285
297
|
/** Internal durable-run compatibility fields. Public callers must use workflowScript. */
|
|
286
298
|
chain?: ChainStep[];
|
|
287
299
|
tasks?: TaskParam[];
|
|
@@ -386,6 +398,7 @@ interface ExecutionContextData {
|
|
|
386
398
|
artifactsDir: string;
|
|
387
399
|
backgroundRequestedWhileClarifying: boolean;
|
|
388
400
|
effectiveAsync: boolean;
|
|
401
|
+
asyncRunId: string;
|
|
389
402
|
controlConfig: ResolvedControlConfig;
|
|
390
403
|
intercomBridge: IntercomBridgeState;
|
|
391
404
|
nestedRoute?: NestedRouteInfo;
|
|
@@ -402,6 +415,9 @@ interface ExecutionContextData {
|
|
|
402
415
|
parentSessionId: string | null;
|
|
403
416
|
parentPiSessionId?: string;
|
|
404
417
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
418
|
+
runFanoutBudget: RunFanoutBudgetDescriptor;
|
|
419
|
+
topLevelAsyncCapacityEligible: boolean;
|
|
420
|
+
activeAsyncCapacity?: ActiveAsyncCapacityHandle;
|
|
405
421
|
}
|
|
406
422
|
|
|
407
423
|
function resolveRequestedCwd(runtimeCwd: string, requestedCwd: string | undefined): string {
|
|
@@ -430,6 +446,17 @@ function getForegroundControl(state: SubagentState, runId: string | undefined) {
|
|
|
430
446
|
return newest;
|
|
431
447
|
}
|
|
432
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
|
+
|
|
433
460
|
function formatForegroundActivity(control: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never): string | undefined {
|
|
434
461
|
const facts: string[] = [];
|
|
435
462
|
if (control.currentTool && control.currentToolStartedAt) facts.push(`tool ${control.currentTool} for ${Math.floor(Math.max(0, Date.now() - control.currentToolStartedAt) / 1000)}s`);
|
|
@@ -481,12 +508,18 @@ function withSpawnBudgetStatus(
|
|
|
481
508
|
sessionId: string | null,
|
|
482
509
|
): AgentToolResult<Details> {
|
|
483
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;
|
|
484
517
|
return {
|
|
485
518
|
...result,
|
|
486
519
|
content: result.content.map((item, index) => index === 0 && item.type === "text"
|
|
487
|
-
? { ...item, text: `${formatSpawnBudget(spawnBudget)}\n${item.text}` }
|
|
520
|
+
? { ...item, text: `${formatSpawnBudget(spawnBudget)}\nActive async capacity: ${activeAsyncCapacity.used}/${activeAsyncCapacity.limit || "unlimited"} used\n${item.text}` }
|
|
488
521
|
: item),
|
|
489
|
-
details: { ...result.details, spawnBudget },
|
|
522
|
+
details: { ...result.details, spawnBudget, activeAsyncCapacity },
|
|
490
523
|
};
|
|
491
524
|
}
|
|
492
525
|
|
|
@@ -507,6 +540,31 @@ function countRequestedSubagentSpawns(params: SubagentParamsLike, config: Extens
|
|
|
507
540
|
return params.agent ? 1 : 0;
|
|
508
541
|
}
|
|
509
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
|
+
|
|
510
568
|
function foregroundStatusResult(control: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never): AgentToolResult<Details> {
|
|
511
569
|
let nestedWarning: string | undefined;
|
|
512
570
|
try {
|
|
@@ -1139,18 +1197,30 @@ function appendStepToAsyncChain(input: {
|
|
|
1139
1197
|
}
|
|
1140
1198
|
|
|
1141
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
|
+
});
|
|
1142
1211
|
const result = enqueueChainAppendRequest({
|
|
1143
|
-
asyncDir
|
|
1212
|
+
asyncDir,
|
|
1144
1213
|
runId: resolved.id,
|
|
1145
1214
|
steps: built.steps,
|
|
1215
|
+
admit: (persist) => claimRunFanoutBatchWithCommit(runFanoutBudget, appendPaths, persist),
|
|
1146
1216
|
});
|
|
1147
1217
|
const stepText = built.steps.length === 1 ? "step" : "steps";
|
|
1148
1218
|
return {
|
|
1149
1219
|
content: [{
|
|
1150
1220
|
type: "text",
|
|
1151
|
-
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}` : ""}`,
|
|
1152
1222
|
}],
|
|
1153
|
-
details: { mode: "management", results: [], asyncId: resolved.id, asyncDir
|
|
1223
|
+
details: { mode: "management", results: [], asyncId: resolved.id, asyncDir },
|
|
1154
1224
|
};
|
|
1155
1225
|
} catch (error) {
|
|
1156
1226
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1485,6 +1555,20 @@ async function resumeAsyncRun(input: {
|
|
|
1485
1555
|
};
|
|
1486
1556
|
}
|
|
1487
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
|
+
}
|
|
1488
1572
|
const artifactConfig: ArtifactConfig = omitUndefinedProperties({ ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir });
|
|
1489
1573
|
const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
1490
1574
|
const contextPolicy = resolveExplicitContextPolicy(input.params);
|
|
@@ -1536,9 +1620,14 @@ async function resumeAsyncRun(input: {
|
|
|
1536
1620
|
controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
|
|
1537
1621
|
childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
1538
1622
|
globalConcurrencyLimit: input.deps.config.globalConcurrencyLimit,
|
|
1623
|
+
runFanoutBudget: createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
|
|
1539
1624
|
capabilityCeiling: intersectSubagentCapabilityCeilings("capabilityCeiling" in target ? target.capabilityCeiling : undefined, resolveCurrentSubagentCapabilityCeiling(input.deps.state.currentSessionId)),
|
|
1625
|
+
activeAsyncCapacity,
|
|
1540
1626
|
}));
|
|
1541
|
-
if (result.isError)
|
|
1627
|
+
if (result.isError) {
|
|
1628
|
+
activeAsyncCapacity?.rollback();
|
|
1629
|
+
return result;
|
|
1630
|
+
}
|
|
1542
1631
|
const attachedId = result.details.asyncId ?? runId;
|
|
1543
1632
|
const lines = [
|
|
1544
1633
|
`Attached async subagent ${target.runId} as the first step of a new chain.`,
|
|
@@ -1557,12 +1646,38 @@ async function resumeAsyncRun(input: {
|
|
|
1557
1646
|
if (!revivalSessionFile) {
|
|
1558
1647
|
return { content: [{ type: "text", text: `Async child '${target.runId}' has no persisted session file to resume.` }], isError: true, details: { mode: "management", results: [] } };
|
|
1559
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
|
+
}
|
|
1560
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
|
+
}
|
|
1561
1675
|
const recoveryAgentConfig = recoveryDescriptor ? applySteeringRecoveryAgentConfig(agentConfig, recoveryDescriptor) : agentConfig;
|
|
1562
1676
|
const artifactConfig: ArtifactConfig = recoveryDescriptor?.artifactConfig ?? omitUndefinedProperties({ ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir });
|
|
1563
1677
|
const artifactsDir = recoveryDescriptor?.artifactsDir ?? getArtifactsDir(parentSessionFile, effectiveCwd, artifactConfig.dir);
|
|
1564
1678
|
const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
1565
1679
|
const parentModel = input.parentModel;
|
|
1680
|
+
const revivalAsyncDir = path.join(DIRS.async, runId);
|
|
1566
1681
|
const result = executeAsyncSingle(runId, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
|
|
1567
1682
|
agent: target.agent,
|
|
1568
1683
|
task: buildRevivedAsyncTask(target as Parameters<typeof buildRevivedAsyncTask>[0], effectiveFollowUp),
|
|
@@ -1616,10 +1731,28 @@ async function resumeAsyncRun(input: {
|
|
|
1616
1731
|
...(input.params.turnBudget !== undefined ? { turnBudget: input.params.turnBudget } : {}),
|
|
1617
1732
|
...(input.params.toolBudget !== undefined ? { toolBudget: input.params.toolBudget } : {}),
|
|
1618
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)),
|
|
1619
1735
|
parentWorkflowRunId: input.params.workflowParentRunId,
|
|
1620
1736
|
workflowKey: input.params.workflowKey,
|
|
1737
|
+
activeAsyncCapacity,
|
|
1621
1738
|
}));
|
|
1622
|
-
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
|
+
}
|
|
1623
1756
|
for (const brief of queuedBriefs) fs.rmSync(brief.path, { force: true });
|
|
1624
1757
|
if (queuedBriefs.length > 0 && sourceAsyncDir) {
|
|
1625
1758
|
const sourceStatus = readStatus(sourceAsyncDir);
|
|
@@ -2115,6 +2248,27 @@ export const DEFAULT_FOREGROUND_TIMEOUT_MS = 30 * 60 * 1000;
|
|
|
2115
2248
|
// stay unbounded as a whole, while each runner child has its own deadline.
|
|
2116
2249
|
export { DEFAULT_ASYNC_TIMEOUT_MS };
|
|
2117
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
|
+
|
|
2118
2272
|
export function resolveForegroundTimeout(params: SubagentParamsLike, defaultTimeoutMs?: number): { timeoutMs?: number; error?: string } {
|
|
2119
2273
|
const rawTimeout = params.timeoutMs;
|
|
2120
2274
|
const rawMaxRuntime = params.maxRuntimeMs;
|
|
@@ -2138,14 +2292,18 @@ export function resolveForegroundTimeout(params: SubagentParamsLike, defaultTime
|
|
|
2138
2292
|
* Resolve the effective launch timeout for a single-agent run, applying the
|
|
2139
2293
|
* async/foreground default when neither the caller nor the agent set one.
|
|
2140
2294
|
*
|
|
2141
|
-
*
|
|
2142
|
-
*
|
|
2143
|
-
*
|
|
2144
|
-
*
|
|
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.
|
|
2145
2301
|
*/
|
|
2146
|
-
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 } {
|
|
2147
2303
|
const isComposite = (params.chain?.length ?? 0) > 0 || (params.tasks?.length ?? 0) > 0 || params.workflowScript !== undefined;
|
|
2148
|
-
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;
|
|
2149
2307
|
return resolveForegroundTimeout(params, defaultTimeoutMs);
|
|
2150
2308
|
}
|
|
2151
2309
|
|
|
@@ -2558,8 +2716,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2558
2716
|
details: { mode: "single" as const, results: [] },
|
|
2559
2717
|
};
|
|
2560
2718
|
}
|
|
2561
|
-
const
|
|
2562
|
-
const id = requestedWorkflowChildAsyncId && path.basename(requestedWorkflowChildAsyncId) === requestedWorkflowChildAsyncId ? requestedWorkflowChildAsyncId : randomUUID();
|
|
2719
|
+
const id = data.asyncRunId;
|
|
2563
2720
|
const parentModel = data.parentModel;
|
|
2564
2721
|
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
2565
2722
|
pi: deps.pi,
|
|
@@ -2613,6 +2770,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2613
2770
|
shareEnabled,
|
|
2614
2771
|
sessionRoot,
|
|
2615
2772
|
chainSkills: [],
|
|
2773
|
+
activeAsyncCapacity: data.activeAsyncCapacity,
|
|
2616
2774
|
sessionFilesByFlatIndex: params.tasks.map((task, index) => sessionFileForTask(task.agent, index, task.model)),
|
|
2617
2775
|
thinkingOverridesByFlatIndex: params.tasks.map((task, index) => thinkingOverrideForTask(task.agent, index, task.model)),
|
|
2618
2776
|
contextForAgent: contextPolicy.contextForAgent,
|
|
@@ -2632,6 +2790,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2632
2790
|
usageBudget: data.usageBudget,
|
|
2633
2791
|
configToolBudget: data.configToolBudget,
|
|
2634
2792
|
capabilityCeiling: data.capabilityCeiling,
|
|
2793
|
+
runFanoutBudget: data.runFanoutBudget,
|
|
2635
2794
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2636
2795
|
parentWorkflowRunId: params.workflowParentRunId,
|
|
2637
2796
|
workflowKey: params.workflowKey,
|
|
@@ -2657,6 +2816,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2657
2816
|
shareEnabled,
|
|
2658
2817
|
sessionRoot,
|
|
2659
2818
|
chainSkills,
|
|
2819
|
+
activeAsyncCapacity: data.activeAsyncCapacity,
|
|
2660
2820
|
sessionFilesByFlatIndex: collectChainSessionFiles(chain, sessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
2661
2821
|
thinkingOverridesByFlatIndex: collectChainThinkingOverrides(chain, thinkingOverrideForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
2662
2822
|
contextForAgent: contextPolicy.contextForAgent,
|
|
@@ -2677,6 +2837,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2677
2837
|
usageBudget: data.usageBudget,
|
|
2678
2838
|
configToolBudget: data.configToolBudget,
|
|
2679
2839
|
capabilityCeiling: data.capabilityCeiling,
|
|
2840
|
+
runFanoutBudget: data.runFanoutBudget,
|
|
2680
2841
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2681
2842
|
parentWorkflowRunId: params.workflowParentRunId,
|
|
2682
2843
|
workflowKey: params.workflowKey,
|
|
@@ -2698,7 +2859,12 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2698
2859
|
const normalizedSkills = normalizeSkillInput(params.skill);
|
|
2699
2860
|
const skills = normalizedSkills === false ? [] : normalizedSkills;
|
|
2700
2861
|
const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, a.maxSubagentDepth);
|
|
2701
|
-
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 });
|
|
2702
2868
|
return executeAsyncSingle(id, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
|
|
2703
2869
|
agent: params.agent!,
|
|
2704
2870
|
task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
|
|
@@ -2711,6 +2877,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2711
2877
|
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
2712
2878
|
artifactConfig,
|
|
2713
2879
|
shareEnabled,
|
|
2880
|
+
activeAsyncCapacity: data.activeAsyncCapacity,
|
|
2714
2881
|
sessionRoot,
|
|
2715
2882
|
sessionFile: sessionFileForTask(params.agent!, 0, modelOverride),
|
|
2716
2883
|
context: contextPolicy.contextForAgent(params.agent!),
|
|
@@ -2720,7 +2887,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2720
2887
|
...(params.reads !== undefined ? { reads: params.reads } : {}),
|
|
2721
2888
|
outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
|
|
2722
2889
|
modelOverride,
|
|
2723
|
-
thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride),
|
|
2890
|
+
thinkingOverride: externalRunnerWithoutExplicitModel ? undefined : thinkingOverrideForTask(params.agent!, 0, modelOverride),
|
|
2724
2891
|
maxSubagentDepth,
|
|
2725
2892
|
waitToolEnabled: deps.waitToolEnabled,
|
|
2726
2893
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
@@ -2739,6 +2906,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2739
2906
|
usageBudget: data.usageBudget,
|
|
2740
2907
|
configToolBudget: data.configToolBudget,
|
|
2741
2908
|
capabilityCeiling: data.capabilityCeiling,
|
|
2909
|
+
runFanoutBudget: data.runFanoutBudget,
|
|
2742
2910
|
parentWorkflowRunId: params.workflowParentRunId,
|
|
2743
2911
|
workflowKey: params.workflowKey,
|
|
2744
2912
|
}));
|
|
@@ -2829,6 +2997,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2829
2997
|
permissions: deps.config.permissions,
|
|
2830
2998
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2831
2999
|
capabilityCeiling: data.capabilityCeiling,
|
|
3000
|
+
runFanoutBudget: data.runFanoutBudget,
|
|
2832
3001
|
}));
|
|
2833
3002
|
|
|
2834
3003
|
if (chainResult.requestedAsync) {
|
|
@@ -2839,7 +3008,9 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2839
3008
|
details: { mode: "chain" as const, results: [] },
|
|
2840
3009
|
};
|
|
2841
3010
|
}
|
|
2842
|
-
const id =
|
|
3011
|
+
const id = data.asyncRunId;
|
|
3012
|
+
const capacity = acquireClarifiedBackgroundCapacity(data, deps, "chain", id);
|
|
3013
|
+
if (capacity.errorResult) return capacity.errorResult;
|
|
2843
3014
|
const parentModel = data.parentModel;
|
|
2844
3015
|
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
2845
3016
|
pi: deps.pi,
|
|
@@ -2855,42 +3026,48 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2855
3026
|
const rawAsyncChain = chainResult.requestedAsync.chain;
|
|
2856
3027
|
const asyncChain = wrapChainTasksForFork(rawAsyncChain, contextPolicy);
|
|
2857
3028
|
const firstAgent = firstChainAgent(rawAsyncChain);
|
|
2858
|
-
|
|
2859
|
-
|
|
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
|
-
|
|
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
|
+
}
|
|
2894
3071
|
}
|
|
2895
3072
|
|
|
2896
3073
|
const rawChainDetails = chainResult.details ? compactOptional<Details>({ ...chainResult.details, runId, timeoutMs: data.timeoutMs }) : undefined;
|
|
@@ -2935,6 +3112,7 @@ interface ForegroundParallelRunInput {
|
|
|
2935
3112
|
intercomEvents: IntercomEventBus;
|
|
2936
3113
|
parentSessionId: string | null;
|
|
2937
3114
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
3115
|
+
runFanoutBudget: RunFanoutBudgetDescriptor;
|
|
2938
3116
|
signal: AbortSignal;
|
|
2939
3117
|
runId: string;
|
|
2940
3118
|
sessionDirForIndex: (idx?: number) => string | undefined;
|
|
@@ -2984,6 +3162,48 @@ function buildParallelModeError(message: string): AgentToolResult<Details> {
|
|
|
2984
3162
|
};
|
|
2985
3163
|
}
|
|
2986
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
|
+
|
|
2987
3207
|
function createParallelWorktreeSetup(
|
|
2988
3208
|
enabled: boolean | undefined,
|
|
2989
3209
|
cwd: string,
|
|
@@ -3172,7 +3392,33 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
3172
3392
|
beginForegroundChild(input.foregroundControl, {
|
|
3173
3393
|
index,
|
|
3174
3394
|
agent: task.agent,
|
|
3175
|
-
|
|
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`,
|
|
3176
3422
|
...(model ? { model } : {}),
|
|
3177
3423
|
...(thinking ? { thinking } : {}),
|
|
3178
3424
|
interrupt: () => {
|
|
@@ -3189,8 +3435,10 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
3189
3435
|
const result = await runSync(input.ctx.cwd, input.agents, task.agent, taskText, compactOptional<Parameters<typeof runSync>[4]>({
|
|
3190
3436
|
permissions: input.permissions,
|
|
3191
3437
|
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
3438
|
+
llmIntentArbiter: createTaskMutationArbiter(input.ctx),
|
|
3192
3439
|
...workflowForegroundSteeringLaunchOptions(input.foregroundControl, index),
|
|
3193
3440
|
context: input.contextPolicy.contextForAgent(task.agent),
|
|
3441
|
+
runFanoutBudget: { ...input.runFanoutBudget, parentPath: `${input.runFanoutBudget.parentPath ? `${input.runFanoutBudget.parentPath}/` : ""}tasks[${index}]` },
|
|
3194
3442
|
cwd: taskCwd,
|
|
3195
3443
|
signal: input.signal,
|
|
3196
3444
|
interruptSignal: interruptController.signal,
|
|
@@ -3235,6 +3483,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
3235
3483
|
agentContract: task.agentContract ?? input.agentContract,
|
|
3236
3484
|
acceptance: task.acceptance,
|
|
3237
3485
|
acceptanceContext: { mode: "parallel" },
|
|
3486
|
+
onEffectivePrompt: input.foregroundControl ? (prompt) => updateLiveEffectivePrompt(input.foregroundControl!, index, prompt) : undefined,
|
|
3238
3487
|
timeoutMs: input.timeoutMs,
|
|
3239
3488
|
deadlineAt: input.deadlineAt,
|
|
3240
3489
|
turnBudget: input.turnBudget,
|
|
@@ -3418,7 +3667,9 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3418
3667
|
details: { mode: "parallel" as const, results: [] },
|
|
3419
3668
|
};
|
|
3420
3669
|
}
|
|
3421
|
-
const id =
|
|
3670
|
+
const id = data.asyncRunId;
|
|
3671
|
+
const capacity = acquireClarifiedBackgroundCapacity(data, deps, "parallel", id);
|
|
3672
|
+
if (capacity.errorResult) return capacity.errorResult;
|
|
3422
3673
|
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
3423
3674
|
pi: deps.pi,
|
|
3424
3675
|
cwd: ctx.cwd,
|
|
@@ -3449,40 +3700,46 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3449
3700
|
...(t.agentContract !== undefined ? { agentContract: t.agentContract } : {}),
|
|
3450
3701
|
});
|
|
3451
3702
|
});
|
|
3452
|
-
|
|
3453
|
-
|
|
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
|
-
|
|
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
|
+
}
|
|
3486
3743
|
}
|
|
3487
3744
|
}
|
|
3488
3745
|
|
|
@@ -3561,6 +3818,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3561
3818
|
intercomEvents: deps.pi.events,
|
|
3562
3819
|
parentSessionId: data.parentSessionId,
|
|
3563
3820
|
capabilityCeiling: data.capabilityCeiling,
|
|
3821
|
+
runFanoutBudget: data.runFanoutBudget,
|
|
3564
3822
|
signal,
|
|
3565
3823
|
runId,
|
|
3566
3824
|
sessionDirForIndex,
|
|
@@ -3795,7 +4053,9 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3795
4053
|
details: { mode: "single" as const, results: [] },
|
|
3796
4054
|
};
|
|
3797
4055
|
}
|
|
3798
|
-
const id =
|
|
4056
|
+
const id = data.asyncRunId;
|
|
4057
|
+
const capacity = acquireClarifiedBackgroundCapacity(data, deps, "single", id);
|
|
4058
|
+
if (capacity.errorResult) return capacity.errorResult;
|
|
3799
4059
|
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
3800
4060
|
pi: deps.pi,
|
|
3801
4061
|
cwd: ctx.cwd,
|
|
@@ -3807,49 +4067,56 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3807
4067
|
interactive: ctx.hasUI,
|
|
3808
4068
|
permissions: deps.config.permissions,
|
|
3809
4069
|
});
|
|
3810
|
-
|
|
3811
|
-
|
|
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
|
-
|
|
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
|
+
}
|
|
3850
4116
|
}
|
|
3851
4117
|
}
|
|
3852
4118
|
|
|
4119
|
+
const authoredTask = task;
|
|
3853
4120
|
if (shouldForkAgent(contextPolicy, params.agent!)) {
|
|
3854
4121
|
task = wrapForkTask(task);
|
|
3855
4122
|
}
|
|
@@ -3886,6 +4153,11 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3886
4153
|
beginForegroundChild(foregroundControl, omitUndefinedProperties({
|
|
3887
4154
|
index: 0,
|
|
3888
4155
|
agent: params.agent!,
|
|
4156
|
+
authoredTask,
|
|
4157
|
+
effectivePrompt: task,
|
|
4158
|
+
cwd: effectiveCwd,
|
|
4159
|
+
outputPath,
|
|
4160
|
+
rerun: { params: { ...params, task: authoredTask, async: params.async ?? false } },
|
|
3889
4161
|
description: foregroundControl.description,
|
|
3890
4162
|
...(modelOverride ? { model: modelOverride } : {}),
|
|
3891
4163
|
...(thinking ? { thinking } : {}),
|
|
@@ -3911,8 +4183,10 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3911
4183
|
r = await runSync(ctx.cwd, agents, params.agent!, task, compactOptional<Parameters<typeof runSync>[4]>({
|
|
3912
4184
|
permissions: deps.config.permissions,
|
|
3913
4185
|
parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
|
|
4186
|
+
llmIntentArbiter: createTaskMutationArbiter(ctx),
|
|
3914
4187
|
...workflowForegroundSteeringLaunchOptions(foregroundControl, 0),
|
|
3915
4188
|
context: data.contextPolicy.contextForAgent(params.agent!),
|
|
4189
|
+
runFanoutBudget: params.runFanoutAdmitted ? data.runFanoutBudget : { ...data.runFanoutBudget, parentPath: `${data.runFanoutBudget.parentPath ? `${data.runFanoutBudget.parentPath}/` : ""}single` },
|
|
3916
4190
|
cwd: effectiveCwd,
|
|
3917
4191
|
signal,
|
|
3918
4192
|
interruptSignal: interruptController.signal,
|
|
@@ -3946,6 +4220,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3946
4220
|
agentContract: params.agentContract,
|
|
3947
4221
|
acceptance: params.acceptance,
|
|
3948
4222
|
acceptanceContext: { mode: "single" },
|
|
4223
|
+
onEffectivePrompt: foregroundControl ? (prompt) => updateLiveEffectivePrompt(foregroundControl, 0, prompt) : undefined,
|
|
3949
4224
|
onDetachReady: (detach) => {
|
|
3950
4225
|
detachForeground = detach;
|
|
3951
4226
|
},
|
|
@@ -3994,12 +4269,14 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3994
4269
|
if (r.artifactPaths) allArtifactPaths.push(r.artifactPaths);
|
|
3995
4270
|
|
|
3996
4271
|
const fullOutput = getSingleResultOutput(r);
|
|
4272
|
+
const preserveRejectedSavedOutput = r.acceptance?.explicit && r.acceptance.status === "rejected" && r.savedOutputPath !== undefined;
|
|
3997
4273
|
const finalizedOutput = finalizeSingleOutput(omitUndefinedProperties({
|
|
3998
4274
|
fullOutput,
|
|
3999
4275
|
truncatedOutput: r.truncation?.text,
|
|
4000
4276
|
outputPath,
|
|
4001
4277
|
outputMode: r.outputMode,
|
|
4002
4278
|
exitCode: r.exitCode,
|
|
4279
|
+
preserveSavedOutput: preserveRejectedSavedOutput,
|
|
4003
4280
|
savedPath: r.savedOutputPath,
|
|
4004
4281
|
outputReference: r.outputReference,
|
|
4005
4282
|
saveError: r.outputSaveError,
|
|
@@ -4090,7 +4367,7 @@ function duplicateSubagentCallResult(params: SubagentParamsLike): AgentToolResul
|
|
|
4090
4367
|
};
|
|
4091
4368
|
}
|
|
4092
4369
|
|
|
4093
|
-
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>();
|
|
4094
4371
|
|
|
4095
4372
|
function recordMissionWorkflowChild(
|
|
4096
4373
|
binding: MissionLaunchBinding | undefined,
|
|
@@ -4099,8 +4376,9 @@ function recordMissionWorkflowChild(
|
|
|
4099
4376
|
update: Omit<MissionWorkflowChildUpdate, "workflowRunId" | "key">,
|
|
4100
4377
|
): void {
|
|
4101
4378
|
if (!binding) return;
|
|
4379
|
+
const { task: _task, ...durableUpdate } = update;
|
|
4102
4380
|
try {
|
|
4103
|
-
updateMission(binding.location, binding.missionId, { upsertWorkflowChildren: [{ workflowRunId, key, ...
|
|
4381
|
+
updateMission(binding.location, binding.missionId, { upsertWorkflowChildren: [{ workflowRunId, key, ...durableUpdate }] });
|
|
4104
4382
|
} catch (error) {
|
|
4105
4383
|
console.warn(`[pi-subagents] Failed to record mission workflow child '${key}': ${error instanceof Error ? error.message : String(error)}`);
|
|
4106
4384
|
}
|
|
@@ -4182,7 +4460,7 @@ export function prepareWorkflowLaunchParams(
|
|
|
4182
4460
|
childParams: Record<string, unknown>,
|
|
4183
4461
|
parentWorkflowRunId: string,
|
|
4184
4462
|
workflowKey: string,
|
|
4185
|
-
options: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean } = {},
|
|
4463
|
+
options: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor } = {},
|
|
4186
4464
|
): SubagentParamsLike {
|
|
4187
4465
|
if (typeof childParams.resume === "string") {
|
|
4188
4466
|
if (childParams.gate !== undefined || workflowDefaults.gate !== undefined) {
|
|
@@ -4210,6 +4488,7 @@ export function prepareWorkflowLaunchParams(
|
|
|
4210
4488
|
...(options.missionDetached ? { mission: false } : {}),
|
|
4211
4489
|
workflowParentRunId: parentWorkflowRunId,
|
|
4212
4490
|
workflowKey,
|
|
4491
|
+
...(options.runFanoutBudget ? { runFanoutBudget: { ...options.runFanoutBudget, parentPath: `${options.runFanoutBudget.parentPath ? `${options.runFanoutBudget.parentPath}/` : ""}workflow[${workflowKey}]` } } : {}),
|
|
4213
4492
|
...(options.suppressRoutineResultIntercom ? { suppressRoutineResultIntercom: true } : {}),
|
|
4214
4493
|
} as SubagentParamsLike;
|
|
4215
4494
|
const normalizedGate = normalizeGateParams(launchParams);
|
|
@@ -4357,6 +4636,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4357
4636
|
} {
|
|
4358
4637
|
const delegatedThinkingOverrides = new WeakMap<object, AgentConfig["thinking"]>();
|
|
4359
4638
|
const delegatedZeroToolBudgets = new WeakSet<object>();
|
|
4639
|
+
const delegatedExecutions = new WeakSet<object>();
|
|
4360
4640
|
const warnedArtifactPackageDirs = new Set<string>();
|
|
4361
4641
|
const scheduledOwnerExecutors = new Map<string, ReturnType<typeof createSubagentExecutor>>();
|
|
4362
4642
|
const execute = async (
|
|
@@ -4370,6 +4650,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4370
4650
|
const workflowLaunchObserver = workflowLaunchObservers.get(params);
|
|
4371
4651
|
const delegatedThinkingOverride = delegatedThinkingOverrides.get(params);
|
|
4372
4652
|
const allowZeroToolBudget = delegatedZeroToolBudgets.has(params);
|
|
4653
|
+
const delegatedExecution = delegatedExecutions.has(params);
|
|
4373
4654
|
if (!preserveActiveSession) deps.state.baseCwd = ctx.cwd;
|
|
4374
4655
|
deps.state.foregroundRuns ??= new Map();
|
|
4375
4656
|
deps.state.foregroundControls ??= new Map();
|
|
@@ -4380,7 +4661,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4380
4661
|
const normalizedAction = typeof requestParams.action === "string" ? requestParams.action.trim() : requestParams.action;
|
|
4381
4662
|
if (requestParams.workflowScript !== undefined && normalizedAction === undefined) {
|
|
4382
4663
|
const parentCwd = ctx.cwd;
|
|
4383
|
-
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);
|
|
4384
4665
|
const workflowUsageBudget = validateUsageBudgetConfig(requestParams.usageBudget ?? deps.config.usageBudget, requestParams.usageBudget ? "usageBudget" : "config.usageBudget");
|
|
4385
4666
|
if (workflowUsageBudget.error) return buildRequestedModeError(requestParams, workflowUsageBudget.error);
|
|
4386
4667
|
const workflowCwd = resolveRequestedCwd(parentCwd, requestParams.cwd);
|
|
@@ -4391,11 +4672,45 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4391
4672
|
const explicitMission = requestParams.missionId !== undefined || requestParams.mission !== undefined;
|
|
4392
4673
|
const autoMission = !explicitMission;
|
|
4393
4674
|
const workflowPreview = autoMission ? previewSimpleWorkflowRun(requestParams.workflowScript) : undefined;
|
|
4394
|
-
const previewTask = workflowPreview?.task?.trim() || undefined;
|
|
4395
4675
|
const previewAgent = workflowPreview?.agent?.trim() || undefined;
|
|
4396
4676
|
const scriptFirstLine = requestParams.workflowScript.split(/\r?\n/).map((line) => line.trim()).find(Boolean) || "Workflow";
|
|
4397
4677
|
const boundedScriptPreview = scriptFirstLine.length > 100 ? `${scriptFirstLine.slice(0, 97)}...` : scriptFirstLine;
|
|
4398
|
-
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
|
+
}
|
|
4399
4714
|
let missionBinding: MissionLaunchBinding | undefined;
|
|
4400
4715
|
let missionWarning: string | undefined;
|
|
4401
4716
|
try {
|
|
@@ -4406,22 +4721,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4406
4721
|
ownerSessionId: resolveCurrentSessionId(ctx.sessionManager),
|
|
4407
4722
|
});
|
|
4408
4723
|
} catch (error) {
|
|
4409
|
-
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
|
+
}
|
|
4410
4728
|
missionWarning = `Mission tracking unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
4411
4729
|
}
|
|
4412
|
-
let shouldPatchMissionObjective = autoMission && previewTask === undefined && missionBinding !== undefined;
|
|
4413
|
-
const patchMissionObjective = (task: unknown): void => {
|
|
4414
|
-
if (!shouldPatchMissionObjective || !missionBinding || typeof task !== "string" || !task.trim()) return;
|
|
4415
|
-
shouldPatchMissionObjective = false;
|
|
4416
|
-
const objective = task.trim();
|
|
4417
|
-
const firstLine = objective.split(/\r?\n/, 1)[0]?.trim() || objective;
|
|
4418
|
-
const title = firstLine.length > 100 ? `${firstLine.slice(0, 97)}...` : firstLine;
|
|
4419
|
-
try {
|
|
4420
|
-
updateMission(missionBinding.location, missionBinding.missionId, { title, objective });
|
|
4421
|
-
} catch (error) {
|
|
4422
|
-
console.warn(`[pi-subagents] Failed to update automatic mission objective: ${error instanceof Error ? error.message : String(error)}`);
|
|
4423
|
-
}
|
|
4424
|
-
};
|
|
4425
4730
|
const detachWorkflowChildMissions = autoMission || missionBinding !== undefined || requestParams.mission === false;
|
|
4426
4731
|
const workflowState = missionBinding ? createMissionWorkflowState(missionBinding.location, missionBinding.missionId) : undefined;
|
|
4427
4732
|
const attachWorkflowMission = (result: AgentToolResult<Details>): AgentToolResult<Details> => {
|
|
@@ -4435,20 +4740,27 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4435
4740
|
: { ...result, details: { ...result.details, missionWarning: warning } };
|
|
4436
4741
|
}
|
|
4437
4742
|
};
|
|
4438
|
-
if (
|
|
4743
|
+
if (workflowRunId) {
|
|
4439
4744
|
const toolCallId = _id;
|
|
4440
|
-
const workflowRunId = randomUUID();
|
|
4441
4745
|
const asyncDir = path.join(DIRS.async, workflowRunId);
|
|
4442
4746
|
const resultPath = path.join(DIRS.results, `${workflowRunId}.json`);
|
|
4443
4747
|
const statusPath = path.join(asyncDir, "status.json");
|
|
4444
4748
|
const eventsPath = path.join(asyncDir, "events.jsonl");
|
|
4445
4749
|
const startedAt = Date.now();
|
|
4446
4750
|
const currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
4447
|
-
|
|
4448
|
-
|
|
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
|
+
}
|
|
4449
4759
|
const controller = new AbortController();
|
|
4450
4760
|
deps.state.workflowControllers ??= new Map();
|
|
4451
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()) });
|
|
4452
4764
|
let status: AsyncStatus = {
|
|
4453
4765
|
runId: workflowRunId,
|
|
4454
4766
|
toolCallId,
|
|
@@ -4462,11 +4774,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4462
4774
|
pid: process.pid,
|
|
4463
4775
|
steps: [],
|
|
4464
4776
|
workflow: { trace: [], emits: [], console: [] },
|
|
4777
|
+
runFanoutBudget: getRunFanoutBudgetSnapshot(workflowFanoutBudget),
|
|
4465
4778
|
};
|
|
4466
4779
|
const appendWorkflowEvent = (event: Record<string, unknown>) => fs.appendFileSync(eventsPath, `${JSON.stringify({ ts: Date.now(), runId: workflowRunId, ...event })}\n`, "utf-8");
|
|
4780
|
+
let indexedState: AsyncStatus["state"] | undefined;
|
|
4467
4781
|
const persist = () => {
|
|
4468
4782
|
status.lastUpdate = Date.now();
|
|
4469
4783
|
writeAtomicJson(statusPath, status);
|
|
4784
|
+
if (indexedState !== status.state) {
|
|
4785
|
+
updateActiveRunIndex(asyncDir, status.state);
|
|
4786
|
+
indexedState = status.state;
|
|
4787
|
+
}
|
|
4470
4788
|
const job = deps.state.asyncJobs.get(workflowRunId);
|
|
4471
4789
|
if (job) {
|
|
4472
4790
|
job.status = status.state;
|
|
@@ -4543,12 +4861,23 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4543
4861
|
if (existing) {
|
|
4544
4862
|
existing.status = mapped;
|
|
4545
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;
|
|
4546
4866
|
if (entry.error === undefined) delete existing.error;
|
|
4547
4867
|
else existing.error = entry.error;
|
|
4548
4868
|
if (entry.durationMs === undefined) delete existing.durationMs;
|
|
4549
4869
|
else existing.durationMs = entry.durationMs;
|
|
4550
4870
|
} else {
|
|
4551
|
-
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
|
+
};
|
|
4552
4881
|
status.steps?.push(step);
|
|
4553
4882
|
workflowSteps.set(entry.key, step);
|
|
4554
4883
|
}
|
|
@@ -4567,30 +4896,32 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4567
4896
|
prompts: workflowPrompts,
|
|
4568
4897
|
...(workflowState ? { state: workflowState } : {}),
|
|
4569
4898
|
onTrace: updateTrace,
|
|
4899
|
+
admit: (calls) => {
|
|
4900
|
+
status.runFanoutBudget = claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
|
|
4901
|
+
persist();
|
|
4902
|
+
},
|
|
4570
4903
|
onEmit: (emits) => {
|
|
4571
4904
|
// Each emit is validated at the host boundary in runWorkflowScript before onEmit fires.
|
|
4572
4905
|
status.workflow = { ...(status.workflow ?? { trace: [], console: [] }), emits };
|
|
4573
4906
|
persist();
|
|
4574
4907
|
appendWorkflowEvent({ type: "subagent.workflow.emit", value: emits.at(-1) });
|
|
4575
4908
|
},
|
|
4576
|
-
launch: async (key, childParams, workflowSignal) => {
|
|
4909
|
+
launch: async (key, childParams, workflowSignal, admission) => {
|
|
4577
4910
|
if (workflowUsageBudget.budget && childParams.async === true) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, "workflow usageBudget does not support async runs.run launches."));
|
|
4578
4911
|
const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
|
|
4579
4912
|
if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)));
|
|
4580
|
-
patchMissionObjective(childParams.task);
|
|
4581
4913
|
const childPhase = typeof childParams.phase === "string" && childParams.phase.trim() ? childParams.phase.trim() : undefined;
|
|
4582
4914
|
const childLabel = typeof childParams.label === "string" && childParams.label.trim() ? childParams.label.trim() : undefined;
|
|
4583
4915
|
recordMissionWorkflowChild(missionBinding, workflowRunId, key, {
|
|
4584
4916
|
status: "running",
|
|
4585
4917
|
...(typeof childParams.agent === "string" && childParams.agent.trim() ? { agent: childParams.agent.trim() } : {}),
|
|
4586
|
-
...(typeof childParams.task === "string" && childParams.task.trim() ? { task: childParams.task.trim() } : {}),
|
|
4587
4918
|
...(childLabel ? { label: childLabel } : {}),
|
|
4588
4919
|
...(childPhase ? { phase: childPhase } : {}),
|
|
4589
4920
|
heartbeat: { status: "running", ...(childPhase ? { phase: childPhase } : {}) },
|
|
4590
4921
|
});
|
|
4591
4922
|
const result = await runMissionWorkflowChild(missionBinding, workflowRunId, key, childPhase, () => {
|
|
4592
4923
|
const childRequest = bindMissionWorkflowChildAsyncLaunch(
|
|
4593
|
-
prepareWorkflowLaunchParams(workflowChildDefaults, childParams, workflowRunId, key, { missionDetached: detachWorkflowChildMissions }),
|
|
4924
|
+
{ ...prepareWorkflowLaunchParams(workflowChildDefaults, childParams, workflowRunId, key, { missionDetached: detachWorkflowChildMissions, runFanoutBudget: workflowFanoutBudget }), runFanoutAdmitted: admission.admitted },
|
|
4594
4925
|
missionBinding,
|
|
4595
4926
|
deps.asyncByDefault,
|
|
4596
4927
|
);
|
|
@@ -4599,6 +4930,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4599
4930
|
if (step) {
|
|
4600
4931
|
step.agent = launch.agent;
|
|
4601
4932
|
step.sessionFile = launch.sessionFile;
|
|
4933
|
+
step.async = launch.async;
|
|
4934
|
+
if (launch.runId) step.runId = launch.runId;
|
|
4602
4935
|
persist();
|
|
4603
4936
|
}
|
|
4604
4937
|
recordMissionWorkflowChild(missionBinding, workflowRunId, key, { status: "running", agent: launch.agent, ...(launch.sessionFile ? { sessionPath: launch.sessionFile } : {}) });
|
|
@@ -4630,8 +4963,13 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4630
4963
|
}, ctx, preserveActiveSession);
|
|
4631
4964
|
});
|
|
4632
4965
|
workflowResults.push(...result.details.results);
|
|
4633
|
-
if (result.details.asyncDir && missionBinding) writeMissionAsyncBinding(result.details.asyncDir, missionBinding);
|
|
4634
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);
|
|
4635
4973
|
const childStatus = missionWorkflowChildStatus(result);
|
|
4636
4974
|
recordMissionWorkflowChild(missionBinding, workflowRunId, key, {
|
|
4637
4975
|
status: childStatus,
|
|
@@ -4667,12 +5005,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4667
5005
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, error: status.error });
|
|
4668
5006
|
} finally {
|
|
4669
5007
|
deps.state.workflowControllers?.delete(workflowRunId);
|
|
5008
|
+
deps.state.activeAsyncCapacity = workflowCapacity?.reconcile(new Set(deps.state.workflowControllers?.keys() ?? []))
|
|
5009
|
+
?? deps.state.activeAsyncCapacity;
|
|
4670
5010
|
}
|
|
4671
5011
|
});
|
|
4672
|
-
return attachWorkflowMission({
|
|
5012
|
+
return attachWorkflowMission(withRunFanoutBudget({
|
|
4673
5013
|
content: [{ type: "text", text: formatAsyncStartedMessage(`Async workflow [${workflowRunId}]`, ctx.hasUI === true) }],
|
|
4674
|
-
details: { mode: "workflow", runId: workflowRunId, toolCallId, asyncId: workflowRunId, asyncDir, results: [], chatProgress },
|
|
4675
|
-
});
|
|
5014
|
+
details: { mode: "workflow", runId: workflowRunId, toolCallId, asyncId: workflowRunId, asyncDir, results: [], chatProgress, ...(deps.state.activeAsyncCapacity ? { activeAsyncCapacity: deps.state.activeAsyncCapacity } : {}) },
|
|
5015
|
+
}, workflowFanoutBudget));
|
|
4676
5016
|
}
|
|
4677
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;
|
|
4678
5018
|
const workflowResults: SingleResult[] = [];
|
|
@@ -4692,28 +5032,29 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4692
5032
|
liveWorkflow = { ...liveWorkflow, trace };
|
|
4693
5033
|
sendWorkflowProgress();
|
|
4694
5034
|
},
|
|
5035
|
+
admit: (calls) => {
|
|
5036
|
+
claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
|
|
5037
|
+
},
|
|
4695
5038
|
onEmit: (emits) => {
|
|
4696
5039
|
liveWorkflow = { ...liveWorkflow, emits };
|
|
4697
5040
|
sendWorkflowProgress();
|
|
4698
5041
|
},
|
|
4699
|
-
launch: async (key, childParams, workflowSignal) => {
|
|
5042
|
+
launch: async (key, childParams, workflowSignal, admission) => {
|
|
4700
5043
|
if (workflowUsageBudget.budget && childParams.async === true) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, "workflow usageBudget does not support async runs.run launches."));
|
|
4701
5044
|
const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
|
|
4702
5045
|
if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)));
|
|
4703
|
-
patchMissionObjective(childParams.task);
|
|
4704
5046
|
const childPhase = typeof childParams.phase === "string" && childParams.phase.trim() ? childParams.phase.trim() : undefined;
|
|
4705
5047
|
const childLabel = typeof childParams.label === "string" && childParams.label.trim() ? childParams.label.trim() : undefined;
|
|
4706
5048
|
recordMissionWorkflowChild(missionBinding, _id, key, {
|
|
4707
5049
|
status: "running",
|
|
4708
5050
|
...(typeof childParams.agent === "string" && childParams.agent.trim() ? { agent: childParams.agent.trim() } : {}),
|
|
4709
|
-
...(typeof childParams.task === "string" && childParams.task.trim() ? { task: childParams.task.trim() } : {}),
|
|
4710
5051
|
...(childLabel ? { label: childLabel } : {}),
|
|
4711
5052
|
...(childPhase ? { phase: childPhase } : {}),
|
|
4712
5053
|
heartbeat: { status: "running", ...(childPhase ? { phase: childPhase } : {}) },
|
|
4713
5054
|
});
|
|
4714
5055
|
const result = await runMissionWorkflowChild(missionBinding, _id, key, childPhase, () => {
|
|
4715
5056
|
const childRequest = bindMissionWorkflowChildAsyncLaunch(
|
|
4716
|
-
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 },
|
|
4717
5058
|
missionBinding,
|
|
4718
5059
|
deps.asyncByDefault,
|
|
4719
5060
|
);
|
|
@@ -4754,10 +5095,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4754
5095
|
if (workflow.emits.length > 0) sections.push(`Emitted:\n${workflow.emits.map(formatWorkflowValue).join("\n")}`);
|
|
4755
5096
|
if (workflow.console.length > 0) sections.push(`Console:\n${workflow.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
|
|
4756
5097
|
if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
|
|
4757
|
-
return attachWorkflowMission({
|
|
5098
|
+
return attachWorkflowMission(withRunFanoutBudget({
|
|
4758
5099
|
content: [{ type: "text", text: sections.join("\n\n") }],
|
|
4759
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 }),
|
|
4760
|
-
});
|
|
5101
|
+
}, workflowFanoutBudget));
|
|
4761
5102
|
} catch (error) {
|
|
4762
5103
|
const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
|
|
4763
5104
|
const text = error instanceof Error ? error.message : String(error);
|
|
@@ -4766,11 +5107,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4766
5107
|
if (partial.emits.length > 0) sections.push(`Emitted:\n${partial.emits.map(formatWorkflowValue).join("\n")}`);
|
|
4767
5108
|
if (partial.console.length > 0) sections.push(`Console:\n${partial.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
|
|
4768
5109
|
if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
|
|
4769
|
-
return attachWorkflowMission({
|
|
5110
|
+
return attachWorkflowMission(withRunFanoutBudget({
|
|
4770
5111
|
content: [{ type: "text", text: sections.join("\n\n") }],
|
|
4771
5112
|
isError: true,
|
|
4772
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 }),
|
|
4773
|
-
});
|
|
5114
|
+
}, workflowFanoutBudget));
|
|
4774
5115
|
}
|
|
4775
5116
|
}
|
|
4776
5117
|
const directParams = prepareWorkflowChildParams(requestParams);
|
|
@@ -5008,6 +5349,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5008
5349
|
if (!sessionError) sessionError = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
5009
5350
|
}
|
|
5010
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;
|
|
5011
5356
|
return {
|
|
5012
5357
|
content: [{
|
|
5013
5358
|
type: "text",
|
|
@@ -5024,7 +5369,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5024
5369
|
expandTilde: deps.expandTilde,
|
|
5025
5370
|
})),
|
|
5026
5371
|
}],
|
|
5027
|
-
details: { mode: "management", results: [], spawnBudget },
|
|
5372
|
+
details: { mode: "management", results: [], spawnBudget, activeAsyncCapacity },
|
|
5028
5373
|
};
|
|
5029
5374
|
}
|
|
5030
5375
|
if (action === "status") {
|
|
@@ -5249,6 +5594,28 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5249
5594
|
}
|
|
5250
5595
|
return deps.handleScheduledRunAction(paramsWithResolvedCwd, ctx);
|
|
5251
5596
|
}
|
|
5597
|
+
if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
|
|
5598
|
+
return {
|
|
5599
|
+
content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }],
|
|
5600
|
+
isError: true,
|
|
5601
|
+
details: { mode: "management" as const, results: [] },
|
|
5602
|
+
};
|
|
5603
|
+
}
|
|
5604
|
+
if (action === "dismiss") {
|
|
5605
|
+
const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
|
|
5606
|
+
if (!targetRunId) return { content: [{ type: "text", text: "action='dismiss' requires id." }], isError: true, details: { mode: "management", results: [] } };
|
|
5607
|
+
let resolved: ResolvedSubagentRunId | undefined;
|
|
5608
|
+
try {
|
|
5609
|
+
resolved = resolveSubagentRunId(targetRunId, omitUndefinedProperties({ state: deps.state, nested: nestedResolutionScopeForExecutor(deps) }));
|
|
5610
|
+
} catch (error) {
|
|
5611
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5612
|
+
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
5613
|
+
}
|
|
5614
|
+
if (resolved?.kind !== "async") {
|
|
5615
|
+
return { content: [{ type: "text", text: `Run '${targetRunId}' is not a recovered workflow.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5616
|
+
}
|
|
5617
|
+
return dismissRecoveredWorkflow(deps.state, resolved.location);
|
|
5618
|
+
}
|
|
5252
5619
|
if (action === "stop") {
|
|
5253
5620
|
const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
|
|
5254
5621
|
const workflowController = targetRunId ? deps.state.workflowControllers?.get(targetRunId) : undefined;
|
|
@@ -5260,6 +5627,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5260
5627
|
if (paramsWithResolvedCwd.dir) {
|
|
5261
5628
|
try {
|
|
5262
5629
|
const location = resolveAsyncRunLocation(paramsWithResolvedCwd, DIRS.async, DIRS.results);
|
|
5630
|
+
const existingStatus = readStatus(location.asyncDir ?? "");
|
|
5631
|
+
if (existingStatus?.mode === "workflow" && existingStatus.state === "running") {
|
|
5632
|
+
return { content: [{ type: "text", text: `Workflow ${existingStatus.runId} is not controlled by this extension runtime; reload recovery cannot stop it safely.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5633
|
+
}
|
|
5263
5634
|
const stopResult = stopAsyncRun(deps.state, location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir ?? paramsWithResolvedCwd.dir), deps.kill, location);
|
|
5264
5635
|
return stopResult ?? { content: [{ type: "text", text: `No running or queued async run was found for '${targetRunId ?? paramsWithResolvedCwd.dir}'.` }], isError: true, details: { mode: "management", results: [] } };
|
|
5265
5636
|
} catch (error) {
|
|
@@ -5344,13 +5715,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5344
5715
|
details: { mode: "management" as const, results: [] },
|
|
5345
5716
|
};
|
|
5346
5717
|
}
|
|
5347
|
-
if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
|
|
5348
|
-
return {
|
|
5349
|
-
content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }],
|
|
5350
|
-
isError: true,
|
|
5351
|
-
details: { mode: "management" as const, results: [] },
|
|
5352
|
-
};
|
|
5353
|
-
}
|
|
5354
5718
|
return handleManagementAction(action, paramsWithResolvedCwd, {
|
|
5355
5719
|
...ctx,
|
|
5356
5720
|
cwd: requestCwd,
|
|
@@ -5422,7 +5786,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5422
5786
|
const runId = randomUUID().slice(0, 8);
|
|
5423
5787
|
const inheritedNestedRoute = resolveInheritedNestedRouteFromEnv();
|
|
5424
5788
|
const nestedParentAddress = inheritedNestedRoute ? resolveNestedParentAddressFromEnv() : undefined;
|
|
5425
|
-
const nestedRoute = inheritedNestedRoute ?? createNestedRoute(runId);
|
|
5426
5789
|
const shareEnabled = effectiveParams.share === true;
|
|
5427
5790
|
const hasChain = (effectiveParams.chain?.length ?? 0) > 0;
|
|
5428
5791
|
const hasTasks = (effectiveParams.tasks?.length ?? 0) > 0;
|
|
@@ -5506,9 +5869,50 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5506
5869
|
const foregroundTimeout = resolveSingleAgentLaunchTimeout(
|
|
5507
5870
|
effectiveParams,
|
|
5508
5871
|
effectiveAsync,
|
|
5872
|
+
resolveConfigDefaultTimeoutMs(deps.config.timeoutMs),
|
|
5509
5873
|
);
|
|
5510
5874
|
if (foregroundTimeout.error) return buildRequestedModeError(effectiveParams, foregroundTimeout.error);
|
|
5511
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);
|
|
5512
5916
|
|
|
5513
5917
|
const artifactConfig: ArtifactConfig = omitUndefinedProperties({
|
|
5514
5918
|
...DEFAULT_ARTIFACT_CONFIG,
|
|
@@ -5534,6 +5938,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5534
5938
|
try {
|
|
5535
5939
|
fs.mkdirSync(sessionRoot, { recursive: true });
|
|
5536
5940
|
} catch (error) {
|
|
5941
|
+
activeAsyncCapacity?.rollback();
|
|
5537
5942
|
const message = error instanceof Error ? error.message : String(error);
|
|
5538
5943
|
return toExecutionErrorResult(
|
|
5539
5944
|
effectiveParams,
|
|
@@ -5564,10 +5969,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5564
5969
|
preflightForkSessionsForStaticTasks(effectiveParams, contextPolicy, forkSessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems);
|
|
5565
5970
|
}
|
|
5566
5971
|
} catch (error) {
|
|
5972
|
+
activeAsyncCapacity?.rollback();
|
|
5567
5973
|
return toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary);
|
|
5568
5974
|
}
|
|
5569
5975
|
const chainBindingsError = validateExecutionChainBindings(effectiveParams, deps.config.chain?.dynamicFanout?.maxItems);
|
|
5570
|
-
if (chainBindingsError)
|
|
5976
|
+
if (chainBindingsError) {
|
|
5977
|
+
activeAsyncCapacity?.rollback();
|
|
5978
|
+
return withResolvedContext(chainBindingsError, contextPolicy.contextSummary);
|
|
5979
|
+
}
|
|
5571
5980
|
|
|
5572
5981
|
const onUpdateWithContext = onUpdate
|
|
5573
5982
|
? (r: AgentToolResult<Details>) => onUpdate(withResolvedContext({
|
|
@@ -5587,14 +5996,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5587
5996
|
ownerSessionId: requestSessionId,
|
|
5588
5997
|
});
|
|
5589
5998
|
} catch (error) {
|
|
5590
|
-
if (explicitMission)
|
|
5999
|
+
if (explicitMission) {
|
|
6000
|
+
activeAsyncCapacity?.rollback();
|
|
6001
|
+
return toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary);
|
|
6002
|
+
}
|
|
5591
6003
|
missionWarning = `Mission tracking unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
5592
6004
|
}
|
|
5593
6005
|
|
|
5594
6006
|
const attachMission = (result: AgentToolResult<Details>): AgentToolResult<Details> => {
|
|
5595
6007
|
if (!missionBinding) return missionWarning ? { ...result, details: { ...result.details, missionWarning } } : result;
|
|
5596
6008
|
try {
|
|
5597
|
-
return attachMissionToLaunchResult({ binding: missionBinding, result });
|
|
6009
|
+
return attachMissionToLaunchResult({ binding: delegatedExecution ? { ...missionBinding, announceInContent: false } : missionBinding, result });
|
|
5598
6010
|
} catch (error) {
|
|
5599
6011
|
const warning = `Mission tracking unavailable after launch: ${error instanceof Error ? error.message : String(error)}`;
|
|
5600
6012
|
if (explicitMission) {
|
|
@@ -5615,7 +6027,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5615
6027
|
requestSessionId,
|
|
5616
6028
|
requestedSpawns,
|
|
5617
6029
|
);
|
|
5618
|
-
if (reservation.error)
|
|
6030
|
+
if (reservation.error) {
|
|
6031
|
+
activeAsyncCapacity?.rollback();
|
|
6032
|
+
return attachMission(spawnBudgetErrorResult(reservation.error, foregroundMode));
|
|
6033
|
+
}
|
|
5619
6034
|
|
|
5620
6035
|
const execData: ExecutionContextData = omitUndefinedProperties({
|
|
5621
6036
|
params: effectiveParams,
|
|
@@ -5635,6 +6050,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5635
6050
|
artifactsDir,
|
|
5636
6051
|
backgroundRequestedWhileClarifying,
|
|
5637
6052
|
effectiveAsync,
|
|
6053
|
+
asyncRunId,
|
|
5638
6054
|
controlConfig,
|
|
5639
6055
|
intercomBridge,
|
|
5640
6056
|
nestedRoute,
|
|
@@ -5650,11 +6066,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5650
6066
|
parentSessionId: requestSessionId,
|
|
5651
6067
|
parentPiSessionId: requestPiSessionId,
|
|
5652
6068
|
capabilityCeiling: resolveCurrentSubagentCapabilityCeiling(requestSessionId),
|
|
6069
|
+
runFanoutBudget,
|
|
6070
|
+
topLevelAsyncCapacityEligible,
|
|
6071
|
+
activeAsyncCapacity,
|
|
5653
6072
|
});
|
|
5654
6073
|
|
|
5655
|
-
const foregroundDescription =
|
|
5656
|
-
|
|
5657
|
-
|
|
6074
|
+
const foregroundDescription = selectedAgentNames.length === 1
|
|
6075
|
+
? `${selectedAgentNames[0]} child`
|
|
6076
|
+
: `${selectedAgentNames.length} live children`;
|
|
5658
6077
|
const parentWorkflowStatus = effectiveParams.workflowParentRunId
|
|
5659
6078
|
? readStatus(path.join(DIRS.async, effectiveParams.workflowParentRunId))
|
|
5660
6079
|
: null;
|
|
@@ -5689,6 +6108,37 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5689
6108
|
interrupt: undefined,
|
|
5690
6109
|
});
|
|
5691
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
|
+
};
|
|
5692
6142
|
deps.state.foregroundControls.set(runId, foregroundControl);
|
|
5693
6143
|
deps.state.lastForegroundControlId = runId;
|
|
5694
6144
|
}
|
|
@@ -5783,9 +6233,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5783
6233
|
if (workflowLaunchObserver) {
|
|
5784
6234
|
const singleTask = hasTasks && effectiveParams.tasks?.length === 1 ? effectiveParams.tasks[0] : undefined;
|
|
5785
6235
|
const launch = hasSingle
|
|
5786
|
-
? { 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 } : {}) }
|
|
5787
6237
|
: singleTask
|
|
5788
|
-
? { 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 } : {}) }
|
|
5789
6239
|
: undefined;
|
|
5790
6240
|
if (launch) {
|
|
5791
6241
|
workflowLaunchObservers.delete(params);
|
|
@@ -5793,31 +6243,33 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5793
6243
|
}
|
|
5794
6244
|
}
|
|
5795
6245
|
const asyncResult = runAsyncPath(execData, deps);
|
|
5796
|
-
if (asyncResult) return attachMission(withResolvedContext(withForkThinkingNotes(asyncResult, forkThinkingDowngrades), contextPolicy.contextSummary));
|
|
6246
|
+
if (asyncResult) return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(asyncResult, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget));
|
|
5797
6247
|
if (foregroundControl) {
|
|
5798
6248
|
writeNestedForegroundEvent("subagent.nested.started");
|
|
5799
6249
|
nestedForegroundStarted = true;
|
|
5800
6250
|
}
|
|
6251
|
+
const runFanoutAnnotateContent = !delegatedExecution;
|
|
5801
6252
|
if (hasChain && effectiveParams.chain) {
|
|
5802
6253
|
const result = await runChainPath(execData, deps);
|
|
5803
6254
|
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
5804
|
-
return attachMission(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary));
|
|
6255
|
+
return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget, { annotateContent: runFanoutAnnotateContent }));
|
|
5805
6256
|
}
|
|
5806
6257
|
if (hasTasks && effectiveParams.tasks) {
|
|
5807
6258
|
const result = await runParallelPath(execData, deps);
|
|
5808
6259
|
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
5809
|
-
return attachMission(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary));
|
|
6260
|
+
return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget, { annotateContent: runFanoutAnnotateContent }));
|
|
5810
6261
|
}
|
|
5811
6262
|
if (hasSingle) {
|
|
5812
6263
|
const result = await runSinglePath(execData, deps);
|
|
5813
6264
|
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
5814
|
-
return attachMission(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary));
|
|
6265
|
+
return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget, { annotateContent: runFanoutAnnotateContent }));
|
|
5815
6266
|
}
|
|
5816
6267
|
} catch (error) {
|
|
5817
6268
|
const errorResult = withForkThinkingNotes(toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary), forkThinkingDowngrades);
|
|
5818
6269
|
if (nestedForegroundStarted) writeNestedForegroundEvent("subagent.nested.completed", errorResult);
|
|
5819
6270
|
return attachMission(errorResult);
|
|
5820
6271
|
} finally {
|
|
6272
|
+
if (activeAsyncCapacity && !activeAsyncCapacity.owner.runnerStartedAt) activeAsyncCapacity.rollback();
|
|
5821
6273
|
if (foregroundControl) {
|
|
5822
6274
|
settleForegroundSchedulingOwner(foregroundControl);
|
|
5823
6275
|
removeForegroundControlIfIdle(deps.state, runId);
|
|
@@ -5886,6 +6338,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5886
6338
|
delete privateParams.delegatedAllowZeroToolBudget;
|
|
5887
6339
|
if (thinkingOverride !== undefined) delegatedThinkingOverrides.set(delegatedParams, thinkingOverride);
|
|
5888
6340
|
if (allowZeroToolBudget) delegatedZeroToolBudgets.add(delegatedParams);
|
|
6341
|
+
delegatedExecutions.add(delegatedParams);
|
|
5889
6342
|
return execute(id, delegatedParams, signal, onUpdate, ctx);
|
|
5890
6343
|
};
|
|
5891
6344
|
|