pi-subagents 0.63.0 → 0.65.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +57 -1
- package/README.md +2 -2
- package/agents/reviewer.md +1 -1
- package/agents/scout.md +1 -1
- package/docs/agents.md +18 -16
- package/docs/configuration.md +7 -15
- package/docs/extension-api.md +15 -6
- package/docs/missions.md +2 -0
- package/docs/observability.md +10 -12
- package/docs/tool-reference.md +7 -6
- package/docs/watchdog.md +92 -114
- package/docs/workflows.md +5 -3
- package/package.json +3 -4
- package/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
- package/skills/pi-subagents/references/execution-controls.md +6 -5
- 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 +2 -3
- package/src/extension/doctor.ts +2 -10
- package/src/extension/fanout-child.ts +9 -11
- package/src/extension/index.ts +27 -5
- package/src/extension/public-execution.ts +14 -0
- package/src/extension/rpc.ts +3 -2
- package/src/extension/schemas.ts +2 -1
- package/src/extension/tool-description.ts +9 -8
- package/src/intercom/native-supervisor-channel.ts +138 -60
- package/src/intercom/supervisor-ui.ts +243 -0
- package/src/runs/background/async-execution.ts +46 -24
- package/src/runs/background/async-job-tracker.ts +11 -0
- package/src/runs/background/async-resume.ts +11 -2
- package/src/runs/background/control-channel.ts +2 -204
- package/src/runs/background/notify.ts +41 -2
- package/src/runs/background/process-terminal.ts +1 -1
- package/src/runs/background/run-child-session.ts +613 -0
- package/src/runs/background/run-status.ts +0 -1
- package/src/runs/background/runner-aliases.ts +125 -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 -898
- package/src/runs/foreground/async-steering-action.ts +1 -17
- package/src/runs/foreground/execution.ts +196 -378
- package/src/runs/foreground/foreground-control.ts +4 -0
- package/src/runs/foreground/subagent-executor.ts +121 -58
- 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 +10 -0
- package/src/runs/shared/async-status-projection.ts +11 -43
- 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 +342 -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/mcp-direct-tool-allowlist.ts +5 -4
- package/src/runs/shared/model-exclusions.ts +82 -14
- package/src/runs/shared/model-fallback.ts +47 -4
- package/src/runs/shared/nested-events.ts +29 -45
- 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 +0 -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 +6 -2
- 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 +25 -6
- package/src/runs/shared/worktree.ts +117 -30
- package/src/shared/child-session-name.ts +1 -1
- package/src/shared/jsonl-writer.ts +11 -0
- package/src/shared/thinking-ceiling.ts +0 -6
- package/src/shared/types.ts +64 -30
- package/src/shared/utils.ts +3 -4
- package/src/slash/slash-commands.ts +0 -6
- package/src/tui/fleet.ts +0 -1
- package/src/tui/render.ts +238 -31
- package/src/watchdog/child-status.ts +54 -34
- package/src/watchdog/diff-tool.ts +77 -0
- package/src/watchdog/emission-guard.ts +5 -3
- package/src/watchdog/guidance.ts +20 -0
- package/src/watchdog/register-child.ts +28 -25
- package/src/watchdog/register-main.ts +10 -9
- package/src/watchdog/render.ts +4 -5
- package/src/watchdog/review.ts +15 -4
- package/src/watchdog/rules.ts +70 -0
- package/src/watchdog/runtime.ts +75 -92
- package/src/watchdog/scope.ts +0 -11
- package/src/watchdog/settings.ts +48 -104
- package/src/watchdog/types.ts +18 -32
- package/src/watchdog/warning-format.ts +0 -1
- package/src/workflows/chat-progress.ts +3 -2
- package/src/workflows/scripted-workflow.ts +48 -1
- package/src/workflows/workflow-checklist.ts +10 -12
- package/src/workflows/workflow-preflight.ts +28 -1
- package/src/runs/shared/child-protocol.ts +0 -415
- package/src/runs/shared/pi-args.ts +0 -1062
- package/src/runs/shared/subagent-startup-retry.ts +0 -116
- package/src/shared/post-exit-stdio-guard.ts +0 -85
|
@@ -27,6 +27,7 @@ import { normalizePublicSubagentExecution, validateWorkflowCapacityOverrides } f
|
|
|
27
27
|
import { runSync } from "./execution.ts";
|
|
28
28
|
import { handleWatchdogToolAction, WATCHDOG_TOOL_ACTIONS } from "../../watchdog/tool-actions.ts";
|
|
29
29
|
import type { MainWatchdogRuntime } from "../../watchdog/runtime.ts";
|
|
30
|
+
import { applyWatchdogLaunchRules } from "../../watchdog/rules.ts";
|
|
30
31
|
import { buildModelCandidates, normalizeParentModel, resolveEffectiveSubagentModel, resolveModelOrigin, type ModelOrigin, type ParentModel } from "../shared/model-fallback.ts";
|
|
31
32
|
import { formatRetainedChildren, listRetainedChildren } from "../background/retained-children.ts";
|
|
32
33
|
import { resolveModelScopesForAgent, type ModelScopeConfig } from "../shared/model-scope.ts";
|
|
@@ -58,7 +59,7 @@ import { currentCompletionOwnerId } from "../../shared/completion-owner.ts";
|
|
|
58
59
|
import { applyIntercomBridgeToAgent, INTERCOM_BRIDGE_MARKER, resolveIntercomBridge, resolveIntercomSessionTarget, resolveSubagentIntercomTarget, type IntercomBridgeState } from "../../intercom/intercom-bridge.ts";
|
|
59
60
|
import { formatControlIntercomMessage, formatControlNoticeMessage, resolveControlConfig, shouldNotifyControlEvent } from "../shared/subagent-control.ts";
|
|
60
61
|
import { formatSpawnBudget, getSpawnBudgetSnapshot, grantSpawnBudget, preflightSpawnBudget, preflightSpawnBudgetGrant, reserveSpawnBudget } from "../shared/spawn-budget.ts";
|
|
61
|
-
import { claimRunFanoutBatch, claimRunFanoutBatchWithCommit, createRunFanoutBudget,
|
|
62
|
+
import { claimRunFanoutBatch, claimRunFanoutBatchWithCommit, createRunFanoutBudget, formatRunFanoutBudget, getRunFanoutBudgetSnapshot, readRunFanoutBudgetDescriptor, RunFanoutLimitError, writeRunFanoutBudgetDescriptor } from "../shared/run-fanout-budget.ts";
|
|
62
63
|
import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
|
|
63
64
|
import { usageBudgetExceededMessage, usageBudgetState, validateUsageBudgetConfig } from "../shared/usage-budget.ts";
|
|
64
65
|
import { intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
@@ -83,11 +84,8 @@ import { deliverInterruptRequest, readRevivalBriefs, requestAsyncSteer, type Ste
|
|
|
83
84
|
import { updateSteeringTarget, waitForSteeringAction } from "../background/steering.ts";
|
|
84
85
|
import { canQueueRetainedAsyncFollowUp, steerAsyncRun } from "./async-steering-action.ts";
|
|
85
86
|
import {
|
|
86
|
-
removeWorkflowForegroundSteeringRoute,
|
|
87
87
|
resolveWorkflowForegroundSteeringTarget,
|
|
88
88
|
steerWorkflowForegroundTarget,
|
|
89
|
-
workflowForegroundSteeringDir,
|
|
90
|
-
workflowForegroundSteeringLaunchOptions,
|
|
91
89
|
} from "./workflow-foreground-steering.ts";
|
|
92
90
|
import { stopAsyncRun } from "./async-stop-action.ts";
|
|
93
91
|
import { dismissRecoveredWorkflow } from "./async-dismiss-action.ts";
|
|
@@ -95,10 +93,11 @@ import { promotePausedWorkflowIfSettled, reconcileDetachedWorkflowChildCompletio
|
|
|
95
93
|
import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
|
|
96
94
|
import { resolveAsyncRootResultPath, waitForImportedAsyncRoot } from "../background/chain-root-attachment.ts";
|
|
97
95
|
import { resultFilePath, writeAsyncResultFile } from "../background/result-files.ts";
|
|
98
|
-
import { attachRootChildrenToSteps, createNestedRoute, findNestedControlResult,
|
|
96
|
+
import { attachRootChildrenToSteps, createNestedRoute, findNestedControlResult, inheritedNestedParentAddressOf, inheritedNestedRouteOf, resolveNestedAsyncDir, snapshotNestedEventFiles, updateForegroundNestedProjection, writeNestedControlRequest, writeNestedEvent, type NestedParentAddress, type NestedRoute, type NestedRunResolutionScope } from "../shared/nested-events.ts";
|
|
97
|
+
import type { ChildRuntimeConfig } from "../shared/child-runtime-config.ts";
|
|
99
98
|
import { resolveSubagentRunId, type ResolvedSubagentRunId } from "../background/run-id-resolver.ts";
|
|
100
99
|
import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
|
|
101
|
-
import { isStoppableAsyncStatusStep, resolveAsyncStatusChild } from "../shared/child-identity.ts";
|
|
100
|
+
import { isStoppableAsyncStatusStep, resolveAsyncStatusChild, stopStoppableAsyncStatusChildren } from "../shared/child-identity.ts";
|
|
102
101
|
import { inspectSubagentStatus } from "../background/run-status.ts";
|
|
103
102
|
import { getExternalJobProvider } from "../../api/external-job-provider.ts";
|
|
104
103
|
import { externalJobFollowUpRequestDigest, externalJobFollowUpRequestId, externalJobFollowUpRunId, externalJobPromptDigest, externalJobStableJson } from "../shared/external-job-runner.ts";
|
|
@@ -152,6 +151,9 @@ import {
|
|
|
152
151
|
type Details,
|
|
153
152
|
type ExtensionConfig,
|
|
154
153
|
type ForegroundResumeChild,
|
|
154
|
+
type ForegroundChildSessionControls,
|
|
155
|
+
type ForegroundSteerInput,
|
|
156
|
+
type ForegroundSteerOutcome,
|
|
155
157
|
type ForegroundRunControl,
|
|
156
158
|
type HostStepNodeV1,
|
|
157
159
|
type IntercomBridgeConfig,
|
|
@@ -350,6 +352,8 @@ export interface SubagentParamsLike {
|
|
|
350
352
|
tasks?: TaskParam[];
|
|
351
353
|
concurrency?: number;
|
|
352
354
|
worktree?: boolean;
|
|
355
|
+
/** Git ref used as the managed worktree base. */
|
|
356
|
+
baseRef?: string;
|
|
353
357
|
context?: "fresh" | "fork" | "profile";
|
|
354
358
|
/** Per-run intercom bridge config. It replaces the global config for this launch only. */
|
|
355
359
|
intercomBridge?: IntercomBridgeConfig;
|
|
@@ -435,6 +439,20 @@ interface ExecutorDeps {
|
|
|
435
439
|
activateSupervisorTransport?: () => void;
|
|
436
440
|
refreshResultDelivery?: () => void;
|
|
437
441
|
kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;
|
|
442
|
+
/** Set when this executor runs inside a child session; carries the runtime settings the host passes instead of environment variables. */
|
|
443
|
+
childRuntime?: ChildRuntimeConfig;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function inheritedNestedRoute(deps: Pick<ExecutorDeps, "childRuntime">): NestedRoute | undefined {
|
|
447
|
+
return inheritedNestedRouteOf(deps.childRuntime);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function inheritedNestedParentAddress(deps: Pick<ExecutorDeps, "childRuntime">): NestedParentAddress | undefined {
|
|
451
|
+
return inheritedNestedParentAddressOf(deps.childRuntime);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function inheritedRunFanoutBudget(deps: Pick<ExecutorDeps, "childRuntime">): RunFanoutBudgetDescriptor | undefined {
|
|
455
|
+
return deps.childRuntime?.runFanoutBudget;
|
|
438
456
|
}
|
|
439
457
|
|
|
440
458
|
type ForkSessionFileForTask = (agentName: string, idx?: number, modelOverride?: string, modelOverrideFromParent?: boolean, modelOrigin?: ModelOrigin) => string | undefined;
|
|
@@ -511,7 +529,6 @@ function loadWorkflowScriptPath(params: SubagentParamsLike, runtimeCwd: string):
|
|
|
511
529
|
function removeForegroundControlIfIdle(state: SubagentState, runId: string): boolean {
|
|
512
530
|
const control = state.foregroundControls.get(runId);
|
|
513
531
|
if (control && (!foregroundSchedulingSettled(control) || (control.activeChildren?.size ?? 0) > 0)) return false;
|
|
514
|
-
if (control) removeWorkflowForegroundSteeringRoute(control);
|
|
515
532
|
state.foregroundControls.delete(runId);
|
|
516
533
|
if (state.lastForegroundControlId === runId) state.lastForegroundControlId = null;
|
|
517
534
|
return true;
|
|
@@ -564,8 +581,8 @@ function formatForegroundActivity(control: SubagentState["foregroundControls"] e
|
|
|
564
581
|
|
|
565
582
|
function nestedResolutionScopeForExecutor(deps: ExecutorDeps): NestedRunResolutionScope | undefined {
|
|
566
583
|
if (deps.allowMutatingManagementActions !== false) return undefined;
|
|
567
|
-
const route =
|
|
568
|
-
const address = route ?
|
|
584
|
+
const route = inheritedNestedRoute(deps);
|
|
585
|
+
const address = route ? inheritedNestedParentAddress(deps) : undefined;
|
|
569
586
|
return {
|
|
570
587
|
routes: route ? [route] : [],
|
|
571
588
|
...(address ? { descendantOf: { parentRunId: address.parentRunId, ...(address.parentStepIndex !== undefined ? { parentStepIndex: address.parentStepIndex } : {}) } } : {}),
|
|
@@ -1298,6 +1315,7 @@ function appendStepToAsyncChain(input: {
|
|
|
1298
1315
|
modelScope: discoveredForAppend.modelScope,
|
|
1299
1316
|
interactive: input.ctx.hasUI,
|
|
1300
1317
|
permissions: input.deps.config.permissions,
|
|
1318
|
+
childRuntime: input.deps.childRuntime,
|
|
1301
1319
|
});
|
|
1302
1320
|
const built = buildAsyncRunnerSteps(resolved.id, compactOptional<Parameters<typeof buildAsyncRunnerSteps>[1]>({
|
|
1303
1321
|
chain: wrapChainTasksForFork(chain, contextPolicy),
|
|
@@ -1310,7 +1328,7 @@ function appendStepToAsyncChain(input: {
|
|
|
1310
1328
|
cwd: status.cwd ?? input.requestCwd,
|
|
1311
1329
|
chainSkills,
|
|
1312
1330
|
dynamicFanoutMaxItems: input.deps.config.chain?.dynamicFanout?.maxItems,
|
|
1313
|
-
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
1331
|
+
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth, input.deps.childRuntime),
|
|
1314
1332
|
waitToolEnabled: input.deps.waitToolEnabled,
|
|
1315
1333
|
waitToolDefaultTimeoutMs: input.deps.waitToolDefaultTimeoutMs,
|
|
1316
1334
|
contextForAgent: contextPolicy.contextForAgent,
|
|
@@ -1636,11 +1654,11 @@ async function resumeExternalJobFollowUp(input: {
|
|
|
1636
1654
|
return externalJobFollowUpStarted({ sourceRunId: input.target.runId, runId, asyncDir, duplicate: true, interactive: input.ctx.hasUI });
|
|
1637
1655
|
}
|
|
1638
1656
|
|
|
1639
|
-
const depthState = checkSubagentDepth(input.deps.config.maxSubagentDepth);
|
|
1657
|
+
const depthState = checkSubagentDepth(input.deps.config.maxSubagentDepth, input.deps.childRuntime);
|
|
1640
1658
|
if (depthState.blocked) {
|
|
1641
1659
|
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: [] } };
|
|
1642
1660
|
}
|
|
1643
|
-
const topLevelResume = depthState.depth === 0 && !
|
|
1661
|
+
const topLevelResume = depthState.depth === 0 && !inheritedNestedRoute(input.deps) && !input.deps.state.workflowControllers?.has(input.target.runId);
|
|
1644
1662
|
let activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined;
|
|
1645
1663
|
try {
|
|
1646
1664
|
activeAsyncCapacity = topLevelResume ? acquireActiveAsyncCapacity({
|
|
@@ -1678,13 +1696,14 @@ async function resumeExternalJobFollowUp(input: {
|
|
|
1678
1696
|
modelScope: input.modelScope,
|
|
1679
1697
|
interactive: input.ctx.hasUI,
|
|
1680
1698
|
permissions: input.deps.config.permissions,
|
|
1699
|
+
childRuntime: input.deps.childRuntime,
|
|
1681
1700
|
}),
|
|
1682
1701
|
cwd: input.effectiveCwd,
|
|
1683
1702
|
artifactsDir,
|
|
1684
1703
|
artifactConfig,
|
|
1685
1704
|
shareEnabled: false,
|
|
1686
1705
|
...(input.parentSessionFile ? { sessionRoot: input.deps.getSubagentSessionRoot(input.parentSessionFile) } : {}),
|
|
1687
|
-
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
1706
|
+
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth, input.deps.childRuntime),
|
|
1688
1707
|
waitToolEnabled: input.deps.waitToolEnabled,
|
|
1689
1708
|
waitToolDefaultTimeoutMs: input.deps.waitToolDefaultTimeoutMs,
|
|
1690
1709
|
worktreeSetupHook: input.deps.config.worktreeSetupHook,
|
|
@@ -1795,7 +1814,7 @@ async function resumeAsyncRun(input: {
|
|
|
1795
1814
|
};
|
|
1796
1815
|
}
|
|
1797
1816
|
|
|
1798
|
-
const { blocked, depth, maxDepth } = checkSubagentDepth(input.deps.config.maxSubagentDepth);
|
|
1817
|
+
const { blocked, depth, maxDepth } = checkSubagentDepth(input.deps.config.maxSubagentDepth, input.deps.childRuntime);
|
|
1799
1818
|
if (blocked) {
|
|
1800
1819
|
return {
|
|
1801
1820
|
content: [{ type: "text", text: `Nested subagent resume blocked (depth=${depth}, max=${maxDepth}). Complete the follow-up directly instead.` }],
|
|
@@ -1884,7 +1903,7 @@ async function resumeAsyncRun(input: {
|
|
|
1884
1903
|
};
|
|
1885
1904
|
}
|
|
1886
1905
|
const runId = randomUUID();
|
|
1887
|
-
const topLevelResume = depth === 0 && !
|
|
1906
|
+
const topLevelResume = depth === 0 && !inheritedNestedRoute(input.deps) && !input.params.workflowParentRunId;
|
|
1888
1907
|
let activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined;
|
|
1889
1908
|
try {
|
|
1890
1909
|
activeAsyncCapacity = topLevelResume ? acquireActiveAsyncCapacity({
|
|
@@ -1930,6 +1949,7 @@ async function resumeAsyncRun(input: {
|
|
|
1930
1949
|
modelScope,
|
|
1931
1950
|
interactive: input.ctx.hasUI,
|
|
1932
1951
|
permissions: input.deps.config.permissions,
|
|
1952
|
+
childRuntime: input.deps.childRuntime,
|
|
1933
1953
|
}),
|
|
1934
1954
|
availableModels,
|
|
1935
1955
|
cwd: effectiveCwd,
|
|
@@ -1942,12 +1962,13 @@ async function resumeAsyncRun(input: {
|
|
|
1942
1962
|
agentContract: input.params.agentContract,
|
|
1943
1963
|
fast: input.params.fast,
|
|
1944
1964
|
dynamicFanoutMaxItems: input.deps.config.chain?.dynamicFanout?.maxItems,
|
|
1945
|
-
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
1965
|
+
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth, input.deps.childRuntime),
|
|
1946
1966
|
waitToolEnabled: input.deps.waitToolEnabled,
|
|
1947
1967
|
waitToolDefaultTimeoutMs: input.deps.waitToolDefaultTimeoutMs,
|
|
1948
1968
|
worktreeSetupHook: input.deps.config.worktreeSetupHook,
|
|
1949
1969
|
worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
|
|
1950
1970
|
worktreeBaseDir: input.deps.config.worktreeBaseDir,
|
|
1971
|
+
baseRef: input.params.baseRef ?? target.recoveryDescriptor?.baseRef,
|
|
1951
1972
|
worktreeProvider: input.deps.config.worktreeProvider,
|
|
1952
1973
|
worktreeBranchPrefix: input.deps.config.worktreeBranchPrefix,
|
|
1953
1974
|
controlConfig: resolveControlConfig(input.deps.config.control, input.params.control),
|
|
@@ -1984,8 +2005,11 @@ async function resumeAsyncRun(input: {
|
|
|
1984
2005
|
if (target.source === "async" && asyncReviveRequiresRecoveryDescriptor(target)) {
|
|
1985
2006
|
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: [] } };
|
|
1986
2007
|
}
|
|
2008
|
+
if (input.params.baseRef !== undefined && "managedWorktree" in target && target.managedWorktree === true) {
|
|
2009
|
+
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: [] } };
|
|
2010
|
+
}
|
|
1987
2011
|
const runId = randomUUID();
|
|
1988
|
-
const topLevelResume = depth === 0 && !
|
|
2012
|
+
const topLevelResume = depth === 0 && !inheritedNestedRoute(input.deps) && !input.params.workflowParentRunId;
|
|
1989
2013
|
let activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined;
|
|
1990
2014
|
try {
|
|
1991
2015
|
activeAsyncCapacity = !topLevelResume ? undefined : target.source === "async"
|
|
@@ -2033,6 +2057,7 @@ async function resumeAsyncRun(input: {
|
|
|
2033
2057
|
modelScope,
|
|
2034
2058
|
interactive: input.ctx.hasUI,
|
|
2035
2059
|
permissions: input.deps.config.permissions,
|
|
2060
|
+
childRuntime: input.deps.childRuntime,
|
|
2036
2061
|
}),
|
|
2037
2062
|
cwd: effectiveCwd,
|
|
2038
2063
|
maxOutput: input.params.maxOutput ?? recoveryDescriptor?.maxOutput,
|
|
@@ -2057,19 +2082,20 @@ async function resumeAsyncRun(input: {
|
|
|
2057
2082
|
thinkingCeiling: recoveryDescriptor?.thinkingCeiling ?? ("thinkingCeiling" in target ? target.thinkingCeiling : undefined),
|
|
2058
2083
|
extensionBindings: recoveryDescriptor?.extensionBindings ?? ("extensionBindings" in target ? target.extensionBindings : undefined),
|
|
2059
2084
|
outputBaseDir: resolveSingleRunOutputBaseDir(input.deps, artifactsDir, runId),
|
|
2060
|
-
maxSubagentDepth: recoveryDescriptor?.maxSubagentDepth ?? resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
2085
|
+
maxSubagentDepth: recoveryDescriptor?.maxSubagentDepth ?? resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth, input.deps.childRuntime),
|
|
2061
2086
|
waitToolEnabled: input.deps.waitToolEnabled,
|
|
2062
2087
|
waitToolDefaultTimeoutMs: input.deps.waitToolDefaultTimeoutMs,
|
|
2063
2088
|
worktreeSetupHook: input.deps.config.worktreeSetupHook,
|
|
2064
2089
|
worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
|
|
2065
2090
|
worktreeBaseDir: input.deps.config.worktreeBaseDir,
|
|
2091
|
+
baseRef: input.params.baseRef ?? recoveryDescriptor?.baseRef,
|
|
2066
2092
|
worktreeProvider: input.deps.config.worktreeProvider,
|
|
2067
2093
|
worktreeBranchPrefix: input.deps.config.worktreeBranchPrefix,
|
|
2068
2094
|
// A retained async child already owns the recorded worktree. Resume it in
|
|
2069
2095
|
// place rather than allocating a second provider worktree around it.
|
|
2070
2096
|
worktree: input.params.worktree === true && !("managedWorktree" in target && target.managedWorktree === true),
|
|
2071
2097
|
lane: input.params.lane ?? recoveryDescriptor?.lane,
|
|
2072
|
-
controlConfig:
|
|
2098
|
+
controlConfig: resolveRevivalControlConfig({ globalConfig: input.deps.config.control, requestedControl: input.params.control, recoveryControlConfig: recoveryDescriptor?.controlConfig }),
|
|
2073
2099
|
intercomBridge: input.params.intercomBridge ?? recoveryDescriptor?.intercomBridge,
|
|
2074
2100
|
controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
|
|
2075
2101
|
childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
@@ -3186,9 +3212,10 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
3186
3212
|
modelScope: data.modelScope,
|
|
3187
3213
|
interactive: ctx.hasUI,
|
|
3188
3214
|
permissions: deps.config.permissions,
|
|
3215
|
+
childRuntime: deps.childRuntime,
|
|
3189
3216
|
});
|
|
3190
3217
|
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
3191
|
-
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
3218
|
+
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth, deps.childRuntime);
|
|
3192
3219
|
const currentProvider = parentModel?.provider;
|
|
3193
3220
|
const controlIntercomTarget = resolveRunLevelIntercomTarget(intercomBridge, contextPolicy);
|
|
3194
3221
|
const childIntercomTarget = resolveChildIntercomTargetFactory(intercomBridge, contextPolicy, id);
|
|
@@ -3229,6 +3256,8 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
3229
3256
|
source: modelOrigin === "explicit" ? "explicit" : "inherited",
|
|
3230
3257
|
});
|
|
3231
3258
|
const modelOverrideFromParent = modelOrigin === "inherited";
|
|
3259
|
+
const launchRuleError = applyWatchdogLaunchRules({ cwd: effectiveCwd, agent: a.name, model: modelOverride ?? (parentModel && `${parentModel.provider}/${parentModel.id}`), warn: (violation) => deps.watchdog?.displayRuleWarning(violation) });
|
|
3260
|
+
if (launchRuleError) return toExecutionErrorResult(params, new Error(launchRuleError), data.contextPolicy.contextSummary);
|
|
3232
3261
|
const asyncResult = executeAsyncSingle(id, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
|
|
3233
3262
|
agent: params.agent!,
|
|
3234
3263
|
task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
|
|
@@ -3266,6 +3295,7 @@ async function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
3266
3295
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
3267
3296
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
3268
3297
|
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
3298
|
+
baseRef: params.baseRef,
|
|
3269
3299
|
worktreeProvider: deps.config.worktreeProvider,
|
|
3270
3300
|
worktreeBranchPrefix: deps.config.worktreeBranchPrefix,
|
|
3271
3301
|
controlConfig,
|
|
@@ -3304,6 +3334,7 @@ function createSingleWorktreeSetup(
|
|
|
3304
3334
|
setupHook: ExtensionConfig["worktreeSetupHook"],
|
|
3305
3335
|
setupHookTimeoutMs: ExtensionConfig["worktreeSetupHookTimeoutMs"],
|
|
3306
3336
|
baseDir: ExtensionConfig["worktreeBaseDir"],
|
|
3337
|
+
baseRef: string | undefined,
|
|
3307
3338
|
provider: ExtensionConfig["worktreeProvider"],
|
|
3308
3339
|
branchPrefix: ExtensionConfig["worktreeBranchPrefix"],
|
|
3309
3340
|
label?: string,
|
|
@@ -3319,6 +3350,7 @@ function createSingleWorktreeSetup(
|
|
|
3319
3350
|
? { hookPath: setupHook, ...(setupHookTimeoutMs === undefined ? {} : { timeoutMs: setupHookTimeoutMs }) }
|
|
3320
3351
|
: undefined,
|
|
3321
3352
|
baseDir,
|
|
3353
|
+
baseRef,
|
|
3322
3354
|
provider,
|
|
3323
3355
|
branchPrefix,
|
|
3324
3356
|
labels: [label],
|
|
@@ -3664,12 +3696,14 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3664
3696
|
},
|
|
3665
3697
|
);
|
|
3666
3698
|
const modelOverrideFromParent = modelOrigin === "inherited";
|
|
3699
|
+
const launchRuleError = applyWatchdogLaunchRules({ cwd: effectiveCwd, agent: agentConfig.name, model: modelOverride ?? (parentModel && `${parentModel.provider}/${parentModel.id}`), warn: (violation) => deps.watchdog?.displayRuleWarning(violation) });
|
|
3700
|
+
if (launchRuleError) return toExecutionErrorResult(params, new Error(launchRuleError), data.contextPolicy.contextSummary);
|
|
3667
3701
|
let skillOverride: string[] | false | undefined = normalizeSkillInput(params.skill);
|
|
3668
3702
|
let readsOverride: string[] | false | undefined = params.reads;
|
|
3669
3703
|
const rawOutput = params.output !== undefined ? params.output : agentConfig.output;
|
|
3670
3704
|
let effectiveOutput = normalizeSingleOutputOverride(rawOutput, agentConfig.output);
|
|
3671
3705
|
const effectiveOutputMode = params.outputMode ?? agentConfig.outputMode ?? "inline";
|
|
3672
|
-
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
3706
|
+
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth, deps.childRuntime);
|
|
3673
3707
|
const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, agentConfig.maxSubagentDepth);
|
|
3674
3708
|
|
|
3675
3709
|
|
|
@@ -3683,6 +3717,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3683
3717
|
deps.config.worktreeSetupHook,
|
|
3684
3718
|
deps.config.worktreeSetupHookTimeoutMs,
|
|
3685
3719
|
deps.config.worktreeBaseDir,
|
|
3720
|
+
params.baseRef,
|
|
3686
3721
|
deps.config.worktreeProvider,
|
|
3687
3722
|
deps.config.worktreeBranchPrefix,
|
|
3688
3723
|
params.lane?.key ?? params.workflowKey,
|
|
@@ -3736,6 +3771,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3736
3771
|
}
|
|
3737
3772
|
const interruptController = new AbortController();
|
|
3738
3773
|
let detachForeground: ((reason?: string) => boolean) | undefined;
|
|
3774
|
+
let childSessionControls: ForegroundChildSessionControls | undefined;
|
|
3739
3775
|
const foregroundControl = deps.state.foregroundControls.get(runId);
|
|
3740
3776
|
if (foregroundControl) {
|
|
3741
3777
|
const thinking = resolveEffectiveThinking(modelOverride, thinkingOverrideForTask(params.agent!, 0, modelOverride, modelOverrideFromParent, modelOrigin));
|
|
@@ -3756,6 +3792,19 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3756
3792
|
return true;
|
|
3757
3793
|
},
|
|
3758
3794
|
detach: () => detachForeground?.("user request") === true,
|
|
3795
|
+
steer: async (input: ForegroundSteerInput): Promise<ForegroundSteerOutcome> => {
|
|
3796
|
+
if (!childSessionControls) return { state: "failed", reason: CHILD_SESSION_NOT_RUNNING_YET };
|
|
3797
|
+
try {
|
|
3798
|
+
if (input.mode === "follow_up") {
|
|
3799
|
+
await childSessionControls.followUp(input.message);
|
|
3800
|
+
return { state: "queued" };
|
|
3801
|
+
}
|
|
3802
|
+
await childSessionControls.steer(input.message);
|
|
3803
|
+
return { state: "delivered" };
|
|
3804
|
+
} catch (error) {
|
|
3805
|
+
return { state: "failed", reason: error instanceof Error ? error.message : String(error) };
|
|
3806
|
+
}
|
|
3807
|
+
},
|
|
3759
3808
|
}));
|
|
3760
3809
|
}
|
|
3761
3810
|
|
|
@@ -3778,7 +3827,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3778
3827
|
runtimeSnapshotHost: deps.pi,
|
|
3779
3828
|
parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
|
|
3780
3829
|
llmIntentArbiter: createTaskMutationArbiter(ctx),
|
|
3781
|
-
|
|
3830
|
+
childRuntime: deps.childRuntime,
|
|
3831
|
+
onChildSession: (controls) => { childSessionControls = controls; },
|
|
3782
3832
|
context: data.contextPolicy.contextForAgent(params.agent!),
|
|
3783
3833
|
unknownAgentDiagnosticContext: data.unknownAgentDiagnosticContext,
|
|
3784
3834
|
runFanoutBudget: params.runFanoutAdmitted ? data.runFanoutBudget : { ...data.runFanoutBudget, parentPath: `${data.runFanoutBudget.parentPath ? `${data.runFanoutBudget.parentPath}/` : ""}single` },
|
|
@@ -4210,6 +4260,7 @@ function workflowSteerReceipt(key: string, result: AgentToolResult<Details>): Wo
|
|
|
4210
4260
|
};
|
|
4211
4261
|
}
|
|
4212
4262
|
|
|
4263
|
+
const CHILD_SESSION_NOT_RUNNING_YET = "Child session is not running yet.";
|
|
4213
4264
|
const MAX_WORKFLOW_RESUME_HINT_BYTES = 1024;
|
|
4214
4265
|
const MAX_WORKFLOW_CHILD_RUN_ID_BYTES = 256;
|
|
4215
4266
|
const WORKFLOW_RESUME_HINT_PARENT_STATES = new Set(["complete", "failed", "partial"]);
|
|
@@ -4345,7 +4396,6 @@ export async function steerWorkflowChildByKey(input: {
|
|
|
4345
4396
|
while (true) {
|
|
4346
4397
|
const control = [...input.state.foregroundControls.values()].find((candidate) => candidate.parentWorkflowRunId === input.workflowRunId
|
|
4347
4398
|
&& candidate.workflowKey === input.key
|
|
4348
|
-
&& Boolean(candidate.workflowSteeringDir)
|
|
4349
4399
|
&& (candidate.activeChildren?.size ?? 0) > 0);
|
|
4350
4400
|
if (control) {
|
|
4351
4401
|
const result = await steerWorkflowForegroundTarget({
|
|
@@ -4353,10 +4403,9 @@ export async function steerWorkflowChildByKey(input: {
|
|
|
4353
4403
|
message: input.message,
|
|
4354
4404
|
mode: input.options.mode,
|
|
4355
4405
|
index: input.options.index,
|
|
4356
|
-
ackTimeoutMs: Math.max(1, deadline - Date.now()),
|
|
4357
|
-
signal: input.signal,
|
|
4358
4406
|
});
|
|
4359
|
-
|
|
4407
|
+
// The control registers before its child session exists; keep polling until the steer can route.
|
|
4408
|
+
if (!result.details.steering?.targets.some((target) => target.reason === CHILD_SESSION_NOT_RUNNING_YET) || Date.now() >= deadline) return workflowSteerReceipt(input.key, result);
|
|
4360
4409
|
}
|
|
4361
4410
|
|
|
4362
4411
|
const workflowStatus = readStatus(path.join(asyncDirRoot, input.workflowRunId));
|
|
@@ -4429,6 +4478,7 @@ export function prepareWorkflowLaunchParams(
|
|
|
4429
4478
|
const toolBudget = childParams.toolBudget ?? workflowDefaults.toolBudget;
|
|
4430
4479
|
const intercomBridge = childParams.intercomBridge ?? workflowDefaults.intercomBridge;
|
|
4431
4480
|
const worktree = childParams.worktree ?? workflowDefaults.worktree;
|
|
4481
|
+
const baseRef = Object.hasOwn(childParams, "baseRef") ? childParams.baseRef : undefined;
|
|
4432
4482
|
const outputSchema = Object.hasOwn(childParams, "outputSchema") ? childParams.outputSchema : workflowDefaults.outputSchema;
|
|
4433
4483
|
if (outputSchema !== undefined) assertJsonSchemaObject(outputSchema, "outputSchema");
|
|
4434
4484
|
const agentContract = Object.hasOwn(childParams, "agentContract") ? childParams.agentContract : workflowDefaults.agentContract;
|
|
@@ -4438,6 +4488,7 @@ export function prepareWorkflowLaunchParams(
|
|
|
4438
4488
|
if (output !== undefined && typeof output !== "string" && typeof output !== "boolean") throw new Error("output must be a path string or boolean.");
|
|
4439
4489
|
const outputMode = Object.hasOwn(childParams, "outputMode") ? childParams.outputMode : workflowDefaults.outputMode;
|
|
4440
4490
|
if (outputMode !== undefined && outputMode !== "inline" && outputMode !== "file-only") throw new Error("outputMode must be 'inline' or 'file-only'.");
|
|
4491
|
+
const control = mergeWorkflowControlOverrides(workflowDefaults.control, childParams.control as ControlConfig | undefined);
|
|
4441
4492
|
return {
|
|
4442
4493
|
action: "resume",
|
|
4443
4494
|
id: childParams.resume.trim(),
|
|
@@ -4447,6 +4498,7 @@ export function prepareWorkflowLaunchParams(
|
|
|
4447
4498
|
workflowKey,
|
|
4448
4499
|
...(lane ? { lane } : {}),
|
|
4449
4500
|
...(worktree !== undefined ? { worktree: worktree as boolean } : {}),
|
|
4501
|
+
...(baseRef !== undefined ? { baseRef: baseRef as string } : {}),
|
|
4450
4502
|
...(outputSchema !== undefined ? { outputSchema: outputSchema as JsonSchemaObject } : {}),
|
|
4451
4503
|
...(agentContract !== undefined ? { agentContract: agentContract as AgentContract } : {}),
|
|
4452
4504
|
...(acceptance !== undefined ? { acceptance: acceptance as AcceptanceInput } : {}),
|
|
@@ -4456,14 +4508,17 @@ export function prepareWorkflowLaunchParams(
|
|
|
4456
4508
|
...(options.missionDetached ? { mission: false } : {}),
|
|
4457
4509
|
...(timeoutMs !== undefined ? { timeoutMs: timeoutMs as number } : {}),
|
|
4458
4510
|
...(toolBudget !== undefined ? { toolBudget: toolBudget as ToolBudgetConfig } : {}),
|
|
4511
|
+
...(control !== undefined ? { control } : {}),
|
|
4459
4512
|
...(intercomBridge !== undefined ? { intercomBridge: intercomBridge as IntercomBridgeConfig } : {}),
|
|
4460
4513
|
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
4461
4514
|
};
|
|
4462
4515
|
}
|
|
4516
|
+
const control = mergeWorkflowControlOverrides(workflowDefaults.control, childParams.control as ControlConfig | undefined);
|
|
4463
4517
|
const launchParams = {
|
|
4464
4518
|
...workflowDefaults,
|
|
4465
4519
|
async: options.externalAsyncRequired === true && childParams.async === undefined && workflowDefaults.async === undefined ? true : false,
|
|
4466
4520
|
...childParams,
|
|
4521
|
+
...(control !== undefined ? { control } : {}),
|
|
4467
4522
|
...(options.externalAsyncRequired === true && childParams.async === undefined && workflowDefaults.async === undefined ? { workflowAwaitAsync: true } : {}),
|
|
4468
4523
|
...(options.missionDetached ? { mission: false } : {}),
|
|
4469
4524
|
workflowParentRunId: parentWorkflowRunId,
|
|
@@ -4482,6 +4537,17 @@ export function prepareWorkflowLaunchParams(
|
|
|
4482
4537
|
return normalizedGate.params;
|
|
4483
4538
|
}
|
|
4484
4539
|
|
|
4540
|
+
function mergeWorkflowControlOverrides(workflowControl: ControlConfig | undefined, childControl: ControlConfig | undefined): ControlConfig | undefined {
|
|
4541
|
+
if (childControl === undefined) return workflowControl;
|
|
4542
|
+
if (workflowControl === undefined) return childControl;
|
|
4543
|
+
return { ...workflowControl, ...childControl };
|
|
4544
|
+
}
|
|
4545
|
+
|
|
4546
|
+
export function resolveRevivalControlConfig(input: { globalConfig?: ControlConfig; requestedControl?: ControlConfig; recoveryControlConfig?: ResolvedControlConfig }): ResolvedControlConfig {
|
|
4547
|
+
if (input.requestedControl === undefined) return input.recoveryControlConfig ?? resolveControlConfig(input.globalConfig, undefined);
|
|
4548
|
+
return resolveControlConfig(input.recoveryControlConfig ?? input.globalConfig, input.requestedControl);
|
|
4549
|
+
}
|
|
4550
|
+
|
|
4485
4551
|
type GateParamsNormalizationResult =
|
|
4486
4552
|
| { ok: true; params: SubagentParamsLike }
|
|
4487
4553
|
| { ok: false; error: string };
|
|
@@ -4797,11 +4863,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4797
4863
|
const scriptFirstLine = requestParams.workflowScript.split(/\r?\n/).map((line) => line.trim()).find(Boolean) || "Workflow";
|
|
4798
4864
|
const boundedScriptPreview = scriptFirstLine.length > 100 ? `${scriptFirstLine.slice(0, 97)}...` : scriptFirstLine;
|
|
4799
4865
|
const derivedObjective = previewAgent ? `Workflow: ${previewAgent}` : boundedScriptPreview;
|
|
4800
|
-
const workflowDepth = checkSubagentDepth(deps.config.maxSubagentDepth).depth;
|
|
4866
|
+
const workflowDepth = checkSubagentDepth(deps.config.maxSubagentDepth, deps.childRuntime).depth;
|
|
4801
4867
|
const asyncWorkflow = requestParams.async !== false;
|
|
4802
4868
|
const topLevelAsyncWorkflow = asyncWorkflow
|
|
4803
4869
|
&& workflowDepth === 0
|
|
4804
|
-
&& !
|
|
4870
|
+
&& !inheritedNestedRoute(deps)
|
|
4805
4871
|
&& !requestParams.workflowParentRunId;
|
|
4806
4872
|
const workflowRunId = asyncWorkflow ? randomUUID() : undefined;
|
|
4807
4873
|
let workflowCapacity: ActiveAsyncCapacityHandle | undefined;
|
|
@@ -4826,7 +4892,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4826
4892
|
let workflowFanoutBudget: RunFanoutBudgetDescriptor;
|
|
4827
4893
|
try {
|
|
4828
4894
|
workflowFanoutBudget = requestParams.runFanoutBudget
|
|
4829
|
-
??
|
|
4895
|
+
?? inheritedRunFanoutBudget(deps)
|
|
4830
4896
|
?? createRunFanoutBudget(_id, requestParams.maxSubagentSpawnsPerRun ?? resolveMaxSubagentSpawnsPerRun(deps.config.maxSubagentSpawnsPerRun));
|
|
4831
4897
|
} catch (error) {
|
|
4832
4898
|
workflowCapacity?.rollback();
|
|
@@ -5185,6 +5251,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5185
5251
|
globalConcurrencyLimit: requestParams.globalConcurrencyLimit ?? deps.config.globalConcurrencyLimit,
|
|
5186
5252
|
timeoutMs: timeout,
|
|
5187
5253
|
signal: controller.signal,
|
|
5254
|
+
continueAfterAbortWhenChildrenSettled: (abortError) => {
|
|
5255
|
+
if (abortError.message !== "Workflow stopped because the extension session was replaced or reloaded.") return false;
|
|
5256
|
+
const activeAsyncChild = [...(deps.state.asyncJobs?.values() ?? [])].some((job) => job.parentWorkflowRunId === workflowRunId && (job.status === "queued" || job.status === "running"));
|
|
5257
|
+
const activeForegroundChild = [...deps.state.foregroundControls.values()].some((control) => control.parentWorkflowRunId === workflowRunId && (control.activeChildren?.size ?? 0) > 0);
|
|
5258
|
+
return !activeAsyncChild && !activeForegroundChild;
|
|
5259
|
+
},
|
|
5188
5260
|
registerStopChild: (stop) => {
|
|
5189
5261
|
if (stop) deps.state.workflowChildStops?.set(workflowRunId, stop);
|
|
5190
5262
|
else deps.state.workflowChildStops?.delete(workflowRunId);
|
|
@@ -5972,7 +6044,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5972
6044
|
if (directoryStatus?.mode === "workflow") {
|
|
5973
6045
|
const route = resolveWorkflowForegroundSteeringTarget({ state: deps.state, workflowRunId: directoryStatus.runId || runId, asyncDirRoot: DIRS.async });
|
|
5974
6046
|
if (!route.ok) return { content: [{ type: "text", text: route.message }], isError: true, details: { mode: "management", results: [] } };
|
|
5975
|
-
return steerWorkflowForegroundTarget({ target: route.target, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index
|
|
6047
|
+
return steerWorkflowForegroundTarget({ target: route.target, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index });
|
|
5976
6048
|
}
|
|
5977
6049
|
if (location.asyncDir) {
|
|
5978
6050
|
const unsupported = externalRunnerControlError(location.asyncDir, "steer");
|
|
@@ -6012,14 +6084,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6012
6084
|
if (resolved?.kind === "foreground") {
|
|
6013
6085
|
const route = resolveWorkflowForegroundSteeringTarget({ state: deps.state, childRunId: resolved.id, asyncDirRoot: DIRS.async });
|
|
6014
6086
|
if (!route.ok) return { content: [{ type: "text", text: route.message }], isError: true, details: { mode: "management", results: [] } };
|
|
6015
|
-
return steerWorkflowForegroundTarget({ target: route.target, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index
|
|
6087
|
+
return steerWorkflowForegroundTarget({ target: route.target, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index });
|
|
6016
6088
|
}
|
|
6017
6089
|
if (resolved?.kind !== "async") return { content: [{ type: "text", text: `No async run found for '${targetRunId}'.` }], isError: true, details: { mode: "management", results: [] } };
|
|
6018
6090
|
const resolvedStatus = resolved.location.asyncDir ? readStatus(resolved.location.asyncDir) : null;
|
|
6019
6091
|
if (resolvedStatus?.mode === "workflow") {
|
|
6020
6092
|
const route = resolveWorkflowForegroundSteeringTarget({ state: deps.state, workflowRunId: resolvedStatus.runId || resolved.id, asyncDirRoot: DIRS.async });
|
|
6021
6093
|
if (!route.ok) return { content: [{ type: "text", text: route.message }], isError: true, details: { mode: "management", results: [] } };
|
|
6022
|
-
return steerWorkflowForegroundTarget({ target: route.target, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index
|
|
6094
|
+
return steerWorkflowForegroundTarget({ target: route.target, message, mode: resolveSteerDeliveryMode(paramsWithResolvedCwd.mode), index: paramsWithResolvedCwd.index });
|
|
6023
6095
|
}
|
|
6024
6096
|
if (resolved.location.asyncDir) {
|
|
6025
6097
|
const unsupported = externalRunnerControlError(resolved.location.asyncDir, "steer");
|
|
@@ -6098,9 +6170,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6098
6170
|
if (action === "stop") {
|
|
6099
6171
|
const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
|
|
6100
6172
|
const workflowController = targetRunId ? deps.state.workflowControllers?.get(targetRunId) : undefined;
|
|
6101
|
-
if (workflowController) {
|
|
6173
|
+
if (workflowController && targetRunId) {
|
|
6174
|
+
const stopChild = deps.state.workflowChildStops?.get(targetRunId);
|
|
6102
6175
|
if (paramsWithResolvedCwd.childId !== undefined) {
|
|
6103
|
-
const workflowRunId = targetRunId
|
|
6176
|
+
const workflowRunId = targetRunId;
|
|
6104
6177
|
const asyncJob = deps.state.asyncJobs.get(workflowRunId);
|
|
6105
6178
|
if (!asyncJob?.asyncDir) return { content: [{ type: "text", text: `Status file not found for async workflow '${workflowRunId}'.` }], isError: true, details: { mode: "management", results: [] } };
|
|
6106
6179
|
const status = readStatus(asyncJob.asyncDir);
|
|
@@ -6108,7 +6181,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6108
6181
|
const resolution = resolveAsyncStatusChild(status, paramsWithResolvedCwd.childId);
|
|
6109
6182
|
if (!resolution.ok) return { content: [{ type: "text", text: resolution.message }], isError: true, details: { mode: "management", results: [] } };
|
|
6110
6183
|
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: [] } };
|
|
6111
|
-
const stopChild = deps.state.workflowChildStops?.get(targetRunId!);
|
|
6112
6184
|
if (!stopChild) return { content: [{ type: "text", text: `Workflow ${targetRunId} child stop is unavailable in this extension runtime.` }], isError: true, details: { mode: "management", results: [] } };
|
|
6113
6185
|
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: [] } };
|
|
6114
6186
|
try {
|
|
@@ -6133,6 +6205,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6133
6205
|
}
|
|
6134
6206
|
return { content: [{ type: "text", text: `Stop requested for child ${resolution.child.id} in async workflow ${workflowRunId}.` }], details: { mode: "management", results: [] } };
|
|
6135
6207
|
}
|
|
6208
|
+
const asyncJob = deps.state.asyncJobs.get(targetRunId);
|
|
6209
|
+
const status = asyncJob?.asyncDir ? readStatus(asyncJob.asyncDir) : undefined;
|
|
6210
|
+
if (status) stopStoppableAsyncStatusChildren(status, stopChild, "Workflow stopped.");
|
|
6136
6211
|
workflowController.abort(new Error("Workflow stopped."));
|
|
6137
6212
|
return { content: [{ type: "text", text: `Stop requested for async workflow ${targetRunId}.` }], details: { mode: "management", results: [] } };
|
|
6138
6213
|
}
|
|
@@ -6228,7 +6303,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6228
6303
|
});
|
|
6229
6304
|
}
|
|
6230
6305
|
|
|
6231
|
-
const { blocked, depth, maxDepth } = checkSubagentDepth(deps.config.maxSubagentDepth);
|
|
6306
|
+
const { blocked, depth, maxDepth } = checkSubagentDepth(deps.config.maxSubagentDepth, deps.childRuntime);
|
|
6232
6307
|
if (blocked) {
|
|
6233
6308
|
return {
|
|
6234
6309
|
content: [
|
|
@@ -6303,8 +6378,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6303
6378
|
});
|
|
6304
6379
|
const agents = applyScopedIntercomBridgeToAgents(discoveredAgents, intercomBridge, contextPolicy);
|
|
6305
6380
|
const runId = randomUUID();
|
|
6306
|
-
const
|
|
6307
|
-
const nestedParentAddress =
|
|
6381
|
+
const inheritedNestedRouteValue = inheritedNestedRoute(deps);
|
|
6382
|
+
const nestedParentAddress = inheritedNestedRouteValue ? inheritedNestedParentAddress(deps) : undefined;
|
|
6308
6383
|
const shareEnabled = effectiveParams.share === true;
|
|
6309
6384
|
const hasChain = (effectiveParams.chain?.length ?? 0) > 0;
|
|
6310
6385
|
const hasTasks = (effectiveParams.tasks?.length ?? 0) > 0;
|
|
@@ -6425,7 +6500,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6425
6500
|
const asyncRunId = requestedWorkflowChildAsyncId && path.basename(requestedWorkflowChildAsyncId) === requestedWorkflowChildAsyncId
|
|
6426
6501
|
? requestedWorkflowChildAsyncId
|
|
6427
6502
|
: randomUUID();
|
|
6428
|
-
const topLevelAsyncCapacityEligible = depth === 0 && !
|
|
6503
|
+
const topLevelAsyncCapacityEligible = depth === 0 && !inheritedNestedRouteValue && !effectiveParams.workflowParentRunId;
|
|
6429
6504
|
const topLevelAsync = effectiveAsync && topLevelAsyncCapacityEligible;
|
|
6430
6505
|
let activeAsyncCapacity: ActiveAsyncCapacityHandle | undefined;
|
|
6431
6506
|
if (topLevelAsync) {
|
|
@@ -6450,9 +6525,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6450
6525
|
|
|
6451
6526
|
let runFanoutBudget: RunFanoutBudgetDescriptor;
|
|
6452
6527
|
try {
|
|
6453
|
-
const
|
|
6528
|
+
const inheritedRunFanoutBudgetValue = effectiveParams.runFanoutBudget ? undefined : inheritedRunFanoutBudget(deps);
|
|
6454
6529
|
runFanoutBudget = effectiveParams.runFanoutBudget
|
|
6455
|
-
?? (
|
|
6530
|
+
?? (inheritedRunFanoutBudgetValue ? { ...inheritedRunFanoutBudgetValue, parentPath: `${inheritedRunFanoutBudgetValue.parentPath ? `${inheritedRunFanoutBudgetValue.parentPath}/` : ""}${runId}` } : undefined)
|
|
6456
6531
|
?? createRunFanoutBudget(runId, resolveMaxSubagentSpawnsPerRun(deps.config.maxSubagentSpawnsPerRun));
|
|
6457
6532
|
if (!effectiveParams.runFanoutAdmitted) claimRunFanoutBatch(runFanoutBudget, staticRunFanoutPaths(effectiveParams));
|
|
6458
6533
|
} catch (error) {
|
|
@@ -6460,7 +6535,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6460
6535
|
if (error instanceof RunFanoutLimitError) return runFanoutErrorResult(error, foregroundMode);
|
|
6461
6536
|
return buildRequestedModeError(effectiveParams, error instanceof Error ? error.message : String(error));
|
|
6462
6537
|
}
|
|
6463
|
-
const nestedRoute =
|
|
6538
|
+
const nestedRoute = inheritedNestedRouteValue ?? createNestedRoute(runId);
|
|
6464
6539
|
|
|
6465
6540
|
const artifactConfig: ArtifactConfig = omitUndefinedProperties({
|
|
6466
6541
|
...DEFAULT_ARTIFACT_CONFIG,
|
|
@@ -6632,17 +6707,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6632
6707
|
const foregroundDescription = selectedAgentNames.length === 1
|
|
6633
6708
|
? `${selectedAgentNames[0]} child`
|
|
6634
6709
|
: `${selectedAgentNames.length} live children`;
|
|
6635
|
-
const parentWorkflowStatus = effectiveParams.workflowParentRunId
|
|
6636
|
-
? readStatus(path.join(DIRS.async, effectiveParams.workflowParentRunId))
|
|
6637
|
-
: null;
|
|
6638
|
-
const workflowSteeringDir = effectiveParams.workflowParentRunId
|
|
6639
|
-
&& requestSessionId
|
|
6640
|
-
&& deps.state.workflowControllers?.has(effectiveParams.workflowParentRunId)
|
|
6641
|
-
&& parentWorkflowStatus?.mode === "workflow"
|
|
6642
|
-
&& (parentWorkflowStatus.state === "running" || parentWorkflowStatus.state === "queued")
|
|
6643
|
-
&& parentWorkflowStatus.sessionId === requestSessionId
|
|
6644
|
-
? workflowForegroundSteeringDir(DIRS.async, effectiveParams.workflowParentRunId, runId)
|
|
6645
|
-
: undefined;
|
|
6646
6710
|
const foregroundControl: ForegroundRunControl | undefined = effectiveAsync
|
|
6647
6711
|
? undefined
|
|
6648
6712
|
: compactOptional<ForegroundRunControl>({
|
|
@@ -6651,7 +6715,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6651
6715
|
mode: foregroundMode,
|
|
6652
6716
|
...(effectiveParams.workflowParentRunId ? { parentWorkflowRunId: effectiveParams.workflowParentRunId } : {}),
|
|
6653
6717
|
...(effectiveParams.workflowKey ? { workflowKey: effectiveParams.workflowKey } : {}),
|
|
6654
|
-
workflowSteeringDir,
|
|
6655
6718
|
startedAt: Date.now(),
|
|
6656
6719
|
updatedAt: Date.now(),
|
|
6657
6720
|
cwd: effectiveCwd,
|
|
@@ -6704,7 +6767,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6704
6767
|
}
|
|
6705
6768
|
|
|
6706
6769
|
const writeNestedForegroundEvent = (type: "subagent.nested.started" | "subagent.nested.completed", result?: AgentToolResult<Details>): void => {
|
|
6707
|
-
if (!
|
|
6770
|
+
if (!inheritedNestedRouteValue || !nestedParentAddress) return;
|
|
6708
6771
|
const now = Date.now();
|
|
6709
6772
|
const details = result?.details;
|
|
6710
6773
|
const state = type === "subagent.nested.started"
|
|
@@ -6738,7 +6801,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6738
6801
|
? resolveSubagentIntercomTarget(runId, agentsForSummary[0], 0)
|
|
6739
6802
|
: undefined;
|
|
6740
6803
|
try {
|
|
6741
|
-
writeNestedEvent(
|
|
6804
|
+
writeNestedEvent(inheritedNestedRouteValue, compactOptional<Parameters<typeof writeNestedEvent>[1]>({
|
|
6742
6805
|
type,
|
|
6743
6806
|
ts: now,
|
|
6744
6807
|
parentRunId: nestedParentAddress.parentRunId,
|
|
@@ -6749,7 +6812,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6749
6812
|
parentStepIndex: nestedParentAddress.parentStepIndex,
|
|
6750
6813
|
depth: nestedParentAddress.depth,
|
|
6751
6814
|
path: nestedParentAddress.path,
|
|
6752
|
-
ownerIntercomTarget:
|
|
6815
|
+
ownerIntercomTarget: deps.childRuntime?.intercomSessionName,
|
|
6753
6816
|
leafIntercomTarget,
|
|
6754
6817
|
intercomTarget: leafIntercomTarget,
|
|
6755
6818
|
ownerState: state === "running" ? "live" : "gone",
|
|
@@ -6860,7 +6923,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
6860
6923
|
const normalizedAction = typeof params.action === "string" ? params.action.trim() : params.action;
|
|
6861
6924
|
const requestParams = normalizedAction ? { ...params, action: normalizedAction } : params;
|
|
6862
6925
|
if (normalizedAction) return execute(id, requestParams, signal, onUpdate, ctx).then(withAggregatedToolUsage);
|
|
6863
|
-
const { depth } = checkSubagentDepth(deps.config.maxSubagentDepth);
|
|
6926
|
+
const { depth } = checkSubagentDepth(deps.config.maxSubagentDepth, deps.childRuntime);
|
|
6864
6927
|
const dispatchParams = applyForceTopLevelAsyncOverride(requestParams, depth, deps.config.forceTopLevelAsync === true);
|
|
6865
6928
|
const runsForeground = dispatchParams.clarify === true || (dispatchParams.async ?? deps.asyncByDefault) !== true;
|
|
6866
6929
|
if (!runsForeground) return execute(id, requestParams, signal, onUpdate, ctx).then(withAggregatedToolUsage);
|