@nylorun/runtime 0.23.0-beta → 0.24.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 (252) hide show
  1. package/CHANGELOG.md +148 -0
  2. package/README.md +79 -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/endpoint.js +5 -1
  10. package/dist/api/a2a/routes.d.ts +2 -0
  11. package/dist/api/a2a/routes.js +2 -2
  12. package/dist/api/a2a/tasks.js +2 -1
  13. package/dist/api/ag-ui/endpoint.js +4 -2
  14. package/dist/api/ag-ui/history.js +4 -1
  15. package/dist/api/ag-ui/routes.d.ts +4 -1
  16. package/dist/api/ag-ui/routes.js +11 -10
  17. package/dist/api/ag-ui/translate.js +3 -2
  18. package/dist/api/components.d.ts +4953 -1204
  19. package/dist/api/components.js +25 -16
  20. package/dist/api/gate/keys.js +7 -1
  21. package/dist/api/gate/routes.d.ts +6 -0
  22. package/dist/api/gate/routes.js +39 -3
  23. package/dist/api/http/app.js +4 -4
  24. package/dist/api/http/define.d.ts +6 -0
  25. package/dist/api/http/define.js +13 -1
  26. package/dist/api/http/respond.js +1 -1
  27. package/dist/api/http/routes/artifacts.js +35 -8
  28. package/dist/api/http/routes/coverage.d.ts +9 -0
  29. package/dist/api/http/routes/coverage.js +34 -0
  30. package/dist/api/http/routes/environments.d.ts +11 -0
  31. package/dist/api/http/routes/environments.js +283 -0
  32. package/dist/api/http/routes/mcp.d.ts +1 -1
  33. package/dist/api/http/routes/mcp.js +2 -2
  34. package/dist/api/http/routes/me.js +1 -1
  35. package/dist/api/http/routes/reads.d.ts +45 -9
  36. package/dist/api/http/routes/reads.js +54 -5
  37. package/dist/api/http/routes/sessions.js +11 -6
  38. package/dist/api/http/routes/tenant.js +32 -10
  39. package/dist/api/openapi.js +31 -26
  40. package/dist/artifacts/disk.d.ts +59 -0
  41. package/dist/artifacts/disk.js +563 -0
  42. package/dist/artifacts/export.d.ts +1 -1
  43. package/dist/artifacts/export.js +2 -3
  44. package/dist/artifacts/folders.d.ts +6 -3
  45. package/dist/artifacts/folders.js +17 -12
  46. package/dist/artifacts/service.d.ts +2 -1
  47. package/dist/artifacts/service.js +6 -1
  48. package/dist/artifacts/workspace.d.ts +1 -1
  49. package/dist/artifacts/workspace.js +1 -1
  50. package/dist/core/flow-host.d.ts +2 -3
  51. package/dist/core/flow-host.js +3 -0
  52. package/dist/core/provider.d.ts +3 -2
  53. package/dist/core/provider.js +5 -37
  54. package/dist/core/sandbox-routes.d.ts +2 -21
  55. package/dist/core/sandbox-routes.js +10 -43
  56. package/dist/execution/types.d.ts +1 -1
  57. package/dist/gates/contract.d.ts +3 -24
  58. package/dist/gates/contract.js +1 -36
  59. package/dist/gates/egress-credentials.d.ts +9 -0
  60. package/dist/gates/egress-credentials.js +11 -4
  61. package/dist/gates/egress.d.ts +28 -1
  62. package/dist/gates/egress.js +115 -10
  63. package/dist/gates/fixture-model.d.ts +28 -0
  64. package/dist/gates/fixture-model.js +92 -0
  65. package/dist/gates/handler.js +4 -4
  66. package/dist/gates/http-tool.d.ts +6 -2
  67. package/dist/gates/http-tool.js +3 -5
  68. package/dist/gates/in-process.d.ts +5 -0
  69. package/dist/gates/in-process.js +12 -1
  70. package/dist/gates/journal.d.ts +30 -0
  71. package/dist/gates/journal.js +52 -0
  72. package/dist/gates/mcp-handler.d.ts +4 -0
  73. package/dist/gates/mcp-handler.js +5 -1
  74. package/dist/gates/meter.d.ts +10 -2
  75. package/dist/gates/meter.js +20 -5
  76. package/dist/gates/policy.d.ts +49 -0
  77. package/dist/gates/policy.js +134 -0
  78. package/dist/gates/tenant-vaults.d.ts +7 -7
  79. package/dist/gates/tenant-vaults.js +5 -5
  80. package/dist/gates/tool-calls.d.ts +10 -5
  81. package/dist/gates/tool-calls.js +28 -10
  82. package/dist/gates/tool-client.d.ts +8 -0
  83. package/dist/gates/tool-client.js +27 -11
  84. package/dist/gates/tool-contract.d.ts +10 -68
  85. package/dist/gates/tool-contract.js +1 -85
  86. package/dist/gates/tool-gate.d.ts +1 -1
  87. package/dist/harness/calls.d.ts +12 -5
  88. package/dist/harness/calls.js +21 -12
  89. package/dist/harness/executors.js +12 -9
  90. package/dist/harness/main.js +18 -5
  91. package/dist/harness/pod.d.ts +34 -0
  92. package/dist/harness/pod.js +41 -0
  93. package/dist/harness/service.d.ts +0 -1
  94. package/dist/harness/service.js +13 -5
  95. package/dist/harness/setup.d.ts +27 -0
  96. package/dist/harness/setup.js +150 -0
  97. package/dist/harness/ws-client.js +3 -2
  98. package/dist/harness/ws-port.d.ts +1 -5
  99. package/dist/harness/ws-port.js +1 -5
  100. package/dist/harness-api/in-process.js +3 -3
  101. package/dist/harness-api/record.d.ts +15 -1
  102. package/dist/harness-api/record.js +174 -44
  103. package/dist/harness-api/redact.d.ts +3 -0
  104. package/dist/harness-api/redact.js +45 -0
  105. package/dist/harness-api/server.d.ts +8 -0
  106. package/dist/harness-api/server.js +93 -13
  107. package/dist/harness-api/start.d.ts +10 -3
  108. package/dist/harness-api/start.js +40 -10
  109. package/dist/harness-api/tool-journal.d.ts +96 -0
  110. package/dist/harness-api/tool-journal.js +102 -0
  111. package/dist/harness-api/tool-status.d.ts +13 -0
  112. package/dist/harness-api/tool-status.js +26 -0
  113. package/dist/harness-api/workspace.d.ts +6 -3
  114. package/dist/harness-api/workspace.js +27 -11
  115. package/dist/harness-api/ws-server.d.ts +11 -3
  116. package/dist/harness-api/ws-server.js +94 -15
  117. package/dist/host/gates.d.ts +2 -1
  118. package/dist/host/gates.js +10 -2
  119. package/dist/host/main.js +54 -5
  120. package/dist/host/operate.js +67 -5
  121. package/dist/host/stack-config.d.ts +64 -0
  122. package/dist/host/stack-config.js +61 -1
  123. package/dist/keys/client.js +4 -1
  124. package/dist/keys/keys.d.ts +5 -5
  125. package/dist/keys/keys.js +5 -7
  126. package/dist/management-openapi.json +999 -2211
  127. package/dist/mcp/connect.d.ts +25 -20
  128. package/dist/mcp/connect.js +29 -30
  129. package/dist/mcp/pool.d.ts +2 -2
  130. package/dist/mcp/pool.js +1 -1
  131. package/dist/mcp/preview.d.ts +3 -3
  132. package/dist/mcp/preview.js +10 -20
  133. package/dist/mcp/snapshot.d.ts +5 -11
  134. package/dist/mcp/snapshot.js +6 -7
  135. package/dist/model/pi-model.js +8 -0
  136. package/dist/openapi.json +6955 -2526
  137. package/dist/reads/types.d.ts +16 -3
  138. package/dist/record/append.d.ts +3 -1
  139. package/dist/record/append.js +1 -0
  140. package/dist/record/envelope.d.ts +5 -1
  141. package/dist/record/envelope.js +7 -3
  142. package/dist/record/environment.d.ts +25 -0
  143. package/dist/record/environment.js +44 -0
  144. package/dist/record/index.d.ts +1 -1
  145. package/dist/record/index.js +1 -1
  146. package/dist/sandbox/disk-fs.d.ts +95 -0
  147. package/dist/sandbox/disk-fs.js +206 -0
  148. package/dist/sandbox/join.d.ts +17 -18
  149. package/dist/sandbox/join.js +42 -15
  150. package/dist/sandbox/manager.d.ts +16 -8
  151. package/dist/sandbox/manager.js +33 -17
  152. package/dist/sandbox/placement.d.ts +13 -12
  153. package/dist/sandbox/placement.js +29 -12
  154. package/dist/sandbox/pods/client.d.ts +6 -1
  155. package/dist/sandbox/pods/lifecycle.d.ts +28 -27
  156. package/dist/sandbox/pods/lifecycle.js +14 -26
  157. package/dist/sandbox/pods/reconcile.js +18 -12
  158. package/dist/sandbox/pods/spec.d.ts +15 -13
  159. package/dist/sandbox/pods/spec.js +16 -20
  160. package/dist/sandbox/records.d.ts +2 -2
  161. package/dist/sandbox/records.js +3 -3
  162. package/dist/sandbox/resolve.d.ts +35 -27
  163. package/dist/sandbox/resolve.js +66 -41
  164. package/dist/sandbox/session-sandbox.js +1 -1
  165. package/dist/sandbox/setup.d.ts +61 -0
  166. package/dist/sandbox/setup.js +123 -0
  167. package/dist/sandbox/share.d.ts +11 -21
  168. package/dist/sandbox/share.js +12 -41
  169. package/dist/sandbox/tenant-config.d.ts +12 -14
  170. package/dist/sandbox/tenant-config.js +12 -17
  171. package/dist/sandbox/types.d.ts +6 -0
  172. package/dist/store/postgres/drizzle/0020_environments.sql +63 -0
  173. package/dist/store/postgres/drizzle/0021_disk_versions.sql +7 -0
  174. package/dist/store/postgres/drizzle/0022_model_usage_timing.sql +5 -0
  175. package/dist/store/postgres/drizzle/0023_artifact_pages.sql +1 -0
  176. package/dist/store/postgres/drizzle/meta/_journal.json +28 -0
  177. package/dist/store/postgres/reads.js +117 -17
  178. package/dist/store/postgres/record-writer.d.ts +4 -4
  179. package/dist/store/postgres/record-writer.js +9 -9
  180. package/dist/store/postgres/record.d.ts +7 -0
  181. package/dist/store/postgres/record.js +23 -2
  182. package/dist/store/postgres/schema.d.ts +934 -761
  183. package/dist/store/postgres/schema.js +102 -73
  184. package/dist/store/postgres/store.js +119 -66
  185. package/dist/store/types.d.ts +109 -48
  186. package/dist/tenant/advance.d.ts +7 -6
  187. package/dist/tenant/advance.js +115 -42
  188. package/dist/tenant/artifact-tool.js +3 -4
  189. package/dist/tenant/commands.d.ts +15 -2
  190. package/dist/tenant/commands.js +93 -12
  191. package/dist/tenant/context.d.ts +48 -22
  192. package/dist/tenant/context.js +4 -15
  193. package/dist/tenant/effects.d.ts +1 -1
  194. package/dist/tenant/effects.js +37 -20
  195. package/dist/tenant/environments.d.ts +167 -0
  196. package/dist/tenant/environments.js +823 -0
  197. package/dist/tenant/harness-tokens.d.ts +21 -0
  198. package/dist/tenant/harness-tokens.js +61 -0
  199. package/dist/tenant/host-token.d.ts +1 -1
  200. package/dist/tenant/host-token.js +2 -4
  201. package/dist/tenant/identity-file.d.ts +4 -4
  202. package/dist/tenant/identity-file.js +17 -8
  203. package/dist/tenant/issuers.js +3 -3
  204. package/dist/tenant/principals.js +1 -1
  205. package/dist/tenant/reset.js +3 -3
  206. package/dist/tenant/runtime.d.ts +6 -1
  207. package/dist/tenant/runtime.js +7 -1
  208. package/dist/tenant/session.d.ts +3 -3
  209. package/dist/tenant/session.js +0 -14
  210. package/dist/tenant/sessions.d.ts +13 -13
  211. package/dist/tenant/sessions.js +61 -138
  212. package/dist/tenant/signing-keys.d.ts +5 -1
  213. package/dist/tenant/status.js +3 -3
  214. package/dist/tenant/sweep.js +12 -6
  215. package/dist/tenant/transcript.d.ts +210 -20
  216. package/dist/tenant/transcript.js +62 -11
  217. package/dist/tenant/turn-timing.d.ts +43 -0
  218. package/dist/tenant/turn-timing.js +39 -0
  219. package/dist/tenant/types.d.ts +2 -0
  220. package/dist/traces/ids.d.ts +42 -0
  221. package/dist/traces/ids.js +85 -0
  222. package/dist/traces/key.d.ts +6 -0
  223. package/dist/traces/key.js +37 -0
  224. package/dist/traces/map.d.ts +113 -0
  225. package/dist/traces/map.js +317 -0
  226. package/dist/traces/options.d.ts +7 -0
  227. package/dist/traces/options.js +1 -0
  228. package/dist/traces/otlp.d.ts +42 -0
  229. package/dist/traces/otlp.js +84 -0
  230. package/dist/traces/projector.d.ts +58 -0
  231. package/dist/traces/projector.js +200 -0
  232. package/dist/traces/read.d.ts +23 -0
  233. package/dist/traces/read.js +16 -0
  234. package/dist/vault/coverage.d.ts +26 -0
  235. package/dist/vault/coverage.js +64 -0
  236. package/dist/vault/error.d.ts +11 -1
  237. package/dist/vault/error.js +5 -1
  238. package/dist/vault/service.d.ts +47 -48
  239. package/dist/vault/service.js +321 -382
  240. package/dist/vault/sources.d.ts +5 -16
  241. package/dist/vault/sources.js +6 -8
  242. package/dist/version.d.ts +1 -1
  243. package/dist/version.js +2 -2
  244. package/package.json +13 -13
  245. package/dist/api/http/routes/sandboxes.d.ts +0 -9
  246. package/dist/api/http/routes/sandboxes.js +0 -153
  247. package/dist/api/http/routes/vaults.d.ts +0 -10
  248. package/dist/api/http/routes/vaults.js +0 -139
  249. package/dist/record/sandbox.d.ts +0 -25
  250. package/dist/record/sandbox.js +0 -43
  251. package/dist/tenant/sandboxes.d.ts +0 -123
  252. package/dist/tenant/sandboxes.js +0 -566
