@assemblyline-agents/node 2.0.0 → 3.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 (47) hide show
  1. package/LICENSE +1 -1
  2. package/dist/adapters.d.ts +4 -1
  3. package/dist/adapters.d.ts.map +1 -1
  4. package/dist/adapters.js +38 -18
  5. package/dist/adapters.js.map +1 -1
  6. package/dist/auth.d.ts +2 -2
  7. package/dist/auth.d.ts.map +1 -1
  8. package/dist/auth.js +10 -22
  9. package/dist/auth.js.map +1 -1
  10. package/dist/http-io.d.ts.map +1 -1
  11. package/dist/http-io.js +14 -3
  12. package/dist/http-io.js.map +1 -1
  13. package/dist/index.d.ts +5 -4
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +242 -33
  16. package/dist/index.js.map +1 -1
  17. package/dist/lifecycle.d.ts +2 -2
  18. package/dist/lifecycle.d.ts.map +1 -1
  19. package/dist/lifecycle.js +2 -2
  20. package/dist/lifecycle.js.map +1 -1
  21. package/dist/memory-admin-http.d.ts +27 -0
  22. package/dist/memory-admin-http.d.ts.map +1 -0
  23. package/dist/memory-admin-http.js +115 -0
  24. package/dist/memory-admin-http.js.map +1 -0
  25. package/dist/rate-limit.d.ts +4 -4
  26. package/dist/rate-limit.d.ts.map +1 -1
  27. package/dist/rate-limit.js +9 -7
  28. package/dist/rate-limit.js.map +1 -1
  29. package/dist/route-match.d.ts +4 -0
  30. package/dist/route-match.d.ts.map +1 -1
  31. package/dist/route-match.js +8 -0
  32. package/dist/route-match.js.map +1 -1
  33. package/dist/run-replay-http.d.ts +11 -0
  34. package/dist/run-replay-http.d.ts.map +1 -0
  35. package/dist/run-replay-http.js +16 -0
  36. package/dist/run-replay-http.js.map +1 -0
  37. package/dist/secrets.d.ts +19 -0
  38. package/dist/secrets.d.ts.map +1 -0
  39. package/dist/secrets.js +58 -0
  40. package/dist/secrets.js.map +1 -0
  41. package/dist/usage-http.js +10 -10
  42. package/dist/usage-http.js.map +1 -1
  43. package/dist/workspace-admin-http.d.ts +5 -0
  44. package/dist/workspace-admin-http.d.ts.map +1 -0
  45. package/dist/workspace-admin-http.js +128 -0
  46. package/dist/workspace-admin-http.js.map +1 -0
  47. package/package.json +11 -12
package/dist/index.js CHANGED
@@ -1,18 +1,23 @@
1
1
  import { createServer } from "node:http";
2
2
  import { resolve } from "node:path";
3
3
  import { codexAgentHarness } from "@assemblyline-agents/codex";
4
- import { adapter, applyEnvAliases, mirrorEnvAliasesInPlace } from "@assemblyline-agents/core";
5
- import { AssemblyLineRuntime, RunCapacityError, loadRuntimeBundle, queryAgentRuns, retryAfterSeconds, validateEvalRunOptions } from "@assemblyline-agents/runtime";
6
- import { createBlobAdapter, createConnectionStores, createSandboxAdapter, createSelfImprovementStores, createStateAdapter, loadRunTimeline } from "./adapters.js";
4
+ import { adapter, ConnectionEventError } from "@assemblyline-agents/core";
5
+ import { AssemblyLineRuntime, RunReplayError, RunCapacityError, durableAgentScopeForManifest, loadRuntimeBundle, queryAgentRuns, retryAfterSeconds, validateEvalRunOptions } from "@assemblyline-agents/runtime";
6
+ import { deleteMemoryAdmin, listMemoryAdmin, parseMemoryAdminRoute, readMemoryAdmin } from "./memory-admin-http.js";
7
+ import { createBlobAdapter, createConnectionStores, createMediaProcessors, createSandboxAdapter, createSelfImprovementStores, createStateAdapter, loadRunTimeline, loadRunTimelinePage } from "./adapters.js";
7
8
  import { apiRunCreationEnabled, evalRunsEnabled, authorizeOrSend, logHttpRequest, numberBodyValue, numberQueryValue, rateLimitOrSend, stringBodyValue, validateRuntimeHttpSecurity } from "./auth.js";
