@nylorun/runtime 0.15.2-beta → 0.17.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 (291) hide show
  1. package/CHANGELOG.md +134 -0
  2. package/README.md +68 -29
  3. package/dist/adapters/execution/restate.d.ts +9 -1
  4. package/dist/adapters/execution/restate.js +64 -5
  5. package/dist/adapters/sandbox/local.d.ts +10 -0
  6. package/dist/adapters/sandbox/local.js +195 -0
  7. package/dist/adapters/sandbox/virtual.js +36 -1
  8. package/dist/admin-openapi.json +341 -7
  9. package/dist/api/a2a/tasks.js +6 -1
  10. package/dist/api/ag-ui/endpoint.js +1 -2
  11. package/dist/api/ag-ui/history.js +6 -1
  12. package/dist/api/ag-ui/routes.d.ts +1 -1
  13. package/dist/api/ag-ui/routes.js +15 -13
  14. package/dist/api/components.d.ts +2667 -711
  15. package/dist/api/components.js +24 -11
  16. package/dist/api/gate/keys.d.ts +4 -3
  17. package/dist/api/gate/routes.d.ts +19 -3
  18. package/dist/api/gate/routes.js +162 -53
  19. package/dist/api/http/app.js +6 -0
  20. package/dist/api/http/define.d.ts +19 -12
  21. package/dist/api/http/define.js +13 -27
  22. package/dist/api/http/routes/access.d.ts +3 -19
  23. package/dist/api/http/routes/access.js +12 -172
  24. package/dist/api/http/routes/actions.js +0 -1
  25. package/dist/api/http/routes/artifacts.d.ts +11 -0
  26. package/dist/api/http/routes/artifacts.js +516 -0
  27. package/dist/api/http/routes/me.d.ts +13 -0
  28. package/dist/api/http/routes/me.js +45 -0
  29. package/dist/api/http/routes/sandboxes.d.ts +9 -0
  30. package/dist/api/http/routes/sandboxes.js +150 -0
  31. package/dist/api/http/routes/sessions.js +9 -13
  32. package/dist/api/http/routes/tenant.js +36 -2
  33. package/dist/api/http/routes/vaults.d.ts +12 -3
  34. package/dist/api/http/routes/vaults.js +122 -54
  35. package/dist/api/openapi.js +4 -10
  36. package/dist/artifacts/config.d.ts +23 -0
  37. package/dist/artifacts/config.js +27 -0
  38. package/dist/artifacts/export.d.ts +8 -0
  39. package/dist/artifacts/export.js +205 -0
  40. package/dist/artifacts/files.d.ts +36 -0
  41. package/dist/artifacts/files.js +45 -0
  42. package/dist/artifacts/folders.d.ts +60 -0
  43. package/dist/artifacts/folders.js +209 -0
  44. package/dist/artifacts/links.d.ts +28 -0
  45. package/dist/artifacts/links.js +92 -0
  46. package/dist/artifacts/media-types.d.ts +13 -0
  47. package/dist/artifacts/media-types.js +80 -0
  48. package/dist/artifacts/parts.d.ts +34 -0
  49. package/dist/artifacts/parts.js +38 -0
  50. package/dist/artifacts/service.d.ts +74 -0
  51. package/dist/artifacts/service.js +301 -0
  52. package/dist/artifacts/workspace.d.ts +60 -0
  53. package/dist/artifacts/workspace.js +57 -0
  54. package/dist/artifacts/zip.d.ts +10 -0
  55. package/dist/artifacts/zip.js +126 -0
  56. package/dist/blob/fs.d.ts +13 -0
  57. package/dist/blob/fs.js +194 -0
  58. package/dist/blob/index.d.ts +9 -0
  59. package/dist/blob/index.js +9 -0
  60. package/dist/blob/s3.d.ts +42 -0
  61. package/dist/blob/s3.js +366 -0
  62. package/dist/blob/types.d.ts +108 -0
  63. package/dist/blob/types.js +85 -0
  64. package/dist/core/sandbox-routes.d.ts +2 -2
  65. package/dist/core/sandbox-routes.js +4 -3
  66. package/dist/execution/memory.d.ts +11 -1
  67. package/dist/execution/memory.js +77 -3
  68. package/dist/execution/types.d.ts +54 -1
  69. package/dist/execution/types.js +31 -0
  70. package/dist/gates/contract.d.ts +14 -13
  71. package/dist/gates/contract.js +13 -12
  72. package/dist/gates/egress.d.ts +72 -0
  73. package/dist/gates/egress.js +319 -0
  74. package/dist/gates/handler.d.ts +3 -0
  75. package/dist/gates/handler.js +3 -2
  76. package/dist/gates/http-client.d.ts +3 -2
  77. package/dist/gates/http-client.js +17 -6
  78. package/dist/gates/in-process.d.ts +13 -1
  79. package/dist/gates/in-process.js +11 -0
  80. package/dist/gates/inflight.d.ts +23 -4
  81. package/dist/gates/inflight.js +18 -4
  82. package/dist/gates/mcp-handler.js +5 -1
  83. package/dist/gates/model-gate.d.ts +6 -2
  84. package/dist/gates/tenant-vaults.d.ts +18 -9
  85. package/dist/gates/tenant-vaults.js +17 -12
  86. package/dist/gates/tool-calls.d.ts +9 -5
  87. package/dist/gates/tool-calls.js +6 -6
  88. package/dist/gates/tool-client.d.ts +9 -2
  89. package/dist/gates/tool-client.js +56 -22
  90. package/dist/gates/tool-contract.d.ts +12 -7
  91. package/dist/gates/tool-contract.js +10 -4
  92. package/dist/gates/tool-gate.d.ts +1 -0
  93. package/dist/harness/calls.d.ts +52 -0
  94. package/dist/harness/calls.js +113 -0
  95. package/dist/harness/executors.d.ts +22 -0
  96. package/dist/harness/executors.js +96 -0
  97. package/dist/harness/main.d.ts +6 -0
  98. package/dist/harness/main.js +103 -0
  99. package/dist/harness/pod.d.ts +21 -0
  100. package/dist/harness/pod.js +119 -0
  101. package/dist/harness/run-tokens.d.ts +18 -0
  102. package/dist/harness/run-tokens.js +15 -0
  103. package/dist/harness/service.d.ts +46 -0
  104. package/dist/harness/service.js +179 -0
  105. package/dist/harness/ws-client.d.ts +43 -0
  106. package/dist/harness/ws-client.js +122 -0
  107. package/dist/harness/ws-port.d.ts +22 -0
  108. package/dist/harness/ws-port.js +78 -0
  109. package/dist/harness-api/in-process.d.ts +24 -0
  110. package/dist/harness-api/in-process.js +119 -0
  111. package/dist/harness-api/record.d.ts +44 -0
  112. package/dist/harness-api/record.js +250 -0
  113. package/dist/harness-api/renew.d.ts +37 -0
  114. package/dist/harness-api/renew.js +77 -0
  115. package/dist/harness-api/server.d.ts +104 -0
  116. package/dist/harness-api/server.js +408 -0
  117. package/dist/harness-api/start.d.ts +36 -0
  118. package/dist/harness-api/start.js +118 -0
  119. package/dist/harness-api/workspace.d.ts +53 -0
  120. package/dist/harness-api/workspace.js +201 -0
  121. package/dist/harness-api/ws-server.d.ts +34 -0
  122. package/dist/harness-api/ws-server.js +227 -0
  123. package/dist/host/admin-api.d.ts +12 -5
  124. package/dist/host/admin-api.js +72 -6
  125. package/dist/host/app.d.ts +0 -1
  126. package/dist/host/app.js +35 -24
  127. package/dist/host/config-for.d.ts +4 -0
  128. package/dist/host/config-for.js +2 -0
  129. package/dist/host/config.d.ts +0 -6
  130. package/dist/host/create-host.d.ts +3 -10
  131. package/dist/host/create-host.js +4 -1
  132. package/dist/host/gates.d.ts +27 -2
  133. package/dist/host/gates.js +52 -2
  134. package/dist/host/http.d.ts +4 -1
  135. package/dist/host/http.js +6 -1
  136. package/dist/host/main.js +167 -37
  137. package/dist/host/stack-config.d.ts +112 -9
  138. package/dist/host/stack-config.js +222 -20
  139. package/dist/index.d.ts +3 -0
  140. package/dist/index.js +2 -0
  141. package/dist/keys/client.js +9 -1
  142. package/dist/keys/keys.d.ts +12 -2
  143. package/dist/keys/keys.js +8 -0
  144. package/dist/mcp/connect.js +2 -1
  145. package/dist/mcp/pool.d.ts +1 -0
  146. package/dist/mcp/pool.js +1 -0
  147. package/dist/mcp/snapshot.d.ts +8 -1
  148. package/dist/mcp/snapshot.js +28 -0
  149. package/dist/media.d.ts +5 -24
  150. package/dist/media.js +5 -0
  151. package/dist/model/pi-model.d.ts +6 -2
  152. package/dist/model/pi-model.js +39 -22
  153. package/dist/node/index.d.ts +1 -1
  154. package/dist/node/index.js +1 -1
  155. package/dist/openapi.json +14084 -9426
  156. package/dist/record/index.d.ts +1 -0
  157. package/dist/record/index.js +1 -0
  158. package/dist/record/sandbox.d.ts +25 -0
  159. package/dist/record/sandbox.js +43 -0
  160. package/dist/sandbox/capability.d.ts +3 -0
  161. package/dist/sandbox/capability.js +10 -0
  162. package/dist/sandbox/egress-token.d.ts +26 -0
  163. package/dist/sandbox/egress-token.js +8 -0
  164. package/dist/sandbox/join.d.ts +37 -0
  165. package/dist/sandbox/join.js +165 -0
  166. package/dist/sandbox/manager.d.ts +99 -34
  167. package/dist/sandbox/manager.js +216 -84
  168. package/dist/sandbox/placement.d.ts +27 -0
  169. package/dist/sandbox/placement.js +44 -0
  170. package/dist/sandbox/pods/client.d.ts +90 -0
  171. package/dist/sandbox/pods/client.js +79 -0
  172. package/dist/sandbox/pods/lifecycle.d.ts +91 -0
  173. package/dist/sandbox/pods/lifecycle.js +178 -0
  174. package/dist/sandbox/pods/name.d.ts +1 -0
  175. package/dist/sandbox/pods/name.js +28 -0
  176. package/dist/sandbox/pods/network-gate.d.ts +25 -0
  177. package/dist/sandbox/pods/network-gate.js +71 -0
  178. package/dist/sandbox/pods/reconcile.d.ts +11 -0
  179. package/dist/sandbox/pods/reconcile.js +152 -0
  180. package/dist/sandbox/pods/spec.d.ts +45 -0
  181. package/dist/sandbox/pods/spec.js +62 -0
  182. package/dist/sandbox/records.d.ts +29 -0
  183. package/dist/sandbox/records.js +79 -0
  184. package/dist/sandbox/resolve.d.ts +5 -0
  185. package/dist/sandbox/resolve.js +3 -2
  186. package/dist/sandbox/session-sandbox.js +7 -5
  187. package/dist/sandbox/share.d.ts +11 -0
  188. package/dist/sandbox/share.js +10 -0
  189. package/dist/sandbox/tenant-config.d.ts +16 -1
  190. package/dist/sandbox/tenant-config.js +9 -0
  191. package/dist/sandbox/types.d.ts +29 -1
  192. package/dist/sandbox/types.js +13 -1
  193. package/dist/store/postgres/drizzle/0004_sandbox_resources.sql +20 -0
  194. package/dist/store/postgres/drizzle/0005_artifacts.sql +27 -0
  195. package/dist/store/postgres/drizzle/0006_folder_artifacts.sql +12 -0
  196. package/dist/store/postgres/drizzle/0007_sandbox_pods.sql +14 -0
  197. package/dist/store/postgres/drizzle/0008_installation_vaults.sql +2 -0
  198. package/dist/store/postgres/drizzle/0009_oauth_pending.sql +19 -0
  199. package/dist/store/postgres/drizzle/0010_oss_auth_removals.sql +3 -0
  200. package/dist/store/postgres/drizzle/meta/_journal.json +49 -0
  201. package/dist/store/postgres/record-writer.d.ts +6 -1
  202. package/dist/store/postgres/record-writer.js +20 -2
  203. package/dist/store/postgres/schema.d.ts +1168 -250
  204. package/dist/store/postgres/schema.js +174 -28
  205. package/dist/store/postgres/store.js +339 -103
  206. package/dist/store/postgres/tenant.d.ts +1 -1
  207. package/dist/store/postgres/tenant.js +2 -2
  208. package/dist/store/types.d.ts +163 -32
  209. package/dist/streams/control.d.ts +2 -5
  210. package/dist/streams/control.js +5 -8
  211. package/dist/streams/types.d.ts +8 -8
  212. package/dist/streams/types.js +1 -1
  213. package/dist/tenant/advance.js +218 -160
  214. package/dist/tenant/artifact-tool.d.ts +5 -0
  215. package/dist/tenant/artifact-tool.js +119 -0
  216. package/dist/tenant/auth.d.ts +13 -9
  217. package/dist/tenant/auth.js +25 -32
  218. package/dist/tenant/commands.js +31 -10
  219. package/dist/tenant/context.d.ts +83 -26
  220. package/dist/tenant/delivery-token.js +4 -4
  221. package/dist/tenant/effects.d.ts +24 -14
  222. package/dist/tenant/effects.js +10 -398
  223. package/dist/tenant/ephemeral.d.ts +14 -2
  224. package/dist/tenant/ephemeral.js +8 -1
  225. package/dist/tenant/history.d.ts +13 -23
  226. package/dist/tenant/history.js +14 -62
  227. package/dist/tenant/host-token.d.ts +92 -0
  228. package/dist/tenant/host-token.js +179 -0
  229. package/dist/tenant/identity-file.d.ts +75 -0
  230. package/dist/tenant/identity-file.js +202 -0
  231. package/dist/tenant/issuers.d.ts +48 -0
  232. package/dist/tenant/issuers.js +311 -0
  233. package/dist/tenant/jwt.js +3 -3
  234. package/dist/tenant/module.js +1 -0
  235. package/dist/tenant/operator-keys.d.ts +18 -0
  236. package/dist/tenant/operator-keys.js +55 -0
  237. package/dist/tenant/outbound.d.ts +30 -0
  238. package/dist/tenant/outbound.js +142 -10
  239. package/dist/tenant/paths.d.ts +2 -1
  240. package/dist/tenant/paths.js +3 -1
  241. package/dist/tenant/principals.d.ts +2 -11
  242. package/dist/tenant/principals.js +10 -30
  243. package/dist/tenant/reset.d.ts +5 -2
  244. package/dist/tenant/reset.js +22 -2
  245. package/dist/tenant/run-grants.d.ts +49 -0
  246. package/dist/tenant/run-grants.js +62 -0
  247. package/dist/tenant/run-token.d.ts +70 -0
  248. package/dist/tenant/run-token.js +138 -0
  249. package/dist/tenant/runtime.d.ts +42 -0
  250. package/dist/tenant/runtime.js +134 -33
  251. package/dist/tenant/sandboxes.d.ts +119 -0
  252. package/dist/tenant/sandboxes.js +551 -0
  253. package/dist/tenant/session-streams.d.ts +6 -10
  254. package/dist/tenant/session-streams.js +9 -27
  255. package/dist/tenant/session.d.ts +2 -13
  256. package/dist/tenant/session.js +4 -28
  257. package/dist/tenant/sessions.d.ts +6 -1
  258. package/dist/tenant/sessions.js +29 -2
  259. package/dist/tenant/signing-keys.d.ts +2 -1
  260. package/dist/tenant/signing-keys.js +2 -2
  261. package/dist/tenant/status.d.ts +8 -3
  262. package/dist/tenant/status.js +5 -0
  263. package/dist/tenant/streams.d.ts +8 -2
  264. package/dist/tenant/streams.js +25 -6
  265. package/dist/tenant/sweep.js +37 -4
  266. package/dist/tenant/types.d.ts +39 -1
  267. package/dist/tenant/worker.d.ts +11 -5
  268. package/dist/tenant/worker.js +12 -6
  269. package/dist/vault/crypto.d.ts +7 -0
  270. package/dist/vault/crypto.js +16 -0
  271. package/dist/vault/oauth.d.ts +55 -0
  272. package/dist/vault/oauth.js +186 -0
  273. package/dist/vault/service.d.ts +54 -7
  274. package/dist/vault/service.js +261 -17
  275. package/dist/vault/sources.d.ts +54 -0
  276. package/dist/vault/sources.js +185 -0
  277. package/dist/version.d.ts +1 -1
  278. package/dist/version.js +1 -1
  279. package/package.json +14 -10
  280. package/dist/adapters/media.d.ts +0 -24
  281. package/dist/adapters/media.js +0 -111
  282. package/dist/host/cors.d.ts +0 -27
  283. package/dist/host/cors.js +0 -64
  284. package/dist/tenant/access-policy.d.ts +0 -30
  285. package/dist/tenant/access-policy.js +0 -52
  286. package/dist/tenant/browser.d.ts +0 -15
  287. package/dist/tenant/browser.js +0 -39
  288. package/dist/tenant/subject-limits.d.ts +0 -16
  289. package/dist/tenant/subject-limits.js +0 -45
  290. package/dist/tenant/tokens.d.ts +0 -14
  291. package/dist/tenant/tokens.js +0 -180
