@bastani/atomic 0.9.15 → 0.9.16-alpha.2

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 (151) hide show
  1. package/CHANGELOG.md +19 -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 +26 -0
  12. package/dist/builtin/subagents/README.md +37 -114
  13. package/dist/builtin/subagents/agents/worker.md +2 -2
  14. package/dist/builtin/subagents/package.json +1 -5
  15. package/dist/builtin/subagents/skills/subagent/SKILL.md +26 -103
  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 -4
  20. package/dist/builtin/subagents/src/extension/tool-rendering.ts +53 -0
  21. package/dist/builtin/subagents/src/intercom/intercom-bridge.ts +1 -54
  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 -257
  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/slash/slash-commands.ts +4 -468
  50. package/dist/builtin/subagents/src/tui/render-progress.ts +2 -2
  51. package/dist/builtin/subagents/src/tui/render-result-compact.ts +2 -2
  52. package/dist/builtin/subagents/src/tui/render-result.ts +38 -14
  53. package/dist/builtin/subagents/src/tui/render-status-progress.ts +1 -1
  54. package/dist/builtin/web-access/package.json +1 -1
  55. package/dist/builtin/workflows/CHANGELOG.md +16 -0
  56. package/dist/builtin/workflows/README.md +1 -1
  57. package/dist/builtin/workflows/package.json +1 -1
  58. package/dist/builtin/workflows/skills/bro/LICENSE.txt +21 -0
  59. package/dist/builtin/workflows/skills/bro/SKILL.md +14 -0
  60. package/dist/builtin/workflows/skills/how/LICENSE.txt +21 -0
  61. package/dist/builtin/workflows/skills/how/SKILL.md +162 -0
  62. package/dist/builtin/workflows/skills/how/references/critic-prompt.md +59 -0
  63. package/dist/builtin/workflows/skills/how/references/critique-rubric.md +58 -0
  64. package/dist/builtin/workflows/skills/how/references/explainer-prompt.md +55 -0
  65. package/dist/builtin/workflows/skills/how/references/explorer-prompt.md +52 -0
  66. package/dist/builtin/workflows/skills/teach/LICENSE.txt +21 -0
  67. package/dist/builtin/workflows/skills/teach/SKILL.md +28 -0
  68. package/dist/builtin/workflows/skills/unslop/LICENSE.txt +21 -0
  69. package/dist/builtin/workflows/skills/unslop/SKILL.md +87 -0
  70. package/dist/builtin/workflows/skills/why/LICENSE.txt +21 -0
  71. package/dist/builtin/workflows/skills/why/SKILL.md +267 -0
  72. package/dist/builtin/workflows/skills/why/references/epistemics.md +144 -0
  73. package/dist/builtin/workflows/skills/why/references/investigator-prompt.md +103 -0
  74. package/dist/builtin/workflows/skills/why/references/source-playbook.md +17 -0
  75. package/dist/builtin/workflows/skills/why/references/sources/code-archaeology.md +88 -0
  76. package/dist/builtin/workflows/skills/why/references/sources/databricks.md +70 -0
  77. package/dist/builtin/workflows/skills/why/references/sources/datadog.md +99 -0
  78. package/dist/builtin/workflows/skills/why/references/sources/incident-postmortem.md +15 -0
  79. package/dist/builtin/workflows/skills/why/references/sources/linear.md +48 -0
  80. package/dist/builtin/workflows/skills/why/references/sources/notion.md +55 -0
  81. package/dist/builtin/workflows/skills/why/references/sources/sentry.md +100 -0
  82. package/dist/builtin/workflows/skills/why/references/sources/slack.md +54 -0
  83. package/dist/builtin/workflows/skills/why/references/synthesizer-prompt.md +135 -0
  84. package/dist/builtin/workflows/src/durable/dbos-embedded-postgres-root.ts +548 -39
  85. package/dist/builtin/workflows/src/durable/dbos-embedded-postgres.ts +529 -65
  86. package/dist/builtin/workflows/src/durable/dbos-lifecycle.ts +30 -8
  87. package/dist/builtin/workflows/src/durable/dbos-local-postgres.ts +36 -3
  88. package/dist/builtin/workflows/src/durable/local-command.ts +68 -10
  89. package/dist/builtin/workflows/src/extension/companions.ts +1 -1
  90. package/dist/builtin/workflows/src/extension/dispatcher.ts +7 -1
  91. package/dist/builtin/workflows/src/extension/extension-factory.ts +2 -2
  92. package/dist/builtin/workflows/src/extension/index.bundle.mjs +1094 -298
  93. package/dist/builtin/workflows/src/extension/public-types.ts +2 -2
  94. package/dist/builtin/workflows/src/extension/render-result.ts +16 -2
  95. package/dist/builtin/workflows/src/extension/runtime.ts +8 -1
  96. package/dist/builtin/workflows/src/extension/workflow-request-abort.ts +15 -0
  97. package/dist/builtin/workflows/src/extension/workflow-tool-content.ts +4 -3
  98. package/dist/builtin/workflows/src/extension/workflow-tool-registration.ts +107 -13
  99. package/dist/builtin/workflows/src/extension/workflow-tool.ts +22 -12
  100. package/dist/builtin/workflows/src/tui/widget.ts +25 -6
  101. package/dist/core/agent-session-auto-compaction.js +10 -10
  102. package/dist/core/agent-session-auto-compaction.js.map +1 -1
  103. package/dist/core/agent-session-events.d.ts.map +1 -1
  104. package/dist/core/agent-session-events.js +11 -13
  105. package/dist/core/agent-session-events.js.map +1 -1
  106. package/dist/core/agent-session-methods.d.ts +1 -1
  107. package/dist/core/agent-session-methods.d.ts.map +1 -1
  108. package/dist/core/agent-session-methods.js.map +1 -1
  109. package/dist/core/agent-session-models.d.ts.map +1 -1
  110. package/dist/core/agent-session-models.js +5 -7
  111. package/dist/core/agent-session-models.js.map +1 -1
  112. package/dist/core/agent-session-prompt.js +4 -4
  113. package/dist/core/agent-session-prompt.js.map +1 -1
  114. package/dist/core/agent-session-retry.d.ts +4 -4
  115. package/dist/core/agent-session-retry.d.ts.map +1 -1
  116. package/dist/core/agent-session-retry.js +8 -34
  117. package/dist/core/agent-session-retry.js.map +1 -1
  118. package/dist/core/agent-session-tool-hooks.js +4 -4
  119. package/dist/core/agent-session-tool-hooks.js.map +1 -1
  120. package/dist/core/atomic-guide-command.d.ts.map +1 -1
  121. package/dist/core/atomic-guide-command.js +3 -10
  122. package/dist/core/atomic-guide-command.js.map +1 -1
  123. package/dist/core/event-bus.d.ts.map +1 -1
  124. package/dist/core/event-bus.js +19 -3
  125. package/dist/core/event-bus.js.map +1 -1
  126. package/dist/core/slash-commands.d.ts.map +1 -1
  127. package/dist/core/slash-commands.js +0 -3
  128. package/dist/core/slash-commands.js.map +1 -1
  129. package/dist/core/system-prompt.d.ts.map +1 -1
  130. package/dist/core/system-prompt.js +0 -12
  131. package/dist/core/system-prompt.js.map +1 -1
  132. package/dist/modes/interactive-engine/remote-command-catalog.d.ts +1 -1
  133. package/dist/modes/interactive-engine/remote-command-catalog.js +1 -1
  134. package/dist/modes/interactive-engine/remote-command-catalog.js.map +1 -1
  135. package/docs/intercom.md +29 -27
  136. package/docs/settings.md +2 -2
  137. package/docs/subagents.md +18 -15
  138. package/docs/usage.md +0 -9
  139. package/docs/workflows.md +27 -10
  140. package/npm-shrinkwrap.json +32 -32
  141. package/package.json +3 -3
  142. package/dist/builtin/subagents/prompts/gather-context-and-clarify.md +0 -27
  143. package/dist/builtin/subagents/prompts/parallel-cleanup.md +0 -61
  144. package/dist/builtin/subagents/prompts/parallel-context-build.md +0 -43
  145. package/dist/builtin/subagents/prompts/parallel-handoff-plan.md +0 -47
  146. package/dist/builtin/subagents/prompts/parallel-research.md +0 -34
  147. package/dist/builtin/subagents/prompts/parallel-review.md +0 -47
  148. package/dist/builtin/subagents/prompts/review-loop.md +0 -39
  149. package/dist/builtin/subagents/src/extension/doctor.ts +0 -188
  150. package/dist/builtin/subagents/src/runs/inprocess/attempt-handles.ts +0 -104
  151. /package/dist/builtin/subagents/src/runs/foreground/{subagent-executor-resume.ts → subagent-executor-cwd.ts} +0 -0
