@nylorun/runtime 0.22.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 (273) hide show
  1. package/CHANGELOG.md +161 -0
  2. package/README.md +95 -30
  3. package/dist/adapters/execution/restate.d.ts +15 -3
  4. package/dist/adapters/execution/restate.js +78 -84
  5. package/dist/adapters/replication/pgoutput.d.ts +2 -0
  6. package/dist/adapters/replication/pgoutput.js +6 -3
  7. package/dist/adapters/sandbox/local.d.ts +9 -1
  8. package/dist/adapters/sandbox/local.js +16 -4
  9. package/dist/adapters/sandbox/virtual.js +4 -1
  10. package/dist/adapters/streams/s2.js +1 -13
  11. package/dist/api/a2a/endpoint.js +5 -1
  12. package/dist/api/a2a/routes.d.ts +2 -0
  13. package/dist/api/a2a/routes.js +2 -2
  14. package/dist/api/a2a/tasks.js +2 -1
  15. package/dist/api/ag-ui/endpoint.js +4 -2
  16. package/dist/api/ag-ui/history.js +4 -1
  17. package/dist/api/ag-ui/routes.d.ts +4 -1
  18. package/dist/api/ag-ui/routes.js +11 -10
  19. package/dist/api/ag-ui/translate.js +3 -2
  20. package/dist/api/components.d.ts +4953 -1204
  21. package/dist/api/components.js +25 -16
  22. package/dist/api/gate/keys.js +7 -1
  23. package/dist/api/gate/routes.d.ts +6 -0
  24. package/dist/api/gate/routes.js +39 -3
  25. package/dist/api/http/app.js +4 -4
  26. package/dist/api/http/define.d.ts +6 -0
  27. package/dist/api/http/define.js +13 -1
  28. package/dist/api/http/respond.js +1 -1
  29. package/dist/api/http/routes/artifacts.js +35 -8
  30. package/dist/api/http/routes/coverage.d.ts +9 -0
  31. package/dist/api/http/routes/coverage.js +34 -0
  32. package/dist/api/http/routes/environments.d.ts +11 -0
  33. package/dist/api/http/routes/environments.js +283 -0
  34. package/dist/api/http/routes/mcp.d.ts +1 -1
  35. package/dist/api/http/routes/mcp.js +2 -2
  36. package/dist/api/http/routes/me.js +1 -1
  37. package/dist/api/http/routes/reads.d.ts +45 -9
  38. package/dist/api/http/routes/reads.js +54 -5
  39. package/dist/api/http/routes/sessions.js +11 -6
  40. package/dist/api/http/routes/tenant.js +32 -10
  41. package/dist/api/openapi.js +31 -26
  42. package/dist/artifacts/disk.d.ts +59 -0
  43. package/dist/artifacts/disk.js +563 -0
  44. package/dist/artifacts/export.d.ts +1 -1
  45. package/dist/artifacts/export.js +2 -3
  46. package/dist/artifacts/folders.d.ts +6 -3
  47. package/dist/artifacts/folders.js +17 -12
  48. package/dist/artifacts/service.d.ts +2 -1
  49. package/dist/artifacts/service.js +6 -1
  50. package/dist/artifacts/workspace.d.ts +1 -1
  51. package/dist/artifacts/workspace.js +1 -1
  52. package/dist/core/flow-host.d.ts +3 -22
  53. package/dist/core/flow-host.js +11 -37
  54. package/dist/core/provider.d.ts +3 -2
  55. package/dist/core/provider.js +5 -37
  56. package/dist/core/sandbox-routes.d.ts +2 -21
  57. package/dist/core/sandbox-routes.js +10 -43
  58. package/dist/execution/types.d.ts +11 -9
  59. package/dist/execution/types.js +5 -7
  60. package/dist/gates/contract.d.ts +3 -24
  61. package/dist/gates/contract.js +1 -36
  62. package/dist/gates/egress-credentials.d.ts +9 -0
  63. package/dist/gates/egress-credentials.js +11 -4
  64. package/dist/gates/egress.d.ts +28 -1
  65. package/dist/gates/egress.js +115 -10
  66. package/dist/gates/fixture-model.d.ts +28 -0
  67. package/dist/gates/fixture-model.js +92 -0
  68. package/dist/gates/handler.js +4 -4
  69. package/dist/gates/http-tool.d.ts +6 -2
  70. package/dist/gates/http-tool.js +3 -5
  71. package/dist/gates/in-process.d.ts +5 -0
  72. package/dist/gates/in-process.js +12 -1
  73. package/dist/gates/journal.d.ts +30 -0
  74. package/dist/gates/journal.js +52 -0
  75. package/dist/gates/mcp-handler.d.ts +4 -0
  76. package/dist/gates/mcp-handler.js +5 -1
  77. package/dist/gates/meter.d.ts +10 -2
  78. package/dist/gates/meter.js +20 -5
  79. package/dist/gates/policy.d.ts +49 -0
  80. package/dist/gates/policy.js +134 -0
  81. package/dist/gates/tenant-vaults.d.ts +7 -7
  82. package/dist/gates/tenant-vaults.js +5 -5
  83. package/dist/gates/tool-calls.d.ts +10 -5
  84. package/dist/gates/tool-calls.js +28 -10
  85. package/dist/gates/tool-client.d.ts +8 -0
  86. package/dist/gates/tool-client.js +27 -11
  87. package/dist/gates/tool-contract.d.ts +10 -68
  88. package/dist/gates/tool-contract.js +1 -85
  89. package/dist/gates/tool-gate.d.ts +1 -1
  90. package/dist/harness/calls.d.ts +12 -5
  91. package/dist/harness/calls.js +21 -12
  92. package/dist/harness/executors.js +12 -9
  93. package/dist/harness/main.js +18 -5
  94. package/dist/harness/pod.d.ts +34 -0
  95. package/dist/harness/pod.js +41 -0
  96. package/dist/harness/service.d.ts +0 -1
  97. package/dist/harness/service.js +13 -5
  98. package/dist/harness/setup.d.ts +27 -0
  99. package/dist/harness/setup.js +150 -0
  100. package/dist/harness/ws-client.js +3 -2
  101. package/dist/harness/ws-port.d.ts +1 -5
  102. package/dist/harness/ws-port.js +1 -5
  103. package/dist/harness-api/in-process.js +3 -3
  104. package/dist/harness-api/record.d.ts +15 -1
  105. package/dist/harness-api/record.js +174 -44
  106. package/dist/harness-api/redact.d.ts +3 -0
  107. package/dist/harness-api/redact.js +45 -0
  108. package/dist/harness-api/server.d.ts +8 -0
  109. package/dist/harness-api/server.js +93 -13
  110. package/dist/harness-api/start.d.ts +10 -3
  111. package/dist/harness-api/start.js +40 -10
  112. package/dist/harness-api/tool-journal.d.ts +96 -0
  113. package/dist/harness-api/tool-journal.js +102 -0
  114. package/dist/harness-api/tool-status.d.ts +13 -0
  115. package/dist/harness-api/tool-status.js +26 -0
  116. package/dist/harness-api/workspace.d.ts +6 -3
  117. package/dist/harness-api/workspace.js +27 -11
  118. package/dist/harness-api/ws-server.d.ts +11 -3
  119. package/dist/harness-api/ws-server.js +94 -15
  120. package/dist/host/execution.d.ts +8 -6
  121. package/dist/host/gates.d.ts +2 -1
  122. package/dist/host/gates.js +10 -2
  123. package/dist/host/main.js +64 -21
  124. package/dist/host/operate.js +67 -5
  125. package/dist/host/stack-config.d.ts +70 -0
  126. package/dist/host/stack-config.js +67 -1
  127. package/dist/infra/execution.d.ts +18 -1
  128. package/dist/infra/execution.js +28 -4
  129. package/dist/infra/index.d.ts +1 -1
  130. package/dist/keys/client.js +4 -1
  131. package/dist/keys/keys.d.ts +5 -5
  132. package/dist/keys/keys.js +5 -7
  133. package/dist/management-openapi.json +999 -2211
  134. package/dist/mcp/connect.d.ts +25 -20
  135. package/dist/mcp/connect.js +29 -30
  136. package/dist/mcp/pool.d.ts +2 -2
  137. package/dist/mcp/pool.js +1 -1
  138. package/dist/mcp/preview.d.ts +3 -3
  139. package/dist/mcp/preview.js +10 -20
  140. package/dist/mcp/snapshot.d.ts +5 -11
  141. package/dist/mcp/snapshot.js +6 -7
  142. package/dist/model/pi-model.js +8 -0
  143. package/dist/openapi.json +6955 -2526
  144. package/dist/reads/types.d.ts +16 -3
  145. package/dist/record/append.d.ts +3 -1
  146. package/dist/record/append.js +1 -0
  147. package/dist/record/envelope.d.ts +5 -1
  148. package/dist/record/envelope.js +7 -3
  149. package/dist/record/environment.d.ts +25 -0
  150. package/dist/record/environment.js +44 -0
  151. package/dist/record/index.d.ts +1 -1
  152. package/dist/record/index.js +1 -1
  153. package/dist/retry.d.ts +21 -0
  154. package/dist/retry.js +34 -0
  155. package/dist/sandbox/disk-fs.d.ts +95 -0
  156. package/dist/sandbox/disk-fs.js +206 -0
  157. package/dist/sandbox/join.d.ts +17 -18
  158. package/dist/sandbox/join.js +46 -20
  159. package/dist/sandbox/manager.d.ts +16 -8
  160. package/dist/sandbox/manager.js +33 -17
  161. package/dist/sandbox/placement.d.ts +13 -12
  162. package/dist/sandbox/placement.js +29 -12
  163. package/dist/sandbox/pods/client.d.ts +6 -1
  164. package/dist/sandbox/pods/lifecycle.d.ts +28 -27
  165. package/dist/sandbox/pods/lifecycle.js +14 -26
  166. package/dist/sandbox/pods/reconcile.js +21 -15
  167. package/dist/sandbox/pods/spec.d.ts +15 -13
  168. package/dist/sandbox/pods/spec.js +16 -20
  169. package/dist/sandbox/records.d.ts +2 -2
  170. package/dist/sandbox/records.js +3 -3
  171. package/dist/sandbox/resolve.d.ts +35 -27
  172. package/dist/sandbox/resolve.js +66 -41
  173. package/dist/sandbox/session-sandbox.js +1 -1
  174. package/dist/sandbox/setup.d.ts +61 -0
  175. package/dist/sandbox/setup.js +123 -0
  176. package/dist/sandbox/share.d.ts +11 -21
  177. package/dist/sandbox/share.js +12 -41
  178. package/dist/sandbox/tenant-config.d.ts +12 -14
  179. package/dist/sandbox/tenant-config.js +12 -17
  180. package/dist/sandbox/types.d.ts +6 -0
  181. package/dist/store/postgres/control.js +8 -1
  182. package/dist/store/postgres/drizzle/0018_wake_outbox_retries.sql +3 -0
  183. package/dist/store/postgres/drizzle/0019_sandbox_signal_outbox.sql +18 -0
  184. package/dist/store/postgres/drizzle/0020_environments.sql +63 -0
  185. package/dist/store/postgres/drizzle/0021_disk_versions.sql +7 -0
  186. package/dist/store/postgres/drizzle/0022_model_usage_timing.sql +5 -0
  187. package/dist/store/postgres/drizzle/0023_artifact_pages.sql +1 -0
  188. package/dist/store/postgres/drizzle/meta/_journal.json +42 -0
  189. package/dist/store/postgres/reads.js +117 -17
  190. package/dist/store/postgres/record-writer.d.ts +4 -4
  191. package/dist/store/postgres/record-writer.js +9 -9
  192. package/dist/store/postgres/record.d.ts +7 -0
  193. package/dist/store/postgres/record.js +23 -2
  194. package/dist/store/postgres/schema.d.ts +1213 -779
  195. package/dist/store/postgres/schema.js +136 -76
  196. package/dist/store/postgres/store.js +283 -117
  197. package/dist/store/types.d.ts +206 -88
  198. package/dist/streams/relay/core.js +1 -13
  199. package/dist/tenant/advance.d.ts +7 -6
  200. package/dist/tenant/advance.js +121 -49
  201. package/dist/tenant/artifact-tool.js +3 -4
  202. package/dist/tenant/commands.d.ts +15 -2
  203. package/dist/tenant/commands.js +94 -14
  204. package/dist/tenant/context.d.ts +67 -33
  205. package/dist/tenant/context.js +4 -15
  206. package/dist/tenant/control.d.ts +8 -1
  207. package/dist/tenant/control.js +2 -0
  208. package/dist/tenant/effects.d.ts +1 -1
  209. package/dist/tenant/effects.js +37 -20
  210. package/dist/tenant/environments.d.ts +167 -0
  211. package/dist/tenant/environments.js +823 -0
  212. package/dist/tenant/harness-tokens.d.ts +21 -0
  213. package/dist/tenant/harness-tokens.js +61 -0
  214. package/dist/tenant/host-token.d.ts +1 -1
  215. package/dist/tenant/host-token.js +2 -4
  216. package/dist/tenant/identity-file.d.ts +4 -4
  217. package/dist/tenant/identity-file.js +17 -8
  218. package/dist/tenant/issuers.js +3 -3
  219. package/dist/tenant/principals.js +1 -1
  220. package/dist/tenant/reset.js +3 -3
  221. package/dist/tenant/runtime.d.ts +6 -1
  222. package/dist/tenant/runtime.js +30 -4
  223. package/dist/tenant/scheduler.d.ts +7 -0
  224. package/dist/tenant/scheduler.js +38 -7
  225. package/dist/tenant/session-streams.d.ts +0 -1
  226. package/dist/tenant/session-streams.js +1 -13
  227. package/dist/tenant/session.d.ts +3 -3
  228. package/dist/tenant/session.js +0 -14
  229. package/dist/tenant/sessions.d.ts +13 -13
  230. package/dist/tenant/sessions.js +61 -138
  231. package/dist/tenant/signing-keys.d.ts +5 -1
  232. package/dist/tenant/status.js +3 -3
  233. package/dist/tenant/streams.js +14 -29
  234. package/dist/tenant/sweep.d.ts +39 -12
  235. package/dist/tenant/sweep.js +168 -63
  236. package/dist/tenant/transcript.d.ts +210 -20
  237. package/dist/tenant/transcript.js +62 -11
  238. package/dist/tenant/turn-timing.d.ts +43 -0
  239. package/dist/tenant/turn-timing.js +39 -0
  240. package/dist/tenant/types.d.ts +2 -0
  241. package/dist/traces/ids.d.ts +42 -0
  242. package/dist/traces/ids.js +85 -0
  243. package/dist/traces/key.d.ts +6 -0
  244. package/dist/traces/key.js +37 -0
  245. package/dist/traces/map.d.ts +113 -0
  246. package/dist/traces/map.js +317 -0
  247. package/dist/traces/options.d.ts +7 -0
  248. package/dist/traces/options.js +1 -0
  249. package/dist/traces/otlp.d.ts +42 -0
  250. package/dist/traces/otlp.js +84 -0
  251. package/dist/traces/projector.d.ts +58 -0
  252. package/dist/traces/projector.js +200 -0
  253. package/dist/traces/read.d.ts +23 -0
  254. package/dist/traces/read.js +16 -0
  255. package/dist/vault/coverage.d.ts +26 -0
  256. package/dist/vault/coverage.js +64 -0
  257. package/dist/vault/error.d.ts +11 -1
  258. package/dist/vault/error.js +5 -1
  259. package/dist/vault/service.d.ts +47 -48
  260. package/dist/vault/service.js +321 -382
  261. package/dist/vault/sources.d.ts +5 -16
  262. package/dist/vault/sources.js +6 -8
  263. package/dist/version.d.ts +1 -1
  264. package/dist/version.js +2 -2
  265. package/package.json +14 -13
  266. package/dist/api/http/routes/sandboxes.d.ts +0 -9
  267. package/dist/api/http/routes/sandboxes.js +0 -153
  268. package/dist/api/http/routes/vaults.d.ts +0 -10
  269. package/dist/api/http/routes/vaults.js +0 -139
  270. package/dist/record/sandbox.d.ts +0 -25
  271. package/dist/record/sandbox.js +0 -43
  272. package/dist/tenant/sandboxes.d.ts +0 -122
  273. package/dist/tenant/sandboxes.js +0 -562
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The Tool Gate's wire format (blueprint §12, F4.1): the routes the loop calls on the gates
3
- * service for remote MCP servers and HTTP tools. Internal, like the model call route
4
- * (`contract.ts`): same optional Tenant header, not in the published OpenAPI documents.
3
+ * service for remote MCP servers and HTTP tools. Like the model call route (`contract.ts`): same
4
+ * optional Tenant header, bodies published in `@nylorun/core/harness-api` (R4.5).
5
5
  *
