@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
@@ -5,8 +5,9 @@
5
5
  *
6
6
  * Business code changes state only inside `ctx.store.tx(async (t) => …)` and follows the
7
7
  * seam rules: events through `t.event(...)` (the relay appends them to Durable Streams after
8
- * commit), and advances through `await t.wake(id, { reason, dedupeKey }, ctx.wake)`, which
9
- * writes the wake to the outbox in the transaction and delivers it after commit. It never
8
+ * commit), and advances through `await t.wake(id, { reason, dedupeKey })`, which writes the
9
+ * wake to the outbox in the transaction; the store delivers it after commit (`ctx.wake`,
10
+ * wired with `SessionStore.deliverTo`). It never
10
11
  * publishes or notifies itself; `runtime.ts` wires the streams with `wireStreams()`. No
11
12
  * external I/O runs inside a tx.
12
13
  *
@@ -17,8 +18,7 @@ import type { SessionHistory } from "./history.js";
17
18
  import type { IssuerScope, SubjectScope, TenantEnvelope } from "@nylorun/core/contracts";
18
19
  import type { DurableCheckpoint, FlowCheckpoint } from "@nylorun/harness/run";
19
20
  import type { AgentManifest, JsonValue, SandboxManifest } from "@nylorun/core/define";
20
- import type { CredentialSelection } from "@nylorun/core/contracts";
21
- import type { SessionStore, Tx } from "../store/types.js";
21
+ import type { OutboxRequest, SessionStore, Tx } from "../store/types.js";
22
22
  import type { FlowLimits } from "../core/limits.js";
23
23
  import type { ModelProvider } from "../core/provider.js";
24
24
  import type { ModelGate } from "../gates/model-gate.js";
@@ -39,6 +39,8 @@ import type { SigningKeys } from "./signing-keys.js";
39
39
  import type { BlobStore } from "../blob/index.js";
40
40
  import type { WorkspaceReader } from "../artifacts/workspace.js";
41
41
  import type { RunGrants } from "./run-grants.js";
