@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
package/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.9.16-alpha.1] - 2026-08-23
6
+
7
+ ### Fixed
8
+
9
+ - Foreground child decisions, structured interviews, and `intercom.ask` calls resolved to their launching parent now pause the retained child and return the question plus ordered attachments through the parent `subagent` call instead of deadlocking behind Intercom reply delivery. Real typed foreground children receive exact broker authorization for `contact_supervisor`, including successful non-blocking progress delivery. Bare run-ID resume preserves each paused child's session, cwd, Intercom group, execution settings, canonical index, worktree, and dirty changes while rebuilding control and detach callbacks; a sibling completed at the ask boundary stays terminal without blocking paused siblings. Queued work remains unlaunched and unauthorized, worktree diff capture and cleanup wait for terminal resume, and completed children remain non-resumable ([#2589](https://github.com/bastani-inc/atomic/issues/2589)).
10
+ - Coalesced same-turn sibling `subagent` execution calls into one indexed parallel run instead of rejecting every call after the first. Live result, progress, control, and artifact updates and final results stay route-local without sibling data, while the TUI redraws the shared run as one aggregate parallel widget. Solitary, sequential, management, and true-overlap behavior remains unchanged ([#2588](https://github.com/bastani-inc/atomic/issues/2588)).
11
+
5
12
  ## [0.9.15] - 2026-08-21
6
13
 
7
14
  Cumulative release of the `0.9.15-alpha.1` prerelease. The summary below covers the user-visible outcome of that work; the per-change detail remains in the prerelease section below.
@@ -4,6 +4,16 @@ All notable changes to the `pi-intercom` extension will be documented in this fi
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.9.16-alpha.1] - 2026-08-23
8
+
9
+ ### Breaking Changes
10
+
11
+ - Parent-targeted blocking asks no longer pause a child for `subagent` resume. They end the child and return a fresh-subagent `[TASK_CONTEXT]` handoff; migrate supervisors to launch a new child with the answer ([#2604](https://github.com/bastani-inc/atomic/issues/2604)).
12
+
13
+ ### Changed
14
+
15
+ - `contact_supervisor` decisions/interviews and child-to-parent `intercom.ask` now preserve the original question, ordered duplicate attachments, and agent identity in the fresh-start handoff before broker send or reply-waiter admission ([#2604](https://github.com/bastani-inc/atomic/issues/2604)).
16
+
7
17
  ## [0.9.13] - 2026-08-13
8
18
 
9
19
  Cumulative release of the `0.9.13-alpha.1` – `0.9.13-alpha.3` prereleases. The summary below covers the user-visible outcome of that work; the per-change detail remains in the prerelease sections below.
@@ -134,7 +134,7 @@ When a blocking `intercom.ask` targets a workflow stage that has already complet
134
134
 
135
135
  When a blocking ask reaches a sibling workflow stage during an active model/tool turn, the target reserves it synchronously in the open stage generation before any asynchronous foreground-owner detach handshake. Queue insertion waits inside that reservation, and stage finalization drains it before publishing the terminal snapshot. This prevents a structured-output or other terminal tool call from overtaking a mid-turn ask. If destination-side admission genuinely cannot complete, the asker receives an exact-thread actionable error instead of consuming the full 10-minute timeout.
136
136
 
137
- For delegated children, queued messages and terminal lifecycle notices are ordered per child. Intercom claims the terminal childs pre-terminal ordinary entries in FIFO order and atomically admits that prelude together with the paused, completed, or failed notice. A process-local companion bridge covers lazily loaded extensions whose event buses are distinct, while exact terminal-identity deduplication prevents double admission even when the successful terminal dispatch has no queued prelude. Failed dispatches remain retryable, and pause/resume/completion identities remain distinct. Other childrens entries remain independently queued, messages are not discarded, terminal admission does not wait for a separate model turn, and correlated ask replies still bypass unrelated queued sends.
137
+ For delegated children, queued messages and terminal lifecycle notices are ordered per child. Intercom claims the terminal child's pre-terminal ordinary entries in FIFO order and atomically admits that prelude together with the interrupted, completed, or failed notice. A process-local companion bridge covers lazily loaded extensions whose event buses are distinct, while exact terminal-identity deduplication prevents double admission even when the successful terminal dispatch has no queued prelude. Failed dispatches remain retryable, and each terminal child identity is admitted only once. Other children's entries remain independently queued, messages are not discarded, terminal admission does not wait for a separate model turn, and correlated ask replies still bypass unrelated queued sends.
138
138
 
139
139
  ## Workflow: Planner-Worker Coordination
140
140
 
@@ -244,21 +244,21 @@ This workflow uses Atomic's in-process subagent admission. When the runtime admi
244
244
 
245
245
  `contact_supervisor` is registered from the typed admission record. The record binds the supervisor target, canonical child identity, child index, session name, and broker-issued capability to the child session; these values are not inherited from environment variables. If the parent does not grant supervisor coordination, the session falls back to the regular `intercom` tool.
246
246
 
247
- The child identity remains stable across foreground continuation, interruption, and cold resume. Intercom detach uses the same in-process continuation as foreground coordination, so the terminal envelope retains one canonical path.
247
+ Parent-targeted decisions, interviews, and `intercom.ask` make the current child terminal for continuation. The parent receives the original question, ordered attachments, agent identity, and a dynamic `[TASK_CONTEXT]` handoff for a fresh child with a new run identity. Ordinary Intercom detach for sends, progress updates, and non-parent asks remains separate.
248
248
 
249
249
  ### Three Reasons
250
250
 
251
251
  | Reason | Behavior | Use When |
252
252
  |--------|----------|----------|
253
- | `need_decision` | Sends an ask and blocks until the supervisor replies (10-minute timeout) | The subagent is blocked, uncertain, needs approval, or faces a product/API/scope decision |
254
- | `interview_request` | Sends structured questions and blocks until the supervisor replies | The subagent needs multiple machine-readable answers from the supervisor in one exchange |
253
+ | `need_decision` | In a claimed foreground run, ends the child and returns a fresh-child handoff; otherwise uses the normal ask fallback | The subagent is blocked, uncertain, needs approval, or faces a product/API/scope decision |
254
+ | `interview_request` | In a claimed foreground run, ends the child and returns structured questions in a fresh-child handoff | The subagent needs multiple machine-readable answers from the supervisor in one exchange |
255
255
  | `progress_update` | Fire-and-forget update to the supervisor | Meaningful progress or unexpected discoveries that change the plan |
256
256
 
257
- Do not use `contact_supervisor` for routine completion handoffs. Return the final subagent result normally through `pi-subagents`.
257
+ Do not use `contact_supervisor` for routine completion handoffs. Return the final subagent result normally.
258
258
 
259
259
  Cross-group delivery uses a dedicated broker protocol. Ordinary raw `send` frames always remain group-isolated and are rejected if they include a forged `channel: "supervisor"` marker. A child can cross groups only after its broker-issued capability has bound its registered socket to the exact supervisor. The broker adds the `supervisor` channel marker to validated inbound traffic so parent relays can distinguish it. Replies cross back only when `replyTo` matches a recorded supervisor message in the exact reverse direction; fabricated thread IDs do not bypass isolation.
260
260
 
261
- During a foreground subagent run, Atomic probes for an exact live foreground owner before delivery. The matching child reserves the request, accepts a generation-scoped detach commit, and acknowledges it before asks, sends, decisions, interviews, and progress updates enter the parent's model-visible steering queue. A busy workflow stage first reserves the message in its AgentSession generation boundary, then waits inside that admission for the same detach handshake before model-visible queue insertion. This prevents terminal stage close from overtaking the handshake while still preventing the active subagent tool from blocking the child request that would release it; unclaimed traffic and a still-current receiver whose owner disappears before commit fall back to ordinary queue insertion. One accepted commit releases foreground supervision for every active member of a parallel group while retaining each in-process session and eventual result. Blocking calls remain alive until the parent sends the exact threaded reply; fire-and-forget calls create no reply waiter. Background and unmatched traffic for ordinary sessions keeps queued-until-idle behavior, and generation cancellation/replacement invalidates stale handshakes.
261
+ During a foreground subagent run, parent-targeted decisions, interviews, and asks are claimed before broker delivery or reply-waiter admission. The current child ends and its parent tool call receives the fresh-start handoff. Parallel claims interrupt active siblings, prevent queued tasks from launching, and retain no sibling set for later bare-run-ID continuation. Sends, progress updates, and asks to other peers retain the exact-child probe/commit detach path and ordinary Intercom delivery behavior.
262
262
 
263
263
  ### Example: Blocked Subagent Asks for Guidance
264
264
 
@@ -267,7 +267,7 @@ contact_supervisor({
267
267
  reason: "need_decision",
268
268
  message: "The auth service returns 403 instead of 401 for expired tokens. Should I treat 403 as a re-auth trigger or a hard failure?"
269
269
  })
270
- // → Reply from supervisor: Treat 403 as re-auth trigger. Update the token refresh logic.
270
+ // → Parent receives a [TASK_CONTEXT] handoff and launches a fresh child with the answer.
271
271
  ```
272
272
 
273
273
  ### Example: Structured Supervisor Interview
@@ -284,7 +284,7 @@ contact_supervisor({
284
284
  ]
285
285
  }
286
286
  })
287
- // → Reply from supervisor: { "responses": [{ "id": "api", "value": "Stable API" }, ...] }
287
+ // → Parent includes the structured supervisor answer in a fresh child's task.
288
288
  ```
289
289
 
290
290
  ### Example: Progress Update
@@ -4,6 +4,7 @@ import { Type } from "typebox";
4
4
  import { Text } from "@earendil-works/pi-tui";
5
5
  import type { IntercomClient } from "./broker/client.js";
6
6
  import type { ReplyWait, ReplyWaitAdmission } from "./reply-waiter.ts";
7
+ import { requestParentAskHandoff } from "./parent-ask-handoff.js";
7
8
  import { renderContactSupervisorResult } from "./result-renderers.js";
8
9
  import {
9
10
  type ChildOrchestratorMetadata,
@@ -42,18 +43,18 @@ export function registerContactSupervisorTool(pi: ExtensionAPI, deps: ContactSup
42
43
  pi.registerTool({
43
44
  name: "contact_supervisor",
44
45
  label: "Contact Supervisor",
45
- description: "Subagent-only tool for contacting the supervisor agent that delegated this task. Use need_decision when blocked, uncertain, needing approval, or facing a product/API/scope decision before continuing; this waits for the supervisor's reply. Use interview_request when multiple structured questions need supervisor answers; this also waits for a reply. Use progress_update only for meaningful progress or unexpected discoveries that change the plan; this does not wait for a reply. Do not use for routine completion handoffs.",
46
- promptSnippet: "Subagent-only: contact the supervisor for decisions, structured interviews, or meaningful plan-changing updates. Do not use for routine completion handoffs.",
46
+ description: "Subagent-only tool for contacting the supervisor agent that delegated this task. In a live foreground child, need_decision and interview_request end the child and return a fresh-subagent handoff to the supervisor; fallback Intercom delivery waits for a reply when no foreground owner claims the request. progress_update is fire-and-forget. Do not use for routine completion handoffs.",
47
+ promptSnippet: "Subagent-only: yield decisions or structured interviews to the supervisor for a fresh-child follow-up, or send meaningful plan-changing progress updates.",
47
48
  promptGuidelines: [
48
- "Use contact_supervisor with reason='need_decision' when a subagent is blocked, uncertain, needs approval, or faces a product/API/scope decision before continuing.",
49
- "Use contact_supervisor with reason='interview_request' when the child needs multiple structured answers from the supervisor in one blocking exchange.",
49
+ "Use contact_supervisor with reason='need_decision' when a subagent is blocked, uncertain, needs approval, or faces a product/API/scope decision. A claimed foreground request ends this child; do not wait for a reply.",
50
+ "Use contact_supervisor with reason='interview_request' when the child needs multiple structured answers. A claimed foreground request ends this child; the supervisor starts a fresh child with the answers.",
50
51
  "Use contact_supervisor with reason='progress_update' only for meaningful progress or unexpected discoveries that change the plan.",
51
52
  "Do not use contact_supervisor for routine completion handoffs; return the final subagent result normally.",
52
53
  ],
53
54
  parameters: Type.Object({
54
55
  reason: Type.String({
55
56
  enum: ["need_decision", "progress_update", "interview_request"],
56
- description: "Contact reason: 'need_decision' waits for a reply; 'interview_request' sends structured questions and waits for a reply; 'progress_update' sends a non-blocking update",
57
+ description: "Contact reason: 'need_decision' and 'interview_request' yield a live foreground child for a fresh follow-up; 'progress_update' sends a non-blocking update",
57
58
  }),
58
59
  message: Type.Optional(Type.String({
59
60
  description: "Decision request, optional interview note, or meaningful progress update for the supervisor",
@@ -79,7 +80,7 @@ export function registerContactSupervisorTool(pi: ExtensionAPI, deps: ContactSup
79
80
  details: { error: true },
80
81
  };
81
82
  }
82
- if ((reason === "need_decision" || reason === "progress_update") && typeof params.message !== "string") {
83
+ if (reason === "progress_update" && typeof params.message !== "string") {
83
84
  return {
84
85
  content: [{ type: "text", text: `Missing 'message' parameter for reason '${reason}'.` }],
85
86
  isError: true,
@@ -97,36 +98,71 @@ export function registerContactSupervisorTool(pi: ExtensionAPI, deps: ContactSup
97
98
  };
98
99
  }
99
100
  const supervisorInterview = interviewValidation?.ok === true ? interviewValidation.interview : undefined;
101
+ const metadata = getMetadata();
102
+ if (reason === "need_decision" && typeof params.message !== "string" && !metadata) {
103
+ return {
104
+ content: [{ type: "text", text: `Missing 'message' parameter for reason '${reason}'.` }],
105
+ isError: true,
106
+ details: { error: true },
107
+ };
108
+ }
100
109
 
101
- let connectedClient: IntercomClient;
102
- try {
103
- connectedClient = await ensureConnected("tool");
104
- } catch (error) {
105
- return {
106
- content: [{ type: "text", text: `Intercom not connected: ${getErrorMessage(error)}` }],
107
- isError: true,
108
- details: { error: true },
109
- };
110
- }
110
+ if (!metadata) {
111
+ return {
112
+ content: [{ type: "text", text: "Supervisor contact is unavailable for this session" }],
113
+ isError: true,
114
+ details: { error: true },
115
+ };
116
+ }
117
+ if (signal?.aborted) {
118
+ return {
119
+ content: [{ type: "text", text: "Cancelled" }],
120
+ isError: true,
121
+ details: { error: true },
122
+ };
123
+ }
124
+ if (
125
+ reason !== "progress_update" &&
126
+ requestParentAskHandoff(pi.events, metadata, {
127
+ kind: reason === "interview_request" ? "interview" : "decision",
128
+ question: typeof params.message === "string" ? params.message : "",
129
+ ...(supervisorInterview ? { interview: supervisorInterview } : {}),
130
+ })
131
+ ) {
132
+ return {
133
+ content: [{ type: "text", text: "Parent ask claimed; this child is ending for a fresh subagent start." }],
134
+ isError: false,
135
+ details: { yielded: true },
136
+ };
137
+ }
111
138
 
112
- syncPresenceIdentity(ctx.sessionManager.getSessionId());
139
+ if (reason === "need_decision" && typeof params.message !== "string") {
140
+ return {
141
+ content: [{ type: "text", text: `Missing 'message' parameter for reason '${reason}'.` }],
142
+ isError: true,
143
+ details: { error: true },
144
+ };
145
+ }
146
+ let connectedClient: IntercomClient;
147
+ try {
148
+ connectedClient = await ensureConnected("tool");
149
+ } catch (error) {
150
+ return {
151
+ content: [{ type: "text", text: `Intercom not connected: ${getErrorMessage(error)}` }],
152
+ isError: true,
153
+ details: { error: true },
154
+ };
155
+ }
113
156
 
114
- if (signal?.aborted) {
115
- return {
116
- content: [{ type: "text", text: "Cancelled" }],
117
- isError: true,
118
- details: { error: true },
119
- };
120
- }
157
+ syncPresenceIdentity(ctx.sessionManager.getSessionId());
121
158
 
122
- const metadata = getMetadata();
123
- if (!metadata) {
124
- return {
125
- content: [{ type: "text", text: "Supervisor contact is unavailable for this session" }],
126
- isError: true,
127
- details: { error: true },
128
- };
129
- }
159
+ if (signal?.aborted) {
160
+ return {
161
+ content: [{ type: "text", text: "Cancelled" }],
162
+ isError: true,
163
+ details: { error: true },
164
+ };
165
+ }
130
166
  let sendTo: string;
131
167
  if (connectedClient.supervisorSessionId || metadata.supervisor) {
132
168
  sendTo = connectedClient.supervisorSessionId ?? metadata.supervisor!.supervisorSessionId;
@@ -518,6 +518,7 @@ export default function piIntercomExtension(pi: ExtensionAPI, testOverrides: Int
518
518
  hasReplyWaiter: () => replyWaiters.has(),
519
519
  });
520
520
  registerIntercomTool(pi, {
521
+ childOrchestratorMetadata: currentChildOrchestratorMetadata,
521
522
  ensureConnected,
522
523
  syncPresenceIdentity,
523
524
  beginReplyWait: (from, replyTo, signal) => replyWaiters.begin(from, replyTo, signal),
@@ -230,22 +230,28 @@ export default function intercom(pi: ExtensionAPI, options: LightweightIntercomO
230
230
  return promise;
231
231
  }
232
232
  let typedContactSupervisorRegistered = hasSubagentIntercomEnv();
233
+ const activateTypedContactSupervisor = (): void => {
234
+ const activeTools = pi.getActiveTools();
235
+ if (!activeTools.includes("contact_supervisor")) pi.setActiveTools([...activeTools, "contact_supervisor"]);
236
+ };
233
237
  const registerTypedContactSupervisor = (): void => {
234
- if (typedContactSupervisorRegistered) return;
235
- typedContactSupervisorRegistered = true;
236
- pi.registerTool({
237
- name: "contact_supervisor",
238
- label: "Contact Supervisor",
239
- description: "Subagent-only tool for contacting the supervisor agent that delegated this task.",
240
- promptSnippet: "Subagent-only: contact the supervisor for decisions, interviews, or meaningful updates.",
241
- parameters: Type.Object({
242
- reason: Type.String({ enum: ["need_decision", "progress_update", "interview_request"] }),
243
- message: Type.Optional(Type.String()),
244
- interview: Type.Optional(Type.Unknown()),
245
- }),
246
- execute: (...args) => executeHeavyTool(loadHeavy, "contact_supervisor", args),
247
- renderResult: (...args) => renderHeavyToolResult(loadedHeavy?.heavy ?? null, "contact_supervisor", args),
248
- });
238
+ if (!typedContactSupervisorRegistered) {
239
+ typedContactSupervisorRegistered = true;
240
+ pi.registerTool({
241
+ name: "contact_supervisor",
242
+ label: "Contact Supervisor",
243
+ description: "Subagent-only tool for contacting the supervisor agent that delegated this task.",
244
+ promptSnippet: "Subagent-only: contact the supervisor for decisions, interviews, or meaningful updates.",
245
+ parameters: Type.Object({
246
+ reason: Type.String({ enum: ["need_decision", "progress_update", "interview_request"] }),
247
+ message: Type.Optional(Type.String()),
248
+ interview: Type.Optional(Type.Unknown()),
249
+ }),
250
+ execute: (...args) => executeHeavyTool(loadHeavy, "contact_supervisor", args),
251
+ renderResult: (...args) => renderHeavyToolResult(loadedHeavy?.heavy ?? null, "contact_supervisor", args),
252
+ });
253
+ }
254
+ activateTypedContactSupervisor();
249
255
  };
250
256
  pi.on("session_start", async (event, ctx) => {
251
257
  const typedIdentity = ctx.subagentPolicy?.intercom;
@@ -3,9 +3,11 @@ import { randomUUID } from "crypto";
3
3
  import { Type } from "typebox";
4
4
  import { Text } from "@earendil-works/pi-tui";
5
5
  import type { IntercomClient } from "./broker/client.js";
6
+ import { requestParentAskHandoff } from "./parent-ask-handoff.js";
6
7
  import type { ReplyWait, ReplyWaitAdmission } from "./reply-waiter.ts";
7
8
  import { renderIntercomResult } from "./result-renderers.js";
8
9
  import {
10
+ type ChildOrchestratorMetadata,
9
11
  formatAttachments,
10
12
  formatSessionListRow,
11
13
  getErrorMessage,
@@ -17,6 +19,7 @@ import { resolveSessionTargetId } from "./session-target.js";
17
19
  import { normalizeGroup, validateRuntimeGroup } from "./group.js";
18
20
 
19
21
  interface IntercomToolDeps {
22
+ childOrchestratorMetadata?: ChildOrchestratorMetadata | null | (() => ChildOrchestratorMetadata | null);
20
23
  ensureConnected(reason: "tool"): Promise<IntercomClient>;
21
24
  syncPresenceIdentity(sessionId: string): void;
22
25
  resolveSessionTarget?(activeClient: IntercomClient, nameOrId: string): Promise<string | null>;
@@ -36,8 +39,11 @@ interface IntercomToolDeps {
36
39
  }
37
40
 
38
41
  export function registerIntercomTool(pi: ExtensionAPI, deps: IntercomToolDeps): void {
39
- const { ensureConnected, syncPresenceIdentity, beginReplyWait, hasReplyWaiter } = deps;
42
+ const { childOrchestratorMetadata, ensureConnected, syncPresenceIdentity, beginReplyWait, hasReplyWaiter } = deps;
40
43
  const resolveTarget = deps.resolveSessionTarget ?? resolveSessionTargetId;
44
+ const getMetadata = typeof childOrchestratorMetadata === "function"
45
+ ? childOrchestratorMetadata
46
+ : () => childOrchestratorMetadata ?? null;
41
47
  const activeReplyTracker = (): ReplyTracker =>
42
48
  typeof deps.replyTracker === "function" ? deps.replyTracker() : deps.replyTracker;
43
49
  pi.registerTool({
@@ -306,7 +312,7 @@ does not grant cross-group access: contact_supervisor is the only cross-group pa
306
312
  }
307
313
 
308
314
  case "ask": {
309
- if (!to || !message) {
315
+ if (!to) {
310
316
  return {
311
317
  content: [{ type: "text", text: "Missing 'to' or 'message' parameter" }],
312
318
  isError: true,
@@ -314,14 +320,6 @@ does not grant cross-group access: contact_supervisor is the only cross-group pa
314
320
  };
315
321
  }
316
322
 
317
- if (hasReplyWaiter()) {
318
- return {
319
- content: [{ type: "text", text: "Already waiting for a reply" }],
320
- isError: true,
321
- details: { error: true },
322
- };
323
- }
324
-
325
323
  if (_signal?.aborted) {
326
324
  return {
327
325
  content: [{ type: "text", text: "Cancelled" }],
@@ -332,6 +330,36 @@ does not grant cross-group access: contact_supervisor is the only cross-group pa
332
330
  let wait: ReplyWait | null = null;
333
331
 
334
332
  try {
333
+ const metadata = getMetadata();
334
+ const currentSupervisorId = connectedClient.supervisorSessionId ?? undefined;
335
+ const metadataSupervisorId = metadata?.supervisor?.supervisorSessionId;
336
+ const directParentTarget = Boolean(
337
+ metadata &&
338
+ (to === metadata.orchestratorTarget ||
339
+ to === currentSupervisorId ||
340
+ to === metadataSupervisorId),
341
+ );
342
+ const claimParentAsk = (resolvedTargetId: string): boolean =>
343
+ Boolean(
344
+ metadata &&
345
+ requestParentAskHandoff(pi.events, metadata, {
346
+ kind: "intercom",
347
+ question: typeof message === "string" ? message : "",
348
+ attachments,
349
+ resolvedTargetId,
350
+ }),
351
+ );
352
+ if (
353
+ directParentTarget &&
354
+ claimParentAsk(currentSupervisorId ?? metadataSupervisorId ?? to)
355
+ ) {
356
+ return {
357
+ content: [{ type: "text", text: "Parent ask claimed; this child is ending for a fresh subagent start." }],
358
+ isError: false,
359
+ details: { yielded: true },
360
+ };
361
+ }
362
+
335
363
  const sendTo = await resolveTarget(connectedClient, to) ?? to;
336
364
  if (_signal?.aborted) {
337
365
  return {
@@ -347,6 +375,34 @@ does not grant cross-group access: contact_supervisor is the only cross-group pa
347
375
  details: { error: true },
348
376
  };
349
377
  }
378
+ if (metadata && !directParentTarget) {
379
+ const authoritativeParent = [currentSupervisorId, metadataSupervisorId].find(
380
+ (candidate) => candidate === sendTo,
381
+ );
382
+ const resolvedParent =
383
+ authoritativeParent ?? await resolveTarget(connectedClient, metadata.orchestratorTarget);
384
+ if (resolvedParent !== null && resolvedParent === sendTo && claimParentAsk(sendTo)) {
385
+ return {
386
+ content: [{ type: "text", text: "Parent ask claimed; this child is ending for a fresh subagent start." }],
387
+ isError: false,
388
+ details: { yielded: true },
389
+ };
390
+ }
391
+ }
392
+ if (!message) {
393
+ return {
394
+ content: [{ type: "text", text: "Missing 'to' or 'message' parameter" }],
395
+ isError: true,
396
+ details: { error: true },
397
+ };
398
+ }
399
+ if (hasReplyWaiter()) {
400
+ return {
401
+ content: [{ type: "text", text: "Already waiting for a reply" }],
402
+ isError: true,
403
+ details: { error: true },
404
+ };
405
+ }
350
406
  const questionId = randomUUID();
351
407
  const admission = beginReplyWait(sendTo, questionId, _signal);
352
408
  if (!admission.ok) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/intercom",
3
- "version": "0.9.15",
3
+ "version": "0.9.16-alpha.1",
4
4
  "private": true,
5
5
  "description": "Atomic extension providing a private coordination channel between parent and child agent sessions. Fork of: https://github.com/nicobailon/pi-intercom",
6
6
  "contributors": [
@@ -0,0 +1,72 @@
1
+ import { isStaleExtensionContextError, type ExtensionAPI } from "@bastani/atomic";
2
+ import type {
3
+ ChildOrchestratorMetadata,
4
+ SupervisorInterviewRequest,
5
+ } from "./intercom-utils.js";
6
+ import type { Attachment } from "./types.js";
7
+
8
+ export const PARENT_ASK_HANDOFF_REQUEST_EVENT = "subagent:parent-ask-handoff-request";
9
+
10
+ const PROCESS_PARENT_ASK_CLAIMS = Symbol.for("atomic/subagents/parent-ask-claims@1");
11
+
12
+ type ProcessParentAskClaimHandler = (payload: unknown) => void;
13
+
14
+ interface ProcessParentAskClaimRegistry {
15
+ handlers: Set<ProcessParentAskClaimHandler>;
16
+ }
17
+
18
+ function processClaimRegistry(): ProcessParentAskClaimRegistry | undefined {
19
+ const slots = globalThis as typeof globalThis & Record<symbol, ProcessParentAskClaimRegistry | undefined>;
20
+ return slots[PROCESS_PARENT_ASK_CLAIMS];
21
+ }
22
+
23
+ export type ParentAskKind = "decision" | "interview" | "intercom";
24
+
25
+ export interface ParentAskHandoffRequest {
26
+ runId: string;
27
+ index: number;
28
+ agent: string;
29
+ childIntercomTarget: string;
30
+ orchestratorTarget: string;
31
+ kind: ParentAskKind;
32
+ question: string;
33
+ attachments?: Attachment[];
34
+ interview?: SupervisorInterviewRequest;
35
+ resolvedTargetId?: string;
36
+ taskContext?: string;
37
+ claimed: boolean;
38
+ }
39
+
40
+ export function requestParentAskHandoff(
41
+ events: ExtensionAPI["events"] | undefined,
42
+ metadata: ChildOrchestratorMetadata,
43
+ input: Pick<ParentAskHandoffRequest, "kind" | "question" | "attachments" | "interview" | "resolvedTargetId">,
44
+ ): boolean {
45
+ const index = Number(metadata.index);
46
+ if (!events || !Number.isInteger(index) || index < 0 || !metadata.sessionName) return false;
47
+ const request: ParentAskHandoffRequest = {
48
+ runId: metadata.runId,
49
+ index,
50
+ agent: metadata.agent,
51
+ childIntercomTarget: metadata.sessionName,
52
+ orchestratorTarget: metadata.orchestratorTarget,
53
+ kind: input.kind,
54
+ question: input.question,
55
+ ...(input.attachments ? { attachments: input.attachments } : {}),
56
+ ...(input.interview ? { interview: input.interview } : {}),
57
+ ...(input.resolvedTargetId ? { resolvedTargetId: input.resolvedTargetId } : {}),
58
+ claimed: false,
59
+ };
60
+ try {
61
+ events.emit(PARENT_ASK_HANDOFF_REQUEST_EVENT, request);
62
+ } catch (error) {
63
+ if (!isStaleExtensionContextError(error)) throw error;
64
+ }
65
+ if (!request.claimed) {
66
+ for (const handler of [...(processClaimRegistry()?.handlers ?? [])]) {
67
+ handler(request);
68
+ if (request.claimed) break;
69
+ }
70
+ }
71
+ return request.claimed;
72
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/mcp",
3
- "version": "0.9.15",
3
+ "version": "0.9.16-alpha.1",
4
4
  "private": true,
5
5
  "description": "Atomic extension that adapts MCP (Model Context Protocol) servers into the coding agent. Fork of: https://github.com/nicobailon/pi-mcp-adapter",
6
6
  "contributors": [
@@ -2,6 +2,19 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.9.16-alpha.1] - 2026-08-23
6
+
7
+ ### Breaking Changes
8
+
9
+ - Removed the public `subagent({ action: "resume" })` action and its `index`/`message` inputs. Completed, interrupted, and parent-question children are terminal; migrate follow-ups to a fresh `subagent({ agent, task })` launch with explicit `[TASK_CONTEXT]` and expect a new run identity ([#2604](https://github.com/bastani-inc/atomic/issues/2604)).
10
+
11
+ ### Changed
12
+
13
+ - Parent-targeted `contact_supervisor` and Intercom asks now end the child and return the verbatim question, ordered attachments, agent identity, and a dynamic fresh-child handoff. Parallel asks no longer retain sibling sets for bare-run-ID continuation ([#2604](https://github.com/bastani-inc/atomic/issues/2604)).
14
+
15
+ ### Fixed
16
+ - Coalesced same-turn sibling `subagent` execution calls into one indexed parallel run instead of rejecting every call after the first. Live result, progress, control, and artifact updates and final results stay route-local without sibling data, while the TUI redraws the shared run as one aggregate parallel widget. Solitary, sequential, management, and true-overlap behavior remains unchanged ([#2588](https://github.com/bastani-inc/atomic/issues/2588)).
17
+
5
18
  ## [0.9.15] - 2026-08-21
6
19
 
7
20
  Cumulative release of the `0.9.15-alpha.1` prerelease. The summary below covers the user-visible outcome of that work; the per-change detail remains in the prerelease section below.