6
6
  * Credentials (F5). A session's MCP requests carry its run token, and the gate takes the
7
7
  * session from it: `server.sessionId` is then absent (or the token's). Core's credential
@@ -25,9 +25,9 @@
25
25
  * answer, not a gate error. A tool call's failure is coded (`error.failure`, R2b C7), and neither
26
26
  * a result nor a failure carries a credential value the gate sent (C8). An answer past 8 MiB is
27
27
  * `mcp.too-large` (C11), as an HTTP tool's is `http.too-large`; core shapes what fits into a
28
- * result the model can take (`tenant/tool-results.ts`). A keyed call whose
29
- * earlier attempt was lost with the gateway answers `{ok: false, error: {uncertain: true}}` and
30
- * is never run again.
28
+ * result the model can take (`tenant/tool-results.ts`). A keyed call whose earlier attempt was
29
+ * lost with the gateway runs again under the same key (R5.3); one another gateway process holds
30
+ * answers `{ok: false, error: {uncertain: true}}`, and the caller sends it again.
31
31
  *
32
32
  * HTTP tools (R2 M3). `POST /nylorun/v1/http-calls` `{tool, effectId, turnId, input}`: one call
33
33
  * of an HTTP tool. The loop names the tool (`{sessionId?, agentId?, capabilityId, toolName}`), or a
@@ -36,67 +36,10 @@
36
36
  * (`gates/http-tool.ts`). A run token's turn is the call's; `turnId` counts only under core's
37
37
  * credential. Keyed like a tool call, and cancelled by the same route. It answers
38
38
  * `200 {ok: true, result}` with the tool outcome (a failed request is a failed outcome the
39
- * model sees), or `{ok: false, error: {uncertain: true}}` for a call lost with the gateway.
39
+ * model sees), or `{ok: false, error: {uncertain: true}}` for a call another gateway process holds.
40
40
  */
