@nylorun/runtime 0.17.0-beta → 0.19.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 (223) hide show
  1. package/CHANGELOG.md +149 -0
  2. package/README.md +88 -58
  3. package/dist/adapters/execution/restate.d.ts +1 -3
  4. package/dist/adapters/execution/restate.js +4 -31
  5. package/dist/adapters/replication/pgoutput.js +1 -1
  6. package/dist/adapters/sandbox/local.js +1 -1
  7. package/dist/adapters/sandbox/virtual.js +1 -1
  8. package/dist/api/ag-ui/routes.js +1 -1
  9. package/dist/api/ag-ui/translate.d.ts +1 -3
  10. package/dist/api/ag-ui/translate.js +2 -17
  11. package/dist/api/components.d.ts +4871 -2614
  12. package/dist/api/components.js +12 -11
  13. package/dist/api/gate/routes.d.ts +7 -8
  14. package/dist/api/gate/routes.js +58 -52
  15. package/dist/api/http/app.d.ts +6 -0
  16. package/dist/api/http/app.js +18 -14
  17. package/dist/api/http/define.d.ts +14 -2
  18. package/dist/api/http/define.js +34 -15
  19. package/dist/api/http/routes/access.d.ts +3 -3
  20. package/dist/api/http/routes/access.js +18 -17
  21. package/dist/api/http/routes/artifacts.js +2 -4
  22. package/dist/api/http/routes/files.d.ts +8 -0
  23. package/dist/api/http/routes/files.js +77 -0
  24. package/dist/api/http/routes/keys.d.ts +10 -0
  25. package/dist/api/http/routes/keys.js +64 -0
  26. package/dist/api/http/routes/me.js +5 -2
  27. package/dist/api/http/routes/reads.d.ts +66 -0
  28. package/dist/api/http/routes/reads.js +127 -0
  29. package/dist/api/http/routes/sandboxes.js +7 -4
  30. package/dist/api/http/routes/sessions.js +25 -8
  31. package/dist/api/http/routes/tenant.js +15 -18
  32. package/dist/api/http/routes/vaults.d.ts +10 -9
  33. package/dist/api/http/routes/vaults.js +27 -23
  34. package/dist/api/openapi.d.ts +10 -11
  35. package/dist/api/openapi.js +291 -61
  36. package/dist/artifacts/links.js +1 -1
  37. package/dist/core/flow-host.d.ts +44 -23
  38. package/dist/core/flow-host.js +88 -106
  39. package/dist/core/provider.d.ts +4 -1
  40. package/dist/core/provider.js +6 -1
  41. package/dist/core/sandbox-routes.d.ts +0 -9
  42. package/dist/core/sandbox-routes.js +2 -21
  43. package/dist/execution/memory.d.ts +4 -5
  44. package/dist/execution/memory.js +11 -25
  45. package/dist/execution/types.d.ts +3 -33
  46. package/dist/execution/types.js +2 -12
  47. package/dist/gates/http-tool.d.ts +86 -0
  48. package/dist/gates/http-tool.js +197 -0
  49. package/dist/gates/mcp-handler.d.ts +3 -0
  50. package/dist/gates/mcp-handler.js +1 -3
  51. package/dist/gates/meter.d.ts +2 -2
  52. package/dist/gates/meter.js +6 -0
  53. package/dist/gates/tenant-vaults.d.ts +5 -9
  54. package/dist/gates/tool-calls.d.ts +7 -15
  55. package/dist/gates/tool-calls.js +4 -4
  56. package/dist/gates/tool-client.d.ts +1 -1
  57. package/dist/gates/tool-client.js +23 -47
  58. package/dist/gates/tool-contract.d.ts +27 -39
  59. package/dist/gates/tool-contract.js +30 -21
  60. package/dist/gates/tool-gate.d.ts +25 -24
  61. package/dist/gates/tool-gate.js +11 -7
  62. package/dist/harness/calls.d.ts +10 -4
  63. package/dist/harness/calls.js +15 -6
  64. package/dist/harness/executors.d.ts +1 -1
  65. package/dist/harness/executors.js +17 -12
  66. package/dist/harness/main.d.ts +2 -2
  67. package/dist/harness/main.js +5 -13
  68. package/dist/harness/service.d.ts +4 -5
  69. package/dist/harness/service.js +9 -2
  70. package/dist/harness-api/in-process.js +2 -2
  71. package/dist/harness-api/record.d.ts +6 -3
  72. package/dist/harness-api/record.js +65 -72
  73. package/dist/harness-api/server.d.ts +1 -9
  74. package/dist/harness-api/server.js +19 -17
  75. package/dist/harness-api/start.js +1 -5
  76. package/dist/harness-api/workspace.js +2 -3
  77. package/dist/harness-api/ws-server.js +1 -1
  78. package/dist/host/app.d.ts +2 -9
  79. package/dist/host/app.js +15 -25
  80. package/dist/host/config-for.d.ts +1 -5
  81. package/dist/host/config-for.js +0 -3
  82. package/dist/host/config.d.ts +0 -5
  83. package/dist/host/create-host.d.ts +3 -33
  84. package/dist/host/create-host.js +18 -94
  85. package/dist/host/environment.d.ts +0 -3
  86. package/dist/host/environment.js +0 -9
  87. package/dist/host/gates.d.ts +4 -1
  88. package/dist/host/gates.js +3 -2
  89. package/dist/host/http.d.ts +1 -2
  90. package/dist/host/http.js +1 -7
  91. package/dist/host/index.d.ts +2 -2
  92. package/dist/host/index.js +2 -2
  93. package/dist/host/main.js +27 -34
  94. package/dist/host/operate.d.ts +29 -0
  95. package/dist/host/operate.js +223 -0
  96. package/dist/host/stack-config.d.ts +12 -12
  97. package/dist/host/stack-config.js +5 -32
  98. package/dist/index.d.ts +1 -1
  99. package/dist/index.js +1 -1
  100. package/dist/infra/workers.d.ts +1 -1
  101. package/dist/keys/keys.js +2 -2
  102. package/dist/management-openapi.json +6491 -0
  103. package/dist/mcp/connect.d.ts +8 -4
  104. package/dist/mcp/connect.js +6 -26
  105. package/dist/mcp/pool.d.ts +5 -9
  106. package/dist/mcp/pool.js +8 -19
  107. package/dist/mcp/snapshot.d.ts +8 -2
  108. package/dist/mcp/snapshot.js +29 -2
  109. package/dist/media.d.ts +1 -1
  110. package/dist/media.js +1 -1
  111. package/dist/model/pi-model.js +3 -0
  112. package/dist/openapi.json +4700 -12350
  113. package/dist/reads/access.d.ts +4 -0
  114. package/dist/reads/access.js +12 -0
  115. package/dist/reads/cursor.d.ts +11 -0
  116. package/dist/reads/cursor.js +80 -0
  117. package/dist/reads/types.d.ts +25 -0
  118. package/dist/reads/types.js +1 -0
  119. package/dist/sandbox/manager.d.ts +16 -0
  120. package/dist/sandbox/manager.js +40 -0
  121. package/dist/sandbox/session-sandbox.js +13 -3
  122. package/dist/sandbox/skills.d.ts +13 -0
  123. package/dist/sandbox/skills.js +46 -0
  124. package/dist/sandbox/types.d.ts +2 -2
  125. package/dist/store/ownership.d.ts +9 -0
  126. package/dist/store/ownership.js +13 -0
  127. package/dist/store/postgres/connect.d.ts +2 -0
  128. package/dist/store/postgres/connect.js +29 -0
  129. package/dist/store/postgres/drizzle/0011_key_roles.sql +3 -0
  130. package/dist/store/postgres/drizzle/0012_session_reads.sql +8 -0
  131. package/dist/store/postgres/drizzle/0013_definition_files.sql +16 -0
  132. package/dist/store/postgres/drizzle/0014_action_endpoints_removed.sql +2 -0
  133. package/dist/store/postgres/drizzle/meta/_journal.json +28 -0
  134. package/dist/store/postgres/reads.d.ts +3 -0
  135. package/dist/store/postgres/reads.js +272 -0
  136. package/dist/store/postgres/schema.d.ts +294 -489
  137. package/dist/store/postgres/schema.js +41 -76
  138. package/dist/store/postgres/store.js +51 -154
  139. package/dist/store/postgres/tenant.d.ts +34 -2
  140. package/dist/store/postgres/tenant.js +16 -32
  141. package/dist/store/types.d.ts +27 -96
  142. package/dist/store/types.js +0 -1
  143. package/dist/streams/control.d.ts +0 -2
  144. package/dist/streams/control.js +0 -5
  145. package/dist/streams/types.d.ts +1 -10
  146. package/dist/tenant/advance.d.ts +3 -3
  147. package/dist/tenant/advance.js +10 -7
  148. package/dist/tenant/auth.d.ts +14 -9
  149. package/dist/tenant/auth.js +24 -28
  150. package/dist/tenant/cause.d.ts +3 -3
  151. package/dist/tenant/cause.js +1 -1
  152. package/dist/tenant/commands.d.ts +2 -18
  153. package/dist/tenant/commands.js +35 -172
  154. package/dist/tenant/context.d.ts +14 -24
  155. package/dist/tenant/definition-files.d.ts +24 -0
  156. package/dist/tenant/definition-files.js +103 -0
  157. package/dist/tenant/effects.d.ts +18 -20
  158. package/dist/tenant/effects.js +56 -119
  159. package/dist/tenant/ephemeral.d.ts +7 -6
  160. package/dist/tenant/ephemeral.js +3 -11
  161. package/dist/tenant/host-token.js +1 -1
  162. package/dist/tenant/jwt.js +1 -1
  163. package/dist/tenant/module.d.ts +1 -4
  164. package/dist/tenant/module.js +3 -12
  165. package/dist/tenant/operator-keys.d.ts +34 -8
  166. package/dist/tenant/operator-keys.js +50 -18
  167. package/dist/tenant/outbound.d.ts +10 -1
  168. package/dist/tenant/outbound.js +44 -27
  169. package/dist/tenant/paths.d.ts +1 -1
  170. package/dist/tenant/paths.js +1 -3
  171. package/dist/tenant/principals.d.ts +11 -2
  172. package/dist/tenant/principals.js +27 -8
  173. package/dist/tenant/run-token.js +3 -3
  174. package/dist/tenant/runtime.d.ts +4 -6
  175. package/dist/tenant/runtime.js +19 -18
  176. package/dist/tenant/sandboxes.d.ts +4 -1
  177. package/dist/tenant/sandboxes.js +11 -0
  178. package/dist/tenant/scheduler.d.ts +1 -5
  179. package/dist/tenant/scheduler.js +2 -5
  180. package/dist/tenant/session-streams.d.ts +2 -1
  181. package/dist/tenant/session-streams.js +39 -5
  182. package/dist/tenant/session.d.ts +8 -41
  183. package/dist/tenant/session.js +0 -37
  184. package/dist/tenant/sessions.js +16 -8
  185. package/dist/tenant/signing-keys.d.ts +3 -3
  186. package/dist/tenant/signing-keys.js +5 -5
  187. package/dist/tenant/skill-tool.d.ts +20 -0
  188. package/dist/tenant/skill-tool.js +72 -0
  189. package/dist/tenant/slim.d.ts +1 -1
  190. package/dist/tenant/slim.js +1 -1
  191. package/dist/tenant/status.js +7 -12
  192. package/dist/tenant/store-pg.d.ts +15 -0
  193. package/dist/tenant/store-pg.js +7 -3
  194. package/dist/tenant/streams.d.ts +3 -10
  195. package/dist/tenant/streams.js +1 -24
  196. package/dist/tenant/sweep.js +0 -5
  197. package/dist/tenant/transcript.d.ts +2 -2
  198. package/dist/tenant/transcript.js +2 -2
  199. package/dist/tenant/types.d.ts +15 -22
  200. package/dist/tenant/worker.d.ts +1 -3
  201. package/dist/tenant/worker.js +3 -8
  202. package/dist/vault/sources.d.ts +7 -1
  203. package/dist/vault/sources.js +22 -11
  204. package/dist/version.d.ts +1 -1
  205. package/dist/version.js +1 -1
  206. package/package.json +4 -4
  207. package/dist/admin-openapi.json +0 -904
  208. package/dist/api/http/routes/actions.d.ts +0 -8
  209. package/dist/api/http/routes/actions.js +0 -75
  210. package/dist/api/http/routes/endpoints.d.ts +0 -7
  211. package/dist/api/http/routes/endpoints.js +0 -100
  212. package/dist/host/admin-api.d.ts +0 -30
  213. package/dist/host/admin-api.js +0 -140
  214. package/dist/mcp/stdio.d.ts +0 -17
  215. package/dist/mcp/stdio.js +0 -82
  216. package/dist/tenant/actions.d.ts +0 -14
  217. package/dist/tenant/actions.js +0 -29
  218. package/dist/tenant/delivery-token.d.ts +0 -41
  219. package/dist/tenant/delivery-token.js +0 -104
  220. package/dist/tenant/delivery.d.ts +0 -62
  221. package/dist/tenant/delivery.js +0 -481
  222. package/dist/tenant/endpoints.d.ts +0 -67
  223. package/dist/tenant/endpoints.js +0 -90
