@osolmaz/pi-workflows 0.16.1 → 0.16.3

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 (327) 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 +7 -1
  16. package/dist/client/view.js +1 -0
  17. package/dist/client/view.js.map +1 -1
  18. package/dist/extension/index.d.ts +1 -1
  19. package/dist/extension/index.js +66 -47
  20. package/dist/extension/index.js.map +1 -1
  21. package/dist/extension/recorder.d.ts +1 -1
  22. package/dist/extension/recorder.js +1 -1
  23. package/dist/extension/recorder.js.map +1 -1
  24. package/dist/extension/remote-recorder-store.d.ts +1 -1
  25. package/dist/extension/remote-recorder-store.js +2 -2
  26. package/dist/extension/remote-recorder-store.js.map +1 -1
  27. package/dist/extension/{controller-command.d.ts → resource-manager-command.d.ts} +6 -6
  28. package/dist/extension/{controller-command.js → resource-manager-command.js} +7 -7
  29. package/dist/extension/resource-manager-command.js.map +1 -0
  30. package/dist/extension/session-delivery.d.ts +1 -1
  31. package/dist/extension/session-delivery.js +2 -2
  32. package/dist/extension/session-delivery.js.map +1 -1
  33. package/dist/extension/session-view.d.ts +1 -1
  34. package/dist/extension/session-view.js +1 -1
  35. package/dist/extension/session-view.js.map +1 -1
  36. package/dist/extension/workflow-message-coordinator.d.ts +4 -3
  37. package/dist/extension/workflow-message-coordinator.js +81 -29
  38. package/dist/extension/workflow-message-coordinator.js.map +1 -1
  39. package/dist/resource-managers/conditions.d.ts +6 -0
  40. package/dist/{controllers → resource-managers}/conditions.js +3 -3
  41. package/dist/resource-managers/conditions.js.map +1 -0
  42. package/dist/resource-managers/definition.d.ts +6 -0
  43. package/dist/resource-managers/definition.js +45 -0
  44. package/dist/resource-managers/definition.js.map +1 -0
  45. package/dist/resource-managers/effects.d.ts +15 -0
  46. package/dist/{controllers → resource-managers}/effects.js +2 -2
  47. package/dist/resource-managers/effects.js.map +1 -0
  48. package/dist/resource-managers/errors.d.ts +12 -0
  49. package/dist/resource-managers/errors.js +25 -0
  50. package/dist/resource-managers/errors.js.map +1 -0
  51. package/dist/resource-managers/index.d.ts +12 -0
  52. package/dist/resource-managers/index.js +12 -0
  53. package/dist/resource-managers/index.js.map +1 -0
  54. package/dist/resource-managers/json.js.map +1 -0
  55. package/dist/resource-managers/loader.d.ts +23 -0
  56. package/dist/resource-managers/loader.js +75 -0
  57. package/dist/resource-managers/loader.js.map +1 -0
  58. package/dist/resource-managers/results.d.ts +5 -0
  59. package/dist/resource-managers/results.js.map +1 -0
  60. package/dist/resource-managers/runtime.d.ts +59 -0
  61. package/dist/{controllers/manager.js → resource-managers/runtime.js} +57 -57
  62. package/dist/resource-managers/runtime.js.map +1 -0
  63. package/dist/{controllers → resource-managers}/sqlite.d.ts +51 -43
  64. package/dist/{controllers → resource-managers}/sqlite.js +187 -125
  65. package/dist/resource-managers/sqlite.js.map +1 -0
  66. package/dist/{controllers → resource-managers}/store.d.ts +33 -33
  67. package/dist/{controllers → resource-managers}/store.js.map +1 -1
  68. package/dist/{controllers → resource-managers}/types.d.ts +41 -41
  69. package/dist/{controllers → resource-managers}/types.js.map +1 -1
  70. package/dist/resource-managers/workflows.d.ts +34 -0
  71. package/dist/{controllers → resource-managers}/workflows.js +15 -15
  72. package/dist/resource-managers/workflows.js.map +1 -0
  73. package/dist/server/channel-effects.js.map +1 -0
  74. package/dist/{host → server}/channel-supervisor.d.ts +6 -6
  75. package/dist/{host → server}/channel-supervisor.js +3 -3
  76. package/dist/server/channel-supervisor.js.map +1 -0
  77. package/dist/{host/child-worker-supervisor.d.ts → server/child-runner-supervisor.d.ts} +15 -13
  78. package/dist/{host/child-worker-supervisor.js → server/child-runner-supervisor.js} +6 -5
  79. package/dist/server/child-runner-supervisor.js.map +1 -0
  80. package/dist/{host → server}/processes.d.ts +3 -3
  81. package/dist/{host → server}/processes.js +3 -3
  82. package/dist/server/processes.js.map +1 -0
  83. package/dist/{host → server}/resolver-entry.d.ts +6 -6
  84. package/dist/{host → server}/resolver-entry.js +14 -14
  85. package/dist/server/resolver-entry.js.map +1 -0
  86. package/dist/server/resource-runner-entry.d.ts +2 -0
  87. package/dist/{host/controller-worker-entry.js → server/resource-runner-entry.js} +33 -33
  88. package/dist/server/resource-runner-entry.js.map +1 -0
  89. package/dist/server/resource-runner-protocol.d.ts +36 -0
  90. package/dist/server/resource-runner-protocol.js +49 -0
  91. package/dist/server/resource-runner-protocol.js.map +1 -0
  92. package/dist/server/resource-runner-supervisor.d.ts +21 -0
  93. package/dist/{host/worker-supervisor.js → server/resource-runner-supervisor.js} +17 -17
  94. package/dist/server/resource-runner-supervisor.js.map +1 -0
  95. package/dist/{host → server}/rpc-bridge.d.ts +3 -3
  96. package/dist/{host → server}/rpc-bridge.js +3 -3
  97. package/dist/server/rpc-bridge.js.map +1 -0
  98. package/dist/{host → server}/rpc-executor.d.ts +1 -1
  99. package/dist/{host → server}/rpc-executor.js +3 -3
  100. package/dist/server/rpc-executor.js.map +1 -0
  101. package/dist/{host/host-entry.js → server/server-entry.js} +6 -6
  102. package/dist/server/server-entry.js.map +1 -0
  103. package/dist/{host/runner.d.ts → server/server.d.ts} +47 -39
  104. package/dist/{host/runner.js → server/server.js} +624 -535
  105. package/dist/server/server.js.map +1 -0
  106. package/dist/{host → server}/state.d.ts +25 -25
  107. package/dist/{host → server}/state.js +49 -49
  108. package/dist/server/state.js.map +1 -0
  109. package/dist/{host → server}/view.d.ts +7 -7
  110. package/dist/{host → server}/view.js +19 -15
  111. package/dist/server/view.js.map +1 -0
  112. package/dist/server/workflow-runner-content.d.ts +6 -0
  113. package/dist/server/workflow-runner-content.js +96 -0
  114. package/dist/server/workflow-runner-content.js.map +1 -0
  115. package/dist/server/workflow-runner-entry.d.ts +13 -0
  116. package/dist/{host/worker-entry.js → server/workflow-runner-entry.js} +89 -57
  117. package/dist/server/workflow-runner-entry.js.map +1 -0
  118. package/dist/server/workflow-runner-protocol.d.ts +69 -0
  119. package/dist/server/workflow-runner-protocol.js +176 -0
  120. package/dist/server/workflow-runner-protocol.js.map +1 -0
  121. package/dist/{host/worker-store.d.ts → server/workflow-runner-store.d.ts} +11 -11
  122. package/dist/{host/worker-store.js → server/workflow-runner-store.js} +8 -8
  123. package/dist/server/workflow-runner-store.js.map +1 -0
  124. package/dist/server/workflow-runner-supervisor.d.ts +22 -0
  125. package/dist/server/workflow-runner-supervisor.js +56 -0
  126. package/dist/server/workflow-runner-supervisor.js.map +1 -0
  127. package/dist/state/prune.d.ts +1 -1
  128. package/dist/state/prune.js +17 -10
  129. package/dist/state/prune.js.map +1 -1
  130. package/dist/state/schema.js +2 -2
  131. package/dist/state/workflow-messages.d.ts +3 -1
  132. package/dist/state/workflow-messages.js +32 -1
  133. package/dist/state/workflow-messages.js.map +1 -1
  134. package/dist/viewer/backup.js +1 -1
  135. package/dist/viewer/backup.js.map +1 -1
  136. package/dist/viewer/cli.d.ts +2 -2
  137. package/dist/viewer/cli.js +46 -32
  138. package/dist/viewer/cli.js.map +1 -1
  139. package/dist/viewer/tui.d.ts +1 -1
  140. package/dist/viewer/tui.js +1 -1
  141. package/dist/viewer/tui.js.map +1 -1
  142. package/dist/workflows/command-batch.js +2 -2
  143. package/dist/workflows/composition.js +0 -4
  144. package/dist/workflows/composition.js.map +1 -1
  145. package/dist/workflows/definition.js +0 -8
  146. package/dist/workflows/definition.js.map +1 -1
  147. package/dist/workflows/engine.js +18 -20
  148. package/dist/workflows/engine.js.map +1 -1
  149. package/dist/workflows/schema.js +0 -4
  150. package/dist/workflows/schema.js.map +1 -1
  151. package/dist/workflows/store.d.ts +27 -11
  152. package/dist/workflows/store.js +66 -18
  153. package/dist/workflows/store.js.map +1 -1
  154. package/dist/workflows/types.d.ts +3 -5
  155. package/docs/2026-08-20-durable-workflow-launch-plan.md +12 -12
  156. package/docs/2026-08-25-workflow-follow-ups.md +7 -7
  157. package/docs/2026-08-25-workflow-settings.md +3 -3
  158. package/docs/2026-08-30-out-of-process-workflow-host-plan.md +3 -3
  159. package/docs/2026-09-01-restore-session-delivery-controls-plan.md +5 -5
  160. package/docs/2026-09-01-unified-workflow-client-plan.md +6 -6
  161. package/docs/2026-09-02-installed-live-e2e-plan.md +3 -3
  162. package/docs/2026-09-02-unify-workflow-messages-plan.md +8 -8
  163. package/docs/2026-09-04-workflow-run-state-plan.md +363 -0
  164. package/docs/DEFERRED_TURNS.md +7 -7
  165. package/docs/DESIGN_PHILOSOPHY.md +1 -1
  166. package/docs/HUMAN_DECISIONS.md +11 -11
  167. package/docs/HUMAN_DECISION_PRESENTATIONS.md +4 -4
  168. package/docs/MONITOR.md +1 -1
  169. package/docs/RESOURCE_MANAGERS.md +221 -0
  170. package/docs/SQLITE_STATE.md +40 -34
  171. package/docs/WORKFLOW_COMPOSITION.md +1 -1
  172. package/docs/{WORKFLOW_HOST.md → WORKFLOW_SERVER.md} +131 -116
  173. package/docs/WORKFLOW_STEP_MESSAGES.md +18 -18
  174. package/docs/WORKFLOW_UPDATES.md +16 -16
  175. package/docs/development.md +15 -14
  176. package/docs/live-replay-protocol.md +20 -20
  177. package/docs/plans/2026-08-04-controller-runtime-plan.md +25 -25
  178. package/docs/plans/2026-08-05-always-on-workflows-plan.md +8 -8
  179. package/docs/plans/2026-08-10-agent-managed-monitor-workflows-plan.md +2 -2
  180. package/docs/plans/2026-08-13-session-addressed-workflow-notifications-plan.md +1 -1
  181. package/docs/plans/2026-08-16-workflow-updates-plan.md +5 -5
  182. package/docs/plans/2026-08-19-human-decision-gates-plan.md +1 -1
  183. package/docs/plans/2026-08-20-bounded-command-batches-plan.md +1 -1
  184. package/docs/plans/2026-08-21-deferred-turn-intents-plan.md +3 -3
  185. package/docs/plans/2026-08-21-sanity-check-plan.md +2 -2
  186. package/docs/plans/2026-08-23-assistant-agent-completion-plan.md +5 -5
  187. package/docs/plans/2026-08-23-sqlite-state-plan.md +25 -25
  188. package/docs/plans/2026-08-25-live-workflow-settings-plan.md +4 -4
  189. package/docs/plans/2026-08-27-workflow-terminal-restart-plan.md +3 -3
  190. package/docs/plans/2026-09-04-workflow-runner-resume-state-plan.md +318 -0
  191. package/docs/plans/piw-viewer-experience-implementation-plan.md +1 -1
  192. package/docs/tui-viewer.md +9 -9
  193. package/docs/workflows.md +78 -63
  194. package/examples/{controllers/pull-request.controller.ts → resource-managers/pull-request.resource-manager.ts} +12 -12
  195. package/herdr-plugin.toml +1 -1
  196. package/package.json +5 -5
  197. package/protocol/client.v1.schema.json +7 -7
  198. package/protocol/fixtures/client-v1.json +1 -1
  199. package/src/builtins/monitor.workflow.ts +9 -9
  200. package/src/builtins/pi-agent-group.ts +3 -3
  201. package/src/channels/adapter-entry.ts +14 -14
  202. package/src/client/client.ts +33 -32
  203. package/src/client/protocol.ts +16 -11
  204. package/src/client/resolver.ts +4 -4
  205. package/src/client/view.ts +9 -1
  206. package/src/extension/index.ts +81 -53
  207. package/src/extension/recorder.ts +1 -1
  208. package/src/extension/remote-recorder-store.ts +2 -2
  209. package/src/extension/resource-manager-command.ts +45 -0
  210. package/src/extension/session-delivery.ts +2 -2
  211. package/src/extension/session-view.ts +1 -1
  212. package/src/extension/workflow-message-coordinator.ts +94 -30
  213. package/src/{controllers → resource-managers}/conditions.ts +23 -23
  214. package/src/resource-managers/definition.ts +71 -0
  215. package/src/{controllers → resource-managers}/effects.ts +9 -9
  216. package/src/resource-managers/errors.ts +27 -0
  217. package/src/resource-managers/index.ts +88 -0
  218. package/src/resource-managers/loader.ts +111 -0
  219. package/src/{controllers → resource-managers}/results.ts +4 -4
  220. package/src/{controllers/manager.ts → resource-managers/runtime.ts} +95 -92
  221. package/src/{controllers → resource-managers}/sqlite.ts +275 -181
  222. package/src/{controllers → resource-managers}/store.ts +46 -38
  223. package/src/{controllers → resource-managers}/types.ts +50 -41
  224. package/src/{controllers → resource-managers}/workflows.ts +43 -41
  225. package/src/{host → server}/channel-supervisor.ts +8 -8
  226. package/src/{host/child-worker-supervisor.ts → server/child-runner-supervisor.ts} +22 -18
  227. package/src/{host → server}/processes.ts +3 -3
  228. package/src/{host → server}/resolver-entry.ts +28 -25
  229. package/src/{host/controller-worker-entry.ts → server/resource-runner-entry.ts} +67 -65
  230. package/src/server/resource-runner-protocol.ts +103 -0
  231. package/src/server/resource-runner-supervisor.ts +76 -0
  232. package/src/{host → server}/rpc-bridge.ts +3 -3
  233. package/src/{host → server}/rpc-executor.ts +4 -4
  234. package/src/{host/host-entry.ts → server/server-entry.ts} +5 -5
  235. package/src/{host/runner.ts → server/server.ts} +786 -672
  236. package/src/{host → server}/state.ts +78 -67
  237. package/src/{host → server}/view.ts +21 -17
  238. package/src/server/workflow-runner-content.ts +119 -0
  239. package/src/{host/worker-entry.ts → server/workflow-runner-entry.ts} +131 -82
  240. package/src/server/workflow-runner-protocol.ts +241 -0
  241. package/src/{host/worker-store.ts → server/workflow-runner-store.ts} +18 -20
  242. package/src/server/workflow-runner-supervisor.ts +79 -0
  243. package/src/state/prune.ts +27 -9
  244. package/src/state/schema.ts +2 -2
  245. package/src/state/workflow-messages.ts +52 -1
  246. package/src/viewer/backup.ts +1 -1
  247. package/src/viewer/cli.ts +50 -36
  248. package/src/viewer/tui.ts +1 -1
  249. package/src/workflows/command-batch.ts +2 -2
  250. package/src/workflows/composition.ts +0 -5
  251. package/src/workflows/definition.ts +0 -8
  252. package/src/workflows/engine.ts +18 -21
  253. package/src/workflows/schema.ts +0 -6
  254. package/src/workflows/store.ts +95 -28
  255. package/src/workflows/types.ts +3 -5
  256. package/dist/controllers/conditions.d.ts +0 -6
  257. package/dist/controllers/conditions.js.map +0 -1
  258. package/dist/controllers/definition.d.ts +0 -6
  259. package/dist/controllers/definition.js +0 -45
  260. package/dist/controllers/definition.js.map +0 -1
  261. package/dist/controllers/effects.d.ts +0 -15
  262. package/dist/controllers/effects.js.map +0 -1
  263. package/dist/controllers/errors.d.ts +0 -12
  264. package/dist/controllers/errors.js +0 -25
  265. package/dist/controllers/errors.js.map +0 -1
  266. package/dist/controllers/index.d.ts +0 -12
  267. package/dist/controllers/index.js +0 -12
  268. package/dist/controllers/index.js.map +0 -1
  269. package/dist/controllers/json.js.map +0 -1
  270. package/dist/controllers/loader.d.ts +0 -23
  271. package/dist/controllers/loader.js +0 -74
  272. package/dist/controllers/loader.js.map +0 -1
  273. package/dist/controllers/manager.d.ts +0 -59
  274. package/dist/controllers/manager.js.map +0 -1
  275. package/dist/controllers/results.d.ts +0 -5
  276. package/dist/controllers/results.js.map +0 -1
  277. package/dist/controllers/sqlite.js.map +0 -1
  278. package/dist/controllers/workflows.d.ts +0 -34
  279. package/dist/controllers/workflows.js.map +0 -1
  280. package/dist/extension/controller-command.js.map +0 -1
  281. package/dist/host/channel-effects.js.map +0 -1
  282. package/dist/host/channel-supervisor.js.map +0 -1
  283. package/dist/host/child-worker-supervisor.js.map +0 -1
  284. package/dist/host/controller-worker-entry.d.ts +0 -2
  285. package/dist/host/controller-worker-entry.js.map +0 -1
  286. package/dist/host/controller-worker-protocol.d.ts +0 -36
  287. package/dist/host/controller-worker-protocol.js +0 -49
  288. package/dist/host/controller-worker-protocol.js.map +0 -1
  289. package/dist/host/controller-worker-supervisor.d.ts +0 -21
  290. package/dist/host/controller-worker-supervisor.js +0 -54
  291. package/dist/host/controller-worker-supervisor.js.map +0 -1
  292. package/dist/host/host-entry.js.map +0 -1
  293. package/dist/host/processes.js.map +0 -1
  294. package/dist/host/resolver-entry.js.map +0 -1
  295. package/dist/host/rpc-bridge.js.map +0 -1
  296. package/dist/host/rpc-executor.js.map +0 -1
  297. package/dist/host/runner.js.map +0 -1
  298. package/dist/host/state.js.map +0 -1
  299. package/dist/host/view.js.map +0 -1
  300. package/dist/host/worker-entry.d.ts +0 -10
  301. package/dist/host/worker-entry.js.map +0 -1
  302. package/dist/host/worker-protocol.d.ts +0 -31
  303. package/dist/host/worker-protocol.js +0 -122
  304. package/dist/host/worker-protocol.js.map +0 -1
  305. package/dist/host/worker-store.js.map +0 -1
  306. package/dist/host/worker-supervisor.d.ts +0 -22
  307. package/dist/host/worker-supervisor.js.map +0 -1
  308. package/docs/CONTROLLERS.md +0 -217
  309. package/src/controllers/definition.ts +0 -65
  310. package/src/controllers/errors.ts +0 -27
  311. package/src/controllers/index.ts +0 -88
  312. package/src/controllers/loader.ts +0 -104
  313. package/src/extension/controller-command.ts +0 -45
  314. package/src/host/controller-worker-protocol.ts +0 -104
  315. package/src/host/controller-worker-supervisor.ts +0 -79
  316. package/src/host/worker-protocol.ts +0 -165
  317. package/src/host/worker-supervisor.ts +0 -74
  318. /package/dist/{controllers → resource-managers}/json.d.ts +0 -0
  319. /package/dist/{controllers → resource-managers}/json.js +0 -0
  320. /package/dist/{controllers → resource-managers}/results.js +0 -0
  321. /package/dist/{controllers → resource-managers}/store.js +0 -0
  322. /package/dist/{controllers → resource-managers}/types.js +0 -0
  323. /package/dist/{host → server}/channel-effects.d.ts +0 -0
  324. /package/dist/{host → server}/channel-effects.js +0 -0
  325. /package/dist/{host/host-entry.d.ts → server/server-entry.d.ts} +0 -0
  326. /package/src/{controllers → resource-managers}/json.ts +0 -0
  327. /package/src/{host → server}/channel-effects.ts +0 -0