8
9
  import { HttpRequestError, firstHeaderValue, maxRequestBodyBytes, normalizeHeaders, readBody, send, summarizeRun } from "./http-io.js";
9
10
  import { serveRunEventStream, serveStreamingRun } from "./run-stream-http.js";
10
- import { matchRoute, parseAgentControlRoute, parseRunFeedbackRoute, parseRunResumeRoute, parseRunRoute, parseSelfImprovementRoute } from "./route-match.js";
11
+ import { resolveSecretsEnv } from "./secrets.js";
12
+ import { startRunReplay } from "./run-replay-http.js";
13
+ import { matchRoute, parseAgentControlRoute, parseRunFeedbackRoute, parseRunReplayRoute, parseRunResumeRoute, parseRunRoute, parseSelfImprovementRoute } from "./route-match.js";
11
14
  import { resolveIngressRateLimit } from "./rate-limit.js";
12
15
  import { createNodeRuntimeHandle, installSignalHandlers } from "./lifecycle.js";
13
16
  import { DIRECT_CHANNEL, conversationBelongsToAgent, decodeConversationCursor, decodeMessageCursor, directAgentScope, directConversationPatch, directTurnFromBody, encodeConversationCursor, encodeMessageCursor, parseConversationHttpRoute } from "./direct-conversations-http.js";
14
17
  import { usageHttpResult, usageReconciliationHttpResult } from "./usage-http.js";
18
+ import { isWorkspaceAdminPath, workspaceAdminHttpResult } from "./workspace-admin-http.js";
15
19
  export { resolveProvider } from "./provider-resolver.js";
20
+ export { resolveSecretsEnv } from "./secrets.js";
16
21
  export { createMemoryRateLimiterStore, parseRateLimitSpec, resolveIngressRateLimit } from "./rate-limit.js";
17
22
  export { createNodeRuntimeHandle, installSignalHandlers } from "./lifecycle.js";
