pi-subagents 0.64.0 → 0.65.1
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 +65 -0
- package/README.md +2 -2
- package/agents/reviewer.md +1 -1
- package/agents/scout.md +1 -1
- package/docs/agents.md +19 -17
- package/docs/configuration.md +23 -15
- package/docs/extension-api.md +18 -6
- package/docs/missions.md +2 -0
- package/docs/observability.md +10 -12
- package/docs/tool-reference.md +15 -8
- package/docs/watchdog.md +1 -1
- package/docs/workflows.md +13 -3
- package/package.json +5 -4
- package/runner-server-preload.mjs +13 -0
- package/skills/pi-subagents/SKILL.md +2 -1
- package/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
- package/skills/pi-subagents/references/execution-controls.md +7 -3
- package/skills/pi-subagents/references/multi-lane-orchestration.md +2 -0
- package/src/agents/agent-management.ts +47 -15
- package/src/api/capability-ceiling.ts +0 -1
- package/src/api/{pi-args.ts → child-tool-plan.ts} +1 -1
- package/src/api/preflight.ts +7 -4
- package/src/extension/config.ts +4 -2
- package/src/extension/doctor.ts +2 -10
- package/src/extension/fanout-child.ts +9 -11
- package/src/extension/index.ts +58 -7
- package/src/extension/public-execution.ts +14 -0
- package/src/extension/rpc.ts +3 -2
- package/src/extension/schemas.ts +3 -2
- package/src/extension/tool-description.ts +13 -8
- package/src/integrations/pi-web-session-liveness.ts +73 -0
- package/src/intercom/native-supervisor-channel.ts +159 -95
- package/src/intercom/supervisor-ui.ts +244 -0
- package/src/missions/workflow-state.ts +37 -16
- package/src/runs/background/async-execution.ts +51 -25
- package/src/runs/background/async-job-tracker.ts +11 -0
- package/src/runs/background/async-resume.ts +14 -3
- package/src/runs/background/async-retention.ts +9 -0
- package/src/runs/background/control-channel.ts +2 -204
- package/src/runs/background/notify.ts +2 -0
- package/src/runs/background/process-terminal.ts +1 -1
- package/src/runs/background/retained-nested-route-tracker.ts +96 -0
- package/src/runs/background/run-child-session.ts +614 -0
- package/src/runs/background/run-status.ts +0 -1
- package/src/runs/background/runner-aliases.ts +152 -0
- package/src/runs/background/runner-child-sessions.ts +31 -0
- package/src/runs/background/scheduled-runs.ts +18 -4
- package/src/runs/background/subagent-runner.ts +203 -901
- package/src/runs/foreground/async-steering-action.ts +1 -17
- package/src/runs/foreground/execution.ts +204 -378
- package/src/runs/foreground/foreground-control.ts +4 -0
- package/src/runs/foreground/foreground-history.ts +3 -1
- package/src/runs/foreground/prompt-audit.ts +9 -5
- package/src/runs/foreground/subagent-executor.ts +161 -91
- package/src/runs/foreground/workflow-foreground-steering.ts +24 -98
- package/src/runs/shared/abort-recovery.ts +3 -3
- package/src/runs/shared/acceptance.ts +14 -1
- package/src/runs/shared/capability-ceiling.ts +1 -2
- package/src/runs/shared/child-hooks.ts +25 -0
- package/src/runs/shared/child-identity.ts +13 -2
- package/src/runs/shared/child-launch.ts +314 -0
- package/src/runs/shared/child-lifecycle.ts +25 -0
- package/src/runs/shared/child-runtime-config.ts +126 -0
- package/src/runs/shared/child-session.ts +337 -0
- package/src/runs/shared/child-tool-plan.ts +530 -0
- package/src/runs/shared/claude-code-adapter.ts +5 -1
- package/src/runs/shared/completion-guard.ts +1 -1
- package/src/runs/shared/external-cli-preflight.ts +16 -0
- package/src/runs/shared/llm-intent-arbiter.ts +20 -11
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +5 -4
- package/src/runs/shared/model-exclusions.ts +84 -15
- package/src/runs/shared/model-fallback.ts +78 -6
- package/src/runs/shared/nested-events.ts +32 -48
- package/src/runs/shared/nested-path.ts +0 -14
- package/src/runs/shared/orca-progress-tabs.ts +12 -7
- package/src/runs/shared/parallel-utils.ts +1 -2
- package/src/runs/shared/permissions.ts +0 -13
- package/src/runs/shared/process-signal.ts +4 -1
- package/src/runs/shared/run-fanout-budget.ts +0 -13
- package/src/runs/shared/runtime-acknowledged-extensions.ts +0 -27
- package/src/runs/shared/structured-output.ts +17 -4
- package/src/runs/shared/subagent-control.ts +2 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +87 -384
- package/src/runs/shared/tool-availability.ts +18 -62
- package/src/runs/shared/tool-budget.ts +0 -14
- package/src/runs/shared/worktree-cleanup-plan.ts +31 -9
- package/src/runs/shared/worktree.ts +192 -40
- package/src/shared/child-session-name.ts +1 -1
- package/src/shared/jsonl-writer.ts +11 -0
- package/src/shared/model-response-aliases.ts +13 -0
- package/src/shared/thinking-ceiling.ts +0 -6
- package/src/shared/types.ts +63 -28
- package/src/shared/utils.ts +6 -4
- package/src/shared/watch-strategy.ts +2 -0
- package/src/slash/slash-commands.ts +0 -6
- package/src/tui/fleet-status.ts +1 -1
- package/src/tui/fleet.ts +0 -1
- package/src/tui/render.ts +254 -41
- package/src/watchdog/child-status.ts +0 -1
- package/src/watchdog/register-child.ts +12 -12
- package/src/workflows/scripted-workflow.ts +80 -7
- package/src/workflows/workflow-checklist.ts +4 -3
- package/src/runs/shared/child-protocol.ts +0 -415
- package/src/runs/shared/pi-args.ts +0 -1059
- package/src/runs/shared/subagent-startup-retry.ts +0 -116
- package/src/shared/post-exit-stdio-guard.ts +0 -85
|
@@ -49,6 +49,7 @@ import { updateActiveRunIndex } from "../background/active-run-index.ts";
|
|
|
49
49
|
import { steeringReceipt } from "../background/steering.ts";
|
|
50
50
|
import { acquireActiveAsyncCapacity, ActiveAsyncCapacityError, getActiveAsyncCapacitySnapshot, resolveAbandonedSlotReleaseAfterMs, resolveMaxActiveAsyncRunsPerSession, transferActiveAsyncCapacity, type ActiveAsyncCapacityHandle } from "../background/active-async-capacity.ts";
|
|
51
51
|
import { isScheduledRunAction } from "../background/scheduled-runs.ts";
|
|
52
|
+
import { encodeIndexSegment } from "../background/index-segment.ts";
|
|
52
53
|
import { enqueueChainAppendRequest, readPendingChainAppendRequests, runnerStepOutputNames } from "../background/chain-append.ts";
|
|
53
54
|
import { ChainOutputValidationError, validateChainOutputBindingsWithContext } from "../shared/chain-outputs.ts";
|
|
54
55
|
import { normalizeGateAcceptance, resolveAcceptanceReportMode, validateExecutionAcceptance } from "../shared/acceptance.ts";
|
|
@@ -59,7 +60,8 @@ import { currentCompletionOwnerId } from "../../shared/completion-owner.ts";
|
|
|
59
60
|
import { applyIntercomBridgeToAgent, INTERCOM_BRIDGE_MARKER, resolveIntercomBridge, resolveIntercomSessionTarget, resolveSubagentIntercomTarget, type IntercomBridgeState } from "../../intercom/intercom-bridge.ts";
|
|
60
61
|
import { formatControlIntercomMessage, formatControlNoticeMessage, resolveControlConfig, shouldNotifyControlEvent } from "../shared/subagent-control.ts";
|
|
61
62
|
import { formatSpawnBudget, getSpawnBudgetSnapshot, grantSpawnBudget, preflightSpawnBudget, preflightSpawnBudgetGrant, reserveSpawnBudget } from "../shared/spawn-budget.ts";
|
|
62
|
-
import { claimRunFanoutBatch, claimRunFanoutBatchWithCommit, createRunFanoutBudget,
|
|
63
|
+
import { claimRunFanoutBatch, claimRunFanoutBatchWithCommit, createRunFanoutBudget, formatRunFanoutBudget, getRunFanoutBudgetSnapshot, readRunFanoutBudgetDescriptor, RunFanoutLimitError, writeRunFanoutBudgetDescriptor } from "../shared/run-fanout-budget.ts";
|
|
64
|
+
import { retainLiveForegroundNestedRoute } from "../../integrations/pi-web-session-liveness.ts";
|
|
63
65
|
import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
|
|
64
66
|
import { usageBudgetExceededMessage, usageBudgetState, validateUsageBudgetConfig } from "../shared/usage-budget.ts";
|
|
65
67
|
import { intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
@@ -84,11 +86,8 @@ import { deliverInterruptRequest, readRevivalBriefs, requestAsyncSteer, type Ste
|
|
|
84
86
|
import { updateSteeringTarget, waitForSteeringAction } from "../background/steering.ts";
|
|
85
87
|
import { canQueueRetainedAsyncFollowUp, steerAsyncRun } from "./async-steering-action.ts";
|
|
86
88
|
import {
|
|
87
|
-
removeWorkflowForegroundSteeringRoute,
|
|
88
89
|
resolveWorkflowForegroundSteeringTarget,
|
|
89
90
|
steerWorkflowForegroundTarget,
|
|
90
|
-
workflowForegroundSteeringDir,
|
|
91
|
-
workflowForegroundSteeringLaunchOptions,
|
|
92
91
|
} from "./workflow-foreground-steering.ts";
|
|
93
92
|
import { stopAsyncRun } from "./async-stop-action.ts";
|
|
94
93
|
import { dismissRecoveredWorkflow } from "./async-dismiss-action.ts";
|
|
@@ -96,10 +95,11 @@ import { promotePausedWorkflowIfSettled, reconcileDetachedWorkflowChildCompletio
|
|
|
96
95
|
import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
|
|
97
96
|
import { resolveAsyncRootResultPath, waitForImportedAsyncRoot } from "../background/chain-root-attachment.ts";
|
|
98
97
|
import { resultFilePath, writeAsyncResultFile } from "../background/result-files.ts";
|
|
99
|
-
import { attachRootChildrenToSteps, createNestedRoute, findNestedControlResult,
|
|
98
|
+
import { attachRootChildrenToSteps, createNestedRoute, findNestedControlResult, inheritedNestedParentAddressOf, inheritedNestedRouteOf, resolveNestedAsyncDir, snapshotNestedEventFiles, updateForegroundNestedProjection, writeNestedControlRequest, writeNestedEvent, type NestedParentAddress, type NestedRoute, type NestedRunResolutionScope } from "../shared/nested-events.ts";
|
|
99
|
+
import type { ChildRuntimeConfig } from "../shared/child-runtime-config.ts";
|
|
100
100
|
import { resolveSubagentRunId, type ResolvedSubagentRunId } from "../background/run-id-resolver.ts";
|
|
101
101
|
import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
|
|
102
|
-
import { isStoppableAsyncStatusStep, resolveAsyncStatusChild } from "../shared/child-identity.ts";
|
|
102
|
+
import { isStoppableAsyncStatusStep, resolveAsyncStatusChild, stopStoppableAsyncStatusChildren } from "../shared/child-identity.ts";
|
|
103
103
|
import { inspectSubagentStatus } from "../background/run-status.ts";
|
|
104
104
|
import { getExternalJobProvider } from "../../api/external-job-provider.ts";
|
|
105
105
|
import { externalJobFollowUpRequestDigest, externalJobFollowUpRequestId, externalJobFollowUpRunId, externalJobPromptDigest, externalJobStableJson } from "../shared/external-job-runner.ts";
|
|
@@ -153,6 +153,9 @@ import {
|
|
|
153
153
|
type Details,
|
|
154
154
|
type ExtensionConfig,
|
|
155
155
|
type ForegroundResumeChild,
|
|
156
|
+
type ForegroundChildSessionControls,
|
|
157
|
+
type ForegroundSteerInput,
|
|
158
|
+
type ForegroundSteerOutcome,
|
|
156
159
|
type ForegroundRunControl,
|
|
157
160
|
type HostStepNodeV1,
|
|
158
161
|
type IntercomBridgeConfig,
|
|
@@ -351,6 +354,8 @@ export interface SubagentParamsLike {
|
|
|
351
354
|
tasks?: TaskParam[];
|
|
352
355
|
concurrency?: number;
|
|
353
356
|
worktree?: boolean;
|
|
357
|
+
/** Git ref used as the managed worktree base. */
|
|
358
|
+
baseRef?: string;
|
|
354
359
|
context?: "fresh" | "fork" | "profile";
|
|
355
360
|
/** Per-run intercom bridge config. It replaces the global config for this launch only. */
|
|
356
361
|
intercomBridge?: IntercomBridgeConfig;
|
|
@@ -435,7 +440,22 @@ interface ExecutorDeps {
|
|
|
435
440
|
allowMutatingManagementActions?: boolean;
|
|
436
441
|
activateSupervisorTransport?: () => void;
|
|
437
442
|
refreshResultDelivery?: () => void;
|
|
443
|
+
trackRetainedNestedRoute?: (rootRunId: string) => void;
|
|
438
444
|
kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;
|
|
445
|
+
/** Set when this executor runs inside a child session; carries the runtime settings the host passes instead of environment variables. */
|
|
446
|
+
childRuntime?: ChildRuntimeConfig;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function inheritedNestedRoute(deps: Pick<ExecutorDeps, "childRuntime">): NestedRoute | undefined {
|
|
450
|
+
return inheritedNestedRouteOf(deps.childRuntime);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function inheritedNestedParentAddress(deps: Pick<ExecutorDeps, "childRuntime">): NestedParentAddress | undefined {
|
|
454
|
+
return inheritedNestedParentAddressOf(deps.childRuntime);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function inheritedRunFanoutBudget(deps: Pick<ExecutorDeps, "childRuntime">): RunFanoutBudgetDescriptor | undefined {
|
|
458
|
+
return deps.childRuntime?.runFanoutBudget;
|
|
439
459
|
}
|
|
440
460
|
|
|
441
461
|
type ForkSessionFileForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean, modelOrigin?: ModelOrigin) => string | undefined;
|
|
@@ -509,10 +529,16 @@ function loadWorkflowScriptPath(params: SubagentParamsLike, runtimeCwd: string):
|
|
|
509
529
|
return { params: { ...rest, workflowScript } };
|
|
510
530
|
}
|
|
511
531
|
|
|
512
|
-
function removeForegroundControlIfIdle(state: SubagentState, runId: string): boolean {
|
|
532
|
+
export function removeForegroundControlIfIdle(state: SubagentState, runId: string, trackRetainedNestedRoute?: (rootRunId: string) => void): boolean {
|
|
513
533
|
const control = state.foregroundControls.get(runId);
|
|
514
534
|
if (control && (!foregroundSchedulingSettled(control) || (control.activeChildren?.size ?? 0) > 0)) return false;
|
|
515
|
-
if (control)
|
|
535
|
+
if (control?.nestedRoute && trackRetainedNestedRoute) {
|
|
536
|
+
try {
|
|
537
|
+
if (retainLiveForegroundNestedRoute(state, control.nestedRoute)) trackRetainedNestedRoute(runId);
|
|
538
|
+
} catch (error) {
|
|
539
|
+
console.error(`Failed to retain live nested descendants for foreground run '${runId}':`, error);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
516
542
|
state.foregroundControls.delete(runId);
|
|
517
543
|
if (state.lastForegroundControlId === runId) state.lastForegroundControlId = null;
|
|
518
544
|
return true;
|
|
@@ -565,8 +591,8 @@ function formatForegroundActivity(control: SubagentState["foregroundControls"] e
|
|
|
565
591
|
|
|
566
592
|
function nestedResolutionScopeForExecutor(deps: ExecutorDeps): NestedRunResolutionScope | undefined {
|
|
567
593
|
if (deps.allowMutatingManagementActions !== false) return undefined;
|
|
568
|
-
const route =
|
|
569
|
-
const address = route ?
|
|
594
|
+
const route = inheritedNestedRoute(deps);
|
|
595
|
+
const address = route ? inheritedNestedParentAddress(deps) : undefined;
|
|
570
596
|
return {
|
|
571
597
|
routes: route ? [route] : [],
|
|
572
598
|
...(address ? { descendantOf: { parentRunId: address.parentRunId, ...(address.parentStepIndex !== undefined ? { parentStepIndex: address.parentStepIndex } : {}) } } : {}),
|
|
@@ -707,7 +733,7 @@ function foregroundChildActivityFromProgress(progress: SingleResult["progress"]
|
|
|
707
733
|
};
|
|
708
734
|
}
|
|
709
735
|
|
|
710
|
-
function rememberForegroundRun(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[]; params: SubagentParamsLike; effectiveOutput?: string | boolean; effectiveOutputMode: OutputMode; extensionBindings?: ExtensionBindings }): void {
|
|
736
|
+
function rememberForegroundRun(state: SubagentState, input: { modelResponseAliases?: Record<string, string[]>; runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[]; params: SubagentParamsLike; effectiveOutput?: string | boolean; effectiveOutputMode: OutputMode; extensionBindings?: ExtensionBindings }): void {
|
|
711
737
|
state.foregroundRuns ??= new Map();
|
|
712
738
|
const previous = state.foregroundRuns.get(input.runId);
|
|
713
739
|
const updatedAt = Date.now();
|
|
@@ -719,6 +745,7 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
|
|
|
719
745
|
updatedAt,
|
|
720
746
|
children: input.results.map((result, index) => {
|
|
721
747
|
const resumeContract = omitUndefinedProperties({
|
|
748
|
+
modelResponseAliases: input.modelResponseAliases,
|
|
722
749
|
outputSchema: input.params.outputSchema,
|
|
723
750
|
agentContract: input.params.agentContract,
|
|
724
751
|
acceptance: input.params.acceptance,
|
|
@@ -1297,8 +1324,10 @@ function appendStepToAsyncChain(input: {
|
|
|
1297
1324
|
currentModelProvider: parentModel?.provider,
|
|
1298
1325
|
currentModel: parentModel,
|
|
1299
1326
|
modelScope: discoveredForAppend.modelScope,
|
|
1327
|
+
modelResponseAliases: input.deps.config.modelResponseAliases,
|
|
1300
1328
|
interactive: input.ctx.hasUI,
|
|
1301
1329
|
permissions: input.deps.config.permissions,
|
|
1330
|
+
childRuntime: input.deps.childRuntime,
|
|
1302
1331
|
});
|
|
1303
1332
|
const built = buildAsyncRunnerSteps(resolved.id, compactOptional<Parameters<typeof buildAsyncRunnerSteps>[1]>({
|
|
1304
1333
|
chain: wrapChainTasksForFork(chain, contextPolicy),
|
|
@@ -1311,7 +1340,7 @@ function appendStepToAsyncChain(input: {
|
|
|
1311
1340
|
cwd: status.cwd ?? input.requestCwd,
|
|
1312
1341
|
chainSkills,
|
|
1313
1342
|
dynamicFanoutMaxItems: input.deps.config.chain?.dynamicFanout?.maxItems,
|
|
1314
|
-
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
1343
|
+
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth, input.deps.childRuntime),
|
|
1315
1344
|
waitToolEnabled: input.deps.waitToolEnabled,
|
|
1316
1345
|
waitToolDefaultTimeoutMs: input.deps.waitToolDefaultTimeoutMs,
|
|
1317
1346
|
contextForAgent: contextPolicy.contextForAgent,
|
|
@@ -1637,11 +1666,11 @@ async function resumeExternalJobFollowUp(input: {
|
|
|
1637
1666
|
return externalJobFollowUpStarted({ sourceRunId: input.target.runId, runId, asyncDir, duplicate: true, interactive: input.ctx.hasUI });
|
|
1638
1667
|
}
|
|
1639
1668
|
|
|
1640
|
-
const depthState = checkSubagentDepth(input.deps.config.maxSubagentDepth);
|
|
1669
|
+
const depthState = checkSubagentDepth(input.deps.config.maxSubagentDepth, input.deps.childRuntime);
|
|
1641
1670
|
if (depthState.blocked) {
|
|
1642
1671
|
return { content: [{ type: "text", text: `Nested subagent resume blocked (depth=${depthState.depth}, max=${depthState.maxDepth}). Complete the follow-up directly instead.` }], isError: true, details: { mode: "management", results: [] } };
|
|
1643
1672
|
}
|
|
1644
|
-
const topLevelResume = depthState.depth === 0 && !
|
|
1673
|
+
const topLevelResume = depthState.depth === 0 && !inheritedNestedRoute(input.deps) && !input.deps.state.workflowControllers?.has(input.target.runId);
|
|
1645
1674
|
let activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined;
|
|
1646
1675
|
try {
|
|
1647
1676
|
activeAsyncCapacity = topLevelResume ? acquireActiveAsyncCapacity({
|
|
@@ -1677,15 +1706,17 @@ async function resumeExternalJobFollowUp(input: {
|
|
|
1677
1706
|
currentModelProvider: parentModel?.provider,
|
|
1678
1707
|
currentModel: parentModel,
|
|
1679
1708
|
modelScope: input.modelScope,
|
|
1709
|
+
modelResponseAliases: input.deps.config.modelResponseAliases,
|
|
1680
1710
|
interactive: input.ctx.hasUI,
|
|
1681
1711
|
permissions: input.deps.config.permissions,
|
|
1712
|
+
childRuntime: input.deps.childRuntime,
|
|
1682
1713
|
}),
|
|
1683
1714
|
cwd: input.effectiveCwd,
|
|
1684
1715
|
artifactsDir,
|
|
1685
1716
|
artifactConfig,
|
|
1686
1717
|
shareEnabled: false,
|
|
1687
1718
|
...(input.parentSessionFile ? { sessionRoot: input.deps.getSubagentSessionRoot(input.parentSessionFile) } : {}),
|
|
1688
|
-
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
1719
|
+
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth, input.deps.childRuntime),
|
|
1689
1720
|
waitToolEnabled: input.deps.waitToolEnabled,
|
|
1690
1721
|
waitToolDefaultTimeoutMs: input.deps.waitToolDefaultTimeoutMs,
|
|
1691
1722
|
worktreeSetupHook: input.deps.config.worktreeSetupHook,
|
|
@@ -1796,7 +1827,7 @@ async function resumeAsyncRun(input: {
|
|
|
1796
1827
|
};
|
|
1797
1828
|
}
|
|
1798
1829
|
|
|
1799
|
-
const { blocked, depth, maxDepth } = checkSubagentDepth(input.deps.config.maxSubagentDepth);
|
|
1830
|
+
const { blocked, depth, maxDepth } = checkSubagentDepth(input.deps.config.maxSubagentDepth, input.deps.childRuntime);
|
|
1800
1831
|
if (blocked) {
|
|
1801
1832
|
return {
|
|
1802
1833
|
content: [{ type: "text", text: `Nested subagent resume blocked (depth=${depth}, max=${maxDepth}). Complete the follow-up directly instead.` }],
|
|
@@ -1885,7 +1916,7 @@ async function resumeAsyncRun(input: {
|
|
|
1885
1916
|
};
|
|
1886
1917
|
}
|
|
1887
1918
|
const runId = randomUUID();
|
|
1888
|
-
const topLevelResume = depth === 0 && !
|
|
1919
|
+
const topLevelResume = depth === 0 && !inheritedNestedRoute(input.deps) && !input.params.workflowParentRunId;
|
|
1889
1920
|
let activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined;
|
|
1890
1921
|
try {
|
|
1891
1922
|
activeAsyncCapacity = topLevelResume ? acquireActiveAsyncCapacity({
|
|
@@ -1929,8 +1960,10 @@ async function resumeAsyncRun(input: {
|
|
|
1929
1960
|
currentModelProvider: parentModel?.provider,
|
|
1930
1961
|
currentModel: parentModel,
|
|
1931
1962
|
modelScope,
|
|
1963
|
+
modelResponseAliases: input.deps.config.modelResponseAliases,
|
|
1932
1964
|
interactive: input.ctx.hasUI,
|
|
1933
1965
|
permissions: input.deps.config.permissions,
|
|
1966
|
+
childRuntime: input.deps.childRuntime,
|
|
1934
1967
|
}),
|
|
1935
1968
|
availableModels,
|
|
1936
1969
|
cwd: effectiveCwd,
|
|
@@ -1943,12 +1976,13 @@ async function resumeAsyncRun(input: {
|
|
|
1943
1976
|
agentContract: input.params.agentContract,
|
|
1944
1977
|
fast: input.params.fast,
|
|
1945
1978
|
dynamicFanoutMaxItems: input.deps.config.chain?.dynamicFanout?.maxItems,
|
|
1946
|
-
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
1979
|
+
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth, input.deps.childRuntime),
|
|
1947
1980
|
waitToolEnabled: input.deps.waitToolEnabled,
|
|
1948
1981
|
waitToolDefaultTimeoutMs: input.deps.waitToolDefaultTimeoutMs,
|
|
1949
1982
|
worktreeSetupHook: input.deps.config.worktreeSetupHook,
|
|
1950
1983
|
worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
|
|
1951
1984
|
worktreeBaseDir: input.deps.config.worktreeBaseDir,
|
|
1985
|
+
baseRef: input.params.baseRef ?? target.recoveryDescriptor?.baseRef,
|
|
1952
1986
|
worktreeProvider: input.deps.config.worktreeProvider,
|
|
1953
1987
|
worktreeBranchPrefix: input.deps.config.worktreeBranchPrefix,
|
|
1954
1988
|
controlConfig: resolveControlConfig(input.deps.config.control, input.params.control),
|
|
@@ -1985,8 +2019,11 @@ async function resumeAsyncRun(input: {
|
|
|
1985
2019
|
if (target.source === "async" && asyncReviveRequiresRecoveryDescriptor(target)) {
|
|
1986
2020
|
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: [] } };
|
|
1987
2021
|
}
|
|
2022
|
+
if (input.params.baseRef !== undefined && "managedWorktree" in target && target.managedWorktree === true) {
|
|
2023
|
+
return { content: [{ type: "text", text: "Cannot resume with baseRef: retained managed-worktree children continue in their existing worktree. Start a new worktree run from that base ref instead." }], isError: true, details: { mode: "management", results: [] } };
|
|
2024
|
+
}
|
|
1988
2025
|
const runId = randomUUID();
|
|
1989
|
-
const topLevelResume = depth === 0 && !
|
|
2026
|
+
const topLevelResume = depth === 0 && !inheritedNestedRoute(input.deps) && !input.params.workflowParentRunId;
|
|
1990
2027
|
let activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined;
|
|
1991
2028
|
try {
|
|
1992
2029
|
activeAsyncCapacity = !topLevelResume ? undefined : target.source === "async"
|
|
@@ -2032,8 +2069,11 @@ async function resumeAsyncRun(input: {
|
|
|
2032
2069
|
currentModelProvider: parentModel?.provider,
|
|
2033
2070
|
currentModel: parentModel,
|
|
2034
2071
|
modelScope,
|
|
2072
|
+
// Absence in the retained contract is meaningful; never acquire current aliases.
|
|
2073
|
+
modelResponseAliases: recoveryDescriptor ? recoveryDescriptor.modelResponseAliases : foregroundContract?.modelResponseAliases,
|
|
2035
2074
|
interactive: input.ctx.hasUI,
|
|
2036
2075
|
permissions: input.deps.config.permissions,
|
|
2076
|
+
childRuntime: input.deps.childRuntime,
|
|
2037
2077
|
}),
|
|
2038
2078
|
cwd: effectiveCwd,
|
|
2039
2079
|
maxOutput: input.params.maxOutput ?? recoveryDescriptor?.maxOutput,
|
|
@@ -2058,12 +2098,13 @@ async function resumeAsyncRun(input: {
|
|
|
2058
2098
|
thinkingCeiling: recoveryDescriptor?.thinkingCeiling ?? ("thinkingCeiling" in target ? target.thinkingCeiling : undefined),
|
|
2059
2099
|
extensionBindings: recoveryDescriptor?.extensionBindings ?? ("extensionBindings" in target ? target.extensionBindings : undefined),
|
|
2060
2100
|
outputBaseDir: resolveSingleRunOutputBaseDir(input.deps, artifactsDir, runId),
|
|
2061
|
-
maxSubagentDepth: recoveryDescriptor?.maxSubagentDepth ?? resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
2101
|
+
maxSubagentDepth: recoveryDescriptor?.maxSubagentDepth ?? resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth, input.deps.childRuntime),
|
|
2062
2102
|
waitToolEnabled: input.deps.waitToolEnabled,
|
|
2063
2103
|
waitToolDefaultTimeoutMs: input.deps.waitToolDefaultTimeoutMs,
|
|
2064
2104
|
worktreeSetupHook: input.deps.config.worktreeSetupHook,
|
|
2065
2105
|
worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
|
|
2066
2106
|
worktreeBaseDir: input.deps.config.worktreeBaseDir,
|
|
2107
|
+
baseRef: input.params.baseRef ?? recoveryDescriptor?.baseRef,
|
|
2067
2108
|
worktreeProvider: input.deps.config.worktreeProvider,
|
|
2068
2109
|
worktreeBranchPrefix: input.deps.config.worktreeBranchPrefix,
|
|
2069
2110
|
// A retained async child already owns the recorded worktree. Resume it in
|
|
@@ -3185,11 +3226,13 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
3185
3226
|
currentModelProvider: parentModel?.provider,
|
|
3186
3227
|
currentModel: parentModel,
|
|
3187
3228
|
modelScope: data.modelScope,
|
|
3229
|
+
modelResponseAliases: deps.config.modelResponseAliases,
|
|
3188
3230
|
interactive: ctx.hasUI,
|
|
3189
3231
|
permissions: deps.config.permissions,
|
|
3232
|
+
childRuntime: deps.childRuntime,
|
|
3190
3233
|
});
|
|
3191
3234
|
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
3192
|
-
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
3235
|
+
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth, deps.childRuntime);
|
|
3193
3236
|
const currentProvider = parentModel?.provider;
|
|
3194
3237
|
const controlIntercomTarget = resolveRunLevelIntercomTarget(intercomBridge, contextPolicy);
|
|
3195
3238
|
const childIntercomTarget = resolveChildIntercomTargetFactory(intercomBridge, contextPolicy, id);
|
|
@@ -3269,6 +3312,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
3269
3312
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
3270
3313
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
3271
3314
|
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
3315
|
+
baseRef: params.baseRef,
|
|
3272
3316
|
worktreeProvider: deps.config.worktreeProvider,
|
|
3273
3317
|
worktreeBranchPrefix: deps.config.worktreeBranchPrefix,
|
|
3274
3318
|
controlConfig,
|
|
@@ -3307,6 +3351,7 @@ function createSingleWorktreeSetup(
|
|
|
3307
3351
|
setupHook: ExtensionConfig["worktreeSetupHook"],
|
|
3308
3352
|
setupHookTimeoutMs: ExtensionConfig["worktreeSetupHookTimeoutMs"],
|
|
3309
3353
|
baseDir: ExtensionConfig["worktreeBaseDir"],
|
|
3354
|
+
baseRef: string | undefined,
|
|
3310
3355
|
provider: ExtensionConfig["worktreeProvider"],
|
|
3311
3356
|
branchPrefix: ExtensionConfig["worktreeBranchPrefix"],
|
|
3312
3357
|
label?: string,
|
|
@@ -3322,6 +3367,7 @@ function createSingleWorktreeSetup(
|
|
|
3322
3367
|
? { hookPath: setupHook, ...(setupHookTimeoutMs === undefined ? {} : { timeoutMs: setupHookTimeoutMs }) }
|
|
3323
3368
|
: undefined,
|
|
3324
3369
|
baseDir,
|
|
3370
|
+
baseRef,
|
|
3325
3371
|
provider,
|
|
3326
3372
|
branchPrefix,
|
|
3327
3373
|
labels: [label],
|
|
@@ -3674,7 +3720,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3674
3720
|
const rawOutput = params.output !== undefined ? params.output : agentConfig.output;
|
|
3675
3721
|
let effectiveOutput = normalizeSingleOutputOverride(rawOutput, agentConfig.output);
|
|
3676
3722
|
const effectiveOutputMode = params.outputMode ?? agentConfig.outputMode ?? "inline";
|
|
3677
|
-
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
3723
|
+
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth, deps.childRuntime);
|
|
3678
3724
|
const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, agentConfig.maxSubagentDepth);
|
|
3679
3725
|
|
|
3680
3726
|
|
|
@@ -3688,6 +3734,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3688
3734
|
deps.config.worktreeSetupHook,
|
|
3689
3735
|
deps.config.worktreeSetupHookTimeoutMs,
|
|
3690
3736
|
deps.config.worktreeBaseDir,
|
|
3737
|
+
params.baseRef,
|
|
3691
3738
|
deps.config.worktreeProvider,
|
|
3692
3739
|
deps.config.worktreeBranchPrefix,
|
|
3693
3740
|
params.lane?.key ?? params.workflowKey,
|
|
@@ -3741,6 +3788,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3741
3788
|
}
|
|
3742
3789
|
const interruptController = new AbortController();
|
|
3743
3790
|
let detachForeground: ((reason?: string) => boolean) | undefined;
|
|
3791
|
+
let childSessionControls: ForegroundChildSessionControls | undefined;
|
|
3744
3792
|
const foregroundControl = deps.state.foregroundControls.get(runId);
|
|
3745
3793
|
if (foregroundControl) {
|
|
3746
3794
|
const thinking = resolveEffectiveThinking(modelOverride, thinkingOverrideForTask(params.agent!, 0, modelOverride, modelOverrideFromParent, modelOrigin));
|
|
@@ -3761,9 +3809,23 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3761
3809
|
return true;
|
|
3762
3810
|
},
|
|
3763
3811
|
detach: () => detachForeground?.("user request") === true,
|
|
3812
|
+
steer: async (input: ForegroundSteerInput): Promise<ForegroundSteerOutcome> => {
|
|
3813
|
+
if (!childSessionControls) return { state: "failed", reason: CHILD_SESSION_NOT_RUNNING_YET };
|
|
3814
|
+
try {
|
|
3815
|
+
if (input.mode === "follow_up") {
|
|
3816
|
+
await childSessionControls.followUp(input.message);
|
|
3817
|
+
return { state: "queued" };
|
|
3818
|
+
}
|
|
3819
|
+
await childSessionControls.steer(input.message);
|
|
3820
|
+
return { state: "delivered" };
|
|
3821
|
+
} catch (error) {
|
|
3822
|
+
return { state: "failed", reason: error instanceof Error ? error.message : String(error) };
|
|
3823
|
+
}
|
|
3824
|
+
},
|
|
3764
3825
|
}));
|
|
3765
3826
|
}
|
|
3766
3827
|
|
|
3828
|
+
const modelResponseAliases = deps.config.modelResponseAliases === undefined ? undefined : structuredClone(deps.config.modelResponseAliases);
|
|
3767
3829
|
const forwardSingleUpdate = onUpdate
|
|
3768
3830
|
? (update: AgentToolResult<Details>) => {
|
|
3769
3831
|
if (foregroundControl) updateForegroundChild(foregroundControl, 0, update.details?.progress?.[0]);
|
|
@@ -3783,7 +3845,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3783
3845
|
runtimeSnapshotHost: deps.pi,
|
|
3784
3846
|
parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
|
|
3785
3847
|
llmIntentArbiter: createTaskMutationArbiter(ctx),
|
|
3786
|
-
|
|
3848
|
+
childRuntime: deps.childRuntime,
|
|
3849
|
+
onChildSession: (controls) => { childSessionControls = controls; },
|
|
3787
3850
|
context: data.contextPolicy.contextForAgent(params.agent!),
|
|
3788
3851
|
unknownAgentDiagnosticContext: data.unknownAgentDiagnosticContext,
|
|
3789
3852
|
runFanoutBudget: params.runFanoutAdmitted ? data.runFanoutBudget : { ...data.runFanoutBudget, parentPath: `${data.runFanoutBudget.parentPath ? `${data.runFanoutBudget.parentPath}/` : ""}single` },
|
|
@@ -3822,6 +3885,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3822
3885
|
thinkingCeiling: agentConfig.maxThinking,
|
|
3823
3886
|
extensionBindings: params.extensionBindings,
|
|
3824
3887
|
availableModels,
|
|
3888
|
+
modelResponseAliases,
|
|
3825
3889
|
preferredModelProvider: currentProvider,
|
|
3826
3890
|
modelScope: modelScopes,
|
|
3827
3891
|
skills: effectiveSkills,
|
|
@@ -3866,7 +3930,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3866
3930
|
try {
|
|
3867
3931
|
if (foregroundControl) finishForegroundChild(foregroundControl, 0);
|
|
3868
3932
|
} finally {
|
|
3869
|
-
removeForegroundControlIfIdle(deps.state, runId);
|
|
3933
|
+
removeForegroundControlIfIdle(deps.state, runId, deps.trackRetainedNestedRoute);
|
|
3870
3934
|
}
|
|
3871
3935
|
}
|
|
3872
3936
|
}
|
|
@@ -3939,7 +4003,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3939
4003
|
usageBudget: usageBudgetState(data.usageBudget, totalCost),
|
|
3940
4004
|
...(worktreeHandoff?.reference ? { parallelHandoff: worktreeHandoff.reference } : {}),
|
|
3941
4005
|
}));
|
|
3942
|
-
rememberForegroundRun(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, results: details.results, params, effectiveOutput, effectiveOutputMode, extensionBindings: params.extensionBindings });
|
|
4006
|
+
rememberForegroundRun(deps.state, { modelResponseAliases, runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, results: details.results, params, effectiveOutput, effectiveOutputMode, extensionBindings: params.extensionBindings });
|
|
3943
4007
|
|
|
3944
4008
|
const suppressRoutineResultIntercom = shouldSuppressRoutineResultIntercom({ suppressRoutineResultIntercom: params.suppressRoutineResultIntercom, results: [r] });
|
|
3945
4009
|
if (!r.detached && !r.interrupted && !suppressRoutineResultIntercom) {
|
|
@@ -4095,9 +4159,13 @@ function workflowChildResult(
|
|
|
4095
4159
|
? result.details.results[0].finalOutput
|
|
4096
4160
|
: receiptOutput;
|
|
4097
4161
|
const childError = result.details.results.map((child) => child.error).find((error): error is string => Boolean(error));
|
|
4098
|
-
const
|
|
4099
|
-
? `${childError}\n\n${receiptOutput}`
|
|
4162
|
+
const failureErrorBase = childError && receiptOutput
|
|
4163
|
+
? receiptOutput.includes(childError) ? receiptOutput : `${childError}\n\n${receiptOutput}`
|
|
4100
4164
|
: childError || receiptOutput || output || "Child run failed.";
|
|
4165
|
+
const savedOutputEvidence = [...new Set(result.details.results.map((child) => child.savedOutputPath).filter((value): value is string => Boolean(value)))]
|
|
4166
|
+
.filter((savedOutputPath) => !failureErrorBase.includes(savedOutputPath))
|
|
4167
|
+
.map((savedOutputPath) => `Saved output: ${savedOutputPath}`);
|
|
4168
|
+
const failureError = [failureErrorBase, ...savedOutputEvidence].join("\n");
|
|
4101
4169
|
const detached = result.details.results.some((child) => child.detached);
|
|
4102
4170
|
const interrupted = result.details.results.some((child) => child.interrupted);
|
|
4103
4171
|
const stopped = result.details.results.some((child) => child.stopped);
|
|
@@ -4215,6 +4283,7 @@ function workflowSteerReceipt(key: string, result: AgentToolResult<Details>): Wo
|
|
|
4215
4283
|
};
|
|
4216
4284
|
}
|
|
4217
4285
|
|
|
4286
|
+
const CHILD_SESSION_NOT_RUNNING_YET = "Child session is not running yet.";
|
|
4218
4287
|
const MAX_WORKFLOW_RESUME_HINT_BYTES = 1024;
|
|
4219
4288
|
const MAX_WORKFLOW_CHILD_RUN_ID_BYTES = 256;
|
|
4220
4289
|
const WORKFLOW_RESUME_HINT_PARENT_STATES = new Set(["complete", "failed", "partial"]);
|
|
@@ -4350,7 +4419,6 @@ export async function steerWorkflowChildByKey(input: {
|
|
|
4350
4419
|
while (true) {
|
|
4351
4420
|
const control = [...input.state.foregroundControls.values()].find((candidate) => candidate.parentWorkflowRunId === input.workflowRunId
|
|
4352
4421
|
&& candidate.workflowKey === input.key
|
|
4353
|
-
&& Boolean(candidate.workflowSteeringDir)
|
|
4354
4422
|
&& (candidate.activeChildren?.size ?? 0) > 0);
|
|
4355
4423
|
if (control) {
|
|
4356
4424
|
const result = await steerWorkflowForegroundTarget({
|
|
@@ -4358,10 +4426,9 @@ export async function steerWorkflowChildByKey(input: {
|
|
|
4358
4426
|
message: input.message,
|
|
4359
4427
|
mode: input.options.mode,
|
|
4360
4428
|
index: input.options.index,
|
|
4361
|
-
ackTimeoutMs: Math.max(1, deadline - Date.now()),
|
|
4362
|
-
signal: input.signal,
|
|
4363
4429
|
});
|
|
4364
|
-
|
|
4430
|
+
// The control registers before its child session exists; keep polling until the steer can route.
|
|
4431
|
+
if (!result.details.steering?.targets.some((target) => target.reason === CHILD_SESSION_NOT_RUNNING_YET) || Date.now() >= deadline) return workflowSteerReceipt(input.key, result);
|
|
4365
4432
|
}
|
|
4366
4433
|
|
|
4367
4434
|
const workflowStatus = readStatus(path.join(asyncDirRoot, input.workflowRunId));
|
|
@@ -4434,6 +4501,7 @@ export function prepareWorkflowLaunchParams(
|
|
|
4434
4501
|
const toolBudget = childParams.toolBudget ?? workflowDefaults.toolBudget;
|
|
4435
4502
|
const intercomBridge = childParams.intercomBridge ?? workflowDefaults.intercomBridge;
|
|
4436
4503
|
const worktree = childParams.worktree ?? workflowDefaults.worktree;
|
|
4504
|
+
const baseRef = Object.hasOwn(childParams, "baseRef") ? childParams.baseRef : undefined;
|
|
4437
4505
|
const outputSchema = Object.hasOwn(childParams, "outputSchema") ? childParams.outputSchema : workflowDefaults.outputSchema;
|
|
4438
4506
|
if (outputSchema !== undefined) assertJsonSchemaObject(outputSchema, "outputSchema");
|
|
4439
4507
|
const agentContract = Object.hasOwn(childParams, "agentContract") ? childParams.agentContract : workflowDefaults.agentContract;
|
|
@@ -4453,6 +4521,7 @@ export function prepareWorkflowLaunchParams(
|
|
|
4453
4521
|
workflowKey,
|
|
4454
4522
|
...(lane ? { lane } : {}),
|
|
4455
4523
|
...(worktree !== undefined ? { worktree: worktree as boolean } : {}),
|
|
4524
|
+
...(baseRef !== undefined ? { baseRef: baseRef as string } : {}),
|
|
4456
4525
|
...(outputSchema !== undefined ? { outputSchema: outputSchema as JsonSchemaObject } : {}),
|
|
4457
4526
|
...(agentContract !== undefined ? { agentContract: agentContract as AgentContract } : {}),
|
|
4458
4527
|
...(acceptance !== undefined ? { acceptance: acceptance as AcceptanceInput } : {}),
|
|
@@ -4468,12 +4537,13 @@ export function prepareWorkflowLaunchParams(
|
|
|
4468
4537
|
};
|
|
4469
4538
|
}
|
|
4470
4539
|
const control = mergeWorkflowControlOverrides(workflowDefaults.control, childParams.control as ControlConfig | undefined);
|
|
4540
|
+
const asyncOmitted = childParams.async === undefined && workflowDefaults.async === undefined;
|
|
4471
4541
|
const launchParams = {
|
|
4472
4542
|
...workflowDefaults,
|
|
4473
|
-
|
|
4543
|
+
...(options.externalAsyncRequired === true && asyncOmitted ? { async: true } : {}),
|
|
4474
4544
|
...childParams,
|
|
4475
4545
|
...(control !== undefined ? { control } : {}),
|
|
4476
|
-
...(
|
|
4546
|
+
...(asyncOmitted ? { workflowAwaitAsync: true } : {}),
|
|
4477
4547
|
...(options.missionDetached ? { mission: false } : {}),
|
|
4478
4548
|
workflowParentRunId: parentWorkflowRunId,
|
|
4479
4549
|
workflowKey,
|
|
@@ -4769,8 +4839,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4769
4839
|
workflowResource = { permit: workflowResourcePermit, ...consumed };
|
|
4770
4840
|
}
|
|
4771
4841
|
if (requestParams.workflowScript !== undefined && normalizedAction === undefined) {
|
|
4842
|
+
const foregroundWorkflowRunId = encodeIndexSegment(_id);
|
|
4772
4843
|
if (delegatedWorkflowPermit) {
|
|
4773
|
-
const permitError = validateWorkflowChildPermitRoot(delegatedWorkflowPermit,
|
|
4844
|
+
const permitError = validateWorkflowChildPermitRoot(delegatedWorkflowPermit, foregroundWorkflowRunId);
|
|
4774
4845
|
if (permitError) return buildRequestedModeError(requestParams, permitError);
|
|
4775
4846
|
if (requestParams.async !== false) return buildRequestedModeError(requestParams, "Workflow child permit supports foreground workflow roots only; set async:false.");
|
|
4776
4847
|
}
|
|
@@ -4817,11 +4888,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4817
4888
|
const scriptFirstLine = requestParams.workflowScript.split(/\r?\n/).map((line) => line.trim()).find(Boolean) || "Workflow";
|
|
4818
4889
|
const boundedScriptPreview = scriptFirstLine.length > 100 ? `${scriptFirstLine.slice(0, 97)}...` : scriptFirstLine;
|
|
4819
4890
|
const derivedObjective = previewAgent ? `Workflow: ${previewAgent}` : boundedScriptPreview;
|
|
4820
|
-
const workflowDepth = checkSubagentDepth(deps.config.maxSubagentDepth).depth;
|
|
4891
|
+
const workflowDepth = checkSubagentDepth(deps.config.maxSubagentDepth, deps.childRuntime).depth;
|
|
4821
4892
|
const asyncWorkflow = requestParams.async !== false;
|
|
4822
4893
|
const topLevelAsyncWorkflow = asyncWorkflow
|
|
4823
4894
|
&& workflowDepth === 0
|
|
4824
|
-
&& !
|
|
4895
|
+
&& !inheritedNestedRoute(deps)
|
|
4825
4896
|
&& !requestParams.workflowParentRunId;
|
|
4826
4897
|
const workflowRunId = asyncWorkflow ? randomUUID() : undefined;
|
|
4827
4898
|
let workflowCapacity: ActiveAsyncCapacityHandle | undefined;
|
|
@@ -4846,7 +4917,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4846
4917
|
let workflowFanoutBudget: RunFanoutBudgetDescriptor;
|
|
4847
4918
|
try {
|
|
4848
4919
|
workflowFanoutBudget = requestParams.runFanoutBudget
|
|
4849
|
-
??
|
|
4920
|
+
?? inheritedRunFanoutBudget(deps)
|
|
4850
4921
|
?? createRunFanoutBudget(_id, requestParams.maxSubagentSpawnsPerRun ?? resolveMaxSubagentSpawnsPerRun(deps.config.maxSubagentSpawnsPerRun));
|
|
4851
4922
|
} catch (error) {
|
|
4852
4923
|
workflowCapacity?.rollback();
|
|
@@ -5205,6 +5276,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5205
5276
|
globalConcurrencyLimit: requestParams.globalConcurrencyLimit ?? deps.config.globalConcurrencyLimit,
|
|
5206
5277
|
timeoutMs: timeout,
|
|
5207
5278
|
signal: controller.signal,
|
|
5279
|
+
continueAfterAbortWhenChildrenSettled: (abortError) => {
|
|
5280
|
+
if (abortError.message !== "Workflow stopped because the extension session was replaced or reloaded.") return false;
|
|
5281
|
+
const activeAsyncChild = [...(deps.state.asyncJobs?.values() ?? [])].some((job) => job.parentWorkflowRunId === workflowRunId && (job.status === "queued" || job.status === "running"));
|
|
5282
|
+
const activeForegroundChild = [...deps.state.foregroundControls.values()].some((control) => control.parentWorkflowRunId === workflowRunId && (control.activeChildren?.size ?? 0) > 0);
|
|
5283
|
+
return !activeAsyncChild && !activeForegroundChild;
|
|
5284
|
+
},
|
|
5208
5285
|
registerStopChild: (stop) => {
|
|
5209
5286
|
if (stop) deps.state.workflowChildStops?.set(workflowRunId, stop);
|
|
5210
5287
|
else deps.state.workflowChildStops?.delete(workflowRunId);
|
|
@@ -5307,7 +5384,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5307
5384
|
if (child.runId) workflowChildRunIds.set(key, child.runId);
|
|
5308
5385
|
const step = status.steps?.find((candidate) => candidate.workflowKey === key);
|
|
5309
5386
|
if (step) {
|
|
5310
|
-
step.async = Boolean(result.details.asyncId || result.details.asyncDir);
|
|
5387
|
+
step.async = step.async === true || Boolean(result.details.asyncId || result.details.asyncDir);
|
|
5311
5388
|
if (child.runId) step.runId = child.runId;
|
|
5312
5389
|
if (child.lane) step.lane = child.lane;
|
|
5313
5390
|
}
|
|
@@ -5416,7 +5493,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5416
5493
|
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, preflight: _preflight, globalConcurrencyLimit: _globalConcurrencyLimit, maxSubagentSpawnsPerRun: _maxSubagentSpawnsPerRun, ...workflowChildDefaults } = requestParams;
|
|
5417
5494
|
const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
|
|
5418
5495
|
const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
|
|
5419
|
-
const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir,
|
|
5496
|
+
const workflowAggregateOutputPath = resolveWorkflowAggregateOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, foregroundWorkflowRunId));
|
|
5420
5497
|
const claimedOutputPaths = new Map<string, string>();
|
|
5421
5498
|
const childOutputOverrides = new Map<string, string>();
|
|
5422
5499
|
const childOutputClaimPaths = new Map<string, string>();
|
|
@@ -5426,7 +5503,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5426
5503
|
const runHostCommand = workflowHostCommandRunner({
|
|
5427
5504
|
workflowCwd,
|
|
5428
5505
|
artifactsDir: workflowArtifactsDir,
|
|
5429
|
-
workflowRunId:
|
|
5506
|
+
workflowRunId: foregroundWorkflowRunId,
|
|
5430
5507
|
claimedOutputPaths,
|
|
5431
5508
|
producedOutputPaths: producedChildOutputPaths,
|
|
5432
5509
|
...(workflowResource ? { authorize: (key, params) => authorizeWorkflowResourceHost(workflowResource!.permit, key, params.command) } : {}),
|
|
@@ -5438,17 +5515,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5438
5515
|
: publicExecution ? undefined : runHostCommand;
|
|
5439
5516
|
const workflowHostSteps = new Map<string, HostStepNodeV1>();
|
|
5440
5517
|
let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [], ...(workflowResource ? { resource: workflowResource.provenance } : {}) };
|
|
5441
|
-
let liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId:
|
|
5518
|
+
let liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: foregroundWorkflowRunId, workflowState: "running", inventoryComplete: false });
|
|
5442
5519
|
const workflowDeadlineAt = timeout === undefined ? undefined : Date.now() + timeout;
|
|
5443
5520
|
const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(resolveCurrentSessionId(ctx.sessionManager)));
|
|
5444
5521
|
const sendWorkflowProgress = () => {
|
|
5445
|
-
const update = workflowChatProgressUpdate(
|
|
5522
|
+
const update = workflowChatProgressUpdate(foregroundWorkflowRunId, chatProgress, liveWorkflow, liveWorkflowChildren, workflowPreflight);
|
|
5446
5523
|
if (update) onUpdate?.(update);
|
|
5447
5524
|
};
|
|
5448
5525
|
try {
|
|
5449
5526
|
const workflow = await runWorkflowScript({
|
|
5450
5527
|
script: requestParams.workflowScript,
|
|
5451
|
-
...(delegatedWorkflowPermit ? { oneUsePermit: { claim: (key: string) => claimWorkflowChildPermit(delegatedWorkflowPermit,
|
|
5528
|
+
...(delegatedWorkflowPermit ? { oneUsePermit: { claim: (key: string) => claimWorkflowChildPermit(delegatedWorkflowPermit, foregroundWorkflowRunId, key) } } : {}),
|
|
5452
5529
|
globalConcurrencyLimit: requestParams.globalConcurrencyLimit ?? deps.config.globalConcurrencyLimit,
|
|
5453
5530
|
timeoutMs: timeout,
|
|
5454
5531
|
signal,
|
|
@@ -5461,11 +5538,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5461
5538
|
const projectedTrace = annotateWorkflowPreflightTrace(trace, workflowPreflight);
|
|
5462
5539
|
const preflightWarnings = workflowPreflightWarnings(workflowPreflight, trace);
|
|
5463
5540
|
liveWorkflow = { ...liveWorkflow, trace: projectedTrace, ...(preflightWarnings.length ? { preflightWarnings } : {}) };
|
|
5464
|
-
liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId:
|
|
5541
|
+
liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: foregroundWorkflowRunId, workflowState: "running", inventoryComplete: false, trace });
|
|
5465
5542
|
sendWorkflowProgress();
|
|
5466
5543
|
},
|
|
5467
5544
|
admit: (calls) => {
|
|
5468
|
-
const outputClaims = workflowChildOutputClaims({ ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId:
|
|
5545
|
+
const outputClaims = workflowChildOutputClaims({ ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId: foregroundWorkflowRunId, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: discoverWorkflowAgents, agents: workflowAgents, workflowAgentScope: workflowChildDefaults.agentScope, state: deps.state, claimedOutputPaths, entries: calls });
|
|
5469
5546
|
if (outputClaims.error) throw new Error(outputClaims.error);
|
|
5470
5547
|
claimRunFanoutBatch(workflowFanoutBudget, calls.map(({ key }) => `workflow[${key}]`));
|
|
5471
5548
|
if (outputClaims.claims) applyWorkflowChildOutputClaims(claimedOutputPaths, outputClaims.claims);
|
|
@@ -5485,7 +5562,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5485
5562
|
if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)), childParams, deps.state, { state: "partial", reason: "budget_exhausted" });
|
|
5486
5563
|
const childPhase = typeof childParams.phase === "string" && childParams.phase.trim() ? childParams.phase.trim() : undefined;
|
|
5487
5564
|
const childLabel = typeof childParams.label === "string" && childParams.label.trim() ? childParams.label.trim() : undefined;
|
|
5488
|
-
recordMissionWorkflowChild(missionBinding,
|
|
5565
|
+
recordMissionWorkflowChild(missionBinding, foregroundWorkflowRunId, key, {
|
|
5489
5566
|
status: "running",
|
|
5490
5567
|
...(typeof childParams.agent === "string" && childParams.agent.trim() ? { agent: childParams.agent.trim() } : {}),
|
|
5491
5568
|
...(childLabel ? { label: childLabel } : {}),
|
|
@@ -5493,9 +5570,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5493
5570
|
heartbeat: { status: "running", ...(childPhase ? { phase: childPhase } : {}) },
|
|
5494
5571
|
});
|
|
5495
5572
|
let preparedChildParams: SubagentParamsLike | undefined;
|
|
5496
|
-
const result = await runMissionWorkflowChild(missionBinding,
|
|
5573
|
+
const result = await runMissionWorkflowChild(missionBinding, foregroundWorkflowRunId, key, childPhase, () => {
|
|
5497
5574
|
const childRequest = bindMissionWorkflowChildAsyncLaunch(
|
|
5498
|
-
{ ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams, parentWorkflowRunId:
|
|
5575
|
+
{ ...prepareWorkflowChildLaunchParams({ workflowDefaults: workflowChildDefaults, childParams: delegatedWorkflowPermit ? { ...childParams, async: false } : childParams, parentWorkflowRunId: foregroundWorkflowRunId, workflowKey: key, ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: discoverWorkflowAgents, agents: workflowAgents, workflowAgentScope: workflowChildDefaults.agentScope, outputOverride: childOutputOverrides.get(key), outputClaimPath: childOutputClaimPaths.get(key), options: { missionDetached: detachWorkflowChildMissions, suppressRoutineResultIntercom: chatProgress.mode === "live-card", runFanoutBudget: workflowFanoutBudget, parentDeadlineAt: workflowDeadlineAt, capabilityCeiling: workflowCapabilityCeiling } }), runFanoutAdmitted: admission.admitted },
|
|
5499
5576
|
missionBinding,
|
|
5500
5577
|
deps.asyncByDefault,
|
|
5501
5578
|
);
|
|
@@ -5507,9 +5584,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5507
5584
|
? resolveAgentName(childRequest.agent, discoverWorkflowAgents(childCwd, resolveExecutionAgentScope(childRequest.agentScope)).agents).agent
|
|
5508
5585
|
: undefined;
|
|
5509
5586
|
if (childAgent?.runner?.type === "external-cli" || childAgent?.runner?.type === "external-job") throw new Error("Workflow child permit supports native Pi children only.");
|
|
5510
|
-
workflowPermitContexts.set(childRequest, { child: { permit: delegatedWorkflowPermit, workflowRunId:
|
|
5587
|
+
workflowPermitContexts.set(childRequest, { child: { permit: delegatedWorkflowPermit, workflowRunId: foregroundWorkflowRunId, childKey: key } });
|
|
5511
5588
|
}
|
|
5512
|
-
workflowLaunchObservers.set(childRequest, (launch) => recordMissionWorkflowChild(missionBinding,
|
|
5589
|
+
workflowLaunchObservers.set(childRequest, (launch) => recordMissionWorkflowChild(missionBinding, foregroundWorkflowRunId, key, {
|
|
5513
5590
|
status: "running",
|
|
5514
5591
|
agent: launch.agent,
|
|
5515
5592
|
...(launch.sessionFile ? { sessionPath: launch.sessionFile } : {}),
|
|
@@ -5518,7 +5595,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5518
5595
|
const progress = update.details.progress?.[0];
|
|
5519
5596
|
if (!progress) return;
|
|
5520
5597
|
const progressStatus = progress.status === "completed" ? "completed" : progress.status === "failed" ? "failed" : "running";
|
|
5521
|
-
recordMissionWorkflowChild(missionBinding,
|
|
5598
|
+
recordMissionWorkflowChild(missionBinding, foregroundWorkflowRunId, key, {
|
|
5522
5599
|
status: progressStatus,
|
|
5523
5600
|
heartbeat: { status: progressStatus, ...(childPhase ? { phase: childPhase } : {}) },
|
|
5524
5601
|
});
|
|
@@ -5532,7 +5609,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5532
5609
|
const child = workflowChildResult(key, result, preparedChildParams ?? childParams, deps.state);
|
|
5533
5610
|
if (child.runId) workflowChildRunIds.set(key, child.runId);
|
|
5534
5611
|
const childStatus = missionWorkflowChildStatus(result);
|
|
5535
|
-
recordMissionWorkflowChild(missionBinding,
|
|
5612
|
+
recordMissionWorkflowChild(missionBinding, foregroundWorkflowRunId, key, {
|
|
5536
5613
|
status: childStatus,
|
|
5537
5614
|
...(child.runId ? { runId: child.runId } : {}),
|
|
5538
5615
|
...(result.details.results[0]?.agent ? { agent: result.details.results[0].agent } : {}),
|
|
@@ -5545,12 +5622,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5545
5622
|
},
|
|
5546
5623
|
status: async (keyOrRunId, workflowSignal) => workflowChildResult(keyOrRunId, await execute(randomUUID(), { action: "status", id: keyOrRunId }, workflowSignal, undefined, ctx, preserveActiveSession, workflowParentModel)),
|
|
5547
5624
|
resolveResume: (reference) => resolveKeyedWorkflowResume(reference, deps.state),
|
|
5548
|
-
steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId:
|
|
5625
|
+
steer: (key, message, options, workflowSignal) => steerWorkflowChildByKey({ state: deps.state, workflowRunId: foregroundWorkflowRunId, key, message, options, signal: workflowSignal, resolveRunId: () => workflowChildRunIds.get(key) }),
|
|
5549
5626
|
});
|
|
5550
5627
|
const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, workflow.trace, { settled: true });
|
|
5551
5628
|
const finalPreflightTrace = annotateWorkflowPreflightTrace(workflow.trace, workflowPreflight);
|
|
5552
|
-
const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId:
|
|
5553
|
-
const receipt = terminalWorkflowReceipt(
|
|
5629
|
+
const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: foregroundWorkflowRunId, workflowState: "completed", inventoryComplete: true, trace: workflow.trace, children: workflow.children });
|
|
5630
|
+
const receipt = terminalWorkflowReceipt(foregroundWorkflowRunId, "complete", workflow.children, workflowChildren, undefined, [...workflowHostSteps.values()], workflowResource?.provenance);
|
|
5554
5631
|
const traceLines = finalPreflightTrace.map((entry) => `- ${entry.operation} ${entry.key}: ${entry.state}${entry.runId ? ` (${entry.runId})` : ""}${entry.durationMs !== undefined ? ` in ${entry.durationMs}ms` : ""}${entry.warning ? ` — ${entry.warning}` : ""}${entry.error ? ` — ${entry.error}` : ""}`);
|
|
5555
5632
|
const sections = [
|
|
5556
5633
|
...(workflowPreflight ? [formatWorkflowPreflight(workflowPreflight)] : []),
|
|
@@ -5568,11 +5645,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5568
5645
|
const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
|
|
5569
5646
|
return attachWorkflowMission(withRunFanoutBudget({
|
|
5570
5647
|
content: [{ type: "text", text: displayText }],
|
|
5571
|
-
details: compactOptional<Details>({ mode: "workflow", runId:
|
|
5648
|
+
details: compactOptional<Details>({ mode: "workflow", runId: foregroundWorkflowRunId, results: workflowDetailsResults(workflow.children), ...(workflowPreflight ? { preflight: workflowPreflight } : {}), workflowChildren, totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { value: workflow.value, trace: finalPreflightTrace, emits: workflow.emits, console: workflow.console, ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}), receipt }, chatProgress }),
|
|
5572
5649
|
}, workflowFanoutBudget));
|
|
5573
5650
|
} catch (error) {
|
|
5574
5651
|
const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
|
|
5575
|
-
const text = workflowFailureMessage(error,
|
|
5652
|
+
const text = workflowFailureMessage(error, foregroundWorkflowRunId, partial.children);
|
|
5576
5653
|
const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, partial.trace, { settled: true });
|
|
5577
5654
|
const finalPreflightTrace = annotateWorkflowPreflightTrace(partial.trace, workflowPreflight);
|
|
5578
5655
|
const traceLines = finalPreflightTrace.map((entry) => `- ${entry.operation} ${entry.key}: ${entry.state}${entry.runId ? ` (${entry.runId})` : ""}${entry.warning ? ` — ${entry.warning}` : ""}${entry.error ? ` — ${entry.error}` : ""}`);
|
|
@@ -5589,13 +5666,13 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5589
5666
|
const workflowText = sections.join("\n\n");
|
|
5590
5667
|
const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText, producedChildOutputPaths);
|
|
5591
5668
|
const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
|
|
5592
|
-
const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId:
|
|
5669
|
+
const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: foregroundWorkflowRunId, workflowState: "failed", inventoryComplete: true, trace: partial.trace, children: partial.children });
|
|
5593
5670
|
const terminalOutcome = workflowFailureTerminalOutcome(error, partial.children, usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)));
|
|
5594
|
-
const receipt = terminalWorkflowReceipt(
|
|
5671
|
+
const receipt = terminalWorkflowReceipt(foregroundWorkflowRunId, "failed", partial.children, workflowChildren, terminalOutcome, [...workflowHostSteps.values()], workflowResource?.provenance);
|
|
5595
5672
|
return attachWorkflowMission(withRunFanoutBudget({
|
|
5596
5673
|
content: [{ type: "text", text: displayText }],
|
|
5597
5674
|
isError: true,
|
|
5598
|
-
details: compactOptional<Details>({ mode: "workflow", runId:
|
|
5675
|
+
details: compactOptional<Details>({ mode: "workflow", runId: foregroundWorkflowRunId, results: workflowDetailsResults(partial.children), ...(workflowPreflight ? { preflight: workflowPreflight } : {}), workflowChildren, totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { trace: finalPreflightTrace, emits: partial.emits, console: partial.console, ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}), receipt }, chatProgress }),
|
|
5599
5676
|
}, workflowFanoutBudget));
|
|
5600
5677
|
}
|
|
5601
5678
|
}
|
|
@@ -5992,7 +6069,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5992
6069
|
if (directoryStatus?.mode === "workflow") {
|
|
5993
6070
|
const route = resolveWorkflowForegroundSteeringTarget({ state: deps.state, workflowRunId: directoryStatus.runId || runId, asyncDirRoot: DIRS.async });
|
|
5994
6071
|
if (!route.ok) return { content: [{ type: "text", text: route.message }], isError: true, details: { mode: "management", results: [] } };
|
|
5995
|
-
return steerWorkflowForegroundTarget({ target: route.target, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index
|
|
6072
|
+
return steerWorkflowForegroundTarget({ target: route.target, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index });
|
|
5996
6073
|
}
|
|
5997
6074
|
if (location.asyncDir) {
|
|
5998
6075
|
const unsupported = externalRunnerControlError(location.asyncDir, "steer");
|
|
@@ -6032,14 +6109,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6032
6109
|
if (resolved?.kind === "foreground") {
|
|
6033
6110
|
const route = resolveWorkflowForegroundSteeringTarget({ state: deps.state, childRunId: resolved.id, asyncDirRoot: DIRS.async });
|
|
6034
6111
|
if (!route.ok) return { content: [{ type: "text", text: route.message }], isError: true, details: { mode: "management", results: [] } };
|
|
6035
|
-
return steerWorkflowForegroundTarget({ target: route.target, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index
|
|
6112
|
+
return steerWorkflowForegroundTarget({ target: route.target, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index });
|
|
6036
6113
|
}
|
|
6037
6114
|
if (resolved?.kind !== "async") return { content: [{ type: "text", text: `No async run found for '${targetRunId}'.` }], isError: true, details: { mode: "management", results: [] } };
|
|
6038
6115
|
const resolvedStatus = resolved.location.asyncDir ? readStatus(resolved.location.asyncDir) : null;
|
|
6039
6116
|
if (resolvedStatus?.mode === "workflow") {
|
|
6040
6117
|
const route = resolveWorkflowForegroundSteeringTarget({ state: deps.state, workflowRunId: resolvedStatus.runId || resolved.id, asyncDirRoot: DIRS.async });
|
|
6041
6118
|
if (!route.ok) return { content: [{ type: "text", text: route.message }], isError: true, details: { mode: "management", results: [] } };
|
|
6042
|
-
return steerWorkflowForegroundTarget({ target: route.target, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index
|
|
6119
|
+
return steerWorkflowForegroundTarget({ target: route.target, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index });
|
|
6043
6120
|
}
|
|
6044
6121
|
if (resolved.location.asyncDir) {
|
|
6045
6122
|
const unsupported = externalRunnerControlError(resolved.location.asyncDir, "steer");
|
|
@@ -6118,9 +6195,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6118
6195
|
if (action === "stop") {
|
|
6119
6196
|
const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
|
|
6120
6197
|
const workflowController = targetRunId ? deps.state.workflowControllers?.get(targetRunId) : undefined;
|
|
6121
|
-
if (workflowController) {
|
|
6198
|
+
if (workflowController && targetRunId) {
|
|
6199
|
+
const stopChild = deps.state.workflowChildStops?.get(targetRunId);
|
|
6122
6200
|
if (paramsWithResolvedCwd.childId !== undefined) {
|
|
6123
|
-
const workflowRunId = targetRunId
|
|
6201
|
+
const workflowRunId = targetRunId;
|
|
6124
6202
|
const asyncJob = deps.state.asyncJobs.get(workflowRunId);
|
|
6125
6203
|
if (!asyncJob?.asyncDir) return { content: [{ type: "text", text: `Status file not found for async workflow '${workflowRunId}'.` }], isError: true, details: { mode: "management", results: [] } };
|
|
6126
6204
|
const status = readStatus(asyncJob.asyncDir);
|
|
@@ -6128,7 +6206,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6128
6206
|
const resolution = resolveAsyncStatusChild(status, paramsWithResolvedCwd.childId);
|
|
6129
6207
|
if (!resolution.ok) return { content: [{ type: "text", text: resolution.message }], isError: true, details: { mode: "management", results: [] } };
|
|
6130
6208
|
if (!isStoppableAsyncStatusStep(resolution.child.step)) return { content: [{ type: "text", text: `Child '${paramsWithResolvedCwd.childId}' in async run '${targetRunId}' is ${resolution.child.step.status}; stop only supports pending or running children.` }], isError: true, details: { mode: "management", results: [] } };
|
|
6131
|
-
const stopChild = deps.state.workflowChildStops?.get(targetRunId!);
|
|
6132
6209
|
if (!stopChild) return { content: [{ type: "text", text: `Workflow ${targetRunId} child stop is unavailable in this extension runtime.` }], isError: true, details: { mode: "management", results: [] } };
|
|
6133
6210
|
if (!stopChild(resolution.child.id, `Workflow child '${resolution.child.id}' stopped.`)) return { content: [{ type: "text", text: `Child '${paramsWithResolvedCwd.childId}' in workflow ${workflowRunId} is not available to stop.` }], isError: true, details: { mode: "management", results: [] } };
|
|
6134
6211
|
try {
|
|
@@ -6153,6 +6230,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6153
6230
|
}
|
|
6154
6231
|
return { content: [{ type: "text", text: `Stop requested for child ${resolution.child.id} in async workflow ${workflowRunId}.` }], details: { mode: "management", results: [] } };
|
|
6155
6232
|
}
|
|
6233
|
+
const asyncJob = deps.state.asyncJobs.get(targetRunId);
|
|
6234
|
+
const status = asyncJob?.asyncDir ? readStatus(asyncJob.asyncDir) : undefined;
|
|
6235
|
+
if (status) stopStoppableAsyncStatusChildren(status, stopChild, "Workflow stopped.");
|
|
6156
6236
|
workflowController.abort(new Error("Workflow stopped."));
|
|
6157
6237
|
return { content: [{ type: "text", text: `Stop requested for async workflow ${targetRunId}.` }], details: { mode: "management", results: [] } };
|
|
6158
6238
|
}
|
|
@@ -6248,7 +6328,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6248
6328
|
});
|
|
6249
6329
|
}
|
|
6250
6330
|
|
|
6251
|
-
const { blocked, depth, maxDepth } = checkSubagentDepth(deps.config.maxSubagentDepth);
|
|
6331
|
+
const { blocked, depth, maxDepth } = checkSubagentDepth(deps.config.maxSubagentDepth, deps.childRuntime);
|
|
6252
6332
|
if (blocked) {
|
|
6253
6333
|
return {
|
|
6254
6334
|
content: [
|
|
@@ -6323,8 +6403,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6323
6403
|
});
|
|
6324
6404
|
const agents = applyScopedIntercomBridgeToAgents(discoveredAgents, intercomBridge, contextPolicy);
|
|
6325
6405
|
const runId = randomUUID();
|
|
6326
|
-
const
|
|
6327
|
-
const nestedParentAddress =
|
|
6406
|
+
const inheritedNestedRouteValue = inheritedNestedRoute(deps);
|
|
6407
|
+
const nestedParentAddress = inheritedNestedRouteValue ? inheritedNestedParentAddress(deps) : undefined;
|
|
6328
6408
|
const shareEnabled = effectiveParams.share === true;
|
|
6329
6409
|
const hasChain = (effectiveParams.chain?.length ?? 0) > 0;
|
|
6330
6410
|
const hasTasks = (effectiveParams.tasks?.length ?? 0) > 0;
|
|
@@ -6445,7 +6525,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6445
6525
|
const asyncRunId = requestedWorkflowChildAsyncId && path.basename(requestedWorkflowChildAsyncId) === requestedWorkflowChildAsyncId
|
|
6446
6526
|
? requestedWorkflowChildAsyncId
|
|
6447
6527
|
: randomUUID();
|
|
6448
|
-
const topLevelAsyncCapacityEligible = depth === 0 && !
|
|
6528
|
+
const topLevelAsyncCapacityEligible = depth === 0 && !inheritedNestedRouteValue && !effectiveParams.workflowParentRunId;
|
|
6449
6529
|
const topLevelAsync = effectiveAsync && topLevelAsyncCapacityEligible;
|
|
6450
6530
|
let activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined;
|
|
6451
6531
|
if (topLevelAsync) {
|
|
@@ -6470,9 +6550,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6470
6550
|
|
|
6471
6551
|
let runFanoutBudget: RunFanoutBudgetDescriptor;
|
|
6472
6552
|
try {
|
|
6473
|
-
const
|
|
6553
|
+
const inheritedRunFanoutBudgetValue = effectiveParams.runFanoutBudget ? undefined : inheritedRunFanoutBudget(deps);
|
|
6474
6554
|
runFanoutBudget = effectiveParams.runFanoutBudget
|
|
6475
|
-
?? (
|
|
6555
|
+
?? (inheritedRunFanoutBudgetValue ? { ...inheritedRunFanoutBudgetValue, parentPath: `${inheritedRunFanoutBudgetValue.parentPath ? `${inheritedRunFanoutBudgetValue.parentPath}/` : ""}${runId}` } : undefined)
|
|
6476
6556
|
?? createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(deps.config.maxSubagentSpawnsPerRun));
|
|
6477
6557
|
if (!effectiveParams.runFanoutAdmitted) claimRunFanoutBatch(runFanoutBudget, staticRunFanoutPaths(effectiveParams));
|
|
6478
6558
|
} catch (error) {
|
|
@@ -6480,7 +6560,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6480
6560
|
if (error instanceof RunFanoutLimitError) return runFanoutErrorResult(error, foregroundMode);
|
|
6481
6561
|
return buildRequestedModeError(effectiveParams, error instanceof Error ? error.message : String(error));
|
|
6482
6562
|
}
|
|
6483
|
-
const nestedRoute =
|
|
6563
|
+
const nestedRoute = inheritedNestedRouteValue ?? createNestedRoute(runId);
|
|
6484
6564
|
|
|
6485
6565
|
const artifactConfig: ArtifactConfig = omitUndefinedProperties({
|
|
6486
6566
|
...DEFAULT_ARTIFACT_CONFIG,
|
|
@@ -6496,7 +6576,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6496
6576
|
|
|
6497
6577
|
let sessionRoot: string;
|
|
6498
6578
|
if (effectiveParams.sessionDir) {
|
|
6499
|
-
|
|
6579
|
+
// An explicit sessionDir is a root keyed by this launch's run id so
|
|
6580
|
+
// concurrent children resolve distinct per-child session files.
|
|
6581
|
+
sessionRoot = path.join(path.resolve(deps.expandTilde(effectiveParams.sessionDir)), runId);
|
|
6500
6582
|
} else {
|
|
6501
6583
|
const baseSessionRoot = deps.config.defaultSessionDir
|
|
6502
6584
|
? path.resolve(deps.expandTilde(deps.config.defaultSessionDir))
|
|
@@ -6652,17 +6734,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6652
6734
|
const foregroundDescription = selectedAgentNames.length === 1
|
|
6653
6735
|
? `${selectedAgentNames[0]} child`
|
|
6654
6736
|
: `${selectedAgentNames.length} live children`;
|
|
6655
|
-
const parentWorkflowStatus = effectiveParams.workflowParentRunId
|
|
6656
|
-
? readStatus(path.join(DIRS.async, effectiveParams.workflowParentRunId))
|
|
6657
|
-
: null;
|
|
6658
|
-
const workflowSteeringDir = effectiveParams.workflowParentRunId
|
|
6659
|
-
&& requestSessionId
|
|
6660
|
-
&& deps.state.workflowControllers?.has(effectiveParams.workflowParentRunId)
|
|
6661
|
-
&& parentWorkflowStatus?.mode === "workflow"
|
|
6662
|
-
&& (parentWorkflowStatus.state === "running" || parentWorkflowStatus.state === "queued")
|
|
6663
|
-
&& parentWorkflowStatus.sessionId === requestSessionId
|
|
6664
|
-
? workflowForegroundSteeringDir(DIRS.async, effectiveParams.workflowParentRunId, runId)
|
|
6665
|
-
: undefined;
|
|
6666
6737
|
const foregroundControl: ForegroundRunControl | undefined = effectiveAsync
|
|
6667
6738
|
? undefined
|
|
6668
6739
|
: compactOptional<ForegroundRunControl>({
|
|
@@ -6671,7 +6742,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6671
6742
|
mode: foregroundMode,
|
|
6672
6743
|
...(effectiveParams.workflowParentRunId ? { parentWorkflowRunId: effectiveParams.workflowParentRunId } : {}),
|
|
6673
6744
|
...(effectiveParams.workflowKey ? { workflowKey: effectiveParams.workflowKey } : {}),
|
|
6674
|
-
workflowSteeringDir,
|
|
6675
6745
|
startedAt: Date.now(),
|
|
6676
6746
|
updatedAt: Date.now(),
|
|
6677
6747
|
cwd: effectiveCwd,
|
|
@@ -6724,7 +6794,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6724
6794
|
}
|
|
6725
6795
|
|
|
6726
6796
|
const writeNestedForegroundEvent = (type: "subagent.nested.started" | "subagent.nested.completed", result?: AgentToolResult<Details>): void => {
|
|
6727
|
-
if (!
|
|
6797
|
+
if (!inheritedNestedRouteValue || !nestedParentAddress) return;
|
|
6728
6798
|
const now = Date.now();
|
|
6729
6799
|
const details = result?.details;
|
|
6730
6800
|
const state = type === "subagent.nested.started"
|
|
@@ -6758,7 +6828,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6758
6828
|
? resolveSubagentIntercomTarget(runId, agentsForSummary[0], 0)
|
|
6759
6829
|
: undefined;
|
|
6760
6830
|
try {
|
|
6761
|
-
writeNestedEvent(
|
|
6831
|
+
writeNestedEvent(inheritedNestedRouteValue, compactOptional<Parameters<typeof writeNestedEvent>[1]>({
|
|
6762
6832
|
type,
|
|
6763
6833
|
ts: now,
|
|
6764
6834
|
parentRunId: nestedParentAddress.parentRunId,
|
|
@@ -6769,7 +6839,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6769
6839
|
parentStepIndex: nestedParentAddress.parentStepIndex,
|
|
6770
6840
|
depth: nestedParentAddress.depth,
|
|
6771
6841
|
path: nestedParentAddress.path,
|
|
6772
|
-
ownerIntercomTarget:
|
|
6842
|
+
ownerIntercomTarget: deps.childRuntime?.intercomSessionName,
|
|
6773
6843
|
leafIntercomTarget,
|
|
6774
6844
|
intercomTarget: leafIntercomTarget,
|
|
6775
6845
|
ownerState: state === "running" ? "live" : "gone",
|
|
@@ -6859,7 +6929,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6859
6929
|
if (activeAsyncCapacity && !activeAsyncCapacity.owner.runnerStartedAt) activeAsyncCapacity.rollback();
|
|
6860
6930
|
if (foregroundControl) {
|
|
6861
6931
|
settleForegroundSchedulingOwner(foregroundControl);
|
|
6862
|
-
removeForegroundControlIfIdle(deps.state, runId);
|
|
6932
|
+
removeForegroundControlIfIdle(deps.state, runId, deps.trackRetainedNestedRoute);
|
|
6863
6933
|
}
|
|
6864
6934
|
}
|
|
6865
6935
|
|
|
@@ -6880,7 +6950,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6880
6950
|
const normalizedAction = typeof params.action === "string" ? params.action.trim() : params.action;
|
|
6881
6951
|
const requestParams = normalizedAction ? { ...params, action: normalizedAction } : params;
|
|
6882
6952
|
if (normalizedAction) return execute(id, requestParams, signal, onUpdate, ctx).then(withAggregatedToolUsage);
|
|
6883
|
-
const { depth } = checkSubagentDepth(deps.config.maxSubagentDepth);
|
|
6953
|
+
const { depth } = checkSubagentDepth(deps.config.maxSubagentDepth, deps.childRuntime);
|
|
6884
6954
|
const dispatchParams = applyForceTopLevelAsyncOverride(requestParams, depth, deps.config.forceTopLevelAsync === true);
|
|
6885
6955
|
const runsForeground = dispatchParams.clarify === true || (dispatchParams.async ?? deps.asyncByDefault) !== true;
|
|
6886
6956
|
if (!runsForeground) return execute(id, requestParams, signal, onUpdate, ctx).then(withAggregatedToolUsage);
|