@nylorun/runtime 0.13.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 +330 -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 +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/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 +107 -113
- 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 +78 -37
- 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/module.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { StreamRelayStatus } from "@nylorun/core/contracts";
|
|
1
2
|
import { isQuarantineError, quarantine, TenantBusyError, TenantConflictError } from "./quarantine.js";
|
|
2
3
|
import { TenantNotFoundError, TenantUnavailableError, type Logger, type OpenTenantRuntime, type TenantConfig, type TenantModule, type TenantStore } from "./types.js";
|
|
3
4
|
export interface CreateTenantModuleOptions {
|
|
@@ -21,6 +22,8 @@ export interface CreateTenantModuleOptions {
|
|
|
21
22
|
* Durable Streams basin, its armed sweep). A failure is logged; the Tenant stays deleted.
|
|
22
23
|
*/
|
|
23
24
|
onDeleted?: (tenantId: string) => Promise<void>;
|
|
25
|
+
/** This process's stream relay, for the Host aggregate (a Host with S2 runs one). */
|
|
26
|
+
relayStatus?: () => Promise<StreamRelayStatus>;
|
|
24
27
|
}
|
|
25
28
|
/**
|
|
26
29
|
* Deep Tenant module (§8): discovery, create, resolve, list, delete and summarize over an
|
package/dist/tenant/module.js
CHANGED
|
@@ -7,7 +7,7 @@ import { TenantNotFoundError, TenantUnavailableError, } from "./types.js";
|
|
|
7
7
|
const OPEN_TIMEOUT_MS = 30_000;
|
|
8
8
|
function hasLiveWork(summary) {
|
|
9
9
|
return (summary.runningSessions > 0 ||
|
|
10
|
-
summary.
|
|
10
|
+
summary.inFlightDeliveries > 0 ||
|
|
11
11
|
summary.pendingActions > 0);
|
|
12
12
|
}
|
|
13
13
|
function toAdmin(id, entry, envelope) {
|
|
@@ -284,28 +284,22 @@ export function createTenantModule(options) {
|
|
|
284
284
|
},
|
|
285
285
|
async summarize() {
|
|
286
286
|
let runningSessions = 0;
|
|
287
|
-
let
|
|
287
|
+
let inFlightDeliveries = 0;
|
|
288
288
|
let pendingActions = 0;
|
|
289
289
|
let uncertainEffects = 0;
|
|
290
|
-
let outboxDepth;
|
|
291
|
-
let relayLagMs;
|
|
292
290
|
for (const s of await Promise.all(openEntries().map((e) => e.handle.summary()))) {
|
|
293
291
|
runningSessions += s.runningSessions;
|
|
294
|
-
|
|
292
|
+
inFlightDeliveries += s.inFlightDeliveries;
|
|
295
293
|
pendingActions += s.pendingActions;
|
|
296
294
|
uncertainEffects += s.uncertainEffects;
|
|
297
|
-
if (s.outboxDepth !== undefined)
|
|
298
|
-
outboxDepth = (outboxDepth ?? 0) + s.outboxDepth;
|
|
299
|
-
if (s.relayLagMs !== undefined)
|
|
300
|
-
relayLagMs = Math.max(relayLagMs ?? 0, s.relayLagMs);
|
|
301
295
|
}
|
|
296
|
+
const relay = await options.relayStatus?.().catch(() => undefined);
|
|
302
297
|
return {
|
|
303
298
|
runningSessions,
|
|
304
|
-
|
|
299
|
+
inFlightDeliveries,
|
|
305
300
|
pendingActions,
|
|
306
301
|
uncertainEffects,
|
|
307
|
-
...(
|
|
308
|
-
...(relayLagMs !== undefined ? { relayLagMs } : {}),
|
|
302
|
+
...(relay ? { relay } : {}),
|
|
309
303
|
};
|
|
310
304
|
},
|
|
311
305
|
async close() {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type IncomingHttpHeaders } from "node:http";
|
|
2
|
+
/** How the Host lets the Runtime call developer URLs (Host settings, `TenantConfig.delivery`). */
|
|
3
|
+
export interface OutboundPolicy {
|
|
4
|
+
/** `http` URLs as well as `https`. Default true (OSS). */
|
|
5
|
+
allowHttp?: boolean;
|
|
6
|
+
/** Private, loopback and link-local addresses. Default `allow` (OSS); Cloud refuses. */
|
|
7
|
+
privateAddresses?: "allow" | "refuse";
|
|
8
|
+
/**
|
|
9
|
+
* `docker-host`: the Runtime runs in the local stack's container, so `localhost`,
|
|
10
|
+
* `127.0.0.1` and `[::1]` mean the machine that runs Docker (`host.docker.internal`).
|
|
11
|
+
*/
|
|
12
|
+
loopback?: "docker-host";
|
|
13
|
+
}
|
|
14
|
+
export type OutboundResult = {
|
|
15
|
+
kind: "response";
|
|
16
|
+
status: number;
|
|
17
|
+
headers: IncomingHttpHeaders;
|
|
18
|
+
body: Buffer;
|
|
19
|
+
}
|
|
20
|
+
/** The answer was larger than `maxResponseBytes`. */
|
|
21
|
+
| {
|
|
22
|
+
kind: "too_large";
|
|
23
|
+
status: number;
|
|
24
|
+
} | {
|
|
25
|
+
kind: "not_sent";
|
|
26
|
+
code: string;
|
|
27
|
+
message: string;
|
|
28
|
+
} | {
|
|
29
|
+
kind: "lost";
|
|
30
|
+
code: string;
|
|
31
|
+
message: string;
|
|
32
|
+
};
|
|
33
|
+
/** The largest answer read from an endpoint. */
|
|
34
|
+
export declare const MAX_RESPONSE_BYTES: number;
|
|
35
|
+
/** True when `address` is private, loopback, link-local or unspecified (IPv4-mapped included). */
|
|
36
|
+
export declare function isPrivateAddress(address: string): boolean;
|
|
37
|
+
/** Why the Host refuses `url`, or `undefined` when it may be called. */
|
|
38
|
+
export declare function refusal(url: URL, policy: OutboundPolicy): string | undefined;
|
|
39
|
+
/** POSTs `body` to `url` under `policy`. Never throws; `signal` aborts the request. */
|
|
40
|
+
export declare function post(target: string, body: string, headers: Record<string, string>, options: {
|
|
41
|
+
policy: OutboundPolicy;
|
|
42
|
+
signal: AbortSignal;
|
|
43
|
+
maxResponseBytes?: number;
|
|
44
|
+
}): Promise<OutboundResult>;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Runtime's requests to Action endpoints (design: Action endpoints §8.3): one POST, no
|
|
3
|
+
* redirects, a bounded answer, and the Host's address policy checked on the address actually
|
|
4
|
+
* connected to, so a DNS answer cannot steer a delivery somewhere the Host forbids.
|
|
5
|
+
*
|
|
6
|
+
* The result says whether anything reached the endpoint. A request that failed before its body
|
|
7
|
+
* was flushed (refused connection, unknown host, TLS failure, a refused address) was `not_sent`
|
|
8
|
+
* and is safe to send again, even for a tool; one that failed after (reset, timeout) is `lost`.
|
|
9
|
+
*/
|
|
10
|
+
import { lookup as dnsLookup } from "node:dns";
|
|
11
|
+
import { request as httpRequest } from "node:http";
|
|
12
|
+
import { request as httpsRequest } from "node:https";
|
|
13
|
+
import { BlockList, isIP } from "node:net";
|
|
14
|
+
/** The largest answer read from an endpoint. */
|
|
15
|
+
export const MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
16
|
+
const PRIVATE = new BlockList();
|
|
17
|
+
for (const [network, prefix] of [
|
|
18
|
+
["0.0.0.0", 8],
|
|
19
|
+
["10.0.0.0", 8],
|
|
20
|
+
["100.64.0.0", 10],
|
|
21
|
+
["127.0.0.0", 8],
|
|
22
|
+
["169.254.0.0", 16],
|
|
23
|
+
["172.16.0.0", 12],
|
|
24
|
+
["192.168.0.0", 16],
|
|
25
|
+
])
|
|
26
|
+
PRIVATE.addSubnet(network, prefix, "ipv4");
|
|
27
|
+
for (const [network, prefix] of [
|
|
28
|
+
["::", 128],
|
|
29
|
+
["::1", 128],
|
|
30
|
+
["fc00::", 7],
|
|
31
|
+
["fe80::", 10],
|
|
32
|
+
])
|
|
33
|
+
PRIVATE.addSubnet(network, prefix, "ipv6");
|
|
34
|
+
/** True when `address` is private, loopback, link-local or unspecified (IPv4-mapped included). */
|
|
35
|
+
export function isPrivateAddress(address) {
|
|
36
|
+
const mapped = /^::ffff:(\d+\.\d+\.\d+\.\d+)$/i.exec(address);
|
|
37
|
+
if (mapped)
|
|
38
|
+
return PRIVATE.check(mapped[1], "ipv4");
|
|
39
|
+
const family = isIP(address);
|
|
40
|
+
if (family === 0)
|
|
41
|
+
return false;
|
|
42
|
+
return PRIVATE.check(address, family === 4 ? "ipv4" : "ipv6");
|
|
43
|
+
}
|
|
44
|
+
class RefusedAddress extends Error {
|
|
45
|
+
code = "ENDPOINT_ADDRESS_REFUSED";
|
|
46
|
+
}
|
|
47
|
+
/** Why the Host refuses `url`, or `undefined` when it may be called. */
|
|
48
|
+
export function refusal(url, policy) {
|
|
49
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && policy.allowHttp !== false))
|
|
50
|
+
return `This Runtime calls only ${policy.allowHttp === false ? "https" : "http and https"} endpoints`;
|
|
51
|
+
const host = url.hostname.replace(/^\[|\]$/g, "");
|
|
52
|
+
if (policy.privateAddresses === "refuse" && isIP(host) && isPrivateAddress(host))
|
|
53
|
+
return "This Runtime does not call private addresses; expose the endpoint through a public URL (a tunnel, for a local app)";
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
/** POSTs `body` to `url` under `policy`. Never throws; `signal` aborts the request. */
|
|
57
|
+
export async function post(target, body, headers, options) {
|
|
58
|
+
const { policy, signal } = options;
|
|
59
|
+
const limit = options.maxResponseBytes ?? MAX_RESPONSE_BYTES;
|
|
60
|
+
const url = new URL(target);
|
|
61
|
+
const refused = refusal(url, policy);
|
|
62
|
+
if (refused)
|
|
63
|
+
return { kind: "not_sent", code: "ENDPOINT_ADDRESS_REFUSED", message: refused };
|
|
64
|
+
const originalHost = url.host;
|
|
65
|
+
if (policy.loopback === "docker-host" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))
|
|
66
|
+
url.hostname = "host.docker.internal";
|
|
67
|
+
const lookup = (hostname, lookupOptions, callback) => dnsLookup(hostname, { ...lookupOptions, all: true }, (error, addresses) => {
|
|
68
|
+
if (error)
|
|
69
|
+
return callback(error, []);
|
|
70
|
+
const allowed = policy.privateAddresses === "refuse"
|
|
71
|
+
? addresses.filter((a) => !isPrivateAddress(a.address))
|
|
72
|
+
: addresses;
|
|
73
|
+
if (allowed.length === 0)
|
|
74
|
+
return callback(new RefusedAddress(`${hostname} resolves only to private addresses, which this Runtime does not call`), []);
|
|
75
|
+
if (lookupOptions.all)
|
|
76
|
+
return callback(null, allowed);
|
|
77
|
+
return callback(null, allowed[0].address, allowed[0].family);
|
|
78
|
+
});
|
|
79
|
+
return new Promise((resolve) => {
|
|
80
|
+
let sent = false;
|
|
81
|
+
let settled = false;
|
|
82
|
+
const finish = (result) => {
|
|
83
|
+
if (settled)
|
|
84
|
+
return;
|
|
85
|
+
settled = true;
|
|
86
|
+
resolve(result);
|
|
87
|
+
};
|
|
88
|
+
const failed = (error) => finish({
|
|
89
|
+
kind: sent ? "lost" : "not_sent",
|
|
90
|
+
code: signal.aborted ? "ABORTED" : (error.code ?? error.name),
|
|
91
|
+
message: signal.aborted ? "The delivery was aborted" : error.message,
|
|
92
|
+
});
|
|
93
|
+
const request = (url.protocol === "https:" ? httpsRequest : httpRequest)(url, {
|
|
94
|
+
method: "POST",
|
|
95
|
+
headers: {
|
|
96
|
+
...headers,
|
|
97
|
+
host: originalHost,
|
|
98
|
+
"content-type": "application/json",
|
|
99
|
+
"content-length": String(Buffer.byteLength(body)),
|
|
100
|
+
},
|
|
101
|
+
lookup: lookup,
|
|
102
|
+
signal,
|
|
103
|
+
}, (response) => {
|
|
104
|
+
const status = response.statusCode ?? 0;
|
|
105
|
+
const chunks = [];
|
|
106
|
+
let size = 0;
|
|
107
|
+
response.on("data", (chunk) => {
|
|
108
|
+
size += chunk.length;
|
|
109
|
+
if (size > limit) {
|
|
110
|
+
// Settle first: destroying the answer reports an error of its own.
|
|
111
|
+
finish({ kind: "too_large", status });
|
|
112
|
+
response.destroy();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
chunks.push(chunk);
|
|
116
|
+
});
|
|
117
|
+
response.on("end", () => finish({ kind: "response", status, headers: response.headers, body: Buffer.concat(chunks) }));
|
|
118
|
+
response.on("error", failed);
|
|
119
|
+
response.on("aborted", () => failed(Object.assign(new Error("The answer was cut off"), { code: "ECONNRESET" })));
|
|
120
|
+
});
|
|
121
|
+
request.on("finish", () => {
|
|
122
|
+
sent = true;
|
|
123
|
+
});
|
|
124
|
+
request.on("error", failed);
|
|
125
|
+
request.end(body);
|
|
126
|
+
});
|
|
127
|
+
}
|
package/dist/tenant/pool.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/** Run async work over items with a fixed concurrency limit. */
|
|
2
|
-
export declare function mapPool<T, R>(items: readonly T[], concurrency: number, worker: (item: T, index: number) => Promise<R>): Promise<R[]>;
|
|
3
1
|
export declare class TimeoutError extends Error {
|
|
4
2
|
constructor(message?: string);
|
|
5
3
|
}
|
package/dist/tenant/pool.js
CHANGED
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
/** Run async work over items with a fixed concurrency limit. */
|
|
2
|
-
export async function mapPool(items, concurrency, worker) {
|
|
3
|
-
if (items.length === 0)
|
|
4
|
-
return [];
|
|
5
|
-
const limit = Math.max(1, Math.min(concurrency, items.length));
|
|
6
|
-
const results = new Array(items.length);
|
|
7
|
-
let next = 0;
|
|
8
|
-
async function run() {
|
|
9
|
-
for (;;) {
|
|
10
|
-
const index = next++;
|
|
11
|
-
if (index >= items.length)
|
|
12
|
-
return;
|
|
13
|
-
results[index] = await worker(items[index], index);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
await Promise.all(Array.from({ length: limit }, () => run()));
|
|
17
|
-
return results;
|
|
18
|
-
}
|
|
19
1
|
export class TimeoutError extends Error {
|
|
20
2
|
constructor(message = "operation timed out") {
|
|
21
3
|
super(message);
|
package/dist/tenant/reset.d.ts
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import type { ResetScope, SessionStore } from "../store/types.js";
|
|
2
|
-
import type { ExecutorRegistry } from "../core/executors.js";
|
|
3
2
|
import type { SandboxManager } from "../sandbox/manager.js";
|
|
4
3
|
import type { TenantPaths } from "./types.js";
|
|
5
4
|
export type { ResetScope } from "../store/types.js";
|
|
6
5
|
export interface ResetTenantContext {
|
|
7
6
|
store: SessionStore;
|
|
8
|
-
registry: ExecutorRegistry;
|
|
9
7
|
sandbox: SandboxManager;
|
|
10
8
|
paths: TenantPaths;
|
|
11
9
|
/** Clear in-memory session observers after the store wipe. */
|
|
12
10
|
clearSessionState: () => void;
|
|
13
|
-
/** Clear executor SSE streams when registrations are removed. */
|
|
14
|
-
clearExecutorStreams: () => void;
|
|
15
11
|
}
|
|
16
12
|
/**
|
|
17
13
|
* Reset Tenant durable state for `scope` (A17).
|
package/dist/tenant/reset.js
CHANGED
|
@@ -42,13 +42,8 @@ export async function resetTenant(ctx, scope) {
|
|
|
42
42
|
await ctx.sandbox.reconcile(() => false);
|
|
43
43
|
}
|
|
44
44
|
await ctx.store.tx((t) => t.reset(scope));
|
|
45
|
-
if (clearAll)
|
|
46
|
-
for (const row of ctx.registry.list())
|
|
47
|
-
ctx.registry.remove(row.agentId);
|
|
48
45
|
if (clearSessions)
|
|
49
46
|
ctx.clearSessionState();
|
|
50
|
-
if (clearAll)
|
|
51
|
-
ctx.clearExecutorStreams();
|
|
52
47
|
if (clearSandboxes)
|
|
53
48
|
replaceDirectory(ctx.paths.sandboxes);
|
|
54
49
|
if (clearAll) {
|
package/dist/tenant/runtime.d.ts
CHANGED
|
@@ -1,25 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Composition root of an open Tenant: validates the config, opens the Session Store and the
|
|
3
|
-
* vault key, builds the services and the `TenantContext`, wires the seams, registers the
|
|
4
|
-
* Tenant's Worker handlers and arms its sweep, and implements `TenantHandle` (handle,
|
|
5
|
-
* summary, drain, close) by delegating to the Tenant modules.
|
|
6
|
-
*
|
|
7
|
-
* Seams wired here: `wireStreams()` connects the store's commits to Durable Streams (the
|
|
8
|
-
* relay, and the history, SSE, work and control readers); `wake` goes to the
|
|
9
|
-
* `DurableExecution`, whose handlers (`worker.ts`) call `advance` and `sweep`; `abortLocal`
|
|
10
|
-
* aborts an advance running on this process. The sweep also drains the outbox.
|
|
11
|
-
*
|
|
12
|
-
* Execution: the Host passes one `TenantExecution` for every Tenant it opens
|
|
13
|
-
* (`TenantOpenHooks.execution`); without one, the Tenant runs its own in-process
|
|
14
|
-
* `MemoryExecution`. No lock file: several processes may open a Tenant, and ownership of each
|
|
15
|
-
* session (§10.6) keeps its advances apart.
|
|
16
|
-
*/
|
|
17
|
-
import type { ServerResponse, IncomingMessage } from "node:http";
|
|
18
1
|
import type { TenantEnvelope } from "@nylorun/core/contracts";
|
|
19
2
|
import { type ModelProvider } from "../core/provider.js";
|
|
20
3
|
import { type AuthorizeResult } from "../vault/service.js";
|
|
21
4
|
import type { DurableStreams } from "../streams/types.js";
|
|
22
|
-
import type { OpenedTenant, TenantConfig, TenantHandle, TenantSummary } from "./types.js";
|
|
5
|
+
import type { NodeBindings, OpenedTenant, TenantConfig, TenantHandle, TenantSummary } from "./types.js";
|
|
23
6
|
import { type StreamsStatus } from "./streams.js";
|
|
24
7
|
import { type TenantExecution, type TenantWorker } from "./worker.js";
|
|
25
8
|
/** TENANTS-CCR: test/injection hooks until TenantConfig gains them. */
|
|
@@ -42,6 +25,13 @@ export type TenantOpenHooks = {
|
|
|
42
25
|
* survive a restart (tests, and a Host without S2).
|
|
43
26
|
*/
|
|
44
27
|
streams?: DurableStreams;
|
|
28
|
+
/**
|
|
29
|
+
* The Host's stream relay feeds `streams` from the record for every Tenant. Without it the
|
|
30
|
+
* Tenant relays its own commits.
|
|
31
|
+
*/
|
|
32
|
+
hostRelay?: boolean;
|
|
33
|
+
/** How long a retired stream basin is kept after a reset (tests). Default 60 s. */
|
|
34
|
+
retireGraceMs?: number;
|
|
45
35
|
} & OpenedTenant;
|
|
46
36
|
export declare class TenantRuntime implements TenantHandle {
|
|
47
37
|
private readonly ctx;
|
|
@@ -53,7 +43,7 @@ export declare class TenantRuntime implements TenantHandle {
|
|
|
53
43
|
private readonly closeGraceMs;
|
|
54
44
|
private constructor();
|
|
55
45
|
static open(config: TenantConfig, hooks: TenantOpenHooks): Promise<TenantRuntime>;
|
|
56
|
-
|
|
46
|
+
fetch(request: Request, node: NodeBindings): Promise<Response>;
|
|
57
47
|
authorize(sessionId: string, request: {
|
|
58
48
|
url: string;
|
|
59
49
|
serverName?: string;
|
|
@@ -67,7 +57,7 @@ export declare class TenantRuntime implements TenantHandle {
|
|
|
67
57
|
abortLocal(sessionId: string): void;
|
|
68
58
|
/**
|
|
69
59
|
* The Durable Streams seam's status for Tenant status and readiness: S2 reachability, the
|
|
70
|
-
* basin,
|
|
60
|
+
* basin, its generation and the Tenant's relay (`streamsStatus` in `tenant/streams.ts`).
|
|
71
61
|
*/
|
|
72
62
|
streamsStatus(): Promise<StreamsStatus>;
|
|
73
63
|
/** Adds a callback to the Tenant sweep. Returns a function that removes it. */
|
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) {
|