@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
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Local backend (F7.2): the workspace of the pod sandbox the engine runs in. The pod is the
3
+ * boundary (its own user, no capabilities, a read-only root, a NetworkPolicy that lets it reach
4
+ * only the Harness API, the gates and egress-gate), so commands run as plain child processes:
5
+ * `/bin/sh -c <command>` in `/workspace`, its own process group, killed whole on a timeout or a
6
+ * cancel. Files are read and written with `fs`. Egress goes through egress-gate: every command
7
+ * gets `HTTPS_PROXY`/`HTTP_PROXY` with the sandbox's current egress token as the proxy password
8
+ * (`proxyEnv`), and nothing of the engine's own environment (`NYLORUN_*`).
9
+ *
10
+ * One pod holds one sandbox: every key opens the same `/workspace`. Removing a workspace is the
11
+ * sandbox's reset or delete (a new volume), so `remove` and `list` do nothing here.
12
+ */
13
+ import { spawn } from "node:child_process";
14
+ import { accessSync, constants } from "node:fs";
15
+ import { lstat, readdir, readFile, stat, writeFile } from "node:fs/promises";
16
+ import { posix } from "node:path";
17
+ import { SANDBOX_WORKSPACE } from "@nylorun/core/define";
18
+ import { SandboxFileTooLargeError } from "../../sandbox/types.js";
19
+ /** The most of each stream kept from one command (the tools keep 30 000 characters of it). */
20
+ const STREAM_LIMIT = 4 * 1024 * 1024;
21
+ /** This process's environment without the engine's own variables. */
22
+ function baseEnv(env, workspace) {
23
+ const out = {};
24
+ for (const [name, value] of Object.entries(env))
25
+ if (value !== undefined && !name.startsWith("NYLORUN_") && !/_PROXY$/i.test(name))
26
+ out[name] = value;
27
+ return { PATH: "/usr/local/bin:/usr/bin:/bin", LANG: "C.UTF-8", TERM: "dumb", ...out, HOME: workspace };
28
+ }
29
+ /** Collects a stream, keeping at most `STREAM_LIMIT` bytes (head first). */
30
+ function collector() {
31
+ const chunks = [];
32
+ let size = 0;
33
+ let dropped = 0;
34
+ return {
35
+ push(chunk) {
36
+ if (size >= STREAM_LIMIT) {
37
+ dropped += chunk.length;
38
+ return;
39
+ }
40
+ const room = STREAM_LIMIT - size;
41
+ const kept = chunk.length > room ? chunk.subarray(0, room) : chunk;
42
+ chunks.push(kept);
43
+ size += kept.length;
44
+ dropped += chunk.length - kept.length;
45
+ },
46
+ text() {
47
+ const text = Buffer.concat(chunks).toString("utf8");
48
+ return dropped > 0 ? `${text}\n… [${dropped} bytes dropped]` : text;
49
+ },
50
+ };
51
+ }
52
+ export function localBackend(options) {
53
+ const workspace = options.workspace ?? SANDBOX_WORKSPACE;
54
+ const env = baseEnv(options.env, workspace);
55
+ const exec = (request, signal) => new Promise((resolve, reject) => {
56
+ if (signal.aborted)
57
+ return reject(signal.reason ?? new Error("Turn cancelled"));
58
+ const child = spawn("/bin/sh", ["-c", request.command], {
59
+ cwd: request.cwd,
60
+ env: { ...env, ...(options.proxyEnv?.() ?? {}) },
61
+ stdio: ["ignore", "pipe", "pipe"],
62
+ // Its own process group, so a timeout or a cancel kills everything it started.
63
+ detached: true,
64
+ });
65
+ const stdout = collector();
66
+ const stderr = collector();
67
+ child.stdout.on("data", (chunk) => stdout.push(chunk));
68
+ child.stderr.on("data", (chunk) => stderr.push(chunk));
69
+ let killed = false;
70
+ let timedOut = false;
71
+ const kill = () => {
72
+ if (killed || child.pid === undefined)
73
+ return;
74
+ killed = true;
75
+ try {
76
+ process.kill(-child.pid, "SIGKILL");
77
+ }
78
+ catch {
79
+ child.kill("SIGKILL");
80
+ }
81
+ };
82
+ const timer = setTimeout(() => {
83
+ timedOut = true;
84
+ kill();
85
+ }, request.timeoutMs);
86
+ const onAbort = () => kill();
87
+ signal.addEventListener("abort", onAbort, { once: true });
88
+ child.once("error", (error) => {
89
+ clearTimeout(timer);
90
+ signal.removeEventListener("abort", onAbort);
91
+ reject(error);
92
+ });
93
+ child.once("close", (code, by) => {
94
+ clearTimeout(timer);
95
+ signal.removeEventListener("abort", onAbort);
96
+ resolve({
97
+ exitCode: killed ? (timedOut ? 124 : 130) : (code ?? (by ? 128 : 1)),
98
+ stdout: stdout.text(),
99
+ stderr: stderr.text(),
100
+ killed,
101
+ timedOut,
102
+ });
103
+ });
104
+ });
105
+ const missing = (error) => error?.code === "ENOENT" || error?.code === "ENOTDIR";
106
+ const handle = {
107
+ workspace,
108
+ exec,
109
+ async readFile(path) {
110
+ try {
111
+ return await readFile(path, "utf8");
112
+ }
113
+ catch (error) {
114
+ if (missing(error))
115
+ return undefined;
116
+ throw error;
117
+ }
118
+ },
119
+ async readBytes(path, maxBytes) {
120
+ let size;
121
+ try {
122
+ size = (await stat(path)).size;
123
+ }
124
+ catch (error) {
125
+ if (missing(error))
126
+ return undefined;
127
+ throw error;
128
+ }
129
+ if (size > maxBytes)
130
+ throw new SandboxFileTooLargeError(path, size, maxBytes);
131
+ return new Uint8Array(await readFile(path));
132
+ },
133
+ async listFiles(dir, maxEntries) {
134
+ try {
135
+ if (!(await lstat(dir)).isDirectory())
136
+ return undefined;
137
+ }
138
+ catch (error) {
139
+ if (missing(error))
140
+ return undefined;
141
+ throw error;
142
+ }
143
+ const entries = [];
144
+ const walk = async (absolute, relative) => {
145
+ for (const name of (await readdir(absolute)).sort()) {
146
+ const path = posix.join(absolute, name);
147
+ const inFolder = relative === "" ? name : `${relative}/${name}`;
148
+ const info = await lstat(path);
149
+ if (info.isSymbolicLink())
150
+ continue;
151
+ if (info.isDirectory()) {
152
+ if (!(await walk(path, inFolder)))
153
+ return false;
154
+ }
155
+ else if (info.isFile()) {
156
+ entries.push({ path: inFolder, size: info.size });
157
+ if (entries.length > maxEntries)
158
+ return false;
159
+ }
160
+ }
161
+ return true;
162
+ };
163
+ const complete = await walk(dir, "");
164
+ return { entries, truncated: !complete };
165
+ },
166
+ async writeFile(path, content) {
167
+ await writeFile(path, content, "utf8");
168
+ },
169
+ async stop() { },
170
+ };
171
+ return {
172
+ name: "local",
173
+ isolation: "container",
174
+ async probe() {
175
+ const base = { name: "local", isolation: "container" };
176
+ try {
177
+ accessSync(workspace, constants.W_OK);
178
+ return { ...base, available: true, reason: `the pod sandbox's ${workspace}` };
179
+ }
180
+ catch (error) {
181
+ return {
182
+ ...base,
183
+ available: false,
184
+ reason: `${workspace} is not writable: ${error instanceof Error ? error.message : String(error)}`,
185
+ };
186
+ }
187
+ },
188
+ unmet: () => undefined,
189
+ open: async () => handle,
190
+ async remove() { },
191
+ async list() {
192
+ return [];
193
+ },
194
+ };
195
+ }
@@ -4,8 +4,9 @@
4
4
  * It is not a VM boundary; it exists so the first run and CI work on any machine.