@@ -8,25 +8,24 @@ import { type HarnessClient, type HarnessClientOptions } from "./ws-client.js";
8
8
  export interface HarnessServiceOptions {
9
9
  readonly url: string;
10
10
  readonly token: HarnessClientOptions["token"];
11
- /** Where workspaces (and their records) live, and the plugins' data. */
11
+ /** Where workspaces (and their records) live. */
12
12
  readonly paths: {
13
13
  readonly sandboxes: string;
14
- readonly pluginData: string;
15
14
  };
16
- /** The environment of MCP stdio servers: the allowlisted base, with HOME and TMPDIR. */
17
- readonly childEnv: Readonly<Record<string, string>>;
18
15
  /** Vault-backed model calls (the gates service's client). */
19
16
  readonly modelGate: ModelGate;
20
17
  /** Models served without the vault; a harness process has none (`useVaultModel`). */
21
18
  readonly modelProvider?: ModelProvider;
22
19
  readonly useVaultModel: boolean;
23
20
  /** Remote MCP servers (`openMcp`) and keyed cancels; the gates service's client. */
24
- readonly toolGate: Pick<ToolGate, "recovers" | "cancel" | "openMcp">;
21
+ readonly toolGate: Pick<ToolGate, "recovers" | "cancel" | "openMcp" | "callHttp">;
25
22
  /**
26
23
  * Authorizes a remote MCP server opened in this process. A harness process has no vault: it
27
24
  * opens remote servers through the Tool Gate, and refuses them without one.
28
25
  */
29
26
  readonly authorize?: ConstructorParameters<typeof McpPool>[0]["authorize"];
27
+ /** How remote MCP servers opened in this process are reached (`TenantConfig.delivery`). */
28
+ readonly delivery?: ConstructorParameters<typeof McpPool>[0]["policy"];
30
29
  /** Default: the virtual backend under `paths.sandboxes`. */
31
30
  readonly sandboxBackends?: readonly SandboxBackend[];
32
31
  /** Delete the workspaces on stop (an ephemeral Runtime's loopback harness). */
@@ -51,12 +51,11 @@ export function startHarnessService(options) {
51
51
  return manager;
52
52
  };
53
53
  const pool = new McpPool({
54
- pluginData: options.paths.pluginData,
55
- childEnv: options.childEnv,
56
54
  authorize: options.authorize ??
57
55
  (async () => {
58
56
  throw new Error("This harness opens remote MCP servers only through the gates service");
59
57
  }),
58
+ ...(options.delivery ? { policy: options.delivery } : {}),
60
59
  ...(options.toolGate.openMcp ? { openRemote: (server) => options.toolGate.openMcp(server) } : {}),
61
60
  });
62
61
  const sweep = setInterval(() => void pool.sweep().catch(() => undefined), MCP_SWEEP_MS);
@@ -140,6 +139,14 @@ export function startHarnessService(options) {
140
139
  backends,
141
140
  preference: answer.sandbox.backend ?? "auto",
142
141
  ephemeral: options.ephemeral === true,
142
+ // A skill file's bytes come from core, for the run that made the call.
143
+ definitionFile: async (sha256, session) => {
144
+ const channel = client.channel();
145
+ if (!channel || session.claim === undefined)
146
+ throw new Error(`No run to fetch ${sha256} for`);
147
+ const { base64 } = await channel.request("definition.file", { runId: session.claim, sha256 });
148
+ return new Uint8Array(Buffer.from(base64, "base64"));
149
+ },
143
150
  // A claim core refuses (its run ended, the session is gone) is dropped, as core's own
144
151
  // SandboxManager drops an event it cannot write.
145
152
  emit: async (sessionId, turnId, type, payload, meta) => {
@@ -66,8 +66,7 @@ export async function startLoopbackHarness(ctx, options = {}) {
66
66
  const service = startHarnessService({
67
67
  url: listener.url,
68
68
  token,
69
- paths: { sandboxes: ctx.config.paths.sandboxes, pluginData: ctx.config.paths.pluginData },
70
- childEnv: ctx.config.childEnv,
69
+ paths: { sandboxes: ctx.config.paths.sandboxes },
71
70
  modelGate: route.modelGate,
72
71
  modelProvider: route.modelProvider,
73
72
  useVaultModel: route.useVaultModel,
@@ -75,6 +74,7 @@ export async function startLoopbackHarness(ctx, options = {}) {
75
74
  ...(ctx.toolGate.openMcp
76
75
  ? {}
77
76
  : { authorize: (sessionId, request) => authorize(ctx, sessionId, request) }),
77
+ ...(ctx.config.delivery ? { delivery: ctx.config.delivery } : {}),
78
78
  ...(options.sandboxBackends ? { sandboxBackends: options.sandboxBackends } : {}),
79
79
  logger: quietLogger(ctx),
80
80
  name: "loopback",
@@ -1,9 +1,12 @@
1
1
  /**
2
2
  * The Record seam of the Harness API: core's journal for the effects of a run. `recordIntent`
3
3
  * journals an effect before anything runs it, and either answers it from the journal, hands it
4
- * to core's own executors (Actions, flow work, delegation), or tells the harness to `execute`
5
- * it. `recordOutcome` records what the harness's call returned. Both run under the advance's
6
- * lease: every write is epoch-checked (`ownedSession`), and a lost epoch writes nothing.
4
+ * to core's own executors (flow work, delegation, `save_artifact` and the skill tools), tells
5
+ * the harness to `execute` it (model calls; MCP, HTTP and sandbox tools; a flow's HTTP stages
6
+ * and HTTP verifiers), or fails a tool the Runtime cannot run (one that would run the
7
+ * developer's code, R2 M6). `recordOutcome`
8
+ * records what the harness's call returned. Both run under the advance's lease: every write is
9
+ * epoch-checked (`ownedSession`), and a lost epoch writes nothing.
7
10
  *
8
11
  * Rows store the request's hash (`requestHash`) and, for a model call, the request without its
9
12
  * prompt: drift is a hash compare. Rows written before carry their full request, hashed on read.
@@ -1,25 +1,28 @@
1
1
  /**
2
2
  * The Record seam of the Harness API: core's journal for the effects of a run. `recordIntent`
3
3
  * journals an effect before anything runs it, and either answers it from the journal, hands it
4
- * to core's own executors (Actions, flow work, delegation), or tells the harness to `execute`
5
- * it. `recordOutcome` records what the harness's call returned. Both run under the advance's
6
- * lease: every write is epoch-checked (`ownedSession`), and a lost epoch writes nothing.
4
+ * to core's own executors (flow work, delegation, `save_artifact` and the skill tools), tells
5
+ * the harness to `execute` it (model calls; MCP, HTTP and sandbox tools; a flow's HTTP stages
6
+ * and HTTP verifiers), or fails a tool the Runtime cannot run (one that would run the
7
+ * developer's code, R2 M6). `recordOutcome`
8
+ * records what the harness's call returned. Both run under the advance's lease: every write is
9
+ * epoch-checked (`ownedSession`), and a lost epoch writes nothing.
7
10
  *
8
11
  * Rows store the request's hash (`requestHash`) and, for a model call, the request without its
9
12
  * prompt: drift is a hash compare. Rows written before carry their full request, hashed on read.
10
13
  */
11
14
  import { HarnessApiError, effectRequestHash, } from "@nylorun/core/harness-api";
12
- import { isFlowEffect, isFlowToolEffect } from "../core/flow-host.js";
15
+ import { isFlowEffect, isFlowToolEffect, recordVerdict, settleAgentEffect, } from "../core/flow-host.js";
13
16
  import { sandboxCapabilityOf } from "../sandbox/capability.js";
14
17
  import { isSaveArtifactCall } from "../harness/calls.js";
15
18
  import { callSaveArtifact } from "../tenant/artifact-tool.js";
19
+ import { callSkillTool, isSkillToolCall } from "../tenant/skill-tool.js";
16
20
  import { isOwnershipLost } from "../store/ownership.js";
17
21
  import { manifestFor, mcpToolOf } from "../mcp/snapshot.js";
22
+ import { isHttpToolCall } from "../gates/http-tool.js";
18
23
  import { ownedSession } from "../tenant/context.js";
19
- import { actionTarget, pinnedTool } from "../tenant/session.js";
20
- import { offerAction } from "../tenant/delivery.js";
21
- import { isRemoteMcpEffect, linkedOutcome, recoversMcpCalls, recoversModelCalls, resolveNewFlowEffect, } from "../tenant/effects.js";
22
- import { assistantMessage, contextCompacted, modelFailed, toolCompleted, toolIds, } from "../tenant/transcript.js";
24
+ import { isGateToolEffect, linkedOutcome, recoversModelCalls, recoversToolCalls, resolveNewFlowEffect, } from "../tenant/effects.js";
25
+ import { assistantMessage, contextCompacted, modelFailed, toolCompleted, } from "../tenant/transcript.js";
23
26
  import { abortKind } from "../tenant/worker.js";
24
27
  const TURN_CANCELLED = () => new HarnessApiError("turn_cancelled", "Turn cancelled");
25
28
  /** The hash a journal row's request was recorded with. */
@@ -54,18 +57,17 @@ export async function recordIntent(scope, effect, requestHash) {
54
57
  if (existing.status === "completed")
55
58
  return { status: "completed", outcome: existing.outcome };
56
59
  // A call its previous owner left running at the gate: re-send it, same key and request,
57
- // to join it or collect its outcome (P1.2 for model calls, F4.1 for remote MCP calls).
60
+ // to join it or collect its outcome (P1.2 for model calls, F4.1 for remote MCP and HTTP
61
+ // tool calls).
58
62
  if (existing.status === "invoking" && request.kind === "model" && recoversModelCalls(ctx))
59
63
  return { status: "execute", rejoin: true };
60
- if (existing.status === "invoking" && recoversMcpCalls(ctx) && isRemoteMcpEffect(s, existing.request))
64
+ if (existing.status === "invoking" && recoversToolCalls(ctx) && isGateToolEffect(s, existing.request))
61
65
  return { status: "execute", rejoin: true };
62
66
  if (request.kind === "agent" && existing.status === "pending" && existing.agentSessionId) {
63
67
  const agent = await t.get("sessions", existing.agentSessionId);
64
68
  const outcome = await linkedOutcome(t, existing, agent);
65
69
  if (outcome) {
66
- existing.status = "completed";
67
- existing.outcome = outcome;
68
- await t.put("effects", request.effectId, existing);
70
+ await settleAgentEffect({ t, effect: existing, outcome });
69
71
  return { status: "completed", outcome };
70
72
  }
71
73
  }
@@ -73,13 +75,22 @@ export async function recordIntent(scope, effect, requestHash) {
73
75
  status: existing.status === "uncertain" || existing.status === "invoking" ? "uncertain" : "pending",
74
76
  };
75
77
  }
76
- if (request.kind === "agent" ||
77
- request.kind === "fn" ||
78
- request.kind === "verify" ||
79
- isFlowToolEffect(request))
78
+ // A flow's HTTP stages and HTTP verifiers cross the Tool Gate like an agent's HTTP tool.
79
+ const flowHttp = isFlowToolEffect(request) && isHttpToolCall(s.manifest, request);
80
+ if (request.kind === "agent" || (isFlowToolEffect(request) && !flowHttp))
80
81
  return "flow";
81
82
  if (request.kind !== "model" && effectRequestHash(request) !== requestHash)
82
83
  throw new HarnessApiError("invalid", "The request hash does not match the request");
84
+ if (flowHttp) {
85
+ await t.put("effects", request.effectId, { request, requestHash, status: "invoking" });
86
+ await t.event(s.id, s.activeTurnId, "node.started", {
87
+ path: request.path,
88
+ kind: "http",
89
+ key: request.key,
90
+ ...(request.iterations !== undefined ? { iterations: request.iterations } : {}),
91
+ });
92
+ return { status: "execute" };
93
+ }
83
94
  if (request.kind === "delegation") {
84
95
  // Lifecycle points of an agent used as a tool: journaled once, so replays never re-emit.
85
96
  const outcome = { value: null };
@@ -99,25 +110,40 @@ export async function recordIntent(scope, effect, requestHash) {
99
110
  const executed = request.kind === "model" ||
100
111
  (request.kind === "tool" &&
101
112
  (mcpToolOf(s.mcpSnapshot, request) !== undefined ||
113
+ // HTTP tools (R2 M3) cross the Tool Gate.
114
+ isHttpToolCall(s.manifest, request) ||
102
115
  sandboxCapabilityOf(agentManifest, request.capabilityId, request.toolName) !== undefined ||
103
- // `save_artifact` (F8.1) runs beside the sandbox tools.
104
- isSaveArtifactCall(agentManifest, request)));
116
+ // `save_artifact` (F8.1) runs beside the sandbox tools, and the skill tools (R2 M4).
117
+ isSaveArtifactCall(agentManifest, request) ||
118
+ isSkillToolCall(agentManifest, request)));
119
+ if (!executed) {
120
+ // A tool that would run the developer's code: refused at save (a turn's manifest only
121
+ // removes tools), so this is a backstop. The model sees the failure; nothing runs.
122
+ const outcome = { value: unrunnableTool(request) };
123
+ await t.put("effects", request.effectId, { request, requestHash, status: "completed", outcome });
124
+ const transcript = toolCompleted(request, outcome.value);
125
+ if (transcript)
126
+ await t.event(s.id, request.turnId, "tool.completed", transcript);
127
+ return { status: "completed", outcome };
128
+ }
105
129
  await t.put("effects", request.effectId, {
106
130
  request: storedRequest(request),
107
131
  requestHash,
108
- status: executed ? "invoking" : "pending",
132
+ status: "invoking",
109
133
  });
110
134
  // `save_artifact` writes the Tenant's artifacts: core runs it, reading the file through the
111
135
  // workspace capability, wherever the sandbox is.
112
- if (executed && request.kind === "tool" && isSaveArtifactCall(agentManifest, request))
136
+ if (request.kind === "tool" && isSaveArtifactCall(agentManifest, request))
113
137
  return "save";
114
- if (executed)
115
- return { status: "execute" };
116
- await offerActionFor(scope, t, s, agentManifest, request);
117
- return { status: "pending" };
138
+ // The skill tools read the agent's definition files: core serves them.
139
+ if (isSkillToolCall(agentManifest, request))
140
+ return { skill: agentManifest };
141
+ return { status: "execute" };
118
142
  });
119
143
  if (answer === "save")
120
- return saveArtifact(scope, request);
144
+ return runInCore(scope, request, () => callSaveArtifact(scope.ctx, request, scope.signal));
145
+ if (typeof answer === "object" && "skill" in answer)
146
+ return runInCore(scope, request, () => callSkillTool(scope.ctx, answer.skill, request, scope.signal));
121
147
  if (answer !== "flow")
122
148
  return answer;
123
149
  if (!isFlowEffect(request))
@@ -125,13 +151,14 @@ export async function recordIntent(scope, effect, requestHash) {
125
151
  return resolveNewFlowEffect(ctx, request, signal, lease);
126
152
  }
127
153
  /**
128
- * Runs `save_artifact` (F8.1) for the run and records its outcome, as a harness would record a
129
- * call it ran: the run gets the outcome as the intent's answer.
154
+ * Runs a tool core serves (`save_artifact`, F8.1; the skill tools, R2 M4) for the run and records
155
+ * its outcome, as a harness would record a call it ran: the run gets the outcome as the intent's
156
+ * answer.
130
157
  */
131
- async function saveArtifact(scope, request) {
158
+ async function runInCore(scope, request, call) {
132
159
  let value;
133
160
  try {
134
- value = await callSaveArtifact(scope.ctx, request, scope.signal);
161
+ value = await call();
135
162
  }
136
163
  catch (error) {
137
164
  if (isOwnershipLost(error))
@@ -142,49 +169,13 @@ async function saveArtifact(scope, request) {
142
169
  }
143
170
  return recordOutcome(scope, request.effectId, { value });
144
171
  }
145
- /** A tool or hook Action for the agent's endpoint, in the intent's transaction. */
146
- async function offerActionFor(scope, t, s, agentManifest, request) {
147
- const tool = request.kind === "tool" ? pinnedTool(agentManifest, request.capabilityId, request.toolName) : undefined;
148
- const base = {
149
- actionId: request.effectId,
150
- sessionId: request.sessionId,
151
- turnId: request.turnId,
152
- agentId: request.agentId,
153
- manifestHash: request.manifestHash,
154
- implementationVersion: s.implementationVersion,
155
- input: request.input,
156
- context: request.context,
157
- status: "pending",
158
- generation: 0,
159
- ...(request.agent ? { agent: request.agent } : {}),
172
+ /** The failed outcome of a tool the Runtime cannot run: it would run the developer's code. */
173
+ function unrunnableTool(request) {
174
+ return {
175
+ kind: "failed",
176
+ code: "tool.unavailable",
177
+ message: `Tool '${request.toolName ?? ""}' runs your code, and the Runtime runs no code of yours during a session. Make it an http() tool or serve it from a remote MCP server.`,
160
178
  };
161
- const action = request.kind === "hook"
162
- ? {
163
- ...base,
164
- kind: "hook",
165
- hook: {
166
- at: request.hook.at,
167
- scope: request.hook.scope,
168
- capabilityIds: [...request.hook.capabilityIds],
169
- },
170
- }
171
- : {
172
- ...base,
173
- kind: "tool",
174
- capabilityId: request.capabilityId,
175
- toolName: request.toolName,
176
- ...(tool?.inputSchema ? { inputSchema: tool.inputSchema } : {}),
177
- ...(tool?.outputSchema ? { outputSchema: tool.outputSchema } : {}),
178
- };
179
- await t.put("actions", action.actionId, action);
180
- await t.event(s.id, s.activeTurnId, "action.pending", {
181
- actionId: action.actionId,
182
- kind: action.kind,
183
- ...actionTarget(action),
184
- ...(action.kind === "tool" ? toolIds(request.context) : {}),
185
- input: action.input,
186
- });
187
- await offerAction(t, scope.ctx, action);
188
179
  }
189
180
  /**
190
181
  * Records the outcome of a call the harness ran: `value`, with its transcript events, or the
@@ -230,6 +221,8 @@ export async function recordOutcome(scope, effectId, result) {
230
221
  : model
231
222
  ? assistantMessage(request, value)
232
223
  : toolCompleted(request, value);
224
+ if (request.context?.role === "verify-http")
225
+ await recordVerdict(t, request, value);
233
226
  if (failed)
234
227
  await t.event(s.id, request.turnId, "model.failed", failed);
235
228
  if (compacted)
@@ -1,5 +1,4 @@
1
1
  import { type CoreMethod, type HarnessChannel, type OutputMethod, type ParamsOf, type ReleaseReason, type ResultOf, type TurnOutput, type TurnStart } from "@nylorun/core/harness-api";
2
- import type { ActionOutcome } from "@nylorun/core/contracts";
3
2
  import { type Lease, type TenantContext } from "../tenant/context.js";
4
3
  import type { RunOf } from "../tenant/run-grants.js";
5
4
  /** Who is on the other end of a connection. */
@@ -57,13 +56,6 @@ export interface HarnessApiServer {
57
56
  /** Serves a harness's channel until it closes. Returns a function that detaches it. */
58
57
  attach(channel: HarnessChannel, peer: HarnessPeer): () => void;
59
58
  offer(offer: RunOffer): Promise<RunEnd>;
60
- /**
61
- * An Action outcome of session `sessionId` was recorded: the run holding it, if any, gets it
62
- * (`effect.resolved`). Call it after the outcome's commit.
63
- */
64
- resolved(sessionId: string, effectId: string, outcome: ActionOutcome): void;
65
- /** A harness holds a run of `sessionId` here. */
66
- holds(sessionId: string): boolean;
67
59
  /**
68
60
  * Sends a `workspace.*` request to a harness that serves workspaces: with `pod`, the host of
69
61
  * that pod sandbox; otherwise one that hosts no sandbox. Throws `NoWorkspaceHarness` when
@@ -85,7 +77,7 @@ export interface HarnessApiServer {
85
77
  readonly connected: number;
86
78
  close(): void;
87
79
  }
88
- /** The Harness API's state, for Tenant and admin status. */
80
+ /** The Harness API's state, for Tenant status and `/ready`. */
89
81
  export interface HarnessStatus {
90
82
  readonly connected: number;
91
83
  readonly workspace: boolean;
@@ -10,8 +10,7 @@
10
10
  * the connection's loss (`connection.lost`).
11
11
  *
12
12
  * F6.2: a harness readies the session's MCP servers itself and records what it found
13
- * (`session.mcp`); an Action outcome recorded while its run is held reaches it as
14
- * `effect.resolved`; and a harness that declared `workspace` in `hello` serves the Tenant's
13
+ * (`session.mcp`), and a harness that declared `workspace` in `hello` serves the Tenant's
15
14
  * workspaces (`workspace`, the `workspace.*` requests). A harness claims `sandbox.*` events
16
15
  * only for a run it holds (its session, or the session owning the run's sandbox) or a session a
17
16
  * workspace request it is serving acts for; a `sandbox.state` claim's record becomes the
@@ -27,11 +26,13 @@
27
26
  */
28
27
  import { randomUUID } from "node:crypto";
29
28
  import { HARNESS_API_VERSION, HARNESS_CLAIMS, HarnessApiError, } from "@nylorun/core/harness-api";
29
+ import { definitionFilesOf } from "@nylorun/core/contracts";
30
30
  import { isOwnershipLost } from "../store/ownership.js";
31
31
  import { ownedSession } from "../tenant/context.js";
32
- import { sessionToolsOf } from "../mcp/snapshot.js";
32
+ import { mcpDiscovered, sessionToolsOf } from "../mcp/snapshot.js";
33
33
  import { sandboxWorkspaceKey, workspacePrefix } from "../sandbox/records.js";
34
34
  import { abortKind } from "../tenant/worker.js";
35
+ import { readDefinitionFile } from "../tenant/definition-files.js";
35
36
  import { recordIntent, recordOutcome } from "./record.js";
36
37
  import { beat, renewEveryMs, tokenOf } from "./renew.js";
37
38
  /** No attached harness serves workspaces (or, with `pod`, that pod sandbox's). */
@@ -141,6 +142,8 @@ export function createHarnessApiServer(ctx, options = {}) {
141
142
  return { cursor: run.offer.start.transcript.cursor, entries: [...run.offer.transcript] };
142
143
  case "session.mcp":
143
144
  return recordMcp(run, p);
145
+ case "definition.file":
146
+ return definitionFile(run, p.sha256, signal);
144
147
  default:
145
148
  throw new HarnessApiError("invalid", `Unknown request ${method}`);
146
149
  }
@@ -188,8 +191,8 @@ export function createHarnessApiServer(ctx, options = {}) {
188
191
  };
189
192
  /**
190
193
  * What a harness found readying the run's MCP servers: the first snapshot recorded for the
191
- * session wins; diagnostics replace those of the same server. Answers the session's snapshot
192
- * and the tools the engine advertises.
194
+ * session wins, with `mcp.discovered` in its log; diagnostics replace those of the same server.
195
+ * Answers the session's snapshot and the tools the engine advertises.
193
196
  */
194
197
  const recordMcp = (run, params) => ctx.store.tx(async (t) => {
195
198
  const { lease } = run.offer;
@@ -202,6 +205,7 @@ export function createHarnessApiServer(ctx, options = {}) {
202
205
  current.mcpSnapshot = snapshot;
203
206
  current.mcpDiagnostics = diagnostics;
204
207
  await t.put("sessions", current.id, current);
208
+ await t.event(current.id, run.grant.turnId, "mcp.discovered", mcpDiscovered(snapshot, diagnostics));
205
209
  }
206
210
  else if (diagnostics.length > 0) {
207
211
  const prior = [...(current.mcpDiagnostics ?? [])];
@@ -217,9 +221,18 @@ export function createHarnessApiServer(ctx, options = {}) {
217
221
  }
218
222
  return {
219
223
  ...(current.mcpSnapshot ? { snapshot: current.mcpSnapshot } : {}),
220
- sessionTools: [...(sessionToolsOf(current.mcpSnapshot) ?? [])],
224
+ sessionTools: [...(sessionToolsOf(current.mcpSnapshot, current.manifest) ?? [])],
221
225
  };
222
226
  });
227
+ /** A definition file the run's definition names (its skills', for its sandbox), base64. */
228
+ const definitionFile = async (run, sha256, signal) => {
229
+ if (!definitionFilesOf(run.offer.start.routing.rootManifest).has(sha256))
230
+ throw new HarnessApiError("invalid", `The run's definition names no file ${sha256}`);
231
+ const bytes = await readDefinitionFile(ctx.blobs, sha256, signal);
232
+ if (!bytes)
233
+ throw new HarnessApiError("unavailable", `The Object store has no bytes for ${sha256}`);
234
+ return { base64: Buffer.from(bytes).toString("base64") };
235
+ };
223
236
  const output = (run, method, params) => {
224
237
  const known = run.outputs.get(method);
225
238
  if (known)
@@ -252,17 +265,6 @@ export function createHarnessApiServer(ctx, options = {}) {
252
265
  if (connection.host?.sandboxId === sandboxId && connection.host.epoch < epoch)
253
266
  connection.channel.close("the sandbox's host epoch moved");
254
267
  },
255
- holds(sessionId) {
256
- for (const run of runs.values())
257
- if (run.grant.sessionId === sessionId && !run.ended && run.connection)
258
- return true;
259
- return false;
260
- },
261
- resolved(sessionId, effectId, outcome) {
262
- for (const run of runs.values())
263
- if (run.grant.sessionId === sessionId && !run.ended && run.connection)
264
- run.connection.channel.notify("effect.resolved", { runId: run.grant.runId, effectId, outcome });
265
- },
266
268
  async workspace(method, params, signal, target) {
267
269
  const pod = target?.pod;
268
270
  const connection = [...connections].find((item) => item.workspace && !item.channel.closed && item.host?.sandboxId === pod);
@@ -6,8 +6,6 @@ import { checkParity, foldTranscriptWithCursor, leanState, transcriptOf, transcr
6
6
  import { usesFixtureModel } from "../tenant/model-setting.js";
7
7
  import { rebaseSessionState, sessionToolsOf, turnManifestOf } from "../tenant/session.js";
8
8
  import { bulkOutcomes } from "./record.js";
9
- /** How long a run waits in its lease for a pending Action's outcome (F6.2), by default. */
10
- const ACTION_HOLD_MS = 5 * 60_000;
11
9
  /** Segment rollover defaults (Model Calls §10), well inside the advance deadline. */
12
10
  const ROLLOVER_STEPS = 50;
13
11
  const ROLLOVER_MS = 20 * 60_000;
@@ -59,7 +57,7 @@ export function buildTurnStart(ctx, segment) {
59
57
  checkpoint: !flow && cp.state
60
58
  ? { ...cp, state: withTranscript(cp.state, []) }
61
59
  : cp,
62
- ...(flow ? {} : { sessionTools: sessionToolsOf(current.mcpSnapshot) ?? [] }),
60
+ ...(flow ? {} : { sessionTools: sessionToolsOf(current.mcpSnapshot, current.manifest) ?? [] }),
63
61
  outcomes: segment.outcomes,
64
62
  transcript: { cursor: segment.cursor },
65
63
  options: {
@@ -67,11 +65,9 @@ export function buildTurnStart(ctx, segment) {
67
65
  ? { flowLimits: ctx.flowLimits }
68
66
  : { yieldAfter: yieldAfterOf(ctx) }),
69
67
  fixtureModel: segment.fixtureModel,
70
- holdMs: ctx.config.actionHoldMs ?? ACTION_HOLD_MS,
71
68
  },
72
69
  routing: {
73
70
  rootManifest: current.manifest,
74
- pluginRoots: current.pluginRoots ?? {},
75
71
  ...(current.mcpSnapshot ? { mcpSnapshot: current.mcpSnapshot } : {}),
76
72
  sandbox: segment.sandbox,
77
73
  },
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * The workspace capability (F6.2): what core does with the Tenant's sandbox workspaces outside a
3
- * run. `ctx.sandbox` is a `WorkspacePort`: the sandbox tool routes (`POST
4
- * /v1/sessions/:id/sandbox/:tool` and an Action endpoint's `POST /v1/actions/:id/sandbox/:tool`),
5
- * Tenant status, the Tenant sweep, a sandbox resource's deletion and a sandboxes reset.
3
+ * run. `ctx.sandbox` is a `WorkspacePort`: the sandbox tool route (`POST
4
+ * /v1/sessions/:id/sandbox/:tool`), Tenant status, the Tenant sweep, a sandbox resource's deletion and a sandboxes reset.
6
5
  *
7
6
  * - **In process** (`localWorkspace`): the Tenant's own SandboxManager, which its in-process
8
7
  * harness also runs sandbox tools with.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * The Harness API listener (F6.2): core's own port for harnesses (`NYLORUN_HARNESS_LISTEN_*`,
3
- * default 4200), apart from the Tenant and Admin APIs. It accepts the harness credential
3
+ * default 4200), apart from the Runtime and Management APIs. It accepts the harness credential
4
4
  * (`NYLORUN_HARNESS_TOKEN`), and nothing else does; nothing else is served here.
5
5
  *
6
6
  * An upgrade is accepted only on `/nylorun/harness/v1`, with a `Host` in the allowlist (421),
@@ -1,11 +1,7 @@
1
1
  import { Hono } from "hono";
2
2
  import type { Logger, NodeBindings, TenantModule } from "../tenant/types.js";
3
- import type { ListenerRole } from "./create-host.js";
4
- export type HostBindings = NodeBindings & {
5
- readonly role: ListenerRole;
6
- };
7
3
  export type HostEnv = {
8
- Bindings: HostBindings;
4
+ Bindings: NodeBindings;
9
5
  Variables: {
10
6
  /** The Tenant a request reached, for the request log. */
11
7
  tenantId?: string;
@@ -17,17 +13,14 @@ export interface HostAppOptions {
17
13
  module: TenantModule;
18
14
  logger: Logger;
19
15
  hostId: string;
20
- adminKey: string;
21
16
  coreVersion: string;
22
17
  pid: number;
23
18
  readiness?: () => Promise<{
24
19
  ok: boolean;
25
20
  checks: Record<string, boolean>;
26
21
  }>;
27
- /** Every listener is listening. */
22
+ /** The listener is listening. */
28
23
  listening(): boolean;
29
24
  closing(): boolean;
30
- /** The Admin API (`admin-api.ts`), for a request whose admin key checked out. */
31
- admin(request: Request, node: NodeBindings): Promise<Response>;
32
25
  }
33
26
  export declare function createHostApp(options: HostAppOptions): Hono<HostEnv>;
package/dist/host/app.js CHANGED
@@ -3,8 +3,8 @@ import { RESPONSE_ALREADY_SENT } from "@hono/node-server/utils/response";
3
3
  import { checkCompatibility, HOST_PROTOCOL, PROTOCOL_HEADER, TENANT_HEADER, } from "@nylorun/core/compatibility";
4
4
  import { RUNTIME_VERSION } from "../version.js";
5
5
  import { findTenantRoute } from "../api/http/app.js";
6
- import { tenantDocument } from "../api/openapi.js";
7
- import { adminKeyMatches, headerValue, isJsonContentType, jsonResponse, opaqueNotFoundResponse, protocolRejectedResponse, readBearer, pathnameIsLogged, redactRoutePath, rejectedResponse, requestHasBody, } from "./http.js";
6
+ import { managementDocument, runtimeDocument } from "../api/openapi.js";
7
+ import { headerValue, isJsonContentType, jsonResponse, opaqueNotFoundResponse, protocolRejectedResponse, pathnameIsLogged, redactRoutePath, rejectedResponse, requestHasBody, } from "./http.js";
8
8
  export function createHostApp(options) {
9
9
  const { module, logger } = options;
10
10
  const app = new Hono();
@@ -26,13 +26,11 @@ export function createHostApp(options) {
26
26
  }
27
27
  });
28
28
  app.use(async (c, next) => {
29
- const { incoming, role } = c.env;
29
+ const { incoming } = c.env;
30
30
  if (headerValue(incoming, "origin") !== undefined) {
31
- const route = pathnameOf(incoming).split("/").filter(Boolean);
32
- // Only Tenant routes of the public listener; the Tenant then accepts only a trusted
33
- // issuer's token from a browser. The operator listener never serves browsers.
34
- const tenantRoute = route[0] === "v1" && route[1] !== "admin";
35
- if (role === "operator" || !tenantRoute)
31
+ // Only Tenant routes; the Tenant then accepts only a trusted issuer's token from a
32
+ // browser.
33
+ if (pathnameOf(incoming).split("/").filter(Boolean)[0] !== "v1")
36
34
  return rejectedResponse(403, "origin_rejected", "Browser Origin headers are not accepted");
37
35
  }
38
36
  // CORS is the operator's proxy's: an OPTIONS request gets no CORS header.
@@ -50,7 +48,7 @@ export function createHostApp(options) {
50
48
  await next();
51
49
  });
52
50
  app.all("*", async (c) => {
53
- const { incoming, outgoing, role } = c.env;
51
+ const { incoming, outgoing } = c.env;
54
52
  const url = new URL(incoming.url ?? "/", "http://runtime.local");
55
53
  const pathname = url.pathname;
56
54
  if (pathname === "/health")
@@ -72,27 +70,19 @@ export function createHostApp(options) {
72
70
  const tenant = module.ready;
73
71
  const infra = await options.readiness?.();
74
72
  const ready = listener && tenant && !options.closing() && (infra?.ok ?? true);
73
+ const harness = module.harnessStatus();
75
74
  return jsonResponse(ready ? 200 : 503, {
76
75
  status: ready ? "ready" : "not_ready",
77
76
  service: "nylorun-runtime",
78
77
  checks: { listener, tenant, ...infra?.checks },
78
+ ...(harness ? { harness: { mode: harness.mode, connected: harness.connected } } : {}),
79
79
  });
80
80
  }
81
- // This API's own description: public, as the npm package that ships it.
82
- if (pathname === "/openapi.json" && incoming.method === "GET")
83
- return jsonResponse(200, tenantDocument(), { "cache-control": "no-cache" });
84
- const segments = pathname.split("/").filter(Boolean);
85
- if (segments[0] === "v1" && segments[1] === "admin") {
86
- // A public listener has no admin routes: the same 404 as a wrong admin key.
87
- if (role === "public")
88
- return opaqueNotFoundResponse();
89
- if (!protocolAccepted(headerValue(incoming, PROTOCOL_HEADER)))
90
- return protocolRejectedResponse();
91
- const token = readBearer(headerValue(incoming, "authorization"));
92
- if (!adminKeyMatches(token, options.adminKey))
93
- return opaqueNotFoundResponse();
94
- return await options.admin(c.req.raw, { incoming, outgoing });
95
- }
81
+ // The two APIs' descriptions: public, as the npm package that ships them.
82
+ if ((pathname === "/openapi.json" || pathname === "/openapi/runtime.json") && incoming.method === "GET")
83
+ return jsonResponse(200, runtimeDocument(), { "cache-control": "no-cache" });
84
+ if (pathname === "/openapi/management.json" && incoming.method === "GET")
85
+ return jsonResponse(200, managementDocument(), { "cache-control": "no-cache" });
96
86
  // Tenant routes: protocol → the Tenant → selection → the Tenant's routes.
97
87
  const tenantHeader = headerValue(incoming, TENANT_HEADER);
98
88
  const named = tenantHeader === undefined || tenantHeader.trim() === "" ? undefined : tenantHeader.trim();
@@ -159,7 +149,7 @@ function tenantRouteOf(incoming) {
159
149
  return undefined;
160
150
  }
161
151
  }
162
- if (segments[0] !== "v1" || segments[1] === "admin")
152
+ if (segments[0] !== "v1")
163
153
  return undefined;
164
154
  return findTenantRoute(incoming.method ?? "GET", segments);
165
155
  }
@@ -1,4 +1,3 @@
1
- import type { HostConfigFile } from "./config.js";
2
1
  import type { Logger, TenantConfig } from "../tenant/types.js";
3
2
  /**
4
3
  * Builds the TenantConfig of the Host's Tenant for `openTenantRuntime`.
@@ -7,14 +6,11 @@ import type { Logger, TenantConfig } from "../tenant/types.js";
7
6
  */
8
7
  export declare function configForFactory(options: {
9
8
  hostRoot: string;
10
- hostConfig: HostConfigFile;
11
9
  logger: Logger;
12
- /** Allowlisted baseline from `baselineEnvironment` (built in `host/main.ts`). */
13
- baseline: Readonly<Record<string, string>>;
14
10
  mode?: TenantConfig["mode"];
15
11
  /** Override model; default vault. Fixture/scripted require ephemeral/test mode. */
16
12
  model?: TenantConfig["model"];
17
- /** How Tenants may call Action endpoints (`StackConfig.delivery`). */
13
+ /** How Tenants may call MCP servers and HTTP tools (`StackConfig.delivery`). */
18
14
  delivery?: TenantConfig["delivery"];
19
15
  /** The trusted issuers of the identity file (`NYLORUN_IDENTITY_FILE`). */
20
16
  issuers?: TenantConfig["issuers"];