@bastani/atomic 0.8.19 → 0.8.20

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 (103) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/builtin/intercom/package.json +1 -1
  3. package/dist/builtin/mcp/CHANGELOG.md +10 -0
  4. package/dist/builtin/mcp/package.json +2 -2
  5. package/dist/builtin/subagents/CHANGELOG.md +17 -2
  6. package/dist/builtin/subagents/agents/code-simplifier.md +1 -1
  7. package/dist/builtin/subagents/agents/codebase-analyzer.md +1 -1
  8. package/dist/builtin/subagents/agents/codebase-online-researcher.md +1 -1
  9. package/dist/builtin/subagents/agents/codebase-research-analyzer.md +1 -1
  10. package/dist/builtin/subagents/agents/debugger.md +1 -1
  11. package/dist/builtin/subagents/package.json +1 -1
  12. package/dist/builtin/subagents/skills/subagent/SKILL.md +12 -12
  13. package/dist/builtin/subagents/src/agents/agent-management.ts +16 -11
  14. package/dist/builtin/subagents/src/agents/skills.ts +13 -1
  15. package/dist/builtin/subagents/src/extension/index.ts +14 -3
  16. package/dist/builtin/subagents/src/runs/background/async-execution.ts +8 -0
  17. package/dist/builtin/subagents/src/runs/background/run-status.ts +2 -3
  18. package/dist/builtin/subagents/src/runs/background/subagent-runner.ts +11 -1
  19. package/dist/builtin/subagents/src/runs/foreground/chain-clarify.ts +2 -2
  20. package/dist/builtin/subagents/src/runs/foreground/chain-execution.ts +31 -23
  21. package/dist/builtin/subagents/src/runs/foreground/execution.ts +13 -7
  22. package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +160 -93
  23. package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +1 -0
  24. package/dist/builtin/subagents/src/runs/shared/run-history.ts +1 -1
  25. package/dist/builtin/subagents/src/shared/settings.ts +1 -0
  26. package/dist/builtin/subagents/src/shared/types.ts +78 -4
  27. package/dist/builtin/subagents/src/tui/render.ts +203 -19
  28. package/dist/builtin/web-access/CHANGELOG.md +10 -0
  29. package/dist/builtin/web-access/package.json +2 -2
  30. package/dist/builtin/workflows/CHANGELOG.md +25 -0
  31. package/dist/builtin/workflows/README.md +22 -3
  32. package/dist/builtin/workflows/builtin/deep-research-codebase.ts +1 -1
  33. package/dist/builtin/workflows/builtin/open-claude-design.ts +12 -4
  34. package/dist/builtin/workflows/builtin/ralph.ts +2 -2
  35. package/dist/builtin/workflows/package.json +1 -1
  36. package/dist/builtin/workflows/src/extension/config-loader.ts +68 -0
  37. package/dist/builtin/workflows/src/extension/index.ts +246 -55
  38. package/dist/builtin/workflows/src/extension/lifecycle-notifications.ts +372 -0
  39. package/dist/builtin/workflows/src/extension/render-call.ts +1 -1
  40. package/dist/builtin/workflows/src/extension/wiring.ts +32 -3
  41. package/dist/builtin/workflows/src/runs/background/status.ts +14 -74
  42. package/dist/builtin/workflows/src/shared/persistence-restore.ts +5 -3
  43. package/dist/builtin/workflows/src/tui/chat-surface-message.ts +3 -13
  44. package/dist/builtin/workflows/src/tui/inline-form-overlay.ts +2 -10
  45. package/dist/builtin/workflows/src/tui/overlay-adapter.ts +5 -5
  46. package/dist/builtin/workflows/src/tui/session-confirm.ts +6 -7
  47. package/dist/builtin/workflows/src/tui/session-picker.ts +18 -14
  48. package/dist/builtin/workflows/src/tui/status-list.ts +2 -2
  49. package/dist/builtin/workflows/src/tui/store-widget-installer.ts +125 -30
  50. package/dist/config.d.ts +1 -0
  51. package/dist/config.d.ts.map +1 -1
  52. package/dist/config.js +1 -0
  53. package/dist/config.js.map +1 -1
  54. package/dist/core/agent-session.d.ts +4 -1
  55. package/dist/core/agent-session.d.ts.map +1 -1
  56. package/dist/core/agent-session.js +2 -1
  57. package/dist/core/agent-session.js.map +1 -1
  58. package/dist/core/atomic-guide-command.d.ts.map +1 -1
  59. package/dist/core/atomic-guide-command.js +3 -2
  60. package/dist/core/atomic-guide-command.js.map +1 -1
  61. package/dist/core/extensions/index.d.ts +1 -1
  62. package/dist/core/extensions/index.d.ts.map +1 -1
  63. package/dist/core/extensions/index.js.map +1 -1
  64. package/dist/core/extensions/runner.d.ts +3 -2
  65. package/dist/core/extensions/runner.d.ts.map +1 -1
  66. package/dist/core/extensions/runner.js +6 -1
  67. package/dist/core/extensions/runner.js.map +1 -1
  68. package/dist/core/extensions/types.d.ts +13 -0
  69. package/dist/core/extensions/types.d.ts.map +1 -1
  70. package/dist/core/extensions/types.js.map +1 -1
  71. package/dist/core/model-resolver.d.ts.map +1 -1
  72. package/dist/core/model-resolver.js +63 -17
  73. package/dist/core/model-resolver.js.map +1 -1
  74. package/dist/core/output-guard.d.ts.map +1 -1
  75. package/dist/core/output-guard.js +29 -0
  76. package/dist/core/output-guard.js.map +1 -1
  77. package/dist/core/sdk.d.ts +3 -1
  78. package/dist/core/sdk.d.ts.map +1 -1
  79. package/dist/core/sdk.js +1 -0
  80. package/dist/core/sdk.js.map +1 -1
  81. package/dist/core/system-prompt.d.ts.map +1 -1
  82. package/dist/core/system-prompt.js +1 -1
  83. package/dist/core/system-prompt.js.map +1 -1
  84. package/dist/index.d.ts +2 -2
  85. package/dist/index.d.ts.map +1 -1
  86. package/dist/index.js +1 -1
  87. package/dist/index.js.map +1 -1
  88. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  89. package/dist/modes/interactive/interactive-mode.js +46 -13
  90. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  91. package/dist/utils/pi-user-agent.d.ts.map +1 -1
  92. package/dist/utils/pi-user-agent.js +2 -1
  93. package/dist/utils/pi-user-agent.js.map +1 -1
  94. package/dist/utils/syntax-highlight.d.ts.map +1 -1
  95. package/dist/utils/syntax-highlight.js +1 -1
  96. package/dist/utils/syntax-highlight.js.map +1 -1
  97. package/dist/utils/tools-manager.d.ts.map +1 -1
  98. package/dist/utils/tools-manager.js +3 -5
  99. package/dist/utils/tools-manager.js.map +1 -1
  100. package/docs/models.md +52 -52
  101. package/docs/quickstart.md +2 -2
  102. package/docs/workflows.md +22 -5
  103. package/package.json +9 -9
