@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
@@ -1,15 +1,14 @@
1
- import { embeddedAgent, flowDelegateManifest, hashManifest, isWorkflowManifestV2, } from "@nylorun/core/define";
2
- import { countActiveFlowWork, deriveAgentEffectSessionId, isFlowToolEffect, isWorkflowManifest, linkedMessageKey, linkedTurnEnd, } from "../core/flow-host.js";
1
+ import { embeddedAgent, flowDelegateManifest, hashManifest, } from "@nylorun/core/define";
2
+ import { countActiveFlowWork, deriveAgentEffectSessionId, isFlowToolEffect, isWorkflowManifest, linkedMessageInput, linkedMessageKey, linkedTurnEnd, settleAgentEffect, } from "../core/flow-host.js";
3
3
  import { mayDispatchMore } from "../core/limits.js";
4
- import { scrub } from "../redact.js";
5
4
  import { isRemoteMcpCall } from "../harness/calls.js";
5
+ import { isHttpToolCall } from "../gates/http-tool.js";
6
6
  import { owningSandboxSessionId, sandboxSpecOf, sessionSandboxSpec, } from "../sandbox/share.js";
7
7
  import { withSandboxCapability } from "../sandbox/session-sandbox.js";
8
8
  import { loadSession, ownedSession, sandboxLookup, sessionOf, } from "./context.js";
9
9
  import { fail } from "./http.js";
10
- import { linkedAgentOutput, turnManifestOf } from "./session.js";
10
+ import { turnManifestOf } from "./session.js";
11
11
  import { command } from "./commands.js";
12
- import { offerAction } from "./delivery.js";
13
12
  /**
14
13
  * The outcome of a flow `agent` effect once the linked turn it started ended, or undefined
15
14
  * while it has not. The linked session may still show an earlier iteration's turn.
@@ -19,7 +18,7 @@ export async function linkedOutcome(t, effect, agent) {
19
18
  if (!end || !agent)
20
19
  return undefined;
21
20
  if (end.status === "completed")
22
- return { value: linkedAgentOutput(agent, end.output) };
21
+ return { value: end.output };
23
22
  return {
24
23
  value: {
25
24
  kind: "failed",
@@ -32,16 +31,17 @@ export async function linkedOutcome(t, effect, agent) {
32
31
  };
33
32
  }
34
33
  /**
35
- * True when this Tenant's remote MCP calls outlive the process that sent them (the gates
36
- * service, F4.1 G3): after a takeover or a shutdown, the journaled call is re-sent and joins
37
- * the running call or gets its answer, instead of becoming `uncertain`.
34
+ * True when this Tenant's remote MCP and HTTP tool calls outlive the process that sent them
35
+ * (the gates service, F4.1 G3): after a takeover or a shutdown, the journaled call is re-sent
36
+ * and joins the running call or gets its answer, instead of becoming `uncertain`.
38
37
  */
39
- export function recoversMcpCalls(ctx) {
38
+ export function recoversToolCalls(ctx) {
40
39
  return ctx.toolGate.recovers === true;
41
40
  }
42
- /** True when `request` calls a tool of a remote (`streamable-http` or `sse`) MCP server. */
43
- export function isRemoteMcpEffect(s, request) {
44
- return isRemoteMcpCall({ rootManifest: s.manifest, mcpSnapshot: s.mcpSnapshot }, request);
41
+ /** True when `request` calls a tool of a declared MCP server (every one is remote) or an HTTP tool. */
42
+ export function isGateToolEffect(s, request) {
43
+ return (isRemoteMcpCall({ rootManifest: s.manifest, mcpSnapshot: s.mcpSnapshot }, request) ||
44
+ isHttpToolCall(s.manifest, request));
45
45
  }
46
46
  /**
47
47
  * True when this Tenant's vault-backed model calls outlive the process that sent them (the
@@ -51,7 +51,7 @@ export function isRemoteMcpEffect(s, request) {
51
51
  export function recoversModelCalls(ctx) {
52
52
  return ctx.useVaultModel && ctx.modelGate.recovers === true;
53
53
  }
54
- /** Journal and dispatch a new flow effect (agent / tool node / fn / verify). */
54
+ /** Journal and dispatch a new flow effect (agent or tool node). */
55
55
  export async function resolveNewFlowEffect(ctx, request, signal, lease) {
56
56
  const { store } = ctx;
57
57
  signal.throwIfAborted();
@@ -76,9 +76,7 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
76
76
  const outcome = await linkedOutcome(t, existing, agent);
77
77
  if (!outcome)
78
78
  return resolved({ status: "pending" });
79
- existing.status = "completed";
80
- existing.outcome = outcome;
81
- await t.put("effects", request.effectId, existing);
79
+ await settleAgentEffect({ t, effect: existing, outcome });
82
80
  return resolved({ status: "completed", outcome });
83
81
  }
84
82
  else {
@@ -94,64 +92,25 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
94
92
  });
95
93
  return resolved({ status: "pending" });
96
94
  }
