@bastani/atomic 0.9.16-alpha.7 → 0.9.16-alpha.8

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 (207) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/builtin/intercom/CHANGELOG.md +6 -0
  3. package/dist/builtin/intercom/README.md +4 -4
  4. package/dist/builtin/intercom/broker/broker.ts +617 -22
  5. package/dist/builtin/intercom/broker/client-message-validation.ts +9 -0
  6. package/dist/builtin/intercom/broker/client.ts +200 -10
  7. package/dist/builtin/intercom/broker/pending-send-registry.ts +4 -0
  8. package/dist/builtin/intercom/broker/send-handler.ts +90 -36
  9. package/dist/builtin/intercom/broker/send-signature.ts +20 -8
  10. package/dist/builtin/intercom/group.ts +85 -0
  11. package/dist/builtin/intercom/index.bundle.mjs +680 -53
  12. package/dist/builtin/intercom/package.json +1 -1
  13. package/dist/builtin/intercom/session-target.ts +54 -0
  14. package/dist/builtin/intercom/skills/intercom/SKILL.md +18 -3
  15. package/dist/builtin/intercom/source-ownership.ts +31 -0
  16. package/dist/builtin/intercom/types.ts +115 -0
  17. package/dist/builtin/mcp/index.bundle.mjs +109 -35
  18. package/dist/builtin/mcp/package.json +1 -1
  19. package/dist/builtin/subagents/package.json +1 -1
  20. package/dist/builtin/subagents/src/extension/index.bundle.mjs +173 -51
  21. package/dist/builtin/web-access/index.bundle.mjs +109 -35
  22. package/dist/builtin/web-access/package.json +1 -1
  23. package/dist/builtin/workflows/CHANGELOG.md +15 -0
  24. package/dist/builtin/workflows/README.md +40 -37
  25. package/dist/builtin/workflows/builtin/adversarial-verification.js +13 -0
  26. package/dist/builtin/workflows/builtin/{shared-prompts.ts → chunk-0x6e303p.js} +133 -154
  27. package/dist/builtin/workflows/builtin/chunk-29wrp38a.js +199 -0
  28. package/dist/builtin/workflows/builtin/chunk-2dqb5s2q.js +602 -0
  29. package/dist/builtin/workflows/builtin/chunk-4febxsv4.js +264 -0
  30. package/dist/builtin/workflows/builtin/chunk-5n10x7j2.js +69730 -0
  31. package/dist/builtin/workflows/builtin/chunk-5wgwscd0.js +4 -0
  32. package/dist/builtin/workflows/builtin/chunk-6fqs7c01.js +155 -0
  33. package/dist/builtin/workflows/builtin/chunk-7430zyas.js +126 -0
  34. package/dist/builtin/workflows/builtin/chunk-7at6dnkr.js +33 -0
  35. package/dist/builtin/workflows/builtin/chunk-82ha8p41.js +175 -0
  36. package/dist/builtin/workflows/builtin/chunk-bfkmzv9h.js +156 -0
  37. package/dist/builtin/workflows/builtin/chunk-c53y8bdh.js +345 -0
  38. package/dist/builtin/workflows/builtin/chunk-cdtd3m3w.js +529 -0
  39. package/dist/builtin/workflows/builtin/chunk-cg9tmks0.js +206 -0
  40. package/dist/builtin/workflows/builtin/chunk-hdpj1dkw.js +2447 -0
  41. package/dist/builtin/workflows/builtin/chunk-hzzn6adg.js +221 -0
  42. package/dist/builtin/workflows/builtin/chunk-nqr34qp3.js +1061 -0
  43. package/dist/builtin/workflows/builtin/chunk-qwzvgxnq.js +404 -0
  44. package/dist/builtin/workflows/builtin/chunk-qx2ptjs3.js +215 -0
  45. package/dist/builtin/workflows/builtin/chunk-v04c5qmd.js +1207 -0
  46. package/dist/builtin/workflows/builtin/chunk-wpckd35c.js +12 -0
  47. package/dist/builtin/workflows/builtin/chunk-x2ghn0je.js +4110 -0
  48. package/dist/builtin/workflows/builtin/chunk-zf28603f.js +1080 -0
  49. package/dist/builtin/workflows/builtin/classify-and-act.js +12 -0
  50. package/dist/builtin/workflows/builtin/fan-out-and-synthesize.js +10 -0
  51. package/dist/builtin/workflows/builtin/generate-and-filter.js +12 -0
  52. package/dist/builtin/workflows/builtin/goal.js +16 -0
  53. package/dist/builtin/workflows/builtin/index.js +49 -0
  54. package/dist/builtin/workflows/builtin/loop-until-done.js +12 -0
  55. package/dist/builtin/workflows/builtin/open-claude-design.js +11 -0
  56. package/dist/builtin/workflows/builtin/ralph.js +16 -0
  57. package/dist/builtin/workflows/builtin/steering-context.d.ts +6 -0
  58. package/dist/builtin/workflows/builtin/steering-context.js +7 -0
  59. package/dist/builtin/workflows/builtin/tournament.js +13 -0
  60. package/dist/builtin/workflows/package.json +10 -7
  61. package/dist/builtin/workflows/src/authoring/typebox-defaults.d.ts +41 -0
  62. package/dist/builtin/workflows/src/extension/index.bundle.mjs +1228 -428
  63. package/dist/builtin/workflows/src/{index.bundle.mjs → index.js} +956 -427
  64. package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +403 -0
  65. package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +327 -0
  66. package/dist/builtin/workflows/src/shared/budget.d.ts +67 -0
  67. package/dist/builtin/workflows/src/shared/workflow-authoring-types.d.ts +60 -0
  68. package/dist/builtin/workflows/src/shared/workflow-heartbeat-contract.d.ts +21 -0
  69. package/dist/core/agent-session-custom-message-commit.d.ts.map +1 -1
  70. package/dist/core/agent-session-custom-message-commit.js +12 -5
  71. package/dist/core/agent-session-custom-message-commit.js.map +1 -1
  72. package/dist/core/agent-session-persistent-custom-messages.d.ts.map +1 -1
  73. package/dist/core/agent-session-persistent-custom-messages.js +2 -0
  74. package/dist/core/agent-session-persistent-custom-messages.js.map +1 -1
  75. package/dist/core/agent-session.d.ts.map +1 -1
  76. package/dist/core/agent-session.js +2 -1
  77. package/dist/core/agent-session.js.map +1 -1
  78. package/dist/core/auth-storage.d.ts +2 -0
  79. package/dist/core/auth-storage.d.ts.map +1 -1
  80. package/dist/core/auth-storage.js +4 -0
  81. package/dist/core/auth-storage.js.map +1 -1
  82. package/dist/core/builtin-install-layout.d.ts +1 -1
  83. package/dist/core/builtin-install-layout.d.ts.map +1 -1
  84. package/dist/core/builtin-install-layout.js +1 -1
  85. package/dist/core/builtin-install-layout.js.map +1 -1
  86. package/dist/core/codex-fast-mode.d.ts +10 -7
  87. package/dist/core/codex-fast-mode.d.ts.map +1 -1
  88. package/dist/core/codex-fast-mode.js +31 -13
  89. package/dist/core/codex-fast-mode.js.map +1 -1
  90. package/dist/core/extensions/context-types.d.ts +35 -0
  91. package/dist/core/extensions/context-types.d.ts.map +1 -1
  92. package/dist/core/extensions/context-types.js.map +1 -1
  93. package/dist/core/extensions/index.d.ts +1 -1
  94. package/dist/core/extensions/index.d.ts.map +1 -1
  95. package/dist/core/extensions/index.js.map +1 -1
  96. package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
  97. package/dist/core/extensions/loader-virtual-modules.js +6 -1
  98. package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
  99. package/dist/core/model-runtime.d.ts +2 -0
  100. package/dist/core/model-runtime.d.ts.map +1 -1
  101. package/dist/core/model-runtime.js +4 -0
  102. package/dist/core/model-runtime.js.map +1 -1
  103. package/dist/core/runtime-credentials.d.ts +1 -0
  104. package/dist/core/runtime-credentials.d.ts.map +1 -1
  105. package/dist/core/runtime-credentials.js +8 -0
  106. package/dist/core/runtime-credentials.js.map +1 -1
  107. package/dist/core/sdk.d.ts.map +1 -1
  108. package/dist/core/sdk.js +5 -5
  109. package/dist/core/sdk.js.map +1 -1
  110. package/dist/core/session-manager-storage.d.ts +2 -0
  111. package/dist/core/session-manager-storage.d.ts.map +1 -1
  112. package/dist/core/session-manager-storage.js +4 -2
  113. package/dist/core/session-manager-storage.js.map +1 -1
  114. package/dist/core/slash-commands.js +1 -1
  115. package/dist/core/slash-commands.js.map +1 -1
  116. package/dist/core/workflow-stage-admission.d.ts +6 -1
  117. package/dist/core/workflow-stage-admission.d.ts.map +1 -1
  118. package/dist/core/workflow-stage-admission.js +8 -9
  119. package/dist/core/workflow-stage-admission.js.map +1 -1
  120. package/dist/index.d.ts +0 -1
  121. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  122. package/dist/modes/interactive/components/footer.js +1 -1
  123. package/dist/modes/interactive/components/footer.js.map +1 -1
  124. package/dist/modes/interactive/interactive-autocomplete.js +1 -1
  125. package/dist/modes/interactive/interactive-autocomplete.js.map +1 -1
  126. package/dist/modes/interactive/interactive-startup.js +1 -1
  127. package/dist/modes/interactive/interactive-startup.js.map +1 -1
  128. package/dist/modes/interactive-engine/isolated-runtime.d.ts +4 -0
  129. package/dist/modes/interactive-engine/isolated-runtime.d.ts.map +1 -1
  130. package/dist/modes/interactive-engine/isolated-runtime.js +69 -10
  131. package/dist/modes/interactive-engine/isolated-runtime.js.map +1 -1
  132. package/dist/modes/rpc/rpc-client-api.d.ts +18 -2
  133. package/dist/modes/rpc/rpc-client-api.d.ts.map +1 -1
  134. package/dist/modes/rpc/rpc-client-api.js +33 -5
  135. package/dist/modes/rpc/rpc-client-api.js.map +1 -1
  136. package/dist/modes/rpc/rpc-command-handler.d.ts.map +1 -1
  137. package/dist/modes/rpc/rpc-command-handler.js +8 -4
  138. package/dist/modes/rpc/rpc-command-handler.js.map +1 -1
  139. package/dist/modes/rpc/rpc-types.d.ts +13 -0
  140. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  141. package/dist/modes/rpc/rpc-types.js.map +1 -1
  142. package/docs/intercom.md +15 -6
  143. package/docs/packages.md +2 -2
  144. package/docs/providers.md +8 -3
  145. package/docs/quickstart.md +2 -2
  146. package/docs/rpc.md +21 -4
  147. package/docs/settings.md +6 -4
  148. package/docs/subagents.md +1 -1
  149. package/docs/usage.md +1 -1
  150. package/docs/workflows.md +108 -239
  151. package/npm-shrinkwrap.json +32 -32
  152. package/package.json +9 -13
  153. package/dist/builtin/workflows/ambient.d.ts +0 -61
  154. package/dist/builtin/workflows/builtin/adversarial-verification-prompts.ts +0 -26
  155. package/dist/builtin/workflows/builtin/adversarial-verification-runner.ts +0 -391
  156. package/dist/builtin/workflows/builtin/adversarial-verification.ts +0 -34
  157. package/dist/builtin/workflows/builtin/classify-and-act-prompts.ts +0 -14
  158. package/dist/builtin/workflows/builtin/classify-and-act-runner.ts +0 -108
  159. package/dist/builtin/workflows/builtin/classify-and-act.ts +0 -40
  160. package/dist/builtin/workflows/builtin/fan-out-and-synthesize-prompts.ts +0 -14
  161. package/dist/builtin/workflows/builtin/fan-out-and-synthesize-runner.ts +0 -96
  162. package/dist/builtin/workflows/builtin/fan-out-and-synthesize.ts +0 -37
  163. package/dist/builtin/workflows/builtin/generate-and-filter-prompts.ts +0 -41
  164. package/dist/builtin/workflows/builtin/generate-and-filter-runner.ts +0 -90
  165. package/dist/builtin/workflows/builtin/generate-and-filter.ts +0 -31
  166. package/dist/builtin/workflows/builtin/goal-artifacts.ts +0 -64
  167. package/dist/builtin/workflows/builtin/goal-convergence.ts +0 -87
  168. package/dist/builtin/workflows/builtin/goal-ledger.ts +0 -139
  169. package/dist/builtin/workflows/builtin/goal-models.ts +0 -72
  170. package/dist/builtin/workflows/builtin/goal-orchestrator-prompts.ts +0 -94
  171. package/dist/builtin/workflows/builtin/goal-prompts.ts +0 -217
  172. package/dist/builtin/workflows/builtin/goal-reducer.ts +0 -176
  173. package/dist/builtin/workflows/builtin/goal-reports.ts +0 -76
  174. package/dist/builtin/workflows/builtin/goal-reverify.ts +0 -305
  175. package/dist/builtin/workflows/builtin/goal-review.ts +0 -139
  176. package/dist/builtin/workflows/builtin/goal-runner.ts +0 -504
  177. package/dist/builtin/workflows/builtin/goal-schemas.ts +0 -89
  178. package/dist/builtin/workflows/builtin/goal-types.ts +0 -164
  179. package/dist/builtin/workflows/builtin/goal.ts +0 -78
  180. package/dist/builtin/workflows/builtin/index.ts +0 -16
  181. package/dist/builtin/workflows/builtin/loop-until-done-prompts.ts +0 -87
  182. package/dist/builtin/workflows/builtin/loop-until-done-runner.ts +0 -282
  183. package/dist/builtin/workflows/builtin/loop-until-done.ts +0 -51
  184. package/dist/builtin/workflows/builtin/open-claude-design-live-protocol.ts +0 -305
  185. package/dist/builtin/workflows/builtin/open-claude-design-phases.ts +0 -337
  186. package/dist/builtin/workflows/builtin/open-claude-design-runner.ts +0 -347
  187. package/dist/builtin/workflows/builtin/open-claude-design-setup.ts +0 -313
  188. package/dist/builtin/workflows/builtin/open-claude-design-utils.ts +0 -311
  189. package/dist/builtin/workflows/builtin/open-claude-design.ts +0 -57
  190. package/dist/builtin/workflows/builtin/pattern-artifact-root.ts +0 -28
  191. package/dist/builtin/workflows/builtin/progress-scoring.ts +0 -230
  192. package/dist/builtin/workflows/builtin/ralph-core.ts +0 -449
  193. package/dist/builtin/workflows/builtin/ralph-forked-prompts.ts +0 -100
  194. package/dist/builtin/workflows/builtin/ralph-models.ts +0 -183
  195. package/dist/builtin/workflows/builtin/ralph-review-gate.ts +0 -103
  196. package/dist/builtin/workflows/builtin/ralph-reviewer-prompt.ts +0 -104
  197. package/dist/builtin/workflows/builtin/ralph-runner.ts +0 -459
  198. package/dist/builtin/workflows/builtin/ralph.ts +0 -82
  199. package/dist/builtin/workflows/builtin/review-convergence.ts +0 -229
  200. package/dist/builtin/workflows/builtin/selection-math.ts +0 -156
  201. package/dist/builtin/workflows/builtin/steering-context.ts +0 -51
  202. package/dist/builtin/workflows/builtin/tournament-prompts.ts +0 -70
  203. package/dist/builtin/workflows/builtin/tournament-runner.ts +0 -400
  204. package/dist/builtin/workflows/builtin/tournament.ts +0 -73
  205. package/dist/builtin/workflows/builtin/verification-criteria.ts +0 -330
  206. package/dist/builtin/workflows/builtin/verification-prompts.ts +0 -206
  207. package/dist/builtin/workflows/builtin/verification-usage.ts +0 -44
