@nylorun/runtime 0.13.0-beta → 0.15.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 +434 -0
- package/README.md +137 -41
- package/dist/adapters/execution/restate.d.ts +7 -2
- package/dist/adapters/execution/restate.js +40 -10
- package/dist/adapters/replication/pgoutput.d.ts +21 -0
- package/dist/adapters/replication/pgoutput.js +170 -0
- package/dist/adapters/streams/s2.js +37 -20
- package/dist/admin-openapi.json +570 -0
- package/dist/api/a2a/card.d.ts +15 -0
- package/dist/api/a2a/card.js +24 -0
- package/dist/api/a2a/endpoint.d.ts +8 -0
- package/dist/api/a2a/endpoint.js +81 -0
- package/dist/api/a2a/protocol.d.ts +144 -0
- package/dist/api/a2a/protocol.js +188 -0
- package/dist/api/a2a/routes.d.ts +19 -0
- package/dist/api/a2a/routes.js +289 -0
- package/dist/api/a2a/tasks.d.ts +50 -0
- package/dist/api/a2a/tasks.js +186 -0
- package/dist/api/ag-ui/endpoint.d.ts +8 -0
- package/dist/api/ag-ui/endpoint.js +126 -0
- package/dist/api/ag-ui/history.d.ts +7 -0
- package/dist/api/ag-ui/history.js +60 -0
- package/dist/api/ag-ui/routes.d.ts +32 -0
- package/dist/api/ag-ui/routes.js +305 -0
- package/dist/api/ag-ui/session-id.d.ts +1 -0
- package/dist/api/ag-ui/session-id.js +12 -0
- package/dist/api/ag-ui/sse.d.ts +9 -0
- package/dist/api/ag-ui/sse.js +7 -0
- package/dist/api/ag-ui/translate.d.ts +38 -0
- package/dist/api/ag-ui/translate.js +222 -0
- package/dist/api/components.d.ts +8065 -0
- package/dist/api/components.js +82 -0
- package/dist/api/gate/keys.d.ts +14 -0
- package/dist/api/gate/keys.js +59 -0
- package/dist/api/gate/routes.d.ts +34 -0
- package/dist/api/gate/routes.js +235 -0
- package/dist/api/http/app.d.ts +18 -0
- package/dist/api/http/app.js +58 -0
- package/dist/api/http/body.d.ts +4 -0
- package/dist/api/http/body.js +37 -0
- package/dist/api/http/define.d.ts +39 -0
- package/dist/api/http/define.js +122 -0
- package/dist/api/http/respond.d.ts +9 -0
- package/dist/api/http/respond.js +44 -0
- package/dist/api/http/routes/access.d.ts +24 -0
- package/dist/api/http/routes/access.js +266 -0
- package/dist/api/http/routes/actions.d.ts +8 -0
- package/dist/api/http/routes/actions.js +76 -0
- package/dist/api/http/routes/endpoints.d.ts +7 -0
- package/dist/api/http/routes/endpoints.js +100 -0
- package/dist/api/http/routes/sessions.d.ts +3 -0
- package/dist/api/http/routes/sessions.js +205 -0
- package/dist/api/http/routes/tenant.d.ts +11 -0
- package/dist/api/http/routes/tenant.js +244 -0
- package/dist/api/http/routes/vaults.d.ts +8 -0
- package/dist/api/http/routes/vaults.js +167 -0
- package/dist/api/openapi.d.ts +19 -0
- package/dist/api/openapi.js +137 -0
- package/dist/api/route.d.ts +12 -0
- package/dist/api/route.js +4 -0
- package/dist/core/bearer.d.ts +8 -0
- package/dist/core/bearer.js +11 -0
- package/dist/core/flow-host.d.ts +7 -15
- package/dist/core/flow-host.js +16 -41
- package/dist/core/sandbox-routes.d.ts +12 -7
- package/dist/core/sandbox-routes.js +24 -25
- package/dist/execution/memory.d.ts +8 -5
- package/dist/execution/memory.js +33 -13
- package/dist/execution/types.d.ts +38 -6
- package/dist/execution/types.js +12 -3
- package/dist/gates/contract.d.ts +51 -0
- package/dist/gates/contract.js +37 -0
- package/dist/gates/handler.d.ts +21 -0
- package/dist/gates/handler.js +55 -0
- package/dist/gates/http-client.d.ts +12 -0
- package/dist/gates/http-client.js +160 -0
- package/dist/gates/in-process.d.ts +32 -0
- package/dist/gates/in-process.js +42 -0
- package/dist/gates/inflight.d.ts +39 -0
- package/dist/gates/inflight.js +70 -0
- package/dist/gates/mcp-handler.d.ts +35 -0
- package/dist/gates/mcp-handler.js +142 -0
- package/dist/gates/meter.d.ts +24 -0
- package/dist/gates/meter.js +165 -0
- package/dist/gates/model-gate.d.ts +45 -0
- package/dist/gates/model-gate.js +1 -0
- package/dist/gates/tenant-vaults.d.ts +64 -0
- package/dist/gates/tenant-vaults.js +94 -0
- package/dist/gates/tool-calls.d.ts +51 -0
- package/dist/gates/tool-calls.js +64 -0
- package/dist/gates/tool-client.d.ts +12 -0
- package/dist/gates/tool-client.js +201 -0
- package/dist/gates/tool-contract.d.ts +116 -0
- package/dist/gates/tool-contract.js +64 -0
- package/dist/gates/tool-gate.d.ts +45 -0
- package/dist/gates/tool-gate.js +10 -0
- package/dist/host/admin-api.d.ts +23 -0
- package/dist/host/admin-api.js +74 -0
- package/dist/host/app.d.ts +34 -0
- package/dist/host/app.js +181 -0
- package/dist/host/config-for.d.ts +3 -1
- package/dist/host/config-for.js +3 -2
- package/dist/host/config.d.ts +11 -0
- package/dist/host/cors.d.ts +27 -0
- package/dist/host/cors.js +64 -0
- package/dist/host/create-host.d.ts +36 -8
- package/dist/host/create-host.js +161 -330
- package/dist/host/execution.d.ts +20 -18
- package/dist/host/execution.js +2 -2
- package/dist/host/gates.d.ts +58 -0
- package/dist/host/gates.js +127 -0
- package/dist/host/http.d.ts +20 -4
- package/dist/host/http.js +81 -23
- package/dist/host/index.d.ts +1 -1
- package/dist/host/main.js +189 -58
- package/dist/host/stack-config.d.ts +91 -19
- package/dist/host/stack-config.js +246 -17
- package/dist/infra/database.d.ts +2 -2
- package/dist/infra/execution.d.ts +5 -5
- package/dist/infra/execution.js +8 -6
- package/dist/infra/index.d.ts +1 -1
- package/dist/infra/workers.d.ts +17 -17
- package/dist/infra/workers.js +9 -9
- package/dist/keys/client.d.ts +9 -0
- package/dist/keys/client.js +70 -0
- package/dist/keys/contract.d.ts +24 -0
- package/dist/keys/contract.js +12 -0
- package/dist/keys/keys.d.ts +41 -0
- package/dist/keys/keys.js +50 -0
- package/dist/mcp/connect.d.ts +43 -3
- package/dist/mcp/connect.js +12 -5
- package/dist/mcp/pool.d.ts +39 -3
- package/dist/mcp/pool.js +74 -11
- package/dist/model/catalog.d.ts +2 -10
- package/dist/model/classify.d.ts +13 -0
- package/dist/model/classify.js +61 -0
- package/dist/model/models.d.ts +10 -1
- package/dist/model/models.js +14 -6
- package/dist/model/pi-model.d.ts +17 -2
- package/dist/model/pi-model.js +366 -222
- package/dist/openapi.json +25889 -0
- package/dist/record/append.d.ts +51 -0
- package/dist/record/append.js +20 -0
- package/dist/record/envelope.d.ts +19 -0
- package/dist/record/envelope.js +49 -0
- package/dist/record/index.d.ts +11 -0
- package/dist/record/index.js +11 -0
- package/dist/sandbox/manager.d.ts +13 -1
- package/dist/sandbox/manager.js +8 -11
- package/dist/sandbox/resolve.d.ts +33 -0
- package/dist/sandbox/resolve.js +81 -0
- package/dist/sandbox/session-sandbox.d.ts +11 -0
- package/dist/sandbox/session-sandbox.js +51 -0
- package/dist/sandbox/share.d.ts +14 -2
- package/dist/sandbox/share.js +33 -3
- package/dist/sandbox/tenant-config.d.ts +37 -0
- package/dist/sandbox/tenant-config.js +51 -0
- package/dist/sandbox/tools.d.ts +7 -9
- package/dist/sandbox/tools.js +11 -3
- package/dist/sandbox/types.d.ts +8 -2
- package/dist/store/postgres/connect.d.ts +25 -9
- package/dist/store/postgres/connect.js +27 -10
- package/dist/store/postgres/db.d.ts +16 -0
- package/dist/store/postgres/db.js +28 -0
- package/dist/store/postgres/drizzle/0000_foundation.sql +22 -0
- package/dist/store/postgres/drizzle/0001_baseline.sql +255 -0
- package/dist/store/postgres/drizzle/0002_stream_relay.sql +4 -0
- package/dist/store/postgres/drizzle/0003_tool_crossings.sql +9 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +34 -0
- package/dist/store/postgres/migrate.d.ts +68 -0
- package/dist/store/postgres/migrate.js +183 -0
- package/dist/store/postgres/record-writer.d.ts +4 -0
- package/dist/store/postgres/record-writer.js +34 -0
- package/dist/store/postgres/record.d.ts +5 -0
- package/dist/store/postgres/record.js +60 -0
- package/dist/store/postgres/schema.d.ts +3096 -0
- package/dist/store/postgres/schema.js +363 -0
- package/dist/store/postgres/store.d.ts +2 -4
- package/dist/store/postgres/store.js +590 -434
- package/dist/store/postgres/tenant.d.ts +56 -0
- package/dist/store/postgres/tenant.js +154 -0
- package/dist/store/types.d.ts +183 -132
- package/dist/store/types.js +0 -1
- package/dist/streams/basin.d.ts +18 -1
- package/dist/streams/basin.js +34 -2
- package/dist/streams/control.d.ts +18 -0
- package/dist/streams/control.js +33 -0
- package/dist/streams/memory.d.ts +3 -1
- package/dist/streams/memory.js +14 -8
- package/dist/streams/relay/core.d.ts +35 -0
- package/dist/streams/relay/core.js +366 -0
- package/dist/streams/relay/types.d.ts +78 -0
- package/dist/streams/relay/types.js +8 -0
- package/dist/streams/types.d.ts +71 -36
- package/dist/streams/types.js +18 -53
- package/dist/tenant/access-policy.d.ts +30 -0
- package/dist/tenant/access-policy.js +52 -0
- package/dist/tenant/actions.d.ts +10 -149
- package/dist/tenant/actions.js +5 -167
- package/dist/tenant/advance.d.ts +7 -2
- package/dist/tenant/advance.js +125 -14
- package/dist/tenant/auth.d.ts +19 -17
- package/dist/tenant/auth.js +90 -93
- package/dist/tenant/browser.d.ts +15 -0
- package/dist/tenant/browser.js +39 -0
- package/dist/tenant/cause.d.ts +25 -0
- package/dist/tenant/cause.js +45 -0
- package/dist/tenant/commands.d.ts +18 -2
- package/dist/tenant/commands.js +111 -115
- package/dist/tenant/context.d.ts +101 -21
- package/dist/tenant/context.js +14 -10
- package/dist/tenant/delivery-token.d.ts +41 -0
- package/dist/tenant/delivery-token.js +104 -0
- package/dist/tenant/delivery.d.ts +62 -0
- package/dist/tenant/delivery.js +481 -0
- package/dist/tenant/effects.d.ts +20 -1
- package/dist/tenant/effects.js +159 -42
- package/dist/tenant/endpoints.d.ts +67 -0
- package/dist/tenant/endpoints.js +90 -0
- package/dist/tenant/ephemeral.d.ts +22 -7
- package/dist/tenant/ephemeral.js +83 -125
- package/dist/tenant/history.d.ts +64 -0
- package/dist/tenant/history.js +120 -0
- package/dist/tenant/http.d.ts +8 -9
- package/dist/tenant/http.js +13 -27
- package/dist/tenant/jwt.d.ts +16 -0
- package/dist/tenant/jwt.js +50 -0
- package/dist/tenant/model-setting.d.ts +2 -2
- package/dist/tenant/module.d.ts +17 -28
- package/dist/tenant/module.js +133 -279
- package/dist/tenant/outbound.d.ts +44 -0
- package/dist/tenant/outbound.js +127 -0
- package/dist/tenant/paths.d.ts +11 -16
- package/dist/tenant/paths.js +24 -68
- package/dist/tenant/pool.d.ts +1 -3
- package/dist/tenant/pool.js +1 -19
- package/dist/tenant/principals.d.ts +23 -11
- package/dist/tenant/principals.js +61 -49
- package/dist/tenant/reset.d.ts +0 -4
- package/dist/tenant/reset.js +0 -5
- package/dist/tenant/runtime.d.ts +28 -20
- package/dist/tenant/runtime.js +95 -64
- package/dist/tenant/scheduler.d.ts +10 -3
- package/dist/tenant/scheduler.js +9 -5
- package/dist/tenant/session-streams.d.ts +127 -0
- package/dist/tenant/session-streams.js +404 -0
- package/dist/tenant/sessions.d.ts +28 -4
- package/dist/tenant/sessions.js +118 -24
- package/dist/tenant/signing-keys.d.ts +55 -0
- package/dist/tenant/signing-keys.js +172 -0
- package/dist/tenant/slim.d.ts +8 -0
- package/dist/tenant/slim.js +21 -0
- package/dist/tenant/status.d.ts +3 -3
- package/dist/tenant/status.js +20 -13
- package/dist/tenant/store-pg.d.ts +9 -6
- package/dist/tenant/store-pg.js +60 -125
- package/dist/tenant/streams.d.ts +62 -85
- package/dist/tenant/streams.js +143 -226
- package/dist/tenant/subject-limits.d.ts +16 -0
- package/dist/tenant/subject-limits.js +45 -0
- package/dist/tenant/sweep.d.ts +1 -10
- package/dist/tenant/sweep.js +29 -69
- package/dist/tenant/tokens.d.ts +14 -0
- package/dist/tenant/tokens.js +180 -0
- package/dist/tenant/transcript.d.ts +48 -5
- package/dist/tenant/transcript.js +58 -1
- package/dist/tenant/types.d.ts +69 -98
- package/dist/tenant/types.js +4 -15
- package/dist/tenant/worker.d.ts +3 -1
- package/dist/tenant/worker.js +13 -7
- package/dist/vault/host-model.d.ts +31 -0
- package/dist/vault/host-model.js +57 -0
- package/dist/vault/service.d.ts +44 -17
- package/dist/vault/service.js +126 -121
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +25 -9
- package/dist/core/executors.d.ts +0 -44
- package/dist/core/executors.js +0 -95
- package/dist/store/memory.d.ts +0 -57
- package/dist/store/memory.js +0 -645
- package/dist/store/postgres/migrations/001_initial.d.ts +0 -27
- package/dist/store/postgres/migrations/001_initial.js +0 -196
- package/dist/store/postgres/migrations/002_session_owner.d.ts +0 -6
- package/dist/store/postgres/migrations/002_session_owner.js +0 -13
- package/dist/store/postgres/migrations/index.d.ts +0 -49
- package/dist/store/postgres/migrations/index.js +0 -76
- package/dist/store/postgres/names.d.ts +0 -11
- package/dist/store/postgres/names.js +0 -42
- package/dist/store/postgres/tenants.d.ts +0 -90
- package/dist/store/postgres/tenants.js +0 -214
- package/dist/streams/relay.d.ts +0 -37
- package/dist/streams/relay.js +0 -254
- package/dist/tenant/envelope.d.ts +0 -7
- package/dist/tenant/envelope.js +0 -22
- package/dist/tenant/live.d.ts +0 -92
- package/dist/tenant/live.js +0 -290
- package/dist/tenant/quarantine-error.d.ts +0 -6
- package/dist/tenant/quarantine-error.js +0 -9
- package/dist/tenant/quarantine.d.ts +0 -27
- package/dist/tenant/quarantine.js +0 -72
- package/dist/tenant/routes-tenant.d.ts +0 -12
- package/dist/tenant/routes-tenant.js +0 -137
- package/dist/tenant/routes.d.ts +0 -9
- package/dist/tenant/routes.js +0 -151
- package/dist/tenant/store-memory.d.ts +0 -16
- package/dist/tenant/store-memory.js +0 -111
- /package/dist/{store → record}/cursor.d.ts +0 -0
- /package/dist/{store → record}/cursor.js +0 -0
package/dist/tenant/runtime.js
CHANGED
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
import { mkdirSync } from "node:fs";
|
|
2
2
|
import { resolveFlowLimits } from "../core/limits.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { scriptedModel, toolFixtureModel, } from "../core/provider.js";
|
|
4
|
+
import { tenantModelGate } from "../gates/in-process.js";
|
|
5
5
|
import { createKekFile, readVaultKek } from "../vault/kek.js";
|
|
6
|
+
import { SigningKeys } from "./signing-keys.js";
|
|
6
7
|
import { VaultService } from "../vault/service.js";
|
|
7
8
|
import { McpPool } from "../mcp/pool.js";
|
|
8
9
|
import { SandboxManager } from "../sandbox/manager.js";
|
|
9
10
|
import { defaultSandboxBackends } from "../sandbox/select.js";
|
|
10
11
|
import { MemoryExecution } from "../execution/memory.js";
|
|
11
|
-
import {
|
|
12
|
+
import { openError } from "./cause.js";
|
|
12
13
|
import { MemoryStreams } from "../streams/memory.js";
|
|
13
|
-
import {
|
|
14
|
-
import { closeStreams,
|
|
14
|
+
import { createSessionStreams, endAllStreams, } from "./session-streams.js";
|
|
15
|
+
import { closeStreams, streamsStatus, wireStreams, } from "./streams.js";
|
|
15
16
|
import { abortAll, abortLocal, createWorkState, drain, waitForIdle, } from "./scheduler.js";
|
|
16
17
|
import { advance } from "./advance.js";
|
|
17
18
|
import { sweep } from "./sweep.js";
|
|
19
|
+
import { deliverAction } from "./delivery.js";
|
|
18
20
|
import { TenantWorkers, WORKER_ID, } from "./worker.js";
|
|
19
21
|
import { authorize } from "./effects.js";
|
|
20
|
-
import {
|
|
22
|
+
import { inProcessToolGate } from "../gates/tool-gate.js";
|
|
23
|
+
import { inProcessKeys } from "../keys/keys.js";
|
|
24
|
+
import { tenantApi } from "../api/http/app.js";
|
|
21
25
|
/** Default ownership lease of an advance; the heartbeat renews it every third. */
|
|
22
26
|
const DEFAULT_OWNER_LEASE_MS = 30_000;
|
|
23
27
|
function validateConfig(config) {
|
|
@@ -66,24 +70,32 @@ export class TenantRuntime {
|
|
|
66
70
|
let wired;
|
|
67
71
|
let detach;
|
|
68
72
|
try {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
// With the keys service (F4.2) the key lives in the gateway: this process never reads,
|
|
74
|
+
// creates or holds it, and the gateway reports a missing key on its readiness.
|
|
75
|
+
let kek = hooks.keys
|
|
76
|
+
? undefined
|
|
77
|
+
: readVaultKek({
|
|
78
|
+
vaultKek: hooks.vaultKek,
|
|
79
|
+
vaultKekPath: paths.kek,
|
|
80
|
+
});
|
|
81
|
+
if (!hooks.keys) {
|
|
82
|
+
const sealed = await store.tx(async (t) => (await t.countCredentials()) + (await t.countSigningKeys()));
|
|
83
|
+
if (sealed > 0 && !kek) {
|
|
84
|
+
throw openError("kek-missing", "Vault key-encryption key is missing for ciphertext (vault credentials or signing keys) in this Tenant");
|
|
85
|
+
}
|
|
75
86
|
}
|
|
76
87
|
const createKekIfMissing = hooks.createKekIfMissing !== false;
|
|
77
88
|
const ensureKek = () => {
|
|
89
|
+
if (hooks.keys)
|
|
90
|
+
throw new Error("The vault key lives in the gateway's keys service: this process never reads it");
|
|
78
91
|
if (kek)
|
|
79
92
|
return kek;
|
|
80
93
|
if (!createKekIfMissing)
|
|
81
|
-
throw
|
|
94
|
+
throw openError("kek-missing", "Vault key-encryption key is required");
|
|
82
95
|
kek = createKekFile(paths.kek);
|
|
83
96
|
return kek;
|
|
84
97
|
};
|
|
85
98
|
const opened = store;
|
|
86
|
-
const registry = await loadExecutorRegistry(opened);
|
|
87
99
|
// A seeded Tenant setting wins over the Host default (A18).
|
|
88
100
|
const seededBackend = await opened.tx((t) => t.getSetting("sandbox.backend"));
|
|
89
101
|
const vault = new VaultService({
|
|
@@ -93,10 +105,14 @@ export class TenantRuntime {
|
|
|
93
105
|
});
|
|
94
106
|
// `ctx` is assigned below; these callbacks only run once the Tenant is open.
|
|
95
107
|
let ctx;
|
|
108
|
+
const toolGate = hooks.toolGate ?? inProcessToolGate(config.delivery ?? {});
|
|
109
|
+
const signingKeys = new SigningKeys({ tenantId: config.tenantId, kek: ensureKek });
|
|
110
|
+
const keys = hooks.keys ?? inProcessKeys({ store: opened, vault, signingKeys, kek: ensureKek });
|
|
96
111
|
const mcp = new McpPool({
|
|
97
112
|
pluginData: paths.pluginData,
|
|
98
113
|
childEnv: config.childEnv,
|
|
99
114
|
authorize: (sessionId, request) => authorize(ctx, sessionId, request),
|
|
115
|
+
...(toolGate.openMcp ? { openRemote: (server) => toolGate.openMcp(server) } : {}),
|
|
100
116
|
});
|
|
101
117
|
const ephemeral = config.mode === "ephemeral";
|
|
102
118
|
const sandbox = new SandboxManager({
|
|
@@ -125,23 +141,19 @@ export class TenantRuntime {
|
|
|
125
141
|
modelProvider = scriptedModel(config.model.output);
|
|
126
142
|
else if (config.model.kind === "fixture")
|
|
127
143
|
modelProvider = toolFixtureModel();
|
|
128
|
-
else if (config.model.kind === "gateway") {
|
|
129
|
-
const gateway = config.model;
|
|
130
|
-
modelProvider = async (effect, signal) => {
|
|
131
|
-
const secret = await vault.readHostModel();
|
|
132
|
-
const token = typeof secret?.credential.key === "string"
|
|
133
|
-
? secret.credential.key
|
|
134
|
-
: "";
|
|
135
|
-
return gatewayModel({
|
|
136
|
-
url: gateway.url,
|
|
137
|
-
model: gateway.model,
|
|
138
|
-
token,
|
|
139
|
-
})(effect, signal);
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
144
|
else
|
|
143
145
|
modelProvider = scriptedModel();
|
|
144
|
-
|
|
146
|
+
// With the gates service the loop never reads a model credential: only a Runtime
|
|
147
|
+
// without one (embedding, the ephemeral Runtime, tests) reads it here.
|
|
148
|
+
const modelGate = hooks.modelGate ??
|
|
149
|
+
tenantModelGate({
|
|
150
|
+
store: opened,
|
|
151
|
+
kek: ensureKek,
|
|
152
|
+
root: paths.home,
|
|
153
|
+
logger: config.logger,
|
|
154
|
+
...(config.modelCall ? { settings: config.modelCall } : {}),
|
|
155
|
+
});
|
|
156
|
+
const sessionStreams = createSessionStreams();
|
|
145
157
|
const local = hooks.execution
|
|
146
158
|
? undefined
|
|
147
159
|
: new MemoryExecution({
|
|
@@ -160,16 +172,19 @@ export class TenantRuntime {
|
|
|
160
172
|
envelope,
|
|
161
173
|
store: opened,
|
|
162
174
|
vault,
|
|
163
|
-
registry,
|
|
164
175
|
mcp,
|
|
165
176
|
sandbox,
|
|
166
177
|
flowLimits,
|
|
167
178
|
modelProvider,
|
|
168
179
|
useVaultModel,
|
|
180
|
+
modelGate,
|
|
181
|
+
toolGate,
|
|
169
182
|
closing: false,
|
|
170
183
|
closed: false,
|
|
171
184
|
work: createWorkState(),
|
|
172
|
-
|
|
185
|
+
sessionStreams,
|
|
186
|
+
signingKeys,
|
|
187
|
+
keys,
|
|
173
188
|
workerId: hooks.workerId ?? WORKER_ID,
|
|
174
189
|
ownerLeaseMs: config.ownerLeaseMs ?? DEFAULT_OWNER_LEASE_MS,
|
|
175
190
|
wake: async (sessionId, wake) => {
|
|
@@ -177,7 +192,12 @@ export class TenantRuntime {
|
|
|
177
192
|
return;
|
|
178
193
|
await execution.wake(config.tenantId, sessionId, wake);
|
|
179
194
|
},
|
|
180
|
-
abortLocal: (sessionId) => abortLocal(ctx, sessionId),
|
|
195
|
+
abortLocal: (sessionId, turnId) => abortLocal(ctx, sessionId, "cancel", turnId),
|
|
196
|
+
deliver: async (actionId) => {
|
|
197
|
+
if (ctx.closing || ctx.closed)
|
|
198
|
+
return;
|
|
199
|
+
await execution.deliver(config.tenantId, actionId);
|
|
200
|
+
},
|
|
181
201
|
...(hooks.execution?.stuckInvocations
|
|
182
202
|
? {
|
|
183
203
|
stuckInvocations: () => hooks.execution.stuckInvocations(config.tenantId),
|
|
@@ -194,21 +214,15 @@ export class TenantRuntime {
|
|
|
194
214
|
streams: hooks.streams ?? new MemoryStreams(),
|
|
195
215
|
ownsStreams: !hooks.streams,
|
|
196
216
|
tenantId: config.tenantId,
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
sweepHooks.add(async () => {
|
|
200
|
-
await drainOutbox(ctx);
|
|
217
|
+
...(hooks.hostRelay ? { hostRelay: true } : {}),
|
|
218
|
+
...(hooks.retireGraceMs !== undefined ? { retireGraceMs: hooks.retireGraceMs } : {}),
|
|
201
219
|
});
|
|
202
220
|
// Register the handlers, then arm the sweep: its first pass runs at once and re-wakes
|
|
203
221
|
// sessions a previous process left runnable or running (takeover handles the rest).
|
|
204
|
-
let afterOpen = true;
|
|
205
222
|
const worker = {
|
|
206
223
|
advance: (sessionId, signal) => advance(ctx, sessionId, signal),
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
afterOpen = false;
|
|
210
|
-
await sweep(ctx, { afterOpen: first });
|
|
211
|
-
},
|
|
224
|
+
deliver: (actionId, signal) => deliverAction(ctx, actionId, signal),
|
|
225
|
+
sweep: () => sweep(ctx),
|
|
212
226
|
};
|
|
213
227
|
const unregister = workers.register(config.tenantId, worker);
|
|
214
228
|
detach = async () => {
|
|
@@ -227,28 +241,21 @@ export class TenantRuntime {
|
|
|
227
241
|
throw error;
|
|
228
242
|
}
|
|
229
243
|
}
|
|
230
|
-
|
|
231
|
-
return
|
|
244
|
+
async fetch(request, node) {
|
|
245
|
+
return await tenantApi().fetch(request, { ...node, tenant: this.ctx });
|
|
232
246
|
}
|
|
233
247
|
authorize(sessionId, request) {
|
|
234
248
|
return authorize(this.ctx, sessionId, request);
|
|
235
249
|
}
|
|
236
250
|
async summary() {
|
|
237
|
-
const { store
|
|
238
|
-
const
|
|
239
|
-
counts: await t.counts(),
|
|
240
|
-
outbox: await t.outboxStats(),
|
|
241
|
-
}));
|
|
251
|
+
const { store } = this.ctx;
|
|
252
|
+
const counts = await store.tx((t) => t.counts());
|
|
242
253
|
return {
|
|
243
254
|
ready: !this.ctx.closing && !this.ctx.closed,
|
|
244
255
|
runningSessions: counts.runningSessions,
|
|
245
|
-
|
|
256
|
+
inFlightDeliveries: [...this.ctx.work.deliveries.values()].reduce((n, set) => n + set.size, 0),
|
|
246
257
|
pendingActions: counts.pendingActions,
|
|
247
258
|
uncertainEffects: counts.uncertainEffects,
|
|
248
|
-
outboxDepth: outbox.depth,
|
|
249
|
-
relayLagMs: outbox.oldestCreatedAt === null
|
|
250
|
-
? 0
|
|
251
|
-
: Math.max(0, Date.now() - Date.parse(outbox.oldestCreatedAt)),
|
|
252
259
|
};
|
|
253
260
|
}
|
|
254
261
|
drain(activeWork, timeoutMs = 30_000) {
|
|
@@ -263,7 +270,7 @@ export class TenantRuntime {
|
|
|
263
270
|
}
|
|
264
271
|
/**
|
|
265
272
|
* The Durable Streams seam's status for Tenant status and readiness: S2 reachability, the
|
|
266
|
-
* basin,
|
|
273
|
+
* basin, its generation and the Tenant's relay (`streamsStatus` in `tenant/streams.ts`).
|
|
267
274
|
*/
|
|
268
275
|
streamsStatus() {
|
|
269
276
|
return streamsStatus(this.ctx);
|
|
@@ -278,15 +285,39 @@ export class TenantRuntime {
|
|
|
278
285
|
// A shutdown abort: the advances leave their sessions to the next advance, unsettled.
|
|
279
286
|
abortAll(ctx, "shutdown");
|
|
280
287
|
const idleBy = Date.now() + this.closeGraceMs;
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
288
|
+
// Every step runs even when one before it fails, so nothing is left running; the first
|
|
289
|
+
// error is rethrown at the end.
|
|
290
|
+
const steps = [
|
|
291
|
+
["detach", () => this.detach()],
|
|
292
|
+
["mcp", () => ctx.mcp.close()],
|
|
293
|
+
["session streams", () => endAllStreams(ctx.sessionStreams)],
|
|
294
|
+
// Bounded: an advance that ignores its abort is abandoned; its lease lapses (§11.4).
|
|
295
|
+
["idle", () => waitForIdle(ctx, Math.max(0, idleBy - Date.now()))],
|
|
296
|
+
["sandbox", () => ctx.sandbox.close()],
|
|
297
|
+
[
|
|
298
|
+
"streams",
|
|
299
|
+
() => {
|
|
300
|
+
ctx.closed = true;
|
|
301
|
+
return closeStreams(ctx);
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
["store", () => ctx.store.close()],
|
|
305
|
+
];
|
|
306
|
+
let failure;
|
|
307
|
+
for (const [step, run] of steps) {
|
|
308
|
+
try {
|
|
309
|
+
await run();
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
ctx.config.logger.warn("tenant close step failed", {
|
|
313
|
+
step,
|
|
314
|
+
message: error instanceof Error ? error.message : String(error),
|
|
315
|
+
});
|
|
316
|
+
failure ??= { error };
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (failure)
|
|
320
|
+
throw failure.error;
|
|
290
321
|
}
|
|
291
322
|
}
|
|
292
323
|
/**
|
|
@@ -3,13 +3,20 @@ import { type AdvanceAbortKind } from "./worker.js";
|
|
|
3
3
|
export interface WorkState {
|
|
4
4
|
/** Advances running on this process, by session id. */
|
|
5
5
|
readonly running: Map<string, AbortController>;
|
|
6
|
+
/** The turn each running advance belongs to, by session id (`null`: none). */
|
|
7
|
+
readonly runningTurns: Map<string, string | null>;
|
|
8
|
+
/** Deliveries to Action endpoints in flight on this process, by their Action's session id. */
|
|
9
|
+
readonly deliveries: Map<string, Set<AbortController>>;
|
|
10
|
+
/** When each Action last reported a failed delivery (`action.delivery_failed` throttling). */
|
|
11
|
+
readonly deliveryNotices: Map<string, number>;
|
|
6
12
|
}
|
|
7
13
|
export declare function createWorkState(): WorkState;
|
|
8
14
|
/**
|
|
9
|
-
* Abort the advance of `id` if it runs on this process
|
|
10
|
-
* `cancelled`, and so does the control stream for cancels made
|
|
15
|
+
* Abort the advance of `id` if it runs on this process, and its deliveries to Action endpoints.
|
|
16
|
+
* Cancel calls it after committing `cancelled`, and so does the control stream for cancels made
|
|
17
|
+
* elsewhere. With `turnId`, an advance of another turn keeps running: the signal came late.
|
|
11
18
|
*/
|
|
12
|
-
export declare function abortLocal(ctx: TenantContext, id: string, kind?: AdvanceAbortKind): void;
|
|
19
|
+
export declare function abortLocal(ctx: TenantContext, id: string, kind?: AdvanceAbortKind, turnId?: string): void;
|
|
13
20
|
/** Reset: abort every advance running on this process; their sessions are being cleared. */
|
|
14
21
|
export declare function clearWork(ctx: TenantContext): void;
|
|
15
22
|
/** Stop new advances; cancel active turns if asked; wait for running advances until the timeout. */
|
package/dist/tenant/scheduler.js
CHANGED
|
@@ -13,7 +13,7 @@ import { randomUUID } from "node:crypto";
|
|
|
13
13
|
import { command } from "./commands.js";
|
|
14
14
|
import { AdvanceAbort } from "./worker.js";
|
|
15
15
|
export function createWorkState() {
|
|
16
|
-
return { running: new Map() };
|
|
16
|
+
return { running: new Map(), runningTurns: new Map(), deliveries: new Map(), deliveryNotices: new Map() };
|
|
17
17
|
}
|
|
18
18
|
const MESSAGES = {
|
|
19
19
|
cancel: "Turn cancelled",
|
|
@@ -26,13 +26,17 @@ function abortWith(controller, kind) {
|
|
|
26
26
|
controller.abort(new AdvanceAbort(kind, MESSAGES[kind]));
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
29
|
-
* Abort the advance of `id` if it runs on this process
|
|
30
|
-
* `cancelled`, and so does the control stream for cancels made
|
|
29
|
+
* Abort the advance of `id` if it runs on this process, and its deliveries to Action endpoints.
|
|
30
|
+
* Cancel calls it after committing `cancelled`, and so does the control stream for cancels made
|
|
31
|
+
* elsewhere. With `turnId`, an advance of another turn keeps running: the signal came late.
|
|
31
32
|
*/
|
|
32
|
-
export function abortLocal(ctx, id, kind = "cancel") {
|
|
33
|
+
export function abortLocal(ctx, id, kind = "cancel", turnId) {
|
|
33
34
|
const controller = ctx.work.running.get(id);
|
|
34
|
-
if (controller)
|
|
35
|
+
if (controller && (turnId === undefined || ctx.work.runningTurns.get(id) === turnId))
|
|
35
36
|
abortWith(controller, kind);
|
|
37
|
+
// A cancel also closes the session's deliveries, which aborts the tools' `ctx.signal`.
|
|
38
|
+
for (const delivery of ctx.work.deliveries.get(id) ?? [])
|
|
39
|
+
abortWith(delivery, kind);
|
|
36
40
|
}
|
|
37
41
|
/** Reset: abort every advance running on this process; their sessions are being cleared. */
|
|
38
42
|
export function clearWork(ctx) {
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live delivery over Durable Streams (architecture §12.4, "Reading"): session history
|
|
3
|
+
* (`GET /v1/sessions/:id/items`) and session SSE (`GET /v1/sessions/:id/events`).
|
|
4
|
+
*
|
|
5
|
+
* - **Streams.** A session's events are in `sessions/<id>` in the Tenant's current basin
|
|
6
|
+
* generation (`currentBasin`); cursors carry only the session id and sequence.
|
|
7
|
+
* - **History** reads the session's stream from the cursor up to the tail seen when the read
|
|
8
|
+
* starts. The `agent` filter runs here, and the response cursor is the last record read. If
|
|
9
|
+
* the streams fail, history answers `503`.
|
|
10
|
+
* - **SSE** shares one stream read per observed session in this process (a `SessionStream`)
|
|
11
|
+
* among that session's observers. Each observer keeps the next sequence it needs and skips
|
|
12
|
+
* what it already has, so a client resuming from `Last-Event-ID` sees no gap and no
|
|
13
|
+
* duplicate. An observer behind the feed restarts the shared read from its own position.
|
|
14
|
+
* A feed follows one basin generation: it ends once its session is gone or the Tenant moved
|
|
15
|
+
* to a new generation (a reset, possibly on another node), checked when a client joins, when
|
|
16
|
+
* its read fails, on a `sessions.reset` signal, and periodically (`checkSessionStreams`).
|
|
17
|
+
*
|
|
18
|
+
* Business code never writes here: events reach streams only through the stream relay, from
|
|
19
|
+
* the record, after commit.
|
|
20
|
+
*/
|
|
21
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
22
|
+
import type { LiveEvent } from "@nylorun/core/contracts";
|
|
23
|
+
import type { TenantContext } from "./context.js";
|
|
24
|
+
import type { StreamsWiring } from "./streams.js";
|
|
25
|
+
/** Why the Runtime ended a stream a subject token opened. */
|
|
26
|
+
export type StreamEndReason = "token_expired" | "revoked";
|
|
27
|
+
/** Where an observer's events go: an SSE response, or an in-process reader. */
|
|
28
|
+
export interface ObserverSink {
|
|
29
|
+
write(event: LiveEvent): void;
|
|
30
|
+
/** Ends the stream; with a reason, the client is told why before it ends. */
|
|
31
|
+
end(reason?: StreamEndReason): void;
|
|
32
|
+
}
|
|
33
|
+
/** The subject token a stream was opened with: it ends at expiry or revocation. */
|
|
34
|
+
export interface StreamHolder {
|
|
35
|
+
readonly subject: string;
|
|
36
|
+
readonly epoch: number;
|
|
37
|
+
/** Epoch ms when the token expires. */
|
|
38
|
+
readonly expiresAt: number;
|
|
39
|
+
}
|
|
40
|
+
/** One client of a session: the next sequence it needs. */
|
|
41
|
+
interface Observer {
|
|
42
|
+
readonly sink: ObserverSink;
|
|
43
|
+
next: number;
|
|
44
|
+
readonly holder?: StreamHolder;
|
|
45
|
+
/** Ends the observer when its token expires. */
|
|
46
|
+
deadline?: NodeJS.Timeout;
|
|
47
|
+
}
|
|
48
|
+
/** The shared stream read of one observed session. */
|
|
49
|
+
interface SessionStream {
|
|
50
|
+
readonly sessionId: string;
|
|
51
|
+
/** The basin the feed reads (the Tenant's generation when it started). */
|
|
52
|
+
readonly basin: string;
|
|
53
|
+
readonly stream: string;
|
|
54
|
+
/** The sequence the shared read yields next. Every observer's `next` is at or past it. */
|
|
55
|
+
next: number;
|
|
56
|
+
readonly observers: Set<Observer>;
|
|
57
|
+
/** Aborts the current read; replaced when the read restarts from an earlier sequence. */
|
|
58
|
+
read: AbortController;
|
|
59
|
+
}
|
|
60
|
+
export interface SessionStreams {
|
|
61
|
+
/** Set once by `wireStreams`. */
|
|
62
|
+
wiring: StreamsWiring | undefined;
|
|
63
|
+
/** The Tenant's basin generation as this process last saw it (`wireStreams` keeps it). */
|
|
64
|
+
generation: number;
|
|
65
|
+
/** Session id → its shared read and observers. */
|
|
66
|
+
readonly sessions: Map<string, SessionStream>;
|
|
67
|
+
}
|
|
68
|
+
export declare function createSessionStreams(): SessionStreams;
|
|
69
|
+
export declare function send(response: ServerResponse, data: string): void;
|
|
70
|
+
/** Start an SSE response with keepalives; `onClose` runs once the client goes away. */
|
|
71
|
+
export declare function openSse(request: IncomingMessage, response: ServerResponse, onClose: () => void): void;
|
|
72
|
+
/** The resume cursor: `?cursor=` wins over `Last-Event-ID`. */
|
|
73
|
+
export declare function requestCursor(request: IncomingMessage, url: URL): string | undefined;
|
|
74
|
+
/** The basin the Tenant's session streams are in now. */
|
|
75
|
+
export declare function currentBasin(ctx: TenantContext): string;
|
|
76
|
+
/**
|
|
77
|
+
* `GET /v1/sessions/:id/items`: the session stream after the cursor, up to its tail when the
|
|
78
|
+
* read starts. The cursor returned is the last record read (the request's cursor when none
|
|
79
|
+
* was), whatever the `agent` filter kept.
|
|
80
|
+
*/
|
|
81
|
+
export declare function readHistory(ctx: TenantContext, sessionId: string, cursor: string | undefined, agent: string | undefined): Promise<{
|
|
82
|
+
items: LiveEvent[];
|
|
83
|
+
cursor: string | null;
|
|
84
|
+
}>;
|
|
85
|
+
/**
|
|
86
|
+
* `GET /v1/sessions/:id/events`: joins the session's shared read at the client's cursor and
|
|
87
|
+
* follows it until the client leaves, the session is reset or deleted, or the Tenant closes.
|
|
88
|
+
*/
|
|
89
|
+
export declare function streamSessionEvents(ctx: TenantContext, request: IncomingMessage, response: ServerResponse, sessionId: string, cursor: string | undefined, holder?: StreamHolder): Promise<void>;
|
|
90
|
+
/** Thrown by `observeSession` when the Runtime ended the stream for its token. */
|
|
91
|
+
export declare class StreamClosed extends Error {
|
|
92
|
+
readonly reason: StreamEndReason;
|
|
93
|
+
/** The cursor of the last event delivered, if any. */
|
|
94
|
+
readonly cursor: string | undefined;
|
|
95
|
+
constructor(reason: StreamEndReason,
|
|
96
|
+
/** The cursor of the last event delivered, if any. */
|
|
97
|
+
cursor: string | undefined);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* A session's events from `cursor`, in process: the same shared read and deadlines as an SSE
|
|
101
|
+
* client (`streamSessionEvents`). Ends when `signal` aborts or the feed ends; throws
|
|
102
|
+
* `StreamClosed` when the Runtime ends it for its token (expiry or revocation).
|
|
103
|
+
*/
|
|
104
|
+
export declare function observeSession(ctx: TenantContext, sessionId: string, cursor: string | undefined, options: {
|
|
105
|
+
holder?: StreamHolder;
|
|
106
|
+
signal: AbortSignal;
|
|
107
|
+
}): AsyncGenerator<LiveEvent>;
|
|
108
|
+
/** Internal events (`transcript.updated`) are recorded and streamed but never served. */
|
|
109
|
+
export declare function served(event: LiveEvent): boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Ends every feed in this process whose session is gone or whose Tenant moved to a new basin
|
|
112
|
+
* generation, and moves this process to the Tenant's current generation. Run on a
|
|
113
|
+
* `sessions.reset` signal and periodically, so feeds on nodes other than the one that reset
|
|
114
|
+
* the Tenant end too.
|
|
115
|
+
*/
|
|
116
|
+
export declare function checkSessionStreams(ctx: TenantContext): Promise<void>;
|
|
117
|
+
/**
|
|
118
|
+
* Ends the streams of `subject` opened with a token older than `epoch` (a revocation), on
|
|
119
|
+
* this process. Other processes do the same on the `subject.revoked` signal.
|
|
120
|
+
*/
|
|
121
|
+
export declare function endSubjectStreams(hub: SessionStreams, subject: string, epoch: number): void;
|
|
122
|
+
/** Reset or close: end every session observer and stop every shared read. */
|
|
123
|
+
export declare function clearObservers(hub: SessionStreams): void;
|
|
124
|
+
/** Close: end every session observer. */
|
|
125
|
+
export declare function endAllStreams(hub: SessionStreams): void;
|
|
126
|
+
export declare function sleep(ms: number, signal: AbortSignal): Promise<void>;
|
|
127
|
+
export {};
|