@nylorun/runtime 0.13.0-beta → 0.15.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 (309) hide show
  1. package/CHANGELOG.md +434 -0
  2. package/README.md +137 -41
  3. package/dist/adapters/execution/restate.d.ts +7 -2
  4. package/dist/adapters/execution/restate.js +40 -10
  5. package/dist/adapters/replication/pgoutput.d.ts +21 -0
  6. package/dist/adapters/replication/pgoutput.js +170 -0
  7. package/dist/adapters/streams/s2.js +37 -20
  8. package/dist/admin-openapi.json +570 -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 +8065 -0
  32. package/dist/api/components.js +82 -0
  33. package/dist/api/gate/keys.d.ts +14 -0
  34. package/dist/api/gate/keys.js +59 -0
  35. package/dist/api/gate/routes.d.ts +34 -0
  36. package/dist/api/gate/routes.js +235 -0
  37. package/dist/api/http/app.d.ts +18 -0
  38. package/dist/api/http/app.js +58 -0
  39. package/dist/api/http/body.d.ts +4 -0
  40. package/dist/api/http/body.js +37 -0
  41. package/dist/api/http/define.d.ts +39 -0
  42. package/dist/api/http/define.js +122 -0
  43. package/dist/api/http/respond.d.ts +9 -0
  44. package/dist/api/http/respond.js +44 -0
  45. package/dist/api/http/routes/access.d.ts +24 -0
  46. package/dist/api/http/routes/access.js +266 -0
  47. package/dist/api/http/routes/actions.d.ts +8 -0
  48. package/dist/api/http/routes/actions.js +76 -0
  49. package/dist/api/http/routes/endpoints.d.ts +7 -0
  50. package/dist/api/http/routes/endpoints.js +100 -0
  51. package/dist/api/http/routes/sessions.d.ts +3 -0
  52. package/dist/api/http/routes/sessions.js +205 -0
  53. package/dist/api/http/routes/tenant.d.ts +11 -0
  54. package/dist/api/http/routes/tenant.js +244 -0
  55. package/dist/api/http/routes/vaults.d.ts +8 -0
  56. package/dist/api/http/routes/vaults.js +167 -0
  57. package/dist/api/openapi.d.ts +19 -0
  58. package/dist/api/openapi.js +137 -0
  59. package/dist/api/route.d.ts +12 -0
  60. package/dist/api/route.js +4 -0
  61. package/dist/core/bearer.d.ts +8 -0
  62. package/dist/core/bearer.js +11 -0
  63. package/dist/core/flow-host.d.ts +7 -15
  64. package/dist/core/flow-host.js +16 -41
  65. package/dist/core/sandbox-routes.d.ts +12 -7
  66. package/dist/core/sandbox-routes.js +24 -25
  67. package/dist/execution/memory.d.ts +8 -5
  68. package/dist/execution/memory.js +33 -13
  69. package/dist/execution/types.d.ts +38 -6
  70. package/dist/execution/types.js +12 -3
  71. package/dist/gates/contract.d.ts +51 -0
  72. package/dist/gates/contract.js +37 -0
  73. package/dist/gates/handler.d.ts +21 -0
  74. package/dist/gates/handler.js +55 -0
  75. package/dist/gates/http-client.d.ts +12 -0
  76. package/dist/gates/http-client.js +160 -0
  77. package/dist/gates/in-process.d.ts +32 -0
  78. package/dist/gates/in-process.js +42 -0
  79. package/dist/gates/inflight.d.ts +39 -0
  80. package/dist/gates/inflight.js +70 -0
  81. package/dist/gates/mcp-handler.d.ts +35 -0
  82. package/dist/gates/mcp-handler.js +142 -0
  83. package/dist/gates/meter.d.ts +24 -0
  84. package/dist/gates/meter.js +165 -0
  85. package/dist/gates/model-gate.d.ts +45 -0
  86. package/dist/gates/model-gate.js +1 -0
  87. package/dist/gates/tenant-vaults.d.ts +64 -0
  88. package/dist/gates/tenant-vaults.js +94 -0
  89. package/dist/gates/tool-calls.d.ts +51 -0
  90. package/dist/gates/tool-calls.js +64 -0
  91. package/dist/gates/tool-client.d.ts +12 -0
  92. package/dist/gates/tool-client.js +201 -0
  93. package/dist/gates/tool-contract.d.ts +116 -0
  94. package/dist/gates/tool-contract.js +64 -0
  95. package/dist/gates/tool-gate.d.ts +45 -0
  96. package/dist/gates/tool-gate.js +10 -0
  97. package/dist/host/admin-api.d.ts +23 -0
  98. package/dist/host/admin-api.js +74 -0
  99. package/dist/host/app.d.ts +34 -0
  100. package/dist/host/app.js +181 -0
  101. package/dist/host/config-for.d.ts +3 -1
  102. package/dist/host/config-for.js +3 -2
  103. package/dist/host/config.d.ts +11 -0
  104. package/dist/host/cors.d.ts +27 -0
  105. package/dist/host/cors.js +64 -0
  106. package/dist/host/create-host.d.ts +36 -8
  107. package/dist/host/create-host.js +161 -330
  108. package/dist/host/execution.d.ts +20 -18
  109. package/dist/host/execution.js +2 -2
  110. package/dist/host/gates.d.ts +58 -0
  111. package/dist/host/gates.js +127 -0
  112. package/dist/host/http.d.ts +20 -4
  113. package/dist/host/http.js +81 -23
  114. package/dist/host/index.d.ts +1 -1
  115. package/dist/host/main.js +189 -58
  116. package/dist/host/stack-config.d.ts +91 -19
  117. package/dist/host/stack-config.js +246 -17
  118. package/dist/infra/database.d.ts +2 -2
  119. package/dist/infra/execution.d.ts +5 -5
  120. package/dist/infra/execution.js +8 -6
  121. package/dist/infra/index.d.ts +1 -1
  122. package/dist/infra/workers.d.ts +17 -17
  123. package/dist/infra/workers.js +9 -9
  124. package/dist/keys/client.d.ts +9 -0
  125. package/dist/keys/client.js +70 -0
  126. package/dist/keys/contract.d.ts +24 -0
  127. package/dist/keys/contract.js +12 -0
  128. package/dist/keys/keys.d.ts +41 -0
  129. package/dist/keys/keys.js +50 -0
  130. package/dist/mcp/connect.d.ts +43 -3
  131. package/dist/mcp/connect.js +12 -5
  132. package/dist/mcp/pool.d.ts +39 -3
  133. package/dist/mcp/pool.js +74 -11
  134. package/dist/model/catalog.d.ts +2 -10
  135. package/dist/model/classify.d.ts +13 -0
  136. package/dist/model/classify.js +61 -0
  137. package/dist/model/models.d.ts +10 -1
  138. package/dist/model/models.js +14 -6
  139. package/dist/model/pi-model.d.ts +17 -2
  140. package/dist/model/pi-model.js +366 -222
  141. package/dist/openapi.json +25889 -0
  142. package/dist/record/append.d.ts +51 -0
  143. package/dist/record/append.js +20 -0
  144. package/dist/record/envelope.d.ts +19 -0
  145. package/dist/record/envelope.js +49 -0
  146. package/dist/record/index.d.ts +11 -0
  147. package/dist/record/index.js +11 -0
  148. package/dist/sandbox/manager.d.ts +13 -1
  149. package/dist/sandbox/manager.js +8 -11
  150. package/dist/sandbox/resolve.d.ts +33 -0
  151. package/dist/sandbox/resolve.js +81 -0
  152. package/dist/sandbox/session-sandbox.d.ts +11 -0
  153. package/dist/sandbox/session-sandbox.js +51 -0
  154. package/dist/sandbox/share.d.ts +14 -2
  155. package/dist/sandbox/share.js +33 -3
  156. package/dist/sandbox/tenant-config.d.ts +37 -0
  157. package/dist/sandbox/tenant-config.js +51 -0
  158. package/dist/sandbox/tools.d.ts +7 -9
  159. package/dist/sandbox/tools.js +11 -3
  160. package/dist/sandbox/types.d.ts +8 -2
  161. package/dist/store/postgres/connect.d.ts +25 -9
  162. package/dist/store/postgres/connect.js +27 -10
  163. package/dist/store/postgres/db.d.ts +16 -0
  164. package/dist/store/postgres/db.js +28 -0
  165. package/dist/store/postgres/drizzle/0000_foundation.sql +22 -0
  166. package/dist/store/postgres/drizzle/0001_baseline.sql +255 -0
  167. package/dist/store/postgres/drizzle/0002_stream_relay.sql +4 -0
  168. package/dist/store/postgres/drizzle/0003_tool_crossings.sql +9 -0
  169. package/dist/store/postgres/drizzle/meta/_journal.json +34 -0
  170. package/dist/store/postgres/migrate.d.ts +68 -0
  171. package/dist/store/postgres/migrate.js +183 -0
  172. package/dist/store/postgres/record-writer.d.ts +4 -0
  173. package/dist/store/postgres/record-writer.js +34 -0
  174. package/dist/store/postgres/record.d.ts +5 -0
  175. package/dist/store/postgres/record.js +60 -0
  176. package/dist/store/postgres/schema.d.ts +3096 -0
  177. package/dist/store/postgres/schema.js +363 -0
  178. package/dist/store/postgres/store.d.ts +2 -4
  179. package/dist/store/postgres/store.js +590 -434
  180. package/dist/store/postgres/tenant.d.ts +56 -0
  181. package/dist/store/postgres/tenant.js +154 -0
  182. package/dist/store/types.d.ts +183 -132
  183. package/dist/store/types.js +0 -1
  184. package/dist/streams/basin.d.ts +18 -1
  185. package/dist/streams/basin.js +34 -2
  186. package/dist/streams/control.d.ts +18 -0
  187. package/dist/streams/control.js +33 -0
  188. package/dist/streams/memory.d.ts +3 -1
  189. package/dist/streams/memory.js +14 -8
  190. package/dist/streams/relay/core.d.ts +35 -0
  191. package/dist/streams/relay/core.js +366 -0
  192. package/dist/streams/relay/types.d.ts +78 -0
  193. package/dist/streams/relay/types.js +8 -0
  194. package/dist/streams/types.d.ts +71 -36
  195. package/dist/streams/types.js +18 -53
  196. package/dist/tenant/access-policy.d.ts +30 -0
  197. package/dist/tenant/access-policy.js +52 -0
  198. package/dist/tenant/actions.d.ts +10 -149
  199. package/dist/tenant/actions.js +5 -167
  200. package/dist/tenant/advance.d.ts +7 -2
  201. package/dist/tenant/advance.js +125 -14
  202. package/dist/tenant/auth.d.ts +19 -17
  203. package/dist/tenant/auth.js +90 -93
  204. package/dist/tenant/browser.d.ts +15 -0
  205. package/dist/tenant/browser.js +39 -0
  206. package/dist/tenant/cause.d.ts +25 -0
  207. package/dist/tenant/cause.js +45 -0
  208. package/dist/tenant/commands.d.ts +18 -2
  209. package/dist/tenant/commands.js +111 -115
  210. package/dist/tenant/context.d.ts +101 -21
  211. package/dist/tenant/context.js +14 -10
  212. package/dist/tenant/delivery-token.d.ts +41 -0
  213. package/dist/tenant/delivery-token.js +104 -0
  214. package/dist/tenant/delivery.d.ts +62 -0
  215. package/dist/tenant/delivery.js +481 -0
  216. package/dist/tenant/effects.d.ts +20 -1
  217. package/dist/tenant/effects.js +159 -42
  218. package/dist/tenant/endpoints.d.ts +67 -0
  219. package/dist/tenant/endpoints.js +90 -0
  220. package/dist/tenant/ephemeral.d.ts +22 -7
  221. package/dist/tenant/ephemeral.js +83 -125
  222. package/dist/tenant/history.d.ts +64 -0
  223. package/dist/tenant/history.js +120 -0
  224. package/dist/tenant/http.d.ts +8 -9
  225. package/dist/tenant/http.js +13 -27
  226. package/dist/tenant/jwt.d.ts +16 -0
  227. package/dist/tenant/jwt.js +50 -0
  228. package/dist/tenant/model-setting.d.ts +2 -2
  229. package/dist/tenant/module.d.ts +17 -28
  230. package/dist/tenant/module.js +133 -279
  231. package/dist/tenant/outbound.d.ts +44 -0
  232. package/dist/tenant/outbound.js +127 -0
  233. package/dist/tenant/paths.d.ts +11 -16
  234. package/dist/tenant/paths.js +24 -68
  235. package/dist/tenant/pool.d.ts +1 -3
  236. package/dist/tenant/pool.js +1 -19
  237. package/dist/tenant/principals.d.ts +23 -11
  238. package/dist/tenant/principals.js +61 -49
  239. package/dist/tenant/reset.d.ts +0 -4
  240. package/dist/tenant/reset.js +0 -5
  241. package/dist/tenant/runtime.d.ts +28 -20
  242. package/dist/tenant/runtime.js +95 -64
  243. package/dist/tenant/scheduler.d.ts +10 -3
  244. package/dist/tenant/scheduler.js +9 -5
  245. package/dist/tenant/session-streams.d.ts +127 -0
  246. package/dist/tenant/session-streams.js +404 -0
  247. package/dist/tenant/sessions.d.ts +28 -4
  248. package/dist/tenant/sessions.js +118 -24
  249. package/dist/tenant/signing-keys.d.ts +55 -0
  250. package/dist/tenant/signing-keys.js +172 -0
  251. package/dist/tenant/slim.d.ts +8 -0
  252. package/dist/tenant/slim.js +21 -0
  253. package/dist/tenant/status.d.ts +3 -3
  254. package/dist/tenant/status.js +20 -13
  255. package/dist/tenant/store-pg.d.ts +9 -6
  256. package/dist/tenant/store-pg.js +60 -125
  257. package/dist/tenant/streams.d.ts +62 -85
  258. package/dist/tenant/streams.js +143 -226
  259. package/dist/tenant/subject-limits.d.ts +16 -0
  260. package/dist/tenant/subject-limits.js +45 -0
  261. package/dist/tenant/sweep.d.ts +1 -10
  262. package/dist/tenant/sweep.js +29 -69
  263. package/dist/tenant/tokens.d.ts +14 -0
  264. package/dist/tenant/tokens.js +180 -0
  265. package/dist/tenant/transcript.d.ts +48 -5
  266. package/dist/tenant/transcript.js +58 -1
  267. package/dist/tenant/types.d.ts +69 -98
  268. package/dist/tenant/types.js +4 -15
  269. package/dist/tenant/worker.d.ts +3 -1
  270. package/dist/tenant/worker.js +13 -7
  271. package/dist/vault/host-model.d.ts +31 -0
  272. package/dist/vault/host-model.js +57 -0
  273. package/dist/vault/service.d.ts +44 -17
  274. package/dist/vault/service.js +126 -121
  275. package/dist/version.d.ts +1 -1
  276. package/dist/version.js +1 -1
  277. package/package.json +25 -9
  278. package/dist/core/executors.d.ts +0 -44
  279. package/dist/core/executors.js +0 -95
  280. package/dist/store/memory.d.ts +0 -57
  281. package/dist/store/memory.js +0 -645
  282. package/dist/store/postgres/migrations/001_initial.d.ts +0 -27
  283. package/dist/store/postgres/migrations/001_initial.js +0 -196
  284. package/dist/store/postgres/migrations/002_session_owner.d.ts +0 -6
  285. package/dist/store/postgres/migrations/002_session_owner.js +0 -13
  286. package/dist/store/postgres/migrations/index.d.ts +0 -49
  287. package/dist/store/postgres/migrations/index.js +0 -76
  288. package/dist/store/postgres/names.d.ts +0 -11
  289. package/dist/store/postgres/names.js +0 -42
  290. package/dist/store/postgres/tenants.d.ts +0 -90
  291. package/dist/store/postgres/tenants.js +0 -214
  292. package/dist/streams/relay.d.ts +0 -37
  293. package/dist/streams/relay.js +0 -254
  294. package/dist/tenant/envelope.d.ts +0 -7
  295. package/dist/tenant/envelope.js +0 -22
  296. package/dist/tenant/live.d.ts +0 -92
  297. package/dist/tenant/live.js +0 -290
  298. package/dist/tenant/quarantine-error.d.ts +0 -6
  299. package/dist/tenant/quarantine-error.js +0 -9
  300. package/dist/tenant/quarantine.d.ts +0 -27
  301. package/dist/tenant/quarantine.js +0 -72
  302. package/dist/tenant/routes-tenant.d.ts +0 -12
  303. package/dist/tenant/routes-tenant.js +0 -137
  304. package/dist/tenant/routes.d.ts +0 -9
  305. package/dist/tenant/routes.js +0 -151
  306. package/dist/tenant/store-memory.d.ts +0 -16
  307. package/dist/tenant/store-memory.js +0 -111
  308. /package/dist/{store → record}/cursor.d.ts +0 -0
  309. /package/dist/{store → record}/cursor.js +0 -0
