@nylorun/runtime 0.12.0-beta → 0.14.0-beta

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 (223) hide show
  1. package/CHANGELOG.md +361 -0
  2. package/README.md +69 -15
  3. package/dist/adapters/execution/restate.d.ts +7 -2
  4. package/dist/adapters/execution/restate.js +38 -8
  5. package/dist/adapters/replication/pgoutput.d.ts +16 -0
  6. package/dist/adapters/replication/pgoutput.js +169 -0
  7. package/dist/adapters/streams/s2.js +37 -20
  8. package/dist/admin-openapi.json +1071 -0
  9. package/dist/api/a2a/card.d.ts +15 -0
  10. package/dist/api/a2a/card.js +24 -0
  11. package/dist/api/a2a/endpoint.d.ts +8 -0
  12. package/dist/api/a2a/endpoint.js +81 -0
  13. package/dist/api/a2a/protocol.d.ts +144 -0
  14. package/dist/api/a2a/protocol.js +188 -0
  15. package/dist/api/a2a/routes.d.ts +19 -0
  16. package/dist/api/a2a/routes.js +289 -0
  17. package/dist/api/a2a/tasks.d.ts +50 -0
  18. package/dist/api/a2a/tasks.js +186 -0
  19. package/dist/api/ag-ui/endpoint.d.ts +8 -0
  20. package/dist/api/ag-ui/endpoint.js +126 -0
  21. package/dist/api/ag-ui/history.d.ts +7 -0
  22. package/dist/api/ag-ui/history.js +60 -0
  23. package/dist/api/ag-ui/routes.d.ts +32 -0
  24. package/dist/api/ag-ui/routes.js +305 -0
  25. package/dist/api/ag-ui/session-id.d.ts +1 -0
  26. package/dist/api/ag-ui/session-id.js +12 -0
  27. package/dist/api/ag-ui/sse.d.ts +9 -0
  28. package/dist/api/ag-ui/sse.js +7 -0
  29. package/dist/api/ag-ui/translate.d.ts +38 -0
  30. package/dist/api/ag-ui/translate.js +222 -0
  31. package/dist/api/components.d.ts +7825 -0
  32. package/dist/api/components.js +83 -0
  33. package/dist/api/http/app.d.ts +18 -0
  34. package/dist/api/http/app.js +58 -0
  35. package/dist/api/http/body.d.ts +4 -0
  36. package/dist/api/http/body.js +37 -0
  37. package/dist/api/http/define.d.ts +39 -0
  38. package/dist/api/http/define.js +130 -0
  39. package/dist/api/http/respond.d.ts +9 -0
  40. package/dist/api/http/respond.js +44 -0
  41. package/dist/api/http/routes/access.d.ts +24 -0
  42. package/dist/api/http/routes/access.js +259 -0
  43. package/dist/api/http/routes/actions.d.ts +8 -0
  44. package/dist/api/http/routes/actions.js +76 -0
  45. package/dist/api/http/routes/endpoints.d.ts +7 -0
  46. package/dist/api/http/routes/endpoints.js +100 -0
  47. package/dist/api/http/routes/sessions.d.ts +3 -0
  48. package/dist/api/http/routes/sessions.js +205 -0
  49. package/dist/api/http/routes/tenant.d.ts +10 -0
  50. package/dist/api/http/routes/tenant.js +172 -0
  51. package/dist/api/http/routes/vaults.d.ts +8 -0
  52. package/dist/api/http/routes/vaults.js +167 -0
  53. package/dist/api/openapi.d.ts +19 -0
  54. package/dist/api/openapi.js +138 -0
  55. package/dist/api/route.d.ts +12 -0
  56. package/dist/api/route.js +4 -0
  57. package/dist/core/bearer.d.ts +8 -0
  58. package/dist/core/bearer.js +11 -0
  59. package/dist/core/flow-host.d.ts +7 -15
  60. package/dist/core/flow-host.js +19 -41
  61. package/dist/core/sandbox-routes.d.ts +12 -7
  62. package/dist/core/sandbox-routes.js +24 -25
  63. package/dist/execution/memory.d.ts +8 -5
  64. package/dist/execution/memory.js +33 -13
  65. package/dist/execution/types.d.ts +38 -6
  66. package/dist/execution/types.js +12 -3
  67. package/dist/host/admin-api.d.ts +22 -0
  68. package/dist/host/admin-api.js +178 -0
  69. package/dist/host/app.d.ts +34 -0
  70. package/dist/host/app.js +185 -0
  71. package/dist/host/config-for.d.ts +2 -0
  72. package/dist/host/config-for.js +1 -0
  73. package/dist/host/config.d.ts +11 -0
  74. package/dist/host/cors.d.ts +27 -0
  75. package/dist/host/cors.js +64 -0
  76. package/dist/host/create-host.d.ts +31 -3
  77. package/dist/host/create-host.js +168 -327
  78. package/dist/host/http.d.ts +14 -3
  79. package/dist/host/http.js +64 -23
  80. package/dist/host/main.js +55 -4
  81. package/dist/host/stack-config.d.ts +20 -0
  82. package/dist/host/stack-config.js +55 -0
  83. package/dist/mcp/pool.d.ts +19 -1
  84. package/dist/mcp/pool.js +60 -10
  85. package/dist/model/catalog.d.ts +2 -10
  86. package/dist/model/classify.d.ts +13 -0
  87. package/dist/model/classify.js +61 -0
  88. package/dist/model/models.d.ts +10 -1
  89. package/dist/model/models.js +14 -6
  90. package/dist/model/pi-model.d.ts +17 -2
  91. package/dist/model/pi-model.js +366 -222
  92. package/dist/openapi.json +25696 -0
  93. package/dist/sandbox/manager.d.ts +13 -1
  94. package/dist/sandbox/manager.js +8 -11
  95. package/dist/sandbox/resolve.d.ts +33 -0
  96. package/dist/sandbox/resolve.js +81 -0
  97. package/dist/sandbox/session-sandbox.d.ts +11 -0
  98. package/dist/sandbox/session-sandbox.js +51 -0
  99. package/dist/sandbox/share.d.ts +14 -2
  100. package/dist/sandbox/share.js +33 -3
  101. package/dist/sandbox/tenant-config.d.ts +37 -0
  102. package/dist/sandbox/tenant-config.js +51 -0
  103. package/dist/sandbox/tools.d.ts +7 -9
  104. package/dist/sandbox/tools.js +11 -3
  105. package/dist/sandbox/types.d.ts +8 -2
  106. package/dist/store/event.d.ts +19 -0
  107. package/dist/store/event.js +49 -0
  108. package/dist/store/memory.d.ts +20 -8
  109. package/dist/store/memory.js +265 -100
  110. package/dist/store/postgres/connect.d.ts +13 -0
  111. package/dist/store/postgres/connect.js +5 -0
  112. package/dist/store/postgres/migrations/003_subject_tokens.d.ts +8 -0
  113. package/dist/store/postgres/migrations/003_subject_tokens.js +42 -0
  114. package/dist/store/postgres/migrations/004_publishable_keys.d.ts +6 -0
  115. package/dist/store/postgres/migrations/004_publishable_keys.js +18 -0
  116. package/dist/store/postgres/migrations/005_action_endpoints.d.ts +6 -0
  117. package/dist/store/postgres/migrations/005_action_endpoints.js +32 -0
  118. package/dist/store/postgres/migrations/006_drop_executors.d.ts +11 -0
  119. package/dist/store/postgres/migrations/006_drop_executors.js +32 -0
  120. package/dist/store/postgres/migrations/007_event_time.d.ts +7 -0
  121. package/dist/store/postgres/migrations/007_event_time.js +14 -0
  122. package/dist/store/postgres/migrations/008_durable_streams.d.ts +13 -0
  123. package/dist/store/postgres/migrations/008_durable_streams.js +35 -0
  124. package/dist/store/postgres/migrations/index.d.ts +5 -1
  125. package/dist/store/postgres/migrations/index.js +22 -2
  126. package/dist/store/postgres/migrations/shared/index.d.ts +31 -0
  127. package/dist/store/postgres/migrations/shared/index.js +92 -0
  128. package/dist/store/postgres/record.d.ts +11 -0
  129. package/dist/store/postgres/record.js +64 -0
  130. package/dist/store/postgres/store.js +337 -95
  131. package/dist/store/postgres/tenants.js +14 -0
  132. package/dist/store/types.d.ts +176 -81
  133. package/dist/streams/basin.d.ts +18 -1
  134. package/dist/streams/basin.js +34 -2
  135. package/dist/streams/control.d.ts +18 -0
  136. package/dist/streams/control.js +29 -0
  137. package/dist/streams/memory.d.ts +3 -1
  138. package/dist/streams/memory.js +14 -8
  139. package/dist/streams/relay/core.d.ts +35 -0
  140. package/dist/streams/relay/core.js +366 -0
  141. package/dist/streams/relay/memory.d.ts +69 -0
  142. package/dist/streams/relay/memory.js +176 -0
  143. package/dist/streams/relay/types.d.ts +77 -0
  144. package/dist/streams/relay/types.js +7 -0
  145. package/dist/streams/types.d.ts +66 -36
  146. package/dist/streams/types.js +18 -53
  147. package/dist/tenant/access-policy.d.ts +30 -0
  148. package/dist/tenant/access-policy.js +52 -0
  149. package/dist/tenant/actions.d.ts +10 -149
  150. package/dist/tenant/actions.js +5 -167
  151. package/dist/tenant/advance.js +32 -0
  152. package/dist/tenant/auth.d.ts +19 -17
  153. package/dist/tenant/auth.js +90 -93
  154. package/dist/tenant/browser.d.ts +15 -0
  155. package/dist/tenant/browser.js +39 -0
  156. package/dist/tenant/commands.d.ts +18 -2
  157. package/dist/tenant/commands.js +121 -129
  158. package/dist/tenant/context.d.ts +75 -18
  159. package/dist/tenant/context.js +14 -10
  160. package/dist/tenant/delivery-token.d.ts +41 -0
  161. package/dist/tenant/delivery-token.js +105 -0
  162. package/dist/tenant/delivery.d.ts +62 -0
  163. package/dist/tenant/delivery.js +476 -0
  164. package/dist/tenant/effects.d.ts +5 -0
  165. package/dist/tenant/effects.js +138 -48
  166. package/dist/tenant/endpoints.d.ts +67 -0
  167. package/dist/tenant/endpoints.js +90 -0
  168. package/dist/tenant/ephemeral.d.ts +6 -0
  169. package/dist/tenant/ephemeral.js +60 -36
  170. package/dist/tenant/http.d.ts +8 -9
  171. package/dist/tenant/http.js +13 -27
  172. package/dist/tenant/jwt.d.ts +16 -0
  173. package/dist/tenant/jwt.js +50 -0
  174. package/dist/tenant/module.d.ts +3 -0
  175. package/dist/tenant/module.js +6 -12
  176. package/dist/tenant/outbound.d.ts +44 -0
  177. package/dist/tenant/outbound.js +127 -0
  178. package/dist/tenant/pool.d.ts +0 -2
  179. package/dist/tenant/pool.js +0 -18
  180. package/dist/tenant/reset.d.ts +0 -4
  181. package/dist/tenant/reset.js +0 -5
  182. package/dist/tenant/runtime.d.ts +10 -20
  183. package/dist/tenant/runtime.js +59 -42
  184. package/dist/tenant/scheduler.d.ts +7 -2
  185. package/dist/tenant/scheduler.js +7 -3
  186. package/dist/tenant/session-streams.d.ts +125 -0
  187. package/dist/tenant/session-streams.js +399 -0
  188. package/dist/tenant/sessions.d.ts +28 -4
  189. package/dist/tenant/sessions.js +118 -24
  190. package/dist/tenant/signing-keys.d.ts +55 -0
  191. package/dist/tenant/signing-keys.js +172 -0
  192. package/dist/tenant/slim.d.ts +8 -0
  193. package/dist/tenant/slim.js +21 -0
  194. package/dist/tenant/status.d.ts +0 -3
  195. package/dist/tenant/status.js +19 -12
  196. package/dist/tenant/streams.d.ts +60 -67
  197. package/dist/tenant/streams.js +140 -208
  198. package/dist/tenant/subject-limits.d.ts +16 -0
  199. package/dist/tenant/subject-limits.js +45 -0
  200. package/dist/tenant/sweep.d.ts +1 -10
  201. package/dist/tenant/sweep.js +29 -69
  202. package/dist/tenant/tokens.d.ts +14 -0
  203. package/dist/tenant/tokens.js +178 -0
  204. package/dist/tenant/transcript.d.ts +48 -5
  205. package/dist/tenant/transcript.js +58 -1
  206. package/dist/tenant/types.d.ts +29 -7
  207. package/dist/tenant/worker.d.ts +3 -1
  208. package/dist/tenant/worker.js +9 -3
  209. package/dist/vault/service.d.ts +18 -2
  210. package/dist/vault/service.js +57 -13
  211. package/dist/version.d.ts +1 -1
  212. package/dist/version.js +1 -1
  213. package/package.json +20 -8
  214. package/dist/core/executors.d.ts +0 -44
  215. package/dist/core/executors.js +0 -95
  216. package/dist/streams/relay.d.ts +0 -37
  217. package/dist/streams/relay.js +0 -254
  218. package/dist/tenant/live.d.ts +0 -92
  219. package/dist/tenant/live.js +0 -290
  220. package/dist/tenant/routes-tenant.d.ts +0 -12
  221. package/dist/tenant/routes-tenant.js +0 -137
  222. package/dist/tenant/routes.d.ts +0 -9
  223. package/dist/tenant/routes.js +0 -151
