@nylorun/runtime 0.24.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 +104 -0
- package/README.md +10 -2
- package/dist/api/a2a/card.d.ts +11 -1
- package/dist/api/a2a/card.js +26 -2
- package/dist/api/a2a/endpoint.js +4 -2
- package/dist/api/a2a/protocol.d.ts +25 -2
- package/dist/api/a2a/protocol.js +20 -3
- package/dist/api/a2a/routes.d.ts +4 -1
- package/dist/api/a2a/routes.js +65 -7
- package/dist/api/ag-ui/endpoint.js +5 -2
- package/dist/api/ag-ui/history.d.ts +6 -1
- package/dist/api/ag-ui/history.js +35 -2
- package/dist/api/ag-ui/routes.js +53 -11
- package/dist/api/components.d.ts +1165 -678
- package/dist/api/components.js +16 -1
- package/dist/api/gate/keys.d.ts +2 -1
- package/dist/api/gate/keys.js +7 -0
- package/dist/api/gate/routes.js +13 -0
- package/dist/api/http/app.js +13 -2
- package/dist/api/http/body.d.ts +3 -3
- package/dist/api/http/body.js +7 -7
- 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.js +61 -12
- package/dist/api/http/routes/reads.d.ts +52 -3
- package/dist/api/http/routes/sessions.js +13 -3
- package/dist/api/http/routes/tenant.js +35 -7
- package/dist/api/inbound-files.d.ts +65 -0
- package/dist/api/inbound-files.js +115 -0
- package/dist/api/openapi.js +18 -3
- package/dist/artifacts/disk.js +20 -14
- 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/workspace.d.ts +4 -4
- package/dist/artifacts/workspace.js +6 -18
- package/dist/core/flow-host.d.ts +1 -1
- package/dist/core/flow-host.js +2 -2
- package/dist/core/sandbox-routes.js +2 -2
- package/dist/core/turn-manifest.d.ts +3 -2
- package/dist/core/turn-manifest.js +29 -5
- package/dist/gates/egress-credentials.js +1 -1
- package/dist/gates/egress.d.ts +13 -19
- package/dist/gates/egress.js +30 -29
- package/dist/gates/http-tool.d.ts +3 -4
- package/dist/gates/http-tool.js +8 -12
- package/dist/gates/journal.js +0 -1
- package/dist/gates/mcp-handler.js +2 -3
- package/dist/gates/policy.js +1 -3
- package/dist/gates/tool-contract.d.ts +1 -1
- package/dist/harness/calls.d.ts +2 -2
- package/dist/harness/calls.js +9 -12
- package/dist/harness/main.js +7 -1
- package/dist/harness/pod.d.ts +6 -1
- package/dist/harness/pod.js +5 -0
- package/dist/harness/service.js +5 -5
- package/dist/harness/setup.d.ts +2 -0
- package/dist/harness/setup.js +25 -1
- package/dist/harness-api/record.d.ts +1 -1
- package/dist/harness-api/record.js +13 -13
- package/dist/harness-api/server.d.ts +27 -4
- package/dist/harness-api/server.js +41 -8
- package/dist/harness-api/tool-journal.d.ts +17 -8
- package/dist/harness-api/tool-journal.js +41 -11
- package/dist/harness-api/workspace.d.ts +7 -5
- package/dist/harness-api/workspace.js +25 -19
- package/dist/host/gates.js +2 -2
- package/dist/host/stack-config.js +1 -1
- package/dist/keys/keys.d.ts +9 -2
- package/dist/keys/keys.js +4 -1
- package/dist/management-openapi.json +366 -61
- package/dist/mcp/connect.d.ts +1 -2
- package/dist/mcp/connect.js +1 -4
- package/dist/mcp/pool.d.ts +1 -4
- package/dist/mcp/pool.js +13 -27
- package/dist/mcp/preview.js +0 -1
- package/dist/mcp/snapshot.d.ts +11 -13
- package/dist/mcp/snapshot.js +27 -38
- 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 +106 -94
- package/dist/openapi.json +9554 -5104
- package/dist/record/environment.js +1 -1
- package/dist/sandbox/join.js +4 -0
- package/dist/sandbox/manager.d.ts +32 -14
- package/dist/sandbox/manager.js +132 -93
- package/dist/sandbox/placement.d.ts +1 -1
- package/dist/sandbox/placement.js +2 -2
- package/dist/sandbox/pods/client.d.ts +3 -1
- package/dist/sandbox/pods/lifecycle.d.ts +8 -0
- package/dist/sandbox/pods/lifecycle.js +15 -0
- package/dist/sandbox/pods/spec.js +2 -1
- package/dist/sandbox/resolve.d.ts +5 -3
- package/dist/sandbox/resolve.js +9 -2
- 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 +4 -1
- package/dist/sandbox/setup.js +46 -9
- package/dist/sandbox/share.d.ts +12 -7
- package/dist/sandbox/share.js +27 -25
- package/dist/sandbox/skills.d.ts +8 -6
- package/dist/sandbox/skills.js +24 -23
- 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 +28 -0
- package/dist/store/postgres/reads.js +8 -2
- package/dist/store/postgres/schema.d.ts +217 -0
- package/dist/store/postgres/schema.js +36 -2
- package/dist/store/postgres/store.js +79 -4
- package/dist/store/types.d.ts +68 -14
- package/dist/tenant/advance.js +3 -0
- package/dist/tenant/artifact-tool.d.ts +1 -1
- package/dist/tenant/artifact-tool.js +43 -22
- package/dist/tenant/commands.js +24 -4
- package/dist/tenant/context.d.ts +10 -4
- package/dist/tenant/effects.js +7 -9
- package/dist/tenant/environment-manifests.d.ts +57 -0
- package/dist/tenant/environment-manifests.js +200 -0
- package/dist/tenant/environments.d.ts +54 -47
- package/dist/tenant/environments.js +304 -83
- package/dist/tenant/model-setting.d.ts +14 -0
- package/dist/tenant/model-setting.js +51 -0
- package/dist/tenant/sessions.d.ts +5 -1
- package/dist/tenant/sessions.js +45 -12
- package/dist/tenant/skill-tool.d.ts +3 -3
- package/dist/tenant/skill-tool.js +6 -11
- package/dist/tenant/sweep.js +1 -1
- 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 +51 -32
- package/dist/tenant/transcript.js +2 -1
- package/dist/traces/ids.d.ts +0 -1
- package/dist/traces/ids.js +0 -4
- package/dist/traces/map.d.ts +2 -2
- package/dist/traces/map.js +1 -29
- package/dist/vault/coverage.js +11 -13
- package/dist/vault/host-model.d.ts +4 -1
- package/dist/vault/host-model.js +10 -2
- package/dist/vault/service.d.ts +49 -4
- package/dist/vault/service.js +153 -6
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/dist/sandbox/capability.d.ts +0 -3
- package/dist/sandbox/capability.js +0 -10
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Resolves an Environment's recipe (`PUT /v1/environments/{id}`) against its Tenant's limits,
|
|
3
|
-
* and the sandbox spec a recipe gives
|
|
3
|
+
* and the sandbox spec a recipe gives a session's pinned manifest (its `runtime` block) and the pod. Pure: the
|
|
4
4
|
* caller supplies the configuration. Every problem is reported, not only the first, so one
|
|
5
5
|
* response lists all of them.
|
|
6
6
|
*/
|
|
7
|
-
import type { EnvironmentFile, EnvironmentKind, EnvironmentLifecycle, EnvironmentRecipe, EnvironmentSetupCommand } from "@nylorun/core/contracts";
|
|
7
|
+
import type { EnvironmentFile, EnvironmentKind, EnvironmentLifecycle, EnvironmentRecipe, EnvironmentSecretSlots, EnvironmentSetupCommand } from "@nylorun/core/contracts";
|
|
8
8
|
import { type SandboxManifest } from "@nylorun/core/define";
|
|
9
9
|
import type { PodSandboxSpec } from "./pods/spec.js";
|
|
10
10
|
import { type EffectiveSandboxConfig } from "./tenant-config.js";
|
|
@@ -23,6 +23,8 @@ export interface RecipeRequest {
|
|
|
23
23
|
readonly lifecycle?: EnvironmentLifecycle;
|
|
24
24
|
readonly setup?: readonly EnvironmentSetupCommand[];
|
|
25
25
|
readonly files?: readonly EnvironmentFile[];
|
|
26
|
+
/** The secret slots (D64): passed through for every kind. */
|
|
27
|
+
readonly secrets?: EnvironmentSecretSlots;
|
|
26
28
|
}
|
|
27
29
|
export type RecipeResolution = {
|
|
28
30
|
readonly kind: "recipe";
|
|
@@ -34,7 +36,7 @@ export type RecipeResolution = {
|
|
|
34
36
|
/** The recipe `request` asks for an Environment of `kind`, or every reason it cannot have it. */
|
|
35
37
|
export declare function resolveRecipe(kind: EnvironmentKind, request: RecipeRequest, config: EffectiveSandboxConfig): RecipeResolution;
|
|
36
38
|
/**
|
|
37
|
-
* The sandbox spec of a recipe: what
|
|
39
|
+
* The sandbox spec of a recipe: what a session's `runtime` block pins and the virtual
|
|
38
40
|
* workspace runs. Idle is the Environment's `lifecycle.idle`, else the Tenant's.
|
|
39
41
|
*/
|
|
40
42
|
export declare function sandboxSpecOf(recipe: EnvironmentRecipe, tenantIdle: string): SandboxManifest;
|
package/dist/sandbox/resolve.js
CHANGED
|
@@ -20,7 +20,7 @@ export function resolveRecipe(kind, request, config) {
|
|
|
20
20
|
errors.push(`${field} is for an Environment with a sandbox: kind none has none.`);
|
|
21
21
|
if (errors.length > 0)
|
|
22
22
|
return { kind: "error", errors };
|
|
23
|
-
return { kind: "recipe", recipe: { harness, lifecycle: { ...lifecycle } } };
|
|
23
|
+
return { kind: "recipe", recipe: { harness, lifecycle: { ...lifecycle }, ...slotsOf(request) } };
|
|
24
24
|
}
|
|
25
25
|
if (request.image !== undefined && !pod)
|
|
26
26
|
errors.push("image is for kind pod: a virtual Environment runs no images.");
|
|
@@ -57,11 +57,18 @@ export function resolveRecipe(kind, request, config) {
|
|
|
57
57
|
lifecycle: { ...lifecycle },
|
|
58
58
|
...(pod && request.setup?.length ? { setup: request.setup.map((command) => ({ ...command })) } : {}),
|
|
59
59
|
...(pod && request.files?.length ? { files: request.files.map((file) => ({ ...file })) } : {}),
|
|
60
|
+
...slotsOf(request),
|
|
60
61
|
},
|
|
61
62
|
};
|
|
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
|
+
}
|
|
63
70
|
/**
|
|
64
|
-
* The sandbox spec of a recipe: what
|
|
71
|
+
* The sandbox spec of a recipe: what a session's `runtime` block pins and the virtual
|
|
65
72
|
* workspace runs. Idle is the Environment's `lifecycle.idle`, else the Tenant's.
|
|
66
73
|
*/
|
|
67
74
|
export function sandboxSpecOf(recipe, tenantIdle) {
|
|
@@ -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 on no Environment or one of kind none.`
|
|
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
|
}
|
package/dist/sandbox/setup.d.ts
CHANGED
|
@@ -37,7 +37,10 @@ export declare function parseSetupReport(value: unknown): SetupReport | undefine
|
|
|
37
37
|
* command failed and how. A report from an older host, or a success, changes nothing.
|
|
38
38
|
*/
|
|
39
39
|
export declare function recordSetup(ctx: TenantContext, id: string, epoch: number, report: SetupReport): Promise<void>;
|
|
40
|
-
/**
|
|
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
|
+
*/
|
|
41
44
|
export declare function setupState(ctx: TenantContext, id: string): Promise<{
|
|
42
45
|
retry: boolean;
|
|
43
46
|
setup?: SetupPlan;
|
package/dist/sandbox/setup.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { parseSandboxDuration } from "@nylorun/core/define";
|
|
2
2
|
import { contentKey, readManifest } from "../artifacts/folders.js";
|
|
3
|
+
import { contentTypeFor } from "../artifacts/media-types.js";
|
|
4
|
+
import { definitionFileKey } from "../tenant/definition-files.js";
|
|
3
5
|
import { SETUP_FAILED } from "./pods/lifecycle.js";
|
|
4
6
|
export { SETUP_FAILED };
|
|
5
7
|
/** The most of a failed command's output kept in its event and reason. */
|
|
@@ -49,8 +51,12 @@ export async function recordSetup(ctx, id, epoch, report) {
|
|
|
49
51
|
const pod = environment?.pod;
|
|
50
52
|
if (!environment || !pod || pod.hostEpoch !== epoch || pod.desired !== "running")
|
|
51
53
|
return;
|
|
52
|
-
if (report.status === "ok")
|
|
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());
|
|
53
58
|
return;
|
|
59
|
+
}
|
|
54
60
|
const command = report.index === undefined ? undefined : environment.applied.setup?.[report.index];
|
|
55
61
|
const what = command === undefined ? "copying the input files failed" : `command ${report.index + 1} (${command.command.slice(0, 80)}) exited ${report.exitCode ?? "abnormally"}`;
|
|
56
62
|
const tail = (report.output ?? "").trim().slice(-500);
|
|
@@ -70,7 +76,10 @@ export async function recordSetup(ctx, id, epoch, report) {
|
|
|
70
76
|
else
|
|
71
77
|
ctx.config.logger.info("environment setup done", { environmentId: id });
|
|
72
78
|
}
|
|
73
|
-
/**
|
|
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
|
+
*/
|
|
74
83
|
export async function setupState(ctx, id) {
|
|
75
84
|
return ctx.store.tx(async (t) => {
|
|
76
85
|
const environment = await t.environment(id);
|
|
@@ -79,6 +88,8 @@ export async function setupState(ctx, id) {
|
|
|
79
88
|
return { retry: false };
|
|
80
89
|
if (pod.observed === "failed")
|
|
81
90
|
return { retry: false };
|
|
91
|
+
if (environment.diskSeed && !environment.diskSeed.written)
|
|
92
|
+
return { retry: false };
|
|
82
93
|
const setup = await setupPlanOf(ctx, t, environment);
|
|
83
94
|
return { retry: true, ...(setup ? { setup } : {}) };
|
|
84
95
|
});
|
|
@@ -88,11 +99,37 @@ export async function setupState(ctx, id) {
|
|
|
88
99
|
* listing, or (with `path`) one of the folder's files. Undefined when there is no such file.
|
|
89
100
|
*/
|
|
90
101
|
export async function setupFile(ctx, id, index, path) {
|
|
91
|
-
const
|
|
102
|
+
const entry = await ctx.store.tx(async (t) => {
|
|
92
103
|
const environment = await t.environment(id);
|
|
93
|
-
|
|
94
|
-
|
|
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)
|
|
95
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) => {
|
|
96
133
|
const artifact = await t.artifact(file.artifactId);
|
|
97
134
|
if (!artifact || artifact.sessionId !== null)
|
|
98
135
|
return undefined;
|
|
@@ -115,9 +152,9 @@ export async function setupFile(ctx, id, index, path) {
|
|
|
115
152
|
.filter((entry) => entry.type === undefined)
|
|
116
153
|
.map((entry) => ({ path: entry.path, size: entry.size })),
|
|
117
154
|
};
|
|
118
|
-
const
|
|
119
|
-
if (!
|
|
155
|
+
const listed = manifest.entries.find((item) => item.path === path && item.type === undefined);
|
|
156
|
+
if (!listed)
|
|
120
157
|
return undefined;
|
|
121
|
-
const got = await ctx.blobs.get(contentKey(
|
|
122
|
-
return got ? { kind: "file", contentType:
|
|
158
|
+
const got = await ctx.blobs.get(contentKey(listed.sha256));
|
|
159
|
+
return got ? { kind: "file", contentType: listed.contentType, body: got.body } : undefined;
|
|
123
160
|
}
|
package/dist/sandbox/share.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* workspace, with the spec pinned when the session was opened (`Session.sandbox`). Definitions
|
|
4
4
|
* no longer declare one (Sandboxes v3); `sandboxSpecOf` still reads a pinned manifest's.
|
|
5
5
|
*/
|
|
6
|
-
import { type AgentManifest, type
|
|
6
|
+
import { type AgentManifest, type SandboxManifest, type WorkflowManifest } from "@nylorun/core/define";
|
|
7
7
|
export type SessionSandboxRef = {
|
|
8
8
|
readonly id: string;
|
|
9
9
|
readonly ownerUserId: string;
|
|
@@ -15,16 +15,21 @@ export type SessionSandboxRef = {
|
|
|
15
15
|
};
|
|
16
16
|
/** The session's sandbox spec: the one pinned at open, else the one its definition declares. */
|
|
17
17
|
export declare function sessionSandboxSpec(session: SessionSandboxRef): SandboxManifest | undefined;
|
|
18
|
-
/**
|
|
18
|
+
/** The sandbox spec of a pinned agent manifest (its `runtime` block) or a workflow manifest. */
|
|
19
19
|
export declare function sandboxSpecOf(manifest: AgentManifest | WorkflowManifest | Record<string, unknown> | undefined): SandboxManifest | undefined;
|
|
20
20
|
/**
|
|
21
|
-
* Where a definition document declares a sandbox:
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* Where a definition document declares a sandbox: a workflow's `sandbox`, and the agents embedded
|
|
22
|
+
* in a flow agent. Definitions no longer declare one (Sandboxes v3), so registration refuses any
|
|
23
|
+
* of these. An agent manifest cannot: its sandbox is in the `runtime` block, which the schema
|
|
24
|
+
* refuses at registration.
|
|
24
25
|
*/
|
|
25
26
|
export declare function declaredSandboxes(manifest: unknown, into?: string[]): string[];
|
|
26
|
-
/**
|
|
27
|
-
|
|
27
|
+
/**
|
|
28
|
+
* The agents of a definition document that set `runtime`: the document itself, the agents it
|
|
29
|
+
* uses as tools and those embedded in a flow agent. Only the Runtime writes that block, when it
|
|
30
|
+
* pins a session's manifest, so registration refuses any of these.
|
|
31
|
+
*/
|
|
32
|
+
export declare function authoredRuntimeBlocks(manifest: unknown, into?: string[]): string[];
|
|
28
33
|
/**
|
|
29
34
|
* The workspace a session's sandbox tools run in: the session, whose log records the
|
|
30
35
|
* workspace's events, and its Environment, whose id keys the workspace.
|
package/dist/sandbox/share.js
CHANGED
|
@@ -8,24 +8,19 @@ import {} from "@nylorun/core/define";
|
|
|
8
8
|
export function sessionSandboxSpec(session) {
|
|
9
9
|
return session.sandbox ?? sandboxSpecOf(session.manifest);
|
|
10
10
|
}
|
|
11
|
-
/**
|
|
11
|
+
/** The sandbox spec of a pinned agent manifest (its `runtime` block) or a workflow manifest. */
|
|
12
12
|
export function sandboxSpecOf(manifest) {
|
|
13
13
|
if (!manifest || typeof manifest !== "object")
|
|
14
14
|
return undefined;
|
|
15
15
|
if (manifest.kind === "workflow")
|
|
16
16
|
return manifest.sandbox;
|
|
17
|
-
|
|
18
|
-
if (!Array.isArray(capabilities))
|
|
19
|
-
return undefined;
|
|
20
|
-
for (const capability of capabilities)
|
|
21
|
-
if (capability.sandbox)
|
|
22
|
-
return capability.sandbox;
|
|
23
|
-
return undefined;
|
|
17
|
+
return manifest.runtime?.sandbox;
|
|
24
18
|
}
|
|
25
19
|
/**
|
|
26
|
-
* Where a definition document declares a sandbox:
|
|
27
|
-
*
|
|
28
|
-
*
|
|
20
|
+
* Where a definition document declares a sandbox: a workflow's `sandbox`, and the agents embedded
|
|
21
|
+
* in a flow agent. Definitions no longer declare one (Sandboxes v3), so registration refuses any
|
|
22
|
+
* of these. An agent manifest cannot: its sandbox is in the `runtime` block, which the schema
|
|
23
|
+
* refuses at registration.
|
|
29
24
|
*/
|
|
30
25
|
export function declaredSandboxes(manifest, into = []) {
|
|
31
26
|
if (!manifest || typeof manifest !== "object")
|
|
@@ -38,22 +33,29 @@ export function declaredSandboxes(manifest, into = []) {
|
|
|
38
33
|
declaredSandboxes(agent, into);
|
|
39
34
|
return into;
|
|
40
35
|
}
|
|
41
|
-
for (const
|
|
42
|
-
|
|
43
|
-
into.push(`'${document.id}' (capability '${capability.id}')`);
|
|
44
|
-
for (const tool of capability.tools ?? [])
|
|
45
|
-
declaredSandboxes(tool.agent, into);
|
|
46
|
-
}
|
|
36
|
+
for (const tool of document.tools ?? [])
|
|
37
|
+
declaredSandboxes(tool.agent, into);
|
|
47
38
|
return into;
|
|
48
39
|
}
|
|
49
|
-
/**
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
40
|
+
/**
|
|
41
|
+
* The agents of a definition document that set `runtime`: the document itself, the agents it
|
|
42
|
+
* uses as tools and those embedded in a flow agent. Only the Runtime writes that block, when it
|
|
43
|
+
* pins a session's manifest, so registration refuses any of these.
|
|
44
|
+
*/
|
|
45
|
+
export function authoredRuntimeBlocks(manifest, into = []) {
|
|
46
|
+
if (!manifest || typeof manifest !== "object")
|
|
47
|
+
return into;
|
|
48
|
+
const document = manifest;
|
|
49
|
+
if (document.kind === "workflow") {
|
|
50
|
+
for (const agent of Object.values(document.agents ?? {}))
|
|
51
|
+
authoredRuntimeBlocks(agent, into);
|
|
52
|
+
return into;
|
|
53
|
+
}
|
|
54
|
+
if (document.runtime !== undefined)
|
|
55
|
+
into.push(`'${document.id}'`);
|
|
56
|
+
for (const tool of document.tools ?? [])
|
|
57
|
+
authoredRuntimeBlocks(tool.agent, into);
|
|
58
|
+
return into;
|
|
57
59
|
}
|
|
58
60
|
/**
|
|
59
61
|
* The workspace a session's sandbox tools run in: the session, whose log records the
|
package/dist/sandbox/skills.d.ts
CHANGED
|
@@ -2,12 +2,14 @@ import { type AgentManifest } from "@nylorun/core/define";
|
|
|
2
2
|
import type { SandboxHandle } from "./types.js";
|
|
3
3
|
/** A skill's files, by path in its folder, as `sha256:<hex>`. */
|
|
4
4
|
export type SkillFiles = Readonly<Record<string, string>>;
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* The skill versions of the agent and the agents it uses as tools, by folder
|
|
7
|
+
* (`/skills/<name>@<hash>`): each distinct version once.
|
|
8
|
+
*/
|
|
6
9
|
export declare function sandboxSkillsOf(manifest: AgentManifest | undefined, into?: Map<string, Readonly<Record<string, string>>>): Map<string, SkillFiles>;
|
|
7
|
-
/** What a mounted skill holds, to tell whether a workspace has these files already. */
|
|
8
|
-
export declare function skillSignature(files: SkillFiles): string;
|
|
9
10
|
/**
|
|
10
|
-
* Writes one skill's files to `/skills/<name
|
|
11
|
-
* makes them read-only. `read` gives a file's bytes by
|
|
11
|
+
* Writes one skill version's files to its folder `root` (`/skills/<name>@<hash>`), replacing a
|
|
12
|
+
* partial copy a failed mount left, and makes them read-only. `read` gives a file's bytes by
|
|
13
|
+
* `sha256:<hex>`.
|
|
12
14
|
*/
|
|
13
|
-
export declare function mountSkill(handle: SandboxHandle,
|
|
15
|
+
export declare function mountSkill(handle: SandboxHandle, root: string, files: SkillFiles, read: (sha256: string) => Promise<Uint8Array>, signal: AbortSignal): Promise<void>;
|
package/dist/sandbox/skills.js
CHANGED
|
@@ -1,36 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* A sandbox's skills (track R2 M4): each skill of the session's agent, and of
|
|
3
|
-
* as tools (they share its sandbox), gets its files
|
|
4
|
-
* model can read them and run their
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* A sandbox's skills (track R2 M4, D66): each version of a skill of the session's agent, and of
|
|
3
|
+
* the agents it uses as tools (they share its sandbox), gets its files in its own folder,
|
|
4
|
+
* `/skills/<name>@<hash>/` (`skillFolderOf`), read-only, so the model can read them and run their
|
|
5
|
+
* scripts with bash. A version's folder is written once and never replaced: sessions whose agents
|
|
6
|
+
* carry different versions of one skill share an Environment, and run turns at once, without
|
|
7
|
+
* changing each other's files. The SandboxManager mounts the folders a session needs before the
|
|
8
|
+
* first call that needs them; the bytes come from the Tenant's definition files. Folders live
|
|
9
|
+
* outside the persisted disk, so a stop clears them and the next open mounts what is in use.
|
|
7
10
|
*/
|
|
8
11
|
import { posix } from "node:path";
|
|
9
|
-
import {
|
|
10
|
-
import { canonical } from "../store/canonical.js";
|
|
12
|
+
import { skillFolderOf } from "@nylorun/core/define";
|
|
11
13
|
import { quote } from "./tools.js";
|
|
12
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* The skill versions of the agent and the agents it uses as tools, by folder
|
|
16
|
+
* (`/skills/<name>@<hash>`): each distinct version once.
|
|
17
|
+
*/
|
|
13
18
|
export function sandboxSkillsOf(manifest, into = new Map()) {
|
|
14
|
-
for (const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
for (const tool of capability.tools ?? [])
|
|
19
|
-
if (tool.agent !== undefined && !("kind" in tool.agent))
|
|
20
|
-
sandboxSkillsOf(tool.agent, into);
|
|
19
|
+
for (const skill of Object.values(manifest?.skills ?? {})) {
|
|
20
|
+
const folder = skillFolderOf(skill);
|
|
21
|
+
if (!into.has(folder))
|
|
22
|
+
into.set(folder, skill.files);
|
|
21
23
|
}
|
|
24
|
+
for (const tool of manifest?.tools ?? [])
|
|
25
|
+
if (tool.agent !== undefined && !("kind" in tool.agent))
|
|
26
|
+
sandboxSkillsOf(tool.agent, into);
|
|
22
27
|
return into;
|
|
23
28
|
}
|
|
24
|
-
/** What a mounted skill holds, to tell whether a workspace has these files already. */
|
|
25
|
-
export function skillSignature(files) {
|
|
26
|
-
return canonical(files);
|
|
27
|
-
}
|
|
28
29
|
/**
|
|
29
|
-
* Writes one skill's files to `/skills/<name
|
|
30
|
-
* makes them read-only. `read` gives a file's bytes by
|
|
30
|
+
* Writes one skill version's files to its folder `root` (`/skills/<name>@<hash>`), replacing a
|
|
31
|
+
* partial copy a failed mount left, and makes them read-only. `read` gives a file's bytes by
|
|
32
|
+
* `sha256:<hex>`.
|
|
31
33
|
*/
|
|
32
|
-
export async function mountSkill(handle,
|
|
33
|
-
const root = `${SKILLS_MOUNT}/${name}`;
|
|
34
|
+
export async function mountSkill(handle, root, files, read, signal) {
|
|
34
35
|
const exec = async (command) => {
|
|
35
36
|
const result = await handle.exec({ command, cwd: "/", timeoutMs: 60_000 }, signal);
|
|
36
37
|
if (result.exitCode !== 0)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
-- Protocol 13: manifest 7, flat (no capabilities), with the Runtime's tools in a `runtime` block.
|
|
2
|
+
-- A fresh start for sessions: one opened before protocol 13 pinned a manifest 5 or 6, which the
|
|
3
|
+
-- engine no longer runs, so it is closed (`session_closed`) and its history stays readable. No
|
|
4
|
+
-- column changes: tool calls are read from the effect journal's documents, whose old
|
|
5
|
+
-- `capabilityId` field is ignored.
|
|
6
|
+
UPDATE "nylorun"."sessions" SET "body" = ("body"::jsonb || '{"closedReason":"protocol_13"}'::jsonb)::json
|
|
7
|
+
WHERE nylorun.doc("body") ->> 'closedReason' IS NULL;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
CREATE TABLE "nylorun"."environment_manifests" (
|
|
2
|
+
"name" text COLLATE "C" NOT NULL,
|
|
3
|
+
"version" integer NOT NULL,
|
|
4
|
+
"body" json NOT NULL,
|
|
5
|
+
"hash" text COLLATE "C" NOT NULL,
|
|
6
|
+
"saved_by" json NOT NULL,
|
|
7
|
+
"created_at" text COLLATE "C" NOT NULL,
|
|
8
|
+
CONSTRAINT "environment_manifests_pkey" PRIMARY KEY("name","version")
|
|
9
|
+
);
|
|
10
|
+
--> statement-breakpoint
|
|
11
|
+
ALTER TABLE "nylorun"."environments" ADD COLUMN "manifest_name" text COLLATE "C";--> statement-breakpoint
|
|
12
|
+
ALTER TABLE "nylorun"."environments" ADD COLUMN "manifest_version" integer;--> statement-breakpoint
|
|
13
|
+
ALTER TABLE "nylorun"."environments" ADD COLUMN "manifest_hash" text COLLATE "C";--> statement-breakpoint
|
|
14
|
+
CREATE INDEX "environments_manifest" ON "nylorun"."environments" USING btree ("manifest_name","manifest_version");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE "nylorun"."environments" ADD COLUMN "forked_from" json;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE "nylorun"."environments" ADD COLUMN "image_digest" text COLLATE "C";
|