@nylorun/runtime 0.24.0-beta → 0.26.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/CHANGELOG.md +143 -0
  2. package/README.md +18 -4
  3. package/dist/api/a2a/card.d.ts +11 -1
  4. package/dist/api/a2a/card.js +26 -2
  5. package/dist/api/a2a/endpoint.js +4 -2
  6. package/dist/api/a2a/protocol.d.ts +25 -2
  7. package/dist/api/a2a/protocol.js +20 -3
  8. package/dist/api/a2a/routes.d.ts +4 -1
  9. package/dist/api/a2a/routes.js +65 -7
  10. package/dist/api/ag-ui/endpoint.js +5 -2
  11. package/dist/api/ag-ui/history.d.ts +6 -1
  12. package/dist/api/ag-ui/history.js +35 -2
  13. package/dist/api/ag-ui/routes.js +53 -11
  14. package/dist/api/components.d.ts +1165 -678
  15. package/dist/api/components.js +16 -1
  16. package/dist/api/gate/keys.d.ts +2 -1
  17. package/dist/api/gate/keys.js +7 -0
  18. package/dist/api/gate/routes.js +13 -0
  19. package/dist/api/http/app.d.ts +2 -2
  20. package/dist/api/http/app.js +15 -4
  21. package/dist/api/http/body.d.ts +3 -3
  22. package/dist/api/http/body.js +7 -7
  23. package/dist/api/http/define.d.ts +1 -1
  24. package/dist/api/http/respond.js +1 -1
  25. package/dist/api/http/routes/environment-manifests.d.ts +9 -0
  26. package/dist/api/http/routes/environment-manifests.js +118 -0
  27. package/dist/api/http/routes/environments.js +61 -12
  28. package/dist/api/http/routes/reads.d.ts +52 -3
  29. package/dist/api/http/routes/sessions.js +14 -4
  30. package/dist/api/http/routes/tenant.js +35 -7
  31. package/dist/api/inbound-files.d.ts +65 -0
  32. package/dist/api/inbound-files.js +115 -0
  33. package/dist/api/openapi.js +18 -3
  34. package/dist/artifacts/disk.js +20 -14
  35. package/dist/artifacts/media-types.d.ts +15 -0
  36. package/dist/artifacts/media-types.js +56 -4
  37. package/dist/artifacts/parts.d.ts +12 -3
  38. package/dist/artifacts/parts.js +15 -4
  39. package/dist/artifacts/workspace.d.ts +4 -4
  40. package/dist/artifacts/workspace.js +6 -18
  41. package/dist/core/flow-host.d.ts +1 -1
  42. package/dist/core/flow-host.js +2 -2
  43. package/dist/core/sandbox-routes.js +2 -2
  44. package/dist/core/turn-manifest.d.ts +3 -2
  45. package/dist/core/turn-manifest.js +29 -5
  46. package/dist/gates/egress-credentials.js +1 -1
  47. package/dist/gates/egress.d.ts +13 -19
  48. package/dist/gates/egress.js +32 -29
  49. package/dist/gates/http-tool.d.ts +3 -4
  50. package/dist/gates/http-tool.js +23 -19
  51. package/dist/gates/journal.js +0 -1
  52. package/dist/gates/mcp-handler.js +2 -3
  53. package/dist/gates/policy.js +1 -3
  54. package/dist/gates/tool-contract.d.ts +1 -1
  55. package/dist/harness/calls.d.ts +2 -2
  56. package/dist/harness/calls.js +9 -12
  57. package/dist/harness/main.js +7 -1
  58. package/dist/harness/pod.d.ts +6 -1
  59. package/dist/harness/pod.js +5 -0
  60. package/dist/harness/service.js +5 -5
  61. package/dist/harness/setup.d.ts +2 -0
  62. package/dist/harness/setup.js +25 -1
  63. package/dist/harness-api/record.d.ts +1 -1
  64. package/dist/harness-api/record.js +13 -13
  65. package/dist/harness-api/server.d.ts +27 -4
  66. package/dist/harness-api/server.js +41 -8
  67. package/dist/harness-api/tool-journal.d.ts +17 -8
  68. package/dist/harness-api/tool-journal.js +41 -11
  69. package/dist/harness-api/workspace.d.ts +7 -5
  70. package/dist/harness-api/workspace.js +25 -19
  71. package/dist/host/gates.js +2 -2
  72. package/dist/host/stack-config.js +1 -1
  73. package/dist/keys/contract.d.ts +1 -1
  74. package/dist/keys/contract.js +1 -1
  75. package/dist/keys/keys.d.ts +9 -2
  76. package/dist/keys/keys.js +4 -1
  77. package/dist/management-openapi.json +366 -61
  78. package/dist/mcp/connect.d.ts +1 -2
  79. package/dist/mcp/connect.js +1 -4
  80. package/dist/mcp/pool.d.ts +1 -4
  81. package/dist/mcp/pool.js +13 -27
  82. package/dist/mcp/preview.d.ts +1 -1
  83. package/dist/mcp/preview.js +1 -2
  84. package/dist/mcp/snapshot.d.ts +11 -13
  85. package/dist/mcp/snapshot.js +27 -38
  86. package/dist/model/capabilities.d.ts +26 -0
  87. package/dist/model/capabilities.js +44 -0
  88. package/dist/model/catalog.d.ts +34 -3
  89. package/dist/model/catalog.js +38 -2
  90. package/dist/model/models.d.ts +2 -0
  91. package/dist/model/models.js +1 -1
  92. package/dist/model/native-blocks.d.ts +35 -0
  93. package/dist/model/native-blocks.js +170 -0
  94. package/dist/model/pi-model.js +106 -94
  95. package/dist/openapi.json +9554 -5076
  96. package/dist/record/environment.js +1 -1
  97. package/dist/sandbox/join.js +4 -0
  98. package/dist/sandbox/manager.d.ts +32 -14
  99. package/dist/sandbox/manager.js +132 -93
  100. package/dist/sandbox/placement.d.ts +1 -1
  101. package/dist/sandbox/placement.js +2 -2
  102. package/dist/sandbox/pods/client.d.ts +3 -1
  103. package/dist/sandbox/pods/lifecycle.d.ts +8 -0
  104. package/dist/sandbox/pods/lifecycle.js +15 -0
  105. package/dist/sandbox/pods/spec.js +2 -1
  106. package/dist/sandbox/resolve.d.ts +5 -3
  107. package/dist/sandbox/resolve.js +9 -2
  108. package/dist/sandbox/runtime-tools.d.ts +9 -0
  109. package/dist/sandbox/runtime-tools.js +16 -0
  110. package/dist/sandbox/session-sandbox.d.ts +4 -6
  111. package/dist/sandbox/session-sandbox.js +47 -94
  112. package/dist/sandbox/setup.d.ts +4 -1
  113. package/dist/sandbox/setup.js +46 -9
  114. package/dist/sandbox/share.d.ts +12 -7
  115. package/dist/sandbox/share.js +27 -25
  116. package/dist/sandbox/skills.d.ts +8 -6
  117. package/dist/sandbox/skills.js +24 -23
  118. package/dist/store/postgres/drizzle/0024_flat_manifest.sql +7 -0
  119. package/dist/store/postgres/drizzle/0025_environment_manifests.sql +14 -0
  120. package/dist/store/postgres/drizzle/0026_environment_forks.sql +1 -0
  121. package/dist/store/postgres/drizzle/0027_environment_image_digest.sql +1 -0
  122. package/dist/store/postgres/drizzle/meta/_journal.json +28 -0
  123. package/dist/store/postgres/migrate.js +1 -1
  124. package/dist/store/postgres/reads.js +8 -2
  125. package/dist/store/postgres/schema.d.ts +217 -0
  126. package/dist/store/postgres/schema.js +36 -2
  127. package/dist/store/postgres/store.js +79 -4
  128. package/dist/store/types.d.ts +68 -14
  129. package/dist/tenant/advance.js +3 -0
  130. package/dist/tenant/artifact-tool.d.ts +1 -1
  131. package/dist/tenant/artifact-tool.js +43 -22
  132. package/dist/tenant/commands.js +24 -4
  133. package/dist/tenant/context.d.ts +10 -4
  134. package/dist/tenant/effects.js +7 -9
  135. package/dist/tenant/environment-manifests.d.ts +57 -0
  136. package/dist/tenant/environment-manifests.js +200 -0
  137. package/dist/tenant/environments.d.ts +54 -47
  138. package/dist/tenant/environments.js +309 -85
  139. package/dist/tenant/ephemeral.d.ts +1 -1
  140. package/dist/tenant/host-token.js +1 -1
  141. package/dist/tenant/identity-file.d.ts +1 -1
  142. package/dist/tenant/identity-file.js +1 -1
  143. package/dist/tenant/model-setting.d.ts +14 -0
  144. package/dist/tenant/model-setting.js +51 -0
  145. package/dist/tenant/outbound.d.ts +6 -5
  146. package/dist/tenant/outbound.js +8 -6
  147. package/dist/tenant/sessions.d.ts +5 -1
  148. package/dist/tenant/sessions.js +45 -12
  149. package/dist/tenant/skill-tool.d.ts +3 -3
  150. package/dist/tenant/skill-tool.js +6 -11
  151. package/dist/tenant/sweep.js +1 -1
  152. package/dist/tenant/tool-results.js +1 -1
  153. package/dist/tenant/tool-search.d.ts +7 -11
  154. package/dist/tenant/tool-search.js +8 -5
  155. package/dist/tenant/transcript.d.ts +51 -32
  156. package/dist/tenant/transcript.js +2 -1
  157. package/dist/tenant/types.d.ts +2 -2
  158. package/dist/traces/ids.d.ts +0 -1
  159. package/dist/traces/ids.js +0 -4
  160. package/dist/traces/map.d.ts +2 -2
  161. package/dist/traces/map.js +1 -29
  162. package/dist/vault/coverage.js +11 -13
  163. package/dist/vault/host-model.d.ts +4 -1
  164. package/dist/vault/host-model.js +10 -2
  165. package/dist/vault/service.d.ts +49 -4
  166. package/dist/vault/service.js +153 -6
  167. package/dist/version.d.ts +1 -1
  168. package/dist/version.js +1 -1
  169. package/package.json +3 -3
  170. package/dist/sandbox/capability.d.ts +0 -3
  171. 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 the session's sandbox capability and the pod. Pure: the
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 the session's sandbox capability pins and the virtual
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;
@@ -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 the session's sandbox capability pins and the virtual
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 capability added, or why it cannot take one. */
11
- export declare function withSandboxCapability(manifest: AgentManifest, spec: SandboxManifest): SandboxedManifest;
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 with a remote MCP server or an HTTP tool (R2b C11), and `nylorun.tools` for each with
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 withPlatformTools(manifest: AgentManifest): SandboxedManifest | undefined;
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 platform tools its agents
3
- * need. An agent session's pinned manifest is its definition plus:
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 `nylorun.sandbox` capability (the six built-in tools, the sandbox
6
- * instructions and the resolved spec) and the `nylorun.artifacts` capability
7
- * (`save_artifact`), also added to each agent it uses as a tool, so the tree shares one spec.
8
- * The sandbox's instructions name the agent's skills' folders (`/skills/<name>/`);
9
- * - for each agent of the tree with a remote MCP server or an HTTP tool, `read_artifact` in its
10
- * `nylorun.artifacts` capability (R2b C11), with or without a sandbox: the Runtime stores a
11
- * result of those tools too large to show as an artifact, and the model reads it in pages.
12
- * An agent that declares a tool named `read_artifact` keeps its own, and sees previews only;
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
- * The definition in the registry never changes.
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 { READ_ARTIFACT_TOOL, TOOL_CALL_TOOL, TOOL_SEARCH_TOOL, TOOLS_CAPABILITY_ID, artifactsCapabilityManifest, hashManifest, sandboxCapabilityManifest, toolsCapabilityManifest, } from "@nylorun/core/define";
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
- Array.isArray(value.capabilities));
26
+ typeof value.id === "string");
27
27
  }