@@ -25,27 +25,31 @@ import type {
25
25
  WorkflowSource,
26
26
  } from "../workflows/types.js";
27
27
  import { RpcStepExecutor } from "./rpc-executor.js";
28
- import type { WorkerLaunchEnvelope } from "./state.js";
28
+ import type { WorkflowRunnerLaunchEnvelope } from "./state.js";
29
+ import { materializeRunnerContent } from "./workflow-runner-content.js";
29
30
  import {
30
- encodeWorkerLine,
31
- parseWorkerResponse,
32
- type WorkerMessage,
33
- type WorkerResponse,
34
- } from "./worker-protocol.js";
35
- import { HostBackedWorkflowStore, type WorkerStoreTransport } from "./worker-store.js";
31
+ MAX_WORKFLOW_RUNNER_PROTOCOL_MESSAGE_BYTES,
32
+ encodeRunnerLine,
33
+ isRunnerContentChunk,
34
+ isRunnerContentReference,
35
+ parseRunnerResponse,
36
+ type WorkflowRunnerMessage,
37
+ type WorkflowRunnerResponse,
38
+ type WorkflowRunnerCommand,
39
+ } from "./workflow-runner-protocol.js";
40
+ import {
41
+ ServerBackedWorkflowStore,
42
+ type WorkflowRunnerStoreTransport,
43
+ } from "./workflow-runner-store.js";
36
44
 