@@ -1,7 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
- import type { AgentToolResult } from "@earendil-works/pi-agent-core";
5
4
  import { APP_NAME, getEnvValue, type ExtensionAPI, type ExtensionContext } from "@bastani/atomic";
6
5
  import { type AgentConfig, type AgentScope } from "../../agents/agents.ts";
7
6
  import { getArtifactsDir } from "../../shared/artifacts.ts";
@@ -78,15 +77,19 @@ import {
78
77
  type SingleResult,
79
78
  type SubagentRunMode,
80
79
  type SubagentState,
80
+ type SubagentToolResult,
81
81
  DEFAULT_ARTIFACT_CONFIG,
82
82
  SUBAGENT_ACTIONS,
83
83
  SUBAGENT_CONTROL_EVENT,
84
84
  SUBAGENT_CONTROL_INTERCOM_EVENT,
85
85
  checkSubagentDepth,
86
+ isWorkflowStageOrchestrationContext,
86
87
  resolveTopLevelParallelConcurrency,
87
88
  resolveTopLevelParallelMaxTasks,
88
89
  resolveChildMaxSubagentDepth,
89
- resolveCurrentMaxSubagentDepth,
90
+ resolveSubagentDepthPolicy,
91
+ resolveWorkflowStageMaxSubagentDepth,
92
+ subagentDepthBlockedMessage,
90
93
  wrapForkTask,
91
94
  } from "../../shared/types.ts";
92
95
 
@@ -107,7 +110,7 @@ interface TaskParam {
107
110
  }
108
111
 