@@ -13,7 +13,17 @@
13
13
  * listener (`NYLORUN_GATES_LISTEN_*`), and only one that runs loop parses
14
14
  * where to reach the gate (`NYLORUN_GATES_URL`). Both read
15
15
  * `NYLORUN_GATES_TOKEN`. In a container, loop requires the gate: the loop
16
- * process must never hold a model credential.
16
+ * process must never hold a model credential. `egress` (egress-gate, F7.2) joins gates and keys
17
+ * in the gateway and parses its listener (`NYLORUN_EGRESS_LISTEN_*`, default `0.0.0.0:4200`).
18
+ * Only gates reads the operator's credential resolver (`NYLORUN_RESOLVER_URL`,
19
+ * `NYLORUN_RESOLVER_TOKEN`, F9 C1): remote MCP calls are authorized in the gateway.
20
+ *
21
+ * `harness` (F6.2) runs alone: it holds the harness credential and nothing else. It connects to
22
+ * core's Harness API listener (`NYLORUN_HARNESS_URL`, `NYLORUN_HARNESS_TOKEN`), calls models and
23
+ * remote MCP servers through the gates service (`NYLORUN_GATES_URL`, with run tokens only), and
24
+ * keeps its workspaces under `NYLORUN_HARNESS_ROOT`. It refuses to start with a database, the
25
+ * gates' or keys' credential, or Restate settings. A process running core starts the Harness API
26
+ * listener when `NYLORUN_HARNESS=remote` (`NYLORUN_HARNESS_LISTEN_*`, `NYLORUN_HARNESS_TOKEN`).
17
27
  *
