@nylorun/runtime 0.16.0-beta → 0.19.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 (256) hide show
  1. package/CHANGELOG.md +207 -0
  2. package/README.md +105 -63
  3. package/dist/adapters/execution/restate.d.ts +1 -3
  4. package/dist/adapters/execution/restate.js +4 -31
  5. package/dist/adapters/replication/pgoutput.js +1 -1
  6. package/dist/adapters/sandbox/local.js +1 -1
  7. package/dist/adapters/sandbox/virtual.js +1 -1
  8. package/dist/api/ag-ui/endpoint.js +1 -2
  9. package/dist/api/ag-ui/routes.d.ts +1 -1
  10. package/dist/api/ag-ui/routes.js +11 -13
  11. package/dist/api/ag-ui/translate.d.ts +1 -3
  12. package/dist/api/ag-ui/translate.js +2 -17
  13. package/dist/api/components.d.ts +4783 -2588
  14. package/dist/api/components.js +19 -21
  15. package/dist/api/gate/routes.d.ts +7 -8
  16. package/dist/api/gate/routes.js +58 -52
  17. package/dist/api/http/app.d.ts +6 -0
  18. package/dist/api/http/app.js +20 -14
  19. package/dist/api/http/define.d.ts +22 -13
  20. package/dist/api/http/define.js +43 -39
  21. package/dist/api/http/routes/access.d.ts +3 -19
  22. package/dist/api/http/routes/access.js +25 -184
  23. package/dist/api/http/routes/artifacts.js +3 -7
  24. package/dist/api/http/routes/files.d.ts +8 -0
  25. package/dist/api/http/routes/files.js +77 -0
  26. package/dist/api/http/routes/keys.d.ts +10 -0
  27. package/dist/api/http/routes/keys.js +64 -0
  28. package/dist/api/http/routes/me.d.ts +13 -0
  29. package/dist/api/http/routes/me.js +48 -0
  30. package/dist/api/http/routes/reads.d.ts +66 -0
  31. package/dist/api/http/routes/reads.js +127 -0
  32. package/dist/api/http/routes/sandboxes.d.ts +2 -2
  33. package/dist/api/http/routes/sandboxes.js +8 -7
  34. package/dist/api/http/routes/sessions.js +31 -20
  35. package/dist/api/http/routes/tenant.js +15 -18
  36. package/dist/api/http/routes/vaults.d.ts +13 -3
  37. package/dist/api/http/routes/vaults.js +136 -64
  38. package/dist/api/openapi.d.ts +10 -11
  39. package/dist/api/openapi.js +292 -68
  40. package/dist/artifacts/links.js +6 -6
  41. package/dist/artifacts/service.js +1 -1
  42. package/dist/core/flow-host.d.ts +44 -23
  43. package/dist/core/flow-host.js +88 -106
  44. package/dist/core/provider.d.ts +4 -1
  45. package/dist/core/provider.js +6 -1
  46. package/dist/core/sandbox-routes.d.ts +0 -9
  47. package/dist/core/sandbox-routes.js +2 -21
  48. package/dist/execution/memory.d.ts +4 -5
  49. package/dist/execution/memory.js +11 -25
  50. package/dist/execution/types.d.ts +3 -33
  51. package/dist/execution/types.js +2 -12
  52. package/dist/gates/http-tool.d.ts +86 -0
  53. package/dist/gates/http-tool.js +197 -0
  54. package/dist/gates/mcp-handler.d.ts +3 -0
  55. package/dist/gates/mcp-handler.js +6 -4
  56. package/dist/gates/meter.d.ts +2 -2
  57. package/dist/gates/meter.js +6 -0
  58. package/dist/gates/tenant-vaults.d.ts +19 -14
  59. package/dist/gates/tenant-vaults.js +17 -12
  60. package/dist/gates/tool-calls.d.ts +7 -15
  61. package/dist/gates/tool-calls.js +4 -4
  62. package/dist/gates/tool-client.d.ts +1 -1
  63. package/dist/gates/tool-client.js +23 -47
  64. package/dist/gates/tool-contract.d.ts +27 -39
  65. package/dist/gates/tool-contract.js +30 -21
  66. package/dist/gates/tool-gate.d.ts +25 -24
  67. package/dist/gates/tool-gate.js +11 -7
  68. package/dist/harness/calls.d.ts +10 -4
  69. package/dist/harness/calls.js +15 -6
  70. package/dist/harness/executors.d.ts +1 -1
  71. package/dist/harness/executors.js +17 -12
  72. package/dist/harness/main.d.ts +2 -2
  73. package/dist/harness/main.js +5 -13
  74. package/dist/harness/service.d.ts +4 -5
  75. package/dist/harness/service.js +9 -2
  76. package/dist/harness-api/in-process.js +2 -2
  77. package/dist/harness-api/record.d.ts +6 -3
  78. package/dist/harness-api/record.js +65 -72
  79. package/dist/harness-api/server.d.ts +1 -9
  80. package/dist/harness-api/server.js +19 -17
  81. package/dist/harness-api/start.js +1 -5
  82. package/dist/harness-api/workspace.js +2 -3
  83. package/dist/harness-api/ws-server.js +1 -1
  84. package/dist/host/app.d.ts +2 -10
  85. package/dist/host/app.js +24 -44
  86. package/dist/host/config-for.d.ts +5 -5
  87. package/dist/host/config-for.js +2 -3
  88. package/dist/host/config.d.ts +0 -11
  89. package/dist/host/create-host.d.ts +3 -40
  90. package/dist/host/create-host.js +18 -91
  91. package/dist/host/environment.d.ts +0 -3
  92. package/dist/host/environment.js +0 -9
  93. package/dist/host/gates.d.ts +4 -1
  94. package/dist/host/gates.js +9 -3
  95. package/dist/host/http.d.ts +1 -2
  96. package/dist/host/http.js +1 -7
  97. package/dist/host/index.d.ts +2 -2
  98. package/dist/host/index.js +2 -2
  99. package/dist/host/main.js +55 -40
  100. package/dist/host/operate.d.ts +29 -0
  101. package/dist/host/operate.js +223 -0
  102. package/dist/host/stack-config.d.ts +23 -20
  103. package/dist/host/stack-config.js +28 -44
  104. package/dist/index.d.ts +4 -1
  105. package/dist/index.js +3 -1
  106. package/dist/infra/workers.d.ts +1 -1
  107. package/dist/keys/client.js +9 -1
  108. package/dist/keys/keys.d.ts +12 -2
  109. package/dist/keys/keys.js +10 -2
  110. package/dist/management-openapi.json +6491 -0
  111. package/dist/mcp/connect.d.ts +8 -4
  112. package/dist/mcp/connect.js +8 -27
  113. package/dist/mcp/pool.d.ts +6 -9
  114. package/dist/mcp/pool.js +8 -18
  115. package/dist/mcp/snapshot.d.ts +8 -2
  116. package/dist/mcp/snapshot.js +29 -2
  117. package/dist/media.d.ts +1 -1
  118. package/dist/media.js +1 -1
  119. package/dist/model/pi-model.js +3 -0
  120. package/dist/openapi.json +6228 -15615
  121. package/dist/reads/access.d.ts +4 -0
  122. package/dist/reads/access.js +12 -0
  123. package/dist/reads/cursor.d.ts +11 -0
  124. package/dist/reads/cursor.js +80 -0
  125. package/dist/reads/types.d.ts +25 -0
  126. package/dist/reads/types.js +1 -0
  127. package/dist/sandbox/manager.d.ts +16 -0
  128. package/dist/sandbox/manager.js +40 -0
  129. package/dist/sandbox/session-sandbox.js +13 -3
  130. package/dist/sandbox/skills.d.ts +13 -0
  131. package/dist/sandbox/skills.js +46 -0
  132. package/dist/sandbox/types.d.ts +2 -2
  133. package/dist/store/ownership.d.ts +9 -0
  134. package/dist/store/ownership.js +13 -0
  135. package/dist/store/postgres/connect.d.ts +2 -0
  136. package/dist/store/postgres/connect.js +29 -0
  137. package/dist/store/postgres/drizzle/0008_installation_vaults.sql +2 -0
  138. package/dist/store/postgres/drizzle/0009_oauth_pending.sql +19 -0
  139. package/dist/store/postgres/drizzle/0010_oss_auth_removals.sql +3 -0
  140. package/dist/store/postgres/drizzle/0011_key_roles.sql +3 -0
  141. package/dist/store/postgres/drizzle/0012_session_reads.sql +8 -0
  142. package/dist/store/postgres/drizzle/0013_definition_files.sql +16 -0
  143. package/dist/store/postgres/drizzle/0014_action_endpoints_removed.sql +2 -0
  144. package/dist/store/postgres/drizzle/meta/_journal.json +49 -0
  145. package/dist/store/postgres/reads.d.ts +3 -0
  146. package/dist/store/postgres/reads.js +272 -0
  147. package/dist/store/postgres/schema.d.ts +423 -594
  148. package/dist/store/postgres/schema.js +84 -101
  149. package/dist/store/postgres/store.js +96 -248
  150. package/dist/store/postgres/tenant.d.ts +34 -2
  151. package/dist/store/postgres/tenant.js +16 -32
  152. package/dist/store/types.d.ts +44 -119
  153. package/dist/store/types.js +0 -1
  154. package/dist/streams/control.d.ts +0 -7
  155. package/dist/streams/control.js +0 -13
  156. package/dist/streams/types.d.ts +2 -20
  157. package/dist/streams/types.js +1 -1
  158. package/dist/tenant/advance.d.ts +3 -3
  159. package/dist/tenant/advance.js +10 -7
  160. package/dist/tenant/auth.d.ts +24 -15
  161. package/dist/tenant/auth.js +44 -55
  162. package/dist/tenant/cause.d.ts +3 -3
  163. package/dist/tenant/cause.js +1 -1
  164. package/dist/tenant/commands.d.ts +2 -18
  165. package/dist/tenant/commands.js +38 -179
  166. package/dist/tenant/context.d.ts +33 -45
  167. package/dist/tenant/definition-files.d.ts +24 -0
  168. package/dist/tenant/definition-files.js +103 -0
  169. package/dist/tenant/effects.d.ts +20 -18
  170. package/dist/tenant/effects.js +59 -133
  171. package/dist/tenant/ephemeral.d.ts +21 -8
  172. package/dist/tenant/ephemeral.js +11 -12
  173. package/dist/tenant/host-token.js +5 -5
  174. package/dist/tenant/identity-file.d.ts +75 -0
  175. package/dist/tenant/identity-file.js +202 -0
  176. package/dist/tenant/issuers.d.ts +48 -0
  177. package/dist/tenant/issuers.js +311 -0
  178. package/dist/tenant/jwt.js +3 -3
  179. package/dist/tenant/module.d.ts +1 -4
  180. package/dist/tenant/module.js +3 -12
  181. package/dist/tenant/operator-keys.d.ts +44 -0
  182. package/dist/tenant/operator-keys.js +87 -0
  183. package/dist/tenant/outbound.d.ts +40 -1
  184. package/dist/tenant/outbound.js +167 -18
  185. package/dist/tenant/paths.d.ts +1 -1
  186. package/dist/tenant/paths.js +1 -3
  187. package/dist/tenant/principals.d.ts +13 -13
  188. package/dist/tenant/principals.js +35 -36
  189. package/dist/tenant/run-token.js +6 -6
  190. package/dist/tenant/runtime.d.ts +5 -3
  191. package/dist/tenant/runtime.js +29 -15
  192. package/dist/tenant/sandboxes.d.ts +9 -6
  193. package/dist/tenant/sandboxes.js +16 -5
  194. package/dist/tenant/scheduler.d.ts +1 -5
  195. package/dist/tenant/scheduler.js +2 -5
  196. package/dist/tenant/session-streams.d.ts +8 -11
  197. package/dist/tenant/session-streams.js +48 -32
  198. package/dist/tenant/session.d.ts +8 -41
  199. package/dist/tenant/session.js +0 -37
  200. package/dist/tenant/sessions.d.ts +2 -2
  201. package/dist/tenant/sessions.js +17 -9
  202. package/dist/tenant/signing-keys.d.ts +3 -2
  203. package/dist/tenant/signing-keys.js +5 -5
  204. package/dist/tenant/skill-tool.d.ts +20 -0
  205. package/dist/tenant/skill-tool.js +72 -0
  206. package/dist/tenant/slim.d.ts +1 -1
  207. package/dist/tenant/slim.js +1 -1
  208. package/dist/tenant/status.js +7 -12
  209. package/dist/tenant/store-pg.d.ts +15 -0
  210. package/dist/tenant/store-pg.js +7 -3
  211. package/dist/tenant/streams.d.ts +3 -11
  212. package/dist/tenant/streams.js +2 -29
  213. package/dist/tenant/sweep.js +0 -5
  214. package/dist/tenant/transcript.d.ts +2 -2
  215. package/dist/tenant/transcript.js +2 -2
  216. package/dist/tenant/types.d.ts +33 -19
  217. package/dist/tenant/worker.d.ts +1 -3
  218. package/dist/tenant/worker.js +3 -8
  219. package/dist/vault/crypto.d.ts +7 -0
  220. package/dist/vault/crypto.js +16 -0
  221. package/dist/vault/oauth.d.ts +55 -0
  222. package/dist/vault/oauth.js +186 -0
  223. package/dist/vault/service.d.ts +54 -7
  224. package/dist/vault/service.js +261 -17
  225. package/dist/vault/sources.d.ts +60 -0
  226. package/dist/vault/sources.js +196 -0
  227. package/dist/version.d.ts +1 -1
  228. package/dist/version.js +1 -1
  229. package/package.json +5 -4
  230. package/dist/admin-openapi.json +0 -604
  231. package/dist/api/http/routes/actions.d.ts +0 -8
  232. package/dist/api/http/routes/actions.js +0 -76
  233. package/dist/api/http/routes/endpoints.d.ts +0 -7
  234. package/dist/api/http/routes/endpoints.js +0 -100
  235. package/dist/host/admin-api.d.ts +0 -23
  236. package/dist/host/admin-api.js +0 -74
  237. package/dist/host/cors.d.ts +0 -27
  238. package/dist/host/cors.js +0 -64
  239. package/dist/mcp/stdio.d.ts +0 -17
  240. package/dist/mcp/stdio.js +0 -82
  241. package/dist/tenant/access-policy.d.ts +0 -30
  242. package/dist/tenant/access-policy.js +0 -52
  243. package/dist/tenant/actions.d.ts +0 -14
  244. package/dist/tenant/actions.js +0 -29
  245. package/dist/tenant/browser.d.ts +0 -15
  246. package/dist/tenant/browser.js +0 -39
  247. package/dist/tenant/delivery-token.d.ts +0 -41
  248. package/dist/tenant/delivery-token.js +0 -104
  249. package/dist/tenant/delivery.d.ts +0 -62
  250. package/dist/tenant/delivery.js +0 -481
  251. package/dist/tenant/endpoints.d.ts +0 -67
  252. package/dist/tenant/endpoints.js +0 -90
  253. package/dist/tenant/subject-limits.d.ts +0 -16
  254. package/dist/tenant/subject-limits.js +0 -45
  255. package/dist/tenant/tokens.d.ts +0 -14
  256. package/dist/tenant/tokens.js +0 -187