@@ -8,6 +8,14 @@ function isAttachment(value: unknown): value is Attachment {
8
8
  return attachment.language === undefined || typeof attachment.language === "string";
9
9
  }
10
10
 
11
+ function isMessageSource(value: unknown): value is NonNullable<Message["source"]> {
12
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
13
+ const source = value as Record<string, unknown>;
14
+ return typeof source.subagentRunId === "string"
15
+ && (source.subagentAgent === undefined || typeof source.subagentAgent === "string")
16
+ && (source.subagentIndex === undefined || typeof source.subagentIndex === "number");
17
+ }
18
+
11
19
  export function isMessage(value: unknown): value is Message {
12
20
  if (typeof value !== "object" || value === null) return false;
13
21
  const message = value as Record<string, unknown>;
@@ -15,6 +23,7 @@ export function isMessage(value: unknown): value is Message {
15
23
  if (message.replyTo !== undefined && typeof message.replyTo !== "string") return false;
16
24
  if (message.expectsReply !== undefined && typeof message.expectsReply !== "boolean") return false;
17
25
  if (message.replyError !== undefined && typeof message.replyError !== "string") return false;
26
+ if (message.source !== undefined && !isMessageSource(message.source)) return false;
18
27
  if (typeof message.content !== "object" || message.content === null) return false;
19
28
  const content = message.content as Record<string, unknown>;
20
29
  if (typeof content.text !== "string") return false;
@@ -24,8 +24,36 @@ export interface SendOptions {
24
24
  export interface SendResult {
25
25
  id: string;
26
26
  delivered: boolean;
27
+ queued?: boolean;
27
28
  reason?: string;
29
+ reasonCode?: "message_id_conflict";
30
+ runId?: string;
31
+ stageKey?: string;
32
+ position?: number;
28
33
  }
34
+
35
+ export interface PendingStageMessageRequest {
36
+ readonly requestId: string;
37
+ readonly from: SessionInfo;
38
+ readonly runId: string;
39
+ readonly stageKey: string;
40
+ readonly message: Message;
41
+ readonly senderRegistrationName?: string;
42
+ readonly senderReturnAddress?: string;
43
+ readonly live?: boolean;
44
+ }
45
+
46
+ export interface PendingStageNotificationRequest {
47
+ readonly requestId: string;
48
+ readonly from: SessionInfo;
49
+ readonly message: Message;
50
+ }
51
+
52
+ export type PendingStageMessageResult =
53
+ | { readonly outcome: "queued"; readonly position: number }
54
+ | { readonly outcome: "delivered" }
55
+ | { readonly outcome: "forward" }
56
+ | { readonly outcome: "refused"; readonly reason: string; readonly reasonCode?: "message_id_conflict" };
29
57
  export interface PresenceUpdates {
30
58
  name?: string;
31
59
  status?: string;
@@ -44,6 +72,7 @@ function toError(error: unknown): Error {
44
72
 
45
73
 
46
74
  export class IntercomClient extends EventEmitter {
75
+ private readonly returnAddress: string;
47
76
  private socket: net.Socket | null = null;
48
77
  private _sessionId: string | null = null;
49
78
  private _supervisorSessionId: string | null = null;
@@ -52,6 +81,7 @@ export class IntercomClient extends EventEmitter {
52
81
  private pendingSends = new PendingSendRegistry();
53
82
  private pendingLists = new Map<string, { resolve: (sessions: SessionInfo[]) => void; reject: (e: Error) => void }>();
54
83
  private pendingPresence = new Map<string, { resolve: (group: string) => void; reject: (error: Error) => void }>();
84
+ private pendingLiveWorkflowStageRoutes = new Map<string, { resolve(): void; reject(error: Error): void }>();
55
85
 
56
86
  private pendingSupervisorAuthorizations = new Map<string, {
57
87
  resolve: (authorization: SupervisorAuthorization) => void;
@@ -60,14 +90,22 @@ export class IntercomClient extends EventEmitter {
60
90
  private disconnecting = false;
61
91
  private disconnectError: Error | null = null;
62
92
 
93
+ constructor(returnAddress: string = randomUUID()) {
94
+ super();
95
+ if (returnAddress.length === 0) throw new Error("Intercom return address must not be empty");
96
+ this.returnAddress = returnAddress;
97
+ }
98
+
63
99
  private failPending(error: Error): void {
64
100
  this.pendingSends.rejectAll(error);
65
101
  for (const pending of this.pendingLists.values()) pending.reject(error);
66
102
  for (const pending of this.pendingSupervisorAuthorizations.values()) pending.reject(error);
67
103
  for (const pending of this.pendingPresence.values()) pending.reject(error);
104
+ for (const pending of this.pendingLiveWorkflowStageRoutes.values()) pending.reject(error);
68
105
  this.pendingLists.clear();
69
106
  this.pendingSupervisorAuthorizations.clear();
70
107
  this.pendingPresence.clear();
108
+ this.pendingLiveWorkflowStageRoutes.clear();
71
109
  }
72
110
 
73
111
 
@@ -220,6 +258,7 @@ export class IntercomClient extends EventEmitter {
220
258
  writeMessage(socket, {
221
259
  type: "register",
222
260
  session,
261
+ returnAddress: this.returnAddress,
223
262
  ...(supervisor ? { supervisor } : {}),
224
263
  ...(supervisorOwnerToken ? { supervisorOwnerToken } : {}),
225
264
  });
@@ -303,6 +342,70 @@ export class IntercomClient extends EventEmitter {
303
342
  }
304
343
  this.emit("message", from, message, channel);
305
344
  break;
345
+ }
346
+ case "pending_stage_message": {
347
+ const { requestId, from, senderRegistrationName, senderReturnAddress, runId, stageKey, message, live } = brokerMessage;
348
+ if (
349
+ typeof requestId !== "string" ||
350
+ !isSessionInfo(from) ||
351
+ (senderRegistrationName !== undefined && typeof senderRegistrationName !== "string") ||
352
+ (senderReturnAddress !== undefined && typeof senderReturnAddress !== "string") ||
353
+ typeof runId !== "string" ||
354
+ typeof stageKey !== "string" ||
355
+ !isMessage(message) ||
356
+ (live !== undefined && typeof live !== "boolean")
357
+ ) {
358
+ throw new Error("Invalid pending-stage message event");
359
+ }
360
+ this.emit("pending_stage_message", {
361
+ requestId,
362
+ from,
363
+ ...(senderRegistrationName === undefined ? {} : { senderRegistrationName }),
364
+ ...(senderReturnAddress === undefined ? {} : { senderReturnAddress }),
365
+ runId,
366
+ stageKey,
367
+ message,
368
+ ...(live === true ? { live: true } : {}),
369
+ } satisfies PendingStageMessageRequest);
370
+ break;
371
+ }
372
+ case "pending_stage_notification": {
373
+ const { requestId, from, message } = brokerMessage;
374
+ if (typeof requestId !== "string" || !isSessionInfo(from) || !isMessage(message)) {
375
+ throw new Error("Invalid pending-stage notification event");
376
+ }
377
+ this.emit("pending_stage_notification", {
378
+ requestId,
379
+ from,
380
+ message,
381
+ } satisfies PendingStageNotificationRequest);
382
+ break;
383
+ }
384
+ case "live_workflow_stage_route_registered": {
385
+ const { requestId } = brokerMessage;
386
+ if (typeof requestId !== "string") throw new Error("Invalid live workflow-stage route registration");
387
+ const pending = this.pendingLiveWorkflowStageRoutes.get(requestId);
388
+ if (!pending) return;
389
+ this.pendingLiveWorkflowStageRoutes.delete(requestId);
390
+ pending.resolve();
391
+ break;
392
+ }
393
+ case "queued": {
394
+ const { messageId, attemptId, runId, stageKey, position } = brokerMessage;
395
+ if (
396
+ typeof messageId !== "string" ||
397
+ (attemptId !== undefined && typeof attemptId !== "string") ||
398
+ typeof runId !== "string" ||
399
+ typeof stageKey !== "string" ||
400
+ typeof position !== "number" ||
401
+ position < 1
402
+ ) {
403
+ throw new Error("Invalid queued message");
404
+ }
405
+ const result = { id: messageId, delivered: false, queued: true, runId, stageKey, position } as const;
406
+ if (attemptId === undefined) this.pendingSends.resolveLegacy(messageId, result);
407
+ else this.pendingSends.resolve(messageId, attemptId, result);
408
+ break;
306
409
  }
307
410
  case "delivered": {
308
411
  const { messageId, attemptId } = brokerMessage;
@@ -315,14 +418,24 @@ export class IntercomClient extends EventEmitter {
315
418
  break;
316
419
  }
317
420
  case "delivery_failed": {
318
- const { messageId, attemptId, reason } = brokerMessage;
319
- if (typeof messageId !== "string" || (attemptId !== undefined && typeof attemptId !== "string") || typeof reason !== "string") {
320
- throw new Error("Invalid delivery_failed message");
321
- }
322
- const result = { id: messageId, delivered: false, reason } as const;
323
- if (attemptId === undefined) this.pendingSends.resolveLegacy(messageId, result);
324
- else this.pendingSends.resolve(messageId, attemptId, result);
325
- break;
421
+ const { messageId, attemptId, reason, reasonCode } = brokerMessage;
422
+ if (
423
+ typeof messageId !== "string" ||
424
+ (attemptId !== undefined && typeof attemptId !== "string") ||
425
+ typeof reason !== "string" ||
426
+ (reasonCode !== undefined && reasonCode !== "message_id_conflict")
427
+ ) {
428
+ throw new Error("Invalid delivery_failed message");
429
+ }
430
+ const result = {
431
+ id: messageId,
432
+ delivered: false,
433
+ reason,
434
+ ...(reasonCode === undefined ? {} : { reasonCode }),
435
+ } as const;
436
+ if (attemptId === undefined) this.pendingSends.resolveLegacy(messageId, result);
437
+ else this.pendingSends.resolve(messageId, attemptId, result);
438
+ break;
326
439
  }
327
440
  case "session_joined": {
328
441
  if (!isSessionInfo(brokerMessage.session)) {
@@ -490,15 +603,92 @@ export class IntercomClient extends EventEmitter {
490
603
  }
491
604
  });
492
605
  }
606
+ registerPendingStageRoute(runId: string, group: string, capability: string): void {
607
+ writeMessage(this.requireActiveSocket(), { type: "register_pending_stage_route", runId, group, capability });
608
+ }
609
+
610
+ registerLiveWorkflowStageRoute(runId: string, stageKeys: readonly string[], capability: string): Promise<void> {
611
+ let socket: net.Socket;
612
+ try {
613
+ socket = this.requireActiveSocket();
614
+ } catch (error) {
615
+ return Promise.reject(toError(error));
616
+ }
617
+ const requestId = randomUUID();
618
+ return new Promise((resolve, reject) => {
619
+ this.pendingLiveWorkflowStageRoutes.set(requestId, { resolve, reject });
620
+ try {
621
+ writeMessage(socket, {
622
+ type: "register_live_workflow_stage_route",
623
+ requestId,
624
+ runId,
625
+ stageKeys: [...stageKeys],
626
+ capability,
627
+ });
628
+ } catch (error) {
629
+ this.pendingLiveWorkflowStageRoutes.delete(requestId);
630
+ reject(toError(error));
631
+ }
632
+ });
633
+ }
634
+
635
+ respondPendingStageMessage(requestId: string, result: PendingStageMessageResult): void {
636
+ const socket = this.requireActiveSocket();
637
+ writeMessage(
638
+ socket,
639
+ result.outcome === "queued"
640
+ ? { type: "pending_stage_message_result", requestId, outcome: "queued", position: result.position }
641
+ : result.outcome === "refused"
642
+ ? {
643
+ type: "pending_stage_message_result",
644
+ requestId,
645
+ outcome: "refused",
646
+ reason: result.reason,
647
+ ...(result.reasonCode === undefined ? {} : { reasonCode: result.reasonCode }),
648
+ }
649
+ : { type: "pending_stage_message_result", requestId, outcome: result.outcome },
650
+ );
651
+ }
652
+
653
+ respondPendingStageNotification(requestId: string, delivered: boolean): void {
654
+ writeMessage(this.requireActiveSocket(), { type: "pending_stage_notification_result", requestId, delivered });
655
+ }
656
+
493
657
  send(to: string, options: SendOptions): Promise<SendResult> {
494
658
  return this.sendFrame("send", to, options);
495
659
  }
496
660
 
661
+ sendPendingStageNotification(
662
+ runId: string,
663
+ capability: string,
664
+ to: string,
665
+ senderRegistrationName: string | undefined,
666
+ options: SendOptions,
667
+ senderReturnAddress?: string,
668
+ ): Promise<SendResult> {
669
+ return this.sendFrame("send_pending_stage_notification", to, options, {
670
+ runId,
671
+ capability,
672
+ ...(senderRegistrationName === undefined ? {} : { senderRegistrationName }),
673
+ ...(senderReturnAddress === undefined ? {} : { senderReturnAddress }),
674
+ });
675
+ }
676
+
497
677
  sendToSupervisor(to: string, options: SendOptions): Promise<SendResult> {
498
678
  return this.sendFrame("supervisor_send", to, options);
499
679
  }
500
680
 
501
- private sendFrame(type: "send" | "supervisor_send", to: string, options: SendOptions): Promise<SendResult> {
681
+ private sendFrame(
682
+ type: "send" | "supervisor_send" | "send_pending_stage_notification",
683
+ to: string,
684
+ options: SendOptions,
685
+ extra: {
686
+ readonly runId: string;
687
+ readonly capability: string;
688
+ readonly senderRegistrationName?: string;
689
+ readonly senderReturnAddress?: string;
690
+ } | undefined = undefined,
691
+ ): Promise<SendResult> {
502
692
  let socket: net.Socket;
503
693
  try {
504
694
  socket = this.requireActiveSocket();
@@ -523,7 +713,7 @@ export class IntercomClient extends EventEmitter {
523
713
  content: { text: options.text, attachments: options.attachments },
524
714
  };
525
715
  try {
526
- writeMessage(socket, { type, to, message, attemptId: acquired.attempt.attemptId });
716
+ writeMessage(socket, { type, to, message, attemptId: acquired.attempt.attemptId, ...extra });
527
717
  } catch (error) {
528
718
  this.pendingSends.reject(acquired.attempt, toError(error));
529
719
  }
@@ -11,6 +11,10 @@ export interface SendResultLike {
11
11
  id: string;
12
12
  delivered: boolean;
13
13
  reason?: string;
14
+ queued?: boolean;
15
+ runId?: string;
16
+ stageKey?: string;
17
+ position?: number;
14
18
  }
15
19
 
16
20
  export interface PendingSendAttempt {
@@ -1,5 +1,6 @@
1
1
  import type net from "node:net";
2
- import type { Attachment, BrokerMessage, Message, SessionInfo } from "../types.js";
2
+ import type { BrokerMessage, Message, SessionInfo } from "../types.js";
3
+ import { isMessage } from "./client-message-validation.js";
3
4
  import { resolveSessionTarget, sessionTargetFailureReason } from "../session-target.js";
4
5
  import { DeliveredMessageCache } from "./delivered-message-cache.js";
5
6
  import { buildMessageSendSignature } from "./send-signature.js";
@@ -11,36 +12,60 @@ import { PendingQuestionIndex } from "./pending-question-index.js";
11
12
  export interface BrokerConnectedSession {
12
13
  socket: net.Socket;
13
14
  info: SessionInfo;
15
+ /**
16
+ * Immutable route authority from the host-admitted registration context.
17
+ * Model-accessible presence changes only `info.group`; they never rewrite this field.
18
+ * The local broker trusts initial registration metadata, matching all existing session fields.
19
+ */
20
+ readonly registrationGroup?: string;
21
+ /** Immutable registration-time name; mutable presence names never rewrite this reconnect alias. */
22
+ readonly registrationName?: string;
23
+ /** Opaque host-owned return identity; immutable after initial registration. */
24
+ readonly registrationReturnAddress?: string;
14
25
  /** Broker-bound supervisor relationship established by a capability. */
15
26
  supervisorId?: string;
16
27
  /** Private issuer identity used to restore child capabilities after reconnects. */
17
28
  supervisorOwnerToken?: string;
18
29
  }
19
30
 
20
- interface SendClientMessage extends Record<string, unknown> {
21
- type: string;
31
+ export interface PendingStageRoute {
32
+ readonly socket: net.Socket;
33
+ readonly from: BrokerConnectedSession;
34
+ readonly runId: string;
35
+ readonly stageKey: string;
36
+ readonly message: Message;
37
+ readonly attemptId?: string;
38
+ readonly liveTargetId?: string;
39
+ readonly signature?: string;
22
40
  }
23
41
 
24
- function isAttachment(value: unknown): value is Attachment {
25
- if (typeof value !== "object" || value === null) return false;
26
- const attachment = value as Record<string, unknown>;
27
- if (attachment.type !== "file" && attachment.type !== "snippet" && attachment.type !== "context") return false;
28
- if (typeof attachment.name !== "string" || typeof attachment.content !== "string") return false;
29
- return attachment.language === undefined || typeof attachment.language === "string";
42
+ export type PendingStageRouter = (route: PendingStageRoute) => boolean;
43
+
44
+ export type LiveWorkflowStageResolver = (target: string) => BrokerConnectedSession | undefined;
45
+
46
+ const WORKFLOW_RUN_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
47
+
48
+ export function parsePendingStageTarget(target: string): { runId: string; stageKey: string } | undefined {
49
+ const separator = target.indexOf(":");
50
+ if (separator < 0) return undefined;
51
+ const runId = target.slice(0, separator);
52
+ const stageKey = target.slice(separator + 1);
53
+ return WORKFLOW_RUN_ID_PATTERN.test(runId) && stageKey.length > 0 ? { runId, stageKey } : undefined;
30
54
  }
55
+ export const PENDING_STAGE_ASK_REFUSAL =
56
+ "Cannot ask a workflow stage whose session has not initialized. Use send; Atomic will queue the message until the stage session initializes.";
57
+
31
58
 
32
- function isMessage(value: unknown): value is Message {
33
- if (typeof value !== "object" || value === null) return false;
34
- const message = value as Record<string, unknown>;
35
- if (typeof message.id !== "string" || typeof message.timestamp !== "number") return false;
36
- if (message.replyTo !== undefined && typeof message.replyTo !== "string") return false;
37
- if (message.expectsReply !== undefined && typeof message.expectsReply !== "boolean") return false;
38
- if (typeof message.content !== "object" || message.content === null) return false;
39
- const content = message.content as Record<string, unknown>;
40
- if (typeof content.text !== "string") return false;
41
- return content.attachments === undefined || (Array.isArray(content.attachments) && content.attachments.every(isAttachment));
59
+ interface SendClientMessage extends Record<string, unknown> {
60
+ type: string;
61
+ }
62
+ function wireMessageId(value: unknown): string {
63
+ if (typeof value !== "object" || value === null) return "unknown";
64
+ const id = (value as Record<string, unknown>).id;
65
+ return typeof id === "string" ? id : "unknown";
42
66
  }
43
67
 
68
+
44
69
  /** Validate and route one wire-level send request. */
45
70
  export function handleBrokerSend(
46
71
  socket: net.Socket,
@@ -51,9 +76,11 @@ export function handleBrokerSend(
51
76
  write: (target: net.Socket, message: BrokerMessage) => void,
52
77
  supervisorCache: SupervisorChannelCache = new SupervisorChannelCache(),
53
78
  pendingQuestions: PendingQuestionIndex = new PendingQuestionIndex(),
79
+ routePendingStage?: PendingStageRouter,
80
+ resolveLiveWorkflowStage?: LiveWorkflowStageResolver,
54
81
  ): void {
55
82
  const message = clientMessage.message;
56
- const messageId = isMessage(message) ? message.id : "unknown";
83
+ const messageId = wireMessageId(message);
57
84
  const hasAttemptId = Object.prototype.hasOwnProperty.call(clientMessage, "attemptId");
58
85
  if (hasAttemptId && typeof clientMessage.attemptId !== "string") {
59
86
  write(socket, {
@@ -74,27 +101,28 @@ export function handleBrokerSend(
74
101
  }
75
102
  const supervisorSend = clientMessage.type === "supervisor_send";
76
103
 
77
- const signature = buildMessageSendSignature(clientMessage.to, message);
78
- const deliveredMatch = deliveredMessages.lookup(message.id, signature);
79
- if (deliveredMatch === "match") {
80
- write(socket, { type: "delivered", messageId: message.id, attemptId });
81
- return;
82
- }
83
- if (deliveredMatch === "conflict") {
84
- write(socket, {
85
- type: "delivery_failed",
86
- messageId: message.id,
87
- attemptId,
88
- reason: `Intercom message ID '${message.id}' was already delivered with a different target or payload`,
89
- });
90
- return;
91
- }
92
104
 
93
105
  const fromSession = currentId ? sessions.get(currentId) : undefined;
94
106
  if (!fromSession) {
95
107
  write(socket, { type: "delivery_failed", messageId: message.id, attemptId, reason: "Sender session not found" });
96
108
  return;
97
109
  }
110
+ const signature = buildMessageSendSignature(clientMessage.to, message, fromSession.info.id);
111
+ const deliveredMatch = deliveredMessages.lookup(message.id, signature);
112
+ if (deliveredMatch === "match") {
113
+ write(socket, { type: "delivered", messageId: message.id, attemptId });
114
+ return;
115
+ }
116
+ if (deliveredMatch === "conflict") {
117
+ write(socket, {
118
+ type: "delivery_failed",
119
+ messageId: message.id,
120
+ attemptId,
121
+ reason: `Intercom message ID '${message.id}' was already delivered with a different target or payload`,
122
+ reasonCode: "message_id_conflict",
123
+ });
124
+ return;
125
+ }
98
126
  const trimmedTo = clientMessage.to.trim();
99
127
  if (supervisorSend && !fromSession.supervisorId) {
100
128
  write(socket, { type: "delivery_failed", messageId: message.id, attemptId, reason: "Supervisor channel is not authorized" });
@@ -109,7 +137,8 @@ export function handleBrokerSend(
109
137
  // Exact-id targeting always resolves against the full pool so a cross-group id
110
138
  // is caught by the defense-in-depth group check below. Only a broker-authorized
111
139
  // supervisor frame or an exact recorded reply may resolve across groups.
112
- const exactIdTarget = sessions.get(trimmedTo);
140
+ const liveWorkflowTarget = sessions.has(trimmedTo) ? undefined : resolveLiveWorkflowStage?.(trimmedTo);
141
+ const exactIdTarget = sessions.get(trimmedTo) ?? liveWorkflowTarget;
113
142
  const senderGroup = normalizeGroup(fromSession.info.group);
114
143
  const reachableAcrossGroups = supervisorSend || Boolean(message.replyTo);
115
144
  const candidates = reachableAcrossGroups
@@ -145,6 +174,22 @@ export function handleBrokerSend(
145
174
  });
146
175
  return;
147
176
  }
177
+ const liveTarget = parsePendingStageTarget(trimmedTo);
178
+ if (
179
+ liveWorkflowTarget !== undefined &&
180
+ liveTarget !== undefined &&
181
+ routePendingStage?.({
182
+ socket,
183
+ from: fromSession,
184
+ ...liveTarget,
185
+ message,
186
+ ...(attemptId ? { attemptId } : {}),
187
+ liveTargetId: target.info.id,
188
+ signature,
189
+ })
190
+ ) {
191
+ return;
192
+ }
148
193
  write(target.socket, supervisorSend
149
194
  ? { type: "message", from: fromSession.info, message, channel: "supervisor" }
150
195
  : { type: "message", from: fromSession.info, message });
@@ -159,6 +204,15 @@ export function handleBrokerSend(
159
204
  write(socket, { type: "delivered", messageId: message.id, attemptId });
160
205
  return;
161
206
  }
207
+ if (resolution.kind === "not_found" && !supervisorSend && routePendingStage !== undefined) {
208
+ const pendingTarget = parsePendingStageTarget(trimmedTo);
209
+ if (
210
+ pendingTarget !== undefined &&
211
+ routePendingStage({ socket, from: fromSession, ...pendingTarget, message, ...(attemptId ? { attemptId } : {}) })
212
+ ) {
213
+ return;
214
+ }
215
+ }
162
216
  write(socket, {
163
217
  type: "delivery_failed",
164
218
  messageId: message.id,
@@ -29,12 +29,24 @@ export function buildSendSignature(to: string, options: LogicalSendOptions): str
29
29
  });
30
30
  }
31
31
 
32
- export function buildMessageSendSignature(to: string, message: Message): string {
33
- return buildSendSignature(to, {
34
- text: message.content.text,
35
- attachments: message.content.attachments,
36
- replyTo: message.replyTo,
37
- expectsReply: message.expectsReply,
38
- replyError: message.replyError,
39
- });
32
+ export function buildMessageSendSignature(to: string, message: Message, senderId?: string): string {
33
+ const logicalSignature = buildSendSignature(to, {
34
+ text: message.content.text,
35
+ attachments: message.content.attachments,
36
+ replyTo: message.replyTo,
37
+ expectsReply: message.expectsReply,
38
+ replyError: message.replyError,
39
+ });
40
+ if (senderId === undefined && message.source === undefined) return logicalSignature;
41
+ return JSON.stringify({
42
+ senderId: senderId ?? null,
43
+ logicalSignature,
44
+ source: message.source === undefined
45
+ ? null
46
+ : {
47
+ subagentRunId: message.source.subagentRunId,
48
+ subagentAgent: message.source.subagentAgent ?? null,
49
+ subagentIndex: message.source.subagentIndex ?? null,
50
+ },
51
+ });
40
52
  }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Group primitives shared by the detached broker and the host extension.
3
+ * Keep this module free of host-package imports: the broker loads it in a
4
+ * detached process with only Node built-ins and Intercom-local modules.
5
+ */
6
+
7
+ /**
8
+ * Mirrors `getEnvValue("ATOMIC_INTERCOM_GROUP")` locally so the detached broker subprocess
9
+ * needs no host package. `getEnvValue` returns the first value that is `!== undefined` across
10
+ * `[ATOMIC_INTERCOM_GROUP, PI_INTERCOM_GROUP]`, so a defined-but-empty ATOMIC value
11
+ * deliberately shadows the legacy name and yields `""`; `resolveHomeGroup` then falls through
12
+ * on the empty string. `??` reproduces that exactly — do not "improve" it to `||`.
13
+ */
14
+ function intercomGroupFromEnv(): string | undefined {
15
+ return process.env.ATOMIC_INTERCOM_GROUP ?? process.env.PI_INTERCOM_GROUP;
16
+ }
17
+
18
+ /** The implicit group every ungrouped session belongs to. */
19
+ export const DEFAULT_GROUP = "default";
20
+
21
+ const RESERVED_RUNTIME_GROUPS = new Set(["true", "auto"]);
22
+
23
+ /**
24
+ * Normalize an intercom group id. Undefined, empty, or whitespace-only values
25
+ * collapse to the shared {@link DEFAULT_GROUP} so ungrouped sessions all compare
26
+ * equal and can still talk to each other (backward compatible).
27
+ */
28
+ export function normalizeGroup(value?: string | null): string {
29
+ if (typeof value !== "string") return DEFAULT_GROUP;
30
+ const trimmed = value.trim();
31
+ return trimmed.length > 0 ? trimmed : DEFAULT_GROUP;
32
+ }
33
+
34
+ /**
35
+ * Validate a group requested by the runtime join action. `default` is a real,
36
+ * explicit group name; `true` and `auto` remain reserved for subagent auto-grouping.
37
+ */
38
+ export function validateRuntimeGroup(value: unknown): string {
39
+ if (typeof value !== "string" || value.trim().length === 0) {
40
+ throw new Error("A non-empty intercom group name is required");
41
+ }
42
+ const group = normalizeGroup(value);
43
+ if (RESERVED_RUNTIME_GROUPS.has(group.toLowerCase())) {
44
+ throw new Error(`Intercom group name "${group}" is reserved; choose another name`);
45
+ }
46
+ return group;
47
+ }
48
+
49
+ interface HomeGroupContext {
50
+ orchestrationContext?: { intercomGroup?: string } | undefined;
51
+ subagentPolicy?: { intercomGroup?: string } | undefined;
52
+ /** Accepted from the live extension context but intentionally ignored for home resolution. */
53
+ sessionManager?: { getSessionId(): string } | undefined;
54
+ }
55
+
56
+ /**
57
+ * Resolve a session's static home intercom group with precedence (most specific first):
58
+ * admitted subagentPolicy.intercomGroup > orchestrationContext.intercomGroup >
59
+ * env ATOMIC_INTERCOM_GROUP (also PI_INTERCOM_GROUP legacy) > config.json "group" > "default".
60
+ * Always returns a concrete normalized string.
61
+ *
62
+ * Runtime join entries are deliberately not read here. They are child-inheritance state, not
63
+ * home state, so a reconnect cannot turn the joined group into the destination of `leave`.
64
+ */
65
+ export function resolveHomeGroup(
66
+ config: { group?: string } | undefined,
67
+ ctx?: HomeGroupContext | null,
68
+ ): string {
69
+ const fromPolicy = ctx?.subagentPolicy?.intercomGroup;
70
+ if (typeof fromPolicy === "string" && fromPolicy.trim().length > 0) {
71
+ return normalizeGroup(fromPolicy);
72
+ }
73
+ const fromContext = ctx?.orchestrationContext?.intercomGroup;
74
+ if (typeof fromContext === "string" && fromContext.trim().length > 0) {
75
+ return normalizeGroup(fromContext);
76
+ }
77
+ const fromEnv = intercomGroupFromEnv();
78
+ if (typeof fromEnv === "string" && fromEnv.trim().length > 0) {
79
+ return normalizeGroup(fromEnv);
80
+ }
81
+ if (config && typeof config.group === "string" && config.group.trim().length > 0) {
82
+ return normalizeGroup(config.group);
83
+ }
84
+ return DEFAULT_GROUP;
85
+ }