@nylorun/runtime 0.23.0-beta → 0.24.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) hide show
  1. package/CHANGELOG.md +148 -0
  2. package/README.md +79 -28
  3. package/dist/adapters/execution/restate.js +1 -1
  4. package/dist/adapters/replication/pgoutput.d.ts +2 -0
  5. package/dist/adapters/replication/pgoutput.js +6 -3
  6. package/dist/adapters/sandbox/local.d.ts +9 -1
  7. package/dist/adapters/sandbox/local.js +16 -4
  8. package/dist/adapters/sandbox/virtual.js +4 -1
  9. package/dist/api/a2a/endpoint.js +5 -1
  10. package/dist/api/a2a/routes.d.ts +2 -0
  11. package/dist/api/a2a/routes.js +2 -2
  12. package/dist/api/a2a/tasks.js +2 -1
  13. package/dist/api/ag-ui/endpoint.js +4 -2
  14. package/dist/api/ag-ui/history.js +4 -1
  15. package/dist/api/ag-ui/routes.d.ts +4 -1
  16. package/dist/api/ag-ui/routes.js +11 -10
  17. package/dist/api/ag-ui/translate.js +3 -2
  18. package/dist/api/components.d.ts +4953 -1204
  19. package/dist/api/components.js +25 -16
  20. package/dist/api/gate/keys.js +7 -1
  21. package/dist/api/gate/routes.d.ts +6 -0
  22. package/dist/api/gate/routes.js +39 -3
  23. package/dist/api/http/app.js +4 -4
  24. package/dist/api/http/define.d.ts +6 -0
  25. package/dist/api/http/define.js +13 -1
  26. package/dist/api/http/respond.js +1 -1
  27. package/dist/api/http/routes/artifacts.js +35 -8
  28. package/dist/api/http/routes/coverage.d.ts +9 -0
  29. package/dist/api/http/routes/coverage.js +34 -0
  30. package/dist/api/http/routes/environments.d.ts +11 -0
  31. package/dist/api/http/routes/environments.js +283 -0
  32. package/dist/api/http/routes/mcp.d.ts +1 -1
  33. package/dist/api/http/routes/mcp.js +2 -2
  34. package/dist/api/http/routes/me.js +1 -1
  35. package/dist/api/http/routes/reads.d.ts +45 -9
  36. package/dist/api/http/routes/reads.js +54 -5
  37. package/dist/api/http/routes/sessions.js +11 -6
  38. package/dist/api/http/routes/tenant.js +32 -10
  39. package/dist/api/openapi.js +31 -26
  40. package/dist/artifacts/disk.d.ts +59 -0
  41. package/dist/artifacts/disk.js +563 -0
  42. package/dist/artifacts/export.d.ts +1 -1
  43. package/dist/artifacts/export.js +2 -3
  44. package/dist/artifacts/folders.d.ts +6 -3
  45. package/dist/artifacts/folders.js +17 -12
  46. package/dist/artifacts/service.d.ts +2 -1
  47. package/dist/artifacts/service.js +6 -1
  48. package/dist/artifacts/workspace.d.ts +1 -1
  49. package/dist/artifacts/workspace.js +1 -1
  50. package/dist/core/flow-host.d.ts +2 -3
  51. package/dist/core/flow-host.js +3 -0
  52. package/dist/core/provider.d.ts +3 -2
  53. package/dist/core/provider.js +5 -37
  54. package/dist/core/sandbox-routes.d.ts +2 -21
  55. package/dist/core/sandbox-routes.js +10 -43
  56. package/dist/execution/types.d.ts +1 -1
  57. package/dist/gates/contract.d.ts +3 -24
  58. package/dist/gates/contract.js +1 -36
  59. package/dist/gates/egress-credentials.d.ts +9 -0
  60. package/dist/gates/egress-credentials.js +11 -4
  61. package/dist/gates/egress.d.ts +28 -1
  62. package/dist/gates/egress.js +115 -10
  63. package/dist/gates/fixture-model.d.ts +28 -0
  64. package/dist/gates/fixture-model.js +92 -0
  65. package/dist/gates/handler.js +4 -4
  66. package/dist/gates/http-tool.d.ts +6 -2
  67. package/dist/gates/http-tool.js +3 -5
  68. package/dist/gates/in-process.d.ts +5 -0
  69. package/dist/gates/in-process.js +12 -1
  70. package/dist/gates/journal.d.ts +30 -0
  71. package/dist/gates/journal.js +52 -0
  72. package/dist/gates/mcp-handler.d.ts +4 -0
  73. package/dist/gates/mcp-handler.js +5 -1
  74. package/dist/gates/meter.d.ts +10 -2
  75. package/dist/gates/meter.js +20 -5
  76. package/dist/gates/policy.d.ts +49 -0
  77. package/dist/gates/policy.js +134 -0
  78. package/dist/gates/tenant-vaults.d.ts +7 -7
  79. package/dist/gates/tenant-vaults.js +5 -5
  80. package/dist/gates/tool-calls.d.ts +10 -5
  81. package/dist/gates/tool-calls.js +28 -10
  82. package/dist/gates/tool-client.d.ts +8 -0
  83. package/dist/gates/tool-client.js +27 -11
  84. package/dist/gates/tool-contract.d.ts +10 -68
  85. package/dist/gates/tool-contract.js +1 -85
  86. package/dist/gates/tool-gate.d.ts +1 -1
  87. package/dist/harness/calls.d.ts +12 -5
  88. package/dist/harness/calls.js +21 -12
  89. package/dist/harness/executors.js +12 -9
  90. package/dist/harness/main.js +18 -5
  91. package/dist/harness/pod.d.ts +34 -0
  92. package/dist/harness/pod.js +41 -0
  93. package/dist/harness/service.d.ts +0 -1
  94. package/dist/harness/service.js +13 -5
  95. package/dist/harness/setup.d.ts +27 -0
  96. package/dist/harness/setup.js +150 -0
  97. package/dist/harness/ws-client.js +3 -2
  98. package/dist/harness/ws-port.d.ts +1 -5
  99. package/dist/harness/ws-port.js +1 -5
  100. package/dist/harness-api/in-process.js +3 -3
  101. package/dist/harness-api/record.d.ts +15 -1
  102. package/dist/harness-api/record.js +174 -44
  103. package/dist/harness-api/redact.d.ts +3 -0
  104. package/dist/harness-api/redact.js +45 -0
  105. package/dist/harness-api/server.d.ts +8 -0
  106. package/dist/harness-api/server.js +93 -13
  107. package/dist/harness-api/start.d.ts +10 -3
  108. package/dist/harness-api/start.js +40 -10
  109. package/dist/harness-api/tool-journal.d.ts +96 -0
  110. package/dist/harness-api/tool-journal.js +102 -0
  111. package/dist/harness-api/tool-status.d.ts +13 -0
  112. package/dist/harness-api/tool-status.js +26 -0
  113. package/dist/harness-api/workspace.d.ts +6 -3
  114. package/dist/harness-api/workspace.js +27 -11
  115. package/dist/harness-api/ws-server.d.ts +11 -3
  116. package/dist/harness-api/ws-server.js +94 -15
  117. package/dist/host/gates.d.ts +2 -1
  118. package/dist/host/gates.js +10 -2
  119. package/dist/host/main.js +54 -5
  120. package/dist/host/operate.js +67 -5
  121. package/dist/host/stack-config.d.ts +64 -0
  122. package/dist/host/stack-config.js +61 -1
  123. package/dist/keys/client.js +4 -1
  124. package/dist/keys/keys.d.ts +5 -5
  125. package/dist/keys/keys.js +5 -7
  126. package/dist/management-openapi.json +999 -2211
  127. package/dist/mcp/connect.d.ts +25 -20
  128. package/dist/mcp/connect.js +29 -30
  129. package/dist/mcp/pool.d.ts +2 -2
  130. package/dist/mcp/pool.js +1 -1
  131. package/dist/mcp/preview.d.ts +3 -3
  132. package/dist/mcp/preview.js +10 -20
  133. package/dist/mcp/snapshot.d.ts +5 -11
  134. package/dist/mcp/snapshot.js +6 -7
  135. package/dist/model/pi-model.js +8 -0
  136. package/dist/openapi.json +6955 -2526
  137. package/dist/reads/types.d.ts +16 -3
  138. package/dist/record/append.d.ts +3 -1
  139. package/dist/record/append.js +1 -0
  140. package/dist/record/envelope.d.ts +5 -1
  141. package/dist/record/envelope.js +7 -3
  142. package/dist/record/environment.d.ts +25 -0
  143. package/dist/record/environment.js +44 -0
  144. package/dist/record/index.d.ts +1 -1
  145. package/dist/record/index.js +1 -1
  146. package/dist/sandbox/disk-fs.d.ts +95 -0
  147. package/dist/sandbox/disk-fs.js +206 -0
  148. package/dist/sandbox/join.d.ts +17 -18
  149. package/dist/sandbox/join.js +42 -15
  150. package/dist/sandbox/manager.d.ts +16 -8
  151. package/dist/sandbox/manager.js +33 -17
  152. package/dist/sandbox/placement.d.ts +13 -12
  153. package/dist/sandbox/placement.js +29 -12
  154. package/dist/sandbox/pods/client.d.ts +6 -1
  155. package/dist/sandbox/pods/lifecycle.d.ts +28 -27
  156. package/dist/sandbox/pods/lifecycle.js +14 -26
  157. package/dist/sandbox/pods/reconcile.js +18 -12
  158. package/dist/sandbox/pods/spec.d.ts +15 -13
  159. package/dist/sandbox/pods/spec.js +16 -20
  160. package/dist/sandbox/records.d.ts +2 -2
  161. package/dist/sandbox/records.js +3 -3
  162. package/dist/sandbox/resolve.d.ts +35 -27
  163. package/dist/sandbox/resolve.js +66 -41
  164. package/dist/sandbox/session-sandbox.js +1 -1
  165. package/dist/sandbox/setup.d.ts +61 -0
  166. package/dist/sandbox/setup.js +123 -0
  167. package/dist/sandbox/share.d.ts +11 -21
  168. package/dist/sandbox/share.js +12 -41
  169. package/dist/sandbox/tenant-config.d.ts +12 -14
  170. package/dist/sandbox/tenant-config.js +12 -17
  171. package/dist/sandbox/types.d.ts +6 -0
  172. package/dist/store/postgres/drizzle/0020_environments.sql +63 -0
  173. package/dist/store/postgres/drizzle/0021_disk_versions.sql +7 -0
  174. package/dist/store/postgres/drizzle/0022_model_usage_timing.sql +5 -0
  175. package/dist/store/postgres/drizzle/0023_artifact_pages.sql +1 -0
  176. package/dist/store/postgres/drizzle/meta/_journal.json +28 -0
  177. package/dist/store/postgres/reads.js +117 -17
  178. package/dist/store/postgres/record-writer.d.ts +4 -4
  179. package/dist/store/postgres/record-writer.js +9 -9
  180. package/dist/store/postgres/record.d.ts +7 -0
  181. package/dist/store/postgres/record.js +23 -2
  182. package/dist/store/postgres/schema.d.ts +934 -761
  183. package/dist/store/postgres/schema.js +102 -73
  184. package/dist/store/postgres/store.js +119 -66
  185. package/dist/store/types.d.ts +109 -48
  186. package/dist/tenant/advance.d.ts +7 -6
  187. package/dist/tenant/advance.js +115 -42
  188. package/dist/tenant/artifact-tool.js +3 -4
  189. package/dist/tenant/commands.d.ts +15 -2
  190. package/dist/tenant/commands.js +93 -12
  191. package/dist/tenant/context.d.ts +48 -22
  192. package/dist/tenant/context.js +4 -15
  193. package/dist/tenant/effects.d.ts +1 -1
  194. package/dist/tenant/effects.js +37 -20
  195. package/dist/tenant/environments.d.ts +167 -0
  196. package/dist/tenant/environments.js +823 -0
  197. package/dist/tenant/harness-tokens.d.ts +21 -0
  198. package/dist/tenant/harness-tokens.js +61 -0
  199. package/dist/tenant/host-token.d.ts +1 -1
  200. package/dist/tenant/host-token.js +2 -4
  201. package/dist/tenant/identity-file.d.ts +4 -4
  202. package/dist/tenant/identity-file.js +17 -8
  203. package/dist/tenant/issuers.js +3 -3
  204. package/dist/tenant/principals.js +1 -1
  205. package/dist/tenant/reset.js +3 -3
  206. package/dist/tenant/runtime.d.ts +6 -1
  207. package/dist/tenant/runtime.js +7 -1
  208. package/dist/tenant/session.d.ts +3 -3
  209. package/dist/tenant/session.js +0 -14
  210. package/dist/tenant/sessions.d.ts +13 -13
  211. package/dist/tenant/sessions.js +61 -138
  212. package/dist/tenant/signing-keys.d.ts +5 -1
  213. package/dist/tenant/status.js +3 -3
  214. package/dist/tenant/sweep.js +12 -6
  215. package/dist/tenant/transcript.d.ts +210 -20
  216. package/dist/tenant/transcript.js +62 -11
  217. package/dist/tenant/turn-timing.d.ts +43 -0
  218. package/dist/tenant/turn-timing.js +39 -0
  219. package/dist/tenant/types.d.ts +2 -0
  220. package/dist/traces/ids.d.ts +42 -0
  221. package/dist/traces/ids.js +85 -0
  222. package/dist/traces/key.d.ts +6 -0
  223. package/dist/traces/key.js +37 -0
  224. package/dist/traces/map.d.ts +113 -0
  225. package/dist/traces/map.js +317 -0
  226. package/dist/traces/options.d.ts +7 -0
  227. package/dist/traces/options.js +1 -0
  228. package/dist/traces/otlp.d.ts +42 -0
  229. package/dist/traces/otlp.js +84 -0
  230. package/dist/traces/projector.d.ts +58 -0
  231. package/dist/traces/projector.js +200 -0
  232. package/dist/traces/read.d.ts +23 -0
  233. package/dist/traces/read.js +16 -0
  234. package/dist/vault/coverage.d.ts +26 -0
  235. package/dist/vault/coverage.js +64 -0
  236. package/dist/vault/error.d.ts +11 -1
  237. package/dist/vault/error.js +5 -1
  238. package/dist/vault/service.d.ts +47 -48
  239. package/dist/vault/service.js +321 -382
  240. package/dist/vault/sources.d.ts +5 -16
  241. package/dist/vault/sources.js +6 -8
  242. package/dist/version.d.ts +1 -1
  243. package/dist/version.js +2 -2
  244. package/package.json +13 -13
  245. package/dist/api/http/routes/sandboxes.d.ts +0 -9
  246. package/dist/api/http/routes/sandboxes.js +0 -153
  247. package/dist/api/http/routes/vaults.d.ts +0 -10
  248. package/dist/api/http/routes/vaults.js +0 -139
  249. package/dist/record/sandbox.d.ts +0 -25
  250. package/dist/record/sandbox.js +0 -43
  251. package/dist/tenant/sandboxes.d.ts +0 -123
  252. package/dist/tenant/sandboxes.js +0 -566