18
28
  * Two listen modes:
19
29
  * - **local** (no `NYLORUN_LISTEN_*` / `NYLORUN_ALLOWED_HOSTS`): the Host binds
@@ -24,21 +34,31 @@
24
34
  * probes from inside the container.
25
35
  *
26
36
  * The Postgres, Restate and S2 endpoints are parsed and validated here;
27
- * `infra/*` builds the clients from them. So is who the Host's Tenant is when its database
28
- * holds none yet (`NYLORUN_TENANT_ID`, `NYLORUN_TENANT_NAME`, `NYLORUN_DERIVED_PRINCIPALS`).
37
+ * `infra/*` builds the clients from them. So is the Object store (`NYLORUN_OBJECT_STORE_*`),
38
+ * which every service may read; `host/main.ts` builds the `BlobStore` from it. So is who the Host's Tenant is when its database
39
+ * holds none yet (`NYLORUN_TENANT_ID`, `NYLORUN_TENANT_NAME`).
29
40
  */
30
- import { DERIVED_PRINCIPAL_ID_PATTERN, isTenantId } from "@nylorun/core/compatibility";
31
- export const RUNTIME_SERVICES = ["core", "loop", "gates", "keys"];
41
+ import { isTenantId } from "@nylorun/core/compatibility";
42
+ import { blockedAddresses } from "../sandbox/pods/network-gate.js";
43
+ export const RUNTIME_SERVICES = ["core", "loop", "gates", "keys", "egress", "harness"];
32
44
  /**
33
45
  * Services that may share a process (D12): they hold the same secrets and parse the same
34
- * trust class of input. keys joins gates (F4.2); egress joins them in a later release.
46
+ * trust class of input. keys joins gates (F4.2), and egress joins them (F7.2). The harness
47
+ * (F6.2) runs agent code's neighbours and holds only its own credential: it shares with nothing.
35
48
  */
36
49
  const SERVICE_GROUPS = [
37
50
  ["core", "loop"],
38
- ["gates", "keys"],
51
+ ["gates", "keys", "egress"],
52
+ ["harness"],
39
53
  ];
40
54
  /** Where the gates service listens by default. */
41
55
  export const DEFAULT_GATES_LISTEN_PORT = 4100;