41
- import { z } from "zod";
42
41
  import type { McpCallFailure } from "../mcp/connect.js";
43
- export declare const MCP_CONNECT_PATH = "/nylorun/v1/mcp/connect";
44
- export declare const MCP_LIST_PATH = "/nylorun/v1/mcp/list";
45
- export declare const MCP_CLOSE_PATH = "/nylorun/v1/mcp/close";
46
- export declare const TOOL_CALLS_PATH = "/nylorun/v1/tool-calls";
47
- export declare const HTTP_CALLS_PATH = "/nylorun/v1/http-calls";
48
- /** Largest tool call body the gate reads. */
49
- export declare const MAX_TOOL_BODY_BYTES: number;
50
- export declare const McpServerRefSchema: z.ZodObject<{
51
- sessionId: z.ZodOptional<z.ZodString>;
52
- agentId: z.ZodOptional<z.ZodString>;
53
- capabilityId: z.ZodString;
54
- serverName: z.ZodString;
55
- }, z.core.$strip>;
56
- export declare const McpServerBodySchema: z.ZodObject<{
57
- server: z.ZodObject<{
58
- sessionId: z.ZodOptional<z.ZodString>;
59
- agentId: z.ZodOptional<z.ZodString>;
60
- capabilityId: z.ZodString;
61
- serverName: z.ZodString;
62
- }, z.core.$strip>;
63
- }, z.core.$strip>;
64
- export declare const McpListBodySchema: z.ZodObject<{
65
- server: z.ZodObject<{
66
- sessionId: z.ZodOptional<z.ZodString>;
67
- agentId: z.ZodOptional<z.ZodString>;
68
- capabilityId: z.ZodString;
69
- serverName: z.ZodString;
70
- }, z.core.$strip>;
71
- cursor: z.ZodOptional<z.ZodString>;
72
- }, z.core.$strip>;
73
- export declare const ToolCallBodySchema: z.ZodObject<{
74
- server: z.ZodObject<{
75
- sessionId: z.ZodOptional<z.ZodString>;
76
- agentId: z.ZodOptional<z.ZodString>;
77
- capabilityId: z.ZodString;
78
- serverName: z.ZodString;
79
- }, z.core.$strip>;
80
- effectId: z.ZodString;
81
- name: z.ZodString;
82
- arguments: z.ZodRecord<z.ZodString, z.ZodUnknown>;
83
- }, z.core.$strip>;
84
- export type ToolCallBody = z.infer<typeof ToolCallBodySchema>;
85
- export declare const HttpCallBodySchema: z.ZodObject<{
86
- tool: z.ZodUnion<readonly [z.ZodObject<{
87
- sessionId: z.ZodOptional<z.ZodString>;
88
- agentId: z.ZodOptional<z.ZodString>;
89
- capabilityId: z.ZodString;
90
- toolName: z.ZodString;
91
- }, z.core.$strip>, z.ZodObject<{
92
- sessionId: z.ZodOptional<z.ZodString>;
93
- stage: z.ZodString;
94
- }, z.core.$strip>]>;
95
- effectId: z.ZodString;
96
- turnId: z.ZodString;
97
- input: z.ZodUnknown;
98
- }, z.core.$strip>;
99
- export type HttpCallBody = z.infer<typeof HttpCallBodySchema>;
42
+ export { HTTP_CALLS_PATH, HttpCallBodySchema, MAX_TOOL_BODY_BYTES, MCP_CLOSE_PATH, MCP_CONNECT_PATH, MCP_LIST_PATH, McpListBodySchema, McpServerBodySchema, McpServerRefSchema, TOOL_CALLS_PATH, ToolCallBodySchema, type HttpCallBody, type ToolCallBody, } from "@nylorun/core/harness-api";
100
43
  /** Why an MCP request failed, as the loop's diagnostics read it (`mcp/connect.ts`). */