28
- /** The names of the agent's own skills. */
29
- function skillNamesOf(manifest) {
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 capabilities = manifest.capabilities.map((capability) => {
57
- if (capability.tools === undefined)
58
- return capability;
59
- let toolsChanged = false;
60
- const tools = capability.tools.map((tool) => {
61
- // Agents used as tools share the session's sandbox; flow agents used as tools run in
62
- // linked sessions that inherit it. One that declares its own sandbox keeps it.
63
- if (tool.agent === undefined || !isAgentManifest(tool.agent))
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 { ...capability, tools };
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
- const save = spec !== undefined;
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
- // With a sandbox comes `save_artifact`, so files the agent makes reach the user (F8.1). The
84
- // sandbox's instructions name where the agent's skills are (R2 M4).
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
- /** `sandboxed` checked as a manifest, with its hash. */
97
- function checked(original, sandboxed) {
98
- const parsed = AgentManifestSchema.safeParse(sandboxed);
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: clash
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: sandboxed, manifestHash: hashManifest(sandboxed) };
65
+ return { ok: true, manifest: pinned, manifestHash: hashManifest(pinned) };
110
66
  }
111
- /** The agent's manifest with the sandbox capability added, or why it cannot take one. */
112
- export function withSandboxCapability(manifest, spec) {
113
- return checked(manifest, addCapabilities(manifest, spec));
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 with a remote MCP server or an HTTP tool (R2b C11), and `nylorun.tools` for each with
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 withPlatformTools(manifest) {
122
- const pinned = addCapabilities(manifest, undefined);
123
- return pinned === manifest ? undefined : checked(manifest, pinned);
75
+ export function withRuntimeTools(manifest) {
76
+ return checked(manifest, pinRuntime(manifest, undefined));
124
77
  }
@@ -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
- /** Whether the engine of `id` should run its setup again: the pod is no longer `failed`. */
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;
@@ -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
- /** Whether the engine of `id` should run its setup again: the pod is no longer `failed`. */
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 found = await ctx.store.tx(async (t) => {
102
+ const entry = await ctx.store.tx(async (t) => {
92
103
  const environment = await t.environment(id);
93
- const file = environment?.applied.files?.[index];
94
- if (!file)
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 entry = manifest.entries.find((item) => item.path === path && item.type === undefined);
119
- if (!entry)
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(entry.sha256));
122
- return got ? { kind: "file", contentType: entry.contentType, body: got.body } : undefined;
158
+ const got = await ctx.blobs.get(contentKey(listed.sha256));
159
+ return got ? { kind: "file", contentType: listed.contentType, body: got.body } : undefined;
123
160
  }
@@ -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 CapabilityManifest, type SandboxManifest, type WorkflowManifest } from "@nylorun/core/define";
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
- /** Sandbox spec declared on an agent capability or a workflow manifest. */
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: agent capabilities, the agents inlined as
22
- * tools or embedded in a flow agent, and a workflow's `sandbox`. Definitions no longer declare
23
- * one (Sandboxes v3), so registration refuses any of these.
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
- /** Synthetic capability so SandboxManager.run can use a workflow (or shared) spec. */
27
- export declare function capabilityForSandbox(sandbox: SandboxManifest): CapabilityManifest;
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.
@@ -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
- /** Sandbox spec declared on an agent capability or a workflow manifest. */
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
- const capabilities = manifest.capabilities;
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: agent capabilities, the agents inlined as
27
- * tools or embedded in a flow agent, and a workflow's `sandbox`. Definitions no longer declare
28
- * one (Sandboxes v3), so registration refuses any of these.
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 capability of document.capabilities ?? []) {
42
- if (capability.sandbox !== undefined)
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
- /** Synthetic capability so SandboxManager.run can use a workflow (or shared) spec. */
50
- export function capabilityForSandbox(sandbox) {
51
- return {
52
- id: "sandbox",
53
- type: "agent",
54
- sandbox,
55
- tools: [],
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
@@ -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
- /** The skills of the agent and the agents it uses as tools; the first of a name wins. */
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>/` of the sandbox, replacing what was there, and
11
- * makes them read-only. `read` gives a file's bytes by `sha256:<hex>`.
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, name: string, files: SkillFiles, read: (sha256: string) => Promise<Uint8Array>, signal: AbortSignal): Promise<void>;
15
+ export declare function mountSkill(handle: SandboxHandle, root: string, files: SkillFiles, read: (sha256: string) => Promise<Uint8Array>, signal: AbortSignal): Promise<void>;
@@ -1,36 +1,37 @@
1
1
  /**
2
- * A sandbox's skills (track R2 M4): each skill of the session's agent, and of the agents it uses
3
- * as tools (they share its sandbox), gets its files under `/skills/<name>/`, read-only, so the
4
- * model can read them and run their scripts with bash. The SandboxManager mounts them on a
5
- * workspace before the first call of a session's run that opens it, and again when a session
6
- * with other skills uses it; the bytes come from the Tenant's definition files.
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 { SKILLS_MOUNT } from "@nylorun/core/define";
10
- import { canonical } from "../store/canonical.js";
12
+ import { skillFolderOf } from "@nylorun/core/define";
11
13
  import { quote } from "./tools.js";
12
- /** The skills of the agent and the agents it uses as tools; the first of a name wins. */
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 capability of manifest?.capabilities ?? []) {
15
- for (const skill of Object.values(capability.skills ?? {}))
16
- if (!into.has(skill.name))
17
- into.set(skill.name, skill.files);
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>/` of the sandbox, replacing what was there, and
30
- * makes them read-only. `read` gives a file's bytes by `sha256:<hex>`.
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, name, files, read, signal) {
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";