@pikku/core 0.11.1 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (363) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/dist/crypto-utils.d.ts +2 -0
  3. package/dist/crypto-utils.js +78 -0
  4. package/dist/errors/error-handler.js +3 -3
  5. package/dist/errors/errors.d.ts +8 -0
  6. package/dist/errors/errors.js +16 -0
  7. package/dist/errors/index.d.ts +0 -1
  8. package/dist/errors/index.js +0 -1
  9. package/dist/function/function-runner.d.ts +10 -3
  10. package/dist/function/function-runner.js +136 -31
  11. package/dist/function/functions.types.d.ts +35 -12
  12. package/dist/function/functions.types.js +16 -6
  13. package/dist/function/index.d.ts +2 -2
  14. package/dist/function/index.js +1 -2
  15. package/dist/handle-error.js +1 -10
  16. package/dist/index.d.ts +30 -24
  17. package/dist/index.js +19 -27
  18. package/dist/middleware/auth-apikey.d.ts +1 -1
  19. package/dist/middleware/auth-apikey.js +3 -3
  20. package/dist/middleware/auth-bearer.d.ts +1 -1
  21. package/dist/middleware/auth-bearer.js +3 -6
  22. package/dist/middleware/auth-cookie.d.ts +1 -1
  23. package/dist/middleware/auth-cookie.js +6 -8
  24. package/dist/middleware/cors.d.ts +46 -0
  25. package/dist/middleware/cors.js +73 -0
  26. package/dist/middleware/index.d.ts +5 -3
  27. package/dist/middleware/index.js +5 -3
  28. package/dist/middleware/remote-auth.d.ts +5 -0
  29. package/dist/middleware/remote-auth.js +55 -0
  30. package/dist/middleware/timeout.d.ts +1 -1
  31. package/dist/middleware-runner.d.ts +3 -14
  32. package/dist/middleware-runner.js +14 -16
  33. package/dist/permissions.d.ts +3 -86
  34. package/dist/permissions.js +20 -80
  35. package/dist/pikku-state.d.ts +33 -104
  36. package/dist/pikku-state.js +150 -66
  37. package/dist/schema.d.ts +8 -6
  38. package/dist/schema.js +31 -18
  39. package/dist/services/ai-agent-runner-service.d.ts +24 -0
  40. package/dist/services/ai-agent-runner-service.js +1 -0
  41. package/dist/services/ai-run-state-service.d.ts +13 -0
  42. package/dist/services/ai-run-state-service.js +1 -0
  43. package/dist/services/ai-storage-service.d.ts +18 -0
  44. package/dist/services/ai-storage-service.js +1 -0
  45. package/dist/services/deployment-service.d.ts +19 -0
  46. package/dist/services/deployment-service.js +1 -0
  47. package/dist/services/gopass-secrets.d.ts +47 -0
  48. package/dist/services/gopass-secrets.js +94 -0
  49. package/dist/services/in-memory-trigger-service.d.ts +23 -0
  50. package/dist/services/in-memory-trigger-service.js +50 -0
  51. package/dist/services/in-memory-workflow-service.d.ts +59 -0
  52. package/dist/services/in-memory-workflow-service.js +287 -0
  53. package/dist/services/index.d.ts +25 -11
  54. package/dist/services/index.js +10 -13
  55. package/dist/services/local-secrets.d.ts +23 -0
  56. package/dist/services/local-secrets.js +45 -0
  57. package/dist/services/local-variables.d.ts +5 -0
  58. package/dist/services/local-variables.js +18 -0
  59. package/dist/services/scheduler-service.d.ts +21 -1
  60. package/dist/services/scheduler-service.js +14 -0
  61. package/dist/services/schema-service.d.ts +6 -0
  62. package/dist/services/scoped-secret-service.d.ts +16 -0
  63. package/dist/services/scoped-secret-service.js +35 -0
  64. package/dist/services/secret-service.d.ts +25 -4
  65. package/dist/services/trigger-service.d.ts +23 -0
  66. package/dist/services/trigger-service.js +1 -0
  67. package/dist/services/typed-secret-service.d.ts +30 -0
  68. package/dist/services/typed-secret-service.js +40 -0
  69. package/dist/services/typed-variables-service.d.ts +27 -0
  70. package/dist/services/typed-variables-service.js +46 -0
  71. package/dist/services/user-session-service.d.ts +13 -0
  72. package/dist/services/user-session-service.js +17 -0
  73. package/dist/services/variables-service.d.ts +5 -0
  74. package/dist/services/workflow-service.d.ts +43 -0
  75. package/dist/services/workflow-service.js +1 -0
  76. package/dist/types/core.types.d.ts +54 -10
  77. package/dist/types/core.types.js +7 -0
  78. package/dist/types/state.types.d.ts +127 -0
  79. package/dist/types/state.types.js +1 -0
  80. package/dist/utils.d.ts +10 -1
  81. package/dist/utils.js +60 -3
  82. package/dist/version.d.ts +6 -0
  83. package/dist/version.js +19 -0
  84. package/dist/wirings/ai-agent/ai-agent-memory.d.ts +29 -0
  85. package/dist/wirings/ai-agent/ai-agent-memory.js +199 -0
  86. package/dist/wirings/ai-agent/ai-agent-model-config.d.ts +9 -0
  87. package/dist/wirings/ai-agent/ai-agent-model-config.js +36 -0
  88. package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +60 -0
  89. package/dist/wirings/ai-agent/ai-agent-prepare.js +312 -0
  90. package/dist/wirings/ai-agent/ai-agent-registry.d.ts +15 -0
  91. package/dist/wirings/ai-agent/ai-agent-registry.js +47 -0
  92. package/dist/wirings/ai-agent/ai-agent-runner.d.ts +3 -0
  93. package/dist/wirings/ai-agent/ai-agent-runner.js +96 -0
  94. package/dist/wirings/ai-agent/ai-agent-stream.d.ts +11 -0
  95. package/dist/wirings/ai-agent/ai-agent-stream.js +503 -0
  96. package/dist/wirings/ai-agent/ai-agent.types.d.ts +254 -0
  97. package/dist/wirings/ai-agent/ai-agent.types.js +1 -0
  98. package/dist/wirings/ai-agent/index.d.ts +5 -0
  99. package/dist/wirings/ai-agent/index.js +4 -0
  100. package/dist/wirings/channel/channel-common.d.ts +7 -4
  101. package/dist/wirings/channel/channel-common.js +15 -8
  102. package/dist/wirings/channel/channel-handler.js +13 -4
  103. package/dist/wirings/channel/channel-middleware-runner.d.ts +9 -0
  104. package/dist/wirings/channel/channel-middleware-runner.js +73 -0
  105. package/dist/wirings/channel/channel-runner.js +12 -12
  106. package/dist/wirings/channel/channel.types.d.ts +5 -1
  107. package/dist/wirings/channel/define-channel-routes.d.ts +21 -0
  108. package/dist/wirings/channel/define-channel-routes.js +23 -0
  109. package/dist/wirings/channel/index.d.ts +10 -8
  110. package/dist/wirings/channel/index.js +7 -8
  111. package/dist/wirings/channel/local/index.d.ts +3 -3
  112. package/dist/wirings/channel/local/index.js +3 -3
  113. package/dist/wirings/channel/local/local-channel-runner.js +18 -6
  114. package/dist/wirings/channel/log-channels.js +1 -1
  115. package/dist/wirings/channel/serverless/index.d.ts +1 -1
  116. package/dist/wirings/channel/serverless/index.js +1 -1
  117. package/dist/wirings/channel/serverless/serverless-channel-runner.js +17 -6
  118. package/dist/wirings/cli/channel/cli-channel-runner.js +3 -2
  119. package/dist/wirings/cli/channel/index.d.ts +1 -1
  120. package/dist/wirings/cli/channel/index.js +1 -1
  121. package/dist/wirings/cli/cli-runner.d.ts +1 -1
  122. package/dist/wirings/cli/cli-runner.js +22 -18
  123. package/dist/wirings/cli/cli.types.d.ts +7 -6
  124. package/dist/wirings/cli/define-cli-commands.d.ts +19 -0
  125. package/dist/wirings/cli/define-cli-commands.js +20 -0
  126. package/dist/wirings/cli/index.d.ts +2 -3
  127. package/dist/wirings/cli/index.js +1 -3
  128. package/dist/wirings/http/http-routes.d.ts +69 -0
  129. package/dist/wirings/http/http-routes.js +139 -0
  130. package/dist/wirings/http/http-runner.d.ts +2 -2
  131. package/dist/wirings/http/http-runner.js +62 -27
  132. package/dist/wirings/http/http.types.d.ts +77 -24
  133. package/dist/wirings/http/index.d.ts +5 -4
  134. package/dist/wirings/http/index.js +4 -3
  135. package/dist/wirings/http/log-http-routes.js +1 -1
  136. package/dist/wirings/http/routers/path-to-regex.js +2 -2
  137. package/dist/wirings/mcp/index.d.ts +4 -3
  138. package/dist/wirings/mcp/index.js +3 -3
  139. package/dist/wirings/mcp/mcp-runner.d.ts +1 -3
  140. package/dist/wirings/mcp/mcp-runner.js +42 -56
  141. package/dist/wirings/mcp/mcp.types.d.ts +3 -3
  142. package/dist/wirings/node/index.d.ts +2 -0
  143. package/dist/wirings/node/index.js +1 -0
  144. package/dist/wirings/node/node.types.d.ts +20 -0
  145. package/dist/wirings/node/node.types.js +1 -0
  146. package/dist/wirings/oauth2/index.d.ts +3 -0
  147. package/dist/wirings/oauth2/index.js +2 -0
  148. package/dist/wirings/oauth2/oauth2-client.d.ts +47 -0
  149. package/dist/wirings/oauth2/oauth2-client.js +267 -0
  150. package/dist/wirings/oauth2/oauth2.types.d.ts +65 -0
  151. package/dist/wirings/oauth2/oauth2.types.js +4 -0
  152. package/dist/wirings/oauth2/wire-oauth2-credential.d.ts +20 -0
  153. package/dist/wirings/oauth2/wire-oauth2-credential.js +21 -0
  154. package/dist/wirings/queue/index.d.ts +2 -2
  155. package/dist/wirings/queue/index.js +1 -1
  156. package/dist/wirings/queue/queue-runner.d.ts +7 -0
  157. package/dist/wirings/queue/queue-runner.js +23 -12
  158. package/dist/wirings/queue/queue.types.d.ts +8 -2
  159. package/dist/wirings/rpc/index.d.ts +1 -1
  160. package/dist/wirings/rpc/index.js +1 -1
  161. package/dist/wirings/rpc/rpc-runner.d.ts +40 -13
  162. package/dist/wirings/rpc/rpc-runner.js +191 -25
  163. package/dist/wirings/rpc/rpc-types.d.ts +15 -4
  164. package/dist/wirings/scheduler/index.d.ts +3 -3
  165. package/dist/wirings/scheduler/index.js +2 -3
  166. package/dist/wirings/scheduler/log-schedulers.js +1 -1
  167. package/dist/wirings/scheduler/scheduler-runner.d.ts +5 -0
  168. package/dist/wirings/scheduler/scheduler-runner.js +28 -10
  169. package/dist/wirings/secret/index.d.ts +3 -0
  170. package/dist/wirings/secret/index.js +2 -0
  171. package/dist/wirings/secret/secret.types.d.ts +27 -0
  172. package/dist/wirings/secret/secret.types.js +1 -0
  173. package/dist/wirings/secret/validate-secret-definitions.d.ts +6 -0
  174. package/dist/wirings/secret/validate-secret-definitions.js +55 -0
  175. package/dist/wirings/trigger/index.d.ts +3 -0
  176. package/dist/wirings/trigger/index.js +2 -0
  177. package/dist/wirings/trigger/pikku-trigger-service.d.ts +26 -0
  178. package/dist/wirings/trigger/pikku-trigger-service.js +75 -0
  179. package/dist/wirings/trigger/trigger-runner.d.ts +42 -0
  180. package/dist/wirings/trigger/trigger-runner.js +85 -0
  181. package/dist/wirings/trigger/trigger.types.d.ts +130 -0
  182. package/dist/wirings/trigger/trigger.types.js +40 -0
  183. package/dist/wirings/variable/index.d.ts +3 -0
  184. package/dist/wirings/variable/index.js +2 -0
  185. package/dist/wirings/variable/validate-variable-definitions.d.ts +6 -0
  186. package/dist/wirings/variable/validate-variable-definitions.js +45 -0
  187. package/dist/wirings/variable/variable.types.d.ts +18 -0
  188. package/dist/wirings/variable/variable.types.js +1 -0
  189. package/dist/wirings/workflow/dsl/index.d.ts +5 -0
  190. package/dist/wirings/workflow/dsl/index.js +4 -0
  191. package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +292 -0
  192. package/dist/wirings/workflow/dsl/workflow-dsl.types.js +5 -0
  193. package/dist/wirings/workflow/dsl/workflow-runner.d.ts +5 -0
  194. package/dist/wirings/workflow/dsl/workflow-runner.js +23 -0
  195. package/dist/wirings/workflow/graph/graph-node.d.ts +117 -0
  196. package/dist/wirings/workflow/graph/graph-node.js +58 -0
  197. package/dist/wirings/workflow/graph/graph-runner.d.ts +9 -0
  198. package/dist/wirings/workflow/graph/graph-runner.js +540 -0
  199. package/dist/wirings/workflow/graph/index.d.ts +4 -0
  200. package/dist/wirings/workflow/graph/index.js +3 -0
  201. package/dist/wirings/workflow/graph/template.d.ts +15 -0
  202. package/dist/wirings/workflow/graph/template.js +27 -0
  203. package/dist/wirings/workflow/graph/wire-workflow-graph.d.ts +15 -0
  204. package/dist/wirings/workflow/graph/wire-workflow-graph.js +8 -0
  205. package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +82 -0
  206. package/dist/wirings/workflow/graph/workflow-graph.types.js +15 -0
  207. package/dist/wirings/workflow/index.d.ts +7 -12
  208. package/dist/wirings/workflow/index.js +8 -11
  209. package/dist/wirings/workflow/pikku-workflow-service.d.ts +114 -10
  210. package/dist/wirings/workflow/pikku-workflow-service.js +386 -115
  211. package/dist/wirings/workflow/workflow-helpers.d.ts +36 -0
  212. package/dist/wirings/workflow/workflow-helpers.js +38 -0
  213. package/dist/wirings/workflow/workflow-utils.d.ts +1 -0
  214. package/dist/wirings/workflow/workflow-utils.js +1 -0
  215. package/dist/wirings/workflow/workflow.types.d.ts +83 -214
  216. package/package.json +13 -6
  217. package/run-tests.sh +4 -1
  218. package/src/crypto-utils.ts +99 -0
  219. package/src/errors/error-handler.ts +3 -3
  220. package/src/errors/errors.ts +17 -0
  221. package/src/errors/index.ts +0 -1
  222. package/src/factory-functions.test.ts +42 -2
  223. package/src/function/function-runner.test.ts +5 -12
  224. package/src/function/function-runner.ts +195 -40
  225. package/src/function/functions.types.ts +66 -14
  226. package/src/function/index.ts +5 -2
  227. package/src/handle-error.ts +2 -10
  228. package/src/index.ts +106 -27
  229. package/src/middleware/auth-apikey.test.ts +15 -12
  230. package/src/middleware/auth-apikey.ts +3 -3
  231. package/src/middleware/auth-bearer.test.ts +17 -14
  232. package/src/middleware/auth-bearer.ts +25 -27
  233. package/src/middleware/auth-cookie.test.ts +16 -13
  234. package/src/middleware/auth-cookie.ts +6 -8
  235. package/src/middleware/cors.ts +96 -0
  236. package/src/middleware/index.ts +5 -3
  237. package/src/middleware/remote-auth.ts +68 -0
  238. package/src/middleware-runner.test.ts +122 -0
  239. package/src/middleware-runner.ts +24 -17
  240. package/src/permissions.test.ts +102 -86
  241. package/src/permissions.ts +32 -93
  242. package/src/pikku-state.ts +168 -201
  243. package/src/run-tests-script.test.ts +49 -0
  244. package/src/schema.ts +50 -18
  245. package/src/services/ai-agent-runner-service.ts +29 -0
  246. package/src/services/ai-run-state-service.ts +20 -0
  247. package/src/services/ai-storage-service.ts +39 -0
  248. package/src/services/deployment-service.ts +22 -0
  249. package/src/services/gopass-secrets.ts +95 -0
  250. package/src/services/in-memory-trigger-service.ts +68 -0
  251. package/src/services/in-memory-workflow-service.ts +395 -0
  252. package/src/services/index.ts +49 -14
  253. package/src/services/local-secrets.ts +51 -0
  254. package/src/services/local-variables.ts +22 -0
  255. package/src/services/scheduler-service.ts +29 -1
  256. package/src/services/schema-service.ts +7 -0
  257. package/src/services/scoped-secret-service.ts +41 -0
  258. package/src/services/secret-service.ts +25 -4
  259. package/src/services/trigger-service.ts +38 -0
  260. package/src/services/typed-secret-service.ts +70 -0
  261. package/src/services/typed-variables-service.ts +81 -0
  262. package/src/services/user-session-service.ts +23 -0
  263. package/src/services/variables-service.ts +5 -0
  264. package/src/services/workflow-service.ts +94 -0
  265. package/src/types/core.types.ts +82 -8
  266. package/src/types/state.types.ts +169 -0
  267. package/src/utils.ts +74 -5
  268. package/src/version.test.ts +80 -0
  269. package/src/version.ts +25 -0
  270. package/src/wirings/ai-agent/ai-agent-memory.ts +300 -0
  271. package/src/wirings/ai-agent/ai-agent-model-config.ts +43 -0
  272. package/src/wirings/ai-agent/ai-agent-prepare.ts +470 -0
  273. package/src/wirings/ai-agent/ai-agent-registry.test.ts +37 -0
  274. package/src/wirings/ai-agent/ai-agent-registry.ts +82 -0
  275. package/src/wirings/ai-agent/ai-agent-runner.test.ts +75 -0
  276. package/src/wirings/ai-agent/ai-agent-runner.ts +137 -0
  277. package/src/wirings/ai-agent/ai-agent-stream.test.ts +246 -0
  278. package/src/wirings/ai-agent/ai-agent-stream.ts +774 -0
  279. package/src/wirings/ai-agent/ai-agent.types.ts +273 -0
  280. package/src/wirings/ai-agent/index.ts +28 -0
  281. package/src/wirings/channel/channel-common.ts +32 -19
  282. package/src/wirings/channel/channel-handler.ts +18 -5
  283. package/src/wirings/channel/channel-middleware-runner.ts +119 -0
  284. package/src/wirings/channel/channel-runner.ts +14 -15
  285. package/src/wirings/channel/channel.types.ts +17 -1
  286. package/src/wirings/channel/define-channel-routes.ts +25 -0
  287. package/src/wirings/channel/index.ts +23 -8
  288. package/src/wirings/channel/local/index.ts +3 -3
  289. package/src/wirings/channel/local/local-channel-runner.test.ts +47 -2
  290. package/src/wirings/channel/local/local-channel-runner.ts +22 -6
  291. package/src/wirings/channel/log-channels.ts +1 -1
  292. package/src/wirings/channel/serverless/index.ts +5 -1
  293. package/src/wirings/channel/serverless/serverless-channel-runner.ts +22 -6
  294. package/src/wirings/cli/channel/cli-channel-runner.ts +4 -3
  295. package/src/wirings/cli/channel/index.ts +1 -1
  296. package/src/wirings/cli/cli-runner.test.ts +28 -28
  297. package/src/wirings/cli/cli-runner.ts +27 -19
  298. package/src/wirings/cli/cli.types.ts +12 -11
  299. package/src/wirings/cli/command-parser.test.ts +8 -8
  300. package/src/wirings/cli/define-cli-commands.ts +24 -0
  301. package/src/wirings/cli/index.ts +10 -4
  302. package/src/wirings/http/http-routes.test.ts +322 -0
  303. package/src/wirings/http/http-routes.ts +198 -0
  304. package/src/wirings/http/http-runner.test.ts +5 -5
  305. package/src/wirings/http/http-runner.ts +80 -29
  306. package/src/wirings/http/http.types.ts +121 -23
  307. package/src/wirings/http/index.ts +16 -4
  308. package/src/wirings/http/log-http-routes.ts +1 -1
  309. package/src/wirings/http/routers/path-to-regex.test.ts +30 -19
  310. package/src/wirings/http/routers/path-to-regex.ts +2 -2
  311. package/src/wirings/mcp/index.ts +27 -3
  312. package/src/wirings/mcp/mcp-endpoint-registry.test.ts +3 -3
  313. package/src/wirings/mcp/mcp-runner.ts +56 -67
  314. package/src/wirings/mcp/mcp.types.ts +3 -3
  315. package/src/wirings/node/index.ts +2 -0
  316. package/src/wirings/node/node.types.ts +23 -0
  317. package/src/wirings/oauth2/index.ts +3 -0
  318. package/src/wirings/oauth2/oauth2-client.test.ts +929 -0
  319. package/src/wirings/oauth2/oauth2-client.ts +335 -0
  320. package/src/wirings/oauth2/oauth2.types.ts +69 -0
  321. package/src/wirings/oauth2/wire-oauth2-credential.ts +23 -0
  322. package/src/wirings/queue/index.ts +14 -2
  323. package/src/wirings/queue/queue-runner.test.ts +115 -73
  324. package/src/wirings/queue/queue-runner.ts +39 -13
  325. package/src/wirings/queue/queue.types.ts +12 -2
  326. package/src/wirings/rpc/index.ts +1 -1
  327. package/src/wirings/rpc/rpc-runner.ts +270 -34
  328. package/src/wirings/rpc/rpc-types.ts +21 -4
  329. package/src/wirings/scheduler/index.ts +7 -4
  330. package/src/wirings/scheduler/log-schedulers.ts +1 -1
  331. package/src/wirings/scheduler/scheduler-runner.test.ts +97 -53
  332. package/src/wirings/scheduler/scheduler-runner.ts +50 -10
  333. package/src/wirings/secret/index.ts +9 -0
  334. package/src/wirings/secret/secret.types.ts +32 -0
  335. package/src/wirings/secret/validate-secret-definitions.ts +82 -0
  336. package/src/wirings/trigger/index.ts +10 -0
  337. package/src/wirings/trigger/pikku-trigger-service.ts +134 -0
  338. package/src/wirings/trigger/trigger-runner.ts +138 -0
  339. package/src/wirings/trigger/trigger.types.ts +178 -0
  340. package/src/wirings/variable/index.ts +8 -0
  341. package/src/wirings/variable/validate-variable-definitions.ts +69 -0
  342. package/src/wirings/variable/variable.types.ts +22 -0
  343. package/src/wirings/workflow/dsl/index.ts +31 -0
  344. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +320 -0
  345. package/src/wirings/workflow/dsl/workflow-runner.ts +28 -0
  346. package/src/wirings/workflow/graph/graph-node.ts +222 -0
  347. package/src/wirings/workflow/graph/graph-runner.test.ts +495 -0
  348. package/src/wirings/workflow/graph/graph-runner.ts +816 -0
  349. package/src/wirings/workflow/graph/index.ts +14 -0
  350. package/src/wirings/workflow/graph/template.ts +42 -0
  351. package/src/wirings/workflow/graph/wire-workflow-graph.ts +29 -0
  352. package/src/wirings/workflow/graph/workflow-graph.types.ts +104 -0
  353. package/src/wirings/workflow/index.ts +64 -26
  354. package/src/wirings/workflow/pikku-workflow-service.test.ts +212 -0
  355. package/src/wirings/workflow/pikku-workflow-service.ts +571 -136
  356. package/src/wirings/workflow/workflow-helpers.ts +79 -0
  357. package/src/wirings/workflow/workflow-utils.ts +0 -0
  358. package/src/wirings/workflow/workflow.types.ts +123 -279
  359. package/tsconfig.tsbuildinfo +1 -1
  360. package/dist/wirings/workflow/workflow-runner.d.ts +0 -34
  361. package/dist/wirings/workflow/workflow-runner.js +0 -65
  362. package/src/function/function-runner.ts.bak +0 -188
  363. package/src/wirings/workflow/workflow-runner.ts +0 -72
