@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
@@ -1,7 +1,69 @@
1
1
  import { runPikkuFunc } from '../../function/function-runner.js';
2
2
  import { pikkuState } from '../../pikku-state.js';
3
3
  import { getDurationInMilliseconds } from '../../time-utils.js';
4
- import { WorkflowAsyncException, WorkflowCancelledException, WorkflowNotFoundError, WorkflowRunNotFound, } from './workflow-runner.js';
4
+ import { continueGraph, executeGraphStep, runWorkflowGraph, runFromMeta, } from './graph/graph-runner.js';
5
+ import { PikkuError, addError } from '../../errors/error-handler.js';
6
+ import { RPCNotFoundError } from '../rpc/rpc-runner.js';
7
+ /**
8
+ * Exception thrown when workflow needs to pause for async step
9
+ */
10
+ export class WorkflowAsyncException extends Error {
11
+ runId;
12
+ stepName;
13
+ constructor(runId, stepName) {
14
+ super(`Workflow paused at step: ${stepName}`);
15
+ this.runId = runId;
16
+ this.stepName = stepName;
17
+ this.name = 'WorkflowAsyncException';
18
+ }
19
+ }
20
+ /**
21
+ * Exception thrown when workflow is cancelled
22
+ */
23
+ export class WorkflowCancelledException extends Error {
24
+ runId;
25
+ reason;
26
+ constructor(runId, reason) {
27
+ super(reason || 'Workflow cancelled');
28
+ this.runId = runId;
29
+ this.reason = reason;
30
+ this.name = 'WorkflowCancelledException';
31
+ }
32
+ }
33
+ /**
34
+ * Exception thrown when workflow is suspended
35
+ */
36
+ export class WorkflowSuspendedException extends Error {
37
+ runId;
38
+ reason;
39
+ constructor(runId, reason) {
40
+ super(reason || 'Workflow suspended');
41
+ this.runId = runId;
42
+ this.reason = reason;
43
+ this.name = 'WorkflowSuspendedException';
44
+ }
45
+ }
46
+ /**
47
+ * Error class for workflow not found
48
+ */
49
+ export class WorkflowNotFoundError extends PikkuError {
50
+ constructor(name) {
51
+ super(`Workflow not found: ${name}`);
52
+ }
53
+ }
54
+ addError(WorkflowNotFoundError, {
55
+ status: 404,
56
+ message: 'Workflow not found.',
57
+ });
58
+ export class WorkflowRunNotFoundError extends PikkuError {
59
+ constructor(runId) {
60
+ super(`Workflow run not found: ${runId}`);
61
+ }
62
+ }
63
+ addError(WorkflowRunNotFoundError, {
64
+ status: 404,
65
+ message: 'Workflow run not found.',
66
+ });
5
67
  export class WorkflowServiceNotInitialized extends Error {
6
68
  }
7
69
  export class WorkflowStepNameNotString extends Error {
@@ -9,6 +71,12 @@ export class WorkflowStepNameNotString extends Error {
9
71
  super(`Workflow step name must be a string. Received: ${typeof stepName}`);
10
72
  }
11
73
  }
74
+ const WORKFLOW_END_STATES = new Set([
75
+ 'completed',
76
+ 'failed',
77
+ 'cancelled',
78
+ 'suspended',
79
+ ]);
12
80
  /**
13
81
  * Abstract workflow state service
14
82
  * Implementations provide pluggable storage backends (SQLite, PostgreSQL, etc.)
@@ -18,7 +86,26 @@ export class PikkuWorkflowService {
18
86
  config;
19
87
  singletonServices;
20
88
  createWireServices;
89
+ inlineRuns = new Set();
21
90
  constructor() { }
91
+ /**
92
+ * Check if a run is executing inline (without queues)
93
+ */
94
+ isInline(runId) {
95
+ return this.inlineRuns.has(runId);
96
+ }
97
+ /**
98
+ * Register a run as inline (for graph-runner to use)
99
+ */
100
+ registerInlineRun(runId) {
101
+ this.inlineRuns.add(runId);
102
+ }
103
+ /**
104
+ * Unregister a run from inline tracking
105
+ */
106
+ unregisterInlineRun(runId) {
107
+ this.inlineRuns.delete(runId);
108
+ }
22
109
  setServices(singletonServices, createWireServices, { workflow }) {
23
110
  this.singletonServices = singletonServices;
24
111
  this.createWireServices = createWireServices;
@@ -27,9 +114,17 @@ export class PikkuWorkflowService {
27
114
  retryDelay: workflow?.retryDelay ?? 0,
28
115
  orchestratorQueueName: workflow?.orchestratorQueueName ?? 'pikku-workflow-orchestrator',
29
116
  stepWorkerQueueName: workflow?.stepWorkerQueueName ?? 'pikku-workflow-step-worker',
30
- sleeperRPCName: workflow?.sleeperRPCName ?? 'pikkuWorkflowStepSleeper',
117
+ sleeperRPCName: workflow?.sleeperRPCName ?? 'pikkuWorkflowSleeper',
31
118
  };
32
119
  }
