@nylorun/runtime 0.13.0-beta → 0.15.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. package/CHANGELOG.md +434 -0
  2. package/README.md +137 -41
  3. package/dist/adapters/execution/restate.d.ts +7 -2
  4. package/dist/adapters/execution/restate.js +40 -10
  5. package/dist/adapters/replication/pgoutput.d.ts +21 -0
  6. package/dist/adapters/replication/pgoutput.js +170 -0
  7. package/dist/adapters/streams/s2.js +37 -20
  8. package/dist/admin-openapi.json +570 -0
  9. package/dist/api/a2a/card.d.ts +15 -0
  10. package/dist/api/a2a/card.js +24 -0
  11. package/dist/api/a2a/endpoint.d.ts +8 -0
  12. package/dist/api/a2a/endpoint.js +81 -0
  13. package/dist/api/a2a/protocol.d.ts +144 -0
  14. package/dist/api/a2a/protocol.js +188 -0
  15. package/dist/api/a2a/routes.d.ts +19 -0
  16. package/dist/api/a2a/routes.js +289 -0
  17. package/dist/api/a2a/tasks.d.ts +50 -0
  18. package/dist/api/a2a/tasks.js +186 -0
  19. package/dist/api/ag-ui/endpoint.d.ts +8 -0
  20. package/dist/api/ag-ui/endpoint.js +126 -0
  21. package/dist/api/ag-ui/history.d.ts +7 -0
  22. package/dist/api/ag-ui/history.js +60 -0
  23. package/dist/api/ag-ui/routes.d.ts +32 -0
  24. package/dist/api/ag-ui/routes.js +305 -0
  25. package/dist/api/ag-ui/session-id.d.ts +1 -0
  26. package/dist/api/ag-ui/session-id.js +12 -0
  27. package/dist/api/ag-ui/sse.d.ts +9 -0
  28. package/dist/api/ag-ui/sse.js +7 -0
  29. package/dist/api/ag-ui/translate.d.ts +38 -0
  30. package/dist/api/ag-ui/translate.js +222 -0
  31. package/dist/api/components.d.ts +8065 -0
  32. package/dist/api/components.js +82 -0
  33. package/dist/api/gate/keys.d.ts +14 -0
  34. package/dist/api/gate/keys.js +59 -0
  35. package/dist/api/gate/routes.d.ts +34 -0
  36. package/dist/api/gate/routes.js +235 -0
  37. package/dist/api/http/app.d.ts +18 -0
  38. package/dist/api/http/app.js +58 -0
  39. package/dist/api/http/body.d.ts +4 -0
  40. package/dist/api/http/body.js +37 -0
  41. package/dist/api/http/define.d.ts +39 -0
  42. package/dist/api/http/define.js +122 -0
  43. package/dist/api/http/respond.d.ts +9 -0
  44. package/dist/api/http/respond.js +44 -0
  45. package/dist/api/http/routes/access.d.ts +24 -0
  46. package/dist/api/http/routes/access.js +266 -0
  47. package/dist/api/http/routes/actions.d.ts +8 -0
  48. package/dist/api/http/routes/actions.js +76 -0
  49. package/dist/api/http/routes/endpoints.d.ts +7 -0
  50. package/dist/api/http/routes/endpoints.js +100 -0
  51. package/dist/api/http/routes/sessions.d.ts +3 -0
  52. package/dist/api/http/routes/sessions.js +205 -0
  53. package/dist/api/http/routes/tenant.d.ts +11 -0
  54. package/dist/api/http/routes/tenant.js +244 -0
  55. package/dist/api/http/routes/vaults.d.ts +8 -0
  56. package/dist/api/http/routes/vaults.js +167 -0
  57. package/dist/api/openapi.d.ts +19 -0
  58. package/dist/api/openapi.js +137 -0
  59. package/dist/api/route.d.ts +12 -0
  60. package/dist/api/route.js +4 -0
  61. package/dist/core/bearer.d.ts +8 -0
  62. package/dist/core/bearer.js +11 -0
  63. package/dist/core/flow-host.d.ts +7 -15
  64. package/dist/core/flow-host.js +16 -41
  65. package/dist/core/sandbox-routes.d.ts +12 -7
  66. package/dist/core/sandbox-routes.js +24 -25
  67. package/dist/execution/memory.d.ts +8 -5
  68. package/dist/execution/memory.js +33 -13
  69. package/dist/execution/types.d.ts +38 -6
  70. package/dist/execution/types.js +12 -3
  71. package/dist/gates/contract.d.ts +51 -0
  72. package/dist/gates/contract.js +37 -0
  73. package/dist/gates/handler.d.ts +21 -0
  74. package/dist/gates/handler.js +55 -0
  75. package/dist/gates/http-client.d.ts +12 -0
  76. package/dist/gates/http-client.js +160 -0
  77. package/dist/gates/in-process.d.ts +32 -0
  78. package/dist/gates/in-process.js +42 -0
  79. package/dist/gates/inflight.d.ts +39 -0
  80. package/dist/gates/inflight.js +70 -0
  81. package/dist/gates/mcp-handler.d.ts +35 -0
  82. package/dist/gates/mcp-handler.js +142 -0
  83. package/dist/gates/meter.d.ts +24 -0
  84. package/dist/gates/meter.js +165 -0
  85. package/dist/gates/model-gate.d.ts +45 -0
  86. package/dist/gates/model-gate.js +1 -0
  87. package/dist/gates/tenant-vaults.d.ts +64 -0
  88. package/dist/gates/tenant-vaults.js +94 -0
  89. package/dist/gates/tool-calls.d.ts +51 -0
  90. package/dist/gates/tool-calls.js +64 -0
  91. package/dist/gates/tool-client.d.ts +12 -0
  92. package/dist/gates/tool-client.js +201 -0
  93. package/dist/gates/tool-contract.d.ts +116 -0
  94. package/dist/gates/tool-contract.js +64 -0
  95. package/dist/gates/tool-gate.d.ts +45 -0
  96. package/dist/gates/tool-gate.js +10 -0
  97. package/dist/host/admin-api.d.ts +23 -0
  98. package/dist/host/admin-api.js +74 -0
  99. package/dist/host/app.d.ts +34 -0
  100. package/dist/host/app.js +181 -0
  101. package/dist/host/config-for.d.ts +3 -1
  102. package/dist/host/config-for.js +3 -2
  103. package/dist/host/config.d.ts +11 -0
  104. package/dist/host/cors.d.ts +27 -0
  105. package/dist/host/cors.js +64 -0
  106. package/dist/host/create-host.d.ts +36 -8
  107. package/dist/host/create-host.js +161 -330
  108. package/dist/host/execution.d.ts +20 -18
  109. package/dist/host/execution.js +2 -2
  110. package/dist/host/gates.d.ts +58 -0
  111. package/dist/host/gates.js +127 -0
  112. package/dist/host/http.d.ts +20 -4
  113. package/dist/host/http.js +81 -23
  114. package/dist/host/index.d.ts +1 -1
  115. package/dist/host/main.js +189 -58
  116. package/dist/host/stack-config.d.ts +91 -19
  117. package/dist/host/stack-config.js +246 -17
  118. package/dist/infra/database.d.ts +2 -2
  119. package/dist/infra/execution.d.ts +5 -5
  120. package/dist/infra/execution.js +8 -6
  121. package/dist/infra/index.d.ts +1 -1
  122. package/dist/infra/workers.d.ts +17 -17
  123. package/dist/infra/workers.js +9 -9
  124. package/dist/keys/client.d.ts +9 -0
  125. package/dist/keys/client.js +70 -0
  126. package/dist/keys/contract.d.ts +24 -0
  127. package/dist/keys/contract.js +12 -0
  128. package/dist/keys/keys.d.ts +41 -0
  129. package/dist/keys/keys.js +50 -0
  130. package/dist/mcp/connect.d.ts +43 -3
  131. package/dist/mcp/connect.js +12 -5
  132. package/dist/mcp/pool.d.ts +39 -3
  133. package/dist/mcp/pool.js +74 -11
  134. package/dist/model/catalog.d.ts +2 -10
  135. package/dist/model/classify.d.ts +13 -0
  136. package/dist/model/classify.js +61 -0
  137. package/dist/model/models.d.ts +10 -1
  138. package/dist/model/models.js +14 -6
  139. package/dist/model/pi-model.d.ts +17 -2
  140. package/dist/model/pi-model.js +366 -222
  141. package/dist/openapi.json +25889 -0
  142. package/dist/record/append.d.ts +51 -0
  143. package/dist/record/append.js +20 -0
  144. package/dist/record/envelope.d.ts +19 -0
  145. package/dist/record/envelope.js +49 -0
  146. package/dist/record/index.d.ts +11 -0
  147. package/dist/record/index.js +11 -0
  148. package/dist/sandbox/manager.d.ts +13 -1
  149. package/dist/sandbox/manager.js +8 -11
  150. package/dist/sandbox/resolve.d.ts +33 -0
  151. package/dist/sandbox/resolve.js +81 -0
  152. package/dist/sandbox/session-sandbox.d.ts +11 -0
  153. package/dist/sandbox/session-sandbox.js +51 -0
  154. package/dist/sandbox/share.d.ts +14 -2
  155. package/dist/sandbox/share.js +33 -3
  156. package/dist/sandbox/tenant-config.d.ts +37 -0
  157. package/dist/sandbox/tenant-config.js +51 -0
  158. package/dist/sandbox/tools.d.ts +7 -9
  159. package/dist/sandbox/tools.js +11 -3
  160. package/dist/sandbox/types.d.ts +8 -2
  161. package/dist/store/postgres/connect.d.ts +25 -9
  162. package/dist/store/postgres/connect.js +27 -10
  163. package/dist/store/postgres/db.d.ts +16 -0
  164. package/dist/store/postgres/db.js +28 -0
  165. package/dist/store/postgres/drizzle/0000_foundation.sql +22 -0
  166. package/dist/store/postgres/drizzle/0001_baseline.sql +255 -0
  167. package/dist/store/postgres/drizzle/0002_stream_relay.sql +4 -0
  168. package/dist/store/postgres/drizzle/0003_tool_crossings.sql +9 -0
  169. package/dist/store/postgres/drizzle/meta/_journal.json +34 -0
  170. package/dist/store/postgres/migrate.d.ts +68 -0
  171. package/dist/store/postgres/migrate.js +183 -0
  172. package/dist/store/postgres/record-writer.d.ts +4 -0
  173. package/dist/store/postgres/record-writer.js +34 -0
  174. package/dist/store/postgres/record.d.ts +5 -0
  175. package/dist/store/postgres/record.js +60 -0
  176. package/dist/store/postgres/schema.d.ts +3096 -0
  177. package/dist/store/postgres/schema.js +363 -0
  178. package/dist/store/postgres/store.d.ts +2 -4
  179. package/dist/store/postgres/store.js +590 -434
  180. package/dist/store/postgres/tenant.d.ts +56 -0
  181. package/dist/store/postgres/tenant.js +154 -0
  182. package/dist/store/types.d.ts +183 -132
  183. package/dist/store/types.js +0 -1
  184. package/dist/streams/basin.d.ts +18 -1
  185. package/dist/streams/basin.js +34 -2
  186. package/dist/streams/control.d.ts +18 -0
  187. package/dist/streams/control.js +33 -0
  188. package/dist/streams/memory.d.ts +3 -1
  189. package/dist/streams/memory.js +14 -8
  190. package/dist/streams/relay/core.d.ts +35 -0
  191. package/dist/streams/relay/core.js +366 -0
  192. package/dist/streams/relay/types.d.ts +78 -0
  193. package/dist/streams/relay/types.js +8 -0
  194. package/dist/streams/types.d.ts +71 -36
  195. package/dist/streams/types.js +18 -53
  196. package/dist/tenant/access-policy.d.ts +30 -0
  197. package/dist/tenant/access-policy.js +52 -0
  198. package/dist/tenant/actions.d.ts +10 -149
  199. package/dist/tenant/actions.js +5 -167
  200. package/dist/tenant/advance.d.ts +7 -2
  201. package/dist/tenant/advance.js +125 -14
  202. package/dist/tenant/auth.d.ts +19 -17
  203. package/dist/tenant/auth.js +90 -93
  204. package/dist/tenant/browser.d.ts +15 -0
  205. package/dist/tenant/browser.js +39 -0
  206. package/dist/tenant/cause.d.ts +25 -0
  207. package/dist/tenant/cause.js +45 -0
  208. package/dist/tenant/commands.d.ts +18 -2
  209. package/dist/tenant/commands.js +111 -115
  210. package/dist/tenant/context.d.ts +101 -21
  211. package/dist/tenant/context.js +14 -10
  212. package/dist/tenant/delivery-token.d.ts +41 -0
  213. package/dist/tenant/delivery-token.js +104 -0
  214. package/dist/tenant/delivery.d.ts +62 -0
  215. package/dist/tenant/delivery.js +481 -0
  216. package/dist/tenant/effects.d.ts +20 -1
  217. package/dist/tenant/effects.js +159 -42
  218. package/dist/tenant/endpoints.d.ts +67 -0
  219. package/dist/tenant/endpoints.js +90 -0
  220. package/dist/tenant/ephemeral.d.ts +22 -7
  221. package/dist/tenant/ephemeral.js +83 -125
  222. package/dist/tenant/history.d.ts +64 -0
  223. package/dist/tenant/history.js +120 -0
  224. package/dist/tenant/http.d.ts +8 -9
  225. package/dist/tenant/http.js +13 -27
  226. package/dist/tenant/jwt.d.ts +16 -0
  227. package/dist/tenant/jwt.js +50 -0
  228. package/dist/tenant/model-setting.d.ts +2 -2
  229. package/dist/tenant/module.d.ts +17 -28
  230. package/dist/tenant/module.js +133 -279
  231. package/dist/tenant/outbound.d.ts +44 -0
  232. package/dist/tenant/outbound.js +127 -0
  233. package/dist/tenant/paths.d.ts +11 -16
  234. package/dist/tenant/paths.js +24 -68
  235. package/dist/tenant/pool.d.ts +1 -3
  236. package/dist/tenant/pool.js +1 -19
  237. package/dist/tenant/principals.d.ts +23 -11
  238. package/dist/tenant/principals.js +61 -49
  239. package/dist/tenant/reset.d.ts +0 -4
  240. package/dist/tenant/reset.js +0 -5
  241. package/dist/tenant/runtime.d.ts +28 -20
  242. package/dist/tenant/runtime.js +95 -64
  243. package/dist/tenant/scheduler.d.ts +10 -3
  244. package/dist/tenant/scheduler.js +9 -5
  245. package/dist/tenant/session-streams.d.ts +127 -0
  246. package/dist/tenant/session-streams.js +404 -0
  247. package/dist/tenant/sessions.d.ts +28 -4
  248. package/dist/tenant/sessions.js +118 -24
  249. package/dist/tenant/signing-keys.d.ts +55 -0
  250. package/dist/tenant/signing-keys.js +172 -0
  251. package/dist/tenant/slim.d.ts +8 -0
  252. package/dist/tenant/slim.js +21 -0
  253. package/dist/tenant/status.d.ts +3 -3
  254. package/dist/tenant/status.js +20 -13
  255. package/dist/tenant/store-pg.d.ts +9 -6
  256. package/dist/tenant/store-pg.js +60 -125
  257. package/dist/tenant/streams.d.ts +62 -85
  258. package/dist/tenant/streams.js +143 -226
  259. package/dist/tenant/subject-limits.d.ts +16 -0
  260. package/dist/tenant/subject-limits.js +45 -0
  261. package/dist/tenant/sweep.d.ts +1 -10
  262. package/dist/tenant/sweep.js +29 -69
  263. package/dist/tenant/tokens.d.ts +14 -0
  264. package/dist/tenant/tokens.js +180 -0
  265. package/dist/tenant/transcript.d.ts +48 -5
  266. package/dist/tenant/transcript.js +58 -1
  267. package/dist/tenant/types.d.ts +69 -98
  268. package/dist/tenant/types.js +4 -15
  269. package/dist/tenant/worker.d.ts +3 -1
  270. package/dist/tenant/worker.js +13 -7
  271. package/dist/vault/host-model.d.ts +31 -0
  272. package/dist/vault/host-model.js +57 -0
  273. package/dist/vault/service.d.ts +44 -17
  274. package/dist/vault/service.js +126 -121
  275. package/dist/version.d.ts +1 -1
  276. package/dist/version.js +1 -1
  277. package/package.json +25 -9
  278. package/dist/core/executors.d.ts +0 -44
  279. package/dist/core/executors.js +0 -95
  280. package/dist/store/memory.d.ts +0 -57
  281. package/dist/store/memory.js +0 -645
  282. package/dist/store/postgres/migrations/001_initial.d.ts +0 -27
  283. package/dist/store/postgres/migrations/001_initial.js +0 -196
  284. package/dist/store/postgres/migrations/002_session_owner.d.ts +0 -6
  285. package/dist/store/postgres/migrations/002_session_owner.js +0 -13
  286. package/dist/store/postgres/migrations/index.d.ts +0 -49
  287. package/dist/store/postgres/migrations/index.js +0 -76
  288. package/dist/store/postgres/names.d.ts +0 -11
  289. package/dist/store/postgres/names.js +0 -42
  290. package/dist/store/postgres/tenants.d.ts +0 -90
  291. package/dist/store/postgres/tenants.js +0 -214
  292. package/dist/streams/relay.d.ts +0 -37
  293. package/dist/streams/relay.js +0 -254
  294. package/dist/tenant/envelope.d.ts +0 -7
  295. package/dist/tenant/envelope.js +0 -22
  296. package/dist/tenant/live.d.ts +0 -92
  297. package/dist/tenant/live.js +0 -290
  298. package/dist/tenant/quarantine-error.d.ts +0 -6
  299. package/dist/tenant/quarantine-error.js +0 -9
  300. package/dist/tenant/quarantine.d.ts +0 -27
  301. package/dist/tenant/quarantine.js +0 -72
  302. package/dist/tenant/routes-tenant.d.ts +0 -12
  303. package/dist/tenant/routes-tenant.js +0 -137
  304. package/dist/tenant/routes.d.ts +0 -9
  305. package/dist/tenant/routes.js +0 -151
  306. package/dist/tenant/store-memory.d.ts +0 -16
  307. package/dist/tenant/store-memory.js +0 -111
  308. /package/dist/{store → record}/cursor.d.ts +0 -0
  309. /package/dist/{store → record}/cursor.js +0 -0