97
- if (request.kind === "fn" ||
98
- request.kind === "verify" ||
99
- isFlowToolEffect(request)) {
100
- await t.put("effects", request.effectId, {
101
- request,
102
- status: "pending",
95
+ if (isFlowToolEffect(request)) {
96
+ // A tool stage without `http` would run the developer's code: refused at save, so this
97
+ // is a backstop (HTTP stages are executed by the harness, `harness-api/record.ts`). The
98
+ // stage fails; nothing runs.
99
+ const outcome = {
100
+ value: {
101
+ kind: "failed",
102
+ code: "tool.unavailable",
103
+ message: `The tool stage '${request.key ?? request.path ?? ""}' runs your code, and the Runtime runs no code of yours during a session`,
104
+ },
105
+ };
106
+ await t.put("effects", request.effectId, { request, status: "completed", outcome });
107
+ await t.event(workflow.id, workflow.activeTurnId, "node.started", {
108
+ path: request.path,
109
+ kind: "tool",
110
+ key: request.key,
111
+ ...(request.iterations !== undefined ? { iterations: request.iterations } : {}),
103
112
  });
104
- const action = request.kind === "fn" || request.kind === "verify"
105
- ? {
106
- actionId: request.effectId,
107
- sessionId: request.sessionId,
108
- turnId: request.turnId,
109
- agentId: request.agentId,
110
- manifestHash: request.manifestHash,
111
- implementationVersion: workflow.implementationVersion,
112
- input: request.input,
113
- context: request.context,
114
- status: "pending",
115
- generation: 0,
116
- kind: request.kind,
117
- path: request.path,
118
- key: request.key,
119
- }
120
- : {
121
- actionId: request.effectId,
122
- sessionId: request.sessionId,
123
- turnId: request.turnId,
124
- agentId: request.agentId,
125
- manifestHash: request.manifestHash,
126
- implementationVersion: workflow.implementationVersion,
127
- input: request.input,
128
- context: request.context,
129
- status: "pending",
130
- generation: 0,
131
- kind: "tool",
132
- path: request.path,
133
- key: request.key,
134
- };
135
- await t.put("actions", action.actionId, action);
136
- if (isFlowToolEffect(request)) {
137
- await t.event(workflow.id, workflow.activeTurnId, "node.started", {
138
- path: request.path,
139
- kind: "tool",
140
- key: request.key,
141
- ...(request.iterations !== undefined
142
- ? { iterations: request.iterations }
143
- : {}),
144
- });
145
- }
146
- await t.event(workflow.id, workflow.activeTurnId, "action.pending", {
147
- actionId: action.actionId,
148
- kind: action.kind,
149
- path: action.path,
150
- key: action.key,
151
- input: action.input,
152
- });
153
- await offerAction(t, ctx, action);
154
- return resolved({ status: "pending" });
113
+ return resolved({ status: "completed", outcome });
155
114
  }
156
115
  return { kind: "agent", workflow };
157
116
  });
@@ -184,7 +143,7 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
184
143
  await ownedSession(t, lease, workflow.id);
185
144
  if (exists)
186
145
  return;
187
- const definition = await leafDefinition(t, workflow, body, request);
146
+ const definition = leafDefinition(workflow, body, request);
188
147
  const lookup = await sandboxLookup(t, workflow.sandboxOwnerId);
189
148
  const sandboxOwnerId = sessionSandboxSpec(workflow) || workflow.sandboxOwnerId
190
149
  ? owningSandboxSessionId(workflow, lookup)
@@ -208,7 +167,6 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
208
167
  },
