@acpus/runtime 0.8.0 → 0.9.0-alpha.1

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 (289) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/admission/input.d.ts +5 -0
  3. package/dist/admission/input.d.ts.map +1 -0
  4. package/dist/admission/input.js +17 -0
  5. package/dist/admission/input.js.map +1 -0
  6. package/dist/artifacts/rewrite.d.ts +7 -0
  7. package/dist/artifacts/rewrite.d.ts.map +1 -0
  8. package/dist/artifacts/rewrite.js +28 -0
  9. package/dist/artifacts/rewrite.js.map +1 -0
  10. package/dist/control/agent-overrides.d.ts +4 -0
  11. package/dist/control/agent-overrides.d.ts.map +1 -0
  12. package/dist/control/agent-overrides.js +40 -0
  13. package/dist/control/agent-overrides.js.map +1 -0
  14. package/dist/daemon/loop.d.ts +16 -0
  15. package/dist/daemon/loop.d.ts.map +1 -0
  16. package/dist/daemon/loop.js +191 -0
  17. package/dist/daemon/loop.js.map +1 -0
  18. package/dist/daemon/sessions.d.ts +23 -0
  19. package/dist/daemon/sessions.d.ts.map +1 -0
  20. package/dist/daemon/sessions.js +190 -0
  21. package/dist/daemon/sessions.js.map +1 -0
  22. package/dist/daemon/socket.d.ts +82 -0
  23. package/dist/daemon/socket.d.ts.map +1 -0
  24. package/dist/daemon/socket.js +341 -0
  25. package/dist/daemon/socket.js.map +1 -0
  26. package/dist/daemon/tick.d.ts +9 -0
  27. package/dist/daemon/tick.d.ts.map +1 -0
  28. package/dist/daemon/tick.js +18 -0
  29. package/dist/daemon/tick.js.map +1 -0
  30. package/dist/evaluation/admissible.d.ts +2 -0
  31. package/dist/evaluation/admissible.d.ts.map +1 -0
  32. package/dist/evaluation/admissible.js +50 -0
  33. package/dist/evaluation/admissible.js.map +1 -0
  34. package/dist/evaluation/evaluator.d.ts +14 -0
  35. package/dist/evaluation/evaluator.d.ts.map +1 -0
  36. package/dist/evaluation/evaluator.js +34 -0
  37. package/dist/evaluation/evaluator.js.map +1 -0
  38. package/dist/evaluation/loop-limit.d.ts +4 -0
  39. package/dist/evaluation/loop-limit.d.ts.map +1 -0
  40. package/dist/evaluation/loop-limit.js +9 -0
  41. package/dist/evaluation/loop-limit.js.map +1 -0
  42. package/dist/evaluation/schema.d.ts +4 -0
  43. package/dist/evaluation/schema.d.ts.map +1 -0
  44. package/dist/evaluation/schema.js +92 -0
  45. package/dist/evaluation/schema.js.map +1 -0
  46. package/dist/execution/advance.d.ts +18 -0
  47. package/dist/execution/advance.d.ts.map +1 -0
  48. package/dist/execution/advance.js +57 -0
  49. package/dist/execution/advance.js.map +1 -0
  50. package/dist/execution/agent-node.d.ts +23 -0
  51. package/dist/execution/agent-node.d.ts.map +1 -0
  52. package/dist/execution/agent-node.js +512 -0
  53. package/dist/execution/agent-node.js.map +1 -0
  54. package/dist/execution/duration.d.ts +2 -0
  55. package/dist/execution/duration.d.ts.map +1 -0
  56. package/dist/execution/duration.js +15 -0
  57. package/dist/execution/duration.js.map +1 -0
  58. package/dist/execution/ir.d.ts +3 -0
  59. package/dist/execution/ir.d.ts.map +1 -0
  60. package/dist/execution/ir.js +46 -0
  61. package/dist/execution/ir.js.map +1 -0
  62. package/dist/execution/scheduler.d.ts +59 -0
  63. package/dist/execution/scheduler.d.ts.map +1 -0
  64. package/dist/execution/scheduler.js +306 -0
  65. package/dist/execution/scheduler.js.map +1 -0
  66. package/dist/execution/task-executor.d.ts +14 -0
  67. package/dist/execution/task-executor.d.ts.map +1 -0
  68. package/dist/execution/task-executor.js +186 -0
  69. package/dist/execution/task-executor.js.map +1 -0
  70. package/dist/hooks/config.d.ts +32 -0
  71. package/dist/hooks/config.d.ts.map +1 -0
  72. package/dist/hooks/config.js +112 -0
  73. package/dist/hooks/config.js.map +1 -0
  74. package/dist/hooks/context.d.ts +50 -0
  75. package/dist/hooks/context.d.ts.map +1 -0
  76. package/dist/hooks/context.js +116 -0
  77. package/dist/hooks/context.js.map +1 -0
  78. package/dist/hooks/events.d.ts +22 -0
  79. package/dist/hooks/events.d.ts.map +1 -0
  80. package/dist/hooks/events.js +41 -0
  81. package/dist/hooks/events.js.map +1 -0
  82. package/dist/hooks/journal.d.ts +21 -19
  83. package/dist/hooks/journal.d.ts.map +1 -1
  84. package/dist/hooks/journal.js +1 -48
  85. package/dist/hooks/journal.js.map +1 -1
  86. package/dist/hooks/loader.d.ts +33 -39
  87. package/dist/hooks/loader.d.ts.map +1 -1
  88. package/dist/hooks/loader.js +79 -95
  89. package/dist/hooks/loader.js.map +1 -1
  90. package/dist/hooks/runner.d.ts +14 -41
  91. package/dist/hooks/runner.d.ts.map +1 -1
  92. package/dist/hooks/runner.js +177 -145
  93. package/dist/hooks/runner.js.map +1 -1
  94. package/dist/index.d.ts +13 -28
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +13 -33
  97. package/dist/index.js.map +1 -1
  98. package/dist/runs/advance-runtime.d.ts +50 -0
  99. package/dist/runs/advance-runtime.d.ts.map +1 -0
  100. package/dist/runs/advance-runtime.js +82 -0
  101. package/dist/runs/advance-runtime.js.map +1 -0
  102. package/dist/runs/use-cases.d.ts +111 -0
  103. package/dist/runs/use-cases.d.ts.map +1 -0
  104. package/dist/runs/use-cases.js +539 -0
  105. package/dist/runs/use-cases.js.map +1 -0
  106. package/dist/scheduler/advance.d.ts +65 -0
  107. package/dist/scheduler/advance.d.ts.map +1 -0
  108. package/dist/scheduler/advance.js +435 -0
  109. package/dist/scheduler/advance.js.map +1 -0
  110. package/dist/scheduler/control.d.ts +49 -0
  111. package/dist/scheduler/control.d.ts.map +1 -0
  112. package/dist/scheduler/control.js +212 -0
  113. package/dist/scheduler/control.js.map +1 -0
  114. package/dist/scheduler/event-codec.d.ts +4 -0
  115. package/dist/scheduler/event-codec.d.ts.map +1 -0
  116. package/dist/scheduler/event-codec.js +57 -0
  117. package/dist/scheduler/event-codec.js.map +1 -0
  118. package/dist/scheduler/events.d.ts +193 -0
  119. package/dist/scheduler/events.d.ts.map +1 -0
  120. package/dist/scheduler/events.js +13 -0
  121. package/dist/scheduler/events.js.map +1 -0
  122. package/dist/scheduler/fork-seed.d.ts +38 -0
  123. package/dist/scheduler/fork-seed.d.ts.map +1 -0
  124. package/dist/scheduler/fork-seed.js +590 -0
  125. package/dist/scheduler/fork-seed.js.map +1 -0
  126. package/dist/scheduler/identity.d.ts +8 -0
  127. package/dist/scheduler/identity.d.ts.map +1 -0
  128. package/dist/scheduler/identity.js +49 -0
  129. package/dist/scheduler/identity.js.map +1 -0
  130. package/dist/scheduler/ir-walk.d.ts +3 -0
  131. package/dist/scheduler/ir-walk.d.ts.map +1 -0
  132. package/dist/scheduler/ir-walk.js +25 -0
  133. package/dist/scheduler/ir-walk.js.map +1 -0
  134. package/dist/scheduler/limiter.d.ts +7 -0
  135. package/dist/scheduler/limiter.d.ts.map +1 -0
  136. package/dist/scheduler/limiter.js +10 -0
  137. package/dist/scheduler/limiter.js.map +1 -0
  138. package/dist/scheduler/materialize.d.ts +7 -0
  139. package/dist/scheduler/materialize.d.ts.map +1 -0
  140. package/dist/scheduler/materialize.js +756 -0
  141. package/dist/scheduler/materialize.js.map +1 -0
  142. package/dist/scheduler/membership.d.ts +12 -0
  143. package/dist/scheduler/membership.d.ts.map +1 -0
  144. package/dist/scheduler/membership.js +74 -0
  145. package/dist/scheduler/membership.js.map +1 -0
  146. package/dist/scheduler/node-executor.d.ts +15 -0
  147. package/dist/scheduler/node-executor.d.ts.map +1 -0
  148. package/dist/scheduler/node-executor.js +69 -0
  149. package/dist/scheduler/node-executor.js.map +1 -0
  150. package/dist/scheduler/runtime-runner.d.ts +44 -0
  151. package/dist/scheduler/runtime-runner.d.ts.map +1 -0
  152. package/dist/scheduler/runtime-runner.js +155 -0
  153. package/dist/scheduler/runtime-runner.js.map +1 -0
  154. package/dist/scheduler/scope.d.ts +6 -0
  155. package/dist/scheduler/scope.d.ts.map +1 -0
  156. package/dist/scheduler/scope.js +56 -0
  157. package/dist/scheduler/scope.js.map +1 -0
  158. package/dist/scheduler/store-port.d.ts +211 -0
  159. package/dist/scheduler/store-port.d.ts.map +1 -0
  160. package/dist/scheduler/store-port.js +28 -0
  161. package/dist/scheduler/store-port.js.map +1 -0
  162. package/dist/scheduler/transitions.d.ts +89 -0
  163. package/dist/scheduler/transitions.d.ts.map +1 -0
  164. package/dist/scheduler/transitions.js +967 -0
  165. package/dist/scheduler/transitions.js.map +1 -0
  166. package/dist/scheduler/types.d.ts +146 -0
  167. package/dist/scheduler/types.d.ts.map +1 -0
  168. package/dist/{executors → scheduler}/types.js.map +1 -1
  169. package/dist/store/store.d.ts +390 -0
  170. package/dist/store/store.d.ts.map +1 -0
  171. package/dist/store/store.js +3053 -0
  172. package/dist/store/store.js.map +1 -0
  173. package/dist/visualization/overlay.d.ts +86 -0
  174. package/dist/visualization/overlay.d.ts.map +1 -0
  175. package/dist/visualization/overlay.js +161 -0
  176. package/dist/visualization/overlay.js.map +1 -0
  177. package/package.json +19 -37
  178. package/README.md +0 -7
  179. package/dist/agent-telemetry.d.ts +0 -56
  180. package/dist/agent-telemetry.d.ts.map +0 -1
  181. package/dist/agent-telemetry.js +0 -304
  182. package/dist/agent-telemetry.js.map +0 -1
  183. package/dist/artifacts.d.ts +0 -45
  184. package/dist/artifacts.d.ts.map +0 -1
  185. package/dist/artifacts.js +0 -163
  186. package/dist/artifacts.js.map +0 -1
  187. package/dist/attempt-artifacts.d.ts +0 -35
  188. package/dist/attempt-artifacts.d.ts.map +0 -1
  189. package/dist/attempt-artifacts.js +0 -90
  190. package/dist/attempt-artifacts.js.map +0 -1
  191. package/dist/client.d.ts +0 -82
  192. package/dist/client.d.ts.map +0 -1
  193. package/dist/client.js +0 -200
  194. package/dist/client.js.map +0 -1
  195. package/dist/evaluator.d.ts +0 -36
  196. package/dist/evaluator.d.ts.map +0 -1
  197. package/dist/evaluator.js +0 -101
  198. package/dist/evaluator.js.map +0 -1
  199. package/dist/executors/agent.d.ts +0 -103
  200. package/dist/executors/agent.d.ts.map +0 -1
  201. package/dist/executors/agent.js +0 -516
  202. package/dist/executors/agent.js.map +0 -1
  203. package/dist/executors/mock-program.d.ts +0 -26
  204. package/dist/executors/mock-program.d.ts.map +0 -1
  205. package/dist/executors/mock-program.js +0 -100
  206. package/dist/executors/mock-program.js.map +0 -1
  207. package/dist/executors/output-preview.d.ts +0 -2
  208. package/dist/executors/output-preview.d.ts.map +0 -1
  209. package/dist/executors/output-preview.js +0 -20
  210. package/dist/executors/output-preview.js.map +0 -1
  211. package/dist/executors/program.d.ts +0 -30
  212. package/dist/executors/program.d.ts.map +0 -1
  213. package/dist/executors/program.js +0 -230
  214. package/dist/executors/program.js.map +0 -1
  215. package/dist/executors/types.d.ts +0 -32
  216. package/dist/executors/types.d.ts.map +0 -1
  217. package/dist/fork.d.ts +0 -74
  218. package/dist/fork.d.ts.map +0 -1
  219. package/dist/fork.js +0 -278
  220. package/dist/fork.js.map +0 -1
  221. package/dist/hooks/payload.d.ts +0 -17
  222. package/dist/hooks/payload.d.ts.map +0 -1
  223. package/dist/hooks/payload.js +0 -45
  224. package/dist/hooks/payload.js.map +0 -1
  225. package/dist/interpreter.d.ts +0 -168
  226. package/dist/interpreter.d.ts.map +0 -1
  227. package/dist/interpreter.js +0 -1747
  228. package/dist/interpreter.js.map +0 -1
  229. package/dist/keys.d.ts +0 -77
  230. package/dist/keys.d.ts.map +0 -1
  231. package/dist/keys.js +0 -382
  232. package/dist/keys.js.map +0 -1
  233. package/dist/run-control.d.ts +0 -45
  234. package/dist/run-control.d.ts.map +0 -1
  235. package/dist/run-control.js +0 -255
  236. package/dist/run-control.js.map +0 -1
  237. package/dist/run-id.d.ts +0 -3
  238. package/dist/run-id.d.ts.map +0 -1
  239. package/dist/run-id.js +0 -10
  240. package/dist/run-id.js.map +0 -1
  241. package/dist/state-machine.d.ts +0 -31
  242. package/dist/state-machine.d.ts.map +0 -1
  243. package/dist/state-machine.js +0 -102
  244. package/dist/state-machine.js.map +0 -1
  245. package/dist/store.d.ts +0 -92
  246. package/dist/store.d.ts.map +0 -1
  247. package/dist/store.js +0 -420
  248. package/dist/store.js.map +0 -1
  249. package/dist/supervisor-app.d.ts +0 -27
  250. package/dist/supervisor-app.d.ts.map +0 -1
  251. package/dist/supervisor-app.js +0 -694
  252. package/dist/supervisor-app.js.map +0 -1
  253. package/dist/supervisor-discovery.d.ts +0 -18
  254. package/dist/supervisor-discovery.d.ts.map +0 -1
  255. package/dist/supervisor-discovery.js +0 -180
  256. package/dist/supervisor-discovery.js.map +0 -1
  257. package/dist/supervisor-entry.d.ts +0 -11
  258. package/dist/supervisor-entry.d.ts.map +0 -1
  259. package/dist/supervisor-entry.js +0 -50
  260. package/dist/supervisor-entry.js.map +0 -1
  261. package/dist/supervisor-lock.d.ts +0 -26
  262. package/dist/supervisor-lock.d.ts.map +0 -1
  263. package/dist/supervisor-lock.js +0 -49
  264. package/dist/supervisor-lock.js.map +0 -1
  265. package/dist/supervisor-runner.d.ts +0 -11
  266. package/dist/supervisor-runner.d.ts.map +0 -1
  267. package/dist/supervisor-runner.js +0 -128
  268. package/dist/supervisor-runner.js.map +0 -1
  269. package/dist/types.d.ts +0 -375
  270. package/dist/types.d.ts.map +0 -1
  271. package/dist/types.js +0 -7
  272. package/dist/types.js.map +0 -1
  273. package/dist/validate-input.d.ts +0 -27
  274. package/dist/validate-input.d.ts.map +0 -1
  275. package/dist/validate-input.js +0 -56
  276. package/dist/validate-input.js.map +0 -1
  277. package/dist/validate-signal.d.ts +0 -14
  278. package/dist/validate-signal.d.ts.map +0 -1
  279. package/dist/validate-signal.js +0 -31
  280. package/dist/validate-signal.js.map +0 -1
  281. package/dist/workflow-context.d.ts +0 -4
  282. package/dist/workflow-context.d.ts.map +0 -1
  283. package/dist/workflow-context.js +0 -11
  284. package/dist/workflow-context.js.map +0 -1
  285. package/dist/workflow-outputs.d.ts +0 -9
  286. package/dist/workflow-outputs.d.ts.map +0 -1
  287. package/dist/workflow-outputs.js +0 -60
  288. package/dist/workflow-outputs.js.map +0 -1
  289. /package/dist/{executors → scheduler}/types.js +0 -0
