@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,35 +1,51 @@
1
- import { countActiveFlowWork, deriveAgentEffectSessionId, isFlowEffect, isFlowToolEffect, linkedMessageKey, linkedTurnEnd, } from "../core/flow-host.js";
1
+ import { embeddedAgent, flowDelegateManifest, hashManifest, isWorkflowManifestV2, } from "@nylorun/core/define";
2
+ import { countActiveFlowWork, deriveAgentEffectSessionId, isFlowEffect, isFlowToolEffect, isWorkflowManifest, linkedMessageKey, linkedTurnEnd, } from "../core/flow-host.js";
2
3
  import { mayDispatchMore } from "../core/limits.js";
3
4
  import { canonical } from "../store/canonical.js";
4
5
  import { isOwnershipLost } from "../store/ownership.js";
5
- import { newStreamIncarnation } from "../streams/types.js";
6
6
  import { piModel } from "../model/pi-model.js";
7
7
  import { scrub } from "../redact.js";
8
8
  import { serversOf } from "../mcp/pool.js";
9
9
  import { sandboxCapabilityOf } from "../sandbox/manager.js";
10
- import { owningSandboxSessionId, sandboxSpecOf } from "../sandbox/share.js";
10
+ import { owningSandboxSessionId, sandboxSpecOf, sessionSandboxSpec, } from "../sandbox/share.js";
11
+ import { withSandboxCapability } from "../sandbox/session-sandbox.js";
11
12
  import { loadSession, ownedSession, sandboxLookup, sessionOf, } from "./context.js";
12
13
  import { fail } from "./http.js";
13
- import { actionTarget, linkedAgentOutput, manifestFor, mcpToolOf, pinnedTool, } from "./session.js";
14
+ import { actionTarget, linkedAgentOutput, manifestFor, mcpToolOf, pinnedTool, turnManifestOf, } from "./session.js";
14
15
  import { command } from "./commands.js";
15
- import { assistantMessage, toolCompleted, toolIds } from "./transcript.js";
16
+ import { offerAction } from "./delivery.js";
17
+ import { assistantMessage, contextCompacted, modelFailed, toolCompleted, toolIds, } from "./transcript.js";
18
+ import { classifyThrown } from "../model/classify.js";
16
19
  import { abortKind } from "./worker.js";