109
112
  export interface SubagentParamsLike {
110
- action?: string;
113
+ action?: (typeof SUBAGENT_ACTIONS)[number];
111
114
  id?: string;
112
115
  runId?: string;
113
116
  dir?: string;
@@ -115,6 +118,8 @@ export interface SubagentParamsLike {
115
118
  agent?: string;
116
119
  task?: string;
117
120
  message?: string;
121
+ chainName?: string;
122
+ config?: unknown;
118
123
  chain?: ChainStep[];
119
124
  tasks?: TaskParam[];
120
125
  concurrency?: number;
@@ -133,10 +138,32 @@ export interface SubagentParamsLike {
133
138
  skill?: string | string[] | boolean;
134
139
  output?: string | boolean;
135
140
  outputMode?: "inline" | "file-only";
136
- agentScope?: unknown;
141
+ agentScope?: string;
137
142
  chainDir?: string;
138
143
  }
139
144
 
145
+ export interface SubagentExecutorRuntimeDeps {
146
+ runSync: typeof runSync;
147
+ executeAsyncChain: typeof executeAsyncChain;
148
+ executeAsyncSingle: typeof executeAsyncSingle;
149
+ isAsyncAvailable: typeof isAsyncAvailable;
150
+ formatAsyncStartedMessage: typeof formatAsyncStartedMessage;
151
+ }
152
+
153
+ const defaultSubagentExecutorRuntimeDeps: SubagentExecutorRuntimeDeps = {
154
+ runSync,
155
+ executeAsyncChain,
156
+ executeAsyncSingle,
157
+ isAsyncAvailable,
158
+ formatAsyncStartedMessage,
159
+ };
160
+
161
+ function resolveSubagentExecutorRuntimeDeps(
162
+ overrides?: Partial<SubagentExecutorRuntimeDeps>,
163
+ ): SubagentExecutorRuntimeDeps {
164
+ return { ...defaultSubagentExecutorRuntimeDeps, ...overrides };
165
+ }
166
+
140
167
  interface ExecutorDeps {
141
168
  pi: ExtensionAPI;
142
169
  state: SubagentState;
@@ -147,6 +174,11 @@ interface ExecutorDeps {
147
174
  expandTilde: (p: string) => string;
148
175
  discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
149
176
  allowMutatingManagementActions?: boolean;
177
+ runtime?: Partial<SubagentExecutorRuntimeDeps>;
178
+ }
179
+
180
+ interface ResolvedExecutorDeps extends Omit<ExecutorDeps, "runtime"> {
181
+ runtime: SubagentExecutorRuntimeDeps;
150
182
  }
151
183
 
152
184
  interface ExecutionContextData {
@@ -154,7 +186,7 @@ interface ExecutionContextData {
154
186
  effectiveCwd: string;
155
187
  ctx: ExtensionContext;
156
188
  signal: AbortSignal;
157
- onUpdate?: (r: AgentToolResult<Details>) => void;
189
+ onUpdate?: (r: SubagentToolResult) => void;
158
190
  agents: AgentConfig[];
159
191
  runId: string;
160
192
  shareEnabled: boolean;
@@ -206,7 +238,7 @@ function formatForegroundActivity(control: SubagentState["foregroundControls"] e
206
238
  return [`active ${seconds}s ago`, ...facts].join(" | ");
207
239
  }
208
240
 
209
- function nestedResolutionScopeForExecutor(deps: ExecutorDeps): NestedRunResolutionScope | undefined {
241
+ function nestedResolutionScopeForExecutor(deps: ResolvedExecutorDeps): NestedRunResolutionScope | undefined {
210
242
  if (deps.allowMutatingManagementActions !== false) return undefined;
211
243
  const route = resolveInheritedNestedRouteFromEnv();
212
244
  const address = route ? resolveNestedParentAddressFromEnv() : undefined;
@@ -216,7 +248,7 @@ function nestedResolutionScopeForExecutor(deps: ExecutorDeps): NestedRunResoluti
216
248
  };
217
249
  }
218
250
 
219
- function foregroundStatusResult(control: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never): AgentToolResult<Details> {
251
+ function foregroundStatusResult(control: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never): SubagentToolResult {
220
252
  let nestedWarning: string | undefined;
221
253
  try {
222
254
  updateForegroundNestedProjection(control);
@@ -399,7 +431,7 @@ function emitControlNotification(input: {
399
431
  }
400
432
  }
401
433
 
402
- function interruptAsyncRun(state: SubagentState, runId: string | undefined): AgentToolResult<Details> | null {
434
+ function interruptAsyncRun(state: SubagentState, runId: string | undefined): SubagentToolResult | null {
403
435
  const target = getAsyncInterruptTarget(state, runId);
404
436
  if (!target) return null;
405
437
  const status = readStatus(target.asyncDir);
@@ -509,7 +541,7 @@ async function sendNestedControlRequest(target: ResolvedSubagentRunId & { kind:
509
541
  return waitForNestedControlResult(target, requestId);
510
542
  }
511
543
 
512
- function directNestedAsyncInterrupt(target: ResolvedSubagentRunId & { kind: "nested" }): AgentToolResult<Details> | undefined {
544
+ function directNestedAsyncInterrupt(target: ResolvedSubagentRunId & { kind: "nested" }): SubagentToolResult | undefined {
513
545
  const run = target.match.run;
514
546
  const asyncDir = resolveNestedAsyncDir(target.match.rootRunId, run);
515
547
  if (!asyncDir) return undefined;
@@ -525,7 +557,7 @@ function directNestedAsyncInterrupt(target: ResolvedSubagentRunId & { kind: "nes
525
557
  }
526
558
  }
527
559
 
528
- async function interruptNestedRun(target: ResolvedSubagentRunId & { kind: "nested" }): Promise<AgentToolResult<Details>> {
560
+ async function interruptNestedRun(target: ResolvedSubagentRunId & { kind: "nested" }): Promise<SubagentToolResult> {
529
561
  const run = target.match.run;
530
562
  if (run.state === "complete") return { content: [{ type: "text", text: `Nested run ${run.id} is already complete and cannot be interrupted.` }], isError: true, details: { mode: "management", results: [] } };
531
563
  if (run.state === "failed") return { content: [{ type: "text", text: `Nested run ${run.id} has failed and cannot be interrupted.` }], isError: true, details: { mode: "management", results: [] } };
@@ -537,7 +569,7 @@ async function interruptNestedRun(target: ResolvedSubagentRunId & { kind: "neste
537
569
  return { content: [{ type: "text", text: `Nested run ${run.id} owner is not reachable and no safe direct async interrupt fallback is available.` }], isError: true, details: { mode: "management", results: [] } };
538
570
  }
539
571
 
540
- async function resumeLiveNestedRun(input: { target: ResolvedSubagentRunId & { kind: "nested" }; message: string }): Promise<AgentToolResult<Details>> {
572
+ async function resumeLiveNestedRun(input: { target: ResolvedSubagentRunId & { kind: "nested" }; message: string }): Promise<SubagentToolResult> {
541
573
  const run = input.target.match.run;
542
574
  const result = await sendNestedControlRequest(input.target, "resume", input.message);
543
575
  if (result) return { content: [{ type: "text", text: result.message }], isError: result.ok ? undefined : true, details: { mode: "management", results: [] } };
@@ -548,8 +580,8 @@ async function resumeAsyncRun(input: {
548
580
  params: SubagentParamsLike;
549
581
  requestCwd: string;
550
582
  ctx: ExtensionContext;
551
- deps: ExecutorDeps;
552
- }): Promise<AgentToolResult<Details>> {
583
+ deps: ResolvedExecutorDeps;
584
+ }): Promise<SubagentToolResult> {
553
585
  const followUp = (input.params.message ?? input.params.task ?? "").trim();
554
586
  if (!followUp) {
555
587
  return {
@@ -602,10 +634,17 @@ async function resumeAsyncRun(input: {
602
634
  };
603
635
  }
604
636
 
605
- const { blocked, depth, maxDepth } = checkSubagentDepth(input.deps.config.maxSubagentDepth);
637
+ const resumeDepthPolicy = resolveSubagentDepthPolicy(input.ctx, input.deps.config.maxSubagentDepth);
638
+ const { blocked, depth, maxDepth, workflowStageGuard } = checkSubagentDepth(resumeDepthPolicy.maxSubagentDepth);
606
639
  if (blocked) {
607
640
  return {
608
- content: [{ type: "text", text: `Nested subagent resume blocked (depth=${depth}, max=${maxDepth}). Complete the follow-up directly instead.` }],
641
+ content: [{
642
+ type: "text",
643
+ text: subagentDepthBlockedMessage(depth, maxDepth, {
644
+ action: "resume",
645
+ workflowStageGuard: workflowStageGuard || resumeDepthPolicy.workflowStageSubagentGuard,
646
+ }),
647
+ }],
609
648
  isError: true,
610
649
  details: { mode: "management", results: [] },
611
650
  };
@@ -637,7 +676,7 @@ async function resumeAsyncRun(input: {
637
676
  const runId = randomUUID().slice(0, 8);
638
677
  const artifactConfig: ArtifactConfig = { ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false };
639
678
  const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
640
- const result = executeAsyncSingle(runId, {
679
+ const result = input.deps.runtime.executeAsyncSingle(runId, {
641
680
  agent: target.agent,
642
681
  task: buildRevivedAsyncTask(target, followUp),
643
682
  agentConfig,
@@ -655,7 +694,8 @@ async function resumeAsyncRun(input: {
655
694
  shareEnabled: input.params.share === true,
656
695
  sessionRoot: input.deps.getSubagentSessionRoot(parentSessionFile),
657
696
  sessionFile: target.sessionFile,
658
- maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
697
+ maxSubagentDepth: resolveWorkflowStageMaxSubagentDepth(input.ctx, input.deps.config.maxSubagentDepth),
698
+ workflowStageSubagentGuard: isWorkflowStageOrchestrationContext(input.ctx),
659
699
  worktreeSetupHook: input.deps.config.worktreeSetupHook,
660
700
  worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
661
701
  controlConfig: resolveControlConfig(input.deps.config.control, input.params.control),
@@ -677,7 +717,7 @@ async function resumeAsyncRun(input: {
677
717
  revivedTarget ? `Intercom target: ${revivedTarget} (if registered)` : undefined,
678
718
  `Status if needed: subagent({ action: "status", id: "${revivedId}" })`,
679
719
  ].filter((line): line is string => Boolean(line));
680
- return { content: [{ type: "text", text: formatAsyncStartedMessage(lines.join("\n")) }], details: result.details };
720
+ return { content: [{ type: "text", text: input.deps.runtime.formatAsyncStartedMessage(lines.join("\n")) }], details: result.details };
681
721
  }
682
722
 
683
723
  function resultSummaryForIntercom(result: SingleResult): string {
@@ -765,7 +805,7 @@ function validateExecutionInput(
765
805
  hasTasks: boolean,
766
806
  hasSingle: boolean,
767
807
  allowClarifyTaskPrompt: boolean,
768
- ): AgentToolResult<Details> | null {
808
+ ): SubagentToolResult | null {
769
809
  if (Number(hasChain) + Number(hasTasks) + Number(hasSingle) !== 1) {
770
810
  return {
771
811
  content: [
@@ -869,7 +909,7 @@ function applyAgentDefaultContext(params: SubagentParamsLike, agents: AgentConfi
869
909
  : params;
870
910
  }
871
911
 
872
- function buildRequestedModeError(params: SubagentParamsLike, message: string): AgentToolResult<Details> {
912
+ function buildRequestedModeError(params: SubagentParamsLike, message: string): SubagentToolResult {
873
913
  return withForkContext(
874
914
  {
875
915
  content: [{ type: "text", text: message }],
@@ -921,7 +961,7 @@ function expandChainParallelCounts(chain: ChainStep[]): { chain?: ChainStep[]; e
921
961
  return { chain: expandedChain };
922
962
  }
923
963
 
924
- function normalizeRepeatedParallelCounts(params: SubagentParamsLike): { params?: SubagentParamsLike; error?: AgentToolResult<Details> } {
964
+ function normalizeRepeatedParallelCounts(params: SubagentParamsLike): { params?: SubagentParamsLike; error?: SubagentToolResult } {
925
965
  if (params.tasks) {
926
966
  const expandedTasks = expandTopLevelTaskCounts(params.tasks);
927
967
  if (expandedTasks.error) {
@@ -940,9 +980,9 @@ function normalizeRepeatedParallelCounts(params: SubagentParamsLike): { params?:
940
980
  }
941
981
 
942
982
  function withForkContext(
943
- result: AgentToolResult<Details>,
983
+ result: SubagentToolResult,
944
984
  context: SubagentParamsLike["context"],
945
- ): AgentToolResult<Details> {
985
+ ): SubagentToolResult {
946
986
  if (context !== "fork" || !result.details) return result;
947
987
  return {
948
988
  ...result,
@@ -953,7 +993,7 @@ function withForkContext(
953
993
  };
954
994
  }
955
995
 
956
- function toExecutionErrorResult(params: SubagentParamsLike, error: unknown): AgentToolResult<Details> {
996
+ function toExecutionErrorResult(params: SubagentParamsLike, error: unknown): SubagentToolResult {
957
997
  const message = error instanceof Error ? error.message : String(error);
958
998
  return withForkContext(
959
999
  {
@@ -1005,7 +1045,7 @@ function wrapChainTasksForFork(chain: ChainStep[], context: SubagentParamsLike["
1005
1045
  });
1006
1046
  }
1007
1047
 
1008
- function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentToolResult<Details> | null {
1048
+ function runAsyncPath(data: ExecutionContextData, deps: ResolvedExecutorDeps): SubagentToolResult | null {
1009
1049
  const {
1010
1050
  params,
1011
1051
  effectiveCwd,
@@ -1048,7 +1088,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
1048
1088
  }
1049
1089
  }
1050
1090
 
1051
- if (!isAsyncAvailable()) {
1091
+ if (!deps.runtime.isAsyncAvailable()) {
1052
1092
  return {
1053
1093
  content: [{ type: "text", text: `Async mode requires upstream jiti for TypeScript execution but it could not be found. Ensure the ${APP_NAME}-subagents package dependencies are installed.` }],
1054
1094
  isError: true,
@@ -1064,7 +1104,9 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
1064
1104
  currentModel: currentModelFullId(ctx.model),
1065
1105
  };
1066
1106
  const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
1067
- const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
1107
+ const depthPolicy = resolveSubagentDepthPolicy(ctx, deps.config.maxSubagentDepth);
1108
+ const currentMaxSubagentDepth = depthPolicy.maxSubagentDepth;
1109
+ const workflowStageSubagentGuard = depthPolicy.workflowStageSubagentGuard;
1068
1110
  const currentProvider = ctx.model?.provider;
1069
1111
  const controlIntercomTarget = intercomBridge.active ? intercomBridge.orchestratorTarget : undefined;
1070
1112
  const childIntercomTarget = intercomBridge.active ? (agent: string, index: number) => resolveSubagentIntercomTarget(id, agent, index) : undefined;
@@ -1086,7 +1128,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
1086
1128
  ...(task.reads !== undefined && task.reads !== true ? { reads: task.reads } : {}),
1087
1129
  ...(task.progress !== undefined ? { progress: task.progress } : {}),
1088
1130
  }));
1089
- return executeAsyncChain(id, {
1131
+ return deps.runtime.executeAsyncChain(id, {
1090
1132
  chain: [{
1091
1133
  parallel: parallelTasks,
1092
1134
  concurrency: resolveTopLevelParallelConcurrency(params.concurrency, deps.config.parallel?.concurrency),
@@ -1105,6 +1147,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
1105
1147
  chainSkills: [],
1106
1148
  sessionFilesByFlatIndex: params.tasks.map((_, index) => sessionFileForIndex(index)),
1107
1149
  maxSubagentDepth: currentMaxSubagentDepth,
1150
+ workflowStageSubagentGuard,
1108
1151
  worktreeSetupHook: deps.config.worktreeSetupHook,
1109
1152
  worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
1110
1153
  controlConfig,
@@ -1118,7 +1161,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
1118
1161
  const normalized = normalizeSkillInput(params.skill);
1119
1162
  const chainSkills = normalized === false ? [] : (normalized ?? []);
1120
1163
  const chain = wrapChainTasksForFork(params.chain as ChainStep[], params.context);
1121
- return executeAsyncChain(id, {
1164
+ return deps.runtime.executeAsyncChain(id, {
1122
1165
  chain,
1123
1166
  task: params.task,
1124
1167
  agents,
@@ -1133,6 +1176,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
1133
1176
  chainSkills,
1134
1177
  sessionFilesByFlatIndex: collectChainSessionFiles(chain, sessionFileForIndex),
1135
1178
  maxSubagentDepth: currentMaxSubagentDepth,
1179
+ workflowStageSubagentGuard,
1136
1180
  worktreeSetupHook: deps.config.worktreeSetupHook,
1137
1181
  worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
1138
1182
  controlConfig,
@@ -1158,7 +1202,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
1158
1202
  const skills = normalizedSkills === false ? [] : normalizedSkills;
1159
1203
  const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, a.maxSubagentDepth);
1160
1204
  const modelOverride = resolveModelCandidate((params.model as string | undefined) ?? a.model, availableModels, currentProvider);
1161
- return executeAsyncSingle(id, {
1205
+ return deps.runtime.executeAsyncSingle(id, {
1162
1206
  agent: params.agent!,
1163
1207
  task: params.context === "fork" ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
1164
1208
  agentConfig: a,
@@ -1176,6 +1220,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
1176
1220
  outputMode: effectiveOutputMode,
1177
1221
  modelOverride,
1178
1222
  maxSubagentDepth,
1223
+ workflowStageSubagentGuard,
1179
1224
  worktreeSetupHook: deps.config.worktreeSetupHook,
1180
1225
  worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
1181
1226
  controlConfig,
@@ -1188,7 +1233,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
1188
1233
  return null;
1189
1234
  }
1190
1235
 
1191
- async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Promise<AgentToolResult<Details>> {
1236
+ async function runChainPath(data: ExecutionContextData, deps: ResolvedExecutorDeps): Promise<SubagentToolResult> {
1192
1237
  const {
1193
1238
  params,
1194
1239
  effectiveCwd,
@@ -1211,7 +1256,9 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
1211
1256
  const normalized = normalizeSkillInput(params.skill);
1212
1257
  const chainSkills = normalized === false ? [] : (normalized ?? []);
1213
1258
  const chain = wrapChainTasksForFork(params.chain as ChainStep[], params.context);
1214
- const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
1259
+ const depthPolicy = resolveSubagentDepthPolicy(ctx, deps.config.maxSubagentDepth);
1260
+ const currentMaxSubagentDepth = depthPolicy.maxSubagentDepth;
1261
+ const workflowStageSubagentGuard = depthPolicy.workflowStageSubagentGuard;
1215
1262
  const chainResult = await executeChain({
1216
1263
  chain,
1217
1264
  task: params.task,
@@ -1238,12 +1285,14 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
1238
1285
  chainSkills,
1239
1286
  chainDir: params.chainDir,
1240
1287
  maxSubagentDepth: currentMaxSubagentDepth,
1288
+ workflowStageSubagentGuard,
1241
1289
  worktreeSetupHook: deps.config.worktreeSetupHook,
1242
1290
  worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
1291
+ runSync: deps.runtime.runSync,
1243
1292
  });
1244
1293
 
1245
1294
  if (chainResult.requestedAsync) {
1246
- if (!isAsyncAvailable()) {
1295
+ if (!deps.runtime.isAsyncAvailable()) {
1247
1296
  return {
1248
1297
  content: [{ type: "text", text: `Background mode requires upstream jiti for TypeScript execution but it could not be found. Ensure the ${APP_NAME}-subagents package dependencies are installed.` }],
1249
1298
  isError: true,
@@ -1259,7 +1308,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
1259
1308
  currentModel: currentModelFullId(ctx.model),
1260
1309
  };
1261
1310
  const asyncChain = wrapChainTasksForFork(chainResult.requestedAsync.chain, params.context);
1262
- return executeAsyncChain(id, {
1311
+ return deps.runtime.executeAsyncChain(id, {
1263
1312
  chain: asyncChain,
1264
1313
  task: params.task,
1265
1314
  agents,
@@ -1274,6 +1323,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
1274
1323
  chainSkills: chainResult.requestedAsync.chainSkills,
1275
1324
  sessionFilesByFlatIndex: collectChainSessionFiles(asyncChain, sessionFileForIndex),
1276
1325
  maxSubagentDepth: currentMaxSubagentDepth,
1326
+ workflowStageSubagentGuard,
1277
1327
  worktreeSetupHook: deps.config.worktreeSetupHook,
1278
1328
  worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
1279
1329
  controlConfig,
@@ -1323,6 +1373,7 @@ interface ForegroundParallelRunInput {
1323
1373
  maxOutput?: MaxOutputConfig;
1324
1374
  paramsCwd: string;
1325
1375
  maxSubagentDepths: number[];
1376
+ workflowStageSubagentGuard?: boolean;
1326
1377
  availableModels: ModelInfo[];
1327
1378
  modelOverrides: (string | undefined)[];
1328
1379
  behaviors: Array<ReturnType<typeof resolveStepBehavior>>;
@@ -1335,11 +1386,12 @@ interface ForegroundParallelRunInput {
1335
1386
  concurrencyLimit: number;
1336
1387
  liveResults: (SingleResult | undefined)[];
1337
1388
  liveProgress: (AgentProgress | undefined)[];
1338
- onUpdate?: (r: AgentToolResult<Details>) => void;
1389
+ onUpdate?: (r: SubagentToolResult) => void;
1339
1390
  worktreeSetup?: WorktreeSetup;
1391
+ runtime: Pick<SubagentExecutorRuntimeDeps, "runSync">;
1340
1392
  }
1341
1393
 
1342
- function buildParallelModeError(message: string): AgentToolResult<Details> {
1394
+ function buildParallelModeError(message: string): SubagentToolResult {
1343
1395
  return {
1344
1396
  content: [{ type: "text", text: message }],
1345
1397
  isError: true,
@@ -1354,7 +1406,7 @@ function createParallelWorktreeSetup(
1354
1406
  tasks: TaskParam[],
1355
1407
  setupHook: ExtensionConfig["worktreeSetupHook"],
1356
1408
  setupHookTimeoutMs: ExtensionConfig["worktreeSetupHookTimeoutMs"],
1357
- ): { setup?: WorktreeSetup; errorResult?: AgentToolResult<Details> } {
1409
+ ): { setup?: WorktreeSetup; errorResult?: SubagentToolResult } {
1358
1410
  if (!enabled) return {};
1359
1411
  try {
1360
1412
  return {
@@ -1469,7 +1521,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
1469
1521
  };
1470
1522
  }
1471
1523
  const agentConfig = input.agents.find((agent) => agent.name === task.agent);
1472
- return runSync(input.ctx.cwd, input.agents, task.agent, taskText, {
1524
+ return input.runtime.runSync(input.ctx.cwd, input.agents, task.agent, taskText, {
1473
1525
  cwd: taskCwd,
1474
1526
  signal: input.signal,
1475
1527
  interruptSignal: interruptController.signal,
@@ -1486,6 +1538,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
1486
1538
  outputPath,
1487
1539
  outputMode: behavior?.outputMode,
1488
1540
  maxSubagentDepth: input.maxSubagentDepths[index],
1541
+ workflowStageSubagentGuard: input.workflowStageSubagentGuard,
1489
1542
  controlConfig: input.controlConfig,
1490
1543
  onControlEvent: input.onControlEvent,
1491
1544
  intercomSessionName: input.childIntercomTarget?.(task.agent, index),
@@ -1496,39 +1549,39 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
1496
1549
  preferredModelProvider: input.ctx.model?.provider,
1497
1550
  currentModel: currentModelFullId(input.ctx.model),
1498
1551
  skills: effectiveSkills === false ? [] : effectiveSkills,
1499
- onUpdate: input.onUpdate
1500
- ? (progressUpdate) => {
1501
- const stepResults = progressUpdate.details?.results || [];
1502
- const stepProgress = progressUpdate.details?.progress || [];
1503
- if (input.foregroundControl && stepProgress.length > 0) {
1504
- const current = stepProgress[0];
1505
- input.foregroundControl.currentAgent = task.agent;
1506
- input.foregroundControl.currentIndex = index;
1507
- input.foregroundControl.currentActivityState = current?.activityState;
1508
- input.foregroundControl.lastActivityAt = current?.lastActivityAt;
1509
- input.foregroundControl.currentTool = current?.currentTool;
1510
- input.foregroundControl.currentToolStartedAt = current?.currentToolStartedAt;
1511
- input.foregroundControl.currentPath = current?.currentPath;
1512
- input.foregroundControl.turnCount = current?.turnCount;
1513
- input.foregroundControl.tokens = current?.tokens;
1514
- input.foregroundControl.toolCount = current?.toolCount;
1515
- input.foregroundControl.updatedAt = Date.now();
1516
- }
1517
- if (stepResults.length > 0) input.liveResults[index] = stepResults[0];
1518
- if (stepProgress.length > 0) input.liveProgress[index] = stepProgress[0];
1519
- const mergedResults = input.liveResults.filter((result): result is SingleResult => result !== undefined);
1520
- const mergedProgress = input.liveProgress.filter((progress): progress is AgentProgress => progress !== undefined);
1521
- input.onUpdate?.({
1522
- content: progressUpdate.content,
1523
- details: {
1524
- mode: "parallel",
1525
- results: mergedResults,
1526
- progress: mergedProgress,
1527
- controlEvents: progressUpdate.details?.controlEvents,
1528
- totalSteps: input.tasks.length,
1529
- },
1530
- });
1552
+ onUpdate: input.onUpdate
1553
+ ? (progressUpdate) => {
1554
+ const stepResults = progressUpdate.details?.results || [];
1555
+ const stepProgress = progressUpdate.details?.progress || [];
1556
+ if (input.foregroundControl && stepProgress.length > 0) {
1557
+ const current = stepProgress[0];
1558
+ input.foregroundControl.currentAgent = task.agent;
1559
+ input.foregroundControl.currentIndex = index;
1560
+ input.foregroundControl.currentActivityState = current?.activityState;
1561
+ input.foregroundControl.lastActivityAt = current?.lastActivityAt;
1562
+ input.foregroundControl.currentTool = current?.currentTool;
1563
+ input.foregroundControl.currentToolStartedAt = current?.currentToolStartedAt;
1564
+ input.foregroundControl.currentPath = current?.currentPath;
1565
+ input.foregroundControl.turnCount = current?.turnCount;
1566
+ input.foregroundControl.tokens = current?.tokens;
1567
+ input.foregroundControl.toolCount = current?.toolCount;
1568
+ input.foregroundControl.updatedAt = Date.now();
1531
1569
  }
1570
+ if (stepResults.length > 0) input.liveResults[index] = stepResults[0];
1571
+ if (stepProgress.length > 0) input.liveProgress[index] = stepProgress[0];
1572
+ const mergedResults = input.liveResults.filter((result): result is SingleResult => result !== undefined);
1573
+ const mergedProgress = input.liveProgress.filter((progress): progress is AgentProgress => progress !== undefined);
1574
+ input.onUpdate?.({
1575
+ content: progressUpdate.content,
1576
+ details: {
1577
+ mode: "parallel",
1578
+ results: mergedResults,
1579
+ progress: mergedProgress,
1580
+ controlEvents: progressUpdate.details?.controlEvents,
1581
+ totalSteps: input.tasks.length,
1582
+ },
1583
+ });
1584
+ }
1532
1585
  : undefined,
1533
1586
  }).finally(() => {
1534
1587
  if (input.foregroundControl?.currentIndex === index) {
@@ -1539,7 +1592,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
1539
1592
  });
1540
1593
  }
1541
1594
 
1542
- async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps): Promise<AgentToolResult<Details>> {
1595
+ async function runParallelPath(data: ExecutionContextData, deps: ResolvedExecutorDeps): Promise<SubagentToolResult> {
1543
1596
  const {
1544
1597
  params,
1545
1598
  effectiveCwd,
@@ -1585,7 +1638,9 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
1585
1638
  agentConfigs.push(config);
1586
1639
  }
1587
1640
 
1588
- const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
1641
+ const depthPolicy = resolveSubagentDepthPolicy(ctx, deps.config.maxSubagentDepth);
1642
+ const currentMaxSubagentDepth = depthPolicy.maxSubagentDepth;
1643
+ const workflowStageSubagentGuard = depthPolicy.workflowStageSubagentGuard;
1589
1644
  const maxSubagentDepths = agentConfigs.map((config) =>
1590
1645
  resolveChildMaxSubagentDepth(currentMaxSubagentDepth, config.maxSubagentDepth),
1591
1646
  );
@@ -1658,7 +1713,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
1658
1713
  }
1659
1714
 
1660
1715
  if (result.runInBackground) {
1661
- if (!isAsyncAvailable()) {
1716
+ if (!deps.runtime.isAsyncAvailable()) {
1662
1717
  return {
1663
1718
  content: [{ type: "text", text: `Background mode requires upstream jiti for TypeScript execution but it could not be found. Ensure the ${APP_NAME}-subagents package dependencies are installed.` }],
1664
1719
  isError: true,
@@ -1688,7 +1743,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
1688
1743
  ...(progress !== undefined ? { progress } : {}),
1689
1744
  };
1690
1745
  });
1691
- return executeAsyncChain(id, {
1746
+ return deps.runtime.executeAsyncChain(id, {
1692
1747
  chain: [{ parallel: parallelTasks, concurrency: parallelConcurrency, worktree: params.worktree }],
1693
1748
  resultMode: "parallel",
1694
1749
  agents,
@@ -1703,6 +1758,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
1703
1758
  chainSkills: [],
1704
1759
  sessionFilesByFlatIndex: tasks.map((_, index) => sessionFileForIndex(index)),
1705
1760
  maxSubagentDepth: currentMaxSubagentDepth,
1761
+ workflowStageSubagentGuard,
1706
1762
  worktreeSetupHook: deps.config.worktreeSetupHook,
1707
1763
  worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
1708
1764
  controlConfig,
@@ -1767,6 +1823,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
1767
1823
  artifactsDir,
1768
1824
  maxOutput: params.maxOutput,
1769
1825
  paramsCwd: effectiveCwd,
1826
+ workflowStageSubagentGuard,
1770
1827
  availableModels,
1771
1828
  modelOverrides,
1772
1829
  behaviors,
@@ -1782,6 +1839,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
1782
1839
  liveProgress,
1783
1840
  onUpdate,
1784
1841
  worktreeSetup,
1842
+ runtime: deps.runtime,
1785
1843
  });
1786
1844
  for (let i = 0; i < results.length; i++) {
1787
1845
  const run = results[i]!;
@@ -1860,7 +1918,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
1860
1918
  }
1861
1919
  }
1862
1920
 
1863
- async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Promise<AgentToolResult<Details>> {
1921
+ async function runSinglePath(data: ExecutionContextData, deps: ResolvedExecutorDeps): Promise<SubagentToolResult> {
1864
1922
  const {
1865
1923
  params,
1866
1924
  effectiveCwd,
@@ -1902,7 +1960,9 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
1902
1960
  const rawOutput = params.output !== undefined ? params.output : agentConfig.output;
1903
1961
  let effectiveOutput = normalizeSingleOutputOverride(rawOutput, agentConfig.output);
1904
1962
  const effectiveOutputMode = params.outputMode ?? "inline";
1905
- const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
1963
+ const depthPolicy = resolveSubagentDepthPolicy(ctx, deps.config.maxSubagentDepth);
1964
+ const currentMaxSubagentDepth = depthPolicy.maxSubagentDepth;
1965
+ const workflowStageSubagentGuard = depthPolicy.workflowStageSubagentGuard;
1906
1966
  const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, agentConfig.maxSubagentDepth);
1907
1967
 
1908
1968
  if (params.clarify === true && ctx.hasUI) {
@@ -1938,7 +1998,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
1938
1998
  if (override?.skills !== undefined) skillOverride = override.skills;
1939
1999
 
1940
2000
  if (result.runInBackground) {
1941
- if (!isAsyncAvailable()) {
2001
+ if (!deps.runtime.isAsyncAvailable()) {
1942
2002
  return {
1943
2003
  content: [{ type: "text", text: `Background mode requires upstream jiti for TypeScript execution but it could not be found. Ensure the ${APP_NAME}-subagents package dependencies are installed.` }],
1944
2004
  isError: true,
@@ -1953,7 +2013,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
1953
2013
  currentModelProvider: ctx.model?.provider,
1954
2014
  currentModel: currentModelFullId(ctx.model),
1955
2015
  };
1956
- return executeAsyncSingle(id, {
2016
+ return deps.runtime.executeAsyncSingle(id, {
1957
2017
  agent: params.agent!,
1958
2018
  task: params.context === "fork" ? wrapForkTask(task) : task,
1959
2019
  agentConfig,
@@ -1971,6 +2031,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
1971
2031
  outputMode: effectiveOutputMode,
1972
2032
  modelOverride,
1973
2033
  maxSubagentDepth,
2034
+ workflowStageSubagentGuard,
1974
2035
  worktreeSetupHook: deps.config.worktreeSetupHook,
1975
2036
  worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
1976
2037
  controlConfig,
@@ -2014,7 +2075,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
2014
2075
  }
2015
2076
 
2016
2077
  const forwardSingleUpdate = onUpdate
2017
- ? (update: AgentToolResult<Details>) => {
2078
+ ? (update: SubagentToolResult) => {
2018
2079
  if (foregroundControl) {
2019
2080
  const firstProgress = update.details?.progress?.[0];
2020
2081
  foregroundControl.currentAgent = params.agent;
@@ -2033,7 +2094,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
2033
2094
  }
2034
2095
  : undefined;
2035
2096
 
2036
- const r = await runSync(ctx.cwd, agents, params.agent!, task, {
2097
+ const r = await deps.runtime.runSync(ctx.cwd, agents, params.agent!, task, {
2037
2098
  cwd: effectiveCwd,
2038
2099
  signal,
2039
2100
  interruptSignal: interruptController.signal,
@@ -2049,6 +2110,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
2049
2110
  outputPath,
2050
2111
  outputMode: effectiveOutputMode,
2051
2112
  maxSubagentDepth,
2113
+ workflowStageSubagentGuard,
2052
2114
  onUpdate: forwardSingleUpdate,
2053
2115
  controlConfig,
2054
2116
  onControlEvent,
@@ -2145,22 +2207,23 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
2145
2207
  };
2146
2208
  }
2147
2209
 
2148
- export function createSubagentExecutor(deps: ExecutorDeps): {
2210
+ export function createSubagentExecutor(rawDeps: ExecutorDeps): {
2149
2211
  execute: (
2150
2212
  id: string,
2151
2213
  params: SubagentParamsLike,
2152
2214
  signal: AbortSignal,
2153
- onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
2215
+ onUpdate: ((r: SubagentToolResult) => void) | undefined,
2154
2216
  ctx: ExtensionContext,
2155
- ) => Promise<AgentToolResult<Details>>;
2217
+ ) => Promise<SubagentToolResult>;
2156
2218
  } {
2219
+ const deps: ResolvedExecutorDeps = { ...rawDeps, runtime: resolveSubagentExecutorRuntimeDeps(rawDeps.runtime) };
2157
2220
  const execute = async (
2158
2221
  _id: string,
2159
2222
  params: SubagentParamsLike,
2160
2223
  signal: AbortSignal,
2161
- onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
2224
+ onUpdate: ((r: SubagentToolResult) => void) | undefined,
2162
2225
  ctx: ExtensionContext,
2163
- ): Promise<AgentToolResult<Details>> => {
2226
+ ): Promise<SubagentToolResult> => {
2164
2227
  deps.state.baseCwd = ctx.cwd;
2165
2228
  deps.state.foregroundRuns ??= new Map();
2166
2229
  deps.state.foregroundControls ??= new Map();
@@ -2219,7 +2282,12 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
2219
2282
  const foreground = getForegroundControl(deps.state, undefined);
2220
2283
  if (foreground) return foregroundStatusResult(foreground);
2221
2284
  }
2222
- return inspectSubagentStatus(paramsWithResolvedCwd, { state: deps.state, nested: nestedResolutionScopeForExecutor(deps) });
2285
+ return inspectSubagentStatus({
2286
+ action: "status",
2287
+ id: paramsWithResolvedCwd.id,
2288
+ runId: paramsWithResolvedCwd.runId,
2289
+ dir: paramsWithResolvedCwd.dir,
2290
+ }, { state: deps.state, nested: nestedResolutionScopeForExecutor(deps) });
2223
2291
  }
2224
2292
  if (params.action === "resume") {
2225
2293
  return resumeAsyncRun({ params: paramsWithResolvedCwd, requestCwd, ctx, deps });
@@ -2278,16 +2346,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
2278
2346
  return handleManagementAction(params.action, paramsWithResolvedCwd, { ...ctx, cwd: requestCwd });
2279
2347
  }
2280
2348
 
2281
- const { blocked, depth, maxDepth } = checkSubagentDepth(deps.config.maxSubagentDepth);
2349
+ const depthPolicy = resolveSubagentDepthPolicy(ctx, deps.config.maxSubagentDepth);
2350
+ const { blocked, depth, maxDepth, workflowStageGuard } = checkSubagentDepth(depthPolicy.maxSubagentDepth);
2351
+ const workflowStageSubagentGuard = workflowStageGuard || depthPolicy.workflowStageSubagentGuard;
2282
2352
  if (blocked) {
2283
2353
  return {
2284
2354
  content: [
2285
2355
  {
2286
2356
  type: "text",
2287
- text:
2288
- `Nested subagent call blocked (depth=${depth}, max=${maxDepth}). ` +
2289
- "You are running at the maximum subagent nesting depth. " +
2290
- "Complete your current task directly without delegating to further subagents.",
2357
+ text: subagentDepthBlockedMessage(depth, maxDepth, { workflowStageGuard: workflowStageSubagentGuard }),
2291
2358
  },
2292
2359
  ],
2293
2360
  isError: true,
@@ -2385,7 +2452,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
2385
2452
  sessionFileForIndex(idx) ?? path.join(sessionDirForIndex(idx), "session.jsonl");
2386
2453
 
2387
2454
  const onUpdateWithContext = onUpdate
2388
- ? (r: AgentToolResult<Details>) => onUpdate(withForkContext(r, effectiveParams.context))
2455
+ ? (r: SubagentToolResult) => onUpdate(withForkContext(r, effectiveParams.context))
2389
2456
  : undefined;
2390
2457
 
2391
2458
  const execData: ExecutionContextData = {
@@ -2428,7 +2495,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
2428
2495
  deps.state.lastForegroundControlId = runId;
2429
2496
  }
2430
2497
 
2431
- const writeNestedForegroundEvent = (type: "subagent.nested.started" | "subagent.nested.completed", result?: AgentToolResult<Details>): void => {
2498
+ const writeNestedForegroundEvent = (type: "subagent.nested.started" | "subagent.nested.completed", result?: SubagentToolResult): void => {
2432
2499
  if (!inheritedNestedRoute || !nestedParentAddress) return;
2433
2500
  const now = Date.now();
2434
2501
  const details = result?.details;