@intx/workflow-host 0.2.2 → 0.4.0

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 (101) hide show
  1. package/README.md +77 -14
  2. package/dist/adapters/mail-part-store.d.ts +46 -0
  3. package/dist/adapters/mail-part-store.js +251 -0
  4. package/dist/adapters/repo-store.d.ts +22 -1
  5. package/dist/adapters/repo-store.js +56 -65
  6. package/dist/adapters/spawn-child.d.ts +109 -44
  7. package/dist/adapters/spawn-child.js +77 -81
  8. package/dist/adapters/step-invoker.d.ts +52 -2
  9. package/dist/adapters/step-invoker.js +284 -37
  10. package/dist/adapters/substrate-mailbox-store.d.ts +80 -0
  11. package/dist/adapters/substrate-mailbox-store.js +404 -0
  12. package/dist/child/child-mailbox-reader.d.ts +10 -0
  13. package/dist/child/child-mailbox-reader.js +23 -0
  14. package/dist/child/credential-cell.d.ts +8 -0
  15. package/dist/child/credential-cell.js +66 -0
  16. package/dist/child/env-bootstrap.d.ts +20 -6
  17. package/dist/child/env-bootstrap.js +9 -1
  18. package/dist/child/from-process-env.d.ts +12 -0
  19. package/dist/child/from-process-env.js +6 -0
  20. package/dist/child/index.d.ts +6 -2
  21. package/dist/child/index.js +4 -1
  22. package/dist/child/mailbox-mutation-bridge.d.ts +61 -0
  23. package/dist/child/mailbox-mutation-bridge.js +101 -0
  24. package/dist/child/mailbox-watch-registry.d.ts +17 -0
  25. package/dist/child/mailbox-watch-registry.js +61 -0
  26. package/dist/child/outbound-mail-bridge.d.ts +3 -2
  27. package/dist/child/outbound-mail-bridge.js +20 -32
  28. package/dist/child/parked-correlations.d.ts +42 -0
  29. package/dist/child/parked-correlations.js +80 -0
  30. package/dist/child/pending-request.d.ts +89 -0
  31. package/dist/child/pending-request.js +80 -0
  32. package/dist/child/proxy-repo-store.d.ts +3 -2
  33. package/dist/child/proxy-repo-store.js +2 -0
  34. package/dist/child/run-child.d.ts +170 -14
  35. package/dist/child/run-child.js +569 -155
  36. package/dist/child/self-discovery.d.ts +10 -0
  37. package/dist/child/self-discovery.js +25 -1
  38. package/dist/child/substrate-write-bridge.d.ts +3 -2
  39. package/dist/child/substrate-write-bridge.js +21 -38
  40. package/dist/child/supervisor-backed-transport.d.ts +52 -6
  41. package/dist/child/supervisor-backed-transport.js +205 -62
  42. package/dist/child/verified-definition-loader.d.ts +33 -0
  43. package/dist/child/verified-definition-loader.js +43 -0
  44. package/dist/child/warm-agent-cache.d.ts +44 -4
  45. package/dist/child/warm-agent-cache.js +41 -10
  46. package/dist/index.d.ts +6 -4
  47. package/dist/index.js +6 -4
  48. package/dist/ipc/control-channel.d.ts +151 -2
  49. package/dist/ipc/control-channel.js +222 -29
  50. package/dist/ipc/event-channel.d.ts +32 -1
  51. package/dist/ipc/index.d.ts +1 -1
  52. package/dist/ipc/index.js +1 -1
  53. package/dist/mail-bus/hub-transport-adapter.d.ts +12 -7
  54. package/dist/mail-bus/hub-transport-adapter.js +9 -5
  55. package/dist/run-body-then-cleanup.d.ts +17 -0
  56. package/dist/run-body-then-cleanup.js +38 -0
  57. package/dist/seams/scheduler.d.ts +16 -6
  58. package/dist/seams/scheduler.js +87 -97
  59. package/dist/supervisor/cancel-signing.d.ts +2 -2
  60. package/dist/supervisor/cancel-signing.js +4 -8
  61. package/dist/supervisor/credentials.d.ts +28 -15
  62. package/dist/supervisor/credentials.js +7 -7
  63. package/dist/supervisor/dispatch-attribution.js +1 -1
  64. package/dist/supervisor/drain-timeout.d.ts +2 -2
  65. package/dist/supervisor/drain-timeout.js +1 -1
  66. package/dist/supervisor/index.d.ts +3 -3
  67. package/dist/supervisor/index.js +2 -2
  68. package/dist/supervisor/recycle.d.ts +10 -3
  69. package/dist/supervisor/recycle.js +18 -7
  70. package/dist/supervisor/run-event-compaction.d.ts +5 -5
  71. package/dist/supervisor/run-event-compaction.js +14 -19
  72. package/dist/supervisor/run-event-recovery.d.ts +34 -0
  73. package/dist/supervisor/run-event-recovery.js +45 -0
  74. package/dist/supervisor/spawn-env.d.ts +2 -2
  75. package/dist/supervisor/spawn-env.js +1 -1
  76. package/dist/supervisor/supervisor.d.ts +106 -26
  77. package/dist/supervisor/supervisor.js +1903 -414
  78. package/dist/supervisor/terminal-commit.d.ts +36 -0
  79. package/dist/supervisor/terminal-commit.js +130 -0
  80. package/dist/supervisor/types.d.ts +180 -23
  81. package/dist/testing/change-notifier.d.ts +12 -0
  82. package/dist/testing/change-notifier.js +63 -0
  83. package/dist/testing/index.d.ts +8 -0
  84. package/dist/testing/index.js +16 -0
  85. package/dist/testing/log-capture.d.ts +52 -0
  86. package/dist/testing/log-capture.js +124 -0
  87. package/dist/testing/mail-bus.d.ts +22 -0
  88. package/dist/testing/mail-bus.js +78 -0
  89. package/dist/testing/memory-streams.d.ts +43 -0
  90. package/dist/testing/memory-streams.js +211 -0
  91. package/dist/testing/spawn-observer.d.ts +12 -0
  92. package/dist/testing/spawn-observer.js +36 -0
  93. package/dist/testing/stub-repo-store.d.ts +10 -0
  94. package/dist/testing/stub-repo-store.js +39 -0
  95. package/dist/testing/supervisor-reaper.d.ts +24 -0
  96. package/dist/testing/supervisor-reaper.js +49 -0
  97. package/dist/testing/upstream-frames.d.ts +47 -0
  98. package/dist/testing/upstream-frames.js +94 -0
  99. package/dist/workflow-definition-loader.d.ts +187 -0
  100. package/dist/workflow-definition-loader.js +422 -0
  101. package/package.json +18 -11