@@ -25,10 +25,10 @@
25
25
  * `connection.lost`.
26
26
  */
27
27
  import { randomUUID } from "node:crypto";
28
- import { HARNESS_API_VERSION, HARNESS_CLAIMS, HarnessApiError, } from "@nylorun/core/harness-api";
28
+ import { DEFAULT_HARNESS, HARNESS_API_VERSION, HARNESS_CLAIMS, MAX_HARNESS_STATE_BYTES, isHarnessId, HarnessApiError, } from "@nylorun/core/harness-api";
29
29
  import { definitionFilesOf } from "@nylorun/core/contracts";
30
30
  import { isOwnershipLost } from "../store/ownership.js";
31
- import { ownedSession } from "../tenant/context.js";
31
+ import { harnessOf, ownedSession } from "../tenant/context.js";
32
32
  import { mcpDiscovered, pinDeferral, sessionToolsOf, } from "../mcp/snapshot.js";
33
33
  import { contextWindowOf } from "../model/catalog.js";
34
34
  import { turnManifestOf } from "../tenant/session.js";
@@ -36,6 +36,7 @@ import { sandboxWorkspaceKey, workspacePrefix } from "../sandbox/records.js";
36
36
  import { abortKind } from "../tenant/worker.js";
37
37
  import { readDefinitionFile } from "../tenant/definition-files.js";
