@bastani/atomic 0.9.19-alpha.2 → 0.9.19-alpha.4

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 (197) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/dist/builtin/intercom/CHANGELOG.md +15 -0
  3. package/dist/builtin/intercom/broker/broker.ts +8 -0
  4. package/dist/builtin/intercom/broker/client.ts +18 -0
  5. package/dist/builtin/intercom/broker/send-handler.ts +9 -0
  6. package/dist/builtin/intercom/index.bundle.mjs +161 -30
  7. package/dist/builtin/intercom/package.json +1 -1
  8. package/dist/builtin/intercom/types.ts +2 -1
  9. package/dist/builtin/mcp/package.json +1 -1
  10. package/dist/builtin/subagents/CHANGELOG.md +18 -0
  11. package/dist/builtin/subagents/README.md +14 -14
  12. package/dist/builtin/subagents/package.json +1 -1
  13. package/dist/builtin/subagents/skills/subagent/SKILL.md +14 -14
  14. package/dist/builtin/subagents/src/extension/index.bundle.mjs +142 -60
  15. package/dist/builtin/web-access/package.json +1 -1
  16. package/dist/builtin/workflows/CHANGELOG.md +33 -0
  17. package/dist/builtin/workflows/README.md +14 -15
  18. package/dist/builtin/workflows/builtin/{chunk-mygmtz6e.js → chunk-9k3ywfgd.js} +1 -1
  19. package/dist/builtin/workflows/builtin/{chunk-r9qq7xfj.js → chunk-ahbntmx2.js} +1 -1
  20. package/dist/builtin/workflows/builtin/{chunk-zggjyg5z.js → chunk-n8zysf1t.js} +1 -1
  21. package/dist/builtin/workflows/builtin/goal.js +2 -2
  22. package/dist/builtin/workflows/builtin/index.js +3 -3
  23. package/dist/builtin/workflows/builtin/ralph.js +2 -2
  24. package/dist/builtin/workflows/package.json +5 -5
  25. package/dist/builtin/workflows/src/extension/index.bundle.mjs +171 -207
  26. package/dist/builtin/workflows/src/index.js +33 -6
  27. package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +2 -1
  28. package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +1 -1
  29. package/dist/core/agent-session-auto-compaction.js +1 -1
  30. package/dist/core/agent-session-auto-compaction.js.map +1 -1
  31. package/dist/core/agent-session-compaction.js +1 -1
  32. package/dist/core/agent-session-compaction.js.map +1 -1
  33. package/dist/core/agent-session-custom-message-commit.d.ts.map +1 -1
  34. package/dist/core/agent-session-custom-message-commit.js +33 -2
  35. package/dist/core/agent-session-custom-message-commit.js.map +1 -1
  36. package/dist/core/agent-session-message-queue.d.ts +1 -1
  37. package/dist/core/agent-session-message-queue.d.ts.map +1 -1
  38. package/dist/core/agent-session-message-queue.js +22 -8
  39. package/dist/core/agent-session-message-queue.js.map +1 -1
  40. package/dist/core/agent-session-methods.d.ts +7 -1
  41. package/dist/core/agent-session-methods.d.ts.map +1 -1
  42. package/dist/core/agent-session-methods.js.map +1 -1
  43. package/dist/core/agent-session-persistent-custom-messages.d.ts +1 -1
  44. package/dist/core/agent-session-persistent-custom-messages.d.ts.map +1 -1
  45. package/dist/core/agent-session-persistent-custom-messages.js +13 -3
  46. package/dist/core/agent-session-persistent-custom-messages.js.map +1 -1
  47. package/dist/core/agent-session-post-tool-compaction.js +1 -1
  48. package/dist/core/agent-session-post-tool-compaction.js.map +1 -1
  49. package/dist/core/agent-session-prompt.d.ts.map +1 -1
  50. package/dist/core/agent-session-prompt.js +83 -0
  51. package/dist/core/agent-session-prompt.js.map +1 -1
  52. package/dist/core/agent-session-queue-pause.d.ts.map +1 -1
  53. package/dist/core/agent-session-queue-pause.js +11 -0
  54. package/dist/core/agent-session-queue-pause.js.map +1 -1
  55. package/dist/core/agent-session-tasks.d.ts +5 -0
  56. package/dist/core/agent-session-tasks.d.ts.map +1 -1
  57. package/dist/core/agent-session-tasks.js +8 -1
  58. package/dist/core/agent-session-tasks.js.map +1 -1
  59. package/dist/core/agent-session-tool-hooks.d.ts.map +1 -1
  60. package/dist/core/agent-session-tool-hooks.js +2 -0
  61. package/dist/core/agent-session-tool-hooks.js.map +1 -1
  62. package/dist/core/agent-session-tool-registry.d.ts.map +1 -1
  63. package/dist/core/agent-session-tool-registry.js +12 -0
  64. package/dist/core/agent-session-tool-registry.js.map +1 -1
  65. package/dist/core/agent-session-transfer.d.ts.map +1 -1
  66. package/dist/core/agent-session-transfer.js +7 -0
  67. package/dist/core/agent-session-transfer.js.map +1 -1
  68. package/dist/core/agent-session-types.d.ts +2 -0
  69. package/dist/core/agent-session-types.d.ts.map +1 -1
  70. package/dist/core/agent-session-types.js +2 -0
  71. package/dist/core/agent-session-types.js.map +1 -1
  72. package/dist/core/agent-session.d.ts +4 -0
  73. package/dist/core/agent-session.d.ts.map +1 -1
  74. package/dist/core/agent-session.js +22 -0
  75. package/dist/core/agent-session.js.map +1 -1
  76. package/dist/core/extensions/context-types.d.ts +6 -0
  77. package/dist/core/extensions/context-types.d.ts.map +1 -1
  78. package/dist/core/extensions/context-types.js.map +1 -1
  79. package/dist/core/extensions/loader-api.d.ts.map +1 -1
  80. package/dist/core/extensions/loader-api.js +3 -0
  81. package/dist/core/extensions/loader-api.js.map +1 -1
  82. package/dist/core/extensions/loader-host-modules.d.ts +1 -1
  83. package/dist/core/extensions/loader-host-modules.d.ts.map +1 -1
  84. package/dist/core/extensions/loader-host-modules.js +2 -1
  85. package/dist/core/extensions/loader-host-modules.js.map +1 -1
  86. package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
  87. package/dist/core/extensions/loader-virtual-modules.js +4 -1
  88. package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
  89. package/dist/core/extensions/runner-context.d.ts +1 -1
  90. package/dist/core/extensions/runner-context.d.ts.map +1 -1
  91. package/dist/core/extensions/runner-context.js +9 -3
  92. package/dist/core/extensions/runner-context.js.map +1 -1
  93. package/dist/core/extensions/runner.js +1 -1
  94. package/dist/core/extensions/runner.js.map +1 -1
  95. package/dist/core/extensions/workflow-events.d.ts +1 -1
  96. package/dist/core/extensions/workflow-events.d.ts.map +1 -1
  97. package/dist/core/extensions/workflow-events.js.map +1 -1
  98. package/dist/core/retry-policy.d.ts +1 -0
  99. package/dist/core/retry-policy.d.ts.map +1 -1
  100. package/dist/core/retry-policy.js +2 -1
  101. package/dist/core/retry-policy.js.map +1 -1
  102. package/dist/core/sdk-exports.d.ts +1 -1
  103. package/dist/core/sdk-exports.d.ts.map +1 -1
  104. package/dist/core/sdk-exports.js +1 -1
  105. package/dist/core/sdk-exports.js.map +1 -1
  106. package/dist/core/settings-manager-basic-accessors.d.ts +6 -3
  107. package/dist/core/settings-manager-basic-accessors.d.ts.map +1 -1
  108. package/dist/core/settings-manager-basic-accessors.js +25 -8
  109. package/dist/core/settings-manager-basic-accessors.js.map +1 -1
  110. package/dist/core/settings-manager.d.ts +1 -1
  111. package/dist/core/settings-manager.d.ts.map +1 -1
  112. package/dist/core/settings-manager.js.map +1 -1
  113. package/dist/core/settings-types.d.ts +6 -0
  114. package/dist/core/settings-types.d.ts.map +1 -1
  115. package/dist/core/settings-types.js.map +1 -1
  116. package/dist/core/slash-commands.d.ts.map +1 -1
  117. package/dist/core/slash-commands.js +4 -5
  118. package/dist/core/slash-commands.js.map +1 -1
  119. package/dist/core/tasks/agent-adapter.d.ts +14 -2
  120. package/dist/core/tasks/agent-adapter.d.ts.map +1 -1
  121. package/dist/core/tasks/agent-adapter.js +45 -4
  122. package/dist/core/tasks/agent-adapter.js.map +1 -1
  123. package/dist/core/tasks/completion.d.ts.map +1 -1
  124. package/dist/core/tasks/completion.js +5 -1
  125. package/dist/core/tasks/completion.js.map +1 -1
  126. package/dist/core/tasks/pause.d.ts +4 -0
  127. package/dist/core/tasks/pause.d.ts.map +1 -0
  128. package/dist/core/tasks/pause.js +49 -0
  129. package/dist/core/tasks/pause.js.map +1 -0
  130. package/dist/core/tasks/supervisor.d.ts +3 -1
  131. package/dist/core/tasks/supervisor.d.ts.map +1 -1
  132. package/dist/core/tasks/supervisor.js +48 -22
  133. package/dist/core/tasks/supervisor.js.map +1 -1
  134. package/dist/core/tools/bash-pty-native.d.ts +16 -2
  135. package/dist/core/tools/bash-pty-native.d.ts.map +1 -1
  136. package/dist/core/tools/bash-pty-native.js +82 -0
  137. package/dist/core/tools/bash-pty-native.js.map +1 -1
  138. package/dist/core/tools/bash.d.ts +15 -2
  139. package/dist/core/tools/bash.d.ts.map +1 -1
  140. package/dist/core/tools/bash.js +49 -4
  141. package/dist/core/tools/bash.js.map +1 -1
  142. package/dist/core/tools/index.d.ts +4 -1
  143. package/dist/core/tools/index.d.ts.map +1 -1
  144. package/dist/core/tools/index.js +12 -0
  145. package/dist/core/tools/index.js.map +1 -1
  146. package/dist/core/tools/kill.d.ts +17 -0
  147. package/dist/core/tools/kill.d.ts.map +1 -0
  148. package/dist/core/tools/kill.js +49 -0
  149. package/dist/core/tools/kill.js.map +1 -0
  150. package/dist/core/tools/powershell.d.ts +84 -28
  151. package/dist/core/tools/powershell.d.ts.map +1 -1
  152. package/dist/core/tools/powershell.js +11 -3
  153. package/dist/core/tools/powershell.js.map +1 -1
  154. package/dist/core/tools/resource-selectors.d.ts +1 -1
  155. package/dist/core/tools/resource-selectors.d.ts.map +1 -1
  156. package/dist/core/tools/resource-selectors.js +7 -4
  157. package/dist/core/tools/resource-selectors.js.map +1 -1
  158. package/dist/core/workflow-stage-admission.d.ts +14 -0
  159. package/dist/core/workflow-stage-admission.d.ts.map +1 -1
  160. package/dist/core/workflow-stage-admission.js +63 -3
  161. package/dist/core/workflow-stage-admission.js.map +1 -1
  162. package/dist/index.d.ts +2 -2
  163. package/dist/index.d.ts.map +1 -1
  164. package/dist/index.js +1 -1
  165. package/dist/index.js.map +1 -1
  166. package/dist/modes/interactive/components/chat-session-host.d.ts.map +1 -1
  167. package/dist/modes/interactive/components/chat-session-host.js +3 -2
  168. package/dist/modes/interactive/components/chat-session-host.js.map +1 -1
  169. package/dist/modes/interactive/components/task-row.d.ts.map +1 -1
  170. package/dist/modes/interactive/components/task-row.js +8 -1
  171. package/dist/modes/interactive/components/task-row.js.map +1 -1
  172. package/docs/background-tasks.md +31 -2
  173. package/docs/compaction.md +20 -0
  174. package/docs/computer-use.md +390 -0
  175. package/docs/development.md +23 -74
  176. package/docs/docs.json +1 -0
  177. package/docs/extensions.md +15 -1
  178. package/docs/herdr.md +26 -77
  179. package/docs/index.md +1 -0
  180. package/docs/intercom.md +14 -2
  181. package/docs/models/evals.md +2 -0
  182. package/docs/models/model-selection.md +10 -7
  183. package/docs/quickstart.md +1 -1
  184. package/docs/sdk.md +11 -1
  185. package/docs/settings.md +24 -0
  186. package/docs/subagents.md +10 -6
  187. package/docs/tmux.md +2 -0
  188. package/docs/tools.md +11 -1
  189. package/docs/usage.md +1 -1
  190. package/docs/workflows/api-reference.md +5 -4
  191. package/docs/workflows/authoring.md +3 -3
  192. package/docs/workflows/operations.md +44 -33
  193. package/docs/workflows/reliable-design.md +3 -3
  194. package/docs/workflows/verification.md +99 -40
  195. package/docs/workflows.md +4 -2
  196. package/npm-shrinkwrap.json +59 -59
  197. package/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -2,6 +2,48 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.9.19-alpha.4] - 2026-09-10
