@assemblyline-agents/node 5.0.10 → 6.0.0

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 (57) hide show
  1. package/dist/adapters.d.ts.map +1 -1
  2. package/dist/adapters.js +6 -5
  3. package/dist/adapters.js.map +1 -1
  4. package/dist/admin-http-routes.d.ts +3 -0
  5. package/dist/admin-http-routes.d.ts.map +1 -0
  6. package/dist/admin-http-routes.js +285 -0
  7. package/dist/admin-http-routes.js.map +1 -0
  8. package/dist/auth.d.ts +1 -1
  9. package/dist/auth.d.ts.map +1 -1
  10. package/dist/auth.js +2 -16
  11. package/dist/auth.js.map +1 -1
  12. package/dist/conversation-http-routes.d.ts +3 -0
  13. package/dist/conversation-http-routes.d.ts.map +1 -0
  14. package/dist/conversation-http-routes.js +206 -0
  15. package/dist/conversation-http-routes.js.map +1 -0
  16. package/dist/core-http-routes.d.ts +3 -0
  17. package/dist/core-http-routes.d.ts.map +1 -0
  18. package/dist/core-http-routes.js +112 -0
  19. package/dist/core-http-routes.js.map +1 -0
  20. package/dist/http-io.d.ts +1 -1
  21. package/dist/http-io.d.ts.map +1 -1
  22. package/dist/index.d.ts +9 -39
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +44 -882
  25. package/dist/index.js.map +1 -1
  26. package/dist/ingress-http-routes.d.ts +3 -0
  27. package/dist/ingress-http-routes.d.ts.map +1 -0
  28. package/dist/ingress-http-routes.js +210 -0
  29. package/dist/ingress-http-routes.js.map +1 -0
  30. package/dist/model-auth.d.ts.map +1 -1
  31. package/dist/model-auth.js +22 -21
  32. package/dist/model-auth.js.map +1 -1
  33. package/dist/node-types.d.ts +24 -0
  34. package/dist/node-types.d.ts.map +1 -0
  35. package/dist/node-types.js +2 -0
  36. package/dist/node-types.js.map +1 -0
  37. package/dist/provider-resolver.d.ts +2 -2
  38. package/dist/provider-resolver.d.ts.map +1 -1
  39. package/dist/provider-resolver.js +2 -2
  40. package/dist/provider-resolver.js.map +1 -1
  41. package/dist/run-http-routes.d.ts +3 -0
  42. package/dist/run-http-routes.d.ts.map +1 -0
  43. package/dist/run-http-routes.js +252 -0
  44. package/dist/run-http-routes.js.map +1 -0
  45. package/dist/runtime-http-route.d.ts +14 -0
  46. package/dist/runtime-http-route.d.ts.map +1 -0
  47. package/dist/runtime-http-route.js +2 -0
  48. package/dist/runtime-http-route.js.map +1 -0
  49. package/dist/secrets.d.ts +3 -13
  50. package/dist/secrets.d.ts.map +1 -1
  51. package/dist/secrets.js +6 -16
  52. package/dist/secrets.js.map +1 -1
  53. package/dist/usage-http.d.ts +2 -2
  54. package/dist/usage-http.d.ts.map +1 -1
  55. package/dist/usage-http.js +14 -78
  56. package/dist/usage-http.js.map +1 -1
  57. package/package.json +11 -11
package/dist/index.js CHANGED
@@ -1,31 +1,29 @@
1
1
  import { createServer } from "node:http";
2
2
  import { resolve } from "node:path";
3
- import { adapter, ConnectionEventError } from "@assemblyline-agents/core";
4
- import { AssemblyLineRuntime, RunReplayError, RunCapacityError, durableAgentScopeForManifest, loadRuntimeBundle, queryAgentRuns, retryAfterSeconds, validateEvalRunOptions } from "@assemblyline-agents/runtime";
5
- import { deleteMemoryAdmin, listMemoryAdmin, parseMemoryAdminRoute, readMemoryAdmin } from "./memory-admin-http.js";
6
- import { createBlobAdapter, createConnectionStores, createMediaProcessors, createModelCredentialStore, createSandboxAdapter, createSelfImprovementStores, createStateAdapter, loadRunTimeline, loadRunTimelinePage } from "./adapters.js";
7
- import { apiRunCreationEnabled, evalRunsEnabled, authorizeOrSend, logHttpRequest, numberBodyValue, numberQueryValue, rateLimitOrSend, stringBodyValue, validateRuntimeHttpSecurity } from "./auth.js";
8
- import { HttpRequestError, firstHeaderValue, maxRequestBodyBytes, normalizeHeaders, readBody, send, summarizeRun } from "./http-io.js";
9
- import { serveRunEventStream, serveStreamingRun } from "./run-stream-http.js";
3
+ import { adapter } from "@assemblyline-agents/core";
4
+ import { AssemblyLineRuntime, loadRuntimeBundle, } from "@assemblyline-agents/runtime";
5
+ import { createBlobAdapter, createConnectionStores, createMediaProcessors, createModelCredentialStore, createSandboxAdapter, createSelfImprovementStores, createStateAdapter, } from "./adapters.js";
6
+ import { logHttpRequest, validateRuntimeHttpSecurity } from "./auth.js";
7
+ import { HttpRequestError, send } from "./http-io.js";
10
8
  import { createCredentialBoundary, resolveGatewayCredentials, resolveGatewayEnvironment } from "./secrets.js";
11
- import { startRunReplay } from "./run-replay-http.js";
12
- import { matchRoute, parseAgentControlRoute, parseRunFeedbackRoute, parseRunReplayRoute, parseRunResumeRoute, parseRunRoute, parseSelfImprovementRoute } from "./route-match.js";
13
9
  import { resolveIngressRateLimit } from "./rate-limit.js";
14
- import { createNodeRuntimeHandle, installSignalHandlers } from "./lifecycle.js";
15
- import { DIRECT_CHANNEL, conversationBelongsToAgent, decodeConversationCursor, decodeMessageCursor, directAgentScope, directConversationPatch, directTurnFromBody, encodeConversationCursor, encodeMessageCursor, parseConversationHttpRoute } from "./direct-conversations-http.js";
16
- import { usageHttpResult, usageReconciliationHttpResult } from "./usage-http.js";
17
- import { isWorkspaceAdminPath, workspaceAdminHttpResult } from "./workspace-admin-http.js";
18
- export { resolveProvider } from "./provider-resolver.js";
10
+ import { createNodeRuntimeHandle, installSignalHandlers, } from "./lifecycle.js";
11
+ import { handleAdminHttpRoutes } from "./admin-http-routes.js";
12
+ import { handleCoreHttpRoutes } from "./core-http-routes.js";
13
+ import { handleConversationHttpRoutes } from "./conversation-http-routes.js";
14
+ import { handleIngressHttpRoutes } from "./ingress-http-routes.js";
15
+ import { handleRunHttpRoutes } from "./run-http-routes.js";
16
+ export { resolveProvider, } from "./provider-resolver.js";
17
+ export { createCredentialBoundary, resolveCredentialInventory, resolveGatewayCredentials, resolveGatewayEnvironment, } from "./secrets.js";
19
18
  export { createModelCredentialStore } from "./adapters.js";
