@nylorun/runtime 0.23.0-beta → 0.25.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 +252 -0
- package/README.md +87 -28
- package/dist/adapters/execution/restate.js +1 -1
- 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/api/a2a/card.d.ts +11 -1
- package/dist/api/a2a/card.js +26 -2
- package/dist/api/a2a/endpoint.js +9 -3
- package/dist/api/a2a/protocol.d.ts +25 -2
- package/dist/api/a2a/protocol.js +20 -3
- package/dist/api/a2a/routes.d.ts +6 -1
- package/dist/api/a2a/routes.js +67 -9
- package/dist/api/a2a/tasks.js +2 -1
- package/dist/api/ag-ui/endpoint.js +8 -3
- package/dist/api/ag-ui/history.d.ts +6 -1
- package/dist/api/ag-ui/history.js +39 -3
- package/dist/api/ag-ui/routes.d.ts +4 -1
- package/dist/api/ag-ui/routes.js +63 -20
- package/dist/api/ag-ui/translate.js +3 -2
- package/dist/api/components.d.ts +5327 -1091
- package/dist/api/components.js +40 -16
- package/dist/api/gate/keys.d.ts +2 -1
- package/dist/api/gate/keys.js +14 -1
- package/dist/api/gate/routes.d.ts +6 -0
- package/dist/api/gate/routes.js +52 -3
- package/dist/api/http/app.js +17 -6
- package/dist/api/http/body.d.ts +3 -3
- package/dist/api/http/body.js +7 -7
- 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/environment-manifests.d.ts +9 -0
- package/dist/api/http/routes/environment-manifests.js +118 -0
- package/dist/api/http/routes/environments.d.ts +11 -0
- package/dist/api/http/routes/environments.js +332 -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 +94 -9
- package/dist/api/http/routes/reads.js +54 -5
- package/dist/api/http/routes/sessions.js +24 -9
- package/dist/api/http/routes/tenant.js +65 -15
- package/dist/api/inbound-files.d.ts +65 -0
- package/dist/api/inbound-files.js +115 -0
- package/dist/api/openapi.js +48 -28
- package/dist/artifacts/disk.d.ts +59 -0
- package/dist/artifacts/disk.js +569 -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/media-types.d.ts +15 -0
- package/dist/artifacts/media-types.js +56 -4
- package/dist/artifacts/parts.d.ts +12 -3
- package/dist/artifacts/parts.js +15 -4
- package/dist/artifacts/service.d.ts +2 -1
- package/dist/artifacts/service.js +6 -1
- package/dist/artifacts/workspace.d.ts +5 -5
- package/dist/artifacts/workspace.js +7 -19
- package/dist/core/flow-host.d.ts +3 -4
- package/dist/core/flow-host.js +5 -2
- 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 +11 -44
- package/dist/core/turn-manifest.d.ts +3 -2
- package/dist/core/turn-manifest.js +29 -5
- package/dist/execution/types.d.ts +1 -1
- 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 +22 -1
- package/dist/gates/egress.js +116 -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 +9 -6
- package/dist/gates/http-tool.js +11 -17
- 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 +51 -0
- package/dist/gates/mcp-handler.d.ts +4 -0
- package/dist/gates/mcp-handler.js +7 -4
- 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 +132 -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 +11 -69
- package/dist/gates/tool-contract.js +1 -85
- package/dist/gates/tool-gate.d.ts +1 -1
- package/dist/harness/calls.d.ts +14 -7
- package/dist/harness/calls.js +30 -24
- package/dist/harness/executors.js +12 -9
- package/dist/harness/main.js +24 -5
- package/dist/harness/pod.d.ts +39 -0
- package/dist/harness/pod.js +46 -0
- package/dist/harness/service.d.ts +0 -1
- package/dist/harness/service.js +17 -9
- package/dist/harness/setup.d.ts +29 -0
- package/dist/harness/setup.js +174 -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 +180 -50
- package/dist/harness-api/redact.d.ts +3 -0
- package/dist/harness-api/redact.js +45 -0
- package/dist/harness-api/server.d.ts +35 -4
- package/dist/harness-api/server.js +130 -17
- 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 +105 -0
- package/dist/harness-api/tool-journal.js +132 -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 +12 -7
- package/dist/harness-api/workspace.js +47 -25
- package/dist/harness-api/ws-server.d.ts +11 -3
- package/dist/harness-api/ws-server.js +94 -15
- package/dist/host/gates.d.ts +2 -1
- package/dist/host/gates.js +10 -2
- package/dist/host/main.js +54 -5
- package/dist/host/operate.js +67 -5
- package/dist/host/stack-config.d.ts +64 -0
- package/dist/host/stack-config.js +62 -2
- package/dist/keys/client.js +4 -1
- package/dist/keys/keys.d.ts +13 -6
- package/dist/keys/keys.js +9 -8
- package/dist/management-openapi.json +1264 -2171
- package/dist/mcp/connect.d.ts +26 -22
- package/dist/mcp/connect.js +30 -34
- package/dist/mcp/pool.d.ts +3 -6
- package/dist/mcp/pool.js +14 -28
- package/dist/mcp/preview.d.ts +3 -3
- package/dist/mcp/preview.js +10 -21
- package/dist/mcp/snapshot.d.ts +16 -24
- package/dist/mcp/snapshot.js +33 -45
- package/dist/model/capabilities.d.ts +26 -0
- package/dist/model/capabilities.js +44 -0
- package/dist/model/catalog.d.ts +34 -3
- package/dist/model/catalog.js +38 -2
- package/dist/model/models.d.ts +2 -0
- package/dist/model/models.js +1 -1
- package/dist/model/native-blocks.d.ts +35 -0
- package/dist/model/native-blocks.js +170 -0
- package/dist/model/pi-model.js +114 -94
- package/dist/openapi.json +12822 -3943
- 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/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 -15
- package/dist/sandbox/manager.d.ts +47 -21
- package/dist/sandbox/manager.js +162 -107
- package/dist/sandbox/placement.d.ts +14 -13
- package/dist/sandbox/placement.js +31 -14
- package/dist/sandbox/pods/client.d.ts +9 -2
- package/dist/sandbox/pods/lifecycle.d.ts +36 -27
- package/dist/sandbox/pods/lifecycle.js +29 -26
- package/dist/sandbox/pods/reconcile.js +18 -12
- package/dist/sandbox/pods/spec.d.ts +15 -13
- package/dist/sandbox/pods/spec.js +17 -20
- package/dist/sandbox/records.d.ts +2 -2
- package/dist/sandbox/records.js +3 -3
- package/dist/sandbox/resolve.d.ts +37 -27
- package/dist/sandbox/resolve.js +73 -41
- package/dist/sandbox/runtime-tools.d.ts +9 -0
- package/dist/sandbox/runtime-tools.js +16 -0
- package/dist/sandbox/session-sandbox.d.ts +4 -6
- package/dist/sandbox/session-sandbox.js +47 -94
- package/dist/sandbox/setup.d.ts +64 -0
- package/dist/sandbox/setup.js +160 -0
- package/dist/sandbox/share.d.ts +21 -26
- package/dist/sandbox/share.js +36 -63
- package/dist/sandbox/skills.d.ts +8 -6
- package/dist/sandbox/skills.js +24 -23
- 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/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/0024_flat_manifest.sql +7 -0
- package/dist/store/postgres/drizzle/0025_environment_manifests.sql +14 -0
- package/dist/store/postgres/drizzle/0026_environment_forks.sql +1 -0
- package/dist/store/postgres/drizzle/0027_environment_image_digest.sql +1 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +56 -0
- package/dist/store/postgres/reads.js +123 -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 +1126 -736
- package/dist/store/postgres/schema.js +136 -73
- package/dist/store/postgres/store.js +195 -67
- package/dist/store/types.d.ts +165 -50
- package/dist/tenant/advance.d.ts +7 -6
- package/dist/tenant/advance.js +118 -42
- package/dist/tenant/artifact-tool.d.ts +1 -1
- package/dist/tenant/artifact-tool.js +46 -26
- package/dist/tenant/commands.d.ts +15 -2
- package/dist/tenant/commands.js +115 -14
- package/dist/tenant/context.d.ts +56 -24
- package/dist/tenant/context.js +4 -15
- package/dist/tenant/effects.d.ts +1 -1
- package/dist/tenant/effects.js +44 -29
- package/dist/tenant/environment-manifests.d.ts +57 -0
- package/dist/tenant/environment-manifests.js +200 -0
- package/dist/tenant/environments.d.ts +174 -0
- package/dist/tenant/environments.js +1044 -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/model-setting.d.ts +14 -0
- package/dist/tenant/model-setting.js +51 -0
- 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 +7 -1
- package/dist/tenant/session.d.ts +3 -3
- package/dist/tenant/session.js +0 -14
- package/dist/tenant/sessions.d.ts +18 -14
- package/dist/tenant/sessions.js +99 -143
- package/dist/tenant/signing-keys.d.ts +5 -1
- package/dist/tenant/skill-tool.d.ts +3 -3
- package/dist/tenant/skill-tool.js +6 -11
- package/dist/tenant/status.js +3 -3
- package/dist/tenant/sweep.js +13 -7
- package/dist/tenant/tool-results.js +1 -1
- package/dist/tenant/tool-search.d.ts +7 -11
- package/dist/tenant/tool-search.js +8 -5
- package/dist/tenant/transcript.d.ts +245 -36
- package/dist/tenant/transcript.js +64 -12
- 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 +41 -0
- package/dist/traces/ids.js +81 -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 +289 -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 +62 -0
- package/dist/vault/error.d.ts +11 -1
- package/dist/vault/error.js +5 -1
- package/dist/vault/host-model.d.ts +4 -1
- package/dist/vault/host-model.js +10 -2
- package/dist/vault/service.d.ts +92 -48
- package/dist/vault/service.js +462 -376
- 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 +13 -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/sandbox/capability.d.ts +0 -3
- package/dist/sandbox/capability.js +0 -10
- package/dist/tenant/sandboxes.d.ts +0 -123
- package/dist/tenant/sandboxes.js +0 -566
package/dist/sandbox/resolve.js
CHANGED
|
@@ -1,50 +1,91 @@
|
|
|
1
|
+
import { DEFAULT_HARNESS } from "@nylorun/core/contracts";
|
|
2
|
+
import { parseSandboxDuration } from "@nylorun/core/define";
|
|
1
3
|
import { memoryMiB } from "./tenant-config.js";
|
|
2
|
-
|
|
3
|
-
export function
|
|
4
|
-
const {
|
|
5
|
-
if (request === false)
|
|
6
|
-
return { kind: "none" };
|
|
7
|
-
if (request === undefined) {
|
|
8
|
-
const fallback = config.default;
|
|
9
|
-
if (fallback === "none")
|
|
10
|
-
return { kind: "none" };
|
|
11
|
-
return build(fallback === "virtual" ? {} : fallback, "default", input);
|
|
12
|
-
}
|
|
13
|
-
if (input.actingForSubject)
|
|
14
|
-
return { kind: "error", status: 403, errors: [SUBJECT_INLINE_MESSAGE] };
|
|
15
|
-
return build(request, "inline", input);
|
|
16
|
-
}
|
|
17
|
-
function build(request, source, input) {
|
|
18
|
-
const { limits } = input.config;
|
|
4
|
+
/** The recipe `request` asks for an Environment of `kind`, or every reason it cannot have it. */
|
|
5
|
+
export function resolveRecipe(kind, request, config) {
|
|
6
|
+
const { limits } = config;
|
|
19
7
|
const errors = [];
|
|
20
|
-
const pod =
|
|
8
|
+
const pod = kind === "pod";
|
|
9
|
+
// Any harness id (R4.1): placement decides where it may run, when a session opens.
|
|
10
|
+
const harness = request.harness ?? DEFAULT_HARNESS;
|
|
11
|
+
if (pod && harness !== DEFAULT_HARNESS && request.image === undefined)
|
|
12
|
+
errors.push(`harness ${harness} needs image: a pod Environment runs its image as the harness.`);
|
|
13
|
+
const lifecycle = request.lifecycle ?? {};
|
|
14
|
+
const idle = lifecycle.idle === undefined ? undefined : parseSandboxDuration(lifecycle.idle);
|
|
15
|
+
if (idle !== undefined && idle > (parseSandboxDuration(limits.idle) ?? Infinity))
|
|
16
|
+
errors.push(`lifecycle.idle is ${lifecycle.idle}; this Tenant allows at most ${limits.idle}.`);
|
|
17
|
+
if (kind === "none") {
|
|
18
|
+
for (const field of ["image", "resources", "storage", "network", "setup", "files"])
|
|
19
|
+
if (request[field] !== undefined)
|
|
20
|
+
errors.push(`${field} is for an Environment with a sandbox: kind none has none.`);
|
|
21
|
+
if (errors.length > 0)
|
|
22
|
+
return { kind: "error", errors };
|
|
23
|
+
return { kind: "recipe", recipe: { harness, lifecycle: { ...lifecycle }, ...slotsOf(request) } };
|
|
24
|
+
}
|
|
21
25
|
if (request.image !== undefined && !pod)
|
|
22
|
-
errors.push("
|
|
23
|
-
|
|
26
|
+
errors.push("image is for kind pod: a virtual Environment runs no images.");
|
|
27
|
+
if (request.storage !== undefined && !pod)
|
|
28
|
+
errors.push("storage is for kind pod: a virtual Environment has no volume.");
|
|
29
|
+
for (const field of ["setup", "files"])
|
|
30
|
+
if (request[field] !== undefined && request[field].length > 0 && !pod)
|
|
31
|
+
errors.push(`${field} is for kind pod: it runs when a pod's disk is made or reset.`);
|
|
32
|
+
const allow = (request.network?.allowedHosts ?? []).map((host) => host.toLowerCase());
|
|
24
33
|
for (const host of allow) {
|
|
25
34
|
if (!withinCeiling(host, limits.network))
|
|
26
|
-
errors.push(`
|
|
35
|
+
errors.push(`network.allowedHosts includes ${host}, which this Tenant does not allow.`);
|
|
27
36
|
else if (host.startsWith("*.") && !pod)
|
|
28
|
-
errors.push(`
|
|
37
|
+
errors.push(`network.allowedHosts includes ${host}; a virtual Environment allows exact host names only, such as ${host.slice(2)}.`);
|
|
29
38
|
}
|
|
30
39
|
const cpus = request.resources?.cpus ?? limits.defaultResources.cpus;
|
|
31
40
|
const memory = request.resources?.memory === undefined
|
|
32
41
|
? limits.defaultResources.memoryMiB
|
|
33
42
|
: memoryMiB(request.resources.memory);
|
|
34
43
|
if (cpus > limits.resources.cpus)
|
|
35
|
-
errors.push(`
|
|
44
|
+
errors.push(`resources.cpus asks for ${cpus}; this Tenant allows at most ${limits.resources.cpus}.`);
|
|
36
45
|
if (memory > limits.resources.memoryMiB)
|
|
37
|
-
errors.push(`
|
|
46
|
+
errors.push(`resources.memory asks for ${memory}MiB; this Tenant allows at most ${limits.resources.memoryMiB}MiB.`);
|
|
38
47
|
if (errors.length > 0)
|
|
39
|
-
return { kind: "error",
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
return { kind: "error", errors };
|
|
49
|
+
return {
|
|
50
|
+
kind: "recipe",
|
|
51
|
+
recipe: {
|
|
52
|
+
...(request.image === undefined ? {} : { image: request.image }),
|
|
53
|
+
resources: { cpus, memory: `${memory}MiB` },
|
|
54
|
+
...(request.storage === undefined ? {} : { storage: request.storage }),
|
|
55
|
+
network: { allowedHosts: [...new Set(allow)].sort() },
|
|
56
|
+
harness,
|
|
57
|
+
lifecycle: { ...lifecycle },
|
|
58
|
+
...(pod && request.setup?.length ? { setup: request.setup.map((command) => ({ ...command })) } : {}),
|
|
59
|
+
...(pod && request.files?.length ? { files: request.files.map((file) => ({ ...file })) } : {}),
|
|
60
|
+
...slotsOf(request),
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** The request's secret slots, when it declares any. */
|
|
65
|
+
function slotsOf(request) {
|
|
66
|
+
return request.secrets !== undefined && Object.keys(request.secrets).length > 0
|
|
67
|
+
? { secrets: structuredClone(request.secrets) }
|
|
68
|
+
: {};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The sandbox spec of a recipe: what a session's `runtime` block pins and the virtual
|
|
72
|
+
* workspace runs. Idle is the Environment's `lifecycle.idle`, else the Tenant's.
|
|
73
|
+
*/
|
|
74
|
+
export function sandboxSpecOf(recipe, tenantIdle) {
|
|
75
|
+
const allow = recipe.network?.allowedHosts ?? [];
|
|
76
|
+
return {
|
|
77
|
+
...(recipe.image === undefined ? {} : { image: recipe.image }),
|
|
78
|
+
network: { preset: "none", ...(allow.length > 0 ? { allow: [...allow] } : {}) },
|
|
79
|
+
...(recipe.resources === undefined ? {} : { resources: { ...recipe.resources } }),
|
|
80
|
+
idle: recipe.lifecycle.idle ?? tenantIdle,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/** The pod's spec of a recipe: its sandbox spec and its volume's size. */
|
|
84
|
+
export function podSandboxSpecOf(recipe, tenantIdle) {
|
|
85
|
+
return {
|
|
86
|
+
...sandboxSpecOf(recipe, tenantIdle),
|
|
87
|
+
...(recipe.storage === undefined ? {} : { storage: recipe.storage }),
|
|
46
88
|
};
|
|
47
|
-
return { kind: "sandbox", spec, source };
|
|
48
89
|
}
|
|
49
90
|
/** Whether `host` (a name or `*.suffix` pattern) is inside one of the ceiling's patterns. */
|
|
50
91
|
export function withinCeiling(host, ceiling) {
|
|
@@ -69,14 +110,5 @@ export function sandboxConfigErrors(config) {
|
|
|
69
110
|
errors.push("limits.defaultResources.cpus is above limits.resources.cpus.");
|
|
70
111
|
if (limits.defaultResources.memoryMiB > limits.resources.memoryMiB)
|
|
71
112
|
errors.push("limits.defaultResources.memory is above limits.resources.memory.");
|
|
72
|
-
if (config.default !== "none" && config.default !== "virtual") {
|
|
73
|
-
const resolved = resolveSandbox({
|
|
74
|
-
request: undefined,
|
|
75
|
-
config,
|
|
76
|
-
actingForSubject: false,
|
|
77
|
-
});
|
|
78
|
-
if (resolved.kind === "error")
|
|
79
|
-
errors.push(...resolved.errors.map((message) => `default: ${message}`));
|
|
80
|
-
}
|
|
81
113
|
return errors;
|
|
82
114
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type AgentManifest, type SandboxManifest } from "@nylorun/core/define";
|
|
2
|
+
/**
|
|
3
|
+
* The session's sandbox spec when `toolName` is one of the sandbox tools the Runtime pinned on
|
|
4
|
+
* `manifest` (its `runtime` block); undefined for any other tool. A tool's name alone says whose
|
|
5
|
+
* it is: an author tool may not take a Runtime tool's name.
|
|
6
|
+
*/
|
|
7
|
+
export declare function sandboxToolSpec(manifest: AgentManifest | undefined, toolName: string | undefined): SandboxManifest | undefined;
|
|
8
|
+
/** True when `toolName` is a tool the Runtime pinned on `manifest` (its `runtime.tools`). */
|
|
9
|
+
export declare function isPinnedRuntimeTool(manifest: AgentManifest | undefined, toolName: string | undefined): boolean;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { isSandboxToolName, runtimeToolOf, } from "@nylorun/core/define";
|
|
2
|
+
/**
|
|
3
|
+
* The session's sandbox spec when `toolName` is one of the sandbox tools the Runtime pinned on
|
|
4
|
+
* `manifest` (its `runtime` block); undefined for any other tool. A tool's name alone says whose
|
|
5
|
+
* it is: an author tool may not take a Runtime tool's name.
|
|
6
|
+
*/
|
|
7
|
+
export function sandboxToolSpec(manifest, toolName) {
|
|
8
|
+
if (!toolName || !isSandboxToolName(toolName))
|
|
9
|
+
return undefined;
|
|
10
|
+
const sandbox = manifest?.runtime?.sandbox;
|
|
11
|
+
return sandbox !== undefined && runtimeToolOf(manifest, toolName) !== undefined ? sandbox : undefined;
|
|
12
|
+
}
|
|
13
|
+
/** True when `toolName` is a tool the Runtime pinned on `manifest` (its `runtime.tools`). */
|
|
14
|
+
export function isPinnedRuntimeTool(manifest, toolName) {
|
|
15
|
+
return runtimeToolOf(manifest, toolName) !== undefined;
|
|
16
|
+
}
|
|
@@ -7,12 +7,10 @@ export type SandboxedManifest = {
|
|
|
7
7
|
readonly ok: false;
|
|
8
8
|
readonly message: string;
|
|
9
9
|
};
|
|
10
|
-
/** The agent's manifest with the sandbox
|
|
11
|
-
export declare function
|
|
10
|
+
/** The agent's pinned manifest with the session's sandbox, or why it cannot take one. */
|
|
11
|
+
export declare function withSandbox(manifest: AgentManifest, spec: SandboxManifest): SandboxedManifest;
|
|
12
12
|
/**
|
|
13
13
|
* The pinned manifest of an agent session without a sandbox: `read_artifact` for each agent of
|
|
14
|
-
* the tree
|
|
15
|
-
* a remote MCP server (R2b C10). Undefined when nothing is added, and the session pins its
|
|
16
|
-
* definition as it is.
|
|
14
|
+
* the tree (R2b C11, protocol 12), and the skill tools for each with skills.
|
|
17
15
|
*/
|
|
18
|
-
export declare function
|
|
16
|
+
export declare function withRuntimeTools(manifest: AgentManifest): SandboxedManifest;
|
|
@@ -1,124 +1,77 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Gives a session the sandbox chosen when it was opened, and the
|
|
3
|
-
*
|
|
2
|
+
* Gives a session the sandbox chosen when it was opened, and the Runtime tools its agents need.
|
|
3
|
+
* An agent session's pinned manifest is its definition with a `runtime` block
|
|
4
|
+
* (`runtimeManifestFor`) on each agent of the tree:
|
|
4
5
|
*
|
|
5
|
-
* - with a sandbox, the
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* -
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* - for each agent of the tree with a remote MCP server, the `nylorun.tools` capability (R2b
|
|
14
|
-
* C10), empty: when the session defers some of the agent's MCP tools, its session tools put
|
|
15
|
-
* `tool_search` and `tool_call` there. An agent that declares either name defers nothing.
|
|
6
|
+
* - with a sandbox, the six sandbox tools, their instructions and the resolved spec, and
|
|
7
|
+
* `save_artifact`, so files the agent makes reach the user (F8.1). The agents it uses as tools
|
|
8
|
+
* get the same spec, so the tree shares one sandbox. The sandbox's instructions name the
|
|
9
|
+
* agent's skills' folders (`/skills/<name>@<hash>/`);
|
|
10
|
+
* - with or without a sandbox, `read_artifact` (R2b C11, protocol 12): the Runtime stores a result
|
|
11
|
+
* of an MCP or HTTP tool too large to show as an artifact, and the model reads it in pages; a
|
|
12
|
+
* file of an earlier turn is a reference line the model reads again with it;
|
|
13
|
+
* - with skills, `load_skill`, `read_skill_resource` and the skill catalog.
|
|
16
14
|
*
|
|
17
|
-
*
|
|
15
|
+
* `tool_search` and `tool_call` are not pinned: they are session tools, given per run when the
|
|
16
|
+
* session defers some MCP tools (R2b C10). An author tool may not take any of these names (the
|
|
17
|
+
* manifest schema refuses it), so a tool's name alone says whose it is. The definition in the
|
|
18
|
+
* registry never changes.
|
|
18
19
|
*/
|
|
19
20
|
import { AgentManifestSchema } from "@nylorun/core/contracts";
|
|
20
|
-
import {
|
|
21
|
-
import { sandboxSpecOf } from "./share.js";
|
|
21
|
+
import { hashManifest, runtimeManifestFor, } from "@nylorun/core/define";
|
|
22
22
|
function isAgentManifest(value) {
|
|
23
23
|
return (typeof value === "object" &&
|
|
24
24
|
value !== null &&
|
|
25
25
|
value.kind !== "workflow" &&
|
|
26
|
-
|
|
26
|
+
typeof value.id === "string");
|
|
27
27
|
}
|
|
28
|
-
/**
|
|
29
|
-
function
|
|
30
|
-
return manifest.capabilities.flatMap((capability) => Object.keys(capability.skills ?? {})).sort();
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* True when the agent itself declares a remote MCP server or an HTTP tool, whose results the
|
|
34
|
-
* Runtime may store as artifacts (R2b C11), and no tool of its own is named `read_artifact`.
|
|
35
|
-
*/
|
|
36
|
-
function readsStoredResults(manifest) {
|
|
37
|
-
const remote = manifest.capabilities.some((capability) => Object.keys(capability.mcpServers ?? {}).length > 0 ||
|
|
38
|
-
(capability.tools ?? []).some((tool) => tool.http !== undefined));
|
|
39
|
-
const taken = manifest.capabilities.some((capability) => (capability.tools ?? []).some((tool) => tool.name === READ_ARTIFACT_TOOL));
|
|
40
|
-
return remote && !taken;
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* True when the agent itself declares a remote MCP server, some of whose tools the session may
|
|
44
|
-
* defer (R2b C10), and names no tool `tool_search` or `tool_call` (it then keeps every tool in
|
|
45
|
-
* its list).
|
|
46
|
-
*/
|
|
47
|
-
function defersTools(manifest) {
|
|
48
|
-
const servers = manifest.capabilities.some((capability) => Object.keys(capability.mcpServers ?? {}).length > 0);
|
|
49
|
-
const taken = manifest.capabilities.some((capability) => capability.id === TOOLS_CAPABILITY_ID ||
|
|
50
|
-
(capability.tools ?? []).some((tool) => tool.name === TOOL_SEARCH_TOOL || tool.name === TOOL_CALL_TOOL));
|
|
51
|
-
return servers && !taken;
|
|
52
|
-
}
|
|
53
|
-
/** `manifest` with the session's capabilities added; the same object when none are. */
|
|
54
|
-
function addCapabilities(manifest, spec) {
|
|
28
|
+
/** `manifest` with its `runtime` block, and each agent it uses as a tool with its own. */
|
|
29
|
+
function pinRuntime(manifest, spec) {
|
|
55
30
|
let changed = false;
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return tool;
|
|
65
|
-
const agent = addCapabilities(tool.agent, sandboxSpecOf(tool.agent) === undefined ? spec : undefined);
|
|
66
|
-
if (agent === tool.agent)
|
|
67
|
-
return tool;
|
|
68
|
-
toolsChanged = true;
|
|
69
|
-
return { ...tool, agent };
|
|
70
|
-
});
|
|
71
|
-
if (!toolsChanged)
|
|
72
|
-
return capability;
|
|
31
|
+
const tools = manifest.tools?.map((tool) => {
|
|
32
|
+
// Agents used as tools share the session's sandbox; flow agents used as tools run in linked
|
|
33
|
+
// sessions that inherit it.
|
|
34
|
+
if (tool.agent === undefined || !isAgentManifest(tool.agent))
|
|
35
|
+
return tool;
|
|
36
|
+
const agent = pinRuntime(tool.agent, spec);
|
|
37
|
+
if (agent === tool.agent)
|
|
38
|
+
return tool;
|
|
73
39
|
changed = true;
|
|
74
|
-
return { ...
|
|
40
|
+
return { ...tool, agent };
|
|
41
|
+
});
|
|
42
|
+
const runtime = runtimeManifestFor(manifest, {
|
|
43
|
+
...(spec === undefined ? {} : { sandbox: spec }),
|
|
44
|
+
save: spec !== undefined,
|
|
45
|
+
read: true,
|
|
75
46
|
});
|
|
76
|
-
|
|
77
|
-
const read = readsStoredResults(manifest);
|
|
78
|
-
const defer = defersTools(manifest);
|
|
79
|
-
if (!save && !read && !defer && !changed)
|
|
47
|
+
if (runtime === undefined && !changed)
|
|
80
48
|
return manifest;
|
|
81
49
|
return {
|
|
82
50
|
...manifest,
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
capabilities: [
|
|
86
|
-
...capabilities,
|
|
87
|
-
...(spec === undefined
|
|
88
|
-
? []
|
|
89
|
-
: [sandboxCapabilityManifest(spec, { skills: skillNamesOf(manifest) })]),
|
|
90
|
-
...(save || read ? [artifactsCapabilityManifest({ save, read })] : []),
|
|
91
|
-
// Empty: when the session defers MCP tools, its tools put tool_search and tool_call here.
|
|
92
|
-
...(defer ? [toolsCapabilityManifest()] : []),
|
|
93
|
-
],
|
|
51
|
+
...(changed ? { tools } : {}),
|
|
52
|
+
...(runtime === undefined ? {} : { runtime }),
|
|
94
53
|
};
|
|
95
54
|
}
|
|
96
|
-
/** `
|
|
97
|
-
function checked(original,
|
|
98
|
-
const parsed = AgentManifestSchema.safeParse(
|
|
55
|
+
/** `pinned` checked as a manifest, with its hash. */
|
|
56
|
+
function checked(original, pinned) {
|
|
57
|
+
const parsed = AgentManifestSchema.safeParse(pinned);
|
|
99
58
|
if (!parsed.success) {
|
|
100
59
|
const issues = parsed.error.issues.map((issue) => issue.message);
|
|
101
|
-
const clash = issues.some((message) => message === "Tool names must be unique");
|
|
102
60
|
return {
|
|
103
61
|
ok: false,
|
|
104
|
-
message:
|
|
105
|
-
? `'${original.id}' declares a tool named like a sandbox tool (bash, read, write, edit, grep, glob, save_artifact). Rename it, or open the session with sandbox: false.`
|
|
106
|
-
: `'${original.id}' cannot take this sandbox: ${[...new Set(issues)].join("; ")}`,
|
|
62
|
+
message: `'${original.id}' cannot take the Runtime's tools: ${[...new Set(issues)].join("; ")}`,
|
|
107
63
|
};
|
|
108
64
|
}
|
|
109
|
-
return { ok: true, manifest:
|
|
65
|
+
return { ok: true, manifest: pinned, manifestHash: hashManifest(pinned) };
|
|
110
66
|
}
|
|
111
|
-
/** The agent's manifest with the sandbox
|
|
112
|
-
export function
|
|
113
|
-
return checked(manifest,
|
|
67
|
+
/** The agent's pinned manifest with the session's sandbox, or why it cannot take one. */
|
|
68
|
+
export function withSandbox(manifest, spec) {
|
|
69
|
+
return checked(manifest, pinRuntime(manifest, spec));
|
|
114
70
|
}
|
|
115
71
|
/**
|
|
116
72
|
* The pinned manifest of an agent session without a sandbox: `read_artifact` for each agent of
|
|
117
|
-
* the tree
|
|
118
|
-
* a remote MCP server (R2b C10). Undefined when nothing is added, and the session pins its
|
|
119
|
-
* definition as it is.
|
|
73
|
+
* the tree (R2b C11, protocol 12), and the skill tools for each with skills.
|
|
120
74
|
*/
|
|
121
|
-
export function
|
|
122
|
-
|
|
123
|
-
return pinned === manifest ? undefined : checked(manifest, pinned);
|
|
75
|
+
export function withRuntimeTools(manifest) {
|
|
76
|
+
return checked(manifest, pinRuntime(manifest, undefined));
|
|
124
77
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Environment, Tx } from "../store/types.js";
|
|
2
|
+
import type { TenantContext } from "../tenant/context.js";
|
|
3
|
+
import { SETUP_FAILED } from "./pods/lifecycle.js";
|
|
4
|
+
export { SETUP_FAILED };
|
|
5
|
+
/** What the engine runs on a new disk. */
|
|
6
|
+
export interface SetupPlan {
|
|
7
|
+
/** The applied recipe's version: written into the volume's marker. */
|
|
8
|
+
readonly recipeVersion: number;
|
|
9
|
+
readonly commands: readonly {
|
|
10
|
+
readonly command: string;
|
|
11
|
+
readonly cwd: string;
|
|
12
|
+
readonly timeoutMs: number;
|
|
13
|
+
}[];
|
|
14
|
+
/** Each input file by its index into the recipe's `files`, and where it goes. */
|
|
15
|
+
readonly files: readonly {
|
|
16
|
+
readonly index: number;
|
|
17
|
+
readonly path: string;
|
|
18
|
+
}[];
|
|
19
|
+
/** Variables every command gets: secrets as the sentinel, plain values (R2c). */
|
|
20
|
+
readonly environment: Readonly<Record<string, string>>;
|
|
21
|
+
}
|
|
22
|
+
/** The setup of `recipe`, or undefined when it has neither commands nor files. */
|
|
23
|
+
export declare function setupPlanOf(ctx: TenantContext, t: Tx, environment: Environment): Promise<SetupPlan | undefined>;
|
|
24
|
+
/** What the engine reports after running a setup. */
|
|
25
|
+
export interface SetupReport {
|
|
26
|
+
readonly status: "ok" | "failed";
|
|
27
|
+
/** The failed command's index into the plan's commands; absent for an input file. */
|
|
28
|
+
readonly index?: number;
|
|
29
|
+
readonly exitCode?: number;
|
|
30
|
+
/** The end of the failed command's output, or why an input file could not be copied. */
|
|
31
|
+
readonly output?: string;
|
|
32
|
+
}
|
|
33
|
+
export declare function parseSetupReport(value: unknown): SetupReport | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* Records a failed setup on Environment `id` held at host epoch `epoch` (the reporting engine's):
|
|
36
|
+
* the pod is `failed` with a reason that starts `setup:`, and `environment.failed` says which
|
|
37
|
+
* command failed and how. A report from an older host, or a success, changes nothing.
|
|
38
|
+
*/
|
|
39
|
+
export declare function recordSetup(ctx: TenantContext, id: string, epoch: number, report: SetupReport): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Whether the engine of `id` should run its setup (again): the pod is no longer `failed`, and
|
|
42
|
+
* the disk version a fork starts from is written (Q17).
|
|
43
|
+
*/
|
|
44
|
+
export declare function setupState(ctx: TenantContext, id: string): Promise<{
|
|
45
|
+
retry: boolean;
|
|
46
|
+
setup?: SetupPlan;
|
|
47
|
+
}>;
|
|
48
|
+
/** One input file's bytes, or a folder's listing (`path` absent). */
|
|
49
|
+
export type SetupFile = {
|
|
50
|
+
readonly kind: "file";
|
|
51
|
+
readonly contentType: string;
|
|
52
|
+
readonly body: ReadableStream<Uint8Array>;
|
|
53
|
+
} | {
|
|
54
|
+
readonly kind: "folder";
|
|
55
|
+
readonly entries: readonly {
|
|
56
|
+
readonly path: string;
|
|
57
|
+
readonly size: number;
|
|
58
|
+
}[];
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Input file `index` of Environment `id`'s applied recipe: a file artifact's bytes, a folder's
|
|
62
|
+
* listing, or (with `path`) one of the folder's files. Undefined when there is no such file.
|
|
63
|
+
*/
|
|
64
|
+
export declare function setupFile(ctx: TenantContext, id: string, index: number, path: string | undefined): Promise<SetupFile | undefined>;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { parseSandboxDuration } from "@nylorun/core/define";
|
|
2
|
+
import { contentKey, readManifest } from "../artifacts/folders.js";
|
|
3
|
+
import { contentTypeFor } from "../artifacts/media-types.js";
|
|
4
|
+
import { definitionFileKey } from "../tenant/definition-files.js";
|
|
5
|
+
import { SETUP_FAILED } from "./pods/lifecycle.js";
|
|
6
|
+
export { SETUP_FAILED };
|
|
7
|
+
/** The most of a failed command's output kept in its event and reason. */
|
|
8
|
+
const OUTPUT_TAIL = 4 * 1024;
|
|
9
|
+
/** A command's timeout when the recipe gives none. */
|
|
10
|
+
const DEFAULT_TIMEOUT_MS = 10 * 60_000;
|
|
11
|
+
/** The setup of `recipe`, or undefined when it has neither commands nor files. */
|
|
12
|
+
export async function setupPlanOf(ctx, t, environment) {
|
|
13
|
+
const recipe = environment.applied;
|
|
14
|
+
if (!recipe.setup?.length && !recipe.files?.length)
|
|
15
|
+
return undefined;
|
|
16
|
+
return {
|
|
17
|
+
recipeVersion: environment.appliedVersion,
|
|
18
|
+
commands: (recipe.setup ?? []).map((command) => ({
|
|
19
|
+
command: command.command,
|
|
20
|
+
cwd: command.cwd ?? "/workspace",
|
|
21
|
+
timeoutMs: (command.timeout && parseSandboxDuration(command.timeout)) || DEFAULT_TIMEOUT_MS,
|
|
22
|
+
})),
|
|
23
|
+
files: (recipe.files ?? []).map((file, index) => ({ index, path: file.path })),
|
|
24
|
+
environment: await ctx.vault.environmentVariables(t, environment.id),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function parseSetupReport(value) {
|
|
28
|
+
if (!value || typeof value !== "object")
|
|
29
|
+
return undefined;
|
|
30
|
+
const report = value;
|
|
31
|
+
if (report.status !== "ok" && report.status !== "failed")
|
|
32
|
+
return undefined;
|
|
33
|
+
const index = typeof report.index === "number" && Number.isInteger(report.index) ? report.index : undefined;
|
|
34
|
+
const exitCode = typeof report.exitCode === "number" && Number.isInteger(report.exitCode) ? report.exitCode : undefined;
|
|
35
|
+
const output = typeof report.output === "string" ? report.output.slice(-OUTPUT_TAIL) : undefined;
|
|
36
|
+
return {
|
|
37
|
+
status: report.status,
|
|
38
|
+
...(index === undefined ? {} : { index }),
|
|
39
|
+
...(exitCode === undefined ? {} : { exitCode }),
|
|
40
|
+
...(output === undefined ? {} : { output }),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Records a failed setup on Environment `id` held at host epoch `epoch` (the reporting engine's):
|
|
45
|
+
* the pod is `failed` with a reason that starts `setup:`, and `environment.failed` says which
|
|
46
|
+
* command failed and how. A report from an older host, or a success, changes nothing.
|
|
47
|
+
*/
|
|
48
|
+
export async function recordSetup(ctx, id, epoch, report) {
|
|
49
|
+
await ctx.store.tx(async (t) => {
|
|
50
|
+
const environment = await t.environment(id, { lock: true });
|
|
51
|
+
const pod = environment?.pod;
|
|
52
|
+
if (!environment || !pod || pod.hostEpoch !== epoch || pod.desired !== "running")
|
|
53
|
+
return;
|
|
54
|
+
if (report.status === "ok") {
|
|
55
|
+
// A fork's setup ran on its written disk (Q17): its turns may start.
|
|
56
|
+
if (environment.diskSeed?.setup && environment.diskSeed.written)
|
|
57
|
+
await t.updateEnvironment(id, { diskSeed: null }, new Date().toISOString());
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const command = report.index === undefined ? undefined : environment.applied.setup?.[report.index];
|
|
61
|
+
const what = command === undefined ? "copying the input files failed" : `command ${report.index + 1} (${command.command.slice(0, 80)}) exited ${report.exitCode ?? "abnormally"}`;
|
|
62
|
+
const tail = (report.output ?? "").trim().slice(-500);
|
|
63
|
+
const reason = `${SETUP_FAILED} ${what}${tail ? `: ${tail}` : ""}`;
|
|
64
|
+
await t.updateEnvironmentPod(id, { observed: "failed", reason }, new Date().toISOString());
|
|
65
|
+
await t.environmentEvent(id, "environment.failed", {
|
|
66
|
+
reason,
|
|
67
|
+
setup: {
|
|
68
|
+
...(report.index === undefined ? {} : { index: report.index }),
|
|
69
|
+
...(report.exitCode === undefined ? {} : { exitCode: report.exitCode }),
|
|
70
|
+
output: report.output ?? "",
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
if (report.status === "failed")
|
|
75
|
+
ctx.config.logger.warn("environment setup failed", { environmentId: id, index: report.index, exitCode: report.exitCode });
|
|
76
|
+
else
|
|
77
|
+
ctx.config.logger.info("environment setup done", { environmentId: id });
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Whether the engine of `id` should run its setup (again): the pod is no longer `failed`, and
|
|
81
|
+
* the disk version a fork starts from is written (Q17).
|
|
82
|
+
*/
|
|
83
|
+
export async function setupState(ctx, id) {
|
|
84
|
+
return ctx.store.tx(async (t) => {
|
|
85
|
+
const environment = await t.environment(id);
|
|
86
|
+
const pod = environment?.pod;
|
|
87
|
+
if (!environment || !pod)
|
|
88
|
+
return { retry: false };
|
|
89
|
+
if (pod.observed === "failed")
|
|
90
|
+
return { retry: false };
|
|
91
|
+
if (environment.diskSeed && !environment.diskSeed.written)
|
|
92
|
+
return { retry: false };
|
|
93
|
+
const setup = await setupPlanOf(ctx, t, environment);
|
|
94
|
+
return { retry: true, ...(setup ? { setup } : {}) };
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Input file `index` of Environment `id`'s applied recipe: a file artifact's bytes, a folder's
|
|
99
|
+
* listing, or (with `path`) one of the folder's files. Undefined when there is no such file.
|
|
100
|
+
*/
|
|
101
|
+
export async function setupFile(ctx, id, index, path) {
|
|
102
|
+
const entry = await ctx.store.tx(async (t) => {
|
|
103
|
+
const environment = await t.environment(id);
|
|
104
|
+
return environment?.applied.files?.[index];
|
|
105
|
+
});
|
|
106
|
+
if (!entry)
|
|
107
|
+
return undefined;
|
|
108
|
+
if ("files" in entry) {
|
|
109
|
+
// Files by hash (Q6): a folder of definition files.
|
|
110
|
+
if (path === undefined) {
|
|
111
|
+
const sizes = await ctx.store.tx(async (t) => {
|
|
112
|
+
const found = new Map();
|
|
113
|
+
for (const sha256 of new Set(Object.values(entry.files))) {
|
|
114
|
+
const row = await t.definitionFile(sha256);
|
|
115
|
+
if (row)
|
|
116
|
+
found.set(sha256, row.size);
|
|
117
|
+
}
|
|
118
|
+
return found;
|
|
119
|
+
});
|
|
120
|
+
return {
|
|
121
|
+
kind: "folder",
|
|
122
|
+
entries: Object.entries(entry.files).map(([item, sha256]) => ({ path: item, size: sizes.get(sha256) ?? 0 })),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
const sha256 = Object.hasOwn(entry.files, path) ? entry.files[path] : undefined;
|
|
126
|
+
if (sha256 === undefined)
|
|
127
|
+
return undefined;
|
|
128
|
+
const got = await ctx.blobs.get(definitionFileKey(sha256));
|
|
129
|
+
return got ? { kind: "file", contentType: contentTypeFor(path), body: got.body } : undefined;
|
|
130
|
+
}
|
|
131
|
+
const file = entry;
|
|
132
|
+
const found = await ctx.store.tx(async (t) => {
|
|
133
|
+
const artifact = await t.artifact(file.artifactId);
|
|
134
|
+
if (!artifact || artifact.sessionId !== null)
|
|
135
|
+
return undefined;
|
|
136
|
+
const version = await t.artifactVersion(file.artifactId, file.version);
|
|
137
|
+
return version ? { artifact, version } : undefined;
|
|
138
|
+
});
|
|
139
|
+
if (!found)
|
|
140
|
+
return undefined;
|
|
141
|
+
if (found.artifact.kind === "file") {
|
|
142
|
+
if (path !== undefined)
|
|
143
|
+
return undefined;
|
|
144
|
+
const got = await ctx.blobs.get(found.version.blobKey);
|
|
145
|
+
return got ? { kind: "file", contentType: found.version.contentType ?? found.artifact.contentType, body: got.body } : undefined;
|
|
146
|
+
}
|
|
147
|
+
const manifest = await readManifest(ctx.blobs, found.version);
|
|
148
|
+
if (path === undefined)
|
|
149
|
+
return {
|
|
150
|
+
kind: "folder",
|
|
151
|
+
entries: manifest.entries
|
|
152
|
+
.filter((entry) => entry.type === undefined)
|
|
153
|
+
.map((entry) => ({ path: entry.path, size: entry.size })),
|
|
154
|
+
};
|
|
155
|
+
const listed = manifest.entries.find((item) => item.path === path && item.type === undefined);
|
|
156
|
+
if (!listed)
|
|
157
|
+
return undefined;
|
|
158
|
+
const got = await ctx.blobs.get(contentKey(listed.sha256));
|
|
159
|
+
return got ? { kind: "file", contentType: listed.contentType, body: got.body } : undefined;
|
|
160
|
+
}
|