@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.
- package/CHANGELOG.md +161 -0
- package/README.md +95 -30
- package/dist/adapters/execution/restate.d.ts +15 -3
- package/dist/adapters/execution/restate.js +78 -84
- package/dist/adapters/replication/pgoutput.d.ts +2 -0
- package/dist/adapters/replication/pgoutput.js +6 -3
- package/dist/adapters/sandbox/local.d.ts +9 -1
- package/dist/adapters/sandbox/local.js +16 -4
- package/dist/adapters/sandbox/virtual.js +4 -1
- package/dist/adapters/streams/s2.js +1 -13
- package/dist/api/a2a/endpoint.js +5 -1
- package/dist/api/a2a/routes.d.ts +2 -0
- package/dist/api/a2a/routes.js +2 -2
- package/dist/api/a2a/tasks.js +2 -1
- package/dist/api/ag-ui/endpoint.js +4 -2
- package/dist/api/ag-ui/history.js +4 -1
- package/dist/api/ag-ui/routes.d.ts +4 -1
- package/dist/api/ag-ui/routes.js +11 -10
- package/dist/api/ag-ui/translate.js +3 -2
- package/dist/api/components.d.ts +4953 -1204
- package/dist/api/components.js +25 -16
- package/dist/api/gate/keys.js +7 -1
- package/dist/api/gate/routes.d.ts +6 -0
- package/dist/api/gate/routes.js +39 -3
- package/dist/api/http/app.js +4 -4
- package/dist/api/http/define.d.ts +6 -0
- package/dist/api/http/define.js +13 -1
- package/dist/api/http/respond.js +1 -1
- package/dist/api/http/routes/artifacts.js +35 -8
- package/dist/api/http/routes/coverage.d.ts +9 -0
- package/dist/api/http/routes/coverage.js +34 -0
- package/dist/api/http/routes/environments.d.ts +11 -0
- package/dist/api/http/routes/environments.js +283 -0
- package/dist/api/http/routes/mcp.d.ts +1 -1
- package/dist/api/http/routes/mcp.js +2 -2
- package/dist/api/http/routes/me.js +1 -1
- package/dist/api/http/routes/reads.d.ts +45 -9
- package/dist/api/http/routes/reads.js +54 -5
- package/dist/api/http/routes/sessions.js +11 -6
- package/dist/api/http/routes/tenant.js +32 -10
- package/dist/api/openapi.js +31 -26
- package/dist/artifacts/disk.d.ts +59 -0
- package/dist/artifacts/disk.js +563 -0
- package/dist/artifacts/export.d.ts +1 -1
- package/dist/artifacts/export.js +2 -3
- package/dist/artifacts/folders.d.ts +6 -3
- package/dist/artifacts/folders.js +17 -12
- package/dist/artifacts/service.d.ts +2 -1
- package/dist/artifacts/service.js +6 -1
- package/dist/artifacts/workspace.d.ts +1 -1
- package/dist/artifacts/workspace.js +1 -1
- package/dist/core/flow-host.d.ts +3 -22
- package/dist/core/flow-host.js +11 -37
- package/dist/core/provider.d.ts +3 -2
- package/dist/core/provider.js +5 -37
- package/dist/core/sandbox-routes.d.ts +2 -21
- package/dist/core/sandbox-routes.js +10 -43
- package/dist/execution/types.d.ts +11 -9
- package/dist/execution/types.js +5 -7
- package/dist/gates/contract.d.ts +3 -24
- package/dist/gates/contract.js +1 -36
- package/dist/gates/egress-credentials.d.ts +9 -0
- package/dist/gates/egress-credentials.js +11 -4
- package/dist/gates/egress.d.ts +28 -1
- package/dist/gates/egress.js +115 -10
- package/dist/gates/fixture-model.d.ts +28 -0
- package/dist/gates/fixture-model.js +92 -0
- package/dist/gates/handler.js +4 -4
- package/dist/gates/http-tool.d.ts +6 -2
- package/dist/gates/http-tool.js +3 -5
- package/dist/gates/in-process.d.ts +5 -0
- package/dist/gates/in-process.js +12 -1
- package/dist/gates/journal.d.ts +30 -0
- package/dist/gates/journal.js +52 -0
- package/dist/gates/mcp-handler.d.ts +4 -0
- package/dist/gates/mcp-handler.js +5 -1
- package/dist/gates/meter.d.ts +10 -2
- package/dist/gates/meter.js +20 -5
- package/dist/gates/policy.d.ts +49 -0
- package/dist/gates/policy.js +134 -0
- package/dist/gates/tenant-vaults.d.ts +7 -7
- package/dist/gates/tenant-vaults.js +5 -5
- package/dist/gates/tool-calls.d.ts +10 -5
- package/dist/gates/tool-calls.js +28 -10
- package/dist/gates/tool-client.d.ts +8 -0
- package/dist/gates/tool-client.js +27 -11
- package/dist/gates/tool-contract.d.ts +10 -68
- package/dist/gates/tool-contract.js +1 -85
- package/dist/gates/tool-gate.d.ts +1 -1
- package/dist/harness/calls.d.ts +12 -5
- package/dist/harness/calls.js +21 -12
- package/dist/harness/executors.js +12 -9
- package/dist/harness/main.js +18 -5
- package/dist/harness/pod.d.ts +34 -0
- package/dist/harness/pod.js +41 -0
- package/dist/harness/service.d.ts +0 -1
- package/dist/harness/service.js +13 -5
- package/dist/harness/setup.d.ts +27 -0
- package/dist/harness/setup.js +150 -0
- package/dist/harness/ws-client.js +3 -2
- package/dist/harness/ws-port.d.ts +1 -5
- package/dist/harness/ws-port.js +1 -5
- package/dist/harness-api/in-process.js +3 -3
- package/dist/harness-api/record.d.ts +15 -1
- package/dist/harness-api/record.js +174 -44
- package/dist/harness-api/redact.d.ts +3 -0
- package/dist/harness-api/redact.js +45 -0
- package/dist/harness-api/server.d.ts +8 -0
- package/dist/harness-api/server.js +93 -13
- package/dist/harness-api/start.d.ts +10 -3
- package/dist/harness-api/start.js +40 -10
- package/dist/harness-api/tool-journal.d.ts +96 -0
- package/dist/harness-api/tool-journal.js +102 -0
- package/dist/harness-api/tool-status.d.ts +13 -0
- package/dist/harness-api/tool-status.js +26 -0
- package/dist/harness-api/workspace.d.ts +6 -3
- package/dist/harness-api/workspace.js +27 -11
- package/dist/harness-api/ws-server.d.ts +11 -3
- package/dist/harness-api/ws-server.js +94 -15
- package/dist/host/execution.d.ts +8 -6
- package/dist/host/gates.d.ts +2 -1
- package/dist/host/gates.js +10 -2
- package/dist/host/main.js +64 -21
- package/dist/host/operate.js +67 -5
- package/dist/host/stack-config.d.ts +70 -0
- package/dist/host/stack-config.js +67 -1
- package/dist/infra/execution.d.ts +18 -1
- package/dist/infra/execution.js +28 -4
- package/dist/infra/index.d.ts +1 -1
- package/dist/keys/client.js +4 -1
- package/dist/keys/keys.d.ts +5 -5
- package/dist/keys/keys.js +5 -7
- package/dist/management-openapi.json +999 -2211
- package/dist/mcp/connect.d.ts +25 -20
- package/dist/mcp/connect.js +29 -30
- package/dist/mcp/pool.d.ts +2 -2
- package/dist/mcp/pool.js +1 -1
- package/dist/mcp/preview.d.ts +3 -3
- package/dist/mcp/preview.js +10 -20
- package/dist/mcp/snapshot.d.ts +5 -11
- package/dist/mcp/snapshot.js +6 -7
- package/dist/model/pi-model.js +8 -0
- package/dist/openapi.json +6955 -2526
- package/dist/reads/types.d.ts +16 -3
- package/dist/record/append.d.ts +3 -1
- package/dist/record/append.js +1 -0
- package/dist/record/envelope.d.ts +5 -1
- package/dist/record/envelope.js +7 -3
- package/dist/record/environment.d.ts +25 -0
- package/dist/record/environment.js +44 -0
- package/dist/record/index.d.ts +1 -1
- package/dist/record/index.js +1 -1
- package/dist/retry.d.ts +21 -0
- package/dist/retry.js +34 -0
- package/dist/sandbox/disk-fs.d.ts +95 -0
- package/dist/sandbox/disk-fs.js +206 -0
- package/dist/sandbox/join.d.ts +17 -18
- package/dist/sandbox/join.js +46 -20
- package/dist/sandbox/manager.d.ts +16 -8
- package/dist/sandbox/manager.js +33 -17
- package/dist/sandbox/placement.d.ts +13 -12
- package/dist/sandbox/placement.js +29 -12
- package/dist/sandbox/pods/client.d.ts +6 -1
- package/dist/sandbox/pods/lifecycle.d.ts +28 -27
- package/dist/sandbox/pods/lifecycle.js +14 -26
- package/dist/sandbox/pods/reconcile.js +21 -15
- package/dist/sandbox/pods/spec.d.ts +15 -13
- package/dist/sandbox/pods/spec.js +16 -20
- package/dist/sandbox/records.d.ts +2 -2
- package/dist/sandbox/records.js +3 -3
- package/dist/sandbox/resolve.d.ts +35 -27
- package/dist/sandbox/resolve.js +66 -41
- package/dist/sandbox/session-sandbox.js +1 -1
- package/dist/sandbox/setup.d.ts +61 -0
- package/dist/sandbox/setup.js +123 -0
- package/dist/sandbox/share.d.ts +11 -21
- package/dist/sandbox/share.js +12 -41
- package/dist/sandbox/tenant-config.d.ts +12 -14
- package/dist/sandbox/tenant-config.js +12 -17
- package/dist/sandbox/types.d.ts +6 -0
- package/dist/store/postgres/control.js +8 -1
- package/dist/store/postgres/drizzle/0018_wake_outbox_retries.sql +3 -0
- package/dist/store/postgres/drizzle/0019_sandbox_signal_outbox.sql +18 -0
- package/dist/store/postgres/drizzle/0020_environments.sql +63 -0
- package/dist/store/postgres/drizzle/0021_disk_versions.sql +7 -0
- package/dist/store/postgres/drizzle/0022_model_usage_timing.sql +5 -0
- package/dist/store/postgres/drizzle/0023_artifact_pages.sql +1 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +42 -0
- package/dist/store/postgres/reads.js +117 -17
- package/dist/store/postgres/record-writer.d.ts +4 -4
- package/dist/store/postgres/record-writer.js +9 -9
- package/dist/store/postgres/record.d.ts +7 -0
- package/dist/store/postgres/record.js +23 -2
- package/dist/store/postgres/schema.d.ts +1213 -779
- package/dist/store/postgres/schema.js +136 -76
- package/dist/store/postgres/store.js +283 -117
- package/dist/store/types.d.ts +206 -88
- package/dist/streams/relay/core.js +1 -13
- package/dist/tenant/advance.d.ts +7 -6
- package/dist/tenant/advance.js +121 -49
- package/dist/tenant/artifact-tool.js +3 -4
- package/dist/tenant/commands.d.ts +15 -2
- package/dist/tenant/commands.js +94 -14
- package/dist/tenant/context.d.ts +67 -33
- package/dist/tenant/context.js +4 -15
- package/dist/tenant/control.d.ts +8 -1
- package/dist/tenant/control.js +2 -0
- package/dist/tenant/effects.d.ts +1 -1
- package/dist/tenant/effects.js +37 -20
- package/dist/tenant/environments.d.ts +167 -0
- package/dist/tenant/environments.js +823 -0
- package/dist/tenant/harness-tokens.d.ts +21 -0
- package/dist/tenant/harness-tokens.js +61 -0
- package/dist/tenant/host-token.d.ts +1 -1
- package/dist/tenant/host-token.js +2 -4
- package/dist/tenant/identity-file.d.ts +4 -4
- package/dist/tenant/identity-file.js +17 -8
- package/dist/tenant/issuers.js +3 -3
- package/dist/tenant/principals.js +1 -1
- package/dist/tenant/reset.js +3 -3
- package/dist/tenant/runtime.d.ts +6 -1
- package/dist/tenant/runtime.js +30 -4
- package/dist/tenant/scheduler.d.ts +7 -0
- package/dist/tenant/scheduler.js +38 -7
- package/dist/tenant/session-streams.d.ts +0 -1
- package/dist/tenant/session-streams.js +1 -13
- package/dist/tenant/session.d.ts +3 -3
- package/dist/tenant/session.js +0 -14
- package/dist/tenant/sessions.d.ts +13 -13
- package/dist/tenant/sessions.js +61 -138
- package/dist/tenant/signing-keys.d.ts +5 -1
- package/dist/tenant/status.js +3 -3
- package/dist/tenant/streams.js +14 -29
- package/dist/tenant/sweep.d.ts +39 -12
- package/dist/tenant/sweep.js +168 -63
- package/dist/tenant/transcript.d.ts +210 -20
- package/dist/tenant/transcript.js +62 -11
- package/dist/tenant/turn-timing.d.ts +43 -0
- package/dist/tenant/turn-timing.js +39 -0
- package/dist/tenant/types.d.ts +2 -0
- package/dist/traces/ids.d.ts +42 -0
- package/dist/traces/ids.js +85 -0
- package/dist/traces/key.d.ts +6 -0
- package/dist/traces/key.js +37 -0
- package/dist/traces/map.d.ts +113 -0
- package/dist/traces/map.js +317 -0
- package/dist/traces/options.d.ts +7 -0
- package/dist/traces/options.js +1 -0
- package/dist/traces/otlp.d.ts +42 -0
- package/dist/traces/otlp.js +84 -0
- package/dist/traces/projector.d.ts +58 -0
- package/dist/traces/projector.js +200 -0
- package/dist/traces/read.d.ts +23 -0
- package/dist/traces/read.js +16 -0
- package/dist/vault/coverage.d.ts +26 -0
- package/dist/vault/coverage.js +64 -0
- package/dist/vault/error.d.ts +11 -1
- package/dist/vault/error.js +5 -1
- package/dist/vault/service.d.ts +47 -48
- package/dist/vault/service.js +321 -382
- package/dist/vault/sources.d.ts +5 -16
- package/dist/vault/sources.js +6 -8
- package/dist/version.d.ts +1 -1
- package/dist/version.js +2 -2
- package/package.json +14 -13
- package/dist/api/http/routes/sandboxes.d.ts +0 -9
- package/dist/api/http/routes/sandboxes.js +0 -153
- package/dist/api/http/routes/vaults.d.ts +0 -10
- package/dist/api/http/routes/vaults.js +0 -139
- package/dist/record/sandbox.d.ts +0 -25
- package/dist/record/sandbox.js +0 -43
- package/dist/tenant/sandboxes.d.ts +0 -122
- package/dist/tenant/sandboxes.js +0 -562
package/dist/sandbox/join.js
CHANGED
|
@@ -32,7 +32,9 @@
|
|
|
32
32
|
* which check failed.
|
|
33
33
|
*/
|
|
34
34
|
import { createHash, timingSafeEqual } from "node:crypto";
|
|
35
|
-
import {
|
|
35
|
+
import { isEnvironmentId } from "@nylorun/core/contracts";
|
|
36
|
+
import { recoverLostVolume, runPendingSeed } from "../artifacts/disk.js";
|
|
37
|
+
import { recordSetup, setupFile, setupPlanOf, setupState, } from "./setup.js";
|
|
36
38
|
import { mintHostTokens, staleHost, verifyHostToken, } from "../tenant/host-token.js";
|
|
37
39
|
/** A refused join, renewal or host connection. */
|
|
38
40
|
export class HostAuthError extends Error {
|
|
@@ -84,7 +86,7 @@ export function hostAuthority(ctx) {
|
|
|
84
86
|
return {
|
|
85
87
|
async join(request) {
|
|
86
88
|
const { sandboxId, podUid, joinToken, volumeId } = request;
|
|
87
|
-
if (!
|
|
89
|
+
if (!isEnvironmentId(sandboxId) ||
|
|
88
90
|
typeof podUid !== "string" ||
|
|
89
91
|
!POD_UID.test(podUid) ||
|
|
90
92
|
typeof joinToken !== "string" ||
|
|
@@ -107,11 +109,11 @@ export function hostAuthority(ctx) {
|
|
|
107
109
|
// A pod being stopped joins nothing: its sandbox runs again only when asked to.
|
|
108
110
|
if (pod.desired !== "running")
|
|
109
111
|
return refuse("the sandbox is stopped");
|
|
110
|
-
if (pod.observed === "lost"
|
|
111
|
-
return refuse(
|
|
112
|
+
if (pod.observed === "lost")
|
|
113
|
+
return refuse("the sandbox is lost");
|
|
112
114
|
return pod;
|
|
113
115
|
};
|
|
114
|
-
const readRow = () => ctx.store.tx((t) => t.
|
|
116
|
+
const readRow = () => ctx.store.tx((t) => t.environment(sandboxId));
|
|
115
117
|
const before = usable(await readRow());
|
|
116
118
|
let current;
|
|
117
119
|
try {
|
|
@@ -123,7 +125,7 @@ export function hostAuthority(ctx) {
|
|
|
123
125
|
if (current !== podUid)
|
|
124
126
|
refuse("the pod is not the sandbox's current pod");
|
|
125
127
|
const joined = await ctx.store.tx(async (t) => {
|
|
126
|
-
const row = await t.
|
|
128
|
+
const row = await t.environment(sandboxId, { lock: true });
|
|
127
129
|
const pod = usable(row);
|
|
128
130
|
if (pod.k8sName !== before.k8sName)
|
|
129
131
|
refuse("the sandbox was reset during the join");
|
|
@@ -132,11 +134,14 @@ export function hostAuthority(ctx) {
|
|
|
132
134
|
if (pod.volumeId !== undefined && volumeId !== pod.volumeId) {
|
|
133
135
|
// A new, empty volume under the same Sandbox: the sandbox's files are gone.
|
|
134
136
|
const reason = "The sandbox's volume was replaced: its files are gone";
|
|
135
|
-
await t.
|
|
136
|
-
await t.
|
|
137
|
+
await t.updateEnvironmentPod(sandboxId, { observed: "lost", reason, hostEpoch: epoch }, now);
|
|
138
|
+
await t.environmentEvent(sandboxId, "environment.lost", { reason });
|
|
139
|
+
// Its Sandbox is deleted; only a reset brings the sandbox back.
|
|
140
|
+
await t.signal({ type: "host.revoked", sandboxId, epoch });
|
|
141
|
+
await t.signalSandbox(sandboxId, { kind: "reconcile" });
|
|
137
142
|
return { lost: epoch };
|
|
138
143
|
}
|
|
139
|
-
await t.
|
|
144
|
+
await t.updateEnvironmentPod(sandboxId, {
|
|
140
145
|
hostEpoch: epoch,
|
|
141
146
|
podUid,
|
|
142
147
|
...(pod.volumeId === undefined && volumeId !== undefined ? { volumeId } : {}),
|
|
@@ -144,27 +149,48 @@ export function hostAuthority(ctx) {
|
|
|
144
149
|
}, now);
|
|
145
150
|
if (pod.podUid !== podUid) {
|
|
146
151
|
if (pod.podUid !== undefined)
|
|
147
|
-
await t.
|
|
152
|
+
await t.environmentEvent(sandboxId, "environment.relaunched", { hostEpoch: epoch });
|
|
148
153
|
else
|
|
149
|
-
await t.
|
|
154
|
+
await t.environmentEvent(sandboxId, "environment.running", { hostEpoch: epoch });
|
|
150
155
|
}
|
|
151
|
-
|
|
156
|
+
// The previous host's connection (an older epoch) serves nothing more, on any process.
|
|
157
|
+
await t.signal({ type: "host.revoked", sandboxId, epoch });
|
|
158
|
+
return {
|
|
159
|
+
epoch,
|
|
160
|
+
setup: row ? await setupPlanOf(ctx, t, row) : undefined,
|
|
161
|
+
seeded: row?.diskSeed !== undefined,
|
|
162
|
+
};
|
|
152
163
|
});
|
|
153
|
-
if (
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
await ctx.sandboxSignal(sandboxId, { kind: "reconcile" });
|
|
164
|
+
if ("lost" in joined) {
|
|
165
|
+
// With a disk version, a new volume seeded from the latest one (R2d step 3).
|
|
166
|
+
void recoverLostVolume(ctx, sandboxId).catch(() => undefined);
|
|
157
167
|
return refuse("the volume was replaced: the sandbox is lost");
|
|
158
168
|
}
|
|
159
|
-
|
|
160
|
-
ctx
|
|
161
|
-
|
|
162
|
-
|
|
169
|
+
ctx.config.logger.info("sandbox host joined", { sandboxId, epoch: joined.epoch });
|
|
170
|
+
const answer = answerOf(await mintHostTokens(ctx, { sandboxId, epoch: joined.epoch, podUid }), await ctx.keys.egressCa());
|
|
171
|
+
if (joined.seeded) {
|
|
172
|
+
// The engine connects next; the seed waits for it, then writes the version.
|
|
173
|
+
void runPendingSeed(ctx, sandboxId);
|
|
174
|
+
return { ...answer, seeded: true };
|
|
175
|
+
}
|
|
176
|
+
return joined.setup ? { ...answer, setup: joined.setup } : answer;
|
|
163
177
|
},
|
|
164
178
|
async renew(hostToken) {
|
|
165
179
|
const claims = await verified(hostToken);
|
|
166
180
|
return answerOf(await mintHostTokens(ctx, claims), await ctx.keys.egressCa());
|
|
167
181
|
},
|
|
168
182
|
verify: verified,
|
|
183
|
+
async setup(hostToken) {
|
|
184
|
+
const claims = await verified(hostToken);
|
|
185
|
+
return setupState(ctx, claims.sandboxId);
|
|
186
|
+
},
|
|
187
|
+
async reportSetup(hostToken, report) {
|
|
188
|
+
const claims = await verified(hostToken);
|
|
189
|
+
await recordSetup(ctx, claims.sandboxId, claims.epoch, report);
|
|
190
|
+
},
|
|
191
|
+
async setupFile(hostToken, index, path) {
|
|
192
|
+
const claims = await verified(hostToken);
|
|
193
|
+
return setupFile(ctx, claims.sandboxId, index, path);
|
|
194
|
+
},
|
|
169
195
|
};
|
|
170
196
|
}
|
|
@@ -20,6 +20,7 @@ import type { EventPayload } from "@nylorun/core/contracts";
|
|
|
20
20
|
import { type AgentManifest, type CapabilityManifest, type SandboxToolName } from "@nylorun/core/define";
|
|
21
21
|
import { type SandboxRecord, type SandboxRecords } from "./records.js";
|
|
22
22
|
import { type SandboxSelection, type SandboxSelectionReport } from "./select.js";
|
|
23
|
+
import { type DiskAnswer, type DiskOp } from "./disk-fs.js";
|
|
23
24
|
import { type SandboxToolOutcome } from "./tools.js";
|
|
24
25
|
import { type SandboxBackend, type SandboxListing } from "./types.js";
|
|
25
26
|
export { sandboxCapabilityOf } from "./capability.js";
|
|
@@ -33,7 +34,7 @@ export interface SandboxSessionRef {
|
|
|
33
34
|
* The sandbox resource the owning session is attached to. Its workspace is keyed by this id,
|
|
34
35
|
* so every session attached to it shares the files; absent, the workspace is the session's.
|
|
35
36
|
*/
|
|
36
|
-
readonly
|
|
37
|
+
readonly environmentId?: string;
|
|
37
38
|
/** Variables every command of the call gets (R2c): secret names set to the sentinel, plain values. */
|
|
38
39
|
readonly environment?: Readonly<Record<string, string>>;
|
|
39
40
|
/**
|
|
@@ -55,7 +56,7 @@ export interface SandboxSweepOptions {
|
|
|
55
56
|
/** Whether a session still exists; sandboxes of deleted sessions are removed. */
|
|
56
57
|
readonly sessionExists: (sessionId: string) => boolean | Promise<boolean>;
|
|
57
58
|
/** Whether a sandbox resource still exists. Default: every one does. */
|
|
58
|
-
readonly sandboxExists?: (
|
|
59
|
+
readonly sandboxExists?: (environmentId: string) => boolean | Promise<boolean>;
|
|
59
60
|
}
|
|
60
61
|
export interface SandboxManagerOptions {
|
|
61
62
|
/** Distinguishes this Runtime's sandboxes from other Runtimes on the same machine: the Tenant id. */
|
|
@@ -77,7 +78,7 @@ export interface SandboxManagerOptions {
|
|
|
77
78
|
readonly emit: <T extends SandboxEventType>(sessionId: string, turnId: string | null, type: T, payload: EventPayload<T>, meta: SandboxEventMeta) => void | Promise<void>;
|
|
78
79
|
}
|
|
79
80
|
/** The session events a sandbox writes. */
|
|
80
|
-
export type SandboxEventType = "
|
|
81
|
+
export type SandboxEventType = "environment.state" | "environment.exec";
|
|
81
82
|
export declare class SandboxManager {
|
|
82
83
|
private readonly options;
|
|
83
84
|
private selection?;
|
|
@@ -104,8 +105,8 @@ export declare class SandboxManager {
|
|
|
104
105
|
/** Whether any sandbox was ever recorded, so startup can skip probing for agents without one. */
|
|
105
106
|
hasRecords(): Promise<boolean>;
|
|
106
107
|
keyOf(sessionId: string): string;
|
|
107
|
-
/** The workspace key of sandbox resource `
|
|
108
|
-
sandboxKeyOf(
|
|
108
|
+
/** The workspace key of sandbox resource `environmentId` (`sandboxWorkspaceKey`). */
|
|
109
|
+
sandboxKeyOf(environmentId: string): string;
|
|
109
110
|
private keyFor;
|
|
110
111
|
report(): Promise<SandboxSelectionReport & {
|
|
111
112
|
readonly defaultImage: string;
|
|
@@ -141,6 +142,13 @@ export declare class SandboxManager {
|
|
|
141
142
|
} | Extract<SandboxToolOutcome, {
|
|
142
143
|
kind: "failed";
|
|
143
144
|
}>>;
|
|
145
|
+
/**
|
|
146
|
+
* One operation on the disk of the session's sandbox (R2d step 3, `sandbox/disk-fs.ts`), in the
|
|
147
|
+
* same queue as its tool calls, starting it first when it is stopped.
|
|
148
|
+
*/
|
|
149
|
+
disk(session: SandboxSessionRef, capability: CapabilityManifest, op: DiskOp, signal: AbortSignal): Promise<DiskAnswer | Extract<SandboxToolOutcome, {
|
|
150
|
+
kind: "failed";
|
|
151
|
+
}>>;
|
|
144
152
|
/** Runs `use` on the session's sandbox in its queue, starting it first when it is stopped. */
|
|
145
153
|
private withHandle;
|
|
146
154
|
/** The session's live sandbox entry and spec, or why it cannot run here. */
|
|
@@ -159,16 +167,16 @@ export declare class SandboxManager {
|
|
|
159
167
|
/** The sandbox record for a session, if one was ever created. */
|
|
160
168
|
status(sessionId: string): Promise<SandboxRecord | undefined>;
|
|
161
169
|
/**
|
|
162
|
-
* Deletes sandbox resource `
|
|
170
|
+
* Deletes sandbox resource `environmentId`'s workspace, its files and its compute record, after
|
|
163
171
|
* any command queued on it. Called once the resource is deleted.
|
|
164
172
|
*/
|
|
165
|
-
removeSandbox(
|
|
173
|
+
removeSandbox(environmentId: string): Promise<void>;
|
|
166
174
|
/** Deletes the workspace `key`, its files and its record, after any command queued on it. */
|
|
167
175
|
remove(key: string): Promise<void>;
|
|
168
176
|
/**
|
|
169
177
|
* Delete workspaces this Runtime owns whose owner is gone: a session's when `sessionExists`
|
|
170
178
|
* says the session is gone, a sandbox resource's when `sandboxExists` says the resource is.
|
|
171
179
|
*/
|
|
172
|
-
reconcile(sessionExists: (sessionId: string) => boolean | Promise<boolean>, sandboxExists: (
|
|
180
|
+
reconcile(sessionExists: (sessionId: string) => boolean | Promise<boolean>, sandboxExists: (environmentId: string) => boolean | Promise<boolean>): Promise<void>;
|
|
173
181
|
close(): Promise<void>;
|
|
174
182
|
}
|
package/dist/sandbox/manager.js
CHANGED
|
@@ -3,6 +3,7 @@ import { sandboxWorkspaceKey, sessionWorkspaceKey, workspacePrefix, } from "./re
|
|
|
3
3
|
import { DEFAULT_SANDBOX_CPUS, DEFAULT_SANDBOX_IMAGE, describeNetwork, idleMsOf, memoryMiBOf, resolveNetwork, } from "./policy.js";
|
|
4
4
|
import { parseSandboxPreference, reportSelection, selectSandboxBackend, } from "./select.js";
|
|
5
5
|
import { mountSkill, sandboxSkillsOf, skillSignature } from "./skills.js";
|
|
6
|
+
import { runDiskOp } from "./disk-fs.js";
|
|
6
7
|
import { resolveSandboxPath, runSandboxTool, } from "./tools.js";
|
|
7
8
|
import { SandboxFileTooLargeError, } from "./types.js";
|
|
8
9
|
export { sandboxCapabilityOf } from "./capability.js";
|
|
@@ -89,14 +90,14 @@ export class SandboxManager {
|
|
|
89
90
|
keyOf(sessionId) {
|
|
90
91
|
return sessionWorkspaceKey(this.options.scope, sessionId);
|
|
91
92
|
}
|
|
92
|
-
/** The workspace key of sandbox resource `
|
|
93
|
-
sandboxKeyOf(
|
|
94
|
-
return sandboxWorkspaceKey(this.options.scope,
|
|
93
|
+
/** The workspace key of sandbox resource `environmentId` (`sandboxWorkspaceKey`). */
|
|
94
|
+
sandboxKeyOf(environmentId) {
|
|
95
|
+
return sandboxWorkspaceKey(this.options.scope, environmentId);
|
|
95
96
|
}
|
|
96
97
|
keyFor(session) {
|
|
97
|
-
return session.
|
|
98
|
+
return session.environmentId === undefined
|
|
98
99
|
? this.keyOf(session.id)
|
|
99
|
-
: this.sandboxKeyOf(session.
|
|
100
|
+
: this.sandboxKeyOf(session.environmentId);
|
|
100
101
|
}
|
|
101
102
|
async report() {
|
|
102
103
|
return { ...reportSelection(await this.ready), defaultImage: DEFAULT_SANDBOX_IMAGE };
|
|
@@ -167,6 +168,21 @@ export class SandboxManager {
|
|
|
167
168
|
: { kind: "listed", path: resolved, listing };
|
|
168
169
|
});
|
|
169
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* One operation on the disk of the session's sandbox (R2d step 3, `sandbox/disk-fs.ts`), in the
|
|
173
|
+
* same queue as its tool calls, starting it first when it is stopped.
|
|
174
|
+
*/
|
|
175
|
+
async disk(session, capability, op, signal) {
|
|
176
|
+
return this.withHandle(session, capability, signal, async (handle, live) => {
|
|
177
|
+
if (!handle.diskRoots)
|
|
178
|
+
return {
|
|
179
|
+
kind: "failed",
|
|
180
|
+
code: "sandbox.unsupported",
|
|
181
|
+
message: `The ${live.backend.name} backend has no disk to version`,
|
|
182
|
+
};
|
|
183
|
+
return runDiskOp(handle.diskRoots, op);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
170
186
|
/** Runs `use` on the session's sandbox in its queue, starting it first when it is stopped. */
|
|
171
187
|
async withHandle(session, capability, signal, use) {
|
|
172
188
|
const prepared = await this.prepare(session, capability);
|
|
@@ -234,7 +250,7 @@ export class SandboxManager {
|
|
|
234
250
|
if (!live) {
|
|
235
251
|
live = {
|
|
236
252
|
sessionId: session.id,
|
|
237
|
-
...(session.
|
|
253
|
+
...(session.environmentId === undefined ? {} : { environmentId: session.environmentId }),
|
|
238
254
|
backend,
|
|
239
255
|
idleMs: idleMsOf(capability.sandbox),
|
|
240
256
|
active: 0,
|
|
@@ -268,7 +284,7 @@ export class SandboxManager {
|
|
|
268
284
|
catch (error) {
|
|
269
285
|
const message = error instanceof Error ? error.message : String(error);
|
|
270
286
|
const record = await this.record(spec.key, live, session.id, spec.image, "stopped");
|
|
271
|
-
await this.options.emit(session.id, session.activeTurnId, "
|
|
287
|
+
await this.options.emit(session.id, session.activeTurnId, "environment.state", { state: "stopped", backend: live.backend.name, error: message }, { record, ...claimOf(session) });
|
|
272
288
|
// Nothing ran yet, so a failed start is a definite outcome, not an uncertain one.
|
|
273
289
|
return {
|
|
274
290
|
kind: "failed",
|
|
@@ -286,7 +302,7 @@ export class SandboxManager {
|
|
|
286
302
|
const outcome = await runSandboxTool(live.handle, toolName, (input ?? {}), signal, (value) => {
|
|
287
303
|
report = value;
|
|
288
304
|
}, session.environment);
|
|
289
|
-
await this.options.emit(session.id, session.activeTurnId, "
|
|
305
|
+
await this.options.emit(session.id, session.activeTurnId, "environment.exec", {
|
|
290
306
|
tool: toolName,
|
|
291
307
|
...report,
|
|
292
308
|
...(report.command ? { command: report.command.slice(0, 500) } : {}),
|
|
@@ -347,12 +363,12 @@ export class SandboxManager {
|
|
|
347
363
|
network: describeNetwork(spec.network),
|
|
348
364
|
};
|
|
349
365
|
const creating = await this.record(spec.key, live, session.id, spec.image, "creating", existing);
|
|
350
|
-
await this.options.emit(session.id, session.activeTurnId, "
|
|
366
|
+
await this.options.emit(session.id, session.activeTurnId, "environment.state", { state: "creating", ...payload, ...(existing ? { reattach: true } : {}) }, { record: creating, ...claimOf(session) });
|
|
351
367
|
const handle = await live.backend.open(spec);
|
|
352
368
|
// A handle opened afresh holds no skills this manager knows of.
|
|
353
369
|
live.skills.clear();
|
|
354
370
|
const running = await this.record(spec.key, live, session.id, spec.image, "running", existing);
|
|
355
|
-
await this.options.emit(session.id, session.activeTurnId, "
|
|
371
|
+
await this.options.emit(session.id, session.activeTurnId, "environment.state", {
|
|
356
372
|
state: "running",
|
|
357
373
|
...payload,
|
|
358
374
|
...(existing && handle.created
|
|
@@ -371,7 +387,7 @@ export class SandboxManager {
|
|
|
371
387
|
}
|
|
372
388
|
finally {
|
|
373
389
|
const record = await this.record(key, live, live.sessionId, undefined, "stopped");
|
|
374
|
-
await this.options.emit(live.sessionId, null, "
|
|
390
|
+
await this.options.emit(live.sessionId, null, "environment.state", { state: "stopped", backend: live.backend.name }, { record });
|
|
375
391
|
}
|
|
376
392
|
}
|
|
377
393
|
async record(key, live, sessionId, image, state, known) {
|
|
@@ -380,7 +396,7 @@ export class SandboxManager {
|
|
|
380
396
|
const record = {
|
|
381
397
|
key,
|
|
382
398
|
sessionId,
|
|
383
|
-
...(live.
|
|
399
|
+
...(live.environmentId === undefined ? {} : { environmentId: live.environmentId }),
|
|
384
400
|
backend: live.backend.name,
|
|
385
401
|
image: image ?? existing?.image ?? "",
|
|
386
402
|
state,
|
|
@@ -395,11 +411,11 @@ export class SandboxManager {
|
|
|
395
411
|
return this.options.records.get(this.keyOf(sessionId));
|
|
396
412
|
}
|
|
397
413
|
/**
|
|
398
|
-
* Deletes sandbox resource `
|
|
414
|
+
* Deletes sandbox resource `environmentId`'s workspace, its files and its compute record, after
|
|
399
415
|
* any command queued on it. Called once the resource is deleted.
|
|
400
416
|
*/
|
|
401
|
-
async removeSandbox(
|
|
402
|
-
await this.remove(this.sandboxKeyOf(
|
|
417
|
+
async removeSandbox(environmentId) {
|
|
418
|
+
await this.remove(this.sandboxKeyOf(environmentId));
|
|
403
419
|
}
|
|
404
420
|
/** Deletes the workspace `key`, its files and its record, after any command queued on it. */
|
|
405
421
|
async remove(key) {
|
|
@@ -425,9 +441,9 @@ export class SandboxManager {
|
|
|
425
441
|
for (const key of await backend.list(this.prefix)) {
|
|
426
442
|
const record = await this.options.records.get(key);
|
|
427
443
|
if (record) {
|
|
428
|
-
const kept = record.
|
|
444
|
+
const kept = record.environmentId === undefined
|
|
429
445
|
? await sessionExists(record.sessionId)
|
|
430
|
-
: await sandboxExists(record.
|
|
446
|
+
: await sandboxExists(record.environmentId);
|
|
431
447
|
if (kept)
|
|
432
448
|
continue;
|
|
433
449
|
}
|
|
@@ -1,27 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Placement (F7.2, D38): where a session's harness runs, checked when the session opens and
|
|
3
3
|
* never downgraded afterwards. The Tenant maps harness ids (`nylorun`, or `*` for the rest) to
|
|
4
|
-
* the hosts they may run on (`
|
|
5
|
-
* harness: in process, or the harness container) and `sandbox` (the engine in a pod
|
|
6
|
-
* Default: both.
|
|
4
|
+
* the hosts they may run on (`environments.config` `placement`): `harness-container` (the
|
|
5
|
+
* Runtime's harness: in process, or the harness container) and `sandbox` (the engine in a pod).
|
|
6
|
+
* Default: both. `"*": { hosts: [] }` refuses every harness the map does not name (R4.1).
|
|
7
7
|
*
|
|
8
|
-
* A session
|
|
9
|
-
* `harness-container
|
|
10
|
-
*
|
|
11
|
-
* sandboxes service not ready, it is `409
|
|
8
|
+
* A session on a pod Environment runs on `sandbox`; every other session (a virtual or `none`
|
|
9
|
+
* Environment, or none) on `harness-container` (D52). A host the Tenant does not allow is
|
|
10
|
+
* `409 placement_refused`. A pod Environment needs sandbox pods: without a cluster (`nylorun
|
|
11
|
+
* sandbox enable`), or with the sandboxes service not ready, it is `409 environment_unavailable`.
|
|
12
12
|
*/
|
|
13
|
-
import type
|
|
13
|
+
import { type EnvironmentKind, type PlacementHost as SandboxPlacementHost } from "@nylorun/core/contracts";
|
|
14
|
+
import type { TenantSandboxConfig } from "./tenant-config.js";
|
|
14
15
|
import type { TenantPods } from "../tenant/context.js";
|
|
15
|
-
/**
|
|
16
|
+
/** Our harness's id (blueprint D38): what a session runs unless its Environment names another. */
|
|
16
17
|
export declare const HARNESS_ID = "nylorun";
|
|
17
18
|
export declare const DEFAULT_PLACEMENT: readonly SandboxPlacementHost[];
|
|
18
19
|
/** The hosts harness `harnessId` may run on. */
|
|
19
20
|
export declare function placementOf(config: TenantSandboxConfig, harnessId?: string): readonly SandboxPlacementHost[];
|
|
20
21
|
/** How to enable sandbox pods, for the messages that need them. */
|
|
21
22
|
export declare const ENABLE_PODS = "run `nylorun sandbox enable --context <name>` to give this Tenant a cluster";
|
|
22
|
-
/** Refuses a session whose
|
|
23
|
-
export declare function checkPlacement(config: TenantSandboxConfig, kind:
|
|
23
|
+
/** Refuses a session whose harness, on the host its Environment needs, the Tenant does not allow. */
|
|
24
|
+
export declare function checkPlacement(config: TenantSandboxConfig, kind: EnvironmentKind | undefined, harness?: string): void;
|
|
24
25
|
/** Whether the sandboxes service is up, cached for a few seconds. */
|
|
25
26
|
export declare function podsReady(pods: TenantPods): Promise<boolean>;
|
|
26
|
-
/** Refuses a pod
|
|
27
|
+
/** Refuses a pod Environment without sandbox pods, or with the sandboxes service down. */
|
|
27
28
|
export declare function requirePods(pods: TenantPods | undefined, what: string): Promise<TenantPods>;
|
|
@@ -1,6 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Placement (F7.2, D38): where a session's harness runs, checked when the session opens and
|
|
3
|
+
* never downgraded afterwards. The Tenant maps harness ids (`nylorun`, or `*` for the rest) to
|
|
4
|
+
* the hosts they may run on (`environments.config` `placement`): `harness-container` (the
|
|
5
|
+
* Runtime's harness: in process, or the harness container) and `sandbox` (the engine in a pod).
|
|
6
|
+
* Default: both. `"*": { hosts: [] }` refuses every harness the map does not name (R4.1).
|
|
7
|
+
*
|
|
8
|
+
* A session on a pod Environment runs on `sandbox`; every other session (a virtual or `none`
|
|
9
|
+
* Environment, or none) on `harness-container` (D52). A host the Tenant does not allow is
|
|
10
|
+
* `409 placement_refused`. A pod Environment needs sandbox pods: without a cluster (`nylorun
|
|
11
|
+
* sandbox enable`), or with the sandboxes service not ready, it is `409 environment_unavailable`.
|
|
12
|
+
*/
|
|
13
|
+
import { DEFAULT_HARNESS, } from "@nylorun/core/contracts";
|
|
1
14
|
import { fail } from "../tenant/http.js";
|
|
2
|
-
/**
|
|
3
|
-
export const HARNESS_ID =
|
|
15
|
+
/** Our harness's id (blueprint D38): what a session runs unless its Environment names another. */
|
|
16
|
+
export const HARNESS_ID = DEFAULT_HARNESS;
|
|
4
17
|
export const DEFAULT_PLACEMENT = ["harness-container", "sandbox"];
|
|
5
18
|
/** The hosts harness `harnessId` may run on. */
|
|
6
19
|
export function placementOf(config, harnessId = HARNESS_ID) {
|
|
@@ -9,14 +22,18 @@ export function placementOf(config, harnessId = HARNESS_ID) {
|
|
|
9
22
|
}
|
|
10
23
|
/** How to enable sandbox pods, for the messages that need them. */
|
|
11
24
|
export const ENABLE_PODS = "run `nylorun sandbox enable --context <name>` to give this Tenant a cluster";
|
|
12
|
-
/** Refuses a session whose
|
|
13
|
-
export function checkPlacement(config, kind) {
|
|
25
|
+
/** Refuses a session whose harness, on the host its Environment needs, the Tenant does not allow. */
|
|
26
|
+
export function checkPlacement(config, kind, harness = HARNESS_ID) {
|
|
14
27
|
const host = kind === "pod" ? "sandbox" : "harness-container";
|
|
15
|
-
const allowed = placementOf(config);
|
|
16
|
-
if (
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
28
|
+
const allowed = placementOf(config, harness);
|
|
29
|
+
if (allowed.includes(host))
|
|
30
|
+
return;
|
|
31
|
+
const where = allowed.length === 0 ? "it may run nowhere" : `placement allows ${allowed.join(", ")}`;
|
|
32
|
+
fail(409, allowed.length === 0
|
|
33
|
+
? `This Tenant does not run the ${harness} harness (${where}).`
|
|
34
|
+
: kind === "pod"
|
|
35
|
+
? `This Tenant does not let the ${harness} harness run in a sandbox pod (${where}).`
|
|
36
|
+
: `This Tenant runs the ${harness} harness only in sandbox pods (${where}): open the session on a pod Environment.`, { code: "placement_refused", details: { harness, host, allowed: [...allowed] } });
|
|
20
37
|
}
|
|
21
38
|
const READY_CACHE_MS = 5_000;
|
|
22
39
|
const ready = new WeakMap();
|
|
@@ -29,15 +46,15 @@ export async function podsReady(pods) {
|
|
|
29
46
|
ready.set(pods, { at: Date.now(), ready: answer });
|
|
30
47
|
return answer;
|
|
31
48
|
}
|
|
32
|
-
/** Refuses a pod
|
|
49
|
+
/** Refuses a pod Environment without sandbox pods, or with the sandboxes service down. */
|
|
33
50
|
export async function requirePods(pods, what) {
|
|
34
51
|
if (!pods)
|
|
35
52
|
return fail(409, `${what} needs sandbox pods, and this Runtime has no cluster: ${ENABLE_PODS}.`, {
|
|
36
|
-
code: "
|
|
53
|
+
code: "environment_unavailable",
|
|
37
54
|
});
|
|
38
55
|
if (!(await podsReady(pods)))
|
|
39
56
|
fail(409, `${what} needs sandbox pods, and the sandboxes service is not ready. Check \`nylorun sandbox status\`.`, {
|
|
40
|
-
code: "
|
|
57
|
+
code: "environment_unavailable",
|
|
41
58
|
details: { retryAfterSeconds: 5 },
|
|
42
59
|
}, { "retry-after": "5" });
|
|
43
60
|
return pods;
|
|
@@ -13,7 +13,12 @@ export interface PodSpec {
|
|
|
13
13
|
readonly mode: "Running" | "Suspended";
|
|
14
14
|
/** The workload image; the service defaults it to `python:3.13-slim`. */
|
|
15
15
|
readonly image?: string;
|
|
16
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* What the workload runs: `engine` (default), our engine copied from `harnessImage`, or
|
|
18
|
+
* `image`, the image's own entrypoint, for a custom harness built into it (R4.4).
|
|
19
|
+
*/
|
|
20
|
+
readonly entrypoint?: "engine" | "image";
|
|
21
|
+
/** The Runtime image the engine is copied from; not with `entrypoint: "image"`. */
|
|
17
22
|
readonly harnessImage?: string;
|
|
18
23
|
/** Replaces the engine command (diagnostics). */
|
|
19
24
|
readonly command?: readonly string[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* A pod
|
|
2
|
+
* A pod Environment's lifecycle (blueprint D34, D36), as one pure decision: from the row, the
|
|
3
3
|
* Tenant's lifecycle settings, the Sandbox's status as the sandboxes service sees it and the
|
|
4
4
|
* clock, what to ask of the service, what to write to the row, which events to record, and
|
|
5
5
|
* when to look again. `reconcile.ts` gathers the facts, carries the decision out and runs it
|
|
@@ -12,42 +12,43 @@
|
|
|
12
12
|
* - **Every change** the Runtime asks for bumps `rev`; the `PUT` carries `opId =
|
|
13
13
|
* <name>.r<rev>`, so a repeated apply is a no-op at the service, and a Sandbox whose `opId`
|
|
14
14
|
* is not the row's is applied again.
|
|
15
|
-
* - **Idle** (`trigger: idle`): no turn on
|
|
16
|
-
* Tenant's `limits.idle` → `Suspended` (`
|
|
17
|
-
* kept; the next turn start asks for `Running` again.
|
|
18
|
-
* - **TTL**: the Sandbox carries `shutdownTime = createdAt + lifecycle.ttl` and the Tenant's
|
|
19
|
-
* `onExpiry` as its `shutdownPolicy`. Once it passes, the sandbox is `expired`
|
|
20
|
-
* (`sandbox.expired`), its host epoch moves (its tokens stop working) and turns are refused
|
|
21
|
-
* (`sandbox_expired`); with `onExpiry: delete` it is deleted. A `PUT` with a longer TTL moves
|
|
22
|
-
* `expiresAt`, and the next apply revives it with the later `shutdownTime`.
|
|
15
|
+
* - **Idle** (`trigger: idle`): no turn on a session that names it, and none ended within its
|
|
16
|
+
* `lifecycle.idle` (at most the Tenant's `limits.idle`) → `Suspended` (`environment.suspended`,
|
|
17
|
+
* reason `idle`). The volume is kept; the next turn start asks for `Running` again.
|
|
23
18
|
* - **Lost**: an incarnation that has run (a pod joined) whose Sandbox is gone or whose volume
|
|
24
|
-
* is missing is `lost` (`
|
|
25
|
-
* only a reset brings the
|
|
26
|
-
* engine's join also checks the id it keeps on the volume (`join.ts`): a new volume is
|
|
27
|
-
* too. A pod replaced on the same volume is not lost: the new pod's join bumps the epoch
|
|
28
|
-
* (`
|
|
19
|
+
* is missing is `lost` (`environment.lost`): its host epoch moves, its Sandbox is deleted, and
|
|
20
|
+
* only a reset brings the Environment back. agent-sandbox recreates a deleted claim empty, so
|
|
21
|
+
* the engine's join also checks the id it keeps on the volume (`join.ts`): a new volume is
|
|
22
|
+
* lost too. A pod replaced on the same volume is not lost: the new pod's join bumps the epoch
|
|
23
|
+
* (`environment.relaunched`).
|
|
29
24
|
* - **Delete** (`desired: deleted`): the Sandbox is deleted, and the row once it is gone.
|
|
30
|
-
* - **Reset** (`tenant/
|
|
25
|
+
* - **Reset** (`tenant/environments.ts`) bumps the volume generation: a new name, so a new
|
|
31
26
|
* Sandbox and volume; the old name is `retiring` and deleted here.
|
|
27
|
+
*
|
|
28
|
+
* - **Setup** (R2d step 2): a failed setup (`sandbox/setup.ts`) leaves the pod `failed` with a
|
|
29
|
+
* reason that starts `setup:`, even while the pod is ready, until a turn start asks it to run.
|
|
30
|
+
*
|
|
31
|
+
* An unused Environment is deleted by the Tenant sweep (`lifecycle.deleteAfter`), not here.
|
|
32
32
|
*/
|
|
33
|
-
import type {
|
|
34
|
-
import type { SandboxPodPatch, SandboxPodState
|
|
33
|
+
import type { EnvironmentEventPayload, EnvironmentEventType } from "@nylorun/core/contracts";
|
|
34
|
+
import type { Environment, SandboxPodPatch, SandboxPodState } from "../../store/types.js";
|
|
35
35
|
import type { PodStatus } from "./client.js";
|
|
36
|
+
/** A setup failure's `reason` starts with this (`sandbox/setup.ts`); it is sticky. */
|
|
37
|
+
export declare const SETUP_FAILED = "setup:";
|
|
36
38
|
/** A pod not ready this long after it was asked to run has failed. */
|
|
37
39
|
export declare const READY_DEADLINE_MS: number;
|
|
38
40
|
/** How soon to look again while the pod changes. */
|
|
39
41
|
export declare const POLL_MS = 2000;
|
|
40
42
|
/** How soon to look again when the sandboxes service does not answer. */
|
|
41
43
|
export declare const UNREACHABLE_RETRY_MS = 5000;
|
|
42
|
-
/** The
|
|
44
|
+
/** The lifecycle settings (D36), read each time a decision is made. */
|
|
43
45
|
export interface PodLifecycleConfig {
|
|
44
|
-
readonly onExpiry: "retain" | "delete";
|
|
45
46
|
readonly idleMs: number;
|
|
46
47
|
}
|
|
47
|
-
/** Why the decision is made: a reconcile, or
|
|
48
|
-
export type PodTrigger = "reconcile" | "idle"
|
|
49
|
-
export type PodTimer = "idle"
|
|
50
|
-
export type PodRow =
|
|
48
|
+
/** Why the decision is made: a reconcile, or the idle timer firing. */
|
|
49
|
+
export type PodTrigger = "reconcile" | "idle";
|
|
50
|
+
export type PodTimer = "idle";
|
|
51
|
+
export type PodRow = Environment & {
|
|
51
52
|
readonly pod: SandboxPodState;
|
|
52
53
|
};
|
|
53
54
|
export interface PodFacts {
|
|
@@ -57,14 +58,14 @@ export interface PodFacts {
|
|
|
57
58
|
readonly status: PodStatus | undefined;
|
|
58
59
|
/** The retiring Sandbox's status, when the row names one. */
|
|
59
60
|
readonly retiring?: PodStatus | undefined;
|
|
60
|
-
/** A session
|
|
61
|
+
/** A session that names the Environment has a turn running. */
|
|
61
62
|
readonly busy: boolean;
|
|
62
63
|
readonly now: number;
|
|
63
64
|
readonly trigger: PodTrigger;
|
|
64
65
|
}
|
|
65
|
-
export interface PodEvent<T extends
|
|
66
|
+
export interface PodEvent<T extends EnvironmentEventType = EnvironmentEventType> {
|
|
66
67
|
readonly type: T;
|
|
67
|
-
readonly payload:
|
|
68
|
+
readonly payload: EnvironmentEventPayload<T>;
|
|
68
69
|
}
|
|
69
70
|
export interface PodDecision {
|
|
70
71
|
/** Apply the row's spec (after `patch`) in this mode; `rotateJoin` mints a new join token. */
|
|
@@ -76,7 +77,7 @@ export interface PodDecision {
|
|
|
76
77
|
readonly delete: readonly string[];
|
|
77
78
|
readonly patch: SandboxPodPatch;
|
|
78
79
|
readonly events: readonly PodEvent[];
|
|
79
|
-
/** The Sandbox is gone and the
|
|
80
|
+
/** The Sandbox is gone and the Environment deleted: remove the row. */
|
|
80
81
|
readonly removeRow?: true;
|
|
81
82
|
/** Look again this soon. */
|
|
82
83
|
readonly retryAfterMs?: number;
|