38
38
  import { recordIntent, recordOutcome } from "./record.js";
39
+ import { redactTokens } from "./redact.js";
39
40
  import { beat, renewEveryMs, tokenOf } from "./renew.js";
40
41
  /** No attached harness serves workspaces (or, with `pod`, that pod sandbox's). */
41
42
  export class NoWorkspaceHarness extends Error {
@@ -48,6 +49,8 @@ export class NoWorkspaceHarness extends Error {
48
49
  this.pod = pod;
49
50
  }
50
51
  }
52
+ /** What our harness said in `hello` before harness ids (R4.1): refused, with the new id. */
53
+ const LEGACY_NAME = "@nylorun/harness";
51
54
  const OUTPUTS = [
52
55
  "turn.completed",
53
56
  "turn.paused",
@@ -62,8 +65,11 @@ export function createHarnessApiServer(ctx, options = {}) {
62
65
  const queued = [];
63
66
  const runs = new Map();
64
67
  let closed = false;
65
- /** A harness may take a run: a pod sandbox's host its sandbox's runs, any other the rest. */
66
- const takes = (connection, run) => connection.host?.sandboxId === run.offer.pod;
68
+ /**
69
+ * A harness may take a run of its own harness id (R4.1): a pod sandbox's host its sandbox's
70
+ * runs, any other the rest.
71
+ */
72
+ const takes = (connection, run) => connection.harness === (run.offer.harness ?? DEFAULT_HARNESS) && connection.host?.sandboxId === run.offer.pod;
67
73
  /** Hands queued runs to waiting harnesses that may take them, in order. */
68
74
  const match = () => {
69
75
  for (let i = 0; i < queued.length;) {
@@ -89,16 +95,30 @@ export function createHarnessApiServer(ctx, options = {}) {
89
95
  throw new HarnessApiError("run_not_held", `Run ${runId} is not held by this harness`);
90
96
  return run;
91
97
  };
98
+ /** What a harness reports is its claim (R4.9): its harness id and, in a run, the run. */
99
+ const writerOf = (connection, runId) => ({
100
+ source: { kind: "harness", id: connection.harness ?? connection.peer.name },
101
+ evidence: "claimed",
102
+ ...(runId === undefined ? {} : { runId }),
103
+ });
92
104
  const scope = (run) => ({
93
105
  ctx,
94
106
  lease: run.offer.lease,
95
107
  signal: run.offer.controller.signal,
108
+ ...(run.connection?.tools ? { nativeTools: run.connection.tools } : {}),
109
+ ...(run.connection ? { writer: writerOf(run.connection, run.grant.runId) } : {}),
96
110
  });
97
111
  const serve = (connection) => async (method, params, signal) => {
98
112
  const p = params;
99
113
  if (method === "hello") {
100
- const { capabilities } = p;
114
+ const { api, name, capabilities } = p;
115
+ // R3's next version adds a window of supported versions; until then there is one.
116
+ if (api !== HARNESS_API_VERSION)
117
+ throw new HarnessApiError("invalid", `unsupported Harness API version ${api}: this Runtime speaks ${HARNESS_API_VERSION}`);
118
+ connection.harness = await harnessIdOf(connection, name);
101
119
  connection.workspace = capabilities.workspace !== undefined;
120
+ if (capabilities.tools?.length)
121
+ connection.tools = new Set(capabilities.tools.map((tool) => tool.name));
102
122
  return {
103
123
  api: HARNESS_API_VERSION,
104
124
  tenantId: ctx.config.tenantId,
@@ -109,6 +129,8 @@ export function createHarnessApiServer(ctx, options = {}) {
109
129
  }
110
130
  if (method === "event")
111
131
  return claim(connection, p);
132
+ if (method === "lease" && connection.harness === undefined)
133
+ throw new HarnessApiError("invalid", "Say hello first: a harness leases runs of the id it names");
112
134
  if (method === "lease")
113
135
  return new Promise((resolve, reject) => {
114
136
  const entry = { connection, resolve };
@@ -121,8 +143,12 @@ export function createHarnessApiServer(ctx, options = {}) {
121
143
  });
122
144
  match();
123
145
  });
124
- if (OUTPUTS.includes(method))
125
- return output(held(connection, String(p.runId), method), method, p);
146
+ if (OUTPUTS.includes(method)) {
147
+ const run = held(connection, String(p.runId), method);
148
+ if (connection.harness !== DEFAULT_HARNESS)
149
+ checkCustomOutput(run, method, p);
150
+ return output(run, method, p);
151
+ }
126
152
  const run = held(connection, String(p.runId));
127
153
  switch (method) {
128
154
  case "lease.renew":
@@ -150,6 +176,27 @@ export function createHarnessApiServer(ctx, options = {}) {
150
176
  throw new HarnessApiError("invalid", `Unknown request ${method}`);
151
177
  }
152
178
  };
179
+ /**
180
+ * The harness id a `hello` names (R4.1): a harness id, and the one the connection's credential
181
+ * is for. A pod's host must be its Environment's harness, so an image that is not the harness
182
+ * its recipe names never takes the Environment's runs.
183
+ */
184
+ const harnessIdOf = async (connection, name) => {
185
+ if (!isHarnessId(name))
186
+ throw new HarnessApiError("invalid", name === LEGACY_NAME
187
+ ? `A harness names its harness id in hello: ours is ${DEFAULT_HARNESS}. Update the harness image (it said ${LEGACY_NAME}).`
188
+ : `hello.name ${JSON.stringify(name)} is not a harness id: lowercase letters, digits and dashes, starting with a letter`);
189
+ const bound = connection.peer.harness;
190
+ if (bound !== undefined && bound !== name)
191
+ throw new HarnessApiError("invalid", `This credential is for the ${bound} harness, not ${name}`);
192
+ if (connection.host) {
193
+ const environment = await ctx.store.tx((t) => t.environment(connection.host.sandboxId));
194
+ const expected = environment?.applied.harness ?? DEFAULT_HARNESS;
195
+ if (expected !== name)
196
+ throw new HarnessApiError("invalid", `Environment ${connection.host.sandboxId} runs the ${expected} harness, and this pod says ${name}`);
197
+ }
198
+ return name;
199
+ };
153
200
  /**
154
201
  * A `sandbox.*` event the harness claims: for a run it holds (the run's session, or the
155
202
  * session owning its sandbox), or a session one of its workspace requests acts for. A
@@ -169,7 +216,7 @@ export function createHarnessApiServer(ctx, options = {}) {
169
216
  throw new HarnessApiError("run_not_held", `This harness may not claim events of ${event.sessionId}`);
170
217
  const record = event.record;
171
218
  if (record &&
172
- (event.type !== "sandbox.state" ||
219
+ (event.type !== "environment.state" ||
173
220
  record.sessionId !== event.sessionId ||
174
221
  !record.key.startsWith(workspacePrefix(ctx.config.tenantId)) ||
175
222
  (connection.host !== undefined &&
@@ -179,7 +226,7 @@ export function createHarnessApiServer(ctx, options = {}) {
179
226
  await ctx.store.tx(async (t) => {
180
227
  if (record)
181
228
  await t.put("sandboxes", record.key, record);
182
- await t.event(event.sessionId, event.turnId, event.type, event.payload);
229
+ await t.event(event.sessionId, event.turnId, event.type, redactTokens(event.payload), writerOf(connection, event.runId));
183
230
  });
184
231
  }
185
232
  catch (error) {
@@ -211,7 +258,7 @@ export function createHarnessApiServer(ctx, options = {}) {
211
258
  current.mcpSnapshot = pinDeferral(snapshot, current.manifest, contextWindow);
212
259
  current.mcpDiagnostics = diagnostics;
213
260
  await t.put("sessions", current.id, current);
214
- await t.event(current.id, run.grant.turnId, "mcp.discovered", mcpDiscovered(current.mcpSnapshot, diagnostics));
261
+ await t.event(current.id, run.grant.turnId, "mcp.discovered", mcpDiscovered(current.mcpSnapshot, diagnostics), run.connection ? writerOf(run.connection, run.grant.runId) : undefined);
215
262
  }
216
263
  else if (diagnostics.length > 0) {
217
264
  const prior = [...(current.mcpDiagnostics ?? [])];
@@ -241,6 +288,27 @@ export function createHarnessApiServer(ctx, options = {}) {
241
288
  throw new HarnessApiError("unavailable", `The Object store has no bytes for ${sha256}`);
242
289
  return { base64: Buffer.from(bytes).toString("base64") };
243
290
  };
291
+ /**
292
+ * A custom harness's output (R4.2): its state is its own, stored as sent and never read, so it
293
+ * must be JSON and at most `MAX_HARNESS_STATE_BYTES`; a larger one fails the turn, as an engine
294
+ * that threw would. It cannot pause for approvals in v2 (Q14): approvals read our engine's plan
295
+ * until R5 makes them core holds.
296
+ */
297
+ const checkCustomOutput = (run, method, params) => {
298
+ if (method === "turn.paused")
299
+ throw new HarnessApiError("invalid", "A custom harness cannot pause for approvals in Harness API 2");
300
+ if (params.state === undefined || run.outputs.has(method))
301
+ return;
302
+ const bytes = Buffer.byteLength(JSON.stringify(params.state) ?? "");
303
+ if (bytes <= MAX_HARNESS_STATE_BYTES)
304
+ return;
305
+ output(run, "turn.failed", {
306
+ runId: run.grant.runId,
307
+ status: "failed",
308
+ thrown: { code: "invalid", message: `The harness's state is ${bytes} bytes, over ${MAX_HARNESS_STATE_BYTES}` },
309
+ }).catch(() => undefined);
310
+ throw new HarnessApiError("invalid", `state over 256 KiB (${bytes} bytes): the turn failed`);
311
+ };
244
312
  const output = (run, method, params) => {
245
313
  const known = run.outputs.get(method);
246
314
  if (known)
@@ -262,7 +330,10 @@ export function createHarnessApiServer(ctx, options = {}) {
262
330
  status() {
263
331
  return {
264
332
  connected: connections.size,
265
- workspace: [...connections].some((connection) => connection.workspace && !connection.host && !connection.channel.closed),
333
+ workspace: [...connections].some((connection) => connection.workspace &&
334
+ !connection.host &&
335
+ connection.harness === DEFAULT_HARNESS &&
336
+ !connection.channel.closed),
266
337
  };
267
338
  },
268
339
  hosting(sandboxId) {
@@ -275,10 +346,19 @@ export function createHarnessApiServer(ctx, options = {}) {
275
346
  },
276
347
  async workspace(method, params, signal, target) {
277
348
  const pod = target?.pod;
278
- const connection = [...connections].find((item) => item.workspace && !item.channel.closed && item.host?.sandboxId === pod);
349
+ const session = params.session?.ownerId;
350
+ // A pod's workspace is its host's, whatever harness it is. Any other session's is served
351
+ // by its own harness, if that harness serves workspaces: our harness's workspaces are not
352
+ // a custom harness's files (R4.1). Sweeps and removes go to ours.
353
+ const harness = pod !== undefined || session === undefined
354
+ ? DEFAULT_HARNESS
355
+ : harnessOf((await ctx.store.tx((t) => t.get("sessions", session))) ?? {});
356
+ const connection = [...connections].find((item) => item.workspace &&
357
+ !item.channel.closed &&
358
+ item.host?.sandboxId === pod &&
359
+ (pod !== undefined || item.harness === harness));
279
360
  if (!connection)
280
361
  throw new NoWorkspaceHarness(pod);
281
- const session = params.session?.ownerId;
282
362
  if (session)
283
363
  connection.claims.set(session, (connection.claims.get(session) ?? 0) + 1);
284
364
  else
@@ -1,6 +1,7 @@
1
1
  import type { RecordedOutcome, TurnStart } from "@nylorun/core/harness-api";
2
2
  import type { SandboxPodState } from "../store/types.js";
3
3
  import { type Lease, type Session, type TenantContext } from "../tenant/context.js";
4
+ import { type TraceContext } from "../traces/ids.js";
4
5
  /** A segment ready to run: the session as it starts, and its transcript folded from the record. */
5
6
  export interface SegmentStart {
6
7
  readonly current: Session;
@@ -10,18 +11,24 @@ export interface SegmentStart {
10
11
  /** The record position of `transcript` (`foldTranscriptWithCursor`). */
11
12
  readonly cursor: number;
12
13
  readonly outcomes: RecordedOutcome[];
13
- /** The tree's sandbox workspace: its owning session, and the sandbox resource it is attached to. */
14
+ /** The session's sandbox workspace: the session, and the Environment whose workspace it is. */
14
15
  readonly sandbox: {
15
16
  readonly ownerId: string;
16
- readonly sandboxId?: string;
17
+ readonly environmentId?: string;
17
18
  /** Variables every sandbox command gets (R2c): secret names set to the sentinel, plain values. */
18
19
  readonly environment?: Readonly<Record<string, string>>;
19
20
  };
20
- /** The sandbox is a pod sandbox (F7.2): its engine runs the segment. Its lifecycle state. */
21
+ /**
22
+ * The Environment is a pod (F7.2): its engine runs the segment. Its lifecycle state, and
23
+ * whether its new volume is still being seeded from a disk version (R2d step 3).
24
+ */
21
25
  readonly pod?: {
22
26
  readonly id: string;
23
27
  readonly pod: SandboxPodState;
28
+ readonly seeding?: boolean;
24
29
  };
30
+ /** The turn's span context (R7.4): what `TurnStart.trace` hands the harness. */
31
+ readonly trace?: TraceContext;
25
32
  }
26
33
  /** The segment's starting transaction and fold. */
27
34
  export declare function startSegment(ctx: TenantContext, lease: Lease,
@@ -1,11 +1,14 @@
1
1
  import { isWorkflowManifest } from "../core/flow-host.js";
2
2
  import { sandboxWorkspaceOf } from "../sandbox/share.js";
3
3
  import { ownedTx } from "../store/ownership.js";
4
- import { sandboxLookup } from "../tenant/context.js";
4
+ import { harnessOf } from "../tenant/context.js";
5
+ import { HARNESS_ID } from "../sandbox/placement.js";
5
6
  import { checkParity, foldTranscriptWithCursor, leanState, transcriptOf, transcriptUpdates, withTranscript, } from "../tenant/history.js";
6
7
  import { usesFixtureModel } from "../tenant/model-setting.js";
7
- import { rebaseSessionState, sessionToolsOf, turnManifestOf } from "../tenant/session.js";
8
+ import { sessionToolsOf, turnManifestOf } from "../tenant/session.js";
8
9
  import { bulkOutcomes } from "./record.js";
10
+ import { traceKey } from "../traces/key.js";
11
+ import { traceparentOf, turnSpanOf, turnTraceIdOf, } from "../traces/ids.js";
9
12
  /** Segment rollover defaults (Model Calls §10), well inside the advance deadline. */
10
13
  const ROLLOVER_STEPS = 50;
11
14
  const ROLLOVER_MS = 20 * 60_000;
@@ -14,9 +17,12 @@ export async function startSegment(ctx, lease,
14
17
  /** For a harness: also the segment's outcomes and the sandbox's owner. */
15
18
  options = {}) {
16
19
  const id = lease.sessionId;
20
+ // Read before the transaction: the first read of the Tenant may create the key.
21
+ const key = options.harness ? await traceKey(ctx.store) : undefined;
17
22
  const started = await ownedTx(ctx.store, id, lease.epoch, async (t, current) => {
18
- if (!isWorkflowManifest(current.manifest) && current.checkpoint) {
19
- rebaseSessionState(current, current.checkpoint.manifestHash);
23
+ // A transcript left on our engine's state by an older Runtime is recorded once; another
24
+ // harness's state is its own (R4.2).
25
+ if (!isWorkflowManifest(current.manifest) && current.checkpoint && harnessOf(current) === HARNESS_ID) {
20
26
  await adoptStoredTranscript(t, current);
21
27
  const cp = current.checkpoint;
22
28
  current.checkpoint = { ...cp, state: current.state };
@@ -25,22 +31,33 @@ options = {}) {
25
31
  const fixtureModel = await usesFixtureModel(t);
26
32
  if (!options.harness)
27
33
  return { current, fixtureModel, outcomes: [], sandbox: { ownerId: id } };
28
- const workspace = sandboxWorkspaceOf(current, await sandboxLookup(t, current.id));
29
- const resource = workspace.sandboxId === undefined ? undefined : await t.sandboxResource(workspace.sandboxId);
30
- const { environment } = await ctx.vault.sessionEnvironment(t, current.vaultIds ?? []);
34
+ const workspace = sandboxWorkspaceOf(current);
35
+ const resource = workspace.environmentId === undefined ? undefined : await t.environment(workspace.environmentId);
36
+ const environment = await ctx.vault.environmentVariables(t, current.environmentId);
31
37
  const sandbox = Object.keys(environment).length > 0 ? { ...workspace, environment } : workspace;
38
+ const turnId = current.activeTurnId;
32
39
  return {
33
40
  current,
34
41
  fixtureModel,
35
42
  outcomes: current.checkpoint ? await bulkOutcomes(t, current, current.checkpoint) : [],
36
43
  sandbox,
37
- ...(resource?.pod ? { pod: { id: resource.id, pod: resource.pod } } : {}),
44
+ ...(key && turnId ? { trace: turnContextOf(key, ctx.store.tenantId, current, turnId) } : {}),
45
+ ...(resource?.pod
46
+ ? { pod: { id: resource.id, pod: resource.pod, ...(resource.diskSeed ? { seeding: true } : {}) } }
47
+ : {}),
38
48
  };
39
49
  });
40
50
  // One history (P0.3): the engine resumes from the transcript folded from the record.
41
51
  const { current } = started;
42
52
  const cp = current.checkpoint;
43
- if (isWorkflowManifest(current.manifest) || !cp?.state)
53
+ if (isWorkflowManifest(current.manifest))
54
+ return { ...started, transcript: [], cursor: -1 };
55
+ // Another harness's state says nothing about its transcript (R4.2): fold it every segment.
56
+ if (harnessOf(current) !== HARNESS_ID) {
57
+ const { transcript, cursor } = await foldSession(ctx, current);
58
+ return { ...started, transcript, cursor };
59
+ }
60
+ if (!cp?.state)
44
61
  return { ...started, transcript: [], cursor: -1 };
45
62
  const { transcript, cursor } = await foldSession(ctx, current);
46
63
  checkParity(id, transcript, transcriptOf(cp.state));
@@ -56,7 +73,7 @@ export function buildTurnStart(ctx, segment) {
56
73
  type: !flow && (input?.kind === "approve" || input?.kind === "respond") ? "approval.answer" : "turn.start",
57
74
  engine: flow ? "flow" : "agent",
58
75
  manifest: flow ? current.manifest : turnManifestOf(current),
59
- checkpoint: !flow && cp.state
76
+ checkpoint: !flow && cp.state && harnessOf(current) === HARNESS_ID
60
77
  ? { ...cp, state: withTranscript(cp.state, []) }
61
78
  : cp,
62
79
  // The turn's manifest: a variant may have disabled MCP tools or required their approval.
@@ -74,6 +91,19 @@ export function buildTurnStart(ctx, segment) {
74
91
  ...(current.mcpSnapshot ? { mcpSnapshot: current.mcpSnapshot } : {}),
75
92
  sandbox: segment.sandbox,
76
93
  },
94
+ ...(segment.trace ? { trace: segment.trace } : {}),
95
+ };
96
+ }
97
+ /**
98
+ * The context of turn `turnId`'s span (R7.4): its trace (the caller's when its message carried a
99
+ * `traceparent`) and its span, as the trace projector derives them, with the caller's
100
+ * `tracestate`.
101
+ */
102
+ function turnContextOf(key, tenantId, s, turnId) {
103
+ const traceId = turnTraceIdOf(key, { tenantId, sessionId: s.id, turnId }, s.turnTrace);
104
+ return {
105
+ traceparent: traceparentOf(traceId, turnSpanOf(key, turnId)),
106
+ ...(s.turnTrace?.tracestate ? { tracestate: s.turnTrace.tracestate } : {}),
77
107
  };
78
108
  }
79
109
  /** The engine's yield budget for a segment. */
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The effect journal's tool calls (R5): every tool call is a row of `nylorun.effects`, and how
3
+ * it ends is one `tool.completed` with its status, kind, server, attempts and duration (R5.1).
4
+ *
5
+ * Delivery (R5.3, R5.4). A tool call is sent at least once, and every send carries its effect
6
+ * id as the key. After a crash, a shutdown or a lost gate hop, a row still `invoking` is sent
7
+ * again: an MCP or HTTP tool call (it crosses the Tool Gate, which hands the tool the key) up to
8
+ * `MAX_TOOL_ATTEMPTS` sends, then it fails to the model as `tool.interrupted`. A sandbox or
9
+ * core-served call has no key to dedupe on, so a crash fails it to the model at once, and
10
+ * nothing runs it again (Q2). No tool call leaves its session `uncertain`.
11
+ *
12
+ * The row body keeps the journal's facts: `toolKind`, `server`, `attempts`, `startedAt` and
13
+ * `settledAt`. A row written before R5 has none of them.
14
+ *
15
+ * Spans (R7.1). `tool.completed` also carries the call's effect id, the Tool Gate's own start
16
+ * and end of its last send (`gate`, from its `tool_crossings` row: MCP and HTTP calls through
17
+ * the gateway, while the row is kept), and what the tool policy did when it did more than allow
18
+ * (`policy`). A flow's HTTP stage, which names no tool call, ends in `node.completed` instead.
19
+ */
20
+ import type { EventWriter } from "@nylorun/core/contracts";
21
+ import type { AgentManifest } from "@nylorun/core/define";
22
+ import type { HostEffect } from "@nylorun/harness/run";
23
+ import type { Session } from "../tenant/context.js";
24
+ import { type GateTimes, type ToolCallFacts, type ToolCallKind, type ToolCallStatus, type ToolPolicyFact } from "../tenant/transcript.js";
25
+ import type { EffectDoc, Tx } from "../store/types.js";
26
+ /** How many times a tool call is sent before it fails to the model as `tool.interrupted`. */
27
+ export declare const MAX_TOOL_ATTEMPTS = 3;
28
+ /** A tool row's journal facts (R5), kept in its body. */
29
+ export interface ToolRowFacts {
30
+ /** Why the Tool Gate held the call for approval, and since when (R5 step 2). */
31
+ hold?: {
32
+ reason: string;
33
+ at: string;
34
+ };
35
+ /** A person's approval of a held call (R5 step 2): the gate then sends it. */
36
+ approval?: {
37
+ approved: true;
38
+ at: string;
39
+ by?: string;
40
+ };
41
+ toolKind?: ToolCallKind;
42
+ server?: string;
43
+ attempts?: number;
44
+ startedAt?: string;
45
+ settledAt?: string;
46
+ /**
47
+ * The tool span's context (R7.4), `00-<trace>-<span>-01`: the Tool Gate reads it with the row
48
+ * and sends it as `traceparent`, and a harness hands it to a sandbox command as `TRACEPARENT`.
49
+ */
50
+ traceparent?: string;
51
+ }
52
+ export type ToolRow = EffectDoc & ToolRowFacts & {
53
+ requestHash?: string;
54
+ outcome?: {
55
+ value: unknown;
56
+ statePatch?: Record<string, unknown>;
57
+ };
58
+ error?: string;
59
+ };
60
+ /** Where a tool call runs, and its MCP server or HTTP tool capability. */
61
+ export declare function toolKindOf(s: Pick<Session, "manifest" | "mcpSnapshot">, agentManifest: AgentManifest | undefined, request: HostEffect): {
62
+ toolKind: ToolCallKind;
63
+ server?: string;
64
+ };
65
+ /** The facts a new tool row starts with: its first send, now. */
66
+ export declare function firstSend(kind: {
67
+ toolKind: ToolCallKind;
68
+ server?: string;
69
+ }, now?: Date): ToolRowFacts;
70
+ /** A failed outcome the Runtime gives a call a crash interrupted: it may have run. */
71
+ export declare function interrupted(message: string): {
72
+ kind: string;
73
+ code: string;
74
+ message: string;
75
+ retryable: boolean;
76
+ };
77
+ /** The facts `tool.completed` carries for `row`, settled with `status`, and the gate's times. */
78
+ export declare function factsOf(row: ToolRow, status: ToolCallStatus, gate?: GateTimes): ToolCallFacts;
79
+ /**
80
+ * What the Tenant's tool policy did with `row` (R5 step 2), when it did more than allow it: held
81
+ * it for a person (`ask`, with who approved it, when someone did), or refused it unsent (`deny`).
82
+ */
83
+ export declare function policyOf(row: ToolRow): ToolPolicyFact | undefined;
84
+ /**
85
+ * Settles a tool row with `value` (`completed`) or as `cancelled`, and writes its one
86
+ * `tool.completed` (an agent's tool call), or `node.completed` (a flow's HTTP stage or verifier,
87
+ * which names no call, R7.1). Writes `row`.
88
+ */
89
+ export declare function settleToolRow(t: Tx, s: Pick<Session, "id">, row: ToolRow, value: unknown, options?: {
90
+ cancelled?: boolean;
91
+ now?: Date;
92
+ /** Who reported the outcome (R4.9): the harness that ran the call; absent, core. */
93
+ writer?: EventWriter;
94
+ }): Promise<{
95
+ value: unknown;
96
+ }>;
@@ -0,0 +1,102 @@
1
+ import { isFlowToolEffect } from "../core/flow-host.js";
2
+ import { isHttpToolCall } from "../gates/http-tool.js";
3
+ import { mcpToolOf } from "../mcp/snapshot.js";
4
+ import { sandboxCapabilityOf } from "../sandbox/capability.js";
5
+ import { toolCompleted, } from "../tenant/transcript.js";
6
+ import { toolCallDuration, toolCallStatus } from "./tool-status.js";
7
+ /** How many times a tool call is sent before it fails to the model as `tool.interrupted`. */
8
+ export const MAX_TOOL_ATTEMPTS = 3;
9
+ /** Where a tool call runs, and its MCP server or HTTP tool capability. */
10
+ export function toolKindOf(s, agentManifest, request) {
11
+ const mcp = mcpToolOf(s.mcpSnapshot, request);
12
+ if (mcp)
13
+ return { toolKind: "mcp", server: mcp.serverName };
14
+ if (isHttpToolCall(s.manifest, request))
15
+ return { toolKind: "http", ...(request.capabilityId ? { server: request.capabilityId } : {}) };
16
+ if (sandboxCapabilityOf(agentManifest, request.capabilityId, request.toolName))
17
+ return { toolKind: "sandbox" };
18
+ return { toolKind: "platform" };
19
+ }
20
+ /** The facts a new tool row starts with: its first send, now. */
21
+ export function firstSend(kind, now = new Date()) {
22
+ return { ...kind, attempts: 1, startedAt: now.toISOString() };
23
+ }
24
+ /** A failed outcome the Runtime gives a call a crash interrupted: it may have run. */
25
+ export function interrupted(message) {
26
+ return { kind: "failed", code: "tool.interrupted", message, retryable: false };
27
+ }
28
+ /** The facts `tool.completed` carries for `row`, settled with `status`, and the gate's times. */
29
+ export function factsOf(row, status, gate) {
30
+ const durationMs = toolCallDuration(row);
31
+ const policy = policyOf(row);
32
+ return {
33
+ status,
34
+ ...(row.toolKind === undefined ? {} : { kind: row.toolKind }),
35
+ ...(row.server === undefined ? {} : { server: row.server }),
36
+ ...(row.attempts === undefined ? {} : { attempts: row.attempts }),
37
+ ...(durationMs === undefined ? {} : { durationMs }),
38
+ effectId: row.request.effectId,
39
+ ...(gate === undefined ? {} : { gate }),
40
+ ...(policy === undefined ? {} : { policy }),
41
+ };
42
+ }
43
+ /**
44
+ * What the Tenant's tool policy did with `row` (R5 step 2), when it did more than allow it: held
45
+ * it for a person (`ask`, with who approved it, when someone did), or refused it unsent (`deny`).
46
+ */
47
+ export function policyOf(row) {
48
+ if (row.hold)
49
+ return {
50
+ decision: "ask",
51
+ reason: row.hold.reason,
52
+ ...(row.approval?.by === undefined ? {} : { approvedBy: row.approval.by }),
53
+ };
54
+ const value = row.outcome?.value;
55
+ // `bypolicy` refuses a call before its first send.
56
+ if (row.attempts === 0 && value?.code === "tool.denied")
57
+ return { decision: "deny", reason: String(value.message ?? "") };
58
+ return undefined;
59
+ }
60
+ /**
61
+ * The Tool Gate's own start and end of `row`'s last send (R7.1), from its `tool_crossings` row:
62
+ * an MCP or HTTP call the gateway ran and settled. Undefined for any other call, one never sent,
63
+ * and one whose crossing is gone (pruned a day after it settled) or not settled.
64
+ */
65
+ async function gateOf(t, row) {
66
+ if ((row.toolKind !== "mcp" && row.toolKind !== "http") || !row.attempts)
67
+ return undefined;
68
+ const crossing = await t.toolCrossing(row.request.effectId);
69
+ return crossing?.settledAt ? { startedAt: crossing.startedAt, endedAt: crossing.settledAt } : undefined;
70
+ }
71
+ /**
72
+ * Settles a tool row with `value` (`completed`) or as `cancelled`, and writes its one
73
+ * `tool.completed` (an agent's tool call), or `node.completed` (a flow's HTTP stage or verifier,
74
+ * which names no call, R7.1). Writes `row`.
75
+ */
76
+ export async function settleToolRow(t, s, row, value, options = {}) {
77
+ const request = row.request;
78
+ row.status = options.cancelled ? "cancelled" : "completed";
79
+ row.outcome = { value };
80
+ row.settledAt = (options.now ?? new Date()).toISOString();
81
+ await t.put("effects", request.effectId, row);
82
+ const status = toolCallStatus(row);
83
+ const gate = await gateOf(t, row);
84
+ if (isFlowToolEffect(request)) {
85
+ const durationMs = toolCallDuration(row);
86
+ await t.event(s.id, request.turnId, "node.completed", {
87
+ path: request.path,
88
+ kind: row.toolKind ?? "http",
89
+ key: request.key,
90
+ ...(request.iterations !== undefined ? { iterations: request.iterations } : {}),
91
+ effectId: request.effectId,
92
+ status,
93
+ ...(durationMs === undefined ? {} : { durationMs }),
94
+ ...(gate === undefined ? {} : { gate }),
95
+ }, options.writer);
96
+ return row.outcome;
97
+ }
98
+ const transcript = toolCompleted(request, value, factsOf(row, status, gate));
99
+ if (transcript)
100
+ await t.event(s.id, request.turnId, "tool.completed", transcript, options.writer);
101
+ return row.outcome;
102
+ }
@@ -0,0 +1,13 @@
1
+ /** What a tool row's body says of how it ended. */
2
+ export interface ToolRowState {
3
+ readonly status: string;
4
+ readonly outcome?: {
5
+ readonly value: unknown;
6
+ };
7
+ readonly startedAt?: string;
8
+ readonly settledAt?: string;
9
+ }
10
+ /** How a tool row ended, as `tool.completed` and `/calls/tool` say it. */
11
+ export declare function toolCallStatus(row: ToolRowState): "completed" | "failed" | "cancelled" | "invoking" | "held" | "uncertain";
12
+ /** From the first send to the outcome, or undefined when the call never ran or has not ended. */
13
+ export declare function toolCallDuration(row: ToolRowState): number | undefined;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * How a tool row of the effect journal reads (R5.1, R5.2), from its body alone: shared by the
3
+ * journal (`tool-journal.ts`) and the session reads (`/calls/tool`).
4
+ */
5
+ import { durationBetween } from "../tenant/transcript.js";
6
+ /** The codes of a call the Runtime ended rather than the tool: its status is `failed`. */
7
+ const RUNTIME_ENDED = new Set(["tool.disabled", "tool.unavailable", "tool.interrupted", "tool.denied"]);
8
+ /** How a tool row ended, as `tool.completed` and `/calls/tool` say it. */
9
+ export function toolCallStatus(row) {
10
+ if (row.status === "cancelled")
11
+ return "cancelled";
12
+ if (row.status === "held")
13
+ return "held";
14
+ if (row.status === "uncertain")
15
+ return "uncertain";
16
+ if (row.status !== "completed")
17
+ return "invoking";
18
+ const value = row.outcome?.value;
19
+ return value?.kind === "failed" && typeof value.code === "string" && RUNTIME_ENDED.has(value.code)
20
+ ? "failed"
21
+ : "completed";
22
+ }
23
+ /** From the first send to the outcome, or undefined when the call never ran or has not ended. */
24
+ export function toolCallDuration(row) {
25
+ return durationBetween(row.startedAt, row.settledAt);
26
+ }
@@ -1,5 +1,6 @@
1
1
  import type { CapabilityManifest, SandboxToolName } from "@nylorun/core/define";
2
2
  import type { SandboxManager, SandboxSessionRef } from "../sandbox/manager.js";
3
+ import { type DiskOp } from "../sandbox/disk-fs.js";
3
4
  import type { SandboxSelectionReport } from "../sandbox/select.js";
4
5
  import type { SandboxToolOutcome } from "../sandbox/tools.js";
5
6
  import type { SessionStore } from "../store/types.js";
@@ -17,6 +18,8 @@ export interface WorkspacePort {
17
18
  listFiles(session: SandboxSessionRef, capability: CapabilityManifest, dir: string, maxEntries: number, signal: AbortSignal): ReturnType<SandboxManager["listFiles"]>;
18
19
  /** Reads a file of the workspace of `session` as bytes, at most `maxBytes` (`save_artifact`). */
19
20
  readBytes(session: SandboxSessionRef, capability: CapabilityManifest, path: string, maxBytes: number, signal: AbortSignal): ReturnType<SandboxManager["readBytes"]>;
21
+ /** One operation on the disk of the workspace of `session` (R2d step 3). */
22
+ disk(session: SandboxSessionRef, capability: CapabilityManifest, op: DiskOp, signal: AbortSignal): ReturnType<SandboxManager["disk"]>;
20
23
  report(): Promise<WorkspaceReport>;
21
24
  /** One Tenant sweep pass: stop idle workspaces, remove those whose owner is gone. */
22
25
  sweep(options: {
@@ -24,8 +27,8 @@ export interface WorkspacePort {
24
27
  sessionExists: Exists;
25
28
  sandboxExists: Exists;
26
29
  }): Promise<void>;
27
- /** Deletes sandbox resource `sandboxId`'s workspace (the resource was deleted). */
28
- removeSandbox(sandboxId: string): Promise<void>;
30
+ /** Deletes sandbox resource `environmentId`'s workspace (the resource was deleted). */
31
+ removeSandbox(environmentId: string): Promise<void>;
29
32
  /** Deletes every workspace with its files (a sandboxes reset). */
30
33
  removeAll(): Promise<void>;
31
34
  close(): Promise<void>;
@@ -48,6 +51,6 @@ export declare function remoteWorkspace(options: RemoteWorkspaceOptions): Worksp
48
51
  * everything else goes to `base`. `podOf` says whether a sandbox resource is a pod sandbox.
49
52
  */
50
53
  export declare function withPodWorkspaces(base: WorkspacePort, options: Omit<RemoteWorkspaceOptions, "pod"> & {
51
- readonly isPod: (sandboxId: string) => Promise<boolean>;
54
+ readonly isPod: (environmentId: string) => Promise<boolean>;
52
55
  }): WorkspacePort;
53
56
  export {};