209
168
  vaultIds: workflow.vaultIds,
210
169
  credentialSelections: workflow.credentialSelections,
211
- pluginRoots: definition.pluginRoots ?? {},
212
170
  ...(sandboxOwnerId ? { sandboxOwnerId } : {}),
213
171
  ...(inherited
214
172
  ? { sandbox: inherited.spec, sandboxSource: "shared" }
@@ -218,21 +176,19 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
218
176
  });
219
177
  // Binds this effect to the one linked turn its message opens (`linkedTurnEnd`).
220
178
  const idempotencyKey = linkedMessageKey(request);
221
- const messageInput = body.input;
179
+ const messageInput = linkedMessageInput(body);
222
180
  const messageCommand = typeof messageInput === "string"
223
181
  ? {
224
182
  type: "message",
225
183
  content: messageInput,
226
184
  requestId: `flow-${request.effectId}`,
227
185
  idempotencyKey,
228
- ...(body.manifest ? { manifest: body.manifest } : {}),
229
186
  }
230
187
  : {
231
188
  type: "message",
232
189
  data: messageInput,
233
190
  requestId: `flow-${request.effectId}`,
234
191
  idempotencyKey,
235
- ...(body.manifest ? { manifest: body.manifest } : {}),
236
192
  };
237
193
  const accepted = (await command(ctx, agentSessionId, messageCommand, {
238
194
  kind: "application",
@@ -242,8 +198,8 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
242
198
  return store.tx(async (t) => {
243
199
  const s = await ownedSession(t, lease, request.sessionId);
244
200
  const agent = await sessionOf(t, agentSessionId);
245
- // Only a Loop's own agent turns are iterations; a step elsewhere in the flow is not.
246
- if (typeof request.context.loopPath === "string")
201
+ // Only a Loop body's agent turns are iterations: not its verifier, nor a step elsewhere.
202
+ if (typeof request.context.loopPath === "string" && request.context.role !== "verify-agent")
247
203
  await t.event(s.id, s.activeTurnId, "loop.iteration", {
248
204
  path: request.context.loopPath,
249
205
  n,
@@ -260,11 +216,10 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
260
216
  // May already be settled if the agent was fast / replayed.
261
217
  const outcome = await linkedOutcome(t, { request, agentSessionId }, agent);
262
218
  if (outcome) {
263
- await t.put("effects", request.effectId, {
264
- request,
265
- status: "completed",
219
+ await settleAgentEffect({
220
+ t,
221
+ effect: { request, status: "pending", agentSessionId },
266
222
  outcome,
267
- agentSessionId,
268
223
  });
269
224
  return { status: "completed", outcome };
270
225
  }
@@ -286,30 +241,19 @@ function inheritedSandbox(owner, definition) {
286
241
  throw new Error(sandboxed.message);
287
242
  return { spec, manifest: sandboxed.manifest, manifestHash: sandboxed.manifestHash };
288
243
  }
289
- /** Vault authorization for an MCP server request made on behalf of a session. */
244
+ /**
245
+ * The credential of an MCP server or HTTP tool request made on behalf of a session: from its
246
+ * attached vaults, else the operator's credential resolver (`vault/sources.ts`).
247
+ */
290
248
  export async function authorize(ctx, sessionId, request) {
291
249
  const s = await loadSession(ctx, sessionId);
292
- const result = await ctx.vault.authorize({
293
- sessionId,
294
- vaultIds: s.vaultIds ?? [],
295
- credentialSelections: s.credentialSelections ?? [],
296
- url: request.url,
297
- serverName: request.serverName,
298
- });
299
- if (result.status === "authorized") {
300
- const token = result.headers.authorization.slice("Bearer ".length);
301
- scrub({ authorization: result.headers.authorization, url: result.url }, [
302
- token,
303
- ]);
304
- }
305
- return result;
250
+ return ctx.credentials.authorize({ ...s, id: sessionId }, request);
306
251
  }
307
252
  /**
308
- * The definition a flow leaf's session runs. A v2 workflow embeds its leaves, so they
309
- * come from the workflow's own manifest and can't drift from it; v1 leaves come from the
310
- * registry by id.
253
+ * The definition a flow leaf's session runs. A flow agent embeds its leaves, so they come
254
+ * from the workflow's own manifest and can't drift from it.
311
255
  */
312
- async function leafDefinition(t, workflow, body, request) {
256
+ function leafDefinition(workflow, body, request) {
313
257
  // A flow agent used as a tool: its manifest is inlined in the parent's pinned tool.
314
258
  if (request.context.role === "delegate" && !isWorkflowManifest(workflow.manifest)) {
315
259
  const flow = flowDelegateManifest(turnManifestOf(workflow), body.agentId);
@@ -319,35 +263,17 @@ async function leafDefinition(t, workflow, body, request) {
319
263
  manifest: flow,
320
264
  manifestHash: hashManifest(flow),
321
265
  implementationVersion: workflow.implementationVersion,
322
- pluginRoots: leafPluginRoots(workflow.pluginRoots, body.agentId),
323
266
  };
324
267
  }
325
- if (isWorkflowManifestV2(workflow.manifest)) {
326
- const leaf = embeddedAgent(workflow.manifest, body.flow ?? [], body.agentId);
327
- if (!leaf || isWorkflowManifestV2(leaf))
328
- fail(404, `Agent '${body.agentId}' is not embedded in workflow '${workflow.manifest.id}'`);
329
- const manifest = leaf;
330
- return {
331
- manifest,
332
- manifestHash: hashManifest(manifest),
333
- implementationVersion: workflow.implementationVersion,
334
- pluginRoots: leafPluginRoots(workflow.pluginRoots, body.agentId),
335
- };
336
- }
337
- const definition = await t.get("definitions", body.agentId);
338
- if (!definition)
339
- fail(404, "Definition not found");
340
- return definition;
341
- }
342
- /**
343
- * A v2 workflow keys its leaves' plugin roots `<agentId>/<capability>`, and an agent keys
344
- * a flow agent it uses as a tool the same way (`<flowId>/<leafId>/<capability>`).
345
- */
346
- function leafPluginRoots(roots, agentId) {
347
- const own = {};
348
- const prefix = `${agentId}/`;
349
- for (const [key, root] of Object.entries(roots ?? {}))
350
- if (key.startsWith(prefix))
351
- own[key.slice(prefix.length)] = root;
352
- return own;
268
+ const leaf = isWorkflowManifest(workflow.manifest)
269
+ ? embeddedAgent(workflow.manifest, body.flow ?? [], body.agentId)
270
+ : undefined;
271
+ if (!leaf || isWorkflowManifest(leaf))
272
+ return fail(404, `Agent '${body.agentId}' is not embedded in workflow '${workflow.manifest.id}'`);
273
+ const manifest = leaf;
274
+ return {
275
+ manifest,
276
+ manifestHash: hashManifest(manifest),
277
+ implementationVersion: workflow.implementationVersion,
278
+ };
353
279
  }
@@ -1,5 +1,8 @@
1
1
  import { type PostgresClient } from "../store/postgres/connect.js";
2
+ import type { TrustedIssuerConfig } from "./identity-file.js";
3
+ import { type TrustedIssuers } from "./issuers.js";
2
4
  import type { Logger, TenantModelConfig } from "./types.js";
5
+ import type { ResolverConfig } from "../vault/sources.js";
3
6
  export interface StartEphemeralRuntimeOptions {
4
7
  /**
5
8
  * Absolute Host root for `host.json`, the admin key and the Tenant's files (vault key,
@@ -12,6 +15,11 @@ export interface StartEphemeralRuntimeOptions {
12
15
  name?: string;
13
16
  /** An application key for an application principal of the Tenant. Default: a new key. */
14
17
  applicationKey?: string;
18
+ /**
19
+ * A management key (protocol 8), registered as the key `bootstrap` like
20
+ * `NYLORUN_MANAGEMENT_KEY_FILE`. Default: a new key.
21
+ */
22
+ managementKey?: string;
15
23
  adminKey?: string;
16
24
  principalId?: string;
17
25
  /**
@@ -19,16 +27,21 @@ export interface StartEphemeralRuntimeOptions {
19
27
  * admin key derives (`deriveStudioToken` in `@nylorun/admin`).
20
28
  */
21
29
  studioCredentialHash?: string;
22
- /** Allowlisted baseline for childEnv (e.g. PATH). Never read from ambient here. */
23
- baseline?: Readonly<Record<string, string>>;
24
30
  model?: TenantModelConfig;
25
31
  sandboxBackend?: "auto" | "virtual";
26
32
  /** When true, close() leaves hostRoot on disk. */
27
33
  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;
34
+ /**
35
+ * Trusted issuers whose JWTs the Tenant API accepts (Host feature `trusted-issuers`): the
36
+ * issuers of an identity file (`parseIdentityFile`), or ones already built with
37
+ * `createTrustedIssuers`. Default none.
38
+ */
39
+ issuers?: readonly TrustedIssuerConfig[] | TrustedIssuers;
40
+ /**
41
+ * The operator's credential resolver (F9 C1): asked for a person's MCP credential when the
42
+ * session's vaults hold none. Default: none (vaults only).
43
+ */
44
+ resolver?: ResolverConfig;
32
45
  logger?: Logger;
33
46
  /**
34
47
  * The Postgres database of the Tenant (one Tenant per database): a pool, which the caller
@@ -40,10 +53,10 @@ export interface StartEphemeralRuntimeOptions {
40
53
  }
41
54
  export interface EphemeralRuntime {
42
55
  url: string;
43
- /** Where the Admin API answers: `url`, or the operator listener when requested. */
44
- adminUrl: string;
45
56
  tenantId: string;
46
57
  applicationKey: string;
58
+ /** The Management API's key (`/v1/tenant/*`), principal `bootstrap`. */
59
+ managementKey: string;
47
60
  adminKey: string;
48
61
  principalId: string;
49
62
  hostRoot: string;
@@ -24,6 +24,7 @@ import { createTenantLogger } from "./logger.js";
24
24
  import { hostPaths, tenantPaths } from "./paths.js";
25
25
  import { hostPrincipals } from "./principals.js";
26
26
  import { openTenantRuntime } from "./runtime.js";
27
+ import { createTrustedIssuers } from "./issuers.js";
27
28
  const nodeRequire = createRequire(import.meta.url);
28
29
  function coreVersion() {
29
30
  try {
@@ -64,8 +65,12 @@ export async function startEphemeralRuntime(options) {
64
65
  warn() { },
65
66
  error() { },
66
67
  };
67
- const baseline = options.baseline ?? {};
68
68
  const model = options.model ?? { kind: "scripted", output: "ok" };
69
+ const issuers = options.issuers === undefined
70
+ ? undefined
71
+ : Array.isArray(options.issuers)
72
+ ? createTrustedIssuers(options.issuers)
73
+ : options.issuers;
69
74
  const configFor = (tenantId) => {
70
75
  const tenant = tenantPaths(hostRoot);
71
76
  // A seeded `sandbox.backend` setting overrides this when the Tenant opens.
@@ -76,11 +81,8 @@ export async function startEphemeralRuntime(options) {
76
81
  paths: tenant,
77
82
  sandbox: { backend: sandboxBackend },
78
83
  model,
79
- childEnv: Object.freeze({
80
- ...baseline,
81
- HOME: tenant.home,
82
- TMPDIR: tenant.tmp,
83
- }),
84
+ ...(issuers ? { issuers } : {}),
85
+ ...(options.resolver ? { resolver: options.resolver } : {}),
84
86
  logger: createTenantLogger({
85
87
  tenantId,
86
88
  logPath: tenant.log,
@@ -92,6 +94,7 @@ export async function startEphemeralRuntime(options) {
92
94
  ? createPostgresClient(options.database)
93
95
  : undefined;
94
96
  const applicationKey = options.applicationKey ?? mintBearerToken();
97
+ const managementKey = options.managementKey ?? mintBearerToken();
95
98
  const principalId = options.principalId ?? `principal_${randomBytes(8).toString("hex")}`;
96
99
  const module = createTenantModule({
97
100
  open: createPostgresTenantOpener({
@@ -103,6 +106,7 @@ export async function startEphemeralRuntime(options) {
103
106
  principals: hostPrincipals({
104
107
  adminKey,
105
108
  application: { principalId, key: applicationKey },
109
+ bootstrapKey: managementKey,
106
110
  ...(options.studioCredentialHash
107
111
  ? { studioCredentialHash: options.studioCredentialHash }
108
112
  : {}),
@@ -139,13 +143,8 @@ export async function startEphemeralRuntime(options) {
139
143
  hostRoot,
140
144
  module,
141
145
  config: hostConfig,
142
- credentials,
143
146
  logger,
144
147
  coreVersion: coreVersion(),
145
- browserAccess: options.browserAccess === true,
146
- ...(options.operatorListener
147
- ? { operator: { host: "127.0.0.1", port: 0 } }
148
- : {}),
149
148
  });
150
149
  // Opens the Tenant: creates it in the database first when it holds none.
151
150
  await host.listen();
@@ -166,9 +165,9 @@ export async function startEphemeralRuntime(options) {
166
165
  let closed = false;
167
166
  return {
168
167
  url: started.url,
169
- adminUrl: started.adminUrl,
170
168
  tenantId,
171
169
  applicationKey,
170
+ managementKey,
172
171
  adminKey,
173
172
  principalId,
174
173
  hostRoot,
@@ -17,7 +17,7 @@
17
17
  * The host token is accepted only by the Harness API listener (a connection serving that one
18
18
  * sandbox, and `host.renew`); the egress token only by egress-gate, as the proxy credential
19
19
  * (`Proxy-Authorization`). Neither is a bearer anywhere else: the Tenant API's bearer check
20
- * accepts subject tokens only, and the gates run tokens only.
20
+ * accepts trusted issuers' tokens only, and the gates run tokens only.
21
21
  *
22
22
  * Verification reads the key row on every call (a revoked key refuses at once) and, with
23
23
  * `currentHost`, the sandbox's row (one indexed read): the epoch must be the sandbox's, the
@@ -25,7 +25,7 @@
25
25
  */
26
26
  import { randomUUID } from "node:crypto";
27
27
  import { errors, importJWK, jwtVerify } from "jose";
28
- import { subjectTokenIssuer } from "@nylorun/core/contracts";
28
+ import { tenantTokenIssuer } from "@nylorun/core/contracts";
29
29
  import { CLOCK_TOLERANCE_SECONDS, tokenKeyId } from "./jwt.js";
30
30
  export const HOST_TOKEN_TYP = "nylorun-host+jwt";
31
31
  export const HOST_TOKEN_AUD = "nylorun-harness-api";
@@ -52,7 +52,7 @@ export async function mintEgressToken(signer, request) {
52
52
  const { token } = await signer.keys.sign({
53
53
  typ: EGRESS_TOKEN_TYP,
54
54
  claims: {
55
- iss: subjectTokenIssuer(tenantId),
55
+ iss: tenantTokenIssuer(tenantId),
56
56
  aud: EGRESS_TOKEN_AUD,
57
57
  sbx: sandboxId,
58
58
  epc: epoch,
@@ -73,7 +73,7 @@ export async function mintHostTokens(signer, host) {
73
73
  signer.keys.sign({
74
74
  typ: HOST_TOKEN_TYP,
75
75
  claims: {
76
- iss: subjectTokenIssuer(tenantId),
76
+ iss: tenantTokenIssuer(tenantId),
77
77
  aud: HOST_TOKEN_AUD,
78
78
  sub: host.sandboxId,
79
79
  sbx: host.sandboxId,
@@ -126,7 +126,7 @@ export async function verifyHostToken(store, tenantId, raw, kind, cache = new Ma
126
126
  try {
127
127
  const verified = await jwtVerify(raw, key, {
128
128
  algorithms: ["ES256"],
129
- issuer: subjectTokenIssuer(tenantId),
129
+ issuer: tenantTokenIssuer(tenantId),
130
130
  audience: aud,
131
131
  typ,
132
132
  clockTolerance: CLOCK_TOLERANCE_SECONDS,
@@ -0,0 +1,75 @@
1
+ /**
2
+ * The identity file (Host feature `trusted-issuers`, F9 I2): the YAML file
3
+ * `NYLORUN_IDENTITY_FILE` names, listing the issuers whose JWTs the Tenant API accepts as
4
+ * bearers. Read once at boot (`host/main.ts`); a change takes a restart. A malformed file stops
5
+ * the boot with a message naming the issuer and the field.
6
+ *
7
+ * ```yaml
8
+ * issuers:
9
+ * - name: keycloak
10
+ * issuer: https://sso.acme.dev/realms/eng
11
+ * audience: nylorun
12
+ * jwks: https://sso.acme.dev/realms/eng/protocol/openid-connect/certs # or keys: [<PEM>]
13
+ * subject: "u:{sub}" # scalar claims only
14
+ * scopes: { claim: nylorun_scopes } # or { fixed: [sessions:own, agents:read] }
15
+ * allowedScopes: [agents:read, sessions:own, sandboxes:write, studio]
16
+ * agents: [support] # optional allowlist; absent means all
17
+ * sandboxes: ["{org_id}/*"] # optional grants; absent reaches none
18
+ * maxLifetime: 15m
19
+ * ```
20
+ */
21
+ import { type KeyObject } from "node:crypto";
22
+ import { type IssuerScope } from "@nylorun/core/contracts";
23
+ /** The JWT algorithms an issuer may sign with. */
24
+ export declare const ISSUER_ALGORITHMS: readonly ["RS256", "ES256", "EdDSA"];
25
+ export type IssuerAlgorithm = (typeof ISSUER_ALGORITHMS)[number];
26
+ /** A public key an issuer signs with, and the one algorithm it verifies. */
27
+ export interface IssuerKey {
28
+ readonly key: KeyObject;
29
+ readonly alg: IssuerAlgorithm;
30
+ readonly kid?: string;
31
+ }
32
+ /** One trusted issuer, as the identity file declares it. */
33
+ export interface TrustedIssuerConfig {
34
+ /** Its name: `issuer:<name>` in `GET /v1/me` and the token's role. */
35
+ readonly name: string;
36
+ /** The `iss` its tokens carry, exactly. */
37
+ readonly issuer: string;
38
+ /** The `aud` its tokens must carry. */
39
+ readonly audience: string;
40
+ /** Where its public keys are fetched (a JWKS URL); or `keys`. */
41
+ readonly jwks?: string;
42
+ /** Its public keys, given in the file (PEM); or `jwks`. */
43
+ readonly keys?: readonly IssuerKey[];
44
+ /** The subject template, such as `u:{sub}`: at least one claim reference. */
45
+ readonly subject: string;
46
+ /** Where its tokens' scopes come from: a claim, or a fixed list. */
47
+ readonly scopes: {
48
+ readonly claim: string;
49
+ } | {
50
+ readonly fixed: readonly IssuerScope[];
51
+ };
52
+ /** The scopes its tokens may carry; others are dropped. */
53
+ readonly allowedScopes: readonly IssuerScope[];
54
+ /** The agents its tokens reach; absent reaches all. */
55
+ readonly agents?: readonly string[];
56
+ /** Sandbox grant templates, such as `{org_id}/*`; absent reaches no sandbox. */
57
+ readonly sandboxes?: readonly string[];
58
+ /** The longest `exp - iat` it may sign, in seconds. */
59
+ readonly maxLifetimeSeconds: number;
60
+ }
61
+ /** A malformed identity file: the message names the issuer and the field. */
62
+ export declare class IdentityFileError extends Error {
63
+ constructor(message: string);
64
+ }
65
+ /** The claim names a template references. */
66
+ export declare function claimReferences(template: string): string[];
67
+ /** `template` with every reference replaced by `value(claim)`; undefined when one has none. */
68
+ export declare function renderTemplate(template: string, value: (claim: string) => string | undefined): string | undefined;
69
+ /** A PEM public key an issuer may sign with, and its algorithm; throws a message otherwise. */
70
+ export declare function issuerKey(key: KeyObject, kid?: string): IssuerKey;
71
+ /**
72
+ * The issuers of an identity file's text. Throws `IdentityFileError` naming the file, the issuer
73
+ * and the field when the file is malformed.
74
+ */
75
+ export declare function parseIdentityFile(text: string, source?: string): TrustedIssuerConfig[];