pi-subagents 0.47.1 → 0.49.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 +51 -0
- package/README.md +2 -0
- package/docs/agents.md +1 -0
- package/docs/configuration.md +74 -7
- package/docs/missions.md +4 -2
- package/docs/observability.md +27 -3
- package/docs/tool-reference.md +1 -1
- package/package.json +1 -1
- package/src/agents/agents.ts +36 -15
- package/src/api/preflight.ts +1 -1
- package/src/extension/config.ts +26 -0
- package/src/extension/doctor.ts +40 -0
- package/src/extension/index.ts +39 -8
- package/src/extension/public-execution.ts +35 -4
- package/src/extension/rpc.ts +3 -9
- package/src/extension/schemas.ts +10 -9
- package/src/extension/tool-description.ts +10 -8
- package/src/inspectors/herdr/actions.ts +11 -2
- package/src/inspectors/herdr/inspector-runner.ts +16 -3
- package/src/intercom/intercom-bridge.ts +7 -2
- package/src/missions/lifecycle.ts +4 -7
- package/src/missions/store.ts +12 -7
- package/src/missions/workflow-state.ts +6 -2
- package/src/runs/background/active-async-capacity.ts +431 -0
- package/src/runs/background/active-run-index.ts +9 -5
- package/src/runs/background/async-execution.ts +141 -65
- package/src/runs/background/async-job-tracker.ts +4 -0
- package/src/runs/background/async-resume.ts +18 -2
- package/src/runs/background/async-status.ts +11 -5
- package/src/runs/background/chain-append.ts +33 -15
- package/src/runs/background/fleet-view.ts +18 -5
- package/src/runs/background/owned-process-tree.ts +104 -0
- package/src/runs/background/process-terminal.ts +17 -3
- package/src/runs/background/resume-guidance.ts +27 -7
- package/src/runs/background/retained-children.ts +14 -6
- package/src/runs/background/run-status.ts +101 -5
- package/src/runs/background/stale-run-reconciler.ts +3 -3
- package/src/runs/background/subagent-runner.ts +60 -32
- 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 +172 -0
- package/src/runs/foreground/subagent-executor.ts +829 -203
- package/src/runs/shared/acceptance.ts +13 -4
- package/src/runs/shared/completion-guard.ts +17 -1
- package/src/runs/shared/llm-intent-arbiter.ts +302 -0
- package/src/runs/shared/parallel-utils.ts +2 -0
- package/src/runs/shared/pi-args.ts +44 -1
- package/src/runs/shared/run-fanout-budget.ts +280 -0
- package/src/runs/shared/single-output.ts +4 -2
- package/src/runs/shared/task-intent.ts +19 -3
- package/src/runs/shared/worktree.ts +17 -5
- package/src/shared/agent-stream-options.ts +5 -0
- package/src/shared/artifacts.ts +2 -6
- package/src/shared/display-text.ts +50 -0
- package/src/shared/node-executable.ts +21 -0
- package/src/shared/types.ts +115 -4
- package/src/shared/utils.ts +3 -1
- package/src/tui/fleet-status.ts +7 -5
- package/src/tui/fleet-transcript.ts +1 -48
- package/src/tui/fleet.ts +228 -13
- package/src/tui/render.ts +86 -39
- package/src/watchdog/permission-arbiter.ts +2 -1
- package/src/watchdog/review.ts +4 -3
- package/src/workflows/chat-progress.ts +8 -2
- package/src/workflows/scripted-workflow.ts +66 -10
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
settleForegroundSchedulingOwner,
|
|
18
18
|
updateForegroundChild,
|
|
19
19
|
} from "./foreground-control.ts";
|
|
20
|
+
import { getLivePromptAudit, rewritePromptWithGuidance, updateLiveEffectivePrompt } from "./prompt-audit.ts";
|
|
20
21
|
import { persistForegroundRunHistory, MAX_REMEMBERED_FOREGROUND_RUNS } from "./foreground-history.ts";
|
|
21
22
|
import { resolveExecutionAgentScope } from "../../agents/agent-scope.ts";
|
|
22
23
|
import { handleManagementAction } from "../../agents/agent-management.ts";
|
|
@@ -54,6 +55,7 @@ import {
|
|
|
54
55
|
import { discoverAvailableSkills, normalizeSkillInput } from "../../agents/skills.ts";
|
|
55
56
|
import { buildAsyncRunnerSteps, DEFAULT_ASYNC_TIMEOUT_MS, executeAsyncChain, executeAsyncSingle, formatAsyncStartedMessage, isAsyncAvailable, workflowAwaitedAsyncResultPath } from "../background/async-execution.ts";
|
|
56
57
|
import { updateActiveRunIndex } from "../background/active-run-index.ts";
|
|
58
|
+
import { acquireActiveAsyncCapacity, ActiveAsyncCapacityError, getActiveAsyncCapacitySnapshot, resolveMaxActiveAsyncRunsPerSession, transferActiveAsyncCapacity, type ActiveAsyncCapacityHandle } from "../background/active-async-capacity.ts";
|
|
57
59
|
import { isScheduledRunAction, type ScheduledRunAction } from "../background/scheduled-runs.ts";
|
|
58
60
|
import { enqueueChainAppendRequest, readPendingChainAppendRequests, runnerStepOutputNames } from "../background/chain-append.ts";
|
|
59
61
|
import { ChainOutputValidationError, validateChainOutputBindingsWithContext } from "../shared/chain-outputs.ts";
|
|
@@ -64,6 +66,7 @@ import { applyIntercomBridgeToAgent, INTERCOM_BRIDGE_MARKER, resolveIntercomBrid
|
|
|
64
66
|
import { formatControlIntercomMessage, formatControlNoticeMessage, resolveControlConfig, shouldNotifyControlEvent } from "../shared/subagent-control.ts";
|
|
65
67
|
import { resolveTurnBudgetConfig } from "../shared/turn-budget.ts";
|
|
66
68
|
import { formatSpawnBudget, getSpawnBudgetSnapshot, grantSpawnBudget, preflightSpawnBudget, preflightSpawnBudgetGrant, reserveSpawnBudget } from "../shared/spawn-budget.ts";
|
|
69
|
+
import { claimRunFanoutBatch, claimRunFanoutBatchWithCommit, createRunFanoutBudget, decodeRunFanoutBudgetDescriptor, formatRunFanoutBudget, getRunFanoutBudgetSnapshot, readRunFanoutBudgetDescriptor, RunFanoutLimitError, RUN_FANOUT_BUDGET_ENV, writeRunFanoutBudgetDescriptor } from "../shared/run-fanout-budget.ts";
|
|
67
70
|
import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
|
|
68
71
|
import { usageBudgetExceededMessage, usageBudgetState, validateUsageBudgetConfig } from "../shared/usage-budget.ts";
|
|
69
72
|
import { intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
@@ -71,6 +74,7 @@ import { isAgentContractV1 } from "../shared/agent-contract.ts";
|
|
|
71
74
|
import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
72
75
|
import { cleanupStructuredOutputRuntime, createStructuredOutputRuntime } from "../shared/structured-output.ts";
|
|
73
76
|
import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, readStatus, resolveChildCwd, sumResultsCost, sumResultsUsage } from "../../shared/utils.ts";
|
|
77
|
+
import { createTaskMutationArbiter } from "../shared/llm-intent-arbiter.ts";
|
|
74
78
|
import { DEFAULT_GLOBAL_CONCURRENCY_LIMIT, Semaphore } from "../shared/parallel-utils.ts";
|
|
75
79
|
import { discardPreservedWorktrees, formatParallelHandoffError, formatParallelHandoffReference, parallelHandoffPath, writeParallelHandoffGroup, writePendingParallelHandoff } from "../shared/parallel-handoff.ts";
|
|
76
80
|
import { summarizeContextModes, type ContextMode, type ContextSummary } from "../shared/context-mode.ts";
|
|
@@ -135,6 +139,7 @@ import {
|
|
|
135
139
|
type Details,
|
|
136
140
|
type ExtensionConfig,
|
|
137
141
|
type ForegroundRunControl,
|
|
142
|
+
type IntercomBridgeConfig,
|
|
138
143
|
type IntercomEventBus,
|
|
139
144
|
type JsonSchemaObject,
|
|
140
145
|
type MaxOutputConfig,
|
|
@@ -143,6 +148,7 @@ import {
|
|
|
143
148
|
type ResolvedControlConfig,
|
|
144
149
|
type ResolvedTurnBudget,
|
|
145
150
|
type ResolvedToolBudget,
|
|
151
|
+
type RunFanoutBudgetDescriptor,
|
|
146
152
|
type SingleResult,
|
|
147
153
|
type ToolBudgetConfig,
|
|
148
154
|
type TurnBudgetConfig,
|
|
@@ -161,6 +167,7 @@ import {
|
|
|
161
167
|
resolveTopLevelParallelMaxTasks,
|
|
162
168
|
resolveChildMaxSubagentDepth,
|
|
163
169
|
resolveCurrentMaxSubagentDepth,
|
|
170
|
+
resolveMaxSubagentSpawnsPerRun,
|
|
164
171
|
wrapForkTask,
|
|
165
172
|
} from "../../shared/types.ts";
|
|
166
173
|
|
|
@@ -284,12 +291,18 @@ export interface SubagentParamsLike {
|
|
|
284
291
|
workflowKey?: string;
|
|
285
292
|
workflowChildAsyncId?: string;
|
|
286
293
|
suppressRoutineResultIntercom?: boolean;
|
|
294
|
+
/** Internal inherited cumulative run-tree budget. */
|
|
295
|
+
runFanoutBudget?: RunFanoutBudgetDescriptor;
|
|
296
|
+
/** Internal workflow host admission proof. */
|
|
297
|
+
runFanoutAdmitted?: boolean;
|
|
287
298
|
/** Internal durable-run compatibility fields. Public callers must use workflowScript. */
|
|
288
299
|
chain?: ChainStep[];
|
|
289
300
|
tasks?: TaskParam[];
|
|
290
301
|
concurrency?: number;
|
|
291
302
|
worktree?: boolean;
|
|
292
303
|
context?: "fresh" | "fork";
|
|
304
|
+
/** Per-run intercom bridge config. It replaces the global config for this launch only. */
|
|
305
|
+
intercomBridge?: IntercomBridgeConfig;
|
|
293
306
|
async?: boolean;
|
|
294
307
|
foregroundOnly?: boolean;
|
|
295
308
|
timeoutMs?: number;
|
|
@@ -377,6 +390,8 @@ interface ExecutionContextData {
|
|
|
377
390
|
signal: AbortSignal;
|
|
378
391
|
onUpdate?: (r: AgentToolResult<Details>) => void;
|
|
379
392
|
agents: AgentConfig[];
|
|
393
|
+
/** Discovered agent definitions before per-run bridge injection. */
|
|
394
|
+
recoveryAgents: AgentConfig[];
|
|
380
395
|
runId: string;
|
|
381
396
|
shareEnabled: boolean;
|
|
382
397
|
sessionRoot: string;
|
|
@@ -388,6 +403,7 @@ interface ExecutionContextData {
|
|
|
388
403
|
artifactsDir: string;
|
|
389
404
|
backgroundRequestedWhileClarifying: boolean;
|
|
390
405
|
effectiveAsync: boolean;
|
|
406
|
+
asyncRunId: string;
|
|
391
407
|
controlConfig: ResolvedControlConfig;
|
|
392
408
|
intercomBridge: IntercomBridgeState;
|
|
393
409
|
nestedRoute?: NestedRouteInfo;
|
|
@@ -404,6 +420,9 @@ interface ExecutionContextData {
|
|
|
404
420
|
parentSessionId: string | null;
|
|
405
421
|
parentPiSessionId?: string;
|
|
406
422
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
423
|
+
runFanoutBudget: RunFanoutBudgetDescriptor;
|
|
424
|
+
topLevelAsyncCapacityEligible: boolean;
|
|
425
|
+
activeAsyncCapacity?: ActiveAsyncCapacityHandle;
|
|
407
426
|
}
|
|
408
427
|
|
|
409
428
|
function resolveRequestedCwd(runtimeCwd: string, requestedCwd: string | undefined): string {
|
|
@@ -432,6 +451,17 @@ function getForegroundControl(state: SubagentState, runId: string | undefined) {
|
|
|
432
451
|
return newest;
|
|
433
452
|
}
|
|
434
453
|
|
|
454
|
+
function promptAuditRedoParams(value: unknown, rewrittenTask: string): SubagentParamsLike {
|
|
455
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Prompt redo is missing a safe live launch contract.");
|
|
456
|
+
const params = { ...(value as SubagentParamsLike), task: rewrittenTask, async: false };
|
|
457
|
+
delete params.workflowParentRunId;
|
|
458
|
+
delete params.workflowKey;
|
|
459
|
+
delete params.workflowChildAsyncId;
|
|
460
|
+
delete params.suppressRoutineResultIntercom;
|
|
461
|
+
if (params.worktree === true && Array.isArray(params.tasks)) delete params.cwd;
|
|
462
|
+
return params;
|
|
463
|
+
}
|
|
464
|
+
|
|
435
465
|
function formatForegroundActivity(control: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never): string | undefined {
|
|
436
466
|
const facts: string[] = [];
|
|
437
467
|
if (control.currentTool && control.currentToolStartedAt) facts.push(`tool ${control.currentTool} for ${Math.floor(Math.max(0, Date.now() - control.currentToolStartedAt) / 1000)}s`);
|
|
@@ -483,12 +513,18 @@ function withSpawnBudgetStatus(
|
|
|
483
513
|
sessionId: string | null,
|
|
484
514
|
): AgentToolResult<Details> {
|
|
485
515
|
const spawnBudget = getSpawnBudgetSnapshot(state, config, sessionId);
|
|
516
|
+
const activeAsyncCapacity = sessionId
|
|
517
|
+
? getActiveAsyncCapacitySnapshot(sessionId, resolveMaxActiveAsyncRunsPerSession(config.maxActiveAsyncRunsPerSession), {
|
|
518
|
+
liveWorkflowRunIds: new Set(state.workflowControllers?.keys() ?? []),
|
|
519
|
+
})
|
|
520
|
+
: { used: 0, limit: resolveMaxActiveAsyncRunsPerSession(config.maxActiveAsyncRunsPerSession) ?? 0 };
|
|
521
|
+
state.activeAsyncCapacity = activeAsyncCapacity;
|
|
486
522
|
return {
|
|
487
523
|
...result,
|
|
488
524
|
content: result.content.map((item, index) => index === 0 && item.type === "text"
|
|
489
|
-
? { ...item, text: `${formatSpawnBudget(spawnBudget)}\n${item.text}` }
|
|
525
|
+
? { ...item, text: `${formatSpawnBudget(spawnBudget)}\nActive async capacity: ${activeAsyncCapacity.used}/${activeAsyncCapacity.limit || "unlimited"} used\n${item.text}` }
|
|
490
526
|
: item),
|
|
491
|
-
details: { ...result.details, spawnBudget },
|
|
527
|
+
details: { ...result.details, spawnBudget, activeAsyncCapacity },
|
|
492
528
|
};
|
|
493
529
|
}
|
|
494
530
|
|
|
@@ -509,6 +545,31 @@ function countRequestedSubagentSpawns(params: SubagentParamsLike, config: Extens
|
|
|
509
545
|
return params.agent ? 1 : 0;
|
|
510
546
|
}
|
|
511
547
|
|
|
548
|
+
function staticRunFanoutPaths(params: SubagentParamsLike): string[] {
|
|
549
|
+
if (params.tasks) return params.tasks.map((_, index) => `tasks[${index}]`);
|
|
550
|
+
if (params.chain) return params.chain.flatMap((step, stepIndex) => {
|
|
551
|
+
if (isDynamicParallelStep(step) || "checkpoint" in step) return [];
|
|
552
|
+
if (isParallelStep(step)) return step.parallel.map((_, itemIndex) => `chain[${stepIndex}].parallel[${itemIndex}]`);
|
|
553
|
+
return [`chain[${stepIndex}]`];
|
|
554
|
+
});
|
|
555
|
+
return params.agent ? ["single"] : [];
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function runFanoutErrorResult(error: RunFanoutLimitError, mode: "single" | "parallel" | "chain"): AgentToolResult<Details> {
|
|
559
|
+
return { content: [{ type: "text", text: error.message }], isError: true, details: { mode, results: [], runFanoutBudget: error.snapshot, runFanoutRejection: error.rejection } };
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function withRunFanoutBudget(result: AgentToolResult<Details>, descriptor: RunFanoutBudgetDescriptor, options: { annotateContent?: boolean } = {}): AgentToolResult<Details> {
|
|
563
|
+
const runFanoutBudget = getRunFanoutBudgetSnapshot(descriptor);
|
|
564
|
+
return {
|
|
565
|
+
...result,
|
|
566
|
+
content: options.annotateContent === false
|
|
567
|
+
? result.content
|
|
568
|
+
: result.content.map((item, index) => index === 0 && item.type === "text" ? { ...item, text: `${formatRunFanoutBudget(runFanoutBudget)}\n${item.text}` } : item),
|
|
569
|
+
details: { ...result.details, runFanoutBudget },
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
|
|
512
573
|
function foregroundStatusResult(control: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never): AgentToolResult<Details> {
|
|
513
574
|
let nestedWarning: string | undefined;
|
|
514
575
|
try {
|
|
@@ -1141,18 +1202,30 @@ function appendStepToAsyncChain(input: {
|
|
|
1141
1202
|
}
|
|
1142
1203
|
|
|
1143
1204
|
try {
|
|
1205
|
+
const asyncDir = resolved.location.asyncDir;
|
|
1206
|
+
if (!asyncDir) throw new Error(`Run '${resolved.id}' is missing its async directory.`);
|
|
1207
|
+
const runFanoutBudget = readRunFanoutBudgetDescriptor(asyncDir);
|
|
1208
|
+
if (!runFanoutBudget) throw new Error(`Run '${resolved.id}' is missing its run fan-out budget identity.`);
|
|
1209
|
+
const startIndex = (status.chainStepCount ?? status.steps?.length ?? 0) + pendingAppendRequests.reduce((total, request) => total + request.steps.length, 0);
|
|
1210
|
+
const appendPaths = chain.flatMap((step, localIndex) => {
|
|
1211
|
+
const absoluteIndex = startIndex + localIndex;
|
|
1212
|
+
if (isDynamicParallelStep(step) || "checkpoint" in step) return [];
|
|
1213
|
+
if (isParallelStep(step)) return step.parallel.map((_, itemIndex) => `chain[${absoluteIndex}].parallel[${itemIndex}]`);
|
|
1214
|
+
return [`chain[${absoluteIndex}]`];
|
|
1215
|
+
});
|
|
1144
1216
|
const result = enqueueChainAppendRequest({
|
|
1145
|
-
asyncDir
|
|
1217
|
+
asyncDir,
|
|
1146
1218
|
runId: resolved.id,
|
|
1147
1219
|
steps: built.steps,
|
|
1220
|
+
admit: (persist) => claimRunFanoutBatchWithCommit(runFanoutBudget, appendPaths, persist),
|
|
1148
1221
|
});
|
|
1149
1222
|
const stepText = built.steps.length === 1 ? "step" : "steps";
|
|
1150
1223
|
return {
|
|
1151
1224
|
content: [{
|
|
1152
1225
|
type: "text",
|
|
1153
|
-
text: `Append queued for chain run ${resolved.id}: ${built.steps.length} ${stepText}. It becomes eligible after the chain's already-queued steps finish. Pending appends: ${result.pendingCount}
|
|
1226
|
+
text: `Append queued for chain run ${resolved.id}: ${built.steps.length} ${stepText}. It becomes eligible after the chain's already-queued steps finish. Pending appends: ${result.pendingCount}.${result.bookkeepingError ? ` Bookkeeping warning: ${result.bookkeepingError}` : ""}`,
|
|
1154
1227
|
}],
|
|
1155
|
-
details: { mode: "management", results: [], asyncId: resolved.id, asyncDir
|
|
1228
|
+
details: { mode: "management", results: [], asyncId: resolved.id, asyncDir },
|
|
1156
1229
|
};
|
|
1157
1230
|
} catch (error) {
|
|
1158
1231
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1443,17 +1516,18 @@ async function resumeAsyncRun(input: {
|
|
|
1443
1516
|
const discoveredAgents = discovered.agents;
|
|
1444
1517
|
const modelScope = discovered.modelScope;
|
|
1445
1518
|
const sessionName = resolveIntercomSessionTarget(input.deps.pi.getSessionName(), input.ctx.sessionManager.getSessionId());
|
|
1519
|
+
const recoveryDescriptor = "recoveryDescriptor" in target ? target.recoveryDescriptor : undefined;
|
|
1446
1520
|
const intercomBridge = resolveIntercomBridge({
|
|
1447
1521
|
config: input.deps.config.intercomBridge,
|
|
1522
|
+
override: input.params.intercomBridge ?? recoveryDescriptor?.intercomBridge,
|
|
1448
1523
|
context: input.params.context,
|
|
1449
1524
|
orchestratorTarget: sessionName,
|
|
1450
1525
|
});
|
|
1451
1526
|
const agents = intercomBridge.active
|
|
1452
1527
|
? discoveredAgents.map((agent) => applyIntercomBridgeToAgent(agent, intercomBridge))
|
|
1453
1528
|
: discoveredAgents;
|
|
1454
|
-
const
|
|
1455
|
-
const
|
|
1456
|
-
const agentConfig: AgentConfig | undefined = discoveredAgentConfig ?? (recoveryDescriptor ? {
|
|
1529
|
+
const discoveredAgentConfig = discoveredAgents.find((agent) => agent.name === target.agent);
|
|
1530
|
+
const baseAgentConfig: AgentConfig | undefined = discoveredAgentConfig ?? (recoveryDescriptor ? {
|
|
1457
1531
|
name: recoveryDescriptor.agent,
|
|
1458
1532
|
description: "Persisted async recovery contract",
|
|
1459
1533
|
systemPrompt: "",
|
|
@@ -1463,7 +1537,7 @@ async function resumeAsyncRun(input: {
|
|
|
1463
1537
|
source: "project",
|
|
1464
1538
|
filePath: recoveryDescriptor.agentFilePath ?? path.join(getProjectSubagentsDir(recoveryDescriptor.cwd), "recovery-agent"),
|
|
1465
1539
|
} : undefined);
|
|
1466
|
-
if (!
|
|
1540
|
+
if (!baseAgentConfig) {
|
|
1467
1541
|
return {
|
|
1468
1542
|
content: [{ type: "text", text: `Unknown agent for resume: ${target.agent}` }],
|
|
1469
1543
|
isError: true,
|
|
@@ -1487,6 +1561,20 @@ async function resumeAsyncRun(input: {
|
|
|
1487
1561
|
};
|
|
1488
1562
|
}
|
|
1489
1563
|
const runId = randomUUID().slice(0, 8);
|
|
1564
|
+
const topLevelResume = depth === 0 && !resolveInheritedNestedRouteFromEnv() && !input.params.workflowParentRunId;
|
|
1565
|
+
let activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined;
|
|
1566
|
+
try {
|
|
1567
|
+
activeAsyncCapacity = topLevelResume ? acquireActiveAsyncCapacity({
|
|
1568
|
+
sessionId: input.deps.state.currentSessionId!,
|
|
1569
|
+
limit: resolveMaxActiveAsyncRunsPerSession(input.deps.config.maxActiveAsyncRunsPerSession),
|
|
1570
|
+
runId,
|
|
1571
|
+
kind: "runner",
|
|
1572
|
+
asyncDir: path.join(DIRS.async, runId),
|
|
1573
|
+
}, { liveWorkflowRunIds: new Set(input.deps.state.workflowControllers?.keys() ?? []) }) : undefined;
|
|
1574
|
+
} catch (error) {
|
|
1575
|
+
if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "chain", results: [], activeAsyncCapacity: error.snapshot } };
|
|
1576
|
+
throw error;
|
|
1577
|
+
}
|
|
1490
1578
|
const artifactConfig: ArtifactConfig = omitUndefinedProperties({ ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir });
|
|
1491
1579
|
const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
1492
1580
|
const contextPolicy = resolveExplicitContextPolicy(input.params);
|
|
@@ -1538,9 +1626,14 @@ async function resumeAsyncRun(input: {
|
|
|
1538
1626
|
controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
|
|
1539
1627
|
childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
1540
1628
|
globalConcurrencyLimit: input.deps.config.globalConcurrencyLimit,
|
|
1629
|
+
runFanoutBudget: createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
|
|
1541
1630
|
capabilityCeiling: intersectSubagentCapabilityCeilings("capabilityCeiling" in target ? target.capabilityCeiling : undefined, resolveCurrentSubagentCapabilityCeiling(input.deps.state.currentSessionId)),
|
|
1631
|
+
activeAsyncCapacity,
|
|
1542
1632
|
}));
|
|
1543
|
-
if (result.isError)
|
|
1633
|
+
if (result.isError) {
|
|
1634
|
+
activeAsyncCapacity?.rollback();
|
|
1635
|
+
return result;
|
|
1636
|
+
}
|
|
1544
1637
|
const attachedId = result.details.asyncId ?? runId;
|
|
1545
1638
|
const lines = [
|
|
1546
1639
|
`Attached async subagent ${target.runId} as the first step of a new chain.`,
|
|
@@ -1559,17 +1652,45 @@ async function resumeAsyncRun(input: {
|
|
|
1559
1652
|
if (!revivalSessionFile) {
|
|
1560
1653
|
return { content: [{ type: "text", text: `Async child '${target.runId}' has no persisted session file to resume.` }], isError: true, details: { mode: "management", results: [] } };
|
|
1561
1654
|
}
|
|
1655
|
+
if (target.source === "async" && !recoveryDescriptor) {
|
|
1656
|
+
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: [] } };
|
|
1657
|
+
}
|
|
1562
1658
|
const runId = randomUUID().slice(0, 8);
|
|
1563
|
-
const
|
|
1659
|
+
const topLevelResume = depth === 0 && !resolveInheritedNestedRouteFromEnv() && !input.params.workflowParentRunId;
|
|
1660
|
+
let activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined;
|
|
1661
|
+
try {
|
|
1662
|
+
activeAsyncCapacity = !topLevelResume ? undefined : target.source === "async"
|
|
1663
|
+
? transferActiveAsyncCapacity({
|
|
1664
|
+
sessionId: input.deps.state.currentSessionId!,
|
|
1665
|
+
limit: resolveMaxActiveAsyncRunsPerSession(input.deps.config.maxActiveAsyncRunsPerSession),
|
|
1666
|
+
sourceRunId: target.runId,
|
|
1667
|
+
runId,
|
|
1668
|
+
asyncDir: path.join(DIRS.async, runId),
|
|
1669
|
+
})
|
|
1670
|
+
: acquireActiveAsyncCapacity({
|
|
1671
|
+
sessionId: input.deps.state.currentSessionId!,
|
|
1672
|
+
limit: resolveMaxActiveAsyncRunsPerSession(input.deps.config.maxActiveAsyncRunsPerSession),
|
|
1673
|
+
runId,
|
|
1674
|
+
kind: "runner",
|
|
1675
|
+
asyncDir: path.join(DIRS.async, runId),
|
|
1676
|
+
});
|
|
1677
|
+
} catch (error) {
|
|
1678
|
+
if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "single", results: [], activeAsyncCapacity: error.snapshot } };
|
|
1679
|
+
return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "single", results: [] } };
|
|
1680
|
+
}
|
|
1681
|
+
const recoveryAgentConfig = recoveryDescriptor ? applySteeringRecoveryAgentConfig(baseAgentConfig, recoveryDescriptor) : baseAgentConfig;
|
|
1682
|
+
const agentConfig = intercomBridge.active ? applyIntercomBridgeToAgent(recoveryAgentConfig, intercomBridge) : recoveryAgentConfig;
|
|
1564
1683
|
const artifactConfig: ArtifactConfig = recoveryDescriptor?.artifactConfig ?? omitUndefinedProperties({ ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir });
|
|
1565
1684
|
const artifactsDir = recoveryDescriptor?.artifactsDir ?? getArtifactsDir(parentSessionFile, effectiveCwd, artifactConfig.dir);
|
|
1566
1685
|
const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
1567
1686
|
const parentModel = input.parentModel;
|
|
1687
|
+
const revivalAsyncDir = path.join(DIRS.async, runId);
|
|
1568
1688
|
const result = executeAsyncSingle(runId, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
|
|
1569
1689
|
agent: target.agent,
|
|
1570
1690
|
task: buildRevivedAsyncTask(target as Parameters<typeof buildRevivedAsyncTask>[0], effectiveFollowUp),
|
|
1571
1691
|
goal: effectiveFollowUp,
|
|
1572
|
-
agentConfig
|
|
1692
|
+
agentConfig,
|
|
1693
|
+
recoveryAgentConfig,
|
|
1573
1694
|
ctx: compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
1574
1695
|
pi: input.deps.pi,
|
|
1575
1696
|
cwd: input.requestCwd,
|
|
@@ -1604,6 +1725,7 @@ async function resumeAsyncRun(input: {
|
|
|
1604
1725
|
worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
|
|
1605
1726
|
worktreeBaseDir: input.deps.config.worktreeBaseDir,
|
|
1606
1727
|
controlConfig: recoveryDescriptor?.controlConfig ?? resolveControlConfig(input.deps.config.control, input.params.control),
|
|
1728
|
+
intercomBridge: input.params.intercomBridge ?? recoveryDescriptor?.intercomBridge,
|
|
1607
1729
|
controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
|
|
1608
1730
|
childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
1609
1731
|
availableModels,
|
|
@@ -1618,10 +1740,28 @@ async function resumeAsyncRun(input: {
|
|
|
1618
1740
|
...(input.params.turnBudget !== undefined ? { turnBudget: input.params.turnBudget } : {}),
|
|
1619
1741
|
...(input.params.toolBudget !== undefined ? { toolBudget: input.params.toolBudget } : {}),
|
|
1620
1742
|
capabilityCeiling: intersectSubagentCapabilityCeilings("capabilityCeiling" in target ? target.capabilityCeiling : undefined, recoveryDescriptor?.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(input.deps.state.currentSessionId)),
|
|
1743
|
+
runFanoutBudget: input.params.runFanoutBudget ?? recoveryDescriptor?.runFanoutBudget ?? createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
|
|
1621
1744
|
parentWorkflowRunId: input.params.workflowParentRunId,
|
|
1622
1745
|
workflowKey: input.params.workflowKey,
|
|
1746
|
+
activeAsyncCapacity,
|
|
1623
1747
|
}));
|
|
1624
|
-
if (result.isError)
|
|
1748
|
+
if (result.isError) {
|
|
1749
|
+
const startedStatus = readStatus(revivalAsyncDir);
|
|
1750
|
+
if (input.params.workflowParentRunId !== undefined && startedStatus?.runId === runId && startedStatus.processTerminal?.runnerProcessInstanceId) {
|
|
1751
|
+
return {
|
|
1752
|
+
...result,
|
|
1753
|
+
details: {
|
|
1754
|
+
...result.details,
|
|
1755
|
+
runId,
|
|
1756
|
+
asyncId: runId,
|
|
1757
|
+
asyncDir: revivalAsyncDir,
|
|
1758
|
+
...(target.launchContractDigest ? { sourceLaunchContractDigest: target.launchContractDigest } : {}),
|
|
1759
|
+
},
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1762
|
+
activeAsyncCapacity?.rollback();
|
|
1763
|
+
return result;
|
|
1764
|
+
}
|
|
1625
1765
|
for (const brief of queuedBriefs) fs.rmSync(brief.path, { force: true });
|
|
1626
1766
|
if (queuedBriefs.length > 0 && sourceAsyncDir) {
|
|
1627
1767
|
const sourceStatus = readStatus(sourceAsyncDir);
|
|
@@ -2117,6 +2257,27 @@ export const DEFAULT_FOREGROUND_TIMEOUT_MS = 30 * 60 * 1000;
|
|
|
2117
2257
|
// stay unbounded as a whole, while each runner child has its own deadline.
|
|
2118
2258
|
export { DEFAULT_ASYNC_TIMEOUT_MS };
|
|
2119
2259
|
|
|
2260
|
+
/**
|
|
2261
|
+
* Maximum delay a Node.js timer accepts. Values above the 32-bit signed integer
|
|
2262
|
+
* ceiling overflow `setTimeout`, which silently clamps the delay to ~1ms and
|
|
2263
|
+
* fires almost immediately — so a run configured with a larger deadline would
|
|
2264
|
+
* terminate right away while reporting the long duration. Any timeout destined
|
|
2265
|
+
* for a timer must stay within this bound.
|
|
2266
|
+
*/
|
|
2267
|
+
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
2268
|
+
|
|
2269
|
+
/**
|
|
2270
|
+
* Resolve the optional global default runtime deadline from extension config
|
|
2271
|
+
* (`config.timeoutMs`). Returns undefined for unset or invalid values so callers
|
|
2272
|
+
* fall back to the built-in defaults. "Invalid" covers non-positive-integer
|
|
2273
|
+
* values and values above `MAX_TIMER_DELAY_MS`; the latter would overflow the
|
|
2274
|
+
* Node.js timer and expire the run almost immediately instead of running long.
|
|
2275
|
+
*/
|
|
2276
|
+
export function resolveConfigDefaultTimeoutMs(raw: unknown): number | undefined {
|
|
2277
|
+
if (typeof raw !== "number" || !Number.isInteger(raw) || raw <= 0 || raw > MAX_TIMER_DELAY_MS) return undefined;
|
|
2278
|
+
return raw;
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2120
2281
|
export function resolveForegroundTimeout(params: SubagentParamsLike, defaultTimeoutMs?: number): { timeoutMs?: number; error?: string } {
|
|
2121
2282
|
const rawTimeout = params.timeoutMs;
|
|
2122
2283
|
const rawMaxRuntime = params.maxRuntimeMs;
|
|
@@ -2140,14 +2301,18 @@ export function resolveForegroundTimeout(params: SubagentParamsLike, defaultTime
|
|
|
2140
2301
|
* Resolve the effective launch timeout for a single-agent run, applying the
|
|
2141
2302
|
* async/foreground default when neither the caller nor the agent set one.
|
|
2142
2303
|
*
|
|
2143
|
-
*
|
|
2144
|
-
*
|
|
2145
|
-
*
|
|
2146
|
-
*
|
|
2304
|
+
* A global config default (`config.timeoutMs`, passed as `configDefaultTimeoutMs`)
|
|
2305
|
+
* replaces the built-in 30-minute backstop wherever a concrete default is applied.
|
|
2306
|
+
* The async default is deliberately applied only to plain single-agent launches.
|
|
2307
|
+
* Composite launches keep their top-level execution unbounded when no timeout is
|
|
2308
|
+
* set — even with a config default — while their runner children resolve separate
|
|
2309
|
+
* deadlines. Exported so the executor wiring is directly testable.
|
|
2147
2310
|
*/
|
|
2148
|
-
export function resolveSingleAgentLaunchTimeout(params: SubagentParamsLike, async: boolean): { timeoutMs?: number; error?: string } {
|
|
2311
|
+
export function resolveSingleAgentLaunchTimeout(params: SubagentParamsLike, async: boolean, configDefaultTimeoutMs?: number): { timeoutMs?: number; error?: string } {
|
|
2149
2312
|
const isComposite = (params.chain?.length ?? 0) > 0 || (params.tasks?.length ?? 0) > 0 || params.workflowScript !== undefined;
|
|
2150
|
-
const
|
|
2313
|
+
const foregroundDefault = configDefaultTimeoutMs ?? DEFAULT_FOREGROUND_TIMEOUT_MS;
|
|
2314
|
+
const asyncSingleDefault = configDefaultTimeoutMs ?? DEFAULT_ASYNC_TIMEOUT_MS;
|
|
2315
|
+
const defaultTimeoutMs = !async ? foregroundDefault : isComposite ? undefined : asyncSingleDefault;
|
|
2151
2316
|
return resolveForegroundTimeout(params, defaultTimeoutMs);
|
|
2152
2317
|
}
|
|
2153
2318
|
|
|
@@ -2560,8 +2725,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2560
2725
|
details: { mode: "single" as const, results: [] },
|
|
2561
2726
|
};
|
|
2562
2727
|
}
|
|
2563
|
-
const
|
|
2564
|
-
const id = requestedWorkflowChildAsyncId && path.basename(requestedWorkflowChildAsyncId) === requestedWorkflowChildAsyncId ? requestedWorkflowChildAsyncId : randomUUID();
|
|
2728
|
+
const id = data.asyncRunId;
|
|
2565
2729
|
const parentModel = data.parentModel;
|
|
2566
2730
|
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
2567
2731
|
pi: deps.pi,
|
|
@@ -2615,6 +2779,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2615
2779
|
shareEnabled,
|
|
2616
2780
|
sessionRoot,
|
|
2617
2781
|
chainSkills: [],
|
|
2782
|
+
activeAsyncCapacity: data.activeAsyncCapacity,
|
|
2618
2783
|
sessionFilesByFlatIndex: params.tasks.map((task, index) => sessionFileForTask(task.agent, index, task.model)),
|
|
2619
2784
|
thinkingOverridesByFlatIndex: params.tasks.map((task, index) => thinkingOverrideForTask(task.agent, index, task.model)),
|
|
2620
2785
|
contextForAgent: contextPolicy.contextForAgent,
|
|
@@ -2634,6 +2799,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2634
2799
|
usageBudget: data.usageBudget,
|
|
2635
2800
|
configToolBudget: data.configToolBudget,
|
|
2636
2801
|
capabilityCeiling: data.capabilityCeiling,
|
|
2802
|
+
runFanoutBudget: data.runFanoutBudget,
|
|
2637
2803
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2638
2804
|
parentWorkflowRunId: params.workflowParentRunId,
|
|
2639
2805
|
workflowKey: params.workflowKey,
|
|
@@ -2659,6 +2825,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2659
2825
|
shareEnabled,
|
|
2660
2826
|
sessionRoot,
|
|
2661
2827
|
chainSkills,
|
|
2828
|
+
activeAsyncCapacity: data.activeAsyncCapacity,
|
|
2662
2829
|
sessionFilesByFlatIndex: collectChainSessionFiles(chain, sessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
2663
2830
|
thinkingOverridesByFlatIndex: collectChainThinkingOverrides(chain, thinkingOverrideForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
2664
2831
|
contextForAgent: contextPolicy.contextForAgent,
|
|
@@ -2679,6 +2846,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2679
2846
|
usageBudget: data.usageBudget,
|
|
2680
2847
|
configToolBudget: data.configToolBudget,
|
|
2681
2848
|
capabilityCeiling: data.capabilityCeiling,
|
|
2849
|
+
runFanoutBudget: data.runFanoutBudget,
|
|
2682
2850
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2683
2851
|
parentWorkflowRunId: params.workflowParentRunId,
|
|
2684
2852
|
workflowKey: params.workflowKey,
|
|
@@ -2700,12 +2868,18 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2700
2868
|
const normalizedSkills = normalizeSkillInput(params.skill);
|
|
2701
2869
|
const skills = normalizedSkills === false ? [] : normalizedSkills;
|
|
2702
2870
|
const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, a.maxSubagentDepth);
|
|
2703
|
-
const
|
|
2871
|
+
const externalRunnerWithoutExplicitModel = a.runner?.type === "external-cli"
|
|
2872
|
+
&& params.model === undefined
|
|
2873
|
+
&& (a.model === undefined || (a.modelSource?.type === "subagents.defaultModel" && a.model === a.modelSource.model));
|
|
2874
|
+
const modelOverride = externalRunnerWithoutExplicitModel
|
|
2875
|
+
? undefined
|
|
2876
|
+
: resolveEffectiveSubagentModel(params.model as string | undefined, a.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope });
|
|
2704
2877
|
return executeAsyncSingle(id, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
|
|
2705
2878
|
agent: params.agent!,
|
|
2706
2879
|
task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
|
|
2707
2880
|
goal: params.task ?? "",
|
|
2708
2881
|
agentConfig: a,
|
|
2882
|
+
recoveryAgentConfig: data.recoveryAgents.find((agent) => agent.name === params.agent),
|
|
2709
2883
|
ctx: asyncCtx,
|
|
2710
2884
|
availableModels,
|
|
2711
2885
|
cwd: effectiveCwd,
|
|
@@ -2713,6 +2887,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2713
2887
|
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
2714
2888
|
artifactConfig,
|
|
2715
2889
|
shareEnabled,
|
|
2890
|
+
activeAsyncCapacity: data.activeAsyncCapacity,
|
|
2716
2891
|
sessionRoot,
|
|
2717
2892
|
sessionFile: sessionFileForTask(params.agent!, 0, modelOverride),
|
|
2718
2893
|
context: contextPolicy.contextForAgent(params.agent!),
|
|
@@ -2722,13 +2897,14 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2722
2897
|
...(params.reads !== undefined ? { reads: params.reads } : {}),
|
|
2723
2898
|
outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
|
|
2724
2899
|
modelOverride,
|
|
2725
|
-
thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride),
|
|
2900
|
+
thinkingOverride: externalRunnerWithoutExplicitModel ? undefined : thinkingOverrideForTask(params.agent!, 0, modelOverride),
|
|
2726
2901
|
maxSubagentDepth,
|
|
2727
2902
|
waitToolEnabled: deps.waitToolEnabled,
|
|
2728
2903
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
2729
2904
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
2730
2905
|
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
2731
2906
|
controlConfig,
|
|
2907
|
+
intercomBridge: params.intercomBridge,
|
|
2732
2908
|
controlIntercomTarget,
|
|
2733
2909
|
childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(agent, index) : undefined,
|
|
2734
2910
|
nestedRoute,
|
|
@@ -2741,6 +2917,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2741
2917
|
usageBudget: data.usageBudget,
|
|
2742
2918
|
configToolBudget: data.configToolBudget,
|
|
2743
2919
|
capabilityCeiling: data.capabilityCeiling,
|
|
2920
|
+
runFanoutBudget: data.runFanoutBudget,
|
|
2744
2921
|
parentWorkflowRunId: params.workflowParentRunId,
|
|
2745
2922
|
workflowKey: params.workflowKey,
|
|
2746
2923
|
}));
|
|
@@ -2831,6 +3008,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2831
3008
|
permissions: deps.config.permissions,
|
|
2832
3009
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2833
3010
|
capabilityCeiling: data.capabilityCeiling,
|
|
3011
|
+
runFanoutBudget: data.runFanoutBudget,
|
|
2834
3012
|
}));
|
|
2835
3013
|
|
|
2836
3014
|
if (chainResult.requestedAsync) {
|
|
@@ -2841,7 +3019,9 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2841
3019
|
details: { mode: "chain" as const, results: [] },
|
|
2842
3020
|
};
|
|
2843
3021
|
}
|
|
2844
|
-
const id =
|
|
3022
|
+
const id = data.asyncRunId;
|
|
3023
|
+
const capacity = acquireClarifiedBackgroundCapacity(data, deps, "chain", id);
|
|
3024
|
+
if (capacity.errorResult) return capacity.errorResult;
|
|
2845
3025
|
const parentModel = data.parentModel;
|
|
2846
3026
|
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
2847
3027
|
pi: deps.pi,
|
|
@@ -2857,42 +3037,48 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2857
3037
|
const rawAsyncChain = chainResult.requestedAsync.chain;
|
|
2858
3038
|
const asyncChain = wrapChainTasksForFork(rawAsyncChain, contextPolicy);
|
|
2859
3039
|
const firstAgent = firstChainAgent(rawAsyncChain);
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
3040
|
+
try {
|
|
3041
|
+
return executeAsyncChain(id, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
|
|
3042
|
+
chain: asyncChain,
|
|
3043
|
+
task: params.task,
|
|
3044
|
+
goal: resolveAsyncEventGoal(params.task, rawAsyncChain, firstAgent ? shouldForkAgent(contextPolicy, firstAgent) : false),
|
|
3045
|
+
agents,
|
|
3046
|
+
ctx: asyncCtx,
|
|
3047
|
+
availableModels: ctx.modelRegistry.getAvailable().map(toModelInfo),
|
|
3048
|
+
cwd: effectiveCwd,
|
|
3049
|
+
maxOutput: params.maxOutput,
|
|
3050
|
+
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
3051
|
+
artifactConfig,
|
|
3052
|
+
shareEnabled,
|
|
3053
|
+
sessionRoot,
|
|
3054
|
+
chainSkills: chainResult.requestedAsync.chainSkills,
|
|
3055
|
+
activeAsyncCapacity: capacity.activeAsyncCapacity,
|
|
3056
|
+
sessionFilesByFlatIndex: collectChainSessionFiles(asyncChain, sessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
3057
|
+
thinkingOverridesByFlatIndex: collectChainThinkingOverrides(asyncChain, thinkingOverrideForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
3058
|
+
contextForAgent: contextPolicy.contextForAgent,
|
|
3059
|
+
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
3060
|
+
maxSubagentDepth: currentMaxSubagentDepth,
|
|
3061
|
+
waitToolEnabled: deps.waitToolEnabled,
|
|
3062
|
+
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
3063
|
+
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
3064
|
+
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
3065
|
+
controlConfig,
|
|
3066
|
+
agentContract: params.agentContract,
|
|
3067
|
+
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
3068
|
+
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
3069
|
+
nestedRoute: data.nestedRoute,
|
|
3070
|
+
timeoutMs: data.timeoutMs,
|
|
3071
|
+
turnBudget: data.turnBudget,
|
|
3072
|
+
toolBudget: data.toolBudget,
|
|
3073
|
+
usageBudget: data.usageBudget,
|
|
3074
|
+
configToolBudget: data.configToolBudget,
|
|
3075
|
+
capabilityCeiling: data.capabilityCeiling,
|
|
3076
|
+
runFanoutBudget: data.runFanoutBudget,
|
|
3077
|
+
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
3078
|
+
}));
|
|
3079
|
+
} finally {
|
|
3080
|
+
finalizeClarifiedBackgroundCapacity(data, deps, capacity.activeAsyncCapacity);
|
|
3081
|
+
}
|
|
2896
3082
|
}
|
|
2897
3083
|
|
|
2898
3084
|
const rawChainDetails = chainResult.details ? compactOptional<Details>({ ...chainResult.details, runId, timeoutMs: data.timeoutMs }) : undefined;
|
|
@@ -2937,6 +3123,7 @@ interface ForegroundParallelRunInput {
|
|
|
2937
3123
|
intercomEvents: IntercomEventBus;
|
|
2938
3124
|
parentSessionId: string | null;
|
|
2939
3125
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
3126
|
+
runFanoutBudget: RunFanoutBudgetDescriptor;
|
|
2940
3127
|
signal: AbortSignal;
|
|
2941
3128
|
runId: string;
|
|
2942
3129
|
sessionDirForIndex: (idx?: number) => string | undefined;
|
|
@@ -2986,6 +3173,48 @@ function buildParallelModeError(message: string): AgentToolResult<Details> {
|
|
|
2986
3173
|
};
|
|
2987
3174
|
}
|
|
2988
3175
|
|
|
3176
|
+
function acquireClarifiedBackgroundCapacity(
|
|
3177
|
+
data: ExecutionContextData,
|
|
3178
|
+
deps: ExecutorDeps,
|
|
3179
|
+
mode: "single" | "parallel" | "chain",
|
|
3180
|
+
asyncRunId: string,
|
|
3181
|
+
): { activeAsyncCapacity?: ActiveAsyncCapacityHandle; errorResult?: AgentToolResult<Details> } {
|
|
3182
|
+
if (!data.topLevelAsyncCapacityEligible || !data.parentSessionId) return {};
|
|
3183
|
+
const liveWorkflowRunIds = new Set(deps.state.workflowControllers?.keys() ?? []);
|
|
3184
|
+
const activeLimit = resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession);
|
|
3185
|
+
try {
|
|
3186
|
+
const activeAsyncCapacity = acquireActiveAsyncCapacity({
|
|
3187
|
+
sessionId: data.parentSessionId,
|
|
3188
|
+
limit: activeLimit,
|
|
3189
|
+
runId: asyncRunId,
|
|
3190
|
+
kind: "runner",
|
|
3191
|
+
asyncDir: path.join(DIRS.async, asyncRunId),
|
|
3192
|
+
}, { liveWorkflowRunIds });
|
|
3193
|
+
deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(data.parentSessionId, activeLimit, { liveWorkflowRunIds });
|
|
3194
|
+
return { activeAsyncCapacity };
|
|
3195
|
+
} catch (error) {
|
|
3196
|
+
if (error instanceof ActiveAsyncCapacityError) {
|
|
3197
|
+
deps.state.activeAsyncCapacity = error.snapshot;
|
|
3198
|
+
return { errorResult: { content: [{ type: "text", text: error.message }], isError: true, details: { mode, results: [], activeAsyncCapacity: error.snapshot } } };
|
|
3199
|
+
}
|
|
3200
|
+
throw error;
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
function finalizeClarifiedBackgroundCapacity(
|
|
3205
|
+
data: ExecutionContextData,
|
|
3206
|
+
deps: ExecutorDeps,
|
|
3207
|
+
activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined,
|
|
3208
|
+
): void {
|
|
3209
|
+
if (!activeAsyncCapacity || !data.parentSessionId) return;
|
|
3210
|
+
if (!activeAsyncCapacity.owner.runnerStartedAt) activeAsyncCapacity.rollback();
|
|
3211
|
+
deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(
|
|
3212
|
+
data.parentSessionId,
|
|
3213
|
+
resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession),
|
|
3214
|
+
{ liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) },
|
|
3215
|
+
);
|
|
3216
|
+
}
|
|
3217
|
+
|
|
2989
3218
|
function createParallelWorktreeSetup(
|
|
2990
3219
|
enabled: boolean | undefined,
|
|
2991
3220
|
cwd: string,
|
|
@@ -3021,10 +3250,128 @@ function buildParallelWorktreeTaskCwdError(
|
|
|
3021
3250
|
return formatWorktreeTaskCwdConflict(conflict, sharedCwd);
|
|
3022
3251
|
}
|
|
3023
3252
|
|
|
3024
|
-
function
|
|
3253
|
+
function resolveConfiguredSingleRunOutputBaseDir(deps: ExecutorDeps): string | undefined {
|
|
3025
3254
|
return deps.config.singleRunOutputBaseDir
|
|
3026
3255
|
? path.resolve(deps.expandTilde(deps.config.singleRunOutputBaseDir))
|
|
3027
|
-
:
|
|
3256
|
+
: undefined;
|
|
3257
|
+
}
|
|
3258
|
+
|
|
3259
|
+
function resolveSingleRunOutputBaseDir(deps: ExecutorDeps, artifactsDir: string, runId: string): string {
|
|
3260
|
+
return resolveConfiguredSingleRunOutputBaseDir(deps) ?? path.join(artifactsDir, "outputs", runId);
|
|
3261
|
+
}
|
|
3262
|
+
|
|
3263
|
+
function resolveWorkflowAggregateOutputPath(
|
|
3264
|
+
output: string | boolean | undefined,
|
|
3265
|
+
ctxCwd: string,
|
|
3266
|
+
workflowCwd: string,
|
|
3267
|
+
outputBaseDir: string,
|
|
3268
|
+
configuredOutputBaseDir: string | undefined,
|
|
3269
|
+
): string | undefined {
|
|
3270
|
+
if (typeof output === "string" && output && !path.isAbsolute(output) && !configuredOutputBaseDir) return path.resolve(workflowCwd, output);
|
|
3271
|
+
return resolveSingleOutputPath(output, ctxCwd, workflowCwd, outputBaseDir);
|
|
3272
|
+
}
|
|
3273
|
+
|
|
3274
|
+
function workflowChildDefaultOutput(aggregateOutputPath: string | undefined, artifactsDir: string, workflowRunId: string, workflowKey: string): string {
|
|
3275
|
+
if (aggregateOutputPath) {
|
|
3276
|
+
const parsed = path.parse(aggregateOutputPath);
|
|
3277
|
+
return path.join(parsed.dir, `${parsed.name}.${workflowKey}${parsed.ext || ".md"}`);
|
|
3278
|
+
}
|
|
3279
|
+
return path.join(artifactsDir, "outputs", workflowRunId, `${workflowKey}.md`);
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3282
|
+
function writeWorkflowAggregateOutput(outputPath: string | undefined, text: string): string | undefined {
|
|
3283
|
+
if (!outputPath) return undefined;
|
|
3284
|
+
try {
|
|
3285
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
3286
|
+
fs.writeFileSync(outputPath, text, "utf-8");
|
|
3287
|
+
return undefined;
|
|
3288
|
+
} catch (error) {
|
|
3289
|
+
return `Output file error: ${outputPath}\n${error instanceof Error ? error.message : String(error)}`;
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
|
|
3293
|
+
function appendWorkflowOutputWarning(text: string, warning: string | undefined): string {
|
|
3294
|
+
return warning ? `${text}\n\n${warning}` : text;
|
|
3295
|
+
}
|
|
3296
|
+
|
|
3297
|
+
function resolveWorkflowChildOutputPath(input: {
|
|
3298
|
+
ctxCwd: string;
|
|
3299
|
+
workflowCwd: string;
|
|
3300
|
+
artifactsDir: string;
|
|
3301
|
+
workflowRunId: string;
|
|
3302
|
+
aggregateOutputPath?: string;
|
|
3303
|
+
configuredOutputBaseDir?: string;
|
|
3304
|
+
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
|
|
3305
|
+
workflowAgentScope?: unknown;
|
|
3306
|
+
key: string;
|
|
3307
|
+
params: Record<string, unknown>;
|
|
3308
|
+
}): string | undefined {
|
|
3309
|
+
const rawOutput = input.params.output;
|
|
3310
|
+
const hasExplicitOutput = typeof rawOutput === "string" || typeof rawOutput === "boolean";
|
|
3311
|
+
const childCwd = typeof input.params.cwd === "string" ? resolveChildCwd(input.workflowCwd, input.params.cwd) : input.workflowCwd;
|
|
3312
|
+
const agentScope = resolveExecutionAgentScope(input.params.agentScope ?? input.workflowAgentScope);
|
|
3313
|
+
const agents = input.discoverAgents(childCwd, agentScope).agents;
|
|
3314
|
+
const agent = typeof input.params.agent === "string" ? resolveAgentName(input.params.agent, agents).agent : undefined;
|
|
3315
|
+
const agentOutput = typeof agent?.output === "string" ? agent.output : undefined;
|
|
3316
|
+
const output = rawOutput === true || rawOutput === "true"
|
|
3317
|
+
? agentOutput
|
|
3318
|
+
: hasExplicitOutput
|
|
3319
|
+
? rawOutput
|
|
3320
|
+
: workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.workflowRunId, input.key);
|
|
3321
|
+
return resolveSingleOutputPath(output, input.ctxCwd, childCwd, input.configuredOutputBaseDir);
|
|
3322
|
+
}
|
|
3323
|
+
|
|
3324
|
+
function workflowChildOutputClaims(input: {
|
|
3325
|
+
ctxCwd: string;
|
|
3326
|
+
workflowCwd: string;
|
|
3327
|
+
artifactsDir: string;
|
|
3328
|
+
workflowRunId: string;
|
|
3329
|
+
aggregateOutputPath?: string;
|
|
3330
|
+
configuredOutputBaseDir?: string;
|
|
3331
|
+
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
|
|
3332
|
+
workflowAgentScope?: unknown;
|
|
3333
|
+
claimedOutputPaths: Map<string, string>;
|
|
3334
|
+
entries: Array<{ key: string; params: Record<string, unknown> }>;
|
|
3335
|
+
}): { error?: string; claims?: Map<string, string> } {
|
|
3336
|
+
const claims = new Map(input.claimedOutputPaths);
|
|
3337
|
+
for (const { key, params } of input.entries) {
|
|
3338
|
+
const resolved = resolveWorkflowChildOutputPath({ ...input, key, params });
|
|
3339
|
+
if (!resolved) continue;
|
|
3340
|
+
if (input.aggregateOutputPath && resolved === input.aggregateOutputPath) return { error: `Workflow child '${key}' output resolves to the workflow aggregate output path: ${resolved}. Use a distinct child output path.` };
|
|
3341
|
+
const previous = claims.get(resolved);
|
|
3342
|
+
if (previous) return { error: `Workflow children '${previous}' and '${key}' resolve output to the same path: ${resolved}. Use distinct child output paths.` };
|
|
3343
|
+
claims.set(resolved, key);
|
|
3344
|
+
}
|
|
3345
|
+
return { claims };
|
|
3346
|
+
}
|
|
3347
|
+
|
|
3348
|
+
function applyWorkflowChildOutputClaims(target: Map<string, string>, claims: Map<string, string>): void {
|
|
3349
|
+
target.clear();
|
|
3350
|
+
for (const [resolved, key] of claims) target.set(resolved, key);
|
|
3351
|
+
}
|
|
3352
|
+
|
|
3353
|
+
function prepareWorkflowChildLaunchParams(input: {
|
|
3354
|
+
workflowDefaults: SubagentParamsLike;
|
|
3355
|
+
childParams: Record<string, unknown>;
|
|
3356
|
+
parentWorkflowRunId: string;
|
|
3357
|
+
workflowKey: string;
|
|
3358
|
+
ctxCwd: string;
|
|
3359
|
+
workflowCwd: string;
|
|
3360
|
+
artifactsDir: string;
|
|
3361
|
+
aggregateOutputPath?: string;
|
|
3362
|
+
configuredOutputBaseDir?: string;
|
|
3363
|
+
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
|
|
3364
|
+
workflowAgentScope?: unknown;
|
|
3365
|
+
options?: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor };
|
|
3366
|
+
}): SubagentParamsLike {
|
|
3367
|
+
let childParams = input.childParams;
|
|
3368
|
+
if (input.childParams.output === undefined && input.childParams.resume === undefined) {
|
|
3369
|
+
childParams = { ...input.childParams, output: workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.parentWorkflowRunId, input.workflowKey) };
|
|
3370
|
+
} else if (input.childParams.resume === undefined) {
|
|
3371
|
+
const resolvedOutput = resolveWorkflowChildOutputPath({ ctxCwd: input.ctxCwd, workflowCwd: input.workflowCwd, artifactsDir: input.artifactsDir, workflowRunId: input.parentWorkflowRunId, aggregateOutputPath: input.aggregateOutputPath, configuredOutputBaseDir: input.configuredOutputBaseDir, discoverAgents: input.discoverAgents, workflowAgentScope: input.workflowAgentScope, key: input.workflowKey, params: input.childParams });
|
|
3372
|
+
if (resolvedOutput) childParams = { ...input.childParams, output: resolvedOutput };
|
|
3373
|
+
}
|
|
3374
|
+
return prepareWorkflowLaunchParams(input.workflowDefaults, childParams, input.parentWorkflowRunId, input.workflowKey, input.options);
|
|
3028
3375
|
}
|
|
3029
3376
|
|
|
3030
3377
|
function buildChainWorktreeTaskCwdError(chain: ChainStep[], sharedCwd: string): string | undefined {
|
|
@@ -3174,7 +3521,33 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
3174
3521
|
beginForegroundChild(input.foregroundControl, {
|
|
3175
3522
|
index,
|
|
3176
3523
|
agent: task.agent,
|
|
3177
|
-
|
|
3524
|
+
authoredTask: input.taskDescriptions[index] ?? "",
|
|
3525
|
+
effectivePrompt: taskText,
|
|
3526
|
+
cwd: taskCwd,
|
|
3527
|
+
...(outputPath ? { outputPath } : {}),
|
|
3528
|
+
rerun: { params: compactOptional<Record<string, unknown>>({
|
|
3529
|
+
agent: task.agent,
|
|
3530
|
+
task: input.taskDescriptions[index] ?? "",
|
|
3531
|
+
async: false,
|
|
3532
|
+
cwd: input.worktreeSetup ? undefined : taskCwd,
|
|
3533
|
+
worktree: input.worktreeSetup ? true : false,
|
|
3534
|
+
context: input.contextPolicy.contextForAgent(task.agent),
|
|
3535
|
+
share: input.shareEnabled || undefined,
|
|
3536
|
+
maxOutput: input.maxOutput,
|
|
3537
|
+
timeoutMs: input.timeoutMs,
|
|
3538
|
+
turnBudget: input.turnBudget,
|
|
3539
|
+
...(model ? { model } : {}),
|
|
3540
|
+
...(effectiveSkills !== undefined ? { skill: effectiveSkills } : {}),
|
|
3541
|
+
...(outputPath ? { output: outputPath } : {}),
|
|
3542
|
+
...(behavior?.outputMode !== undefined ? { outputMode: behavior.outputMode } : {}),
|
|
3543
|
+
...(behavior?.reads !== undefined ? { reads: behavior.reads } : {}),
|
|
3544
|
+
...(behavior?.progress !== undefined ? { progress: behavior.progress } : {}),
|
|
3545
|
+
...(task.toolBudget !== undefined ? { toolBudget: task.toolBudget } : {}),
|
|
3546
|
+
...(task.outputSchema !== undefined ? { outputSchema: task.outputSchema } : {}),
|
|
3547
|
+
...(task.acceptance !== undefined ? { acceptance: task.acceptance } : {}),
|
|
3548
|
+
...(task.agentContract !== undefined || input.agentContract !== undefined ? { agentContract: task.agentContract ?? input.agentContract } : {}),
|
|
3549
|
+
}) },
|
|
3550
|
+
description: `${task.agent} child`,
|
|
3178
3551
|
...(model ? { model } : {}),
|
|
3179
3552
|
...(thinking ? { thinking } : {}),
|
|
3180
3553
|
interrupt: () => {
|
|
@@ -3191,8 +3564,10 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
3191
3564
|
const result = await runSync(input.ctx.cwd, input.agents, task.agent, taskText, compactOptional<Parameters<typeof runSync>[4]>({
|
|
3192
3565
|
permissions: input.permissions,
|
|
3193
3566
|
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
3567
|
+
llmIntentArbiter: createTaskMutationArbiter(input.ctx),
|
|
3194
3568
|
...workflowForegroundSteeringLaunchOptions(input.foregroundControl, index),
|
|
3195
3569
|
context: input.contextPolicy.contextForAgent(task.agent),
|
|
3570
|
+
runFanoutBudget: { ...input.runFanoutBudget, parentPath: `${input.runFanoutBudget.parentPath ? `${input.runFanoutBudget.parentPath}/` : ""}tasks[${index}]` },
|
|
3196
3571
|
cwd: taskCwd,
|
|
3197
3572
|
signal: input.signal,
|
|
3198
3573
|
interruptSignal: interruptController.signal,
|
|
@@ -3237,6 +3612,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
3237
3612
|
agentContract: task.agentContract ?? input.agentContract,
|
|
3238
3613
|
acceptance: task.acceptance,
|
|
3239
3614
|
acceptanceContext: { mode: "parallel" },
|
|
3615
|
+
onEffectivePrompt: input.foregroundControl ? (prompt) => updateLiveEffectivePrompt(input.foregroundControl!, index, prompt) : undefined,
|
|
3240
3616
|
timeoutMs: input.timeoutMs,
|
|
3241
3617
|
deadlineAt: input.deadlineAt,
|
|
3242
3618
|
turnBudget: input.turnBudget,
|
|
@@ -3420,7 +3796,9 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3420
3796
|
details: { mode: "parallel" as const, results: [] },
|
|
3421
3797
|
};
|
|
3422
3798
|
}
|
|
3423
|
-
const id =
|
|
3799
|
+
const id = data.asyncRunId;
|
|
3800
|
+
const capacity = acquireClarifiedBackgroundCapacity(data, deps, "parallel", id);
|
|
3801
|
+
if (capacity.errorResult) return capacity.errorResult;
|
|
3424
3802
|
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
3425
3803
|
pi: deps.pi,
|
|
3426
3804
|
cwd: ctx.cwd,
|
|
@@ -3451,40 +3829,46 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3451
3829
|
...(t.agentContract !== undefined ? { agentContract: t.agentContract } : {}),
|
|
3452
3830
|
});
|
|
3453
3831
|
});
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3832
|
+
try {
|
|
3833
|
+
return executeAsyncChain(id, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
|
|
3834
|
+
chain: [compactOptional<ParallelStep>({ parallel: parallelTasks, concurrency: parallelConcurrency, worktree: params.worktree })],
|
|
3835
|
+
resultMode: "parallel",
|
|
3836
|
+
goal: taskTexts[0] ?? "",
|
|
3837
|
+
agents,
|
|
3838
|
+
ctx: asyncCtx,
|
|
3839
|
+
availableModels,
|
|
3840
|
+
cwd: effectiveCwd,
|
|
3841
|
+
maxOutput: params.maxOutput,
|
|
3842
|
+
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
3843
|
+
artifactConfig,
|
|
3844
|
+
shareEnabled,
|
|
3845
|
+
sessionRoot,
|
|
3846
|
+
chainSkills: [],
|
|
3847
|
+
activeAsyncCapacity: capacity.activeAsyncCapacity,
|
|
3848
|
+
sessionFilesByFlatIndex: tasks.map((task, index) => sessionFileForTask(task.agent, index, modelOverrides[index])),
|
|
3849
|
+
thinkingOverridesByFlatIndex: tasks.map((task, index) => thinkingOverrideForTask(task.agent, index, modelOverrides[index])),
|
|
3850
|
+
contextForAgent: contextPolicy.contextForAgent,
|
|
3851
|
+
maxSubagentDepth: currentMaxSubagentDepth,
|
|
3852
|
+
waitToolEnabled: deps.waitToolEnabled,
|
|
3853
|
+
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
3854
|
+
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
3855
|
+
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
3856
|
+
controlConfig,
|
|
3857
|
+
agentContract: params.agentContract,
|
|
3858
|
+
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
3859
|
+
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
3860
|
+
nestedRoute: data.nestedRoute,
|
|
3861
|
+
timeoutMs: data.timeoutMs,
|
|
3862
|
+
turnBudget: data.turnBudget,
|
|
3863
|
+
usageBudget: data.usageBudget,
|
|
3864
|
+
toolBudget: data.toolBudget,
|
|
3865
|
+
configToolBudget: data.configToolBudget,
|
|
3866
|
+
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
3867
|
+
runFanoutBudget: data.runFanoutBudget,
|
|
3868
|
+
}));
|
|
3869
|
+
} finally {
|
|
3870
|
+
finalizeClarifiedBackgroundCapacity(data, deps, capacity.activeAsyncCapacity);
|
|
3871
|
+
}
|
|
3488
3872
|
}
|
|
3489
3873
|
}
|
|
3490
3874
|
|
|
@@ -3563,6 +3947,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3563
3947
|
intercomEvents: deps.pi.events,
|
|
3564
3948
|
parentSessionId: data.parentSessionId,
|
|
3565
3949
|
capabilityCeiling: data.capabilityCeiling,
|
|
3950
|
+
runFanoutBudget: data.runFanoutBudget,
|
|
3566
3951
|
signal,
|
|
3567
3952
|
runId,
|
|
3568
3953
|
sessionDirForIndex,
|
|
@@ -3797,7 +4182,9 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3797
4182
|
details: { mode: "single" as const, results: [] },
|
|
3798
4183
|
};
|
|
3799
4184
|
}
|
|
3800
|
-
const id =
|
|
4185
|
+
const id = data.asyncRunId;
|
|
4186
|
+
const capacity = acquireClarifiedBackgroundCapacity(data, deps, "single", id);
|
|
4187
|
+
if (capacity.errorResult) return capacity.errorResult;
|
|
3801
4188
|
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
3802
4189
|
pi: deps.pi,
|
|
3803
4190
|
cwd: ctx.cwd,
|
|
@@ -3809,49 +4196,58 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3809
4196
|
interactive: ctx.hasUI,
|
|
3810
4197
|
permissions: deps.config.permissions,
|
|
3811
4198
|
});
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
4199
|
+
try {
|
|
4200
|
+
return executeAsyncSingle(id, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
|
|
4201
|
+
agent: params.agent!,
|
|
4202
|
+
task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(task) : task,
|
|
4203
|
+
goal: task,
|
|
4204
|
+
agentConfig,
|
|
4205
|
+
recoveryAgentConfig: data.recoveryAgents.find((agent) => agent.name === params.agent),
|
|
4206
|
+
ctx: asyncCtx,
|
|
4207
|
+
availableModels,
|
|
4208
|
+
cwd: effectiveCwd,
|
|
4209
|
+
maxOutput: params.maxOutput,
|
|
4210
|
+
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
4211
|
+
artifactConfig,
|
|
4212
|
+
shareEnabled,
|
|
4213
|
+
activeAsyncCapacity: capacity.activeAsyncCapacity,
|
|
4214
|
+
sessionRoot,
|
|
4215
|
+
sessionFile: sessionFileForTask(params.agent!, 0, modelOverride),
|
|
4216
|
+
context: contextPolicy.contextForAgent(params.agent!),
|
|
4217
|
+
skills: skillOverride === false ? [] : skillOverride,
|
|
4218
|
+
output: effectiveOutput,
|
|
4219
|
+
outputMode: effectiveOutputMode,
|
|
4220
|
+
...(readsOverride !== undefined ? { reads: readsOverride } : {}),
|
|
4221
|
+
outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
|
|
4222
|
+
modelOverride,
|
|
4223
|
+
thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride),
|
|
4224
|
+
maxSubagentDepth,
|
|
4225
|
+
waitToolEnabled: deps.waitToolEnabled,
|
|
4226
|
+
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
4227
|
+
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
4228
|
+
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
4229
|
+
controlConfig,
|
|
4230
|
+
intercomBridge: params.intercomBridge,
|
|
4231
|
+
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
4232
|
+
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
4233
|
+
nestedRoute: data.nestedRoute,
|
|
4234
|
+
agentContract: params.agentContract,
|
|
4235
|
+
structuredOutputSchema: params.outputSchema,
|
|
4236
|
+
acceptance: params.acceptance,
|
|
4237
|
+
timeoutMs: data.timeoutMs,
|
|
4238
|
+
turnBudget: data.turnBudget,
|
|
4239
|
+
toolBudget: effectiveToolBudget.toolBudget,
|
|
4240
|
+
usageBudget: data.usageBudget,
|
|
4241
|
+
allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
|
|
4242
|
+
runFanoutBudget: data.runFanoutBudget,
|
|
4243
|
+
}));
|
|
4244
|
+
} finally {
|
|
4245
|
+
finalizeClarifiedBackgroundCapacity(data, deps, capacity.activeAsyncCapacity);
|
|
4246
|
+
}
|
|
3852
4247
|
}
|
|
3853
4248
|
}
|
|
3854
4249
|
|
|
4250
|
+
const authoredTask = task;
|
|
3855
4251
|
if (shouldForkAgent(contextPolicy, params.agent!)) {
|
|
3856
4252
|
task = wrapForkTask(task);
|
|
3857
4253
|
}
|
|
@@ -3888,6 +4284,11 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3888
4284
|
beginForegroundChild(foregroundControl, omitUndefinedProperties({
|
|
3889
4285
|
index: 0,
|
|
3890
4286
|
agent: params.agent!,
|
|
4287
|
+
authoredTask,
|
|
4288
|
+
effectivePrompt: task,
|
|
4289
|
+
cwd: effectiveCwd,
|
|
4290
|
+
outputPath,
|
|
4291
|
+
rerun: { params: { ...params, task: authoredTask, async: params.async ?? false } },
|
|
3891
4292
|
description: foregroundControl.description,
|
|
3892
4293
|
...(modelOverride ? { model: modelOverride } : {}),
|
|
3893
4294
|
...(thinking ? { thinking } : {}),
|
|
@@ -3913,8 +4314,10 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3913
4314
|
r = await runSync(ctx.cwd, agents, params.agent!, task, compactOptional<Parameters<typeof runSync>[4]>({
|
|
3914
4315
|
permissions: deps.config.permissions,
|
|
3915
4316
|
parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
|
|
4317
|
+
llmIntentArbiter: createTaskMutationArbiter(ctx),
|
|
3916
4318
|
...workflowForegroundSteeringLaunchOptions(foregroundControl, 0),
|
|
3917
4319
|
context: data.contextPolicy.contextForAgent(params.agent!),
|
|
4320
|
+
runFanoutBudget: params.runFanoutAdmitted ? data.runFanoutBudget : { ...data.runFanoutBudget, parentPath: `${data.runFanoutBudget.parentPath ? `${data.runFanoutBudget.parentPath}/` : ""}single` },
|
|
3918
4321
|
cwd: effectiveCwd,
|
|
3919
4322
|
signal,
|
|
3920
4323
|
interruptSignal: interruptController.signal,
|
|
@@ -3948,6 +4351,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3948
4351
|
agentContract: params.agentContract,
|
|
3949
4352
|
acceptance: params.acceptance,
|
|
3950
4353
|
acceptanceContext: { mode: "single" },
|
|
4354
|
+
onEffectivePrompt: foregroundControl ? (prompt) => updateLiveEffectivePrompt(foregroundControl, 0, prompt) : undefined,
|
|
3951
4355
|
onDetachReady: (detach) => {
|
|
3952
4356
|
detachForeground = detach;
|
|
3953
4357
|
},
|
|
@@ -3996,12 +4400,14 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3996
4400
|
if (r.artifactPaths) allArtifactPaths.push(r.artifactPaths);
|
|
3997
4401
|
|
|
3998
4402
|
const fullOutput = getSingleResultOutput(r);
|
|
4403
|
+
const preserveRejectedSavedOutput = r.acceptance?.explicit && r.acceptance.status === "rejected" && r.savedOutputPath !== undefined;
|
|
3999
4404
|
const finalizedOutput = finalizeSingleOutput(omitUndefinedProperties({
|
|
4000
4405
|
fullOutput,
|
|
4001
4406
|
truncatedOutput: r.truncation?.text,
|
|
4002
4407
|
outputPath,
|
|
4003
4408
|
outputMode: r.outputMode,
|
|
4004
4409
|
exitCode: r.exitCode,
|
|
4410
|
+
preserveSavedOutput: preserveRejectedSavedOutput,
|
|
4005
4411
|
savedPath: r.savedOutputPath,
|
|
4006
4412
|
outputReference: r.outputReference,
|
|
4007
4413
|
saveError: r.outputSaveError,
|
|
@@ -4092,7 +4498,7 @@ function duplicateSubagentCallResult(params: SubagentParamsLike): AgentToolResul
|
|
|
4092
4498
|
};
|
|
4093
4499
|
}
|
|
4094
4500
|
|
|
4095
|
-
const workflowLaunchObservers = new WeakMap<object, (launch: { agent: string; sessionFile?: string }) => void>();
|
|
4501
|
+
const workflowLaunchObservers = new WeakMap<object, (launch: { agent: string; sessionFile?: string; async: boolean; runId?: string }) => void>();
|
|
4096
4502
|
|
|
4097
4503
|
function recordMissionWorkflowChild(
|
|
4098
4504
|
binding: MissionLaunchBinding | undefined,
|
|
@@ -4101,8 +4507,9 @@ function recordMissionWorkflowChild(
|
|
|
4101
4507
|
update: Omit<MissionWorkflowChildUpdate, "workflowRunId" | "key">,
|
|
4102
4508
|
): void {
|
|
4103
4509
|
if (!binding) return;
|
|
4510
|
+
const { task: _task, ...durableUpdate } = update;
|
|
4104
4511
|
try {
|
|
4105
|
-
updateMission(binding.location, binding.missionId, { upsertWorkflowChildren: [{ workflowRunId, key, ...
|
|
4512
|
+
updateMission(binding.location, binding.missionId, { upsertWorkflowChildren: [{ workflowRunId, key, ...durableUpdate }] });
|
|
4106
4513
|
} catch (error) {
|
|
4107
4514
|
console.warn(`[pi-subagents] Failed to record mission workflow child '${key}': ${error instanceof Error ? error.message : String(error)}`);
|
|
4108
4515
|
}
|
|
@@ -4184,7 +4591,7 @@ export function prepareWorkflowLaunchParams(
|
|
|
4184
4591
|
childParams: Record<string, unknown>,
|
|
4185
4592
|
parentWorkflowRunId: string,
|
|
4186
4593
|
workflowKey: string,
|
|
4187
|
-
options: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean } = {},
|
|
4594
|
+
options: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor } = {},
|
|
4188
4595
|
): SubagentParamsLike {
|
|
4189
4596
|
if (typeof childParams.resume === "string") {
|
|
4190
4597
|
if (childParams.gate !== undefined || workflowDefaults.gate !== undefined) {
|
|
@@ -4193,16 +4600,19 @@ export function prepareWorkflowLaunchParams(
|
|
|
4193
4600
|
const timeoutMs = childParams.timeoutMs ?? childParams.maxRuntimeMs ?? workflowDefaults.timeoutMs ?? workflowDefaults.maxRuntimeMs;
|
|
4194
4601
|
const turnBudget = childParams.turnBudget ?? workflowDefaults.turnBudget;
|
|
4195
4602
|
const toolBudget = childParams.toolBudget ?? workflowDefaults.toolBudget;
|
|
4603
|
+
const intercomBridge = childParams.intercomBridge ?? workflowDefaults.intercomBridge;
|
|
4196
4604
|
return {
|
|
4197
4605
|
action: "resume",
|
|
4198
4606
|
id: childParams.resume.trim(),
|
|
4199
4607
|
message: typeof childParams.task === "string" ? childParams.task.trim() : "",
|
|
4200
4608
|
workflowParentRunId: parentWorkflowRunId,
|
|
4201
4609
|
workflowKey,
|
|
4610
|
+
...(options.runFanoutBudget ? { runFanoutBudget: { ...options.runFanoutBudget, parentPath: `${options.runFanoutBudget.parentPath ? `${options.runFanoutBudget.parentPath}/` : ""}workflow[${workflowKey}]` } } : {}),
|
|
4202
4611
|
...(options.missionDetached ? { mission: false } : {}),
|
|
4203
4612
|
...(timeoutMs !== undefined ? { timeoutMs: timeoutMs as number } : {}),
|
|
4204
4613
|
...(turnBudget !== undefined ? { turnBudget: turnBudget as TurnBudgetConfig } : {}),
|
|
4205
4614
|
...(toolBudget !== undefined ? { toolBudget: toolBudget as ToolBudgetConfig } : {}),
|
|
4615
|
+
...(intercomBridge !== undefined ? { intercomBridge: intercomBridge as IntercomBridgeConfig } : {}),
|
|
4206
4616
|
};
|
|
4207
4617
|
}
|
|
4208
4618
|
const launchParams = {
|
|
@@ -4212,6 +4622,7 @@ export function prepareWorkflowLaunchParams(
|
|
|
4212
4622
|
...(options.missionDetached ? { mission: false } : {}),
|
|
4213
4623
|
workflowParentRunId: parentWorkflowRunId,
|
|
4214
4624
|
workflowKey,
|
|
4625
|
+
...(options.runFanoutBudget ? { runFanoutBudget: { ...options.runFanoutBudget, parentPath: `${options.runFanoutBudget.parentPath ? `${options.runFanoutBudget.parentPath}/` : ""}workflow[${workflowKey}]` } } : {}),
|
|
4215
4626
|
...(options.suppressRoutineResultIntercom ? { suppressRoutineResultIntercom: true } : {}),
|
|
4216
4627
|
} as SubagentParamsLike;
|
|
4217
4628
|
const normalizedGate = normalizeGateParams(launchParams);
|
|
@@ -4359,6 +4770,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4359
4770
|
} {
|
|
4360
4771
|
const delegatedThinkingOverrides = new WeakMap<object, AgentConfig["thinking"]>();
|
|
4361
4772
|
const delegatedZeroToolBudgets = new WeakSet<object>();
|
|
4773
|
+
const delegatedExecutions = new WeakSet<object>();
|
|
4362
4774
|
const warnedArtifactPackageDirs = new Set<string>();
|
|
4363
4775
|
const scheduledOwnerExecutors = new Map<string, ReturnType<typeof createSubagentExecutor>>();
|
|
4364
4776
|
const execute = async (
|
|
@@ -4372,6 +4784,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4372
4784
|
const workflowLaunchObserver = workflowLaunchObservers.get(params);
|
|
4373
4785
|
const delegatedThinkingOverride = delegatedThinkingOverrides.get(params);
|
|
4374
4786
|
const allowZeroToolBudget = delegatedZeroToolBudgets.has(params);
|
|
4787
|
+
const delegatedExecution = delegatedExecutions.has(params);
|
|
4375
4788
|
if (!preserveActiveSession) deps.state.baseCwd = ctx.cwd;
|
|
4376
4789
|
deps.state.foregroundRuns ??= new Map();
|
|
4377
4790
|
deps.state.foregroundControls ??= new Map();
|
|
@@ -4382,10 +4795,21 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4382
4795
|
const normalizedAction = typeof requestParams.action === "string" ? requestParams.action.trim() : requestParams.action;
|
|
4383
4796
|
if (requestParams.workflowScript !== undefined && normalizedAction === undefined) {
|
|
4384
4797
|
const parentCwd = ctx.cwd;
|
|
4385
|
-
const timeout = requestParams.timeoutMs ?? requestParams.maxRuntimeMs ?? (requestParams.async === false ? DEFAULT_FOREGROUND_TIMEOUT_MS : undefined);
|
|
4798
|
+
const timeout = requestParams.timeoutMs ?? requestParams.maxRuntimeMs ?? (requestParams.async === false ? resolveConfigDefaultTimeoutMs(deps.config.timeoutMs) ?? DEFAULT_FOREGROUND_TIMEOUT_MS : undefined);
|
|
4386
4799
|
const workflowUsageBudget = validateUsageBudgetConfig(requestParams.usageBudget ?? deps.config.usageBudget, requestParams.usageBudget ? "usageBudget" : "config.usageBudget");
|
|
4387
4800
|
if (workflowUsageBudget.error) return buildRequestedModeError(requestParams, workflowUsageBudget.error);
|
|
4388
4801
|
const workflowCwd = resolveRequestedCwd(parentCwd, requestParams.cwd);
|
|
4802
|
+
const workflowArtifactConfig: ArtifactConfig = omitUndefinedProperties({
|
|
4803
|
+
...DEFAULT_ARTIFACT_CONFIG,
|
|
4804
|
+
enabled: requestParams.artifacts !== false,
|
|
4805
|
+
dir: deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir,
|
|
4806
|
+
});
|
|
4807
|
+
const workflowArtifactsDir = getArtifactsDir(ctx.sessionManager.getSessionFile() ?? null, workflowCwd, workflowArtifactConfig.dir);
|
|
4808
|
+
if (workflowArtifactConfig.dir === "project" && !warnedArtifactPackageDirs.has(workflowCwd)) {
|
|
4809
|
+
warnedArtifactPackageDirs.add(workflowCwd);
|
|
4810
|
+
const warning = getProjectArtifactPackagingWarning(workflowCwd);
|
|
4811
|
+
if (warning) console.warn(`[pi-subagents] ${warning}`);
|
|
4812
|
+
}
|
|
4389
4813
|
const workflowPrompts = { render: (ref: string, vars?: unknown) => renderWorkflowPrompt(ref, vars, workflowCwd) };
|
|
4390
4814
|
const chatProgressResult = resolveWorkflowChatProgress({ requested: requestParams.chatProgress, parentCwd, workflowCwd, background: requestParams.async !== false });
|
|
4391
4815
|
if (chatProgressResult.error) return { content: [{ type: "text", text: chatProgressResult.error }], isError: true, details: { mode: "workflow", results: [] } };
|
|
@@ -4393,11 +4817,45 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4393
4817
|
const explicitMission = requestParams.missionId !== undefined || requestParams.mission !== undefined;
|
|
4394
4818
|
const autoMission = !explicitMission;
|
|
4395
4819
|
const workflowPreview = autoMission ? previewSimpleWorkflowRun(requestParams.workflowScript) : undefined;
|
|
4396
|
-
const previewTask = workflowPreview?.task?.trim() || undefined;
|
|
4397
4820
|
const previewAgent = workflowPreview?.agent?.trim() || undefined;
|
|
4398
4821
|
const scriptFirstLine = requestParams.workflowScript.split(/\r?\n/).map((line) => line.trim()).find(Boolean) || "Workflow";
|
|
4399
4822
|
const boundedScriptPreview = scriptFirstLine.length > 100 ? `${scriptFirstLine.slice(0, 97)}...` : scriptFirstLine;
|
|
4400
|
-
const derivedObjective =
|
|
4823
|
+
const derivedObjective = previewAgent ? `Workflow: ${previewAgent}` : boundedScriptPreview;
|
|
4824
|
+
const workflowDepth = checkSubagentDepth(deps.config.maxSubagentDepth).depth;
|
|
4825
|
+
const asyncWorkflow = requestParams.async !== false;
|
|
4826
|
+
const topLevelAsyncWorkflow = asyncWorkflow
|
|
4827
|
+
&& workflowDepth === 0
|
|
4828
|
+
&& !resolveInheritedNestedRouteFromEnv()
|
|
4829
|
+
&& !requestParams.workflowParentRunId;
|
|
4830
|
+
const workflowRunId = asyncWorkflow ? randomUUID() : undefined;
|
|
4831
|
+
let workflowCapacity: ActiveAsyncCapacityHandle | undefined;
|
|
4832
|
+
if (workflowRunId && topLevelAsyncWorkflow) {
|
|
4833
|
+
const currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
4834
|
+
try {
|
|
4835
|
+
workflowCapacity = acquireActiveAsyncCapacity({
|
|
4836
|
+
sessionId: currentSessionId,
|
|
4837
|
+
limit: resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession),
|
|
4838
|
+
runId: workflowRunId,
|
|
4839
|
+
kind: "workflow",
|
|
4840
|
+
asyncDir: path.join(DIRS.async, workflowRunId),
|
|
4841
|
+
}, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) });
|
|
4842
|
+
} catch (error) {
|
|
4843
|
+
if (error instanceof ActiveAsyncCapacityError) {
|
|
4844
|
+
deps.state.activeAsyncCapacity = error.snapshot;
|
|
4845
|
+
return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "workflow", results: [], activeAsyncCapacity: error.snapshot } };
|
|
4846
|
+
}
|
|
4847
|
+
throw error;
|
|
4848
|
+
}
|
|
4849
|
+
}
|
|
4850
|
+
let workflowFanoutBudget: RunFanoutBudgetDescriptor;
|
|
4851
|
+
try {
|
|
4852
|
+
workflowFanoutBudget = requestParams.runFanoutBudget
|
|
4853
|
+
?? decodeRunFanoutBudgetDescriptor(process.env[RUN_FANOUT_BUDGET_ENV])
|
|
4854
|
+
?? createRunFanoutBudget(_id, resolveMaxSubagentSpawnsPerRun(deps.config.maxSubagentSpawnsPerRun));
|
|
4855
|
+
} catch (error) {
|
|
4856
|
+
workflowCapacity?.rollback();
|
|
4857
|
+
return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "workflow", results: [] } };
|
|
4858
|
+
}
|
|
4401
4859
|
let missionBinding: MissionLaunchBinding | undefined;
|
|
4402
4860
|
let missionWarning: string | undefined;
|
|
4403
4861
|
try {
|
|
@@ -4408,22 +4866,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4408
4866
|
ownerSessionId: resolveCurrentSessionId(ctx.sessionManager),
|
|
4409
4867
|
});
|
|
4410
4868
|
} catch (error) {
|
|
4411
|
-
if (explicitMission)
|
|
4869
|
+
if (explicitMission) {
|
|
4870
|
+
workflowCapacity?.rollback();
|
|
4871
|
+
return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "workflow", results: [] } };
|
|
4872
|
+
}
|
|
4412
4873
|
missionWarning = `Mission tracking unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
4413
4874
|
}
|
|
4414
|
-
let shouldPatchMissionObjective = autoMission && previewTask === undefined && missionBinding !== undefined;
|
|
4415
|
-
const patchMissionObjective = (task: unknown): void => {
|
|
4416
|
-
if (!shouldPatchMissionObjective || !missionBinding || typeof task !== "string" || !task.trim()) return;
|
|
4417
|
-
shouldPatchMissionObjective = false;
|
|
4418
|
-
const objective = task.trim();
|
|
4419
|
-
const firstLine = objective.split(/\r?\n/, 1)[0]?.trim() || objective;
|
|
4420
|
-
const title = firstLine.length > 100 ? `${firstLine.slice(0, 97)}...` : firstLine;
|
|
4421
|
-
try {
|
|
4422
|
-
updateMission(missionBinding.location, missionBinding.missionId, { title, objective });
|
|
4423
|
-
} catch (error) {
|
|
4424
|
-
console.warn(`[pi-subagents] Failed to update automatic mission objective: ${error instanceof Error ? error.message : String(error)}`);
|
|
4425
|
-
}
|
|
4426
|
-
};
|
|
4427
4875
|
const detachWorkflowChildMissions = autoMission || missionBinding !== undefined || requestParams.mission === false;
|
|
4428
4876
|
const workflowState = missionBinding ? createMissionWorkflowState(missionBinding.location, missionBinding.missionId) : undefined;
|
|
4429
4877
|
const attachWorkflowMission = (result: AgentToolResult<Details>): AgentToolResult<Details> => {
|
|
@@ -4437,20 +4885,27 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4437
4885
|
: { ...result, details: { ...result.details, missionWarning: warning } };
|
|
4438
4886
|
}
|
|
4439
4887
|
};
|
|
4440
|
-
if (
|
|
4888
|
+
if (workflowRunId) {
|
|
4441
4889
|
const toolCallId = _id;
|
|
4442
|
-
const workflowRunId = randomUUID();
|
|
4443
4890
|
const asyncDir = path.join(DIRS.async, workflowRunId);
|
|
4444
4891
|
const resultPath = path.join(DIRS.results, `${workflowRunId}.json`);
|
|
4445
4892
|
const statusPath = path.join(asyncDir, "status.json");
|
|
4446
4893
|
const eventsPath = path.join(asyncDir, "events.jsonl");
|
|
4447
4894
|
const startedAt = Date.now();
|
|
4448
4895
|
const currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
4449
|
-
|
|
4450
|
-
|
|
4896
|
+
try {
|
|
4897
|
+
fs.mkdirSync(asyncDir, { recursive: true });
|
|
4898
|
+
writeRunFanoutBudgetDescriptor(asyncDir, workflowFanoutBudget);
|
|
4899
|
+
fs.mkdirSync(DIRS.results, { recursive: true });
|
|
4900
|
+
} catch (error) {
|
|
4901
|
+
workflowCapacity?.rollback();
|
|
4902
|
+
return { content: [{ type: "text", text: `Failed to create async workflow storage: ${error instanceof Error ? error.message : String(error)}` }], isError: true, details: { mode: "workflow", results: [] } };
|
|
4903
|
+
}
|
|
4451
4904
|
const controller = new AbortController();
|
|
4452
4905
|
deps.state.workflowControllers ??= new Map();
|
|
4453
4906
|
deps.state.workflowControllers.set(workflowRunId, controller);
|
|
4907
|
+
workflowCapacity?.markWorkflowStarted();
|
|
4908
|
+
if (workflowCapacity) deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers.keys()) });
|
|
4454
4909
|
let status: AsyncStatus = {
|
|
4455
4910
|
runId: workflowRunId,
|
|
4456
4911
|
toolCallId,
|
|
@@ -4464,6 +4919,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4464
4919
|
pid: process.pid,
|
|
4465
4920
|
steps: [],
|
|
4466
4921
|
workflow: { trace: [], emits: [], console: [] },
|
|
4922
|
+
runFanoutBudget: getRunFanoutBudgetSnapshot(workflowFanoutBudget),
|
|
4467
4923
|
};
|
|
4468
4924
|
const appendWorkflowEvent = (event: Record<string, unknown>) => fs.appendFileSync(eventsPath, `${JSON.stringify({ ts: Date.now(), runId: workflowRunId, ...event })}\n`, "utf-8");
|
|
4469
4925
|
let indexedState: AsyncStatus["state"] | undefined;
|
|
@@ -4527,6 +4983,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4527
4983
|
void Promise.resolve().then(async () => {
|
|
4528
4984
|
const workflowResults: SingleResult[] = [];
|
|
4529
4985
|
const { action: _action, agent: _agent, task: _task, resume: _resume, tasks: _tasks, chain: _chain, concurrency: _concurrency, foregroundOnly: _foregroundOnly, clarify: _clarify, timeoutMs: _timeoutMs, maxRuntimeMs: _maxRuntimeMs, usageBudget: _usageBudget, missionId: _missionId, mission: _mission, ...workflowChildDefaults } = workflowRequest;
|
|
4986
|
+
const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
|
|
4987
|
+
const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
|
|
4988
|
+
const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, workflowRunId), configuredOutputBaseDir);
|
|
4989
|
+
const claimedOutputPaths = new Map<string, string>();
|
|
4530
4990
|
const workflowSteps = new Map<string, NonNullable<AsyncStatus["steps"]>[number]>();
|
|
4531
4991
|
let projectedTraceLength = 0;
|
|
4532
4992
|
let projectedTraceTail: NonNullable<Details["workflow"]>["trace"][number] | undefined;
|
|
@@ -4546,16 +5006,36 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4546
5006
|
if (entry.operation !== "run") continue;
|
|
4547
5007
|
const existing = workflowSteps.get(entry.key);
|
|
4548
5008
|
if (entry.state === "reused" && existing) continue;
|
|
4549
|
-
const mapped = entry.state === "started" || entry.state === "reused"
|
|
5009
|
+
const mapped = entry.state === "started" || entry.state === "reused"
|
|
5010
|
+
? "running"
|
|
5011
|
+
: entry.state === "completed"
|
|
5012
|
+
? "completed"
|
|
5013
|
+
: entry.state === "stopped"
|
|
5014
|
+
? "stopped"
|
|
5015
|
+
: "failed";
|
|
4550
5016
|
if (existing) {
|
|
4551
5017
|
existing.status = mapped;
|
|
4552
5018
|
if (entry.agent) existing.agent = entry.agent;
|
|
5019
|
+
if (entry.runId) existing.runId = entry.runId;
|
|
5020
|
+
if (entry.state === "failed" && !entry.runId && existing.async === undefined) existing.async = false;
|
|
5021
|
+
if (entry.state === "stopped") existing.stopped = true;
|
|
5022
|
+
else delete existing.stopped;
|
|
4553
5023
|
if (entry.error === undefined) delete existing.error;
|
|
4554
5024
|
else existing.error = entry.error;
|
|
4555
5025
|
if (entry.durationMs === undefined) delete existing.durationMs;
|
|
4556
5026
|
else existing.durationMs = entry.durationMs;
|
|
4557
5027
|
} else {
|
|
4558
|
-
const step: NonNullable<AsyncStatus["steps"]>[number] = {
|
|
5028
|
+
const step: NonNullable<AsyncStatus["steps"]>[number] = {
|
|
5029
|
+
agent: entry.agent ?? entry.key,
|
|
5030
|
+
label: entry.key,
|
|
5031
|
+
workflowKey: entry.key,
|
|
5032
|
+
parentWorkflowRunId: workflowRunId,
|
|
5033
|
+
status: mapped,
|
|
5034
|
+
startedAt: Date.now(),
|
|
5035
|
+
...(entry.runId ? { runId: entry.runId } : {}),
|
|
5036
|
+
...(entry.state === "failed" && !entry.runId ? { async: false } : {}),
|
|
5037
|
+
...(entry.state === "stopped" ? { stopped: true } : {}),
|
|
5038
|
+
};
|
|
4559
5039
|
status.steps?.push(step);
|
|
4560
5040
|
workflowSteps.set(entry.key, step);
|
|
4561
5041
|
}
|
|
@@ -4574,30 +5054,35 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4574
5054
|
prompts: workflowPrompts,
|
|
4575
5055
|
...(workflowState ? { state: workflowState } : {}),
|
|
4576
5056
|
onTrace: updateTrace,
|
|
5057
|
+
admit: (calls) => {
|
|
5058
|
+
const outputClaims = workflowChildOutputClaims({ ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, claimedOutputPaths, entries: calls });
|
|
5059
|
+
if (outputClaims.error) throw new Error(outputClaims.error);
|
|
5060
|
+
status.runFanoutBudget = claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
|
|
5061
|
+
if (outputClaims.claims) applyWorkflowChildOutputClaims(claimedOutputPaths, outputClaims.claims);
|
|
5062
|
+
persist();
|
|
5063
|
+
},
|
|
4577
5064
|
onEmit: (emits) => {
|
|
4578
5065
|
// Each emit is validated at the host boundary in runWorkflowScript before onEmit fires.
|
|
4579
5066
|
status.workflow = { ...(status.workflow ?? { trace: [], console: [] }), emits };
|
|
4580
5067
|
persist();
|
|
4581
5068
|
appendWorkflowEvent({ type: "subagent.workflow.emit", value: emits.at(-1) });
|
|
4582
5069
|
},
|
|
4583
|
-
launch: async (key, childParams, workflowSignal) => {
|
|
5070
|
+
launch: async (key, childParams, workflowSignal, admission) => {
|
|
4584
5071
|
if (workflowUsageBudget.budget && childParams.async === true) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, "workflow usageBudget does not support async runs.run launches."));
|
|
4585
5072
|
const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
|
|
4586
5073
|
if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)));
|
|
4587
|
-
patchMissionObjective(childParams.task);
|
|
4588
5074
|
const childPhase = typeof childParams.phase === "string" && childParams.phase.trim() ? childParams.phase.trim() : undefined;
|
|
4589
5075
|
const childLabel = typeof childParams.label === "string" && childParams.label.trim() ? childParams.label.trim() : undefined;
|
|
4590
5076
|
recordMissionWorkflowChild(missionBinding, workflowRunId, key, {
|
|
4591
5077
|
status: "running",
|
|
4592
5078
|
...(typeof childParams.agent === "string" && childParams.agent.trim() ? { agent: childParams.agent.trim() } : {}),
|
|
4593
|
-
...(typeof childParams.task === "string" && childParams.task.trim() ? { task: childParams.task.trim() } : {}),
|
|
4594
5079
|
...(childLabel ? { label: childLabel } : {}),
|
|
4595
5080
|
...(childPhase ? { phase: childPhase } : {}),
|
|
4596
5081
|
heartbeat: { status: "running", ...(childPhase ? { phase: childPhase } : {}) },
|
|
4597
5082
|
});
|
|
4598
5083
|
const result = await runMissionWorkflowChild(missionBinding, workflowRunId, key, childPhase, () => {
|
|
4599
5084
|
const childRequest = bindMissionWorkflowChildAsyncLaunch(
|
|
4600
|
-
|
|
5085
|
+
{ ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId: workflowRunId, workflowKey: key, ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, options: { missionDetached: detachWorkflowChildMissions, runFanoutBudget: workflowFanoutBudget } }), runFanoutAdmitted: admission.admitted },
|
|
4601
5086
|
missionBinding,
|
|
4602
5087
|
deps.asyncByDefault,
|
|
4603
5088
|
);
|
|
@@ -4606,6 +5091,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4606
5091
|
if (step) {
|
|
4607
5092
|
step.agent = launch.agent;
|
|
4608
5093
|
step.sessionFile = launch.sessionFile;
|
|
5094
|
+
step.async = launch.async;
|
|
5095
|
+
if (launch.runId) step.runId = launch.runId;
|
|
4609
5096
|
persist();
|
|
4610
5097
|
}
|
|
4611
5098
|
recordMissionWorkflowChild(missionBinding, workflowRunId, key, { status: "running", agent: launch.agent, ...(launch.sessionFile ? { sessionPath: launch.sessionFile } : {}) });
|
|
@@ -4613,7 +5100,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4613
5100
|
return execute(randomUUID(), childRequest, workflowSignal, (update) => {
|
|
4614
5101
|
const progress = update.details.progress?.[0];
|
|
4615
5102
|
const step = status.steps?.find((candidate) => candidate.workflowKey === key);
|
|
4616
|
-
if (!progress || !step) return;
|
|
5103
|
+
if (!progress || !step || step.stopped) return;
|
|
4617
5104
|
step.status = progress.status === "completed" ? "completed" : progress.status === "failed" ? "failed" : "running";
|
|
4618
5105
|
step.activityState = progress.activityState;
|
|
4619
5106
|
step.lastActivityAt = progress.lastActivityAt;
|
|
@@ -4637,8 +5124,13 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4637
5124
|
}, ctx, preserveActiveSession);
|
|
4638
5125
|
});
|
|
4639
5126
|
workflowResults.push(...result.details.results);
|
|
4640
|
-
if (result.details.asyncDir && missionBinding) writeMissionAsyncBinding(result.details.asyncDir, missionBinding);
|
|
4641
5127
|
const child = workflowChildResult(key, result);
|
|
5128
|
+
const step = status.steps?.find((candidate) => candidate.workflowKey === key);
|
|
5129
|
+
if (step) {
|
|
5130
|
+
step.async = Boolean(result.details.asyncId || result.details.asyncDir);
|
|
5131
|
+
if (child.runId) step.runId = child.runId;
|
|
5132
|
+
}
|
|
5133
|
+
if (result.details.asyncDir && missionBinding) writeMissionAsyncBinding(result.details.asyncDir, missionBinding);
|
|
4642
5134
|
const childStatus = missionWorkflowChildStatus(result);
|
|
4643
5135
|
recordMissionWorkflowChild(missionBinding, workflowRunId, key, {
|
|
4644
5136
|
status: childStatus,
|
|
@@ -4660,28 +5152,38 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4660
5152
|
const returnPreview = formatWorkflowValue(workflow.value).slice(0, 1_000);
|
|
4661
5153
|
const emitPreview = workflow.emits.length > 0 ? ` Emitted: ${workflow.emits.map(formatWorkflowValue).join(", ").slice(0, 1_000)}` : "";
|
|
4662
5154
|
const summary = `Workflow completed with ${workflow.children.length} child run(s). Return: ${returnPreview}${emitPreview} Trace: ${workflow.trace.length} event(s).`;
|
|
5155
|
+
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, summary);
|
|
5156
|
+
const resultSummary = appendWorkflowOutputWarning(summary, outputWarning);
|
|
4663
5157
|
const workflowUsage = sumResultsUsage(workflowResults);
|
|
4664
5158
|
status = { ...status, state: "complete", endedAt: Date.now(), workflow: { value: workflow.value, trace: workflow.trace, emits: workflow.emits, console: workflow.console }, totalTokens: { input: workflowUsage.input, output: workflowUsage.output, total: workflowUsage.input + workflowUsage.output }, totalCost: sumResultsCost(workflowResults) };
|
|
4665
|
-
writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary, output:
|
|
5159
|
+
writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary: resultSummary, output: resultSummary, results: workflow.children.map((child) => ({ workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, asyncDir, cwd: workflowCwd, sessionId: currentSessionId, timestamp: Date.now(), durationMs: Date.now() - startedAt });
|
|
4666
5160
|
persist();
|
|
4667
5161
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: "complete" });
|
|
4668
5162
|
} catch (error) {
|
|
4669
5163
|
const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
|
|
4670
5164
|
const stopped = controller.signal.aborted;
|
|
4671
5165
|
status = compactOptional<AsyncStatus>({ ...status, state: stopped ? "stopped" : "failed", stopped: stopped || undefined, error: error instanceof Error ? error.message : String(error), endedAt: Date.now(), workflow: { trace: partial.trace, emits: partial.emits, console: partial.console } });
|
|
4672
|
-
|
|
5166
|
+
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, status.error ?? "Workflow failed.");
|
|
5167
|
+
const resultSummary = appendWorkflowOutputWarning(status.error ?? "Workflow failed.", outputWarning);
|
|
5168
|
+
writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: false, state: status.state, summary: resultSummary, error: status.error, stopped: status.stopped, results: partial.children.map((child) => ({ workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, success: child.ok, ...(child.artifactPaths[0] ? { artifactPaths: { outputPath: child.artifactPaths[0] } } : {}) })), workflow: status.workflow, asyncDir, cwd: workflowCwd, sessionId: currentSessionId, timestamp: Date.now(), durationMs: Date.now() - startedAt });
|
|
4673
5169
|
persist();
|
|
4674
5170
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, error: status.error });
|
|
4675
5171
|
} finally {
|
|
4676
5172
|
deps.state.workflowControllers?.delete(workflowRunId);
|
|
5173
|
+
deps.state.activeAsyncCapacity = workflowCapacity?.reconcile(new Set(deps.state.workflowControllers?.keys() ?? []))
|
|
5174
|
+
?? deps.state.activeAsyncCapacity;
|
|
4677
5175
|
}
|
|
4678
5176
|
});
|
|
4679
|
-
return attachWorkflowMission({
|
|
5177
|
+
return attachWorkflowMission(withRunFanoutBudget({
|
|
4680
5178
|
content: [{ type: "text", text: formatAsyncStartedMessage(`Async workflow [${workflowRunId}]`, ctx.hasUI === true) }],
|
|
4681
|
-
details: { mode: "workflow", runId: workflowRunId, toolCallId, asyncId: workflowRunId, asyncDir, results: [], chatProgress },
|
|
4682
|
-
});
|
|
5179
|
+
details: { mode: "workflow", runId: workflowRunId, toolCallId, asyncId: workflowRunId, asyncDir, results: [], chatProgress, ...(deps.state.activeAsyncCapacity ? { activeAsyncCapacity: deps.state.activeAsyncCapacity } : {}) },
|
|
5180
|
+
}, workflowFanoutBudget));
|
|
4683
5181
|
}
|
|
4684
5182
|
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;
|
|
5183
|
+
const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
|
|
5184
|
+
const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
|
|
5185
|
+
const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, _id), configuredOutputBaseDir);
|
|
5186
|
+
const claimedOutputPaths = new Map<string, string>();
|
|
4685
5187
|
const workflowResults: SingleResult[] = [];
|
|
4686
5188
|
let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [] };
|
|
4687
5189
|
const sendWorkflowProgress = () => {
|
|
@@ -4699,28 +5201,32 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4699
5201
|
liveWorkflow = { ...liveWorkflow, trace };
|
|
4700
5202
|
sendWorkflowProgress();
|
|
4701
5203
|
},
|
|
5204
|
+
admit: (calls) => {
|
|
5205
|
+
const outputClaims = workflowChildOutputClaims({ ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId: _id, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, claimedOutputPaths, entries: calls });
|
|
5206
|
+
if (outputClaims.error) throw new Error(outputClaims.error);
|
|
5207
|
+
claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
|
|
5208
|
+
if (outputClaims.claims) applyWorkflowChildOutputClaims(claimedOutputPaths, outputClaims.claims);
|
|
5209
|
+
},
|
|
4702
5210
|
onEmit: (emits) => {
|
|
4703
5211
|
liveWorkflow = { ...liveWorkflow, emits };
|
|
4704
5212
|
sendWorkflowProgress();
|
|
4705
5213
|
},
|
|
4706
|
-
launch: async (key, childParams, workflowSignal) => {
|
|
5214
|
+
launch: async (key, childParams, workflowSignal, admission) => {
|
|
4707
5215
|
if (workflowUsageBudget.budget && childParams.async === true) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, "workflow usageBudget does not support async runs.run launches."));
|
|
4708
5216
|
const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
|
|
4709
5217
|
if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)));
|
|
4710
|
-
patchMissionObjective(childParams.task);
|
|
4711
5218
|
const childPhase = typeof childParams.phase === "string" && childParams.phase.trim() ? childParams.phase.trim() : undefined;
|
|
4712
5219
|
const childLabel = typeof childParams.label === "string" && childParams.label.trim() ? childParams.label.trim() : undefined;
|
|
4713
5220
|
recordMissionWorkflowChild(missionBinding, _id, key, {
|
|
4714
5221
|
status: "running",
|
|
4715
5222
|
...(typeof childParams.agent === "string" && childParams.agent.trim() ? { agent: childParams.agent.trim() } : {}),
|
|
4716
|
-
...(typeof childParams.task === "string" && childParams.task.trim() ? { task: childParams.task.trim() } : {}),
|
|
4717
5223
|
...(childLabel ? { label: childLabel } : {}),
|
|
4718
5224
|
...(childPhase ? { phase: childPhase } : {}),
|
|
4719
5225
|
heartbeat: { status: "running", ...(childPhase ? { phase: childPhase } : {}) },
|
|
4720
5226
|
});
|
|
4721
5227
|
const result = await runMissionWorkflowChild(missionBinding, _id, key, childPhase, () => {
|
|
4722
5228
|
const childRequest = bindMissionWorkflowChildAsyncLaunch(
|
|
4723
|
-
|
|
5229
|
+
{ ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId: _id, workflowKey: key, ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: deps.discoverAgents, workflowAgentScope: workflowChildDefaults.agentScope, options: { missionDetached: detachWorkflowChildMissions, suppressRoutineResultIntercom: chatProgress.mode === "live-card", runFanoutBudget: workflowFanoutBudget } }), runFanoutAdmitted: admission.admitted },
|
|
4724
5230
|
missionBinding,
|
|
4725
5231
|
deps.asyncByDefault,
|
|
4726
5232
|
);
|
|
@@ -4761,10 +5267,13 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4761
5267
|
if (workflow.emits.length > 0) sections.push(`Emitted:\n${workflow.emits.map(formatWorkflowValue).join("\n")}`);
|
|
4762
5268
|
if (workflow.console.length > 0) sections.push(`Console:\n${workflow.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
|
|
4763
5269
|
if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
|
|
4764
|
-
|
|
4765
|
-
|
|
5270
|
+
const workflowText = sections.join("\n\n");
|
|
5271
|
+
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText);
|
|
5272
|
+
const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
|
|
5273
|
+
return attachWorkflowMission(withRunFanoutBudget({
|
|
5274
|
+
content: [{ type: "text", text: displayText }],
|
|
4766
5275
|
details: compactOptional<Details>({ mode: "workflow", runId: _id, results: workflow.children.flatMap((child) => (child.results ?? []) as SingleResult[]), totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { value: workflow.value, trace: workflow.trace, emits: workflow.emits, console: workflow.console }, chatProgress }),
|
|
4767
|
-
});
|
|
5276
|
+
}, workflowFanoutBudget));
|
|
4768
5277
|
} catch (error) {
|
|
4769
5278
|
const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
|
|
4770
5279
|
const text = error instanceof Error ? error.message : String(error);
|
|
@@ -4773,11 +5282,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4773
5282
|
if (partial.emits.length > 0) sections.push(`Emitted:\n${partial.emits.map(formatWorkflowValue).join("\n")}`);
|
|
4774
5283
|
if (partial.console.length > 0) sections.push(`Console:\n${partial.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
|
|
4775
5284
|
if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
|
|
4776
|
-
|
|
4777
|
-
|
|
5285
|
+
const workflowText = sections.join("\n\n");
|
|
5286
|
+
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText);
|
|
5287
|
+
const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
|
|
5288
|
+
return attachWorkflowMission(withRunFanoutBudget({
|
|
5289
|
+
content: [{ type: "text", text: displayText }],
|
|
4778
5290
|
isError: true,
|
|
4779
5291
|
details: compactOptional<Details>({ mode: "workflow", runId: _id, results: partial.children.flatMap((child) => (child.results ?? []) as SingleResult[]), totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { trace: partial.trace, emits: partial.emits, console: partial.console }, chatProgress }),
|
|
4780
|
-
});
|
|
5292
|
+
}, workflowFanoutBudget));
|
|
4781
5293
|
}
|
|
4782
5294
|
}
|
|
4783
5295
|
const directParams = prepareWorkflowChildParams(requestParams);
|
|
@@ -5015,6 +5527,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5015
5527
|
if (!sessionError) sessionError = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
5016
5528
|
}
|
|
5017
5529
|
const spawnBudget = getSpawnBudgetSnapshot(deps.state, deps.config, currentSessionId);
|
|
5530
|
+
const activeAsyncCapacity = currentSessionId
|
|
5531
|
+
? getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) })
|
|
5532
|
+
: { used: 0, limit: resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession) ?? 0 };
|
|
5533
|
+
deps.state.activeAsyncCapacity = activeAsyncCapacity;
|
|
5018
5534
|
return {
|
|
5019
5535
|
content: [{
|
|
5020
5536
|
type: "text",
|
|
@@ -5031,14 +5547,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5031
5547
|
expandTilde: deps.expandTilde,
|
|
5032
5548
|
})),
|
|
5033
5549
|
}],
|
|
5034
|
-
details: { mode: "management", results: [], spawnBudget },
|
|
5550
|
+
details: { mode: "management", results: [], spawnBudget, activeAsyncCapacity },
|
|
5035
5551
|
};
|
|
5036
5552
|
}
|
|
5037
|
-
if (action === "status") {
|
|
5553
|
+
if (action === "status" || action === "debug.run") {
|
|
5038
5554
|
if (!preserveActiveSession) deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
5039
5555
|
const targetRunId = paramsWithResolvedCwd.id ?? paramsWithResolvedCwd.runId;
|
|
5040
5556
|
const hasDirectoryTarget = Boolean(paramsWithResolvedCwd.dir);
|
|
5041
|
-
const targetLabel = formatStatusTargetLabel(paramsWithResolvedCwd, targetRunId);
|
|
5557
|
+
const targetLabel = action === "debug.run" ? "Debug run" : formatStatusTargetLabel(paramsWithResolvedCwd, targetRunId);
|
|
5042
5558
|
const withBudget = (result: AgentToolResult<Details>) => {
|
|
5043
5559
|
const budgeted = withSpawnBudgetStatus(result, deps.state, deps.config, deps.state.currentSessionId);
|
|
5044
5560
|
return {
|
|
@@ -5050,6 +5566,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5050
5566
|
};
|
|
5051
5567
|
const nestedScope = nestedResolutionScopeForExecutor(deps);
|
|
5052
5568
|
const sessionRoots = trustedSessionRootsForStatus(ctx, deps);
|
|
5569
|
+
if (action === "debug.run") {
|
|
5570
|
+
if (!targetRunId && !hasDirectoryTarget) {
|
|
5571
|
+
return withBudget({ content: [{ type: "text", text: "action='debug.run' requires id, runId, or dir." }], isError: true, details: { mode: "management", results: [] } });
|
|
5572
|
+
}
|
|
5573
|
+
if (paramsWithResolvedCwd.view) {
|
|
5574
|
+
return withBudget({ content: [{ type: "text", text: "action='debug.run' does not support status views." }], isError: true, details: { mode: "management", results: [] } });
|
|
5575
|
+
}
|
|
5576
|
+
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
|
|
5577
|
+
}
|
|
5053
5578
|
if (paramsWithResolvedCwd.view === "fleet") {
|
|
5054
5579
|
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots })));
|
|
5055
5580
|
}
|
|
@@ -5439,6 +5964,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5439
5964
|
const sessionName = resolveIntercomSessionTarget(deps.pi.getSessionName(), ctx.sessionManager.getSessionId());
|
|
5440
5965
|
const intercomBridge = resolveIntercomBridge({
|
|
5441
5966
|
config: deps.config.intercomBridge,
|
|
5967
|
+
override: effectiveParams.intercomBridge,
|
|
5442
5968
|
context: effectiveParams.context ?? (contextPolicy.usesFork ? "fork" : undefined),
|
|
5443
5969
|
orchestratorTarget: sessionName,
|
|
5444
5970
|
});
|
|
@@ -5448,7 +5974,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5448
5974
|
const runId = randomUUID().slice(0, 8);
|
|
5449
5975
|
const inheritedNestedRoute = resolveInheritedNestedRouteFromEnv();
|
|
5450
5976
|
const nestedParentAddress = inheritedNestedRoute ? resolveNestedParentAddressFromEnv() : undefined;
|
|
5451
|
-
const nestedRoute = inheritedNestedRoute ?? createNestedRoute(runId);
|
|
5452
5977
|
const shareEnabled = effectiveParams.share === true;
|
|
5453
5978
|
const hasChain = (effectiveParams.chain?.length ?? 0) > 0;
|
|
5454
5979
|
const hasTasks = (effectiveParams.tasks?.length ?? 0) > 0;
|
|
@@ -5532,9 +6057,50 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5532
6057
|
const foregroundTimeout = resolveSingleAgentLaunchTimeout(
|
|
5533
6058
|
effectiveParams,
|
|
5534
6059
|
effectiveAsync,
|
|
6060
|
+
resolveConfigDefaultTimeoutMs(deps.config.timeoutMs),
|
|
5535
6061
|
);
|
|
5536
6062
|
if (foregroundTimeout.error) return buildRequestedModeError(effectiveParams, foregroundTimeout.error);
|
|
5537
6063
|
const controlConfig = resolveControlConfig(deps.config.control, effectiveParams.control);
|
|
6064
|
+
const requestedWorkflowChildAsyncId = typeof effectiveParams.workflowChildAsyncId === "string" ? effectiveParams.workflowChildAsyncId.trim() : "";
|
|
6065
|
+
const asyncRunId = requestedWorkflowChildAsyncId && path.basename(requestedWorkflowChildAsyncId) === requestedWorkflowChildAsyncId
|
|
6066
|
+
? requestedWorkflowChildAsyncId
|
|
6067
|
+
: randomUUID();
|
|
6068
|
+
const topLevelAsyncCapacityEligible = depth === 0 && !inheritedNestedRoute && !effectiveParams.workflowParentRunId;
|
|
6069
|
+
const topLevelAsync = effectiveAsync && topLevelAsyncCapacityEligible;
|
|
6070
|
+
let activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined;
|
|
6071
|
+
if (topLevelAsync) {
|
|
6072
|
+
try {
|
|
6073
|
+
const activeLimit = resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession);
|
|
6074
|
+
activeAsyncCapacity = acquireActiveAsyncCapacity({
|
|
6075
|
+
sessionId: requestSessionId,
|
|
6076
|
+
limit: activeLimit,
|
|
6077
|
+
runId: asyncRunId,
|
|
6078
|
+
kind: "runner",
|
|
6079
|
+
asyncDir: path.join(DIRS.async, asyncRunId),
|
|
6080
|
+
}, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) });
|
|
6081
|
+
deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(requestSessionId, activeLimit, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) });
|
|
6082
|
+
} catch (error) {
|
|
6083
|
+
if (error instanceof ActiveAsyncCapacityError) {
|
|
6084
|
+
deps.state.activeAsyncCapacity = error.snapshot;
|
|
6085
|
+
return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: foregroundMode, results: [], activeAsyncCapacity: error.snapshot } };
|
|
6086
|
+
}
|
|
6087
|
+
throw error;
|
|
6088
|
+
}
|
|
6089
|
+
}
|
|
6090
|
+
|
|
6091
|
+
let runFanoutBudget: RunFanoutBudgetDescriptor;
|
|
6092
|
+
try {
|
|
6093
|
+
const inheritedRunFanoutBudget = effectiveParams.runFanoutBudget ? undefined : decodeRunFanoutBudgetDescriptor(process.env[RUN_FANOUT_BUDGET_ENV]);
|
|
6094
|
+
runFanoutBudget = effectiveParams.runFanoutBudget
|
|
6095
|
+
?? (inheritedRunFanoutBudget ? { ...inheritedRunFanoutBudget, parentPath: `${inheritedRunFanoutBudget.parentPath ? `${inheritedRunFanoutBudget.parentPath}/` : ""}${runId}` } : undefined)
|
|
6096
|
+
?? createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(deps.config.maxSubagentSpawnsPerRun));
|
|
6097
|
+
if (!effectiveParams.runFanoutAdmitted) claimRunFanoutBatch(runFanoutBudget, staticRunFanoutPaths(effectiveParams));
|
|
6098
|
+
} catch (error) {
|
|
6099
|
+
activeAsyncCapacity?.rollback();
|
|
6100
|
+
if (error instanceof RunFanoutLimitError) return runFanoutErrorResult(error, foregroundMode);
|
|
6101
|
+
return buildRequestedModeError(effectiveParams, error instanceof Error ? error.message : String(error));
|
|
6102
|
+
}
|
|
6103
|
+
const nestedRoute = inheritedNestedRoute ?? createNestedRoute(runId);
|
|
5538
6104
|
|
|
5539
6105
|
const artifactConfig: ArtifactConfig = omitUndefinedProperties({
|
|
5540
6106
|
...DEFAULT_ARTIFACT_CONFIG,
|
|
@@ -5560,6 +6126,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5560
6126
|
try {
|
|
5561
6127
|
fs.mkdirSync(sessionRoot, { recursive: true });
|
|
5562
6128
|
} catch (error) {
|
|
6129
|
+
activeAsyncCapacity?.rollback();
|
|
5563
6130
|
const message = error instanceof Error ? error.message : String(error);
|
|
5564
6131
|
return toExecutionErrorResult(
|
|
5565
6132
|
effectiveParams,
|
|
@@ -5590,10 +6157,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5590
6157
|
preflightForkSessionsForStaticTasks(effectiveParams, contextPolicy, forkSessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems);
|
|
5591
6158
|
}
|
|
5592
6159
|
} catch (error) {
|
|
6160
|
+
activeAsyncCapacity?.rollback();
|
|
5593
6161
|
return toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary);
|
|
5594
6162
|
}
|
|
5595
6163
|
const chainBindingsError = validateExecutionChainBindings(effectiveParams, deps.config.chain?.dynamicFanout?.maxItems);
|
|
5596
|
-
if (chainBindingsError)
|
|
6164
|
+
if (chainBindingsError) {
|
|
6165
|
+
activeAsyncCapacity?.rollback();
|
|
6166
|
+
return withResolvedContext(chainBindingsError, contextPolicy.contextSummary);
|
|
6167
|
+
}
|
|
5597
6168
|
|
|
5598
6169
|
const onUpdateWithContext = onUpdate
|
|
5599
6170
|
? (r: AgentToolResult<Details>) => onUpdate(withResolvedContext({
|
|
@@ -5613,14 +6184,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5613
6184
|
ownerSessionId: requestSessionId,
|
|
5614
6185
|
});
|
|
5615
6186
|
} catch (error) {
|
|
5616
|
-
if (explicitMission)
|
|
6187
|
+
if (explicitMission) {
|
|
6188
|
+
activeAsyncCapacity?.rollback();
|
|
6189
|
+
return toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary);
|
|
6190
|
+
}
|
|
5617
6191
|
missionWarning = `Mission tracking unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
5618
6192
|
}
|
|
5619
6193
|
|
|
5620
6194
|
const attachMission = (result: AgentToolResult<Details>): AgentToolResult<Details> => {
|
|
5621
6195
|
if (!missionBinding) return missionWarning ? { ...result, details: { ...result.details, missionWarning } } : result;
|
|
5622
6196
|
try {
|
|
5623
|
-
return attachMissionToLaunchResult({ binding: missionBinding, result });
|
|
6197
|
+
return attachMissionToLaunchResult({ binding: delegatedExecution ? { ...missionBinding, announceInContent: false } : missionBinding, result });
|
|
5624
6198
|
} catch (error) {
|
|
5625
6199
|
const warning = `Mission tracking unavailable after launch: ${error instanceof Error ? error.message : String(error)}`;
|
|
5626
6200
|
if (explicitMission) {
|
|
@@ -5641,7 +6215,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5641
6215
|
requestSessionId,
|
|
5642
6216
|
requestedSpawns,
|
|
5643
6217
|
);
|
|
5644
|
-
if (reservation.error)
|
|
6218
|
+
if (reservation.error) {
|
|
6219
|
+
activeAsyncCapacity?.rollback();
|
|
6220
|
+
return attachMission(spawnBudgetErrorResult(reservation.error, foregroundMode));
|
|
6221
|
+
}
|
|
5645
6222
|
|
|
5646
6223
|
const execData: ExecutionContextData = omitUndefinedProperties({
|
|
5647
6224
|
params: effectiveParams,
|
|
@@ -5650,6 +6227,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5650
6227
|
signal,
|
|
5651
6228
|
onUpdate: onUpdateWithContext,
|
|
5652
6229
|
agents,
|
|
6230
|
+
recoveryAgents: discoveredAgents,
|
|
5653
6231
|
runId,
|
|
5654
6232
|
shareEnabled,
|
|
5655
6233
|
sessionRoot,
|
|
@@ -5661,6 +6239,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5661
6239
|
artifactsDir,
|
|
5662
6240
|
backgroundRequestedWhileClarifying,
|
|
5663
6241
|
effectiveAsync,
|
|
6242
|
+
asyncRunId,
|
|
5664
6243
|
controlConfig,
|
|
5665
6244
|
intercomBridge,
|
|
5666
6245
|
nestedRoute,
|
|
@@ -5676,11 +6255,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5676
6255
|
parentSessionId: requestSessionId,
|
|
5677
6256
|
parentPiSessionId: requestPiSessionId,
|
|
5678
6257
|
capabilityCeiling: resolveCurrentSubagentCapabilityCeiling(requestSessionId),
|
|
6258
|
+
runFanoutBudget,
|
|
6259
|
+
topLevelAsyncCapacityEligible,
|
|
6260
|
+
activeAsyncCapacity,
|
|
5679
6261
|
});
|
|
5680
6262
|
|
|
5681
|
-
const foregroundDescription =
|
|
5682
|
-
|
|
5683
|
-
|
|
6263
|
+
const foregroundDescription = selectedAgentNames.length === 1
|
|
6264
|
+
? `${selectedAgentNames[0]} child`
|
|
6265
|
+
: `${selectedAgentNames.length} live children`;
|
|
5684
6266
|
const parentWorkflowStatus = effectiveParams.workflowParentRunId
|
|
5685
6267
|
? readStatus(path.join(DIRS.async, effectiveParams.workflowParentRunId))
|
|
5686
6268
|
: null;
|
|
@@ -5715,6 +6297,37 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5715
6297
|
interrupt: undefined,
|
|
5716
6298
|
});
|
|
5717
6299
|
if (foregroundControl) {
|
|
6300
|
+
foregroundControl.promptAuditRedo = async (index, guidance) => {
|
|
6301
|
+
const audit = getLivePromptAudit(foregroundControl, index);
|
|
6302
|
+
if (!audit?.rerun) return { text: "Redo is not safe for this prompt in this slice.", isError: true };
|
|
6303
|
+
const rewrittenTask = await rewritePromptWithGuidance({
|
|
6304
|
+
ctx,
|
|
6305
|
+
authoredTask: audit.authoredTask,
|
|
6306
|
+
runtimeAdditions: audit.runtimeAdditions,
|
|
6307
|
+
finalEffectivePrompt: audit.finalEffectivePrompt,
|
|
6308
|
+
guidance,
|
|
6309
|
+
signal,
|
|
6310
|
+
});
|
|
6311
|
+
const redoParams = promptAuditRedoParams(audit.rerun.params, rewrittenTask);
|
|
6312
|
+
const previousForegroundId = deps.state.lastForegroundControlId;
|
|
6313
|
+
const launch = execute(randomUUID(), redoParams, signal, undefined, ctx, true);
|
|
6314
|
+
const newRunId = deps.state.lastForegroundControlId && deps.state.lastForegroundControlId !== previousForegroundId
|
|
6315
|
+
? deps.state.lastForegroundControlId
|
|
6316
|
+
: undefined;
|
|
6317
|
+
if (!newRunId) {
|
|
6318
|
+
const result = await launch;
|
|
6319
|
+
return { text: result.content.find((item) => item.type === "text")?.text ?? "Prompt redo could not start.", isError: true };
|
|
6320
|
+
}
|
|
6321
|
+
foregroundControl.supersededByRunId = newRunId;
|
|
6322
|
+
const replacement = deps.state.foregroundControls.get(newRunId);
|
|
6323
|
+
if (replacement) replacement.sourceRunId = foregroundControl.runId;
|
|
6324
|
+
void launch.then((result) => {
|
|
6325
|
+
if (result.isError) console.warn(`[pi-subagents] Prompt redo ${newRunId} failed: ${result.content.find((item) => item.type === "text")?.text ?? "unknown error"}`);
|
|
6326
|
+
}).catch((error) => {
|
|
6327
|
+
console.warn(`[pi-subagents] Prompt redo ${newRunId} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
6328
|
+
});
|
|
6329
|
+
return { text: `Prompt redo started ${newRunId}.` };
|
|
6330
|
+
};
|
|
5718
6331
|
deps.state.foregroundControls.set(runId, foregroundControl);
|
|
5719
6332
|
deps.state.lastForegroundControlId = runId;
|
|
5720
6333
|
}
|
|
@@ -5805,13 +6418,18 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5805
6418
|
};
|
|
5806
6419
|
|
|
5807
6420
|
let nestedForegroundStarted = false;
|
|
6421
|
+
let asyncLaunchFailed = false;
|
|
5808
6422
|
try {
|
|
6423
|
+
if (effectiveAsync) {
|
|
6424
|
+
deps.state.liveAsyncSessionRoots ??= new Map();
|
|
6425
|
+
deps.state.liveAsyncSessionRoots.set(asyncRunId, sessionRoot);
|
|
6426
|
+
}
|
|
5809
6427
|
if (workflowLaunchObserver) {
|
|
5810
6428
|
const singleTask = hasTasks && effectiveParams.tasks?.length === 1 ? effectiveParams.tasks[0] : undefined;
|
|
5811
6429
|
const launch = hasSingle
|
|
5812
|
-
? { agent: effectiveParams.agent!, sessionFile: childSessionFileForTask(effectiveParams.agent!, 0, effectiveParams.model) }
|
|
6430
|
+
? { agent: effectiveParams.agent!, sessionFile: childSessionFileForTask(effectiveParams.agent!, 0, effectiveParams.model), async: effectiveAsync, ...(effectiveAsync ? { runId: asyncRunId } : {}) }
|
|
5813
6431
|
: singleTask
|
|
5814
|
-
? { agent: singleTask.agent, sessionFile: childSessionFileForTask(singleTask.agent, 0, singleTask.model) }
|
|
6432
|
+
? { agent: singleTask.agent, sessionFile: childSessionFileForTask(singleTask.agent, 0, singleTask.model), async: effectiveAsync, ...(effectiveAsync ? { runId: asyncRunId } : {}) }
|
|
5815
6433
|
: undefined;
|
|
5816
6434
|
if (launch) {
|
|
5817
6435
|
workflowLaunchObservers.delete(params);
|
|
@@ -5819,31 +6437,38 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5819
6437
|
}
|
|
5820
6438
|
}
|
|
5821
6439
|
const asyncResult = runAsyncPath(execData, deps);
|
|
5822
|
-
if (asyncResult)
|
|
6440
|
+
if (asyncResult) {
|
|
6441
|
+
asyncLaunchFailed = asyncResult.isError === true;
|
|
6442
|
+
return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(asyncResult, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget));
|
|
6443
|
+
}
|
|
5823
6444
|
if (foregroundControl) {
|
|
5824
6445
|
writeNestedForegroundEvent("subagent.nested.started");
|
|
5825
6446
|
nestedForegroundStarted = true;
|
|
5826
6447
|
}
|
|
6448
|
+
const runFanoutAnnotateContent = !delegatedExecution;
|
|
5827
6449
|
if (hasChain && effectiveParams.chain) {
|
|
5828
6450
|
const result = await runChainPath(execData, deps);
|
|
5829
6451
|
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
5830
|
-
return attachMission(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary));
|
|
6452
|
+
return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget, { annotateContent: runFanoutAnnotateContent }));
|
|
5831
6453
|
}
|
|
5832
6454
|
if (hasTasks && effectiveParams.tasks) {
|
|
5833
6455
|
const result = await runParallelPath(execData, deps);
|
|
5834
6456
|
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
5835
|
-
return attachMission(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary));
|
|
6457
|
+
return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget, { annotateContent: runFanoutAnnotateContent }));
|
|
5836
6458
|
}
|
|
5837
6459
|
if (hasSingle) {
|
|
5838
6460
|
const result = await runSinglePath(execData, deps);
|
|
5839
6461
|
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
5840
|
-
return attachMission(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary));
|
|
6462
|
+
return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget, { annotateContent: runFanoutAnnotateContent }));
|
|
5841
6463
|
}
|
|
5842
6464
|
} catch (error) {
|
|
6465
|
+
asyncLaunchFailed = effectiveAsync;
|
|
5843
6466
|
const errorResult = withForkThinkingNotes(toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary), forkThinkingDowngrades);
|
|
5844
6467
|
if (nestedForegroundStarted) writeNestedForegroundEvent("subagent.nested.completed", errorResult);
|
|
5845
6468
|
return attachMission(errorResult);
|
|
5846
6469
|
} finally {
|
|
6470
|
+
if (effectiveAsync && (asyncLaunchFailed || (activeAsyncCapacity && !activeAsyncCapacity.owner.runnerStartedAt))) deps.state.liveAsyncSessionRoots?.delete(asyncRunId);
|
|
6471
|
+
if (activeAsyncCapacity && !activeAsyncCapacity.owner.runnerStartedAt) activeAsyncCapacity.rollback();
|
|
5847
6472
|
if (foregroundControl) {
|
|
5848
6473
|
settleForegroundSchedulingOwner(foregroundControl);
|
|
5849
6474
|
removeForegroundControlIfIdle(deps.state, runId);
|
|
@@ -5912,6 +6537,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5912
6537
|
delete privateParams.delegatedAllowZeroToolBudget;
|
|
5913
6538
|
if (thinkingOverride !== undefined) delegatedThinkingOverrides.set(delegatedParams, thinkingOverride);
|
|
5914
6539
|
if (allowZeroToolBudget) delegatedZeroToolBudgets.add(delegatedParams);
|
|
6540
|
+
delegatedExecutions.add(delegatedParams);
|
|
5915
6541
|
return execute(id, delegatedParams, signal, onUpdate, ctx);
|
|
5916
6542
|
};
|
|
5917
6543
|
|