@nylorun/runtime 0.10.0-beta → 0.11.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 +46 -0
- package/README.md +53 -51
- package/dist/adapters/execution/restate.d.ts +130 -0
- package/dist/adapters/execution/restate.js +442 -0
- package/dist/adapters/sandbox/virtual.js +1 -1
- package/dist/adapters/streams/s2.d.ts +18 -0
- package/dist/adapters/streams/s2.js +361 -0
- package/dist/core/executors.d.ts +4 -1
- package/dist/core/executors.js +20 -1
- package/dist/core/flow-host.d.ts +93 -132
- package/dist/core/flow-host.js +282 -547
- package/dist/core/sandbox-routes.d.ts +8 -3
- package/dist/core/sandbox-routes.js +10 -10
- package/dist/execution/memory.d.ts +54 -0
- package/dist/execution/memory.js +217 -0
- package/dist/execution/types.d.ts +125 -0
- package/dist/execution/types.js +52 -0
- package/dist/host/config-for.d.ts +2 -1
- package/dist/host/config-for.js +3 -21
- package/dist/host/config.d.ts +0 -8
- package/dist/host/create-host.d.ts +36 -1
- package/dist/host/create-host.js +48 -24
- package/dist/host/execution.d.ts +61 -0
- package/dist/host/execution.js +69 -0
- package/dist/host/http.d.ts +4 -0
- package/dist/host/http.js +6 -0
- package/dist/host/index.d.ts +3 -1
- package/dist/host/index.js +2 -0
- package/dist/host/main.js +106 -47
- package/dist/host/stack-config.d.ts +72 -0
- package/dist/host/stack-config.js +193 -0
- package/dist/index.d.ts +1 -1
- package/dist/infra/database.d.ts +16 -0
- package/dist/infra/database.js +31 -0
- package/dist/infra/execution.d.ts +39 -0
- package/dist/infra/execution.js +68 -0
- package/dist/infra/index.d.ts +42 -0
- package/dist/infra/index.js +56 -0
- package/dist/infra/readiness.d.ts +33 -0
- package/dist/infra/readiness.js +53 -0
- package/dist/infra/streams.d.ts +17 -0
- package/dist/infra/streams.js +33 -0
- package/dist/infra/workers.d.ts +39 -0
- package/dist/infra/workers.js +31 -0
- package/dist/model/pi-model.d.ts +2 -2
- package/dist/model/pi-model.js +5 -5
- package/dist/sandbox/manager.d.ts +26 -8
- package/dist/sandbox/manager.js +95 -47
- package/dist/sandbox/select.d.ts +1 -1
- package/dist/sandbox/select.js +4 -5
- package/dist/sandbox/types.d.ts +2 -2
- package/dist/store/canonical.d.ts +5 -0
- package/dist/store/canonical.js +17 -0
- package/dist/store/cursor.d.ts +9 -0
- package/dist/store/cursor.js +18 -0
- package/dist/store/memory.d.ts +57 -0
- package/dist/store/memory.js +643 -0
- package/dist/store/ownership.d.ts +19 -0
- package/dist/store/ownership.js +33 -0
- package/dist/store/postgres/connect.d.ts +25 -0
- package/dist/store/postgres/connect.js +25 -0
- package/dist/store/postgres/locking.d.ts +16 -0
- package/dist/store/postgres/locking.js +22 -0
- package/dist/store/postgres/migrations/001_initial.d.ts +27 -0
- package/dist/store/postgres/migrations/001_initial.js +196 -0
- package/dist/store/postgres/migrations/index.d.ts +49 -0
- package/dist/store/postgres/migrations/index.js +75 -0
- package/dist/store/postgres/names.d.ts +11 -0
- package/dist/store/postgres/names.js +42 -0
- package/dist/store/postgres/store.d.ts +11 -0
- package/dist/store/postgres/store.js +790 -0
- package/dist/store/postgres/tenants.d.ts +90 -0
- package/dist/store/postgres/tenants.js +207 -0
- package/dist/store/types.d.ts +440 -0
- package/dist/store/types.js +10 -0
- package/dist/streams/basin.d.ts +23 -0
- package/dist/streams/basin.js +51 -0
- package/dist/streams/memory.d.ts +25 -0
- package/dist/streams/memory.js +124 -0
- package/dist/streams/relay.d.ts +37 -0
- package/dist/streams/relay.js +254 -0
- package/dist/streams/types.d.ts +115 -0
- package/dist/streams/types.js +86 -0
- package/dist/tenant/actions.d.ts +153 -0
- package/dist/tenant/actions.js +191 -0
- package/dist/tenant/advance.d.ts +15 -0
- package/dist/tenant/advance.js +439 -0
- package/dist/tenant/auth.d.ts +11 -0
- package/dist/tenant/auth.js +34 -0
- package/dist/tenant/commands.d.ts +3 -0
- package/dist/tenant/commands.js +384 -0
- package/dist/tenant/context.d.ts +140 -0
- package/dist/tenant/context.js +40 -0
- package/dist/tenant/effects.d.ts +20 -0
- package/dist/tenant/effects.js +539 -0
- package/dist/tenant/envelope.d.ts +0 -3
- package/dist/tenant/envelope.js +2 -34
- package/dist/tenant/ephemeral.d.ts +15 -4
- package/dist/tenant/ephemeral.js +106 -38
- package/dist/tenant/http.d.ts +32 -0
- package/dist/tenant/http.js +51 -0
- package/dist/tenant/live.d.ts +92 -0
- package/dist/tenant/live.js +290 -0
- package/dist/tenant/model-setting.d.ts +16 -0
- package/dist/tenant/model-setting.js +12 -0
- package/dist/tenant/module.d.ts +29 -8
- package/dist/tenant/module.js +162 -158
- package/dist/tenant/paths.d.ts +12 -8
- package/dist/tenant/paths.js +38 -12
- package/dist/tenant/principals.d.ts +9 -13
- package/dist/tenant/principals.js +31 -38
- package/dist/tenant/quarantine-error.d.ts +1 -1
- package/dist/tenant/quarantine-error.js +2 -8
- package/dist/tenant/quarantine.d.ts +0 -8
- package/dist/tenant/quarantine.js +8 -30
- package/dist/tenant/reset.d.ts +7 -6
- package/dist/tenant/reset.js +7 -51
- package/dist/tenant/routes-tenant.d.ts +12 -0
- package/dist/tenant/routes-tenant.js +126 -0
- package/dist/tenant/routes.d.ts +9 -0
- package/dist/tenant/routes.js +142 -0
- package/dist/tenant/runtime.d.ts +59 -58
- package/dist/tenant/runtime.js +243 -2201
- package/dist/tenant/scheduler.d.ts +25 -0
- package/dist/tenant/scheduler.js +84 -0
- package/dist/tenant/session.d.ts +60 -0
- package/dist/tenant/session.js +94 -0
- package/dist/tenant/sessions.d.ts +34 -0
- package/dist/tenant/sessions.js +149 -0
- package/dist/tenant/status.d.ts +13 -4
- package/dist/tenant/status.js +76 -41
- package/dist/tenant/store-memory.d.ts +9 -12
- package/dist/tenant/store-memory.js +79 -68
- package/dist/tenant/store-pg.d.ts +15 -0
- package/dist/tenant/store-pg.js +140 -0
- package/dist/tenant/streams.d.ts +126 -0
- package/dist/tenant/streams.js +419 -0
- package/dist/tenant/sweep.d.ts +15 -0
- package/dist/tenant/sweep.js +122 -0
- package/dist/tenant/types.d.ts +95 -15
- package/dist/tenant/types.js +23 -1
- package/dist/tenant/worker.d.ts +84 -0
- package/dist/tenant/worker.js +189 -0
- package/dist/vault/service.d.ts +37 -31
- package/dist/vault/service.js +439 -359
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +13 -13
- package/dist/adapters/sandbox/microsandbox.d.ts +0 -2
- package/dist/adapters/sandbox/microsandbox.js +0 -238
- package/dist/core/store.d.ts +0 -32
- package/dist/core/store.js +0 -143
- package/dist/launcher/commands.d.ts +0 -24
- package/dist/launcher/commands.js +0 -260
- package/dist/launcher/ensure-config.d.ts +0 -16
- package/dist/launcher/ensure-config.js +0 -79
- package/dist/launcher/errors.d.ts +0 -12
- package/dist/launcher/errors.js +0 -22
- package/dist/launcher/host-config.d.ts +0 -17
- package/dist/launcher/host-config.js +0 -64
- package/dist/launcher/host-state.d.ts +0 -17
- package/dist/launcher/host-state.js +0 -61
- package/dist/launcher/lifecycle.d.ts +0 -66
- package/dist/launcher/lifecycle.js +0 -407
- package/dist/launcher/locks.d.ts +0 -26
- package/dist/launcher/locks.js +0 -117
- package/dist/launcher/logs.d.ts +0 -18
- package/dist/launcher/logs.js +0 -117
- package/dist/launcher/main.d.ts +0 -2
- package/dist/launcher/main.js +0 -66
- package/dist/launcher/output.d.ts +0 -11
- package/dist/launcher/output.js +0 -68
- package/dist/launcher/paths.d.ts +0 -20
- package/dist/launcher/paths.js +0 -36
- package/dist/launcher/probe.d.ts +0 -23
- package/dist/launcher/probe.js +0 -72
- package/dist/launcher/protocol.d.ts +0 -62
- package/dist/launcher/protocol.js +0 -1
- package/dist/launcher/schema-guard.d.ts +0 -6
- package/dist/launcher/schema-guard.js +0 -36
- package/dist/launcher/spawn.d.ts +0 -22
- package/dist/launcher/spawn.js +0 -85
- package/dist/launcher/status.d.ts +0 -6
- package/dist/launcher/status.js +0 -65
- package/dist/tenant/migration.d.ts +0 -16
- package/dist/tenant/migration.js +0 -116
- package/dist/tenant/schema.d.ts +0 -23
- package/dist/tenant/schema.js +0 -151
- package/dist/tenant/store-fs.d.ts +0 -16
- package/dist/tenant/store-fs.js +0 -205
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { TenantContext } from "./context.js";
|
|
2
|
+
import { type AdvanceAbortKind } from "./worker.js";
|
|
3
|
+
export interface WorkState {
|
|
4
|
+
/** Advances running on this process, by session id. */
|
|
5
|
+
readonly running: Map<string, AbortController>;
|
|
6
|
+
}
|
|
7
|
+
export declare function createWorkState(): WorkState;
|
|
8
|
+
/**
|
|
9
|
+
* Abort the advance of `id` if it runs on this process. Cancel calls it after committing
|
|
10
|
+
* `cancelled`, and so does the control stream for cancels made elsewhere.
|
|
11
|
+
*/
|
|
12
|
+
export declare function abortLocal(ctx: TenantContext, id: string, kind?: AdvanceAbortKind): void;
|
|
13
|
+
/** Reset: abort every advance running on this process; their sessions are being cleared. */
|
|
14
|
+
export declare function clearWork(ctx: TenantContext): void;
|
|
15
|
+
/** Stop new advances; cancel active turns if asked; wait for running advances until the timeout. */
|
|
16
|
+
export declare function drain(ctx: TenantContext, activeWork: "drain" | "cancel", timeoutMs: number): Promise<void>;
|
|
17
|
+
/** Abort every advance running on this process; closing the Tenant is a `shutdown`. */
|
|
18
|
+
export declare function abortAll(ctx: TenantContext, kind?: AdvanceAbortKind): void;
|
|
19
|
+
/**
|
|
20
|
+
* Close: wait until no advance runs on this process, for at most `timeoutMs`. Advances still
|
|
21
|
+
* running then (an effect that ignores its abort signal) are abandoned and logged: they have
|
|
22
|
+
* stopped renewing their lease, so it lapses and the next advance takes the session over
|
|
23
|
+
* (§11.4); the epoch fences anything they still try to write. Returns their session ids.
|
|
24
|
+
*/
|
|
25
|
+
export declare function waitForIdle(ctx: TenantContext, timeoutMs: number): Promise<string[]>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local work: the advances running on this process, for `abortLocal`, drain, reset and close.
|
|
3
|
+
*
|
|
4
|
+
* Scheduling itself lives behind `DurableExecution` (`ctx.wake`), the Tenant's handlers in
|
|
5
|
+
* `worker.ts`, `advance.ts` and `sweep.ts`. What stays here is only what one process must
|
|
6
|
+
* know about the advances it runs: their `AbortController`s, keyed by session id.
|
|
7
|
+
*
|
|
8
|
+
* Every abort carries an `AdvanceAbort` saying why (`worker.ts`): a cancel or a reset aborts
|
|
9
|
+
* with `cancel`, closing the Tenant with `shutdown`, which leaves the session for the next
|
|
10
|
+
* advance instead of settling it.
|
|
11
|
+
*/
|
|
12
|
+
import { randomUUID } from "node:crypto";
|
|
13
|
+
import { command } from "./commands.js";
|
|
14
|
+
import { AdvanceAbort } from "./worker.js";
|
|
15
|
+
export function createWorkState() {
|
|
16
|
+
return { running: new Map() };
|
|
17
|
+
}
|
|
18
|
+
const MESSAGES = {
|
|
19
|
+
cancel: "Turn cancelled",
|
|
20
|
+
shutdown: "The Tenant is closing",
|
|
21
|
+
deadline: "The advance ran past its deadline",
|
|
22
|
+
"ownership.lost": "Ownership lost",
|
|
23
|
+
};
|
|
24
|
+
function abortWith(controller, kind) {
|
|
25
|
+
if (!controller.signal.aborted)
|
|
26
|
+
controller.abort(new AdvanceAbort(kind, MESSAGES[kind]));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Abort the advance of `id` if it runs on this process. Cancel calls it after committing
|
|
30
|
+
* `cancelled`, and so does the control stream for cancels made elsewhere.
|
|
31
|
+
*/
|
|
32
|
+
export function abortLocal(ctx, id, kind = "cancel") {
|
|
33
|
+
const controller = ctx.work.running.get(id);
|
|
34
|
+
if (controller)
|
|
35
|
+
abortWith(controller, kind);
|
|
36
|
+
}
|
|
37
|
+
/** Reset: abort every advance running on this process; their sessions are being cleared. */
|
|
38
|
+
export function clearWork(ctx) {
|
|
39
|
+
abortAll(ctx, "cancel");
|
|
40
|
+
}
|
|
41
|
+
/** Stop new advances; cancel active turns if asked; wait for running advances until the timeout. */
|
|
42
|
+
export async function drain(ctx, activeWork, timeoutMs) {
|
|
43
|
+
ctx.closing = true;
|
|
44
|
+
if (activeWork === "cancel") {
|
|
45
|
+
const active = await ctx.store.tx((t) => t.sessionsWithStatus(["running", "runnable", "paused"]));
|
|
46
|
+
for (const sess of active)
|
|
47
|
+
if (sess.activeTurnId)
|
|
48
|
+
await command(ctx, sess.id, {
|
|
49
|
+
type: "cancel",
|
|
50
|
+
requestId: randomUUID(),
|
|
51
|
+
idempotencyKey: `drain-cancel-${sess.id}-${sess.activeTurnId}`,
|
|
52
|
+
reason: "tenant drain cancel",
|
|
53
|
+
}, {
|
|
54
|
+
kind: "application",
|
|
55
|
+
principalId: "drain",
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
const deadline = Date.now() + timeoutMs;
|
|
59
|
+
while (ctx.work.running.size > 0 && Date.now() < deadline)
|
|
60
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
61
|
+
}
|
|
62
|
+
/** Abort every advance running on this process; closing the Tenant is a `shutdown`. */
|
|
63
|
+
export function abortAll(ctx, kind = "shutdown") {
|
|
64
|
+
for (const c of ctx.work.running.values())
|
|
65
|
+
abortWith(c, kind);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Close: wait until no advance runs on this process, for at most `timeoutMs`. Advances still
|
|
69
|
+
* running then (an effect that ignores its abort signal) are abandoned and logged: they have
|
|
70
|
+
* stopped renewing their lease, so it lapses and the next advance takes the session over
|
|
71
|
+
* (§11.4); the epoch fences anything they still try to write. Returns their session ids.
|
|
72
|
+
*/
|
|
73
|
+
export async function waitForIdle(ctx, timeoutMs) {
|
|
74
|
+
const deadline = Date.now() + timeoutMs;
|
|
75
|
+
while (ctx.work.running.size > 0 && Date.now() < deadline)
|
|
76
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
77
|
+
const abandoned = [...ctx.work.running.keys()];
|
|
78
|
+
if (abandoned.length > 0)
|
|
79
|
+
ctx.config.logger.warn("tenant closed with advances still running", {
|
|
80
|
+
sessionIds: abandoned,
|
|
81
|
+
waitedMs: timeoutMs,
|
|
82
|
+
});
|
|
83
|
+
return abandoned;
|
|
84
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers over the session document: turn-manifest pins and variants, state rebasing,
|
|
3
|
+
* the tools of an MCP snapshot, and the event target of an Action. No store access and no I/O.
|
|
4
|
+
*
|
|
5
|
+
* Later waves: stable; the async store conversion (Wave 1 / A) does not change these.
|
|
6
|
+
*/
|
|
7
|
+
import { type DurableSessionTool, type HostEffect } from "@nylorun/harness/run";
|
|
8
|
+
import type { Action } from "@nylorun/core/contracts";
|
|
9
|
+
import { type AgentManifest, type JsonValue } from "@nylorun/core/define";
|
|
10
|
+
import { type TurnManifestStore } from "../core/turn-manifest.js";
|
|
11
|
+
import type { McpSnapshot, McpToolRecord } from "../mcp/snapshot.js";
|
|
12
|
+
import type { Session } from "./context.js";
|
|
13
|
+
export declare function sessionToolsOf(snapshot: McpSnapshot | undefined): readonly DurableSessionTool[] | undefined;
|
|
14
|
+
/** Turn-manifest store backed by the session's `variants` map (no schema change). */
|
|
15
|
+
export declare function variantStore(session: Session): TurnManifestStore;
|
|
16
|
+
/** Manifest this turn's checkpoint is pinned to (session pin or a stored variant). */
|
|
17
|
+
export declare function turnManifestOf(session: Session): AgentManifest;
|
|
18
|
+
/** Wrap a linked agent turn so the Loop learns the turn's manifest (SD-I4 pin stays on session). */
|
|
19
|
+
export declare function linkedAgentOutput(session: Session, output: JsonValue | undefined): JsonValue;
|
|
20
|
+
export declare function rebaseSessionState(session: Session, turnHash: string): void;
|
|
21
|
+
export declare function mcpToolOf(session: Session, request: Pick<HostEffect, "agent" | "capabilityId" | "toolName">): McpToolRecord | undefined;
|
|
22
|
+
/** The manifest of the agent an effect belongs to: the root, or an agent it uses as a tool. */
|
|
23
|
+
export declare function manifestFor(manifest: AgentManifest, agent: HostEffect["agent"]): AgentManifest | undefined;
|
|
24
|
+
export declare function pinnedTool(manifest: AgentManifest, capabilityId?: string, toolName?: string): import("@nylorun/core/define").ToolManifest | undefined;
|
|
25
|
+
/** What an action runs, for events: a tool name, or a hook point and its capabilities. */
|
|
26
|
+
export declare function actionTarget(action: Action): {
|
|
27
|
+
hook: {
|
|
28
|
+
at: "after" | "before";
|
|
29
|
+
scope: "step" | "turn";
|
|
30
|
+
capabilityIds: string[];
|
|
31
|
+
};
|
|
32
|
+
agent?: {
|
|
33
|
+
id: string;
|
|
34
|
+
path: string;
|
|
35
|
+
delegationId?: string | undefined;
|
|
36
|
+
} | undefined;
|
|
37
|
+
} | {
|
|
38
|
+
toolName: string;
|
|
39
|
+
agent?: {
|
|
40
|
+
id: string;
|
|
41
|
+
path: string;
|
|
42
|
+
delegationId?: string | undefined;
|
|
43
|
+
} | undefined;
|
|
44
|
+
} | {
|
|
45
|
+
path: string;
|
|
46
|
+
key: string;
|
|
47
|
+
agent?: {
|
|
48
|
+
id: string;
|
|
49
|
+
path: string;
|
|
50
|
+
delegationId?: string | undefined;
|
|
51
|
+
} | {
|
|
52
|
+
id: string;
|
|
53
|
+
path: string;
|
|
54
|
+
delegationId?: string | undefined;
|
|
55
|
+
} | {
|
|
56
|
+
id: string;
|
|
57
|
+
path: string;
|
|
58
|
+
delegationId?: string | undefined;
|
|
59
|
+
} | undefined;
|
|
60
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers over the session document: turn-manifest pins and variants, state rebasing,
|
|
3
|
+
* the tools of an MCP snapshot, and the event target of an Action. No store access and no I/O.
|
|
4
|
+
*
|
|
5
|
+
* Later waves: stable; the async store conversion (Wave 1 / A) does not change these.
|
|
6
|
+
*/
|
|
7
|
+
import { agentTurnValue, } from "@nylorun/harness/run";
|
|
8
|
+
import { delegateManifest, } from "@nylorun/core/define";
|
|
9
|
+
import { isWorkflowManifest } from "../core/flow-host.js";
|
|
10
|
+
import { allowedManifestHashes, rebaseTurnState, } from "../core/turn-manifest.js";
|
|
11
|
+
export function sessionToolsOf(snapshot) {
|
|
12
|
+
if (!snapshot?.mcpTools.length)
|
|
13
|
+
return undefined;
|
|
14
|
+
return snapshot.mcpTools.map((tool) => ({
|
|
15
|
+
...(tool.agentId === undefined ? {} : { agentId: tool.agentId }),
|
|
16
|
+
capabilityId: tool.capabilityId,
|
|
17
|
+
name: tool.name,
|
|
18
|
+
...(tool.description === undefined
|
|
19
|
+
? {}
|
|
20
|
+
: { description: tool.description }),
|
|
21
|
+
inputSchema: tool.inputSchema,
|
|
22
|
+
...(tool.outputSchema === undefined
|
|
23
|
+
? {}
|
|
24
|
+
: { outputSchema: tool.outputSchema }),
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
/** Turn-manifest store backed by the session's `variants` map (no schema change). */
|
|
28
|
+
export function variantStore(session) {
|
|
29
|
+
return {
|
|
30
|
+
get(hash) {
|
|
31
|
+
return session.variants?.[hash];
|
|
32
|
+
},
|
|
33
|
+
put(hash, manifest) {
|
|
34
|
+
session.variants = { ...(session.variants ?? {}), [hash]: manifest };
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** Manifest this turn's checkpoint is pinned to (session pin or a stored variant). */
|
|
39
|
+
export function turnManifestOf(session) {
|
|
40
|
+
const hash = session.checkpoint?.manifestHash;
|
|
41
|
+
if (!hash || hash === session.manifestHash)
|
|
42
|
+
return session.manifest;
|
|
43
|
+
return session.variants?.[hash] ?? session.manifest;
|
|
44
|
+
}
|
|
45
|
+
/** Wrap a linked agent turn so the Loop learns the turn's manifest (SD-I4 pin stays on session). */
|
|
46
|
+
export function linkedAgentOutput(session, output) {
|
|
47
|
+
if (isWorkflowManifest(session.manifest))
|
|
48
|
+
return output ?? null;
|
|
49
|
+
return agentTurnValue(output ?? null, turnManifestOf(session));
|
|
50
|
+
}
|
|
51
|
+
export function rebaseSessionState(session, turnHash) {
|
|
52
|
+
const allowed = allowedManifestHashes({
|
|
53
|
+
pinnedHash: session.manifestHash,
|
|
54
|
+
variantHashes: Object.keys(session.variants ?? {}),
|
|
55
|
+
});
|
|
56
|
+
const next = rebaseTurnState({
|
|
57
|
+
state: session.state,
|
|
58
|
+
turnManifestHash: turnHash,
|
|
59
|
+
isAllowedHash: (hash) => allowed.has(hash),
|
|
60
|
+
});
|
|
61
|
+
if (next !== session.state)
|
|
62
|
+
session.state = next;
|
|
63
|
+
}
|
|
64
|
+
export function mcpToolOf(session, request) {
|
|
65
|
+
return session.mcpSnapshot?.mcpTools.find((tool) => tool.agentId === request.agent?.id &&
|
|
66
|
+
tool.capabilityId === request.capabilityId &&
|
|
67
|
+
tool.name === request.toolName);
|
|
68
|
+
}
|
|
69
|
+
/** The manifest of the agent an effect belongs to: the root, or an agent it uses as a tool. */
|
|
70
|
+
export function manifestFor(manifest, agent) {
|
|
71
|
+
return agent ? delegateManifest(manifest, agent.id) : manifest;
|
|
72
|
+
}
|
|
73
|
+
export function pinnedTool(manifest, capabilityId, toolName) {
|
|
74
|
+
const capability = manifest.capabilities.find((item) => item.id === capabilityId);
|
|
75
|
+
return capability?.tools?.find((tool) => tool.name === toolName);
|
|
76
|
+
}
|
|
77
|
+
/** What an action runs, for events: a tool name, or a hook point and its capabilities. */
|
|
78
|
+
export function actionTarget(action) {
|
|
79
|
+
if (action.kind === "hook")
|
|
80
|
+
return {
|
|
81
|
+
hook: action.hook,
|
|
82
|
+
...(action.agent ? { agent: action.agent } : {}),
|
|
83
|
+
};
|
|
84
|
+
if (action.kind === "tool" && "toolName" in action)
|
|
85
|
+
return {
|
|
86
|
+
toolName: action.toolName,
|
|
87
|
+
...(action.agent ? { agent: action.agent } : {}),
|
|
88
|
+
};
|
|
89
|
+
return {
|
|
90
|
+
path: action.path,
|
|
91
|
+
key: action.key,
|
|
92
|
+
...(action.agent ? { agent: action.agent } : {}),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session and definition resources: `PUT/GET /v1/agents`, `PUT /v1/sessions/:id` (create or
|
|
3
|
+
* re-attach vaults), the session list and the session view. Reads use the store's typed
|
|
4
|
+
* queries; `PUT /v1/sessions/:id` runs in one transaction with the vault attachment checks.
|
|
5
|
+
*/
|
|
6
|
+
import { type PutAgentRequest, type PutSessionRequest } from "@nylorun/core/contracts";
|
|
7
|
+
import type { Tx } from "../store/types.js";
|
|
8
|
+
import { type Session, type TenantContext } from "./context.js";
|
|
9
|
+
export declare function listDefinitions(ctx: TenantContext): Promise<{
|
|
10
|
+
agents: {
|
|
11
|
+
agentId: any;
|
|
12
|
+
manifest: any;
|
|
13
|
+
manifestHash: string;
|
|
14
|
+
implementationVersion: string;
|
|
15
|
+
}[];
|
|
16
|
+
}>;
|
|
17
|
+
export declare function putDefinition(ctx: TenantContext, agentId: string, body: PutAgentRequest): Promise<{
|
|
18
|
+
agentId: string;
|
|
19
|
+
manifestHash: string;
|
|
20
|
+
implementationVersion: string;
|
|
21
|
+
}>;
|
|
22
|
+
export declare function listSessions(ctx: TenantContext, agentId: string | null): Promise<{
|
|
23
|
+
sessions: {
|
|
24
|
+
id: string;
|
|
25
|
+
agentId: string;
|
|
26
|
+
ownerUserId: string;
|
|
27
|
+
status: string;
|
|
28
|
+
activeTurnId: string | null;
|
|
29
|
+
}[];
|
|
30
|
+
}>;
|
|
31
|
+
/** Create a session from its definition, or re-attach vaults to an identical one. */
|
|
32
|
+
export declare function putSession(ctx: TenantContext, id: string, body: PutSessionRequest): Promise<Session>;
|
|
33
|
+
/** The session resource returned by `GET`/`PUT /v1/sessions/:id`. */
|
|
34
|
+
export declare function sessionView(t: Tx, s: Session): Promise<unknown>;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session and definition resources: `PUT/GET /v1/agents`, `PUT /v1/sessions/:id` (create or
|
|
3
|
+
* re-attach vaults), the session list and the session view. Reads use the store's typed
|
|
4
|
+
* queries; `PUT /v1/sessions/:id` runs in one transaction with the vault attachment checks.
|
|
5
|
+
*/
|
|
6
|
+
import { DefinitionDocumentSchema, } from "@nylorun/core/contracts";
|
|
7
|
+
import { hashManifest } from "@nylorun/core/compatibility";
|
|
8
|
+
import { aggregateWaits, isWorkflowManifest } from "../core/flow-host.js";
|
|
9
|
+
import { canonical } from "../store/canonical.js";
|
|
10
|
+
import { newStreamIncarnation } from "../streams/types.js";
|
|
11
|
+
import { validateSandboxAttach } from "../core/sandbox-routes.js";
|
|
12
|
+
import { sandboxLookup } from "./context.js";
|
|
13
|
+
import { fail } from "./http.js";
|
|
14
|
+
/** A session's creation identity: everything but the request id and vault attachments. */
|
|
15
|
+
const sessionIdentity = (value) => {
|
|
16
|
+
const { requestId: _requestId, vaultIds: _vaultIds, credentialSelections: _selections, ...body } = value ?? {};
|
|
17
|
+
return canonical(body);
|
|
18
|
+
};
|
|
19
|
+
export async function listDefinitions(ctx) {
|
|
20
|
+
const definitions = await ctx.store.tx((t) => t.listDefinitions());
|
|
21
|
+
return {
|
|
22
|
+
agents: definitions.map((d) => ({
|
|
23
|
+
agentId: d.manifest.id,
|
|
24
|
+
manifest: d.manifest,
|
|
25
|
+
manifestHash: d.manifestHash,
|
|
26
|
+
implementationVersion: d.implementationVersion,
|
|
27
|
+
})),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export async function putDefinition(ctx, agentId, body) {
|
|
31
|
+
if (body.manifest.id !== agentId)
|
|
32
|
+
fail(400, "Agent id mismatch");
|
|
33
|
+
DefinitionDocumentSchema.parse(body.manifest);
|
|
34
|
+
const definition = {
|
|
35
|
+
...body,
|
|
36
|
+
manifestHash: hashManifest(body.manifest),
|
|
37
|
+
};
|
|
38
|
+
await ctx.store.tx((t) => t.put("definitions", agentId, definition));
|
|
39
|
+
return {
|
|
40
|
+
agentId,
|
|
41
|
+
manifestHash: definition.manifestHash,
|
|
42
|
+
implementationVersion: body.implementationVersion,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export async function listSessions(ctx, agentId) {
|
|
46
|
+
const sessions = await ctx.store.tx((t) => t.listSessions(agentId === null ? {} : { agentId }));
|
|
47
|
+
return {
|
|
48
|
+
sessions: sessions.map((s) => ({
|
|
49
|
+
id: s.id,
|
|
50
|
+
agentId: s.agentId,
|
|
51
|
+
ownerUserId: s.ownerUserId,
|
|
52
|
+
status: s.status,
|
|
53
|
+
activeTurnId: s.activeTurnId,
|
|
54
|
+
})),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** Create a session from its definition, or re-attach vaults to an identical one. */
|
|
58
|
+
export function putSession(ctx, id, body) {
|
|
59
|
+
const vaultIds = body.vaultIds ?? [];
|
|
60
|
+
const credentialSelections = body.credentialSelections ?? [];
|
|
61
|
+
return ctx.store.tx(async (t) => {
|
|
62
|
+
const prior = await t.lockSession(id);
|
|
63
|
+
const definition = prior === undefined || body.sandbox
|
|
64
|
+
? (await t.get("definitions", body.agentId)) ??
|
|
65
|
+
fail(404, "Definition not found")
|
|
66
|
+
: undefined;
|
|
67
|
+
const sandboxOwnerId = body.sandbox
|
|
68
|
+
? validateSandboxAttach(body, (definition ?? prior).manifest, await sandboxLookup(t, body.sandbox.session))
|
|
69
|
+
: undefined;
|
|
70
|
+
await ctx.vault.assertAttachment(t, body.ownerUserId, vaultIds, credentialSelections);
|
|
71
|
+
if (prior) {
|
|
72
|
+
if (sessionIdentity(prior.creation) !== sessionIdentity(body))
|
|
73
|
+
fail(409, "Session already exists with different creation parameters");
|
|
74
|
+
prior.vaultIds = vaultIds;
|
|
75
|
+
prior.credentialSelections = credentialSelections;
|
|
76
|
+
prior.creation = body;
|
|
77
|
+
if (sandboxOwnerId !== undefined)
|
|
78
|
+
prior.sandboxOwnerId = sandboxOwnerId;
|
|
79
|
+
await t.put("sessions", id, prior);
|
|
80
|
+
await ctx.vault.recordAttachment(t, id, vaultIds);
|
|
81
|
+
return prior;
|
|
82
|
+
}
|
|
83
|
+
const created = {
|
|
84
|
+
id,
|
|
85
|
+
agentId: body.agentId,
|
|
86
|
+
ownerUserId: body.ownerUserId,
|
|
87
|
+
manifest: definition.manifest,
|
|
88
|
+
manifestHash: definition.manifestHash,
|
|
89
|
+
implementationVersion: definition.implementationVersion,
|
|
90
|
+
info: body.info,
|
|
91
|
+
status: "idle",
|
|
92
|
+
activeTurnId: null,
|
|
93
|
+
creation: body,
|
|
94
|
+
vaultIds,
|
|
95
|
+
credentialSelections,
|
|
96
|
+
pluginRoots: definition.pluginRoots ?? {},
|
|
97
|
+
...(sandboxOwnerId !== undefined ? { sandboxOwnerId } : {}),
|
|
98
|
+
streamIncarnation: newStreamIncarnation(),
|
|
99
|
+
};
|
|
100
|
+
await t.put("sessions", id, created);
|
|
101
|
+
await ctx.vault.recordAttachment(t, id, vaultIds);
|
|
102
|
+
return created;
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
/** The session resource returned by `GET`/`PUT /v1/sessions/:id`. */
|
|
106
|
+
export async function sessionView(t, s) {
|
|
107
|
+
let waits = Array.isArray(s.waits)
|
|
108
|
+
? s.waits.map((call) => ({
|
|
109
|
+
invocationId: call.invocationId,
|
|
110
|
+
interaction: call.interaction,
|
|
111
|
+
wait: call.wait,
|
|
112
|
+
status: call.status,
|
|
113
|
+
}))
|
|
114
|
+
: s.waits;
|
|
115
|
+
if (isWorkflowManifest(s.manifest)) {
|
|
116
|
+
const aggregated = await aggregateWaits({ t, workflowSessionId: s.id });
|
|
117
|
+
if (aggregated.length > 0)
|
|
118
|
+
waits = aggregated;
|
|
119
|
+
}
|
|
120
|
+
const actions = await t.actionsForSession(s.id, {
|
|
121
|
+
statuses: ["pending", "claimed", "uncertain"],
|
|
122
|
+
});
|
|
123
|
+
const uncertain = await t.effectsForSession(s.id, {
|
|
124
|
+
statuses: ["uncertain"],
|
|
125
|
+
});
|
|
126
|
+
return {
|
|
127
|
+
id: s.id,
|
|
128
|
+
agentId: s.agentId,
|
|
129
|
+
ownerUserId: s.ownerUserId,
|
|
130
|
+
manifestHash: s.manifestHash,
|
|
131
|
+
implementationVersion: s.implementationVersion,
|
|
132
|
+
status: s.status,
|
|
133
|
+
activeTurnId: s.activeTurnId,
|
|
134
|
+
vaultIds: s.vaultIds ?? [],
|
|
135
|
+
credentialSelections: s.credentialSelections ?? [],
|
|
136
|
+
sandboxOwnerId: s.sandboxOwnerId ?? null,
|
|
137
|
+
mcpSnapshot: s.mcpSnapshot ?? null,
|
|
138
|
+
mcpDiagnostics: s.mcpDiagnostics ?? [],
|
|
139
|
+
waits,
|
|
140
|
+
error: s.error,
|
|
141
|
+
actions,
|
|
142
|
+
uncertainEffects: uncertain.map((e) => ({
|
|
143
|
+
effectId: e.request.effectId,
|
|
144
|
+
turnId: e.request.turnId,
|
|
145
|
+
kind: e.request.kind,
|
|
146
|
+
error: e.error,
|
|
147
|
+
})),
|
|
148
|
+
};
|
|
149
|
+
}
|
package/dist/tenant/status.d.ts
CHANGED
|
@@ -1,20 +1,29 @@
|
|
|
1
1
|
import type { SeedTenantConfigRequest, SeedTenantConfigResponse, TenantStatus } from "@nylorun/core/contracts";
|
|
2
|
-
import type {
|
|
2
|
+
import type { SessionStore } from "../store/types.js";
|
|
3
3
|
import type { ExecutorRegistry } from "../core/executors.js";
|
|
4
4
|
import type { VaultService } from "../vault/service.js";
|
|
5
5
|
import type { SandboxManager } from "../sandbox/manager.js";
|
|
6
6
|
import type { TenantConfig } from "./types.js";
|
|
7
7
|
import type { TenantEnvelope } from "@nylorun/core/contracts";
|
|
8
|
+
import type { StuckInvocation } from "../execution/types.js";
|
|
9
|
+
import type { StreamsStatus } from "./streams.js";
|
|
8
10
|
export interface TenantStatusContext {
|
|
9
11
|
envelope: TenantEnvelope;
|
|
10
12
|
config: TenantConfig;
|
|
11
|
-
store:
|
|
13
|
+
store: SessionStore;
|
|
12
14
|
registry: ExecutorRegistry;
|
|
13
15
|
vault: VaultService;
|
|
14
16
|
sandbox: SandboxManager;
|
|
15
17
|
closing: boolean;
|
|
16
18
|
modelConfigured: boolean;
|
|
17
19
|
executorStreams: Map<string, Set<unknown>>;
|
|
20
|
+
/**
|
|
21
|
+
* This Tenant's execution invocations that need an operator (`TenantExecution`). Absent
|
|
22
|
+
* when the execution cannot report them.
|
|
23
|
+
*/
|
|
24
|
+
stuckInvocations?: () => Promise<StuckInvocation[]>;
|
|
25
|
+
/** This Tenant's Durable Streams status (`streamsStatus` in `streams.ts`). */
|
|
26
|
+
streamsStatus?: () => Promise<StreamsStatus>;
|
|
18
27
|
}
|
|
19
28
|
/** Build `TenantStatusSchema` with secrets redacted (A14). */
|
|
20
29
|
export declare function buildTenantStatus(ctx: TenantStatusContext): Promise<TenantStatus>;
|
|
@@ -23,6 +32,6 @@ export declare function buildTenantStatus(ctx: TenantStatusContext): Promise<Ten
|
|
|
23
32
|
* Response lists field names only; never secret values.
|
|
24
33
|
*/
|
|
25
34
|
export declare function seedTenantConfig(ctx: {
|
|
26
|
-
store:
|
|
35
|
+
store: SessionStore;
|
|
27
36
|
vault: VaultService;
|
|
28
|
-
}, body: SeedTenantConfigRequest): SeedTenantConfigResponse
|
|
37
|
+
}, body: SeedTenantConfigRequest): Promise<SeedTenantConfigResponse>;
|
package/dist/tenant/status.js
CHANGED
|
@@ -1,28 +1,57 @@
|
|
|
1
|
-
import {
|
|
2
|
-
/**
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
.all("actions")
|
|
8
|
-
.filter((a) => a.status === "pending" || a.status === "claimed").length;
|
|
9
|
-
const uncertainEffects = ctx.store
|
|
10
|
-
.all("effects")
|
|
11
|
-
.filter((e) => e.status === "uncertain").length;
|
|
12
|
-
let sqlite = false;
|
|
1
|
+
import { FIXTURE_MODEL_SETTING, seedFixtureModel } from "./model-setting.js";
|
|
2
|
+
/** How long status waits for the execution to list stuck invocations. */
|
|
3
|
+
const STUCK_TIMEOUT_MS = 5000;
|
|
4
|
+
/** `TenantStatus.execution`: paused and backing-off invocations, or why they are unknown. */
|
|
5
|
+
async function executionStatus(stuckInvocations) {
|
|
6
|
+
let timer;
|
|
13
7
|
try {
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
const stuck = await Promise.race([
|
|
9
|
+
stuckInvocations(),
|
|
10
|
+
new Promise((_, reject) => {
|
|
11
|
+
timer = setTimeout(() => reject(new Error("Timed out listing stuck invocations")), STUCK_TIMEOUT_MS);
|
|
12
|
+
}),
|
|
13
|
+
]);
|
|
14
|
+
return {
|
|
15
|
+
stuckInvocations: stuck.map((invocation) => ({
|
|
16
|
+
id: invocation.id,
|
|
17
|
+
status: invocation.status,
|
|
18
|
+
service: invocation.service,
|
|
19
|
+
handler: invocation.handler,
|
|
20
|
+
key: invocation.key,
|
|
21
|
+
retryCount: invocation.retryCount,
|
|
22
|
+
...(invocation.lastFailure !== undefined
|
|
23
|
+
? { lastFailure: invocation.lastFailure }
|
|
24
|
+
: {}),
|
|
25
|
+
...(invocation.modifiedAt !== undefined
|
|
26
|
+
? { modifiedAt: invocation.modifiedAt }
|
|
27
|
+
: {}),
|
|
28
|
+
})),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
return {
|
|
33
|
+
stuckInvocations: [],
|
|
34
|
+
error: error instanceof Error ? error.message : String(error),
|
|
35
|
+
};
|
|
16
36
|
}
|
|
17
|
-
|
|
18
|
-
|
|
37
|
+
finally {
|
|
38
|
+
clearTimeout(timer);
|
|
19
39
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
.
|
|
40
|
+
}
|
|
41
|
+
/** Build `TenantStatusSchema` with secrets redacted (A14). */
|
|
42
|
+
export async function buildTenantStatus(ctx) {
|
|
43
|
+
const health = await ctx.store.health();
|
|
44
|
+
const { counts, definitions } = await ctx.store.tx(async (t) => ({
|
|
45
|
+
counts: await t.counts(),
|
|
46
|
+
definitions: await t.listDefinitions(),
|
|
47
|
+
}));
|
|
48
|
+
const [modelView, sandboxReport, execution, streams] = await Promise.all([
|
|
49
|
+
ctx.vault.getHostModel(),
|
|
50
|
+
ctx.sandbox.report(),
|
|
51
|
+
ctx.stuckInvocations ? executionStatus(ctx.stuckInvocations) : undefined,
|
|
52
|
+
ctx.streamsStatus?.(),
|
|
53
|
+
]);
|
|
54
|
+
const definitionIds = new Set(definitions
|
|
26
55
|
.map((d) => d.manifest?.id)
|
|
27
56
|
.filter((id) => typeof id === "string"));
|
|
28
57
|
const executorIds = new Set(ctx.registry.list().map((e) => e.agentId));
|
|
@@ -40,50 +69,56 @@ export async function buildTenantStatus(ctx) {
|
|
|
40
69
|
tenant: ctx.envelope,
|
|
41
70
|
path: ctx.config.paths.root,
|
|
42
71
|
checks: {
|
|
43
|
-
|
|
72
|
+
store: health.schemaVersion > 0,
|
|
44
73
|
scheduler: !ctx.closing,
|
|
45
74
|
model: ctx.modelConfigured || modelView.configured,
|
|
46
75
|
executors: true,
|
|
47
|
-
schema:
|
|
76
|
+
schema: health.ok,
|
|
48
77
|
},
|
|
49
78
|
model: modelView,
|
|
50
79
|
agents,
|
|
51
80
|
counts: {
|
|
52
|
-
sessions: sessions
|
|
53
|
-
runningSessions,
|
|
54
|
-
pendingActions,
|
|
55
|
-
uncertainEffects,
|
|
81
|
+
sessions: counts.sessions,
|
|
82
|
+
runningSessions: counts.runningSessions,
|
|
83
|
+
pendingActions: counts.pendingActions,
|
|
84
|
+
uncertainEffects: counts.uncertainEffects,
|
|
56
85
|
},
|
|
57
86
|
sandbox: {
|
|
58
87
|
backend: sandboxReport.backend,
|
|
59
|
-
retained,
|
|
88
|
+
retained: counts.sandboxes,
|
|
60
89
|
},
|
|
90
|
+
...(execution ? { execution } : {}),
|
|
91
|
+
...(streams ? { streams } : {}),
|
|
61
92
|
};
|
|
62
93
|
}
|
|
63
94
|
/**
|
|
64
95
|
* Insert-if-absent Tenant configuration seed (A18).
|
|
65
96
|
* Response lists field names only; never secret values.
|
|
66
97
|
*/
|
|
67
|
-
export function seedTenantConfig(ctx, body) {
|
|
98
|
+
export async function seedTenantConfig(ctx, body) {
|
|
68
99
|
const applied = [];
|
|
69
100
|
const kept = [];
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
101
|
+
const backend = body.sandbox?.backend;
|
|
102
|
+
if (backend) {
|
|
103
|
+
const inserted = await ctx.store.tx(async (t) => {
|
|
104
|
+
if ((await t.getSetting("sandbox.backend")) !== undefined)
|
|
105
|
+
return false;
|
|
106
|
+
await t.putSetting("sandbox.backend", backend);
|
|
107
|
+
return true;
|
|
108
|
+
});
|
|
109
|
+
(inserted ? applied : kept).push("sandbox.backend");
|
|
110
|
+
}
|
|
111
|
+
if (body.fixtureModel) {
|
|
112
|
+
const inserted = await ctx.store.tx((t) => seedFixtureModel(t));
|
|
113
|
+
(inserted ? applied : kept).push(FIXTURE_MODEL_SETTING);
|
|
79
114
|
}
|
|
80
115
|
if (body.model) {
|
|
81
|
-
const current = ctx.vault.getHostModel();
|
|
116
|
+
const current = await ctx.vault.getHostModel();
|
|
82
117
|
if (current.configured) {
|
|
83
118
|
kept.push("model");
|
|
84
119
|
}
|
|
85
120
|
else {
|
|
86
|
-
ctx.vault.putHostModel({
|
|
121
|
+
await ctx.vault.putHostModel({
|
|
87
122
|
requestId: body.requestId,
|
|
88
123
|
idempotencyKey: `seed-model:${body.requestId}`,
|
|
89
124
|
...body.model,
|