@@ -5,6 +5,7 @@ import { tenantModelGate } from "../gates/in-process.js";
5
5
  import { createKekFile, readVaultKek } from "../vault/kek.js";
6
6
  import { SigningKeys } from "./signing-keys.js";
7
7
  import { VaultService } from "../vault/service.js";
8
+ import { CredentialSources } from "../vault/sources.js";
8
9
  import { McpPool } from "../mcp/pool.js";
9
10
  import { SandboxManager } from "../sandbox/manager.js";
10
11
  import { storeSandboxRecords } from "../sandbox/records.js";
@@ -17,10 +18,10 @@ import { closeStreams, streamsStatus, wireStreams, } from "./streams.js";
17
18
  import { abortAll, abortLocal, createWorkState, drain, waitForIdle, } from "./scheduler.js";
18
19
  import { advance } from "./advance.js";
19
20
  import { sweep } from "./sweep.js";
20
- import { deliverAction } from "./delivery.js";
21
21
  import { TenantWorkers, WORKER_ID, } from "./worker.js";
22
22
  import { authorize } from "./effects.js";
23
23
  import { inProcessToolGate } from "../gates/tool-gate.js";
24
+ import { guardedFetch } from "./outbound.js";
24
25
  import { inProcessKeys } from "../keys/keys.js";
