@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.
Files changed (190) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +53 -51
  3. package/dist/adapters/execution/restate.d.ts +130 -0
  4. package/dist/adapters/execution/restate.js +442 -0
  5. package/dist/adapters/sandbox/virtual.js +1 -1
  6. package/dist/adapters/streams/s2.d.ts +18 -0
  7. package/dist/adapters/streams/s2.js +361 -0
  8. package/dist/core/executors.d.ts +4 -1
  9. package/dist/core/executors.js +20 -1
  10. package/dist/core/flow-host.d.ts +93 -132
  11. package/dist/core/flow-host.js +282 -547
  12. package/dist/core/sandbox-routes.d.ts +8 -3
  13. package/dist/core/sandbox-routes.js +10 -10
  14. package/dist/execution/memory.d.ts +54 -0
  15. package/dist/execution/memory.js +217 -0
  16. package/dist/execution/types.d.ts +125 -0
  17. package/dist/execution/types.js +52 -0
  18. package/dist/host/config-for.d.ts +2 -1
  19. package/dist/host/config-for.js +3 -21
  20. package/dist/host/config.d.ts +0 -8
  21. package/dist/host/create-host.d.ts +36 -1
  22. package/dist/host/create-host.js +48 -24
  23. package/dist/host/execution.d.ts +61 -0
  24. package/dist/host/execution.js +69 -0
  25. package/dist/host/http.d.ts +4 -0
  26. package/dist/host/http.js +6 -0
  27. package/dist/host/index.d.ts +3 -1
  28. package/dist/host/index.js +2 -0
  29. package/dist/host/main.js +106 -47
  30. package/dist/host/stack-config.d.ts +72 -0
  31. package/dist/host/stack-config.js +193 -0
  32. package/dist/index.d.ts +1 -1
  33. package/dist/infra/database.d.ts +16 -0
  34. package/dist/infra/database.js +31 -0
  35. package/dist/infra/execution.d.ts +39 -0
  36. package/dist/infra/execution.js +68 -0
  37. package/dist/infra/index.d.ts +42 -0
  38. package/dist/infra/index.js +56 -0
  39. package/dist/infra/readiness.d.ts +33 -0
  40. package/dist/infra/readiness.js +53 -0
  41. package/dist/infra/streams.d.ts +17 -0
  42. package/dist/infra/streams.js +33 -0
  43. package/dist/infra/workers.d.ts +39 -0
  44. package/dist/infra/workers.js +31 -0
  45. package/dist/model/pi-model.d.ts +2 -2
  46. package/dist/model/pi-model.js +5 -5
  47. package/dist/sandbox/manager.d.ts +26 -8
  48. package/dist/sandbox/manager.js +95 -47
  49. package/dist/sandbox/select.d.ts +1 -1
  50. package/dist/sandbox/select.js +4 -5
  51. package/dist/sandbox/types.d.ts +2 -2
  52. package/dist/store/canonical.d.ts +5 -0
  53. package/dist/store/canonical.js +17 -0
  54. package/dist/store/cursor.d.ts +9 -0
  55. package/dist/store/cursor.js +18 -0
  56. package/dist/store/memory.d.ts +57 -0
  57. package/dist/store/memory.js +643 -0
  58. package/dist/store/ownership.d.ts +19 -0
  59. package/dist/store/ownership.js +33 -0
  60. package/dist/store/postgres/connect.d.ts +25 -0
  61. package/dist/store/postgres/connect.js +25 -0
  62. package/dist/store/postgres/locking.d.ts +16 -0
  63. package/dist/store/postgres/locking.js +22 -0
  64. package/dist/store/postgres/migrations/001_initial.d.ts +27 -0
  65. package/dist/store/postgres/migrations/001_initial.js +196 -0
  66. package/dist/store/postgres/migrations/index.d.ts +49 -0
  67. package/dist/store/postgres/migrations/index.js +75 -0
  68. package/dist/store/postgres/names.d.ts +11 -0
  69. package/dist/store/postgres/names.js +42 -0
  70. package/dist/store/postgres/store.d.ts +11 -0
  71. package/dist/store/postgres/store.js +790 -0
  72. package/dist/store/postgres/tenants.d.ts +90 -0
  73. package/dist/store/postgres/tenants.js +207 -0
  74. package/dist/store/types.d.ts +440 -0
  75. package/dist/store/types.js +10 -0
  76. package/dist/streams/basin.d.ts +23 -0
  77. package/dist/streams/basin.js +51 -0
  78. package/dist/streams/memory.d.ts +25 -0
  79. package/dist/streams/memory.js +124 -0
  80. package/dist/streams/relay.d.ts +37 -0
  81. package/dist/streams/relay.js +254 -0
  82. package/dist/streams/types.d.ts +115 -0
  83. package/dist/streams/types.js +86 -0
  84. package/dist/tenant/actions.d.ts +153 -0
  85. package/dist/tenant/actions.js +191 -0
  86. package/dist/tenant/advance.d.ts +15 -0
  87. package/dist/tenant/advance.js +439 -0
  88. package/dist/tenant/auth.d.ts +11 -0
  89. package/dist/tenant/auth.js +34 -0
  90. package/dist/tenant/commands.d.ts +3 -0
  91. package/dist/tenant/commands.js +384 -0
  92. package/dist/tenant/context.d.ts +140 -0
  93. package/dist/tenant/context.js +40 -0
  94. package/dist/tenant/effects.d.ts +20 -0
  95. package/dist/tenant/effects.js +539 -0
  96. package/dist/tenant/envelope.d.ts +0 -3
  97. package/dist/tenant/envelope.js +2 -34
  98. package/dist/tenant/ephemeral.d.ts +15 -4
  99. package/dist/tenant/ephemeral.js +106 -38
  100. package/dist/tenant/http.d.ts +32 -0
  101. package/dist/tenant/http.js +51 -0
  102. package/dist/tenant/live.d.ts +92 -0
  103. package/dist/tenant/live.js +290 -0
  104. package/dist/tenant/model-setting.d.ts +16 -0
  105. package/dist/tenant/model-setting.js +12 -0
  106. package/dist/tenant/module.d.ts +29 -8
  107. package/dist/tenant/module.js +162 -158
  108. package/dist/tenant/paths.d.ts +12 -8
  109. package/dist/tenant/paths.js +38 -12
  110. package/dist/tenant/principals.d.ts +9 -13
  111. package/dist/tenant/principals.js +31 -38
  112. package/dist/tenant/quarantine-error.d.ts +1 -1
  113. package/dist/tenant/quarantine-error.js +2 -8
  114. package/dist/tenant/quarantine.d.ts +0 -8
  115. package/dist/tenant/quarantine.js +8 -30
  116. package/dist/tenant/reset.d.ts +7 -6
  117. package/dist/tenant/reset.js +7 -51
  118. package/dist/tenant/routes-tenant.d.ts +12 -0
  119. package/dist/tenant/routes-tenant.js +126 -0
  120. package/dist/tenant/routes.d.ts +9 -0
  121. package/dist/tenant/routes.js +142 -0
  122. package/dist/tenant/runtime.d.ts +59 -58
  123. package/dist/tenant/runtime.js +243 -2201
  124. package/dist/tenant/scheduler.d.ts +25 -0
  125. package/dist/tenant/scheduler.js +84 -0
  126. package/dist/tenant/session.d.ts +60 -0
  127. package/dist/tenant/session.js +94 -0
  128. package/dist/tenant/sessions.d.ts +34 -0
  129. package/dist/tenant/sessions.js +149 -0
  130. package/dist/tenant/status.d.ts +13 -4
  131. package/dist/tenant/status.js +76 -41
  132. package/dist/tenant/store-memory.d.ts +9 -12
  133. package/dist/tenant/store-memory.js +79 -68
  134. package/dist/tenant/store-pg.d.ts +15 -0
  135. package/dist/tenant/store-pg.js +140 -0
  136. package/dist/tenant/streams.d.ts +126 -0
  137. package/dist/tenant/streams.js +419 -0
  138. package/dist/tenant/sweep.d.ts +15 -0
  139. package/dist/tenant/sweep.js +122 -0
  140. package/dist/tenant/types.d.ts +95 -15
  141. package/dist/tenant/types.js +23 -1
  142. package/dist/tenant/worker.d.ts +84 -0
  143. package/dist/tenant/worker.js +189 -0
  144. package/dist/vault/service.d.ts +37 -31
  145. package/dist/vault/service.js +439 -359
  146. package/dist/version.d.ts +1 -1
  147. package/dist/version.js +1 -1
  148. package/package.json +13 -13
  149. package/dist/adapters/sandbox/microsandbox.d.ts +0 -2
  150. package/dist/adapters/sandbox/microsandbox.js +0 -238
  151. package/dist/core/store.d.ts +0 -32
  152. package/dist/core/store.js +0 -143
  153. package/dist/launcher/commands.d.ts +0 -24
  154. package/dist/launcher/commands.js +0 -260
  155. package/dist/launcher/ensure-config.d.ts +0 -16
  156. package/dist/launcher/ensure-config.js +0 -79
  157. package/dist/launcher/errors.d.ts +0 -12
  158. package/dist/launcher/errors.js +0 -22
  159. package/dist/launcher/host-config.d.ts +0 -17
  160. package/dist/launcher/host-config.js +0 -64
  161. package/dist/launcher/host-state.d.ts +0 -17
  162. package/dist/launcher/host-state.js +0 -61
  163. package/dist/launcher/lifecycle.d.ts +0 -66
  164. package/dist/launcher/lifecycle.js +0 -407
  165. package/dist/launcher/locks.d.ts +0 -26
  166. package/dist/launcher/locks.js +0 -117
  167. package/dist/launcher/logs.d.ts +0 -18
  168. package/dist/launcher/logs.js +0 -117
  169. package/dist/launcher/main.d.ts +0 -2
  170. package/dist/launcher/main.js +0 -66
  171. package/dist/launcher/output.d.ts +0 -11
  172. package/dist/launcher/output.js +0 -68
  173. package/dist/launcher/paths.d.ts +0 -20
  174. package/dist/launcher/paths.js +0 -36
  175. package/dist/launcher/probe.d.ts +0 -23
  176. package/dist/launcher/probe.js +0 -72
  177. package/dist/launcher/protocol.d.ts +0 -62
  178. package/dist/launcher/protocol.js +0 -1
  179. package/dist/launcher/schema-guard.d.ts +0 -6
  180. package/dist/launcher/schema-guard.js +0 -36
  181. package/dist/launcher/spawn.d.ts +0 -22
  182. package/dist/launcher/spawn.js +0 -85
  183. package/dist/launcher/status.d.ts +0 -6
  184. package/dist/launcher/status.js +0 -65
  185. package/dist/tenant/migration.d.ts +0 -16
  186. package/dist/tenant/migration.js +0 -116
  187. package/dist/tenant/schema.d.ts +0 -23
  188. package/dist/tenant/schema.js +0 -151
  189. package/dist/tenant/store-fs.d.ts +0 -16
  190. 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
