@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.
- package/CHANGELOG.md +149 -0
- package/README.md +88 -58
- package/dist/adapters/execution/restate.d.ts +1 -3
- package/dist/adapters/execution/restate.js +4 -31
- package/dist/adapters/replication/pgoutput.js +1 -1
- package/dist/adapters/sandbox/local.js +1 -1
- package/dist/adapters/sandbox/virtual.js +1 -1
- package/dist/api/ag-ui/routes.js +1 -1
- package/dist/api/ag-ui/translate.d.ts +1 -3
- package/dist/api/ag-ui/translate.js +2 -17
- package/dist/api/components.d.ts +4871 -2614
- package/dist/api/components.js +12 -11
- package/dist/api/gate/routes.d.ts +7 -8
- package/dist/api/gate/routes.js +58 -52
- package/dist/api/http/app.d.ts +6 -0
- package/dist/api/http/app.js +18 -14
- package/dist/api/http/define.d.ts +14 -2
- package/dist/api/http/define.js +34 -15
- package/dist/api/http/routes/access.d.ts +3 -3
- package/dist/api/http/routes/access.js +18 -17
- package/dist/api/http/routes/artifacts.js +2 -4
- package/dist/api/http/routes/files.d.ts +8 -0
- package/dist/api/http/routes/files.js +77 -0
- package/dist/api/http/routes/keys.d.ts +10 -0
- package/dist/api/http/routes/keys.js +64 -0
- package/dist/api/http/routes/me.js +5 -2
- package/dist/api/http/routes/reads.d.ts +66 -0
- package/dist/api/http/routes/reads.js +127 -0
- package/dist/api/http/routes/sandboxes.js +7 -4
- package/dist/api/http/routes/sessions.js +25 -8
- package/dist/api/http/routes/tenant.js +15 -18
- package/dist/api/http/routes/vaults.d.ts +10 -9
- package/dist/api/http/routes/vaults.js +27 -23
- package/dist/api/openapi.d.ts +10 -11
- package/dist/api/openapi.js +291 -61
- package/dist/artifacts/links.js +1 -1
- package/dist/core/flow-host.d.ts +44 -23
- package/dist/core/flow-host.js +88 -106
- package/dist/core/provider.d.ts +4 -1
- package/dist/core/provider.js +6 -1
- package/dist/core/sandbox-routes.d.ts +0 -9
- package/dist/core/sandbox-routes.js +2 -21
- package/dist/execution/memory.d.ts +4 -5
- package/dist/execution/memory.js +11 -25
- package/dist/execution/types.d.ts +3 -33
- package/dist/execution/types.js +2 -12
- package/dist/gates/http-tool.d.ts +86 -0
- package/dist/gates/http-tool.js +197 -0
- package/dist/gates/mcp-handler.d.ts +3 -0
- package/dist/gates/mcp-handler.js +1 -3
- package/dist/gates/meter.d.ts +2 -2
- package/dist/gates/meter.js +6 -0
- package/dist/gates/tenant-vaults.d.ts +5 -9
- package/dist/gates/tool-calls.d.ts +7 -15
- package/dist/gates/tool-calls.js +4 -4
- package/dist/gates/tool-client.d.ts +1 -1
- package/dist/gates/tool-client.js +23 -47
- package/dist/gates/tool-contract.d.ts +27 -39
- package/dist/gates/tool-contract.js +30 -21
- package/dist/gates/tool-gate.d.ts +25 -24
- package/dist/gates/tool-gate.js +11 -7
- package/dist/harness/calls.d.ts +10 -4
- package/dist/harness/calls.js +15 -6
- package/dist/harness/executors.d.ts +1 -1
- package/dist/harness/executors.js +17 -12
- package/dist/harness/main.d.ts +2 -2
- package/dist/harness/main.js +5 -13
- package/dist/harness/service.d.ts +4 -5
- package/dist/harness/service.js +9 -2
- package/dist/harness-api/in-process.js +2 -2
- package/dist/harness-api/record.d.ts +6 -3
- package/dist/harness-api/record.js +65 -72
- package/dist/harness-api/server.d.ts +1 -9
- package/dist/harness-api/server.js +19 -17
- package/dist/harness-api/start.js +1 -5
- package/dist/harness-api/workspace.js +2 -3
- package/dist/harness-api/ws-server.js +1 -1
- package/dist/host/app.d.ts +2 -9
- package/dist/host/app.js +15 -25
- package/dist/host/config-for.d.ts +1 -5
- package/dist/host/config-for.js +0 -3
- package/dist/host/config.d.ts +0 -5
- package/dist/host/create-host.d.ts +3 -33
- package/dist/host/create-host.js +18 -94
- package/dist/host/environment.d.ts +0 -3
- package/dist/host/environment.js +0 -9
- package/dist/host/gates.d.ts +4 -1
- package/dist/host/gates.js +3 -2
- package/dist/host/http.d.ts +1 -2
- package/dist/host/http.js +1 -7
- package/dist/host/index.d.ts +2 -2
- package/dist/host/index.js +2 -2
- package/dist/host/main.js +27 -34
- package/dist/host/operate.d.ts +29 -0
- package/dist/host/operate.js +223 -0
- package/dist/host/stack-config.d.ts +12 -12
- package/dist/host/stack-config.js +5 -32
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/infra/workers.d.ts +1 -1
- package/dist/keys/keys.js +2 -2
- package/dist/management-openapi.json +6491 -0
- package/dist/mcp/connect.d.ts +8 -4
- package/dist/mcp/connect.js +6 -26
- package/dist/mcp/pool.d.ts +5 -9
- package/dist/mcp/pool.js +8 -19
- package/dist/mcp/snapshot.d.ts +8 -2
- package/dist/mcp/snapshot.js +29 -2
- package/dist/media.d.ts +1 -1
- package/dist/media.js +1 -1
- package/dist/model/pi-model.js +3 -0
- package/dist/openapi.json +4700 -12350
- package/dist/reads/access.d.ts +4 -0
- package/dist/reads/access.js +12 -0
- package/dist/reads/cursor.d.ts +11 -0
- package/dist/reads/cursor.js +80 -0
- package/dist/reads/types.d.ts +25 -0
- package/dist/reads/types.js +1 -0
- package/dist/sandbox/manager.d.ts +16 -0
- package/dist/sandbox/manager.js +40 -0
- package/dist/sandbox/session-sandbox.js +13 -3
- package/dist/sandbox/skills.d.ts +13 -0
- package/dist/sandbox/skills.js +46 -0
- package/dist/sandbox/types.d.ts +2 -2
- package/dist/store/ownership.d.ts +9 -0
- package/dist/store/ownership.js +13 -0
- package/dist/store/postgres/connect.d.ts +2 -0
- package/dist/store/postgres/connect.js +29 -0
- package/dist/store/postgres/drizzle/0011_key_roles.sql +3 -0
- package/dist/store/postgres/drizzle/0012_session_reads.sql +8 -0
- package/dist/store/postgres/drizzle/0013_definition_files.sql +16 -0
- package/dist/store/postgres/drizzle/0014_action_endpoints_removed.sql +2 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +28 -0
- package/dist/store/postgres/reads.d.ts +3 -0
- package/dist/store/postgres/reads.js +272 -0
- package/dist/store/postgres/schema.d.ts +294 -489
- package/dist/store/postgres/schema.js +41 -76
- package/dist/store/postgres/store.js +51 -154
- package/dist/store/postgres/tenant.d.ts +34 -2
- package/dist/store/postgres/tenant.js +16 -32
- package/dist/store/types.d.ts +27 -96
- package/dist/store/types.js +0 -1
- package/dist/streams/control.d.ts +0 -2
- package/dist/streams/control.js +0 -5
- package/dist/streams/types.d.ts +1 -10
- package/dist/tenant/advance.d.ts +3 -3
- package/dist/tenant/advance.js +10 -7
- package/dist/tenant/auth.d.ts +14 -9
- package/dist/tenant/auth.js +24 -28
- package/dist/tenant/cause.d.ts +3 -3
- package/dist/tenant/cause.js +1 -1
- package/dist/tenant/commands.d.ts +2 -18
- package/dist/tenant/commands.js +35 -172
- package/dist/tenant/context.d.ts +14 -24
- package/dist/tenant/definition-files.d.ts +24 -0
- package/dist/tenant/definition-files.js +103 -0
- package/dist/tenant/effects.d.ts +18 -20
- package/dist/tenant/effects.js +56 -119
- package/dist/tenant/ephemeral.d.ts +7 -6
- package/dist/tenant/ephemeral.js +3 -11
- package/dist/tenant/host-token.js +1 -1
- package/dist/tenant/jwt.js +1 -1
- package/dist/tenant/module.d.ts +1 -4
- package/dist/tenant/module.js +3 -12
- package/dist/tenant/operator-keys.d.ts +34 -8
- package/dist/tenant/operator-keys.js +50 -18
- package/dist/tenant/outbound.d.ts +10 -1
- package/dist/tenant/outbound.js +44 -27
- package/dist/tenant/paths.d.ts +1 -1
- package/dist/tenant/paths.js +1 -3
- package/dist/tenant/principals.d.ts +11 -2
- package/dist/tenant/principals.js +27 -8
- package/dist/tenant/run-token.js +3 -3
- package/dist/tenant/runtime.d.ts +4 -6
- package/dist/tenant/runtime.js +19 -18
- package/dist/tenant/sandboxes.d.ts +4 -1
- package/dist/tenant/sandboxes.js +11 -0
- package/dist/tenant/scheduler.d.ts +1 -5
- package/dist/tenant/scheduler.js +2 -5
- package/dist/tenant/session-streams.d.ts +2 -1
- package/dist/tenant/session-streams.js +39 -5
- package/dist/tenant/session.d.ts +8 -41
- package/dist/tenant/session.js +0 -37
- package/dist/tenant/sessions.js +16 -8
- package/dist/tenant/signing-keys.d.ts +3 -3
- package/dist/tenant/signing-keys.js +5 -5
- package/dist/tenant/skill-tool.d.ts +20 -0
- package/dist/tenant/skill-tool.js +72 -0
- package/dist/tenant/slim.d.ts +1 -1
- package/dist/tenant/slim.js +1 -1
- package/dist/tenant/status.js +7 -12
- package/dist/tenant/store-pg.d.ts +15 -0
- package/dist/tenant/store-pg.js +7 -3
- package/dist/tenant/streams.d.ts +3 -10
- package/dist/tenant/streams.js +1 -24
- package/dist/tenant/sweep.js +0 -5
- package/dist/tenant/transcript.d.ts +2 -2
- package/dist/tenant/transcript.js +2 -2
- package/dist/tenant/types.d.ts +15 -22
- package/dist/tenant/worker.d.ts +1 -3
- package/dist/tenant/worker.js +3 -8
- package/dist/vault/sources.d.ts +7 -1
- package/dist/vault/sources.js +22 -11
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
- package/dist/admin-openapi.json +0 -904
- package/dist/api/http/routes/actions.d.ts +0 -8
- package/dist/api/http/routes/actions.js +0 -75
- package/dist/api/http/routes/endpoints.d.ts +0 -7
- package/dist/api/http/routes/endpoints.js +0 -100
- package/dist/host/admin-api.d.ts +0 -30
- package/dist/host/admin-api.js +0 -140
- package/dist/mcp/stdio.d.ts +0 -17
- package/dist/mcp/stdio.js +0 -82
- package/dist/tenant/actions.d.ts +0 -14
- package/dist/tenant/actions.js +0 -29
- package/dist/tenant/delivery-token.d.ts +0 -41
- package/dist/tenant/delivery-token.js +0 -104
- package/dist/tenant/delivery.d.ts +0 -62
- package/dist/tenant/delivery.js +0 -481
- package/dist/tenant/endpoints.d.ts +0 -67
- 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
|
|
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). */
|
package/dist/harness/service.js
CHANGED
|
@@ -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
|
|
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 (
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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 (
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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 {
|
|
20
|
-
import {
|
|
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
|
|
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" &&
|
|
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
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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:
|
|
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 (
|
|
136
|
+
if (request.kind === "tool" && isSaveArtifactCall(agentManifest, request))
|
|
113
137
|
return "save";
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
return { status: "
|
|
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
|
|
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
|
|
129
|
-
* call it ran: the run gets the outcome as the intent's
|
|
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
|
|
158
|
+
async function runInCore(scope, request, call) {
|
|
132
159
|
let value;
|
|
133
160
|
try {
|
|
134
|
-
value = await
|
|
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
|
-
/**
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
|
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`)
|
|
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.
|
|
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
|
|
4
|
-
* /v1/sessions/: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
|
|
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),
|
package/dist/host/app.d.ts
CHANGED
|
@@ -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:
|
|
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
|
-
/**
|
|
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 {
|
|
7
|
-
import {
|
|
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
|
|
29
|
+
const { incoming } = c.env;
|
|
30
30
|
if (headerValue(incoming, "origin") !== undefined) {
|
|
31
|
-
|
|
32
|
-
//
|
|
33
|
-
|
|
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
|
|
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
|
-
//
|
|
82
|
-
if (pathname === "/openapi.json" && incoming.method === "GET")
|
|
83
|
-
return jsonResponse(200,
|
|
84
|
-
|
|
85
|
-
|
|
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"
|
|
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
|
|
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"];
|