@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
@@ -3,34 +3,50 @@ import { countActiveFlowWork, deriveAgentEffectSessionId, isFlowEffect, isFlowTo
3
3
  import { mayDispatchMore } from "../core/limits.js";
4
4
  import { canonical } from "../store/canonical.js";
5
5
  import { isOwnershipLost } from "../store/ownership.js";
6
- import { newStreamIncarnation } from "../streams/types.js";
7
- import { piModel } from "../model/pi-model.js";
8
6
  import { scrub } from "../redact.js";
9
- import { serversOf } from "../mcp/pool.js";
7
+ import { findServer, serversOf } from "../mcp/pool.js";
10
8
  import { sandboxCapabilityOf } from "../sandbox/manager.js";
11
- import { owningSandboxSessionId, sandboxSpecOf } from "../sandbox/share.js";
9
+ import { owningSandboxSessionId, sandboxSpecOf, sessionSandboxSpec, } from "../sandbox/share.js";
10
+ import { withSandboxCapability } from "../sandbox/session-sandbox.js";
12
11
  import { loadSession, ownedSession, sandboxLookup, sessionOf, } from "./context.js";
13
12
  import { fail } from "./http.js";
14
13
  import { actionTarget, linkedAgentOutput, manifestFor, mcpToolOf, pinnedTool, turnManifestOf, } from "./session.js";
15
14
  import { command } from "./commands.js";
16
- import { assistantMessage, toolCompleted, toolIds } from "./transcript.js";
15
+ import { offerAction } from "./delivery.js";
16
+ import { assistantMessage, contextCompacted, modelFailed, toolCompleted, toolIds, } from "./transcript.js";
17
+ import { classifyThrown } from "../model/classify.js";
17
18
  import { abortKind } from "./worker.js";
18
- export function invokeModel(ctx, request, signal, model) {
19
- if (model)
20
- return model(request, signal);
21
- if (!ctx.useVaultModel)
22
- return ctx.modelProvider(request, signal);
23
- const adapter = piModel({
24
- root: ctx.config.paths.home,
25
- readHostModel: () => ctx.vault.readHostModel(),
26
- writeHostCredential: (credential) => ctx.vault.updateHostCredential(credential),
27
- });
28
- return adapter(request.input, {
29
- request: request.context.request,
30
- invocationId: String(request.context.invocationId),
31
- signal,
32
- reportPreparedCall() { },
33
- });
19
+ /**
20
+ * Call the model for one effect. A provider failure comes back as a failure outcome
21
+ * (Model Calls §6), whichever provider serves the call; only an abort throws, and the
22
+ * advance decides what the abort means.
23
+ */
24
+ export async function invokeModel(ctx, request, signal, model) {
25
+ try {
26
+ if (model)
27
+ return await model(request, signal);
28
+ if (!ctx.useVaultModel)
29
+ return await ctx.modelProvider(request, signal);
30
+ return await ctx.modelGate.call({
31
+ tenantId: ctx.config.tenantId,
32
+ sessionId: request.sessionId,
33
+ turnId: request.turnId,
34
+ agentId: request.agentId,
35
+ effectId: request.effectId,
36
+ invocationId: String(request.context.invocationId),
37
+ call: request.input,
38
+ }, signal);
39
+ }
40
+ catch (error) {
41
+ if (signal.aborted) {
42
+ // A call that outlives this process stops only when told to: on a user cancel. After a
43
+ // shutdown or a lost lease the next owner re-sends it and picks up its outcome (P1.2).
44
+ if (!model && ctx.useVaultModel && abortKind(signal) === "cancel" && ctx.modelGate.cancel)
45
+ await ctx.modelGate.cancel({ tenantId: ctx.config.tenantId, effectId: request.effectId });
46
+ throw error;
47
+ }
48
+ return classifyThrown(error);
49
+ }
34
50
  }
35
51
  /**
36
52
  * The outcome of a flow `agent` effect once the linked turn it started ended, or undefined
@@ -53,6 +69,32 @@ async function linkedOutcome(t, effect, agent) {
53
69
  },
54
70
  };
55
71
  }
72
+ /**
73
+ * True when this Tenant's remote MCP calls outlive the process that sent them (the gates
74
+ * service, F4.1 G3): after a takeover or a shutdown, the journaled call is re-sent and joins
75
+ * the running call or gets its answer, instead of becoming `uncertain`.
76
+ */
77
+ export function recoversMcpCalls(ctx) {
78
+ return ctx.toolGate.recovers === true;
79
+ }
80
+ /** True when `request` calls a tool of a remote (`streamable-http` or `sse`) MCP server. */
81
+ export function isRemoteMcpEffect(s, request) {
82
+ if (request.kind !== "tool")
83
+ return false;
84
+ const tool = mcpToolOf(s, request);
85
+ if (!tool)
86
+ return false;
87
+ const declared = findServer(s.manifest, tool.agentId, tool.capabilityId, tool.serverName);
88
+ return declared !== undefined && declared.server.type !== "stdio";
89
+ }
90
+ /**
91
+ * True when this Tenant's vault-backed model calls outlive the process that sent them (the
92
+ * gates service, P1.2): after a takeover or a shutdown, the journaled call is re-sent and joins
93
+ * the running call or gets its outcome, instead of becoming `uncertain`.
94
+ */
95
+ export function recoversModelCalls(ctx) {
96
+ return ctx.useVaultModel && ctx.modelGate.recovers === true;
97
+ }
56
98
  /**
57
99
  * What an effect's journal row must match on replay. A delegation's context carries only the
58
100
  * parent's tool call id, for its events; rows journaled before it existed still match.
@@ -85,6 +127,22 @@ export async function resolveEffect(ctx, request, signal, lease, segment = {}) {
85
127
  throw new Error("Effect identity request drift");
86
128
  if (existing.status === "completed")
87
129
  return resolved({ status: "completed", outcome: existing.outcome });
130
+ // A model call its previous owner left running at the gate: re-send it, same key and
131
+ // request, to join it or collect its outcome.
132
+ if (existing.status === "invoking" &&
133
+ request.kind === "model" &&
134
+ recoversModelCalls(ctx))
135
+ return { kind: "invoke", invoke: "model", journaled: existing.request };
136
+ // A remote MCP call its previous owner left running at the gate: the same, by effect id.
137
+ if (existing.status === "invoking" &&
138
+ recoversMcpCalls(ctx) &&
139
+ isRemoteMcpEffect(s, existing.request))
140
+ return {
141
+ kind: "invoke",
142
+ invoke: "mcp",
143
+ journaled: existing.request,
144
+ remote: true,
145
+ };
88
146
  if (request.kind === "agent" && existing.status === "pending") {
89
147
  const agentSessionId = existing.agentSessionId;
90
148
  if (agentSessionId) {
@@ -144,6 +202,7 @@ export async function resolveEffect(ctx, request, signal, lease, segment = {}) {
144
202
  return {
145
203
  kind: "invoke",
146
204
  invoke: mcpTool ? "mcp" : sandboxTool ? "sandbox" : "model",
205
+ ...(mcpTool && isRemoteMcpEffect(s, request) ? { remote: true } : {}),
147
206
  };
148
207
  const tool = request.kind === "tool"
149
208
  ? pinnedTool(agentManifest, request.capabilityId, request.toolName)
@@ -159,8 +218,6 @@ export async function resolveEffect(ctx, request, signal, lease, segment = {}) {
159
218
  context: request.context,
160
219
  status: "pending",
161
220
  generation: 0,
162
- claimId: null,
163
- leaseExpiresAt: null,
164
221
  ...(request.agent ? { agent: request.agent } : {}),
165
222
  };
166
223
  const action = request.kind === "hook"
@@ -189,7 +246,7 @@ export async function resolveEffect(ctx, request, signal, lease, segment = {}) {
189
246
  ...(action.kind === "tool" ? toolIds(request.context) : {}),
190
247
  input: action.input,
191
248
  });
192
- t.signalWork();
249
+ await offerAction(t, ctx, action);
193
250
  return resolved({ status: "pending" });
194
251
  });
195
252
  if (journaled.kind === "resolved")
@@ -203,10 +260,10 @@ export async function resolveEffect(ctx, request, signal, lease, segment = {}) {
203
260
  try {
204
261
  // The intent is committed; the call itself runs outside any transaction.
205
262
  const value = invoke === "mcp"
206
- ? await callMcpTool(ctx, request)
263
+ ? await callMcpTool(ctx, journaled.journaled ?? request, abortOn(signal, journaled.remote && recoversMcpCalls(ctx) ? ["cancel", "shutdown"] : ["cancel"]))
207
264
  : invoke === "sandbox"
208
265
  ? await callSandboxTool(ctx, request, signal)
209
- : await invokeModel(ctx, request, signal, segment.model);
266
+ : await invokeModel(ctx, journaled.journaled ?? request, signal, segment.model);
210
267
  return await store.tx(async (t) => {
211
268
  const s = await ownedSession(t, lease, request.sessionId);
212
269
  // Only a cancel discards an outcome in hand (§10.7). After any other abort (shutdown,
@@ -219,11 +276,23 @@ export async function resolveEffect(ctx, request, signal, lease, segment = {}) {
219
276
  effect.status = "completed";
220
277
  effect.outcome = { value };
221
278
  await t.put("effects", request.effectId, effect);
222
- const transcript = invoke === "model"
223
- ? assistantMessage(request, value)
224
- : toolCompleted(request, value);
225
- if (transcript)
226
- await t.event(s.id, request.turnId, invoke === "model" ? "message.assistant" : "tool.completed", transcript);
279
+ const failed = invoke === "model" ? modelFailed(request, value) : undefined;
280
+ // A summary call is never an assistant message; the last one publishes context.compacted.
281
+ const summarizing = invoke === "model" && Boolean(request.context.compaction);
282
+ const compacted = summarizing && !failed ? contextCompacted(request, value) : undefined;
283
+ const transcript = failed || summarizing
284
+ ? undefined
285
+ : invoke === "model"
286
+ ? assistantMessage(request, value)
287
+ : toolCompleted(request, value);
288
+ if (failed)
289
+ await t.event(s.id, request.turnId, "model.failed", failed);
290
+ if (compacted)
291
+ await t.event(s.id, request.turnId, "context.compacted", compacted);
292
+ if (transcript && invoke === "model")
293
+ await t.event(s.id, request.turnId, "message.assistant", transcript);
294
+ else if (transcript)
295
+ await t.event(s.id, request.turnId, "tool.completed", transcript);
227
296
  return { status: "completed", outcome: effect.outcome };
228
297
  });
229
298
  }
@@ -231,6 +300,18 @@ export async function resolveEffect(ctx, request, signal, lease, segment = {}) {
231
300
  // A lost epoch writes nothing: the new owner decides what the effect became.
232
301
  if (isOwnershipLost(error))
233
302
  throw error;
303
+ // A shutdown leaves a model call running at the gate, still `invoking`: the next advance
304
+ // re-sends it (P1.2). The segment stops for the shutdown as usual.
305
+ if (invoke === "model" && recoversModelCalls(ctx) && abortKind(signal) === "shutdown")
306
+ throw error;
307
+ // The same for a remote MCP call at the Tool Gate (G3). A user cancel stops it there: a
308
+ // keyed call outlives the request that sent it.
309
+ if (invoke === "mcp" && journaled.remote && recoversMcpCalls(ctx)) {
310
+ if (abortKind(signal) === "shutdown")
311
+ throw error;
312
+ if (abortKind(signal) === "cancel" && ctx.toolGate.cancel)
313
+ await ctx.toolGate.cancel({ tenantId: ctx.config.tenantId, effectId: request.effectId });
314
+ }
234
315
  await store.tx(async (t) => {
235
316
  const s = request.sessionId === lease.sessionId
236
317
  ? await t.assertEpoch(request.sessionId, lease.epoch)
@@ -312,8 +393,6 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
312
393
  context: request.context,
313
394
  status: "pending",
314
395
  generation: 0,
315
- claimId: null,
316
- leaseExpiresAt: null,
317
396
  kind: request.kind,
318
397
  path: request.path,
319
398
  key: request.key,
@@ -329,8 +408,6 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
329
408
  context: request.context,
330
409
  status: "pending",
331
410
  generation: 0,
332
- claimId: null,
333
- leaseExpiresAt: null,
334
411
  kind: "tool",
335
412
  path: request.path,
336
413
  key: request.key,
@@ -353,7 +430,7 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
353
430
  key: action.key,
354
431
  input: action.input,
355
432
  });
356
- t.signalWork();
433
+ await offerAction(t, ctx, action);
357
434
  return resolved({ status: "pending" });
358
435
  }
359
436
  return { kind: "agent", workflow };
@@ -388,15 +465,18 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
388
465
  if (exists)
389
466
  return;
390
467
  const definition = await leafDefinition(t, workflow, body, request);
391
- const sandboxOwnerId = sandboxSpecOf(workflow.manifest) || workflow.sandboxOwnerId
392
- ? owningSandboxSessionId(workflow, await sandboxLookup(t, workflow.sandboxOwnerId))
468
+ const lookup = await sandboxLookup(t, workflow.sandboxOwnerId);
469
+ const sandboxOwnerId = sessionSandboxSpec(workflow) || workflow.sandboxOwnerId
470
+ ? owningSandboxSessionId(workflow, lookup)
393
471
  : undefined;
472
+ // A sandbox chosen when the tree was opened reaches every agent in it, with its tools.
473
+ const inherited = inheritedSandbox(sandboxOwnerId === undefined ? undefined : lookup(sandboxOwnerId) ?? workflow, definition);
394
474
  const created = {
395
475
  id: agentSessionId,
396
476
  agentId: body.agentId,
397
477
  ownerUserId: workflow.ownerUserId,
398
- manifest: definition.manifest,
399
- manifestHash: definition.manifestHash,
478
+ manifest: inherited?.manifest ?? definition.manifest,
479
+ manifestHash: inherited?.manifestHash ?? definition.manifestHash,
400
480
  implementationVersion: definition.implementationVersion,
401
481
  status: "idle",
402
482
  activeTurnId: null,
@@ -410,7 +490,9 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
410
490
  credentialSelections: workflow.credentialSelections,
411
491
  pluginRoots: definition.pluginRoots ?? {},
412
492
  ...(sandboxOwnerId ? { sandboxOwnerId } : {}),
413
- streamIncarnation: newStreamIncarnation(),
493
+ ...(inherited
494
+ ? { sandbox: inherited.spec, sandboxSource: "shared" }
495
+ : {}),
414
496
  };
415
497
  await t.put("sessions", agentSessionId, created);
416
498
  });
@@ -518,7 +600,25 @@ export async function prepareMcp(ctx, lease, signal) {
518
600
  await t.put("sessions", id, current);
519
601
  });
520
602
  }
521
- async function callMcpTool(ctx, request) {
603
+ /**
604
+ * A signal that follows `signal` only for the abort kinds in `kinds`. An MCP call stops on a
605
+ * cancel; at the Tool Gate it also stops waiting on a shutdown, since the gate keeps the call
606
+ * for the next owner. Otherwise a shutdown or a deadline lets the call finish and records it,
607
+ * so the next advance replays it.
608
+ */
609
+ function abortOn(signal, kinds) {
610
+ const controller = new AbortController();
611
+ const follow = () => {
612
+ if (kinds.includes(abortKind(signal)))
613
+ controller.abort(signal.reason);
614
+ };
615
+ if (signal.aborted)
616
+ follow();
617
+ else
618
+ signal.addEventListener("abort", follow, { once: true });
619
+ return controller.signal;
620
+ }
621
+ async function callMcpTool(ctx, request, signal) {
522
622
  const s = await loadSession(ctx, request.sessionId);
523
623
  const tool = mcpToolOf(s, request);
524
624
  if (!tool)
@@ -532,8 +632,25 @@ async function callMcpTool(ctx, request) {
532
632
  args: request.input,
533
633
  manifest: s.manifest,
534
634
  pluginRoots: s.pluginRoots ?? {},
635
+ effectId: request.effectId,
636
+ signal,
535
637
  });
536
638
  }
639
+ /**
640
+ * The sandbox a linked session inherits from its tree's owner when that sandbox was chosen at
641
+ * open (pinned on the owner). A definition that declares its own sandbox keeps it, as before.
642
+ */
643
+ function inheritedSandbox(owner, definition) {
644
+ const spec = owner?.sandbox;
645
+ if (!spec || sandboxSpecOf(definition.manifest))
646
+ return undefined;
647
+ if (isWorkflowManifest(definition.manifest))
648
+ return { spec };
649
+ const sandboxed = withSandboxCapability(definition.manifest, spec);
650
+ if (!sandboxed.ok)
651
+ throw new Error(sandboxed.message);
652
+ return { spec, manifest: sandboxed.manifest, manifestHash: sandboxed.manifestHash };
653
+ }
537
654
  async function callSandboxTool(ctx, request, signal) {
538
655
  const { s, ownerId } = await ctx.store.tx(async (t) => {
539
656
  const s = await sessionOf(t, request.sessionId);
@@ -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
+ }
@@ -1,17 +1,23 @@
1
- import type { PostgresClient } from "../store/postgres/connect.js";
1
+ import { type PostgresClient } from "../store/postgres/connect.js";
2
2
  import type { Logger, TenantModelConfig } from "./types.js";
3
3
  export interface StartEphemeralRuntimeOptions {
4
4
  /**
5
- * Absolute Host root for `host.json`, the admin key and each Tenant's files (vault key,
5
+ * Absolute Host root for `host.json`, the admin key and the Tenant's files (vault key,
6
6
  * home, sandboxes). Caller creates any temporary directory. Session data never lands here.
7
7
  */
8
8
  hostRoot: string;
9
+ /** The Tenant's id when the database holds no Tenant yet. Default: a new id. */
9
10
  tenantId?: string;
11
+ /** The Tenant's name when the database holds no Tenant yet. Default `ephemeral`. */
10
12
  name?: string;
13
+ /** An application key for an application principal of the Tenant. Default: a new key. */
11
14
  applicationKey?: string;
12
15
  adminKey?: string;
13
16
  principalId?: string;
14
- /** SHA-256 of a derived Studio key; registers principal `studio`. */
17
+ /**
18
+ * SHA-256 of the Studio key; registers principal `studio` with it. Default: the key the
19
+ * admin key derives (`deriveStudioToken` in `@nylorun/admin`).
20
+ */
15
21
  studioCredentialHash?: string;
16
22
  /** Allowlisted baseline for childEnv (e.g. PATH). Never read from ambient here. */
17
23
  baseline?: Readonly<Record<string, string>>;
@@ -19,15 +25,23 @@ export interface StartEphemeralRuntimeOptions {
19
25
  sandboxBackend?: "auto" | "virtual";
20
26
  /** When true, close() leaves hostRoot on disk. */
21
27
  retainRoot?: boolean;
28
+ /** Allow browser requests (an `Origin` with a publishable key). Default off. */
29
+ browserAccess?: boolean;
30
+ /** Serve the Admin API on its own loopback listener (`adminUrl`). Default off. */
31
+ operatorListener?: boolean;
22
32
  logger?: Logger;
23
33
  /**
24
- * A Postgres pool: Tenants become schemas in it (`store-pg.ts`) instead of living in memory.
25
- * The caller ends the pool; the schemas stay.
34
+ * The Postgres database of the Tenant (one Tenant per database): a pool, which the caller
35
+ * ends, or a URL, for which the Runtime opens a pool and ends it on `close()`. A database
36
+ * that already holds a Tenant serves that one. The data stays after `close()`; a test drops
37
+ * its database.
26
38
  */
27
- database?: PostgresClient;
39
+ database: PostgresClient | string;
28
40
  }
29
41
  export interface EphemeralRuntime {
30
42
  url: string;
43
+ /** Where the Admin API answers: `url`, or the operator listener when requested. */
44
+ adminUrl: string;
31
45
  tenantId: string;
32
46
  applicationKey: string;
33
47
  adminKey: string;
@@ -36,7 +50,8 @@ export interface EphemeralRuntime {
36
50
  close(): Promise<void>;
37
51
  }
38
52
  /**
39
- * Private Host on port 0 with one Tenant (D9 / A19). Not durable (see the module comment).
53
+ * Private Host on port 0 serving one Tenant (D9 / A19). Durable only in its database (see the
54
+ * module comment).
40
55
  * Exported from `@nylorun/runtime` and `@nylorun/runtime/core`.
41
56
  */
42
57
  export declare function startEphemeralRuntime(options: StartEphemeralRuntimeOptions): Promise<EphemeralRuntime>;