@nylorun/runtime 0.23.0-beta → 0.25.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 (301) hide show
  1. package/CHANGELOG.md +252 -0
  2. package/README.md +87 -28
  3. package/dist/adapters/execution/restate.js +1 -1
  4. package/dist/adapters/replication/pgoutput.d.ts +2 -0
  5. package/dist/adapters/replication/pgoutput.js +6 -3
  6. package/dist/adapters/sandbox/local.d.ts +9 -1
  7. package/dist/adapters/sandbox/local.js +16 -4
  8. package/dist/adapters/sandbox/virtual.js +4 -1
  9. package/dist/api/a2a/card.d.ts +11 -1
  10. package/dist/api/a2a/card.js +26 -2
  11. package/dist/api/a2a/endpoint.js +9 -3
  12. package/dist/api/a2a/protocol.d.ts +25 -2
  13. package/dist/api/a2a/protocol.js +20 -3
  14. package/dist/api/a2a/routes.d.ts +6 -1
  15. package/dist/api/a2a/routes.js +67 -9
  16. package/dist/api/a2a/tasks.js +2 -1
  17. package/dist/api/ag-ui/endpoint.js +8 -3
  18. package/dist/api/ag-ui/history.d.ts +6 -1
  19. package/dist/api/ag-ui/history.js +39 -3
  20. package/dist/api/ag-ui/routes.d.ts +4 -1
  21. package/dist/api/ag-ui/routes.js +63 -20
  22. package/dist/api/ag-ui/translate.js +3 -2
  23. package/dist/api/components.d.ts +5327 -1091
  24. package/dist/api/components.js +40 -16
  25. package/dist/api/gate/keys.d.ts +2 -1
  26. package/dist/api/gate/keys.js +14 -1
  27. package/dist/api/gate/routes.d.ts +6 -0
  28. package/dist/api/gate/routes.js +52 -3
  29. package/dist/api/http/app.js +17 -6
  30. package/dist/api/http/body.d.ts +3 -3
  31. package/dist/api/http/body.js +7 -7
  32. package/dist/api/http/define.d.ts +6 -0
  33. package/dist/api/http/define.js +13 -1
  34. package/dist/api/http/respond.js +1 -1
  35. package/dist/api/http/routes/artifacts.js +35 -8
  36. package/dist/api/http/routes/coverage.d.ts +9 -0
  37. package/dist/api/http/routes/coverage.js +34 -0
  38. package/dist/api/http/routes/environment-manifests.d.ts +9 -0
  39. package/dist/api/http/routes/environment-manifests.js +118 -0
  40. package/dist/api/http/routes/environments.d.ts +11 -0
  41. package/dist/api/http/routes/environments.js +332 -0
  42. package/dist/api/http/routes/mcp.d.ts +1 -1
  43. package/dist/api/http/routes/mcp.js +2 -2
  44. package/dist/api/http/routes/me.js +1 -1
  45. package/dist/api/http/routes/reads.d.ts +94 -9
  46. package/dist/api/http/routes/reads.js +54 -5
  47. package/dist/api/http/routes/sessions.js +24 -9
  48. package/dist/api/http/routes/tenant.js +65 -15
  49. package/dist/api/inbound-files.d.ts +65 -0
  50. package/dist/api/inbound-files.js +115 -0
  51. package/dist/api/openapi.js +48 -28
  52. package/dist/artifacts/disk.d.ts +59 -0
  53. package/dist/artifacts/disk.js +569 -0
  54. package/dist/artifacts/export.d.ts +1 -1
  55. package/dist/artifacts/export.js +2 -3
  56. package/dist/artifacts/folders.d.ts +6 -3
  57. package/dist/artifacts/folders.js +17 -12
  58. package/dist/artifacts/media-types.d.ts +15 -0
  59. package/dist/artifacts/media-types.js +56 -4
  60. package/dist/artifacts/parts.d.ts +12 -3
  61. package/dist/artifacts/parts.js +15 -4
  62. package/dist/artifacts/service.d.ts +2 -1
  63. package/dist/artifacts/service.js +6 -1
  64. package/dist/artifacts/workspace.d.ts +5 -5
  65. package/dist/artifacts/workspace.js +7 -19
  66. package/dist/core/flow-host.d.ts +3 -4
  67. package/dist/core/flow-host.js +5 -2
  68. package/dist/core/provider.d.ts +3 -2
  69. package/dist/core/provider.js +5 -37
  70. package/dist/core/sandbox-routes.d.ts +2 -21
  71. package/dist/core/sandbox-routes.js +11 -44
  72. package/dist/core/turn-manifest.d.ts +3 -2
  73. package/dist/core/turn-manifest.js +29 -5
  74. package/dist/execution/types.d.ts +1 -1
  75. package/dist/gates/contract.d.ts +3 -24
  76. package/dist/gates/contract.js +1 -36
  77. package/dist/gates/egress-credentials.d.ts +9 -0
  78. package/dist/gates/egress-credentials.js +11 -4
  79. package/dist/gates/egress.d.ts +22 -1
  80. package/dist/gates/egress.js +116 -10
  81. package/dist/gates/fixture-model.d.ts +28 -0
  82. package/dist/gates/fixture-model.js +92 -0
  83. package/dist/gates/handler.js +4 -4
  84. package/dist/gates/http-tool.d.ts +9 -6
  85. package/dist/gates/http-tool.js +11 -17
  86. package/dist/gates/in-process.d.ts +5 -0
  87. package/dist/gates/in-process.js +12 -1
  88. package/dist/gates/journal.d.ts +30 -0
  89. package/dist/gates/journal.js +51 -0
  90. package/dist/gates/mcp-handler.d.ts +4 -0
  91. package/dist/gates/mcp-handler.js +7 -4
  92. package/dist/gates/meter.d.ts +10 -2
  93. package/dist/gates/meter.js +20 -5
  94. package/dist/gates/policy.d.ts +49 -0
  95. package/dist/gates/policy.js +132 -0
  96. package/dist/gates/tenant-vaults.d.ts +7 -7
  97. package/dist/gates/tenant-vaults.js +5 -5
  98. package/dist/gates/tool-calls.d.ts +10 -5
  99. package/dist/gates/tool-calls.js +28 -10
  100. package/dist/gates/tool-client.d.ts +8 -0
  101. package/dist/gates/tool-client.js +27 -11
  102. package/dist/gates/tool-contract.d.ts +11 -69
  103. package/dist/gates/tool-contract.js +1 -85
  104. package/dist/gates/tool-gate.d.ts +1 -1
  105. package/dist/harness/calls.d.ts +14 -7
  106. package/dist/harness/calls.js +30 -24
  107. package/dist/harness/executors.js +12 -9
  108. package/dist/harness/main.js +24 -5
  109. package/dist/harness/pod.d.ts +39 -0
  110. package/dist/harness/pod.js +46 -0
  111. package/dist/harness/service.d.ts +0 -1
  112. package/dist/harness/service.js +17 -9
  113. package/dist/harness/setup.d.ts +29 -0
  114. package/dist/harness/setup.js +174 -0
  115. package/dist/harness/ws-client.js +3 -2
  116. package/dist/harness/ws-port.d.ts +1 -5
  117. package/dist/harness/ws-port.js +1 -5
  118. package/dist/harness-api/in-process.js +3 -3
  119. package/dist/harness-api/record.d.ts +15 -1
  120. package/dist/harness-api/record.js +180 -50
  121. package/dist/harness-api/redact.d.ts +3 -0
  122. package/dist/harness-api/redact.js +45 -0
  123. package/dist/harness-api/server.d.ts +35 -4
  124. package/dist/harness-api/server.js +130 -17
  125. package/dist/harness-api/start.d.ts +10 -3
  126. package/dist/harness-api/start.js +40 -10
  127. package/dist/harness-api/tool-journal.d.ts +105 -0
  128. package/dist/harness-api/tool-journal.js +132 -0
  129. package/dist/harness-api/tool-status.d.ts +13 -0
  130. package/dist/harness-api/tool-status.js +26 -0
  131. package/dist/harness-api/workspace.d.ts +12 -7
  132. package/dist/harness-api/workspace.js +47 -25
  133. package/dist/harness-api/ws-server.d.ts +11 -3
  134. package/dist/harness-api/ws-server.js +94 -15
  135. package/dist/host/gates.d.ts +2 -1
  136. package/dist/host/gates.js +10 -2
  137. package/dist/host/main.js +54 -5
  138. package/dist/host/operate.js +67 -5
  139. package/dist/host/stack-config.d.ts +64 -0
  140. package/dist/host/stack-config.js +62 -2
  141. package/dist/keys/client.js +4 -1
  142. package/dist/keys/keys.d.ts +13 -6
  143. package/dist/keys/keys.js +9 -8
  144. package/dist/management-openapi.json +1264 -2171
  145. package/dist/mcp/connect.d.ts +26 -22
  146. package/dist/mcp/connect.js +30 -34
  147. package/dist/mcp/pool.d.ts +3 -6
  148. package/dist/mcp/pool.js +14 -28
  149. package/dist/mcp/preview.d.ts +3 -3
  150. package/dist/mcp/preview.js +10 -21
  151. package/dist/mcp/snapshot.d.ts +16 -24
  152. package/dist/mcp/snapshot.js +33 -45
  153. package/dist/model/capabilities.d.ts +26 -0
  154. package/dist/model/capabilities.js +44 -0
  155. package/dist/model/catalog.d.ts +34 -3
  156. package/dist/model/catalog.js +38 -2
  157. package/dist/model/models.d.ts +2 -0
  158. package/dist/model/models.js +1 -1
  159. package/dist/model/native-blocks.d.ts +35 -0
  160. package/dist/model/native-blocks.js +170 -0
  161. package/dist/model/pi-model.js +114 -94
  162. package/dist/openapi.json +12822 -3943
  163. package/dist/reads/types.d.ts +16 -3
  164. package/dist/record/append.d.ts +3 -1
  165. package/dist/record/append.js +1 -0
  166. package/dist/record/envelope.d.ts +5 -1
  167. package/dist/record/envelope.js +7 -3
  168. package/dist/record/environment.d.ts +25 -0
  169. package/dist/record/environment.js +44 -0
  170. package/dist/record/index.d.ts +1 -1
  171. package/dist/record/index.js +1 -1
  172. package/dist/sandbox/disk-fs.d.ts +95 -0
  173. package/dist/sandbox/disk-fs.js +206 -0
  174. package/dist/sandbox/join.d.ts +17 -18
  175. package/dist/sandbox/join.js +46 -15
  176. package/dist/sandbox/manager.d.ts +47 -21
  177. package/dist/sandbox/manager.js +162 -107
  178. package/dist/sandbox/placement.d.ts +14 -13
  179. package/dist/sandbox/placement.js +31 -14
  180. package/dist/sandbox/pods/client.d.ts +9 -2
  181. package/dist/sandbox/pods/lifecycle.d.ts +36 -27
  182. package/dist/sandbox/pods/lifecycle.js +29 -26
  183. package/dist/sandbox/pods/reconcile.js +18 -12
  184. package/dist/sandbox/pods/spec.d.ts +15 -13
  185. package/dist/sandbox/pods/spec.js +17 -20
  186. package/dist/sandbox/records.d.ts +2 -2
  187. package/dist/sandbox/records.js +3 -3
  188. package/dist/sandbox/resolve.d.ts +37 -27
  189. package/dist/sandbox/resolve.js +73 -41
  190. package/dist/sandbox/runtime-tools.d.ts +9 -0
  191. package/dist/sandbox/runtime-tools.js +16 -0
  192. package/dist/sandbox/session-sandbox.d.ts +4 -6
  193. package/dist/sandbox/session-sandbox.js +47 -94
  194. package/dist/sandbox/setup.d.ts +64 -0
  195. package/dist/sandbox/setup.js +160 -0
  196. package/dist/sandbox/share.d.ts +21 -26
  197. package/dist/sandbox/share.js +36 -63
  198. package/dist/sandbox/skills.d.ts +8 -6
  199. package/dist/sandbox/skills.js +24 -23
  200. package/dist/sandbox/tenant-config.d.ts +12 -14
  201. package/dist/sandbox/tenant-config.js +12 -17
  202. package/dist/sandbox/types.d.ts +6 -0
  203. package/dist/store/postgres/drizzle/0020_environments.sql +63 -0
  204. package/dist/store/postgres/drizzle/0021_disk_versions.sql +7 -0
  205. package/dist/store/postgres/drizzle/0022_model_usage_timing.sql +5 -0
  206. package/dist/store/postgres/drizzle/0023_artifact_pages.sql +1 -0
  207. package/dist/store/postgres/drizzle/0024_flat_manifest.sql +7 -0
  208. package/dist/store/postgres/drizzle/0025_environment_manifests.sql +14 -0
  209. package/dist/store/postgres/drizzle/0026_environment_forks.sql +1 -0
  210. package/dist/store/postgres/drizzle/0027_environment_image_digest.sql +1 -0
  211. package/dist/store/postgres/drizzle/meta/_journal.json +56 -0
  212. package/dist/store/postgres/reads.js +123 -17
  213. package/dist/store/postgres/record-writer.d.ts +4 -4
  214. package/dist/store/postgres/record-writer.js +9 -9
  215. package/dist/store/postgres/record.d.ts +7 -0
  216. package/dist/store/postgres/record.js +23 -2
  217. package/dist/store/postgres/schema.d.ts +1126 -736
  218. package/dist/store/postgres/schema.js +136 -73
  219. package/dist/store/postgres/store.js +195 -67
  220. package/dist/store/types.d.ts +165 -50
  221. package/dist/tenant/advance.d.ts +7 -6
  222. package/dist/tenant/advance.js +118 -42
  223. package/dist/tenant/artifact-tool.d.ts +1 -1
  224. package/dist/tenant/artifact-tool.js +46 -26
  225. package/dist/tenant/commands.d.ts +15 -2
  226. package/dist/tenant/commands.js +115 -14
  227. package/dist/tenant/context.d.ts +56 -24
  228. package/dist/tenant/context.js +4 -15
  229. package/dist/tenant/effects.d.ts +1 -1
  230. package/dist/tenant/effects.js +44 -29
  231. package/dist/tenant/environment-manifests.d.ts +57 -0
  232. package/dist/tenant/environment-manifests.js +200 -0
  233. package/dist/tenant/environments.d.ts +174 -0
  234. package/dist/tenant/environments.js +1044 -0
  235. package/dist/tenant/harness-tokens.d.ts +21 -0
  236. package/dist/tenant/harness-tokens.js +61 -0
  237. package/dist/tenant/host-token.d.ts +1 -1
  238. package/dist/tenant/host-token.js +2 -4
  239. package/dist/tenant/identity-file.d.ts +4 -4
  240. package/dist/tenant/identity-file.js +17 -8
  241. package/dist/tenant/issuers.js +3 -3
  242. package/dist/tenant/model-setting.d.ts +14 -0
  243. package/dist/tenant/model-setting.js +51 -0
  244. package/dist/tenant/principals.js +1 -1
  245. package/dist/tenant/reset.js +3 -3
  246. package/dist/tenant/runtime.d.ts +6 -1
  247. package/dist/tenant/runtime.js +7 -1
  248. package/dist/tenant/session.d.ts +3 -3
  249. package/dist/tenant/session.js +0 -14
  250. package/dist/tenant/sessions.d.ts +18 -14
  251. package/dist/tenant/sessions.js +99 -143
  252. package/dist/tenant/signing-keys.d.ts +5 -1
  253. package/dist/tenant/skill-tool.d.ts +3 -3
  254. package/dist/tenant/skill-tool.js +6 -11
  255. package/dist/tenant/status.js +3 -3
  256. package/dist/tenant/sweep.js +13 -7
  257. package/dist/tenant/tool-results.js +1 -1
  258. package/dist/tenant/tool-search.d.ts +7 -11
  259. package/dist/tenant/tool-search.js +8 -5
  260. package/dist/tenant/transcript.d.ts +245 -36
  261. package/dist/tenant/transcript.js +64 -12
  262. package/dist/tenant/turn-timing.d.ts +43 -0
  263. package/dist/tenant/turn-timing.js +39 -0
  264. package/dist/tenant/types.d.ts +2 -0
  265. package/dist/traces/ids.d.ts +41 -0
  266. package/dist/traces/ids.js +81 -0
  267. package/dist/traces/key.d.ts +6 -0
  268. package/dist/traces/key.js +37 -0
  269. package/dist/traces/map.d.ts +113 -0
  270. package/dist/traces/map.js +289 -0
  271. package/dist/traces/options.d.ts +7 -0
  272. package/dist/traces/options.js +1 -0
  273. package/dist/traces/otlp.d.ts +42 -0
  274. package/dist/traces/otlp.js +84 -0
  275. package/dist/traces/projector.d.ts +58 -0
  276. package/dist/traces/projector.js +200 -0
  277. package/dist/traces/read.d.ts +23 -0
  278. package/dist/traces/read.js +16 -0
  279. package/dist/vault/coverage.d.ts +26 -0
  280. package/dist/vault/coverage.js +62 -0
  281. package/dist/vault/error.d.ts +11 -1
  282. package/dist/vault/error.js +5 -1
  283. package/dist/vault/host-model.d.ts +4 -1
  284. package/dist/vault/host-model.js +10 -2
  285. package/dist/vault/service.d.ts +92 -48
  286. package/dist/vault/service.js +462 -376
  287. package/dist/vault/sources.d.ts +5 -16
  288. package/dist/vault/sources.js +6 -8
  289. package/dist/version.d.ts +1 -1
  290. package/dist/version.js +2 -2
  291. package/package.json +13 -13
  292. package/dist/api/http/routes/sandboxes.d.ts +0 -9
  293. package/dist/api/http/routes/sandboxes.js +0 -153
  294. package/dist/api/http/routes/vaults.d.ts +0 -10
  295. package/dist/api/http/routes/vaults.js +0 -139
  296. package/dist/record/sandbox.d.ts +0 -25
  297. package/dist/record/sandbox.js +0 -43
  298. package/dist/sandbox/capability.d.ts +0 -3
  299. package/dist/sandbox/capability.js +0 -10
  300. package/dist/tenant/sandboxes.d.ts +0 -123
  301. package/dist/tenant/sandboxes.js +0 -566