37
- const STARTUP_ENV = "PI_WORKFLOWS_WORKER_LAUNCH";
45
+ const STARTUP_ENV = "PI_WORKFLOWS_WORKFLOW_RUNNER_LAUNCH";
38
46
  const PRESENTATION_TIMEOUT_MS = 30_000;
39
47
 
40
- type WorkerBootstrap = {
41
- initialized: boolean;
42
- input: JsonValue;
43
- launchOptions: JsonValue;
44
- parentRunId: string | null;
48
+ type WorkflowRunnerBootstrap = {
49
+ command: WorkflowRunnerCommand;
45
50
  originSessionId: string | null;
46
51
  stateDirectory: string;
47
52
  piArgs?: string[];
48
- resumeInteractionAttemptId?: string;
49
53
  candidateInteraction?: {
50
54
  requestId: string;
51
55
  attemptId: string;
@@ -55,21 +59,21 @@ type WorkerBootstrap = {
55
59
  };
56
60
  };
57
61
 
58
- class StdioWorkerTransport implements WorkerStoreTransport {
62
+ class StdioRunnerTransport implements WorkflowRunnerStoreTransport {
59
63
  private readonly pending = new Map<
60
64
  string,
61
- { resolve: (response: WorkerResponse) => void; reject: (error: Error) => void }
65
+ { resolve: (response: WorkflowRunnerResponse) => void; reject: (error: Error) => void }
62
66
  >();
63
67
  private buffered = Buffer.alloc(0);
64
68
 
65
- constructor(private readonly launch: WorkerLaunchEnvelope) {
69
+ constructor(private readonly launch: WorkflowRunnerLaunchEnvelope) {
66
70
  process.stdin.on("data", (chunk: Buffer) => this.onData(chunk));
67
71
  process.stdin.on("error", (error) => this.failAll(error));
68
72
  process.stdin.resume();
69
73
  }
70
74
 
71
- async request(options: Parameters<WorkerStoreTransport["request"]>[0]) {
72
- const message: WorkerMessage = {
75
+ async request(options: Parameters<WorkflowRunnerStoreTransport["request"]>[0]) {
76
+ const message: WorkflowRunnerMessage = {
73
77
  schema: "pi-workflows.worker-message.v1",
74
78
  launchSchema: this.launch.schema,
75
79
  messageId: options.messageId,
@@ -77,17 +81,17 @@ class StdioWorkerTransport implements WorkerStoreTransport {
77
81
  operation: options.operation,
78
82
  runId: this.launch.runId,
79
83
  generation: this.launch.generation,
80
- workerEpoch: this.launch.workerEpoch,
84
+ runnerEpoch: this.launch.runnerEpoch,
81
85
  expectedRevision: options.expectedRevision,
82
86
  ...(options.attemptId === undefined ? {} : { attemptId: options.attemptId }),
83
87
  payload: options.payload,
84
88
  };
85
- const response = await this.send(message);
89
+ const response = await this.sendResolved(message);
86
90
  if (response.outcome === "claimLost") {
87
91
  throw new ClaimLostError(this.launch.runId, "ownerChanged");
88
92
  }
89
93
  if (response.outcome === "rejected") {
90
- throw new Error(response.error ?? "Workflow worker message was rejected");
94
+ throw new Error(response.error ?? "Workflow runner message was rejected");
91
95
  }
92
96
  return {
93
97
  ...(response.result === undefined ? {} : { result: response.result }),
@@ -96,10 +100,10 @@ class StdioWorkerTransport implements WorkerStoreTransport {
96
100
  }
97
101
 
98
102
  async control(
99
- operation: "worker.ready" | "worker.exiting",
103
+ operation: "runner.ready" | "runner.exiting",
100
104
  payload: JsonValue,
101
- ): Promise<WorkerResponse> {
102
- return await this.send({
105
+ ): Promise<WorkflowRunnerResponse> {
106
+ return await this.sendResolved({
103
107
  schema: "pi-workflows.worker-message.v1",
104
108
  launchSchema: this.launch.schema,
105
109
  messageId: randomUUID(),
@@ -107,7 +111,7 @@ class StdioWorkerTransport implements WorkerStoreTransport {
107
111
  operation,
108
112
  runId: this.launch.runId,
109
113
  generation: this.launch.generation,
110
- workerEpoch: this.launch.workerEpoch,
114
+ runnerEpoch: this.launch.runnerEpoch,
111
115
  expectedRevision: 0,
112
116
  payload,
113
117
  });
@@ -117,21 +121,55 @@ class StdioWorkerTransport implements WorkerStoreTransport {
117
121
  process.stdin.pause();
118
122
  process.stdin.removeAllListeners("data");
119
123
  process.stdin.removeAllListeners("error");
120
- this.failAll(new Error("Workflow worker transport closed"));
124
+ this.failAll(new Error("Workflow runner transport closed"));
125
+ }
126
+
127
+ private async sendResolved(message: WorkflowRunnerMessage): Promise<WorkflowRunnerResponse> {
128
+ const response = await this.send(message);
129
+ if (!isRunnerContentReference(response.result)) return response;
130
+ const reference = response.result;
131
+ const expectedRevision = response.revision ?? message.expectedRevision;
132
+ return {
133
+ ...response,
134
+ result: await materializeRunnerContent(reference, async (offset) => {
135
+ const chunkResponse = await this.send({
136
+ schema: "pi-workflows.worker-message.v1",
137
+ launchSchema: this.launch.schema,
138
+ messageId: randomUUID(),
139
+ kind: "runner.progress",
140
+ operation: "content.read",
141
+ runId: this.launch.runId,
142
+ generation: this.launch.generation,
143
+ runnerEpoch: this.launch.runnerEpoch,
144
+ expectedRevision,
145
+ payload: { sha256: reference.sha256, offset },
146
+ });
147
+ if (chunkResponse.outcome === "claimLost") {
148
+ throw new ClaimLostError(this.launch.runId, "ownerChanged");
149
+ }
150
+ if (chunkResponse.outcome !== "accepted" || !isRunnerContentChunk(chunkResponse.result)) {
151
+ throw new Error(chunkResponse.error ?? "Workflow runner content read was rejected");
152
+ }
153
+ return chunkResponse.result;
154
+ }),
155
+ };
121
156
  }
122
157
 
123
- private async send(message: WorkerMessage): Promise<WorkerResponse> {
124
- const response = new Promise<WorkerResponse>((resolve, reject) => {
158
+ private async send(message: WorkflowRunnerMessage): Promise<WorkflowRunnerResponse> {
159
+ const response = new Promise<WorkflowRunnerResponse>((resolve, reject) => {
125
160
  this.pending.set(message.messageId, { resolve, reject });
126
161
  });
127
- if (!process.stdout.write(encodeWorkerLine(message))) await once(process.stdout, "drain");
162
+ if (!process.stdout.write(encodeRunnerLine(message))) await once(process.stdout, "drain");
128
163
  return await response;
129
164
  }
130
165
 
131
166
  private onData(chunk: Buffer): void {
132
167
  this.buffered = Buffer.concat([this.buffered, chunk]);
133
- if (this.buffered.byteLength > 1024 * 1024 && !this.buffered.includes(0x0a)) {
134
- this.failAll(new Error("Worker response exceeds 1 MiB"));
168
+ if (
169
+ this.buffered.byteLength > MAX_WORKFLOW_RUNNER_PROTOCOL_MESSAGE_BYTES &&
170
+ !this.buffered.includes(0x0a)
171
+ ) {
172
+ this.failAll(new Error("Runner response exceeds 1 MiB"));
135
173
  return;
136
174
  }
137
175
  for (;;) {
@@ -141,9 +179,9 @@ class StdioWorkerTransport implements WorkerStoreTransport {
141
179
  this.buffered = this.buffered.subarray(newline + 1);
142
180
  if (frame.byteLength === 0) continue;
143
181
  try {
144
- const response = parseWorkerResponse(frame);
182
+ const response = parseRunnerResponse(frame);
145
183
  const pending = this.pending.get(response.messageId);
146
- if (pending === undefined) throw new Error("Worker response has no pending request");
184
+ if (pending === undefined) throw new Error("Runner response has no pending request");
147
185
  this.pending.delete(response.messageId);
148
186
  pending.resolve(response);
149
187
  } catch (error) {
@@ -163,7 +201,7 @@ class InteractiveExecutor implements AgentStepExecutor {
163
201
  readonly preservesDeadlineWhileParked = true;
164
202
 
165
203
  constructor(
166
- private readonly store: HostBackedWorkflowStore,
204
+ private readonly store: ServerBackedWorkflowStore,
167
205
  private readonly candidate:
168
206
  | {
169
207
  requestId: string;
@@ -278,30 +316,59 @@ export function validateAcceptedAssistantSubmission(
278
316
  };
279
317
  }
280
318
 
281
- export async function runWorkflowWorker(): Promise<number> {
319
+ export async function executeRunnerRunCommand(
320
+ engine: Pick<WorkflowEngine, "run" | "resumeRun" | "continueRun">,
321
+ workflow: WorkflowDefinition,
322
+ runId: string,
323
+ workflowSource: WorkflowSource,
324
+ command: WorkflowRunnerCommand,
325
+ ) {
326
+ switch (command.kind) {
327
+ case "start":
328
+ case "restart":
329
+ return await engine.run(workflow, command.input, { runId, workflowSource });
330
+ case "resume":
331
+ return await engine.resumeRun(workflow, runId, {
332
+ workflowSource,
333
+ ...(command.resumeInteractionAttemptId === undefined
334
+ ? {}
335
+ : { resumeInteractionAttemptId: command.resumeInteractionAttemptId }),
336
+ });
337
+ case "continue":
338
+ return await engine.continueRun(workflow, command.parentRunId, command.input, {
339
+ runId,
340
+ workflowSource,
341
+ ...(command.humanDecision === undefined
342
+ ? {}
343
+ : { humanDecision: command.humanDecision as ResolvedHumanDecision }),
344
+ });
345
+ }
346
+ }
347
+
348
+ export async function runWorkflowRunner(): Promise<number> {
282
349
  const launch = readLaunchEnvelope();
283
- const transport = new StdioWorkerTransport(launch);
350
+ const transport = new StdioRunnerTransport(launch);
284
351
  try {
285
- const ready = await transport.control("worker.ready", {});
352
+ const ready = await transport.control("runner.ready", {});
286
353
  if (ready.outcome !== "accepted" || ready.result === undefined) {
287
- throw new Error(ready.error ?? "Workflow host rejected worker startup");
354
+ throw new Error(ready.error ?? "Workflow server rejected runner startup");
288
355
  }
289
- const bootstrap = ready.result as unknown as WorkerBootstrap;
290
- const workflowSource = parseWorkerWorkflowSources(launch.workflowSource);
356
+ const bootstrap = ready.result as unknown as WorkflowRunnerBootstrap;
357
+ const workflowSource = parseRunnerWorkflowSources(launch.workflowSource);
291
358
  let workflow: WorkflowDefinition;
292
359
  try {
293
360
  workflow = await resolveVerifiedWorkflow(launch.runId, workflowSource);
294
361
  } catch (error) {
295
- const response = await transport.control("worker.exiting", {
362
+ const response = await transport.control("runner.exiting", {
296
363
  status: "workflowLoadFailed",
297
364
  error: errorMessage(error),
298
365
  });
299
366
  if (response.outcome !== "accepted") {
300
- throw new Error(response.error ?? "Workflow host rejected the load-failure report");
367
+ throw new Error(response.error ?? "Workflow server rejected the load-failure report");
301
368
  }
302
369
  return 1;
303
370
  }
304
- const store = new HostBackedWorkflowStore(launch.runId, transport, ready.revision ?? 0);
371
+ const store = new ServerBackedWorkflowStore(launch.runId, transport, ready.revision ?? 0);
305
372
  let executor: AgentStepExecutor;
306
373
  let closeExecutor: (() => Promise<void>) | undefined;
307
374
  if (bootstrap.originSessionId !== null) {
@@ -316,7 +383,7 @@ export async function runWorkflowWorker(): Promise<number> {
316
383
  await transport.request({
317
384
  messageId: randomUUID(),
318
385
  operation: "process.register",
319
- kind: "worker.progress",
386
+ kind: "runner.progress",
320
387
  expectedRevision: 0,
321
388
  payload: { pid },
322
389
  });
@@ -325,7 +392,7 @@ export async function runWorkflowWorker(): Promise<number> {
325
392
  await transport.request({
326
393
  messageId: randomUUID(),
327
394
  operation: "process.unregister",
328
- kind: "worker.progress",
395
+ kind: "runner.progress",
329
396
  expectedRevision: 0,
330
397
  payload: { pid },
331
398
  });
@@ -360,33 +427,15 @@ export async function runWorkflowWorker(): Promise<number> {
360
427
  await store.requestPresentation(instructions);
361
428
  },
362
429
  });
363
- const launchOptions =
364
- typeof bootstrap.launchOptions === "object" &&
365
- bootstrap.launchOptions !== null &&
366
- !Array.isArray(bootstrap.launchOptions)
367
- ? (bootstrap.launchOptions as Record<string, unknown>)
368
- : {};
369
430
  try {
370
- const result = bootstrap.initialized
371
- ? await engine.resumeRun(workflow, launch.runId, {
372
- workflowSource: workflowSource.root,
373
- ...(bootstrap.resumeInteractionAttemptId === undefined
374
- ? {}
375
- : { resumeInteractionAttemptId: bootstrap.resumeInteractionAttemptId }),
376
- })
377
- : bootstrap.parentRunId === null
378
- ? await engine.run(workflow, bootstrap.input, {
379
- runId: launch.runId,
380
- workflowSource: workflowSource.root,
381
- })
382
- : await engine.continueRun(workflow, bootstrap.parentRunId, bootstrap.input, {
383
- runId: launch.runId,
384
- workflowSource: workflowSource.root,
385
- ...(launchOptions.humanDecision === undefined
386
- ? {}
387
- : { humanDecision: launchOptions.humanDecision as ResolvedHumanDecision }),
388
- });
389
- await transport.control("worker.exiting", {
431
+ const result = await executeRunnerRunCommand(
432
+ engine,
433
+ workflow,
434
+ launch.runId,
435
+ workflowSource.root,
436
+ bootstrap.command,
437
+ );
438
+ await transport.control("runner.exiting", {
390
439
  status: result.state.status,
391
440
  traceSeq: result.state.traceSeq,
392
441
  });
@@ -436,25 +485,25 @@ async function resolvePresentationInstructions(
436
485
  }
437
486
  }
438
487
 
439
- type WorkerWorkflowSources = {
488
+ type WorkflowRunnerSources = {
440
489
  root: WorkflowSource;
441
490
  mounted: WorkflowMountedSource[];
442
491
  };
443
492
 
444
- function parseWorkerWorkflowSources(value: JsonValue): WorkerWorkflowSources {
493
+ function parseRunnerWorkflowSources(value: JsonValue): WorkflowRunnerSources {
445
494
  if (!isRecord(value) || !isWorkflowSource(value.root) || !Array.isArray(value.mounted)) {
446
- throw new Error("Workflow worker source identity is invalid");
495
+ throw new Error("Workflow runner source identity is invalid");
447
496
  }
448
497
  const mounted = value.mounted;
449
498
  if (!mounted.every(isMountedWorkflowSource)) {
450
- throw new Error("Workflow worker mounted source identity is invalid");
499
+ throw new Error("Workflow runner mounted source identity is invalid");
451
500
  }
452
501
  return { root: value.root, mounted };
453
502
  }
454
503
 
455
504
  async function resolveVerifiedWorkflow(
456
505
  runId: string,
457
- sources: WorkerWorkflowSources,
506
+ sources: WorkflowRunnerSources,
458
507
  ): Promise<WorkflowDefinition> {
459
508
  for (const source of [sources.root, ...sources.mounted.map((mounted) => mounted.source)]) {
460
509
  if (source.kind === "file") {
@@ -506,9 +555,9 @@ function isRecord(value: unknown): value is Record<string, unknown> {
506
555
  return typeof value === "object" && value !== null && !Array.isArray(value);
507
556
  }
508
557
 
509
- function readLaunchEnvelope(): WorkerLaunchEnvelope {
558
+ function readLaunchEnvelope(): WorkflowRunnerLaunchEnvelope {
510
559
  const encoded = process.env[STARTUP_ENV];
511
- if (encoded === undefined) throw new Error("Workflow worker launch envelope is missing");
560
+ if (encoded === undefined) throw new Error("Workflow runner launch envelope is missing");
512
561
  const value = parseJson(Buffer.from(encoded, "base64url").toString("utf8"));
513
562
  if (
514
563
  typeof value !== "object" ||
@@ -516,14 +565,14 @@ function readLaunchEnvelope(): WorkerLaunchEnvelope {
516
565
  Array.isArray(value) ||
517
566
  (value as { schema?: unknown }).schema !== "pi-workflows.worker-launch.v1"
518
567
  ) {
519
- throw new Error("Workflow worker launch envelope is invalid");
568
+ throw new Error("Workflow runner launch envelope is invalid");
520
569
  }
521
- return value as unknown as WorkerLaunchEnvelope;
570
+ return value as unknown as WorkflowRunnerLaunchEnvelope;
522
571
  }
523
572
 
524
573
  async function main(): Promise<void> {
525
574
  try {
526
- process.exitCode = await runWorkflowWorker();
575
+ process.exitCode = await runWorkflowRunner();
527
576
  } catch (error) {
528
577
  process.stderr.write(`${errorMessage(error)}\n`);
529
578
  process.exitCode = 1;
@@ -0,0 +1,241 @@
1
+ import { canonicalJson, parseJson, type JsonValue } from "../state/json.js";
2
+
3
+ export const MAX_WORKFLOW_RUNNER_PROTOCOL_MESSAGE_BYTES = 1024 * 1024;
4
+ export const WORKFLOW_RUNNER_CONTENT_CHUNK_BYTES = 512 * 1024;
5
+ export const WORKFLOW_RUNNER_MESSAGE_SCHEMA = "pi-workflows.worker-message.v1" as const;
6
+ export const WORKFLOW_RUNNER_RESPONSE_SCHEMA = "pi-workflows.worker-response.v1" as const;
7
+ export const WORKFLOW_RUNNER_CONTENT_REFERENCE_SCHEMA =
8
+ "pi-workflows.worker-content-reference.v1" as const;
9
+ export const WORKFLOW_RUNNER_CONTENT_CHUNK_SCHEMA = "pi-workflows.worker-content-chunk.v1" as const;
10
+
11
+ export type WorkflowRunnerCommand =
12
+ | { kind: "start"; input: JsonValue }
13
+ | { kind: "resume"; resumeInteractionAttemptId?: string }
14
+ | {
15
+ kind: "continue";
16
+ parentRunId: string;
17
+ input: JsonValue;
18
+ humanDecision?: JsonValue;
19
+ }
20
+ | { kind: "restart"; input: JsonValue };
21
+
22
+ export const WORKFLOW_RUNNER_MESSAGE_KINDS = [
23
+ "runner.ready",
24
+ "node.started",
25
+ "node.update",
26
+ "node.finished",
27
+ "node.failed",
28
+ "run.parked",
29
+ "run.finished",
30
+ "interaction.requested",
31
+ "interaction.accepted",
32
+ "interaction.rejected",
33
+ "notification.requested",
34
+ "presentation.requested",
35
+ "effect.reserve",
36
+ "effect.settle",
37
+ "runner.progress",
38
+ "runner.exiting",
39
+ ] as const;
40
+
41
+ export type WorkflowRunnerMessageKind = (typeof WORKFLOW_RUNNER_MESSAGE_KINDS)[number];
42
+ export const WORKFLOW_RUNNER_STORE_OPERATIONS = [
43
+ "store.initializeRun",
44
+ "store.prepareRunResume",
45
+ "store.readRunState",
46
+ "store.writeSnapshot",
47
+ "store.publishUpdate",
48
+ "store.findSettingsScope",
49
+ "store.ensureSettingsScope",
50
+ "store.getSettingsScopeAtChange",
51
+ "store.createHumanDecisionRequest",
52
+ "store.readResolvedHumanDecision",
53
+ "store.reserveEffect",
54
+ "store.settleEffect",
55
+ "content.read",
56
+ "process.register",
57
+ "process.unregister",
58
+ "interaction.request",
59
+ "interaction.accept",
60
+ "interaction.reject",
61
+ "notification.request",
62
+ "presentation.request",
63
+ ] as const;
64
+ export type WorkflowRunnerStoreOperation = (typeof WORKFLOW_RUNNER_STORE_OPERATIONS)[number];
65
+ const WORKFLOW_RUNNER_CONTROL_OPERATIONS = ["runner.ready", "runner.exiting"] as const;
66
+
67
+ export type WorkflowRunnerMessage = {
68
+ schema: typeof WORKFLOW_RUNNER_MESSAGE_SCHEMA;
69
+ launchSchema: "pi-workflows.worker-launch.v1";
70
+ messageId: string;
71
+ kind: WorkflowRunnerMessageKind;
72
+ operation: WorkflowRunnerStoreOperation | "runner.ready" | "runner.exiting";
73
+ runId: string;
74
+ generation: number;
75
+ runnerEpoch: string;
76
+ expectedRevision: number;
77
+ attemptId?: string;
78
+ payload: JsonValue;
79
+ };
80
+
81
+ export type WorkflowRunnerResponse = {
82
+ schema: typeof WORKFLOW_RUNNER_RESPONSE_SCHEMA;
83
+ messageId: string;
84
+ outcome: "accepted" | "adopted" | "rejected" | "claimLost";
85
+ revision?: number;
86
+ result?: JsonValue;
87
+ error?: string;
88
+ };
89
+
90
+ export type WorkflowRunnerContentReference = {
91
+ schema: typeof WORKFLOW_RUNNER_CONTENT_REFERENCE_SCHEMA;
92
+ sha256: string;
93
+ mediaType: "application/json";
94
+ bytes: number;
95
+ };
96
+
97
+ export type WorkflowRunnerContentChunk = {
98
+ schema: typeof WORKFLOW_RUNNER_CONTENT_CHUNK_SCHEMA;
99
+ sha256: string;
100
+ mediaType: "application/json";
101
+ bytes: number;
102
+ offset: number;
103
+ nextOffset: number;
104
+ complete: boolean;
105
+ data: string;
106
+ };
107
+
108
+ export function isRunnerContentReference(value: unknown): value is WorkflowRunnerContentReference {
109
+ return (
110
+ isRecord(value) &&
111
+ value.schema === WORKFLOW_RUNNER_CONTENT_REFERENCE_SCHEMA &&
112
+ typeof value.sha256 === "string" &&
113
+ /^[0-9a-f]{64}$/u.test(value.sha256) &&
114
+ value.mediaType === "application/json" &&
115
+ Number.isSafeInteger(value.bytes) &&
116
+ (value.bytes as number) >= 0
117
+ );
118
+ }
119
+
120
+ export function isRunnerContentChunk(value: unknown): value is WorkflowRunnerContentChunk {
121
+ return (
122
+ isRecord(value) &&
123
+ value.schema === WORKFLOW_RUNNER_CONTENT_CHUNK_SCHEMA &&
124
+ typeof value.sha256 === "string" &&
125
+ /^[0-9a-f]{64}$/u.test(value.sha256) &&
126
+ value.mediaType === "application/json" &&
127
+ Number.isSafeInteger(value.bytes) &&
128
+ (value.bytes as number) >= 0 &&
129
+ Number.isSafeInteger(value.offset) &&
130
+ (value.offset as number) >= 0 &&
131
+ Number.isSafeInteger(value.nextOffset) &&
132
+ (value.nextOffset as number) >= (value.offset as number) &&
133
+ typeof value.complete === "boolean" &&
134
+ typeof value.data === "string"
135
+ );
136
+ }
137
+
138
+ export function runnerKindForOperation(
139
+ operation: WorkflowRunnerStoreOperation,
140
+ payload: Record<string, unknown>,
141
+ ): WorkflowRunnerMessageKind {
142
+ if (operation === "store.publishUpdate") return "node.update";
143
+ if (operation === "store.reserveEffect") return "effect.reserve";
144
+ if (operation === "store.settleEffect") return "effect.settle";
145
+ if (operation === "store.createHumanDecisionRequest" || operation === "interaction.request") {
146
+ return "interaction.requested";
147
+ }
148
+ if (operation === "interaction.accept") return "interaction.accepted";
149
+ if (operation === "interaction.reject") return "interaction.rejected";
150
+ if (operation === "notification.request") return "notification.requested";
151
+ if (operation === "presentation.request") return "presentation.requested";
152
+ if (operation === "store.writeSnapshot") {
153
+ const event = payload.event;
154
+ const eventType = isRecord(event) && typeof event.type === "string" ? event.type : "";
155
+ if (eventType === "node_started") return "node.started";
156
+ if (eventType === "node_finished") return "node.finished";
157
+ if (eventType === "node_failed" || eventType === "node_timed_out") return "node.failed";
158
+ if (eventType === "run_waiting" || eventType.includes("park")) return "run.parked";
159
+ if (eventType.startsWith("run_") && eventType !== "run_started") return "run.finished";
160
+ }
161
+ return "runner.progress";
162
+ }
163
+
164
+ export function encodeRunnerLine(message: WorkflowRunnerMessage | WorkflowRunnerResponse): Buffer {
165
+ const encoded = Buffer.from(`${canonicalJson(message)}\n`, "utf8");
166
+ if (encoded.byteLength > MAX_WORKFLOW_RUNNER_PROTOCOL_MESSAGE_BYTES) {
167
+ throw new Error("Runner protocol message exceeds 1 MiB");
168
+ }
169
+ return encoded;
170
+ }
171
+
172
+ export function parseRunnerMessage(line: Buffer | string): WorkflowRunnerMessage {
173
+ const value = parseRunnerValue(line);
174
+ if (
175
+ value.schema !== WORKFLOW_RUNNER_MESSAGE_SCHEMA ||
176
+ value.launchSchema !== "pi-workflows.worker-launch.v1"
177
+ ) {
178
+ throw new Error("Invalid workflow runner message schema");
179
+ }
180
+ requireText(value.messageId, "messageId");
181
+ requireText(value.runId, "runId");
182
+ requireText(value.runnerEpoch, "runnerEpoch");
183
+ if (!WORKFLOW_RUNNER_MESSAGE_KINDS.includes(value.kind as WorkflowRunnerMessageKind)) {
184
+ throw new Error("Invalid workflow runner message kind");
185
+ }
186
+ if (!Number.isSafeInteger(value.generation) || (value.generation as number) <= 0) {
187
+ throw new Error("Runner generation must be positive");
188
+ }
189
+ if (!Number.isSafeInteger(value.expectedRevision) || (value.expectedRevision as number) < 0) {
190
+ throw new Error("Runner expectedRevision must be non-negative");
191
+ }
192
+ requireText(value.operation, "operation");
193
+ if (
194
+ !WORKFLOW_RUNNER_STORE_OPERATIONS.includes(value.operation as WorkflowRunnerStoreOperation) &&
195
+ !WORKFLOW_RUNNER_CONTROL_OPERATIONS.includes(
196
+ value.operation as (typeof WORKFLOW_RUNNER_CONTROL_OPERATIONS)[number],
197
+ )
198
+ ) {
199
+ throw new Error("Invalid workflow runner operation");
200
+ }
201
+ if (value.attemptId !== undefined) requireText(value.attemptId, "attemptId");
202
+ if (!Object.hasOwn(value, "payload")) throw new Error("Runner payload is required");
203
+ return value as WorkflowRunnerMessage;
204
+ }
205
+
206
+ export function parseRunnerResponse(line: Buffer | string): WorkflowRunnerResponse {
207
+ const value = parseRunnerValue(line);
208
+ if (value.schema !== WORKFLOW_RUNNER_RESPONSE_SCHEMA)
209
+ throw new Error("Invalid workflow runner response schema");
210
+ requireText(value.messageId, "messageId");
211
+ if (!["accepted", "adopted", "rejected", "claimLost"].includes(value.outcome as string)) {
212
+ throw new Error("Invalid workflow runner response outcome");
213
+ }
214
+ if (value.error !== undefined && typeof value.error !== "string") {
215
+ throw new Error("Runner response error must be text");
216
+ }
217
+ return value as WorkflowRunnerResponse;
218
+ }
219
+
220
+ function parseRunnerValue(line: Buffer | string): Record<string, unknown> {
221
+ const buffer = Buffer.isBuffer(line) ? line : Buffer.from(line, "utf8");
222
+ if (buffer.byteLength + 1 > MAX_WORKFLOW_RUNNER_PROTOCOL_MESSAGE_BYTES) {
223
+ throw new Error("Runner protocol message exceeds 1 MiB");
224
+ }
225
+ const text = buffer.toString("utf8");
226
+ const value = parseJson(text);
227
+ if (!isRecord(value) || canonicalJson(value) !== text) {
228
+ throw new Error("Runner protocol message must be a canonical JSON object");
229
+ }
230
+ return value;
231
+ }
232
+
233
+ function requireText(value: unknown, field: string): asserts value is string {
234
+ if (typeof value !== "string" || value.length === 0 || value.length > 256) {
235
+ throw new Error(`Runner ${field} must be nonempty bounded text`);
236
+ }
237
+ }
238
+
239
+ function isRecord(value: unknown): value is Record<string, unknown> {
240
+ return typeof value === "object" && value !== null && !Array.isArray(value);
241
+ }