25
26
  import { tenantApi } from "../api/http/app.js";
26
27
  import { createFsBlobStore } from "../blob/index.js";
@@ -31,6 +32,7 @@ import { localWorkspace, remoteWorkspace, withPodWorkspaces, } from "../harness-
31
32
  import { hostAuthority } from "../sandbox/join.js";
32
33
  import { reconcileSandbox } from "../sandbox/pods/reconcile.js";
33
34
  import { sandboxWorkspaceReader } from "../artifacts/workspace.js";
35
+ import { readDefinitionFile } from "./definition-files.js";
34
36
  /** Default ownership lease of an advance; the heartbeat renews it every third. */
35
37
  const DEFAULT_OWNER_LEASE_MS = 30_000;
36
38
  function validateConfig(config) {
@@ -79,9 +81,7 @@ export class TenantRuntime {
79
81
  const paths = config.paths;
80
82
  mkdirSync(paths.root, { recursive: true });
81
83
  mkdirSync(paths.home, { recursive: true });
82
- mkdirSync(paths.tmp, { recursive: true });
83
84
  mkdirSync(paths.sandboxes, { recursive: true });
84
- mkdirSync(paths.pluginData, { recursive: true });
85
85
  mkdirSync(paths.logs, { recursive: true });
86
86
  const store = hooks.store;
87
87
  let wired;
@@ -119,11 +119,21 @@ export class TenantRuntime {
119
119
  const vault = new VaultService({
120
120
  store: opened,
121
121
  kek: ensureKek,
122
- fetch: config.vaultFetch ?? globalThis.fetch,
122
+ // OAuth refresh and connect follow the Host's address policy (F9 C2).
123
+ fetch: config.vaultFetch ?? guardedFetch(config.delivery ?? {}),
124
+ });
125
+ const credentials = new CredentialSources({
126
+ vault,
127
+ ...(config.resolver ? { resolver: config.resolver } : {}),
123
128
  });
124
129
  // `ctx` is assigned below; these callbacks only run once the Tenant is open.
125
130
  let ctx;
126
- const toolGate = hooks.toolGate ?? inProcessToolGate(config.delivery ?? {});
131
+ const toolGate = hooks.toolGate ??
132
+ inProcessToolGate(config.delivery ?? {}, {
133
+ session: (sessionId) => opened.tx((t) => t.get("sessions", sessionId)),
134
+ authorize: (sessionId, request) => authorize(ctx, sessionId, request),
135
+ logger: config.logger,
136
+ });
127
137
  const signingKeys = new SigningKeys({ tenantId: config.tenantId, kek: ensureKek });
128
138
  const keys = hooks.keys ?? inProcessKeys({ store: opened, vault, signingKeys, kek: ensureKek });
129
139
  const harnessMode = hooks.harness ?? "memory";
@@ -133,9 +143,8 @@ export class TenantRuntime {
133
143
  // executors; otherwise the harness holds both (F6.2).
134
144
  const mcp = inProcess
135
145
  ? new McpPool({
136
- pluginData: paths.pluginData,
137
- childEnv: config.childEnv,
138
146
  authorize: (sessionId, request) => authorize(ctx, sessionId, request),
147
+ policy: config.delivery ?? {},
139
148
  ...(toolGate.openMcp ? { openRemote: (server) => toolGate.openMcp(server) } : {}),
140
149
  })
141
150
  : undefined;
@@ -148,6 +157,12 @@ export class TenantRuntime {
148
157
  defaultSandboxBackends({ root: paths.sandboxes }),
149
158
  preference,
150
159
  ephemeral,
160
+ definitionFile: async (sha256) => {
161
+ const bytes = await readDefinitionFile(blobs, sha256);
162
+ if (!bytes)
163
+ throw new Error(`The Object store has no bytes for ${sha256}`);
164
+ return bytes;
165
+ },
151
166
  emit: async (sessionId, turnId, type, payload) => {
152
167
  if (ctx.closed)
153
168
  return;
@@ -222,8 +237,10 @@ export class TenantRuntime {
222
237
  ctx = {
223
238
  config,
224
239
  envelope,
240
+ reads: hooks.reads,
225
241
  store: opened,
226
242
  vault,
243
+ credentials,
227
244
  ...(mcp ? { mcp } : {}),
228
245
  sandbox,
229
246
  flowLimits,
@@ -251,11 +268,6 @@ export class TenantRuntime {
251
268
  await execution.wake(config.tenantId, sessionId, wake);
252
269
  },
253
270
  abortLocal: (sessionId, turnId) => abortLocal(ctx, sessionId, "cancel", turnId),
254
- deliver: async (actionId) => {
255
- if (ctx.closing || ctx.closed)
256
- return;
257
- await execution.deliver(config.tenantId, actionId);
258
- },
259
271
  ...(hooks.pods ? { pods: hooks.pods } : {}),
260
272
  sandboxSignal: async (sandboxId, signal) => {
261
273
  if (ctx.closing || ctx.closed || !execution.sandbox)
@@ -295,7 +307,6 @@ export class TenantRuntime {
295
307
  // sessions a previous process left runnable or running (takeover handles the rest).
296
308
  const worker = {
297
309
  advance: (sessionId, signal) => advance(ctx, sessionId, signal),
298
- deliver: (actionId, signal) => deliverAction(ctx, actionId, signal),
299
310
  sweep: () => sweep(ctx),
300
311
  sandbox: (sandboxId, trigger) => reconcileSandbox(ctx, sandboxId, trigger),
301
312
  };
@@ -313,6 +324,7 @@ export class TenantRuntime {
313
324
  await detach?.().catch(() => undefined);
314
325
  await harness?.stop().catch(() => undefined);
315
326
  await wired?.close().catch(() => undefined);
327
+ await hooks.reads?.close().catch(() => undefined);
316
328
  await store.close().catch(() => undefined);
317
329
  throw error;
318
330
  }
@@ -329,8 +341,6 @@ export class TenantRuntime {
329
341
  return {
330
342
  ready: !this.ctx.closing && !this.ctx.closed,
331
343
  runningSessions: counts.runningSessions,
332
- inFlightDeliveries: [...this.ctx.work.deliveries.values()].reduce((n, set) => n + set.size, 0),
333
- pendingActions: counts.pendingActions,
334
344
  uncertainEffects: counts.uncertainEffects,
335
345
  harness: harnessStatusOf(this.ctx),
336
346
  };
@@ -359,6 +369,9 @@ export class TenantRuntime {
359
369
  attachHarness(channel, peer) {
360
370
  return this.ctx.harness.attach(channel, peer);
361
371
  }
372
+ harnessStatus() {
373
+ return harnessStatusOf(this.ctx);
374
+ }
362
375
  async close() {
363
376
  const ctx = this.ctx;
364
377
  ctx.closing = true;
@@ -389,6 +402,7 @@ export class TenantRuntime {
389
402
  return closeStreams(ctx);
390
403
  },
391
404
  ],
405
+ ["reads", () => ctx.reads?.close()],
392
406
  ["store", () => ctx.store.close()],
393
407
  ];
394
408
  let failure;
@@ -7,9 +7,9 @@
7
7
  * sandbox's id, kind, spec and labels, and the session body names the sandbox it is attached
8
8
  * to (`Session.sandboxId`); deleting a session (a sessions reset) only detaches it.
9
9
  * - **Access.** An application key reaches every sandbox, and so does an application key acting
10
- * for a subject (`Nylorun-Subject`), which needs `sandboxes:write` to change one. A subject
11
- * token reaches only the ids its `sbx` grants match, and changes them only with
12
- * `sandboxes:write`; any other id is the 404 of a missing one.
10
+ * for a subject (`Nylorun-Subject`), which needs `sandboxes:write` to change one. A token
11
+ * caller (a trusted issuer's token) reaches only the ids its grants match, and changes them
12
+ * only with `sandboxes:write`; any other id is the 404 of a missing one.
13
13
  * - **Serial turns.** One turn at a time per sandbox: a session whose sandbox another attached
14
14
  * session's turn holds is refused (`409 sandbox_busy`), as the session sandbox route is
15
15
  * refused while its session's turn runs. Commands within the workspace keep queueing in the
@@ -30,8 +30,9 @@
30
30
  */
31
31
  import { type PutSandboxRequest, type SandboxView } from "@nylorun/core/contracts";
32
32
  import type { SandboxResource, Tx } from "../store/types.js";
33
- import type { AuthScope, Session, TenantContext } from "./context.js";
34
- /** The `sbx` grants that limit `scope`, or undefined when it reaches every sandbox. */
33
+ import type { SandboxRouteDeps } from "../core/sandbox-routes.js";
34
+ import { type AuthScope, type Session, type TenantContext } from "./context.js";
35
+ /** The sandbox grants that limit `scope`, or undefined when it reaches every sandbox. */
35
36
  export declare function sandboxGrantsOf(scope: AuthScope): readonly string[] | undefined;
36
37
  /** A sandbox resource, unless it is gone or being deleted. */
37
38
  export declare function liveSandbox(t: Tx, id: string, options?: {
@@ -95,7 +96,7 @@ export declare function attachSandbox(t: Tx, id: string, grants: readonly string
95
96
  export declare function recordAttachment(t: Tx, sessionId: string, sandboxId: string): Promise<void>;
96
97
  /**
97
98
  * Checks a turn start on a session attached to a sandbox, in the command's transaction after
98
- * the session row lock: the sandbox still exists, a subject token's `sbx` grants reach it, and
99
+ * the session row lock: the sandbox still exists, a token caller's grants reach it, and
99
100
  * no other session attached to it has a turn running. Holds the sandbox row's lock until the
100
101
  * transaction ends, so two turn starts on one sandbox are decided one after the other.
101
102
  *
@@ -117,3 +118,5 @@ export declare function touchPodSandbox(ctx: TenantContext, id: string): Promise
117
118
  * timer is set). Best effort.
118
119
  */
119
120
  export declare function rearmPodTimers(ctx: TenantContext): Promise<void>;
121
+ /** Session lookups for the sandbox route handlers (`core/sandbox-routes.ts`). */
122
+ export declare function sandboxRouteDeps(ctx: TenantContext): SandboxRouteDeps;
@@ -7,9 +7,9 @@
7
7
  * sandbox's id, kind, spec and labels, and the session body names the sandbox it is attached
8
8
  * to (`Session.sandboxId`); deleting a session (a sessions reset) only detaches it.
9
9
  * - **Access.** An application key reaches every sandbox, and so does an application key acting
10
- * for a subject (`Nylorun-Subject`), which needs `sandboxes:write` to change one. A subject
11
- * token reaches only the ids its `sbx` grants match, and changes them only with
12
- * `sandboxes:write`; any other id is the 404 of a missing one.
10
+ * for a subject (`Nylorun-Subject`), which needs `sandboxes:write` to change one. A token
11
+ * caller (a trusted issuer's token) reaches only the ids its grants match, and changes them
12
+ * only with `sandboxes:write`; any other id is the 404 of a missing one.
13
13
  * - **Serial turns.** One turn at a time per sandbox: a session whose sandbox another attached
14
14
  * session's turn holds is refused (`409 sandbox_busy`), as the session sandbox route is
15
15
  * refused while its session's turn runs. Commands within the workspace keep queueing in the
@@ -37,8 +37,9 @@ import { podName } from "../sandbox/pods/name.js";
37
37
  import { expiresAtOf, podLifecycleConfig } from "../sandbox/pods/spec.js";
38
38
  import { requirePods } from "../sandbox/placement.js";
39
39
  import { parseSandboxDuration } from "@nylorun/core/define";
40
+ import { sandboxLookup, sessionOf } from "./context.js";
40
41
  import { fail } from "./http.js";
41
- /** The `sbx` grants that limit `scope`, or undefined when it reaches every sandbox. */
42
+ /** The sandbox grants that limit `scope`, or undefined when it reaches every sandbox. */
42
43
  export function sandboxGrantsOf(scope) {
43
44
  return scope.kind === "token" ? (scope.sandboxes ?? []) : undefined;
44
45
  }
@@ -435,7 +436,7 @@ export async function recordAttachment(t, sessionId, sandboxId) {
435
436
  }
436
437
  /**
437
438
  * Checks a turn start on a session attached to a sandbox, in the command's transaction after
438
- * the session row lock: the sandbox still exists, a subject token's `sbx` grants reach it, and
439
+ * the session row lock: the sandbox still exists, a token caller's grants reach it, and
439
440
  * no other session attached to it has a turn running. Holds the sandbox row's lock until the
440
441
  * transaction ends, so two turn starts on one sandbox are decided one after the other.
441
442
  *
@@ -549,3 +550,13 @@ export async function rearmPodTimers(ctx) {
549
550
  });
550
551
  }
551
552
  }
553
+ /** Session lookups for the sandbox route handlers (`core/sandbox-routes.ts`). */
554
+ export function sandboxRouteDeps(ctx) {
555
+ return {
556
+ sandbox: ctx.sandbox,
557
+ session: (id) => ctx.store.tx(async (t) => {
558
+ const session = await sessionOf(t, id);
559
+ return { session, lookup: await sandboxLookup(t, id) };
560
+ }),
561
+ };
562
+ }
@@ -5,14 +5,10 @@ export interface WorkState {
5
5
  readonly running: Map<string, AbortController>;
6
6
  /** The turn each running advance belongs to, by session id (`null`: none). */
7
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>;
12
8
  }
13
9
  export declare function createWorkState(): WorkState;
14
10
  /**
15
- * Abort the advance of `id` if it runs on this process, and its deliveries to Action endpoints.
11
+ * Abort the advance of `id` if it runs on this process.
16
12
  * Cancel calls it after committing `cancelled`, and so does the control stream for cancels made
17
13
  * elsewhere. With `turnId`, an advance of another turn keeps running: the signal came late.
18
14
  */
@@ -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(), runningTurns: new Map(), deliveries: new Map(), deliveryNotices: new Map() };
16
+ return { running: new Map(), runningTurns: new Map() };
17
17
  }
18
18
  const MESSAGES = {
19
19
  cancel: "Turn cancelled",
@@ -26,7 +26,7 @@ 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, and its deliveries to Action endpoints.
29
+ * Abort the advance of `id` if it runs on this process.
30
30
  * Cancel calls it after committing `cancelled`, and so does the control stream for cancels made
31
31
  * elsewhere. With `turnId`, an advance of another turn keeps running: the signal came late.
32
32
  */
@@ -34,9 +34,6 @@ export function abortLocal(ctx, id, kind = "cancel", turnId) {
34
34
  const controller = ctx.work.running.get(id);
35
35
  if (controller && (turnId === undefined || ctx.work.runningTurns.get(id) === turnId))
36
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);
40
37
  }
41
38
  /** Reset: abort every advance running on this process; their sessions are being cleared. */
42
39
  export function clearWork(ctx) {
@@ -20,23 +20,24 @@
20
20
  */
21
21
  import type { IncomingMessage, ServerResponse } from "node:http";
22
22
  import type { LiveEvent } from "@nylorun/core/contracts";
23
- import type { TenantContext } from "./context.js";
23
+ import type { AuthScope, TenantContext } from "./context.js";
24
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";
25
+ /** Why the Runtime ended a stream a token opened. */
26
+ export type StreamEndReason = "token_expired";
27
27
  /** Where an observer's events go: an SSE response, or an in-process reader. */
28
28
  export interface ObserverSink {
29
29
  write(event: LiveEvent): void;
30
30
  /** Ends the stream; with a reason, the client is told why before it ends. */
31
31
  end(reason?: StreamEndReason): void;
32
32
  }
33
- /** The subject token a stream was opened with: it ends at expiry or revocation. */
33
+ /** The token a stream was opened with (a trusted issuer's): the stream ends at its expiry. */
34
34
  export interface StreamHolder {
35
35
  readonly subject: string;
36
- readonly epoch: number;
37
36
  /** Epoch ms when the token expires. */
38
37
  readonly expiresAt: number;
39
38
  }
39
+ /** The holder a stream opened with `scope` records: token callers only. */
40
+ export declare function streamHolderOf(scope: AuthScope): StreamHolder | undefined;
40
41
  /** One client of a session: the next sequence it needs. */
41
42
  interface Observer {
42
43
  readonly sink: ObserverSink;
@@ -78,9 +79,10 @@ export declare function currentBasin(ctx: TenantContext): string;
78
79
  * read starts. The cursor returned is the last record read (the request's cursor when none
79
80
  * was), whatever the `agent` filter kept.
80
81
  */
81
- export declare function readHistory(ctx: TenantContext, sessionId: string, cursor: string | undefined, agent: string | undefined): Promise<{
82
+ export declare function readHistory(ctx: TenantContext, sessionId: string, cursor: string | undefined, agent: string | undefined, limit?: number): Promise<{
82
83
  items: LiveEvent[];
83
84
  cursor: string | null;
85
+ tail?: boolean;
84
86
  }>;
85
87
  /**
86
88
  * `GET /v1/sessions/:id/events`: joins the session's shared read at the client's cursor and
@@ -114,11 +116,6 @@ export declare function served(event: LiveEvent): boolean;
114
116
  * the Tenant end too.
115
117
  */
116
118
  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
119
  /** Reset or close: end every session observer and stop every shared read. */
123
120
  export declare function clearObservers(hub: SessionStreams): void;
124
121
  /** Close: end every session observer. */
@@ -1,7 +1,34 @@
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 { historyCursor, historyResume } from "../reads/cursor.js";
1
22
  import { decodeCursor, encodeCursor } from "../record/index.js";
2
23
  import { basinOf } from "../streams/basin.js";
3
24
  import { sessionStream, } from "../streams/types.js";
4
25
  import { fail } from "./http.js";
26
+ /** The holder a stream opened with `scope` records: token callers only. */
27
+ export function streamHolderOf(scope) {
28
+ if (scope.kind !== "token")
29
+ return undefined;
30
+ return { subject: scope.subject, expiresAt: scope.expiresAt };
31
+ }
5
32
  export function createSessionStreams() {
6
33
  return { wiring: undefined, generation: 0, sessions: new Map() };
7
34
  }
@@ -79,17 +106,27 @@ function streamsOf(ctx) {
79
106
  * read starts. The cursor returned is the last record read (the request's cursor when none
80
107
  * was), whatever the `agent` filter kept.
81
108
  */
82
- export async function readHistory(ctx, sessionId, cursor, agent) {
83
- const from = startSeq(sessionId, cursor);
109
+ export async function readHistory(ctx, sessionId, cursor, agent, limit) {
110
+ const codec = limit === undefined ? undefined : historyCursor(ctx.config.tenantId, sessionId, agent);
111
+ const from = startSeq(sessionId, codec ? codec.decode(cursor) : historyResume(ctx.config.tenantId, sessionId, cursor));
84
112
  const streams = streamsOf(ctx);
85
113
  const ref = (await currentStream(ctx, sessionId)) ?? fail(404, "Session not found");
86
114
  const items = [];
87
115
  let last;
116
+ let atTail = true;
117
+ let examined = 0;
88
118
  try {
119
+ const tail = limit === undefined ? undefined : await streams.tail(ref.basin, ref.stream);
89
120
  for await (const record of streams.read(ref.basin, ref.stream, from, { follow: false })) {
121
+ if (tail !== undefined && record.seq >= tail)
122
+ break;
90
123
  last = record.seq;
91
124
  if (served(record.body) && (agent === undefined || belongsTo(record.body, agent)))
92
125
  items.push(record.body);
126
+ if (limit !== undefined && ++examined >= limit) {
127
+ atTail = last >= tail - 1;
128
+ break;
129
+ }
93
130
  }
94
131
  }
95
132
  catch (error) {
@@ -101,7 +138,10 @@ export async function readHistory(ctx, sessionId, cursor, agent) {
101
138
  }
102
139
  return {
103
140
  items,
104
- cursor: last !== undefined ? encodeCursor(sessionId, last) : cursor ?? null,
141
+ ...(limit === undefined ? {} : { tail: atTail }),
142
+ cursor: last !== undefined
143
+ ? (codec ? codec.encode(encodeCursor(sessionId, last)) : encodeCursor(sessionId, last))
144
+ : cursor ?? null,
105
145
  };
106
146
  }
107
147
  /**
@@ -111,7 +151,7 @@ export async function readHistory(ctx, sessionId, cursor, agent) {
111
151
  export async function streamSessionEvents(ctx, request, response, sessionId, cursor, holder) {
112
152
  const observer = {
113
153
  sink: sseSink(response),
114
- next: startSeq(sessionId, cursor),
154
+ next: startSeq(sessionId, historyResume(ctx.config.tenantId, sessionId, cursor)),
115
155
  ...(holder ? { holder } : {}),
116
156
  };
117
157
  const joined = await join(ctx, sessionId, observer);
@@ -156,7 +196,7 @@ export async function* observeSession(ctx, sessionId, cursor, options) {
156
196
  notify();
157
197
  },
158
198
  },
159
- next: startSeq(sessionId, cursor),
199
+ next: startSeq(sessionId, historyResume(ctx.config.tenantId, sessionId, cursor)),
160
200
  ...(options.holder ? { holder: options.holder } : {}),
161
201
  };
162
202
  const joined = await join(ctx, sessionId, observer);
@@ -233,7 +273,7 @@ async function join(ctx, sessionId, observer) {
233
273
  feed.observers.add(observer);
234
274
  return feed;
235
275
  }
236
- /** Ends one observer early (its token expired or was revoked). */
276
+ /** Ends one observer early (its token expired). */
237
277
  function endObserver(hub, feed, observer, reason) {
238
278
  if (!feed.observers.has(observer))
239
279
  return;
@@ -328,24 +368,12 @@ async function stillCurrent(ctx, feed) {
328
368
  */
329
369
  export async function checkSessionStreams(ctx) {
330
370
  const feeds = [...ctx.sessionStreams.sessions.values()];
331
- // Also a backstop for lost `subject.revoked` signals: the holders' current epochs.
332
- const subjects = new Set();
333
- for (const feed of feeds)
334
- for (const observer of feed.observers)
335
- if (observer.holder)
336
- subjects.add(observer.holder.subject);
337
- const { exists, epochs, generation } = await ctx.store.tx(async (t) => {
371
+ const { exists, generation } = await ctx.store.tx(async (t) => {
338
372
  const exists = new Set();
339
373
  for (const feed of feeds)
340
374
  if (await t.get("sessions", feed.sessionId))
341
375
  exists.add(feed.sessionId);
342
- return {
343
- exists,
344
- generation: (await t.basinGenerations()).current,
345
- epochs: subjects.size === 0
346
- ? new Map()
347
- : await t.subjectEpochs([...subjects]),
348
- };
376
+ return { exists, generation: (await t.basinGenerations()).current };
349
377
  });
350
378
  if (generation !== ctx.sessionStreams.generation)
351
379
  ctx.sessionStreams.wiring?.moveTo(generation);
@@ -355,18 +383,6 @@ export async function checkSessionStreams(ctx) {
355
383
  ? { basin: currentBasin(ctx), stream: sessionStream(feed.sessionId) }
356
384
  : undefined))
357
385
  endFeed(ctx.sessionStreams, feed);
358
- for (const [subject, epoch] of epochs)
359
- endSubjectStreams(ctx.sessionStreams, subject, epoch);
360
- }
361
- /**
362
- * Ends the streams of `subject` opened with a token older than `epoch` (a revocation), on
363
- * this process. Other processes do the same on the `subject.revoked` signal.
364
- */
365
- export function endSubjectStreams(hub, subject, epoch) {
366
- for (const feed of [...hub.sessions.values()])
367
- for (const observer of [...feed.observers])
368
- if (observer.holder?.subject === subject && observer.holder.epoch < epoch)
369
- endObserver(hub, feed, observer, "revoked");
370
386
  }
371
387
  function endFeed(hub, feed) {
372
388
  feed.read.abort();
@@ -1,5 +1,11 @@
1
- import type { Action } from "@nylorun/core/contracts";
2
- import type { AgentManifest, JsonValue } from "@nylorun/core/define";
1
+ /**
2
+ * Pure helpers over the session document: turn-manifest pins and variants, and state rebasing.
3
+ * No store access and no I/O. (The tools of an MCP snapshot
4
+ * live in `mcp/snapshot.ts`.)
5
+ *
6
+ * Later waves: stable; the async store conversion (Wave 1 / A) does not change these.
7
+ */
8
+ import type { AgentManifest } from "@nylorun/core/define";
3
9
  import { type TurnManifestStore } from "../core/turn-manifest.js";
4
10
  import type { Session } from "./context.js";
5
11
  export { manifestFor, sessionToolsOf } from "../mcp/snapshot.js";
@@ -7,43 +13,4 @@ export { manifestFor, sessionToolsOf } from "../mcp/snapshot.js";
7
13
  export declare function variantStore(session: Session): TurnManifestStore;
8
14
  /** Manifest this turn's checkpoint is pinned to (session pin or a stored variant). */
9
15
  export declare function turnManifestOf(session: Session): AgentManifest;
10
- /** Wrap a linked agent turn so the Loop learns the turn's manifest (SD-I4 pin stays on session). */
11
- export declare function linkedAgentOutput(session: Session, output: JsonValue | undefined): JsonValue;
12
16
  export declare function rebaseSessionState(session: Session, turnHash: string): void;
13
- export declare function pinnedTool(manifest: AgentManifest, capabilityId?: string, toolName?: string): import("@nylorun/core/define").ToolManifest | undefined;
14
- /** What an action runs, for events: a tool name, or a hook point and its capabilities. */
15
- export declare function actionTarget(action: Action): {
16
- hook: {
17
- at: "after" | "before";
18
- scope: "step" | "turn";
19
- capabilityIds: string[];
20
- };
21
- agent?: {
22
- id: string;
23
- path: string;
24
- delegationId?: string | undefined;
25
- } | undefined;
26
- } | {
27
- toolName: string;
28
- agent?: {
29
- id: string;
30
- path: string;
31
- delegationId?: string | undefined;
32
- } | undefined;
33
- } | {
34
- path: string;
35
- key: string;
36
- agent?: {
37
- id: string;
38
- path: string;
39
- delegationId?: string | undefined;
40
- } | {
41
- id: string;
42
- path: string;
43
- delegationId?: string | undefined;
44
- } | {
45
- id: string;
46
- path: string;
47
- delegationId?: string | undefined;
48
- } | undefined;
49
- };
@@ -1,12 +1,3 @@
1
- /**
2
- * Pure helpers over the session document: turn-manifest pins and variants, state rebasing,
3
- * and the event target of an Action. No store access and no I/O. (The tools of an MCP snapshot
4
- * live in `mcp/snapshot.ts`.)
5
- *
6
- * Later waves: stable; the async store conversion (Wave 1 / A) does not change these.
7
- */
8
- import { agentTurnValue } from "@nylorun/harness/run";
9
- import { isWorkflowManifest } from "../core/flow-host.js";
10
1
  import { allowedManifestHashes, rebaseTurnState, } from "../core/turn-manifest.js";
11
2
  export { manifestFor, sessionToolsOf } from "../mcp/snapshot.js";
12
3
  /** Turn-manifest store backed by the session's `variants` map (no schema change). */
@@ -27,12 +18,6 @@ export function turnManifestOf(session) {
27
18
  return session.manifest;
28
19
  return session.variants?.[hash] ?? session.manifest;
29
20
  }
30
- /** Wrap a linked agent turn so the Loop learns the turn's manifest (SD-I4 pin stays on session). */
31
- export function linkedAgentOutput(session, output) {
32
- if (isWorkflowManifest(session.manifest))
33
- return output ?? null;
34
- return agentTurnValue(output ?? null, turnManifestOf(session));
35
- }
36
21
  export function rebaseSessionState(session, turnHash) {
37
22
  const allowed = allowedManifestHashes({
38
23
  pinnedHash: session.manifestHash,
@@ -46,25 +31,3 @@ export function rebaseSessionState(session, turnHash) {
46
31
  if (next !== session.state)
47
32
  session.state = next;
48
33
  }
49
- export function pinnedTool(manifest, capabilityId, toolName) {
50
- const capability = manifest.capabilities.find((item) => item.id === capabilityId);
51
- return capability?.tools?.find((tool) => tool.name === toolName);
52
- }
53
- /** What an action runs, for events: a tool name, or a hook point and its capabilities. */
54
- export function actionTarget(action) {
55
- if (action.kind === "hook")
56
- return {
57
- hook: action.hook,
58
- ...(action.agent ? { agent: action.agent } : {}),
59
- };
60
- if (action.kind === "tool" && "toolName" in action)
61
- return {
62
- toolName: action.toolName,
63
- ...(action.agent ? { agent: action.agent } : {}),
64
- };
65
- return {
66
- path: action.path,
67
- key: action.key,
68
- ...(action.agent ? { agent: action.agent } : {}),
69
- };
70
- }
@@ -15,7 +15,7 @@ export declare function listDefinitions(ctx: TenantContext): Promise<{
15
15
  }[];
16
16
  }>;
17
17
  /**
18
- * The agents a subject token or publishable key may see: id, name and description only, never
18
+ * The agents a token caller may see: id, name and description only, never
19
19
  * instructions, tools or MCP servers, and only the agents allowed.
20
20
  */
21
21
  export declare function listAgentsPublic(ctx: TenantContext, agents: ReadonlySet<string> | "*"): Promise<{
@@ -60,7 +60,7 @@ export declare function putSession(ctx: TenantContext, id: string, body: PutSess
60
60
  */
61
61
  createOnly?: boolean;
62
62
  /**
63
- * The sandboxes the caller reaches (a subject token's `sbx`, `sandboxGrantsOf`); undefined
63
+ * The sandboxes the caller reaches (a token caller's grants, `sandboxGrantsOf`); undefined
64
64
  * reaches every one. `sandbox: { id }` must name one of them.
65
65
  */
66
66
  sandboxGrants?: readonly string[];