@bastani/atomic 0.9.15 → 0.9.16-alpha.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 (82) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/builtin/intercom/CHANGELOG.md +10 -0
  3. package/dist/builtin/intercom/README.md +8 -8
  4. package/dist/builtin/intercom/contact-supervisor-tool.ts +68 -32
  5. package/dist/builtin/intercom/index-heavy.ts +1 -0
  6. package/dist/builtin/intercom/index.ts +21 -15
  7. package/dist/builtin/intercom/intercom-tool.ts +66 -10
  8. package/dist/builtin/intercom/package.json +1 -1
  9. package/dist/builtin/intercom/parent-ask-handoff.ts +72 -0
  10. package/dist/builtin/mcp/package.json +1 -1
  11. package/dist/builtin/subagents/CHANGELOG.md +13 -0
  12. package/dist/builtin/subagents/README.md +23 -12
  13. package/dist/builtin/subagents/agents/worker.md +2 -2
  14. package/dist/builtin/subagents/package.json +1 -1
  15. package/dist/builtin/subagents/skills/subagent/SKILL.md +10 -17
  16. package/dist/builtin/subagents/src/extension/index.ts +5 -30
  17. package/dist/builtin/subagents/src/extension/notification-content.ts +1 -1
  18. package/dist/builtin/subagents/src/extension/schemas.ts +2 -14
  19. package/dist/builtin/subagents/src/extension/tool-description.ts +1 -2
  20. package/dist/builtin/subagents/src/extension/tool-rendering.ts +53 -0
  21. package/dist/builtin/subagents/src/intercom/intercom-bridge.ts +1 -1
  22. package/dist/builtin/subagents/src/intercom/result-intercom.ts +4 -4
  23. package/dist/builtin/subagents/src/runs/foreground/execution-parent-ask-handoff.ts +78 -0
  24. package/dist/builtin/subagents/src/runs/foreground/inprocess-run-sync.ts +7 -5
  25. package/dist/builtin/subagents/src/runs/foreground/notify.ts +5 -6
  26. package/dist/builtin/subagents/src/runs/foreground/parent-ask-output.ts +48 -0
  27. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-burst-display.ts +46 -0
  28. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-burst.ts +403 -0
  29. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-context.ts +48 -9
  30. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-input.ts +3 -1
  31. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-live-update.ts +11 -0
  32. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel-task.ts +164 -93
  33. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel.ts +36 -29
  34. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parent-ask-projection.ts +11 -0
  35. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-single.ts +42 -17
  36. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-status.ts +3 -121
  37. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-types.ts +7 -2
  38. package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +30 -220
  39. package/dist/builtin/subagents/src/runs/inprocess/control-status.ts +0 -34
  40. package/dist/builtin/subagents/src/runs/inprocess/index.ts +0 -12
  41. package/dist/builtin/subagents/src/runs/inprocess/prompt-behavior.ts +1 -1
  42. package/dist/builtin/subagents/src/runs/inprocess/runner.ts +2 -141
  43. package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +8 -0
  44. package/dist/builtin/subagents/src/shared/status-format.ts +3 -3
  45. package/dist/builtin/subagents/src/shared/types-config.ts +41 -0
  46. package/dist/builtin/subagents/src/shared/types-foreground-state.ts +7 -15
  47. package/dist/builtin/subagents/src/shared/types-results.ts +3 -1
  48. package/dist/builtin/subagents/src/shared/types-runtime.ts +1 -11
  49. package/dist/builtin/subagents/src/tui/render-progress.ts +2 -2
  50. package/dist/builtin/subagents/src/tui/render-result-compact.ts +2 -2
  51. package/dist/builtin/subagents/src/tui/render-result.ts +38 -14
  52. package/dist/builtin/subagents/src/tui/render-status-progress.ts +1 -1
  53. package/dist/builtin/web-access/package.json +1 -1
  54. package/dist/builtin/workflows/CHANGELOG.md +10 -0
  55. package/dist/builtin/workflows/README.md +1 -1
  56. package/dist/builtin/workflows/package.json +1 -1
  57. package/dist/builtin/workflows/src/durable/dbos-embedded-postgres-root.ts +548 -39
  58. package/dist/builtin/workflows/src/durable/dbos-embedded-postgres.ts +529 -65
  59. package/dist/builtin/workflows/src/durable/dbos-lifecycle.ts +30 -8
  60. package/dist/builtin/workflows/src/durable/dbos-local-postgres.ts +36 -3
  61. package/dist/builtin/workflows/src/durable/local-command.ts +68 -10
  62. package/dist/builtin/workflows/src/extension/dispatcher.ts +7 -1
  63. package/dist/builtin/workflows/src/extension/extension-factory.ts +2 -2
  64. package/dist/builtin/workflows/src/extension/index.bundle.mjs +1094 -298
  65. package/dist/builtin/workflows/src/extension/public-types.ts +2 -2
  66. package/dist/builtin/workflows/src/extension/render-result.ts +16 -2
  67. package/dist/builtin/workflows/src/extension/runtime.ts +8 -1
  68. package/dist/builtin/workflows/src/extension/workflow-request-abort.ts +15 -0
  69. package/dist/builtin/workflows/src/extension/workflow-tool-content.ts +4 -3
  70. package/dist/builtin/workflows/src/extension/workflow-tool-registration.ts +107 -13
  71. package/dist/builtin/workflows/src/extension/workflow-tool.ts +22 -12
  72. package/dist/builtin/workflows/src/tui/widget.ts +25 -6
  73. package/dist/core/event-bus.d.ts.map +1 -1
  74. package/dist/core/event-bus.js +19 -3
  75. package/dist/core/event-bus.js.map +1 -1
  76. package/docs/intercom.md +28 -26
  77. package/docs/subagents.md +15 -11
  78. package/docs/workflows.md +27 -10
  79. package/npm-shrinkwrap.json +32 -32
  80. package/package.json +3 -3
  81. package/dist/builtin/subagents/src/runs/inprocess/attempt-handles.ts +0 -104
  82. /package/dist/builtin/subagents/src/runs/foreground/{subagent-executor-resume.ts → subagent-executor-cwd.ts} +0 -0
