@selesai/code 0.8.4 → 0.8.6
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 +22 -0
- package/README.md +7 -1
- package/dist/core/slash-commands.js +1 -0
- package/dist/defaults/models.json +46 -55
- package/dist/defaults/settings.json +7 -8
- package/dist/extensions/agent-browser.test.ts +178 -2
- package/dist/extensions/agent-browser.ts +2 -0
- package/dist/extensions/copy-turn.test.ts +229 -0
- package/dist/extensions/copy-turn.ts +11 -0
- package/dist/extensions/grep-app/index.test.ts +356 -1
- package/dist/extensions/grep-app/index.ts +6 -1
- package/dist/extensions/handoff-new.test.ts +351 -161
- package/dist/extensions/handoff-new.ts +3 -0
- package/dist/extensions/inline-skills.test.ts +93 -0
- package/dist/extensions/inline-skills.ts +3 -0
- package/dist/extensions/pi-subagents/CHANGELOG.md +26 -3
- package/dist/extensions/pi-subagents/README.md +2 -0
- package/dist/extensions/pi-subagents/agents/builder.md +5 -2
- package/dist/extensions/pi-subagents/agents/commentator.md +1 -1
- package/dist/extensions/pi-subagents/docs/configuration.md +54 -2
- package/dist/extensions/pi-subagents/docs/observability.md +1 -1
- package/dist/extensions/pi-subagents/docs/tool-reference.md +1 -1
- package/dist/extensions/pi-subagents/docs/workflows.md +1 -1
- package/dist/extensions/pi-subagents/package-lock.json +2 -2
- package/dist/extensions/pi-subagents/package.json +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +1 -1
- package/dist/extensions/pi-subagents/src/agents/agents.ts +9 -3
- package/dist/extensions/pi-subagents/src/extension/config.ts +12 -0
- package/dist/extensions/pi-subagents/src/extension/doctor.ts +40 -0
- package/dist/extensions/pi-subagents/src/extension/index.ts +104 -14
- package/dist/extensions/pi-subagents/src/extension/public-execution.ts +5 -0
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +3 -9
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +2 -2
- package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +4 -1
- package/dist/extensions/pi-subagents/src/missions/lifecycle.ts +4 -7
- package/dist/extensions/pi-subagents/src/missions/store.ts +4 -4
- package/dist/extensions/pi-subagents/src/missions/workflow-state.ts +6 -2
- package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +374 -0
- package/dist/extensions/pi-subagents/src/runs/background/active-run-index.ts +9 -5
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +117 -29
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +12 -6
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +7 -1
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +14 -5
- package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +33 -15
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +5 -2
- package/dist/extensions/pi-subagents/src/runs/background/owned-process-tree.ts +104 -0
- package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +17 -3
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +19 -1
- package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +5 -2
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +12 -6
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +3 -3
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +84 -36
- package/dist/extensions/pi-subagents/src/runs/background/wait-subscriptions.ts +2 -1
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +37 -2
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +135 -22
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +12 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +7 -4
- package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +171 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +653 -196
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +13 -4
- package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +286 -0
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +23 -0
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +44 -1
- package/dist/extensions/pi-subagents/src/runs/shared/run-fanout-budget.ts +280 -0
- package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +4 -2
- package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +19 -3
- package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +17 -5
- package/dist/extensions/pi-subagents/src/shared/session-lineage.ts +71 -0
- package/dist/extensions/pi-subagents/src/shared/types.ts +108 -0
- package/dist/extensions/pi-subagents/src/shared/utils.ts +3 -1
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +7 -5
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +226 -13
- package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +22 -5
- package/dist/extensions/pi-subagents/src/workflows/workflow-auto-relaunch.ts +28 -0
- package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +87 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +96 -6
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +28 -5
- package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +8 -5
- package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +83 -2
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +211 -15
- package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +41 -3
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +314 -10
- package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +31 -1
- package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +317 -0
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +21 -2
- package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +16 -1
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +6 -1
- package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +75 -4
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +18 -0
- package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +29 -1
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +31 -5
- package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +139 -1
- package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +10 -0
- package/dist/extensions/pi-subagents/test/unit/handoff-adoption.test.ts +103 -0
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +22 -0
- package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +2 -1
- package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +171 -0
- package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +30 -4
- package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +14 -0
- package/dist/extensions/pi-subagents/test/unit/owned-process-tree.test.ts +69 -0
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +83 -0
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +55 -2
- package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +12 -1
- package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +7 -7
- package/dist/extensions/pi-subagents/test/unit/run-fanout-budget.test.ts +121 -0
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +9 -0
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +4 -3
- package/dist/extensions/pi-subagents/test/unit/session-lineage.test.ts +73 -0
- package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +28 -0
- package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +10 -1
- package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +39 -0
- package/dist/extensions/pi-subagents/test/unit/timeout-defaults.test.ts +65 -0
- package/dist/extensions/pi-subagents/test/unit/workflow-auto-relaunch.test.ts +45 -0
- package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +61 -0
- package/dist/extensions/ponytail/index.js +11 -0
- package/dist/extensions/ponytail/ponytail-config.cjs +2 -0
- package/dist/extensions/ponytail/ponytail-instructions.cjs +6 -0
- package/dist/extensions/ponytail/test/extension.test.js +274 -140
- package/dist/extensions/ponytail/test/helpers.test.js +280 -92
- package/dist/extensions/question/index.ts +18 -0
- package/dist/extensions/question/question-list.ts +22 -0
- package/dist/extensions/question/tests/batch.test.ts +103 -65
- package/dist/extensions/question/tests/helpers.test.ts +68 -35
- package/dist/extensions/question/tests/question-list.test.ts +506 -204
- package/dist/extensions/question/tests/row-layout.test.ts +180 -114
- package/dist/extensions/question/tests/schemas.test.ts +42 -19
- package/dist/extensions/question/tests/shortcuts.test.ts +106 -84
- package/dist/extensions/question/tests/wizard.test.ts +771 -50
- package/dist/extensions/rtk.test.ts +180 -1
- package/dist/extensions/tokenin-onboarding.ts +3 -0
- package/dist/extensions/undo.test.ts +720 -0
- package/dist/extensions/undo.ts +6 -0
- package/dist/extensions/web-agent-onboarding.test.ts +415 -0
- package/dist/extensions/workflow/extension.ts +3 -3
- package/dist/extensions/workflow/modes.ts +41 -19
- package/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.js +48 -1
- package/dist/skills/pi-subagents/references/execution-controls.md +1 -1
- package/docs/plans/workflow-autoloop-reference.md +1 -2
- package/docs/plans/workflow-handoff-carryover.md +108 -0
- package/docs/quickstart.md +27 -43
- package/docs/settings.md +4 -0
- package/docs/usage.md +1 -0
- package/package.json +5 -1
|
@@ -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";
|
|
@@ -111,6 +115,7 @@ import { resolveAuthorityDecision } from "../../policy/authority.ts";
|
|
|
111
115
|
import { handleHerdrInspectorAction, HERDR_INSPECTOR_ACTIONS } from "../../inspectors/herdr/actions.ts";
|
|
112
116
|
import { handleHerdrProjectPaneAction, HERDR_PROJECT_PANE_ACTIONS } from "../../inspectors/herdr/project-panes.ts";
|
|
113
117
|
import { previewSimpleWorkflowRun, runWorkflowScript, WorkflowScriptError, type WorkflowScriptChildResult } from "../../workflows/scripted-workflow.ts";
|
|
118
|
+
import { resolveAutoRelaunchDecision, resolveMaxWorkflowAutoRelaunches } from "../../workflows/workflow-auto-relaunch.ts";
|
|
114
119
|
import { renderWorkflowPrompt } from "../../shared/prompt-resources.ts";
|
|
115
120
|
import { resolveWorkflowChatProgress, type WorkflowChatProgressProjection } from "../../workflows/chat-progress.ts";
|
|
116
121
|
import {
|
|
@@ -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,6 +291,10 @@ 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[];
|
|
@@ -388,6 +399,7 @@ interface ExecutionContextData {
|
|
|
388
399
|
artifactsDir: string;
|
|
389
400
|
backgroundRequestedWhileClarifying: boolean;
|
|
390
401
|
effectiveAsync: boolean;
|
|
402
|
+
asyncRunId: string;
|
|
391
403
|
controlConfig: ResolvedControlConfig;
|
|
392
404
|
intercomBridge: IntercomBridgeState;
|
|
393
405
|
nestedRoute?: NestedRouteInfo;
|
|
@@ -404,6 +416,9 @@ interface ExecutionContextData {
|
|
|
404
416
|
parentSessionId: string | null;
|
|
405
417
|
parentPiSessionId?: string;
|
|
406
418
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
419
|
+
runFanoutBudget: RunFanoutBudgetDescriptor;
|
|
420
|
+
topLevelAsyncCapacityEligible: boolean;
|
|
421
|
+
activeAsyncCapacity?: ActiveAsyncCapacityHandle;
|
|
407
422
|
}
|
|
408
423
|
|
|
409
424
|
function resolveRequestedCwd(runtimeCwd: string, requestedCwd: string | undefined): string {
|
|
@@ -432,6 +447,17 @@ function getForegroundControl(state: SubagentState, runId: string | undefined) {
|
|
|
432
447
|
return newest;
|
|
433
448
|
}
|
|
434
449
|
|
|
450
|
+
function promptAuditRedoParams(value: unknown, rewrittenTask: string): SubagentParamsLike {
|
|
451
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Prompt redo is missing a safe live launch contract.");
|
|
452
|
+
const params = { ...(value as SubagentParamsLike), task: rewrittenTask, async: false };
|
|
453
|
+
delete params.workflowParentRunId;
|
|
454
|
+
delete params.workflowKey;
|
|
455
|
+
delete params.workflowChildAsyncId;
|
|
456
|
+
delete params.suppressRoutineResultIntercom;
|
|
457
|
+
if (params.worktree === true && Array.isArray(params.tasks)) delete params.cwd;
|
|
458
|
+
return params;
|
|
459
|
+
}
|
|
460
|
+
|
|
435
461
|
function formatForegroundActivity(control: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never): string | undefined {
|
|
436
462
|
const facts: string[] = [];
|
|
437
463
|
if (control.currentTool && control.currentToolStartedAt) facts.push(`tool ${control.currentTool} for ${Math.floor(Math.max(0, Date.now() - control.currentToolStartedAt) / 1000)}s`);
|
|
@@ -483,12 +509,18 @@ function withSpawnBudgetStatus(
|
|
|
483
509
|
sessionId: string | null,
|
|
484
510
|
): AgentToolResult<Details> {
|
|
485
511
|
const spawnBudget = getSpawnBudgetSnapshot(state, config, sessionId);
|
|
512
|
+
const activeAsyncCapacity = sessionId
|
|
513
|
+
? getActiveAsyncCapacitySnapshot(sessionId, resolveMaxActiveAsyncRunsPerSession(config.maxActiveAsyncRunsPerSession), {
|
|
514
|
+
liveWorkflowRunIds: new Set(state.workflowControllers?.keys() ?? []),
|
|
515
|
+
})
|
|
516
|
+
: { used: 0, limit: resolveMaxActiveAsyncRunsPerSession(config.maxActiveAsyncRunsPerSession) ?? 0 };
|
|
517
|
+
state.activeAsyncCapacity = activeAsyncCapacity;
|
|
486
518
|
return {
|
|
487
519
|
...result,
|
|
488
520
|
content: result.content.map((item, index) => index === 0 && item.type === "text"
|
|
489
|
-
? { ...item, text: `${formatSpawnBudget(spawnBudget)}\n${item.text}` }
|
|
521
|
+
? { ...item, text: `${formatSpawnBudget(spawnBudget)}\nActive async capacity: ${activeAsyncCapacity.used}/${activeAsyncCapacity.limit || "unlimited"} used\n${item.text}` }
|
|
490
522
|
: item),
|
|
491
|
-
details: { ...result.details, spawnBudget },
|
|
523
|
+
details: { ...result.details, spawnBudget, activeAsyncCapacity },
|
|
492
524
|
};
|
|
493
525
|
}
|
|
494
526
|
|
|
@@ -509,6 +541,31 @@ function countRequestedSubagentSpawns(params: SubagentParamsLike, config: Extens
|
|
|
509
541
|
return params.agent ? 1 : 0;
|
|
510
542
|
}
|
|
511
543
|
|
|
544
|
+
function staticRunFanoutPaths(params: SubagentParamsLike): string[] {
|
|
545
|
+
if (params.tasks) return params.tasks.map((_, index) => `tasks[${index}]`);
|
|
546
|
+
if (params.chain) return params.chain.flatMap((step, stepIndex) => {
|
|
547
|
+
if (isDynamicParallelStep(step) || "checkpoint" in step) return [];
|
|
548
|
+
if (isParallelStep(step)) return step.parallel.map((_, itemIndex) => `chain[${stepIndex}].parallel[${itemIndex}]`);
|
|
549
|
+
return [`chain[${stepIndex}]`];
|
|
550
|
+
});
|
|
551
|
+
return params.agent ? ["single"] : [];
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function runFanoutErrorResult(error: RunFanoutLimitError, mode: "single" | "parallel" | "chain"): AgentToolResult<Details> {
|
|
555
|
+
return { content: [{ type: "text", text: error.message }], isError: true, details: { mode, results: [], runFanoutBudget: error.snapshot, runFanoutRejection: error.rejection } };
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function withRunFanoutBudget(result: AgentToolResult<Details>, descriptor: RunFanoutBudgetDescriptor, options: { annotateContent?: boolean } = {}): AgentToolResult<Details> {
|
|
559
|
+
const runFanoutBudget = getRunFanoutBudgetSnapshot(descriptor);
|
|
560
|
+
return {
|
|
561
|
+
...result,
|
|
562
|
+
content: options.annotateContent === false
|
|
563
|
+
? result.content
|
|
564
|
+
: result.content.map((item, index) => index === 0 && item.type === "text" ? { ...item, text: `${formatRunFanoutBudget(runFanoutBudget)}\n${item.text}` } : item),
|
|
565
|
+
details: { ...result.details, runFanoutBudget },
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
|
|
512
569
|
function foregroundStatusResult(control: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never): AgentToolResult<Details> {
|
|
513
570
|
let nestedWarning: string | undefined;
|
|
514
571
|
try {
|
|
@@ -725,7 +782,8 @@ function updateRememberedForegroundChild(state: SubagentState, input: { runId: s
|
|
|
725
782
|
function resolveForegroundResumeTarget(params: SubagentParamsLike, state: SubagentState): { runId: string; mode: SubagentRunMode; state: "complete"; agent: string; index: number; cwd: string; sessionFile: string; model?: string; thinking?: string; launchContractDigest?: string; capabilityCeiling?: ResolvedSubagentCapabilityCeiling } | undefined {
|
|
726
783
|
const requested = (params.id ?? params.runId)?.trim();
|
|
727
784
|
if (!requested || !state.foregroundRuns?.size || !state.currentSessionId) return undefined;
|
|
728
|
-
const
|
|
785
|
+
const acceptedSessions = new Set(state.sessionLineage?.length ? state.sessionLineage : [state.currentSessionId]);
|
|
786
|
+
const sessionRuns = [...state.foregroundRuns.values()].filter((run) => run.sessionId !== undefined && acceptedSessions.has(run.sessionId));
|
|
729
787
|
const direct = sessionRuns.find((run) => run.runId === requested);
|
|
730
788
|
const matches = direct ? [direct] : sessionRuns.filter((run) => run.runId.startsWith(requested));
|
|
731
789
|
if (matches.length === 0) return undefined;
|
|
@@ -1141,18 +1199,30 @@ function appendStepToAsyncChain(input: {
|
|
|
1141
1199
|
}
|
|
1142
1200
|
|
|
1143
1201
|
try {
|
|
1202
|
+
const asyncDir = resolved.location.asyncDir;
|
|
1203
|
+
if (!asyncDir) throw new Error(`Run '${resolved.id}' is missing its async directory.`);
|
|
1204
|
+
const runFanoutBudget = readRunFanoutBudgetDescriptor(asyncDir);
|
|
1205
|
+
if (!runFanoutBudget) throw new Error(`Run '${resolved.id}' is missing its run fan-out budget identity.`);
|
|
1206
|
+
const startIndex = (status.chainStepCount ?? status.steps?.length ?? 0) + pendingAppendRequests.reduce((total, request) => total + request.steps.length, 0);
|
|
1207
|
+
const appendPaths = chain.flatMap((step, localIndex) => {
|
|
1208
|
+
const absoluteIndex = startIndex + localIndex;
|
|
1209
|
+
if (isDynamicParallelStep(step) || "checkpoint" in step) return [];
|
|
1210
|
+
if (isParallelStep(step)) return step.parallel.map((_, itemIndex) => `chain[${absoluteIndex}].parallel[${itemIndex}]`);
|
|
1211
|
+
return [`chain[${absoluteIndex}]`];
|
|
1212
|
+
});
|
|
1144
1213
|
const result = enqueueChainAppendRequest({
|
|
1145
|
-
asyncDir
|
|
1214
|
+
asyncDir,
|
|
1146
1215
|
runId: resolved.id,
|
|
1147
1216
|
steps: built.steps,
|
|
1217
|
+
admit: (persist) => claimRunFanoutBatchWithCommit(runFanoutBudget, appendPaths, persist),
|
|
1148
1218
|
});
|
|
1149
1219
|
const stepText = built.steps.length === 1 ? "step" : "steps";
|
|
1150
1220
|
return {
|
|
1151
1221
|
content: [{
|
|
1152
1222
|
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}
|
|
1223
|
+
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
1224
|
}],
|
|
1155
|
-
details: { mode: "management", results: [], asyncId: resolved.id, asyncDir
|
|
1225
|
+
details: { mode: "management", results: [], asyncId: resolved.id, asyncDir },
|
|
1156
1226
|
};
|
|
1157
1227
|
} catch (error) {
|
|
1158
1228
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1487,6 +1557,20 @@ async function resumeAsyncRun(input: {
|
|
|
1487
1557
|
};
|
|
1488
1558
|
}
|
|
1489
1559
|
const runId = randomUUID().slice(0, 8);
|
|
1560
|
+
const topLevelResume = depth === 0 && !resolveInheritedNestedRouteFromEnv() && !input.params.workflowParentRunId;
|
|
1561
|
+
let activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined;
|
|
1562
|
+
try {
|
|
1563
|
+
activeAsyncCapacity = topLevelResume ? acquireActiveAsyncCapacity({
|
|
1564
|
+
sessionId: input.deps.state.currentSessionId!,
|
|
1565
|
+
limit: resolveMaxActiveAsyncRunsPerSession(input.deps.config.maxActiveAsyncRunsPerSession),
|
|
1566
|
+
runId,
|
|
1567
|
+
kind: "runner",
|
|
1568
|
+
asyncDir: path.join(DIRS.async, runId),
|
|
1569
|
+
}, { liveWorkflowRunIds: new Set(input.deps.state.workflowControllers?.keys() ?? []) }) : undefined;
|
|
1570
|
+
} catch (error) {
|
|
1571
|
+
if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "chain", results: [], activeAsyncCapacity: error.snapshot } };
|
|
1572
|
+
throw error;
|
|
1573
|
+
}
|
|
1490
1574
|
const artifactConfig: ArtifactConfig = omitUndefinedProperties({ ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir });
|
|
1491
1575
|
const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
1492
1576
|
const contextPolicy = resolveExplicitContextPolicy(input.params);
|
|
@@ -1538,9 +1622,14 @@ async function resumeAsyncRun(input: {
|
|
|
1538
1622
|
controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
|
|
1539
1623
|
childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
1540
1624
|
globalConcurrencyLimit: input.deps.config.globalConcurrencyLimit,
|
|
1625
|
+
runFanoutBudget: createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
|
|
1541
1626
|
capabilityCeiling: intersectSubagentCapabilityCeilings("capabilityCeiling" in target ? target.capabilityCeiling : undefined, resolveCurrentSubagentCapabilityCeiling(input.deps.state.currentSessionId)),
|
|
1627
|
+
activeAsyncCapacity,
|
|
1542
1628
|
}));
|
|
1543
|
-
if (result.isError)
|
|
1629
|
+
if (result.isError) {
|
|
1630
|
+
activeAsyncCapacity?.rollback();
|
|
1631
|
+
return result;
|
|
1632
|
+
}
|
|
1544
1633
|
const attachedId = result.details.asyncId ?? runId;
|
|
1545
1634
|
const lines = [
|
|
1546
1635
|
`Attached async subagent ${target.runId} as the first step of a new chain.`,
|
|
@@ -1559,12 +1648,38 @@ async function resumeAsyncRun(input: {
|
|
|
1559
1648
|
if (!revivalSessionFile) {
|
|
1560
1649
|
return { content: [{ type: "text", text: `Async child '${target.runId}' has no persisted session file to resume.` }], isError: true, details: { mode: "management", results: [] } };
|
|
1561
1650
|
}
|
|
1651
|
+
if (target.source === "async" && !recoveryDescriptor) {
|
|
1652
|
+
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: [] } };
|
|
1653
|
+
}
|
|
1562
1654
|
const runId = randomUUID().slice(0, 8);
|
|
1655
|
+
const topLevelResume = depth === 0 && !resolveInheritedNestedRouteFromEnv() && !input.params.workflowParentRunId;
|
|
1656
|
+
let activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined;
|
|
1657
|
+
try {
|
|
1658
|
+
activeAsyncCapacity = !topLevelResume ? undefined : target.source === "async"
|
|
1659
|
+
? transferActiveAsyncCapacity({
|
|
1660
|
+
sessionId: input.deps.state.currentSessionId!,
|
|
1661
|
+
limit: resolveMaxActiveAsyncRunsPerSession(input.deps.config.maxActiveAsyncRunsPerSession),
|
|
1662
|
+
sourceRunId: target.runId,
|
|
1663
|
+
runId,
|
|
1664
|
+
asyncDir: path.join(DIRS.async, runId),
|
|
1665
|
+
})
|
|
1666
|
+
: acquireActiveAsyncCapacity({
|
|
1667
|
+
sessionId: input.deps.state.currentSessionId!,
|
|
1668
|
+
limit: resolveMaxActiveAsyncRunsPerSession(input.deps.config.maxActiveAsyncRunsPerSession),
|
|
1669
|
+
runId,
|
|
1670
|
+
kind: "runner",
|
|
1671
|
+
asyncDir: path.join(DIRS.async, runId),
|
|
1672
|
+
});
|
|
1673
|
+
} catch (error) {
|
|
1674
|
+
if (error instanceof ActiveAsyncCapacityError) return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "single", results: [], activeAsyncCapacity: error.snapshot } };
|
|
1675
|
+
return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "single", results: [] } };
|
|
1676
|
+
}
|
|
1563
1677
|
const recoveryAgentConfig = recoveryDescriptor ? applySteeringRecoveryAgentConfig(agentConfig, recoveryDescriptor) : agentConfig;
|
|
1564
1678
|
const artifactConfig: ArtifactConfig = recoveryDescriptor?.artifactConfig ?? omitUndefinedProperties({ ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir });
|
|
1565
1679
|
const artifactsDir = recoveryDescriptor?.artifactsDir ?? getArtifactsDir(parentSessionFile, effectiveCwd, artifactConfig.dir);
|
|
1566
1680
|
const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
1567
1681
|
const parentModel = input.parentModel;
|
|
1682
|
+
const revivalAsyncDir = path.join(DIRS.async, runId);
|
|
1568
1683
|
const result = executeAsyncSingle(runId, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
|
|
1569
1684
|
agent: target.agent,
|
|
1570
1685
|
task: buildRevivedAsyncTask(target as Parameters<typeof buildRevivedAsyncTask>[0], effectiveFollowUp),
|
|
@@ -1618,10 +1733,28 @@ async function resumeAsyncRun(input: {
|
|
|
1618
1733
|
...(input.params.turnBudget !== undefined ? { turnBudget: input.params.turnBudget } : {}),
|
|
1619
1734
|
...(input.params.toolBudget !== undefined ? { toolBudget: input.params.toolBudget } : {}),
|
|
1620
1735
|
capabilityCeiling: intersectSubagentCapabilityCeilings("capabilityCeiling" in target ? target.capabilityCeiling : undefined, recoveryDescriptor?.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(input.deps.state.currentSessionId)),
|
|
1736
|
+
runFanoutBudget: recoveryDescriptor?.runFanoutBudget ?? createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(input.deps.config.maxSubagentSpawnsPerRun)),
|
|
1621
1737
|
parentWorkflowRunId: input.params.workflowParentRunId,
|
|
1622
1738
|
workflowKey: input.params.workflowKey,
|
|
1739
|
+
activeAsyncCapacity,
|
|
1623
1740
|
}));
|
|
1624
|
-
if (result.isError)
|
|
1741
|
+
if (result.isError) {
|
|
1742
|
+
const startedStatus = readStatus(revivalAsyncDir);
|
|
1743
|
+
if (input.params.workflowParentRunId !== undefined && startedStatus?.runId === runId && startedStatus.processTerminal?.runnerProcessInstanceId) {
|
|
1744
|
+
return {
|
|
1745
|
+
...result,
|
|
1746
|
+
details: {
|
|
1747
|
+
...result.details,
|
|
1748
|
+
runId,
|
|
1749
|
+
asyncId: runId,
|
|
1750
|
+
asyncDir: revivalAsyncDir,
|
|
1751
|
+
...(target.launchContractDigest ? { sourceLaunchContractDigest: target.launchContractDigest } : {}),
|
|
1752
|
+
},
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
activeAsyncCapacity?.rollback();
|
|
1756
|
+
return result;
|
|
1757
|
+
}
|
|
1625
1758
|
for (const brief of queuedBriefs) fs.rmSync(brief.path, { force: true });
|
|
1626
1759
|
if (queuedBriefs.length > 0 && sourceAsyncDir) {
|
|
1627
1760
|
const sourceStatus = readStatus(sourceAsyncDir);
|
|
@@ -2117,6 +2250,27 @@ export const DEFAULT_FOREGROUND_TIMEOUT_MS = 30 * 60 * 1000;
|
|
|
2117
2250
|
// stay unbounded as a whole, while each runner child has its own deadline.
|
|
2118
2251
|
export { DEFAULT_ASYNC_TIMEOUT_MS };
|
|
2119
2252
|
|
|
2253
|
+
/**
|
|
2254
|
+
* Maximum delay a Node.js timer accepts. Values above the 32-bit signed integer
|
|
2255
|
+
* ceiling overflow `setTimeout`, which silently clamps the delay to ~1ms and
|
|
2256
|
+
* fires almost immediately — so a run configured with a larger deadline would
|
|
2257
|
+
* terminate right away while reporting the long duration. Any timeout destined
|
|
2258
|
+
* for a timer must stay within this bound.
|
|
2259
|
+
*/
|
|
2260
|
+
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
2261
|
+
|
|
2262
|
+
/**
|
|
2263
|
+
* Resolve the optional global default runtime deadline from extension config
|
|
2264
|
+
* (`config.timeoutMs`). Returns undefined for unset or invalid values so callers
|
|
2265
|
+
* fall back to the built-in defaults. "Invalid" covers non-positive-integer
|
|
2266
|
+
* values and values above `MAX_TIMER_DELAY_MS`; the latter would overflow the
|
|
2267
|
+
* Node.js timer and expire the run almost immediately instead of running long.
|
|
2268
|
+
*/
|
|
2269
|
+
export function resolveConfigDefaultTimeoutMs(raw: unknown): number | undefined {
|
|
2270
|
+
if (typeof raw !== "number" || !Number.isInteger(raw) || raw <= 0 || raw > MAX_TIMER_DELAY_MS) return undefined;
|
|
2271
|
+
return raw;
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2120
2274
|
export function resolveForegroundTimeout(params: SubagentParamsLike, defaultTimeoutMs?: number): { timeoutMs?: number; error?: string } {
|
|
2121
2275
|
const rawTimeout = params.timeoutMs;
|
|
2122
2276
|
const rawMaxRuntime = params.maxRuntimeMs;
|
|
@@ -2140,14 +2294,18 @@ export function resolveForegroundTimeout(params: SubagentParamsLike, defaultTime
|
|
|
2140
2294
|
* Resolve the effective launch timeout for a single-agent run, applying the
|
|
2141
2295
|
* async/foreground default when neither the caller nor the agent set one.
|
|
2142
2296
|
*
|
|
2143
|
-
*
|
|
2144
|
-
*
|
|
2145
|
-
*
|
|
2146
|
-
*
|
|
2297
|
+
* A global config default (`config.timeoutMs`, passed as `configDefaultTimeoutMs`)
|
|
2298
|
+
* replaces the built-in 30-minute backstop wherever a concrete default is applied.
|
|
2299
|
+
* The async default is deliberately applied only to plain single-agent launches.
|
|
2300
|
+
* Composite launches keep their top-level execution unbounded when no timeout is
|
|
2301
|
+
* set — even with a config default — while their runner children resolve separate
|
|
2302
|
+
* deadlines. Exported so the executor wiring is directly testable.
|
|
2147
2303
|
*/
|
|
2148
|
-
export function resolveSingleAgentLaunchTimeout(params: SubagentParamsLike, async: boolean): { timeoutMs?: number; error?: string } {
|
|
2304
|
+
export function resolveSingleAgentLaunchTimeout(params: SubagentParamsLike, async: boolean, configDefaultTimeoutMs?: number): { timeoutMs?: number; error?: string } {
|
|
2149
2305
|
const isComposite = (params.chain?.length ?? 0) > 0 || (params.tasks?.length ?? 0) > 0 || params.workflowScript !== undefined;
|
|
2150
|
-
const
|
|
2306
|
+
const foregroundDefault = configDefaultTimeoutMs ?? DEFAULT_FOREGROUND_TIMEOUT_MS;
|
|
2307
|
+
const asyncSingleDefault = configDefaultTimeoutMs ?? DEFAULT_ASYNC_TIMEOUT_MS;
|
|
2308
|
+
const defaultTimeoutMs = !async ? foregroundDefault : isComposite ? undefined : asyncSingleDefault;
|
|
2151
2309
|
return resolveForegroundTimeout(params, defaultTimeoutMs);
|
|
2152
2310
|
}
|
|
2153
2311
|
|
|
@@ -2560,8 +2718,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2560
2718
|
details: { mode: "single" as const, results: [] },
|
|
2561
2719
|
};
|
|
2562
2720
|
}
|
|
2563
|
-
const
|
|
2564
|
-
const id = requestedWorkflowChildAsyncId && path.basename(requestedWorkflowChildAsyncId) === requestedWorkflowChildAsyncId ? requestedWorkflowChildAsyncId : randomUUID();
|
|
2721
|
+
const id = data.asyncRunId;
|
|
2565
2722
|
const parentModel = data.parentModel;
|
|
2566
2723
|
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
2567
2724
|
pi: deps.pi,
|
|
@@ -2615,6 +2772,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2615
2772
|
shareEnabled,
|
|
2616
2773
|
sessionRoot,
|
|
2617
2774
|
chainSkills: [],
|
|
2775
|
+
activeAsyncCapacity: data.activeAsyncCapacity,
|
|
2618
2776
|
sessionFilesByFlatIndex: params.tasks.map((task, index) => sessionFileForTask(task.agent, index, task.model)),
|
|
2619
2777
|
thinkingOverridesByFlatIndex: params.tasks.map((task, index) => thinkingOverrideForTask(task.agent, index, task.model)),
|
|
2620
2778
|
contextForAgent: contextPolicy.contextForAgent,
|
|
@@ -2634,6 +2792,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2634
2792
|
usageBudget: data.usageBudget,
|
|
2635
2793
|
configToolBudget: data.configToolBudget,
|
|
2636
2794
|
capabilityCeiling: data.capabilityCeiling,
|
|
2795
|
+
runFanoutBudget: data.runFanoutBudget,
|
|
2637
2796
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2638
2797
|
parentWorkflowRunId: params.workflowParentRunId,
|
|
2639
2798
|
workflowKey: params.workflowKey,
|
|
@@ -2659,6 +2818,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2659
2818
|
shareEnabled,
|
|
2660
2819
|
sessionRoot,
|
|
2661
2820
|
chainSkills,
|
|
2821
|
+
activeAsyncCapacity: data.activeAsyncCapacity,
|
|
2662
2822
|
sessionFilesByFlatIndex: collectChainSessionFiles(chain, sessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
2663
2823
|
thinkingOverridesByFlatIndex: collectChainThinkingOverrides(chain, thinkingOverrideForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
2664
2824
|
contextForAgent: contextPolicy.contextForAgent,
|
|
@@ -2679,6 +2839,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2679
2839
|
usageBudget: data.usageBudget,
|
|
2680
2840
|
configToolBudget: data.configToolBudget,
|
|
2681
2841
|
capabilityCeiling: data.capabilityCeiling,
|
|
2842
|
+
runFanoutBudget: data.runFanoutBudget,
|
|
2682
2843
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2683
2844
|
parentWorkflowRunId: params.workflowParentRunId,
|
|
2684
2845
|
workflowKey: params.workflowKey,
|
|
@@ -2700,7 +2861,12 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2700
2861
|
const normalizedSkills = normalizeSkillInput(params.skill);
|
|
2701
2862
|
const skills = normalizedSkills === false ? [] : normalizedSkills;
|
|
2702
2863
|
const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, a.maxSubagentDepth);
|
|
2703
|
-
const
|
|
2864
|
+
const externalRunnerWithoutExplicitModel = a.runner?.type === "external-cli"
|
|
2865
|
+
&& params.model === undefined
|
|
2866
|
+
&& (a.model === undefined || (a.modelSource?.type === "subagents.defaultModel" && a.model === a.modelSource.model));
|
|
2867
|
+
const modelOverride = externalRunnerWithoutExplicitModel
|
|
2868
|
+
? undefined
|
|
2869
|
+
: resolveEffectiveSubagentModel(params.model as string | undefined, a.model, parentModel, availableModels, currentProvider, data.modelScope === undefined ? {} : { scope: data.modelScope });
|
|
2704
2870
|
return executeAsyncSingle(id, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
|
|
2705
2871
|
agent: params.agent!,
|
|
2706
2872
|
task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
|
|
@@ -2713,6 +2879,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2713
2879
|
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
2714
2880
|
artifactConfig,
|
|
2715
2881
|
shareEnabled,
|
|
2882
|
+
activeAsyncCapacity: data.activeAsyncCapacity,
|
|
2716
2883
|
sessionRoot,
|
|
2717
2884
|
sessionFile: sessionFileForTask(params.agent!, 0, modelOverride),
|
|
2718
2885
|
context: contextPolicy.contextForAgent(params.agent!),
|
|
@@ -2722,7 +2889,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2722
2889
|
...(params.reads !== undefined ? { reads: params.reads } : {}),
|
|
2723
2890
|
outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
|
|
2724
2891
|
modelOverride,
|
|
2725
|
-
thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride),
|
|
2892
|
+
thinkingOverride: externalRunnerWithoutExplicitModel ? undefined : thinkingOverrideForTask(params.agent!, 0, modelOverride),
|
|
2726
2893
|
maxSubagentDepth,
|
|
2727
2894
|
waitToolEnabled: deps.waitToolEnabled,
|
|
2728
2895
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
@@ -2741,6 +2908,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
2741
2908
|
usageBudget: data.usageBudget,
|
|
2742
2909
|
configToolBudget: data.configToolBudget,
|
|
2743
2910
|
capabilityCeiling: data.capabilityCeiling,
|
|
2911
|
+
runFanoutBudget: data.runFanoutBudget,
|
|
2744
2912
|
parentWorkflowRunId: params.workflowParentRunId,
|
|
2745
2913
|
workflowKey: params.workflowKey,
|
|
2746
2914
|
}));
|
|
@@ -2831,6 +2999,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2831
2999
|
permissions: deps.config.permissions,
|
|
2832
3000
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2833
3001
|
capabilityCeiling: data.capabilityCeiling,
|
|
3002
|
+
runFanoutBudget: data.runFanoutBudget,
|
|
2834
3003
|
}));
|
|
2835
3004
|
|
|
2836
3005
|
if (chainResult.requestedAsync) {
|
|
@@ -2841,7 +3010,9 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2841
3010
|
details: { mode: "chain" as const, results: [] },
|
|
2842
3011
|
};
|
|
2843
3012
|
}
|
|
2844
|
-
const id =
|
|
3013
|
+
const id = data.asyncRunId;
|
|
3014
|
+
const capacity = acquireClarifiedBackgroundCapacity(data, deps, "chain", id);
|
|
3015
|
+
if (capacity.errorResult) return capacity.errorResult;
|
|
2845
3016
|
const parentModel = data.parentModel;
|
|
2846
3017
|
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
2847
3018
|
pi: deps.pi,
|
|
@@ -2857,42 +3028,48 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2857
3028
|
const rawAsyncChain = chainResult.requestedAsync.chain;
|
|
2858
3029
|
const asyncChain = wrapChainTasksForFork(rawAsyncChain, contextPolicy);
|
|
2859
3030
|
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
|
-
|
|
3031
|
+
try {
|
|
3032
|
+
return executeAsyncChain(id, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
|
|
3033
|
+
chain: asyncChain,
|
|
3034
|
+
task: params.task,
|
|
3035
|
+
goal: resolveAsyncEventGoal(params.task, rawAsyncChain, firstAgent ? shouldForkAgent(contextPolicy, firstAgent) : false),
|
|
3036
|
+
agents,
|
|
3037
|
+
ctx: asyncCtx,
|
|
3038
|
+
availableModels: ctx.modelRegistry.getAvailable().map(toModelInfo),
|
|
3039
|
+
cwd: effectiveCwd,
|
|
3040
|
+
maxOutput: params.maxOutput,
|
|
3041
|
+
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
3042
|
+
artifactConfig,
|
|
3043
|
+
shareEnabled,
|
|
3044
|
+
sessionRoot,
|
|
3045
|
+
chainSkills: chainResult.requestedAsync.chainSkills,
|
|
3046
|
+
activeAsyncCapacity: capacity.activeAsyncCapacity,
|
|
3047
|
+
sessionFilesByFlatIndex: collectChainSessionFiles(asyncChain, sessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
3048
|
+
thinkingOverridesByFlatIndex: collectChainThinkingOverrides(asyncChain, thinkingOverrideForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
3049
|
+
contextForAgent: contextPolicy.contextForAgent,
|
|
3050
|
+
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
3051
|
+
maxSubagentDepth: currentMaxSubagentDepth,
|
|
3052
|
+
waitToolEnabled: deps.waitToolEnabled,
|
|
3053
|
+
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
3054
|
+
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
3055
|
+
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
3056
|
+
controlConfig,
|
|
3057
|
+
agentContract: params.agentContract,
|
|
3058
|
+
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
3059
|
+
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
3060
|
+
nestedRoute: data.nestedRoute,
|
|
3061
|
+
timeoutMs: data.timeoutMs,
|
|
3062
|
+
turnBudget: data.turnBudget,
|
|
3063
|
+
toolBudget: data.toolBudget,
|
|
3064
|
+
usageBudget: data.usageBudget,
|
|
3065
|
+
configToolBudget: data.configToolBudget,
|
|
3066
|
+
capabilityCeiling: data.capabilityCeiling,
|
|
3067
|
+
runFanoutBudget: data.runFanoutBudget,
|
|
3068
|
+
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
3069
|
+
}));
|
|
3070
|
+
} finally {
|
|
3071
|
+
finalizeClarifiedBackgroundCapacity(data, deps, capacity.activeAsyncCapacity);
|
|
3072
|
+
}
|
|
2896
3073
|
}
|
|
2897
3074
|
|
|
2898
3075
|
const rawChainDetails = chainResult.details ? compactOptional<Details>({ ...chainResult.details, runId, timeoutMs: data.timeoutMs }) : undefined;
|
|
@@ -2937,6 +3114,7 @@ interface ForegroundParallelRunInput {
|
|
|
2937
3114
|
intercomEvents: IntercomEventBus;
|
|
2938
3115
|
parentSessionId: string | null;
|
|
2939
3116
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
3117
|
+
runFanoutBudget: RunFanoutBudgetDescriptor;
|
|
2940
3118
|
signal: AbortSignal;
|
|
2941
3119
|
runId: string;
|
|
2942
3120
|
sessionDirForIndex: (idx?: number) => string | undefined;
|
|
@@ -2986,6 +3164,48 @@ function buildParallelModeError(message: string): AgentToolResult<Details> {
|
|
|
2986
3164
|
};
|
|
2987
3165
|
}
|
|
2988
3166
|
|
|
3167
|
+
function acquireClarifiedBackgroundCapacity(
|
|
3168
|
+
data: ExecutionContextData,
|
|
3169
|
+
deps: ExecutorDeps,
|
|
3170
|
+
mode: "single" | "parallel" | "chain",
|
|
3171
|
+
asyncRunId: string,
|
|
3172
|
+
): { activeAsyncCapacity?: ActiveAsyncCapacityHandle; errorResult?: AgentToolResult<Details> } {
|
|
3173
|
+
if (!data.topLevelAsyncCapacityEligible || !data.parentSessionId) return {};
|
|
3174
|
+
const liveWorkflowRunIds = new Set(deps.state.workflowControllers?.keys() ?? []);
|
|
3175
|
+
const activeLimit = resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession);
|
|
3176
|
+
try {
|
|
3177
|
+
const activeAsyncCapacity = acquireActiveAsyncCapacity({
|
|
3178
|
+
sessionId: data.parentSessionId,
|
|
3179
|
+
limit: activeLimit,
|
|
3180
|
+
runId: asyncRunId,
|
|
3181
|
+
kind: "runner",
|
|
3182
|
+
asyncDir: path.join(DIRS.async, asyncRunId),
|
|
3183
|
+
}, { liveWorkflowRunIds });
|
|
3184
|
+
deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(data.parentSessionId, activeLimit, { liveWorkflowRunIds });
|
|
3185
|
+
return { activeAsyncCapacity };
|
|
3186
|
+
} catch (error) {
|
|
3187
|
+
if (error instanceof ActiveAsyncCapacityError) {
|
|
3188
|
+
deps.state.activeAsyncCapacity = error.snapshot;
|
|
3189
|
+
return { errorResult: { content: [{ type: "text", text: error.message }], isError: true, details: { mode, results: [], activeAsyncCapacity: error.snapshot } } };
|
|
3190
|
+
}
|
|
3191
|
+
throw error;
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3194
|
+
|
|
3195
|
+
function finalizeClarifiedBackgroundCapacity(
|
|
3196
|
+
data: ExecutionContextData,
|
|
3197
|
+
deps: ExecutorDeps,
|
|
3198
|
+
activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined,
|
|
3199
|
+
): void {
|
|
3200
|
+
if (!activeAsyncCapacity || !data.parentSessionId) return;
|
|
3201
|
+
if (!activeAsyncCapacity.owner.runnerStartedAt) activeAsyncCapacity.rollback();
|
|
3202
|
+
deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(
|
|
3203
|
+
data.parentSessionId,
|
|
3204
|
+
resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession),
|
|
3205
|
+
{ liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) },
|
|
3206
|
+
);
|
|
3207
|
+
}
|
|
3208
|
+
|
|
2989
3209
|
function createParallelWorktreeSetup(
|
|
2990
3210
|
enabled: boolean | undefined,
|
|
2991
3211
|
cwd: string,
|
|
@@ -3174,7 +3394,33 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
3174
3394
|
beginForegroundChild(input.foregroundControl, {
|
|
3175
3395
|
index,
|
|
3176
3396
|
agent: task.agent,
|
|
3177
|
-
|
|
3397
|
+
authoredTask: input.taskDescriptions[index] ?? "",
|
|
3398
|
+
effectivePrompt: taskText,
|
|
3399
|
+
cwd: taskCwd,
|
|
3400
|
+
...(outputPath ? { outputPath } : {}),
|
|
3401
|
+
rerun: { params: compactOptional<Record<string, unknown>>({
|
|
3402
|
+
agent: task.agent,
|
|
3403
|
+
task: input.taskDescriptions[index] ?? "",
|
|
3404
|
+
async: false,
|
|
3405
|
+
cwd: input.worktreeSetup ? undefined : taskCwd,
|
|
3406
|
+
worktree: input.worktreeSetup ? true : false,
|
|
3407
|
+
context: input.contextPolicy.contextForAgent(task.agent),
|
|
3408
|
+
share: input.shareEnabled || undefined,
|
|
3409
|
+
maxOutput: input.maxOutput,
|
|
3410
|
+
timeoutMs: input.timeoutMs,
|
|
3411
|
+
turnBudget: input.turnBudget,
|
|
3412
|
+
...(model ? { model } : {}),
|
|
3413
|
+
...(effectiveSkills !== undefined ? { skill: effectiveSkills } : {}),
|
|
3414
|
+
...(outputPath ? { output: outputPath } : {}),
|
|
3415
|
+
...(behavior?.outputMode !== undefined ? { outputMode: behavior.outputMode } : {}),
|
|
3416
|
+
...(behavior?.reads !== undefined ? { reads: behavior.reads } : {}),
|
|
3417
|
+
...(behavior?.progress !== undefined ? { progress: behavior.progress } : {}),
|
|
3418
|
+
...(task.toolBudget !== undefined ? { toolBudget: task.toolBudget } : {}),
|
|
3419
|
+
...(task.outputSchema !== undefined ? { outputSchema: task.outputSchema } : {}),
|
|
3420
|
+
...(task.acceptance !== undefined ? { acceptance: task.acceptance } : {}),
|
|
3421
|
+
...(task.agentContract !== undefined || input.agentContract !== undefined ? { agentContract: task.agentContract ?? input.agentContract } : {}),
|
|
3422
|
+
}) },
|
|
3423
|
+
description: `${task.agent} child`,
|
|
3178
3424
|
...(model ? { model } : {}),
|
|
3179
3425
|
...(thinking ? { thinking } : {}),
|
|
3180
3426
|
interrupt: () => {
|
|
@@ -3191,8 +3437,10 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
3191
3437
|
const result = await runSync(input.ctx.cwd, input.agents, task.agent, taskText, compactOptional<Parameters<typeof runSync>[4]>({
|
|
3192
3438
|
permissions: input.permissions,
|
|
3193
3439
|
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
3440
|
+
llmIntentArbiter: createTaskMutationArbiter(input.ctx),
|
|
3194
3441
|
...workflowForegroundSteeringLaunchOptions(input.foregroundControl, index),
|
|
3195
3442
|
context: input.contextPolicy.contextForAgent(task.agent),
|
|
3443
|
+
runFanoutBudget: { ...input.runFanoutBudget, parentPath: `${input.runFanoutBudget.parentPath ? `${input.runFanoutBudget.parentPath}/` : ""}tasks[${index}]` },
|
|
3196
3444
|
cwd: taskCwd,
|
|
3197
3445
|
signal: input.signal,
|
|
3198
3446
|
interruptSignal: interruptController.signal,
|
|
@@ -3237,6 +3485,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
3237
3485
|
agentContract: task.agentContract ?? input.agentContract,
|
|
3238
3486
|
acceptance: task.acceptance,
|
|
3239
3487
|
acceptanceContext: { mode: "parallel" },
|
|
3488
|
+
onEffectivePrompt: input.foregroundControl ? (prompt) => updateLiveEffectivePrompt(input.foregroundControl!, index, prompt) : undefined,
|
|
3240
3489
|
timeoutMs: input.timeoutMs,
|
|
3241
3490
|
deadlineAt: input.deadlineAt,
|
|
3242
3491
|
turnBudget: input.turnBudget,
|
|
@@ -3420,7 +3669,9 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3420
3669
|
details: { mode: "parallel" as const, results: [] },
|
|
3421
3670
|
};
|
|
3422
3671
|
}
|
|
3423
|
-
const id =
|
|
3672
|
+
const id = data.asyncRunId;
|
|
3673
|
+
const capacity = acquireClarifiedBackgroundCapacity(data, deps, "parallel", id);
|
|
3674
|
+
if (capacity.errorResult) return capacity.errorResult;
|
|
3424
3675
|
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
3425
3676
|
pi: deps.pi,
|
|
3426
3677
|
cwd: ctx.cwd,
|
|
@@ -3451,40 +3702,46 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3451
3702
|
...(t.agentContract !== undefined ? { agentContract: t.agentContract } : {}),
|
|
3452
3703
|
});
|
|
3453
3704
|
});
|
|
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
|
-
|
|
3705
|
+
try {
|
|
3706
|
+
return executeAsyncChain(id, compactOptional<Parameters<typeof executeAsyncChain>[1]>({
|
|
3707
|
+
chain: [compactOptional<ParallelStep>({ parallel: parallelTasks, concurrency: parallelConcurrency, worktree: params.worktree })],
|
|
3708
|
+
resultMode: "parallel",
|
|
3709
|
+
goal: taskTexts[0] ?? "",
|
|
3710
|
+
agents,
|
|
3711
|
+
ctx: asyncCtx,
|
|
3712
|
+
availableModels,
|
|
3713
|
+
cwd: effectiveCwd,
|
|
3714
|
+
maxOutput: params.maxOutput,
|
|
3715
|
+
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
3716
|
+
artifactConfig,
|
|
3717
|
+
shareEnabled,
|
|
3718
|
+
sessionRoot,
|
|
3719
|
+
chainSkills: [],
|
|
3720
|
+
activeAsyncCapacity: capacity.activeAsyncCapacity,
|
|
3721
|
+
sessionFilesByFlatIndex: tasks.map((task, index) => sessionFileForTask(task.agent, index, modelOverrides[index])),
|
|
3722
|
+
thinkingOverridesByFlatIndex: tasks.map((task, index) => thinkingOverrideForTask(task.agent, index, modelOverrides[index])),
|
|
3723
|
+
contextForAgent: contextPolicy.contextForAgent,
|
|
3724
|
+
maxSubagentDepth: currentMaxSubagentDepth,
|
|
3725
|
+
waitToolEnabled: deps.waitToolEnabled,
|
|
3726
|
+
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
3727
|
+
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
3728
|
+
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
3729
|
+
controlConfig,
|
|
3730
|
+
agentContract: params.agentContract,
|
|
3731
|
+
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
3732
|
+
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
3733
|
+
nestedRoute: data.nestedRoute,
|
|
3734
|
+
timeoutMs: data.timeoutMs,
|
|
3735
|
+
turnBudget: data.turnBudget,
|
|
3736
|
+
usageBudget: data.usageBudget,
|
|
3737
|
+
toolBudget: data.toolBudget,
|
|
3738
|
+
configToolBudget: data.configToolBudget,
|
|
3739
|
+
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
3740
|
+
runFanoutBudget: data.runFanoutBudget,
|
|
3741
|
+
}));
|
|
3742
|
+
} finally {
|
|
3743
|
+
finalizeClarifiedBackgroundCapacity(data, deps, capacity.activeAsyncCapacity);
|
|
3744
|
+
}
|
|
3488
3745
|
}
|
|
3489
3746
|
}
|
|
3490
3747
|
|
|
@@ -3563,6 +3820,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3563
3820
|
intercomEvents: deps.pi.events,
|
|
3564
3821
|
parentSessionId: data.parentSessionId,
|
|
3565
3822
|
capabilityCeiling: data.capabilityCeiling,
|
|
3823
|
+
runFanoutBudget: data.runFanoutBudget,
|
|
3566
3824
|
signal,
|
|
3567
3825
|
runId,
|
|
3568
3826
|
sessionDirForIndex,
|
|
@@ -3797,7 +4055,9 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3797
4055
|
details: { mode: "single" as const, results: [] },
|
|
3798
4056
|
};
|
|
3799
4057
|
}
|
|
3800
|
-
const id =
|
|
4058
|
+
const id = data.asyncRunId;
|
|
4059
|
+
const capacity = acquireClarifiedBackgroundCapacity(data, deps, "single", id);
|
|
4060
|
+
if (capacity.errorResult) return capacity.errorResult;
|
|
3801
4061
|
const asyncCtx = compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
3802
4062
|
pi: deps.pi,
|
|
3803
4063
|
cwd: ctx.cwd,
|
|
@@ -3809,49 +4069,56 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3809
4069
|
interactive: ctx.hasUI,
|
|
3810
4070
|
permissions: deps.config.permissions,
|
|
3811
4071
|
});
|
|
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
|
-
|
|
4072
|
+
try {
|
|
4073
|
+
return executeAsyncSingle(id, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
|
|
4074
|
+
agent: params.agent!,
|
|
4075
|
+
task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(task) : task,
|
|
4076
|
+
goal: task,
|
|
4077
|
+
agentConfig,
|
|
4078
|
+
ctx: asyncCtx,
|
|
4079
|
+
availableModels,
|
|
4080
|
+
cwd: effectiveCwd,
|
|
4081
|
+
maxOutput: params.maxOutput,
|
|
4082
|
+
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
4083
|
+
artifactConfig,
|
|
4084
|
+
shareEnabled,
|
|
4085
|
+
activeAsyncCapacity: capacity.activeAsyncCapacity,
|
|
4086
|
+
sessionRoot,
|
|
4087
|
+
sessionFile: sessionFileForTask(params.agent!, 0, modelOverride),
|
|
4088
|
+
context: contextPolicy.contextForAgent(params.agent!),
|
|
4089
|
+
skills: skillOverride === false ? [] : skillOverride,
|
|
4090
|
+
output: effectiveOutput,
|
|
4091
|
+
outputMode: effectiveOutputMode,
|
|
4092
|
+
...(readsOverride !== undefined ? { reads: readsOverride } : {}),
|
|
4093
|
+
outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
|
|
4094
|
+
modelOverride,
|
|
4095
|
+
thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride),
|
|
4096
|
+
maxSubagentDepth,
|
|
4097
|
+
waitToolEnabled: deps.waitToolEnabled,
|
|
4098
|
+
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
4099
|
+
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
4100
|
+
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
4101
|
+
controlConfig,
|
|
4102
|
+
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
4103
|
+
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
4104
|
+
nestedRoute: data.nestedRoute,
|
|
4105
|
+
agentContract: params.agentContract,
|
|
4106
|
+
structuredOutputSchema: params.outputSchema,
|
|
4107
|
+
acceptance: params.acceptance,
|
|
4108
|
+
timeoutMs: data.timeoutMs,
|
|
4109
|
+
turnBudget: data.turnBudget,
|
|
4110
|
+
toolBudget: effectiveToolBudget.toolBudget,
|
|
4111
|
+
usageBudget: data.usageBudget,
|
|
4112
|
+
allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
|
|
4113
|
+
runFanoutBudget: data.runFanoutBudget,
|
|
4114
|
+
}));
|
|
4115
|
+
} finally {
|
|
4116
|
+
finalizeClarifiedBackgroundCapacity(data, deps, capacity.activeAsyncCapacity);
|
|
4117
|
+
}
|
|
3852
4118
|
}
|
|
3853
4119
|
}
|
|
3854
4120
|
|
|
4121
|
+
const authoredTask = task;
|
|
3855
4122
|
if (shouldForkAgent(contextPolicy, params.agent!)) {
|
|
3856
4123
|
task = wrapForkTask(task);
|
|
3857
4124
|
}
|
|
@@ -3888,6 +4155,11 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3888
4155
|
beginForegroundChild(foregroundControl, omitUndefinedProperties({
|
|
3889
4156
|
index: 0,
|
|
3890
4157
|
agent: params.agent!,
|
|
4158
|
+
authoredTask,
|
|
4159
|
+
effectivePrompt: task,
|
|
4160
|
+
cwd: effectiveCwd,
|
|
4161
|
+
outputPath,
|
|
4162
|
+
rerun: { params: { ...params, task: authoredTask, async: params.async ?? false } },
|
|
3891
4163
|
description: foregroundControl.description,
|
|
3892
4164
|
...(modelOverride ? { model: modelOverride } : {}),
|
|
3893
4165
|
...(thinking ? { thinking } : {}),
|
|
@@ -3913,8 +4185,10 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3913
4185
|
r = await runSync(ctx.cwd, agents, params.agent!, task, compactOptional<Parameters<typeof runSync>[4]>({
|
|
3914
4186
|
permissions: deps.config.permissions,
|
|
3915
4187
|
parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
|
|
4188
|
+
llmIntentArbiter: createTaskMutationArbiter(ctx),
|
|
3916
4189
|
...workflowForegroundSteeringLaunchOptions(foregroundControl, 0),
|
|
3917
4190
|
context: data.contextPolicy.contextForAgent(params.agent!),
|
|
4191
|
+
runFanoutBudget: params.runFanoutAdmitted ? data.runFanoutBudget : { ...data.runFanoutBudget, parentPath: `${data.runFanoutBudget.parentPath ? `${data.runFanoutBudget.parentPath}/` : ""}single` },
|
|
3918
4192
|
cwd: effectiveCwd,
|
|
3919
4193
|
signal,
|
|
3920
4194
|
interruptSignal: interruptController.signal,
|
|
@@ -3948,6 +4222,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3948
4222
|
agentContract: params.agentContract,
|
|
3949
4223
|
acceptance: params.acceptance,
|
|
3950
4224
|
acceptanceContext: { mode: "single" },
|
|
4225
|
+
onEffectivePrompt: foregroundControl ? (prompt) => updateLiveEffectivePrompt(foregroundControl, 0, prompt) : undefined,
|
|
3951
4226
|
onDetachReady: (detach) => {
|
|
3952
4227
|
detachForeground = detach;
|
|
3953
4228
|
},
|
|
@@ -3996,12 +4271,14 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3996
4271
|
if (r.artifactPaths) allArtifactPaths.push(r.artifactPaths);
|
|
3997
4272
|
|
|
3998
4273
|
const fullOutput = getSingleResultOutput(r);
|
|
4274
|
+
const preserveRejectedSavedOutput = r.acceptance?.explicit && r.acceptance.status === "rejected" && r.savedOutputPath !== undefined;
|
|
3999
4275
|
const finalizedOutput = finalizeSingleOutput(omitUndefinedProperties({
|
|
4000
4276
|
fullOutput,
|
|
4001
4277
|
truncatedOutput: r.truncation?.text,
|
|
4002
4278
|
outputPath,
|
|
4003
4279
|
outputMode: r.outputMode,
|
|
4004
4280
|
exitCode: r.exitCode,
|
|
4281
|
+
preserveSavedOutput: preserveRejectedSavedOutput,
|
|
4005
4282
|
savedPath: r.savedOutputPath,
|
|
4006
4283
|
outputReference: r.outputReference,
|
|
4007
4284
|
saveError: r.outputSaveError,
|
|
@@ -4092,7 +4369,7 @@ function duplicateSubagentCallResult(params: SubagentParamsLike): AgentToolResul
|
|
|
4092
4369
|
};
|
|
4093
4370
|
}
|
|
4094
4371
|
|
|
4095
|
-
const workflowLaunchObservers = new WeakMap<object, (launch: { agent: string; sessionFile?: string }) => void>();
|
|
4372
|
+
const workflowLaunchObservers = new WeakMap<object, (launch: { agent: string; sessionFile?: string; async: boolean; runId?: string }) => void>();
|
|
4096
4373
|
|
|
4097
4374
|
function recordMissionWorkflowChild(
|
|
4098
4375
|
binding: MissionLaunchBinding | undefined,
|
|
@@ -4101,8 +4378,9 @@ function recordMissionWorkflowChild(
|
|
|
4101
4378
|
update: Omit<MissionWorkflowChildUpdate, "workflowRunId" | "key">,
|
|
4102
4379
|
): void {
|
|
4103
4380
|
if (!binding) return;
|
|
4381
|
+
const { task: _task, ...durableUpdate } = update;
|
|
4104
4382
|
try {
|
|
4105
|
-
updateMission(binding.location, binding.missionId, { upsertWorkflowChildren: [{ workflowRunId, key, ...
|
|
4383
|
+
updateMission(binding.location, binding.missionId, { upsertWorkflowChildren: [{ workflowRunId, key, ...durableUpdate }] });
|
|
4106
4384
|
} catch (error) {
|
|
4107
4385
|
console.warn(`[pi-subagents] Failed to record mission workflow child '${key}': ${error instanceof Error ? error.message : String(error)}`);
|
|
4108
4386
|
}
|
|
@@ -4184,7 +4462,7 @@ export function prepareWorkflowLaunchParams(
|
|
|
4184
4462
|
childParams: Record<string, unknown>,
|
|
4185
4463
|
parentWorkflowRunId: string,
|
|
4186
4464
|
workflowKey: string,
|
|
4187
|
-
options: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean } = {},
|
|
4465
|
+
options: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean; runFanoutBudget?: RunFanoutBudgetDescriptor } = {},
|
|
4188
4466
|
): SubagentParamsLike {
|
|
4189
4467
|
if (typeof childParams.resume === "string") {
|
|
4190
4468
|
if (childParams.gate !== undefined || workflowDefaults.gate !== undefined) {
|
|
@@ -4212,6 +4490,7 @@ export function prepareWorkflowLaunchParams(
|
|
|
4212
4490
|
...(options.missionDetached ? { mission: false } : {}),
|
|
4213
4491
|
workflowParentRunId: parentWorkflowRunId,
|
|
4214
4492
|
workflowKey,
|
|
4493
|
+
...(options.runFanoutBudget ? { runFanoutBudget: { ...options.runFanoutBudget, parentPath: `${options.runFanoutBudget.parentPath ? `${options.runFanoutBudget.parentPath}/` : ""}workflow[${workflowKey}]` } } : {}),
|
|
4215
4494
|
...(options.suppressRoutineResultIntercom ? { suppressRoutineResultIntercom: true } : {}),
|
|
4216
4495
|
} as SubagentParamsLike;
|
|
4217
4496
|
const normalizedGate = normalizeGateParams(launchParams);
|
|
@@ -4359,6 +4638,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4359
4638
|
} {
|
|
4360
4639
|
const delegatedThinkingOverrides = new WeakMap<object, AgentConfig["thinking"]>();
|
|
4361
4640
|
const delegatedZeroToolBudgets = new WeakSet<object>();
|
|
4641
|
+
const delegatedExecutions = new WeakSet<object>();
|
|
4362
4642
|
const warnedArtifactPackageDirs = new Set<string>();
|
|
4363
4643
|
const scheduledOwnerExecutors = new Map<string, ReturnType<typeof createSubagentExecutor>>();
|
|
4364
4644
|
const execute = async (
|
|
@@ -4372,6 +4652,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4372
4652
|
const workflowLaunchObserver = workflowLaunchObservers.get(params);
|
|
4373
4653
|
const delegatedThinkingOverride = delegatedThinkingOverrides.get(params);
|
|
4374
4654
|
const allowZeroToolBudget = delegatedZeroToolBudgets.has(params);
|
|
4655
|
+
const delegatedExecution = delegatedExecutions.has(params);
|
|
4375
4656
|
if (!preserveActiveSession) deps.state.baseCwd = ctx.cwd;
|
|
4376
4657
|
deps.state.foregroundRuns ??= new Map();
|
|
4377
4658
|
deps.state.foregroundControls ??= new Map();
|
|
@@ -4382,7 +4663,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4382
4663
|
const normalizedAction = typeof requestParams.action === "string" ? requestParams.action.trim() : requestParams.action;
|
|
4383
4664
|
if (requestParams.workflowScript !== undefined && normalizedAction === undefined) {
|
|
4384
4665
|
const parentCwd = ctx.cwd;
|
|
4385
|
-
const timeout = requestParams.timeoutMs ?? requestParams.maxRuntimeMs ?? (requestParams.async === false ? DEFAULT_FOREGROUND_TIMEOUT_MS : undefined);
|
|
4666
|
+
const timeout = requestParams.timeoutMs ?? requestParams.maxRuntimeMs ?? (requestParams.async === false ? resolveConfigDefaultTimeoutMs(deps.config.timeoutMs) ?? DEFAULT_FOREGROUND_TIMEOUT_MS : undefined);
|
|
4386
4667
|
const workflowUsageBudget = validateUsageBudgetConfig(requestParams.usageBudget ?? deps.config.usageBudget, requestParams.usageBudget ? "usageBudget" : "config.usageBudget");
|
|
4387
4668
|
if (workflowUsageBudget.error) return buildRequestedModeError(requestParams, workflowUsageBudget.error);
|
|
4388
4669
|
const workflowCwd = resolveRequestedCwd(parentCwd, requestParams.cwd);
|
|
@@ -4393,11 +4674,45 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4393
4674
|
const explicitMission = requestParams.missionId !== undefined || requestParams.mission !== undefined;
|
|
4394
4675
|
const autoMission = !explicitMission;
|
|
4395
4676
|
const workflowPreview = autoMission ? previewSimpleWorkflowRun(requestParams.workflowScript) : undefined;
|
|
4396
|
-
const previewTask = workflowPreview?.task?.trim() || undefined;
|
|
4397
4677
|
const previewAgent = workflowPreview?.agent?.trim() || undefined;
|
|
4398
4678
|
const scriptFirstLine = requestParams.workflowScript.split(/\r?\n/).map((line) => line.trim()).find(Boolean) || "Workflow";
|
|
4399
4679
|
const boundedScriptPreview = scriptFirstLine.length > 100 ? `${scriptFirstLine.slice(0, 97)}...` : scriptFirstLine;
|
|
4400
|
-
const derivedObjective =
|
|
4680
|
+
const derivedObjective = previewAgent ? `Workflow: ${previewAgent}` : boundedScriptPreview;
|
|
4681
|
+
const workflowDepth = checkSubagentDepth(deps.config.maxSubagentDepth).depth;
|
|
4682
|
+
const asyncWorkflow = requestParams.async !== false;
|
|
4683
|
+
const topLevelAsyncWorkflow = asyncWorkflow
|
|
4684
|
+
&& workflowDepth === 0
|
|
4685
|
+
&& !resolveInheritedNestedRouteFromEnv()
|
|
4686
|
+
&& !requestParams.workflowParentRunId;
|
|
4687
|
+
const workflowRunId = asyncWorkflow ? randomUUID() : undefined;
|
|
4688
|
+
let workflowCapacity: ActiveAsyncCapacityHandle | undefined;
|
|
4689
|
+
if (workflowRunId && topLevelAsyncWorkflow) {
|
|
4690
|
+
const currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
4691
|
+
try {
|
|
4692
|
+
workflowCapacity = acquireActiveAsyncCapacity({
|
|
4693
|
+
sessionId: currentSessionId,
|
|
4694
|
+
limit: resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession),
|
|
4695
|
+
runId: workflowRunId,
|
|
4696
|
+
kind: "workflow",
|
|
4697
|
+
asyncDir: path.join(DIRS.async, workflowRunId),
|
|
4698
|
+
}, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) });
|
|
4699
|
+
} catch (error) {
|
|
4700
|
+
if (error instanceof ActiveAsyncCapacityError) {
|
|
4701
|
+
deps.state.activeAsyncCapacity = error.snapshot;
|
|
4702
|
+
return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: "workflow", results: [], activeAsyncCapacity: error.snapshot } };
|
|
4703
|
+
}
|
|
4704
|
+
throw error;
|
|
4705
|
+
}
|
|
4706
|
+
}
|
|
4707
|
+
let workflowFanoutBudget: RunFanoutBudgetDescriptor;
|
|
4708
|
+
try {
|
|
4709
|
+
workflowFanoutBudget = requestParams.runFanoutBudget
|
|
4710
|
+
?? decodeRunFanoutBudgetDescriptor(process.env[RUN_FANOUT_BUDGET_ENV])
|
|
4711
|
+
?? createRunFanoutBudget(_id, resolveMaxSubagentSpawnsPerRun(deps.config.maxSubagentSpawnsPerRun));
|
|
4712
|
+
} catch (error) {
|
|
4713
|
+
workflowCapacity?.rollback();
|
|
4714
|
+
return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "workflow", results: [] } };
|
|
4715
|
+
}
|
|
4401
4716
|
let missionBinding: MissionLaunchBinding | undefined;
|
|
4402
4717
|
let missionWarning: string | undefined;
|
|
4403
4718
|
try {
|
|
@@ -4408,22 +4723,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4408
4723
|
ownerSessionId: resolveCurrentSessionId(ctx.sessionManager),
|
|
4409
4724
|
});
|
|
4410
4725
|
} catch (error) {
|
|
4411
|
-
if (explicitMission)
|
|
4726
|
+
if (explicitMission) {
|
|
4727
|
+
workflowCapacity?.rollback();
|
|
4728
|
+
return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "workflow", results: [] } };
|
|
4729
|
+
}
|
|
4412
4730
|
missionWarning = `Mission tracking unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
4413
4731
|
}
|
|
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
4732
|
const detachWorkflowChildMissions = autoMission || missionBinding !== undefined || requestParams.mission === false;
|
|
4428
4733
|
const workflowState = missionBinding ? createMissionWorkflowState(missionBinding.location, missionBinding.missionId) : undefined;
|
|
4429
4734
|
const attachWorkflowMission = (result: AgentToolResult<Details>): AgentToolResult<Details> => {
|
|
@@ -4437,20 +4742,27 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4437
4742
|
: { ...result, details: { ...result.details, missionWarning: warning } };
|
|
4438
4743
|
}
|
|
4439
4744
|
};
|
|
4440
|
-
if (
|
|
4745
|
+
if (workflowRunId) {
|
|
4441
4746
|
const toolCallId = _id;
|
|
4442
|
-
const workflowRunId = randomUUID();
|
|
4443
4747
|
const asyncDir = path.join(DIRS.async, workflowRunId);
|
|
4444
4748
|
const resultPath = path.join(DIRS.results, `${workflowRunId}.json`);
|
|
4445
4749
|
const statusPath = path.join(asyncDir, "status.json");
|
|
4446
4750
|
const eventsPath = path.join(asyncDir, "events.jsonl");
|
|
4447
4751
|
const startedAt = Date.now();
|
|
4448
4752
|
const currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
4449
|
-
|
|
4450
|
-
|
|
4753
|
+
try {
|
|
4754
|
+
fs.mkdirSync(asyncDir, { recursive: true });
|
|
4755
|
+
writeRunFanoutBudgetDescriptor(asyncDir, workflowFanoutBudget);
|
|
4756
|
+
fs.mkdirSync(DIRS.results, { recursive: true });
|
|
4757
|
+
} catch (error) {
|
|
4758
|
+
workflowCapacity?.rollback();
|
|
4759
|
+
return { content: [{ type: "text", text: `Failed to create async workflow storage: ${error instanceof Error ? error.message : String(error)}` }], isError: true, details: { mode: "workflow", results: [] } };
|
|
4760
|
+
}
|
|
4451
4761
|
const controller = new AbortController();
|
|
4452
4762
|
deps.state.workflowControllers ??= new Map();
|
|
4453
4763
|
deps.state.workflowControllers.set(workflowRunId, controller);
|
|
4764
|
+
workflowCapacity?.markWorkflowStarted();
|
|
4765
|
+
if (workflowCapacity) deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers.keys()) });
|
|
4454
4766
|
let status: AsyncStatus = {
|
|
4455
4767
|
runId: workflowRunId,
|
|
4456
4768
|
toolCallId,
|
|
@@ -4464,6 +4776,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4464
4776
|
pid: process.pid,
|
|
4465
4777
|
steps: [],
|
|
4466
4778
|
workflow: { trace: [], emits: [], console: [] },
|
|
4779
|
+
runFanoutBudget: getRunFanoutBudgetSnapshot(workflowFanoutBudget),
|
|
4467
4780
|
};
|
|
4468
4781
|
const appendWorkflowEvent = (event: Record<string, unknown>) => fs.appendFileSync(eventsPath, `${JSON.stringify({ ts: Date.now(), runId: workflowRunId, ...event })}\n`, "utf-8");
|
|
4469
4782
|
let indexedState: AsyncStatus["state"] | undefined;
|
|
@@ -4550,12 +4863,23 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4550
4863
|
if (existing) {
|
|
4551
4864
|
existing.status = mapped;
|
|
4552
4865
|
if (entry.agent) existing.agent = entry.agent;
|
|
4866
|
+
if (entry.runId) existing.runId = entry.runId;
|
|
4867
|
+
if (entry.state === "failed" && !entry.runId && existing.async === undefined) existing.async = false;
|
|
4553
4868
|
if (entry.error === undefined) delete existing.error;
|
|
4554
4869
|
else existing.error = entry.error;
|
|
4555
4870
|
if (entry.durationMs === undefined) delete existing.durationMs;
|
|
4556
4871
|
else existing.durationMs = entry.durationMs;
|
|
4557
4872
|
} else {
|
|
4558
|
-
const step: NonNullable<AsyncStatus["steps"]>[number] = {
|
|
4873
|
+
const step: NonNullable<AsyncStatus["steps"]>[number] = {
|
|
4874
|
+
agent: entry.agent ?? entry.key,
|
|
4875
|
+
label: entry.key,
|
|
4876
|
+
workflowKey: entry.key,
|
|
4877
|
+
parentWorkflowRunId: workflowRunId,
|
|
4878
|
+
status: mapped,
|
|
4879
|
+
startedAt: Date.now(),
|
|
4880
|
+
...(entry.runId ? { runId: entry.runId } : {}),
|
|
4881
|
+
...(entry.state === "failed" && !entry.runId ? { async: false } : {}),
|
|
4882
|
+
};
|
|
4559
4883
|
status.steps?.push(step);
|
|
4560
4884
|
workflowSteps.set(entry.key, step);
|
|
4561
4885
|
}
|
|
@@ -4567,37 +4891,48 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4567
4891
|
appendWorkflowEvent({ type: "subagent.workflow.trace", trace });
|
|
4568
4892
|
};
|
|
4569
4893
|
try {
|
|
4570
|
-
const
|
|
4571
|
-
|
|
4894
|
+
const autoRelaunchCap = resolveMaxWorkflowAutoRelaunches(deps.config.maxWorkflowAutoRelaunches);
|
|
4895
|
+
let relaunched = 0;
|
|
4896
|
+
let capReached = false;
|
|
4897
|
+
let workflow: Awaited<ReturnType<typeof runWorkflowScript>> | undefined;
|
|
4898
|
+
while (true) {
|
|
4899
|
+
if (relaunched > 0) {
|
|
4900
|
+
workflowFanoutBudget = createRunFanoutBudget(`${workflowRunId}#${relaunched + 1}`, workflowFanoutBudget.limit);
|
|
4901
|
+
status = { ...status, workflow: { trace: [], emits: [], console: [] } };
|
|
4902
|
+
}
|
|
4903
|
+
workflow = await runWorkflowScript({
|
|
4904
|
+
script: workflowScript,
|
|
4572
4905
|
timeoutMs: timeout,
|
|
4573
4906
|
signal: controller.signal,
|
|
4574
4907
|
prompts: workflowPrompts,
|
|
4575
4908
|
...(workflowState ? { state: workflowState } : {}),
|
|
4576
4909
|
onTrace: updateTrace,
|
|
4910
|
+
admit: (calls) => {
|
|
4911
|
+
status.runFanoutBudget = claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
|
|
4912
|
+
persist();
|
|
4913
|
+
},
|
|
4577
4914
|
onEmit: (emits) => {
|
|
4578
4915
|
// Each emit is validated at the host boundary in runWorkflowScript before onEmit fires.
|
|
4579
4916
|
status.workflow = { ...(status.workflow ?? { trace: [], console: [] }), emits };
|
|
4580
4917
|
persist();
|
|
4581
4918
|
appendWorkflowEvent({ type: "subagent.workflow.emit", value: emits.at(-1) });
|
|
4582
4919
|
},
|
|
4583
|
-
launch: async (key, childParams, workflowSignal) => {
|
|
4920
|
+
launch: async (key, childParams, workflowSignal, admission) => {
|
|
4584
4921
|
if (workflowUsageBudget.budget && childParams.async === true) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, "workflow usageBudget does not support async runs.run launches."));
|
|
4585
4922
|
const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
|
|
4586
4923
|
if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)));
|
|
4587
|
-
patchMissionObjective(childParams.task);
|
|
4588
4924
|
const childPhase = typeof childParams.phase === "string" && childParams.phase.trim() ? childParams.phase.trim() : undefined;
|
|
4589
4925
|
const childLabel = typeof childParams.label === "string" && childParams.label.trim() ? childParams.label.trim() : undefined;
|
|
4590
4926
|
recordMissionWorkflowChild(missionBinding, workflowRunId, key, {
|
|
4591
4927
|
status: "running",
|
|
4592
4928
|
...(typeof childParams.agent === "string" && childParams.agent.trim() ? { agent: childParams.agent.trim() } : {}),
|
|
4593
|
-
...(typeof childParams.task === "string" && childParams.task.trim() ? { task: childParams.task.trim() } : {}),
|
|
4594
4929
|
...(childLabel ? { label: childLabel } : {}),
|
|
4595
4930
|
...(childPhase ? { phase: childPhase } : {}),
|
|
4596
4931
|
heartbeat: { status: "running", ...(childPhase ? { phase: childPhase } : {}) },
|
|
4597
4932
|
});
|
|
4598
4933
|
const result = await runMissionWorkflowChild(missionBinding, workflowRunId, key, childPhase, () => {
|
|
4599
4934
|
const childRequest = bindMissionWorkflowChildAsyncLaunch(
|
|
4600
|
-
prepareWorkflowLaunchParams(workflowChildDefaults, childParams, workflowRunId, key, { missionDetached: detachWorkflowChildMissions }),
|
|
4935
|
+
{ ...prepareWorkflowLaunchParams(workflowChildDefaults, childParams, workflowRunId, key, { missionDetached: detachWorkflowChildMissions, runFanoutBudget: workflowFanoutBudget }), runFanoutAdmitted: admission.admitted },
|
|
4601
4936
|
missionBinding,
|
|
4602
4937
|
deps.asyncByDefault,
|
|
4603
4938
|
);
|
|
@@ -4606,6 +4941,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4606
4941
|
if (step) {
|
|
4607
4942
|
step.agent = launch.agent;
|
|
4608
4943
|
step.sessionFile = launch.sessionFile;
|
|
4944
|
+
step.async = launch.async;
|
|
4945
|
+
if (launch.runId) step.runId = launch.runId;
|
|
4609
4946
|
persist();
|
|
4610
4947
|
}
|
|
4611
4948
|
recordMissionWorkflowChild(missionBinding, workflowRunId, key, { status: "running", agent: launch.agent, ...(launch.sessionFile ? { sessionPath: launch.sessionFile } : {}) });
|
|
@@ -4637,8 +4974,13 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4637
4974
|
}, ctx, preserveActiveSession);
|
|
4638
4975
|
});
|
|
4639
4976
|
workflowResults.push(...result.details.results);
|
|
4640
|
-
if (result.details.asyncDir && missionBinding) writeMissionAsyncBinding(result.details.asyncDir, missionBinding);
|
|
4641
4977
|
const child = workflowChildResult(key, result);
|
|
4978
|
+
const step = status.steps?.find((candidate) => candidate.workflowKey === key);
|
|
4979
|
+
if (step) {
|
|
4980
|
+
step.async = Boolean(result.details.asyncId || result.details.asyncDir);
|
|
4981
|
+
if (child.runId) step.runId = child.runId;
|
|
4982
|
+
}
|
|
4983
|
+
if (result.details.asyncDir && missionBinding) writeMissionAsyncBinding(result.details.asyncDir, missionBinding);
|
|
4642
4984
|
const childStatus = missionWorkflowChildStatus(result);
|
|
4643
4985
|
recordMissionWorkflowChild(missionBinding, workflowRunId, key, {
|
|
4644
4986
|
status: childStatus,
|
|
@@ -4657,14 +4999,33 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4657
4999
|
},
|
|
4658
5000
|
status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession)),
|
|
4659
5001
|
});
|
|
5002
|
+
const decision = resolveAutoRelaunchDecision(workflow?.value, relaunched, autoRelaunchCap);
|
|
5003
|
+
if (!decision.relaunch) {
|
|
5004
|
+
capReached = decision.capped;
|
|
5005
|
+
break;
|
|
5006
|
+
}
|
|
5007
|
+
relaunched += 1;
|
|
5008
|
+
status = { ...status, workflowRelaunchCount: relaunched };
|
|
5009
|
+
persist();
|
|
5010
|
+
appendWorkflowEvent({ type: "subagent.workflow.relaunched", relaunched });
|
|
5011
|
+
}
|
|
5012
|
+
if (!workflow) throw new Error("Workflow script produced no result.");
|
|
4660
5013
|
const returnPreview = formatWorkflowValue(workflow.value).slice(0, 1_000);
|
|
4661
5014
|
const emitPreview = workflow.emits.length > 0 ? ` Emitted: ${workflow.emits.map(formatWorkflowValue).join(", ").slice(0, 1_000)}` : "";
|
|
4662
|
-
const
|
|
5015
|
+
const relaunchNote = relaunched > 0 ? `Auto-relaunched ${relaunched} time(s) with a fresh fan-out budget. ` : "";
|
|
5016
|
+
const summary = `${relaunchNote}Workflow completed with ${workflow.children.length} child run(s). Return: ${returnPreview}${emitPreview} Trace: ${workflow.trace.length} event(s).`;
|
|
4663
5017
|
const workflowUsage = sumResultsUsage(workflowResults);
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
5018
|
+
if (capReached) {
|
|
5019
|
+
status = compactOptional<AsyncStatus>({ ...status, state: "failed", error: `Workflow auto-relaunch cap reached after ${relaunched} relaunch(es); the goal is not clean yet. The progress file is current; raise config.maxWorkflowAutoRelaunches or relaunch manually.`, endedAt: Date.now(), workflow: { value: workflow.value, trace: workflow.trace, emits: workflow.emits, console: workflow.console } });
|
|
5020
|
+
writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: false, state: "failed", summary: status.error, error: status.error, 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 });
|
|
5021
|
+
persist();
|
|
5022
|
+
appendWorkflowEvent({ type: "subagent.workflow.completed", state: "failed", error: status.error });
|
|
5023
|
+
} else {
|
|
5024
|
+
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) };
|
|
5025
|
+
writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary, output: summary, 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 });
|
|
5026
|
+
persist();
|
|
5027
|
+
appendWorkflowEvent({ type: "subagent.workflow.completed", state: "complete" });
|
|
5028
|
+
}
|
|
4668
5029
|
} catch (error) {
|
|
4669
5030
|
const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
|
|
4670
5031
|
const stopped = controller.signal.aborted;
|
|
@@ -4674,12 +5035,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4674
5035
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, error: status.error });
|
|
4675
5036
|
} finally {
|
|
4676
5037
|
deps.state.workflowControllers?.delete(workflowRunId);
|
|
5038
|
+
deps.state.activeAsyncCapacity = workflowCapacity?.reconcile(new Set(deps.state.workflowControllers?.keys() ?? []))
|
|
5039
|
+
?? deps.state.activeAsyncCapacity;
|
|
4677
5040
|
}
|
|
4678
5041
|
});
|
|
4679
|
-
return attachWorkflowMission({
|
|
5042
|
+
return attachWorkflowMission(withRunFanoutBudget({
|
|
4680
5043
|
content: [{ type: "text", text: formatAsyncStartedMessage(`Async workflow [${workflowRunId}]`, ctx.hasUI === true) }],
|
|
4681
|
-
details: { mode: "workflow", runId: workflowRunId, toolCallId, asyncId: workflowRunId, asyncDir, results: [], chatProgress },
|
|
4682
|
-
});
|
|
5044
|
+
details: { mode: "workflow", runId: workflowRunId, toolCallId, asyncId: workflowRunId, asyncDir, results: [], chatProgress, ...(deps.state.activeAsyncCapacity ? { activeAsyncCapacity: deps.state.activeAsyncCapacity } : {}) },
|
|
5045
|
+
}, workflowFanoutBudget));
|
|
4683
5046
|
}
|
|
4684
5047
|
const { workflowScript: _workflowScript, action: _action, agent: _agent, task: _task, resume: _resume, tasks: _tasks, chain: _chain, concurrency: _concurrency, async: _async, foregroundOnly: _foregroundOnly, clarify: _clarify, timeoutMs: _timeoutMs, maxRuntimeMs: _maxRuntimeMs, usageBudget: _usageBudget, chatProgress: _chatProgress, missionId: _missionId, mission: _mission, ...workflowChildDefaults } = requestParams;
|
|
4685
5048
|
const workflowResults: SingleResult[] = [];
|
|
@@ -4699,28 +5062,29 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4699
5062
|
liveWorkflow = { ...liveWorkflow, trace };
|
|
4700
5063
|
sendWorkflowProgress();
|
|
4701
5064
|
},
|
|
5065
|
+
admit: (calls) => {
|
|
5066
|
+
claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
|
|
5067
|
+
},
|
|
4702
5068
|
onEmit: (emits) => {
|
|
4703
5069
|
liveWorkflow = { ...liveWorkflow, emits };
|
|
4704
5070
|
sendWorkflowProgress();
|
|
4705
5071
|
},
|
|
4706
|
-
launch: async (key, childParams, workflowSignal) => {
|
|
5072
|
+
launch: async (key, childParams, workflowSignal, admission) => {
|
|
4707
5073
|
if (workflowUsageBudget.budget && childParams.async === true) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, "workflow usageBudget does not support async runs.run launches."));
|
|
4708
5074
|
const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
|
|
4709
5075
|
if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)));
|
|
4710
|
-
patchMissionObjective(childParams.task);
|
|
4711
5076
|
const childPhase = typeof childParams.phase === "string" && childParams.phase.trim() ? childParams.phase.trim() : undefined;
|
|
4712
5077
|
const childLabel = typeof childParams.label === "string" && childParams.label.trim() ? childParams.label.trim() : undefined;
|
|
4713
5078
|
recordMissionWorkflowChild(missionBinding, _id, key, {
|
|
4714
5079
|
status: "running",
|
|
4715
5080
|
...(typeof childParams.agent === "string" && childParams.agent.trim() ? { agent: childParams.agent.trim() } : {}),
|
|
4716
|
-
...(typeof childParams.task === "string" && childParams.task.trim() ? { task: childParams.task.trim() } : {}),
|
|
4717
5081
|
...(childLabel ? { label: childLabel } : {}),
|
|
4718
5082
|
...(childPhase ? { phase: childPhase } : {}),
|
|
4719
5083
|
heartbeat: { status: "running", ...(childPhase ? { phase: childPhase } : {}) },
|
|
4720
5084
|
});
|
|
4721
5085
|
const result = await runMissionWorkflowChild(missionBinding, _id, key, childPhase, () => {
|
|
4722
5086
|
const childRequest = bindMissionWorkflowChildAsyncLaunch(
|
|
4723
|
-
prepareWorkflowLaunchParams(workflowChildDefaults, childParams, _id, key, { missionDetached: detachWorkflowChildMissions, suppressRoutineResultIntercom: chatProgress.mode === "live-card" }),
|
|
5087
|
+
{ ...prepareWorkflowLaunchParams(workflowChildDefaults, childParams, _id, key, { missionDetached: detachWorkflowChildMissions, suppressRoutineResultIntercom: chatProgress.mode === "live-card", runFanoutBudget: workflowFanoutBudget }), runFanoutAdmitted: admission.admitted },
|
|
4724
5088
|
missionBinding,
|
|
4725
5089
|
deps.asyncByDefault,
|
|
4726
5090
|
);
|
|
@@ -4761,10 +5125,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4761
5125
|
if (workflow.emits.length > 0) sections.push(`Emitted:\n${workflow.emits.map(formatWorkflowValue).join("\n")}`);
|
|
4762
5126
|
if (workflow.console.length > 0) sections.push(`Console:\n${workflow.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
|
|
4763
5127
|
if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
|
|
4764
|
-
return attachWorkflowMission({
|
|
5128
|
+
return attachWorkflowMission(withRunFanoutBudget({
|
|
4765
5129
|
content: [{ type: "text", text: sections.join("\n\n") }],
|
|
4766
5130
|
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
|
-
});
|
|
5131
|
+
}, workflowFanoutBudget));
|
|
4768
5132
|
} catch (error) {
|
|
4769
5133
|
const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
|
|
4770
5134
|
const text = error instanceof Error ? error.message : String(error);
|
|
@@ -4773,11 +5137,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4773
5137
|
if (partial.emits.length > 0) sections.push(`Emitted:\n${partial.emits.map(formatWorkflowValue).join("\n")}`);
|
|
4774
5138
|
if (partial.console.length > 0) sections.push(`Console:\n${partial.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
|
|
4775
5139
|
if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
|
|
4776
|
-
return attachWorkflowMission({
|
|
5140
|
+
return attachWorkflowMission(withRunFanoutBudget({
|
|
4777
5141
|
content: [{ type: "text", text: sections.join("\n\n") }],
|
|
4778
5142
|
isError: true,
|
|
4779
5143
|
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
|
-
});
|
|
5144
|
+
}, workflowFanoutBudget));
|
|
4781
5145
|
}
|
|
4782
5146
|
}
|
|
4783
5147
|
const directParams = prepareWorkflowChildParams(requestParams);
|
|
@@ -4992,7 +5356,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4992
5356
|
}
|
|
4993
5357
|
if (action === "children.list") {
|
|
4994
5358
|
deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
4995
|
-
const children = listRetainedChildren(DIRS.async, deps.state.currentSessionId);
|
|
5359
|
+
const children = listRetainedChildren(DIRS.async, deps.state.sessionLineage?.length ? deps.state.sessionLineage : [deps.state.currentSessionId]);
|
|
4996
5360
|
return {
|
|
4997
5361
|
content: [{ type: "text", text: formatRetainedChildren(children) }],
|
|
4998
5362
|
details: { mode: "management", results: [] },
|
|
@@ -5015,6 +5379,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5015
5379
|
if (!sessionError) sessionError = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
5016
5380
|
}
|
|
5017
5381
|
const spawnBudget = getSpawnBudgetSnapshot(deps.state, deps.config, currentSessionId);
|
|
5382
|
+
const activeAsyncCapacity = currentSessionId
|
|
5383
|
+
? getActiveAsyncCapacitySnapshot(currentSessionId, resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession), { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) })
|
|
5384
|
+
: { used: 0, limit: resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession) ?? 0 };
|
|
5385
|
+
deps.state.activeAsyncCapacity = activeAsyncCapacity;
|
|
5018
5386
|
return {
|
|
5019
5387
|
content: [{
|
|
5020
5388
|
type: "text",
|
|
@@ -5031,7 +5399,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5031
5399
|
expandTilde: deps.expandTilde,
|
|
5032
5400
|
})),
|
|
5033
5401
|
}],
|
|
5034
|
-
details: { mode: "management", results: [], spawnBudget },
|
|
5402
|
+
details: { mode: "management", results: [], spawnBudget, activeAsyncCapacity },
|
|
5035
5403
|
};
|
|
5036
5404
|
}
|
|
5037
5405
|
if (action === "status") {
|
|
@@ -5448,7 +5816,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5448
5816
|
const runId = randomUUID().slice(0, 8);
|
|
5449
5817
|
const inheritedNestedRoute = resolveInheritedNestedRouteFromEnv();
|
|
5450
5818
|
const nestedParentAddress = inheritedNestedRoute ? resolveNestedParentAddressFromEnv() : undefined;
|
|
5451
|
-
const nestedRoute = inheritedNestedRoute ?? createNestedRoute(runId);
|
|
5452
5819
|
const shareEnabled = effectiveParams.share === true;
|
|
5453
5820
|
const hasChain = (effectiveParams.chain?.length ?? 0) > 0;
|
|
5454
5821
|
const hasTasks = (effectiveParams.tasks?.length ?? 0) > 0;
|
|
@@ -5532,9 +5899,50 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5532
5899
|
const foregroundTimeout = resolveSingleAgentLaunchTimeout(
|
|
5533
5900
|
effectiveParams,
|
|
5534
5901
|
effectiveAsync,
|
|
5902
|
+
resolveConfigDefaultTimeoutMs(deps.config.timeoutMs),
|
|
5535
5903
|
);
|
|
5536
5904
|
if (foregroundTimeout.error) return buildRequestedModeError(effectiveParams, foregroundTimeout.error);
|
|
5537
5905
|
const controlConfig = resolveControlConfig(deps.config.control, effectiveParams.control);
|
|
5906
|
+
const requestedWorkflowChildAsyncId = typeof effectiveParams.workflowChildAsyncId === "string" ? effectiveParams.workflowChildAsyncId.trim() : "";
|
|
5907
|
+
const asyncRunId = requestedWorkflowChildAsyncId && path.basename(requestedWorkflowChildAsyncId) === requestedWorkflowChildAsyncId
|
|
5908
|
+
? requestedWorkflowChildAsyncId
|
|
5909
|
+
: randomUUID();
|
|
5910
|
+
const topLevelAsyncCapacityEligible = depth === 0 && !inheritedNestedRoute && !effectiveParams.workflowParentRunId;
|
|
5911
|
+
const topLevelAsync = effectiveAsync && topLevelAsyncCapacityEligible;
|
|
5912
|
+
let activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined;
|
|
5913
|
+
if (topLevelAsync) {
|
|
5914
|
+
try {
|
|
5915
|
+
const activeLimit = resolveMaxActiveAsyncRunsPerSession(deps.config.maxActiveAsyncRunsPerSession);
|
|
5916
|
+
activeAsyncCapacity = acquireActiveAsyncCapacity({
|
|
5917
|
+
sessionId: requestSessionId,
|
|
5918
|
+
limit: activeLimit,
|
|
5919
|
+
runId: asyncRunId,
|
|
5920
|
+
kind: "runner",
|
|
5921
|
+
asyncDir: path.join(DIRS.async, asyncRunId),
|
|
5922
|
+
}, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) });
|
|
5923
|
+
deps.state.activeAsyncCapacity = getActiveAsyncCapacitySnapshot(requestSessionId, activeLimit, { liveWorkflowRunIds: new Set(deps.state.workflowControllers?.keys() ?? []) });
|
|
5924
|
+
} catch (error) {
|
|
5925
|
+
if (error instanceof ActiveAsyncCapacityError) {
|
|
5926
|
+
deps.state.activeAsyncCapacity = error.snapshot;
|
|
5927
|
+
return { content: [{ type: "text", text: error.message }], isError: true, details: { mode: foregroundMode, results: [], activeAsyncCapacity: error.snapshot } };
|
|
5928
|
+
}
|
|
5929
|
+
throw error;
|
|
5930
|
+
}
|
|
5931
|
+
}
|
|
5932
|
+
|
|
5933
|
+
let runFanoutBudget: RunFanoutBudgetDescriptor;
|
|
5934
|
+
try {
|
|
5935
|
+
const inheritedRunFanoutBudget = effectiveParams.runFanoutBudget ? undefined : decodeRunFanoutBudgetDescriptor(process.env[RUN_FANOUT_BUDGET_ENV]);
|
|
5936
|
+
runFanoutBudget = effectiveParams.runFanoutBudget
|
|
5937
|
+
?? (inheritedRunFanoutBudget ? { ...inheritedRunFanoutBudget, parentPath: `${inheritedRunFanoutBudget.parentPath ? `${inheritedRunFanoutBudget.parentPath}/` : ""}${runId}` } : undefined)
|
|
5938
|
+
?? createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(deps.config.maxSubagentSpawnsPerRun));
|
|
5939
|
+
if (!effectiveParams.runFanoutAdmitted) claimRunFanoutBatch(runFanoutBudget, staticRunFanoutPaths(effectiveParams));
|
|
5940
|
+
} catch (error) {
|
|
5941
|
+
activeAsyncCapacity?.rollback();
|
|
5942
|
+
if (error instanceof RunFanoutLimitError) return runFanoutErrorResult(error, foregroundMode);
|
|
5943
|
+
return buildRequestedModeError(effectiveParams, error instanceof Error ? error.message : String(error));
|
|
5944
|
+
}
|
|
5945
|
+
const nestedRoute = inheritedNestedRoute ?? createNestedRoute(runId);
|
|
5538
5946
|
|
|
5539
5947
|
const artifactConfig: ArtifactConfig = omitUndefinedProperties({
|
|
5540
5948
|
...DEFAULT_ARTIFACT_CONFIG,
|
|
@@ -5560,6 +5968,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5560
5968
|
try {
|
|
5561
5969
|
fs.mkdirSync(sessionRoot, { recursive: true });
|
|
5562
5970
|
} catch (error) {
|
|
5971
|
+
activeAsyncCapacity?.rollback();
|
|
5563
5972
|
const message = error instanceof Error ? error.message : String(error);
|
|
5564
5973
|
return toExecutionErrorResult(
|
|
5565
5974
|
effectiveParams,
|
|
@@ -5590,10 +5999,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5590
5999
|
preflightForkSessionsForStaticTasks(effectiveParams, contextPolicy, forkSessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems);
|
|
5591
6000
|
}
|
|
5592
6001
|
} catch (error) {
|
|
6002
|
+
activeAsyncCapacity?.rollback();
|
|
5593
6003
|
return toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary);
|
|
5594
6004
|
}
|
|
5595
6005
|
const chainBindingsError = validateExecutionChainBindings(effectiveParams, deps.config.chain?.dynamicFanout?.maxItems);
|
|
5596
|
-
if (chainBindingsError)
|
|
6006
|
+
if (chainBindingsError) {
|
|
6007
|
+
activeAsyncCapacity?.rollback();
|
|
6008
|
+
return withResolvedContext(chainBindingsError, contextPolicy.contextSummary);
|
|
6009
|
+
}
|
|
5597
6010
|
|
|
5598
6011
|
const onUpdateWithContext = onUpdate
|
|
5599
6012
|
? (r: AgentToolResult<Details>) => onUpdate(withResolvedContext({
|
|
@@ -5613,14 +6026,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5613
6026
|
ownerSessionId: requestSessionId,
|
|
5614
6027
|
});
|
|
5615
6028
|
} catch (error) {
|
|
5616
|
-
if (explicitMission)
|
|
6029
|
+
if (explicitMission) {
|
|
6030
|
+
activeAsyncCapacity?.rollback();
|
|
6031
|
+
return toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary);
|
|
6032
|
+
}
|
|
5617
6033
|
missionWarning = `Mission tracking unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
5618
6034
|
}
|
|
5619
6035
|
|
|
5620
6036
|
const attachMission = (result: AgentToolResult<Details>): AgentToolResult<Details> => {
|
|
5621
6037
|
if (!missionBinding) return missionWarning ? { ...result, details: { ...result.details, missionWarning } } : result;
|
|
5622
6038
|
try {
|
|
5623
|
-
return attachMissionToLaunchResult({ binding: missionBinding, result });
|
|
6039
|
+
return attachMissionToLaunchResult({ binding: delegatedExecution ? { ...missionBinding, announceInContent: false } : missionBinding, result });
|
|
5624
6040
|
} catch (error) {
|
|
5625
6041
|
const warning = `Mission tracking unavailable after launch: ${error instanceof Error ? error.message : String(error)}`;
|
|
5626
6042
|
if (explicitMission) {
|
|
@@ -5641,7 +6057,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5641
6057
|
requestSessionId,
|
|
5642
6058
|
requestedSpawns,
|
|
5643
6059
|
);
|
|
5644
|
-
if (reservation.error)
|
|
6060
|
+
if (reservation.error) {
|
|
6061
|
+
activeAsyncCapacity?.rollback();
|
|
6062
|
+
return attachMission(spawnBudgetErrorResult(reservation.error, foregroundMode));
|
|
6063
|
+
}
|
|
5645
6064
|
|
|
5646
6065
|
const execData: ExecutionContextData = omitUndefinedProperties({
|
|
5647
6066
|
params: effectiveParams,
|
|
@@ -5661,6 +6080,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5661
6080
|
artifactsDir,
|
|
5662
6081
|
backgroundRequestedWhileClarifying,
|
|
5663
6082
|
effectiveAsync,
|
|
6083
|
+
asyncRunId,
|
|
5664
6084
|
controlConfig,
|
|
5665
6085
|
intercomBridge,
|
|
5666
6086
|
nestedRoute,
|
|
@@ -5676,11 +6096,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5676
6096
|
parentSessionId: requestSessionId,
|
|
5677
6097
|
parentPiSessionId: requestPiSessionId,
|
|
5678
6098
|
capabilityCeiling: resolveCurrentSubagentCapabilityCeiling(requestSessionId),
|
|
6099
|
+
runFanoutBudget,
|
|
6100
|
+
topLevelAsyncCapacityEligible,
|
|
6101
|
+
activeAsyncCapacity,
|
|
5679
6102
|
});
|
|
5680
6103
|
|
|
5681
|
-
const foregroundDescription =
|
|
5682
|
-
|
|
5683
|
-
|
|
6104
|
+
const foregroundDescription = selectedAgentNames.length === 1
|
|
6105
|
+
? `${selectedAgentNames[0]} child`
|
|
6106
|
+
: `${selectedAgentNames.length} live children`;
|
|
5684
6107
|
const parentWorkflowStatus = effectiveParams.workflowParentRunId
|
|
5685
6108
|
? readStatus(path.join(DIRS.async, effectiveParams.workflowParentRunId))
|
|
5686
6109
|
: null;
|
|
@@ -5715,6 +6138,37 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5715
6138
|
interrupt: undefined,
|
|
5716
6139
|
});
|
|
5717
6140
|
if (foregroundControl) {
|
|
6141
|
+
foregroundControl.promptAuditRedo = async (index, guidance) => {
|
|
6142
|
+
const audit = getLivePromptAudit(foregroundControl, index);
|
|
6143
|
+
if (!audit?.rerun) return { text: "Redo is not safe for this prompt in this slice.", isError: true };
|
|
6144
|
+
const rewrittenTask = await rewritePromptWithGuidance({
|
|
6145
|
+
ctx,
|
|
6146
|
+
authoredTask: audit.authoredTask,
|
|
6147
|
+
runtimeAdditions: audit.runtimeAdditions,
|
|
6148
|
+
finalEffectivePrompt: audit.finalEffectivePrompt,
|
|
6149
|
+
guidance,
|
|
6150
|
+
signal,
|
|
6151
|
+
});
|
|
6152
|
+
const redoParams = promptAuditRedoParams(audit.rerun.params, rewrittenTask);
|
|
6153
|
+
const previousForegroundId = deps.state.lastForegroundControlId;
|
|
6154
|
+
const launch = execute(randomUUID(), redoParams, signal, undefined, ctx, true);
|
|
6155
|
+
const newRunId = deps.state.lastForegroundControlId && deps.state.lastForegroundControlId !== previousForegroundId
|
|
6156
|
+
? deps.state.lastForegroundControlId
|
|
6157
|
+
: undefined;
|
|
6158
|
+
if (!newRunId) {
|
|
6159
|
+
const result = await launch;
|
|
6160
|
+
return { text: result.content.find((item) => item.type === "text")?.text ?? "Prompt redo could not start.", isError: true };
|
|
6161
|
+
}
|
|
6162
|
+
foregroundControl.supersededByRunId = newRunId;
|
|
6163
|
+
const replacement = deps.state.foregroundControls.get(newRunId);
|
|
6164
|
+
if (replacement) replacement.sourceRunId = foregroundControl.runId;
|
|
6165
|
+
void launch.then((result) => {
|
|
6166
|
+
if (result.isError) console.warn(`[pi-subagents] Prompt redo ${newRunId} failed: ${result.content.find((item) => item.type === "text")?.text ?? "unknown error"}`);
|
|
6167
|
+
}).catch((error) => {
|
|
6168
|
+
console.warn(`[pi-subagents] Prompt redo ${newRunId} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
6169
|
+
});
|
|
6170
|
+
return { text: `Prompt redo started ${newRunId}.` };
|
|
6171
|
+
};
|
|
5718
6172
|
deps.state.foregroundControls.set(runId, foregroundControl);
|
|
5719
6173
|
deps.state.lastForegroundControlId = runId;
|
|
5720
6174
|
}
|
|
@@ -5809,9 +6263,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5809
6263
|
if (workflowLaunchObserver) {
|
|
5810
6264
|
const singleTask = hasTasks && effectiveParams.tasks?.length === 1 ? effectiveParams.tasks[0] : undefined;
|
|
5811
6265
|
const launch = hasSingle
|
|
5812
|
-
? { agent: effectiveParams.agent!, sessionFile: childSessionFileForTask(effectiveParams.agent!, 0, effectiveParams.model) }
|
|
6266
|
+
? { agent: effectiveParams.agent!, sessionFile: childSessionFileForTask(effectiveParams.agent!, 0, effectiveParams.model), async: effectiveAsync, ...(effectiveAsync ? { runId: asyncRunId } : {}) }
|
|
5813
6267
|
: singleTask
|
|
5814
|
-
? { agent: singleTask.agent, sessionFile: childSessionFileForTask(singleTask.agent, 0, singleTask.model) }
|
|
6268
|
+
? { agent: singleTask.agent, sessionFile: childSessionFileForTask(singleTask.agent, 0, singleTask.model), async: effectiveAsync, ...(effectiveAsync ? { runId: asyncRunId } : {}) }
|
|
5815
6269
|
: undefined;
|
|
5816
6270
|
if (launch) {
|
|
5817
6271
|
workflowLaunchObservers.delete(params);
|
|
@@ -5819,31 +6273,33 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5819
6273
|
}
|
|
5820
6274
|
}
|
|
5821
6275
|
const asyncResult = runAsyncPath(execData, deps);
|
|
5822
|
-
if (asyncResult) return attachMission(withResolvedContext(withForkThinkingNotes(asyncResult, forkThinkingDowngrades), contextPolicy.contextSummary));
|
|
6276
|
+
if (asyncResult) return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(asyncResult, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget));
|
|
5823
6277
|
if (foregroundControl) {
|
|
5824
6278
|
writeNestedForegroundEvent("subagent.nested.started");
|
|
5825
6279
|
nestedForegroundStarted = true;
|
|
5826
6280
|
}
|
|
6281
|
+
const runFanoutAnnotateContent = !delegatedExecution;
|
|
5827
6282
|
if (hasChain && effectiveParams.chain) {
|
|
5828
6283
|
const result = await runChainPath(execData, deps);
|
|
5829
6284
|
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
5830
|
-
return attachMission(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary));
|
|
6285
|
+
return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget, { annotateContent: runFanoutAnnotateContent }));
|
|
5831
6286
|
}
|
|
5832
6287
|
if (hasTasks && effectiveParams.tasks) {
|
|
5833
6288
|
const result = await runParallelPath(execData, deps);
|
|
5834
6289
|
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
5835
|
-
return attachMission(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary));
|
|
6290
|
+
return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget, { annotateContent: runFanoutAnnotateContent }));
|
|
5836
6291
|
}
|
|
5837
6292
|
if (hasSingle) {
|
|
5838
6293
|
const result = await runSinglePath(execData, deps);
|
|
5839
6294
|
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
5840
|
-
return attachMission(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary));
|
|
6295
|
+
return attachMission(withRunFanoutBudget(withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary), runFanoutBudget, { annotateContent: runFanoutAnnotateContent }));
|
|
5841
6296
|
}
|
|
5842
6297
|
} catch (error) {
|
|
5843
6298
|
const errorResult = withForkThinkingNotes(toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary), forkThinkingDowngrades);
|
|
5844
6299
|
if (nestedForegroundStarted) writeNestedForegroundEvent("subagent.nested.completed", errorResult);
|
|
5845
6300
|
return attachMission(errorResult);
|
|
5846
6301
|
} finally {
|
|
6302
|
+
if (activeAsyncCapacity && !activeAsyncCapacity.owner.runnerStartedAt) activeAsyncCapacity.rollback();
|
|
5847
6303
|
if (foregroundControl) {
|
|
5848
6304
|
settleForegroundSchedulingOwner(foregroundControl);
|
|
5849
6305
|
removeForegroundControlIfIdle(deps.state, runId);
|
|
@@ -5912,6 +6368,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5912
6368
|
delete privateParams.delegatedAllowZeroToolBudget;
|
|
5913
6369
|
if (thinkingOverride !== undefined) delegatedThinkingOverrides.set(delegatedParams, thinkingOverride);
|
|
5914
6370
|
if (allowZeroToolBudget) delegatedZeroToolBudgets.add(delegatedParams);
|
|
6371
|
+
delegatedExecutions.add(delegatedParams);
|
|
5915
6372
|
return execute(id, delegatedParams, signal, onUpdate, ctx);
|
|
5916
6373
|
};
|
|
5917
6374
|
|