@@ -1,290 +0,0 @@
1
- import { decodeCursor, encodeCursor } from "../store/cursor.js";
2
- import { streamOfSession, } from "../streams/types.js";
3
- import { fail } from "./http.js";
4
- export function createLiveHub() {
5
- return { wiring: undefined, feeds: new Map(), executorStreams: new Map() };
6
- }
7
- const WORK_AVAILABLE = 'event: work_available\ndata: {"type":"work_available"}\n\n';
8
- const RETRY_MIN_MS = 100;
9
- const RETRY_MAX_MS = 2000;
10
- function frame(event) {
11
- return `id: ${event.cursor}\nevent: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`;
12
- }
13
- export function send(response, data) {
14
- if (!response.write(data))
15
- response.destroy();
16
- }
17
- /** Start an SSE response with keepalives; `onClose` runs once the client goes away. */
18
- export function openSse(request, response, onClose) {
19
- response.writeHead(200, {
20
- "content-type": "text/event-stream",
21
- "cache-control": "no-cache",
22
- connection: "keep-alive",
23
- "x-accel-buffering": "no",
24
- });
25
- response.flushHeaders();
26
- const timer = setInterval(() => send(response, ": keepalive\n\n"), 15000);
27
- timer.unref();
28
- request.on("close", () => {
29
- clearInterval(timer);
30
- onClose();
31
- });
32
- }
33
- /** The resume cursor: `?cursor=` wins over `Last-Event-ID`. */
34
- export function requestCursor(request, url) {
35
- return (url.searchParams.get("cursor") ??
36
- (typeof request.headers["last-event-id"] === "string"
37
- ? request.headers["last-event-id"]
38
- : undefined));
39
- }
40
- /** The first sequence to read: after the cursor's, or the session's first event. */
41
- function startSeq(sessionId, cursor) {
42
- return cursor ? decodeCursor(sessionId, cursor) + 1 : 0;
43
- }
44
- /** `agent` keeps only events of one agent used as a tool, by delegationId or path. */
45
- function belongsTo(event, agent) {
46
- const ref = event.payload?.agent;
47
- return ref?.delegationId === agent || ref?.path === agent;
48
- }
49
- /** The stream of the session's current incarnation, or undefined when the session is gone. */
50
- async function currentStream(ctx, sessionId) {
51
- const session = await ctx.store.tx((t) => t.get("sessions", sessionId));
52
- return session && streamOfSession(session);
53
- }
54
- function streamsOf(ctx) {
55
- return (ctx.live.wiring?.streams ?? fail(503, "Session streams are unavailable"));
56
- }
57
- /**
58
- * `GET /v1/sessions/:id/items`: the session stream after the cursor, up to its tail when the
59
- * read starts. The cursor returned is the last record read (the request's cursor when none
60
- * was), whatever the `agent` filter kept.
61
- */
62
- export async function readHistory(ctx, sessionId, cursor, agent) {
63
- const from = startSeq(sessionId, cursor);
64
- const streams = streamsOf(ctx);
65
- const stream = (await currentStream(ctx, sessionId)) ?? fail(404, "Session not found");
66
- const items = [];
67
- let last;
68
- try {
69
- for await (const record of streams.read(ctx.config.tenantId, stream, from, { follow: false })) {
70
- last = record.seq;
71
- if (agent === undefined || belongsTo(record.body, agent))
72
- items.push(record.body);
73
- }
74
- }
75
- catch (error) {
76
- ctx.config.logger.warn("session history read failed", {
77
- sessionId,
78
- message: error instanceof Error ? error.message : String(error),
79
- });
80
- fail(503, "Session history is unavailable");
81
- }
82
- return {
83
- items,
84
- cursor: last !== undefined ? encodeCursor(sessionId, last) : cursor ?? null,
85
- };
86
- }
87
- /**
88
- * `GET /v1/sessions/:id/events`: joins the session's shared read at the client's cursor and
89
- * follows it until the client leaves, the session is reset or deleted, or the Tenant closes.
90
- */
91
- export async function streamSessionEvents(ctx, request, response, sessionId, cursor) {
92
- const from = startSeq(sessionId, cursor);
93
- streamsOf(ctx);
94
- const stream = (await currentStream(ctx, sessionId)) ?? fail(404, "Session not found");
95
- const hub = ctx.live;
96
- const observer = { response, next: from };
97
- let feed = hub.feeds.get(sessionId);
98
- if (feed && feed.stream !== stream) {
99
- // The session was created again since that feed started: it follows an abandoned stream.
100
- endFeed(hub, feed);
101
- feed = undefined;
102
- }
103
- if (!feed) {
104
- feed = {
105
- sessionId,
106
- stream,
107
- next: from,
108
- observers: new Set(),
109
- read: new AbortController(),
110
- };
111
- hub.feeds.set(sessionId, feed);
112
- runFeed(ctx, feed);
113
- }
114
- else if (from < feed.next) {
115
- // Behind the shared read: restart it here; observers ahead skip what they have.
116
- feed.read.abort();
117
- feed.read = new AbortController();
118
- feed.next = from;
119
- runFeed(ctx, feed);
120
- }
121
- const joined = feed;
122
- joined.observers.add(observer);
123
- openSse(request, response, () => leave(hub, joined, observer));
124
- }
125
- function leave(hub, feed, observer) {
126
- feed.observers.delete(observer);
127
- if (feed.observers.size > 0)
128
- return;
129
- feed.read.abort();
130
- if (hub.feeds.get(feed.sessionId) === feed)
131
- hub.feeds.delete(feed.sessionId);
132
- }
133
- function deliver(observer, record) {
134
- if (record.seq < observer.next)
135
- return;
136
- observer.next = record.seq + 1;
137
- send(observer.response, frame(record.body));
138
- }
139
- /**
140
- * Follows the feed's stream from `feed.next` for the current `feed.read`, retrying failed
141
- * reads from where it stopped while the session still has that stream. A read that ends by
142
- * itself (the stream's Tenant is gone or the streams closed), or a session that is gone or
143
- * has a new incarnation, ends the observers.
144
- */
145
- function runFeed(ctx, feed) {
146
- const read = feed.read;
147
- const signal = read.signal;
148
- void (async () => {
149
- let delay = RETRY_MIN_MS;
150
- while (!signal.aborted) {
151
- const streams = ctx.live.wiring?.streams;
152
- if (!streams)
153
- break;
154
- try {
155
- for await (const record of streams.read(ctx.config.tenantId, feed.stream, feed.next, { signal })) {
156
- // A restart replaced this read: the new one owns `feed.next`.
157
- if (signal.aborted || feed.read !== read)
158
- return;
159
- if (record.seq < feed.next)
160
- continue;
161
- feed.next = record.seq + 1;
162
- delay = RETRY_MIN_MS;
163
- for (const observer of feed.observers)
164
- deliver(observer, record);
165
- }
166
- if (signal.aborted)
167
- return;
168
- break;
169
- }
170
- catch (error) {
171
- if (signal.aborted)
172
- return;
173
- ctx.config.logger.warn("session stream read failed; retrying", {
174
- sessionId: feed.sessionId,
175
- message: error instanceof Error ? error.message : String(error),
176
- });
177
- }
178
- if (!(await stillCurrent(ctx, feed)))
179
- break;
180
- await sleep(delay, signal);
181
- delay = Math.min(delay * 2, RETRY_MAX_MS);
182
- }
183
- if (signal.aborted || feed.read !== read)
184
- return;
185
- endFeed(ctx.live, feed);
186
- })();
187
- }
188
- /** False when the feed's session is gone or has a new incarnation. True when unsure. */
189
- async function stillCurrent(ctx, feed) {
190
- try {
191
- return (await currentStream(ctx, feed.sessionId)) === feed.stream;
192
- }
193
- catch {
194
- return true;
195
- }
196
- }
197
- /**
198
- * Ends every feed in this process whose session is gone or has a new incarnation. Run on a
199
- * `sessions.reset` signal and periodically, so feeds on nodes other than the one that reset
200
- * the Tenant end too.
201
- */
202
- export async function checkFeeds(ctx) {
203
- const feeds = [...ctx.live.feeds.values()];
204
- if (feeds.length === 0)
205
- return;
206
- const current = await ctx.store.tx(async (t) => {
207
- const streams = new Map();
208
- for (const feed of feeds) {
209
- const session = await t.get("sessions", feed.sessionId);
210
- streams.set(feed.sessionId, session && streamOfSession(session));
211
- }
212
- return streams;
213
- });
214
- for (const feed of feeds)
215
- if (ctx.live.feeds.get(feed.sessionId) === feed &&
216
- current.get(feed.sessionId) !== feed.stream)
217
- endFeed(ctx.live, feed);
218
- }
219
- function endFeed(hub, feed) {
220
- feed.read.abort();
221
- if (hub.feeds.get(feed.sessionId) === feed)
222
- hub.feeds.delete(feed.sessionId);
223
- for (const observer of feed.observers)
224
- observer.response.end();
225
- feed.observers.clear();
226
- }
227
- /** `GET /v1/executors/connect`: an executor's work stream, primed with one `work_available`. */
228
- export function streamExecutorWork(hub, request, response, tokenHash) {
229
- let streams = hub.executorStreams.get(tokenHash);
230
- if (!streams)
231
- hub.executorStreams.set(tokenHash, (streams = new Set()));
232
- const set = streams;
233
- set.add(response);
234
- openSse(request, response, () => {
235
- set.delete(response);
236
- if (!set.size && hub.executorStreams.get(tokenHash) === set)
237
- hub.executorStreams.delete(tokenHash);
238
- });
239
- send(response, WORK_AVAILABLE);
240
- }
241
- /** Called by the `tenant/work` reader for each signal: wake every connected executor. */
242
- export function announceWork(hub) {
243
- for (const streams of hub.executorStreams.values())
244
- for (const response of streams)
245
- send(response, WORK_AVAILABLE);
246
- }
247
- export function executorConnected(hub, tokenHash) {
248
- return (hub.executorStreams.get(tokenHash)?.size ?? 0) > 0;
249
- }
250
- export function connectedExecutorCount(hub) {
251
- return [...hub.executorStreams.values()].filter((set) => set.size > 0)
252
- .length;
253
- }
254
- /** End the streams a token owns (rotation or deregistration). */
255
- export function endExecutorStreams(hub, tokenHash) {
256
- for (const r of hub.executorStreams.get(tokenHash) ?? [])
257
- r.end();
258
- hub.executorStreams.delete(tokenHash);
259
- }
260
- /** Reset or close: end every session observer and stop every shared read. */
261
- export function clearObservers(hub) {
262
- for (const feed of [...hub.feeds.values()])
263
- endFeed(hub, feed);
264
- hub.feeds.clear();
265
- }
266
- /** Reset: end and forget every executor stream. */
267
- export function clearExecutorStreams(hub) {
268
- for (const streams of hub.executorStreams.values())
269
- for (const r of streams)
270
- r.end();
271
- hub.executorStreams.clear();
272
- }
273
- /** Close: end every executor stream, then every session observer. */
274
- export function endAllStreams(hub) {
275
- clearExecutorStreams(hub);
276
- clearObservers(hub);
277
- }
278
- export function sleep(ms, signal) {
279
- return new Promise((resolve) => {
280
- if (signal.aborted)
281
- return resolve();
282
- const done = () => {
283
- clearTimeout(timer);
284
- signal.removeEventListener("abort", done);
285
- resolve();
286
- };
287
- const timer = setTimeout(done, ms);
288
- signal.addEventListener("abort", done, { once: true });
289
- });
290
- }
@@ -1,6 +0,0 @@
1
- import type { Quarantine } from "./types.js";
2
- /** Typed quarantine raised while opening a Tenant Runtime (A7). */
3
- export declare class QuarantineError extends Error {
4
- readonly quarantine: Quarantine;
5
- constructor(code: Quarantine["code"], message: string, repair?: string);
6
- }
@@ -1,9 +0,0 @@
1
- /** Typed quarantine raised while opening a Tenant Runtime (A7). */
2
- export class QuarantineError extends Error {
3
- quarantine;
4
- constructor(code, message, repair = "nylo tenant status") {
5
- super(message);
6
- this.name = "QuarantineError";
7
- this.quarantine = { code, message, repair };
8
- }
9
- }
@@ -1,27 +0,0 @@
1
- import type { Quarantine } from "./types.js";
2
- /** Typed quarantine failure thrown by stores and migration. */
3
- export declare class QuarantineError extends Error implements Quarantine {
4
- readonly code: Quarantine["code"];
5
- readonly repair: string;
6
- constructor(fields: Quarantine);
7
- toQuarantine(): Quarantine;
8
- }
9
- export declare function isQuarantineError(error: unknown): error is QuarantineError;
10
- export declare function asQuarantine(error: unknown): Quarantine | undefined;
11
- /** CLI-facing repair strings for every quarantine code (B9). */
12
- export declare function repairFor(code: Quarantine["code"], detail?: {
13
- tenantId?: string;
14
- }): string;
15
- export declare function quarantine(code: Quarantine["code"], message: string, detail?: {
16
- tenantId?: string;
17
- }): QuarantineError;
18
- /** Conflict when create bootstrap material does not match an existing Tenant (HTTP 409). */
19
- export declare class TenantConflictError extends Error {
20
- readonly statusCode: 409;
21
- constructor(message?: string);
22
- }
23
- /** Delete refused because the Tenant still has live work (HTTP 409). */
24
- export declare class TenantBusyError extends Error {
25
- readonly statusCode: 409;
26
- constructor(message?: string);
27
- }
@@ -1,72 +0,0 @@
1
- /** Typed quarantine failure thrown by stores and migration. */
2
- export class QuarantineError extends Error {
3
- code;
4
- repair;
5
- constructor(fields) {
6
- super(fields.message);
7
- this.name = "QuarantineError";
8
- this.code = fields.code;
9
- this.repair = fields.repair;
10
- }
11
- toQuarantine() {
12
- return { code: this.code, message: this.message, repair: this.repair };
13
- }
14
- }
15
- export function isQuarantineError(error) {
16
- return error instanceof QuarantineError;
17
- }
18
- export function asQuarantine(error) {
19
- if (isQuarantineError(error))
20
- return error.toQuarantine();
21
- if (error &&
22
- typeof error === "object" &&
23
- "code" in error &&
24
- "message" in error &&
25
- "repair" in error) {
26
- return error;
27
- }
28
- return undefined;
29
- }
30
- /** CLI-facing repair strings for every quarantine code (B9). */
31
- export function repairFor(code, detail = {}) {
32
- const id = detail.tenantId ? ` ${detail.tenantId}` : "";
33
- switch (code) {
34
- case "kek-missing":
35
- return `nylo tenant status${id} — restore vault-kek for this Tenant (ciphertext cannot be opened without it)`;
36
- case "corrupt":
37
- return `nylo tenant status${id} — restore the Tenant from backup`;
38
- case "schema-too-new":
39
- return `nylo tenant status${id} — the Tenant was migrated by a newer Runtime; run that version or newer`;
40
- case "migration-failed":
41
- return `nylo tenant status${id} — inspect the Runtime log for the failed migration, fix it, then restart the Runtime`;
42
- case "envelope-invalid":
43
- return `nylo tenant status${id} — the Tenant envelope is missing or invalid; restore the Tenant from backup`;
44
- case "open-timeout":
45
- return `nylo tenant status${id} — open timed out; inspect the Runtime log and Postgres, then retry`;
46
- case "open-failed":
47
- return `nylo tenant status${id} — inspect Tenant logs and repair before the Host retries open`;
48
- }
49
- }
50
- export function quarantine(code, message, detail = {}) {
51
- return new QuarantineError({
52
- code,
53
- message,
54
- repair: repairFor(code, detail),
55
- });
56
- }
57
- /** Conflict when create bootstrap material does not match an existing Tenant (HTTP 409). */
58
- export class TenantConflictError extends Error {
59
- statusCode = 409;
60
- constructor(message = "Tenant already exists with different bootstrap material") {
61
- super(message);
62
- this.name = "TenantConflictError";
63
- }
64
- }
65
- /** Delete refused because the Tenant still has live work (HTTP 409). */
66
- export class TenantBusyError extends Error {
67
- statusCode = 409;
68
- constructor(message = "Tenant has live work; pass activeWork=drain|cancel or retry later") {
69
- super(message);
70
- this.name = "TenantBusyError";
71
- }
72
- }
@@ -1,12 +0,0 @@
1
- /**
2
- * Tenant administration and vault routes: `/v1/tenant` (status, reset, config seed, host
3
- * model and providers, sandbox report) and `/v1/vaults` (vaults and credentials).
4
- * A reset that deletes sessions abandons their streams (a session created again gets a new
5
- * incarnation) and collects them afterwards; it never deletes the Tenant's basin.
6
- *
7
- * Later waves: Wave 2 / X changes what reset clears once work leaves process memory.
8
- */
9
- import type { IncomingMessage } from "node:http";
10
- import type { AuthScope, TenantContext } from "./context.js";
11
- export declare function dispatchTenant(ctx: TenantContext, scope: AuthScope, method: string | undefined, path: string[], request: IncomingMessage): Promise<unknown>;
12
- export declare function dispatchVault(ctx: TenantContext, scope: AuthScope, method: string | undefined, path: string[], url: URL, request: IncomingMessage): Promise<unknown>;
@@ -1,137 +0,0 @@
1
- import { CreateCredentialRequestSchema, CreateVaultRequestSchema, PutHostModelRequestSchema, ResetTenantRequestSchema, RotateCredentialRequestSchema, SeedTenantConfigRequestSchema, SelectHostModelRequestSchema, } from "@nylorun/core/contracts";
2
- import { hostModelCatalog } from "../model/catalog.js";
3
- import { resetTenant } from "./reset.js";
4
- import { buildTenantStatus, seedTenantConfig } from "./status.js";
5
- import { ownerOf } from "./auth.js";
6
- import { fail, readBody } from "./http.js";
7
- import { clearExecutorStreams, clearObservers } from "./live.js";
8
- import { requestStreamCollection, sessionStreamsAbandoned, streamsStatus, } from "./streams.js";
9
- import { clearWork, drain } from "./scheduler.js";
10
- import { usesFixtureModel } from "./model-setting.js";
11
- export async function dispatchTenant(ctx, scope, method, path, request) {
12
- const { vault } = ctx;
13
- // A subject reaches only the routes `authorize` allowed its scopes.
14
- if (scope.kind === "executor") {
15
- await vault.reject(path.join("/"));
16
- fail(403, "Application credential required");
17
- }
18
- if (path.length === 2 && method === "GET")
19
- return await buildTenantStatus({
20
- envelope: ctx.envelope,
21
- config: ctx.config,
22
- store: ctx.store,
23
- registry: ctx.registry,
24
- vault,
25
- sandbox: ctx.sandbox,
26
- closing: ctx.closing || ctx.closed,
27
- modelConfigured: !ctx.useVaultModel ||
28
- (await ctx.store.tx((t) => usesFixtureModel(t))) ||
29
- (await vault.getHostModel()).configured,
30
- executorStreams: ctx.live.executorStreams,
31
- ...(ctx.stuckInvocations
32
- ? { stuckInvocations: ctx.stuckInvocations }
33
- : {}),
34
- streamsStatus: () => streamsStatus(ctx),
35
- });
36
- if (path[2] === "reset" && path.length === 3 && method === "POST") {
37
- const body = ResetTenantRequestSchema.parse(await readBody(request));
38
- await drain(ctx, body.activeWork, 30_000);
39
- // The deleted sessions' streams are abandoned: a session created again with the same id,
40
- // during or after the reset, gets a new incarnation and starts at sequence 0. The pending
41
- // collection is recorded first, so a crash before it runs leaves it to the sweep.
42
- const sessionsReset = body.scope !== "sandboxes";
43
- if (sessionsReset)
44
- await requestStreamCollection(ctx);
45
- await resetTenant({
46
- store: ctx.store,
47
- registry: ctx.registry,
48
- sandbox: ctx.sandbox,
49
- paths: ctx.config.paths,
50
- clearSessionState: () => {
51
- clearWork(ctx);
52
- clearObservers(ctx.live);
53
- },
54
- clearExecutorStreams: () => clearExecutorStreams(ctx.live),
55
- }, body.scope);
56
- if (sessionsReset)
57
- sessionStreamsAbandoned(ctx);
58
- // Reset leaves the Tenant open for new work.
59
- ctx.closing = false;
60
- return { ok: true };
61
- }
62
- if (path[2] === "config" &&
63
- path[3] === "seed" &&
64
- path.length === 4 &&
65
- method === "PUT") {
66
- const body = SeedTenantConfigRequestSchema.parse(await readBody(request));
67
- return await seedTenantConfig({ store: ctx.store, vault }, body);
68
- }
69
- if (path[2] === "models" && path.length === 3 && method === "GET")
70
- return hostModelCatalog();
71
- if (path[2] === "sandbox" && path.length === 3 && method === "GET")
72
- return ctx.sandbox.report();
73
- if (path[2] === "providers" && path.length === 3 && method === "GET")
74
- return vault.listHostProviders();
75
- if (path[2] === "model" && path.length === 3 && method === "GET")
76
- return vault.getHostModel();
77
- if (path[2] === "model" && path.length === 3 && method === "PUT")
78
- return vault.putHostModel(PutHostModelRequestSchema.parse(await readBody(request)));
79
- if (path[2] === "model" &&
80
- path[3] === "selection" &&
81
- path.length === 4 &&
82
- method === "PUT")
83
- return vault.selectHostModel(SelectHostModelRequestSchema.parse(await readBody(request)));
84
- fail(404, "Route not found");
85
- }
86
- export async function dispatchVault(ctx, scope, method, path, url, request) {
87
- const { vault } = ctx;
88
- if (scope.kind === "executor") {
89
- await vault.reject(path.join("/"));
90
- fail(403, "Application credential required");
91
- }
92
- // Acting for a subject: only the subject's own vaults, and never on another's behalf.
93
- const owner = ownerOf(scope);
94
- if (path.length === 2 && method === "POST") {
95
- const body = CreateVaultRequestSchema.parse(await readBody(request));
96
- if (owner !== undefined && body.ownerUserId !== owner)
97
- fail(403, "ownerUserId must be the subject");
98
- return vault.createVault(body);
99
- }
100
- if (path.length === 2 && method === "GET") {
101
- const ownerUserId = url.searchParams.get("ownerUserId") ??
102
- owner ??
103
- fail(400, "ownerUserId is required");
104
- if (owner !== undefined && ownerUserId !== owner)
105
- fail(403, "ownerUserId must be the subject");
106
- return { vaults: await vault.listVaults(ownerUserId) };
107
- }
108
- const vaultId = path[2];
109
- if (!vaultId)
110
- fail(404, "Vault not found");
111
- if (owner !== undefined)
112
- await vault.assertOwner(vaultId, owner);
113
- if (path.length === 3 && method === "GET")
114
- return vault.getVault(vaultId);
115
- if (path.length === 3 && method === "DELETE")
116
- return vault.deleteVault(vaultId);
117
- if (path[3] !== "credentials")
118
- fail(404, "Route not found");
119
- if (path.length === 4 && method === "POST") {
120
- const body = CreateCredentialRequestSchema.parse(await readBody(request));
121
- return vault.createCredential(vaultId, body);
122
- }
123
- if (path.length === 4 && method === "GET")
124
- return { credentials: await vault.listCredentials(vaultId) };
125
- const credentialId = path[4];
126
- if (!credentialId)
127
- fail(404, "Credential not found");
128
- if (path.length === 5 && method === "GET")
129
- return vault.getCredential(vaultId, credentialId);
130
- if (path.length === 5 && method === "POST") {
131
- const body = RotateCredentialRequestSchema.parse(await readBody(request));
132
- return vault.rotateCredential(vaultId, credentialId, body);
133
- }
134
- if (path.length === 5 && method === "DELETE")
135
- return vault.deleteCredential(vaultId, credentialId);
136
- fail(404, "Route not found");
137
- }
@@ -1,9 +0,0 @@
1
- /**
2
- * The Tenant HTTP router: authenticates the bearer token, matches `/v1/...` routes and
3
- * delegates to the service modules (commands, actions, sessions, live, routes-tenant). It
4
- * maps typed errors to responses and owns no business logic of its own. History, SSE and
5
- * executor work streams read Durable Streams (`live.ts`).
6
- */
7
- import type { IncomingMessage, ServerResponse } from "node:http";
8
- import { type TenantContext } from "./context.js";
9
- export declare function handle(ctx: TenantContext, request: IncomingMessage, response: ServerResponse, _url?: URL): Promise<void>;