@@ -1,23 +1,27 @@
1
1
  import { mkdirSync } from "node:fs";
2
2
  import { resolveFlowLimits } from "../core/limits.js";
3
- import { loadExecutorRegistry } from "../core/executors.js";
4
- import { scriptedModel, gatewayModel, toolFixtureModel, } from "../core/provider.js";
3
+ import { scriptedModel, toolFixtureModel, } from "../core/provider.js";
4
+ import { tenantModelGate } from "../gates/in-process.js";
5
5
  import { createKekFile, readVaultKek } from "../vault/kek.js";
6
+ import { SigningKeys } from "./signing-keys.js";
6
7
  import { VaultService } from "../vault/service.js";
7
8
  import { McpPool } from "../mcp/pool.js";
8
9
  import { SandboxManager } from "../sandbox/manager.js";
9
10
  import { defaultSandboxBackends } from "../sandbox/select.js";
10
11
  import { MemoryExecution } from "../execution/memory.js";
11
- import { QuarantineError } from "./quarantine-error.js";
12
+ import { openError } from "./cause.js";
12
13
  import { MemoryStreams } from "../streams/memory.js";
13
- import { connectedExecutorCount, createLiveHub, endAllStreams, } from "./live.js";
14
- import { closeStreams, drainOutbox, streamsStatus, wireStreams, } from "./streams.js";
14
+ import { createSessionStreams, endAllStreams, } from "./session-streams.js";
15
+ import { closeStreams, streamsStatus, wireStreams, } from "./streams.js";
15
16
  import { abortAll, abortLocal, createWorkState, drain, waitForIdle, } from "./scheduler.js";