@@ -1,8 +1,8 @@
1
1
  import { mkdirSync } from "node:fs";
2
2
  import { resolveFlowLimits } from "../core/limits.js";
3
- import { loadExecutorRegistry } from "../core/executors.js";
4
3
  import { scriptedModel, gatewayModel, toolFixtureModel, } from "../core/provider.js";
5
4
  import { createKekFile, readVaultKek } from "../vault/kek.js";
5
+ import { SigningKeys } from "./signing-keys.js";
6
6
  import { VaultService } from "../vault/service.js";
7
7
  import { McpPool } from "../mcp/pool.js";
8
8
  import { SandboxManager } from "../sandbox/manager.js";
@@ -10,14 +10,15 @@ import { defaultSandboxBackends } from "../sandbox/select.js";
10
10
  import { MemoryExecution } from "../execution/memory.js";
11
11
  import { QuarantineError } from "./quarantine-error.js";
12
12
  import { MemoryStreams } from "../streams/memory.js";
13
- import { connectedExecutorCount, createLiveHub, endAllStreams, } from "./live.js";
14
- import { closeStreams, drainOutbox, streamsStatus, wireStreams, } from "./streams.js";
13
+ import { createSessionStreams, endAllStreams, } from "./session-streams.js";
14
+ import { closeStreams, streamsStatus, wireStreams, } from "./streams.js";
15
15
  import { abortAll, abortLocal, createWorkState, drain, waitForIdle, } from "./scheduler.js";