@@ -35,7 +35,8 @@
35
35
  // prevented at the type level by the disjoint discriminated union.
36
36
  import { type } from "arktype";
37
37
  import { hexDecode, hexEncode } from "@intx/types";
38
- import { InferenceSource, InterchangeType } from "@intx/types/runtime";
38
+ import { BoundedApprovalSnapshot, ControlParkKind, InferenceSource, InterchangeType, } from "@intx/types/runtime";
39
+ import { CredentialDelivery } from "@intx/types/sidecar";
39
40
  import { decodeEnvelope, encodeEnvelope, FrameEnvelope, SignedEnvelope, } from "./envelope.js";
40
41
  import { signEd25519, verifyEd25519 } from "./crypto.js";
41
42
  /**
@@ -126,10 +127,43 @@ export const OutboundMessagePayload = type({
126
127
  "summary?": "string",
127
128
  "attachments?": OutboundAttachmentPayload.array(),
128
129
  "inReplyTo?": "string",
130
+ "references?": "string[]",
129
131
  "correlationId?": "string",
130
132
  "sessionId?": "string",
131
133
  "tenantId?": "string",
132
134
  });
135
+ /**
136
+ * Wire shape of the parsed `MessageHeaders` the supervisor rides inline on a
137
+ * `mailbox.notify` frame. Mirrors `@intx/types/runtime`'s `MessageHeaders`
138
+ * field-for-field so a child watcher gets the arrived message's envelope for
139
+ * its `exists` `MailboxEvent` without a substrate round-trip. The four
140
+ * unconditionally-dereferenced fields (`from`, `to`, `date`, `messageId`) are
141
+ * required; the rest are optional, matching the runtime type.
142
+ *
143
+ * Duplicated here as an arktype validator (rather than importing the TypeScript
144
+ * `MessageHeaders` type) so the IPC module validates the header block at the
145
+ * wire boundary, exactly as `OutboundMessagePayload` does for outbound mail.
146
+ */
147
+ export const MailboxNotifyHeaders = type({
148
+ from: "string",
149
+ to: "string[]",
150
+ "cc?": "string[]",
151
+ date: "string",
152
+ messageId: "string",
153
+ "inReplyTo?": "string",
154
+ "references?": "string[]",
155
+ "subject?": "string",
156
+ "listId?": "string",
157
+ "interchangeType?": InterchangeType,
158
+ "interchangeCorrelationId?": "string",
159
+ "interchangeTenantId?": "string",
160
+ "interchangeAgentId?": "string",
161
+ "interchangeSessionId?": "string",
162
+ "interchangeOfferingId?": "string",
163
+ "interchangeSchemaVersion?": "string",
164
+ "traceparent?": "string",
165
+ "tracestate?": "string",
166
+ });
133
167
  /**
134
168
  * Discriminated union of every control-channel payload kind. The
135
169
  * `type` discriminator namespaces the control-plane vocabulary so a
@@ -137,35 +171,47 @@ export const OutboundMessagePayload = type({
137
171
  * union and not by widening the envelope shape. Inference events
138
172
  * NEVER appear here; they ride the event channel.
139
173
  */