@@ -1,6 +1,7 @@
1
1
  import type { ExtensionContext } from "@bastani/atomic";
2
2
  import type { AgentConfig } from "../../agents/agents.js";
3
3
  import { INTERCOM_BRIDGE_MARKER } from "../../intercom/intercom-bridge.js";
4
+ import { requestSupervisorAuthorization } from "../../intercom/supervisor-authorization.js";
4
5
  import type { ModelInfo } from "../../shared/model-info.js";
5
6
  import type { CandidateModelResolver } from "../../shared/model-resolution.js";
6
7
  import { buildTaskInstructions, type ResolvedStepBehavior } from "../../shared/settings.js";
@@ -8,8 +9,10 @@ import type {
8
9
  AgentProgress,
9
10
  ArtifactConfig,
10
11
  ControlEvent,
12
+ ForegroundParentAskHandoff,
11
13
  IntercomEventBus,
12
14
  MaxOutputConfig,
15
+ RunSyncOptions,
13
16
  SingleResult,
14
17
  SubagentState,
15
18
  SubagentToolResult,
@@ -20,6 +23,7 @@ import { inheritedIntercomGroup, resolveChildIntercomGroup } from "../shared/int
20
23
  import { currentModelFullId } from "../shared/model-fallback.js";
21
24
  import { injectSingleOutputInstruction, resolveSingleOutputPath } from "../shared/single-output.js";
22
25
  import type { WorktreeSetup } from "../shared/worktree.js";
26
+ import { markLiveResultIndices } from "./subagent-executor-live-update.js";
23
27
  import type { SubagentExecutorRuntimeDeps, TaskParam } from "./subagent-executor-types.js";
24
28
  import { resolveParallelTaskCwd } from "./subagent-executor-worktree.js";
25
29
 
@@ -27,6 +31,7 @@ interface ForegroundParallelRunInput {
27
31
  tasks: TaskParam[];
28
32
  taskTexts: string[];
29
33
  agents: AgentConfig[];
34
+ agentConfigs?: AgentConfig[];
30
35
  ctx: ExtensionContext;
31
36
  intercomEvents: IntercomEventBus;
32
37
  signal: AbortSignal;
@@ -57,23 +62,39 @@ interface ForegroundParallelRunInput {
57
62
  liveResults: (SingleResult | undefined)[];
58
63
  liveProgress: (AgentProgress | undefined)[];
59
64
  onUpdate?: (r: SubagentToolResult) => void;
65
+ onParentAskHandoff?: (handoff: ForegroundParentAskHandoff) => void;
60
66
  onDetachedExit?: (index: number, result: SingleResult) => void;
67
+ onExecution?: (index: number, runtimeCwd: string, options: RunSyncOptions) => void;
61
68
  worktreeSetup?: WorktreeSetup;
62
69
  runtime: Pick<SubagentExecutorRuntimeDeps, "runSync">;
63
70
  }
64
71
 
72
+ function skippedParallelResult(task: TaskParam, taskText: string, error: string): SingleResult {
73
+ return {
74
+ agent: task.agent,
75
+ task: taskText,
76
+ status: "skipped",
77
+ messages: [],
78
+ usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 },
79
+ error,
80
+ };
81
+ }
82
+
65
83
  export async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Promise<SingleResult[]> {
66
84
  const intercomDetachController = new AbortController();
85
+ const parentAskController = new AbortController();
86
+ const startedIndices = new Set<number>();
87
+ const activeIndices = new Set<number>();
67
88
  return mapConcurrent(input.tasks, input.concurrencyLimit, async (task, index) => {
89
+ if (parentAskController.signal.aborted) {
90
+ return skippedParallelResult(task, input.taskTexts[index] ?? task.task, "Skipped after parent ask handoff");
91
+ }
68
92
  if (intercomDetachController.signal.aborted) {
69
- return {
70
- agent: task.agent,
71
- task: input.taskTexts[index] ?? task.task,
72
- status: "skipped",
73
- messages: [],
74
- usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 },
75
- error: "Skipped after foreground group detached for intercom coordination",
76
- };
93
+ return skippedParallelResult(
94
+ task,
95
+ input.taskTexts[index] ?? task.task,
96
+ "Skipped after foreground group detached for intercom coordination",
97
+ );
77
98
  }
78
99
  const behavior = input.behaviors[index];
79
100
  const effectiveSkills = behavior?.skills;
@@ -93,7 +114,24 @@ export async function runForegroundParallelTasks(input: ForegroundParallelRunInp
93
114
  `${readInstructions.prefix}${input.taskTexts[index]!}${progressInstructions.suffix}`,
94
115
  outputPath,
95
116
  );
117
+ const childIntercomTarget = input.childIntercomTarget?.(task.agent, index);
118
+ const supervisorAuthorization = await requestSupervisorAuthorization(input.intercomEvents, childIntercomTarget);
119
+ if (parentAskController.signal.aborted) {
120
+ return skippedParallelResult(task, input.taskTexts[index] ?? task.task, "Skipped after parent ask handoff");
121
+ }
122
+ if (intercomDetachController.signal.aborted) {
123
+ return skippedParallelResult(
124
+ task,
125
+ input.taskTexts[index] ?? task.task,
126
+ "Skipped after foreground group detached for intercom coordination",
127
+ );
128
+ }
96
129
  const interruptController = new AbortController();
130
+ const interruptForParentAsk = () => interruptController.abort();
131
+ parentAskController.signal.addEventListener("abort", interruptForParentAsk, { once: true });
132
+ if (parentAskController.signal.aborted) interruptController.abort();
133
+ startedIndices.add(index);
134
+ activeIndices.add(index);
97
135
  if (input.foregroundControl) {
98
136
  input.foregroundControl.currentAgent = task.agent;
99
137
  input.foregroundControl.currentIndex = index;
@@ -107,91 +145,124 @@ export async function runForegroundParallelTasks(input: ForegroundParallelRunInp
107
145
  return true;
108
146
  };
109
147
  }
110
- const agentConfig = input.agents.find((agent) => agent.name === task.agent);
111
- return input.runtime
112
- .runSync(input.ctx.cwd, input.agents, task.agent, taskText, {
113
- cwd: taskCwd,
114
- signal: input.signal,
115
- interruptSignal: interruptController.signal,
116
- allowIntercomDetach: agentConfig?.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
117
- intercomEvents: input.intercomEvents,
118
- runId: input.runId,
119
- index,
120
- sessionDir: input.sessionDirForIndex(index),
121
- sessionFile: input.sessionFileForIndex(index),
122
- share: input.shareEnabled,
123
- artifactsDir: input.artifactConfig.enabled ? input.artifactsDir : undefined,
124
- artifactConfig: input.artifactConfig,
125
- maxOutput: input.maxOutput,
126
- outputPath,
127
- outputMode: behavior?.outputMode,
128
- parentDepth: input.parentDepth,
129
- workflowStageSubagentGuard: input.workflowStageSubagentGuard,
130
- workflowSessionMetadata: workflowSessionMetadataFromContext(input.ctx),
131
- controlConfig: input.controlConfig,
132
- onControlEvent: input.onControlEvent,
133
- intercomSessionName: input.childIntercomTarget?.(task.agent, index),
134
- orchestratorIntercomTarget: input.orchestratorIntercomTarget,
135
- intercomGroup: resolveChildIntercomGroup(
136
- task.group ?? input.setIntercomGroup,
137
- inheritedIntercomGroup(input.ctx),
138
- input.sharedAutoIntercomGroup,
139
- ),
140
- onDetachedExit: (result) => input.onDetachedExit?.(index, result),
141
- intercomDetachSignal: intercomDetachController.signal,
142
- onIntercomDetachCommit: () => intercomDetachController.abort(),
143
- modelOverride: input.modelOverrides[index],
144
- availableModels: input.availableModels,
145
- knownModelProviders: input.knownModelProviders,
146
- resolveCandidateModel: input.resolveCandidateModel,
147
- preferredModelProvider: input.ctx.model?.provider,
148
- currentModel: currentModelFullId(input.ctx.model),
149
- currentThinkingLevel: input.ctx.thinkingLevel,
150
- skills: effectiveSkills === false ? [] : effectiveSkills,
151
- onUpdate: input.onUpdate
152
- ? (progressUpdate) => {
153
- const stepResults = progressUpdate.details?.results || [];
154
- const stepProgress = progressUpdate.details?.progress || [];
155
- if (input.foregroundControl && stepProgress.length > 0) {
156
- const current = stepProgress[0];
157
- input.foregroundControl.currentAgent = task.agent;
158
- input.foregroundControl.currentIndex = index;
159
- input.foregroundControl.currentActivityState = current?.activityState;
160
- input.foregroundControl.lastActivityAt = current?.lastActivityAt;
161
- input.foregroundControl.currentTool = current?.currentTool;
162
- input.foregroundControl.currentToolStartedAt = current?.currentToolStartedAt;
163
- input.foregroundControl.currentPath = current?.currentPath;
164
- input.foregroundControl.turnCount = current?.turnCount;
165
- input.foregroundControl.tokens = current?.tokens;
166
- input.foregroundControl.toolCount = current?.toolCount;
167
- input.foregroundControl.updatedAt = Date.now();
168
- }
169
- if (stepResults.length > 0) input.liveResults[index] = stepResults[0];
170
- if (stepProgress.length > 0) input.liveProgress[index] = stepProgress[0];
171
- const mergedResults = input.liveResults.filter(
172
- (result): result is SingleResult => result !== undefined,
173
- );
174
- const mergedProgress = input.liveProgress.filter(
175
- (progress): progress is AgentProgress => progress !== undefined,
176
- );
177
- input.onUpdate?.({
178
- content: progressUpdate.content,
179
- details: {
180
- mode: "parallel",
181
- results: mergedResults,
182
- progress: mergedProgress,
183
- controlEvents: progressUpdate.details?.controlEvents,
184
- totalSteps: input.tasks.length,
185
- },
186
- });
148
+ const agentConfig = input.agentConfigs?.[index] ?? input.agents.find((agent) => agent.name === task.agent);
149
+ const taskAgents = input.agentConfigs && agentConfig ? [agentConfig] : input.agents;
150
+ const runOptions: RunSyncOptions = {
151
+ cwd: taskCwd,
152
+ signal: input.signal,
153
+ interruptSignal: interruptController.signal,
154
+ allowIntercomDetach: agentConfig?.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
155
+ intercomEvents: input.intercomEvents,
156
+ runId: input.runId,
157
+ index,
158
+ sessionDir: input.sessionDirForIndex(index),
159
+ sessionFile: input.sessionFileForIndex(index),
160
+ share: input.shareEnabled,
161
+ artifactsDir: input.artifactConfig.enabled ? input.artifactsDir : undefined,
162
+ artifactConfig: input.artifactConfig,
163
+ maxOutput: input.maxOutput,
164
+ outputPath,
165
+ outputMode: behavior?.outputMode,
166
+ parentDepth: input.parentDepth,
167
+ workflowStageSubagentGuard: input.workflowStageSubagentGuard,
168
+ workflowSessionMetadata: workflowSessionMetadataFromContext(input.ctx),
169
+ controlConfig: input.controlConfig,
170
+ onControlEvent: input.onControlEvent,
171
+ intercomSessionName: childIntercomTarget,
172
+ supervisorAuthorization,
173
+ orchestratorIntercomTarget: input.orchestratorIntercomTarget,
174
+ intercomGroup: resolveChildIntercomGroup(
175
+ task.group ?? input.setIntercomGroup,
176
+ inheritedIntercomGroup(input.ctx),
177
+ input.sharedAutoIntercomGroup,
178
+ ),
179
+ onDetachedExit: (result) => input.onDetachedExit?.(index, result),
180
+ intercomDetachSignal: intercomDetachController.signal,
181
+ onIntercomDetachCommit: () => intercomDetachController.abort(),
182
+ onParentAskHandoff: input.onParentAskHandoff
183
+ ? (request) => {
184
+ if (parentAskController.signal.aborted) return;
185
+ request.taskContext = input.tasks[request.index]?.task ?? "";
186
+ input.onParentAskHandoff?.({
187
+ askingChildIndex: request.index,
188
+ releasedChildIndices: [...activeIndices].sort((left, right) => left - right),
189
+ unlaunchedChildIndices: input.tasks
190
+ .map((_, taskIndex) => taskIndex)
191
+ .filter((taskIndex) => !startedIndices.has(taskIndex)),
192
+ request,
193
+ });
194
+ parentAskController.abort();
195
+ }
196
+ : undefined,
197
+ modelOverride: input.modelOverrides[index],
198
+ availableModels: input.availableModels,
199
+ knownModelProviders: input.knownModelProviders,
200
+ resolveCandidateModel: input.resolveCandidateModel,
201
+ preferredModelProvider: input.ctx.model?.provider,
202
+ currentModel: currentModelFullId(input.ctx.model),
203
+ currentThinkingLevel: input.ctx.thinkingLevel,
204
+ skills: effectiveSkills === false ? [] : effectiveSkills,
205
+ onUpdate: input.onUpdate
206
+ ? (progressUpdate) => {
207
+ const stepResults = progressUpdate.details?.results || [];
208
+ const stepProgress = progressUpdate.details?.progress || [];
209
+ if (input.foregroundControl && stepProgress.length > 0) {
210
+ const current = stepProgress[0];
211
+ input.foregroundControl.currentAgent = task.agent;
212
+ input.foregroundControl.currentIndex = index;
213
+ input.foregroundControl.currentActivityState = current?.activityState;
214
+ input.foregroundControl.lastActivityAt = current?.lastActivityAt;
215
+ input.foregroundControl.currentTool = current?.currentTool;
216
+ input.foregroundControl.currentToolStartedAt = current?.currentToolStartedAt;
217
+ input.foregroundControl.currentPath = current?.currentPath;
218
+ input.foregroundControl.turnCount = current?.turnCount;
219
+ input.foregroundControl.tokens = current?.tokens;
220
+ input.foregroundControl.toolCount = current?.toolCount;
221
+ input.foregroundControl.updatedAt = Date.now();
187
222
  }
188
- : undefined,
189
- })
190
- .finally(() => {
191
- if (input.foregroundControl?.currentIndex === index) {
192
- input.foregroundControl.interrupt = undefined;
193
- input.foregroundControl.updatedAt = Date.now();
194
- }
195
- });
223
+ if (stepResults.length > 0) input.liveResults[index] = stepResults[0];
224
+ if (stepProgress.length > 0) input.liveProgress[index] = stepProgress[0];
225
+ const indexedResults = input.liveResults.flatMap((result, resultIndex) =>
226
+ result === undefined ? [] : [{ result, index: resultIndex }],
227
+ );
228
+ const mergedResults = indexedResults.map(({ result }) => result);
229
+ const mergedProgress = input.liveProgress.filter(
230
+ (progress): progress is AgentProgress => progress !== undefined,
231
+ );
232
+ const controlEvents = progressUpdate.details?.controlEvents?.map((event) => ({ ...event, index }));
233
+ const artifactDir = progressUpdate.details?.artifacts?.dir;
234
+ const artifactFiles = mergedResults.flatMap((result) =>
235
+ result.artifactPaths ? [result.artifactPaths] : [],
236
+ );
237
+ const aggregateUpdate: SubagentToolResult = {
238
+ content: progressUpdate.content,
239
+ details: {
240
+ mode: "parallel",
241
+ results: mergedResults,
242
+ progress: mergedProgress,
243
+ ...(controlEvents?.length ? { controlEvents } : {}),
244
+ totalSteps: input.tasks.length,
245
+ ...(artifactDir && artifactFiles.length
246
+ ? { artifacts: { dir: artifactDir, files: artifactFiles } }
247
+ : {}),
248
+ },
249
+ };
250
+ markLiveResultIndices(
251
+ aggregateUpdate,
252
+ indexedResults.map(({ index: resultIndex }) => resultIndex),
253
+ );
254
+ input.onUpdate?.(aggregateUpdate);
255
+ }
256
+ : undefined,
257
+ };
258
+ input.onExecution?.(index, input.ctx.cwd, runOptions);
259
+ return input.runtime.runSync(input.ctx.cwd, taskAgents, task.agent, taskText, runOptions).finally(() => {
260
+ activeIndices.delete(index);
261
+ parentAskController.signal.removeEventListener("abort", interruptForParentAsk);
262
+ if (input.foregroundControl?.currentIndex === index) {
263
+ input.foregroundControl.interrupt = undefined;
264
+ input.foregroundControl.updatedAt = Date.now();
265
+ }
266
+ });
196
267
  });
197
268
  }
@@ -12,6 +12,7 @@ import {
12
12
  import {
13
13
  type AgentProgress,
14
14
  type ArtifactPaths,
15
+ type ForegroundParentAskHandoff,
15
16
  isWorkflowStageOrchestrationContext,
16
17
  resolveTopLevelParallelConcurrency,
17
18
  resolveTopLevelParallelMaxTasks,
@@ -22,18 +23,18 @@ import {
22
23
  import { compactForegroundDetails, getSingleResultOutput } from "../../shared/utils.js";
23
24
  import { sharedAutoGroupForSet } from "../shared/intercom-group.js";
24
25
  import { resolveModelCandidate } from "../shared/model-fallback.js";
25
- import { aggregateParallelOutputs } from "../shared/parallel-utils.js";
26
+ import { formatParallelResultContent } from "../shared/parallel-utils.js";
26
27
  import { recordRun } from "../shared/run-history.js";
27
28
  import { resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.js";
28
29
  import { cleanupWorktrees, type WorktreeSetup } from "../shared/worktree.js";
29
30
  import { createDetachedCleanupBarrier } from "./detached-cleanup-barrier.js";
31
+ import { formatParentAskHandoffOutput } from "./parent-ask-output.js";
30
32
  import { runForegroundParallelTasks } from "./subagent-executor-parallel-task.js";
33
+ import { markParentAskHandoff } from "./subagent-executor-parent-ask-projection.js";
31
34
  import {
32
35
  createForegroundControlNotifier,
33
36
  maybeBuildForegroundIntercomReceipt,
34
37
  notifyDetachedForegroundChildExit,
35
- rememberForegroundRun,
36
- replaceForegroundRunChild,
37
38
  } from "./subagent-executor-status.js";
38
39
  import type { ExecutionContextData, ResolvedExecutorDeps, TaskParam } from "./subagent-executor-types.js";
39
40
  import {
@@ -82,17 +83,19 @@ export async function runParallelPath(
82
83
  details: { mode: "parallel" as const, results: [] },
83
84
  };
84
85
 
85
- const agentConfigs: AgentConfig[] = [];
86
- for (const t of tasks) {
87
- const config = agents.find((a) => a.name === t.agent);
88
- if (!config) {
89
- return {
90
- content: [{ type: "text", text: `Unknown agent: ${t.agent}` }],
91
- isError: true,
92
- details: { mode: "parallel" as const, results: [] },
93
- };
86
+ const agentConfigs: AgentConfig[] = data.parallelAgentConfigs ? [...data.parallelAgentConfigs] : [];
87
+ if (!data.parallelAgentConfigs) {
88
+ for (const task of tasks) {
89
+ const config = agents.find((agent) => agent.name === task.agent);
90
+ if (!config) {
91
+ return {
92
+ content: [{ type: "text", text: `Unknown agent: ${task.agent}` }],
93
+ isError: true,
94
+ details: { mode: "parallel" as const, results: [] },
95
+ };
96
+ }
97
+ agentConfigs.push(config);
94
98
  }
95
- agentConfigs.push(config);
96
99
  }
97
100
 
98
101
  const workflowStageSubagentGuard = isWorkflowStageOrchestrationContext(ctx);
@@ -143,6 +146,7 @@ export async function runParallelPath(
143
146
  cleanupWorktrees(worktreeSetup);
144
147
  });
145
148
  if (errorResult) return errorResult;
149
+ let parentAsk: ForegroundParentAskHandoff | undefined;
146
150
 
147
151
  try {
148
152
  const duplicateOutputError = findDuplicateParallelOutputPath({
@@ -176,7 +180,6 @@ export async function runParallelPath(
176
180
  const results = await runForegroundParallelTasks({
177
181
  onDetachedExit: (index, result) => {
178
182
  try {
179
- replaceForegroundRunChild(deps.state, runId, index, result);
180
183
  notifyDetachedForegroundChildExit({
181
184
  pi: deps.pi,
182
185
  runId,
@@ -189,9 +192,13 @@ export async function runParallelPath(
189
192
  detachedCleanup.recover(index);
190
193
  }
191
194
  },
195
+ onParentAskHandoff: (handoff) => {
196
+ if (!parentAsk) parentAsk = handoff;
197
+ },
192
198
  tasks,
193
199
  taskTexts,
194
200
  agents,
201
+ ...(data.parallelAgentConfigs ? { agentConfigs } : {}),
195
202
  ctx,
196
203
  intercomEvents: deps.pi.events,
197
204
  signal,
@@ -242,21 +249,26 @@ export async function runParallelPath(
242
249
  mode: "parallel",
243
250
  runId,
244
251
  results,
252
+ parentAskYielded: parentAsk !== undefined,
245
253
  progress: params.includeProgress ? allProgress : undefined,
246
254
  artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
247
255
  });
248
- rememberForegroundRun(deps.state, {
249
- runId,
250
- mode: "parallel",
251
- cwd: effectiveCwd,
252
- results: details.results,
253
- });
256
+ if (parentAsk) {
257
+ const worktreeSuffix = buildParallelWorktreeSuffix(worktreeSetup, artifactsDir, tasks as TaskParam[]);
258
+ const handoffText = formatParentAskHandoffOutput(parentAsk);
259
+ const yieldedResult: SubagentToolResult = {
260
+ content: [{ type: "text", text: worktreeSuffix ? `${handoffText}\n\n${worktreeSuffix}` : handoffText }],
261
+ details,
262
+ };
263
+ markParentAskHandoff(yieldedResult, parentAsk);
264
+ return yieldedResult;
265
+ }
254
266
  if (interrupted) {
255
267
  return {
256
268
  content: [
257
269
  {
258
270
  type: "text",
259
- text: `Parallel run paused after interrupt (${interrupted.agent}). Waiting for explicit next action.`,
271
+ text: `Parallel run ended after interrupt (${interrupted.agent}). Launch fresh subagents for any follow-up.`,
260
272
  },
261
273
  ],
262
274
  details,
@@ -272,7 +284,7 @@ export async function runParallelPath(
272
284
  content: [
273
285
  {
274
286
  type: "text",
275
- text: `Parallel run detached for intercom coordination (${detached.agent}). Reply to the supervisor request first. After the child exits, start a fresh follow-up if needed.`,
287
+ text: `Parallel run detached for intercom coordination (${detached.agent}). Reply to the supervisor request first. After the child exits, launch fresh follow-up work if needed.`,
276
288
  },
277
289
  ],
278
290
  details,
@@ -294,8 +306,7 @@ export async function runParallelPath(
294
306
  }
295
307
 
296
308
  const worktreeSuffix = buildParallelWorktreeSuffix(worktreeSetup, artifactsDir, tasks as TaskParam[]);
297
- const ok = results.filter((result) => result.status === "ok").length;
298
- const aggregatedOutput = aggregateParallelOutputs(
309
+ const childContent = formatParallelResultContent(
299
310
  results.map((result) => ({
300
311
  agent: result.agent,
301
312
  output: result.truncation?.text || getSingleResultOutput(result),
@@ -304,11 +315,7 @@ export async function runParallelPath(
304
315
  })),
305
316
  (i, agent) => `=== Task ${i + 1}: ${agent} ===`,
306
317
  );
307
-
308
- const summary = `${ok}/${results.length} succeeded`;
309
- const fullContent = worktreeSuffix
310
- ? `${summary}\n\n${aggregatedOutput}\n\n${worktreeSuffix}`
311
- : `${summary}\n\n${aggregatedOutput}`;
318
+ const fullContent = worktreeSuffix ? `${childContent}\n\n${worktreeSuffix}` : childContent;
312
319
 
313
320
  return {
314
321
  content: [{ type: "text", text: fullContent }],
@@ -0,0 +1,11 @@
1
+ import type { ForegroundParentAskHandoff, SubagentToolResult } from "../../shared/types.js";
2
+
3
+ const parentAskHandoffs = new WeakMap<SubagentToolResult, ForegroundParentAskHandoff>();
4
+
5
+ export function markParentAskHandoff(result: SubagentToolResult, handoff: ForegroundParentAskHandoff): void {
6
+ parentAskHandoffs.set(result, handoff);
7
+ }
8
+
9
+ export function getParentAskHandoff(result: SubagentToolResult): ForegroundParentAskHandoff | undefined {
10
+ return parentAskHandoffs.get(result);
11
+ }
@@ -2,6 +2,7 @@ import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { normalizeSkillInput } from "../../agents/skills.js";
4
4
  import { INTERCOM_BRIDGE_MARKER, resolveSubagentIntercomTarget } from "../../intercom/intercom-bridge.js";
5
+ import { requestSupervisorAuthorization } from "../../intercom/supervisor-authorization.js";
5
6
  import { collectKnownModelProviders, type ModelInfo, toModelInfo } from "../../shared/model-info.js";
6
7
  import { createCandidateModelResolver } from "../../shared/model-resolution.js";
7
8
  import {
@@ -13,6 +14,8 @@ import {
13
14
  type AgentProgress,
14
15
  type ArtifactPaths,
15
16
  isWorkflowStageOrchestrationContext,
17
+ type ParentAskHandoffRequest,
18
+ type RunSyncOptions,
16
19
  type SingleResult,
17
20
  type SubagentToolResult,
18
21
  workflowSessionMetadataFromContext,
@@ -29,12 +32,11 @@ import {
29
32
  resolveSingleOutputPath,
30
33
  validateFileOnlyOutputMode,
31
34
  } from "../shared/single-output.js";
35
+ import { formatParentAskHandoffOutput } from "./parent-ask-output.js";
32
36
  import {
33
37
  createForegroundControlNotifier,
34
38
  maybeBuildForegroundIntercomReceipt,
35
39
  notifyDetachedForegroundChildExit,
36
- rememberForegroundRun,
37
- replaceForegroundRunChild,
38
40
  } from "./subagent-executor-status.js";
39
41
  import type { ExecutionContextData, ForegroundControl, ResolvedExecutorDeps } from "./subagent-executor-types.js";
40
42
 
@@ -123,7 +125,8 @@ export async function runSinglePath(
123
125
  const currentProvider = ctx.model?.provider;
124
126
  const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
125
127
  const knownModelProviders = collectKnownModelProviders(ctx.modelRegistry);
126
- let task = params.task ?? "";
128
+ const handoffTaskContext = params.task ?? "";
129
+ let task = handoffTaskContext;
127
130
  const modelOverride: string | undefined = resolveModelCandidate(
128
131
  (params.model as string | undefined) ?? agentConfig.model,
129
132
  availableModels,
@@ -165,6 +168,7 @@ export async function runSinglePath(
165
168
  effectiveSkills = skillOverride;
166
169
  }
167
170
  const interruptController = new AbortController();
171
+ let parentAsk: ParentAskHandoffRequest | undefined;
168
172
  const foregroundControl = deps.state.foregroundControls.get(runId);
169
173
  if (foregroundControl) {
170
174
  foregroundControl.currentAgent = params.agent;
@@ -184,7 +188,8 @@ export async function runSinglePath(
184
188
 
185
189
  let r: SingleResult;
186
190
  try {
187
- r = await deps.runtime.runSync(ctx.cwd, agents, params.agent!, task, {
191
+ const supervisorAuthorization = await requestSupervisorAuthorization(deps.pi.events, childIntercomTarget);
192
+ const runOptions: RunSyncOptions = {
188
193
  cwd: effectiveCwd,
189
194
  signal,
190
195
  interruptSignal: interruptController.signal,
@@ -206,14 +211,18 @@ export async function runSinglePath(
206
211
  controlConfig,
207
212
  onControlEvent,
208
213
  intercomSessionName: childIntercomTarget,
214
+ supervisorAuthorization,
209
215
  orchestratorIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
210
216
  intercomGroup: resolveChildIntercomGroup(params.group, inheritedIntercomGroup(ctx), undefined),
217
+ onParentAskHandoff: (request) => {
218
+ if (parentAsk) return;
219
+ request.taskContext = handoffTaskContext;
220
+ parentAsk = request;
221
+ interruptController.abort();
222
+ },
211
223
  onDetachedExit: (result) => {
212
224
  cleanupTransientProgress(progressDir, artifactConfig.enabled);
213
- if (result) {
214
- replaceForegroundRunChild(deps.state, runId, 0, result);
215
- notifyDetachedForegroundChildExit({ pi: deps.pi, runId, mode: "single", index: 0, result });
216
- }
225
+ if (result) notifyDetachedForegroundChildExit({ pi: deps.pi, runId, mode: "single", index: 0, result });
217
226
  },
218
227
  index: 0,
219
228
  modelOverride,
@@ -224,7 +233,8 @@ export async function runSinglePath(
224
233
  currentModel: currentModelFullId(ctx.model),
225
234
  currentThinkingLevel: ctx.thinkingLevel,
226
235
  skills: effectiveSkills,
227
- });
236
+ };
237
+ r = await deps.runtime.runSync(ctx.cwd, agents, params.agent!, task, runOptions);
228
238
  } catch (error) {
229
239
  cleanupTransientProgress(progressDir, artifactConfig.enabled);
230
240
  throw error;
@@ -263,16 +273,11 @@ export async function runSinglePath(
263
273
  mode: "single",
264
274
  runId,
265
275
  results: [r],
276
+ parentAskYielded: parentAsk !== undefined && r.interrupted,
266
277
  progress: params.includeProgress ? allProgress : undefined,
267
278
  artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
268
279
  truncation: r.truncation,
269
280
  });
270
- rememberForegroundRun(deps.state, {
271
- runId,
272
- mode: "single",
273
- cwd: effectiveCwd,
274
- results: details.results,
275
- });
276
281
 
277
282
  if (!r.detached && !r.interrupted) {
278
283
  const intercomReceipt = await maybeBuildForegroundIntercomReceipt({
@@ -296,7 +301,24 @@ export async function runSinglePath(
296
301
  content: [
297
302
  {
298
303
  type: "text",
299
- text: `Detached for intercom coordination: ${params.agent}. Reply to the supervisor request first. After the child exits, start a fresh follow-up if needed.`,
304
+ text: `Detached for intercom coordination: ${params.agent}. Reply to the supervisor request first. After the child exits, launch a fresh follow-up if needed.`,
305
+ },
306
+ ],
307
+ details,
308
+ };
309
+ }
310
+
311
+ if (parentAsk && r.interrupted) {
312
+ return {
313
+ content: [
314
+ {
315
+ type: "text",
316
+ text: formatParentAskHandoffOutput({
317
+ askingChildIndex: 0,
318
+ releasedChildIndices: [0],
319
+ unlaunchedChildIndices: [],
320
+ request: parentAsk,
321
+ }),
300
322
  },
301
323
  ],
302
324
  details,
@@ -306,7 +328,10 @@ export async function runSinglePath(
306
328
  if (r.interrupted) {
307
329
  return {
308
330
  content: [
309
- { type: "text", text: `Run paused after interrupt (${params.agent}). Waiting for explicit next action.` },
331
+ {
332
+ type: "text",
333
+ text: `Run ended after interrupt (${params.agent}). Launch a fresh subagent for any follow-up.`,
334
+ },
310
335
  ],
311
336
  details,
312
337
  };