pi-subagents 0.64.0 → 0.65.1

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