5
5
  */
6
6
  import { mkdirSync, readdirSync, rmSync } from "node:fs";
7
- import { join } from "node:path";
7
+ import { join, posix } from "node:path";
8
8
  import { SANDBOX_WORKSPACE } from "@nylorun/core/define";
9
+ import { SandboxFileTooLargeError } from "../../sandbox/types.js";
9
10
  const ENV = Object.freeze({
10
11
  PATH: "/usr/local/bin:/usr/bin:/bin",
11
12
  HOME: SANDBOX_WORKSPACE,
@@ -104,6 +105,40 @@ export function virtualBackend(options) {
104
105
  return undefined;
105
106
  return fs.readFile(path, "utf8");
106
107
  },
108
+ async readBytes(path, maxBytes) {
109
+ if (!(await fs.exists(path)))
110
+ return undefined;
111
+ const stat = await fs.stat(path);
112
+ if (stat.size > maxBytes)
113
+ throw new SandboxFileTooLargeError(path, stat.size, maxBytes);
114
+ return fs.readFileBuffer(path);
115
+ },
116
+ async listFiles(dir, maxEntries) {
117
+ if (!(await fs.exists(dir)) || !(await fs.lstat(dir)).isDirectory)
118
+ return undefined;
119
+ const entries = [];
120
+ const walk = async (absolute, relative) => {
121
+ for (const name of (await fs.readdir(absolute)).sort()) {
122
+ const path = posix.join(absolute, name);
123
+ const inFolder = relative === "" ? name : `${relative}/${name}`;
124
+ const stat = await fs.lstat(path);
125
+ if (stat.isSymbolicLink)
126
+ continue;
127
+ if (stat.isDirectory) {
128
+ if (!(await walk(path, inFolder)))
129
+ return false;
130
+ }
131
+ else if (stat.isFile) {
132
+ entries.push({ path: inFolder, size: stat.size });
133
+ if (entries.length > maxEntries)
134
+ return false;
135
+ }
136
+ }
137
+ return true;
138
+ };
139
+ const complete = await walk(dir, "");
140
+ return { entries, truncated: !complete };
141
+ },
107
142
  async writeFile(path, content) {
108
143
  await fs.writeFile(path, content, "utf8");
109
144
  },
@@ -2,25 +2,27 @@
2
2
  "openapi": "3.2.0",
3
3
  "info": {
4
4
  "title": "Nylorun Runtime: Admin API",
5
- "version": "0.15.2-beta",
6
- "description": "The Host's status, its Tenant's state and shutdown, with the admin key. Served on the operator listener when the Host has one; the public listener answers these routes with the opaque 404.",
5
+ "version": "0.17.0-beta",
6
+ "description": "The Host's status, its Tenant's state, shutdown and the Tenant's operator keys, with the admin key. Served on the operator listener when the Host has one; the public listener answers these routes with the opaque 404.",
7
7
  "x-nylorun-protocol": {
8
8
  "min": 4,
9
- "max": 5,
9
+ "max": 7,
10
10
  "features": [
11
11
  "runtime-tenants",
12
12
  "admin-status",
13
13
  "studio-principal",
14
14
  "action-endpoints",
15
+ "artifacts",
15
16
  "tenant-fixture-model",
16
17
  "transcript-events",
17
- "derived-principals",
18
18
  "subject-headers",
19
- "subject-tokens",
20
- "browser-access",
21
19
  "ag-ui-endpoint",
22
20
  "a2a-endpoint",
23
- "action-endpoints"
21
+ "action-endpoints",
22
+ "sandboxes",
23
+ "sandbox-pods",
24
+ "trusted-issuers",
25
+ "operator-keys"
24
26
  ]
25
27
  }
26
28
  },
@@ -238,6 +240,31 @@
238
240
  "lastError"
239
241
  ],
240
242
  "additionalProperties": false
243
+ },
244
+ "harness": {
245
+ "type": "object",
246
+ "properties": {
247
+ "mode": {
248
+ "type": "string",
249
+ "enum": [
250
+ "in-process",
251
+ "remote"
252
+ ]
253
+ },
254
+ "connected": {
255
+ "type": "integer",
256
+ "minimum": 0
257
+ },
258
+ "workspace": {
259
+ "type": "boolean"
260
+ }
261
+ },
262
+ "required": [
263
+ "mode",
264
+ "connected",
265
+ "workspace"
266
+ ],
267
+ "additionalProperties": false
241
268
  }