56
+ /** Where egress-gate listens by default. */
57
+ export const DEFAULT_EGRESS_LISTEN_PORT = 4200;
58
+ /** Where core's Harness API listener listens by default. */
59
+ export const DEFAULT_HARNESS_LISTEN_PORT = 4200;
60
+ /** Where a harness process answers `/health` (loopback only). */
61
+ export const DEFAULT_HARNESS_HEALTH_PORT = 4300;
42
62
  /** What a process runs without `--service`: core and loop, as `--role all` did. */
43
63
  export const DEFAULT_SERVICES = new Set([
44
64
  "core",
@@ -46,8 +66,6 @@ export const DEFAULT_SERVICES = new Set([
46
66
  ]);
47
67
  /** Services of the blueprint this release doesn't have yet. */
48
68
  const LATER_SERVICES = [
49
- "egress",
50
- "harness",
51
69
  "sandboxd",
52
70
  ];
53
71
  const ROLE_SERVICES = {
@@ -74,7 +92,7 @@ function read(env, name) {
74
92
  const trimmed = value.trim();
75
93
  return trimmed === "" ? undefined : trimmed;
76
94
  }
77
- const USAGE = "Usage: main.js [--service core,loop|gates]";
95
+ const USAGE = "Usage: main.js [--service core,loop|gates,keys,egress|harness]";
78
96
  /** Parses `--service a,b` (or the deprecated `--role`); throws `StackConfigError`. */
79
97
  export function parseServices(argv) {
80
98
  let service;
@@ -248,11 +266,14 @@ function parseIdentityKeys(env) {
248
266
  /** Parse the stack configuration; throws `StackConfigError` naming the variable. */
249
267
  export function parseStackConfig(env, argv) {
250
268
  const { services, deprecatedRole } = parseServices(argv);
269
+ if (services.has("harness"))
270
+ return { services, harness: parseHarnessService(env), endpoints: {} };
251
271
  // The image sets NYLORUN_LISTEN_*: only the API's processes read them.
252
272
  const servesApi = services.has("core") || services.has("loop");
253
273
  const listen = servesApi ? parseListen(env) : undefined;
254
274
  const operator = servesApi ? parseAdminListen(env) : undefined;
255
275
  const gates = services.has("gates") || services.has("keys") ? parseGates(env) : undefined;
276
+ const egress = services.has("egress") ? parseEgress(env) : undefined;
256
277
  // The gates service's clients: the loop's model and tool calls, and core's endpoint pings.
257
278
  const modelGate = servesApi ? parseModelGate(env) : undefined;
258
279
  // The keys service is the gateway's unless NYLORUN_KEYS_URL names another listener.
@@ -299,43 +320,198 @@ export function parseStackConfig(env, argv) {
299
320
  if (restateIdentityKeys)
300
321
  endpoints.restateIdentityKeys = restateIdentityKeys;
301
322
  const publicUrl = parseUrl(env, "NYLORUN_PUBLIC_URL", http)?.replace(/\/+$/, "");
302
- const rawBrowser = read(env, "NYLORUN_BROWSER_ACCESS");
303
- if (rawBrowser !== undefined && rawBrowser !== "on" && rawBrowser !== "off")
304
- throw new StackConfigError(`NYLORUN_BROWSER_ACCESS must be on or off, not ${rawBrowser}`);
323
+ const harnessMode = servesApi ? parseHarnessMode(env) : undefined;
324
+ const sandboxes = servesApi ? parseSandboxes(env) : undefined;
325
+ // Pods connect to the Harness API listener even when the Tenant runs its own harness.
326
+ const harnessListener = harnessMode === "remote" || sandboxes
327
+ ? parseHarnessListener(env, harnessMode === "remote")
328
+ : undefined;
329
+ if (harnessListener &&
330
+ [listen?.port, operator?.port].includes(harnessListener.listen.port))
331
+ throw new StackConfigError("NYLORUN_HARNESS_LISTEN_PORT must differ from NYLORUN_LISTEN_PORT and NYLORUN_ADMIN_LISTEN_PORT");
305
332
  const delivery = parseDelivery(env);
306
333
  const tenant = servesApi ? parseTenant(env) : undefined;
334
+ const objectStore = parseObjectStore(env);
307
335
  return {
308
336
  services,
309
337
  ...(deprecatedRole ? { deprecatedRole } : {}),
310
338
  ...(gates ? { gates } : {}),
339
+ ...(egress ? { egress } : {}),
311
340
  ...(modelGate ? { modelGate } : {}),
312
341
  ...(keys ? { keys } : {}),
313
342
  ...(rawPacking ? { packing: rawPacking } : {}),
314
343
  ...(listen ? { listen } : {}),
315
344
  endpoints,
316
345
  ...(publicUrl ? { publicUrl } : {}),
317
- ...(rawBrowser === undefined ? {} : { browserAccess: rawBrowser === "on" }),
346
+ ...(harnessMode ? { harnessMode } : {}),
347
+ ...(harnessListener ? { harnessListener } : {}),
348
+ ...(sandboxes ? { sandboxes } : {}),
318
349
  ...(operator ? { operator } : {}),
319
350
  ...(delivery ? { delivery } : {}),
320
351
  ...(tenant ? { tenant } : {}),
352
+ ...(objectStore ? { objectStore } : {}),
321
353
  };
322
354
  }
355
+ /** S3 bucket names: 3 to 63 lowercase letters, digits, dots and hyphens. */
356
+ const BUCKET = /^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/;
357
+ /** `StackConfig.objectStore` from `NYLORUN_OBJECT_STORE_*`, or `undefined` without an endpoint. */
358
+ function parseObjectStore(env) {
359
+ const endpoint = parseUrl(env, "NYLORUN_OBJECT_STORE_ENDPOINT", ["http:", "https:"]);
360
+ const accessKeyId = read(env, "NYLORUN_OBJECT_STORE_ACCESS_KEY");
361
+ const secretAccessKey = read(env, "NYLORUN_OBJECT_STORE_SECRET_KEY");
362
+ if (endpoint === undefined) {
363
+ if (accessKeyId !== undefined || secretAccessKey !== undefined)
364
+ throw new StackConfigError("NYLORUN_OBJECT_STORE_ACCESS_KEY or _SECRET_KEY is set without NYLORUN_OBJECT_STORE_ENDPOINT: set the S3 endpoint, e.g. http://rustfs:9000");
365
+ return undefined;
366
+ }
367
+ if (accessKeyId === undefined || secretAccessKey === undefined)
368
+ throw new StackConfigError("NYLORUN_OBJECT_STORE_ACCESS_KEY and NYLORUN_OBJECT_STORE_SECRET_KEY are required with NYLORUN_OBJECT_STORE_ENDPOINT (`nylorun start` sets them)");
369
+ const bucket = read(env, "NYLORUN_OBJECT_STORE_BUCKET") ?? "nylorun";
370
+ if (!BUCKET.test(bucket))
371
+ throw new StackConfigError(`NYLORUN_OBJECT_STORE_BUCKET must be an S3 bucket name (3 to 63 of a-z 0-9 . -), not ${bucket}`);
372
+ const region = read(env, "NYLORUN_OBJECT_STORE_REGION") ?? "us-east-1";
373
+ if (!/^[a-z0-9-]+$/.test(region))
374
+ throw new StackConfigError(`NYLORUN_OBJECT_STORE_REGION is not a region: ${region}`);
375
+ return { endpoint: endpoint.replace(/\/+$/, ""), bucket, region, accessKeyId, secretAccessKey };
376
+ }
323
377
  function parseTenant(env) {
324
378
  const id = read(env, "NYLORUN_TENANT_ID");
325
379
  if (id !== undefined && !isTenantId(id))
326
380
  throw new StackConfigError(`NYLORUN_TENANT_ID must be a Tenant id (tn_ and 26 Crockford characters), not ${id}`);
327
- const raw = read(env, "NYLORUN_DERIVED_PRINCIPALS");
328
- const derivedPrincipals = raw === undefined ? ["project"] : raw.split(",").map((entry) => entry.trim());
329
- for (const principal of derivedPrincipals)
330
- if (!DERIVED_PRINCIPAL_ID_PATTERN.test(principal) || principal === "studio")
331
- throw new StackConfigError(`NYLORUN_DERIVED_PRINCIPALS has '${principal}': each entry must match ${DERIVED_PRINCIPAL_ID_PATTERN} and not be studio`);
381
+ const identityFile = read(env, "NYLORUN_IDENTITY_FILE");
332
382
  return {
333
383
  ...(id ? { id } : {}),
334
384
  name: read(env, "NYLORUN_TENANT_NAME") ?? "default",
335
- derivedPrincipals: [...new Set(derivedPrincipals)],
385
+ ...(identityFile ? { identityFile } : {}),
336
386
  };
337
387
  }
338
388
  const GATES_TOKEN = /^[0-9a-f]{64,}$/i;
389
+ /** `NYLORUN_HARNESS`: `in-process` (the default when unset) or `remote`. */
390
+ function parseHarnessMode(env) {
391
+ const raw = read(env, "NYLORUN_HARNESS");
392
+ if (raw === undefined)
393
+ return undefined;
394
+ if (raw !== "in-process" && raw !== "remote")
395
+ throw new StackConfigError(`NYLORUN_HARNESS must be in-process or remote, not ${raw}`);
396
+ return raw;
397
+ }
398
+ function parseHarnessToken(env, why) {
399
+ const token = read(env, "NYLORUN_HARNESS_TOKEN");
400
+ if (token === undefined)
401
+ throw new StackConfigError(`NYLORUN_HARNESS_TOKEN is required ${why} (\`nylorun start\` sets it)`);
402
+ if (!GATES_TOKEN.test(token))
403
+ throw new StackConfigError("NYLORUN_HARNESS_TOKEN must be at least 32 bytes as hex");
404
+ return token;
405
+ }
406
+ /** `StackConfig.sandboxes` from `NYLORUN_SANDBOXES_*`, or undefined without a URL. */
407
+ function parseSandboxes(env) {
408
+ const url = parseUrl(env, "NYLORUN_SANDBOXES_URL", ["http:", "https:"]);
409
+ if (url === undefined)
410
+ return undefined;
411
+ const token = read(env, "NYLORUN_SANDBOXES_TOKEN");
412
+ if (token === undefined || token.length < 32)
413
+ throw new StackConfigError("NYLORUN_SANDBOXES_TOKEN (at least 32 characters) is required with NYLORUN_SANDBOXES_URL (`nylorun sandbox enable` sets both)");
414
+ const harnessImage = read(env, "NYLORUN_SANDBOX_HARNESS_IMAGE");
415
+ if (harnessImage === undefined || /\s/.test(harnessImage))
416
+ throw new StackConfigError("NYLORUN_SANDBOX_HARNESS_IMAGE is required with NYLORUN_SANDBOXES_URL: the Runtime image sandbox pods copy the engine from (`nylorun start` sets it)");
417
+ return { url: url.replace(/\/+$/, ""), token, harnessImage };
418
+ }
419
+ /**
420
+ * `StackConfig.harnessListener` from `NYLORUN_HARNESS_LISTEN_*` and `NYLORUN_HARNESS_TOKEN`
421
+ * (required with `NYLORUN_HARNESS=remote`; optional when only sandbox pods connect).
422
+ */
423
+ function parseHarnessListener(env, tokenRequired = true) {
424
+ const host = read(env, "NYLORUN_HARNESS_LISTEN_HOST") ?? DEFAULT_CONTAINER_LISTEN_HOST;
425
+ if (/\s|\//.test(host))
426
+ throw new StackConfigError(`NYLORUN_HARNESS_LISTEN_HOST is not an address: ${host}`);
427
+ const rawPort = read(env, "NYLORUN_HARNESS_LISTEN_PORT");
428
+ const port = rawPort === undefined
429
+ ? DEFAULT_HARNESS_LISTEN_PORT
430
+ : parsePort("NYLORUN_HARNESS_LISTEN_PORT", rawPort);
431
+ const rawAllowed = read(env, "NYLORUN_HARNESS_ALLOWED_HOSTS");
432
+ const explicit = rawAllowed === undefined
433
+ ? []
434
+ : rawAllowed
435
+ .split(",")
436
+ .map((entry) => entry.trim())
437
+ .filter((entry) => entry !== "")
438
+ .map((entry) => normalizeAllowedHost("NYLORUN_HARNESS_ALLOWED_HOSTS", entry));
439
+ if (explicit.length === 0 && !isLoopbackAddress(host))
440
+ throw new StackConfigError(`NYLORUN_HARNESS_ALLOWED_HOSTS is required when NYLORUN_HARNESS_LISTEN_HOST is ${host}: list the Host headers harnesses send, e.g. runtime:${port}`);
441
+ const token = tokenRequired || read(env, "NYLORUN_HARNESS_TOKEN") !== undefined
442
+ ? parseHarnessToken(env, "with NYLORUN_HARNESS=remote: the credential harnesses present")
443
+ : undefined;
444
+ return {
445
+ listen: { host, port, allowedHosts: [...new Set([...explicit, ...loopbackForms(port)])] },
446
+ ...(token ? { token } : {}),
447
+ };
448
+ }
449
+ /** What a harness process must not hold: it runs next to agent code. */
450
+ const HARNESS_REFUSED = ["NYLORUN_DATABASE_URL", "NYLORUN_GATES_TOKEN", "NYLORUN_KEYS_URL"];
451
+ /** `StackConfig.harness` from `NYLORUN_HARNESS_*` and `NYLORUN_GATES_URL`. */
452
+ function parseHarnessService(env) {
453
+ const held = [
454
+ ...HARNESS_REFUSED.filter((name) => read(env, name) !== undefined),
455
+ ...Object.keys(env)
456
+ .filter((name) => name.startsWith("NYLORUN_RESTATE_") && read(env, name) !== undefined)
457
+ .sort(),
458
+ ];
459
+ if (held.length > 0)
460
+ throw new StackConfigError(`--service harness refuses to start with ${held.join(", ")} set: a harness reaches no database, no gate or keys credential and no Restate`);
461
+ const pod = read(env, "NYLORUN_SANDBOX_KIND") === "pod";
462
+ const url = parseUrl(env, "NYLORUN_HARNESS_URL", pod ? ["http:", "https:", "ws:", "wss:"] : ["ws:", "wss:"]);
463
+ if (url === undefined)
464
+ throw new StackConfigError("NYLORUN_HARNESS_URL is required for --service harness: core's Harness API, e.g. ws://runtime:4200/nylorun/harness/v1");
465
+ const gatesUrl = parseUrl(env, "NYLORUN_GATES_URL", ["http:", "https:"]);
466
+ if (gatesUrl === undefined)
467
+ throw new StackConfigError("NYLORUN_GATES_URL is required for --service harness: its model and MCP calls go through the gates service, e.g. http://gateway:4100");
468
+ const root = read(env, "NYLORUN_HARNESS_ROOT") ?? "/harness";
469
+ if (!root.startsWith("/"))
470
+ throw new StackConfigError(`NYLORUN_HARNESS_ROOT must be an absolute path, not ${root}`);
471
+ const rawHealth = read(env, "NYLORUN_HARNESS_HEALTH_PORT");
472
+ const podHost = pod ? parsePodHost(env, url) : undefined;
473
+ return {
474
+ url: podHost ? podHost.wsUrl : url,
475
+ ...(podHost
476
+ ? { pod: podHost.config }
477
+ : { token: parseHarnessToken(env, "for --service harness: the credential core's Harness API accepts") }),
478
+ gatesUrl: gatesUrl.replace(/\/+$/, ""),
479
+ root,
480
+ healthPort: rawHealth === undefined
481
+ ? DEFAULT_HARNESS_HEALTH_PORT
482
+ : parsePort("NYLORUN_HARNESS_HEALTH_PORT", rawHealth),
483
+ };
484
+ }
485
+ /**
486
+ * The engine in a pod sandbox: the pod's identity and its join file, from the variables the
487
+ * sandboxes service renders into the pod. `NYLORUN_HARNESS_URL` is the listener's address on the
488
+ * Docker host (`http://<host>:<port>`); the WebSocket URL adds the Harness API path.
489
+ */
490
+ function parsePodHost(env, url) {
491
+ const need = (name) => {
492
+ const value = read(env, name);
493
+ if (value === undefined)
494
+ throw new StackConfigError(`${name} is required for the engine in a sandbox pod (NYLORUN_SANDBOX_KIND=pod)`);
495
+ return value;
496
+ };
497
+ if (read(env, "NYLORUN_HARNESS_TOKEN") !== undefined)
498
+ throw new StackConfigError("A sandbox pod's engine holds no NYLORUN_HARNESS_TOKEN: it joins with its join token");
499
+ const base = new URL(url);
500
+ const secure = base.protocol === "https:" || base.protocol === "wss:";
501
+ const origin = `${secure ? "https" : "http"}://${base.host}`;
502
+ const egress = read(env, "NYLORUN_EGRESS_PROXY");
503
+ return {
504
+ wsUrl: `${secure ? "wss" : "ws"}://${base.host}/nylorun/harness/v1`,
505
+ config: {
506
+ sandboxId: need("NYLORUN_SANDBOX_ID"),
507
+ podUid: need("NYLORUN_POD_UID"),
508
+ joinFile: need("NYLORUN_SANDBOX_JOIN_FILE"),
509
+ httpUrl: origin,
510
+ ...(egress ? { egressProxy: egress.replace(/\/+$/, "") } : {}),
511
+ blocked: blockedAddresses(env),
512
+ },
513
+ };
514
+ }
339
515
  /** `StackConfig.gates` from `NYLORUN_GATES_*`. */
340
516
  function parseGates(env) {
341
517
  const host = read(env, "NYLORUN_GATES_LISTEN_HOST") ?? DEFAULT_CONTAINER_LISTEN_HOST;
@@ -360,11 +536,37 @@ function parseGates(env) {
360
536
  throw new StackConfigError("NYLORUN_GATES_TOKEN is required for --service gates: the token the loop presents (`nylorun start` sets it)");
361
537
  if (!GATES_TOKEN.test(token))
362
538
  throw new StackConfigError("NYLORUN_GATES_TOKEN must be at least 32 bytes as hex");
539
+ const resolver = parseResolver(env);
363
540
  return {
364
541
  listen: { host, port, allowedHosts: [...new Set([...explicit, ...loopbackForms(port)])] },
365
542
  token,
543
+ ...(resolver ? { resolver } : {}),
366
544
  };
367
545
  }
546
+ /** `GatesConfig.resolver` from `NYLORUN_RESOLVER_URL` and `NYLORUN_RESOLVER_TOKEN`: both or neither. */
547
+ function parseResolver(env) {
548
+ const url = parseUrl(env, "NYLORUN_RESOLVER_URL", ["http:", "https:"]);
549
+ const token = read(env, "NYLORUN_RESOLVER_TOKEN");
550
+ if (url === undefined) {
551
+ if (token !== undefined)
552
+ throw new StackConfigError("NYLORUN_RESOLVER_TOKEN is set without NYLORUN_RESOLVER_URL: set the URL of your credential resolver");
553
+ return undefined;
554
+ }
555
+ if (token === undefined)
556
+ throw new StackConfigError("NYLORUN_RESOLVER_TOKEN is required with NYLORUN_RESOLVER_URL: the bearer the gateway presents to your credential resolver");
557
+ if (/\s/.test(token))
558
+ throw new StackConfigError("NYLORUN_RESOLVER_TOKEN cannot contain whitespace");
559
+ return { url, token };
560
+ }
561
+ /** `StackConfig.egress` from `NYLORUN_EGRESS_LISTEN_*`. CONNECT has no Host header to check. */
562
+ function parseEgress(env) {
563
+ const host = read(env, "NYLORUN_EGRESS_LISTEN_HOST") ?? DEFAULT_CONTAINER_LISTEN_HOST;
564
+ if (/\s|\//.test(host))
565
+ throw new StackConfigError(`NYLORUN_EGRESS_LISTEN_HOST is not an address: ${host}`);
566
+ const rawPort = read(env, "NYLORUN_EGRESS_LISTEN_PORT");
567
+ const port = rawPort === undefined ? DEFAULT_EGRESS_LISTEN_PORT : parsePort("NYLORUN_EGRESS_LISTEN_PORT", rawPort);
568
+ return { listen: { host, port } };
569
+ }
368
570
  /** `StackConfig.modelGate` from `NYLORUN_GATES_URL` and `NYLORUN_GATES_TOKEN`. */
369
571
  function parseModelGate(env) {
370
572
  const url = parseUrl(env, "NYLORUN_GATES_URL", ["http:", "https:"]);
package/dist/index.d.ts CHANGED
@@ -1,7 +1,10 @@
1
1
  export { openTenantRuntime, type TenantOpenHooks, } from "./tenant/runtime.js";
2
2
  export { startEphemeralRuntime, type StartEphemeralRuntimeOptions, type EphemeralRuntime, } from "./tenant/ephemeral.js";
3
+ export { parseIdentityFile, IdentityFileError, type TrustedIssuerConfig, } from "./tenant/identity-file.js";
4
+ export { createTrustedIssuers, type TrustedIssuers, type TrustedIssuersOptions, } from "./tenant/issuers.js";
3
5
  export type { TenantConfig, TenantHandle, TenantPaths, Logger, OpenTenantRuntime, } from "./tenant/types.js";
4
6
  export { scriptedModel, gatewayModel, type ModelProvider } from "./core/provider.js";
7
+ export type { ResolverConfig } from "./vault/sources.js";
5
8
  export type { RuntimeAgent } from "./contracts.js";
6
9
  export { IMAGE_MEDIA_TYPES, MAX_IMAGE_BYTES, decodeImageBase64, validateImageBytes, } from "./media.js";
7
10
  export type { RuntimeModelAdapter } from "./contracts.js";
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  export { openTenantRuntime, } from "./tenant/runtime.js";
2
2
  export { startEphemeralRuntime, } from "./tenant/ephemeral.js";
3
+ export { parseIdentityFile, IdentityFileError, } from "./tenant/identity-file.js";
4
+ export { createTrustedIssuers, } from "./tenant/issuers.js";
3
5
  export { scriptedModel, gatewayModel } from "./core/provider.js";
4
6
  export { IMAGE_MEDIA_TYPES, MAX_IMAGE_BYTES, decodeImageBase64, validateImageBytes, } from "./media.js";
5
7
  export { baselineEnvironment, hostProcessEnvironment, tenantChildEnvironment, } from "./host/environment.js";
@@ -12,10 +12,18 @@ import { KEYS_PATH } from "./contract.js";
12
12
  import { KEYS_OPERATIONS } from "./keys.js";
13
13
  /** Signing and vault writes are short; anything slower is a gateway in trouble. */
14
14
  const KEYS_TIMEOUT_MS = 30_000;
15
+ /**
16
+ * MCP OAuth connect calls the authorization server several times (discovery, registration, the
17
+ * code exchange), each bounded by `guardedFetch`'s 30 s: these wait longer.
18
+ */
19
+ const SLOW_OPERATIONS = {
20
+ startOAuth: 120_000,
21
+ finishOAuth: 60_000,
22
+ };
15
23
  export function httpKeys(options) {
16
24
  const where = new URL(options.url).origin;
17
- const timeoutMs = options.timeoutMs ?? KEYS_TIMEOUT_MS;
18
25
  function call(operation, args) {
26
+ const timeoutMs = Math.max(options.timeoutMs ?? KEYS_TIMEOUT_MS, SLOW_OPERATIONS[operation] ?? 0);
19
27
  const url = new URL(`${KEYS_PATH}/${operation}`, options.url);
20
28
  const send = url.protocol === "https:" ? httpsRequest : httpRequest;
21
29
  const payload = Buffer.from(JSON.stringify({ args }), "utf8");
@@ -1,7 +1,7 @@
1
1
  import type { CreateCredentialRequest, CredentialInfo, HostModelView, PutHostModelRequest, RotateCredentialRequest, SelectHostModelRequest, SigningKeyView } from "@nylorun/core/contracts";
2
2
  import type { SessionStore } from "../store/types.js";
3
3
  import { type SigningKeys } from "../tenant/signing-keys.js";
4
- import type { VaultService } from "../vault/service.js";
4
+ import type { FinishOAuthInput, StartOAuthInput, VaultService } from "../vault/service.js";
5
5
  /** One JWT to sign: its `typ` and every claim, `iss`, `aud`, `sub`, `iat`, `exp` and `jti` included. */
6
6
  export interface SignRequest {
7
7
  readonly typ: string;
@@ -26,9 +26,19 @@ export interface Keys {
26
26
  }): Promise<SigningKeyView[]>;
27
27
  /** Creates the current and standby keys when the Tenant has none. */
28
28
  ensureSigningKeys(): Promise<void>;
29
+ /** Starts an MCP OAuth connect into an installation vault (`VaultService.startOAuth`). */
30
+ startOAuth(input: StartOAuthInput): Promise<{
31
+ authorizeUrl: string;
32
+ expiresAt: string;
33
+ }>;
34
+ /** Finishes one from its callback (`VaultService.finishOAuth`). */
35
+ finishOAuth(input: FinishOAuthInput): Promise<{
36
+ vaultId: string;
37
+ credentialId: string;
38
+ }>;
29
39
  }
30
40
  /** The operations, by the name the HTTP route carries. */
31
- export declare const KEYS_OPERATIONS: readonly ["createCredential", "rotateCredential", "putHostModel", "selectHostModel", "sign", "rotateSigningKeys", "ensureSigningKeys"];
41
+ export declare const KEYS_OPERATIONS: readonly ["createCredential", "rotateCredential", "putHostModel", "selectHostModel", "sign", "rotateSigningKeys", "ensureSigningKeys", "startOAuth", "finishOAuth"];
32
42
  export type KeysOperation = (typeof KEYS_OPERATIONS)[number];
33
43
  export interface InProcessKeysOptions {
34
44
  readonly store: SessionStore;
package/dist/keys/keys.js CHANGED
@@ -5,6 +5,10 @@
5
5
  * delivery tokens, signing-key rotation) run here, whole, so the process that calls them never
6
6
  * holds the key or a private signing key.
7
7
  *
8
+ * MCP OAuth connect (F9-D14) runs here too: discovery, registration, the code exchange and the
9
+ * credential it seals, so the plaintext tokens, the PKCE verifier and any client secret never
10
+ * reach the runtime container; core only routes the start and the callback.
11
+ *
8
12
  * Two implementations: `inProcessKeys` (embedding, the ephemeral Runtime, tests, and the gates
9
13
  * service itself, which serves the HTTP routes with it), and `httpKeys` (`client.ts`), the
10
14
  * runtime container's client of the `keys` service in the gateway.
@@ -20,6 +24,8 @@ export const KEYS_OPERATIONS = [
20
24
  "sign",
21
25
  "rotateSigningKeys",
22
26
  "ensureSigningKeys",
27
+ "startOAuth",
28
+ "finishOAuth",
23
29
  ];
24
30
  export function inProcessKeys(options) {
25
31
  const { store, vault, signingKeys, kek } = options;
@@ -28,6 +34,8 @@ export function inProcessKeys(options) {
28
34
  rotateCredential: (vaultId, id, body) => vault.rotateCredential(vaultId, id, body),
29
35
  putHostModel: (body) => vault.putHostModel(body),
30
36
  selectHostModel: (body) => vault.selectHostModel(body),
37
+ startOAuth: (input) => vault.startOAuth(input),
38
+ finishOAuth: (input) => vault.finishOAuth(input),
31
39
  async sign(request) {
32
40
  const key = kek();
33
41
  const row = await store.tx((t) => signingKeys.ensure(t, key));
@@ -162,7 +162,8 @@ function authorizedFetch(server, authorize) {
162
162
  headers.set(key, value);
163
163
  new Headers(init?.headers).forEach((value, key) => headers.set(key, value));
164
164
  if (result.status === "authorized")
165
- headers.set("authorization", result.headers.authorization);
165
+ for (const [key, value] of Object.entries(result.headers))
166
+ headers.set(key, value);
166
167
  return fetch(target, { ...init, headers, redirect: "error" });
167
168
  };
168
169
  }
@@ -19,6 +19,7 @@ export declare class McpPool {
19
19
  readonly authorize: (sessionId: string, request: {
20
20
  url: string;
21
21
  serverName: string;
22
+ agentId?: string;
22
23
  }) => Promise<AuthorizeResult>;
23
24
  /** Default `DEFAULT_MCP_IDLE_MS`. */
24
25
  readonly idleMs?: number;
package/dist/mcp/pool.js CHANGED
@@ -192,6 +192,7 @@ export class McpPool {
192
192
  : (url) => this.options.authorize(input.sessionId, {
193
193
  url,
194
194
  serverName: declared.server.name,
195
+ ...ownerOf(declared),
195
196
  }),
196
197
  });
197
198
  return { ok: true, connection };
@@ -1,5 +1,6 @@
1
1
  import type { JsonObject } from "@nylorun/core/define";
2
- import type { AgentManifest } from "@nylorun/core/define";
2
+ import { type AgentManifest } from "@nylorun/core/define";
3
+ import type { DurableSessionTool, HostEffect } from "@nylorun/harness/run";
3
4
  export interface McpToolRecord {
4
5
  /** The agent used as a tool that declares the server; absent for the session's root agent. */
5
6
  readonly agentId?: string;
@@ -27,3 +28,9 @@ export interface McpDiagnostic {
27
28
  }
28
29
  export declare function modelToolName(serverName: string, serverToolName: string): string;
29
30
  export declare function declaredToolNames(manifest: AgentManifest): Set<string>;
31
+ /** The tools of a snapshot, as the engine advertises them. */
32
+ export declare function sessionToolsOf(snapshot: McpSnapshot | undefined): readonly DurableSessionTool[] | undefined;
33
+ /** The snapshot's record of an MCP tool call, or undefined when the call is not an MCP tool. */
34
+ export declare function mcpToolOf(snapshot: McpSnapshot | undefined, request: Pick<HostEffect, "agent" | "capabilityId" | "toolName">): McpToolRecord | undefined;
35
+ /** The manifest of the agent an effect belongs to: the root, or an agent it uses as a tool. */
36
+ export declare function manifestFor(manifest: AgentManifest, agent: HostEffect["agent"]): AgentManifest | undefined;
@@ -1,3 +1,4 @@
1
+ import { delegateManifest } from "@nylorun/core/define";
1
2
  export function modelToolName(serverName, serverToolName) {
2
3
  return `${serverName}__${serverToolName}`;
3
4
  }
@@ -8,3 +9,30 @@ export function declaredToolNames(manifest) {
8
9
  names.add(tool.name);
9
10
  return names;
10
11
  }
12
+ /** The tools of a snapshot, as the engine advertises them. */
13
+ export function sessionToolsOf(snapshot) {
14
+ if (!snapshot?.mcpTools.length)
15
+ return undefined;
16
+ return snapshot.mcpTools.map((tool) => ({
17
+ ...(tool.agentId === undefined ? {} : { agentId: tool.agentId }),
18
+ capabilityId: tool.capabilityId,
19
+ name: tool.name,
20
+ ...(tool.description === undefined
21
+ ? {}
22
+ : { description: tool.description }),
23
+ inputSchema: tool.inputSchema,
24
+ ...(tool.outputSchema === undefined
25
+ ? {}
26
+ : { outputSchema: tool.outputSchema }),
27
+ }));
28
+ }
29
+ /** The snapshot's record of an MCP tool call, or undefined when the call is not an MCP tool. */
30
+ export function mcpToolOf(snapshot, request) {
31
+ return snapshot?.mcpTools.find((tool) => tool.agentId === request.agent?.id &&
32
+ tool.capabilityId === request.capabilityId &&
33
+ tool.name === request.toolName);
34
+ }
35
+ /** The manifest of the agent an effect belongs to: the root, or an agent it uses as a tool. */
36
+ export function manifestFor(manifest, agent) {
37
+ return agent ? delegateManifest(manifest, agent.id) : manifest;
38
+ }
package/dist/media.d.ts CHANGED
@@ -1,29 +1,10 @@
1
+ /**
2
+ * Image checks for code that handles image bytes itself (an image tool in an Action endpoint):
3
+ * the supported types, the size cap and a signature check. Storing images is the job of file
4
+ * artifacts (`artifacts/`, protocol 6), which replaced `MediaStore`.
5
+ */
1
6
  export declare const IMAGE_MEDIA_TYPES: readonly string[];
2
7
  export declare const MAX_IMAGE_BYTES: number;
3
- export interface MediaAsset {
4
- readonly id: string;
5
- readonly mediaType: string;
6
- readonly bytes: number;
7
- readonly kind: "input" | "generated";
8
- }
9
- /** Opaque reference retained by Harness and resolved by the configured media adapter. */
10
- export interface MediaReference {
11
- readonly agentId: string;
12
- readonly assetId: string;
13
- }
14
8
  export declare function decodeImageBase64(mediaType: string, base64: string): Uint8Array;
15
9
  /** Verify the asserted media type and lightweight file signature for a supported image. */
16
10
  export declare function validateImageBytes(mediaType: string, bytes: Uint8Array): void;
17
- export interface RuntimeMedia {
18
- saveInput(agentId: string, sessionId: string, mediaType: string, base64: string): Promise<MediaAsset>;
19
- saveGenerated(agentId: string, sessionId: string, mediaType: string, bytes: Uint8Array): Promise<MediaAsset>;
20
- dataUrl(reference: MediaReference, sessionId: string): Promise<{
21
- readonly asset: MediaAsset;
22
- readonly url: string;
23
- } | undefined>;
24
- latestInput(agentId: string, sessionId: string): Promise<MediaAsset | undefined>;
25
- read(agentId: string, sessionId: string, assetId: string): Promise<{
26
- asset: MediaAsset;
27
- bytes: Uint8Array;
28
- } | undefined>;
29
- }
package/dist/media.js CHANGED
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Image checks for code that handles image bytes itself (an image tool in an Action endpoint):
3
+ * the supported types, the size cap and a signature check. Storing images is the job of file
4
+ * artifacts (`artifacts/`, protocol 6), which replaced `MediaStore`.
5
+ */
1
6
  export const IMAGE_MEDIA_TYPES = Object.freeze([
2
7
  "image/jpeg",
3
8
  "image/png",
@@ -1,6 +1,6 @@
1
1
  import { type Credential } from "@earendil-works/pi-ai";
2
2
  import type { RuntimeModelAdapter } from "../contracts.js";
3
- import type { RuntimeMedia } from "../adapters/media.js";
3
+ import { type FileResolver } from "../artifacts/files.js";
4
4
  import type { HostModelSecret } from "../vault/service.js";
5
5
  export interface ModelPreview {
6
6
  readonly invocationId: string;
@@ -20,7 +20,11 @@ export interface ModelCallSettings {
20
20
  export interface PiModelOptions {
21
21
  readonly root?: string;
22
22
  readonly onPreview?: (preview: ModelPreview) => void;
23
- readonly media?: Pick<RuntimeMedia, "dataUrl">;
23
+ /**
24
+ * Resolves the files a prompt names (media parts whose reference is an artifact version) to
25
+ * their bytes. Model-gate passes one over the Tenant's artifacts (`artifactFiles`).
26
+ */
27
+ readonly files?: FileResolver;
24
28
  readonly readHostModel?: () => HostModelSecret | undefined | Promise<HostModelSecret | undefined>;
25
29
  readonly writeHostCredential?: (credential: Credential) => void | Promise<void>;
26
30
  readonly settings?: ModelCallSettings;