120
+ async registerWorkflowVersions() {
121
+ const allMeta = pikkuState(null, 'workflows', 'meta');
122
+ for (const [name, meta] of Object.entries(allMeta)) {
123
+ if (!meta.graphHash)
124
+ continue;
125
+ await this.upsertWorkflowVersion(name, meta.graphHash, meta, meta.source);
126
+ }
127
+ }
33
128
  // ============================================================================
34
129
  // Workflow Lifecycle Methods
35
130
  // ============================================================================
@@ -41,12 +136,21 @@ export class PikkuWorkflowService {
41
136
  const queueService = this.verifyQueueService();
42
137
  await queueService.add(this.getConfig().orchestratorQueueName, { runId });
43
138
  }
139
+ async queueStepWorker(runId, stepName, rpcName, data) {
140
+ const queueService = this.verifyQueueService();
141
+ await queueService.add(this.getConfig().stepWorkerQueueName, {
142
+ runId,
143
+ stepName,
144
+ rpcName,
145
+ data,
146
+ });
147
+ }
44
148
  /**
45
149
  * Execute a workflow sleep step completion
46
150
  * Sets the step result to null and resumes the workflow
47
151
  * @param data - Sleeper input data
48
152
  */
49
- async executeWorkflowSleep(runId, stepId) {
153
+ async executeWorkflowSleepCompleted(runId, stepId) {
50
154
  await this.setStepResult(stepId, null);
51
155
  await this.resumeWorkflow(runId);
52
156
  }
@@ -61,44 +165,96 @@ export class PikkuWorkflowService {
61
165
  }
62
166
  /**
63
167
  * Start a new workflow run
168
+ * Automatically detects workflow type (DSL or graph) from meta and executes accordingly
169
+ * @param options.inline - If true, execute workflow directly without queue service
170
+ * @param options.startNode - Starting node ID for graph workflows (from wire config)
64
171
  */
65
- async startWorkflow(name, input, rpcService) {
66
- const registrations = pikkuState('workflows', 'registrations');
172
+ async startWorkflow(name, input, wire, rpcService, options) {
173
+ // Check meta to determine workflow type
174
+ const meta = pikkuState(null, 'workflows', 'meta');
175
+ const workflowMeta = meta[name];
176
+ if (!workflowMeta) {
177
+ throw new WorkflowNotFoundError(name);
178
+ }
179
+ // Check if this is a graph workflow (source === 'graph')
180
+ if (workflowMeta.source === 'graph') {
181
+ const shouldInline = options?.inline || !this.singletonServices?.queueService;
182
+ return runWorkflowGraph(this, name, input, rpcService, shouldInline, options?.startNode, wire);
183
+ }
184
+ // DSL workflow - check registration exists
185
+ const registrations = pikkuState(null, 'workflows', 'registrations');
67
186
  const workflow = registrations.get(name);
68
187
  if (!workflow) {
69
188
  throw new WorkflowNotFoundError(name);
70
189
  }
71
- // Create workflow run in state
72
- const runId = await this.createRun(name, input);
73
- // If queue service is available, use remote execution (queue-based)
74
- // Otherwise, execute directly (inline/synchronous)
75
- if (this.singletonServices?.queueService) {
76
- // Queue orchestrator to start the workflow
77
- await this.resumeWorkflow(runId);
190
+ if (!workflowMeta.graphHash) {
191
+ throw new Error(`Missing workflow graphHash for '${name}'`);
192
+ }
193
+ const runId = await this.createRun(name, input, options?.inline ?? false, workflowMeta.graphHash, wire);
194
+ if (options?.inline) {
195
+ this.inlineRuns.add(runId);
196
+ }
197
+ if (options?.inline || !this.singletonServices?.queueService) {
198
+ this.runWorkflowJob(runId, rpcService)
199
+ .catch(() => { })
200
+ .finally(() => {
201
+ if (options?.inline) {
202
+ this.inlineRuns.delete(runId);
203
+ }
204
+ });
78
205
  }
79
206
  else {
80
- // No queue service - execute directly via runWorkflowJob
81
- await this.runWorkflowJob(runId, rpcService);
207
+ await this.resumeWorkflow(runId);
82
208
  }
83
209
  return { runId };
84
210
  }
211
+ async runToCompletion(name, input, rpcService, options) {
212
+ const pollInterval = options?.pollIntervalMs ?? 1000;
213
+ const { runId } = await this.startWorkflow(name, input, options?.wire ?? { type: 'internal' }, rpcService, { inline: true });
214
+ while (true) {
215
+ const run = await this.getRun(runId);
216
+ if (!run) {
217
+ throw new WorkflowRunNotFoundError(runId);
218
+ }
219
+ if (WORKFLOW_END_STATES.has(run.status)) {
220
+ if (run.status === 'failed') {
221
+ throw new Error(run.error?.message || 'Workflow failed');
222
+ }
223
+ if (run.status === 'cancelled') {
224
+ throw new Error('Workflow was cancelled');
225
+ }
226
+ return run.output;
227
+ }
228
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
229
+ }
230
+ }
85
231
  async runWorkflowJob(runId, rpcService) {
86
232
  const run = await this.getRun(runId);
87
233
  if (!run) {
88
- throw new WorkflowRunNotFound(runId);
234
+ throw new WorkflowRunNotFoundError(runId);
235
+ }
236
+ const meta = pikkuState(null, 'workflows', 'meta');
237
+ const workflowMeta = meta[run.workflow];
238
+ if (run.graphHash &&
239
+ workflowMeta?.graphHash &&
240
+ run.graphHash !== workflowMeta.graphHash) {
241
+ await this.runVersionMismatchFallback(run, workflowMeta, rpcService);
242
+ return;
89
243
  }
90
- const registrations = pikkuState('workflows', 'registrations');
244
+ if (workflowMeta?.source === 'graph') {
245
+ await continueGraph(this, runId, run.workflow);
246
+ return;
247
+ }
248
+ const registrations = pikkuState(null, 'workflows', 'registrations');
91
249
  const workflow = registrations.get(run.workflow);
92
250
  if (!workflow) {
93
251
  throw new WorkflowNotFoundError(run.workflow);
94
252
  }
95
253
  await this.withRunLock(runId, async () => {
96
- const meta = pikkuState('workflows', 'meta');
97
- const workflowMeta = meta[run.workflow];
98
254
  const workflowWire = this.createWorkflowWire(run.workflow, runId, rpcService);
99
255
  const wire = { workflow: workflowWire };
100
256
  try {
101
- const result = await runPikkuFunc('workflow', workflowMeta.workflowName, workflowMeta.pikkuFuncName, {
257
+ const result = await runPikkuFunc('workflow', workflowMeta.name, workflowMeta.pikkuFuncId, {
102
258
  singletonServices: this.singletonServices,
103
259
  wire,
104
260
  createWireServices: this.createWireServices,
@@ -108,12 +264,22 @@ export class PikkuWorkflowService {
108
264
  }
109
265
  catch (error) {
110
266
  if (error instanceof WorkflowAsyncException) {
111
- // Normal - workflow paused for step execution
112
267
  throw error;
113
268
  }
114
- // Check if it's a WorkflowCancelledException
115
269
  if (error instanceof WorkflowCancelledException) {
116
- // Workflow was cancelled - status already updated, just rethrow
270
+ await this.updateRunStatus(runId, 'cancelled', undefined, {
271
+ message: error.message || 'Workflow cancelled',
272
+ stack: '',
273
+ code: 'WORKFLOW_CANCELLED',
274
+ });
275
+ throw error;
276
+ }
277
+ if (error instanceof WorkflowSuspendedException) {
278
+ await this.updateRunStatus(runId, 'suspended', undefined, {
279
+ message: error.message || 'Workflow suspended',
280
+ stack: '',
281
+ code: 'WORKFLOW_SUSPENDED',
282
+ });
117
283
  throw error;
118
284
  }
119
285
  await this.updateRunStatus(runId, 'failed', undefined, {
@@ -125,6 +291,27 @@ export class PikkuWorkflowService {
125
291
  }
126
292
  });
127
293
  }
294
+ async runVersionMismatchFallback(run, currentMeta, rpcService) {
295
+ const source = currentMeta.source;
296
+ if (source === 'complex') {
297
+ await this.updateRunStatus(run.id, 'failed', undefined, {
298
+ message: `Workflow '${run.workflow}' definition changed. Complex workflows with inline steps cannot be migrated.`,
299
+ stack: '',
300
+ code: 'VERSION_CONFLICT',
301
+ });
302
+ return;
303
+ }
304
+ const version = await this.getWorkflowVersion(run.workflow, run.graphHash);
305
+ if (!version) {
306
+ await this.updateRunStatus(run.id, 'failed', undefined, {
307
+ message: `Workflow '${run.workflow}' version '${run.graphHash}' not found. Cannot resume with changed definition.`,
308
+ stack: '',
309
+ code: 'VERSION_NOT_FOUND',
310
+ });
311
+ return;
312
+ }
313
+ await runFromMeta(this, run.id, version.graph, rpcService);
314
+ }
128
315
  /**
129
316
  * Execute a single workflow step (called by worker)
130
317
  * Handles idempotency, RPC execution, result storage, retry logic, and orchestrator triggering
@@ -147,30 +334,44 @@ export class PikkuWorkflowService {
147
334
  if (stepState.status === 'failed') {
148
335
  stepState = await this.createRetryAttempt(stepState.stepId, 'running');
149
336
  }
150
- if (stepState.status !== 'pending') {
151
- // Mark step as running (pending or failed status)
337
+ if (stepState.status === 'pending') {
338
+ // Mark pending step as running before execution
152
339
  await this.setStepRunning(stepState.stepId);
153
340
  }
154
341
  try {
155
- // Execute RPC with workflow step context
156
- const result = await rpcService.rpcWithWire(rpcName, data, {
157
- workflowStep: {
158
- runId,
159
- stepId: stepState.stepId,
160
- attemptCount: stepState.attemptCount,
161
- },
162
- });
342
+ let result;
343
+ const run = await this.getRun(runId);
344
+ if (!run) {
345
+ throw new Error(`Workflow run not found: ${runId}`);
346
+ }
347
+ const meta = pikkuState(null, 'workflows', 'meta');
348
+ const workflowMeta = meta[run.workflow];
349
+ if (workflowMeta?.nodes && stepName in workflowMeta.nodes) {
350
+ result = await executeGraphStep(this, rpcService, runId, stepState.stepId, stepName, rpcName, data, run.workflow);
351
+ }
352
+ else {
353
+ result = await rpcService.rpcWithWire(rpcName, data, {
354
+ workflowStep: {
355
+ runId,
356
+ stepId: stepState.stepId,
357
+ attemptCount: stepState.attemptCount,
358
+ },
359
+ });
360
+ }
163
361
  // Store result and mark succeeded
164
362
  await this.setStepResult(stepState.stepId, result);
165
363
  // Resume orchestrator to continue workflow
166
- try {
167
- await this.resumeWorkflow(runId);
168
- }
169
- catch (resumeError) {
170
- throw resumeError;
171
- }
364
+ await this.resumeWorkflow(runId);
172
365
  }
173
366
  catch (error) {
367
+ if (error instanceof RPCNotFoundError) {
368
+ await this.setStepError(stepState.stepId, error);
369
+ await this.updateRunStatus(runId, 'suspended', undefined, {
370
+ message: `RPC '${rpcName}' not found. Deploy the missing function and resume.`,
371
+ code: 'RPC_NOT_FOUND',
372
+ });
373
+ return;
374
+ }
174
375
  // Store error and mark failed
175
376
  await this.setStepError(stepState.stepId, error);
176
377
  const maxAttempts = (stepState.retries ?? 0) + 1;
@@ -195,7 +396,8 @@ export class PikkuWorkflowService {
195
396
  }
196
397
  catch (error) {
197
398
  if (error.name === 'WorkflowAsyncException' ||
198
- error.name === 'WorkflowCancelledException') {
399
+ error.name === 'WorkflowCancelledException' ||
400
+ error.name === 'WorkflowSuspendedException') {
199
401
  return;
200
402
  }
201
403
  await this.updateRunStatus(runId, 'failed', undefined, {
@@ -218,7 +420,7 @@ export class PikkuWorkflowService {
218
420
  try {
219
421
  stepState = await this.getStepState(runId, stepName);
220
422
  }
221
- catch (error) {
423
+ catch {
222
424
  // Step doesn't exist - create it
223
425
  stepState = await this.insertStepState(runId, stepName, rpcName, data, stepOptions);
224
426
  }
@@ -242,8 +444,8 @@ export class PikkuWorkflowService {
242
444
  }
243
445
  // Step is pending - schedule it
244
446
  await this.setStepScheduled(stepState.stepId);
245
- // Enqueue step worker
246
- if (this.singletonServices.queueService) {
447
+ // Enqueue step worker (unless inline mode)
448
+ if (!this.isInline(runId) && this.singletonServices.queueService) {
247
449
  // Map step retry options to queue job options
248
450
  const retries = stepOptions?.retries ?? 0;
249
451
  const retryDelay = stepOptions?.retryDelay;
@@ -262,41 +464,54 @@ export class PikkuWorkflowService {
262
464
  ? 'exponential'
263
465
  : undefined,
264
466
  });
467
+ // Pause workflow - step will callback when done
468
+ throw new WorkflowAsyncException(runId, stepName);
265
469
  }
266
470
  else {
267
- // No queue service - execute locally
471
+ // Inline or no queue service - execute locally with retry loop
268
472
  const retries = stepOptions?.retries ?? 0;
269
473
  const retryDelay = stepOptions?.retryDelay;
270
- const attemptCount = stepState.attemptCount;
271
- try {
272
- const result = await rpcService.rpcWithWire(rpcName, data, {
273
- workflowStep: {
274
- runId,
275
- stepId: stepState.stepId,
276
- attemptCount: stepState.attemptCount,
277
- },
278
- });
279
- await this.setStepResult(stepState.stepId, result);
280
- return result;
281
- }
282
- catch (error) {
283
- // Record the error (marks step as failed)
284
- await this.setStepError(stepState.stepId, error);
285
- // Check if we should retry
286
- if (attemptCount < retries) {
287
- // Create a new pending retry attempt (copies metadata from failed step)
288
- await this.createRetryAttempt(stepState.stepId, 'pending');
289
- // Schedule orchestrator to retry after delay
290
- await this.scheduleOrchestratorRetry(runId, retryDelay);
291
- // Pause workflow - orchestrator will replay and pick up new attempt
292
- throw new WorkflowAsyncException(runId, stepName);
474
+ let currentStepState = stepState;
475
+ while (true) {
476
+ try {
477
+ await this.setStepRunning(currentStepState.stepId);
478
+ const result = await rpcService.rpcWithWire(rpcName, data, {
479
+ workflowStep: {
480
+ runId,
481
+ stepId: currentStepState.stepId,
482
+ attemptCount: currentStepState.attemptCount,
483
+ },
484
+ });
485
+ await this.setStepResult(currentStepState.stepId, result);
486
+ return result;
487
+ }
488
+ catch (error) {
489
+ if (error instanceof RPCNotFoundError) {
490
+ await this.updateRunStatus(runId, 'suspended', undefined, {
491
+ message: `RPC '${rpcName}' not found. Deploy the missing function and resume.`,
492
+ code: 'RPC_NOT_FOUND',
493
+ });
494
+ throw error;
495
+ }
496
+ // Record the error (marks step as failed)
497
+ await this.setStepError(currentStepState.stepId, error);
498
+ // Check if we should retry
499
+ if (currentStepState.attemptCount < retries) {
500
+ // Create a new pending retry attempt
501
+ currentStepState = await this.createRetryAttempt(currentStepState.stepId, 'pending');
502
+ // Wait for retry delay if specified
503
+ if (retryDelay) {
504
+ await new Promise((resolve) => setTimeout(resolve, getDurationInMilliseconds(retryDelay)));
505
+ }
506
+ // Continue loop to retry
507
+ }
508
+ else {
509
+ // No more retries, fail the workflow
510
+ throw error;
511
+ }
293
512
  }
294
- // No more retries, fail the workflow
295
- throw error;
296
513
  }
297
514
  }
298
- // Pause workflow - step will callback when done
299
- throw new WorkflowAsyncException(runId, stepName);
300
515
  }
301
516
  async inlineStep(runId, stepName, fn, stepOptions) {
302
517
  // Check if step already exists
@@ -304,11 +519,9 @@ export class PikkuWorkflowService {
304
519
  try {
305
520
  stepState = await this.getStepState(runId, stepName);
306
521
  }
307
- catch (error) {
522
+ catch {
308
523
  // Step doesn't exist - create it (inline, no RPC)
309
- stepState = await this.insertStepState(runId, stepName, null, // No RPC for inline steps
310
- null, // No data for inline steps
311
- stepOptions);
524
+ stepState = await this.insertStepState(runId, stepName, null, null, stepOptions);
312
525
  }
313
526
  if (stepState.status === 'succeeded') {
314
527
  // Return cached result
@@ -317,26 +530,60 @@ export class PikkuWorkflowService {
317
530
  // Execute inline function
318
531
  const retries = stepOptions?.retries ?? this.getConfig().retries;
319
532
  const retryDelay = stepOptions?.retryDelay ?? this.getConfig().retryDelay;
320
- const attemptCount = stepState.attemptCount;
321
- try {
322
- const result = await fn();
323
- await this.setStepResult(stepState.stepId, result);
324
- return result;
533
+ let currentStepState = stepState;
534
+ // Check if we're running inline (in-memory) or remote (queue-based)
535
+ if (this.isInline(runId)) {
536
+ // Inline mode - execute with retry loop
537
+ while (true) {
538
+ try {
539
+ await this.setStepRunning(currentStepState.stepId);
540
+ const result = await fn();
541
+ await this.setStepResult(currentStepState.stepId, result);
542
+ return result;
543
+ }
544
+ catch (error) {
545
+ // Record the error (marks step as failed)
546
+ await this.setStepError(currentStepState.stepId, error);
547
+ // Check if we should retry
548
+ if (currentStepState.attemptCount < retries) {
549
+ // Create a new pending retry attempt
550
+ currentStepState = await this.createRetryAttempt(currentStepState.stepId, 'pending');
551
+ // Wait for retry delay if specified
552
+ if (retryDelay) {
553
+ await new Promise((resolve) => setTimeout(resolve, getDurationInMilliseconds(retryDelay)));
554
+ }
555
+ // Continue loop to retry
556
+ }
557
+ else {
558
+ // No more retries, fail the workflow
559
+ throw error;
560
+ }
561
+ }
562
+ }
325
563
  }
326
- catch (error) {
327
- // Record the error (marks step as failed)
328
- await this.setStepError(stepState.stepId, error);
329
- // Check if we should retry
330
- if (attemptCount < retries) {
331
- // Create a new pending retry attempt (copies metadata from failed step)
332
- await this.createRetryAttempt(stepState.stepId, 'pending');
333
- // Schedule orchestrator to retry after delay
334
- await this.scheduleOrchestratorRetry(runId, retryDelay);
335
- // Pause workflow - orchestrator will replay and pick up new attempt
336
- throw new WorkflowAsyncException(runId, stepName);
564
+ else {
565
+ // Remote mode - use queue-based retry
566
+ try {
567
+ await this.setStepRunning(currentStepState.stepId);
568
+ const result = await fn();
569
+ await this.setStepResult(currentStepState.stepId, result);
570
+ return result;
571
+ }
572
+ catch (error) {
573
+ // Record the error (marks step as failed)
574
+ await this.setStepError(currentStepState.stepId, error);
575
+ // Check if we should retry
576
+ if (currentStepState.attemptCount < retries) {
577
+ // Create a new pending retry attempt (copies metadata from failed step)
578
+ await this.createRetryAttempt(currentStepState.stepId, 'pending');
579
+ // Schedule orchestrator to retry after delay
580
+ await this.scheduleOrchestratorRetry(runId, retryDelay);
581
+ // Pause workflow - orchestrator will replay and pick up new attempt
582
+ throw new WorkflowAsyncException(runId, stepName);
583
+ }
584
+ // No more retries, fail the workflow
585
+ throw error;
337
586
  }
338
- // No more retries, fail the workflow
339
- throw error;
340
587
  }
341
588
  }
342
589
  async sleepStep(runId, stepName, duration) {
@@ -345,12 +592,11 @@ export class PikkuWorkflowService {
345
592
  try {
346
593
  stepState = await this.getStepState(runId, stepName);
347
594
  }
348
- catch (error) {
595
+ catch {
349
596
  // Step doesn't exist - create it (sleep step, no RPC)
350
- stepState = await this.insertStepState(runId, stepName, null, // No RPC for sleep steps
351
- { duration }, // Store duration as data
352
- undefined // No retry options for sleep
353
- );
597
+ stepState = await this.insertStepState(runId, stepName, null, {
598
+ duration,
599
+ });
354
600
  }
355
601
  if (stepState.status === 'succeeded') {
356
602
  // Sleep already completed, return immediately
@@ -362,13 +608,15 @@ export class PikkuWorkflowService {
362
608
  }
363
609
  // Step is pending - schedule it
364
610
  await this.setStepScheduled(stepState.stepId);
365
- // Check if we have queue service (remote mode) or inline mode
366
- if (this.singletonServices.schedulerService) {
367
- // Remote mode - enqueue sleep worker with delay
611
+ // Check if inline mode or no scheduler service
612
+ if (!this.isInline(runId) && this.singletonServices.schedulerService) {
613
+ // Remote mode - schedule sleep via scheduler service
368
614
  await this.singletonServices.schedulerService.scheduleRPC(duration, this.getConfig().sleeperRPCName, {
369
615
  runId,
370
616
  stepId: stepState.stepId,
371
617
  });
618
+ // Pause workflow - sleep will callback when done
619
+ throw new WorkflowAsyncException(runId, stepName);
372
620
  }
373
621
  else {
374
622
  // Inline mode - use setTimeout with actual duration
@@ -376,22 +624,46 @@ export class PikkuWorkflowService {
376
624
  await this.setStepResult(stepState.stepId, null);
377
625
  return;
378
626
  }
379
- // Pause workflow - sleep will callback when done
380
- throw new WorkflowAsyncException(runId, stepName);
381
627
  }
382
- async cancelWorkflow(runId, reason) {
383
- // Update workflow run status to cancelled
384
- await this.updateRunStatus(runId, 'cancelled', undefined, {
385
- message: reason || 'Workflow cancelled by user',
386
- stack: '',
387
- code: 'WORKFLOW_CANCELLED',
628
+ getSuspendStepName(reason) {
629
+ if (!reason) {
630
+ return '__workflow_suspend';
631
+ }
632
+ return '__workflow_suspend';
633
+ }
634
+ async suspendStep(runId, reason) {
635
+ const stepName = this.getSuspendStepName(reason);
636
+ await this.withStepLock(runId, stepName, async () => {
637
+ let stepState;
638
+ try {
639
+ stepState = await this.getStepState(runId, stepName);
640
+ }
641
+ catch {
642
+ stepState = await this.insertStepState(runId, stepName, 'pikkuWorkflowSuspend', {
643
+ reason,
644
+ });
645
+ }
646
+ if (!stepState.stepId) {
647
+ stepState = await this.insertStepState(runId, stepName, 'pikkuWorkflowSuspend', {
648
+ reason,
649
+ });
650
+ }
651
+ if (stepState.status === 'succeeded') {
652
+ return;
653
+ }
654
+ if (stepState.status === 'pending') {
655
+ await this.setStepRunning(stepState.stepId);
656
+ }
657
+ await this.setStepResult(stepState.stepId, {
658
+ reason,
659
+ suspendedAt: new Date().toISOString(),
660
+ });
661
+ throw new WorkflowSuspendedException(runId, reason);
388
662
  });
389
- // Throw cancellation exception to stop workflow execution
390
- throw new WorkflowCancelledException(runId, reason);
391
663
  }
392
- createWorkflowWire(workflowName, runId, rpcService) {
664
+ createWorkflowWire(name, runId, rpcService) {
393
665
  const workflowWire = {
394
- workflowName,
666
+ name,
395
667
  runId,
396
668
  getRun: async () => (await this.getRun(runId)),
397
669
  // Implement workflow.do() - RPC form
@@ -409,9 +681,8 @@ export class PikkuWorkflowService {
409
681
  this.verifyStepName(stepName);
410
682
  await this.sleepStep(runId, stepName, getDurationInMilliseconds(duration));
411
683
  },
412
- // Implement workflow.cancel()
413
- cancel: async (reason) => {
414
- await this.cancelWorkflow(runId, reason);
684
+ suspend: async (reason) => {
685
+ await this.suspendStep(runId, reason || 'Workflow suspended');
415
686
  },
416
687
  };
417
688
  return workflowWire;