42
+ import type { TraceOptions } from "../traces/options.js";
43
+ import type { TraceContext } from "../traces/ids.js";
42
44
  export interface Session {
43
45
  id: string;
44
46
  agentId: string;
@@ -73,26 +75,49 @@ export interface Session {
73
75
  * turn it started (`linkedTurnEnd` in `core/flow-host.ts`).
74
76
  */
75
77
  lastTurnId?: string;
78
+ /** When the active turn started (R7.1, `turn-timing.ts`); absent on a turn started before. */
79
+ turnStartedAt?: string;
80
+ /** How long the active turn has waited paused for a person, in ms (R7.1). */
81
+ pausedMs?: number;
82
+ /** Since when the active turn waits paused (R7.1); absent while it runs. */
83
+ pausedAt?: string;
84
+ /**
85
+ * The trace context the active turn's message arrived with (R7.4): its trace is the caller's,
86
+ * and the context core hands on (to the harness, tools and sandbox commands) is in it.
87
+ */
88
+ turnTrace?: TraceContext;
76
89
  creation: unknown;
77
- vaultIds?: readonly string[];
78
- credentialSelections?: readonly CredentialSelection[];
79
90
  mcpSnapshot?: McpSnapshot;
80
91
  mcpDiagnostics?: readonly McpDiagnostic[];
81
- /** Session id that keys the shared sandbox; absent means this session owns it. */
82
- sandboxOwnerId?: string;
83
92
  /**
84
- * The sandbox resource the session is attached to (`sandbox: { id }`): its workspace is the
85
- * sandbox's, shared with every session attached to it, and it outlives the session.
93
+ * The Environment the session names (`environment`, protocol 11): its sandbox is the
94
+ * session's (the Environment's id keys the workspace) and its vault holds the session's
95
+ * credentials. It outlives the session.
96
+ */
97
+ environmentId?: string;
98
+ /** The Environment's recipe version when the session opened (shown, never enforced). */
99
+ environmentVersion?: number;
100
+ /**
101
+ * The harness that runs the session's turns (R4.1), pinned when it opened: its Environment's
102
+ * harness, else `nylorun`. Absent on a session opened before R4: ours (`harnessOf`).
86
103
  */
87
- sandboxId?: string;
104
+ harness?: string;
88
105
  /**
89
- * The sandbox chosen when the session was opened, resolved against the Tenant's limits, or
90
- * inherited from the session it shares with. Absent when the session has no sandbox or its
91
- * definition declares one (`.sandbox()`).
106
+ * A flow agent's linked session: the session whose turn opened the flow tree. Its sessions
107
+ * share the tree's Environment, and their turns nest in the root's, so they do not hold the
108
+ * Environment against each other (`checkEnvironmentTurn`).
109
+ */
110
+ flowRootId?: string;
111
+ /**
112
+ * The Environment's sandbox spec when the session opened (kinds `virtual` and `pod`).
113
+ * Absent without an Environment, or for kind `none`.
92
114
  */
93
115
  sandbox?: SandboxManifest;
94
- /** Where `sandbox` came from: `sandbox` is a sandbox resource's spec. */
95
- sandboxSource?: "default" | "inline" | "shared" | "sandbox";
116
+ /**
117
+ * Why the session takes no new turn: `protocol_11` for a session opened before protocol 11
118
+ * (migration 0020). Its history stays readable.
119
+ */
120
+ closedReason?: string;
96
121
  }
97
122
  export type AuthScope = {
98
123
  kind: "application";
@@ -109,7 +134,7 @@ export type AuthScope = {
109
134
  }
110
135
  /**
111
136
  * A trusted issuer's token (Host feature `trusted-issuers`, F9-D12): one subject, with the
112
- * issuer's scopes, agents and sandbox grants, until `expiresAt` (ms). Only its expiry ends it.
137
+ * issuer's scopes, agents and Environment grants, until `expiresAt` (ms). Only its expiry ends it.
113
138
  */
114
139
  | {
115
140
  kind: "token";
@@ -119,8 +144,8 @@ export type AuthScope = {
119
144
  /** The issuer's scopes the token holds, which may add `studio`. */
120
145
  scopes: ReadonlySet<IssuerScope>;
121
146
  agents: ReadonlySet<string> | "*";
122
- /** The sandboxes it reaches: exact ids or `p/*` prefixes. Absent or empty reaches none. */
123
- sandboxes?: readonly string[];
147
+ /** The Environments it reaches: exact ids or `p/*` prefixes. Absent or empty reaches none. */
148
+ environments?: readonly string[];
124
149
  expiresAt: number;
125
150
  /** The token's `jti`, or a hash of it. */
126
151
  tokenId: string;
@@ -182,6 +207,11 @@ export interface TenantContext {
182
207
  * this process's `sandbox` today; over the Harness API's `workspace.read` from F6.2.
183
208
  */
184
209
  readonly workspaces: WorkspaceReader;
210
+ /**
211
+ * How the trace route (R7.5) makes spans: the resource and the content the installation exports
212
+ * (R7.2). Absent: `service.name` `nylorun`, no content.
213
+ */
214
+ readonly traces?: TraceOptions;
185
215
  /**
186
216
  * The run token of each session an advance of this process owns (F5): the credential the
187
217
  * HTTP gate clients present for that session's model and MCP calls (`run-grants.ts`).
@@ -212,13 +242,18 @@ export interface TenantContext {
212
242
  /** How long an advance's ownership lease lasts; the heartbeat renews it. */
213
243
  readonly ownerLeaseMs: number;
214
244
  /**
215
- * Seam: hand a wake to `DurableExecution.wake`, outside any tx. Business code passes it to
216
- * `t.wake` (the outbox), which calls it after commit; the sweep calls it for outbox rows
217
- * left undelivered and for orphaned sessions. Resolves `true` once the execution accepted
218
- * the wake, `false` while the Tenant is closing: the wake is not sent, and its outbox row
219
- * stays for the next sweep.
245
+ * Seam: hand a wake to `DurableExecution.wake`, outside any tx: the sweep's orphan scan,
246
+ * and `send`. Resolves `true` once the execution accepted the wake, `false` while the
247
+ * Tenant is closing: the wake is not sent.
220
248
  */
221
249
  wake(sessionId: string, wake: Wake): Promise<boolean>;
250
+ /**
251
+ * Seam: hand an outbox request to Durable Session Execution (`wake` or `sandboxSignal`),
252
+ * outside any tx. The store calls it after commit for what business code wrote with
253
+ * `t.wake` and `t.signalSandbox` (`SessionStore.deliverTo`); the sweep calls it for rows
254
+ * left undelivered. `false` (the Tenant is closing) leaves the row for the next sweep.
255
+ */
256
+ send(request: OutboxRequest): Promise<boolean>;
222
257
  /**
223
258
  * Seam: abort the advance of a session running in this process, if any. With `turnId`, only
224
259
  * an advance of that turn (a cancel signal names the turn it cancelled).
@@ -226,14 +261,16 @@ export interface TenantContext {
226
261
  abortLocal(sessionId: string, turnId?: string): void;
227
262
  /**
228
263
  * Sandbox pods (F7.2): the sandboxes service and the Runtime image pods copy the engine from.
229
- * Absent without a cluster (`nylorun sandbox enable`): kind `pod` is `sandbox_unavailable`.
264
+ * Absent without a cluster (`nylorun sandbox enable`): kind `pod` is `environment_unavailable`.
230
265
  */
231
266
  readonly pods?: TenantPods;
232
267
  /**
233
- * Seam: reconcile a pod sandbox, or arm one of its timers (`DurableExecution.sandbox`). Call
234
- * it from `t.afterCommit`. Dropped while the Tenant is closing; the sweep re-sends reconciles.
268
+ * Seam: reconcile a pod sandbox, or arm one of its timers (`DurableExecution.sandbox`),
269
+ * outside any tx. What a transaction causes goes through `t.signalSandbox` instead, which
270
+ * writes it down; a direct call is best effort. `false` while the Tenant is closing (not
271
+ * sent); `true` without sandbox pods, where there is nothing to send.
235
272
  */
236
- sandboxSignal(sandboxId: string, signal: SandboxSignal): Promise<void>;
273
+ sandboxSignal(sandboxId: string, signal: SandboxSignal): Promise<boolean>;
237
274
  /** This Tenant's execution invocations that need an operator, for Tenant status. */
238
275
  readonly stuckInvocations?: () => Promise<StuckInvocation[]>;
239
276
  /** Callbacks the Tenant sweep runs after its own steps (`sweep.ts`). */
@@ -267,14 +304,11 @@ export interface Lease {
267
304
  export declare function ownedSession(t: Tx, lease: Lease, id: string): Promise<Session>;
268
305
  /** The session read in its own transaction, or a 404. */
269
306
  export declare function loadSession(ctx: TenantContext, id: string, access?: SessionAccess): Promise<Session>;
270
- /**
271
- * A synchronous lookup over `start` and every session its `sandboxOwnerId` chain names, for
272
- * the sandbox-sharing helpers, which walk that chain synchronously.
273
- */
274
- export declare function sandboxLookup(t: Tx, start: string | undefined): Promise<(id: string) => Session | undefined>;
275
307
  /** The sandboxes service a Tenant drives its pod sandboxes with (`TenantContext.pods`). */
276
308
  export interface TenantPods {
277
309
  readonly client: SandboxesClient;
278
310
  /** The Runtime image the pods copy the engine from (`NYLORUN_SANDBOX_HARNESS_IMAGE`). */
279
311
  readonly harnessImage: string;
280
312
  }
313
+ /** The harness a session runs (R4.1): the one it pinned, else ours. */
314
+ export declare function harnessOf(session: Pick<Session, "harness">): string;
@@ -1,3 +1,4 @@
1
+ import { DEFAULT_HARNESS } from "@nylorun/core/contracts";
1
2
  import { fail } from "./http.js";
2
3
  /**
3
4
  * `access`, when set, limits the request to the owner's sessions of the allowed agents:
@@ -34,19 +35,7 @@ export async function ownedSession(t, lease, id) {
34
35
  export function loadSession(ctx, id, access) {
35
36
  return ctx.store.tx((t) => sessionOf(t, id, access));
36
37
  }
37
- /**
38
- * A synchronous lookup over `start` and every session its `sandboxOwnerId` chain names, for
39
- * the sandbox-sharing helpers, which walk that chain synchronously.
40
- */
41
- export async function sandboxLookup(t, start) {
42
- const found = new Map();
43
- let id = start;
44
- while (id && !found.has(id)) {
45
- const session = await t.get("sessions", id);
46
- if (!session)
47
- break;
48
- found.set(id, session);
49
- id = session.sandboxOwnerId;
50
- }
51
- return (sid) => found.get(sid);
38
+ /** The harness a session runs (R4.1): the one it pinned, else ours. */
39
+ export function harnessOf(session) {
40
+ return session.harness ?? DEFAULT_HARNESS;
52
41
  }
@@ -8,9 +8,16 @@
8
8
  * - `sessions.reset` is written by the reset's transaction (`reset.ts`): every process moves
9
9
  * its session streams to the new basin generation and ends those of deleted sessions
10
10
  * (`checkSessionStreams`).
11
+ * - `host.revoked` is written by the transaction that moves a pod sandbox's host epoch
12
+ * (`sandboxes.ts`, `sandbox/join.ts`, `sandbox/pods/reconcile.ts`): every process closes its
13
+ * connections hosting that sandbox at an older epoch, wherever the host connected
14
+ * (`HarnessApiServer.revokeHost`).
11
15
  *
12
16
  * A lost signal costs latency, never correctness: the advance checks the Session Store before
13
- * every effect, and the session streams are checked periodically (`streams.ts`).
17
+ * every effect, the session streams are checked periodically (`streams.ts`), and a revoked
18
+ * host's writes are fenced by its runs' lease epochs and its token by the sandbox's row.
19
+ * Signals are read back from their rows for two minutes, so a process whose listener was
20
+ * down catches up.
14
21
  */
15
22
  import type { TenantContext } from "./context.js";
16
23
  export interface WireControlOptions {
@@ -16,6 +16,8 @@ export async function wireControl(ctx, options = {}) {
16
16
  const follower = await ctx.store.followSignals((signal) => {
17
17
  if (signal.type === "session.cancel")
18
18
  ctx.abortLocal(signal.sessionId, signal.turnId);
19
+ else if (signal.type === "host.revoked")
20
+ ctx.harness.revokeHost(signal.sandboxId, signal.epoch);
19
21
  else
20
22
  void checkSessionStreams(ctx).catch(report("session stream check failed"));
21
23
  }, {
@@ -42,6 +42,6 @@ export declare function recoversModelCalls(ctx: TenantContext): boolean;
42
42
  export declare function resolveNewFlowEffect(ctx: TenantContext, request: HostEffect, signal: AbortSignal, lease: Lease): Promise<EffectResolution>;
43
43
  /**
44
44
  * The credential of an MCP server or HTTP tool request made on behalf of a session: from its
45
- * attached vaults (`vault/sources.ts`).
45
+ * Environment's vault (`vault/sources.ts`).
46
46
  */
47
47
  export declare function authorize(ctx: TenantContext, sessionId: string, request: McpCredentialRequest): Promise<AuthorizeResult>;
@@ -4,9 +4,9 @@ import { mayDispatchMore } from "../core/limits.js";
4
4
  import { sessionCredentials } from "../vault/sources.js";
5
5
  import { isRemoteMcpCall } from "../harness/calls.js";
6
6
  import { isHttpToolCall } from "../gates/http-tool.js";
7
- import { owningSandboxSessionId, sandboxSpecOf, sessionSandboxSpec, } from "../sandbox/share.js";
7
+ import { sandboxSpecOf } from "../sandbox/share.js";
8
8
  import { withPlatformTools, withSandboxCapability } from "../sandbox/session-sandbox.js";
9
- import { loadSession, ownedSession, sandboxLookup, sessionOf, } from "./context.js";
9
+ import { loadSession, ownedSession, sessionOf, } from "./context.js";
10
10
  import { fail } from "./http.js";
11
11
  import { turnManifestOf } from "./session.js";
12
12
  import { command } from "./commands.js";
@@ -85,11 +85,14 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
85
85
  }
86
86
  }
87
87
  const active = await countActiveFlowWork(t, request.sessionId, request.turnId);
88
+ // When the run journaled the effect (R7.1); a queued effect keeps it once dispatched.
89
+ const startedAt = existing?.startedAt ?? new Date().toISOString();
88
90
  if (!mayDispatchMore(active, ctx.flowLimits)) {
89
91
  if (!existing || existing.status !== "queued")
90
92
  await t.put("effects", request.effectId, {
91
93
  request,
92
94
  status: "queued",
95
+ startedAt,
93
96
  });
94
97
  return resolved({ status: "pending" });
95
98
  }
@@ -101,19 +104,32 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
101
104
  value: {
102
105
  kind: "failed",
103
106
  code: "tool.unavailable",
104
- message: `The tool stage '${request.key ?? request.path ?? ""}' runs your code, and the Runtime runs no code of yours during a session`,
107
+ message: `The tool stage '${request.key ?? request.path ?? ""}' runs your code, but the Runtime runs agents from their manifests alone`,
105
108
  },
106
109
  };
107
- await t.put("effects", request.effectId, { request, status: "completed", outcome });
108
- await t.event(workflow.id, workflow.activeTurnId, "node.started", {
110
+ await t.put("effects", request.effectId, {
111
+ request,
112
+ status: "completed",
113
+ outcome,
114
+ startedAt,
115
+ settledAt: startedAt,
116
+ });
117
+ const node = {
109
118
  path: request.path,
110
119
  kind: "tool",
111
120
  key: request.key,
112
121
  ...(request.iterations !== undefined ? { iterations: request.iterations } : {}),
122
+ };
123
+ await t.event(workflow.id, workflow.activeTurnId, "node.started", node);
124
+ // Nothing ran (R7.1): the stage's outcome row says so.
125
+ await t.event(workflow.id, workflow.activeTurnId, "node.completed", {
126
+ ...node,
127
+ effectId: request.effectId,
128
+ status: "failed",
113
129
  });
114
130
  return resolved({ status: "completed", outcome });
115
131
  }
116
- return { kind: "agent", workflow };
132
+ return { kind: "agent", workflow, startedAt };
117
133
  });
118
134
  if (step.kind === "resolved")
119
135
  return step.resolution;
@@ -130,6 +146,7 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
130
146
  request,
131
147
  status: "pending",
132
148
  agentSessionId,
149
+ startedAt: step.startedAt,
133
150
  });
134
151
  await t.put("links", agentSessionId, {
135
152
  workflowSessionId: workflow.id,
@@ -145,12 +162,8 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
145
162
  if (exists)
146
163
  return;
147
164
  const definition = leafDefinition(workflow, body, request);
148
- const lookup = await sandboxLookup(t, workflow.sandboxOwnerId);
149
- const sandboxOwnerId = sessionSandboxSpec(workflow) || workflow.sandboxOwnerId
150
- ? owningSandboxSessionId(workflow, lookup)
151
- : undefined;
152
- // A sandbox chosen when the tree was opened reaches every agent in it, with its tools.
153
- const inherited = inheritedSandbox(sandboxOwnerId === undefined ? undefined : lookup(sandboxOwnerId) ?? workflow, definition);
165
+ // The tree's Environment reaches every agent in it: its sandbox, with its tools, and its vault.
166
+ const inherited = inheritedSandbox(workflow, definition);
154
167
  // Without one, an agent with MCP or HTTP tools still reads its stored results (R2b C11).
155
168
  const pinned = inherited?.manifest ? inherited : platformTools(definition.manifest);
156
169
  const created = {
@@ -167,14 +180,18 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
167
180
  requestId: `flow-put-${request.effectId}`,
168
181
  agentId: body.agentId,
169
182
  ownerUserId: workflow.ownerUserId,
170
- ...(sandboxOwnerId ? { sandbox: { session: sandboxOwnerId } } : {}),
183
+ ...(workflow.environmentId === undefined ? {} : { environment: workflow.environmentId }),
171
184
  },
172
- vaultIds: workflow.vaultIds,
173
- credentialSelections: workflow.credentialSelections,
174
- ...(sandboxOwnerId ? { sandboxOwnerId } : {}),
175
- ...(inherited
176
- ? { sandbox: inherited.spec, sandboxSource: "shared" }
177
- : {}),
185
+ flowRootId: workflow.flowRootId ?? workflow.id,
186
+ ...(workflow.environmentId === undefined
187
+ ? {}
188
+ : {
189
+ environmentId: workflow.environmentId,
190
+ ...(workflow.environmentVersion === undefined
191
+ ? {}
192
+ : { environmentVersion: workflow.environmentVersion }),
193
+ }),
194
+ ...(inherited ? { sandbox: inherited.spec } : {}),
178
195
  };
179
196
  await t.put("sessions", agentSessionId, created);
180
197
  });
@@ -258,7 +275,7 @@ function platformTools(manifest) {
258
275
  }
259
276
  /**
260
277
  * The credential of an MCP server or HTTP tool request made on behalf of a session: from its
261
- * attached vaults (`vault/sources.ts`).
278
+ * Environment's vault (`vault/sources.ts`).
262
279
  */
263
280
  export async function authorize(ctx, sessionId, request) {
264
281
  const s = await loadSession(ctx, sessionId);
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Environments (D55, D56, protocol 11): `PUT`, `GET` and `DELETE /v1/environments/{id}`, the
3
+ * list by label, stop, reset and the lifecycle stream; sessions naming one with `environment`,
4
+ * and the checks every turn start makes on it.
5
+ *
6
+ * - **No scope.** The Runtime decides nothing about who uses an Environment: the app maps its
7
+ * people and agents to ids. It stores the id, kind, recipe, labels and the optional owner
8
+ * guard; the session body names the Environment (`Session.environmentId`), and deleting a
9
+ * session (a sessions reset) only detaches it.
10
+ * - **One sandbox, one vault.** Kind `virtual` (a just-bash workspace in the harness container,
11
+ * keyed by the Environment id and created on the first tool call), `pod` (an agent-sandbox pod
12
+ * on the Tenant's cluster, Host feature `sandbox-pods`, created at once) or `none` (no compute).
13
+ * Each has one vault (`vault/service.ts`), created and deleted with it in one transaction.
14
+ * - **A recipe that changes.** `PUT` on an existing id compares the fields sent: `kind` and
15
+ * `storage` are fixed (`409`), labels replace, and any other change writes the recipe and bumps
16
+ * `version`. A pod runs `applied`: image and resources reach it when it is next asked to run
17
+ * from stopped (`wantRunning`), so nothing swaps them mid-turn; so does `harness`, which picks
18
+ * what the pod runs (R4.4). Network and lifecycle apply at once. A virtual or `none`
19
+ * Environment applies every change at once.
20
+ * - **Access.** An application key reaches every Environment, and so does an application key
21
+ * acting for a subject (`Nylorun-Subject`), which needs `environments:write` to change one. A
22
+ * token caller reaches only the ids its grants match, and changes them only with
23
+ * `environments:write`; any other id is the 404 of a missing one.
24
+ * - **The owner guard.** When `ownerUserId` is set, only that owner's sessions may name the
25
+ * Environment, checked when a session opens and at every turn start (`403
26
+ * environment_owner_mismatch`; the opaque 404 to a caller acting for a person).
27
+ * - **Serial turns.** One turn at a time per Environment with a sandbox: a session whose
28
+ * Environment another session's turn holds is refused (`409 environment_busy`).
29
+ * - **Lifecycle.** `POST .../stop` suspends a pod (the volume is kept; the next turn starts it),
30
+ * `POST .../reset` gives it a new pod on a new volume. A lost pod refuses turns
31
+ * (`environment_lost`) until a reset. `lifecycle.deleteAfter` deletes an Environment unused
32
+ * for that long (the Tenant sweep, `sweepUnusedEnvironments`). Deleting a pod Environment keeps
33
+ * its row (`desired: deleted`, invisible here) until its Sandbox is gone; its vault goes at once.
34
+ * - **Lifecycle events** go to the Environment's own stream in the record (`Tx.environmentEvent`,
35
+ * `record/environment.ts`); the session's log records `environment.attached` too.
36
+ */
37
+ import { type EnvironmentView, type PutEnvironmentRequest } from "@nylorun/core/contracts";
38
+ import type { Environment, Tx } from "../store/types.js";
39
+ import type { SandboxRouteDeps } from "../core/sandbox-routes.js";
40
+ import { type AuthScope, type Session, type TenantContext } from "./context.js";
41
+ /** The Environment grants that limit `scope`, or undefined when it reaches every Environment. */
42
+ export declare function environmentGrantsOf(scope: AuthScope): readonly string[] | undefined;
43
+ /** Who the vault's audit rows name for a request: the subject, else the application. */
44
+ export declare function actorOf(scope: AuthScope): string;
45
+ /** Refuses (the 404 of a missing one) Environment `id` when the caller's grants miss it. */
46
+ export declare function assertReaches(scope: AuthScope, id: string): void;
47
+ /** An Environment, unless it is gone or being deleted. */
48
+ export declare function liveEnvironment(t: Tx, id: string, options?: {
49
+ lock?: boolean;
50
+ }): Promise<Environment | undefined>;
51
+ /**
52
+ * `PUT /v1/environments/{id}`: creates the Environment and its vault (within the Tenant's limit
53
+ * on their number), or finds the one with this id, so "get or create" is one call. On an
54
+ * existing one, `kind` and `storage` must match, labels replace, the owner guard is set or
55
+ * cleared, and a recipe change bumps `version`. Every `PUT` counts as the Environment's use.
56
+ */
57
+ export declare function putEnvironment(ctx: TenantContext, id: string, body: PutEnvironmentRequest, scope: AuthScope): Promise<EnvironmentView>;
58
+ /** `GET /v1/environments/{id}`. */
59
+ export declare function getEnvironment(ctx: TenantContext, id: string, scope: AuthScope): Promise<EnvironmentView>;
60
+ /** `GET /v1/environments?label=k=v`: every Environment with all the labels, that the caller reaches. */
61
+ export declare function listEnvironments(ctx: TenantContext, labels: Record<string, string>, scope: AuthScope): Promise<{
62
+ environments: EnvironmentView[];
63
+ }>;
64
+ /**
65
+ * Asks pod Environment `id` to run when it is stopped or failed (a disk copy needs its engine),
66
+ * with its recipe applied, as a turn start would.
67
+ */
68
+ export declare function wakePod(ctx: TenantContext, id: string): Promise<void>;
69
+ /** Whether a disk lease (R2d step 3) holds `environment` now. */
70
+ export declare function diskHeld(environment: Environment, now?: number): boolean;
71
+ /** Refused while a snapshot or restore copies the disk (a pending seed does not count). */
72
+ export declare function refuseDiskCopy(environment: Environment): void;
73
+ /**
74
+ * `DELETE /v1/environments/{id}`: deletes the Environment, its vault and its workspace. Refused
75
+ * while a session that names it has an active turn. Sessions keep its id: their next turn is
76
+ * refused (`environment_unavailable`) unless an Environment with this id exists again.
77
+ */
78
+ export declare function deleteEnvironment(ctx: TenantContext, id: string, scope: AuthScope): Promise<{
79
+ id: string;
80
+ deleted: boolean;
81
+ }>;
82
+ /**
83
+ * Deletes every Environment unused for its `lifecycle.deleteAfter` (Q13): no turn, recipe
84
+ * change, secret write, stop or reset since. One with a turn running waits for the next sweep.
85
+ * Run by the Tenant sweep.
86
+ */
87
+ export declare function sweepUnusedEnvironments(ctx: TenantContext, now?: number): Promise<void>;
88
+ /**
89
+ * `POST /v1/environments/{id}/stop`: suspends a pod Environment. Its volume is kept, and the next
90
+ * turn of a session that names it starts it again. Refused during a turn.
91
+ */
92
+ export declare function stopEnvironment(ctx: TenantContext, id: string, scope: AuthScope): Promise<EnvironmentView>;
93
+ /**
94
+ * `POST /v1/environments/{id}/reset`: a new pod on a new, empty volume (the next volume
95
+ * generation), with a new join token and the recipe applied; the old Sandbox and volume are
96
+ * deleted. Leaves `lost` and `failed`. Refused during a turn, and while an earlier reset's
97
+ * Sandbox is still being deleted.
98
+ */
99
+ export declare function resetEnvironment(ctx: TenantContext, id: string, scope: AuthScope): Promise<EnvironmentView>;
100
+ /**
101
+ * Gives pod Environment `environment` (locked) a new pod on a new, empty volume, the next volume
102
+ * generation, with its recipe applied: the old Sandbox and volume are retired. With `seed`, the
103
+ * new volume starts from that disk version before any turn (R2d step 3): a restore, or the
104
+ * recovery of a lost volume.
105
+ */
106
+ export declare function newVolume(ctx: TenantContext, t: Tx, environment: Environment, options?: {
107
+ seed?: {
108
+ artifactId: string;
109
+ version: number;
110
+ };
111
+ reason?: "restore" | "lost";
112
+ }): Promise<number>;
113
+ /** How long a pending seed holds the disk before another process may take it over. */
114
+ export declare const SEED_LEASE_MS: number;
115
+ /** `GET /v1/environments/{id}/events`: the Environment's lifecycle stream. */
116
+ export declare function environmentEventsOf(ctx: TenantContext, id: string, scope: AuthScope, fromSeq: number | undefined): Promise<{
117
+ events: {
118
+ schema: "nylorun.environment-event/1";
119
+ eventId: string;
120
+ tenantId: string;
121
+ environmentId: string;
122
+ seq: number;
123
+ time: string;
124
+ type: "environment.attached" | "environment.created" | "environment.deleted" | "environment.detached" | "environment.egress" | "environment.failed" | "environment.lost" | "environment.relaunched" | "environment.reset" | "environment.running" | "environment.suspended";
125
+ payload: Record<string, unknown>;
126
+ }[];
127
+ }>;
128
+ /**
129
+ * Opens a new session on Environment `id` (`PutSessionRequest.environment`): it must exist, be
130
+ * reachable with `grants` and pass its owner guard. Runs in the session's transaction, after
131
+ * its session row lock; holds the Environment's row lock.
132
+ */
133
+ export declare function openEnvironment(t: Tx, id: string, options: {
134
+ grants?: readonly string[];
135
+ ownerUserId: string;
136
+ opaque: boolean;
137
+ }): Promise<Environment>;
138
+ /** Records the attachment on the session's log and on the Environment's stream. */
139
+ export declare function recordAttachment(t: Tx, sessionId: string, environmentId: string): Promise<void>;
140
+ /**
141
+ * Checks a turn start on a session that names an Environment, in the command's transaction
142
+ * after the session row lock: the Environment still exists, a token caller's grants reach it,
143
+ * its owner guard admits the session's owner, and, with a sandbox, no other session's turn
144
+ * holds it. Holds the Environment row's lock until the transaction ends, so two turn starts on
145
+ * one Environment are decided one after the other.
146
+ *
147
+ * A pod Environment must also be usable: not lost (`environment_lost`), on a Runtime with
148
+ * sandbox pods (`environment_unavailable`). A stopped or failed pod is asked to run again, with
149
+ * its recipe applied, after the commit.
150
+ */
151
+ export declare function checkEnvironmentTurn(t: Tx, session: Session, scope: AuthScope, ctx?: TenantContext): Promise<void>;
152
+ /** A sessions reset detaches every session from its Environment: each one's stream says so. */
153
+ export declare function detachAllSessions(t: Tx): Promise<void>;
154
+ /**
155
+ * Marks the end of a turn's segment on Environment `id`: it was used now (`deleteAfter` counts
156
+ * from here), and for a running pod idle counts from now and the idle timer is armed (D34).
157
+ * Best effort.
158
+ */
159
+ export declare function touchEnvironment(ctx: TenantContext, id: string): Promise<void>;
160
+ /**
161
+ * Re-arms the idle timer of every running pod after the Tenant's settings change, so a shorter
162
+ * `limits.idle` applies to pods already idle (D36: the settings are read whenever a timer is
163
+ * set). Best effort.
164
+ */
165
+ export declare function rearmPodTimers(ctx: TenantContext): Promise<void>;
166
+ /** Session lookups for the sandbox route handlers (`core/sandbox-routes.ts`). */
167
+ export declare function sandboxRouteDeps(ctx: TenantContext): SandboxRouteDeps;