@@ -2,10 +2,11 @@ import { SANDBOX_WORKSPACE, } from "@nylorun/core/define";
2
2
  import { sandboxWorkspaceKey, sessionWorkspaceKey, workspacePrefix, } from "./records.js";
3
3
  import { DEFAULT_SANDBOX_CPUS, DEFAULT_SANDBOX_IMAGE, describeNetwork, idleMsOf, memoryMiBOf, resolveNetwork, } from "./policy.js";
4
4
  import { parseSandboxPreference, reportSelection, selectSandboxBackend, } from "./select.js";
5
- import { mountSkill, sandboxSkillsOf, skillSignature } from "./skills.js";
5
+ import { mountSkill, sandboxSkillsOf } from "./skills.js";
6
+ import { runDiskOp } from "./disk-fs.js";
6
7
  import { resolveSandboxPath, runSandboxTool, } from "./tools.js";
7
8
  import { SandboxFileTooLargeError, } from "./types.js";
8
- export { sandboxCapabilityOf } from "./capability.js";
9
+ export { sandboxToolSpec } from "./runtime-tools.js";
9
10
  const READ_TOOLS = new Set(["read", "grep", "glob"]);
10
11
  export class SandboxManager {
11
12
  options;
@@ -43,23 +44,20 @@ export class SandboxManager {
43
44
  if (this.closing)
44
45
  return;
45
46
  const now = at ?? Date.now();
46
- const idle = (live) => !!live.handle && live.active === 0 && now - live.lastUsedAt >= live.idleMs;
47
+ const idle = (live) => !!live.handle && live.running.size === 0 && now - live.lastUsedAt >= live.idleMs;
47
48
  const stops = [];
48
49
  for (const [key, live] of this.live) {
49
50
  if (!idle(live))
50
51
  continue;
51
- // Queued behind any call already waiting; checked again when its turn comes.
52
- live.tail = live.tail
53
- .then(() => (idle(live) ? this.stop(key, live) : undefined))
54
- .catch(() => undefined);
55
- stops.push(live.tail);
52
+ // After the calls running; checked again when its turn comes.
53
+ stops.push(this.alone(live, () => (idle(live) ? this.stop(key, live) : undefined)).catch(() => undefined));
56
54
  }
57
55
  await Promise.all(stops);
58
56
  for (const record of await this.options.records.list()) {
59
57
  if (record.state === "stopped")
60
58
  continue;
61
59
  const live = this.live.get(record.key);
62
- if (live && (live.handle || live.active > 0))
60
+ if (live && (live.handle || live.running.size > 0))
63
61
  continue;
64
62
  await this.options.records.put({ ...record, state: "stopped" });
65
63
  }
@@ -89,34 +87,84 @@ export class SandboxManager {
89
87
  keyOf(sessionId) {
90
88
  return sessionWorkspaceKey(this.options.scope, sessionId);
91
89
  }
92
- /** The workspace key of sandbox resource `sandboxId` (`sandboxWorkspaceKey`). */
93
- sandboxKeyOf(sandboxId) {
94
- return sandboxWorkspaceKey(this.options.scope, sandboxId);
90
+ /** The workspace key of sandbox resource `environmentId` (`sandboxWorkspaceKey`). */
91
+ sandboxKeyOf(environmentId) {
92
+ return sandboxWorkspaceKey(this.options.scope, environmentId);
95
93
  }
96
94
  keyFor(session) {
97
- return session.sandboxId === undefined
95
+ return session.environmentId === undefined
98
96
  ? this.keyOf(session.id)
99
- : this.sandboxKeyOf(session.sandboxId);
97
+ : this.sandboxKeyOf(session.environmentId);
100
98
  }
101
99
  async report() {
102
100
  return { ...reportSelection(await this.ready), defaultImage: DEFAULT_SANDBOX_IMAGE };
103
101
  }
104
- async run(session, capability, toolName, input, signal) {
105
- const prepared = await this.prepare(session, capability);
102
+ async run(session, sandbox, toolName, input, signal) {
103
+ const prepared = await this.prepare(session, sandbox);
106
104
  if ("kind" in prepared)
107
105
  return prepared;
108
106
  const { live: entry, spec } = prepared;
109
- const task = entry.tail.then(() => this.execute(entry, session, spec, toolName, input, signal));
110
- entry.tail = task.catch(() => undefined);
107
+ return this.inLane(entry, session.id, () => this.execute(entry, session, spec, toolName, input, signal));
108
+ }
109
+ /**
110
+ * Runs `call` in session `sessionId`'s queue on `live`: after its earlier calls, and after any
111
+ * stop or removal pending, beside other sessions' calls (D66).
112
+ */
113
+ inLane(live, sessionId, call) {
114
+ const task = (live.lanes.get(sessionId) ?? Promise.resolve()).then(async () => {
115
+ // A stop queued while this waited goes first.
116
+ for (let gate = live.gate;; gate = live.gate) {
117
+ await gate;
118
+ if (gate === live.gate)
119
+ break;
120
+ }
121
+ const running = call();
122
+ live.running.add(running);
123
+ try {
124
+ return await running;
125
+ }
126
+ finally {
127
+ live.running.delete(running);
128
+ live.lastUsedAt = Date.now();
129
+ }
130
+ });
131
+ const queued = task.catch(() => undefined);
132
+ live.lanes.set(sessionId, queued);
133
+ void queued.then(() => {
134
+ if (live.lanes.get(sessionId) === queued)
135
+ live.lanes.delete(sessionId);
136
+ });
137
+ return task;
138
+ }
139
+ /** Runs `step` on `live` alone: after the calls running, before the calls that come after it. */
140
+ alone(live, step) {
141
+ const task = live.gate.then(async () => {
142
+ while (live.running.size > 0)
143
+ await Promise.allSettled([...live.running]);
144
+ return step();
145
+ });
146
+ live.gate = task.catch(() => undefined);
111
147
  return task;
112
148
  }
149
+ /** The workspace's handle, opened once for the calls that need it at the same time. */
150
+ async handleOf(live, session, spec) {
151
+ if (live.handle)
152
+ return live.handle;
153
+ const opening = (live.opening ??= this.open(live, session, spec).finally(() => {
154
+ if (live.opening === opening)
155
+ live.opening = undefined;
156
+ }));
157
+ const handle = await opening;
158
+ live.handle ??= handle;
159
+ return handle;
160
+ }
113
161
  /**
114
162
  * The bytes of one file in the session's sandbox, for `save_artifact` (F8.1), in the same queue
115
163
  * as its tool calls: `missing` when there is no such file, a failed outcome when the sandbox
116
164
  * cannot open or the file is larger than `maxBytes`. Relative paths resolve in the workspace.
117
165
  */
118
- async readBytes(session, capability, path, maxBytes, signal) {
119
- return this.withHandle(session, capability, signal, async (handle, live) => {
166
+ async readBytes(session, sandbox, path, maxBytes, signal) {
167
+ return this.withHandle(session, sandbox, signal, async (handle, live) => {
120
168
  const resolved = resolveSandboxPath(path, handle.workspace ?? SANDBOX_WORKSPACE);
121
169
  if (!handle.readBytes)
122
170
  return {
@@ -146,14 +194,14 @@ export class SandboxManager {
146
194
  * the same queue as its tool calls. `missing` when the sandbox was never created (nothing is
147
195
  * started to look) or `dir` is not a directory. Relative paths resolve in the workspace.
148
196
  */
149
- async listFiles(session, capability, dir, maxEntries, signal) {
197
+ async listFiles(session, sandbox, dir, maxEntries, signal) {
150
198
  const key = this.keyFor(session);
151
199
  if (!this.live.get(key)?.handle) {
152
200
  const record = await this.options.records.get(key);
153
201
  if (!record)
154
202
  return { kind: "missing", path: dir };
155
203
  }
156
- return this.withHandle(session, capability, signal, async (handle, live) => {
204
+ return this.withHandle(session, sandbox, signal, async (handle, live) => {
157
205
  const resolved = resolveSandboxPath(dir, handle.workspace ?? SANDBOX_WORKSPACE);
158
206
  if (!handle.listFiles)
159
207
  return {
@@ -167,46 +215,51 @@ export class SandboxManager {
167
215
  : { kind: "listed", path: resolved, listing };
168
216
  });
169
217
  }
218
+ /**
219
+ * One operation on the disk of the session's sandbox (R2d step 3, `sandbox/disk-fs.ts`), in the
220
+ * same queue as its tool calls, starting it first when it is stopped.
221
+ */
222
+ async disk(session, sandbox, op, signal) {
223
+ return this.withHandle(session, sandbox, signal, async (handle, live) => {
224
+ if (!handle.diskRoots)
225
+ return {
226
+ kind: "failed",
227
+ code: "sandbox.unsupported",
228
+ message: `The ${live.backend.name} backend has no disk to version`,
229
+ };
230
+ return runDiskOp(handle.diskRoots, op);
231
+ });
232
+ }
170
233
  /** Runs `use` on the session's sandbox in its queue, starting it first when it is stopped. */
171
- async withHandle(session, capability, signal, use) {
172
- const prepared = await this.prepare(session, capability);
234
+ async withHandle(session, sandbox, signal, use) {
235
+ const prepared = await this.prepare(session, sandbox);
173
236
  if ("kind" in prepared)
174
237
  return prepared;
175
238
  const { live, spec } = prepared;
176
- const task = live.tail.then(async () => {
239
+ return this.inLane(live, session.id, async () => {
177
240
  if (signal.aborted)
178
241
  throw new Error("Turn cancelled");
179
242
  if (this.closing)
180
243
  throw new Error("Runtime is shutting down");
181
- live.active += 1;
244
+ let handle;
182
245
  try {
183
- if (!live.handle) {
184
- try {
185
- live.handle = await this.open(live, session, spec);
186
- }
187
- catch (error) {
188
- return {
189
- kind: "failed",
190
- code: "sandbox.start_failed",
191
- message: `The sandbox could not start: ${error instanceof Error ? error.message : String(error)}`,
192
- };
193
- }
194
- }
195
- const unmounted = await this.mountSkills(live, live.handle, session, signal);
196
- if (unmounted)
197
- return unmounted;
198
- return await use(live.handle, live);
246
+ handle = await this.handleOf(live, session, spec);
199
247
  }
200
- finally {
201
- live.active -= 1;
202
- live.lastUsedAt = Date.now();
248
+ catch (error) {
249
+ return {
250
+ kind: "failed",
251
+ code: "sandbox.start_failed",
252
+ message: `The sandbox could not start: ${error instanceof Error ? error.message : String(error)}`,
253
+ };
203
254
  }
255
+ const unmounted = await this.mountSkills(live, handle, session, signal);
256
+ if (unmounted)
257
+ return unmounted;
258
+ return await use(handle, live);
204
259
  });
205
- live.tail = task.catch(() => undefined);
206
- return task;
207
260
  }
208
261
  /** The session's live sandbox entry and spec, or why it cannot run here. */
209
- async prepare(session, capability) {
262
+ async prepare(session, sandbox) {
210
263
  const selection = await this.ready;
211
264
  const backend = selection.backend;
212
265
  if (!backend)
@@ -218,10 +271,10 @@ export class SandboxManager {
218
271
  const key = this.keyFor(session);
219
272
  const spec = {
220
273
  key,
221
- image: capability.sandbox?.image ?? DEFAULT_SANDBOX_IMAGE,
222
- cpus: capability.sandbox?.resources?.cpus ?? DEFAULT_SANDBOX_CPUS,
223
- memoryMiB: memoryMiBOf(capability.sandbox),
224
- network: resolveNetwork(capability.sandbox),
274
+ image: sandbox?.image ?? DEFAULT_SANDBOX_IMAGE,
275
+ cpus: sandbox?.resources?.cpus ?? DEFAULT_SANDBOX_CPUS,
276
+ memoryMiB: memoryMiBOf(sandbox),
277
+ network: resolveNetwork(sandbox),
225
278
  };
226
279
  const unmet = backend.unmet(spec);
227
280
  if (unmet)
@@ -234,12 +287,13 @@ export class SandboxManager {
234
287
  if (!live) {
235
288
  live = {
236
289
  sessionId: session.id,
237
- ...(session.sandboxId === undefined ? {} : { sandboxId: session.sandboxId }),
290
+ ...(session.environmentId === undefined ? {} : { environmentId: session.environmentId }),
238
291
  backend,
239
- idleMs: idleMsOf(capability.sandbox),
240
- active: 0,
292
+ idleMs: idleMsOf(sandbox),
293
+ running: new Set(),
241
294
  lastUsedAt: Date.now(),
242
- tail: Promise.resolve(),
295
+ lanes: new Map(),
296
+ gate: Promise.resolve(),
243
297
  skills: new Map(),
244
298
  };
245
299
  this.live.set(key, live);
@@ -251,42 +305,34 @@ export class SandboxManager {
251
305
  throw new Error("Turn cancelled");
252
306
  if (this.closing)
253
307
  throw new Error("Runtime is shutting down");
254
- live.active += 1;
255
- try {
256
- return await this.executeOpen(live, session, spec, toolName, input, signal);
257
- }
258
- finally {
259
- live.active -= 1;
260
- live.lastUsedAt = Date.now();
261
- }
308
+ return this.executeOpen(live, session, spec, toolName, input, signal);
262
309
  }
263
310
  async executeOpen(live, session, spec, toolName, input, signal) {
264
- if (!live.handle) {
265
- try {
266
- live.handle = await this.open(live, session, spec);
267
- }
268
- catch (error) {
269
- const message = error instanceof Error ? error.message : String(error);
270
- const record = await this.record(spec.key, live, session.id, spec.image, "stopped");
271
- await this.options.emit(session.id, session.activeTurnId, "sandbox.state", { state: "stopped", backend: live.backend.name, error: message }, { record, ...claimOf(session) });
272
- // Nothing ran yet, so a failed start is a definite outcome, not an uncertain one.
273
- return {
274
- kind: "failed",
275
- code: "sandbox.start_failed",
276
- message: `The sandbox could not start on ${live.backend.name} (image ${spec.image ?? "the backend's default"}): ${message}`,
277
- };
278
- }
311
+ let handle;
312
+ try {
313
+ handle = await this.handleOf(live, session, spec);
279
314
  }
280
- const unmounted = await this.mountSkills(live, live.handle, session, signal);
315
+ catch (error) {
316
+ const message = error instanceof Error ? error.message : String(error);
317
+ const record = await this.record(spec.key, live, session.id, spec.image, "stopped");
318
+ await this.options.emit(session.id, session.activeTurnId, "environment.state", { state: "stopped", backend: live.backend.name, error: message }, { record, ...claimOf(session) });
319
+ // Nothing ran yet, so a failed start is a definite outcome, not an uncertain one.
320
+ return {
321
+ kind: "failed",
322
+ code: "sandbox.start_failed",
323
+ message: `The sandbox could not start on ${live.backend.name} (image ${spec.image ?? "the backend's default"}): ${message}`,
324
+ };
325
+ }
326
+ const unmounted = await this.mountSkills(live, handle, session, signal);
281
327
  if (unmounted)
282
328
  return unmounted;
283
329
  const started = Date.now();
284
330
  let report = {};
285
331
  try {
286
- const outcome = await runSandboxTool(live.handle, toolName, (input ?? {}), signal, (value) => {
332
+ const outcome = await runSandboxTool(handle, toolName, (input ?? {}), signal, (value) => {
287
333
  report = value;
288
334
  }, session.environment);
289
- await this.options.emit(session.id, session.activeTurnId, "sandbox.exec", {
335
+ await this.options.emit(session.id, session.activeTurnId, "environment.exec", {
290
336
  tool: toolName,
291
337
  ...report,
292
338
  ...(report.command ? { command: report.command.slice(0, 500) } : {}),
@@ -297,8 +343,9 @@ export class SandboxManager {
297
343
  return outcome;
298
344
  }
299
345
  catch (error) {
300
- // The backend itself failed; reopen on the next call.
301
- live.handle = undefined;
346
+ // The backend itself failed; reopen on the next call, unless another call already did.
347
+ if (live.handle === handle)
348
+ live.handle = undefined;
302
349
  if (signal.aborted || !READ_TOOLS.has(toolName))
303
350
  throw error;
304
351
  return {
@@ -309,32 +356,38 @@ export class SandboxManager {
309
356
  }
310
357
  }
311
358
  /**
312
- * Mounts the session's skills the workspace does not hold as they are: on a handle just opened
313
- * all of them, later only those another session brought or changed. A failed outcome when
314
- * one cannot be mounted; nothing is marked mounted then, so the next call tries again.
359
+ * Mounts the skill folders the session needs that the workspace does not hold (D66): on a
360
+ * handle just opened all of them, later only versions another session has not mounted. Calls
361
+ * that need one folder at once share its mount. A failed outcome when one cannot be mounted;
362
+ * the folder is not marked mounted then, so the next call tries again.
315
363
  */
316
364
  async mountSkills(live, handle, session, signal) {
317
365
  const read = this.options.definitionFile;
318
366
  if (!read)
319
367
  return undefined;
320
- for (const [name, files] of sandboxSkillsOf(session.manifest)) {
321
- const signature = skillSignature(files);
322
- if (live.skills.get(name) === signature)
323
- continue;
368
+ for (const [folder, files] of sandboxSkillsOf(session.manifest)) {
369
+ let mounting = live.skills.get(folder);
370
+ if (!mounting) {
371
+ const mount = mountSkill(handle, folder, files, (sha256) => read(sha256, session), signal);
372
+ live.skills.set(folder, mount);
373
+ void mount.catch(() => {
374
+ if (live.skills.get(folder) === mount)
375
+ live.skills.delete(folder);
376
+ });
377
+ mounting = mount;
378
+ }
324
379
  try {
325
- await mountSkill(handle, name, files, (sha256) => read(sha256, session), signal);
380
+ await mounting;
326
381
  }
327
382
  catch (error) {
328
383
  if (signal.aborted)
329
384
  throw error;
330
- live.skills.delete(name);
331
385
  return {
332
386
  kind: "failed",
333
387
  code: "sandbox.skills_unavailable",
334
- message: `The files of skill '${name}' could not be put in the sandbox: ${error instanceof Error ? error.message : String(error)}`,
388
+ message: `The files of skill folder ${folder} could not be put in the sandbox: ${error instanceof Error ? error.message : String(error)}`,
335
389
  };
336
390
  }
337
- live.skills.set(name, signature);
338
391
  }
339
392
  return undefined;
340
393
  }
@@ -347,12 +400,12 @@ export class SandboxManager {
347
400
  network: describeNetwork(spec.network),
348
401
  };
349
402
  const creating = await this.record(spec.key, live, session.id, spec.image, "creating", existing);
350
- await this.options.emit(session.id, session.activeTurnId, "sandbox.state", { state: "creating", ...payload, ...(existing ? { reattach: true } : {}) }, { record: creating, ...claimOf(session) });
403
+ await this.options.emit(session.id, session.activeTurnId, "environment.state", { state: "creating", ...payload, ...(existing ? { reattach: true } : {}) }, { record: creating, ...claimOf(session) });
351
404
  const handle = await live.backend.open(spec);
352
405
  // A handle opened afresh holds no skills this manager knows of.
353
406
  live.skills.clear();
354
407
  const running = await this.record(spec.key, live, session.id, spec.image, "running", existing);
355
- await this.options.emit(session.id, session.activeTurnId, "sandbox.state", {
408
+ await this.options.emit(session.id, session.activeTurnId, "environment.state", {
356
409
  state: "running",
357
410
  ...payload,
358
411
  ...(existing && handle.created
@@ -371,7 +424,7 @@ export class SandboxManager {
371
424
  }
372
425
  finally {
373
426
  const record = await this.record(key, live, live.sessionId, undefined, "stopped");
374
- await this.options.emit(live.sessionId, null, "sandbox.state", { state: "stopped", backend: live.backend.name }, { record });
427
+ await this.options.emit(live.sessionId, null, "environment.state", { state: "stopped", backend: live.backend.name }, { record });
375
428
  }
376
429
  }
377
430
  async record(key, live, sessionId, image, state, known) {
@@ -380,7 +433,7 @@ export class SandboxManager {
380
433
  const record = {
381
434
  key,
382
435
  sessionId,
383
- ...(live.sandboxId === undefined ? {} : { sandboxId: live.sandboxId }),
436
+ ...(live.environmentId === undefined ? {} : { environmentId: live.environmentId }),
384
437
  backend: live.backend.name,
385
438
  image: image ?? existing?.image ?? "",
386
439
  state,
@@ -395,18 +448,19 @@ export class SandboxManager {
395
448
  return this.options.records.get(this.keyOf(sessionId));
396
449
  }
397
450
  /**
398
- * Deletes sandbox resource `sandboxId`'s workspace, its files and its compute record, after
451
+ * Deletes sandbox resource `environmentId`'s workspace, its files and its compute record, after
399
452
  * any command queued on it. Called once the resource is deleted.
400
453
  */
401
- async removeSandbox(sandboxId) {
402
- await this.remove(this.sandboxKeyOf(sandboxId));
454
+ async removeSandbox(environmentId) {
455
+ await this.remove(this.sandboxKeyOf(environmentId));
403
456
  }
404
457
  /** Deletes the workspace `key`, its files and its record, after any command queued on it. */
405
458
  async remove(key) {
406
459
  const live = this.live.get(key);
407
460
  if (live) {
408
- await live.tail.catch(() => undefined);
409
- live.handle = undefined;
461
+ await this.alone(live, () => {
462
+ live.handle = undefined;
463
+ }).catch(() => undefined);
410
464
  this.live.delete(key);
411
465
  }
412
466
  const { backend } = await this.ready;
@@ -425,9 +479,9 @@ export class SandboxManager {
425
479
  for (const key of await backend.list(this.prefix)) {
426
480
  const record = await this.options.records.get(key);
427
481
  if (record) {
428
- const kept = record.sandboxId === undefined
482
+ const kept = record.environmentId === undefined
429
483
  ? await sessionExists(record.sessionId)
430
- : await sandboxExists(record.sandboxId);
484
+ : await sandboxExists(record.environmentId);
431
485
  if (kept)
432
486
  continue;
433
487
  }
@@ -439,7 +493,8 @@ export class SandboxManager {
439
493
  if (!this.selection)
440
494
  return;
441
495
  const tasks = [...this.live.entries()].map(async ([key, live]) => {
442
- await live.tail;
496
+ await Promise.allSettled([...live.lanes.values(), live.gate]);
497
+ await Promise.allSettled([...live.running]);
443
498
  if (this.options.ephemeral) {
444
499
  live.handle = undefined;
445
500
  await live.backend.remove(key);
@@ -1,27 +1,28 @@
1
1
  /**
2
2
  * Placement (F7.2, D38): where a session's harness runs, checked when the session opens and
3
3
  * never downgraded afterwards. The Tenant maps harness ids (`nylorun`, or `*` for the rest) to
4
- * the hosts they may run on (`sandbox.config` `placement`): `harness-container` (the Runtime's
5
- * harness: in process, or the harness container) and `sandbox` (the engine in a pod sandbox).
6
- * Default: both.
4
+ * the hosts they may run on (`environments.config` `placement`): `harness-container` (the
5
+ * Runtime's harness: in process, or the harness container) and `sandbox` (the engine in a pod).
6
+ * Default: both. `"*": { hosts: [] }` refuses every harness the map does not name (R4.1).
7
7
  *
8
- * A session attached to a pod sandbox runs on `sandbox`; every other session on
9
- * `harness-container`. A host the Tenant does not allow is `409 placement_refused`. A pod
10
- * sandbox needs sandbox pods: without a cluster (`nylorun sandbox enable`), or with the
11
- * sandboxes service not ready, it is `409 sandbox_unavailable`.
8
+ * A session on a pod Environment runs on `sandbox`; every other session (a virtual or `none`
9
+ * Environment, or none) on `harness-container` (D52). A host the Tenant does not allow is
10
+ * `409 placement_refused`. A pod Environment needs sandbox pods: without a cluster (`nylorun
11
+ * sandbox enable`), or with the sandboxes service not ready, it is `409 environment_unavailable`.
12
12
  */
13
- import type { SandboxPlacementHost, TenantSandboxConfig } from "@nylorun/core/contracts";
13
+ import { type EnvironmentKind, type PlacementHost as SandboxPlacementHost } from "@nylorun/core/contracts";
14
+ import type { TenantSandboxConfig } from "./tenant-config.js";
14
15
  import type { TenantPods } from "../tenant/context.js";
15
- /** The harness every session of this Runtime runs (blueprint D38). */
16
+ /** Our harness's id (blueprint D38): what a session runs unless its Environment names another. */
16
17
  export declare const HARNESS_ID = "nylorun";
17
18
  export declare const DEFAULT_PLACEMENT: readonly SandboxPlacementHost[];
18
19
  /** The hosts harness `harnessId` may run on. */
19
20
  export declare function placementOf(config: TenantSandboxConfig, harnessId?: string): readonly SandboxPlacementHost[];
20
21
  /** How to enable sandbox pods, for the messages that need them. */
21
- export declare const ENABLE_PODS = "run `nylorun sandbox enable --context <name>` to give this Tenant a cluster";
22
- /** Refuses a session whose sandbox needs a host the Tenant does not allow. */
23
- export declare function checkPlacement(config: TenantSandboxConfig, kind: "virtual" | "pod" | undefined): void;
22
+ export declare const ENABLE_PODS = "run `nylorun local sandbox enable --context <name>` to give this Tenant a cluster";
23
+ /** Refuses a session whose harness, on the host its Environment needs, the Tenant does not allow. */
24
+ export declare function checkPlacement(config: TenantSandboxConfig, kind: EnvironmentKind | undefined, harness?: string): void;
24
25
  /** Whether the sandboxes service is up, cached for a few seconds. */
25
26
  export declare function podsReady(pods: TenantPods): Promise<boolean>;
26
- /** Refuses a pod sandbox without sandbox pods, or with the sandboxes service down. */
27
+ /** Refuses a pod Environment without sandbox pods, or with the sandboxes service down. */
27
28
  export declare function requirePods(pods: TenantPods | undefined, what: string): Promise<TenantPods>;
@@ -1,6 +1,19 @@
1
+ /**
2
+ * Placement (F7.2, D38): where a session's harness runs, checked when the session opens and
3
+ * never downgraded afterwards. The Tenant maps harness ids (`nylorun`, or `*` for the rest) to
4
+ * the hosts they may run on (`environments.config` `placement`): `harness-container` (the
5
+ * Runtime's harness: in process, or the harness container) and `sandbox` (the engine in a pod).
6
+ * Default: both. `"*": { hosts: [] }` refuses every harness the map does not name (R4.1).
7
+ *
8
+ * A session on a pod Environment runs on `sandbox`; every other session (a virtual or `none`
9
+ * Environment, or none) on `harness-container` (D52). A host the Tenant does not allow is
10
+ * `409 placement_refused`. A pod Environment needs sandbox pods: without a cluster (`nylorun
11
+ * sandbox enable`), or with the sandboxes service not ready, it is `409 environment_unavailable`.
12
+ */
13
+ import { DEFAULT_HARNESS, } from "@nylorun/core/contracts";
1
14
  import { fail } from "../tenant/http.js";
2
- /** The harness every session of this Runtime runs (blueprint D38). */
3
- export const HARNESS_ID = "nylorun";
15
+ /** Our harness's id (blueprint D38): what a session runs unless its Environment names another. */
16
+ export const HARNESS_ID = DEFAULT_HARNESS;
4
17
  export const DEFAULT_PLACEMENT = ["harness-container", "sandbox"];
5
18
  /** The hosts harness `harnessId` may run on. */
6
19
  export function placementOf(config, harnessId = HARNESS_ID) {
@@ -8,15 +21,19 @@ export function placementOf(config, harnessId = HARNESS_ID) {
8
21
  return placement?.[harnessId]?.hosts ?? placement?.["*"]?.hosts ?? DEFAULT_PLACEMENT;
9
22
  }
10
23
  /** How to enable sandbox pods, for the messages that need them. */
11
- export const ENABLE_PODS = "run `nylorun sandbox enable --context <name>` to give this Tenant a cluster";
12
- /** Refuses a session whose sandbox needs a host the Tenant does not allow. */
13
- export function checkPlacement(config, kind) {
24
+ export const ENABLE_PODS = "run `nylorun local sandbox enable --context <name>` to give this Tenant a cluster";
25
+ /** Refuses a session whose harness, on the host its Environment needs, the Tenant does not allow. */
26
+ export function checkPlacement(config, kind, harness = HARNESS_ID) {
14
27
  const host = kind === "pod" ? "sandbox" : "harness-container";
15
- const allowed = placementOf(config);
16
- if (!allowed.includes(host))
17
- fail(409, kind === "pod"
18
- ? `This Tenant does not let the ${HARNESS_ID} harness run in a sandbox pod (placement allows ${allowed.join(", ")}).`
19
- : `This Tenant runs the ${HARNESS_ID} harness only in sandbox pods (placement allows ${allowed.join(", ")}): attach a pod sandbox with sandbox: { id }.`, { code: "placement_refused", details: { harness: HARNESS_ID, host, allowed: [...allowed] } });
28
+ const allowed = placementOf(config, harness);
29
+ if (allowed.includes(host))
30
+ return;
31
+ const where = allowed.length === 0 ? "it may run nowhere" : `placement allows ${allowed.join(", ")}`;
32
+ fail(409, allowed.length === 0
33
+ ? `This Tenant does not run the ${harness} harness (${where}).`
34
+ : kind === "pod"
35
+ ? `This Tenant does not let the ${harness} harness run in a sandbox pod (${where}).`
36
+ : `This Tenant runs the ${harness} harness only in sandbox pods (${where}): open the session on a pod Environment.`, { code: "placement_refused", details: { harness, host, allowed: [...allowed] } });
20
37
  }
21
38
  const READY_CACHE_MS = 5_000;
22
39
  const ready = new WeakMap();
@@ -29,15 +46,15 @@ export async function podsReady(pods) {
29
46
  ready.set(pods, { at: Date.now(), ready: answer });
30
47
  return answer;
31
48
  }
32
- /** Refuses a pod sandbox without sandbox pods, or with the sandboxes service down. */
49
+ /** Refuses a pod Environment without sandbox pods, or with the sandboxes service down. */
33
50
  export async function requirePods(pods, what) {
34
51
  if (!pods)
35
52
  return fail(409, `${what} needs sandbox pods, and this Runtime has no cluster: ${ENABLE_PODS}.`, {
36
- code: "sandbox_unavailable",
53
+ code: "environment_unavailable",
37
54
  });
38
55
  if (!(await podsReady(pods)))
39
- fail(409, `${what} needs sandbox pods, and the sandboxes service is not ready. Check \`nylorun sandbox status\`.`, {
40
- code: "sandbox_unavailable",
56
+ fail(409, `${what} needs sandbox pods, and the sandboxes service is not ready. Check \`nylorun local sandbox status\`.`, {
57
+ code: "environment_unavailable",
41
58
  details: { retryAfterSeconds: 5 },
42
59
  }, { "retry-after": "5" });
43
60
  return pods;
@@ -13,7 +13,12 @@ export interface PodSpec {
13
13
  readonly mode: "Running" | "Suspended";
14
14
  /** The workload image; the service defaults it to `python:3.13-slim`. */
15
15
  readonly image?: string;
16
- /** The Runtime image the engine is copied from. */
16
+ /**
17
+ * What the workload runs: `engine` (default), our engine copied from `harnessImage`, or
18
+ * `image`, the image's own entrypoint, for a custom harness built into it (R4.4).
19
+ */
20
+ readonly entrypoint?: "engine" | "image";
21
+ /** The Runtime image the engine is copied from; not with `entrypoint: "image"`. */
17
22
  readonly harnessImage?: string;
18
23
  /** Replaces the engine command (diagnostics). */
19
24
  readonly command?: readonly string[];
@@ -39,6 +44,8 @@ export interface PodStatus {
39
44
  readonly expired: boolean;
40
45
  readonly podUID?: string;
41
46
  readonly podPhase?: string;
47
+ /** The workload container's image as the node pulled it (`repo@sha256:…`), once it runs. */
48
+ readonly imageID?: string;
42
49
  readonly volume: "present" | "missing";
43
50
  readonly opId?: string;
44
51
  readonly shutdownTime?: string;
@@ -47,7 +54,7 @@ export interface PodStatus {
47
54
  /** With `?wait=`: whether the state was reached before the timeout. */
48
55
  readonly met?: boolean;
49
56
  }
50
- /** `GET /v1/info`: the cluster `nylorun sandbox enable` recorded. */
57
+ /** `GET /v1/info`: the cluster `nylorun local sandbox enable` recorded. */
51
58
  export interface ClusterInfo {
52
59
  readonly namespace: string;
53
60
  readonly context: string;