6
+
7
+ ### Breaking Changes
8
+
9
+ - Workflow controls, completion, help, and status hints use `/workflow pause`, `/workflow quit`, and `/workflow resume`. The workflow tool supports run, stage, and individual durable-tool pause targets; `/workflow pause [run-id|--all]` controls runs. Workflow lifecycle control events report `action: "pause"` for pause requests.
10
+ - Renamed the bundled subagent `interrupt` action to `kill`. Migrate `subagent({ action: "interrupt", id })` to `subagent({ action: "kill", id })`, including calls using `runId`. The old action is no longer accepted. Kill terminally stops the child and cannot be resumed; follow-up work requires a fresh launch. Workflow controls use `/workflow pause`; parent cancellation behavior is unchanged.
11
+
12
+ ### Added
13
+
14
+ - Added a model row to the `/workflow connect` graph node cards showing each stage's effective model and thinking level, including canonical fast model identity, with model-name truncation preserving the suffixes. Cards retain duration, status and dependencies in a six-row layout, reflect live fallbacks, and restore identity through durable resume ([#1859](https://github.com/bastani-inc/atomic/pull/1859) by [@sina85](https://github.com/sina85)).
15
+ - Added `{ action: "wait", id, budgetMs }` to Bash and PowerShell for observing existing asynchronous tasks without rerunning commands. The observation budget is optional. Waits retain output and terminal metadata, follow owner observation policy, and release on cancellation or incoming messages without stopping execution or extending task lifetime.
16
+ - Added the agent-callable `kill({ id })` tool for owned background bash and PowerShell tasks in main and workflow-stage chat. It cancels by task ID, preserves retained output and original outcomes, reports current cleanup state, and rejects other owners' tasks and subagents.
17
+ - Added exact per-model overrides through `compaction.modelOverrides` for `compaction.reserveTokens` and Atomic's `compaction.preserve_recent` message count. Each field falls back to ordinary settings and then built-in defaults, with non-negative safe-integer validation. Manual, automatic, and post-tool compaction use the active model's budgets while retaining verbatim line compaction.
18
+
19
+ ### Fixed
20
+
21
+ - First-loaded TypeScript extensions share the live host's classes and singletons when native import falls back to transformation, avoiding duplicate host evaluation and slow startup.
22
+ - Fixed repeated yielded shell waits replaying the first output page instead of progressing through retained output while the task is still running ([#2972](https://github.com/bastani-inc/atomic/pull/2972)).
23
+ - Preserved an explicit subagent kill when parent cancellation arrives during execution-capacity waiting, and kept grouped Intercom cancellation status consistent when a killed child has parent-cancelled siblings.
24
+ - Embedded Postgres now starts on Windows administrative accounts. PostgreSQL refuses to run for a member of the Administrators or Power Users groups, so Atomic launches the retained server process with the same restricted access token `pg_ctl` uses, keeping exact-process shutdown semantics; non-administrative Windows accounts are unchanged. Postgres processes that exit during startup (including that administrator refusal on older builds) now fail fast with the actual server log instead of a readiness timeout.
25
+ - Preserved embedded Postgres startup logs on regular Windows accounts, honored Unicode environment overrides on administrative launches, and prevented Windows handle leaks across repeated launches.
26
+ - Preserved `PATH` lookup and relative executable paths for administrative Windows Postgres launches. Invalid launch inputs containing embedded NUL characters now fail before starting a process rather than using truncated paths, arguments, or environment values.
27
+ - Fixed custom Windows Postgres `.cmd` and `.bat` launchers failing with arguments on administrative accounts, including launcher paths containing spaces. Batch arguments retain their existing quoting and line-break rejection.
28
+ - Fixed explicit `cmd.exe` Postgres launchers and safe verbatim working directories on Windows administrative accounts. Concurrent Postgres launches no longer keep one another's log files open or expose them to unrelated commands starting at the same time.
29
+ - Incoming Intercom send and ask messages now act as a priority interrupt queue for working subagents and live workflow stages: the receiver's current model call or cancellable tool is cancelled immediately and the message is processed within the same task, session, and stage generation. Admission survives consumed preflight input and overlapping SDK interrupt turns. Completed tool side effects are never replayed, the original task prompt is not repeated, multiple arrivals stay in arrival order with duplicate suppression, and exact ask/reply correlation survives the cancelled turn. Explicit user abort, host stop, and terminal/closed receivers still win: late input never restarts finished work.
30
+ - Fixed a deadlock where an extension event hook awaiting an ordinary context-only message could wait behind an inbound Intercom delivery that was itself waiting for that event hook.
31
+ - Inbound delivery retries after a transient persistence failure keep their original arrival position and remain part of both child and workflow-stage settlement, and a card appended before a failed flush is completed on retry instead of being appended a second time.
32
+
33
+ ## [0.9.19-alpha.3] - 2026-09-09
34
+
35
+ ### Fixed
36
+
37
+ - Fixed `Failed to initialize class constructor` when npm-installed Node sessions create multiple task supervisors, preventing shell commands and subagent launches from failing during task-host initialization.
38
+ - Fixed foreground subagent launches and explicit waits blocking parent user steering and incoming Intercom asks/sends. Admitted messages now release the waiting parent's observations in main and workflow-stage chat without cancelling children or affecting other owners.
39
+ - Fixed PowerShell internal-URL path quoting so apostrophes and smart single quotes in resolved paths remain literal instead of allowing injected commands. Bash quoting, deliberate shell commands, and balanced command prefixes are unchanged.
40
+ - Capped agent retry backoff with `retry.maxAgentDelayMs` (60 seconds by default), preserving independent provider retry limits ([#8826](https://github.com/earendil-works/pi/issues/8826)).
41
+ - Rejected extension tools with missing or non-object parameter schema containers during registration instead of breaking provider requests ([#9300](https://github.com/earendil-works/pi/issues/9300)).
42
+ - Workflow-stage pause now blocks new task launches, cancels active and admitted queued agents and commands, and waits for in-flight command admission and resource cleanup. Main-chat and sibling tasks stay unaffected; queued user and Intercom messages survive, and resume permits fresh work without restarting cancelled executions.
43
+ - Fixed npm-installed Node startup failing with a missing upstream AI package after the retry-policy update. Core retry imports now use Atomic's declared AI dependency.
44
+ - Fixed an unhandled rejection when session cancellation interrupts an in-flight stage-chat pause, while preserving pause error reporting and rejection for callers awaiting completion.
45
+ - Fixed Windows extension reloads re-evaluating Atomic's host modules, avoiding duplicate host classes and long reload delays while preserving edits to extension dependencies. The supported `@earendil-works/pi-coding-agent` import also shares host identity with `@bastani/atomic` after reload.
46
+
5
47
  ## [0.9.19-alpha.2] - 2026-09-08
6
48
 
7
49
  ### Added
@@ -4,6 +4,21 @@ All notable changes to the `pi-intercom` extension will be documented in this fi
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.9.19-alpha.4] - 2026-09-10
8
+
9
+ ### Fixed
10
+
11
+ - Route lazy initialization, event-relay, and rejected-candidate cleanup diagnostics through the owning interactive session's notifications, including during shutdown, instead of leaking console output and stacks. Retryable initialization uses warning color; non-interactive console diagnostics (including RPC), original failures, acknowledgements, and retry behavior are preserved even when an error cannot be rendered.
12
+ - `send` and `ask` to a working subagent or live workflow stage are delivered as a priority interrupt: the recipient's active model call or cancellable tool is cancelled immediately and the message is processed in the same task instead of waiting for the next natural turn or being refused as busy. Completed side effects are not undone or replayed. Arrival order holds across persistence retries for both receiver kinds; duplicate suppression, exact reply correlation, terminal-child and closed-stage rejection, unrelated headless protection, and group restrictions are unchanged.
13
+ - A cancelled turn that produced no reply no longer consumes the pending ask's reply context, so replies to consecutive asks stay correlated with the right sender and message.
14
+
15
+ ## [0.9.19-alpha.3] - 2026-09-09
16
+
17
+ ### Fixed
18
+
19
+ - Session listings display host-provided live/terminal reply capability separately from idle activity. Closed workflow generations publish `closed` with post-mortem-only or unavailable reply routing rather than remaining misleadingly idle; group isolation and ask rejection rules are unchanged.
20
+ - Canonical workflow-path asks correlate replies from connected retained stages even after their active roster rows disappear. Closed stages without post-mortem routing reject asks with lifecycle guidance rather than attempting a model turn.
21
+
7
22
  ## [0.9.19-alpha.2] - 2026-09-08
8
23
 
9
24
  ### Breaking Changes
@@ -86,6 +86,7 @@ interface PendingStageAcknowledgment {
86
86
  readonly message: Message;
87
87
  readonly liveTargetId?: string;
88
88
  readonly signature?: string;
89
+ readonly resolveReplyTarget?: true;
89
90
  readonly timeout: NodeJS.Timeout;
90
91
  }
91
92
 
@@ -681,6 +682,7 @@ class IntercomBroker {
681
682
  message: route.message,
682
683
  ...(route.liveTargetId === undefined ? {} : { liveTargetId: route.liveTargetId }),
683
684
  ...(route.signature === undefined ? {} : { signature: route.signature }),
685
+ ...(route.resolveReplyTarget === true ? { resolveReplyTarget: true } : {}),
684
686
  timeout,
685
687
  });
686
688
  if (
@@ -828,6 +830,8 @@ class IntercomBroker {
828
830
  return;
829
831
  }
830
832
  this.pendingQuestions.record(from.info.id, target.info.id, pending.messageId);
833
+ if (pending.resolveReplyTarget === true && pending.attemptId !== undefined)
834
+ writeMessageIfOpen(pending.senderSocket, { type: "question_target", messageId: pending.messageId, attemptId: pending.attemptId, sessionId: target.info.id });
831
835
  }
832
836
  writeMessageIfOpen(pending.senderSocket, {
833
837
  type: "delivered",
@@ -880,6 +884,8 @@ class IntercomBroker {
880
884
  return;
881
885
  }
882
886
  this.pendingQuestions.record(from.info.id, target.info.id, pending.messageId);
887
+ if (pending.resolveReplyTarget === true && pending.attemptId !== undefined)
888
+ writeMessageIfOpen(pending.senderSocket, { type: "question_target", messageId: pending.messageId, attemptId: pending.attemptId, sessionId: target.info.id });
883
889
  }
884
890
  writeMessageIfOpen(pending.senderSocket, {
885
891
  type: "delivered",
@@ -903,6 +909,8 @@ class IntercomBroker {
903
909
  });
904
910
  return;
905
911
  }
912
+ if (pending.message.expectsReply === true && pending.resolveReplyTarget === true && pending.attemptId !== undefined)
913
+ writeMessageIfOpen(pending.senderSocket, { type: "question_target", messageId: pending.messageId, attemptId: pending.attemptId, sessionId: target.info.id });
906
914
  if (!writeMessageIfOpen(target.socket, { type: "message", from: pending.sender, message: pending.message })) {
907
915
  this.deliveredMessages.forget(pending.messageId, pending.signature);
908
916
  writeMessageIfOpen(pending.senderSocket, {
@@ -38,6 +38,8 @@ export interface SendOptions {
38
38
  logicalTarget?: string;
39
39
  /** Public reply must use the broker's exact pending reverse question route. */
40
40
  requirePendingReply?: true;
41
+ /** Broker-authorized identity for a canonical-path ask, delivered before its recipient can reply. */
42
+ onReplyTarget?: (sessionId: string) => void;
41
43
  }
42
44
 
43
45
  export interface SendResult {
@@ -153,6 +155,7 @@ export class IntercomClient extends EventEmitter {
153
155
  /** Source identity is captured once at connect, never read from env per message. */
154
156
  private _messageSource: Message["source"] | undefined;
155
157
  private pendingSends = new PendingSendRegistry();
158
+ private pendingReplyTargets = new Map<string, { messageId: string; bind(sessionId: string): void }>();
156
159
  private pendingGroupLists = new Map<string, { resolve: (groups: GroupSummary[]) => void; reject: (error: Error) => void }>();
157
160
  private pendingLists = new Map<string, { resolve: (directory: SessionDirectory) => void; reject: (e: Error) => void }>();
158
161
  private pendingPresence = new Map<string, {
@@ -415,6 +418,15 @@ export class IntercomClient extends EventEmitter {
415
418
  this.emit("_registration_failed", new Error(brokerMessage.reason));
416
419
  break;
417
420
  }
421
+ case "question_target": {
422
+ const { messageId, attemptId, sessionId } = brokerMessage;
423
+ if (typeof messageId !== "string" || typeof attemptId !== "string" || typeof sessionId !== "string") {
424
+ throw new Error("Invalid question_target message");
425
+ }
426
+ const pending = this.pendingReplyTargets.get(attemptId);
427
+ if (pending?.messageId === messageId) pending.bind(sessionId);
428
+ break;
429
+ }
418
430
  case "sessions": {
419
431
  const { requestId, sessions, workflowStages, workflowFutureStages } = brokerMessage;
420
432
  if (
@@ -989,12 +1001,16 @@ export class IntercomClient extends EventEmitter {
989
1001
  source: this._messageSource,
990
1002
  content: { text: options.text, attachments: options.attachments },
991
1003
  };
1004
+ if (options.onReplyTarget !== undefined) {
1005
+ this.pendingReplyTargets.set(acquired.attempt.attemptId, { messageId, bind: options.onReplyTarget });
1006
+ }
992
1007
  try {
993
1008
  writeMessage(socket, {
994
1009
  type,
995
1010
  to,
996
1011
  ...(options.logicalTarget === undefined ? {} : { logicalTarget: options.logicalTarget }),
997
1012
  ...(options.requirePendingReply === undefined ? {} : { requirePendingReply: options.requirePendingReply }),
1013
+ ...(options.onReplyTarget === undefined ? {} : { resolveReplyTarget: true as const }),
998
1014
  message,
999
1015
  attemptId: acquired.attempt.attemptId,
1000
1016
  ...extra,
@@ -1002,6 +1018,8 @@ export class IntercomClient extends EventEmitter {
1002
1018
  } catch (error) {
1003
1019
  this.pendingSends.reject(acquired.attempt, toError(error));
1004
1020
  }
1021
+ const clearReplyTarget = () => { this.pendingReplyTargets.delete(acquired.attempt.attemptId); };
1022
+ void acquired.attempt.promise.then(clearReplyTarget, clearReplyTarget);
1005
1023
  return acquired.attempt.promise;
1006
1024
  }
1007
1025
  updatePresence(updates: PresenceUpdates): boolean {
@@ -59,6 +59,7 @@ export interface PendingStageRoute {
59
59
  readonly attemptId?: string;
60
60
  readonly liveTargetId?: string;
61
61
  readonly signature?: string;
62
+ readonly resolveReplyTarget?: true;
62
63
  }
63
64
 
64
65
  export type PendingStageRouter = (route: PendingStageRoute) => boolean;
@@ -326,6 +327,8 @@ export function handleBrokerSend(
326
327
  return;
327
328
  }
328
329
  pendingQuestions.record(fromSession.info.id, target.info.id, message.id);
330
+ if (clientMessage.resolveReplyTarget === true && attemptId !== undefined)
331
+ write(socket, { type: "question_target", messageId: message.id, attemptId, sessionId: target.info.id });
329
332
  }
330
333
  write(socket, { type: "delivered", messageId: message.id, attemptId });
331
334
  return;
@@ -369,6 +372,7 @@ export function handleBrokerSend(
369
372
  ...(attemptId ? { attemptId } : {}),
370
373
  liveTargetId: target.info.id,
371
374
  signature,
375
+ ...(clientMessage.resolveReplyTarget === true ? { resolveReplyTarget: true } : {}),
372
376
  })
373
377
  ) {
374
378
  return;
@@ -416,6 +420,8 @@ export function handleBrokerSend(
416
420
  return;
417
421
  }
418
422
  if (message.expectsReply === true) pendingQuestions.record(fromSession.info.id, target.info.id, message.id);
423
+ if (message.expectsReply === true && clientMessage.resolveReplyTarget === true && attemptId !== undefined)
424
+ write(socket, { type: "question_target", messageId: message.id, attemptId, sessionId: target.info.id });
419
425
  write(socket, { type: "delivered", messageId: message.id, attemptId });
420
426
  return;
421
427
  }
@@ -433,6 +439,8 @@ export function handleBrokerSend(
433
439
  });
434
440
  return;
435
441
  }
442
+ if (message.expectsReply === true && clientMessage.resolveReplyTarget === true && attemptId !== undefined)
443
+ write(socket, { type: "question_target", messageId: message.id, attemptId, sessionId: target.info.id });
436
444
  const finishDelivery = (): void => {
437
445
  let accepted: ReturnType<DeliveredMessageCache["accept"]>;
438
446
  try {
@@ -508,6 +516,7 @@ export function handleBrokerSend(
508
516
  message,
509
517
  ...(attemptId ? { attemptId } : {}),
510
518
  signature,
519
+ ...(clientMessage.resolveReplyTarget === true ? { resolveReplyTarget: true } : {}),
511
520
  })
512
521
  ) {
513
522
  return;