@nylorun/runtime 0.12.0-beta → 0.14.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 +361 -0
- package/README.md +69 -15
- package/dist/adapters/execution/restate.d.ts +7 -2
- package/dist/adapters/execution/restate.js +38 -8
- package/dist/adapters/replication/pgoutput.d.ts +16 -0
- package/dist/adapters/replication/pgoutput.js +169 -0
- package/dist/adapters/streams/s2.js +37 -20
- package/dist/admin-openapi.json +1071 -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 +7825 -0
- package/dist/api/components.js +83 -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 +130 -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 +259 -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 +10 -0
- package/dist/api/http/routes/tenant.js +172 -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 +138 -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 +19 -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/host/admin-api.d.ts +22 -0
- package/dist/host/admin-api.js +178 -0
- package/dist/host/app.d.ts +34 -0
- package/dist/host/app.js +185 -0
- package/dist/host/config-for.d.ts +2 -0
- package/dist/host/config-for.js +1 -0
- 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 +31 -3
- package/dist/host/create-host.js +168 -327
- package/dist/host/http.d.ts +14 -3
- package/dist/host/http.js +64 -23
- package/dist/host/main.js +55 -4
- package/dist/host/stack-config.d.ts +20 -0
- package/dist/host/stack-config.js +55 -0
- package/dist/mcp/pool.d.ts +19 -1
- package/dist/mcp/pool.js +60 -10
- 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 +25696 -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/event.d.ts +19 -0
- package/dist/store/event.js +49 -0
- package/dist/store/memory.d.ts +20 -8
- package/dist/store/memory.js +265 -100
- package/dist/store/postgres/connect.d.ts +13 -0
- package/dist/store/postgres/connect.js +5 -0
- package/dist/store/postgres/migrations/003_subject_tokens.d.ts +8 -0
- package/dist/store/postgres/migrations/003_subject_tokens.js +42 -0
- package/dist/store/postgres/migrations/004_publishable_keys.d.ts +6 -0
- package/dist/store/postgres/migrations/004_publishable_keys.js +18 -0
- package/dist/store/postgres/migrations/005_action_endpoints.d.ts +6 -0
- package/dist/store/postgres/migrations/005_action_endpoints.js +32 -0
- package/dist/store/postgres/migrations/006_drop_executors.d.ts +11 -0
- package/dist/store/postgres/migrations/006_drop_executors.js +32 -0
- package/dist/store/postgres/migrations/007_event_time.d.ts +7 -0
- package/dist/store/postgres/migrations/007_event_time.js +14 -0
- package/dist/store/postgres/migrations/008_durable_streams.d.ts +13 -0
- package/dist/store/postgres/migrations/008_durable_streams.js +35 -0
- package/dist/store/postgres/migrations/index.d.ts +5 -1
- package/dist/store/postgres/migrations/index.js +22 -2
- package/dist/store/postgres/migrations/shared/index.d.ts +31 -0
- package/dist/store/postgres/migrations/shared/index.js +92 -0
- package/dist/store/postgres/record.d.ts +11 -0
- package/dist/store/postgres/record.js +64 -0
- package/dist/store/postgres/store.js +337 -95
- package/dist/store/postgres/tenants.js +14 -0
- package/dist/store/types.d.ts +176 -81
- 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 +29 -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/memory.d.ts +69 -0
- package/dist/streams/relay/memory.js +176 -0
- package/dist/streams/relay/types.d.ts +77 -0
- package/dist/streams/relay/types.js +7 -0
- package/dist/streams/types.d.ts +66 -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.js +32 -0
- 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/commands.d.ts +18 -2
- package/dist/tenant/commands.js +121 -129
- package/dist/tenant/context.d.ts +75 -18
- package/dist/tenant/context.js +14 -10
- package/dist/tenant/delivery-token.d.ts +41 -0
- package/dist/tenant/delivery-token.js +105 -0
- package/dist/tenant/delivery.d.ts +62 -0
- package/dist/tenant/delivery.js +476 -0
- package/dist/tenant/effects.d.ts +5 -0
- package/dist/tenant/effects.js +138 -48
- package/dist/tenant/endpoints.d.ts +67 -0
- package/dist/tenant/endpoints.js +90 -0
- package/dist/tenant/ephemeral.d.ts +6 -0
- package/dist/tenant/ephemeral.js +60 -36
- 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/module.d.ts +3 -0
- package/dist/tenant/module.js +6 -12
- package/dist/tenant/outbound.d.ts +44 -0
- package/dist/tenant/outbound.js +127 -0
- package/dist/tenant/pool.d.ts +0 -2
- package/dist/tenant/pool.js +0 -18
- package/dist/tenant/reset.d.ts +0 -4
- package/dist/tenant/reset.js +0 -5
- package/dist/tenant/runtime.d.ts +10 -20
- package/dist/tenant/runtime.js +59 -42
- package/dist/tenant/scheduler.d.ts +7 -2
- package/dist/tenant/scheduler.js +7 -3
- package/dist/tenant/session-streams.d.ts +125 -0
- package/dist/tenant/session-streams.js +399 -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 +0 -3
- package/dist/tenant/status.js +19 -12
- package/dist/tenant/streams.d.ts +60 -67
- package/dist/tenant/streams.js +140 -208
- 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 +178 -0
- package/dist/tenant/transcript.d.ts +48 -5
- package/dist/tenant/transcript.js +58 -1
- package/dist/tenant/types.d.ts +29 -7
- package/dist/tenant/worker.d.ts +3 -1
- package/dist/tenant/worker.js +9 -3
- package/dist/vault/service.d.ts +18 -2
- package/dist/vault/service.js +57 -13
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +20 -8
- package/dist/core/executors.d.ts +0 -44
- package/dist/core/executors.js +0 -95
- package/dist/streams/relay.d.ts +0 -37
- package/dist/streams/relay.js +0 -254
- package/dist/tenant/live.d.ts +0 -92
- package/dist/tenant/live.js +0 -290
- 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/runtime.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { mkdirSync } from "node:fs";
|
|
2
2
|
import { resolveFlowLimits } from "../core/limits.js";
|
|
3
|
-
import { loadExecutorRegistry } from "../core/executors.js";
|
|
4
3
|
import { scriptedModel, gatewayModel, toolFixtureModel, } from "../core/provider.js";
|
|
5
4
|
import { createKekFile, readVaultKek } from "../vault/kek.js";
|
|
5
|
+
import { SigningKeys } from "./signing-keys.js";
|
|
6
6
|
import { VaultService } from "../vault/service.js";
|
|
7
7
|
import { McpPool } from "../mcp/pool.js";
|
|
8
8
|
import { SandboxManager } from "../sandbox/manager.js";
|
|
@@ -10,14 +10,15 @@ import { defaultSandboxBackends } from "../sandbox/select.js";
|
|
|
10
10
|
import { MemoryExecution } from "../execution/memory.js";
|
|
11
11
|
import { QuarantineError } from "./quarantine-error.js";
|
|
12
12
|
import { MemoryStreams } from "../streams/memory.js";
|
|
13
|
-
import {
|
|
14
|
-
import { closeStreams,
|
|
13
|
+
import { createSessionStreams, endAllStreams, } from "./session-streams.js";
|
|
14
|
+
import { closeStreams, streamsStatus, wireStreams, } from "./streams.js";
|
|
15
15
|
import { abortAll, abortLocal, createWorkState, drain, waitForIdle, } from "./scheduler.js";
|
|
16
16
|
import { advance } from "./advance.js";
|
|
17
17
|
import { sweep } from "./sweep.js";
|
|
18
|
+
import { deliverAction } from "./delivery.js";
|
|
18
19
|
import { TenantWorkers, WORKER_ID, } from "./worker.js";
|
|
19
20
|
import { authorize } from "./effects.js";
|
|
20
|
-
import {
|
|
21
|
+
import { tenantApi } from "../api/http/app.js";
|
|
21
22
|
/** Default ownership lease of an advance; the heartbeat renews it every third. */
|
|
22
23
|
const DEFAULT_OWNER_LEASE_MS = 30_000;
|
|
23
24
|
function validateConfig(config) {
|
|
@@ -70,8 +71,9 @@ export class TenantRuntime {
|
|
|
70
71
|
vaultKek: hooks.vaultKek,
|
|
71
72
|
vaultKekPath: paths.kek,
|
|
72
73
|
});
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
const sealed = await store.tx(async (t) => (await t.countCredentials()) + (await t.countSigningKeys()));
|
|
75
|
+
if (sealed > 0 && !kek) {
|
|
76
|
+
throw new QuarantineError("kek-missing", "Vault key-encryption key is missing for ciphertext (vault credentials or signing keys) in this Tenant", "restore the vault-kek file in the Tenant directory");
|
|
75
77
|
}
|
|
76
78
|
const createKekIfMissing = hooks.createKekIfMissing !== false;
|
|
77
79
|
const ensureKek = () => {
|
|
@@ -83,7 +85,6 @@ export class TenantRuntime {
|
|
|
83
85
|
return kek;
|
|
84
86
|
};
|
|
85
87
|
const opened = store;
|
|
86
|
-
const registry = await loadExecutorRegistry(opened);
|
|
87
88
|
// A seeded Tenant setting wins over the Host default (A18).
|
|
88
89
|
const seededBackend = await opened.tx((t) => t.getSetting("sandbox.backend"));
|
|
89
90
|
const vault = new VaultService({
|
|
@@ -141,7 +142,7 @@ export class TenantRuntime {
|
|
|
141
142
|
}
|
|
142
143
|
else
|
|
143
144
|
modelProvider = scriptedModel();
|
|
144
|
-
const
|
|
145
|
+
const sessionStreams = createSessionStreams();
|
|
145
146
|
const local = hooks.execution
|
|
146
147
|
? undefined
|
|
147
148
|
: new MemoryExecution({
|
|
@@ -160,7 +161,6 @@ export class TenantRuntime {
|
|
|
160
161
|
envelope,
|
|
161
162
|
store: opened,
|
|
162
163
|
vault,
|
|
163
|
-
registry,
|
|
164
164
|
mcp,
|
|
165
165
|
sandbox,
|
|
166
166
|
flowLimits,
|
|
@@ -169,7 +169,8 @@ export class TenantRuntime {
|
|
|
169
169
|
closing: false,
|
|
170
170
|
closed: false,
|
|
171
171
|
work: createWorkState(),
|
|
172
|
-
|
|
172
|
+
sessionStreams,
|
|
173
|
+
signingKeys: new SigningKeys({ tenantId: config.tenantId, kek: ensureKek }),
|
|
173
174
|
workerId: hooks.workerId ?? WORKER_ID,
|
|
174
175
|
ownerLeaseMs: config.ownerLeaseMs ?? DEFAULT_OWNER_LEASE_MS,
|
|
175
176
|
wake: async (sessionId, wake) => {
|
|
@@ -178,6 +179,11 @@ export class TenantRuntime {
|
|
|
178
179
|
await execution.wake(config.tenantId, sessionId, wake);
|
|
179
180
|
},
|
|
180
181
|
abortLocal: (sessionId) => abortLocal(ctx, sessionId),
|
|
182
|
+
deliver: async (actionId) => {
|
|
183
|
+
if (ctx.closing || ctx.closed)
|
|
184
|
+
return;
|
|
185
|
+
await execution.deliver(config.tenantId, actionId);
|
|
186
|
+
},
|
|
181
187
|
...(hooks.execution?.stuckInvocations
|
|
182
188
|
? {
|
|
183
189
|
stuckInvocations: () => hooks.execution.stuckInvocations(config.tenantId),
|
|
@@ -194,21 +200,15 @@ export class TenantRuntime {
|
|
|
194
200
|
streams: hooks.streams ?? new MemoryStreams(),
|
|
195
201
|
ownsStreams: !hooks.streams,
|
|
196
202
|
tenantId: config.tenantId,
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
sweepHooks.add(async () => {
|
|
200
|
-
await drainOutbox(ctx);
|
|
203
|
+
...(hooks.hostRelay ? { hostRelay: true } : {}),
|
|
204
|
+
...(hooks.retireGraceMs !== undefined ? { retireGraceMs: hooks.retireGraceMs } : {}),
|
|
201
205
|
});
|
|
202
206
|
// Register the handlers, then arm the sweep: its first pass runs at once and re-wakes
|
|
203
207
|
// sessions a previous process left runnable or running (takeover handles the rest).
|
|
204
|
-
let afterOpen = true;
|
|
205
208
|
const worker = {
|
|
206
209
|
advance: (sessionId, signal) => advance(ctx, sessionId, signal),
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
afterOpen = false;
|
|
210
|
-
await sweep(ctx, { afterOpen: first });
|
|
211
|
-
},
|
|
210
|
+
deliver: (actionId, signal) => deliverAction(ctx, actionId, signal),
|
|
211
|
+
sweep: () => sweep(ctx),
|
|
212
212
|
};
|
|
213
213
|
const unregister = workers.register(config.tenantId, worker);
|
|
214
214
|
detach = async () => {
|
|
@@ -227,28 +227,21 @@ export class TenantRuntime {
|
|
|
227
227
|
throw error;
|
|
228
228
|
}
|
|
229
229
|
}
|
|
230
|
-
|
|
231
|
-
return
|
|
230
|
+
async fetch(request, node) {
|
|
231
|
+
return await tenantApi().fetch(request, { ...node, tenant: this.ctx });
|
|
232
232
|
}
|
|
233
233
|
authorize(sessionId, request) {
|
|
234
234
|
return authorize(this.ctx, sessionId, request);
|
|
235
235
|
}
|
|
236
236
|
async summary() {
|
|
237
|
-
const { store
|
|
238
|
-
const
|
|
239
|
-
counts: await t.counts(),
|
|
240
|
-
outbox: await t.outboxStats(),
|
|
241
|
-
}));
|
|
237
|
+
const { store } = this.ctx;
|
|
238
|
+
const counts = await store.tx((t) => t.counts());
|
|
242
239
|
return {
|
|
243
240
|
ready: !this.ctx.closing && !this.ctx.closed,
|
|
244
241
|
runningSessions: counts.runningSessions,
|
|
245
|
-
|
|
242
|
+
inFlightDeliveries: [...this.ctx.work.deliveries.values()].reduce((n, set) => n + set.size, 0),
|
|
246
243
|
pendingActions: counts.pendingActions,
|
|
247
244
|
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
245
|
};
|
|
253
246
|
}
|
|
254
247
|
drain(activeWork, timeoutMs = 30_000) {
|
|
@@ -263,7 +256,7 @@ export class TenantRuntime {
|
|
|
263
256
|
}
|
|
264
257
|
/**
|
|
265
258
|
* The Durable Streams seam's status for Tenant status and readiness: S2 reachability, the
|
|
266
|
-
* basin,
|
|
259
|
+
* basin, its generation and the Tenant's relay (`streamsStatus` in `tenant/streams.ts`).
|
|
267
260
|
*/
|
|
268
261
|
streamsStatus() {
|
|
269
262
|
return streamsStatus(this.ctx);
|
|
@@ -278,15 +271,39 @@ export class TenantRuntime {
|
|
|
278
271
|
// A shutdown abort: the advances leave their sessions to the next advance, unsettled.
|
|
279
272
|
abortAll(ctx, "shutdown");
|
|
280
273
|
const idleBy = Date.now() + this.closeGraceMs;
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
274
|
+
// Every step runs even when one before it fails, so nothing is left running; the first
|
|
275
|
+
// error is rethrown at the end.
|
|
276
|
+
const steps = [
|
|
277
|
+
["detach", () => this.detach()],
|
|
278
|
+
["mcp", () => ctx.mcp.close()],
|
|
279
|
+
["session streams", () => endAllStreams(ctx.sessionStreams)],
|
|
280
|
+
// Bounded: an advance that ignores its abort is abandoned; its lease lapses (§11.4).
|
|
281
|
+
["idle", () => waitForIdle(ctx, Math.max(0, idleBy - Date.now()))],
|
|
282
|
+
["sandbox", () => ctx.sandbox.close()],
|
|
283
|
+
[
|
|
284
|
+
"streams",
|
|
285
|
+
() => {
|
|
286
|
+
ctx.closed = true;
|
|
287
|
+
return closeStreams(ctx);
|
|
288
|
+
},
|
|
289
|
+
],
|
|
290
|
+
["store", () => ctx.store.close()],
|
|
291
|
+
];
|
|
292
|
+
let failure;
|
|
293
|
+
for (const [step, run] of steps) {
|
|
294
|
+
try {
|
|
295
|
+
await run();
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
ctx.config.logger.warn("tenant close step failed", {
|
|
299
|
+
step,
|
|
300
|
+
message: error instanceof Error ? error.message : String(error),
|
|
301
|
+
});
|
|
302
|
+
failure ??= { error };
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (failure)
|
|
306
|
+
throw failure.error;
|
|
290
307
|
}
|
|
291
308
|
}
|
|
292
309
|
/**
|
|
@@ -3,11 +3,16 @@ 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
|
+
/** Deliveries to Action endpoints in flight on this process, by their Action's session id. */
|
|
7
|
+
readonly deliveries: Map<string, Set<AbortController>>;
|
|
8
|
+
/** When each Action last reported a failed delivery (`action.delivery_failed` throttling). */
|
|
9
|
+
readonly deliveryNotices: Map<string, number>;
|
|
6
10
|
}
|
|
7
11
|
export declare function createWorkState(): WorkState;
|
|
8
12
|
/**
|
|
9
|
-
* Abort the advance of `id` if it runs on this process
|
|
10
|
-
* `cancelled`, and so does the control stream for cancels made
|
|
13
|
+
* Abort the advance of `id` if it runs on this process, and its deliveries to Action endpoints.
|
|
14
|
+
* Cancel calls it after committing `cancelled`, and so does the control stream for cancels made
|
|
15
|
+
* elsewhere.
|
|
11
16
|
*/
|
|
12
17
|
export declare function abortLocal(ctx: TenantContext, id: string, kind?: AdvanceAbortKind): void;
|
|
13
18
|
/** Reset: abort every advance running on this process; their sessions are being cleared. */
|
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(), 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.
|
|
31
32
|
*/
|
|
32
33
|
export function abortLocal(ctx, id, kind = "cancel") {
|
|
33
34
|
const controller = ctx.work.running.get(id);
|
|
34
35
|
if (controller)
|
|
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,125 @@
|
|
|
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
|
+
/**
|
|
109
|
+
* Ends every feed in this process whose session is gone or whose Tenant moved to a new basin
|
|
110
|
+
* generation, and moves this process to the Tenant's current generation. Run on a
|
|
111
|
+
* `sessions.reset` signal and periodically, so feeds on nodes other than the one that reset
|
|
112
|
+
* the Tenant end too.
|
|
113
|
+
*/
|
|
114
|
+
export declare function checkSessionStreams(ctx: TenantContext): Promise<void>;
|
|
115
|
+
/**
|
|
116
|
+
* Ends the streams of `subject` opened with a token older than `epoch` (a revocation), on
|
|
117
|
+
* this process. Other processes do the same on the `subject.revoked` signal.
|
|
118
|
+
*/
|
|
119
|
+
export declare function endSubjectStreams(hub: SessionStreams, subject: string, epoch: number): void;
|
|
120
|
+
/** Reset or close: end every session observer and stop every shared read. */
|
|
121
|
+
export declare function clearObservers(hub: SessionStreams): void;
|
|
122
|
+
/** Close: end every session observer. */
|
|
123
|
+
export declare function endAllStreams(hub: SessionStreams): void;
|
|
124
|
+
export declare function sleep(ms: number, signal: AbortSignal): Promise<void>;
|
|
125
|
+
export {};
|