@@ -17,7 +17,7 @@ import {
17
17
  type SubagentState,
18
18
  type SubagentToolResult,
19
19
  } from "../../shared/types.js";
20
- import { compactForegroundDetails, compactForegroundResult, getSingleResultOutput } from "../../shared/utils.js";
20
+ import { compactForegroundDetails, getSingleResultOutput } from "../../shared/utils.js";
21
21
  import {
22
22
  formatControlIntercomMessage,
23
23
  formatControlNoticeMessage,
@@ -77,128 +77,10 @@ export function foregroundStatusResult(control: ForegroundControl): SubagentTool
77
77
  return { content: [{ type: "text", text: lines.join("\n") }], details: { mode: "management", results: [] } };
78
78
  }
79
79
 
80
- export function retainedForegroundStatusResult(state: SubagentState, runId: string): SubagentToolResult | undefined {
81
- const run = state.foregroundRuns?.get(runId);
82
- if (!run) return undefined;
83
- const statuses = run.children.map((child) => child.status);
84
- const stateLabel = statuses.includes("detached")
85
- ? "detached"
86
- : statuses.includes("failed")
87
- ? "failed"
88
- : statuses.includes("paused")
89
- ? "paused"
90
- : statuses.every((status) => status === "completed")
91
- ? "completed"
92
- : (statuses[0] ?? "unknown");
93
- const lines = [`Run: ${run.runId}`, `State: ${stateLabel}`, `Mode: ${run.mode}`];
94
- for (const child of run.children) {
95
- lines.push(`Child ${child.index + 1}: ${child.agent} (${child.status})`);
96
- const output = child.result ? getSingleResultOutput(child.result) : "";
97
- if (output) lines.push(output);
98
- else if (child.result?.error) lines.push(child.result.error);
99
- }
100
- return { content: [{ type: "text", text: lines.join("\n") }], details: { mode: "management", results: [] } };
101
- }
102
-
103
- const earlyDetachedResults = new WeakMap<SubagentState, Map<string, Map<number, SingleResult>>>();
104
- const MAX_EARLY_DETACHED_RUNS = 50;
105
- const MAX_EARLY_DETACHED_CHILDREN_PER_RUN = 50;
106
-
107
- function takeEarlyDetachedResult(state: SubagentState, runId: string, index: number): SingleResult | undefined {
108
- const byRun = earlyDetachedResults.get(state);
109
- const byIndex = byRun?.get(runId);
110
- const result = byIndex?.get(index);
111
- byIndex?.delete(index);
112
- if (byIndex?.size === 0) byRun?.delete(runId);
113
- return result;
114
- }
115
-
116
- export function rememberForegroundRun(
117
- state: SubagentState,
118
- input: {
119
- runId: string;
120
- mode: "single" | "parallel";
121
- cwd: string;
122
- results: SingleResult[];
123
- },
124
- ): void {
125
- state.foregroundRuns ??= new Map();
126
- state.foregroundRuns.set(input.runId, {
127
- runId: input.runId,
128
- mode: input.mode,
129
- cwd: input.cwd,
130
- updatedAt: Date.now(),
131
- children: input.results.map((originalResult, index) => {
132
- const result = takeEarlyDetachedResult(state, input.runId, index) ?? originalResult;
133
- return {
134
- agent: result.agent,
135
- index,
136
- status: resolveSubagentResultStatus({
137
- status: result.status,
138
- interrupted: result.interrupted,
139
- detached: result.detached,
140
- }),
141
- ...(result.sessionFile ? { sessionFile: result.sessionFile } : {}),
142
- result,
143
- };
144
- }),
145
- });
146
- while (state.foregroundRuns.size > 50) {
147
- const oldest = [...state.foregroundRuns.values()].sort((left, right) => left.updatedAt - right.updatedAt)[0];
148
- if (!oldest) break;
149
- state.foregroundRuns.delete(oldest.runId);
150
- }
151
- }
152
-
153
- export function replaceForegroundRunChild(
154
- state: SubagentState,
155
- runId: string,
156
- index: number,
157
- result: SingleResult,
158
- options: { onlyWhenDetached?: boolean } = {},
159
- ): void {
160
- const retainedResult = compactForegroundResult(result);
161
- const run = state.foregroundRuns?.get(runId);
162
- if (!run) {
163
- let byRun = earlyDetachedResults.get(state);
164
- if (!byRun) {
165
- byRun = new Map();
166
- earlyDetachedResults.set(state, byRun);
167
- }
168
- let byIndex = byRun.get(runId);
169
- if (!byIndex) {
170
- byIndex = new Map();
171
- byRun.set(runId, byIndex);
172
- }
173
- byIndex.set(index, retainedResult);
174
- while (byIndex.size > MAX_EARLY_DETACHED_CHILDREN_PER_RUN) {
175
- const oldestIndex = byIndex.keys().next().value;
176
- if (oldestIndex === undefined) break;
177
- byIndex.delete(oldestIndex);
178
- }
179
- while (byRun.size > MAX_EARLY_DETACHED_RUNS) {
180
- const oldestRun = byRun.keys().next().value;
181
- if (oldestRun === undefined) break;
182
- byRun.delete(oldestRun);
183
- }
184
- return;
185
- }
186
- const child = run.children.find((entry) => entry.index === index);
187
- if (!child || (options.onlyWhenDetached !== false && child.status !== "detached")) return;
188
- child.status = resolveSubagentResultStatus({
189
- status: retainedResult.status,
190
- interrupted: retainedResult.interrupted,
191
- detached: retainedResult.detached,
192
- });
193
- child.result = retainedResult;
194
- if (retainedResult.sessionFile) child.sessionFile = retainedResult.sessionFile;
195
- run.updatedAt = Date.now();
196
- }
197
-
198
80
  export function emitControlNotification(input: {
199
81
  pi: ExtensionAPI;
200
82
  controlConfig: ExecutionContextData["controlConfig"];
201
- intercomBridge: IntercomBridgeState;
83
+ intercomBridge: Pick<IntercomBridgeState, "active" | "orchestratorTarget">;
202
84
  event: ControlEvent;
203
85
  }): void {
204
86
  if (!shouldNotifyControlEvent(input.controlConfig, input.event)) return;
@@ -284,7 +166,7 @@ export function notifyDetachedForegroundChildExit(input: {
284
166
  agent: result.agent,
285
167
  status: result.status,
286
168
  summary: resultSummaryForIntercom(result),
287
- ...(result.interrupted ? { state: "paused" } : {}),
169
+ ...(result.interrupted ? { state: "interrupted" } : {}),
288
170
  timestamp: Date.now(),
289
171
  ...(result.progressSummary?.durationMs !== undefined ? { durationMs: result.progressSummary.durationMs } : {}),
290
172
  ...(result.sessionFile ? { sessionFile: result.sessionFile } : {}),
@@ -14,6 +14,8 @@ import type {
14
14
  import type { ChildModePolicy } from "../inprocess/child-policy.js";
15
15
  import type { runSync } from "./execution.js";
16
16
 
17
+ export const BURST_TASK_DISCOVERY_CWD = Symbol("burstTaskDiscoveryCwd");
18
+
17
19
  export interface TaskParam {
18
20
  agent: string;
19
21
  task: string;
@@ -28,14 +30,16 @@ export interface TaskParam {
28
30
  group?: string | true;
29
31
  }
30
32
 
33
+ export type BurstTaskParam = TaskParam & {
34
+ [BURST_TASK_DISCOVERY_CWD]?: string;
35
+ };
36
+
31
37
  export interface SubagentParamsLike {
32
38
  action?: (typeof SUBAGENT_ACTIONS)[number];
33
39
  id?: string;
34
40
  runId?: string;
35
- index?: number;
36
41
  agent?: string;
37
42
  task?: string;
38
- message?: string;
39
43
  config?: unknown;
40
44
  tasks?: TaskParam[];
41
45
  concurrency?: number;
@@ -94,6 +98,7 @@ export interface ExecutionContextData {
94
98
  signal: AbortSignal;
95
99
  onUpdate?: (r: SubagentToolResult) => void;
96
100
  agents: AgentConfig[];
101
+ parallelAgentConfigs?: AgentConfig[];
97
102
  runId: string;
98
103
  shareEnabled: boolean;
99
104
  sessionRoot: string;
@@ -1,55 +1,18 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
1
  import type { ExtensionContext } from "@bastani/atomic";
4
2
  import { handleManagementAction } from "../../agents/agent-management.js";
5
- import { resolveExecutionAgentScope } from "../../agents/agent-scope.js";
6
3
  import { clearPendingForegroundControlNotices } from "../../extension/control-notices.js";
7
4
  import { buildDoctorReport } from "../../extension/doctor.js";
8
- import {
9
- INTERCOM_BRIDGE_MARKER,
10
- resolveIntercomBridge,
11
- resolveIntercomSessionTarget,
12
- resolveSubagentIntercomTarget,
13
- } from "../../intercom/intercom-bridge.js";
14
- import { requestSupervisorAuthorization } from "../../intercom/supervisor-authorization.js";
15
- import { getArtifactsDir } from "../../shared/artifacts.js";
16
- import { collectKnownModelProviders, toModelInfo } from "../../shared/model-info.js";
17
- import { createCandidateModelResolver } from "../../shared/model-resolution.js";
18
- import {
19
- injectSingleProgressInstruction,
20
- resolveSingleProgress,
21
- writeInitialProgressFile,
22
- } from "../../shared/settings.js";
23
- import {
24
- DEFAULT_ARTIFACT_CONFIG,
25
- getCurrentSubagentDepth,
26
- isWorkflowStageOrchestrationContext,
27
- type SingleResult,
28
- SUBAGENT_ACTIONS,
29
- type SubagentToolResult,
30
- workflowSessionMetadataFromContext,
31
- } from "../../shared/types.js";
32
- import {
33
- inspectInProcessChildStatus,
34
- interruptInProcessChild,
35
- resumeInProcessChild,
36
- } from "../inprocess/control-status.js";
37
- import { inheritedIntercomGroup } from "../shared/intercom-group.js";
38
- import { currentModelFullId } from "../shared/model-fallback.js";
39
- import { resolveControlConfig } from "../shared/subagent-control.js";
5
+ import { resolveIntercomSessionTarget } from "../../intercom/intercom-bridge.js";
6
+ import { SUBAGENT_ACTIONS, type SubagentToolResult } from "../../shared/types.js";
7
+ import { inspectInProcessChildStatus, interruptInProcessChild } from "../inprocess/control-status.js";
8
+ import { createExecutionBurstDispatcher } from "./subagent-executor-burst.js";
40
9
  import { prepareExecutionContext, refuseSubagentChildDelegation } from "./subagent-executor-context.js";
10
+ import { resolveRequestedCwd } from "./subagent-executor-cwd.js";
41
11
  import { toExecutionErrorResult, withForkContext } from "./subagent-executor-input.js";
42
12
  import { runParallelPath } from "./subagent-executor-parallel.js";
43
- import { resolveRequestedCwd } from "./subagent-executor-resume.js";
44
13
  import { resolveSubagentExecutorRuntimeDeps } from "./subagent-executor-runtime.js";
45
- import { createForwardSingleUpdate, runSinglePath } from "./subagent-executor-single.js";
46
- import {
47
- foregroundStatusResult,
48
- getForegroundControl,
49
- notifyDetachedForegroundChildExit,
50
- replaceForegroundRunChild,
51
- retainedForegroundStatusResult,
52
- } from "./subagent-executor-status.js";
14
+ import { runSinglePath } from "./subagent-executor-single.js";
15
+ import { foregroundStatusResult, getForegroundControl } from "./subagent-executor-status.js";
53
16
  import {
54
17
  type ExecutorDeps,
55
18
  isManagementActionsRestricted,
@@ -57,129 +20,8 @@ import {
57
20
  type SubagentParamsLike,
58
21
  } from "./subagent-executor-types.js";
59
22
 
60
- async function resumeRetainedForegroundChild(
61
- params: SubagentParamsLike,
62
- message: string,
63
- ctx: ExtensionContext,
64
- deps: ResolvedExecutorDeps,
65
- onUpdate: ((r: SubagentToolResult) => void) | undefined,
66
- ): Promise<SubagentToolResult | undefined> {
67
- const requested = params.id ?? params.runId;
68
- if (!requested) return undefined;
69
- const run = deps.state.foregroundRuns?.get(requested);
70
- if (!run) return undefined;
71
- const child = run.children[params.index ?? 0];
72
- if (!child) return undefined;
73
- const agents = deps.discoverAgents(run.cwd, resolveExecutionAgentScope(params.agentScope)).agents;
74
- const agentConfig = agents.find((agent) => agent.name === child.agent);
75
- if (!agentConfig) {
76
- return {
77
- content: [{ type: "text", text: `Unknown agent for resume: ${child.agent}` }],
78
- isError: true,
79
- details: { mode: "single", results: [] },
80
- };
81
- }
82
- const parentSessionFile = ctx.sessionManager.getSessionFile() ?? null;
83
- const sessionName = resolveIntercomSessionTarget(deps.pi.getSessionName(), ctx.sessionManager.getSessionId());
84
- const intercomBridge = resolveIntercomBridge({
85
- config: deps.config.intercomBridge,
86
- context: params.context,
87
- orchestratorTarget: sessionName,
88
- cwd: run.cwd,
89
- });
90
- const supervisedTarget = intercomBridge.active
91
- ? resolveSubagentIntercomTarget(run.runId, child.agent, child.index)
92
- : undefined;
93
- const supervisorAuthorization = await requestSupervisorAuthorization(deps.pi.events, supervisedTarget);
94
- const artifactConfig = { ...DEFAULT_ARTIFACT_CONFIG, enabled: params.artifacts !== false };
95
- const artifactsDir = getArtifactsDir(parentSessionFile);
96
- const progressDir = resolveSingleProgress(agentConfig, params.progress, message)
97
- ? path.join(artifactsDir, "progress", run.runId)
98
- : undefined;
99
- if (progressDir) {
100
- writeInitialProgressFile(progressDir);
101
- message = injectSingleProgressInstruction(message, progressDir);
102
- }
103
- const cleanupProgress = (): void => {
104
- if (!progressDir || artifactConfig.enabled) return;
105
- try {
106
- fs.rmSync(progressDir, { recursive: true, force: true });
107
- } catch {
108
- // Scratch cleanup must never replace the child run's result or original error.
109
- }
110
- };
111
- const forwardSingleUpdate = createForwardSingleUpdate(
112
- onUpdate,
113
- getForegroundControl(deps.state, run.runId),
114
- child.agent,
115
- child.index,
116
- );
117
- let result: SingleResult;
118
- try {
119
- result = await deps.runtime.runSync(run.cwd, agents, child.agent, message, {
120
- cwd: run.cwd,
121
- signal: ctx.signal,
122
- interruptSignal: ctx.signal,
123
- allowIntercomDetach: agentConfig.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
124
- intercomEvents: deps.pi.events,
125
- runId: run.runId,
126
- index: child.index,
127
- sessionDir: child.sessionFile ? path.dirname(child.sessionFile) : undefined,
128
- sessionFile: child.sessionFile,
129
- share: params.share === true,
130
- artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
131
- artifactConfig,
132
- maxOutput: params.maxOutput,
133
- parentDepth: getCurrentSubagentDepth(ctx),
134
- workflowStageSubagentGuard: isWorkflowStageOrchestrationContext(ctx),
135
- workflowSessionMetadata: workflowSessionMetadataFromContext(ctx),
136
- controlConfig: resolveControlConfig(deps.config.control, params.control),
137
- intercomSessionName: intercomBridge.active
138
- ? resolveSubagentIntercomTarget(run.runId, child.agent, child.index)
139
- : undefined,
140
- orchestratorIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
141
- intercomGroup: inheritedIntercomGroup(ctx),
142
- supervisorAuthorization,
143
- modelOverride: params.model,
144
- availableModels: ctx.modelRegistry.getAvailable().map(toModelInfo),
145
- knownModelProviders: collectKnownModelProviders(ctx.modelRegistry),
146
- resolveCandidateModel: createCandidateModelResolver(ctx.modelRegistry, ctx.model?.provider),
147
- preferredModelProvider: ctx.model?.provider,
148
- currentModel: currentModelFullId(ctx.model),
149
- currentThinkingLevel: ctx.thinkingLevel,
150
- onUpdate: forwardSingleUpdate,
151
- onDetachedExit: (detachedResult) => {
152
- cleanupProgress();
153
- if (detachedResult) {
154
- replaceForegroundRunChild(deps.state, run.runId, child.index, detachedResult);
155
- notifyDetachedForegroundChildExit({
156
- pi: deps.pi,
157
- runId: run.runId,
158
- mode: "single",
159
- index: child.index,
160
- result: detachedResult,
161
- });
162
- }
163
- },
164
- });
165
- } catch (error) {
166
- cleanupProgress();
167
- throw error;
168
- }
169
- if (!result.detached) cleanupProgress();
170
- replaceForegroundRunChild(deps.state, run.runId, child.index, result, { onlyWhenDetached: false });
171
- return {
172
- content: [{ type: "text", text: result.finalOutput ?? result.envelope ?? result.error ?? "" }],
173
- details: { mode: "single", runId: run.runId, results: [result] },
174
- ...(result.status === "error" ? { isError: true } : {}),
175
- };
176
- }
177
23
  const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete"]);
178
- /**
179
- * Management actions that only observe. Every other action either mutates agent
180
- * definitions or starts/continues agent execution, so a child without fanout
181
- * authorization is refused all of them.
182
- */
24
+ /** Observing management actions do not start or mutate child execution. */
183
25
  const READ_ONLY_MANAGEMENT_ACTIONS = new Set(["list", "get", "status", "doctor"]);
184
26
  const FANOUT_REFUSAL_MESSAGE = "Subagent fanout is not authorized for this child.";
185
27
 
@@ -191,9 +33,8 @@ async function handleManagementRequest(input: {
191
33
  requestCwd: string;
192
34
  ctx: ExtensionContext;
193
35
  deps: ResolvedExecutorDeps;
194
- onUpdate?: (r: SubagentToolResult) => void;
195
36
  }): Promise<SubagentToolResult> {
196
- const { params, paramsWithResolvedCwd, requestCwd, ctx, deps, onUpdate } = input;
37
+ const { params, paramsWithResolvedCwd, requestCwd, ctx, deps } = input;
197
38
  const action = params.action;
198
39
  if (!action) {
199
40
  return {
@@ -216,9 +57,8 @@ async function handleManagementRequest(input: {
216
57
  details: { mode: "management" as const, results: [] },
217
58
  };
218
59
  }
219
- // `resume` revives a child and `interrupt` is privileged control over a
220
- // running one; both continue agent execution, so only the observing actions
221
- // reach their handlers for a child without fanout authorization.
60
+ // `interrupt` is privileged control over a running child, so only the
61
+ // observing actions reach handlers for a child without fanout authorization.
222
62
  if (deps.childPolicy && !deps.childPolicy.fanoutAuthorized && !READ_ONLY_MANAGEMENT_ACTIONS.has(action)) {
223
63
  return {
224
64
  content: [{ type: "text", text: FANOUT_REFUSAL_MESSAGE }],
@@ -226,8 +66,7 @@ async function handleManagementRequest(input: {
226
66
  details: { mode: "management" as const, results: [] },
227
67
  };
228
68
  }
229
- // Delegation is one level deep: a session admitted as a subagent child may
230
- // never start or continue another agent, whatever else it is authorized for.
69
+ // Delegation is one level deep: a child may never control another child.
231
70
  if (!READ_ONLY_MANAGEMENT_ACTIONS.has(action)) {
232
71
  const childRefusal = refuseSubagentChildDelegation(ctx, "management");
233
72
  if (childRefusal) return childRefusal;
@@ -273,10 +112,6 @@ async function handleManagementRequest(input: {
273
112
  if (inProcess) return inProcess;
274
113
  const foreground = getForegroundControl(deps.state, targetRunId);
275
114
  if (foreground) return foregroundStatusResult(foreground);
276
- if (targetRunId) {
277
- const retained = retainedForegroundStatusResult(deps.state, targetRunId);
278
- if (retained) return retained;
279
- }
280
115
  return {
281
116
  content: [
282
117
  {
@@ -288,26 +123,6 @@ async function handleManagementRequest(input: {
288
123
  details: { mode: "management", results: [] },
289
124
  };
290
125
  }
291
- if (action === "resume") {
292
- const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
293
- const message = paramsWithResolvedCwd.message ?? paramsWithResolvedCwd.task;
294
- if (!targetRunId || !message) {
295
- return {
296
- content: [{ type: "text", text: "action='resume' requires id/runId and message." }],
297
- isError: true,
298
- details: { mode: "management", results: [] },
299
- };
300
- }
301
- const inProcess = await resumeInProcessChild(targetRunId, message, { model: ctx.model });
302
- if (inProcess) return inProcess;
303
- const retained = await resumeRetainedForegroundChild(paramsWithResolvedCwd, message, ctx, deps, onUpdate);
304
- if (retained) return retained;
305
- return {
306
- content: [{ type: "text", text: `No in-process child found for '${targetRunId}'.` }],
307
- isError: true,
308
- details: { mode: "management", results: [] },
309
- };
310
- }
311
126
  if (action === "interrupt") {
312
127
  const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
313
128
  if (targetRunId) {
@@ -366,18 +181,16 @@ export function createSubagentExecutor(rawDeps: ExecutorDeps): {
366
181
  ctx: ExtensionContext,
367
182
  ): Promise<SubagentToolResult> => {
368
183
  deps.state.baseCwd = ctx.cwd;
369
- deps.state.foregroundRuns ??= new Map();
370
184
  deps.state.foregroundControls ??= new Map();
371
185
  deps.state.lastForegroundControlId ??= null;
372
186
  const requestCwd = resolveRequestedCwd(ctx.cwd, params.cwd);
373
187
  const paramsWithResolvedCwd = params.cwd === undefined ? params : { ...params, cwd: requestCwd };
374
188
  if (params.action) {
375
- return handleManagementRequest({ params, paramsWithResolvedCwd, requestCwd, ctx, deps, onUpdate });
189
+ return handleManagementRequest({ params, paramsWithResolvedCwd, requestCwd, ctx, deps });
376
190
  }
377
- // Fanout authorization gates delegation and every management action that can
378
- // start or continue agent execution. Only `list`, `get`, `status`, and
379
- // `doctor` stay available to an unauthorized child; `resume`, `interrupt`,
380
- // and mutating management are refused inside handleManagementRequest.
191
+ // Fanout authorization gates delegation and privileged control. Only `list`,
192
+ // `get`, `status`, and `doctor` stay available to an unauthorized child;
193
+ // `interrupt` and mutating management are refused by handleManagementRequest.
381
194
  if (deps.childPolicy && !deps.childPolicy.fanoutAuthorized) {
382
195
  return {
383
196
  content: [{ type: "text", text: FANOUT_REFUSAL_MESSAGE }],
@@ -423,22 +236,19 @@ export function createSubagentExecutor(rawDeps: ExecutorDeps): {
423
236
  );
424
237
  };
425
238
 
426
- const executeWithSingleDispatchGuard = async (
427
- id: string,
428
- params: SubagentParamsLike,
429
- signal: AbortSignal,
430
- onUpdate: ((r: SubagentToolResult) => void) | undefined,
431
- ctx: ExtensionContext,
432
- ): Promise<SubagentToolResult> => {
433
- if (params.action) return execute(id, params, signal, onUpdate, ctx);
434
- if (deps.state.subagentInProgress === true) return duplicateSubagentCallResult(params);
435
- deps.state.subagentInProgress = true;
436
- try {
437
- return await execute(id, params, signal, onUpdate, ctx);
438
- } finally {
439
- deps.state.subagentInProgress = false;
440
- }
441
- };
239
+ const executeWithBurstCollection = createExecutionBurstDispatcher({
240
+ execute,
241
+ isActive: () => deps.state.subagentInProgress === true,
242
+ setActive: (active) => {
243
+ deps.state.subagentInProgress = active;
244
+ },
245
+ duplicateResult: duplicateSubagentCallResult,
246
+ });
442
247
 
443
- return { execute: executeWithSingleDispatchGuard };
248
+ return {
249
+ execute: (id, params, signal, onUpdate, ctx) =>
250
+ params.action
251
+ ? execute(id, params, signal, onUpdate, ctx)
252
+ : executeWithBurstCollection(id, params, signal, onUpdate, ctx),
253
+ };
444
254
  }
@@ -1,17 +1,10 @@
1
1
  import type { SubagentToolResult } from "../../shared/types.js";
2
2
  import { findSubagentControl, listSubagentControls } from "./control-registry.js";
3
- import type { ModelCandidate } from "./runner.js";
4
3
 
5
4
  function canonicalChildren(control: NonNullable<ReturnType<typeof findSubagentControl>>) {
6
5
  return [...control.listChildren()].sort((left, right) => left.path.localeCompare(right.path));
7
6
  }
8
7
 
9
- function resumeTarget(control: NonNullable<ReturnType<typeof findSubagentControl>>, id: string) {
10
- if (id !== control.parent.path) return control.findChild(id);
11
- const children = canonicalChildren(control);
12
- return children.find((child) => child.status === "interrupted") ?? children[0];
13
- }
14
-
15
8
  function childLines(control: ReturnType<typeof findSubagentControl>, id?: string): string[] {
16
9
  if (!control) return [];
17
10
  if (id) {
@@ -77,30 +70,3 @@ export async function interruptInProcessChild(id: string): Promise<SubagentToolR
77
70
  details: { mode: "management", results: [] },
78
71
  };
79
72
  }
80
-
81
- export async function resumeInProcessChild(
82
- id: string,
83
- message: string,
84
- candidate: ModelCandidate,
85
- ): Promise<SubagentToolResult | undefined> {
86
- const control = findSubagentControl(id);
87
- if (!control) return undefined;
88
- const target = resumeTarget(control, id);
89
- if (!target) {
90
- return {
91
- content: [{ type: "text", text: `No in-process child found for '${id}'.` }],
92
- isError: true,
93
- details: { mode: "management", results: [] },
94
- };
95
- }
96
- const outcome = await control.resumeChild(target.path, message, candidate);
97
- const selection =
98
- id === control.parent.path && control.listChildren().length > 1
99
- ? `Bare run id resolved to ${target.path}; interrupted children are preferred, then canonical path order.\n\n`
100
- : "";
101
- return {
102
- content: [{ type: "text", text: `${selection}${outcome.envelope}` }],
103
- isError: outcome.status === "error" || outcome.status === "interrupted" ? true : undefined,
104
- details: { mode: "management", results: [] },
105
- };
106
- }
@@ -1,14 +1,3 @@
1
- export type {
2
- InProcessAttemptControlResult,
3
- InProcessAttemptHandle,
4
- InProcessAttemptResumeOutcome,
5
- } from "./attempt-handles.js";
6
- export {
7
- clearInProcessAttemptHandles,
8
- interruptInProcessAttempt,
9
- registerInProcessAttempt,
10
- resumeInProcessAttempt,
11
- } from "./attempt-handles.js";
12
1
  export type {
13
2
  AdmissionRefusal,
14
3
  AdmittedResult,
@@ -32,7 +21,6 @@ export {
32
21
  continue_detached,
33
22
  createSubagentControl,
34
23
  deliver_child_result,
35
- reload_cold_child,
36
24
  run_child_attempt,
37
25
  SubagentControlRuntime,
38
26
  terminate_child_attempt,
@@ -11,7 +11,7 @@ export const CHILD_SUBAGENT_BOUNDARY_INSTRUCTIONS = [
11
11
  export const CHILD_FANOUT_BOUNDARY_INSTRUCTIONS = [
12
12
  "You are a child subagent with explicit fanout responsibility for this assigned task.",
13
13
  "The parent session owns final orchestration, completion decisions, and follow-up implementation launches.",
14
- "Delegation is one level deep: the `subagent` tool refuses every launch, resume, and interrupt from inside a subagent.",
14
+ "Delegation is one level deep: the `subagent` tool refuses every launch and interrupt from inside a subagent.",
15
15
  "Do the assigned fanout work yourself and report what you found; do not plan around delegating it.",
16
16
  "If you need to edit files, call the actual edit/write tools. Do not print tool-call syntax, patches, or pseudo-tool calls as text.",
17
17
  ].join("\n");