16
17
  import { advance } from "./advance.js";
17
18
  import { sweep } from "./sweep.js";
19
+ import { deliverAction } from "./delivery.js";
18
20
  import { TenantWorkers, WORKER_ID, } from "./worker.js";
19
21
  import { authorize } from "./effects.js";
20
- import { handle } from "./routes.js";
22
+ import { inProcessToolGate } from "../gates/tool-gate.js";
23
+ import { inProcessKeys } from "../keys/keys.js";
24
+ import { tenantApi } from "../api/http/app.js";
21
25
  /** Default ownership lease of an advance; the heartbeat renews it every third. */
22
26
  const DEFAULT_OWNER_LEASE_MS = 30_000;
23
27
  function validateConfig(config) {
@@ -66,24 +70,32 @@ export class TenantRuntime {
66
70
  let wired;
67
71
  let detach;
68
72
  try {
69
- let kek = readVaultKek({
70
- vaultKek: hooks.vaultKek,
71
- vaultKekPath: paths.kek,
72
- });
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");
73
+ // With the keys service (F4.2) the key lives in the gateway: this process never reads,
74
+ // creates or holds it, and the gateway reports a missing key on its readiness.
75
+ let kek = hooks.keys
76
+ ? undefined
77
+ : readVaultKek({
78
+ vaultKek: hooks.vaultKek,
79
+ vaultKekPath: paths.kek,
80
+ });
81
+ if (!hooks.keys) {
82
+ const sealed = await store.tx(async (t) => (await t.countCredentials()) + (await t.countSigningKeys()));
83
+ if (sealed > 0 && !kek) {
84
+ throw openError("kek-missing", "Vault key-encryption key is missing for ciphertext (vault credentials or signing keys) in this Tenant");
85
+ }
75
86
  }
76
87
  const createKekIfMissing = hooks.createKekIfMissing !== false;
77
88
  const ensureKek = () => {
89
+ if (hooks.keys)
90
+ throw new Error("The vault key lives in the gateway's keys service: this process never reads it");
78
91
  if (kek)
79
92
  return kek;
80
93
  if (!createKekIfMissing)
81
- throw new QuarantineError("kek-missing", "Vault key-encryption key is required", "restore the vault-kek file in the Tenant directory");
94
+ throw openError("kek-missing", "Vault key-encryption key is required");
82
95
  kek = createKekFile(paths.kek);
83
96
  return kek;
84
97
  };
85
98
  const opened = store;
86
- const registry = await loadExecutorRegistry(opened);
87
99
  // A seeded Tenant setting wins over the Host default (A18).
88
100
  const seededBackend = await opened.tx((t) => t.getSetting("sandbox.backend"));
89
101
  const vault = new VaultService({
@@ -93,10 +105,14 @@ export class TenantRuntime {
93
105
  });
94
106
  // `ctx` is assigned below; these callbacks only run once the Tenant is open.
95
107
  let ctx;
108
+ const toolGate = hooks.toolGate ?? inProcessToolGate(config.delivery ?? {});
109
+ const signingKeys = new SigningKeys({ tenantId: config.tenantId, kek: ensureKek });
110
+ const keys = hooks.keys ?? inProcessKeys({ store: opened, vault, signingKeys, kek: ensureKek });
96
111
  const mcp = new McpPool({
97
112
  pluginData: paths.pluginData,
98
113
  childEnv: config.childEnv,
99
114
  authorize: (sessionId, request) => authorize(ctx, sessionId, request),
115
+ ...(toolGate.openMcp ? { openRemote: (server) => toolGate.openMcp(server) } : {}),
100
116
  });
101
117
  const ephemeral = config.mode === "ephemeral";
102
118
  const sandbox = new SandboxManager({
@@ -125,23 +141,19 @@ export class TenantRuntime {
125
141
  modelProvider = scriptedModel(config.model.output);
126
142
  else if (config.model.kind === "fixture")
127
143
  modelProvider = toolFixtureModel();
128
- else if (config.model.kind === "gateway") {
129
- const gateway = config.model;
130
- modelProvider = async (effect, signal) => {
131
- const secret = await vault.readHostModel();
132
- const token = typeof secret?.credential.key === "string"
133
- ? secret.credential.key
134
- : "";
135
- return gatewayModel({
136
- url: gateway.url,
137
- model: gateway.model,
138
- token,
139
- })(effect, signal);
140
- };
141
- }
142
144
  else
143
145
  modelProvider = scriptedModel();
144
- const live = createLiveHub();
146
+ // With the gates service the loop never reads a model credential: only a Runtime
147
+ // without one (embedding, the ephemeral Runtime, tests) reads it here.
148
+ const modelGate = hooks.modelGate ??
149
+ tenantModelGate({
150
+ store: opened,
151
+ kek: ensureKek,
152
+ root: paths.home,
153
+ logger: config.logger,
154
+ ...(config.modelCall ? { settings: config.modelCall } : {}),
155
+ });
156
+ const sessionStreams = createSessionStreams();
145
157
  const local = hooks.execution
146
158
  ? undefined
147
159
  : new MemoryExecution({
@@ -160,16 +172,19 @@ export class TenantRuntime {
160
172
  envelope,
161
173
  store: opened,
162
174
  vault,
163
- registry,
164
175
  mcp,
165
176
  sandbox,
166
177
  flowLimits,
167
178
  modelProvider,
168
179
  useVaultModel,
180
+ modelGate,
181
+ toolGate,
169
182
  closing: false,
170
183
  closed: false,
171
184
  work: createWorkState(),
172
- live,
185
+ sessionStreams,
186
+ signingKeys,
187
+ keys,
173
188
  workerId: hooks.workerId ?? WORKER_ID,
174
189
  ownerLeaseMs: config.ownerLeaseMs ?? DEFAULT_OWNER_LEASE_MS,
175
190
  wake: async (sessionId, wake) => {
@@ -177,7 +192,12 @@ export class TenantRuntime {
177
192
  return;
178
193
  await execution.wake(config.tenantId, sessionId, wake);
179
194
  },
180
- abortLocal: (sessionId) => abortLocal(ctx, sessionId),
195
+ abortLocal: (sessionId, turnId) => abortLocal(ctx, sessionId, "cancel", turnId),
196
+ deliver: async (actionId) => {
197
+ if (ctx.closing || ctx.closed)
198
+ return;
199
+ await execution.deliver(config.tenantId, actionId);
200
+ },
181
201
  ...(hooks.execution?.stuckInvocations
182
202
  ? {
183
203
  stuckInvocations: () => hooks.execution.stuckInvocations(config.tenantId),
@@ -194,21 +214,15 @@ export class TenantRuntime {
194
214
  streams: hooks.streams ?? new MemoryStreams(),
195
215
  ownsStreams: !hooks.streams,
196
216
  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);
217
+ ...(hooks.hostRelay ? { hostRelay: true } : {}),
218
+ ...(hooks.retireGraceMs !== undefined ? { retireGraceMs: hooks.retireGraceMs } : {}),
201
219
  });
202
220
  // Register the handlers, then arm the sweep: its first pass runs at once and re-wakes
203
221
  // sessions a previous process left runnable or running (takeover handles the rest).
204
- let afterOpen = true;
205
222
  const worker = {
206
223
  advance: (sessionId, signal) => advance(ctx, sessionId, signal),
207
- sweep: async () => {
208
- const first = afterOpen;
209
- afterOpen = false;
210
- await sweep(ctx, { afterOpen: first });
211
- },
224
+ deliver: (actionId, signal) => deliverAction(ctx, actionId, signal),
225
+ sweep: () => sweep(ctx),
212
226
  };
213
227
  const unregister = workers.register(config.tenantId, worker);
214
228
  detach = async () => {
@@ -227,28 +241,21 @@ export class TenantRuntime {
227
241
  throw error;
228
242
  }
229
243
  }
230
- handle(request, response, url) {
231
- return handle(this.ctx, request, response, url);
244
+ async fetch(request, node) {
245
+ return await tenantApi().fetch(request, { ...node, tenant: this.ctx });
232
246
  }
233
247
  authorize(sessionId, request) {
234
248
  return authorize(this.ctx, sessionId, request);
235
249
  }
236
250
  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
- }));
251
+ const { store } = this.ctx;
252
+ const counts = await store.tx((t) => t.counts());
242
253
  return {
243
254
  ready: !this.ctx.closing && !this.ctx.closed,
244
255
  runningSessions: counts.runningSessions,
245
- connectedExecutors: connectedExecutorCount(live),
256
+ inFlightDeliveries: [...this.ctx.work.deliveries.values()].reduce((n, set) => n + set.size, 0),
246
257
  pendingActions: counts.pendingActions,
247
258
  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
259
  };
253
260
  }
254
261
  drain(activeWork, timeoutMs = 30_000) {
@@ -263,7 +270,7 @@ export class TenantRuntime {
263
270
  }
264
271
  /**
265
272
  * 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`).
273
+ * basin, its generation and the Tenant's relay (`streamsStatus` in `tenant/streams.ts`).
267
274
  */
268
275
  streamsStatus() {
269
276
  return streamsStatus(this.ctx);
@@ -278,15 +285,39 @@ export class TenantRuntime {
278
285
  // A shutdown abort: the advances leave their sessions to the next advance, unsettled.
279
286
  abortAll(ctx, "shutdown");
280
287
  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();
288
+ // Every step runs even when one before it fails, so nothing is left running; the first
289
+ // error is rethrown at the end.
290
+ const steps = [
291
+ ["detach", () => this.detach()],
292
+ ["mcp", () => ctx.mcp.close()],
293
+ ["session streams", () => endAllStreams(ctx.sessionStreams)],
294
+ // Bounded: an advance that ignores its abort is abandoned; its lease lapses (§11.4).
295
+ ["idle", () => waitForIdle(ctx, Math.max(0, idleBy - Date.now()))],
296
+ ["sandbox", () => ctx.sandbox.close()],
297
+ [
298
+ "streams",
299
+ () => {
300
+ ctx.closed = true;
301
+ return closeStreams(ctx);
302
+ },
303
+ ],
304
+ ["store", () => ctx.store.close()],
305
+ ];
306
+ let failure;
307
+ for (const [step, run] of steps) {
308
+ try {
309
+ await run();
310
+ }
311
+ catch (error) {
312
+ ctx.config.logger.warn("tenant close step failed", {
313
+ step,
314
+ message: error instanceof Error ? error.message : String(error),
315
+ });
316
+ failure ??= { error };
317
+ }
318
+ }
319
+ if (failure)
320
+ throw failure.error;
290
321
  }
291
322
  }
292
323
  /**
@@ -3,13 +3,20 @@ 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
+ /** The turn each running advance belongs to, by session id (`null`: none). */
7
+ readonly runningTurns: Map<string, string | null>;
8
+ /** Deliveries to Action endpoints in flight on this process, by their Action's session id. */
9
+ readonly deliveries: Map<string, Set<AbortController>>;
10
+ /** When each Action last reported a failed delivery (`action.delivery_failed` throttling). */
11
+ readonly deliveryNotices: Map<string, number>;
6
12
  }
7
13
  export declare function createWorkState(): WorkState;
8
14
  /**
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.
15
+ * Abort the advance of `id` if it runs on this process, and its deliveries to Action endpoints.
16
+ * Cancel calls it after committing `cancelled`, and so does the control stream for cancels made
17
+ * elsewhere. With `turnId`, an advance of another turn keeps running: the signal came late.
11
18
  */
12
- export declare function abortLocal(ctx: TenantContext, id: string, kind?: AdvanceAbortKind): void;
19
+ export declare function abortLocal(ctx: TenantContext, id: string, kind?: AdvanceAbortKind, turnId?: string): void;
13
20
  /** Reset: abort every advance running on this process; their sessions are being cleared. */
14
21
  export declare function clearWork(ctx: TenantContext): void;
15
22
  /** Stop new advances; cancel active turns if asked; wait for running advances until the timeout. */
@@ -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(), runningTurns: 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. With `turnId`, an advance of another turn keeps running: the signal came late.
31
32
  */
32
- export function abortLocal(ctx, id, kind = "cancel") {
33
+ export function abortLocal(ctx, id, kind = "cancel", turnId) {
33
34
  const controller = ctx.work.running.get(id);
34
- if (controller)
35
+ if (controller && (turnId === undefined || ctx.work.runningTurns.get(id) === turnId))
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,127 @@
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
+ /** Internal events (`transcript.updated`) are recorded and streamed but never served. */
109
+ export declare function served(event: LiveEvent): boolean;
110
+ /**
111
+ * Ends every feed in this process whose session is gone or whose Tenant moved to a new basin
112
+ * generation, and moves this process to the Tenant's current generation. Run on a
113
+ * `sessions.reset` signal and periodically, so feeds on nodes other than the one that reset
114
+ * the Tenant end too.
115
+ */
116
+ export declare function checkSessionStreams(ctx: TenantContext): Promise<void>;
117
+ /**
118
+ * Ends the streams of `subject` opened with a token older than `epoch` (a revocation), on
119
+ * this process. Other processes do the same on the `subject.revoked` signal.
120
+ */
121
+ export declare function endSubjectStreams(hub: SessionStreams, subject: string, epoch: number): void;
122
+ /** Reset or close: end every session observer and stop every shared read. */
123
+ export declare function clearObservers(hub: SessionStreams): void;
124
+ /** Close: end every session observer. */
125
+ export declare function endAllStreams(hub: SessionStreams): void;
126
+ export declare function sleep(ms: number, signal: AbortSignal): Promise<void>;
127
+ export {};