16
16
  import { advance } from "./advance.js";
17
17
  import { sweep } from "./sweep.js";
18
+ import { deliverAction } from "./delivery.js";
18
19
  import { TenantWorkers, WORKER_ID, } from "./worker.js";
19
20
  import { authorize } from "./effects.js";
20
- import { handle } from "./routes.js";
21
+ import { tenantApi } from "../api/http/app.js";
21
22
  /** Default ownership lease of an advance; the heartbeat renews it every third. */
22
23
  const DEFAULT_OWNER_LEASE_MS = 30_000;
23
24
  function validateConfig(config) {
@@ -70,8 +71,9 @@ export class TenantRuntime {
70
71
  vaultKek: hooks.vaultKek,
71
72
  vaultKekPath: paths.kek,
72
73
  });
73
- if ((await store.tx((t) => t.countCredentials())) > 0 && !kek) {
74
- throw new QuarantineError("kek-missing", "Vault key-encryption key is missing for ciphertext in this Tenant", "restore the vault-kek file in the Tenant directory");
74
+ const sealed = await store.tx(async (t) => (await t.countCredentials()) + (await t.countSigningKeys()));
75
+ if (sealed > 0 && !kek) {
76
+ throw new QuarantineError("kek-missing", "Vault key-encryption key is missing for ciphertext (vault credentials or signing keys) in this Tenant", "restore the vault-kek file in the Tenant directory");
75
77
  }
76
78
  const createKekIfMissing = hooks.createKekIfMissing !== false;
77
79
  const ensureKek = () => {
@@ -83,7 +85,6 @@ export class TenantRuntime {
83
85
  return kek;
84
86
  };
85
87
  const opened = store;
86
- const registry = await loadExecutorRegistry(opened);
87
88
  // A seeded Tenant setting wins over the Host default (A18).
88
89
  const seededBackend = await opened.tx((t) => t.getSetting("sandbox.backend"));
89
90
  const vault = new VaultService({
@@ -141,7 +142,7 @@ export class TenantRuntime {
141
142
  }
142
143
  else
143
144
  modelProvider = scriptedModel();
144
- const live = createLiveHub();
145
+ const sessionStreams = createSessionStreams();
145
146
  const local = hooks.execution
146
147
  ? undefined
147
148
  : new MemoryExecution({
@@ -160,7 +161,6 @@ export class TenantRuntime {
160
161
  envelope,
161
162
  store: opened,
162
163
  vault,
163
- registry,
164
164
  mcp,
165
165
  sandbox,
166
166
  flowLimits,
@@ -169,7 +169,8 @@ export class TenantRuntime {
169
169
  closing: false,
170
170
  closed: false,
171
171
  work: createWorkState(),
172
- live,
172
+ sessionStreams,
173
+ signingKeys: new SigningKeys({ tenantId: config.tenantId, kek: ensureKek }),
173
174
  workerId: hooks.workerId ?? WORKER_ID,
174
175
  ownerLeaseMs: config.ownerLeaseMs ?? DEFAULT_OWNER_LEASE_MS,
175
176
  wake: async (sessionId, wake) => {
@@ -178,6 +179,11 @@ export class TenantRuntime {
178
179
  await execution.wake(config.tenantId, sessionId, wake);
179
180
  },
180
181
  abortLocal: (sessionId) => abortLocal(ctx, sessionId),
182
+ deliver: async (actionId) => {
183
+ if (ctx.closing || ctx.closed)
184
+ return;
185
+ await execution.deliver(config.tenantId, actionId);
186
+ },
181
187
  ...(hooks.execution?.stuckInvocations
182
188
  ? {
183
189
  stuckInvocations: () => hooks.execution.stuckInvocations(config.tenantId),
@@ -194,21 +200,15 @@ export class TenantRuntime {
194
200
  streams: hooks.streams ?? new MemoryStreams(),
195
201
  ownsStreams: !hooks.streams,
196
202
  tenantId: config.tenantId,
197
- });
198
- // Outbox rows a lost relay step left behind are appended by the sweep.
199
- sweepHooks.add(async () => {
200
- await drainOutbox(ctx);
203
+ ...(hooks.hostRelay ? { hostRelay: true } : {}),
204
+ ...(hooks.retireGraceMs !== undefined ? { retireGraceMs: hooks.retireGraceMs } : {}),
201
205
  });
202
206
  // Register the handlers, then arm the sweep: its first pass runs at once and re-wakes
203
207
  // sessions a previous process left runnable or running (takeover handles the rest).
204
- let afterOpen = true;
205
208
  const worker = {
206
209
  advance: (sessionId, signal) => advance(ctx, sessionId, signal),
207
- sweep: async () => {
208
- const first = afterOpen;
209
- afterOpen = false;
210
- await sweep(ctx, { afterOpen: first });
211
- },
210
+ deliver: (actionId, signal) => deliverAction(ctx, actionId, signal),
211
+ sweep: () => sweep(ctx),
212
212
  };
213
213
  const unregister = workers.register(config.tenantId, worker);
214
214
  detach = async () => {
@@ -227,28 +227,21 @@ export class TenantRuntime {
227
227
  throw error;
228
228
  }
229
229
  }
230
- handle(request, response, url) {
231
- return handle(this.ctx, request, response, url);
230
+ async fetch(request, node) {
231
+ return await tenantApi().fetch(request, { ...node, tenant: this.ctx });
232
232
  }
233
233
  authorize(sessionId, request) {
234
234
  return authorize(this.ctx, sessionId, request);
235
235
  }
236
236
  async summary() {
237
- const { store, live } = this.ctx;
238
- const { counts, outbox } = await store.tx(async (t) => ({
239
- counts: await t.counts(),
240
- outbox: await t.outboxStats(),
241
- }));
237
+ const { store } = this.ctx;
238
+ const counts = await store.tx((t) => t.counts());
242
239
  return {
243
240
  ready: !this.ctx.closing && !this.ctx.closed,
244
241
  runningSessions: counts.runningSessions,
245
- connectedExecutors: connectedExecutorCount(live),
242
+ inFlightDeliveries: [...this.ctx.work.deliveries.values()].reduce((n, set) => n + set.size, 0),
246
243
  pendingActions: counts.pendingActions,
247
244
  uncertainEffects: counts.uncertainEffects,
248
- outboxDepth: outbox.depth,
249
- relayLagMs: outbox.oldestCreatedAt === null
250
- ? 0
251
- : Math.max(0, Date.now() - Date.parse(outbox.oldestCreatedAt)),
252
245
  };
253
246
  }
254
247
  drain(activeWork, timeoutMs = 30_000) {
@@ -263,7 +256,7 @@ export class TenantRuntime {
263
256
  }
264
257
  /**
265
258
  * The Durable Streams seam's status for Tenant status and readiness: S2 reachability, the
266
- * basin, outbox depth and relay lag (`streamsStatus` in `tenant/streams.ts`).
259
+ * basin, its generation and the Tenant's relay (`streamsStatus` in `tenant/streams.ts`).
267
260
  */
268
261
  streamsStatus() {
269
262
  return streamsStatus(this.ctx);
@@ -278,15 +271,39 @@ export class TenantRuntime {
278
271
  // A shutdown abort: the advances leave their sessions to the next advance, unsettled.
279
272
  abortAll(ctx, "shutdown");
280
273
  const idleBy = Date.now() + this.closeGraceMs;
281
- await this.detach();
282
- await ctx.mcp.close();
283
- endAllStreams(ctx.live);
284
- // Bounded: an advance that ignores its abort is abandoned; its lease lapses (§11.4).
285
- await waitForIdle(ctx, Math.max(0, idleBy - Date.now()));
286
- await ctx.sandbox.close();
287
- ctx.closed = true;
288
- await closeStreams(ctx);
289
- await ctx.store.close();
274
+ // Every step runs even when one before it fails, so nothing is left running; the first
275
+ // error is rethrown at the end.
276
+ const steps = [
277
+ ["detach", () => this.detach()],
278
+ ["mcp", () => ctx.mcp.close()],
279
+ ["session streams", () => endAllStreams(ctx.sessionStreams)],
280
+ // Bounded: an advance that ignores its abort is abandoned; its lease lapses (§11.4).
281
+ ["idle", () => waitForIdle(ctx, Math.max(0, idleBy - Date.now()))],
282
+ ["sandbox", () => ctx.sandbox.close()],
283
+ [
284
+ "streams",
285
+ () => {
286
+ ctx.closed = true;
287
+ return closeStreams(ctx);
288
+ },
289
+ ],
290
+ ["store", () => ctx.store.close()],
291
+ ];
292
+ let failure;
293
+ for (const [step, run] of steps) {
294
+ try {
295
+ await run();
296
+ }
297
+ catch (error) {
298
+ ctx.config.logger.warn("tenant close step failed", {
299
+ step,
300
+ message: error instanceof Error ? error.message : String(error),
301
+ });
302
+ failure ??= { error };
303
+ }
304
+ }
305
+ if (failure)
306
+ throw failure.error;
290
307
  }
291
308
  }
292
309
  /**
@@ -3,11 +3,16 @@ import { type AdvanceAbortKind } from "./worker.js";
3
3
  export interface WorkState {
4
4
  /** Advances running on this process, by session id. */
5
5
  readonly running: Map<string, AbortController>;
6
+ /** Deliveries to Action endpoints in flight on this process, by their Action's session id. */
7
+ readonly deliveries: Map<string, Set<AbortController>>;
8
+ /** When each Action last reported a failed delivery (`action.delivery_failed` throttling). */
9
+ readonly deliveryNotices: Map<string, number>;
6
10
  }
7
11
  export declare function createWorkState(): WorkState;
8
12
  /**
9
- * Abort the advance of `id` if it runs on this process. Cancel calls it after committing
10
- * `cancelled`, and so does the control stream for cancels made elsewhere.
13
+ * Abort the advance of `id` if it runs on this process, and its deliveries to Action endpoints.
14
+ * Cancel calls it after committing `cancelled`, and so does the control stream for cancels made
15
+ * elsewhere.
11
16
  */
12
17
  export declare function abortLocal(ctx: TenantContext, id: string, kind?: AdvanceAbortKind): void;
13
18
  /** Reset: abort every advance running on this process; their sessions are being cleared. */
@@ -13,7 +13,7 @@ import { randomUUID } from "node:crypto";
13
13
  import { command } from "./commands.js";
14
14
  import { AdvanceAbort } from "./worker.js";
15
15
  export function createWorkState() {
16
- return { running: new Map() };
16
+ return { running: new Map(), deliveries: new Map(), deliveryNotices: new Map() };
17
17
  }
18
18
  const MESSAGES = {
19
19
  cancel: "Turn cancelled",
@@ -26,13 +26,17 @@ function abortWith(controller, kind) {
26
26
  controller.abort(new AdvanceAbort(kind, MESSAGES[kind]));
27
27
  }
28
28
  /**
29
- * Abort the advance of `id` if it runs on this process. Cancel calls it after committing
30
- * `cancelled`, and so does the control stream for cancels made elsewhere.
29
+ * Abort the advance of `id` if it runs on this process, and its deliveries to Action endpoints.
30
+ * Cancel calls it after committing `cancelled`, and so does the control stream for cancels made
31
+ * elsewhere.
31
32
  */
32
33
  export function abortLocal(ctx, id, kind = "cancel") {
33
34
  const controller = ctx.work.running.get(id);
34
35
  if (controller)
35
36
  abortWith(controller, kind);
37
+ // A cancel also closes the session's deliveries, which aborts the tools' `ctx.signal`.
38
+ for (const delivery of ctx.work.deliveries.get(id) ?? [])
39
+ abortWith(delivery, kind);
36
40
  }
37
41
  /** Reset: abort every advance running on this process; their sessions are being cleared. */
38
42
  export function clearWork(ctx) {
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Live delivery over Durable Streams (architecture §12.4, "Reading"): session history
3
+ * (`GET /v1/sessions/:id/items`) and session SSE (`GET /v1/sessions/:id/events`).
4
+ *
5
+ * - **Streams.** A session's events are in `sessions/<id>` in the Tenant's current basin
6
+ * generation (`currentBasin`); cursors carry only the session id and sequence.
7
+ * - **History** reads the session's stream from the cursor up to the tail seen when the read
8
+ * starts. The `agent` filter runs here, and the response cursor is the last record read. If
9
+ * the streams fail, history answers `503`.
10
+ * - **SSE** shares one stream read per observed session in this process (a `SessionStream`)
11
+ * among that session's observers. Each observer keeps the next sequence it needs and skips
12
+ * what it already has, so a client resuming from `Last-Event-ID` sees no gap and no
13
+ * duplicate. An observer behind the feed restarts the shared read from its own position.
14
+ * A feed follows one basin generation: it ends once its session is gone or the Tenant moved
15
+ * to a new generation (a reset, possibly on another node), checked when a client joins, when
16
+ * its read fails, on a `sessions.reset` signal, and periodically (`checkSessionStreams`).
17
+ *
18
+ * Business code never writes here: events reach streams only through the stream relay, from
19
+ * the record, after commit.
20
+ */
21
+ import type { IncomingMessage, ServerResponse } from "node:http";
22
+ import type { LiveEvent } from "@nylorun/core/contracts";
23
+ import type { TenantContext } from "./context.js";
24
+ import type { StreamsWiring } from "./streams.js";
25
+ /** Why the Runtime ended a stream a subject token opened. */
26
+ export type StreamEndReason = "token_expired" | "revoked";
27
+ /** Where an observer's events go: an SSE response, or an in-process reader. */
28
+ export interface ObserverSink {
29
+ write(event: LiveEvent): void;
30
+ /** Ends the stream; with a reason, the client is told why before it ends. */
31
+ end(reason?: StreamEndReason): void;
32
+ }
33
+ /** The subject token a stream was opened with: it ends at expiry or revocation. */
34
+ export interface StreamHolder {
35
+ readonly subject: string;
36
+ readonly epoch: number;
37
+ /** Epoch ms when the token expires. */
38
+ readonly expiresAt: number;
39
+ }
40
+ /** One client of a session: the next sequence it needs. */
41
+ interface Observer {
42
+ readonly sink: ObserverSink;
43
+ next: number;
44
+ readonly holder?: StreamHolder;
45
+ /** Ends the observer when its token expires. */
46
+ deadline?: NodeJS.Timeout;
47
+ }
48
+ /** The shared stream read of one observed session. */
49
+ interface SessionStream {
50
+ readonly sessionId: string;
51
+ /** The basin the feed reads (the Tenant's generation when it started). */
52
+ readonly basin: string;
53
+ readonly stream: string;
54
+ /** The sequence the shared read yields next. Every observer's `next` is at or past it. */
55
+ next: number;
56
+ readonly observers: Set<Observer>;
57
+ /** Aborts the current read; replaced when the read restarts from an earlier sequence. */
58
+ read: AbortController;
59
+ }
60
+ export interface SessionStreams {
61
+ /** Set once by `wireStreams`. */
62
+ wiring: StreamsWiring | undefined;
63
+ /** The Tenant's basin generation as this process last saw it (`wireStreams` keeps it). */
64
+ generation: number;
65
+ /** Session id → its shared read and observers. */
66
+ readonly sessions: Map<string, SessionStream>;
67
+ }
68
+ export declare function createSessionStreams(): SessionStreams;
69
+ export declare function send(response: ServerResponse, data: string): void;
70
+ /** Start an SSE response with keepalives; `onClose` runs once the client goes away. */
71
+ export declare function openSse(request: IncomingMessage, response: ServerResponse, onClose: () => void): void;
72
+ /** The resume cursor: `?cursor=` wins over `Last-Event-ID`. */
73
+ export declare function requestCursor(request: IncomingMessage, url: URL): string | undefined;
74
+ /** The basin the Tenant's session streams are in now. */
75
+ export declare function currentBasin(ctx: TenantContext): string;
76
+ /**
77
+ * `GET /v1/sessions/:id/items`: the session stream after the cursor, up to its tail when the
78
+ * read starts. The cursor returned is the last record read (the request's cursor when none
79
+ * was), whatever the `agent` filter kept.
80
+ */
81
+ export declare function readHistory(ctx: TenantContext, sessionId: string, cursor: string | undefined, agent: string | undefined): Promise<{
82
+ items: LiveEvent[];
83
+ cursor: string | null;
84
+ }>;
85
+ /**
86
+ * `GET /v1/sessions/:id/events`: joins the session's shared read at the client's cursor and
87
+ * follows it until the client leaves, the session is reset or deleted, or the Tenant closes.
88
+ */
89
+ export declare function streamSessionEvents(ctx: TenantContext, request: IncomingMessage, response: ServerResponse, sessionId: string, cursor: string | undefined, holder?: StreamHolder): Promise<void>;
90
+ /** Thrown by `observeSession` when the Runtime ended the stream for its token. */
91
+ export declare class StreamClosed extends Error {
92
+ readonly reason: StreamEndReason;
93
+ /** The cursor of the last event delivered, if any. */
94
+ readonly cursor: string | undefined;
95
+ constructor(reason: StreamEndReason,
96
+ /** The cursor of the last event delivered, if any. */
97
+ cursor: string | undefined);
98
+ }
99
+ /**
100
+ * A session's events from `cursor`, in process: the same shared read and deadlines as an SSE
101
+ * client (`streamSessionEvents`). Ends when `signal` aborts or the feed ends; throws
102
+ * `StreamClosed` when the Runtime ends it for its token (expiry or revocation).
103
+ */
104
+ export declare function observeSession(ctx: TenantContext, sessionId: string, cursor: string | undefined, options: {
105
+ holder?: StreamHolder;
106
+ signal: AbortSignal;
107
+ }): AsyncGenerator<LiveEvent>;
108
+ /**
109
+ * Ends every feed in this process whose session is gone or whose Tenant moved to a new basin
110
+ * generation, and moves this process to the Tenant's current generation. Run on a
111
+ * `sessions.reset` signal and periodically, so feeds on nodes other than the one that reset
112
+ * the Tenant end too.
113
+ */
114
+ export declare function checkSessionStreams(ctx: TenantContext): Promise<void>;
115
+ /**
116
+ * Ends the streams of `subject` opened with a token older than `epoch` (a revocation), on
117
+ * this process. Other processes do the same on the `subject.revoked` signal.
118
+ */
119
+ export declare function endSubjectStreams(hub: SessionStreams, subject: string, epoch: number): void;
120
+ /** Reset or close: end every session observer and stop every shared read. */
121
+ export declare function clearObservers(hub: SessionStreams): void;
122
+ /** Close: end every session observer. */
123
+ export declare function endAllStreams(hub: SessionStreams): void;
124
+ export declare function sleep(ms: number, signal: AbortSignal): Promise<void>;
125
+ export {};