18
23
  export function nodeAdapter(options = {}) {
@@ -34,11 +39,17 @@ export function withNodeModelHarnesses(options) {
34
39
  }
35
40
  export async function createProductionRuntimeOptions(options) {
36
41
  const artifactRoot = resolve(options.artifactRoot);
37
- const env = options.env ?? process.env;
38
42
  const runtimeOptions = loadRuntimeBundle(artifactRoot);
39
43
  runtimeOptions.devMode = options.devMode ?? false;
44
+ const env = await resolveSecretsEnv({
45
+ manifest: runtimeOptions.manifest,
46
+ env: options.env ?? process.env,
47
+ artifactRoot,
48
+ devMode: options.devMode ?? false
49
+ });
40
50
  const state = await createStateAdapter(runtimeOptions, artifactRoot, env);
41
51
  const blob = await createBlobAdapter(runtimeOptions, artifactRoot, env);
52
+ const mediaProcessors = await createMediaProcessors(runtimeOptions, artifactRoot, env);
42
53
  const connectionStores = createConnectionStores(runtimeOptions, artifactRoot, env, state, options.devMode ?? false);
43
54
  const selfImprovementStores = createSelfImprovementStores(artifactRoot, env, state);
44
55
  const sandbox = await createSandboxAdapter(runtimeOptions, artifactRoot, env);
@@ -46,9 +57,11 @@ export async function createProductionRuntimeOptions(options) {
46
57
  ...runtimeOptions,
47
58
  state,
48
59
  blob,
60
+ mediaProcessors,
49
61
  sandbox,
50
62
  connectionGrantStore: connectionStores.grantStore,
51
63
  connectionAuthorizationSessionStore: connectionStores.authorizationSessionStore,
64
+ connectionEventStore: connectionStores.eventStore,
52
65
  skillStore: selfImprovementStores.skillStore,
53
66
  learningStore: selfImprovementStores.learningStore,
54
67
  scheduleStore: selfImprovementStores.scheduleStore,
@@ -56,11 +69,11 @@ export async function createProductionRuntimeOptions(options) {
56
69
  env,
57
70
  devMode: options.devMode ?? false
58
71
  };
59
- const callbackBaseUrl = env.OPENEVE_CONNECTION_CALLBACK_BASE_URL ?? env.OPENEVE_PUBLIC_URL;
72
+ const callbackBaseUrl = env.ASSEMBLY_LINE_CONNECTION_CALLBACK_BASE_URL ?? env.ASSEMBLY_LINE_PUBLIC_URL;
60
73
  if (callbackBaseUrl)
61
74
  out.connectionCallbackBaseUrl = callbackBaseUrl;
62
75
  // Production default: bound run concurrency unless the operator chose a limit.
63
- if (!(options.devMode ?? false) && env.OPENEVE_MAX_CONCURRENT_RUNS === undefined) {
76
+ if (!(options.devMode ?? false) && env.ASSEMBLY_LINE_MAX_CONCURRENT_RUNS === undefined) {
64
77
  out.maxConcurrentRuns = 16;
65
78
  }
66
79
  return withNodeModelHarnesses(out);
@@ -88,7 +101,7 @@ function createNodeRuntimeServerForRuntime(runtime, options, drain = { draining:
88
101
  if (request.method === "GET" && url.pathname === "/health") {
89
102
  if (!await authorizeOrSend(request, response, runtime, options, "public-health", url.pathname))
90
103
  return;
91
- send(response, 200, { ok: true, agentRevision: options.manifest.agentRevision });
104
+ send(response, 200, revisionHealth(options.manifest));
92
105
  return;
93
106
  }
94
107
  if (request.method === "GET" && url.pathname === "/healthz") {
@@ -97,6 +110,7 @@ function createNodeRuntimeServerForRuntime(runtime, options, drain = { draining:
97
110
  send(response, 200, {
98
111
  ok: true,
99
112
  agentRevision: options.manifest.agentRevision,
113
+ buildRevision: options.manifest.buildRevision ?? options.manifest.agentRevision,
100
114
  capabilities: evalRunsEnabled(runtime, options) ? ["eval-runs"] : []
101
115
  });
102
116
  return;
@@ -107,10 +121,10 @@ function createNodeRuntimeServerForRuntime(runtime, options, drain = { draining:
107
121
  if (!await authorizeOrSend(request, response, runtime, options, "public-health", url.pathname))
108
122
  return;
109
123
  if (drain.draining) {
110
- send(response, 503, { ok: false, draining: true });
124
+ send(response, 503, { ...revisionHealth(options.manifest), ok: false, draining: true });
111
125
  return;
112
126
  }
113
- send(response, 200, { ok: true, agentRevision: options.manifest.agentRevision });
127
+ send(response, 200, revisionHealth(options.manifest));
114
128
  return;
115
129
  }
116
130
  if (request.method === "GET" && url.pathname === "/manifest") {
@@ -149,6 +163,25 @@ function createNodeRuntimeServerForRuntime(runtime, options, drain = { draining:
149
163
  send(response, 200, result);
150
164
  return;
151
165
  }
166
+ if (isWorkspaceAdminPath(url.pathname)) {
167
+ const method = request.method ?? "GET";
168
+ if (!["GET", "POST", "DELETE"].includes(method)) {
169
+ send(response, 405, { error: "Method not allowed." });
170
+ return;
171
+ }
172
+ if (!await authorizeOrSend(request, response, runtime, options, method === "GET" ? "admin-read" : "agent-control", url.pathname))
173
+ return;
174
+ try {
175
+ const body = method === "POST"
176
+ ? (await readBody(request, maxRequestBodyBytes(runtime, options))).body
177
+ : {};
178
+ send(response, 200, await workspaceAdminHttpResult(runtime, method, url.pathname, body, url.searchParams));
179
+ }
180
+ catch (error) {
181
+ send(response, 400, { error: error instanceof Error ? error.message : String(error) });
182
+ }
183
+ return;
184
+ }
152
185
  const conversationRoute = parseConversationHttpRoute(url.pathname);
153
186
  if (conversationRoute?.kind === "collection" && request.method === "GET") {
154
187
  if (!await authorizeOrSend(request, response, runtime, options, "admin-read", url.pathname))
@@ -277,12 +310,21 @@ function createNodeRuntimeServerForRuntime(runtime, options, drain = { draining:
277
310
  send(response, 400, { error: "A message or attachment is required." });
278
311
  return;
279
312
  }
313
+ const workspaceId = stringBodyValue(body.workspaceId);
314
+ const projectId = stringBodyValue(body.projectId);
315
+ const tenantId = stringBodyValue(body.tenantId);
280
316
  const runOptions = {
281
317
  message: direct.turn.message,
282
318
  channel: DIRECT_CHANNEL,
283
319
  approve: direct.approve,
284
- turn: direct.turn
320
+ turn: direct.turn,
321
+ ...(workspaceId ? { workspaceId } : {}),
322
+ ...(projectId ? { projectId } : {}),
323
+ ...(tenantId ? { tenantId } : {})
285
324
  };
325
+ if (body.sandboxCredentials && typeof body.sandboxCredentials === "object" && !Array.isArray(body.sandboxCredentials)) {
326
+ Object.assign(runOptions, { sandboxCredentials: body.sandboxCredentials });
327
+ }
286
328
  try {
287
329
  if (direct.stream) {
288
330
  await serveStreamingRun(runtime, response, (onRunCreated) => runtime.run({ ...runOptions, onRunCreated }), (result) => summarizeRun(result));
@@ -312,20 +354,45 @@ function createNodeRuntimeServerForRuntime(runtime, options, drain = { draining:
312
354
  send(response, 200, await queryAgentRuns(runtime.state, options.manifest, { includeDetails: false, limit }));
313
355
  return;
314
356
  }
357
+ const memoryAdminRoute = parseMemoryAdminRoute(url.pathname);
358
+ if (memoryAdminRoute) {
359
+ const agentScope = durableAgentScopeForManifest(options.manifest);
360
+ if (request.method === "GET") {
361
+ if (!await authorizeOrSend(request, response, runtime, options, "admin-read", url.pathname))
362
+ return;
363
+ const result = memoryAdminRoute.kind === "list"
364
+ ? await listMemoryAdmin(runtime.state, agentScope, url.searchParams)
365
+ : await readMemoryAdmin(runtime.state, agentScope, memoryAdminRoute.path);
366
+ send(response, result.status, result.body);
367
+ return;
368
+ }
369
+ if (request.method === "DELETE" && memoryAdminRoute.kind === "document") {
370
+ if (!await authorizeOrSend(request, response, runtime, options, "agent-control", url.pathname))
371
+ return;
372
+ if (!await rateLimitOrSend(request, response, rateLimit, "agent-control", url.pathname))
373
+ return;
374
+ const result = await deleteMemoryAdmin(runtime.state, agentScope, memoryAdminRoute.path, url.searchParams);
375
+ send(response, result.status, result.body);
376
+ return;
377
+ }
378
+ send(response, 405, { error: "Method not allowed." });
379
+ return;
380
+ }
315
381
  const selfImprovementRoute = parseSelfImprovementRoute(url.pathname);
382
+ const selfImprovementSurface = url.searchParams.get("surface")?.trim() || "root";
316
383
  if (selfImprovementRoute && request.method === "GET") {
317
384
  if (!await authorizeOrSend(request, response, runtime, options, "admin-read", url.pathname))
318
385
  return;
319
386
  if (selfImprovementRoute.kind === "pending") {
320
- send(response, 200, { changes: await runtime.listPendingSkillChanges() });
387
+ send(response, 200, { surface: selfImprovementSurface, changes: await runtime.listPendingSkillChanges(selfImprovementSurface) });
321
388
  return;
322
389
  }
323
390
  if (selfImprovementRoute.kind === "reviews") {
324
- send(response, 200, { reviews: await runtime.listBackgroundReviews() });
391
+ send(response, 200, { surface: selfImprovementSurface, reviews: await runtime.listBackgroundReviews(selfImprovementSurface) });
325
392
  return;
326
393
  }
327
394
  if (selfImprovementRoute.kind === "revisions") {
328
- send(response, 200, { name: selfImprovementRoute.name, revisions: await runtime.listSkillRevisions(selfImprovementRoute.name) });
395
+ send(response, 200, { surface: selfImprovementSurface, name: selfImprovementRoute.name, revisions: await runtime.listSkillRevisions(selfImprovementRoute.name, selfImprovementSurface) });
329
396
  return;
330
397
  }
331
398
  send(response, 405, { error: "Method not allowed." });
@@ -338,7 +405,7 @@ function createNodeRuntimeServerForRuntime(runtime, options, drain = { draining:
338
405
  return;
339
406
  if (selfImprovementRoute.kind === "approve") {
340
407
  try {
341
- send(response, 200, await runtime.approveSkillChange(selfImprovementRoute.id));
408
+ send(response, 200, await runtime.approveSkillChange(selfImprovementRoute.id, selfImprovementSurface));
342
409
  }
343
410
  catch (error) {
344
411
  if (error instanceof Error && /not found/i.test(error.message)) {
@@ -351,8 +418,8 @@ function createNodeRuntimeServerForRuntime(runtime, options, drain = { draining:
351
418
  }
352
419
  if (selfImprovementRoute.kind === "reject") {
353
420
  try {
354
- await runtime.rejectSkillChange(selfImprovementRoute.id);
355
- send(response, 200, { rejected: true, id: selfImprovementRoute.id });
421
+ await runtime.rejectSkillChange(selfImprovementRoute.id, selfImprovementSurface);
422
+ send(response, 200, { rejected: true, id: selfImprovementRoute.id, surface: selfImprovementSurface });
356
423
  }
357
424
  catch (error) {
358
425
  if (error instanceof Error && /not found/i.test(error.message)) {
@@ -371,7 +438,7 @@ function createNodeRuntimeServerForRuntime(runtime, options, drain = { draining:
371
438
  return;
372
439
  }
373
440
  try {
374
- send(response, 200, await runtime.restoreSkillRevision(selfImprovementRoute.name, revision));
441
+ send(response, 200, await runtime.restoreSkillRevision(selfImprovementRoute.name, revision, selfImprovementSurface));
375
442
  }
376
443
  catch (error) {
377
444
  if (error instanceof Error && /not found/i.test(error.message)) {
@@ -476,6 +543,31 @@ function createNodeRuntimeServerForRuntime(runtime, options, drain = { draining:
476
543
  send(response, 200, { ok: true, matched: results.length, results });
477
544
  return;
478
545
  }
546
+ if (request.method === "POST" && url.pathname === "/assembly-line/connections/events/reconcile") {
547
+ if (!await authorizeOrSend(request, response, runtime, options, "agent-control", url.pathname))
548
+ return;
549
+ const { body } = await readBody(request, maxRequestBodyBytes(runtime, options));
550
+ const principal = stringBodyValue(body.userId) && stringBodyValue(body.issuer)
551
+ ? { type: "user", id: stringBodyValue(body.userId), issuer: stringBodyValue(body.issuer) }
552
+ : undefined;
553
+ const connection = stringBodyValue(body.connection);
554
+ const publicUrl = stringBodyValue(body.publicUrl);
555
+ const results = await runtime.reconcileConnectionEvents({
556
+ ...(connection ? { connection } : {}),
557
+ ...(publicUrl ? { publicUrl } : {}),
558
+ ...(principal ? { principal } : {}),
559
+ force: body.force === true
560
+ });
561
+ send(response, 200, { ok: true, results });
562
+ return;
563
+ }
564
+ if (request.method === "GET" && url.pathname === "/assembly-line/connections/events") {
565
+ if (!await authorizeOrSend(request, response, runtime, options, "admin-read", url.pathname))
566
+ return;
567
+ const results = await runtime.checkConnectionEvents(url.searchParams.get("connection") ?? undefined);
568
+ send(response, 200, { ok: true, results });
569
+ return;
570
+ }
479
571
  if (request.method === "GET" && url.pathname.startsWith("/runs/")) {
480
572
  if (!await authorizeOrSend(request, response, runtime, options, "admin-read", url.pathname))
481
573
  return;
@@ -493,7 +585,30 @@ function createNodeRuntimeServerForRuntime(runtime, options, drain = { draining:
493
585
  await serveRunEventStream(runtime, response, runRoute.runId, lastEventId);
494
586
  return;
495
587
  }
496
- const timeline = await loadRunTimeline(runtime, runRoute.runId);
588
+ const paginatedTimeline = runRoute.kind === "timeline" && url.searchParams.has("limit");
589
+ const before = numberQueryValue(url.searchParams.get("before"));
590
+ const after = numberQueryValue(url.searchParams.get("after"));
591
+ const limit = numberQueryValue(url.searchParams.get("limit"));
592
+ if (paginatedTimeline && limit === undefined) {
593
+ send(response, 400, { error: "Run timeline limit must be a number." });
594
+ return;
595
+ }
596
+ let timeline;
597
+ try {
598
+ timeline = paginatedTimeline
599
+ ? await loadRunTimelinePage(runtime, runRoute.runId, {
600
+ ...(before !== undefined ? { before } : {}),
601
+ ...(after !== undefined ? { after } : {}),
602
+ limit: limit
603
+ })
604
+ : await loadRunTimeline(runtime, runRoute.runId);
605
+ }
606
+ catch (error) {
607
+ if (!paginatedTimeline)
608
+ throw error;
609
+ send(response, 400, { error: error instanceof Error ? error.message : String(error) });
610
+ return;
611
+ }
497
612
  if (!timeline) {
498
613
  send(response, 404, { error: `Run not found: ${runRoute.runId}` });
499
614
  return;
@@ -510,6 +625,38 @@ function createNodeRuntimeServerForRuntime(runtime, options, drain = { draining:
510
625
  return;
511
626
  }
512
627
  if (request.method === "POST" && url.pathname.startsWith("/runs/")) {
628
+ const replayRoute = parseRunReplayRoute(url.pathname);
629
+ if (replayRoute) {
630
+ if (!await authorizeOrSend(request, response, runtime, options, "agent-control", url.pathname))
631
+ return;
632
+ if (!await rateLimitOrSend(request, response, rateLimit, "run-create", url.pathname))
633
+ return;
634
+ try {
635
+ const replay = await startRunReplay(runtime, replayRoute.runId);
636
+ void replay.completion.catch((error) => {
637
+ runtime.logger.error("replay run failed after durable creation", {
638
+ sourceRunId: replayRoute.runId,
639
+ runId: replay.run.id,
640
+ error: error instanceof Error ? error.message : String(error)
641
+ });
642
+ });
643
+ send(response, 202, { runId: replay.run.id, status: replay.run.status });
644
+ }
645
+ catch (error) {
646
+ if (error instanceof RunReplayError) {
647
+ send(response, error.code === "run_not_found" ? 404 : 409, { error: error.message, code: error.code });
648
+ return;
649
+ }
650
+ if (error instanceof RunCapacityError) {
651
+ send(response, 429, { error: "Run capacity exhausted. Retry later.", retryAfterMs: error.retryAfterMs }, {
652
+ "retry-after": String(retryAfterSeconds(error.retryAfterMs))
653
+ });
654
+ return;
655
+ }
656
+ throw error;
657
+ }
658
+ return;
659
+ }
513
660
  const feedbackRoute = parseRunFeedbackRoute(url.pathname);
514
661
  if (feedbackRoute) {
515
662
  if (!await authorizeOrSend(request, response, runtime, options, "run-control", url.pathname))
@@ -560,7 +707,18 @@ function createNodeRuntimeServerForRuntime(runtime, options, drain = { draining:
560
707
  send(response, 409, { error: `Run ${resumeRoute.runId} is not ${expected} (status: ${run.status}).` });
561
708
  return;
562
709
  }
563
- if (resumeRoute.kind === "cancel" && ["completed", "failed", "cancelled"].includes(run.status)) {
710
+ if (resumeRoute.kind === "cancel" && run.status === "cancelled") {
711
+ // Cancellation is an idempotent operator command. A retry may be
712
+ // the first response the client receives even when an earlier
713
+ // request already committed the terminal state.
714
+ send(response, 200, {
715
+ runId: run.id,
716
+ status: run.status,
717
+ controlIntent: run.controlIntent ?? null
718
+ });
719
+ return;
720
+ }
721
+ if (resumeRoute.kind === "cancel" && ["completed", "failed"].includes(run.status)) {
564
722
  send(response, 409, { error: `Run ${resumeRoute.runId} is already terminal (status: ${run.status}).` });
565
723
  return;
566
724
  }
@@ -601,6 +759,17 @@ function createNodeRuntimeServerForRuntime(runtime, options, drain = { draining:
601
759
  }
602
760
  // The resume idempotency claim rejects a concurrent/replayed resume.
603
761
  const message = error instanceof Error ? error.message : String(error);
762
+ if (resumeRoute.kind === "cancel" && /already terminal/i.test(message)) {
763
+ const latest = await runtime.state.getRun(resumeRoute.runId);
764
+ if (latest?.status === "cancelled") {
765
+ send(response, 200, {
766
+ runId: latest.id,
767
+ status: latest.status,
768
+ controlIntent: latest.controlIntent ?? null
769
+ });
770
+ return;
771
+ }
772
+ }
604
773
  if (/already in progress|not waiting|not suspended|not running|already terminal/i.test(message)) {
605
774
  send(response, 409, { error: message });
606
775
  return;
@@ -664,6 +833,33 @@ function createNodeRuntimeServerForRuntime(runtime, options, drain = { draining:
664
833
  send(response, result.status, result.body);
665
834
  return;
666
835
  }
836
+ const connectionEvent = /^\/assembly-line\/connections\/([^/]+)\/events\/([^/]+)$/u.exec(url.pathname);
837
+ if ((request.method === "GET" || request.method === "POST") && connectionEvent?.[1] && connectionEvent[2]) {
838
+ if (!await authorizeOrSend(request, response, runtime, options, "connection-event", url.pathname))
839
+ return;
840
+ if (!await rateLimitOrSend(request, response, rateLimit, "connection-event", url.pathname))
841
+ return;
842
+ const rawBody = request.method === "POST"
843
+ ? (await readBody(request, maxRequestBodyBytes(runtime, options))).rawBody
844
+ : "";
845
+ try {
846
+ const result = await runtime.acceptConnectionEvent(decodeURIComponent(connectionEvent[1]), decodeURIComponent(connectionEvent[2]), {
847
+ method: request.method,
848
+ headers: normalizeHeaders(request.headers),
849
+ body: rawBody,
850
+ query: Object.fromEntries(url.searchParams.entries())
851
+ });
852
+ send(response, result.status, result.body ?? { ok: true, accepted: result.accepted, duplicates: result.duplicates, ignored: result.ignored }, result.headers);
853
+ }
854
+ catch (error) {
855
+ if (error instanceof ConnectionEventError) {
856
+ send(response, error.status, { error: error.message });
857
+ return;
858
+ }
859
+ throw error;
860
+ }
861
+ return;
862
+ }
667
863
  if (request.method === "POST" && url.pathname === "/runs") {
668
864
  if (!apiRunCreationEnabled(runtime, options)) {
669
865
  send(response, 403, { error: "API run creation is disabled." });
@@ -683,9 +879,22 @@ function createNodeRuntimeServerForRuntime(runtime, options, drain = { draining:
683
879
  Object.assign(runOptions, { toolName: body.toolName });
684
880
  if (body.input !== undefined)
685
881
  Object.assign(runOptions, { input: body.input });
882
+ if (typeof body.conversationId === "string")
883
+ Object.assign(runOptions, { conversationId: body.conversationId });
884
+ if (typeof body.projectId === "string")
885
+ Object.assign(runOptions, { projectId: body.projectId });
886
+ if (typeof body.workspaceId === "string")
887
+ Object.assign(runOptions, { workspaceId: body.workspaceId });
888
+ if (typeof body.tenantId === "string")
889
+ Object.assign(runOptions, { tenantId: body.tenantId });
890
+ if (typeof body.userId === "string")
891
+ Object.assign(runOptions, { userId: body.userId });
892
+ if (body.sandboxCredentials && typeof body.sandboxCredentials === "object" && !Array.isArray(body.sandboxCredentials)) {
893
+ Object.assign(runOptions, { sandboxCredentials: body.sandboxCredentials });
894
+ }
686
895
  if (body.eval !== undefined) {
687
896
  if (!evalRunsEnabled(runtime, options)) {
688
- send(response, 403, { error: "Eval run options are disabled. Set OPENEVE_ENABLE_EVAL_RUNS=true or allowEvalRuns." });
897
+ send(response, 403, { error: "Eval run options are disabled. Set ASSEMBLY_LINE_ENABLE_EVAL_RUNS=true or allowEvalRuns." });
689
898
  return;
690
899
  }
691
900
  try {
@@ -757,14 +966,7 @@ function createNodeRuntimeServerForRuntime(runtime, options, drain = { draining:
757
966
  }
758
967
  });
759
968
  }
760
- export async function listenNodeRuntime(rawOptions) {
761
- // Rename compatibility: make every branded env var readable under both its
762
- // ASSEMBLY_LINE_* and OPENEVE_* spellings for all downstream readers.
763
- const options = rawOptions.env
764
- ? { ...rawOptions, env: applyEnvAliases(rawOptions.env).env }
765
- : rawOptions;
766
- if (!rawOptions.env)
767
- mirrorEnvAliasesInPlace(process.env);
969
+ export async function listenNodeRuntime(options) {
768
970
  const runtime = new AssemblyLineRuntime(withNodeModelHarnesses(options));
769
971
  runtime.boot();
770
972
  validateRuntimeHttpSecurity(runtime, options);
@@ -783,7 +985,7 @@ export async function listenNodeRuntime(rawOptions) {
783
985
  if (scheduler)
784
986
  server.once("close", stopScheduler);
785
987
  const env = options.env ?? runtime.env ?? process.env;
786
- if (env.OPENEVE_RUN_RECOVERY !== "false" && env.OPENEVE_RUN_RECOVERY !== "0") {
988
+ if (env.ASSEMBLY_LINE_RUN_RECOVERY !== "false" && env.ASSEMBLY_LINE_RUN_RECOVERY !== "0") {
787
989
  // Repair runs orphaned by a previous process before accepting new traffic.
788
990
  await runtime.recoverIncompleteRuns().catch((error) => {
789
991
  runtime.logger.warn("boot run recovery failed", {
@@ -818,14 +1020,21 @@ export async function listenNodeRuntime(rawOptions) {
818
1020
  // Hosts like Railway send SIGTERM on every redeploy/restart. With no handler
819
1021
  // installed, Node terminates immediately and the graceful drain never runs, so
820
1022
  // in-flight runs are orphaned. Install the handlers by default (opt-out via
821
- // option or OPENEVE_SIGNAL_HANDLERS) so redeploys drain instead of hard-kill.
1023
+ // option or ASSEMBLY_LINE_SIGNAL_HANDLERS) so redeploys drain instead of hard-kill.
822
1024
  if (options.installSignalHandlers !== false &&
823
- env.OPENEVE_SIGNAL_HANDLERS !== "false" &&
824
- env.OPENEVE_SIGNAL_HANDLERS !== "0") {
1025
+ env.ASSEMBLY_LINE_SIGNAL_HANDLERS !== "false" &&
1026
+ env.ASSEMBLY_LINE_SIGNAL_HANDLERS !== "0") {
825
1027
  installSignalHandlers(handle);
826
1028
  }
827
1029
  return handle;
828
1030
  }
1031
+ function revisionHealth(manifest) {
1032
+ return {
1033
+ ok: true,
1034
+ agentRevision: manifest.agentRevision,
1035
+ buildRevision: manifest.buildRevision ?? manifest.agentRevision
1036
+ };
1037
+ }
829
1038
  function once(fn) {
830
1039
  let done = false;
831
1040
  return () => {