+ }
@@ -1,20 +1,29 @@
1
1
  import type { SeedTenantConfigRequest, SeedTenantConfigResponse, TenantStatus } from "@nylorun/core/contracts";
2
- import type { Store } from "../core/store.js";
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: 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: Store;
35
+ store: SessionStore;
27
36
  vault: VaultService;
28
- }, body: SeedTenantConfigRequest): SeedTenantConfigResponse;
37
+ }, body: SeedTenantConfigRequest): Promise<SeedTenantConfigResponse>;
@@ -1,28 +1,57 @@
1
- import { readTenantSetting, schemaVersionOf, TENANT_SCHEMA_VERSION, writeTenantSetting, } from "./schema.js";
2
- /** Build `TenantStatusSchema` with secrets redacted (A14). */
3
- export async function buildTenantStatus(ctx) {
4
- const sessions = ctx.store.all("sessions");
5
- const runningSessions = sessions.filter((s) => s.status === "running" || s.status === "runnable").length;
6
- const pendingActions = ctx.store
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
- ctx.store.db.prepare("SELECT 1").get();
15
- sqlite = true;
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
- catch {
18
- sqlite = false;
37
+ finally {
38
+ clearTimeout(timer);
19
39
  }
20
- const schemaOk = schemaVersionOf(ctx.store.db) === TENANT_SCHEMA_VERSION;
21
- const modelView = ctx.vault.getHostModel();
22
- const sandboxReport = await ctx.sandbox.report();
23
- const retained = ctx.store.all("sandboxes").length;
24
- const definitionIds = new Set(ctx.store
25
- .all("definitions")
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
- sqlite,
72
+ store: health.schemaVersion > 0,
44
73
  scheduler: !ctx.closing,
45
74
  model: ctx.modelConfigured || modelView.configured,
46
75
  executors: true,
47
- schema: schemaOk,
76
+ schema: health.ok,
48
77
  },
49
78
  model: modelView,
50
79
  agents,
51
80
  counts: {
52
- sessions: sessions.length,
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
- if (body.sandbox?.backend) {
71
- const existing = readTenantSetting(ctx.store.db, "sandbox.backend");
72
- if (existing === undefined) {
73
- writeTenantSetting(ctx.store.db, "sandbox.backend", body.sandbox.backend);
74
- applied.push("sandbox.backend");
75
- }
76
- else {
77
- kept.push("sandbox.backend");
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,