19
+ export { createMemoryRateLimiterStore, parseRateLimitSpec, resolveIngressRateLimit, } from "./rate-limit.js";
20
+ export { createNodeRuntimeHandle, installSignalHandlers, } from "./lifecycle.js";
20
21
  export { runModelProviderAuth } from "./model-auth.js";
21
- export { createCredentialBoundary, resolveCredentialInventory, resolveGatewayCredentials, resolveGatewayEnvironment } from "./secrets.js";
22
- export { createMemoryRateLimiterStore, parseRateLimitSpec, resolveIngressRateLimit } from "./rate-limit.js";
23
- export { createNodeRuntimeHandle, installSignalHandlers } from "./lifecycle.js";
24
22
  export function nodeAdapter(options = {}) {
25
23
  return {
26
24
  ...adapter("node", options),
27
25
  kind: "node",
28
- capabilities: ["http-runtime", "hosted-container-runtime", "local-server"]
26
+ capabilities: ["http-runtime", "hosted-container-runtime", "local-server"],
29
27
  };
30
28
  }
31
29
  export async function createProductionRuntimeOptions(options) {
@@ -36,7 +34,7 @@ export async function createProductionRuntimeOptions(options) {
36
34
  manifest: runtimeOptions.manifest,
37
35
  env: options.env ?? process.env,
38
36
  artifactRoot,
39
- devMode: options.devMode ?? false
37
+ devMode: options.devMode ?? false,
40
38
  });
41
39
  const gatewayEnv = await resolveGatewayEnvironment(boundary, runtimeOptions.manifest);
42
40
  const gatewayCredentials = await resolveGatewayCredentials(boundary, runtimeOptions.manifest);
@@ -64,13 +62,15 @@ export async function createProductionRuntimeOptions(options) {
64
62
  credentialStore: boundary.store,
65
63
  gatewayCredentials,
66
64
  env: gatewayEnv,
67
- devMode: options.devMode ?? false
65
+ devMode: options.devMode ?? false,
68
66
  };
69
- const callbackBaseUrl = boundary.config.ASSEMBLY_LINE_CONNECTION_CALLBACK_BASE_URL ?? boundary.config.ASSEMBLY_LINE_PUBLIC_URL;
67
+ const callbackBaseUrl = boundary.config.ASSEMBLY_LINE_CONNECTION_CALLBACK_BASE_URL ??
68
+ boundary.config.ASSEMBLY_LINE_PUBLIC_URL;
70
69
  if (callbackBaseUrl)
71
70
  out.connectionCallbackBaseUrl = callbackBaseUrl;
72
71
  // Production default: bound run concurrency unless the operator chose a limit.