242
269
  },
243
270
  "required": [
@@ -306,6 +333,16 @@
306
333
  "request_rejected",
307
334
  "invalid_request",
308
335
  "subject_required",
336
+ "sandbox_not_granted",
337
+ "sandbox_busy",
338
+ "sandbox_unavailable",
339
+ "placement_refused",
340
+ "sandbox_lost",
341
+ "sandbox_expired",
342
+ "issuer_unavailable",
343
+ "oauth_client_required",
344
+ "oauth_state_invalid",
345
+ "oauth_failed",
309
346
  "internal_error"
310
347
  ]
311
348
  },
@@ -386,6 +423,93 @@
386
423
  "status"
387
424
  ],
388
425
  "additionalProperties": false
426
+ },
427
+ "ListOperatorKeysResponse": {
428
+ "type": "object",
429
+ "properties": {
430
+ "keys": {
431
+ "type": "array",
432
+ "items": {
433
+ "type": "object",
434
+ "properties": {
435
+ "id": {
436
+ "type": "string",
437
+ "minLength": 1
438
+ },
439
+ "role": {
440
+ "type": "string",
441
+ "minLength": 1
442
+ },
443
+ "createdAt": {
444
+ "type": "string",
445
+ "minLength": 1
446
+ }
447
+ },
448
+ "required": [
449
+ "id",
450
+ "role",
451
+ "createdAt"
452
+ ],
453
+ "additionalProperties": false
454
+ }
455
+ }
456
+ },
457
+ "required": [
458
+ "keys"
459
+ ],
460
+ "additionalProperties": false
461
+ },
462
+ "PutOperatorKeyResponse": {
463
+ "type": "object",
464
+ "properties": {
465
+ "id": {
466
+ "type": "string",
467
+ "minLength": 1
468
+ },
469
+ "role": {
470
+ "type": "string",
471
+ "minLength": 1
472
+ },
473
+ "createdAt": {
474
+ "type": "string",
475
+ "minLength": 1
476
+ },
477
+ "key": {
478
+ "type": "string",
479
+ "pattern": "^[0-9a-f]{64}$"
480
+ },
481
+ "rotated": {
482
+ "type": "boolean"
483
+ }
484
+ },
485
+ "required": [
486
+ "id",
487
+ "role",
488
+ "createdAt",
489
+ "key",
490
+ "rotated"
491
+ ],
492
+ "additionalProperties": false
493
+ },
494
+ "DeleteOperatorKeyResponse": {
495
+ "type": "object",
496
+ "properties": {
497
+ "id": {
498
+ "type": "string",
499
+ "minLength": 1
500
+ },
501
+ "deleted": {
502
+ "type": "boolean",
503
+ "enum": [
504
+ true
505
+ ]
506
+ }
507
+ },
508
+ "required": [
509
+ "id",
510
+ "deleted"
511
+ ],
512
+ "additionalProperties": false
389
513
  }