@@ -9,21 +9,86 @@ import {
9
9
  SerializedError,
10
10
  } from '../../types/core.types.js'
11
11
  import { QueueService } from '../queue/queue.types.js'
12
- import {
13
- WorkflowAsyncException,
14
- WorkflowCancelledException,
15
- WorkflowNotFoundError,
16
- WorkflowRunNotFound,
17
- } from './workflow-runner.js'
18
12
  import type {
19
13
  PikkuWorkflowWire,
20
14
  StepState,
21
15
  WorkflowRun,
16
+ WorkflowRunWire,
22
17
  WorkflowStatus,
23
- WorkflowService,
24
18
  WorkflowServiceConfig,
25
19
  WorkflowStepOptions,
26
20
  } from './workflow.types.js'
21
+ import {
22
+ continueGraph,
23
+ executeGraphStep,
24
+ runWorkflowGraph,
25
+ runFromMeta,
26
+ } from './graph/graph-runner.js'
27
+ import { WorkflowService } from '../../services/workflow-service.js'
28
+ import { PikkuError, addError } from '../../errors/error-handler.js'
29
+ import { RPCNotFoundError } from '../rpc/rpc-runner.js'
30
+
31
+ /**
32
+ * Exception thrown when workflow needs to pause for async step
33
+ */
34
+ export class WorkflowAsyncException extends Error {
35
+ constructor(
36
+ public readonly runId: string,
37
+ public readonly stepName: string
38
+ ) {
39
+ super(`Workflow paused at step: ${stepName}`)
40
+ this.name = 'WorkflowAsyncException'
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Exception thrown when workflow is cancelled
46
+ */
47
+ export class WorkflowCancelledException extends Error {
48
+ constructor(
49
+ public readonly runId: string,
50
+ public readonly reason?: string
51
+ ) {
52
+ super(reason || 'Workflow cancelled')
53
+ this.name = 'WorkflowCancelledException'
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Exception thrown when workflow is suspended
59
+ */
60
+ export class WorkflowSuspendedException extends Error {
61
+ constructor(
62
+ public readonly runId: string,
63
+ public readonly reason: string
64
+ ) {
65
+ super(reason || 'Workflow suspended')
66
+ this.name = 'WorkflowSuspendedException'
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Error class for workflow not found
72
+ */
73
+ export class WorkflowNotFoundError extends PikkuError {
74
+ constructor(name: string) {
75
+ super(`Workflow not found: ${name}`)
76
+ }
77
+ }
78
+ addError(WorkflowNotFoundError, {
79
+ status: 404,
80
+ message: 'Workflow not found.',
81
+ })
82
+
83
+ export class WorkflowRunNotFoundError extends PikkuError {
84
+ constructor(runId: string) {
85
+ super(`Workflow run not found: ${runId}`)
86
+ }
87
+ }
88
+ addError(WorkflowRunNotFoundError, {
89
+ status: 404,
90
+ message: 'Workflow run not found.',
91
+ })
27
92
 
28
93
  export class WorkflowServiceNotInitialized extends Error {}
29
94
  export class WorkflowStepNameNotString extends Error {
@@ -32,6 +97,13 @@ export class WorkflowStepNameNotString extends Error {
32
97
  }
33
98
  }
34
99
 
100
+ const WORKFLOW_END_STATES: ReadonlySet<string> = new Set([
101
+ 'completed',
102
+ 'failed',
103
+ 'cancelled',
104
+ 'suspended',
105
+ ])
106
+
35
107
  /**
36
108
  * Abstract workflow state service
37
109
  * Implementations provide pluggable storage backends (SQLite, PostgreSQL, etc.)
@@ -41,12 +113,34 @@ export abstract class PikkuWorkflowService implements WorkflowService {
41
113
  private config: WorkflowServiceConfig | undefined
42
114
  private singletonServices: CoreSingletonServices | undefined
43
115
  private createWireServices: CreateWireServices | undefined
116
+ private inlineRuns = new Set<string>()
44
117
 
45
118
  constructor() {}
46
119
 
120
+ /**
121
+ * Check if a run is executing inline (without queues)
122
+ */
123
+ protected isInline(runId: string): boolean {
124
+ return this.inlineRuns.has(runId)
125
+ }
126
+
127
+ /**
128
+ * Register a run as inline (for graph-runner to use)
129
+ */
130
+ public registerInlineRun(runId: string): void {
131
+ this.inlineRuns.add(runId)
132
+ }
133
+
134
+ /**
135
+ * Unregister a run from inline tracking
136
+ */
137
+ public unregisterInlineRun(runId: string): void {
138
+ this.inlineRuns.delete(runId)
139
+ }
140
+
47
141
  public setServices(
48
142
  singletonServices: CoreSingletonServices,
49
- createWireServices: CreateWireServices,
143
+ createWireServices: CreateWireServices | undefined,
50
144
  { workflow }: CoreConfig
51
145
  ) {
52
146
  this.singletonServices = singletonServices
@@ -58,17 +152,25 @@ export abstract class PikkuWorkflowService implements WorkflowService {
58
152
  workflow?.orchestratorQueueName ?? 'pikku-workflow-orchestrator',
59
153
  stepWorkerQueueName:
60
154
  workflow?.stepWorkerQueueName ?? 'pikku-workflow-step-worker',
61
- sleeperRPCName: workflow?.sleeperRPCName ?? 'pikkuWorkflowStepSleeper',
155
+ sleeperRPCName: workflow?.sleeperRPCName ?? 'pikkuWorkflowSleeper',
62
156
  }
63
157
  }
64
158
 
65
- /**
66
- * Create a new workflow run
67
- * @param name - Workflow name
68
- * @param input - Input data for the workflow
69
- * @returns Run ID
70
- */
71
- abstract createRun(workflowName: string, input: any): Promise<string>
159
+ public async registerWorkflowVersions(): Promise<void> {
160
+ const allMeta = pikkuState(null, 'workflows', 'meta')
161
+ for (const [name, meta] of Object.entries(allMeta)) {
162
+ if (!meta.graphHash) continue
163
+ await this.upsertWorkflowVersion(name, meta.graphHash, meta, meta.source)
164
+ }
165
+ }
166
+
167
+ abstract createRun(
168
+ workflowName: string,
169
+ input: any,
170
+ inline: boolean,
171
+ graphHash: string,
172
+ wire: WorkflowRunWire
173
+ ): Promise<string>
72
174
 
73
175
  /**
74
176
  * Get a workflow run by ID
@@ -193,6 +295,81 @@ export abstract class PikkuWorkflowService implements WorkflowService {
193
295
  */
194
296
  abstract close(): Promise<void>
195
297
 
298
+ // ============================================================================
299
+ // Workflow Graph Methods
300
+ // ============================================================================
301
+
302
+ /**
303
+ * Get completed graph state (lightweight - no results)
304
+ * @param runId - Run ID
305
+ * @returns Completed node IDs and their branch keys
306
+ */
307
+ abstract getCompletedGraphState(runId: string): Promise<{
308
+ completedNodeIds: string[]
309
+ failedNodeIds: string[]
310
+ branchKeys: Record<string, string>
311
+ }>
312
+
313
+ /**
314
+ * Filter candidate nodes to only those without existing steps
315
+ * @param runId - Run ID
316
+ * @param nodeIds - Candidate node IDs to check
317
+ * @returns Node IDs that don't have a step yet
318
+ */
319
+ abstract getNodesWithoutSteps(
320
+ runId: string,
321
+ nodeIds: string[]
322
+ ): Promise<string[]>
323
+
324
+ /**
325
+ * Get results for specific nodes
326
+ * @param runId - Run ID
327
+ * @param nodeIds - Node IDs to fetch results for
328
+ * @returns Map of nodeId to result
329
+ */
330
+ abstract getNodeResults(
331
+ runId: string,
332
+ nodeIds: string[]
333
+ ): Promise<Record<string, any>>
334
+
335
+ /**
336
+ * Set the branch key for a graph node step
337
+ * @param stepId - Step ID
338
+ * @param branchKey - Branch key selected by graph.branch()
339
+ */
340
+ abstract setBranchTaken(stepId: string, branchKey: string): Promise<void>
341
+
342
+ /**
343
+ * Update a state variable in the workflow run's state
344
+ * @param runId - Run ID
345
+ * @param name - Variable name
346
+ * @param value - Value to store
347
+ */
348
+ abstract updateRunState(
349
+ runId: string,
350
+ name: string,
351
+ value: unknown
352
+ ): Promise<void>
353
+
354
+ /**
355
+ * Get the entire state object for a workflow run
356
+ * @param runId - Run ID
357
+ * @returns The state object with all variables
358
+ */
359
+ abstract getRunState(runId: string): Promise<Record<string, unknown>>
360
+
361
+ abstract upsertWorkflowVersion(
362
+ name: string,
363
+ graphHash: string,
364
+ graph: any,
365
+ source: string
366
+ ): Promise<void>
367
+
368
+ abstract getWorkflowVersion(
369
+ name: string,
370
+ graphHash: string
371
+ ): Promise<{ graph: any; source: string } | null>
372
+
196
373
  // ============================================================================
197
374
  // Workflow Lifecycle Methods
198
375
  // ============================================================================
@@ -206,12 +383,27 @@ export abstract class PikkuWorkflowService implements WorkflowService {
206
383
  await queueService.add(this.getConfig().orchestratorQueueName, { runId })
207
384
  }
208
385
 
386
+ public async queueStepWorker(
387
+ runId: string,
388
+ stepName: string,
389
+ rpcName: string,
390
+ data: any
391
+ ): Promise<void> {
392
+ const queueService = this.verifyQueueService()
393
+ await queueService.add(this.getConfig().stepWorkerQueueName, {
394
+ runId,
395
+ stepName,
396
+ rpcName,
397
+ data,
398
+ })
399
+ }
400
+
209
401
  /**
210
402
  * Execute a workflow sleep step completion
211
403
  * Sets the step result to null and resumes the workflow
212
404
  * @param data - Sleeper input data
213
405
  */
214
- public async executeWorkflowSleep(
406
+ public async executeWorkflowSleepCompleted(
215
407
  runId: string,
216
408
  stepId: string
217
409
  ): Promise<void> {
@@ -238,51 +430,141 @@ export abstract class PikkuWorkflowService implements WorkflowService {
238
430
 
239
431
  /**
240
432
  * Start a new workflow run
433
+ * Automatically detects workflow type (DSL or graph) from meta and executes accordingly
434
+ * @param options.inline - If true, execute workflow directly without queue service
435
+ * @param options.startNode - Starting node ID for graph workflows (from wire config)
241
436
  */
242
437
  public async startWorkflow<I>(
243
438
  name: string,
244
439
  input: I,
245
- rpcService: any
440
+ wire: WorkflowRunWire,
441
+ rpcService: any,
442
+ options?: { inline?: boolean; startNode?: string }
246
443
  ): Promise<{ runId: string }> {
247
- const registrations = pikkuState('workflows', 'registrations')
444
+ // Check meta to determine workflow type
445
+ const meta = pikkuState(null, 'workflows', 'meta')
446
+ const workflowMeta = meta[name]
447
+
448
+ if (!workflowMeta) {
449
+ throw new WorkflowNotFoundError(name)
450
+ }
451
+
452
+ // Check if this is a graph workflow (source === 'graph')
453
+ if (workflowMeta.source === 'graph') {
454
+ const shouldInline =
455
+ options?.inline || !this.singletonServices?.queueService
456
+ return runWorkflowGraph(
457
+ this,
458
+ name,
459
+ input,
460
+ rpcService,
461
+ shouldInline,
462
+ options?.startNode,
463
+ wire
464
+ )
465
+ }
466
+
467
+ // DSL workflow - check registration exists
468
+ const registrations = pikkuState(null, 'workflows', 'registrations')
248
469
  const workflow = registrations.get(name)
249
470
 
250
471
  if (!workflow) {
251
472
  throw new WorkflowNotFoundError(name)
252
473
  }
253
474
 
254
- // Create workflow run in state
255
- const runId = await this.createRun(name, input)
475
+ if (!workflowMeta.graphHash) {
476
+ throw new Error(`Missing workflow graphHash for '${name}'`)
477
+ }
256
478
 
257
- // If queue service is available, use remote execution (queue-based)
258
- // Otherwise, execute directly (inline/synchronous)
259
- if (this.singletonServices?.queueService) {
260
- // Queue orchestrator to start the workflow
261
- await this.resumeWorkflow(runId)
479
+ const runId = await this.createRun(
480
+ name,
481
+ input,
482
+ options?.inline ?? false,
483
+ workflowMeta.graphHash,
484
+ wire
485
+ )
486
+
487
+ if (options?.inline) {
488
+ this.inlineRuns.add(runId)
489
+ }
490
+
491
+ if (options?.inline || !this.singletonServices?.queueService) {
492
+ this.runWorkflowJob(runId, rpcService)
493
+ .catch(() => {})
494
+ .finally(() => {
495
+ if (options?.inline) {
496
+ this.inlineRuns.delete(runId)
497
+ }
498
+ })
262
499
  } else {
263
- // No queue service - execute directly via runWorkflowJob
264
- await this.runWorkflowJob(runId, rpcService)
500
+ await this.resumeWorkflow(runId)
265
501
  }
266
502
 
267
503
  return { runId }
268
504
  }
269
505
 
506
+ public async runToCompletion<I>(
507
+ name: string,
508
+ input: I,
509
+ rpcService: any,
510
+ options?: { pollIntervalMs?: number; wire?: WorkflowRunWire }
511
+ ): Promise<any> {
512
+ const pollInterval = options?.pollIntervalMs ?? 1000
513
+ const { runId } = await this.startWorkflow(
514
+ name,
515
+ input,
516
+ options?.wire ?? { type: 'internal' },
517
+ rpcService,
518
+ { inline: true }
519
+ )
520
+ while (true) {
521
+ const run = await this.getRun(runId)
522
+ if (!run) {
523
+ throw new WorkflowRunNotFoundError(runId)
524
+ }
525
+ if (WORKFLOW_END_STATES.has(run.status)) {
526
+ if (run.status === 'failed') {
527
+ throw new Error(run.error?.message || 'Workflow failed')
528
+ }
529
+ if (run.status === 'cancelled') {
530
+ throw new Error('Workflow was cancelled')
531
+ }
532
+ return run.output
533
+ }
534
+ await new Promise((resolve) => setTimeout(resolve, pollInterval))
535
+ }
536
+ }
537
+
270
538
  public async runWorkflowJob(runId: string, rpcService: any): Promise<void> {
271
539
  const run = await this.getRun(runId)
272
540
  if (!run) {
273
- throw new WorkflowRunNotFound(runId)
541
+ throw new WorkflowRunNotFoundError(runId)
542
+ }
543
+
544
+ const meta = pikkuState(null, 'workflows', 'meta')
545
+ const workflowMeta = meta[run.workflow]
546
+
547
+ if (
548
+ run.graphHash &&
549
+ workflowMeta?.graphHash &&
550
+ run.graphHash !== workflowMeta.graphHash
551
+ ) {
552
+ await this.runVersionMismatchFallback(run, workflowMeta, rpcService)
553
+ return
554
+ }
555
+
556
+ if (workflowMeta?.source === 'graph') {
557
+ await continueGraph(this, runId, run.workflow)
558
+ return
274
559
  }
275
560
 
276
- const registrations = pikkuState('workflows', 'registrations')
561
+ const registrations = pikkuState(null, 'workflows', 'registrations')
277
562
  const workflow = registrations.get(run.workflow)
278
563
  if (!workflow) {
279
564
  throw new WorkflowNotFoundError(run.workflow)
280
565
  }
281
566
 
282
567
  await this.withRunLock(runId, async () => {
283
- const meta = pikkuState('workflows', 'meta')
284
- const workflowMeta = meta[run.workflow]
285
-
286
568
  const workflowWire = this.createWorkflowWire(
287
569
  run.workflow,
288
570
  runId,
@@ -292,8 +574,8 @@ export abstract class PikkuWorkflowService implements WorkflowService {
292
574
  try {
293
575
  const result = await runPikkuFunc(
294
576
  'workflow',
295
- workflowMeta.workflowName,
296
- workflowMeta.pikkuFuncName,
577
+ workflowMeta.name,
578
+ workflowMeta.pikkuFuncId,
297
579
  {
298
580
  singletonServices: this.singletonServices!,
299
581
  wire,
@@ -305,13 +587,24 @@ export abstract class PikkuWorkflowService implements WorkflowService {
305
587
  await this.updateRunStatus(runId, 'completed', result)
306
588
  } catch (error: any) {
307
589
  if (error instanceof WorkflowAsyncException) {
308
- // Normal - workflow paused for step execution
309
590
  throw error
310
591
  }
311
592
 
312
- // Check if it's a WorkflowCancelledException
313
593
  if (error instanceof WorkflowCancelledException) {
314
- // Workflow was cancelled - status already updated, just rethrow
594
+ await this.updateRunStatus(runId, 'cancelled', undefined, {
595
+ message: error.message || 'Workflow cancelled',
596
+ stack: '',
597
+ code: 'WORKFLOW_CANCELLED',
598
+ })
599
+ throw error
600
+ }
601
+
602
+ if (error instanceof WorkflowSuspendedException) {
603
+ await this.updateRunStatus(runId, 'suspended', undefined, {
604
+ message: error.message || 'Workflow suspended',
605
+ stack: '',
606
+ code: 'WORKFLOW_SUSPENDED',
607
+ })
315
608
  throw error
316
609
  }
317
610
 
@@ -326,6 +619,35 @@ export abstract class PikkuWorkflowService implements WorkflowService {
326
619
  })
327
620
  }
328
621
 
622
+ private async runVersionMismatchFallback(
623
+ run: WorkflowRun,
624
+ currentMeta: { source: string },
625
+ rpcService: any
626
+ ): Promise<void> {
627
+ const source = currentMeta.source
628
+
629
+ if (source === 'complex') {
630
+ await this.updateRunStatus(run.id, 'failed', undefined, {
631
+ message: `Workflow '${run.workflow}' definition changed. Complex workflows with inline steps cannot be migrated.`,
632
+ stack: '',
633
+ code: 'VERSION_CONFLICT',
634
+ })
635
+ return
636
+ }
637
+
638
+ const version = await this.getWorkflowVersion(run.workflow, run.graphHash!)
639
+ if (!version) {
640
+ await this.updateRunStatus(run.id, 'failed', undefined, {
641
+ message: `Workflow '${run.workflow}' version '${run.graphHash}' not found. Cannot resume with changed definition.`,
642
+ stack: '',
643
+ code: 'VERSION_NOT_FOUND',
644
+ })
645
+ return
646
+ }
647
+
648
+ await runFromMeta(this, run.id, version.graph, rpcService)
649
+ }
650
+
329
651
  /**
330
652
  * Execute a single workflow step (called by worker)
331
653
  * Handles idempotency, RPC execution, result storage, retry logic, and orchestrator triggering
@@ -358,31 +680,58 @@ export abstract class PikkuWorkflowService implements WorkflowService {
358
680
  stepState = await this.createRetryAttempt(stepState.stepId, 'running')
359
681
  }
360
682
 
361
- if (stepState.status !== 'pending') {
362
- // Mark step as running (pending or failed status)
683
+ if (stepState.status === 'pending') {
684
+ // Mark pending step as running before execution
363
685
  await this.setStepRunning(stepState.stepId)
364
686
  }
365
687
 
366
688
  try {
367
- // Execute RPC with workflow step context
368
- const result = await rpcService.rpcWithWire(rpcName, data, {
369
- workflowStep: {
689
+ let result: any
690
+
691
+ const run = await this.getRun(runId)
692
+ if (!run) {
693
+ throw new Error(`Workflow run not found: ${runId}`)
694
+ }
695
+
696
+ const meta = pikkuState(null, 'workflows', 'meta')
697
+ const workflowMeta = meta[run.workflow]
698
+
699
+ if (workflowMeta?.nodes && stepName in workflowMeta.nodes) {
700
+ result = await executeGraphStep(
701
+ this,
702
+ rpcService,
370
703
  runId,
371
- stepId: stepState.stepId,
372
- attemptCount: stepState.attemptCount,
373
- },
374
- })
704
+ stepState.stepId,
705
+ stepName,
706
+ rpcName,
707
+ data,
708
+ run.workflow
709
+ )
710
+ } else {
711
+ result = await rpcService.rpcWithWire(rpcName, data, {
712
+ workflowStep: {
713
+ runId,
714
+ stepId: stepState.stepId,
715
+ attemptCount: stepState.attemptCount,
716
+ },
717
+ })
718
+ }
375
719
 
376
720
  // Store result and mark succeeded
377
721
  await this.setStepResult(stepState.stepId, result)
378
722
 
379
723
  // Resume orchestrator to continue workflow
380
- try {
381
- await this.resumeWorkflow(runId)
382
- } catch (resumeError: any) {
383
- throw resumeError
384
- }
724
+ await this.resumeWorkflow(runId)
385
725
  } catch (error: any) {
726
+ if (error instanceof RPCNotFoundError) {
727
+ await this.setStepError(stepState.stepId, error)
728
+ await this.updateRunStatus(runId, 'suspended', undefined, {
729
+ message: `RPC '${rpcName}' not found. Deploy the missing function and resume.`,
730
+ code: 'RPC_NOT_FOUND',
731
+ })
732
+ return
733
+ }
734
+
386
735
  // Store error and mark failed
387
736
  await this.setStepError(stepState.stepId, error)
388
737
 
@@ -414,7 +763,8 @@ export abstract class PikkuWorkflowService implements WorkflowService {
414
763
  } catch (error: any) {
415
764
  if (
416
765
  error.name === 'WorkflowAsyncException' ||
417
- error.name === 'WorkflowCancelledException'
766
+ error.name === 'WorkflowCancelledException' ||
767
+ error.name === 'WorkflowSuspendedException'
418
768
  ) {
419
769
  return
420
770
  }
@@ -451,7 +801,7 @@ export abstract class PikkuWorkflowService implements WorkflowService {
451
801
  let stepState: StepState
452
802
  try {
453
803
  stepState = await this.getStepState(runId, stepName)
454
- } catch (error: any) {
804
+ } catch {
455
805
  // Step doesn't exist - create it
456
806
  stepState = await this.insertStepState(
457
807
  runId,
@@ -488,8 +838,8 @@ export abstract class PikkuWorkflowService implements WorkflowService {
488
838
  // Step is pending - schedule it
489
839
  await this.setStepScheduled(stepState.stepId)
490
840
 
491
- // Enqueue step worker
492
- if (this.singletonServices!.queueService) {
841
+ // Enqueue step worker (unless inline mode)
842
+ if (!this.isInline(runId) && this.singletonServices!.queueService) {
493
843
  // Map step retry options to queue job options
494
844
  const retries = stepOptions?.retries ?? 0
495
845
  const retryDelay = stepOptions?.retryDelay
@@ -514,45 +864,60 @@ export abstract class PikkuWorkflowService implements WorkflowService {
514
864
  : undefined,
515
865
  }
516
866
  )
867
+ // Pause workflow - step will callback when done
868
+ throw new WorkflowAsyncException(runId, stepName)
517
869
  } else {
518
- // No queue service - execute locally
870
+ // Inline or no queue service - execute locally with retry loop
519
871
  const retries = stepOptions?.retries ?? 0
520
872
  const retryDelay = stepOptions?.retryDelay
521
- const attemptCount = stepState.attemptCount
873
+ let currentStepState = stepState
522
874
 
523
- try {
524
- const result = await rpcService.rpcWithWire(rpcName, data, {
525
- workflowStep: {
526
- runId,
527
- stepId: stepState.stepId,
528
- attemptCount: stepState.attemptCount,
529
- },
530
- })
531
- await this.setStepResult(stepState.stepId, result)
532
- return result
533
- } catch (error: any) {
534
- // Record the error (marks step as failed)
535
- await this.setStepError(stepState.stepId, error)
536
-
537
- // Check if we should retry
538
- if (attemptCount < retries) {
539
- // Create a new pending retry attempt (copies metadata from failed step)
540
- await this.createRetryAttempt(stepState.stepId, 'pending')
541
-
542
- // Schedule orchestrator to retry after delay
543
- await this.scheduleOrchestratorRetry(runId, retryDelay)
875
+ while (true) {
876
+ try {
877
+ await this.setStepRunning(currentStepState.stepId)
878
+ const result = await rpcService.rpcWithWire(rpcName, data, {
879
+ workflowStep: {
880
+ runId,
881
+ stepId: currentStepState.stepId,
882
+ attemptCount: currentStepState.attemptCount,
883
+ },
884
+ })
885
+ await this.setStepResult(currentStepState.stepId, result)
886
+ return result
887
+ } catch (error: any) {
888
+ if (error instanceof RPCNotFoundError) {
889
+ await this.updateRunStatus(runId, 'suspended', undefined, {
890
+ message: `RPC '${rpcName}' not found. Deploy the missing function and resume.`,
891
+ code: 'RPC_NOT_FOUND',
892
+ })
893
+ throw error
894
+ }
544
895
 
545
- // Pause workflow - orchestrator will replay and pick up new attempt
546
- throw new WorkflowAsyncException(runId, stepName)
896
+ // Record the error (marks step as failed)
897
+ await this.setStepError(currentStepState.stepId, error)
898
+
899
+ // Check if we should retry
900
+ if (currentStepState.attemptCount < retries) {
901
+ // Create a new pending retry attempt
902
+ currentStepState = await this.createRetryAttempt(
903
+ currentStepState.stepId,
904
+ 'pending'
905
+ )
906
+
907
+ // Wait for retry delay if specified
908
+ if (retryDelay) {
909
+ await new Promise((resolve) =>
910
+ setTimeout(resolve, getDurationInMilliseconds(retryDelay))
911
+ )
912
+ }
913
+ // Continue loop to retry
914
+ } else {
915
+ // No more retries, fail the workflow
916
+ throw error
917
+ }
547
918
  }
548
-
549
- // No more retries, fail the workflow
550
- throw error
551
919
  }
552
920
  }
553
-
554
- // Pause workflow - step will callback when done
555
- throw new WorkflowAsyncException(runId, stepName)
556
921
  }
557
922
 
558
923
  private async inlineStep(
@@ -565,13 +930,13 @@ export abstract class PikkuWorkflowService implements WorkflowService {
565
930
  let stepState: StepState
566
931
  try {
567
932
  stepState = await this.getStepState(runId, stepName)
568
- } catch (error: any) {
933
+ } catch {
569
934
  // Step doesn't exist - create it (inline, no RPC)
570
935
  stepState = await this.insertStepState(
571
936
  runId,
572
937
  stepName,
573
- null, // No RPC for inline steps
574
- null, // No data for inline steps
938
+ null,
939
+ null,
575
940
  stepOptions
576
941
  )
577
942
  }
@@ -584,30 +949,68 @@ export abstract class PikkuWorkflowService implements WorkflowService {
584
949
  // Execute inline function
585
950
  const retries = stepOptions?.retries ?? this.getConfig().retries
586
951
  const retryDelay = stepOptions?.retryDelay ?? this.getConfig().retryDelay
587
- const attemptCount = stepState.attemptCount
952
+ let currentStepState = stepState
588
953
 
589
- try {
590
- const result = await fn()
591
- await this.setStepResult(stepState.stepId, result)
592
- return result
593
- } catch (error: any) {
594
- // Record the error (marks step as failed)
595
- await this.setStepError(stepState.stepId, error)
954
+ // Check if we're running inline (in-memory) or remote (queue-based)
955
+ if (this.isInline(runId)) {
956
+ // Inline mode - execute with retry loop
957
+ while (true) {
958
+ try {
959
+ await this.setStepRunning(currentStepState.stepId)
960
+ const result = await fn()
961
+ await this.setStepResult(currentStepState.stepId, result)
962
+ return result
963
+ } catch (error: any) {
964
+ // Record the error (marks step as failed)
965
+ await this.setStepError(currentStepState.stepId, error)
966
+
967
+ // Check if we should retry
968
+ if (currentStepState.attemptCount < retries) {
969
+ // Create a new pending retry attempt
970
+ currentStepState = await this.createRetryAttempt(
971
+ currentStepState.stepId,
972
+ 'pending'
973
+ )
974
+
975
+ // Wait for retry delay if specified
976
+ if (retryDelay) {
977
+ await new Promise((resolve) =>
978
+ setTimeout(resolve, getDurationInMilliseconds(retryDelay))
979
+ )
980
+ }
981
+ // Continue loop to retry
982
+ } else {
983
+ // No more retries, fail the workflow
984
+ throw error
985
+ }
986
+ }
987
+ }
988
+ } else {
989
+ // Remote mode - use queue-based retry
990
+ try {
991
+ await this.setStepRunning(currentStepState.stepId)
992
+ const result = await fn()
993
+ await this.setStepResult(currentStepState.stepId, result)
994
+ return result
995
+ } catch (error: any) {
996
+ // Record the error (marks step as failed)
997
+ await this.setStepError(currentStepState.stepId, error)
596
998
 
597
- // Check if we should retry
598
- if (attemptCount < retries) {
599
- // Create a new pending retry attempt (copies metadata from failed step)
600
- await this.createRetryAttempt(stepState.stepId, 'pending')
999
+ // Check if we should retry
1000
+ if (currentStepState.attemptCount < retries) {
1001
+ // Create a new pending retry attempt (copies metadata from failed step)
1002
+ await this.createRetryAttempt(currentStepState.stepId, 'pending')
601
1003
 
602
- // Schedule orchestrator to retry after delay
603
- await this.scheduleOrchestratorRetry(runId, retryDelay)
1004
+ // Schedule orchestrator to retry after delay
1005
+ await this.scheduleOrchestratorRetry(runId, retryDelay)
604
1006
 
605
- // Pause workflow - orchestrator will replay and pick up new attempt
606
- throw new WorkflowAsyncException(runId, stepName)
607
- }
1007
+ // Pause workflow - orchestrator will replay and pick up new attempt
1008
+ throw new WorkflowAsyncException(runId, stepName)
1009
+ }
608
1010
 
609
- // No more retries, fail the workflow
610
- throw error
1011
+ // No more retries, fail the workflow
1012
+ throw error
1013
+ }
611
1014
  }
612
1015
  }
613
1016
 
@@ -616,15 +1019,11 @@ export abstract class PikkuWorkflowService implements WorkflowService {
616
1019
  let stepState: StepState
617
1020
  try {
618
1021
  stepState = await this.getStepState(runId, stepName)
619
- } catch (error: any) {
1022
+ } catch {
620
1023
  // Step doesn't exist - create it (sleep step, no RPC)
621
- stepState = await this.insertStepState(
622
- runId,
623
- stepName,
624
- null, // No RPC for sleep steps
625
- { duration }, // Store duration as data
626
- undefined // No retry options for sleep
627
- )
1024
+ stepState = await this.insertStepState(runId, stepName, null, {
1025
+ duration,
1026
+ })
628
1027
  }
629
1028
 
630
1029
  if (stepState.status === 'succeeded') {
@@ -640,9 +1039,9 @@ export abstract class PikkuWorkflowService implements WorkflowService {
640
1039
  // Step is pending - schedule it
641
1040
  await this.setStepScheduled(stepState.stepId)
642
1041
 
643
- // Check if we have queue service (remote mode) or inline mode
644
- if (this.singletonServices!.schedulerService) {
645
- // Remote mode - enqueue sleep worker with delay
1042
+ // Check if inline mode or no scheduler service
1043
+ if (!this.isInline(runId) && this.singletonServices!.schedulerService) {
1044
+ // Remote mode - schedule sleep via scheduler service
646
1045
  await this.singletonServices!.schedulerService.scheduleRPC(
647
1046
  duration,
648
1047
  this.getConfig().sleeperRPCName,
@@ -651,6 +1050,8 @@ export abstract class PikkuWorkflowService implements WorkflowService {
651
1050
  stepId: stepState.stepId,
652
1051
  }
653
1052
  )
1053
+ // Pause workflow - sleep will callback when done
1054
+ throw new WorkflowAsyncException(runId, stepName)
654
1055
  } else {
655
1056
  // Inline mode - use setTimeout with actual duration
656
1057
  await new Promise((resolve) =>
@@ -659,30 +1060,65 @@ export abstract class PikkuWorkflowService implements WorkflowService {
659
1060
  await this.setStepResult(stepState.stepId, null)
660
1061
  return
661
1062
  }
1063
+ }
662
1064
 
663
- // Pause workflow - sleep will callback when done
664
- throw new WorkflowAsyncException(runId, stepName)
1065
+ private getSuspendStepName(reason: string): string {
1066
+ if (!reason) {
1067
+ return '__workflow_suspend'
1068
+ }
1069
+ return '__workflow_suspend'
665
1070
  }
666
1071
 
667
- private async cancelWorkflow(runId: string, reason?: string) {
668
- // Update workflow run status to cancelled
669
- await this.updateRunStatus(runId, 'cancelled', undefined, {
670
- message: reason || 'Workflow cancelled by user',
671
- stack: '',
672
- code: 'WORKFLOW_CANCELLED',
673
- })
1072
+ private async suspendStep(runId: string, reason: string): Promise<void> {
1073
+ const stepName = this.getSuspendStepName(reason)
1074
+ await this.withStepLock(runId, stepName, async () => {
1075
+ let stepState: StepState
1076
+ try {
1077
+ stepState = await this.getStepState(runId, stepName)
1078
+ } catch {
1079
+ stepState = await this.insertStepState(
1080
+ runId,
1081
+ stepName,
1082
+ 'pikkuWorkflowSuspend',
1083
+ {
1084
+ reason,
1085
+ }
1086
+ )
1087
+ }
1088
+ if (!stepState.stepId) {
1089
+ stepState = await this.insertStepState(
1090
+ runId,
1091
+ stepName,
1092
+ 'pikkuWorkflowSuspend',
1093
+ {
1094
+ reason,
1095
+ }
1096
+ )
1097
+ }
1098
+
1099
+ if (stepState.status === 'succeeded') {
1100
+ return
1101
+ }
674
1102
 
675
- // Throw cancellation exception to stop workflow execution
676
- throw new WorkflowCancelledException(runId, reason)
1103
+ if (stepState.status === 'pending') {
1104
+ await this.setStepRunning(stepState.stepId)
1105
+ }
1106
+
1107
+ await this.setStepResult(stepState.stepId, {
1108
+ reason,
1109
+ suspendedAt: new Date().toISOString(),
1110
+ })
1111
+ throw new WorkflowSuspendedException(runId, reason)
1112
+ })
677
1113
  }
678
1114
 
679
1115
  private createWorkflowWire(
680
- workflowName: string,
1116
+ name: string,
681
1117
  runId: string,
682
1118
  rpcService: any
683
1119
  ): PikkuWorkflowWire {
684
1120
  const workflowWire: PikkuWorkflowWire = {
685
- workflowName,
1121
+ name,
686
1122
  runId,
687
1123
  getRun: async () => (await this.getRun(runId)) as WorkflowRun,
688
1124
 
@@ -723,9 +1159,8 @@ export abstract class PikkuWorkflowService implements WorkflowService {
723
1159
  )
724
1160
  },
725
1161
 
726
- // Implement workflow.cancel()
727
- cancel: async (reason?: string): Promise<void> => {
728
- await this.cancelWorkflow(runId, reason)
1162
+ suspend: async (reason: string) => {
1163
+ await this.suspendStep(runId, reason || 'Workflow suspended')
729
1164
  },
730
1165
  }
731
1166
  return workflowWire