73
- if (!(options.devMode ?? false) && boundary.config.ASSEMBLY_LINE_MAX_CONCURRENT_RUNS === undefined) {
72
+ if (!(options.devMode ?? false) &&
73
+ boundary.config.ASSEMBLY_LINE_MAX_CONCURRENT_RUNS === undefined) {
74
74
  out.maxConcurrentRuns = 16;
75
75
  }
76
76
  return out;
@@ -95,854 +95,20 @@ function createNodeRuntimeServerForRuntime(runtime, options, drain = { draining:
95
95
  return;
96
96
  }
97
97
  const url = new URL(request.url, "http://localhost");
98
- if (request.method === "GET" && url.pathname === "/health") {
99
- if (!await authorizeOrSend(request, response, runtime, options, "public-health", url.pathname))
100
- return;
101
- send(response, 200, revisionHealth(options.manifest));
102
- return;
103
- }
104
- if (request.method === "GET" && url.pathname === "/healthz") {
105
- if (!await authorizeOrSend(request, response, runtime, options, "public-health", url.pathname))
106
- return;
107
- send(response, 200, {
108
- ok: true,
109
- agentRevision: options.manifest.agentRevision,
110
- buildRevision: options.manifest.buildRevision ?? options.manifest.agentRevision,
111
- capabilities: evalRunsEnabled(runtime, options) ? ["eval-runs"] : []
112
- });
113
- return;
114
- }
115
- if (request.method === "GET" && url.pathname === "/readyz") {
116
- // Readiness (vs /health liveness): 503 while draining so load
117
- // balancers stop routing new traffic during graceful shutdown.
118
- if (!await authorizeOrSend(request, response, runtime, options, "public-health", url.pathname))
119
- return;
120
- if (drain.draining) {
121
- send(response, 503, { ...revisionHealth(options.manifest), ok: false, draining: true });
122
- return;
123
- }
124
- send(response, 200, revisionHealth(options.manifest));
125
- return;
126
- }
127
- if (request.method === "GET" && url.pathname === "/manifest") {
128
- if (!await authorizeOrSend(request, response, runtime, options, "admin-read", url.pathname))
129
- return;
130
- send(response, 200, options.manifest);
131
- return;
132
- }
133
- if (request.method === "GET" && url.pathname === "/routes") {
134
- if (!await authorizeOrSend(request, response, runtime, options, "admin-read", url.pathname))
135
- return;
136
- send(response, 200, options.manifest.routeTable);
137
- return;
138
- }
139
- const agentControlRoute = parseAgentControlRoute(url.pathname);
140
- if (agentControlRoute && request.method === "GET" && agentControlRoute.kind === "control") {
141
- if (!await authorizeOrSend(request, response, runtime, options, "agent-control", url.pathname))
142
- return;
143
- send(response, 200, await runtime.getAgentControl());
144
- return;
145
- }
146
- if (agentControlRoute && request.method === "POST" && agentControlRoute.kind !== "control") {
147
- if (!await authorizeOrSend(request, response, runtime, options, "agent-control", url.pathname))
148
- return;
149
- if (!await rateLimitOrSend(request, response, rateLimit, "agent-control", url.pathname))
150
- return;
151
- const requestId = firstHeaderValue(request.headers["x-request-id"] ?? request.headers["x-railway-request-id"]);
152
- const metadata = {
153
- source: "http",
154
- path: url.pathname,
155
- ...(requestId ? { requestId } : {})
156
- };
157
- const result = agentControlRoute.kind === "quiesce" || agentControlRoute.kind === "resume"
158
- ? await runtime.setAgentMaintenance(agentControlRoute.kind === "quiesce", metadata)
159
- : await runtime.setAgentEnabled(agentControlRoute.kind === "enable", metadata);
160
- send(response, 200, result);
161
- return;
162
- }
163
- if (isWorkspaceAdminPath(url.pathname)) {
164
- const method = request.method ?? "GET";
165
- if (!["GET", "POST", "DELETE"].includes(method)) {
166
- send(response, 405, { error: "Method not allowed." });
167
- return;
168
- }
169
- if (!await authorizeOrSend(request, response, runtime, options, method === "GET" ? "admin-read" : "agent-control", url.pathname))
170
- return;
171
- try {
172
- const body = method === "POST"
173
- ? (await readBody(request, maxRequestBodyBytes(runtime, options))).body
174
- : {};
175
- send(response, 200, await workspaceAdminHttpResult(runtime, method, url.pathname, body, url.searchParams));
176
- }
177
- catch (error) {
178
- send(response, 400, { error: error instanceof Error ? error.message : String(error) });
179
- }
180
- return;
181
- }
182
- const conversationRoute = parseConversationHttpRoute(url.pathname);
183
- if (conversationRoute?.kind === "collection" && request.method === "GET") {
184
- if (!await authorizeOrSend(request, response, runtime, options, "admin-read", url.pathname))
185
- return;
186
- if (!runtime.state.listConversations) {
187
- send(response, 501, { error: "The configured state adapter does not support conversation listing." });
188
- return;
189
- }
190
- const cursorValue = url.searchParams.get("cursor");
191
- const before = decodeConversationCursor(cursorValue);
192
- if (cursorValue && !before) {
193
- send(response, 400, { error: "Invalid conversation cursor." });
194
- return;
195
- }
196
- const limitParam = Number(url.searchParams.get("limit"));
197
- const limit = Number.isFinite(limitParam) && limitParam > 0 ? Math.min(Math.floor(limitParam), 100) : 40;
198
- const archivedValue = url.searchParams.get("archived");
199
- const archived = archivedValue === "true" ? true : archivedValue === "all" ? undefined : false;
200
- const listed = await runtime.state.listConversations({
201
- agentScope: directAgentScope(options.manifest),
202
- channel: url.searchParams.get("channel") || DIRECT_CHANNEL,
203
- ...(url.searchParams.get("subject") ? { subject: url.searchParams.get("subject") } : {}),
204
- ...(archived !== undefined ? { archived } : {}),
205
- ...(before ? { before } : {}),
206
- limit: limit + 1
207
- });
208
- const hasMore = listed.length > limit;
209
- const conversations = listed.slice(0, limit);
210
- send(response, 200, {
211
- conversations,
212
- nextCursor: hasMore && conversations.length > 0
213
- ? encodeConversationCursor(conversations[conversations.length - 1])
214
- : null
215
- });
216
- return;
217
- }
218
- if (conversationRoute?.kind === "messages" && request.method === "GET") {
219
- if (!await authorizeOrSend(request, response, runtime, options, "admin-read", url.pathname))
220
- return;
221
- const conversation = await runtime.state.getConversation(conversationRoute.conversationId);
222
- if (!conversation || !conversationBelongsToAgent(conversation, directAgentScope(options.manifest))) {
223
- send(response, 404, { error: "Conversation not found." });
224
- return;
225
- }
226
- const cursorValue = url.searchParams.get("before");
227
- const before = decodeMessageCursor(cursorValue);
228
- if (cursorValue && !before) {
229
- send(response, 400, { error: "Invalid message cursor." });
230
- return;
231
- }
232
- const limitParam = Number(url.searchParams.get("limit"));
233
- const limit = Number.isFinite(limitParam) && limitParam > 0 ? Math.min(Math.floor(limitParam), 100) : 50;
234
- const listed = runtime.state.listMessages
235
- ? await runtime.state.listMessages({
236
- conversationId: conversation.id,
237
- ...(before ? { before } : {}),
238
- limit: limit + 1
239
- })
240
- : before
241
- ? []
242
- : await runtime.state.listRecentMessages(conversation.id, limit + 1);
243
- if (before && !runtime.state.listMessages) {
244
- send(response, 501, { error: "The configured state adapter does not support message pagination." });
245
- return;
246
- }
247
- const hasMore = listed.length > limit;
248
- const messages = hasMore ? listed.slice(1) : listed;
249
- send(response, 200, {
250
- messages,
251
- nextCursor: hasMore && messages.length > 0 ? encodeMessageCursor(messages[0]) : null
252
- });
253
- return;
254
- }
255
- if (conversationRoute?.kind === "record" && request.method === "PATCH") {
256
- if (!await authorizeOrSend(request, response, runtime, options, "agent-control", url.pathname))
257
- return;
258
- if (!runtime.state.updateConversation) {
259
- send(response, 501, { error: "The configured state adapter does not support conversation updates." });
260
- return;
261
- }
262
- const existing = await runtime.state.getConversation(conversationRoute.conversationId);
263
- if (!existing || !conversationBelongsToAgent(existing, directAgentScope(options.manifest))) {
264
- send(response, 404, { error: "Conversation not found." });
265
- return;
266
- }
267
- const { body } = await readBody(request, maxRequestBodyBytes(runtime, options));
268
- const patch = directConversationPatch(body);
269
- if (Object.keys(patch).length === 0) {
270
- send(response, 400, { error: "Provide a title, archived flag, or metadata patch." });
271
- return;
272
- }
273
- send(response, 200, await runtime.state.updateConversation(existing.id, patch));
274
- return;
275
- }
276
- if (conversationRoute?.kind === "turns" && request.method === "POST") {
277
- channel = DIRECT_CHANNEL;
278
- if (!apiRunCreationEnabled(runtime, options)) {
279
- send(response, 403, { error: "API run creation is disabled." });
280
- return;
281
- }
282
- if (!await authorizeOrSend(request, response, runtime, options, "run-create", url.pathname))
283
- return;
284
- if (!await rateLimitOrSend(request, response, rateLimit, "run-create", url.pathname, DIRECT_CHANNEL))
285
- return;
286
- const existing = await runtime.state.getConversation(conversationRoute.conversationId);
287
- if (existing && !conversationBelongsToAgent(existing, directAgentScope(options.manifest))) {
288
- send(response, 404, { error: "Conversation not found." });
289
- return;
290
- }
291
- if (existing?.channel && existing.channel !== DIRECT_CHANNEL) {
292
- send(response, 409, { error: `Conversation belongs to channel ${existing.channel}.` });
293
- return;
294
- }
295
- if (existing?.archivedAt) {
296
- send(response, 409, { error: "Archived conversations cannot accept new turns." });
297
- return;
298
- }
299
- const { body } = await readBody(request, maxRequestBodyBytes(runtime, options));
300
- const idempotencyKey = firstHeaderValue(request.headers["idempotency-key"]);
301
- const direct = directTurnFromBody({
302
- conversationId: conversationRoute.conversationId,
303
- body,
304
- ...(idempotencyKey ? { idempotencyKey } : {})
305
- });
306
- if (!direct.turn.message && !(direct.turn.attachments?.length)) {
307
- send(response, 400, { error: "A message or attachment is required." });
308
- return;
309
- }
310
- const workspaceId = stringBodyValue(body.workspaceId);
311
- const projectId = stringBodyValue(body.projectId);
312
- const tenantId = stringBodyValue(body.tenantId);
313
- const runOptions = {
314
- message: direct.turn.message,
315
- channel: DIRECT_CHANNEL,
316
- approve: direct.approve,
317
- turn: direct.turn,
318
- ...(workspaceId ? { workspaceId } : {}),
319
- ...(projectId ? { projectId } : {}),
320
- ...(tenantId ? { tenantId } : {})
321
- };
322
- if (body.sandboxCredentials && typeof body.sandboxCredentials === "object" && !Array.isArray(body.sandboxCredentials)) {
323
- Object.assign(runOptions, { sandboxCredentials: body.sandboxCredentials });
324
- }
325
- try {
326
- if (direct.stream) {
327
- await serveStreamingRun(runtime, response, (onRunCreated) => runtime.run({ ...runOptions, onRunCreated }), (result) => summarizeRun(result));
328
- return;
329
- }
330
- const result = await runtime.run(runOptions);
331
- send(response, 200, summarizeRun(result));
332
- }
333
- catch (error) {
334
- if (error instanceof RunCapacityError) {
335
- send(response, 429, { error: "Run capacity exhausted. Retry later.", retryAfterMs: error.retryAfterMs }, {
336
- "retry-after": String(retryAfterSeconds(error.retryAfterMs))
337
- });
338
- return;
339
- }
340
- throw error;
341
- }
342
- return;
343
- }
344
- if (request.method === "GET" && url.pathname === "/runs") {
345
- if (!await authorizeOrSend(request, response, runtime, options, "admin-read", url.pathname))
346
- return;
347
- // Light list: no per-run detail (avoids the 1+4N fan-out that hangs on
348
- // busy agents). Bounded to the most recent runs; ?limit overrides.
349
- const limitParam = Number(url.searchParams.get("limit"));
350
- const limit = Number.isFinite(limitParam) && limitParam > 0 ? Math.min(limitParam, 1000) : 200;
351
- send(response, 200, await queryAgentRuns(runtime.state, options.manifest, { includeDetails: false, limit }));
352
- return;
353
- }
354
- const memoryAdminRoute = parseMemoryAdminRoute(url.pathname);
355
- if (memoryAdminRoute) {
356
- const agentScope = durableAgentScopeForManifest(options.manifest);
357
- if (request.method === "GET") {
358
- if (!await authorizeOrSend(request, response, runtime, options, "admin-read", url.pathname))
359
- return;
360
- const result = memoryAdminRoute.kind === "list"
361
- ? await listMemoryAdmin(runtime.state, agentScope, url.searchParams)
362
- : await readMemoryAdmin(runtime.state, agentScope, memoryAdminRoute.path);
363
- send(response, result.status, result.body);
364
- return;
365
- }
366
- if (request.method === "DELETE" && memoryAdminRoute.kind === "document") {
367
- if (!await authorizeOrSend(request, response, runtime, options, "agent-control", url.pathname))
368
- return;
369
- if (!await rateLimitOrSend(request, response, rateLimit, "agent-control", url.pathname))
370
- return;
371
- const result = await deleteMemoryAdmin(runtime.state, agentScope, memoryAdminRoute.path, url.searchParams);
372
- send(response, result.status, result.body);
373
- return;
374
- }
375
- send(response, 405, { error: "Method not allowed." });
376
- return;
377
- }
378
- const selfImprovementRoute = parseSelfImprovementRoute(url.pathname);
379
- const selfImprovementSurface = url.searchParams.get("surface")?.trim() || "root";
380
- if (selfImprovementRoute && request.method === "GET") {
381
- if (!await authorizeOrSend(request, response, runtime, options, "admin-read", url.pathname))
382
- return;
383
- if (selfImprovementRoute.kind === "pending") {
384
- send(response, 200, { surface: selfImprovementSurface, changes: await runtime.listPendingSkillChanges(selfImprovementSurface) });
385
- return;
386
- }
387
- if (selfImprovementRoute.kind === "reviews") {
388
- send(response, 200, { surface: selfImprovementSurface, reviews: await runtime.listBackgroundReviews(selfImprovementSurface) });
389
- return;
390
- }
391
- if (selfImprovementRoute.kind === "revisions") {
392
- send(response, 200, { surface: selfImprovementSurface, name: selfImprovementRoute.name, revisions: await runtime.listSkillRevisions(selfImprovementRoute.name, selfImprovementSurface) });
393
- return;
394
- }
395
- send(response, 405, { error: "Method not allowed." });
396
- return;
397
- }
398
- if (selfImprovementRoute && request.method === "POST") {
399
- if (!await authorizeOrSend(request, response, runtime, options, "agent-control", url.pathname))
400
- return;
401
- if (!await rateLimitOrSend(request, response, rateLimit, "agent-control", url.pathname))
402
- return;
403
- if (selfImprovementRoute.kind === "approve") {
404
- try {
405
- send(response, 200, await runtime.approveSkillChange(selfImprovementRoute.id, selfImprovementSurface));
406
- }
407
- catch (error) {
408
- if (error instanceof Error && /not found/i.test(error.message)) {
409
- send(response, 404, { error: error.message });
410
- return;
411
- }
412
- throw error;
413
- }
414
- return;
415
- }
416
- if (selfImprovementRoute.kind === "reject") {
417
- try {
418
- await runtime.rejectSkillChange(selfImprovementRoute.id, selfImprovementSurface);
419
- send(response, 200, { rejected: true, id: selfImprovementRoute.id, surface: selfImprovementSurface });
420
- }
421
- catch (error) {
422
- if (error instanceof Error && /not found/i.test(error.message)) {
423
- send(response, 404, { error: error.message });
424
- return;
425
- }
426
- throw error;
427
- }
428
- return;
429
- }
430
- if (selfImprovementRoute.kind === "restore") {
431
- const { body } = await readBody(request, maxRequestBodyBytes(runtime, options));
432
- const revision = numberBodyValue(body.revision);
433
- if (revision === undefined || revision < 1 || !Number.isInteger(revision)) {
434
- send(response, 400, { error: "A positive integer revision is required." });
435
- return;
436
- }
437
- try {
438
- send(response, 200, await runtime.restoreSkillRevision(selfImprovementRoute.name, revision, selfImprovementSurface));
439
- }
440
- catch (error) {
441
- if (error instanceof Error && /not found/i.test(error.message)) {
442
- send(response, 404, { error: error.message });
443
- return;
444
- }
445
- throw error;
446
- }
447
- return;
448
- }
449
- send(response, 405, { error: "Method not allowed." });
450
- return;
451
- }
452
- if (request.method === "GET" && url.pathname === "/usage") {
453
- if (!await authorizeOrSend(request, response, runtime, options, "admin-read", url.pathname))
454
- return;
455
- try {
456
- send(response, 200, await usageHttpResult(runtime.state, url));
457
- }
458
- catch (error) {
459
- send(response, 400, { error: error instanceof Error ? error.message : String(error) });
460
- }
461
- return;
462
- }
463
- if (request.method === "POST" && url.pathname === "/usage/reconcile") {
464
- if (!await authorizeOrSend(request, response, runtime, options, "agent-control", url.pathname))
465
- return;
466
- try {
467
- const { body } = await readBody(request, maxRequestBodyBytes(runtime, options));
468
- send(response, 200, await usageReconciliationHttpResult(runtime.state, runtime.env, body));
469
- }
470
- catch (error) {
471
- send(response, 400, { error: error instanceof Error ? error.message : String(error) });
472
- }
473
- return;
474
- }
475
- if ((request.method === "GET" || request.method === "POST")
476
- && url.pathname === "/assembly-line/automations/tick") {
477
- if (!await authorizeOrSend(request, response, runtime, options, "scheduler", url.pathname))
478
- return;
479
- if (!await rateLimitOrSend(request, response, rateLimit, "scheduler", url.pathname))
480
- return;
481
- const body = request.method === "POST" ? (await readBody(request, maxRequestBodyBytes(runtime, options))).body : {};
482
- const nowValue = stringBodyValue(body.now) ?? url.searchParams.get("now") ?? undefined;
483
- const lookbackValue = numberBodyValue(body.lookbackMs) ?? numberQueryValue(url.searchParams.get("lookbackMs"));
484
- const tickOptions = {};
485
- if (nowValue)
486
- tickOptions.now = new Date(nowValue);
487
- if (lookbackValue !== undefined)
488
- tickOptions.lookbackMs = lookbackValue;
489
- if (tickOptions.now && Number.isNaN(tickOptions.now.getTime())) {
490
- send(response, 400, { error: "Invalid scheduler now timestamp." });
491
- return;
492
- }
493
- const results = await runtime.runDueAutomations(tickOptions);
494
- send(response, 200, { ok: true, results });
495
- return;
496
- }
497
- if (request.method === "POST" && url.pathname === "/assembly-line/automations/events") {
498
- if (!await authorizeOrSend(request, response, runtime, options, "agent-control", url.pathname))
499
- return;
500
- if (!await rateLimitOrSend(request, response, rateLimit, "run-create", url.pathname))
501
- return;
502
- const { body } = await readBody(request, maxRequestBodyBytes(runtime, options));
503
- const source = stringBodyValue(body.source);
504
- const event = stringBodyValue(body.event);
505
- const eventId = stringBodyValue(body.eventId);
506
- const payload = body.payload;
507
- if (!source || !event || !eventId || !payload || typeof payload !== "object" || Array.isArray(payload)) {
508
- send(response, 400, { error: "Automation events require source, event, eventId, and an object payload." });
509
- return;
510
- }
511
- const automationEvent = {
512
- source,
513
- event,
514
- eventId,
515
- payload: payload
516
- };
517
- const occurredAt = stringBodyValue(body.occurredAt);
518
- const userId = stringBodyValue(body.userId);
519
- const conversationId = stringBodyValue(body.conversationId);
520
- const projectId = stringBodyValue(body.projectId);
521
- if (occurredAt && Number.isNaN(new Date(occurredAt).getTime())) {
522
- send(response, 400, { error: "Automation event occurredAt must be a valid ISO timestamp." });
523
- return;
524
- }
525
- if (occurredAt)
526
- automationEvent.occurredAt = occurredAt;
527
- if (userId)
528
- automationEvent.userId = userId;
529
- if (conversationId)
530
- automationEvent.conversationId = conversationId;
531
- if (projectId)
532
- automationEvent.projectId = projectId;
533
- if (body.delivery && typeof body.delivery === "object" && !Array.isArray(body.delivery)) {
534
- automationEvent.delivery = body.delivery;
535
- }
536
- if (body.metadata && typeof body.metadata === "object" && !Array.isArray(body.metadata)) {
537
- automationEvent.metadata = body.metadata;
538
- }
539
- const results = await runtime.dispatchAutomationEvent(automationEvent);
540
- send(response, 200, { ok: true, matched: results.length, results });
541
- return;
542
- }
543
- if (request.method === "POST" && url.pathname === "/assembly-line/connections/events/reconcile") {
544
- if (!await authorizeOrSend(request, response, runtime, options, "agent-control", url.pathname))
545
- return;
546
- const { body } = await readBody(request, maxRequestBodyBytes(runtime, options));
547
- const principal = stringBodyValue(body.userId) && stringBodyValue(body.issuer)
548
- ? { type: "user", id: stringBodyValue(body.userId), issuer: stringBodyValue(body.issuer) }
549
- : undefined;
550
- const connection = stringBodyValue(body.connection);
551
- const publicUrl = stringBodyValue(body.publicUrl);
552
- const results = await runtime.reconcileConnectionEvents({
553
- ...(connection ? { connection } : {}),
554
- ...(publicUrl ? { publicUrl } : {}),
555
- ...(principal ? { principal } : {}),
556
- force: body.force === true
557
- });
558
- send(response, 200, { ok: true, results });
559
- return;
560
- }
561
- if (request.method === "GET" && url.pathname === "/assembly-line/connections/events") {
562
- if (!await authorizeOrSend(request, response, runtime, options, "admin-read", url.pathname))
563
- return;
564
- const results = await runtime.checkConnectionEvents(url.searchParams.get("connection") ?? undefined);
565
- send(response, 200, { ok: true, results });
566
- return;
567
- }
568
- if (request.method === "GET" && url.pathname.startsWith("/runs/")) {
569
- if (!await authorizeOrSend(request, response, runtime, options, "admin-read", url.pathname))
570
- return;
571
- const runRoute = parseRunRoute(url.pathname);
572
- if (!runRoute) {
573
- send(response, 404, { error: "Not found." });
574
- return;
575
- }
576
- if (runRoute.kind === "stream") {
577
- if (!await runtime.state.getRun(runRoute.runId)) {
578
- send(response, 404, { error: `Run not found: ${runRoute.runId}` });
579
- return;
580
- }
581
- const lastEventId = firstHeaderValue(request.headers["last-event-id"]) ?? url.searchParams.get("lastEventId") ?? undefined;
582
- await serveRunEventStream(runtime, response, runRoute.runId, lastEventId);
583
- return;
584
- }
585
- const paginatedTimeline = runRoute.kind === "timeline" && url.searchParams.has("limit");
586
- const before = numberQueryValue(url.searchParams.get("before"));
587
- const after = numberQueryValue(url.searchParams.get("after"));
588
- const limit = numberQueryValue(url.searchParams.get("limit"));
589
- if (paginatedTimeline && limit === undefined) {
590
- send(response, 400, { error: "Run timeline limit must be a number." });
591
- return;
592
- }
593
- let timeline;
594
- try {
595
- timeline = paginatedTimeline
596
- ? await loadRunTimelinePage(runtime, runRoute.runId, {
597
- ...(before !== undefined ? { before } : {}),
598
- ...(after !== undefined ? { after } : {}),
599
- limit: limit
600
- })
601
- : await loadRunTimeline(runtime, runRoute.runId);
602
- }
603
- catch (error) {
604
- if (!paginatedTimeline)
605
- throw error;
606
- send(response, 400, { error: error instanceof Error ? error.message : String(error) });
607
- return;
608
- }
609
- if (!timeline) {
610
- send(response, 404, { error: `Run not found: ${runRoute.runId}` });
611
- return;
612
- }
613
- if (runRoute.kind === "events") {
614
- send(response, 200, { runId: runRoute.runId, events: timeline.events });
615
- return;
616
- }
617
- if (runRoute.kind === "timeline") {
618
- send(response, 200, timeline);
619
- return;
620
- }
621
- send(response, 200, timeline);
622
- return;
623
- }
624
- if (request.method === "POST" && url.pathname.startsWith("/runs/")) {
625
- const replayRoute = parseRunReplayRoute(url.pathname);
626
- if (replayRoute) {
627
- if (!await authorizeOrSend(request, response, runtime, options, "agent-control", url.pathname))
628
- return;
629
- if (!await rateLimitOrSend(request, response, rateLimit, "run-create", url.pathname))
630
- return;
631
- try {
632
- const replay = await startRunReplay(runtime, replayRoute.runId);
633
- void replay.completion.catch((error) => {
634
- runtime.logger.error("replay run failed after durable creation", {
635
- sourceRunId: replayRoute.runId,
636
- runId: replay.run.id,
637
- error: error instanceof Error ? error.message : String(error)
638
- });
639
- });
640
- send(response, 202, { runId: replay.run.id, status: replay.run.status });
641
- }
642
- catch (error) {
643
- if (error instanceof RunReplayError) {
644
- send(response, error.code === "run_not_found" ? 404 : 409, { error: error.message, code: error.code });
645
- return;
646
- }
647
- if (error instanceof RunCapacityError) {
648
- send(response, 429, { error: "Run capacity exhausted. Retry later.", retryAfterMs: error.retryAfterMs }, {
649
- "retry-after": String(retryAfterSeconds(error.retryAfterMs))
650
- });
651
- return;
652
- }
653
- throw error;
654
- }
655
- return;
656
- }
657
- const feedbackRoute = parseRunFeedbackRoute(url.pathname);
658
- if (feedbackRoute) {
659
- if (!await authorizeOrSend(request, response, runtime, options, "run-control", url.pathname))
660
- return;
661
- if (!await rateLimitOrSend(request, response, rateLimit, "run-control", url.pathname))
662
- return;
663
- if (!await runtime.state.getRun(feedbackRoute.runId)) {
664
- send(response, 404, { error: `Run not found: ${feedbackRoute.runId}` });
665
- return;
666
- }
667
- const { body } = await readBody(request, maxRequestBodyBytes(runtime, options));
668
- const rating = stringBodyValue(body.rating);
669
- const comment = stringBodyValue(body.comment);
670
- if (rating !== "positive" && rating !== "negative") {
671
- send(response, 400, { error: "Feedback rating must be 'positive' or 'negative'." });
672
- return;
673
- }
674
- const result = await runtime.recordRunFeedback(feedbackRoute.runId, {
675
- rating,
676
- ...(comment ? { comment } : {})
677
- });
678
- send(response, 202, result);
679
- return;
680
- }
681
- const resumeRoute = parseRunResumeRoute(url.pathname);
682
- if (resumeRoute) {
683
- const isHitlResume = resumeRoute.kind === "approve" || resumeRoute.kind === "answer";
684
- const routeClass = isHitlResume || resumeRoute.kind === "resume" ? "run-resume" : "run-control";
685
- if (!await authorizeOrSend(request, response, runtime, options, routeClass, url.pathname))
686
- return;
687
- if (!await rateLimitOrSend(request, response, rateLimit, routeClass, url.pathname))
688
- return;
689
- const run = await runtime.state.getRun(resumeRoute.runId);
690
- if (!run) {
691
- send(response, 404, { error: `Run not found: ${resumeRoute.runId}` });
692
- return;
693
- }
694
- const expected = resumeRoute.kind === "approve"
695
- ? "waiting_for_approval"
696
- : resumeRoute.kind === "answer"
697
- ? "waiting_for_input"
698
- : resumeRoute.kind === "suspend"
699
- ? "running"
700
- : resumeRoute.kind === "resume"
701
- ? "suspended"
702
- : undefined;
703
- if (expected && run.status !== expected) {
704
- send(response, 409, { error: `Run ${resumeRoute.runId} is not ${expected} (status: ${run.status}).` });
705
- return;
706
- }
707
- if (resumeRoute.kind === "cancel" && run.status === "cancelled") {
708
- // Cancellation is an idempotent operator command. A retry may be
709
- // the first response the client receives even when an earlier
710
- // request already committed the terminal state.
711
- send(response, 200, {
712
- runId: run.id,
713
- status: run.status,
714
- controlIntent: run.controlIntent ?? null
715
- });
716
- return;
717
- }
718
- if (resumeRoute.kind === "cancel" && ["completed", "failed"].includes(run.status)) {
719
- send(response, 409, { error: `Run ${resumeRoute.runId} is already terminal (status: ${run.status}).` });
720
- return;
721
- }
722
- let answer = "";
723
- if (resumeRoute.kind === "answer") {
724
- const { body } = await readBody(request, maxRequestBodyBytes(runtime, options));
725
- answer = typeof body.answer === "string" ? body.answer : "";
726
- if (!answer) {
727
- send(response, 400, { error: "An 'answer' string is required to resume a waiting run." });
728
- return;
729
- }
730
- }
731
- try {
732
- if (resumeRoute.kind === "cancel" || resumeRoute.kind === "suspend") {
733
- const controlled = resumeRoute.kind === "cancel"
734
- ? await runtime.cancelRun(resumeRoute.runId)
735
- : await runtime.suspendRun(resumeRoute.runId);
736
- send(response, controlled.status === "running" ? 202 : 200, {
737
- runId: controlled.id,
738
- status: controlled.status,
739
- controlIntent: controlled.controlIntent ?? null
740
- });
741
- return;
742
- }
743
- const result = resumeRoute.kind === "approve"
744
- ? await runtime.resumeApproval(resumeRoute.runId)
745
- : resumeRoute.kind === "answer"
746
- ? await runtime.resumeInput(resumeRoute.runId, answer)
747
- : await runtime.resumeSuspended(resumeRoute.runId);
748
- send(response, 200, summarizeRun(result));
749
- }
750
- catch (error) {
751
- if (error instanceof RunCapacityError) {
752
- send(response, 429, { error: "Run capacity exhausted. Retry later.", retryAfterMs: error.retryAfterMs }, {
753
- "retry-after": String(retryAfterSeconds(error.retryAfterMs))
754
- });
755
- return;
756
- }
757
- // The resume idempotency claim rejects a concurrent/replayed resume.
758
- const message = error instanceof Error ? error.message : String(error);
759
- if (resumeRoute.kind === "cancel" && /already terminal/i.test(message)) {
760
- const latest = await runtime.state.getRun(resumeRoute.runId);
761
- if (latest?.status === "cancelled") {
762
- send(response, 200, {
763
- runId: latest.id,
764
- status: latest.status,
765
- controlIntent: latest.controlIntent ?? null
766
- });
767
- return;
768
- }
769
- }
770
- if (/already in progress|not waiting|not suspended|not running|already terminal/i.test(message)) {
771
- send(response, 409, { error: message });
772
- return;
773
- }
774
- throw error;
775
- }
776
- return;
777
- }
778
- // Fall through: unknown POST /runs/... path.
779
- }
780
- if (request.method === "GET" && url.pathname === "/assembly-line/connections/authorize") {
781
- // Authenticated JSON, not a public 302: the operator surface fetches
782
- // the consent URL and opens the browser itself, so unauthenticated
783
- // callers can neither trigger consent flows nor enumerate connection
784
- // names. The provider's redirect lands on the public callback below.
785
- if (!await authorizeOrSend(request, response, runtime, options, "agent-control", url.pathname))
786
- return;
787
- if (!await rateLimitOrSend(request, response, rateLimit, "agent-control", url.pathname))
788
- return;
789
- const connectionName = url.searchParams.get("connection") ?? "";
790
- if (!connectionName) {
791
- send(response, 400, { error: "A 'connection' query parameter is required." });
792
- return;
793
- }
794
- const session = {};
795
- const sessionChannel = url.searchParams.get("channel");
796
- const sessionUserId = url.searchParams.get("userId");
797
- const sessionIssuer = url.searchParams.get("issuer");
798
- const sessionConversationId = url.searchParams.get("conversationId");
799
- if (sessionChannel)
800
- session.channel = sessionChannel;
801
- if (sessionUserId)
802
- session.userId = sessionUserId;
803
- if (sessionUserId && sessionIssuer) {
804
- session.principal = { type: "user", id: sessionUserId, issuer: sessionIssuer };
805
- }
806
- if (sessionConversationId)
807
- session.conversationId = sessionConversationId;
808
- const result = await runtime.beginConnectionAuthorization(connectionName, session);
809
- send(response, result.status === "unavailable" ? 409 : 200, result);
810
- return;
811
- }
812
- if ((request.method === "GET" || request.method === "POST") && url.pathname === "/assembly-line/connections/callback") {
813
- if (!await authorizeOrSend(request, response, runtime, options, "connection-callback", url.pathname))
814
- return;
815
- const { rawBody, body } = request.method === "POST" ? await readBody(request, maxRequestBodyBytes(runtime, options)) : { rawBody: "", body: {} };
816
- const params = Object.fromEntries(url.searchParams.entries());
817
- if (Object.keys(body).length > 0) {
818
- for (const [key, value] of Object.entries(body)) {
819
- if (typeof value === "string")
820
- params[key] = value;
821
- }
822
- }
823
- const callbackInput = {
824
- params,
825
- method: request.method
826
- };
827
- if (rawBody)
828
- callbackInput.body = rawBody;
829
- const result = await runtime.completeConnectionAuthorizationCallback(callbackInput);
830
- send(response, result.status, result.body);
831
- return;
832
- }
833
- const connectionEvent = /^\/assembly-line\/connections\/([^/]+)\/events\/([^/]+)$/u.exec(url.pathname);
834
- if ((request.method === "GET" || request.method === "POST") && connectionEvent?.[1] && connectionEvent[2]) {
835
- if (!await authorizeOrSend(request, response, runtime, options, "connection-event", url.pathname))
836
- return;
837
- if (!await rateLimitOrSend(request, response, rateLimit, "connection-event", url.pathname))
838
- return;
839
- const rawBody = request.method === "POST"
840
- ? (await readBody(request, maxRequestBodyBytes(runtime, options))).rawBody
841
- : "";
842
- try {
843
- const result = await runtime.acceptConnectionEvent(decodeURIComponent(connectionEvent[1]), decodeURIComponent(connectionEvent[2]), {
844
- method: request.method,
845
- headers: normalizeHeaders(request.headers),
846
- body: rawBody,
847
- query: Object.fromEntries(url.searchParams.entries())
848
- });
849
- send(response, result.status, result.body ?? { ok: true, accepted: result.accepted, duplicates: result.duplicates, ignored: result.ignored }, result.headers);
850
- }
851
- catch (error) {
852
- if (error instanceof ConnectionEventError) {
853
- send(response, error.status, { error: error.message });
854
- return;
855
- }
856
- throw error;
857
- }
858
- return;
859
- }
860
- if (request.method === "POST" && url.pathname === "/runs") {
861
- if (!apiRunCreationEnabled(runtime, options)) {
862
- send(response, 403, { error: "API run creation is disabled." });
863
- return;
864
- }
865
- if (!await authorizeOrSend(request, response, runtime, options, "run-create", url.pathname))
866
- return;
867
- if (!await rateLimitOrSend(request, response, rateLimit, "run-create", url.pathname))
868
- return;
869
- const { body } = await readBody(request, maxRequestBodyBytes(runtime, options));
870
- const runOptions = {
871
- message: typeof body.message === "string" ? body.message : "",
872
- approve: body.approve === true,
873
- channel: typeof body.channel === "string" ? body.channel : "http"
874
- };
875
- if (typeof body.toolName === "string")
876
- Object.assign(runOptions, { toolName: body.toolName });
877
- if (body.input !== undefined)
878
- Object.assign(runOptions, { input: body.input });
879
- if (typeof body.conversationId === "string")
880
- Object.assign(runOptions, { conversationId: body.conversationId });
881
- if (typeof body.projectId === "string")
882
- Object.assign(runOptions, { projectId: body.projectId });
883
- if (typeof body.workspaceId === "string")
884
- Object.assign(runOptions, { workspaceId: body.workspaceId });
885
- if (typeof body.tenantId === "string")
886
- Object.assign(runOptions, { tenantId: body.tenantId });
887
- if (typeof body.userId === "string")
888
- Object.assign(runOptions, { userId: body.userId });
889
- if (body.sandboxCredentials && typeof body.sandboxCredentials === "object" && !Array.isArray(body.sandboxCredentials)) {
890
- Object.assign(runOptions, { sandboxCredentials: body.sandboxCredentials });
891
- }
892
- if (body.eval !== undefined) {
893
- if (!evalRunsEnabled(runtime, options)) {
894
- send(response, 403, { error: "Eval run options are disabled. Set ASSEMBLY_LINE_ENABLE_EVAL_RUNS=true or allowEvalRuns." });
895
- return;
896
- }
897
- try {
898
- Object.assign(runOptions, { eval: validateEvalRunOptions(body.eval) });
899
- }
900
- catch (error) {
901
- send(response, 400, { error: error instanceof Error ? error.message : String(error) });
902
- return;
903
- }
904
- }
905
- try {
906
- if (body.stream === true) {
907
- await serveStreamingRun(runtime, response, (onRunCreated) => runtime.run({ ...runOptions, onRunCreated }), (result) => summarizeRun(result));
908
- return;
909
- }
910
- const result = await runtime.run(runOptions);
911
- send(response, 200, summarizeRun(result));
912
- }
913
- catch (error) {
914
- if (error instanceof RunCapacityError) {
915
- send(response, 429, { error: "Run capacity exhausted. Retry later.", retryAfterMs: error.retryAfterMs }, {
916
- "retry-after": String(retryAfterSeconds(error.retryAfterMs))
917
- });
918
- return;
919
- }
920
- throw error;
921
- }
922
- return;
923
- }
924
- const requestMethod = request.method;
925
- const routeMatch = requestMethod ? matchRoute(options.manifest.routeTable, requestMethod, url.pathname) : undefined;
926
- if (routeMatch && requestMethod) {
927
- channel = routeMatch.route.channel;
928
- const authorization = await authorizeOrSend(request, response, runtime, options, "provider-channel", url.pathname, channel);
929
- if (!authorization)
930
- return;
931
- if (!await rateLimitOrSend(request, response, rateLimit, "provider-channel", url.pathname, channel))
932
- return;
933
- const { rawBody, rawBodyBytes, body } = await readBody(request, maxRequestBodyBytes(runtime, options));
934
- const result = await runtime.dispatchHttpChannel(routeMatch.route.channel, {
935
- method: requestMethod,
936
- url: request.url,
937
- path: url.pathname,
938
- params: routeMatch.params,
939
- headers: normalizeHeaders(request.headers),
940
- rawBody,
941
- rawBodyBytes,
942
- body,
943
- trusted: authorization.trusted === true
944
- });
945
- send(response, result.status, result.body, result.headers);
98
+ const routeContext = {
99
+ runtime,
100
+ options,
101
+ rateLimit,
102
+ drain,
103
+ setChannel(value) {
104
+ channel = value;
105
+ },
106
+ };
107
+ if ((await handleCoreHttpRoutes(request, response, url, routeContext)) ||
108
+ (await handleConversationHttpRoutes(request, response, url, routeContext)) ||
109
+ (await handleAdminHttpRoutes(request, response, url, routeContext)) ||
110
+ (await handleRunHttpRoutes(request, response, url, routeContext)) ||
111
+ (await handleIngressHttpRoutes(request, response, url, routeContext))) {
946
112
  return;
947
113
  }
948
114
  send(response, 404, { error: "Not found." });
@@ -957,7 +123,9 @@ function createNodeRuntimeServerForRuntime(runtime, options, drain = { draining:
957
123
  // to callers.
958
124
  runtime.logger.error("unhandled runtime http error", {
959
125
  error: error instanceof Error ? error.message : String(error),
960
- ...(error instanceof Error && error.stack ? { stack: error.stack } : {})
126
+ ...(error instanceof Error && error.stack
127
+ ? { stack: error.stack }
128
+ : {}),
961
129
  });
962
130
  send(response, 500, { error: "Internal server error." });
963
131
  }
@@ -982,11 +150,12 @@ export async function listenNodeRuntime(options) {
982
150
  if (scheduler)
983
151
  server.once("close", stopScheduler);
984
152
  const env = options.env ?? runtime.env ?? process.env;
985
- if (env.ASSEMBLY_LINE_RUN_RECOVERY !== "false" && env.ASSEMBLY_LINE_RUN_RECOVERY !== "0") {
153
+ if (env.ASSEMBLY_LINE_RUN_RECOVERY !== "false" &&
154
+ env.ASSEMBLY_LINE_RUN_RECOVERY !== "0") {
986
155
  // Repair runs orphaned by a previous process before accepting new traffic.
987
156
  await runtime.recoverIncompleteRuns().catch((error) => {
988
157
  runtime.logger.warn("boot run recovery failed", {
989
- error: error instanceof Error ? error.message : String(error)
158
+ error: error instanceof Error ? error.message : String(error),
990
159
  });
991
160
  });
992
161
  }
@@ -1007,14 +176,14 @@ export async function listenNodeRuntime(options) {
1007
176
  env,
1008
177
  stopIngress,
1009
178
  stopScheduler,
1010
- stopWorkers
179
+ stopWorkers,
1011
180
  };
1012
181
  if (scheduler)
1013
182
  handleInput.scheduler = scheduler;
1014
183
  if (ingress)
1015
184
  handleInput.ingress = ingress;
1016
185
  const handle = createNodeRuntimeHandle(handleInput);
1017
- // Hosts like Railway send SIGTERM on every redeploy/restart. With no handler
186
+ // Hosted orchestrators send SIGTERM on redeploy/restart. With no handler
1018
187
  // installed, Node terminates immediately and the graceful drain never runs, so
1019
188
  // in-flight runs are orphaned. Install the handlers by default (opt-out via
1020
189
  // option or ASSEMBLY_LINE_SIGNAL_HANDLERS) so redeploys drain instead of hard-kill.
@@ -1025,13 +194,6 @@ export async function listenNodeRuntime(options) {
1025
194
  }
1026
195
  return handle;
1027
196
  }
1028
- function revisionHealth(manifest) {
1029
- return {
1030
- ok: true,
1031
- agentRevision: manifest.agentRevision,
1032
- buildRevision: manifest.buildRevision ?? manifest.agentRevision
1033
- };
1034
- }
1035
197
  function once(fn) {
1036
198
  let done = false;
1037
199
  return () => {