@osolmaz/pi-workflows 0.16.2 → 0.16.4

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 (315) hide show
  1. package/README.md +41 -41
  2. package/dist/builtins/monitor.workflow.d.ts +1 -1
  3. package/dist/builtins/monitor.workflow.js +8 -8
  4. package/dist/builtins/pi-agent-group.js +3 -3
  5. package/dist/builtins/pi-agent-group.js.map +1 -1
  6. package/dist/channels/adapter-entry.js +14 -14
  7. package/dist/channels/adapter-entry.js.map +1 -1
  8. package/dist/client/client.d.ts +6 -6
  9. package/dist/client/client.js +27 -27
  10. package/dist/client/client.js.map +1 -1
  11. package/dist/client/protocol.d.ts +4 -1
  12. package/dist/client/protocol.js +17 -11
  13. package/dist/client/protocol.js.map +1 -1
  14. package/dist/client/resolver.d.ts +4 -4
  15. package/dist/client/view.d.ts +1 -1
  16. package/dist/extension/index.d.ts +1 -1
  17. package/dist/extension/index.js +32 -32
  18. package/dist/extension/index.js.map +1 -1
  19. package/dist/extension/recorder.d.ts +1 -1
  20. package/dist/extension/recorder.js +1 -1
  21. package/dist/extension/recorder.js.map +1 -1
  22. package/dist/extension/remote-recorder-store.d.ts +1 -1
  23. package/dist/extension/remote-recorder-store.js +2 -2
  24. package/dist/extension/remote-recorder-store.js.map +1 -1
  25. package/dist/extension/{controller-command.d.ts → resource-manager-command.d.ts} +6 -6
  26. package/dist/extension/{controller-command.js → resource-manager-command.js} +7 -7
  27. package/dist/extension/resource-manager-command.js.map +1 -0
  28. package/dist/extension/session-delivery.d.ts +1 -1
  29. package/dist/extension/session-delivery.js +2 -2
  30. package/dist/extension/session-delivery.js.map +1 -1
  31. package/dist/extension/session-view.d.ts +1 -1
  32. package/dist/extension/session-view.js +1 -1
  33. package/dist/extension/session-view.js.map +1 -1
  34. package/dist/extension/workflow-message-coordinator.d.ts +1 -1
  35. package/dist/extension/workflow-message-coordinator.js +6 -6
  36. package/dist/extension/workflow-message-coordinator.js.map +1 -1
  37. package/dist/resource-managers/conditions.d.ts +6 -0
  38. package/dist/{controllers → resource-managers}/conditions.js +3 -3
  39. package/dist/resource-managers/conditions.js.map +1 -0
  40. package/dist/resource-managers/definition.d.ts +6 -0
  41. package/dist/resource-managers/definition.js +45 -0
  42. package/dist/resource-managers/definition.js.map +1 -0
  43. package/dist/resource-managers/effects.d.ts +15 -0
  44. package/dist/{controllers → resource-managers}/effects.js +2 -2
  45. package/dist/resource-managers/effects.js.map +1 -0
  46. package/dist/resource-managers/errors.d.ts +12 -0
  47. package/dist/resource-managers/errors.js +25 -0
  48. package/dist/resource-managers/errors.js.map +1 -0
  49. package/dist/resource-managers/index.d.ts +12 -0
  50. package/dist/resource-managers/index.js +12 -0
  51. package/dist/resource-managers/index.js.map +1 -0
  52. package/dist/resource-managers/json.js.map +1 -0
  53. package/dist/resource-managers/loader.d.ts +23 -0
  54. package/dist/resource-managers/loader.js +75 -0
  55. package/dist/resource-managers/loader.js.map +1 -0
  56. package/dist/resource-managers/results.d.ts +5 -0
  57. package/dist/resource-managers/results.js.map +1 -0
  58. package/dist/resource-managers/runtime.d.ts +59 -0
  59. package/dist/{controllers/manager.js → resource-managers/runtime.js} +57 -57
  60. package/dist/resource-managers/runtime.js.map +1 -0
  61. package/dist/{controllers → resource-managers}/sqlite.d.ts +44 -44
  62. package/dist/{controllers → resource-managers}/sqlite.js +139 -130
  63. package/dist/resource-managers/sqlite.js.map +1 -0
  64. package/dist/{controllers → resource-managers}/store.d.ts +33 -33
  65. package/dist/{controllers → resource-managers}/store.js.map +1 -1
  66. package/dist/{controllers → resource-managers}/types.d.ts +41 -41
  67. package/dist/{controllers → resource-managers}/types.js.map +1 -1
  68. package/dist/resource-managers/workflows.d.ts +34 -0
  69. package/dist/{controllers → resource-managers}/workflows.js +15 -15
  70. package/dist/resource-managers/workflows.js.map +1 -0
  71. package/dist/server/channel-effects.js.map +1 -0
  72. package/dist/{host → server}/channel-supervisor.d.ts +6 -6
  73. package/dist/{host → server}/channel-supervisor.js +3 -3
  74. package/dist/server/channel-supervisor.js.map +1 -0
  75. package/dist/{host/child-worker-supervisor.d.ts → server/child-runner-supervisor.d.ts} +15 -13
  76. package/dist/{host/child-worker-supervisor.js → server/child-runner-supervisor.js} +6 -5
  77. package/dist/server/child-runner-supervisor.js.map +1 -0
  78. package/dist/{host → server}/processes.d.ts +3 -3
  79. package/dist/{host → server}/processes.js +3 -3
  80. package/dist/server/processes.js.map +1 -0
  81. package/dist/{host → server}/resolver-entry.d.ts +6 -6
  82. package/dist/{host → server}/resolver-entry.js +14 -14
  83. package/dist/server/resolver-entry.js.map +1 -0
  84. package/dist/server/resource-runner-entry.d.ts +2 -0
  85. package/dist/{host/controller-worker-entry.js → server/resource-runner-entry.js} +33 -33
  86. package/dist/server/resource-runner-entry.js.map +1 -0
  87. package/dist/server/resource-runner-protocol.d.ts +36 -0
  88. package/dist/server/resource-runner-protocol.js +49 -0
  89. package/dist/server/resource-runner-protocol.js.map +1 -0
  90. package/dist/server/resource-runner-supervisor.d.ts +21 -0
  91. package/dist/{host/worker-supervisor.js → server/resource-runner-supervisor.js} +17 -17
  92. package/dist/server/resource-runner-supervisor.js.map +1 -0
  93. package/dist/{host → server}/rpc-bridge.d.ts +3 -3
  94. package/dist/{host → server}/rpc-bridge.js +3 -3
  95. package/dist/server/rpc-bridge.js.map +1 -0
  96. package/dist/{host → server}/rpc-executor.d.ts +1 -1
  97. package/dist/{host → server}/rpc-executor.js +3 -3
  98. package/dist/server/rpc-executor.js.map +1 -0
  99. package/dist/{host/host-entry.js → server/server-entry.js} +6 -6
  100. package/dist/server/server-entry.js.map +1 -0
  101. package/dist/{host/runner.d.ts → server/server.d.ts} +39 -38
  102. package/dist/{host/runner.js → server/server.js} +408 -374
  103. package/dist/server/server.js.map +1 -0
  104. package/dist/{host → server}/state.d.ts +25 -25
  105. package/dist/{host → server}/state.js +49 -49
  106. package/dist/server/state.js.map +1 -0
  107. package/dist/{host → server}/view.d.ts +7 -7
  108. package/dist/{host → server}/view.js +13 -13
  109. package/dist/server/view.js.map +1 -0
  110. package/dist/server/workflow-runner-content.d.ts +6 -0
  111. package/dist/server/workflow-runner-content.js +96 -0
  112. package/dist/server/workflow-runner-content.js.map +1 -0
  113. package/dist/{host/worker-entry.d.ts → server/workflow-runner-entry.d.ts} +3 -3
  114. package/dist/{host/worker-entry.js → server/workflow-runner-entry.js} +68 -35
  115. package/dist/server/workflow-runner-entry.js.map +1 -0
  116. package/dist/server/workflow-runner-protocol.d.ts +69 -0
  117. package/dist/server/workflow-runner-protocol.js +176 -0
  118. package/dist/server/workflow-runner-protocol.js.map +1 -0
  119. package/dist/{host/worker-store.d.ts → server/workflow-runner-store.d.ts} +11 -11
  120. package/dist/{host/worker-store.js → server/workflow-runner-store.js} +8 -8
  121. package/dist/server/workflow-runner-store.js.map +1 -0
  122. package/dist/server/workflow-runner-supervisor.d.ts +22 -0
  123. package/dist/server/workflow-runner-supervisor.js +56 -0
  124. package/dist/server/workflow-runner-supervisor.js.map +1 -0
  125. package/dist/state/prune.d.ts +1 -1
  126. package/dist/state/prune.js +17 -10
  127. package/dist/state/prune.js.map +1 -1
  128. package/dist/state/schema.js +2 -2
  129. package/dist/state/workflow-messages.d.ts +1 -1
  130. package/dist/state/workflow-messages.js +1 -1
  131. package/dist/state/workflow-messages.js.map +1 -1
  132. package/dist/viewer/backup.js +1 -1
  133. package/dist/viewer/backup.js.map +1 -1
  134. package/dist/viewer/cli.d.ts +2 -2
  135. package/dist/viewer/cli.js +46 -32
  136. package/dist/viewer/cli.js.map +1 -1
  137. package/dist/viewer/tui.d.ts +1 -1
  138. package/dist/viewer/tui.js +1 -1
  139. package/dist/viewer/tui.js.map +1 -1
  140. package/dist/workflows/command-batch.js +2 -2
  141. package/dist/workflows/engine.js +14 -15
  142. package/dist/workflows/engine.js.map +1 -1
  143. package/dist/workflows/store.d.ts +12 -11
  144. package/dist/workflows/store.js +24 -18
  145. package/dist/workflows/store.js.map +1 -1
  146. package/dist/workflows/types.d.ts +2 -2
  147. package/docs/2026-08-20-durable-workflow-launch-plan.md +12 -12
  148. package/docs/2026-08-25-workflow-follow-ups.md +7 -7
  149. package/docs/2026-08-25-workflow-settings.md +3 -3
  150. package/docs/2026-08-30-out-of-process-workflow-host-plan.md +3 -3
  151. package/docs/2026-09-01-restore-session-delivery-controls-plan.md +5 -5
  152. package/docs/2026-09-01-unified-workflow-client-plan.md +6 -6
  153. package/docs/2026-09-02-installed-live-e2e-plan.md +3 -3
  154. package/docs/2026-09-02-unify-workflow-messages-plan.md +8 -8
  155. package/docs/2026-09-04-workflow-run-state-plan.md +6 -6
  156. package/docs/DEFERRED_TURNS.md +7 -7
  157. package/docs/DESIGN_PHILOSOPHY.md +1 -1
  158. package/docs/HUMAN_DECISIONS.md +11 -11
  159. package/docs/HUMAN_DECISION_PRESENTATIONS.md +4 -4
  160. package/docs/MONITOR.md +1 -1
  161. package/docs/RESOURCE_MANAGERS.md +221 -0
  162. package/docs/SQLITE_STATE.md +35 -33
  163. package/docs/WORKFLOW_COMPOSITION.md +1 -1
  164. package/docs/{WORKFLOW_HOST.md → WORKFLOW_SERVER.md} +125 -119
  165. package/docs/WORKFLOW_STEP_MESSAGES.md +18 -18
  166. package/docs/WORKFLOW_UPDATES.md +16 -16
  167. package/docs/development.md +15 -14
  168. package/docs/live-replay-protocol.md +20 -20
  169. package/docs/plans/2026-08-04-controller-runtime-plan.md +25 -25
  170. package/docs/plans/2026-08-05-always-on-workflows-plan.md +8 -8
  171. package/docs/plans/2026-08-10-agent-managed-monitor-workflows-plan.md +2 -2
  172. package/docs/plans/2026-08-13-session-addressed-workflow-notifications-plan.md +1 -1
  173. package/docs/plans/2026-08-16-workflow-updates-plan.md +5 -5
  174. package/docs/plans/2026-08-19-human-decision-gates-plan.md +1 -1
  175. package/docs/plans/2026-08-20-bounded-command-batches-plan.md +1 -1
  176. package/docs/plans/2026-08-21-deferred-turn-intents-plan.md +3 -3
  177. package/docs/plans/2026-08-21-sanity-check-plan.md +2 -2
  178. package/docs/plans/2026-08-23-assistant-agent-completion-plan.md +5 -5
  179. package/docs/plans/2026-08-23-sqlite-state-plan.md +25 -25
  180. package/docs/plans/2026-08-25-live-workflow-settings-plan.md +4 -4
  181. package/docs/plans/2026-08-27-workflow-terminal-restart-plan.md +3 -3
  182. package/docs/plans/2026-09-04-workflow-runner-resume-state-plan.md +318 -0
  183. package/docs/plans/piw-viewer-experience-implementation-plan.md +1 -1
  184. package/docs/tui-viewer.md +9 -9
  185. package/docs/workflows.md +72 -66
  186. package/examples/{controllers/pull-request.controller.ts → resource-managers/pull-request.resource-manager.ts} +12 -12
  187. package/herdr-plugin.toml +1 -1
  188. package/package.json +12 -11
  189. package/protocol/client.v1.schema.json +7 -7
  190. package/protocol/fixtures/client-v1.json +1 -1
  191. package/src/builtins/monitor.workflow.ts +9 -9
  192. package/src/builtins/pi-agent-group.ts +3 -3
  193. package/src/channels/adapter-entry.ts +14 -14
  194. package/src/client/client.ts +33 -32
  195. package/src/client/protocol.ts +16 -11
  196. package/src/client/resolver.ts +4 -4
  197. package/src/client/view.ts +1 -1
  198. package/src/extension/index.ts +44 -34
  199. package/src/extension/recorder.ts +1 -1
  200. package/src/extension/remote-recorder-store.ts +2 -2
  201. package/src/extension/resource-manager-command.ts +45 -0
  202. package/src/extension/session-delivery.ts +2 -2
  203. package/src/extension/session-view.ts +1 -1
  204. package/src/extension/workflow-message-coordinator.ts +6 -6
  205. package/src/{controllers → resource-managers}/conditions.ts +23 -23
  206. package/src/resource-managers/definition.ts +71 -0
  207. package/src/{controllers → resource-managers}/effects.ts +9 -9
  208. package/src/resource-managers/errors.ts +27 -0
  209. package/src/resource-managers/index.ts +88 -0
  210. package/src/resource-managers/loader.ts +111 -0
  211. package/src/{controllers → resource-managers}/results.ts +4 -4
  212. package/src/{controllers/manager.ts → resource-managers/runtime.ts} +95 -92
  213. package/src/{controllers → resource-managers}/sqlite.ts +198 -186
  214. package/src/{controllers → resource-managers}/store.ts +46 -38
  215. package/src/{controllers → resource-managers}/types.ts +50 -41
  216. package/src/{controllers → resource-managers}/workflows.ts +43 -41
  217. package/src/{host → server}/channel-supervisor.ts +8 -8
  218. package/src/{host/child-worker-supervisor.ts → server/child-runner-supervisor.ts} +22 -18
  219. package/src/{host → server}/processes.ts +3 -3
  220. package/src/{host → server}/resolver-entry.ts +28 -25
  221. package/src/{host/controller-worker-entry.ts → server/resource-runner-entry.ts} +67 -65
  222. package/src/server/resource-runner-protocol.ts +103 -0
  223. package/src/server/resource-runner-supervisor.ts +76 -0
  224. package/src/{host → server}/rpc-bridge.ts +3 -3
  225. package/src/{host → server}/rpc-executor.ts +4 -4
  226. package/src/{host/host-entry.ts → server/server-entry.ts} +5 -5
  227. package/src/{host/runner.ts → server/server.ts} +526 -461
  228. package/src/{host → server}/state.ts +78 -67
  229. package/src/{host → server}/view.ts +15 -15
  230. package/src/server/workflow-runner-content.ts +119 -0
  231. package/src/{host/worker-entry.ts → server/workflow-runner-entry.ts} +98 -57
  232. package/src/server/workflow-runner-protocol.ts +241 -0
  233. package/src/{host/worker-store.ts → server/workflow-runner-store.ts} +18 -20
  234. package/src/server/workflow-runner-supervisor.ts +79 -0
  235. package/src/state/prune.ts +27 -9
  236. package/src/state/schema.ts +2 -2
  237. package/src/state/workflow-messages.ts +1 -1
  238. package/src/viewer/backup.ts +1 -1
  239. package/src/viewer/cli.ts +50 -36
  240. package/src/viewer/tui.ts +1 -1
  241. package/src/workflows/command-batch.ts +2 -2
  242. package/src/workflows/engine.ts +14 -15
  243. package/src/workflows/store.ts +31 -28
  244. package/src/workflows/types.ts +2 -2
  245. package/dist/controllers/conditions.d.ts +0 -6
  246. package/dist/controllers/conditions.js.map +0 -1
  247. package/dist/controllers/definition.d.ts +0 -6
  248. package/dist/controllers/definition.js +0 -45
  249. package/dist/controllers/definition.js.map +0 -1
  250. package/dist/controllers/effects.d.ts +0 -15
  251. package/dist/controllers/effects.js.map +0 -1
  252. package/dist/controllers/errors.d.ts +0 -12
  253. package/dist/controllers/errors.js +0 -25
  254. package/dist/controllers/errors.js.map +0 -1
  255. package/dist/controllers/index.d.ts +0 -12
  256. package/dist/controllers/index.js +0 -12
  257. package/dist/controllers/index.js.map +0 -1
  258. package/dist/controllers/json.js.map +0 -1
  259. package/dist/controllers/loader.d.ts +0 -23
  260. package/dist/controllers/loader.js +0 -74
  261. package/dist/controllers/loader.js.map +0 -1
  262. package/dist/controllers/manager.d.ts +0 -59
  263. package/dist/controllers/manager.js.map +0 -1
  264. package/dist/controllers/results.d.ts +0 -5
  265. package/dist/controllers/results.js.map +0 -1
  266. package/dist/controllers/sqlite.js.map +0 -1
  267. package/dist/controllers/workflows.d.ts +0 -34
  268. package/dist/controllers/workflows.js.map +0 -1
  269. package/dist/extension/controller-command.js.map +0 -1
  270. package/dist/host/channel-effects.js.map +0 -1
  271. package/dist/host/channel-supervisor.js.map +0 -1
  272. package/dist/host/child-worker-supervisor.js.map +0 -1
  273. package/dist/host/controller-worker-entry.d.ts +0 -2
  274. package/dist/host/controller-worker-entry.js.map +0 -1
  275. package/dist/host/controller-worker-protocol.d.ts +0 -36
  276. package/dist/host/controller-worker-protocol.js +0 -49
  277. package/dist/host/controller-worker-protocol.js.map +0 -1
  278. package/dist/host/controller-worker-supervisor.d.ts +0 -21
  279. package/dist/host/controller-worker-supervisor.js +0 -54
  280. package/dist/host/controller-worker-supervisor.js.map +0 -1
  281. package/dist/host/host-entry.js.map +0 -1
  282. package/dist/host/processes.js.map +0 -1
  283. package/dist/host/resolver-entry.js.map +0 -1
  284. package/dist/host/rpc-bridge.js.map +0 -1
  285. package/dist/host/rpc-executor.js.map +0 -1
  286. package/dist/host/runner.js.map +0 -1
  287. package/dist/host/state.js.map +0 -1
  288. package/dist/host/view.js.map +0 -1
  289. package/dist/host/worker-entry.js.map +0 -1
  290. package/dist/host/worker-protocol.d.ts +0 -46
  291. package/dist/host/worker-protocol.js +0 -122
  292. package/dist/host/worker-protocol.js.map +0 -1
  293. package/dist/host/worker-store.js.map +0 -1
  294. package/dist/host/worker-supervisor.d.ts +0 -22
  295. package/dist/host/worker-supervisor.js.map +0 -1
  296. package/docs/CONTROLLERS.md +0 -217
  297. package/src/controllers/definition.ts +0 -65
  298. package/src/controllers/errors.ts +0 -27
  299. package/src/controllers/index.ts +0 -88
  300. package/src/controllers/loader.ts +0 -104
  301. package/src/extension/controller-command.ts +0 -45
  302. package/src/host/controller-worker-protocol.ts +0 -104
  303. package/src/host/controller-worker-supervisor.ts +0 -79
  304. package/src/host/worker-protocol.ts +0 -176
  305. package/src/host/worker-supervisor.ts +0 -74
  306. /package/dist/{controllers → resource-managers}/json.d.ts +0 -0
  307. /package/dist/{controllers → resource-managers}/json.js +0 -0
  308. /package/dist/{controllers → resource-managers}/results.js +0 -0
  309. /package/dist/{controllers → resource-managers}/store.js +0 -0
  310. /package/dist/{controllers → resource-managers}/types.js +0 -0
  311. /package/dist/{host → server}/channel-effects.d.ts +0 -0
  312. /package/dist/{host → server}/channel-effects.js +0 -0
  313. /package/dist/{host/host-entry.d.ts → server/server-entry.d.ts} +0 -0
  314. /package/src/{controllers → resource-managers}/json.ts +0 -0
  315. /package/src/{host → server}/channel-effects.ts +0 -0