17
- export function invokeModel(ctx, request, signal, model) {
18
- if (model)
19
- return model(request, signal);
20
- if (!ctx.useVaultModel)
21
- return ctx.modelProvider(request, signal);
22
- const adapter = piModel({
23
- root: ctx.config.paths.home,
24
- readHostModel: () => ctx.vault.readHostModel(),
25
- writeHostCredential: (credential) => ctx.vault.updateHostCredential(credential),
26
- });
27
- return adapter(request.input, {
28
- request: request.context.request,
29
- invocationId: String(request.context.invocationId),
30
- signal,
31
- reportPreparedCall() { },
32
- });
20
+ /**
21
+ * Call the model for one effect. A provider failure comes back as a failure outcome
22
+ * (Model Calls §6), whichever provider serves the call; only an abort throws, and the
23
+ * advance decides what the abort means.
24
+ */
25
+ export async function invokeModel(ctx, request, signal, model) {
26
+ try {
27
+ if (model)
28
+ return await model(request, signal);
29
+ if (!ctx.useVaultModel)
30
+ return await ctx.modelProvider(request, signal);
31
+ const adapter = piModel({
32
+ root: ctx.config.paths.home,
33
+ readHostModel: () => ctx.vault.readHostModel(),
34
+ writeHostCredential: (credential) => ctx.vault.updateHostCredential(credential),
35
+ ...(ctx.config.modelCall ? { settings: ctx.config.modelCall } : {}),
36
+ });
37
+ return await adapter(request.input, {
38
+ request: request.context.request,
39
+ invocationId: String(request.context.invocationId),
40
+ signal,
41
+ reportPreparedCall() { },
42
+ });
43
+ }
44
+ catch (error) {
45
+ if (signal.aborted)
46
+ throw error;
47
+ return classifyThrown(error);
48
+ }
33
49
  }
34
50
  /**
35
51
  * The outcome of a flow `agent` effect once the linked turn it started ended, or undefined
@@ -158,8 +174,6 @@ export async function resolveEffect(ctx, request, signal, lease, segment = {}) {
158
174
  context: request.context,
159
175
  status: "pending",
160
176
  generation: 0,
161
- claimId: null,
162
- leaseExpiresAt: null,
163
177
  ...(request.agent ? { agent: request.agent } : {}),
164
178
  };
165
179
  const action = request.kind === "hook"
@@ -188,7 +202,7 @@ export async function resolveEffect(ctx, request, signal, lease, segment = {}) {
188
202
  ...(action.kind === "tool" ? toolIds(request.context) : {}),
189
203
  input: action.input,
190
204
  });
191
- t.signalWork();
205
+ await offerAction(t, ctx, action);
192
206
  return resolved({ status: "pending" });
193
207
  });
194
208
  if (journaled.kind === "resolved")
@@ -218,11 +232,23 @@ export async function resolveEffect(ctx, request, signal, lease, segment = {}) {
218
232
  effect.status = "completed";
219
233
  effect.outcome = { value };
220
234
  await t.put("effects", request.effectId, effect);
221
- const transcript = invoke === "model"
222
- ? assistantMessage(request, value)
223
- : toolCompleted(request, value);
224
- if (transcript)
225
- await t.event(s.id, request.turnId, invoke === "model" ? "message.assistant" : "tool.completed", transcript);
235
+ const failed = invoke === "model" ? modelFailed(request, value) : undefined;
236
+ // A summary call is never an assistant message; the last one publishes context.compacted.
237
+ const summarizing = invoke === "model" && Boolean(request.context.compaction);
238
+ const compacted = summarizing && !failed ? contextCompacted(request, value) : undefined;
239
+ const transcript = failed || summarizing
240
+ ? undefined
241
+ : invoke === "model"
242
+ ? assistantMessage(request, value)
243
+ : toolCompleted(request, value);
244
+ if (failed)
245
+ await t.event(s.id, request.turnId, "model.failed", failed);
246
+ if (compacted)
247
+ await t.event(s.id, request.turnId, "context.compacted", compacted);
248
+ if (transcript && invoke === "model")
249
+ await t.event(s.id, request.turnId, "message.assistant", transcript);
250
+ else if (transcript)
251
+ await t.event(s.id, request.turnId, "tool.completed", transcript);
226
252
  return { status: "completed", outcome: effect.outcome };
227
253
  });
228
254
  }
@@ -311,8 +337,6 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
311
337
  context: request.context,
312
338
  status: "pending",
313
339
  generation: 0,
314
- claimId: null,
315
- leaseExpiresAt: null,
316
340
  kind: request.kind,
317
341
  path: request.path,
318
342
  key: request.key,
@@ -328,8 +352,6 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
328
352
  context: request.context,
329
353
  status: "pending",
330
354
  generation: 0,
331
- claimId: null,
332
- leaseExpiresAt: null,
333
355
  kind: "tool",
334
356
  path: request.path,
335
357
  key: request.key,
@@ -352,7 +374,7 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
352
374
  key: action.key,
353
375
  input: action.input,
354
376
  });
355
- t.signalWork();
377
+ await offerAction(t, ctx, action);
356
378
  return resolved({ status: "pending" });
357
379
  }
358
380
  return { kind: "agent", workflow };
@@ -386,17 +408,19 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
386
408
  await ownedSession(t, lease, workflow.id);
387
409
  if (exists)
388
410
  return;
389
- const definition = (await t.get("definitions", body.agentId)) ??
390
- fail(404, "Definition not found");
391
- const sandboxOwnerId = sandboxSpecOf(workflow.manifest) || workflow.sandboxOwnerId
392
- ? owningSandboxSessionId(workflow, await sandboxLookup(t, workflow.sandboxOwnerId))
411
+ const definition = await leafDefinition(t, workflow, body, request);
412
+ const lookup = await sandboxLookup(t, workflow.sandboxOwnerId);
413
+ const sandboxOwnerId = sessionSandboxSpec(workflow) || workflow.sandboxOwnerId
414
+ ? owningSandboxSessionId(workflow, lookup)
393
415
  : undefined;
416
+ // A sandbox chosen when the tree was opened reaches every agent in it, with its tools.
417
+ const inherited = inheritedSandbox(sandboxOwnerId === undefined ? undefined : lookup(sandboxOwnerId) ?? workflow, definition);
394
418
  const created = {
395
419
  id: agentSessionId,
396
420
  agentId: body.agentId,
397
421
  ownerUserId: workflow.ownerUserId,
398
- manifest: definition.manifest,
399
- manifestHash: definition.manifestHash,
422
+ manifest: inherited?.manifest ?? definition.manifest,
423
+ manifestHash: inherited?.manifestHash ?? definition.manifestHash,
400
424
  implementationVersion: definition.implementationVersion,
401
425
  status: "idle",
402
426
  activeTurnId: null,
@@ -410,7 +434,9 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
410
434
  credentialSelections: workflow.credentialSelections,
411
435
  pluginRoots: definition.pluginRoots ?? {},
412
436
  ...(sandboxOwnerId ? { sandboxOwnerId } : {}),
413
- streamIncarnation: newStreamIncarnation(),
437
+ ...(inherited
438
+ ? { sandbox: inherited.spec, sandboxSource: "shared" }
439
+ : {}),
414
440
  };
415
441
  await t.put("sessions", agentSessionId, created);
416
442
  });
@@ -440,13 +466,15 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
440
466
  return store.tx(async (t) => {
441
467
  const s = await ownedSession(t, lease, request.sessionId);
442
468
  const agent = await sessionOf(t, agentSessionId);
443
- await t.event(s.id, s.activeTurnId, "loop.iteration", {
444
- path: String(request.context.loopPath ?? path.split("/")[0]),
445
- n,
446
- sessionId: agentSessionId,
447
- turnId: accepted.turnId ?? undefined,
448
- manifestHash: agent.checkpoint?.manifestHash,
449
- });
469
+ // Only a Loop's own agent turns are iterations; a step elsewhere in the flow is not.
470
+ if (typeof request.context.loopPath === "string")
471
+ await t.event(s.id, s.activeTurnId, "loop.iteration", {
472
+ path: request.context.loopPath,
473
+ n,
474
+ sessionId: agentSessionId,
475
+ turnId: accepted.turnId ?? undefined,
476
+ manifestHash: agent.checkpoint?.manifestHash,
477
+ });
450
478
  await t.event(s.id, s.activeTurnId, "node.agent", {
451
479
  path,
452
480
  iterations,
@@ -532,6 +560,21 @@ async function callMcpTool(ctx, request) {
532
560
  pluginRoots: s.pluginRoots ?? {},
533
561
  });
534
562
  }
563
+ /**
564
+ * The sandbox a linked session inherits from its tree's owner when that sandbox was chosen at
565
+ * open (pinned on the owner). A definition that declares its own sandbox keeps it, as before.
566
+ */
567
+ function inheritedSandbox(owner, definition) {
568
+ const spec = owner?.sandbox;
569
+ if (!spec || sandboxSpecOf(definition.manifest))
570
+ return undefined;
571
+ if (isWorkflowManifest(definition.manifest))
572
+ return { spec };
573
+ const sandboxed = withSandboxCapability(definition.manifest, spec);
574
+ if (!sandboxed.ok)
575
+ throw new Error(sandboxed.message);
576
+ return { spec, manifest: sandboxed.manifest, manifestHash: sandboxed.manifestHash };
577
+ }
535
578
  async function callSandboxTool(ctx, request, signal) {
536
579
  const { s, ownerId } = await ctx.store.tx(async (t) => {
537
580
  const s = await sessionOf(t, request.sessionId);
@@ -562,3 +605,50 @@ export async function authorize(ctx, sessionId, request) {
562
605
  }
563
606
  return result;
564
607
  }
608
+ /**
609
+ * The definition a flow leaf's session runs. A v2 workflow embeds its leaves, so they
610
+ * come from the workflow's own manifest and can't drift from it; v1 leaves come from the
611
+ * registry by id.
612
+ */
613
+ async function leafDefinition(t, workflow, body, request) {
614
+ // A flow agent used as a tool: its manifest is inlined in the parent's pinned tool.
615
+ if (request.context.role === "delegate" && !isWorkflowManifest(workflow.manifest)) {
616
+ const flow = flowDelegateManifest(turnManifestOf(workflow), body.agentId);
617
+ if (!flow)
618
+ return fail(404, `Agent '${body.agentId}' is not a flow agent used as a tool`);
619
+ return {
620
+ manifest: flow,
621
+ manifestHash: hashManifest(flow),
622
+ implementationVersion: workflow.implementationVersion,
623
+ pluginRoots: leafPluginRoots(workflow.pluginRoots, body.agentId),
624
+ };
625
+ }
626
+ if (isWorkflowManifestV2(workflow.manifest)) {
627
+ const leaf = embeddedAgent(workflow.manifest, body.flow ?? [], body.agentId);
628
+ if (!leaf || isWorkflowManifestV2(leaf))
629
+ fail(404, `Agent '${body.agentId}' is not embedded in workflow '${workflow.manifest.id}'`);
630
+ const manifest = leaf;
631
+ return {
632
+ manifest,
633
+ manifestHash: hashManifest(manifest),
634
+ implementationVersion: workflow.implementationVersion,
635
+ pluginRoots: leafPluginRoots(workflow.pluginRoots, body.agentId),
636
+ };
637
+ }
638
+ const definition = await t.get("definitions", body.agentId);
639
+ if (!definition)
640
+ fail(404, "Definition not found");
641
+ return definition;
642
+ }
643
+ /**
644
+ * A v2 workflow keys its leaves' plugin roots `<agentId>/<capability>`, and an agent keys
645
+ * a flow agent it uses as a tool the same way (`<flowId>/<leafId>/<capability>`).
646
+ */
647
+ function leafPluginRoots(roots, agentId) {
648
+ const own = {};
649
+ const prefix = `${agentId}/`;
650
+ for (const [key, root] of Object.entries(roots ?? {}))
651
+ if (key.startsWith(prefix))
652
+ own[key.slice(prefix.length)] = root;
653
+ return own;
654
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Action endpoints (design: Action endpoints §4.2): where the Runtime delivers each agent's
3
+ * Actions, registered with the application key.
4
+ */
5
+ import { type Endpoint, type PutEndpointsRequest } from "@nylorun/core/contracts";
6
+ import type { EndpointRow } from "../store/types.js";
7
+ import type { TenantContext } from "./context.js";
8
+ /** An endpoint as the Tenant API answers it. */
9
+ export declare function endpointView(row: EndpointRow): Endpoint;
10
+ /** `GET /v1/endpoints`. */
11
+ export declare function listEndpoints(ctx: TenantContext): Promise<{
12
+ endpoints: {
13
+ agentId: string;
14
+ url: string;
15
+ implementationVersion: string;
16
+ manifestHash?: string | undefined;
17
+ timeoutMs: number;
18
+ maxConcurrent: number;
19
+ health: {
20
+ lastDeliveryAt?: string | undefined;
21
+ lastSuccessAt?: string | undefined;
22
+ lastError?: {
23
+ code: string;
24
+ message: string;
25
+ } | undefined;
26
+ consecutiveFailures: number;
27
+ served?: {
28
+ implementationVersion: string;
29
+ manifestHash?: string | undefined;
30
+ } | undefined;
31
+ };
32
+ updatedAt: string;
33
+ }[];
34
+ }>;
35
+ /**
36
+ * `PUT /v1/endpoints`: upsert the batch in one transaction, then send the Actions already
37
+ * waiting for these agents. Answers every registered endpoint of the batch.
38
+ */
39
+ export declare function putEndpoints(ctx: TenantContext, principalId: string, body: PutEndpointsRequest): Promise<{
40
+ endpoints: {
41
+ agentId: string;
42
+ url: string;
43
+ implementationVersion: string;
44
+ manifestHash?: string | undefined;
45
+ timeoutMs: number;
46
+ maxConcurrent: number;
47
+ health: {
48
+ lastDeliveryAt?: string | undefined;
49
+ lastSuccessAt?: string | undefined;
50
+ lastError?: {
51
+ code: string;
52
+ message: string;
53
+ } | undefined;
54
+ consecutiveFailures: number;
55
+ served?: {
56
+ implementationVersion: string;
57
+ manifestHash?: string | undefined;
58
+ } | undefined;
59
+ };
60
+ updatedAt: string;
61
+ }[];
62
+ }>;
63
+ /** `DELETE /v1/endpoints/:agentId`. The agent's pending Actions wait for a new endpoint. */
64
+ export declare function deleteEndpoint(ctx: TenantContext, agentId: string): Promise<{
65
+ agentId: string;
66
+ deleted: true;
67
+ }>;
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Action endpoints (design: Action endpoints §4.2): where the Runtime delivers each agent's
3
+ * Actions, registered with the application key.
4
+ */
5
+ import { ENDPOINT_MAX_CONCURRENT_DEFAULT, ENDPOINT_TIMEOUT_DEFAULT_MS, } from "@nylorun/core/contracts";
6
+ import { fail } from "./http.js";
7
+ /** An endpoint as the Tenant API answers it. */
8
+ export function endpointView(row) {
9
+ const served = row.servedImplementationVersion === undefined
10
+ ? undefined
11
+ : {
12
+ implementationVersion: row.servedImplementationVersion,
13
+ ...(row.servedManifestHash === undefined
14
+ ? {}
15
+ : { manifestHash: row.servedManifestHash }),
16
+ };
17
+ return {
18
+ agentId: row.agentId,
19
+ url: row.url,
20
+ implementationVersion: row.implementationVersion,
21
+ ...(row.manifestHash === undefined ? {} : { manifestHash: row.manifestHash }),
22
+ timeoutMs: row.timeoutMs,
23
+ maxConcurrent: row.maxConcurrent,
24
+ health: {
25
+ ...(row.lastDeliveryAt === undefined ? {} : { lastDeliveryAt: row.lastDeliveryAt }),
26
+ ...(row.lastSuccessAt === undefined ? {} : { lastSuccessAt: row.lastSuccessAt }),
27
+ ...(row.lastErrorCode === undefined
28
+ ? {}
29
+ : {
30
+ lastError: {
31
+ code: row.lastErrorCode,
32
+ message: row.lastErrorMessage ?? "",
33
+ },
34
+ }),
35
+ consecutiveFailures: row.consecutiveFailures,
36
+ ...(served ? { served } : {}),
37
+ },
38
+ updatedAt: row.updatedAt,
39
+ };
40
+ }
41
+ /** `GET /v1/endpoints`. */
42
+ export async function listEndpoints(ctx) {
43
+ const rows = await ctx.store.tx((t) => t.listEndpoints());
44
+ return { endpoints: rows.map(endpointView) };
45
+ }
46
+ /**
47
+ * `PUT /v1/endpoints`: upsert the batch in one transaction, then send the Actions already
48
+ * waiting for these agents. Answers every registered endpoint of the batch.
49
+ */
50
+ export async function putEndpoints(ctx, principalId, body) {
51
+ const updatedAt = new Date().toISOString();
52
+ const rows = await ctx.store.tx(async (t) => {
53
+ const saved = [];
54
+ for (const endpoint of body.endpoints) {
55
+ await t.putEndpoint({
56
+ agentId: endpoint.agentId,
57
+ url: endpoint.url,
58
+ implementationVersion: endpoint.implementationVersion,
59
+ ...(endpoint.manifestHash === undefined
60
+ ? {}
61
+ : { manifestHash: endpoint.manifestHash }),
62
+ timeoutMs: endpoint.timeoutMs ?? ENDPOINT_TIMEOUT_DEFAULT_MS,
63
+ maxConcurrent: endpoint.maxConcurrent ?? ENDPOINT_MAX_CONCURRENT_DEFAULT,
64
+ principalId,
65
+ updatedAt,
66
+ });
67
+ saved.push((await t.getEndpoint(endpoint.agentId)));
68
+ }
69
+ return saved;
70
+ });
71
+ // Actions already waiting for these agents go to their endpoint now.
72
+ const waiting = await ctx.store.tx(async (t) => {
73
+ const actions = [];
74
+ for (const endpoint of body.endpoints)
75
+ actions.push(...(await t.pendingActions(endpoint.agentId)));
76
+ return actions;
77
+ });
78
+ for (const action of waiting)
79
+ await ctx.deliver(action.actionId);
80
+ return { endpoints: rows.map(endpointView) };
81
+ }
82
+ /** `DELETE /v1/endpoints/:agentId`. The agent's pending Actions wait for a new endpoint. */
83
+ export async function deleteEndpoint(ctx, agentId) {
84
+ await ctx.store.tx(async (t) => {
85
+ if (!(await t.getEndpoint(agentId)))
86
+ fail(404, "Endpoint not found");
87
+ await t.deleteEndpoint(agentId);
88
+ });
89
+ return { agentId, deleted: true };
90
+ }
@@ -19,6 +19,10 @@ export interface StartEphemeralRuntimeOptions {
19
19
  sandboxBackend?: "auto" | "virtual";
20
20
  /** When true, close() leaves hostRoot on disk. */
21
21
  retainRoot?: boolean;
22
+ /** Allow browser requests (an `Origin` with a publishable key). Default off. */
23
+ browserAccess?: boolean;
24
+ /** Serve the Admin API on its own loopback listener (`adminUrl`). Default off. */
25
+ operatorListener?: boolean;
22
26
  logger?: Logger;
23
27
  /**
24
28
  * A Postgres pool: Tenants become schemas in it (`store-pg.ts`) instead of living in memory.
@@ -28,6 +32,8 @@ export interface StartEphemeralRuntimeOptions {
28
32
  }
29
33
  export interface EphemeralRuntime {
30
34
  url: string;
35
+ /** Where the Admin API answers: `url`, or the operator listener when requested. */
36
+ adminUrl: string;
31
37
  tenantId: string;
32
38
  applicationKey: string;
33
39
  adminKey: string;
@@ -13,7 +13,7 @@ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
13
13
  import { randomBytes } from "node:crypto";
14
14
  import { createRequire } from "node:module";
15
15
  import { newTenantId } from "@nylorun/core/compatibility";
16
- import { hashToken, mintBearerToken } from "../core/executors.js";
16
+ import { hashToken, mintBearerToken } from "../core/bearer.js";
17
17
  import { createHost } from "../host/create-host.js";
18
18
  import { createKekFile } from "../vault/kek.js";
19
19
  import { MemorySessionStore } from "../store/memory.js";
@@ -95,6 +95,7 @@ function kept(store) {
95
95
  tenantId: store.tenantId,
96
96
  tx: (fn) => store.tx(fn),
97
97
  onCommit: (listener) => store.onCommit(listener),
98
+ record: () => store.record(),
98
99
  health: () => store.health(),
99
100
  close: async () => { },
100
101
  };
@@ -173,37 +174,65 @@ export async function startEphemeralRuntime(options) {
173
174
  logger,
174
175
  onDeleted: (tenantId) => streams.deleteTenant(tenantId),
175
176
  });
176
- await module.start();
177
- const tenantId = options.tenantId ?? newTenantId();
178
- const applicationKey = options.applicationKey ?? mintBearerToken();
179
- const principalId = options.principalId ?? `principal_${randomBytes(8).toString("hex")}`;
180
- const credentialHash = hashToken(applicationKey);
181
- await module.create({
182
- tenantId,
183
- name: options.name ?? "ephemeral",
184
- principalId,
185
- credentialHash,
186
- idempotencyKey: `ephemeral-${tenantId}`,
187
- ...(options.studioCredentialHash
188
- ? { studioCredentialHash: options.studioCredentialHash }
189
- : {}),
190
- });
191
- // KEK for first vault write; openTenantRuntime also creates when hooks allow.
192
- const kekPath = tenantPaths(hostRoot, tenantId).kek;
193
- if (!existsSync(kekPath))
194
- createKekFile(kekPath);
195
- const host = createHost({
196
- hostRoot,
197
- module,
198
- config: hostConfig,
199
- credentials,
200
- logger,
201
- coreVersion: coreVersion(),
202
- });
203
- await host.listen();
177
+ let host;
178
+ /** Closes what was opened, in reverse order; `close()` and a failed start share it. */
179
+ const release = async () => {
180
+ await host?.close();
181
+ await module.close();
182
+ await memory?.close();
183
+ await streams.close();
184
+ if (!options.retainRoot)
185
+ rmSync(hostRoot, { recursive: true, force: true });
186
+ };
187
+ let tenantId;
188
+ let applicationKey;
189
+ let principalId;
190
+ try {
191
+ await module.start();
192
+ tenantId = options.tenantId ?? newTenantId();
193
+ applicationKey = options.applicationKey ?? mintBearerToken();
194
+ principalId =
195
+ options.principalId ?? `principal_${randomBytes(8).toString("hex")}`;
196
+ const credentialHash = hashToken(applicationKey);
197
+ await module.create({
198
+ tenantId,
199
+ name: options.name ?? "ephemeral",
200
+ principalId,
201
+ credentialHash,
202
+ idempotencyKey: `ephemeral-${tenantId}`,
203
+ ...(options.studioCredentialHash
204
+ ? { studioCredentialHash: options.studioCredentialHash }
205
+ : {}),
206
+ });
207
+ // KEK for first vault write; openTenantRuntime also creates when hooks allow.
208
+ const kekPath = tenantPaths(hostRoot, tenantId).kek;
209
+ if (!existsSync(kekPath))
210
+ createKekFile(kekPath);
211
+ host = createHost({
212
+ hostRoot,
213
+ module,
214
+ config: hostConfig,
215
+ credentials,
216
+ logger,
217
+ coreVersion: coreVersion(),
218
+ browserAccess: options.browserAccess === true,
219
+ ...(options.operatorListener
220
+ ? { operator: { host: "127.0.0.1", port: 0 } }
221
+ : {}),
222
+ });
223
+ await host.listen();
224
+ }
225
+ catch (error) {
226
+ await release().catch((cleanup) => logger.warn("ephemeral runtime cleanup failed", {
227
+ message: cleanup instanceof Error ? cleanup.message : String(cleanup),
228
+ }));
229
+ throw error;
230
+ }
231
+ const started = host;
204
232
  let closed = false;
205
233
  return {
206
- url: host.url,
234
+ url: started.url,
235
+ adminUrl: started.adminUrl,
207
236
  tenantId,
208
237
  applicationKey,
209
238
  adminKey,
@@ -213,12 +242,7 @@ export async function startEphemeralRuntime(options) {
213
242
  if (closed)
214
243
  return;
215
244
  closed = true;
216
- await host.close();
217
- await module.close();
218
- await memory?.close();
219
- await streams.close();
220
- if (!options.retainRoot)
221
- rmSync(hostRoot, { recursive: true, force: true });
245
+ await release();
222
246
  },
223
247
  };
224
248
  }
@@ -1,10 +1,9 @@
1
1
  /**
2
- * HTTP plumbing shared by the Tenant route modules: typed rejections, the opaque 404 for
3
- * unknown credentials (D5), request-body parsing and the client-abort signal.
2
+ * The Tenant's typed rejections: what a route or the code it calls throws for the client to
3
+ * see (`api/http/respond.ts` answers it), and the opaque 404 for unknown credentials (D5).
4
4
  *
5
5
  * Later waves: stable; the streams seam (Wave 2 / Y) keeps using these helpers.
6
6
  */
7
- import type { IncomingMessage } from "node:http";
8
7
  /** D5 opaque failure for unknown/rejected credentials on Tenant routes. */
9
8
  export declare const OPAQUE_NOT_FOUND: {
10
9
  status: "rejected";
@@ -19,9 +18,13 @@ export interface Rejection {
19
18
  export declare class HttpError extends Error {
20
19
  readonly status: number;
21
20
  readonly rejection: Rejection;
22
- constructor(status: number, message: string, rejection?: Rejection);
21
+ /** Response headers the rejection carries, such as `retry-after` or `www-authenticate`. */
22
+ readonly headers: Readonly<Record<string, string>>;
23
+ constructor(status: number, message: string, rejection?: Rejection,
24
+ /** Response headers the rejection carries, such as `retry-after` or `www-authenticate`. */
25
+ headers?: Readonly<Record<string, string>>);
23
26
  }
24
- export declare const fail: (status: number, message: string, rejection?: Rejection) => never;
27
+ export declare const fail: (status: number, message: string, rejection?: Rejection, headers?: Readonly<Record<string, string>>) => never;
25
28
  export declare class OpaqueAuthError extends Error {
26
29
  readonly status = 404;
27
30
  readonly body: {
@@ -32,7 +35,3 @@ export declare class OpaqueAuthError extends Error {
32
35
  constructor();
33
36
  }
34
37
  export declare const failOpaque: () => never;
35
- /** AbortSignal tied to the HTTP request being closed by the client. */
36
- export declare function requestAborted(request: IncomingMessage): AbortSignal;
37
- /** Read a JSON request body, capped at 1 MiB. */
38
- export declare function readBody(request: IncomingMessage): Promise<unknown>;