101
44
  export interface McpGateError {
102
45
  readonly message: string;
@@ -104,13 +47,12 @@ export interface McpGateError {
104
47
  readonly code?: number;
105
48
  /** The vault credentials a refused authorization names. */
106
49
  readonly credentialIds?: readonly string[];
107
- /** The call may have run: its earlier attempt was lost with the gateway. Never re-run. */
50
+ /** Not answered here: another gateway process holds the keyed call. Send it again (R5.3). */
108
51
  readonly uncertain?: boolean;
109
52
  /**
110
53
  * How the request failed, coded (R2b C1, C7): the loop's `McpCallFailed`, which a tool call
111
- * turns into a failed outcome the model sees (`mcp.lost` only for a read-only or idempotent
112
- * tool). `credential_rejected` for a `401`, with the server and the scope of the vault whose
113
- * credential was sent. Its message has no credential value in it (C8).
54
+ * turns into a failed outcome the model sees (`mcp.lost` included). `credential_rejected` for a
55
+ * `401`, with the server. Its message has no credential value in it (C8).
114
56
  */
115
57
  readonly failure?: McpCallFailure;
116
58
  }
@@ -1,85 +1 @@
1
- /**
2
- * The Tool Gate's wire format (blueprint §12, F4.1): the routes the loop calls on the gates
3
- * service for remote MCP servers and HTTP tools. Internal, like the model call route
4
- * (`contract.ts`): same optional Tenant header, not in the published OpenAPI documents.
5
- *
6
- * Credentials (F5). A session's MCP requests carry its run token, and the gate takes the
7
- * session from it: `server.sessionId` is then absent (or the token's). Core's credential
8
- * (`NYLORUN_GATES_TOKEN`) is accepted too, for requests made outside a run, and then
9
- * `server.sessionId` names the session.
10
- *
11
- * Remote MCP. The gate holds the connection and the vault credential; the loop names the
12
- * server, never a URL or a credential, and the gate finds it in the session's pinned manifest.
13
- * - `POST /nylorun/v1/mcp/connect` `{server}`: opens the server's connection (vault
14
- * authorization included), or keeps the open one. Its result is `{instructions}` when the
15
- * server gave instructions at `initialize` (R2b C10, for the note on its deferred tools), else
16
- * `null`.
17
- * - `POST /nylorun/v1/mcp/list` `{server, cursor?}`: one page of `tools/list`.
18
- * - `POST /nylorun/v1/tool-calls` `{server, effectId, name, arguments}`: one
19
- * `tools/call`. With an `Idempotency-Key` (the effect id) the call outlives its client and runs
20
- * once (G3); a re-send joins it or gets its outcome.
21
- * - `POST /nylorun/v1/tool-calls/{key}/cancel`: aborts a keyed call; `204` either way, `403
22
- * gate_forbidden` for another session's call under a run token.
23
- * - `POST /nylorun/v1/mcp/close` `{server}`: closes the server's connection; `204`.
24
- * Each answers `200 {ok: true, result}` or `200 {ok: false, error}`: an MCP failure is an
25
- * answer, not a gate error. A tool call's failure is coded (`error.failure`, R2b C7), and neither
26
- * a result nor a failure carries a credential value the gate sent (C8). An answer past 8 MiB is
27
- * `mcp.too-large` (C11), as an HTTP tool's is `http.too-large`; core shapes what fits into a
28
- * result the model can take (`tenant/tool-results.ts`). A keyed call whose
29
- * earlier attempt was lost with the gateway answers `{ok: false, error: {uncertain: true}}` and
30
- * is never run again.
31
- *
32
- * HTTP tools (R2 M3). `POST /nylorun/v1/http-calls` `{tool, effectId, turnId, input}`: one call
33
- * of an HTTP tool. The loop names the tool (`{sessionId?, agentId?, capabilityId, toolName}`), or a
34
- * flow's HTTP stage or HTTP verifier by its stage key (`{sessionId?, stage}`); the gate finds its
35
- * URL, method and credential in the session's pinned manifest
36
- * (`gates/http-tool.ts`). A run token's turn is the call's; `turnId` counts only under core's
37
- * credential. Keyed like a tool call, and cancelled by the same route. It answers
38
- * `200 {ok: true, result}` with the tool outcome (a failed request is a failed outcome the
39
- * model sees), or `{ok: false, error: {uncertain: true}}` for a call lost with the gateway.
40
- */
41
- import { z } from "zod";
42
- export const MCP_CONNECT_PATH = "/nylorun/v1/mcp/connect";
43
- export const MCP_LIST_PATH = "/nylorun/v1/mcp/list";
44
- export const MCP_CLOSE_PATH = "/nylorun/v1/mcp/close";
45
- export const TOOL_CALLS_PATH = "/nylorun/v1/tool-calls";
46
- export const HTTP_CALLS_PATH = "/nylorun/v1/http-calls";
47
- /** Largest tool call body the gate reads. */
48
- export const MAX_TOOL_BODY_BYTES = 8 * 1024 * 1024;
49
- export const McpServerRefSchema = z.object({
50
- /** Required with core's credential; a run token names the session itself. */
51
- sessionId: z.string().min(1).optional(),
52
- agentId: z.string().min(1).optional(),
53
- capabilityId: z.string().min(1),
54
- serverName: z.string().min(1),
55
- });
56
- export const McpServerBodySchema = z.object({ server: McpServerRefSchema });
57
- export const McpListBodySchema = z.object({
58
- server: McpServerRefSchema,
59
- cursor: z.string().optional(),
60
- });
61
- export const ToolCallBodySchema = z.object({
62
- server: McpServerRefSchema,
63
- effectId: z.string().min(1),
64
- name: z.string().min(1),
65
- arguments: z.record(z.string(), z.unknown()),
66
- });
67
- export const HttpCallBodySchema = z.object({
68
- tool: z.union([
69
- z.object({
70
- /** Required with core's credential; a run token names the session itself. */
71
- sessionId: z.string().min(1).optional(),
72
- agentId: z.string().min(1).optional(),
73
- capabilityId: z.string().min(1),
74
- toolName: z.string().min(1),
75
- }),
76
- z.object({
77
- sessionId: z.string().min(1).optional(),
78
- /** A flow's HTTP stage or HTTP verifier. */
79
- stage: z.string().min(1),
80
- }),
81
- ]),
82
- effectId: z.string().min(1),
83
- turnId: z.string().min(1),
84
- input: z.unknown(),
85
- });
1
+ export { HTTP_CALLS_PATH, HttpCallBodySchema, MAX_TOOL_BODY_BYTES, MCP_CLOSE_PATH, MCP_CONNECT_PATH, MCP_LIST_PATH, McpListBodySchema, McpServerBodySchema, McpServerRefSchema, TOOL_CALLS_PATH, ToolCallBodySchema, } from "@nylorun/core/harness-api";
@@ -28,7 +28,7 @@ export interface ToolGate {
28
28
  /**
29
29
  * True when a keyed MCP or HTTP tool call outlives the caller's process (the gates service,
30
30
  * G3): after a takeover or a shutdown, re-sending it joins the call or returns its outcome,
31
- * and a call lost with the gateway answers `uncertain` instead of running again.
31
+ * and a call lost with the gateway runs again under the same key (R5.3).
32
32
  */
33
33
  readonly recovers?: boolean;
34
34
  /** Stops a keyed tool call that outlives its caller (a user cancel). Never rejects. */
@@ -26,7 +26,7 @@ export interface ToolRouting {
26
26
  readonly mcpSnapshot?: McpSnapshot;
27
27
  /** The session that owns the tree's sandbox, and the sandbox resource it is attached to. */
28
28
  readonly sandboxOwnerId: string;
29
- readonly sandboxId?: string;
29
+ readonly environmentId?: string;
30
30
  /** Variables every sandbox command gets (R2c): secret names set to the sentinel, plain values. */
31
31
  readonly environment?: Readonly<Record<string, string>>;
32
32
  readonly activeTurnId: string | null;
@@ -36,9 +36,12 @@ export interface ToolRouting {
36
36
  /**
37
37
  * Call the model for one effect. A provider failure comes back as a failure outcome
38
38
  * (Model Calls §6), whichever provider serves the call; only an abort throws, and the
39
- * advance decides what the abort means.
39
+ * advance decides what the abort means. `atGate` sends the call to the Model Gate even when the
40
+ * Tenant's model is not from the vault: the Tenant's fixture model is answered there (R4.3).
40
41
  */
41
- export declare function invokeModel(route: ModelRoute, request: HostEffect, signal: AbortSignal, model?: ModelProvider): Promise<unknown>;
42
+ export declare function invokeModel(route: ModelRoute, request: HostEffect, signal: AbortSignal, options?: {
43
+ readonly atGate?: boolean;
44
+ }): Promise<unknown>;
42
45
  /**
43
46
  * A signal that follows `signal` only for the abort kinds in `kinds`. An MCP call stops on a
44
47
  * cancel; at the Tool Gate it also stops waiting on a shutdown, since the gate keeps the call
@@ -52,11 +55,15 @@ export declare function isSaveArtifactCall(manifest: AgentManifest | undefined,
52
55
  export declare function isReadArtifactCall(manifest: AgentManifest | undefined, request: HostEffect): boolean;
53
56
  /** True when `request` calls a tool of a declared MCP server (every one is remote). */
54
57
  export declare function isRemoteMcpCall(routing: Pick<ToolRouting, "rootManifest" | "mcpSnapshot">, request: HostEffect): boolean;
55
- export declare function callMcpTool(mcp: McpPool, routing: ToolRouting, request: HostEffect, signal: AbortSignal): Promise<unknown>;
58
+ export declare function callMcpTool(mcp: McpPool, routing: ToolRouting, request: HostEffect, signal: AbortSignal,
59
+ /** The call's span context (R7.4), sent as `traceparent` where this process holds the connection. */
60
+ traceparent?: string): Promise<unknown>;
56
61
  /**
57
62
  * Calls an HTTP tool through the Tool Gate (R2 M3), keyed by the effect id: an agent's (the
58
63
  * effect's agent used as a tool, or the root), or a flow's HTTP stage or HTTP verifier. Its
59
64
  * outcome is the effect's value; a throw leaves the effect `uncertain`.
60
65
  */
61
- export declare function callHttpTool(gate: Pick<ToolGate, "callHttp">, request: HostEffect, signal: AbortSignal): Promise<unknown>;
66
+ export declare function callHttpTool(gate: Pick<ToolGate, "callHttp">, request: HostEffect, signal: AbortSignal,
67
+ /** The call's span context (R7.4); the gates service reads its own from the journal. */
68
+ traceparent?: string): Promise<unknown>;
62
69
  export declare function callSandboxTool(sandbox: SandboxManager, routing: ToolRouting, request: HostEffect, signal: AbortSignal): Promise<unknown>;
@@ -13,13 +13,13 @@ import { sandboxCapabilityOf } from "../sandbox/capability.js";
13
13
  /**
14
14
  * Call the model for one effect. A provider failure comes back as a failure outcome
15
15
  * (Model Calls §6), whichever provider serves the call; only an abort throws, and the
16
- * advance decides what the abort means.
16
+ * advance decides what the abort means. `atGate` sends the call to the Model Gate even when the
17
+ * Tenant's model is not from the vault: the Tenant's fixture model is answered there (R4.3).
17
18
  */
18
- export async function invokeModel(route, request, signal, model) {
19
+ export async function invokeModel(route, request, signal, options = {}) {
20
+ const atGate = route.useVaultModel || options.atGate === true;
19
21
  try {
20
- if (model)
21
- return await model(request, signal);
22
- if (!route.useVaultModel)
22
+ if (!atGate)
23
23
  return await route.modelProvider(request, signal);
24
24
  return await route.modelGate.call({
25
25
  tenantId: route.tenantId,
@@ -35,7 +35,7 @@ export async function invokeModel(route, request, signal, model) {
35
35
  if (signal.aborted) {
36
36
  // A call that outlives this process stops only when told to: on a user cancel. After a
37
37
  // shutdown or a lost lease the next owner re-sends it and picks up its outcome (P1.2).
38
- if (!model && route.useVaultModel && runAbortKind(signal) === "cancel" && route.modelGate.cancel)
38
+ if (atGate && runAbortKind(signal) === "cancel" && route.modelGate.cancel)
39
39
  await route.modelGate.cancel({
40
40
  tenantId: route.tenantId,
41
41
  sessionId: request.sessionId,
@@ -89,7 +89,9 @@ export function isRemoteMcpCall(routing, request) {
89
89
  return false;
90
90
  return findServer(routing.rootManifest, tool.agentId, tool.capabilityId, tool.serverName) !== undefined;
91
91
  }
92
- export async function callMcpTool(mcp, routing, request, signal) {
92
+ export async function callMcpTool(mcp, routing, request, signal,
93
+ /** The call's span context (R7.4), sent as `traceparent` where this process holds the connection. */
94
+ traceparent) {
93
95
  const tool = mcpToolOf(routing.mcpSnapshot, request);
94
96
  if (!tool)
95
97
  throw new Error(`MCP tool '${request.toolName ?? ""}' is not in the session snapshot`);
@@ -102,8 +104,7 @@ export async function callMcpTool(mcp, routing, request, signal) {
102
104
  args: request.input,
103
105
  manifest: routing.rootManifest,
104
106
  effectId: request.effectId,
105
- // A read-only or idempotent tool's lost answer is the model's to retry (R2b C7, Q15).
106
- retrySafe: tool.annotations?.readOnlyHint === true || tool.annotations?.idempotentHint === true,
107
+ ...(traceparent ? { traceparent } : {}),
107
108
  signal,
108
109
  });
109
110
  }
@@ -112,10 +113,18 @@ export async function callMcpTool(mcp, routing, request, signal) {
112
113
  * effect's agent used as a tool, or the root), or a flow's HTTP stage or HTTP verifier. Its
113
114
  * outcome is the effect's value; a throw leaves the effect `uncertain`.
114
115
  */
115
- export async function callHttpTool(gate, request, signal) {
116
+ export async function callHttpTool(gate, request, signal,
117
+ /** The call's span context (R7.4); the gates service reads its own from the journal. */
118
+ traceparent) {
116
119
  if (!gate.callHttp)
117
120
  throw new Error("This Runtime has no Tool Gate for HTTP tools");
118
- return gate.callHttp({ tool: httpToolRefOf(request), effectId: request.effectId, turnId: request.turnId, input: request.input }, signal);
121
+ return gate.callHttp({
122
+ tool: httpToolRefOf(request),
123
+ effectId: request.effectId,
124
+ turnId: request.turnId,
125
+ input: request.input,
126
+ ...(traceparent ? { traceparent } : {}),
127
+ }, signal);
119
128
  }
120
129
  export async function callSandboxTool(sandbox, routing, request, signal) {
121
130
  const capability = sandboxCapabilityOf(manifestFor(routing.rootManifest, request.agent), request.capabilityId, request.toolName);
@@ -126,7 +135,7 @@ export async function callSandboxTool(sandbox, routing, request, signal) {
126
135
  id: routing.sandboxOwnerId,
127
136
  activeTurnId: routing.activeTurnId,
128
137
  manifest: routing.rootManifest,
129
- ...(routing.sandboxId === undefined ? {} : { sandboxId: routing.sandboxId }),
138
+ ...(routing.environmentId === undefined ? {} : { environmentId: routing.environmentId }),
130
139
  ...(routing.environment === undefined ? {} : { environment: routing.environment }),
131
140
  ...(routing.claim === undefined ? {} : { claim: routing.claim }),
132
141
  }, capability, request.toolName, request.input, signal);
@@ -8,13 +8,11 @@
8
8
  * (`--service harness`, `main.ts`) its own, whose sandbox events it claims from core.
9
9
  */
10
10
  import { hashManifest } from "@nylorun/core/define";
11
- import { toolFixtureModel } from "../core/provider.js";
11
+ import { GateCallLost } from "../gates/tool-client.js";
12
12
  import { serversOf } from "../mcp/pool.js";
13
13
  import { mcpToolOf } from "../mcp/snapshot.js";
14
14
  import { isHttpToolCall } from "../gates/http-tool.js";
15
15
  import { abortOn, callHttpTool, callMcpTool, callSandboxTool, invokeModel, isRemoteMcpCall, isSaveArtifactCall, } from "./calls.js";
16
- /** The Tenant's model of the fixture-model setting. Stateless. */
17
- const fixture = toolFixtureModel();
18
16
  export function harnessExecutors(options) {
19
17
  const recoversTools = options.toolGate.recovers === true;
20
18
  const atGate = (effect, run) => {
@@ -23,16 +21,19 @@ export function harnessExecutors(options) {
23
21
  };
24
22
  const recovered = (effect, run) => recoversTools && atGate(effect, run);
25
23
  return {
26
- model: (effect, signal, run) => invokeModel(options.model, effect, signal, run.start.options.fixtureModel ? fixture : undefined),
27
- tool(effect, signal, run) {
24
+ // With the Tenant's fixture model on, the Model Gate answers it (R4.3), metered like any call.
25
+ model: (effect, signal, run) => invokeModel(options.model, effect, signal, { atGate: run.start.options.fixtureModel }),
26
+ tool(effect, signal, run, call) {
28
27
  const routing = routingOf(run);
28
+ const traceparent = call?.traceparent;
29
29
  // A call at the Tool Gate outlives a shutdown there; a local one finishes and is recorded.
30
30
  const kinds = recovered(effect, run) ? ["cancel", "shutdown"] : ["cancel"];
31
31
  if (isHttpToolCall(routing.rootManifest, effect))
32
- return callHttpTool(options.toolGate, effect, abortOn(signal, kinds));
32
+ return callHttpTool(options.toolGate, effect, abortOn(signal, kinds), traceparent);
33
+ // A sandbox command joins its call's span (R7.4): a CLI that reads TRACEPARENT traces under it.
33
34
  if (!mcpToolOf(routing.mcpSnapshot, effect))
34
- return callSandboxTool(options.sandbox(), routing, effect, signal);
35
- return callMcpTool(options.mcp, routing, effect, abortOn(signal, kinds));
35
+ return callSandboxTool(options.sandbox(), traceparent ? { ...routing, environment: { ...routing.environment, TRACEPARENT: traceparent } } : routing, effect, signal);
36
+ return callMcpTool(options.mcp, routing, effect, abortOn(signal, kinds), traceparent);
36
37
  },
37
38
  recovers: {
38
39
  get model() {
@@ -40,6 +41,8 @@ export function harnessExecutors(options) {
40
41
  },
41
42
  tool: recovered,
42
43
  },
44
+ // A gate hop that lost the answer: the call is sent again with its key (R5.3).
45
+ resends: (effect, error, run) => error instanceof GateCallLost && atGate(effect, run),
43
46
  async cancelAtGate(effect) {
44
47
  await options.toolGate.cancel?.({
45
48
  tenantId: options.model.tenantId,
@@ -94,7 +97,7 @@ export function routingOf(run) {
94
97
  rootManifest: routing.rootManifest,
95
98
  ...(routing.mcpSnapshot ? { mcpSnapshot: routing.mcpSnapshot } : {}),
96
99
  sandboxOwnerId: routing.sandbox?.ownerId ?? run.grant.sessionId,
97
- ...(routing.sandbox?.sandboxId === undefined ? {} : { sandboxId: routing.sandbox.sandboxId }),
100
+ ...(routing.sandbox?.environmentId === undefined ? {} : { environmentId: routing.sandbox.environmentId }),
98
101
  ...(routing.sandbox?.environment === undefined ? {} : { environment: routing.sandbox.environment }),
99
102
  activeTurnId: run.grant.turnId,
100
103
  claim: run.runId,
@@ -10,8 +10,9 @@
10
10
  *
11
11
  * In a pod sandbox (`NYLORUN_SANDBOX_KIND=pod`, F7.2) it is that sandbox's engine: it first waits
12
12
  * until the pod's NetworkPolicy is in force (`awaitNetworkPolicy`), then joins with the
13
- * pod's join token instead of a harness token (`pod.ts`), and runs its sandbox's tools in the
14
- * pod itself (the `local` backend), with egress through egress-gate. SIGTERM (tini forwards
13
+ * pod's join token instead of a harness token (`pod.ts`), runs its disk's setup on a new volume
14
+ * (`setup.ts`), and runs its sandbox's tools in the pod itself (the `local` backend), with egress
15
+ * through egress-gate. SIGTERM (tini forwards
15
16
  * it; the pod's grace is 10 s by default) gives its runs back and exits.
16
17
  */
17
18
  import { mkdirSync } from "node:fs";
@@ -21,9 +22,10 @@ import { httpModelGate } from "../gates/http-client.js";
21
22
  import { httpToolGate } from "../gates/tool-client.js";
22
23
  import { createHostLogger } from "../host/logger.js";
23
24
  import { RUNTIME_VERSION } from "../version.js";
24
- import { localBackend } from "../adapters/sandbox/local.js";
25
+ import { localBackend, localExec } from "../adapters/sandbox/local.js";
25
26
  import { awaitNetworkPolicy } from "../sandbox/pods/network-gate.js";
26
27
  import { podHost } from "./pod.js";
28
+ import { prepareDisk } from "./setup.js";
27
29
  import { harnessRunTokens } from "./run-tokens.js";
28
30
  import { startHarnessService } from "./service.js";
29
31
  /**
@@ -48,6 +50,16 @@ export async function runHarness(stack, baseline) {
48
50
  volumeFile: join(config.root, "volume-id"),
49
51
  caBundleFile: join(config.root, "egress-ca-bundle.pem"),
50
52
  });
53
+ // Join, then make the disk ready (R2d step 2) before taking any turn.
54
+ await pod.token();
55
+ const host = pod;
56
+ await prepareDisk({
57
+ host,
58
+ exec: localExec({ env: baseline, proxyEnv: () => host.proxyEnv() }),
59
+ volumeFile: join(config.root, "volume-id"),
60
+ markerFile: join(config.root, "setup.json"),
61
+ logger,
62
+ });
51
63
  }
52
64
  const token = pod ? () => pod.token() : config.token;
53
65
  if (token === undefined)
@@ -56,13 +68,14 @@ export async function runHarness(stack, baseline) {
56
68
  const service = startHarnessService({
57
69
  url: config.url,
58
70
  token,
59
- ...(pod ? { sandboxBackends: [localBackend({ env: baseline, proxyEnv: () => pod.proxyEnv() })] } : {}),
71
+ ...(pod
72
+ ? { sandboxBackends: [localBackend({ env: baseline, proxyEnv: () => pod.proxyEnv(), harnessRoot: config.root })] }
73
+ : {}),
60
74
  paths,
61
75
  modelGate: httpModelGate({ url: config.gatesUrl, runTokens }),
62
76
  useVaultModel: true,
63
77
  toolGate: httpToolGate({ url: config.gatesUrl, runTokens }),
64
78
  logger,
65
- name: pod ? `sandbox ${config.pod.sandboxId}` : "harness",
66
79
  version: RUNTIME_VERSION,
67
80
  onGrant: (grant) => runTokens.grant(grant),
68
81
  });
@@ -1,6 +1,27 @@
1
1
  import type { PodHostConfig } from "../host/stack-config.js";
2
2
  /** How often the host and egress tokens are renewed. */
3
3
  export declare const RENEW_MS: number;
4
+ /** What to run and copy on a new disk (`sandbox/setup.ts` in core), from the join answer. */
5
+ export interface DiskSetup {
6
+ readonly recipeVersion: number;
7
+ readonly commands: readonly {
8
+ readonly command: string;
9
+ readonly cwd: string;
10
+ readonly timeoutMs: number;
11
+ }[];
12
+ readonly files: readonly {
13
+ readonly index: number;
14
+ readonly path: string;
15
+ }[];
16
+ readonly environment: Readonly<Record<string, string>>;
17
+ }
18
+ /** How a setup ended, as the engine reports it. */
19
+ export interface DiskSetupReport {
20
+ readonly status: "ok" | "failed";
21
+ readonly index?: number;
22
+ readonly exitCode?: number;
23
+ readonly output?: string;
24
+ }
4
25
  export interface PodHost {
5
26
  /** A current host token, for the next connection. Throws when neither renewal nor join works. */
6
27
  token(): Promise<string>;
@@ -8,6 +29,19 @@ export interface PodHost {
8
29
  proxyEnv(): Record<string, string>;
9
30
  /** The host epoch of the tokens held. */
10
31
  readonly epoch: number | undefined;
32
+ /** The disk's setup from the last join, if its recipe has one. */
33
+ readonly setup: DiskSetup | undefined;
34
+ /** The last join's volume starts from a disk version core writes: no setup runs on it. */
35
+ readonly seeded: boolean;
36
+ /** Whether to run the setup again after a failure, with the plan to run now. */
37
+ setupState(): Promise<{
38
+ retry: boolean;
39
+ setup?: DiskSetup;
40
+ }>;
41
+ /** Reports how the setup ended. */
42
+ reportSetup(report: DiskSetupReport): Promise<void>;
43
+ /** Input file `index` (a file's bytes or a folder's listing), or one file of a folder. */
44
+ setupFile(index: number, path?: string): Promise<Response>;
11
45
  stop(): void;
12
46
  }
13
47
  export declare function podHost(config: PodHostConfig, logger: {
@@ -31,6 +31,8 @@ class Refused extends Error {
31
31
  export function podHost(config, logger, options = {}) {
32
32
  const call = options.fetch ?? fetch;
33
33
  let held;
34
+ let setup;
35
+ let seeded = false;
34
36
  /** The CA in the bundle on disk, once one is written. */
35
37
  let bundled;
36
38
  const post = async (path, init) => {
@@ -51,6 +53,10 @@ export function podHost(config, logger, options = {}) {
51
53
  const body = JSON.parse(text);
52
54
  if (typeof body.caCertificate === "string")
53
55
  await writeBundle(body.caCertificate);
56
+ if (path === "join") {
57
+ setup = body.setup;
58
+ seeded = body.seeded === true;
59
+ }
54
60
  return {
55
61
  hostToken: body.hostToken,
56
62
  egressToken: body.egressToken,
@@ -99,6 +105,15 @@ export function podHost(config, logger, options = {}) {
99
105
  logger.info("sandbox_host_joined", { sandboxId: config.sandboxId, epoch: held.epoch });
100
106
  return held;
101
107
  };
108
+ /** A request with a current host token: the held one, renewed near expiry, or a new join. */
109
+ const authorized = async (path, init) => {
110
+ const current = held && held.expiresAt - Date.now() > 60_000 ? held : ((await renew()) ?? (await join()));
111
+ return call(`${config.httpUrl}/nylorun/harness/v1/host/${path}`, {
112
+ ...init,
113
+ headers: { ...init.headers, authorization: `Bearer ${current.hostToken}` },
114
+ signal: AbortSignal.timeout(5 * 60_000),
115
+ });
116
+ };
102
117
  const renew = async () => {
103
118
  if (!held)
104
119
  return undefined;
@@ -147,6 +162,32 @@ export function podHost(config, logger, options = {}) {
147
162
  get epoch() {
148
163
  return held?.epoch;
149
164
  },
165
+ get setup() {
166
+ return setup;
167
+ },
168
+ get seeded() {
169
+ return seeded;
170
+ },
171
+ async setupState() {
172
+ const response = await authorized("setup", { method: "GET" });
173
+ if (!response.ok)
174
+ throw new Error(`host/setup answered ${response.status}`);
175
+ return (await response.json());
176
+ },
177
+ async reportSetup(report) {
178
+ const response = await authorized("setup", {
179
+ method: "POST",
180
+ headers: { "content-type": "application/json" },
181
+ body: JSON.stringify(report),
182
+ });
183
+ await response.body?.cancel();
184
+ if (!response.ok)
185
+ throw new Error(`host/setup answered ${response.status}`);
186
+ },
187
+ setupFile(index, path) {
188
+ const suffix = path === undefined ? "" : `/${path.split("/").map(encodeURIComponent).join("/")}`;
189
+ return authorized(`files/${index}${suffix}`, { method: "GET" });
190
+ },
150
191
  stop() {
151
192
  clearInterval(timer);
152
193
  },
@@ -31,7 +31,6 @@ export interface HarnessServiceOptions {
31
31
  /** Delete the workspaces on stop (an ephemeral Runtime's loopback harness). */
32
32
  readonly ephemeral?: boolean;
33
33
  readonly logger: HarnessClientOptions["logger"];
34
- readonly name?: string;
35
34
  readonly version?: string;
36
35
  readonly onGrant?: HarnessOptions["onGrant"];
37
36
  readonly backoff?: HarnessClientOptions["backoff"];
@@ -9,8 +9,9 @@
9
9
  * keeps its compute records in `<sandboxes>/records.json` and claims its events from core
10
10
  * (`event`): for the run that made the call, or the workspace request core sent.
11
11
  */
12
+ import { fromWireOp, toWireAnswer } from "../sandbox/disk-fs.js";
12
13
  import { join } from "node:path";
13
- import { HarnessApiError, } from "@nylorun/core/harness-api";
14
+ import { DEFAULT_HARNESS, HarnessApiError, } from "@nylorun/core/harness-api";
14
15
  import { McpPool } from "../mcp/pool.js";
15
16
  import { SandboxManager } from "../sandbox/manager.js";
16
17
  import { fileSandboxRecords, workspacePrefix } from "../sandbox/records.js";
@@ -33,7 +34,7 @@ function refOf(session) {
33
34
  return {
34
35
  id: session.ownerId,
35
36
  activeTurnId: session.activeTurnId,
36
- ...(session.sandboxId === undefined ? {} : { sandboxId: session.sandboxId }),
37
+ ...(session.environmentId === undefined ? {} : { environmentId: session.environmentId }),
37
38
  };
38
39
  }
39
40
  const noModel = async () => {
@@ -78,6 +79,12 @@ export function startHarnessService(options) {
78
79
  case "workspace.read":
79
80
  case "workspace.write":
80
81
  case "workspace.exec": {
82
+ if ("disk" in params) {
83
+ // A disk version's read or write (R2d step 3).
84
+ const call = params;
85
+ const answer = await sandbox().disk(refOf(call.session), { id: "sandbox", type: "agent", sandbox: call.spec, tools: [] }, fromWireOp(call.disk), signal);
86
+ return "code" in answer ? answer : toWireAnswer(answer);
87
+ }
81
88
  if (method === "workspace.read" && "list" in params) {
82
89
  const call = params;
83
90
  return sandbox().listFiles(refOf(call.session), { id: "sandbox", type: "agent", sandbox: call.spec, tools: [] }, call.list.dir, call.list.maxEntries, signal);
@@ -102,7 +109,7 @@ export function startHarnessService(options) {
102
109
  return { workspaces: await sandbox().workspaces() };
103
110
  }
104
111
  case "workspace.remove": {
105
- const { keys, sandboxIds, all } = params;
112
+ const { keys, environmentIds, all } = params;
106
113
  const workspaces = sandbox();
107
114
  if (all) {
108
115
  await workspaces.reconcile(() => false, () => false);
@@ -114,7 +121,7 @@ export function startHarnessService(options) {
114
121
  throw new HarnessApiError("invalid", `${key} is not a workspace of this Tenant`);
115
122
  await workspaces.remove(key);
116
123
  }
117
- for (const id of sandboxIds ?? [])
124
+ for (const id of environmentIds ?? [])
118
125
  await workspaces.removeSandbox(id);
119
126
  return {};
120
127
  }
@@ -128,7 +135,8 @@ export function startHarnessService(options) {
128
135
  logger,
129
136
  executors,
130
137
  serve,
131
- name: options.name ?? "harness",
138
+ // Our harness: its hello names our harness id (R4.1).
139
+ name: DEFAULT_HARNESS,
132
140
  ...(options.version ? { version: options.version } : {}),
133
141
  capabilities: { workspace: { backends: backends.map((backend) => backend.name) } },
134
142
  onHello(answer) {