390
514
  },
391
515
  "parameters": {}
@@ -527,6 +651,216 @@
527
651
  }
528
652
  }
529
653
  },
654
+ "/v1/admin/keys": {
655
+ "get": {
656
+ "summary": "List the Tenant's keys",
657
+ "description": "Every application key of the Tenant by name, with its role and when it was issued. Keys themselves are never shown again.",
658
+ "tags": [
659
+ "Admin"
660
+ ],
661
+ "security": [
662
+ {
663
+ "adminKey": []
664
+ }
665
+ ],
666
+ "responses": {
667
+ "200": {
668
+ "description": "The keys, by id",
669
+ "content": {
670
+ "application/json": {
671
+ "schema": {
672
+ "$ref": "#/components/schemas/ListOperatorKeysResponse"
673
+ }
674
+ }
675
+ }
676
+ },
677
+ "404": {
678
+ "description": "Not found: an unknown admin key, or the public listener",
679
+ "content": {
680
+ "application/json": {
681
+ "schema": {
682
+ "$ref": "#/components/schemas/Rejected"
683
+ }
684
+ }
685
+ }
686
+ },
687
+ "426": {
688
+ "description": "`Nylorun-Protocol` missing or unsupported",
689
+ "content": {
690
+ "application/json": {
691
+ "schema": {
692
+ "$ref": "#/components/schemas/ProtocolRejected"
693
+ }
694
+ }
695
+ }
696
+ },
697
+ "503": {
698
+ "description": "The Tenant is not open",
699
+ "content": {
700
+ "application/json": {
701
+ "schema": {
702
+ "$ref": "#/components/schemas/Rejected"
703
+ }
704
+ }
705
+ }
706
+ }
707
+ }
708
+ }
709
+ },
710
+ "/v1/admin/keys/{id}": {
711
+ "put": {
712
+ "summary": "Create or rotate a key",
713
+ "description": "Issues a new key named `id` and returns it this once. A key of that name is replaced: the old one stops authenticating at once. `studio` is refused.",
714
+ "tags": [
715
+ "Admin"
716
+ ],
717
+ "security": [
718
+ {
719
+ "adminKey": []
720
+ }
721
+ ],
722
+ "parameters": [
723
+ {
724
+ "schema": {
725
+ "type": "string",
726
+ "description": "The key's name: `^[a-z][a-z0-9-]{0,31}$`, never `studio`"
727
+ },
728
+ "required": true,
729
+ "description": "The key's name: `^[a-z][a-z0-9-]{0,31}$`, never `studio`",
730
+ "name": "id",
731
+ "in": "path"
732
+ }
733
+ ],
734
+ "responses": {
735
+ "200": {
736
+ "description": "The new key, shown once",
737
+ "content": {
738
+ "application/json": {
739
+ "schema": {
740
+ "$ref": "#/components/schemas/PutOperatorKeyResponse"
741
+ }
742
+ }
743
+ }
744
+ },
745
+ "400": {
746
+ "description": "Not a key name, or `studio`",
747
+ "content": {
748
+ "application/json": {
749
+ "schema": {
750
+ "$ref": "#/components/schemas/Rejected"
751
+ }
752
+ }
753
+ }
754
+ },
755
+ "404": {
756
+ "description": "Not found: an unknown admin key, or the public listener",
757
+ "content": {
758
+ "application/json": {
759
+ "schema": {
760
+ "$ref": "#/components/schemas/Rejected"
761
+ }
762
+ }
763
+ }
764
+ },
765
+ "426": {
766
+ "description": "`Nylorun-Protocol` missing or unsupported",
767
+ "content": {
768
+ "application/json": {
769
+ "schema": {
770
+ "$ref": "#/components/schemas/ProtocolRejected"
771
+ }
772
+ }
773
+ }
774
+ },
775
+ "503": {
776
+ "description": "The Tenant is not open",
777
+ "content": {
778
+ "application/json": {
779
+ "schema": {
780
+ "$ref": "#/components/schemas/Rejected"
781
+ }
782
+ }
783
+ }
784
+ }
785
+ }
786
+ },
787
+ "delete": {
788
+ "summary": "Delete a key",
789
+ "description": "The key stops authenticating at once. `studio` is refused.",
790
+ "tags": [
791
+ "Admin"
792
+ ],
793
+ "security": [
794
+ {
795
+ "adminKey": []
796
+ }
797
+ ],
798
+ "parameters": [
799
+ {
800
+ "schema": {
801
+ "type": "string",
802
+ "description": "The key's name: `^[a-z][a-z0-9-]{0,31}$`, never `studio`"
803
+ },
804
+ "required": true,
805
+ "description": "The key's name: `^[a-z][a-z0-9-]{0,31}$`, never `studio`",
806
+ "name": "id",
807
+ "in": "path"
808
+ }
809
+ ],
810
+ "responses": {
811
+ "200": {
812
+ "description": "The key is deleted",
813
+ "content": {
814
+ "application/json": {
815
+ "schema": {
816
+ "$ref": "#/components/schemas/DeleteOperatorKeyResponse"
817
+ }
818
+ }
819
+ }
820
+ },
821
+ "400": {
822
+ "description": "Not a key name, or `studio`",
823
+ "content": {
824
+ "application/json": {
825
+ "schema": {
826
+ "$ref": "#/components/schemas/Rejected"
827
+ }
828
+ }
829
+ }
830
+ },
831
+ "404": {
832
+ "description": "No key of that name; or an unknown admin key, or the public listener",
833
+ "content": {
834
+ "application/json": {
835
+ "schema": {
836
+ "$ref": "#/components/schemas/Rejected"
837
+ }
838
+ }
839
+ }
840
+ },
841
+ "426": {
842
+ "description": "`Nylorun-Protocol` missing or unsupported",
843
+ "content": {
844
+ "application/json": {
845
+ "schema": {
846
+ "$ref": "#/components/schemas/ProtocolRejected"
847
+ }
848
+ }
849
+ }
850
+ },
851
+ "503": {
852
+ "description": "The Tenant is not open",
853
+ "content": {
854
+ "application/json": {
855
+ "schema": {
856
+ "$ref": "#/components/schemas/Rejected"
857
+ }
858
+ }
859
+ }
860
+ }
861
+ }
862
+ }
863
+ },
530
864
  "/v1/admin/openapi.json": {
531
865
  "get": {
532
866
  "summary": "Get this document",
@@ -86,7 +86,12 @@ export function historyOf(events, contextId, taskId) {
86
86
  const payload = (event.payload ?? {});
87
87
  switch (event.type) {
88
88
  case "command.message":
89
- add(clientMessageId(event), "ROLE_USER", ["content" in payload ? { text: String(payload.content) } : valuePart(payload.data)]);
89
+ add(clientMessageId(event), "ROLE_USER", "content" in payload
90
+ ? [{ text: String(payload.content) }]
91
+ : Array.isArray(payload.parts)
92
+ ? // Parts (protocol 6): text as text, a file part as its artifact reference.
93
+ payload.parts.map((part) => part.type === "text" ? valuePart(part.text) : valuePart(part))
94
+ : [valuePart(payload.data)]);
90
95
  break;
91
96
  case "command.respond":
92
97
  add(clientMessageId(event), "ROLE_USER", [valuePart(payload.value)]);
@@ -29,7 +29,6 @@ function registerComponents(api) {
29
29
  const FOR_A_PERSON = {
30
30
  credentials: ["subject", "token"],
31
31
  scopes: ["sessions:own"],
32
- browser: true,
33
32
  };
34
33
  const events = (description) => ({
35
34
  description,
@@ -50,7 +49,7 @@ export function agUiRoutes(api) {
50
49
  path: "/v1/ag-ui/agents/{agentId}",
51
50
  tags: TAGS,
52
51
  summary: "Run an agent",
53
- description: "For a person, named by a subject token or by `Nylorun-Subject`. The thread is one session per person, agent and thread, created on its first run with `forwardedProps.nylorun.session`. The trailing user message starts a turn (its id is the idempotency key); `resume` answers interrupts. A busy session or a turn limit is a `RUN_ERROR` in the stream.",
52
+ description: "For a person, named by a trusted issuer's token or by `Nylorun-Subject`. The thread is one session per person, agent and thread, created on its first run with `forwardedProps.nylorun.session`. The trailing user message starts a turn (its id is the idempotency key); `resume` answers interrupts. A busy session is a `RUN_ERROR` in the stream.",
54
53
  request: {
55
54
  params: agentId,
56
55
  body: {
@@ -11,7 +11,12 @@ export function messagesFromEvents(items) {
11
11
  // The AG-UI endpoint sends the client's message id as the idempotency key.
12
12
  id: p.idempotencyKey ?? raw.eventId,
13
13
  role: "user",
14
- content: typeof p.content === "string" ? p.content : JSON.stringify(p.data),
14
+ content: typeof p.content === "string"
15
+ ? p.content
16
+ : Array.isArray(p.parts)
17
+ ? // A message with parts (protocol 6): its text; files are artifacts, not text.
18
+ p.parts.flatMap((part) => (typeof part.text === "string" ? [part.text] : [])).join("\n")
19
+ : JSON.stringify(p.data),
15
20
  });
16
21
  sawAssistant = false;
17
22
  continue;