140
- export const ControlPayload = type({
174
+ export const ControlPayload = type.or({
141
175
  type: "'trigger.fire'",
142
176
  data: {
143
177
  runId: "string",
144
178
  messageId: "string",
145
179
  receivedAt: "number",
180
+ // The run's inbound-mail input, resolved by the supervisor (the sole
181
+ // mail owner) before the frame: a decoded `Mail` (headers plus part
182
+ // descriptors that reference the part bytes committed to the workflow-run
183
+ // substrate). The child hands this straight to the runtime as the trigger
184
+ // payload. Refs, not raw mail bytes, ride here; the committed part files
185
+ // stay in the substrate. Typed `unknown` -- `Mail` is a nested structural
186
+ // type validated at the consumption boundary by `isMail`.
187
+ payload: "unknown",
146
188
  },
147
- }, "|", {
189
+ }, {
148
190
  type: "'signal.deliver'",
149
191
  data: {
150
192
  runId: "string",
151
193
  signalName: "string",
152
194
  signalId: "string",
195
+ // The resume decision in FINAL form -- the child commits it as the
196
+ // SignalReceived payload verbatim. Each sender owns any
197
+ // provenance-specific preparation BEFORE this frame: the dispatch loop
198
+ // resolves an inbound mail to a decoded `Mail` (headers plus part
199
+ // references, like the turn-1 trigger), while `deliverSignal` ships a
200
+ // structured signal payload unchanged -- so this field stays
201
+ // polymorphic. Do NOT ship raw inbound mail bytes through here.
153
202
  payload: "unknown",
154
203
  },
155
- })
156
- .or({
204
+ }, {
157
205
  type: "'drain'",
158
206
  data: {
159
207
  deadlineMs: "number",
160
208
  },
161
- })
162
- .or({
209
+ }, {
163
210
  type: "'shutdown'",
164
211
  data: {
165
212
  reason: "string",
166
213
  },
167
- })
168
- .or({
214
+ }, {
169
215
  type: "'grants-updated'",
170
216
  data: {
171
217
  /**
@@ -187,12 +233,28 @@ export const ControlPayload = type({
187
233
  */
188
234
  "stepHashes?": "Record<string, string>",
189
235
  },
190
- })
191
- .or({
236
+ }, {
192
237
  type: "'sources-updated'",
193
238
  data: SourcesUpdatedData,
194
- })
195
- .or({
239
+ }, {
240
+ // Refreshed credential material for the deployment's inference sources and
241
+ // tools. The child MERGES this into its in-memory cell (see
242
+ // `mergeCredentialDelivery`): `delivery.materials` upsert by credentialId
243
+ // and `delivery.bindings` upsert by (consumer, handle); `revoke` names
244
+ // credentialIds to drop, and dropping one drops every binding referencing
245
+ // it. Merge rather than wholesale-replace because the cell has several
246
+ // independently-scoped producers (the deploy frame, an inference rotation,
247
+ // a tool-grant push), each carrying only its own slice -- a swap would let
248
+ // one evict another's credentials. Revocation is therefore explicit, never
249
+ // by omission. Carried inline like the grants and sources snapshots. The
250
+ // secret rides this frame and the in-memory cell only; it is never
251
+ // persisted.
252
+ type: "'credentials-updated'",
253
+ data: {
254
+ delivery: CredentialDelivery,
255
+ "revoke?": "string[]",
256
+ },
257
+ }, {
196
258
  type: "'ready'",
197
259
  data: {
198
260
  childPid: "number",
@@ -204,8 +266,7 @@ export const ControlPayload = type({
204
266
  */
205
267
  childPublicKey: "string",
206
268
  },
207
- })
208
- .or({
269
+ }, {
209
270
  // Child-initiated request to recycle the workflow-process. The
210
271
  // child emits this when its own self-check decides it needs to be
211
272
  // recycled (an internal consistency error it can't recover from,
@@ -219,8 +280,7 @@ export const ControlPayload = type({
219
280
  data: {
220
281
  reason: "string",
221
282
  },
222
- })
223
- .or({
283
+ }, {
224
284
  // Child-initiated `writeTreePreservingPrefix` request. The child
225
285
  // does not hold a substrate write authority for the workflow-run
226
286
  // repo (single-writer at the ref tip belongs to the supervisor);
@@ -243,8 +303,7 @@ export const ControlPayload = type({
243
303
  preservePrefix: "string > 0",
244
304
  message: "string > 0",
245
305
  },
246
- })
247
- .or({
306
+ }, {
248
307
  // Supervisor-initiated request for the child's merge bytes. Fired
249
308
  // from inside the supervisor's `writeTreePreservingPrefix` merge
250
309
  // callback while the per-repo lock is held; the child receives the
@@ -261,8 +320,7 @@ export const ControlPayload = type({
261
320
  contentBase64: "string",
262
321
  }).array(),
263
322
  },
264
- })
265
- .or({
323
+ }, {
266
324
  // Child's merge result. `requestId` correlates with the
267
325
  // `substrate.write.request` that started the write; the supervisor
268
326
  // resumes its merge callback with the supplied entries (or
@@ -281,8 +339,7 @@ export const ControlPayload = type({
281
339
  reason: "string > 0",
282
340
  }),
283
341
  },
284
- })
285
- .or({
342
+ }, {
286
343
  // Supervisor's terminal reply to a child's `substrate.write.request`.
287
344
  // The `requestId` echoes the child's allocated correlation id so
288
345
  // the child's pending-id map resolves the awaiter. A successful
@@ -302,8 +359,7 @@ export const ControlPayload = type({
302
359
  reason: "string > 0",
303
360
  }),
304
361
  },
305
- })
306
- .or({
362
+ }, {
307
363
  // Child-initiated outbound-mail request (OUTBOUND half of mailbox
308
364
  // ownership, §3a). The workflow-process child never holds the
309
365
  // agent's signing key and never calls `transport.send` itself. When
@@ -329,8 +385,7 @@ export const ControlPayload = type({
329
385
  "mailbox?": "string",
330
386
  message: OutboundMessagePayload,
331
387
  },
332
- })
333
- .or({
388
+ }, {
334
389
  // Supervisor's terminal reply to a child's `outbound.message`. The
335
390
  // `requestId` echoes the child's correlation id so the child's
336
391
  // pending mail-tool awaiter resolves. A successful send surfaces the
@@ -350,8 +405,7 @@ export const ControlPayload = type({
350
405
  reason: "string > 0",
351
406
  }),
352
407
  },
353
- })
354
- .or({
408
+ }, {
355
409
  // Child-initiated terminal-run notification. The workflow-process
356
410
  // child emits this when one of its runs reaches a terminal phase
357
411
  // (`RunCompleted`, `RunFailed`, `RunCancelled`) so the supervisor's
@@ -377,6 +431,145 @@ export const ControlPayload = type({
377
431
  message: "string",
378
432
  },
379
433
  },
434
+ }, {
435
+ // Child-initiated control-plane suspension notification. The
436
+ // workflow-process child emits this when a workflow agent step parks
437
+ // on a reserved `signalName(correlationId)` channel (`env.onPark`),
438
+ // so the supervisor can register the correlation out-of-band before
439
+ // the parked run can be resumed. The supervisor stamps the
440
+ // deployment identity it owns (`runId` + `agentAddress`) and
441
+ // forwards a `signal.correlation.register` frame to the hub, which
442
+ // co-writes the run's routing + approval rows. Mirrors
443
+ // `terminal.event`: a peer-channel notification the supervisor fans
444
+ // out, distinct from the substrate commit the run also produces.
445
+ //
446
+ // `signalName` is deliberately NOT carried: it is a pure function of
447
+ // `correlationId` (`signalName(correlationId)`), recomputed by every
448
+ // consumer that needs it, so the two cannot drift.
449
+ type: "'park.notify'",
450
+ data: {
451
+ runId: "string > 0",
452
+ correlationId: "string > 0",
453
+ parkKind: ControlParkKind,
454
+ // Approver-facing snapshot of the parked tool call, size-capped at this
455
+ // process boundary. Optional: only an ask-rail suspension carries one.
456
+ "snapshot?": BoundedApprovalSnapshot,
457
+ },
458
+ }, {
459
+ // Supervisor-initiated request: enumerate the child's currently-parked
460
+ // approval correlations. The supervisor fires this after a
461
+ // re-establishment (child respawn, or hub-link reconnect fanned out to
462
+ // this deployment) so it can re-register at the hub every correlation
463
+ // whose original `park.notify`-driven register may have been lost while
464
+ // the hub was down. Modeled on `substrate.merge.request`: a
465
+ // supervisor->child request the child answers on `parked-correlations.
466
+ // response`, correlated by `requestId`. Carries no filter -- one child
467
+ // owns one deployment, so the child enumerates everything parked and the
468
+ // supervisor re-emits all; the hub co-write is idempotent.
469
+ type: "'parked-correlations.request'",
470
+ data: {
471
+ requestId: "string > 0",
472
+ },
473
+ }, {
474
+ // Child's reply to `parked-correlations.request`. Each entry mirrors
475
+ // `park.notify`'s data -- the child-supplied half of a
476
+ // `SuspensionRegistration` the supervisor stamps its deployment identity
477
+ // onto -- so the supervisor's re-emit path shares one transform with the
478
+ // `park.notify` arm. Only reduced-state approval parks appear here: the
479
+ // child enumerates steps whose reduced phase is `awaiting-signal` on a
480
+ // control-plane `signalName(correlationId)` channel, each of which carries
481
+ // a durable snapshot by construction (a snapshot-less correlated suspend
482
+ // reduces to `failed`, not `awaiting-signal`). `snapshot` is therefore
483
+ // required, and size-capped at this process boundary like `park.notify`.
484
+ type: "'parked-correlations.response'",
485
+ data: {
486
+ requestId: "string > 0",
487
+ parked: type({
488
+ runId: "string > 0",
489
+ correlationId: "string > 0",
490
+ parkKind: ControlParkKind,
491
+ // Snapshot is required for approval parks and absent for input parks.
492
+ "snapshot?": BoundedApprovalSnapshot,
493
+ }).array(),
494
+ },
495
+ }, {
496
+ // Child reports self-discovered runs after reconnect or recycle.
497
+ // The supervisor seeds its cohort tracking from these runIds so
498
+ // drain accumulators and dispatch routing account for runs the
499
+ // supervisor did not personally trigger.fire.
500
+ type: "'resumed.runs'",
501
+ data: {
502
+ runIds: type("string > 0").array(),
503
+ },
504
+ }, {
505
+ // Supervisor-to-child one-way notification that new mail landed in a
506
+ // deployment mailbox (INBOUND half of mailbox ownership, §3b). One-way
507
+ // like `grants-updated`/`sources-updated`: no correlation id, no response.
508
+ // The supervisor -- the sole mail owner -- commits the arrived message to
509
+ // the workflow-run substrate mailbox, then fires this frame so the child's
510
+ // warm-agent `watch`/`mail_wait` observes the arrival decoupled from the
511
+ // FIFO trigger dispatch that resolves a run's first input. `headers` rides
512
+ // inline so a watcher gets the `exists` `MailboxEvent`'s envelope without a
513
+ // substrate round-trip. The child reads the latest committed mailbox state
514
+ // regardless, so the frame carries no commit pin.
515
+ type: "'mailbox.notify'",
516
+ data: {
517
+ runId: "string > 0",
518
+ mailbox: "string > 0",
519
+ uid: "number >= 1",
520
+ headers: MailboxNotifyHeaders,
521
+ },
522
+ }, {
523
+ // Child-initiated mailbox-mutation request (INBOUND half of mailbox
524
+ // ownership, §3b). The supervisor is the sole writer to the
525
+ // workflow-run mailbox: a step agent reads its INBOX locally but
526
+ // every mutation -- flag writes and `expunge` -- routes up here so
527
+ // the supervisor applies it to its owned store. A child flushing the
528
+ // same ref would race the supervisor's in-memory mirror and break
529
+ // uid / modseq monotonicity.
530
+ //
531
+ // `data` is discriminated on `op`: an `addFlags` / `removeFlags`
532
+ // carries the target `uid` and the `flags` to change, so the wire
533
+ // boundary rejects a flag frame that omits them; an `expunge` sweeps
534
+ // every `\Deleted` message in the mailbox and the child constructs it
535
+ // with neither. `requestId` correlates the supervisor's
536
+ // `mailbox.mutate.response` reply.
537
+ type: "'mailbox.mutate.request'",
538
+ data: type({
539
+ requestId: "string > 0",
540
+ runId: "string > 0",
541
+ mailbox: "string > 0",
542
+ op: "'addFlags' | 'removeFlags'",
543
+ uid: "number >= 1",
544
+ flags: "string[]",
545
+ }, "|", {
546
+ requestId: "string > 0",
547
+ runId: "string > 0",
548
+ mailbox: "string > 0",
549
+ op: "'expunge'",
550
+ }),
551
+ }, {
552
+ // Supervisor's terminal reply to a child's `mailbox.mutate.request`.
553
+ // The `requestId` echoes the child's correlation id so the child's
554
+ // pending mail-tool awaiter resolves. The reply is sent only after
555
+ // the supervisor flushes the mutation, so the child's next committed
556
+ // read observes it -- the same flush-before-signal ordering
557
+ // `mailbox.notify` relies on. A successful `expunge` carries the
558
+ // `expungedUids` it swept so the agent tool can report the count; a
559
+ // flag write carries no operand echo. A failed mutation (unknown
560
+ // uid, substrate fault) surfaces a structured `{ ok: false, reason }`
561
+ // the child's bridge rethrows so the mail-tool call fails loudly.
562
+ type: "'mailbox.mutate.response'",
563
+ data: {
564
+ requestId: "string > 0",
565
+ result: type({
566
+ ok: "true",
567
+ "expungedUids?": "number[]",
568
+ }, "|", {
569
+ ok: "false",
570
+ reason: "string > 0",
571
+ }),
572
+ },
380
573
  });
381
574
  /**
382
575
  * Construct the supervisor-side control-channel sender. The
@@ -29,7 +29,7 @@ export declare const EventPayload: import("arktype/internal/variants/object.ts")
29
29
  index?: number;
30
30
  };
31
31
  } | {
32
- type: "inference.thinking.signature";
32
+ type: "inference.block.signature";
33
33
  seq: number;
34
34
  data: {
35
35
  signature: string;
@@ -155,6 +155,7 @@ export declare const EventPayload: import("arktype/internal/variants/object.ts")
155
155
  content: ({
156
156
  type: "text";
157
157
  text: string;
158
+ signature?: string;
158
159
  } | {
159
160
  type: "image";
160
161
  source: {
@@ -170,6 +171,7 @@ export declare const EventPayload: import("arktype/internal/variants/object.ts")
170
171
  mimeType: string;
171
172
  url: string;
172
173
  };
174
+ signature?: string;
173
175
  } | {
174
176
  type: "audio";
175
177
  source: {
@@ -215,6 +217,8 @@ export declare const EventPayload: import("arktype/internal/variants/object.ts")
215
217
  mimeType: string;
216
218
  url: string;
217
219
  };
220
+ title?: string;
221
+ context?: string;
218
222
  } | {
219
223
  type: "thinking";
220
224
  thinking: string;
@@ -230,6 +234,7 @@ export declare const EventPayload: import("arktype/internal/variants/object.ts")
230
234
  id: string;
231
235
  name: string;
232
236
  arguments: Record<string, unknown>;
237
+ signature?: string;
233
238
  } | {
234
239
  type: "citation";
235
240
  citedText: string;
@@ -249,11 +254,15 @@ export declare const EventPayload: import("arktype/internal/variants/object.ts")
249
254
  start: number;
250
255
  end: number;
251
256
  };
257
+ } | {
258
+ type: "safety_rating";
259
+ blockReason: string;
252
260
  } | {
253
261
  type: "code_execution_request";
254
262
  id: string;
255
263
  code: string;
256
264
  language?: string;
265
+ signature?: string;
257
266
  } | {
258
267
  type: "code_execution_result";
259
268
  requestId: string;
@@ -269,6 +278,7 @@ export declare const EventPayload: import("arktype/internal/variants/object.ts")
269
278
  content: ({
270
279
  type: "text";
271
280
  text: string;
281
+ signature?: string;
272
282
  } | {
273
283
  type: "image";
274
284
  source: {
@@ -284,6 +294,7 @@ export declare const EventPayload: import("arktype/internal/variants/object.ts")
284
294
  mimeType: string;
285
295
  url: string;
286
296
  };
297
+ signature?: string;
287
298
  } | {
288
299
  type: "audio";
289
300
  source: {
@@ -329,6 +340,8 @@ export declare const EventPayload: import("arktype/internal/variants/object.ts")
329
340
  mimeType: string;
330
341
  url: string;
331
342
  };
343
+ title?: string;
344
+ context?: string;
332
345
  })[];
333
346
  detail?: unknown;
334
347
  isError?: boolean;
@@ -411,6 +424,15 @@ export declare const EventPayload: import("arktype/internal/variants/object.ts")
411
424
  };
412
425
  index?: number;
413
426
  };
427
+ } | {
428
+ type: "inference.safety_rating";
429
+ seq: number;
430
+ data: {
431
+ safetyRating: {
432
+ type: "safety_rating";
433
+ blockReason: string;
434
+ };
435
+ };
414
436
  } | {
415
437
  type: "inference.code_execution.start";
416
438
  seq: number;
@@ -420,6 +442,7 @@ export declare const EventPayload: import("arktype/internal/variants/object.ts")
420
442
  id: string;
421
443
  code: string;
422
444
  language?: string;
445
+ signature?: string;
423
446
  };
424
447
  index?: number;
425
448
  };
@@ -466,6 +489,7 @@ export declare const EventPayload: import("arktype/internal/variants/object.ts")
466
489
  mimeType: string;
467
490
  url: string;
468
491
  };
492
+ signature?: string;
469
493
  };
470
494
  index?: number;
471
495
  };
@@ -574,6 +598,13 @@ export declare const EventPayload: import("arktype/internal/variants/object.ts")
574
598
  data: {
575
599
  reason: "approval" | "payment" | "credential" | "budget" | "child_completion" | "message_response";
576
600
  gateId: string;
601
+ correlationId?: string;
602
+ approvalSnapshot?: {
603
+ name: string;
604
+ description: string;
605
+ inputSchema: Record<string, unknown>;
606
+ arguments: Record<string, unknown>;
607
+ };
577
608
  };
578
609
  } | {
579
610
  type: "reactor.gate.cleared";
@@ -1,4 +1,4 @@
1
- export { ControlPayload, OutboundAttachmentPayload, OutboundMessagePayload, SourcesUpdatedData, createControlChannelSender, receiveControlChannel, type ControlChannelSender, type ControlChannelSenderOpts, type ControlChannelReceiverOpts, type NdjsonReader, type NdjsonWriter, } from "./control-channel.js";
1
+ export { ControlPayload, MailboxNotifyHeaders, OutboundAttachmentPayload, OutboundMessagePayload, SourcesUpdatedData, createControlChannelSender, receiveControlChannel, type ControlChannelSender, type ControlChannelSenderOpts, type ControlChannelReceiverOpts, type NdjsonReader, type NdjsonWriter, } from "./control-channel.js";
2
2
  export { DEFAULT_EVENT_BUFFER_LIMIT, EventPayload, createEventChannelSender, receiveEventChannel, type EventChannelSender, type EventChannelSenderOpts, type EventChannelReceiverOpts, type FrameReader, type FrameWriter, } from "./event-channel.js";
3
3
  export { FrameEnvelope, MacedEnvelope, SignedEnvelope, decodeEnvelope, encodeEnvelope, } from "./envelope.js";
4
4
  export { IPC_CRYPTO, generateChannelId, generateHmacKey, signEd25519, signHmac, verifyEd25519, verifyHmac, } from "./crypto.js";
package/dist/ipc/index.js CHANGED
@@ -137,7 +137,7 @@
137
137
  // from env, the supervisor enforces.
138
138
  //
139
139
  // =============================================================
140
- export { ControlPayload, OutboundAttachmentPayload, OutboundMessagePayload, SourcesUpdatedData, createControlChannelSender, receiveControlChannel, } from "./control-channel.js";
140
+ export { ControlPayload, MailboxNotifyHeaders, OutboundAttachmentPayload, OutboundMessagePayload, SourcesUpdatedData, createControlChannelSender, receiveControlChannel, } from "./control-channel.js";
141
141
  export { DEFAULT_EVENT_BUFFER_LIMIT, EventPayload, createEventChannelSender, receiveEventChannel, } from "./event-channel.js";
142
142
  export { FrameEnvelope, MacedEnvelope, SignedEnvelope, decodeEnvelope, encodeEnvelope, } from "./envelope.js";
143
143
  export { IPC_CRYPTO, generateChannelId, generateHmacKey, signEd25519, signHmac, verifyEd25519, verifyHmac, } from "./crypto.js";
@@ -10,14 +10,19 @@ import type { MailBusBindings } from "../supervisor/types.js";
10
10
  */
11
11
  export interface HubTransportMailBusAdapter extends MailBusBindings {
12
12
  /**
13
- * Fan a delivered message out to every handler subscribed at
14
- * `address`. Returns immediately if no handler is registered --
15
- * the supervisor's lifecycle (`subscribeMailForAddress` returns a
16
- * disposer the supervisor calls on teardown) is the authoritative
17
- * source of which addresses are live; addresses without an active
18
- * subscriber drop the message silently.
13
+ * Fan a delivered message out to every handler subscribed at `address` and
14
+ * return the combined durable settlement: the promise resolves once every
15
+ * subscribed handler has durably accepted the message and rejects if any
16
+ * handler rejected. The host propagates that settlement to the wire, so
17
+ * resolution is the durable-receipt ACK signal and rejection is WITHHOLD.
18
+ *
19
+ * An address with NO active subscriber rejects rather than resolving. The
20
+ * supervisor's lifecycle (`subscribeMailForAddress` returns a disposer it
21
+ * calls on teardown) is the authoritative source of which addresses are
22
+ * live; a message routed to an address with no live handler was not
23
+ * durably accepted, so it must withhold-and-be-redelivered, never ack.
19
24
  */
20
- routeInbound(address: string, message: Uint8Array): void;
25
+ routeInbound(address: string, message: Uint8Array): Promise<void>;
21
26
  }
22
27
  /**
23
28
  * Wrap an existing `HubTransport` instance as the supervisor-facing
@@ -50,12 +50,16 @@ export function wrapHubTransportAsMailBus(transport) {
50
50
  current?.delete(handler);
51
51
  };
52
52
  },
53
- routeInbound(address, message) {
53
+ async routeInbound(address, message) {
54
54
  const set = subscribers.get(address);
55
- if (set === undefined)
56
- return;
57
- for (const handler of set)
58
- handler(message);
55
+ if (set === undefined || set.size === 0) {
56
+ // No live handler accepted the message, so it was not durably
57
+ // received. Reject so the caller withholds the ack and the hub
58
+ // redelivers, rather than silently dropping (which under the ack
59
+ // model would be an acked loss).
60
+ throw new Error(`no active mail subscriber for ${address}; message not durably accepted`);
61
+ }
62
+ await Promise.all([...set].map((handler) => handler(message)));
59
63
  },
60
64
  async sendOutbound(senderAddress, message) {
61
65
  // OUTBOUND half of mailbox ownership (§3a): route the agent's
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Run `body`, then always run `cleanup`, without letting a `cleanup`
3
+ * failure mask a `body` failure.
4
+ *
5
+ * When `body` throws, `cleanup` still runs and a `cleanup` failure is
6
+ * handed to `onCleanupErrorAfterBodyError` (to log) and then dropped, so
7
+ * the original `body` error is what propagates. When `body` succeeds, a
8
+ * `cleanup` failure propagates -- there is no primary error to protect, so
9
+ * a failing teardown is the error worth surfacing.
10
+ *
11
+ * This exists so teardown in a `finally` (agent close, warm-cache
12
+ * eviction) can surface its own failure without a `throw` inside a
13
+ * `finally` block, which `no-unsafe-finally` forbids precisely because it
14
+ * silently swallows the in-flight exception -- the masking bug this guards
15
+ * against.
16
+ */
17
+ export declare function runBodyThenCleanup<T>(body: () => Promise<T>, cleanup: () => Promise<void>, onCleanupErrorAfterBodyError: (cause: unknown) => void): Promise<T>;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Run `body`, then always run `cleanup`, without letting a `cleanup`
3
+ * failure mask a `body` failure.
4
+ *
5
+ * When `body` throws, `cleanup` still runs and a `cleanup` failure is
6
+ * handed to `onCleanupErrorAfterBodyError` (to log) and then dropped, so
7
+ * the original `body` error is what propagates. When `body` succeeds, a
8
+ * `cleanup` failure propagates -- there is no primary error to protect, so
9
+ * a failing teardown is the error worth surfacing.
10
+ *
11
+ * This exists so teardown in a `finally` (agent close, warm-cache
12
+ * eviction) can surface its own failure without a `throw` inside a
13
+ * `finally` block, which `no-unsafe-finally` forbids precisely because it
14
+ * silently swallows the in-flight exception -- the masking bug this guards
15
+ * against.
16
+ */
17
+ export async function runBodyThenCleanup(body, cleanup, onCleanupErrorAfterBodyError) {
18
+ let outcome;
19
+ try {
20
+ outcome = { ok: true, value: await body() };
21
+ }
22
+ catch (error) {
23
+ outcome = { ok: false, error };
24
+ }
25
+ try {
26
+ await cleanup();
27
+ }
28
+ catch (cleanupError) {
29
+ if (outcome.ok) {
30
+ throw cleanupError;
31
+ }
32
+ onCleanupErrorAfterBodyError(cleanupError);
33
+ }
34
+ if (!outcome.ok) {
35
+ throw outcome.error;
36
+ }
37
+ return outcome.value;
38
+ }
@@ -11,12 +11,10 @@ import type { Principal, RepoId, RepoStore } from "@intx/hub-sessions/substrate"
11
11
  export declare const TimerEventEnvelope: import("arktype/internal/variants/object.ts").ObjectType<{
12
12
  seq: number;
13
13
  type: "TimerSet" | "TimerFired";
14
- data: {
15
- timerId: string;
16
- fireAt?: string;
17
- stepId?: string | null;
18
- cron?: string | null;
19
- };
14
+ timerId: string;
15
+ fireAt?: string;
16
+ stepId?: string | null;
17
+ cron?: string | null;
20
18
  }, {}>;
21
19
  export type TimerEventEnvelope = typeof TimerEventEnvelope.infer;
22
20
  export type SchedulerOpts = {
@@ -54,6 +52,18 @@ export type SchedulerOpts = {
54
52
  * callbacks and to skip past-due cron entries on recovery.
55
53
  */
56
54
  clock: () => Date;
55
+ /**
56
+ * Arms a one-shot timer and returns its canceller. Defaults to the global
57
+ * timer, which is what production wants.
58
+ *
59
+ * The clock above decides WHEN a timer should fire; this decides what
60
+ * actually fires it. Without both, a caller can compute a deterministic
61
+ * delay and still have to wait out the real interval to observe the
62
+ * firing -- which left this module's own tests waiting past a `fireAt`,
63
+ * and, in one case, waiting longer than a cancelled timer's delay to argue
64
+ * from silence that the cancel had worked.
65
+ */
66
+ scheduleTimeout?: (handler: () => void, ms: number) => () => void;
57
67
  };
58
68
  export type SchedulerHandle = {
59
69
  /**