@@ -18,7 +18,6 @@ import type { SessionHistory } from "./history.js";
18
18
  import type { IssuerScope, SubjectScope, TenantEnvelope } from "@nylorun/core/contracts";
19
19
  import type { DurableCheckpoint, FlowCheckpoint } from "@nylorun/harness/run";
20
20
  import type { AgentManifest, JsonValue, SandboxManifest } from "@nylorun/core/define";
21
- import type { CredentialSelection } from "@nylorun/core/contracts";
22
21
  import type { OutboxRequest, SessionStore, Tx } from "../store/types.js";
23
22
  import type { FlowLimits } from "../core/limits.js";
24
23
  import type { ModelProvider } from "../core/provider.js";
@@ -40,6 +39,8 @@ import type { SigningKeys } from "./signing-keys.js";
40
39
  import type { BlobStore } from "../blob/index.js";
41
40
  import type { WorkspaceReader } from "../artifacts/workspace.js";
42
41
  import type { RunGrants } from "./run-grants.js";
42
+ import type { TraceOptions } from "../traces/options.js";
43
+ import type { TraceContext } from "../traces/ids.js";
43
44
  export interface Session {
44
45
  id: string;
45
46
  agentId: string;
@@ -74,26 +75,49 @@ export interface Session {
74
75
  * turn it started (`linkedTurnEnd` in `core/flow-host.ts`).
75
76
  */
76
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;
77
89
  creation: unknown;
78
- vaultIds?: readonly string[];
79
- credentialSelections?: readonly CredentialSelection[];
80
90
  mcpSnapshot?: McpSnapshot;
81
91
  mcpDiagnostics?: readonly McpDiagnostic[];
82
- /** Session id that keys the shared sandbox; absent means this session owns it. */
83
- sandboxOwnerId?: string;
84
92
  /**
85
- * The sandbox resource the session is attached to (`sandbox: { id }`): its workspace is the
86
- * 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`).
103
+ */
104
+ harness?: string;
105
+ /**
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`).
87
109
  */
88
- sandboxId?: string;
110
+ flowRootId?: string;
89
111
  /**
90
- * The sandbox chosen when the session was opened, resolved against the Tenant's limits, or
91
- * inherited from the session it shares with. Absent when the session has no sandbox or its
92
- * definition declares one (`.sandbox()`).
112
+ * The Environment's sandbox spec when the session opened (kinds `virtual` and `pod`).
113
+ * Absent without an Environment, or for kind `none`.
93
114
  */
94
115
  sandbox?: SandboxManifest;
95
- /** Where `sandbox` came from: `sandbox` is a sandbox resource's spec. */
96
- 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;
97
121
  }
98
122
  export type AuthScope = {
99
123
  kind: "application";
@@ -110,7 +134,7 @@ export type AuthScope = {
110
134
  }
111
135
  /**
112
136
  * A trusted issuer's token (Host feature `trusted-issuers`, F9-D12): one subject, with the
113
- * 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.
114
138
  */
115
139
  | {
116
140
  kind: "token";
@@ -120,8 +144,8 @@ export type AuthScope = {
120
144
  /** The issuer's scopes the token holds, which may add `studio`. */
121
145
  scopes: ReadonlySet<IssuerScope>;
122
146
  agents: ReadonlySet<string> | "*";
123
- /** The sandboxes it reaches: exact ids or `p/*` prefixes. Absent or empty reaches none. */
124
- sandboxes?: readonly string[];
147
+ /** The Environments it reaches: exact ids or `p/*` prefixes. Absent or empty reaches none. */
148
+ environments?: readonly string[];
125
149
  expiresAt: number;
126
150
  /** The token's `jti`, or a hash of it. */
127
151
  tokenId: string;
@@ -183,6 +207,11 @@ export interface TenantContext {
183
207
  * this process's `sandbox` today; over the Harness API's `workspace.read` from F6.2.
184
208
  */
185
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;
186
215
  /**
187
216
  * The run token of each session an advance of this process owns (F5): the credential the
188
217
  * HTTP gate clients present for that session's model and MCP calls (`run-grants.ts`).
@@ -232,7 +261,7 @@ export interface TenantContext {
232
261
  abortLocal(sessionId: string, turnId?: string): void;
233
262
  /**
234
263
  * Sandbox pods (F7.2): the sandboxes service and the Runtime image pods copy the engine from.
235
- * 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`.
236
265
  */
237
266
  readonly pods?: TenantPods;
238
267
  /**
@@ -275,14 +304,11 @@ export interface Lease {
275
304
  export declare function ownedSession(t: Tx, lease: Lease, id: string): Promise<Session>;
276
305
  /** The session read in its own transaction, or a 404. */
277
306
  export declare function loadSession(ctx: TenantContext, id: string, access?: SessionAccess): Promise<Session>;
278
- /**
279
- * A synchronous lookup over `start` and every session its `sandboxOwnerId` chain names, for
280
- * the sandbox-sharing helpers, which walk that chain synchronously.
281
- */
282
- export declare function sandboxLookup(t: Tx, start: string | undefined): Promise<(id: string) => Session | undefined>;
283
307
  /** The sandboxes service a Tenant drives its pod sandboxes with (`TenantContext.pods`). */
284
308
  export interface TenantPods {
285
309
  readonly client: SandboxesClient;
286
310
  /** The Runtime image the pods copy the engine from (`NYLORUN_SANDBOX_HARNESS_IMAGE`). */
287
311
  readonly harnessImage: string;
288
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
  }
@@ -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;