@@ -1,694 +0,0 @@
1
- import { Hono } from "hono";
2
- import { WorkflowInterpreter, generateRunId } from "./interpreter.js";
3
- import { InputValidationFailure } from "./validate-input.js";
4
- import { SignalPayloadValidationError } from "./validate-signal.js";
5
- import { applyAgentOverrides, compileWorkflow, emptyAgentOverrideResult, optionalSubmissionMetadata, parseWorkflowSpecForOverrides, serializeWorkflowSpecForOverrides, workflowSourceResolver } from "@acpus/core";
6
- import { resolve } from "node:path";
7
- import { isRunTerminal } from "./types.js";
8
- import { ForkError, materializeForkedRun, planForkedRun } from "./fork.js";
9
- import { ArtifactReferences } from "./artifacts.js";
10
- import { HookConfigLoader } from "./hooks/loader.js";
11
- import { HookRunner } from "./hooks/runner.js";
12
- import { isUnsafeRunId } from "./run-id.js";
13
- /**
14
- * Creates a Hono HTTP app for the acpus Run Supervisor.
15
- *
16
- * Note: Node keys contain "/" characters (e.g. "workflow/step-a"), which are
17
- * incompatible with URL path segments. Node-addressed routes accept the nodeKey
18
- * as a query parameter instead.
19
- *
20
- * Returns the Hono app plus control handles for the runner to query
21
- * idle-shutdown state and inject health-overrides after listen.
22
- */
23
- export function createSupervisorApp(config, store, agentExecutor, programExecutor) {
24
- const app = new Hono();
25
- // Active interpreter instances
26
- const interpreters = new Map();
27
- // In-flight runToCompletion promises, keyed by runId, to prevent concurrent
28
- // execution races during Run-level resume or retry.
29
- const inFlightRuns = new Map();
30
- // Mutex guard: runIds currently being resumed or retried. Prevents concurrent
31
- // resume or retry requests from starting duplicate runToCompletion executions.
32
- const resumingRunIds = new Set();
33
- // Lease tracking for idle shutdown
34
- const clientLeases = new Map();
35
- let lastActiveAt = Date.now();
36
- // Health overrides set by the runner after the server binds to a port
37
- let healthStartedAt = "";
38
- let healthEndpoint = "";
39
- const workspaceRoot = resolve(config.workspace ?? process.cwd());
40
- const sourceResolver = workflowSourceResolver(workspaceRoot);
41
- const hookConfigLoader = new HookConfigLoader(workspaceRoot);
42
- /** Refresh lease on every request with client headers. */
43
- function refreshLease(clientId, clientKind) {
44
- if (!clientId || !clientKind)
45
- return;
46
- clientLeases.set(clientId, { kind: clientKind, lastSeen: Date.now() });
47
- lastActiveAt = Date.now();
48
- }
49
- /** Expire stale leases (TTL 2s). */
50
- function expireLeases() {
51
- const now = Date.now();
52
- for (const [id, lease] of clientLeases) {
53
- if (now - lease.lastSeen > 2000) {
54
- clientLeases.delete(id);
55
- }
56
- }
57
- }
58
- /** Count of non-expired leases. */
59
- function activeClientCount() {
60
- expireLeases();
61
- return clientLeases.size;
62
- }
63
- /** Count of Runs with status === "running". */
64
- function runningCount() {
65
- let count = 0;
66
- for (const runId of store.listRunIds()) {
67
- const meta = store.readRunMeta(runId);
68
- if (meta?.status === "running")
69
- count++;
70
- }
71
- return count;
72
- }
73
- /** Get the most recent active timestamp (for idle shutdown). */
74
- function getLastActiveAt() {
75
- expireLeases();
76
- let maxLease = 0;
77
- for (const lease of clientLeases.values()) {
78
- if (lease.lastSeen > maxLease)
79
- maxLease = lease.lastSeen;
80
- }
81
- return Math.max(lastActiveAt, maxLease);
82
- }
83
- /** Create an interpreter bound to this supervisor's store + executors. */
84
- function newInterpreter(runId) {
85
- const nowTimestamp = runId ? store.readRunMeta(runId)?.createdAt : undefined;
86
- // Rebuild the frozen hook runner from the Run's persisted snapshot so retry,
87
- // resume, and fork executions all reuse the same frozen configuration.
88
- const snapshot = runId ? store.readHookConfig(runId) : undefined;
89
- const hookRunner = snapshot ? new HookRunner(snapshot.mergedConfig) : undefined;
90
- return new WorkflowInterpreter(store, agentExecutor, programExecutor, { nowTimestamp, hookRunner });
91
- }
92
- /**
93
- * Freeze the merged hook configuration for a newly-created Run. Writes
94
- * hook-config.json and records the hash on Run metadata when any hooks apply;
95
- * otherwise leaves no file and no hash so the Run incurs zero hook overhead.
96
- */
97
- function freezeHookConfig(runId) {
98
- const snapshot = hookConfigLoader.freeze();
99
- if (!snapshot)
100
- return;
101
- store.writeHookConfig(runId, snapshot);
102
- const meta = store.readRunMeta(runId);
103
- if (meta) {
104
- meta.hookConfigHash = snapshot.hash;
105
- store.writeRunMeta(runId, meta);
106
- return meta;
107
- }
108
- }
109
- /**
110
- * Interpreter for a forward-progress control action (resume or retry). When the
111
- * supervisor has restarted (in-memory map empty) but the Run persists, lazily
112
- * recover one and reset stale `running` nodes so the action can proceed.
113
- * Returns `undefined` only when the Run does not exist on disk.
114
- */
115
- function getOrRecoverInterpreter(runId) {
116
- const live = interpreters.get(runId);
117
- if (live)
118
- return live;
119
- if (!store.hasRun(runId))
120
- return undefined;
121
- const recovered = newInterpreter(runId);
122
- recovered.recoverStaleNodes(runId);
123
- interpreters.set(runId, recovered);
124
- return recovered;
125
- }
126
- /**
127
- * Read-only interpreter used by replay. Lazily creates one for cross-process
128
- * access but NEVER mutates persisted state (no crash recovery). Returns
129
- * `undefined` only when the Run does not exist on disk.
130
- */
131
- function getReadOnlyInterpreter(runId) {
132
- const live = interpreters.get(runId);
133
- if (live)
134
- return live;
135
- if (!store.hasRun(runId))
136
- return undefined;
137
- return newInterpreter(runId);
138
- }
139
- /** Prune interpreters for terminal Runs from the in-memory map. */
140
- function pruneTerminalInterpreters() {
141
- for (const [runId] of interpreters) {
142
- const meta = store.readRunMeta(runId);
143
- if (meta && isRunTerminal(meta.status)) {
144
- interpreters.delete(runId);
145
- }
146
- }
147
- }
148
- // ─── Lease refresh middleware (runs before route handlers) ────────
149
- app.use("*", async (c, next) => {
150
- const clientId = c.req.header("x-acpus-client-id");
151
- const clientKind = c.req.header("x-acpus-client-kind");
152
- refreshLease(clientId, clientKind);
153
- await next();
154
- });
155
- // ─── runId path-traversal guard ──────────────────────────────────
156
- app.use("/runs/:runId/*", async (c, next) => {
157
- const runId = c.req.param("runId");
158
- if (isUnsafeRunId(runId)) {
159
- return c.json({ error: "Invalid runId format" }, 400);
160
- }
161
- await next();
162
- });
163
- /** Fire-and-forget runToCompletion with proper tracking and cleanup. */
164
- function startRunExecution(interpreter, ir, input, runId) {
165
- const promise = interpreter.runToCompletion(ir, { input }, runId)
166
- .catch(() => {
167
- // runToCompletion always resolves with the RunState (never rejects
168
- // with undefined), but the catch silences any unexpected throw.
169
- const meta = store.readRunMeta(runId);
170
- return meta ?? {
171
- runId,
172
- workflowName: "",
173
- status: "failed",
174
- irDigest: "",
175
- inputDigest: "",
176
- createdAt: "",
177
- updatedAt: "",
178
- runAttempt: 1
179
- };
180
- })
181
- .finally(() => {
182
- inFlightRuns.delete(runId);
183
- pruneTerminalInterpreters();
184
- if (runningCount() === 0)
185
- lastActiveAt = Date.now();
186
- });
187
- inFlightRuns.set(runId, promise);
188
- }
189
- function startPersistedRunExecution(interpreter, runId) {
190
- const ir = store.readIr(runId);
191
- const input = store.readInput(runId);
192
- if (ir && input) {
193
- startRunExecution(interpreter, ir, input, runId);
194
- }
195
- }
196
- /**
197
- * Node-level retry may run for a long time. Wait one event-loop turn so
198
- * synchronous validation errors surface as 409s, then detach once execution
199
- * has started so interactive clients are not kept alive by the request.
200
- */
201
- function settleImmediate(promise) {
202
- return Promise.race([
203
- promise.then((value) => ({ kind: "settled", value })).catch((error) => ({ kind: "rejected", error })),
204
- new Promise((resolve) => setImmediate(() => resolve({ kind: "pending" })))
205
- ]);
206
- }
207
- function consumeDetachedControl(promise) {
208
- void promise.catch(() => undefined);
209
- }
210
- // ─── Health ──────────────────────────────────────────────────────
211
- app.get("/health", (c) => {
212
- const health = {
213
- ok: true,
214
- schemaVersion: 1,
215
- workspace: workspaceRoot,
216
- pid: process.pid,
217
- endpoint: healthEndpoint,
218
- startedAt: healthStartedAt,
219
- version: "0.1.0",
220
- runningCount: runningCount(),
221
- activeClients: activeClientCount()
222
- };
223
- return c.json(health);
224
- });
225
- // ─── Runs ────────────────────────────────────────────────────────
226
- app.post("/runs", async (c) => {
227
- const body = await c.req.json();
228
- if (!body.spec) {
229
- return c.json({ error: "spec is required" }, 400);
230
- }
231
- let sourcePath;
232
- if (body.sourcePath) {
233
- try {
234
- sourcePath = sourceResolver.validateSourcePath(body.sourcePath);
235
- }
236
- catch (error) {
237
- return c.json({ error: errorMessage(error) }, 400);
238
- }
239
- }
240
- let effectiveSource = body.spec;
241
- let submitMetadata = emptyAgentOverrideResult();
242
- try {
243
- if (body.agentOverrides !== undefined) {
244
- const parsedSpec = parseWorkflowSpecForOverrides(body.spec);
245
- submitMetadata = applyAgentOverrides(parsedSpec, body.agentOverrides);
246
- effectiveSource = serializeWorkflowSpecForOverrides(submitMetadata.effectiveSpec);
247
- }
248
- }
249
- catch (error) {
250
- return c.json({ error: errorMessage(error) }, 400);
251
- }
252
- const result = compileWorkflow(effectiveSource, {
253
- sourcePath,
254
- includeResolver: sourceResolver.createIncludeResolver(sourcePath)
255
- });
256
- if (!result.ok || !result.ir) {
257
- return c.json({ error: "Compilation failed", diagnostics: result.diagnostics }, 400);
258
- }
259
- const initInterpreter = newInterpreter();
260
- let runState;
261
- try {
262
- runState = initInterpreter.initRun(result.ir, {
263
- input: body.input ?? {},
264
- workflowRef: body.workflowRef,
265
- workflowSourcePath: sourcePath,
266
- ...optionalSubmissionMetadata(submitMetadata),
267
- skipHooks: Boolean(body.skipHooks)
268
- });
269
- }
270
- catch (error) {
271
- if (error instanceof InputValidationFailure) {
272
- return c.json({ error: "Input validation failed", validationErrors: error.errors }, 400);
273
- }
274
- throw error;
275
- }
276
- // Freeze hook configuration once at Run creation unless this submission
277
- // explicitly disabled hooks. Retry/resume reuse the Run's frozen state.
278
- if (!body.skipHooks)
279
- runState = freezeHookConfig(runState.runId) ?? runState;
280
- const interpreter = newInterpreter(runState.runId);
281
- interpreters.set(runState.runId, interpreter);
282
- lastActiveAt = Date.now();
283
- // initRun has already validated and filled defaults; read the validated
284
- // input from the store rather than re-using the raw body.input.
285
- const validatedInput = store.readInput(runState.runId) ?? body.input ?? {};
286
- startRunExecution(interpreter, result.ir, validatedInput, runState.runId);
287
- return c.json(runState, 201);
288
- });
289
- app.get("/runs", (c) => {
290
- const runIds = store.listRunIds();
291
- const summaries = [];
292
- for (const id of runIds) {
293
- const meta = store.readRunMeta(id);
294
- if (meta) {
295
- summaries.push({
296
- runId: meta.runId,
297
- workflowName: meta.workflowName,
298
- workflowRef: meta.workflowRef,
299
- workflowSourcePath: meta.workflowSourcePath,
300
- status: meta.status,
301
- createdAt: meta.createdAt,
302
- updatedAt: meta.updatedAt,
303
- lineage: meta.lineage
304
- });
305
- }
306
- // Skip corrupt Run metadata deterministically (don't crash)
307
- }
308
- // Sort by updatedAt descending, limit to most recent 50
309
- summaries.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
310
- return c.json(summaries.slice(0, 50));
311
- });
312
- app.get("/runs/:runId", (c) => {
313
- const runId = c.req.param("runId");
314
- const meta = store.readRunMeta(runId);
315
- if (!meta) {
316
- return c.json({ error: "Run not found" }, 404);
317
- }
318
- const nodes = store.listNodeStates(runId);
319
- return c.json({ ...meta, nodes });
320
- });
321
- app.post("/runs/clean", async (c) => {
322
- pruneTerminalInterpreters();
323
- const body = await c.req.json().catch(() => ({}));
324
- const result = store.cleanTerminalRuns({ dryRun: Boolean(body.dryRun) });
325
- if (!body.dryRun) {
326
- for (const item of result.deleted) {
327
- interpreters.delete(item.runId);
328
- }
329
- }
330
- lastActiveAt = Date.now();
331
- return c.json(result);
332
- });
333
- // ─── Nodes ───────────────────────────────────────────────────────
334
- app.get("/runs/:runId/nodes", (c) => {
335
- const runId = c.req.param("runId");
336
- if (!store.hasRun(runId)) {
337
- return c.json({ error: "Run not found" }, 404);
338
- }
339
- return c.json(store.listNodeStates(runId));
340
- });
341
- app.get("/runs/:runId/ir", (c) => {
342
- const runId = c.req.param("runId");
343
- if (!store.hasRun(runId)) {
344
- return c.json({ error: "Run not found" }, 404);
345
- }
346
- const ir = store.readIr(runId);
347
- if (!ir) {
348
- return c.json({ error: "IR not found" }, 404);
349
- }
350
- return c.json(ir);
351
- });
352
- app.get("/runs/:runId/input", (c) => {
353
- const runId = c.req.param("runId");
354
- if (!store.hasRun(runId)) {
355
- return c.json({ error: "Run not found" }, 404);
356
- }
357
- const input = store.readInput(runId);
358
- if (!input) {
359
- return c.json({ error: "Input not found" }, 404);
360
- }
361
- return c.json({ input });
362
- });
363
- app.get("/runs/:runId/node", (c) => {
364
- const runId = c.req.param("runId");
365
- const nodeKey = c.req.query("key");
366
- if (!nodeKey) {
367
- return c.json({ error: "key query parameter is required" }, 400);
368
- }
369
- const state = store.readNodeState(runId, nodeKey);
370
- if (!state) {
371
- return c.json({ error: "Node not found" }, 404);
372
- }
373
- return c.json(state);
374
- });
375
- app.get("/runs/:runId/artifact-path", (c) => {
376
- const runId = c.req.param("runId");
377
- const uri = c.req.query("uri");
378
- if (!uri) {
379
- return c.json({ error: "uri query parameter is required" }, 400);
380
- }
381
- if (!store.hasRun(runId)) {
382
- return c.json({ error: "Run not found" }, 404);
383
- }
384
- // Validate that the URI's runId matches the route's :runId to prevent
385
- // cross-run artifact path resolution.
386
- const parsed = ArtifactReferences.tryParse(uri);
387
- if (parsed && parsed.runId !== runId) {
388
- return c.json({ error: "Artifact URI runId does not match route runId" }, 400);
389
- }
390
- const absPath = store.resolveArtifactPath(uri);
391
- if (!absPath) {
392
- return c.json({ error: "Invalid artifact uri" }, 400);
393
- }
394
- return c.json({ absPath });
395
- });
396
- // ─── Control ────────────────────────────────────────────────────
397
- app.post("/runs/:runId/pause", async (c) => {
398
- const runId = c.req.param("runId");
399
- // Run-level pause
400
- const meta = store.readRunMeta(runId);
401
- if (!meta)
402
- return c.json({ error: "Run not found" }, 404);
403
- if (meta.status !== "running")
404
- return c.json({ error: `Cannot pause a run in state '${meta.status}'` }, 409);
405
- const interpreter = interpreters.get(runId);
406
- if (!interpreter)
407
- return c.json({ error: "Run is not actively executing; pause requires an in-flight run" }, 409);
408
- interpreter.pauseRun(runId);
409
- const updated = store.readRunMeta(runId);
410
- return c.json(updated);
411
- });
412
- app.post("/runs/:runId/resume", async (c) => {
413
- const runId = c.req.param("runId");
414
- // Run-level resume
415
- if (resumingRunIds.has(runId)) {
416
- return c.json({ error: "Run is already being resumed or retried" }, 409);
417
- }
418
- const meta = store.readRunMeta(runId);
419
- if (!meta)
420
- return c.json({ error: "Run not found" }, 404);
421
- if (meta.status !== "paused")
422
- return c.json({ error: `Cannot resume a run in state '${meta.status}'` }, 409);
423
- // Wait for any in-flight runToCompletion to settle before resuming,
424
- // preventing concurrent execution race on the same interpreter.
425
- const inFlight = inFlightRuns.get(runId);
426
- if (inFlight)
427
- await inFlight;
428
- resumingRunIds.add(runId);
429
- try {
430
- const interpreter = getOrRecoverInterpreter(runId);
431
- if (!interpreter)
432
- return c.json({ error: "Run not found" }, 404);
433
- await interpreter.resumeRun(runId);
434
- interpreters.set(runId, interpreter);
435
- lastActiveAt = Date.now();
436
- startPersistedRunExecution(interpreter, runId);
437
- const updated = store.readRunMeta(runId);
438
- return c.json(updated);
439
- }
440
- finally {
441
- resumingRunIds.delete(runId);
442
- }
443
- });
444
- app.post("/runs/:runId/cancel", async (c) => {
445
- const runId = c.req.param("runId");
446
- // Run-level cancel
447
- const meta = store.readRunMeta(runId);
448
- if (!meta)
449
- return c.json({ error: "Run not found" }, 404);
450
- if (meta.status !== "running" && meta.status !== "paused") {
451
- return c.json({ error: `Cannot cancel a run in state '${meta.status}'` }, 409);
452
- }
453
- const interpreter = interpreters.get(runId) ?? getOrRecoverInterpreter(runId);
454
- if (!interpreter)
455
- return c.json({ error: "Run not found" }, 404);
456
- interpreter.cancelRun(runId);
457
- // Wait for in-flight runToCompletion to settle after cancel, so its catch
458
- // block sees the "cancelled" abort intent and writes "cancelled" status
459
- // instead of overwriting with "paused" from a prior pauseRun.
460
- const inFlight = inFlightRuns.get(runId);
461
- if (inFlight)
462
- await inFlight;
463
- pruneTerminalInterpreters();
464
- const updated = store.readRunMeta(runId);
465
- return c.json(updated);
466
- });
467
- app.post("/runs/:runId/retry", async (c) => {
468
- const runId = c.req.param("runId");
469
- const nodeKey = c.req.query("key");
470
- if (nodeKey) {
471
- const interpreter = getOrRecoverInterpreter(runId);
472
- if (!interpreter)
473
- return c.json({ error: "Run not found" }, 404);
474
- const retry = interpreter.retryNode(runId, nodeKey);
475
- const immediate = await settleImmediate(retry);
476
- if (immediate.kind === "rejected") {
477
- const err = immediate.error;
478
- return c.json({ error: err instanceof Error ? err.message : String(err) }, 409);
479
- }
480
- if (immediate.kind === "pending")
481
- consumeDetachedControl(retry);
482
- lastActiveAt = Date.now();
483
- const state = store.readNodeState(runId, nodeKey);
484
- return c.json(state);
485
- }
486
- // Run-level retry
487
- if (resumingRunIds.has(runId)) {
488
- return c.json({ error: "Run is already being resumed or retried" }, 409);
489
- }
490
- const meta = store.readRunMeta(runId);
491
- if (!meta)
492
- return c.json({ error: "Run not found" }, 404);
493
- if (meta.status !== "failed") {
494
- return c.json({ error: `Cannot retry a run in state '${meta.status}'` }, 409);
495
- }
496
- const inFlight = inFlightRuns.get(runId);
497
- if (inFlight)
498
- await inFlight;
499
- resumingRunIds.add(runId);
500
- try {
501
- const interpreter = getOrRecoverInterpreter(runId);
502
- if (!interpreter)
503
- return c.json({ error: "Run not found" }, 404);
504
- interpreter.retryRun(runId);
505
- interpreters.set(runId, interpreter);
506
- lastActiveAt = Date.now();
507
- startPersistedRunExecution(interpreter, runId);
508
- const updated = store.readRunMeta(runId);
509
- return c.json(updated);
510
- }
511
- finally {
512
- resumingRunIds.delete(runId);
513
- }
514
- });
515
- // ─── Fork ────────────────────────────────────────────────────────
516
- //
517
- // Derive a new Run from a terminal source Run by supplying a possibly-
518
- // modified Workflow Spec. Inheritance is keyed by Node Definition Hash; the
519
- // first divergence becomes the default Fork Origin. `dryRun: true` returns
520
- // the plan without creating a Run.
521
- app.post("/runs/:runId/fork", async (c) => {
522
- const runId = c.req.param("runId");
523
- const body = await c.req.json().catch(() => ({}));
524
- const priorMeta = store.readRunMeta(runId);
525
- if (!priorMeta)
526
- return c.json({ error: "Run not found" }, 404);
527
- if (!body.spec) {
528
- return c.json({ error: "spec is required" }, 400);
529
- }
530
- let sourcePath;
531
- if (body.sourcePath) {
532
- try {
533
- sourcePath = sourceResolver.validateSourcePath(body.sourcePath);
534
- }
535
- catch (error) {
536
- return c.json({ error: errorMessage(error) }, 400);
537
- }
538
- }
539
- let submitMetadata = emptyAgentOverrideResult();
540
- let effectiveSource = body.spec;
541
- try {
542
- if (body.agentOverrides !== undefined || Object.keys(priorMeta.agentOverrides ?? {}).length > 0) {
543
- const parsedSpec = parseWorkflowSpecForOverrides(body.spec);
544
- submitMetadata = applyAgentOverrides(parsedSpec, body.agentOverrides, {
545
- inherited: priorMeta.agentOverrides ?? {}
546
- });
547
- effectiveSource = serializeWorkflowSpecForOverrides(submitMetadata.effectiveSpec);
548
- }
549
- }
550
- catch (error) {
551
- return c.json({ kind: "fork-rejected", error: errorMessage(error) }, 400);
552
- }
553
- const compileResult = compileWorkflow(effectiveSource, {
554
- sourcePath,
555
- includeResolver: sourceResolver.createIncludeResolver(sourcePath)
556
- });
557
- if (!compileResult.ok || !compileResult.ir) {
558
- return c.json({ kind: "fork-rejected", error: "Compilation failed", diagnostics: compileResult.diagnostics }, 400);
559
- }
560
- let plan;
561
- try {
562
- plan = planForkedRun(store, {
563
- sourceRunId: runId,
564
- ir: compileResult.ir,
565
- overrideOriginNodeKey: body.overrideOriginNodeKey
566
- });
567
- }
568
- catch (error) {
569
- if (error instanceof ForkError) {
570
- return c.json({ kind: "fork-rejected", error: error.message }, 409);
571
- }
572
- throw error;
573
- }
574
- if (body.dryRun) {
575
- return c.json({
576
- dryRun: true,
577
- plan,
578
- agentOverrides: submitMetadata.agentOverrides,
579
- submissionWarnings: submitMetadata.warnings
580
- });
581
- }
582
- const forkRunId = generateRunId();
583
- let materialized;
584
- try {
585
- materialized = materializeForkedRun(store, {
586
- sourceRunId: runId,
587
- forkRunId,
588
- ir: compileResult.ir,
589
- input: body.input,
590
- workflowRef: body.workflowRef ?? priorMeta.workflowRef,
591
- workflowSourcePath: sourcePath ?? priorMeta.workflowSourcePath,
592
- plan,
593
- ...optionalSubmissionMetadata(submitMetadata),
594
- skipHooks: priorMeta.skipHooks
595
- });
596
- }
597
- catch (error) {
598
- if (error instanceof InputValidationFailure) {
599
- return c.json({ error: "Input validation failed", validationErrors: error.errors }, 400);
600
- }
601
- throw error;
602
- }
603
- const interpreter = newInterpreter(forkRunId);
604
- interpreters.set(forkRunId, interpreter);
605
- lastActiveAt = Date.now();
606
- startRunExecution(interpreter, compileResult.ir, materialized.input, forkRunId);
607
- return c.json({ run: materialized.run, plan: materialized.plan }, 201);
608
- });
609
- // ─── Replay ──────────────────────────────────────────────────────
610
- app.post("/runs/:runId/replay", (c) => {
611
- const runId = c.req.param("runId");
612
- const interpreter = getReadOnlyInterpreter(runId);
613
- if (!interpreter)
614
- return c.json({ error: "Run not found" }, 404);
615
- const result = interpreter.replay(runId);
616
- return c.json(result);
617
- });
618
- // ─── Signal ───────────────────────────────────────────────────────
619
- //
620
- // Deliver an external decision payload to a Signal Node that is currently
621
- // `awaiting`. Node-addressed only (`?key=` required). The request body is the
622
- // payload object directly. The resolver lives in the in-memory interpreter,
623
- // so a live in-flight run is required (409 otherwise). A payload that fails
624
- // the node's declared output schema is rejected with 422 and the node stays
625
- // `awaiting`.
626
- app.post("/runs/:runId/signal", async (c) => {
627
- const runId = c.req.param("runId");
628
- const nodeKey = c.req.query("key");
629
- if (!nodeKey) {
630
- return c.json({ error: "key query parameter is required (signals are node-level)" }, 400);
631
- }
632
- const payload = await c.req.json().catch(() => undefined);
633
- if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
634
- return c.json({ error: "Signal payload must be a JSON object" }, 400);
635
- }
636
- const interpreter = interpreters.get(runId);
637
- if (!interpreter) {
638
- return store.hasRun(runId)
639
- ? c.json({ error: "Run is not actively executing; signal requires an in-flight run" }, 409)
640
- : c.json({ error: "Run not found" }, 404);
641
- }
642
- try {
643
- interpreter.submitSignal(runId, nodeKey, payload);
644
- }
645
- catch (err) {
646
- // A non-conforming payload is a request-body problem (422); the node
647
- // stays `awaiting`. A missing resolver ("not awaiting") is a state
648
- // conflict (409).
649
- if (err instanceof SignalPayloadValidationError) {
650
- return c.json({ error: err.message }, 422);
651
- }
652
- return c.json({ error: err instanceof Error ? err.message : String(err) }, 409);
653
- }
654
- // submitSignal resolves the in-memory promise; the awaiting → completed
655
- // write happens in the executeNode continuation on a later microtask. Wait
656
- // briefly for the node to leave `awaiting` so we return the settled state.
657
- let state = store.readNodeState(runId, nodeKey);
658
- for (let i = 0; i < 100 && state?.state === "awaiting"; i++) {
659
- await new Promise((r) => setTimeout(r, 5));
660
- state = store.readNodeState(runId, nodeKey);
661
- }
662
- return c.json(state);
663
- });
664
- // ─── Output & Artifacts ──────────────────────────────────────────
665
- app.get("/runs/:runId/output", (c) => {
666
- const runId = c.req.param("runId");
667
- const meta = store.readRunMeta(runId);
668
- if (!meta)
669
- return c.json({ error: "Run not found" }, 404);
670
- const output = meta.status === "completed" ? (meta.output ?? {}) : {};
671
- const body = { status: meta.status, output };
672
- if (meta.status === "failed" && meta.error)
673
- body.error = meta.error;
674
- return c.json(body);
675
- });
676
- return {
677
- app,
678
- /** Expose for the runner to read idle-shutdown state. */
679
- getLastActiveAt: () => getLastActiveAt(),
680
- /** Expose for the runner to check how many Runs are still active. */
681
- runningCount: () => runningCount(),
682
- /** Expose for the runner to write the startedAt and endpoint after listen. */
683
- setHealthOverrides: (overrides) => {
684
- if (overrides.startedAt)
685
- healthStartedAt = overrides.startedAt;
686
- if (overrides.endpoint)
687
- healthEndpoint = overrides.endpoint;
688
- }
689
- };
690
- }
691
- function errorMessage(error) {
692
- return error instanceof Error ? error.message : String(error);
693
- }
694
- //# sourceMappingURL=supervisor-app.js.map