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.
Files changed (107) hide show
  1. package/CHANGELOG.md +57 -1
  2. package/README.md +2 -2
  3. package/agents/reviewer.md +1 -1
  4. package/agents/scout.md +1 -1
  5. package/docs/agents.md +18 -16
  6. package/docs/configuration.md +7 -15
  7. package/docs/extension-api.md +15 -6
  8. package/docs/missions.md +2 -0
  9. package/docs/observability.md +10 -12
  10. package/docs/tool-reference.md +7 -6
  11. package/docs/watchdog.md +92 -114
  12. package/docs/workflows.md +5 -3
  13. package/package.json +3 -4
  14. package/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
  15. package/skills/pi-subagents/references/execution-controls.md +6 -5
  16. package/src/agents/agent-management.ts +47 -15
  17. package/src/api/capability-ceiling.ts +0 -1
  18. package/src/api/{pi-args.ts → child-tool-plan.ts} +1 -1
  19. package/src/api/preflight.ts +2 -3
  20. package/src/extension/doctor.ts +2 -10
  21. package/src/extension/fanout-child.ts +9 -11
  22. package/src/extension/index.ts +27 -5
  23. package/src/extension/public-execution.ts +14 -0
  24. package/src/extension/rpc.ts +3 -2
  25. package/src/extension/schemas.ts +2 -1
  26. package/src/extension/tool-description.ts +9 -8
  27. package/src/intercom/native-supervisor-channel.ts +138 -60
  28. package/src/intercom/supervisor-ui.ts +243 -0
  29. package/src/runs/background/async-execution.ts +46 -24
  30. package/src/runs/background/async-job-tracker.ts +11 -0
  31. package/src/runs/background/async-resume.ts +11 -2
  32. package/src/runs/background/control-channel.ts +2 -204
  33. package/src/runs/background/notify.ts +41 -2
  34. package/src/runs/background/process-terminal.ts +1 -1
  35. package/src/runs/background/run-child-session.ts +613 -0
  36. package/src/runs/background/run-status.ts +0 -1
  37. package/src/runs/background/runner-aliases.ts +125 -0
  38. package/src/runs/background/runner-child-sessions.ts +31 -0
  39. package/src/runs/background/scheduled-runs.ts +18 -4
  40. package/src/runs/background/subagent-runner.ts +203 -898
  41. package/src/runs/foreground/async-steering-action.ts +1 -17
  42. package/src/runs/foreground/execution.ts +196 -378
  43. package/src/runs/foreground/foreground-control.ts +4 -0
  44. package/src/runs/foreground/subagent-executor.ts +121 -58
  45. package/src/runs/foreground/workflow-foreground-steering.ts +24 -98
  46. package/src/runs/shared/abort-recovery.ts +3 -3
  47. package/src/runs/shared/acceptance.ts +10 -0
  48. package/src/runs/shared/async-status-projection.ts +11 -43
  49. package/src/runs/shared/capability-ceiling.ts +1 -2
  50. package/src/runs/shared/child-hooks.ts +25 -0
  51. package/src/runs/shared/child-identity.ts +13 -2
  52. package/src/runs/shared/child-launch.ts +314 -0
  53. package/src/runs/shared/child-lifecycle.ts +25 -0
  54. package/src/runs/shared/child-runtime-config.ts +126 -0
  55. package/src/runs/shared/child-session.ts +342 -0
  56. package/src/runs/shared/child-tool-plan.ts +530 -0
  57. package/src/runs/shared/claude-code-adapter.ts +5 -1
  58. package/src/runs/shared/completion-guard.ts +1 -1
  59. package/src/runs/shared/external-cli-preflight.ts +16 -0
  60. package/src/runs/shared/mcp-direct-tool-allowlist.ts +5 -4
  61. package/src/runs/shared/model-exclusions.ts +82 -14
  62. package/src/runs/shared/model-fallback.ts +47 -4
  63. package/src/runs/shared/nested-events.ts +29 -45
  64. package/src/runs/shared/nested-path.ts +0 -14
  65. package/src/runs/shared/orca-progress-tabs.ts +12 -7
  66. package/src/runs/shared/parallel-utils.ts +0 -2
  67. package/src/runs/shared/permissions.ts +0 -13
  68. package/src/runs/shared/process-signal.ts +4 -1
  69. package/src/runs/shared/run-fanout-budget.ts +0 -13
  70. package/src/runs/shared/runtime-acknowledged-extensions.ts +0 -27
  71. package/src/runs/shared/structured-output.ts +17 -4
  72. package/src/runs/shared/subagent-control.ts +6 -2
  73. package/src/runs/shared/subagent-prompt-runtime.ts +87 -384
  74. package/src/runs/shared/tool-availability.ts +18 -62
  75. package/src/runs/shared/tool-budget.ts +0 -14
  76. package/src/runs/shared/worktree-cleanup-plan.ts +25 -6
  77. package/src/runs/shared/worktree.ts +117 -30
  78. package/src/shared/child-session-name.ts +1 -1
  79. package/src/shared/jsonl-writer.ts +11 -0
  80. package/src/shared/thinking-ceiling.ts +0 -6
  81. package/src/shared/types.ts +64 -30
  82. package/src/shared/utils.ts +3 -4
  83. package/src/slash/slash-commands.ts +0 -6
  84. package/src/tui/fleet.ts +0 -1
  85. package/src/tui/render.ts +238 -31
  86. package/src/watchdog/child-status.ts +54 -34
  87. package/src/watchdog/diff-tool.ts +77 -0
  88. package/src/watchdog/emission-guard.ts +5 -3
  89. package/src/watchdog/guidance.ts +20 -0
  90. package/src/watchdog/register-child.ts +28 -25
  91. package/src/watchdog/register-main.ts +10 -9
  92. package/src/watchdog/render.ts +4 -5
  93. package/src/watchdog/review.ts +15 -4
  94. package/src/watchdog/rules.ts +70 -0
  95. package/src/watchdog/runtime.ts +75 -92
  96. package/src/watchdog/scope.ts +0 -11
  97. package/src/watchdog/settings.ts +48 -104
  98. package/src/watchdog/types.ts +18 -32
  99. package/src/watchdog/warning-format.ts +0 -1
  100. package/src/workflows/chat-progress.ts +3 -2
  101. package/src/workflows/scripted-workflow.ts +48 -1
  102. package/src/workflows/workflow-checklist.ts +10 -12
  103. package/src/workflows/workflow-preflight.ts +28 -1
  104. package/src/runs/shared/child-protocol.ts +0 -415
  105. package/src/runs/shared/pi-args.ts +0 -1062
  106. package/src/runs/shared/subagent-startup-retry.ts +0 -116
  107. 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, decodeRunFanoutBudgetDescriptor, formatRunFanoutBudget, getRunFanoutBudgetSnapshot, readRunFanoutBudgetDescriptor, RunFanoutLimitError, RUN_FANOUT_BUDGET_ENV, writeRunFanoutBudgetDescriptor } from "../shared/run-fanout-budget.ts";
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, resolveInheritedNestedRouteFromEnv, resolveNestedAsyncDir, resolveNestedParentAddressFromEnv, snapshotNestedEventFiles, updateForegroundNestedProjection, writeNestedControlRequest, writeNestedEvent, type NestedRunResolutionScope } from "../shared/nested-events.ts";
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 = resolveInheritedNestedRouteFromEnv();
568
- const address = route ? resolveNestedParentAddressFromEnv() : undefined;
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 && !resolveInheritedNestedRouteFromEnv() && !input.deps.state.workflowControllers?.has(input.target.runId);
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 && !resolveInheritedNestedRouteFromEnv() && !input.params.workflowParentRunId;
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 && !resolveInheritedNestedRouteFromEnv() && !input.params.workflowParentRunId;
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: recoveryDescriptor?.controlConfig ?? resolveControlConfig(input.deps.config.control, input.params.control),
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
- ...workflowForegroundSteeringLaunchOptions(foregroundControl, 0),
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
- return workflowSteerReceipt(input.key, result);
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
- && !resolveInheritedNestedRouteFromEnv()
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
- ?? decodeRunFanoutBudgetDescriptor(process.env[RUN_FANOUT_BUDGET_ENV])
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, signal });
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, signal });
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, signal });
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 inheritedNestedRoute = resolveInheritedNestedRouteFromEnv();
6307
- const nestedParentAddress = inheritedNestedRoute ? resolveNestedParentAddressFromEnv() : undefined;
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 && !inheritedNestedRoute && !effectiveParams.workflowParentRunId;
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 inheritedRunFanoutBudget = effectiveParams.runFanoutBudget ? undefined : decodeRunFanoutBudgetDescriptor(process.env[RUN_FANOUT_BUDGET_ENV]);
6528
+ const inheritedRunFanoutBudgetValue = effectiveParams.runFanoutBudget ? undefined : inheritedRunFanoutBudget(deps);
6454
6529
  runFanoutBudget = effectiveParams.runFanoutBudget
6455
- ?? (inheritedRunFanoutBudget ? { ...inheritedRunFanoutBudget, parentPath: `${inheritedRunFanoutBudget.parentPath ? `${inheritedRunFanoutBudget.parentPath}/` : ""}${runId}` } : undefined)
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 = inheritedNestedRoute ?? createNestedRoute(runId);
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 (!inheritedNestedRoute || !nestedParentAddress) return;
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(inheritedNestedRoute, compactOptional<Parameters<typeof writeNestedEvent>[1]>({
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: process.env.PI_SUBAGENT_INTERCOM_SESSION_NAME,
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);