@@ -2,62 +2,66 @@ import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
2
2
  import { once } from "node:events";
3
3
  import { NdjsonFrameDecoder } from "../client/protocol.js";
4
4
  import { errorMessage } from "../workflows/errors.js";
5
- import { HostProcessRegistry, type ProcessIdentity } from "./processes.js";
6
- import type { WorkerOutcome } from "./state.js";
5
+ import { ServerProcessRegistry, type ProcessIdentity } from "./processes.js";
6
+ import type { RunnerOutcome } from "./state.js";
7
7
 
8
8
  const DEFAULT_STARTUP_TIMEOUT_MS = 15_000;
9
9
  const DEFAULT_KILL_GRACE_MS = 2_000;
10
10
  const MAX_DIAGNOSTIC_BYTES = 64 * 1024;
11
11
 
12
- export type SupervisedWorkerResult = {
13
- outcome: WorkerOutcome;
12
+ export type SupervisedRunnerResult = {
13
+ outcome: RunnerOutcome;
14
14
  exitCode: number | null;
15
15
  signal: NodeJS.Signals | null;
16
16
  diagnostic?: string;
17
17
  };
18
18
 
19
- type ChildWorkerSupervisorOptions<Message, Response> = {
19
+ type ChildRunnerSupervisorOptions<Message, Response> = {
20
20
  label: string;
21
- registry: HostProcessRegistry;
21
+ registry: ServerProcessRegistry;
22
22
  onMessage: (message: Message) => Promise<Response>;
23
23
  parseMessage: (frame: Buffer) => Message;
24
24
  encodeResponse: (response: Response) => Buffer;
25
25
  isReady: (message: Message) => boolean;
26
26
  isTerminal: (message: Message) => boolean;
27
27
  startupTimeoutMs?: number;
28
+ maxMessageBytes?: number;
29
+ oversizedMessage?: string;
28
30
  onSpawn?: (identity: ProcessIdentity) => void;
29
31
  onDiagnostic?: (message: string) => void;
30
32
  };
31
33
 
32
- type ChildWorkerStartOptions = {
34
+ type ChildRunnerStartOptions = {
33
35
  args: string[];
34
36
  cwd: string;
35
37
  env: NodeJS.ProcessEnv;
36
38
  };
37
39
 
38
- /** Shared supervision, framing, diagnostics, and process-group shutdown for host workers. */
39
- export class ChildWorkerSupervisor<Message, Response> {
40
+ /** Shared supervision, framing, diagnostics, and process-group shutdown for server runners. */
41
+ export class ChildRunnerSupervisor<Message, Response> {
40
42
  private child: ChildProcessWithoutNullStreams | null = null;
41
43
  private identity: ProcessIdentity | null = null;
42
- private readonly decoder = new NdjsonFrameDecoder();
44
+ private readonly decoder: NdjsonFrameDecoder;
43
45
  private diagnostic = Buffer.alloc(0);
44
46
  private terminalMessage = false;
45
- private stopping: WorkerOutcome | null = null;
47
+ private stopping: RunnerOutcome | null = null;
46
48
  private processing = Promise.resolve();
47
49
  private readyResolve: (() => void) | undefined;
48
50
  private readyReject: ((error: Error) => void) | undefined;
49
- private exitResolve: ((result: SupervisedWorkerResult) => void) | undefined;
51
+ private exitResolve: ((result: SupervisedRunnerResult) => void) | undefined;
50
52
  private readonly readyPromise = new Promise<void>((resolve, reject) => {
51
53
  this.readyResolve = resolve;
52
54
  this.readyReject = reject;
53
55
  });
54
- private readonly exitPromise = new Promise<SupervisedWorkerResult>((resolve) => {
56
+ private readonly exitPromise = new Promise<SupervisedRunnerResult>((resolve) => {
55
57
  this.exitResolve = resolve;
56
58
  });
57
59
 
58
- constructor(private readonly options: ChildWorkerSupervisorOptions<Message, Response>) {}
60
+ constructor(private readonly options: ChildRunnerSupervisorOptions<Message, Response>) {
61
+ this.decoder = new NdjsonFrameDecoder(options.maxMessageBytes, options.oversizedMessage);
62
+ }
59
63
 
60
- async start(options: ChildWorkerStartOptions): Promise<void> {
64
+ async start(options: ChildRunnerStartOptions): Promise<void> {
61
65
  if (this.child !== null) throw new Error(`${this.options.label} already started`);
62
66
  const child = spawn(process.execPath, options.args, {
63
67
  cwd: options.cwd,
@@ -86,11 +90,11 @@ export class ChildWorkerSupervisor<Message, Response> {
86
90
  }
87
91
  }
88
92
 
89
- async wait(): Promise<SupervisedWorkerResult> {
93
+ async wait(): Promise<SupervisedRunnerResult> {
90
94
  return await this.exitPromise;
91
95
  }
92
96
 
93
- async stop(outcome: WorkerOutcome = "cancelled"): Promise<void> {
97
+ async stop(outcome: RunnerOutcome = "cancelled"): Promise<void> {
94
98
  if (this.child === null) {
95
99
  this.exitResolve?.({ outcome, exitCode: null, signal: null });
96
100
  return;
@@ -154,7 +158,7 @@ export class ChildWorkerSupervisor<Message, Response> {
154
158
  const outcome =
155
159
  this.stopping ?? (code === 0 && this.terminalMessage ? "exited" : ("crashed" as const));
156
160
  const diagnostic = this.diagnostic.toString("utf8").trim();
157
- const result: SupervisedWorkerResult = {
161
+ const result: SupervisedRunnerResult = {
158
162
  outcome,
159
163
  exitCode: code,
160
164
  signal,
@@ -14,9 +14,9 @@ type ProcessRegistryFile = {
14
14
 
15
15
  /**
16
16
  * Tracks supervised process groups by PID and operating-system start identity.
17
- * A later host kills a recorded process only when both values still match.
17
+ * A later server kills a recorded process only when both values still match.
18
18
  */
19
- export class HostProcessRegistry {
19
+ export class ServerProcessRegistry {
20
20
  private readonly filePath: string;
21
21
  private readonly processes = new Map<number, ProcessIdentity>();
22
22
 
@@ -69,7 +69,7 @@ export class HostProcessRegistry {
69
69
  this.persist();
70
70
  }
71
71
 
72
- /** Reap only exact stale process identities recorded by an older host. */
72
+ /** Reap only exact stale process identities recorded by an older server. */
73
73
  reapOrphans(): number[] {
74
74
  const recorded = this.readFile();
75
75
  const reaped: number[] = [];
@@ -5,11 +5,11 @@ import { fileURLToPath } from "node:url";
5
5
  import { builtinWorkflowCatalog } from "../builtins/catalog.js";
6
6
  import { MAX_PROTOCOL_MESSAGE_BYTES } from "../client/protocol.js";
7
7
  import type {
8
- ResolvedControllerInitialization,
8
+ ResolvedResourceManagerInitialization,
9
9
  ResolvedSettingsChange,
10
10
  ResolvedWorkflowLaunch,
11
11
  } from "../client/resolver.js";
12
- import { discoverControllers, loadControllerFile } from "../controllers/loader.js";
12
+ import { discoverResourceManagers, loadResourceManagerFile } from "../resource-managers/loader.js";
13
13
  import { canonicalJson, parseJson, type JsonValue } from "../state/json.js";
14
14
  import { compositionMetadata } from "../workflows/composition.js";
15
15
  import { errorMessage } from "../workflows/errors.js";
@@ -19,7 +19,7 @@ import { applyWorkflowSettingsPatch } from "../workflows/settings.js";
19
19
  import { createDefinitionSnapshot } from "../workflows/store.js";
20
20
  import type { WorkflowDefinition, WorkflowSource } from "../workflows/types.js";
21
21
  export type {
22
- ResolvedControllerInitialization,
22
+ ResolvedResourceManagerInitialization,
23
23
  ResolvedSettingsChange,
24
24
  ResolvedWorkflowLaunch,
25
25
  } from "../client/resolver.js";
@@ -41,18 +41,21 @@ type SettingsValidationRequest = {
41
41
  actorId: string;
42
42
  };
43
43
 
44
- type ControllerInitializationRequest = {
45
- schema: "pi-workflows.controller-initialization-request.v1";
44
+ type ResourceManagerInitializationRequest = {
45
+ schema: "pi-workflows.resource-manager-initialization-request.v1";
46
46
  cwd: string;
47
- controllerName: string;
47
+ resourceManagerName: string;
48
48
  spec: JsonValue;
49
49
  };
50
50
 
51
- type ResolverInput = ResolverRequest | SettingsValidationRequest | ControllerInitializationRequest;
51
+ type ResolverInput =
52
+ | ResolverRequest
53
+ | SettingsValidationRequest
54
+ | ResourceManagerInitializationRequest;
52
55
  type ResolverOutput =
53
56
  | ResolvedWorkflowLaunch
54
57
  | ResolvedSettingsChange
55
- | ResolvedControllerInitialization;
58
+ | ResolvedResourceManagerInitialization;
56
59
 
57
60
  export async function resolveWorkflowLaunch(
58
61
  request: ResolverRequest,
@@ -74,27 +77,27 @@ export async function resolveWorkflowLaunch(
74
77
  };
75
78
  }
76
79
 
77
- export async function resolveControllerInitialization(
78
- request: ControllerInitializationRequest,
79
- ): Promise<ResolvedControllerInitialization> {
80
- const discovered = (await discoverControllers({ cwd: request.cwd })).find(
81
- (candidate) => candidate.name === request.controllerName,
80
+ export async function resolveResourceManagerInitialization(
81
+ request: ResourceManagerInitializationRequest,
82
+ ): Promise<ResolvedResourceManagerInitialization> {
83
+ const discovered = (await discoverResourceManagers({ cwd: request.cwd })).find(
84
+ (candidate) => candidate.name === request.resourceManagerName,
82
85
  );
83
86
  if (discovered === undefined) {
84
- throw new Error(`Controller not found: ${request.controllerName}`);
87
+ throw new Error(`ResourceManager not found: ${request.resourceManagerName}`);
85
88
  }
86
- const definition = await loadControllerFile(discovered.path);
87
- if (definition.name !== request.controllerName) {
88
- throw new Error("Controller source name does not match its discovered name");
89
+ const definition = await loadResourceManagerFile(discovered.path);
90
+ if (definition.name !== request.resourceManagerName) {
91
+ throw new Error("ResourceManager source name does not match its discovered name");
89
92
  }
90
93
  const initialStatus = parseJson(canonicalJson(definition.initialStatus(request.spec)));
91
94
  const sourceHash = createHash("sha256")
92
95
  .update(await fs.readFile(discovered.path))
93
96
  .digest("hex");
94
97
  return {
95
- schema: "pi-workflows.resolved-controller-initialization.v1",
96
- controllerName: definition.name,
97
- controllerPath: discovered.path,
98
+ schema: "pi-workflows.resolved-resource-manager-initialization.v1",
99
+ resourceManagerName: definition.name,
100
+ resourceManagerPath: discovered.path,
98
101
  sourceHash,
99
102
  initialStatus,
100
103
  };
@@ -179,11 +182,11 @@ async function readRequest(): Promise<ResolverInput> {
179
182
  return value as ResolverRequest;
180
183
  }
181
184
  if (
182
- value.schema === "pi-workflows.controller-initialization-request.v1" &&
183
- typeof value.controllerName === "string" &&
185
+ value.schema === "pi-workflows.resource-manager-initialization-request.v1" &&
186
+ typeof value.resourceManagerName === "string" &&
184
187
  Object.hasOwn(value, "spec")
185
188
  ) {
186
- return value as ControllerInitializationRequest;
189
+ return value as ResourceManagerInitializationRequest;
187
190
  }
188
191
  if (
189
192
  value.schema === "pi-workflows.settings-validation-request.v1" &&
@@ -205,8 +208,8 @@ async function main(): Promise<void> {
205
208
  const result: ResolverOutput =
206
209
  request.schema === "pi-workflows.resolve-request.v1"
207
210
  ? await resolveWorkflowLaunch(request)
208
- : request.schema === "pi-workflows.controller-initialization-request.v1"
209
- ? await resolveControllerInitialization(request)
211
+ : request.schema === "pi-workflows.resource-manager-initialization-request.v1"
212
+ ? await resolveResourceManagerInitialization(request)
210
213
  : await resolveSettingsChange(request);
211
214
  const encoded = `${canonicalJson(result)}\n`;
212
215
  if (Buffer.byteLength(encoded) > MAX_PROTOCOL_MESSAGE_BYTES) {
@@ -2,66 +2,66 @@
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { once } from "node:events";
4
4
  import { fileURLToPath } from "node:url";
5
- import { jsonFingerprint } from "../controllers/json.js";
6
- import { loadControllerFile } from "../controllers/loader.js";
7
- import { createResultHelpers } from "../controllers/results.js";
5
+ import { jsonFingerprint } from "../resource-managers/json.js";
6
+ import { loadResourceManagerFile } from "../resource-managers/loader.js";
7
+ import { createResultHelpers } from "../resource-managers/results.js";
8
8
  import type {
9
9
  ChildWorkflowRecord,
10
- ControllerEffects,
11
- ControllerFollowUpResult,
12
- ControllerQueueFollowUpRequest,
13
- ControllerRemoveFollowUpRequest,
14
- ControllerSettingsChangeRequest,
15
- ControllerSettingsChangeResult,
16
- ControllerWorkflows,
10
+ ResourceManagerEffects,
11
+ ResourceManagerFollowUpResult,
12
+ ResourceManagerQueueFollowUpRequest,
13
+ ResourceManagerRemoveFollowUpRequest,
14
+ ResourceManagerSettingsChangeRequest,
15
+ ResourceManagerSettingsChangeResult,
16
+ ResourceManagerWorkflows,
17
17
  EffectApplication,
18
18
  EffectDefinition,
19
19
  EffectObservation,
20
20
  EffectRecord,
21
21
  ReconcileContext,
22
22
  ReconcileResult,
23
- } from "../controllers/types.js";
23
+ } from "../resource-managers/types.js";
24
24
  import { parseJson, type JsonValue } from "../state/json.js";
25
25
  import { errorMessage } from "../workflows/errors.js";
26
26
  import {
27
- CONTROLLER_WORKER_LAUNCH_SCHEMA,
28
- encodeControllerWorkerLine,
29
- parseControllerWorkerResponse,
30
- type ControllerWorkerLaunchEnvelope,
31
- type ControllerWorkerMessage,
32
- type ControllerWorkerOperation,
33
- type ControllerWorkerResponse,
34
- } from "./controller-worker-protocol.js";
27
+ RESOURCE_RUNNER_LAUNCH_SCHEMA,
28
+ encodeResourceRunnerLine,
29
+ parseResourceRunnerResponse,
30
+ type ResourceRunnerLaunchEnvelope,
31
+ type ResourceRunnerMessage,
32
+ type ResourceRunnerOperation,
33
+ type ResourceRunnerResponse,
34
+ } from "./resource-runner-protocol.js";
35
35
 
36
- const STARTUP_ENV = "PI_WORKFLOWS_CONTROLLER_WORKER_LAUNCH";
36
+ const STARTUP_ENV = "PI_WORKFLOWS_RESOURCE_RUNNER_LAUNCH";
37
37
  const MAX_FRAME_BYTES = 1024 * 1024;
38
38
 
39
- class ControllerWorkerTransport {
39
+ class ResourceRunnerTransport {
40
40
  private readonly pending = new Map<
41
41
  string,
42
- { resolve: (response: ControllerWorkerResponse) => void; reject: (error: Error) => void }
42
+ { resolve: (response: ResourceRunnerResponse) => void; reject: (error: Error) => void }
43
43
  >();
44
44
  private buffered = Buffer.alloc(0);
45
45
 
46
- constructor(private readonly launch: ControllerWorkerLaunchEnvelope) {
46
+ constructor(private readonly launch: ResourceRunnerLaunchEnvelope) {
47
47
  process.stdin.on("data", (chunk: Buffer) => this.onData(chunk));
48
48
  process.stdin.on("error", (error) => this.failAll(error));
49
49
  process.stdin.resume();
50
50
  }
51
51
 
52
- async request<T>(operation: ControllerWorkerOperation, payload: JsonValue): Promise<T> {
53
- const message: ControllerWorkerMessage = {
52
+ async request<T>(operation: ResourceRunnerOperation, payload: JsonValue): Promise<T> {
53
+ const message: ResourceRunnerMessage = {
54
54
  schema: "pi-workflows.controller-worker-message.v1",
55
55
  launchSchema: this.launch.schema,
56
56
  messageId: randomUUID(),
57
- workerEpoch: this.launch.workerEpoch,
57
+ runnerEpoch: this.launch.runnerEpoch,
58
58
  generation: this.launch.generation,
59
59
  operation,
60
60
  payload,
61
61
  };
62
62
  const response = await this.send(message);
63
63
  if (response.outcome !== "accepted") {
64
- throw new Error(response.error ?? `Controller worker ${response.outcome}`);
64
+ throw new Error(response.error ?? `Resource runner ${response.outcome}`);
65
65
  }
66
66
  return response.result as T;
67
67
  }
@@ -70,14 +70,14 @@ class ControllerWorkerTransport {
70
70
  process.stdin.pause();
71
71
  process.stdin.removeAllListeners("data");
72
72
  process.stdin.removeAllListeners("error");
73
- this.failAll(new Error("Controller worker transport closed"));
73
+ this.failAll(new Error("Resource runner transport closed"));
74
74
  }
75
75
 
76
- private async send(message: ControllerWorkerMessage): Promise<ControllerWorkerResponse> {
77
- const response = new Promise<ControllerWorkerResponse>((resolve, reject) => {
76
+ private async send(message: ResourceRunnerMessage): Promise<ResourceRunnerResponse> {
77
+ const response = new Promise<ResourceRunnerResponse>((resolve, reject) => {
78
78
  this.pending.set(message.messageId, { resolve, reject });
79
79
  });
80
- if (!process.stdout.write(encodeControllerWorkerLine(message))) {
80
+ if (!process.stdout.write(encodeResourceRunnerLine(message))) {
81
81
  await once(process.stdout, "drain");
82
82
  }
83
83
  return await response;
@@ -86,7 +86,7 @@ class ControllerWorkerTransport {
86
86
  private onData(chunk: Buffer): void {
87
87
  this.buffered = Buffer.concat([this.buffered, chunk]);
88
88
  if (this.buffered.byteLength > MAX_FRAME_BYTES && !this.buffered.includes(0x0a)) {
89
- this.failAll(new Error("Controller worker response exceeds 1 MiB"));
89
+ this.failAll(new Error("Resource runner response exceeds 1 MiB"));
90
90
  return;
91
91
  }
92
92
  for (;;) {
@@ -96,9 +96,9 @@ class ControllerWorkerTransport {
96
96
  this.buffered = this.buffered.subarray(newline + 1);
97
97
  if (frame.byteLength === 0) continue;
98
98
  try {
99
- const response = parseControllerWorkerResponse(frame);
99
+ const response = parseResourceRunnerResponse(frame);
100
100
  const pending = this.pending.get(response.messageId);
101
- if (pending === undefined) throw new Error("Controller worker response is unexpected");
101
+ if (pending === undefined) throw new Error("Resource runner response is unexpected");
102
102
  this.pending.delete(response.messageId);
103
103
  pending.resolve(response);
104
104
  } catch (error) {
@@ -113,11 +113,11 @@ class ControllerWorkerTransport {
113
113
  }
114
114
  }
115
115
 
116
- class HostedControllerEffects implements ControllerEffects {
116
+ class ServerBackedResourceManagerEffects implements ResourceManagerEffects {
117
117
  private used = false;
118
118
 
119
119
  constructor(
120
- private readonly transport: ControllerWorkerTransport,
120
+ private readonly transport: ResourceRunnerTransport,
121
121
  private readonly signal: AbortSignal,
122
122
  ) {}
123
123
 
@@ -173,8 +173,8 @@ class HostedControllerEffects implements ControllerEffects {
173
173
  }
174
174
  }
175
175
 
176
- class HostedControllerWorkflows implements ControllerWorkflows {
177
- constructor(private readonly transport: ControllerWorkerTransport) {}
176
+ class ServerBackedResourceManagerWorkflows implements ResourceManagerWorkflows {
177
+ constructor(private readonly transport: ResourceRunnerTransport) {}
178
178
 
179
179
  async ensure(request: {
180
180
  requestKey: string;
@@ -188,42 +188,44 @@ class HostedControllerWorkflows implements ControllerWorkflows {
188
188
  }
189
189
 
190
190
  async changeSettings(
191
- request: ControllerSettingsChangeRequest,
192
- ): Promise<ControllerSettingsChangeResult> {
193
- return await this.transport.request<ControllerSettingsChangeResult>(
191
+ request: ResourceManagerSettingsChangeRequest,
192
+ ): Promise<ResourceManagerSettingsChangeResult> {
193
+ return await this.transport.request<ResourceManagerSettingsChangeResult>(
194
194
  "workflow.changeSettings",
195
195
  request as unknown as JsonValue,
196
196
  );
197
197
  }
198
198
 
199
- async queueFollowUp(request: ControllerQueueFollowUpRequest): Promise<ControllerFollowUpResult> {
200
- return await this.transport.request<ControllerFollowUpResult>(
199
+ async queueFollowUp(
200
+ request: ResourceManagerQueueFollowUpRequest,
201
+ ): Promise<ResourceManagerFollowUpResult> {
202
+ return await this.transport.request<ResourceManagerFollowUpResult>(
201
203
  "workflow.queueFollowUp",
202
204
  request as unknown as JsonValue,
203
205
  );
204
206
  }
205
207
 
206
208
  async removeFollowUp(
207
- request: ControllerRemoveFollowUpRequest,
208
- ): Promise<ControllerFollowUpResult> {
209
- return await this.transport.request<ControllerFollowUpResult>(
209
+ request: ResourceManagerRemoveFollowUpRequest,
210
+ ): Promise<ResourceManagerFollowUpResult> {
211
+ return await this.transport.request<ResourceManagerFollowUpResult>(
210
212
  "workflow.removeFollowUp",
211
213
  request as unknown as JsonValue,
212
214
  );
213
215
  }
214
216
  }
215
217
 
216
- export async function runControllerWorker(): Promise<number> {
218
+ export async function runResourceRunner(): Promise<number> {
217
219
  const launch = readLaunchEnvelope();
218
- const transport = new ControllerWorkerTransport(launch);
220
+ const transport = new ResourceRunnerTransport(launch);
219
221
  try {
220
- const definition = await loadControllerFile(launch.definitionPath);
221
- if (definition.name !== launch.controllerName) {
222
+ const definition = await loadResourceManagerFile(launch.definitionPath);
223
+ if (definition.name !== launch.resourceManagerName) {
222
224
  throw new Error(
223
- `Controller source name changed: expected ${launch.controllerName}, got ${definition.name}`,
225
+ `ResourceManager source name changed: expected ${launch.resourceManagerName}, got ${definition.name}`,
224
226
  );
225
227
  }
226
- await transport.request("worker.ready", {});
228
+ await transport.request("runner.ready", {});
227
229
  const abort = new AbortController();
228
230
  const timeoutMs = definition.timeoutMs ?? launch.timeoutMs;
229
231
  const timer = setTimeout(
@@ -233,8 +235,8 @@ export async function runControllerWorker(): Promise<number> {
233
235
  try {
234
236
  const context: ReconcileContext<unknown> = {
235
237
  signal: abort.signal,
236
- effects: new HostedControllerEffects(transport, abort.signal),
237
- workflows: new HostedControllerWorkflows(transport),
238
+ effects: new ServerBackedResourceManagerEffects(transport, abort.signal),
239
+ workflows: new ServerBackedResourceManagerWorkflows(transport),
238
240
  ...createResultHelpers<unknown>(),
239
241
  };
240
242
  const result = await raceWithAbort(
@@ -242,9 +244,9 @@ export async function runControllerWorker(): Promise<number> {
242
244
  abort.signal,
243
245
  );
244
246
  validateResult(result);
245
- await transport.request("worker.finished", result as unknown as JsonValue);
247
+ await transport.request("runner.finished", result as unknown as JsonValue);
246
248
  } catch (error) {
247
- await transport.request("worker.failed", { error: boundedError(error) });
249
+ await transport.request("runner.failed", { error: boundedError(error) });
248
250
  } finally {
249
251
  clearTimeout(timer);
250
252
  }
@@ -254,34 +256,34 @@ export async function runControllerWorker(): Promise<number> {
254
256
  }
255
257
  }
256
258
 
257
- function readLaunchEnvelope(): ControllerWorkerLaunchEnvelope {
259
+ function readLaunchEnvelope(): ResourceRunnerLaunchEnvelope {
258
260
  const encoded = process.env[STARTUP_ENV];
259
- if (encoded === undefined) throw new Error("Controller worker launch envelope is missing");
261
+ if (encoded === undefined) throw new Error("Resource runner launch envelope is missing");
260
262
  const value = parseJson(Buffer.from(encoded, "base64url").toString("utf8"));
261
263
  if (
262
264
  typeof value !== "object" ||
263
265
  value === null ||
264
266
  Array.isArray(value) ||
265
- (value as { schema?: unknown }).schema !== CONTROLLER_WORKER_LAUNCH_SCHEMA
267
+ (value as { schema?: unknown }).schema !== RESOURCE_RUNNER_LAUNCH_SCHEMA
266
268
  ) {
267
- throw new Error("Controller worker launch envelope is invalid");
269
+ throw new Error("Resource runner launch envelope is invalid");
268
270
  }
269
- return value as unknown as ControllerWorkerLaunchEnvelope;
271
+ return value as unknown as ResourceRunnerLaunchEnvelope;
270
272
  }
271
273
 
272
274
  function validateResult(value: unknown): asserts value is ReconcileResult<unknown> {
273
275
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
274
- throw new Error("Controller reconcile result must be an object");
276
+ throw new Error("ResourceManager reconcile result must be an object");
275
277
  }
276
278
  const result = value as { kind?: unknown; afterMs?: unknown };
277
279
  if (result.kind !== "settled" && result.kind !== "requeue") {
278
- throw new Error("Controller reconcile result kind is invalid");
280
+ throw new Error("ResourceManager reconcile result kind is invalid");
279
281
  }
280
282
  if (
281
283
  result.afterMs !== undefined &&
282
284
  (!Number.isSafeInteger(result.afterMs) || (result.afterMs as number) < 0)
283
285
  ) {
284
- throw new Error("Controller reconcile afterMs must be a non-negative safe integer");
286
+ throw new Error("ResourceManager reconcile afterMs must be a non-negative safe integer");
285
287
  }
286
288
  }
287
289
 
@@ -301,7 +303,7 @@ function boundedError(error: unknown): string {
301
303
 
302
304
  async function main(): Promise<void> {
303
305
  try {
304
- process.exitCode = await runControllerWorker();
306
+ process.exitCode = await runResourceRunner();
305
307
  } catch (error) {
306
308
  process.stderr.write(`${errorMessage(error)}\n`);
307
309
  process.exitCode = 1;
@@ -0,0 +1,103 @@
1
+ import type { ManagedResource } from "../resource-managers/types.js";
2
+ import { canonicalJson, parseJson, type JsonValue } from "../state/json.js";
3
+
4
+ export const RESOURCE_RUNNER_LAUNCH_SCHEMA = "pi-workflows.controller-worker-launch.v1" as const;
5
+ export const RESOURCE_RUNNER_MESSAGE_SCHEMA = "pi-workflows.controller-worker-message.v1" as const;
6
+ export const RESOURCE_RUNNER_RESPONSE_SCHEMA =
7
+ "pi-workflows.controller-worker-response.v1" as const;
8
+
9
+ export type ResourceRunnerLaunchEnvelope = {
10
+ schema: typeof RESOURCE_RUNNER_LAUNCH_SCHEMA;
11
+ runnerEpoch: string;
12
+ serverEpoch: number;
13
+ generation: number;
14
+ projectPath: string;
15
+ definitionPath: string;
16
+ resourceManagerName: string;
17
+ resource: ManagedResource;
18
+ timeoutMs: number;
19
+ };
20
+
21
+ export type ResourceRunnerOperation =
22
+ | "runner.ready"
23
+ | "effect.reserve"
24
+ | "effect.settle"
25
+ | "workflow.ensure"
26
+ | "workflow.changeSettings"
27
+ | "workflow.queueFollowUp"
28
+ | "workflow.removeFollowUp"
29
+ | "runner.finished"
30
+ | "runner.failed";
31
+
32
+ export type ResourceRunnerMessage = {
33
+ schema: typeof RESOURCE_RUNNER_MESSAGE_SCHEMA;
34
+ launchSchema: typeof RESOURCE_RUNNER_LAUNCH_SCHEMA;
35
+ messageId: string;
36
+ runnerEpoch: string;
37
+ generation: number;
38
+ operation: ResourceRunnerOperation;
39
+ payload: JsonValue;
40
+ };
41
+
42
+ export type ResourceRunnerResponse = {
43
+ schema: typeof RESOURCE_RUNNER_RESPONSE_SCHEMA;
44
+ messageId: string;
45
+ outcome: "accepted" | "rejected" | "claimLost";
46
+ result?: JsonValue;
47
+ error?: string;
48
+ };
49
+
50
+ export function encodeResourceRunnerLine(
51
+ value: ResourceRunnerMessage | ResourceRunnerResponse,
52
+ ): Buffer {
53
+ return Buffer.from(`${canonicalJson(value)}\n`, "utf8");
54
+ }
55
+
56
+ export function parseResourceRunnerMessage(frame: Buffer): ResourceRunnerMessage {
57
+ const value = parseJson(frame.toString("utf8"));
58
+ if (!isRecord(value) || value.schema !== RESOURCE_RUNNER_MESSAGE_SCHEMA) {
59
+ throw new Error("Invalid resource runner message schema");
60
+ }
61
+ if (
62
+ value.launchSchema !== RESOURCE_RUNNER_LAUNCH_SCHEMA ||
63
+ typeof value.messageId !== "string" ||
64
+ typeof value.runnerEpoch !== "string" ||
65
+ !Number.isSafeInteger(value.generation) ||
66
+ typeof value.operation !== "string" ||
67
+ !OPERATIONS.has(value.operation as ResourceRunnerOperation) ||
68
+ !Object.hasOwn(value, "payload")
69
+ ) {
70
+ throw new Error("Invalid resource runner message");
71
+ }
72
+ return value as ResourceRunnerMessage;
73
+ }
74
+
75
+ export function parseResourceRunnerResponse(frame: Buffer): ResourceRunnerResponse {
76
+ const value = parseJson(frame.toString("utf8"));
77
+ if (!isRecord(value) || value.schema !== RESOURCE_RUNNER_RESPONSE_SCHEMA) {
78
+ throw new Error("Invalid resource runner response schema");
79
+ }
80
+ if (
81
+ typeof value.messageId !== "string" ||
82
+ !["accepted", "rejected", "claimLost"].includes(String(value.outcome))
83
+ ) {
84
+ throw new Error("Invalid resource runner response");
85
+ }
86
+ return value as ResourceRunnerResponse;
87
+ }
88
+
89
+ const OPERATIONS = new Set<ResourceRunnerOperation>([
90
+ "runner.ready",
91
+ "effect.reserve",
92
+ "effect.settle",
93
+ "workflow.ensure",
94
+ "workflow.changeSettings",
95
+ "workflow.queueFollowUp",
96
+ "workflow.removeFollowUp",
97
+ "runner.finished",
98
+ "runner.failed",
99
+ ]);
100
+
101
+ function isRecord(value: unknown): value is Record<string, unknown> {
102
+ return typeof value === "object" && value !== null && !Array.isArray(value);
103
+ }