@pikku/core 0.11.2 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (436) hide show
  1. package/CHANGELOG.md +57 -2
  2. package/dist/crypto-utils.d.ts +2 -0
  3. package/dist/crypto-utils.js +78 -0
  4. package/dist/errors/errors.d.ts +14 -0
  5. package/dist/errors/errors.js +26 -0
  6. package/dist/errors/index.d.ts +1 -1
  7. package/dist/errors/index.js +1 -1
  8. package/dist/function/function-runner.d.ts +10 -4
  9. package/dist/function/function-runner.js +126 -45
  10. package/dist/function/functions.types.d.ts +32 -87
  11. package/dist/function/functions.types.js +16 -46
  12. package/dist/function/index.d.ts +2 -2
  13. package/dist/function/index.js +1 -2
  14. package/dist/handle-error.d.ts +2 -2
  15. package/dist/handle-error.js +3 -12
  16. package/dist/index.d.ts +30 -26
  17. package/dist/index.js +19 -29
  18. package/dist/internal.d.ts +3 -0
  19. package/dist/internal.js +2 -0
  20. package/dist/middleware/auth-apikey.d.ts +2 -2
  21. package/dist/middleware/auth-apikey.js +3 -3
  22. package/dist/middleware/auth-bearer.d.ts +3 -3
  23. package/dist/middleware/auth-bearer.js +4 -7
  24. package/dist/middleware/auth-cookie.d.ts +4 -4
  25. package/dist/middleware/auth-cookie.js +7 -9
  26. package/dist/middleware/cors.d.ts +46 -0
  27. package/dist/middleware/cors.js +81 -0
  28. package/dist/middleware/index.d.ts +5 -3
  29. package/dist/middleware/index.js +5 -3
  30. package/dist/middleware/remote-auth.d.ts +5 -0
  31. package/dist/middleware/remote-auth.js +55 -0
  32. package/dist/middleware/timeout.d.ts +2 -2
  33. package/dist/middleware-runner.d.ts +1 -1
  34. package/dist/middleware-runner.js +8 -10
  35. package/dist/permissions.d.ts +2 -2
  36. package/dist/permissions.js +11 -15
  37. package/dist/pikku-state.d.ts +10 -8
  38. package/dist/pikku-state.js +48 -25
  39. package/dist/schema.d.ts +4 -4
  40. package/dist/schema.js +20 -15
  41. package/dist/services/ai-agent-runner-service.d.ts +43 -0
  42. package/dist/services/ai-agent-runner-service.js +1 -0
  43. package/dist/services/ai-run-state-service.d.ts +13 -0
  44. package/dist/services/ai-run-state-service.js +1 -0
  45. package/dist/services/ai-storage-service.d.ts +18 -0
  46. package/dist/services/ai-storage-service.js +1 -0
  47. package/dist/services/content-service.d.ts +6 -0
  48. package/dist/services/deployment-service.d.ts +19 -0
  49. package/dist/services/deployment-service.js +1 -0
  50. package/dist/services/gopass-secrets.d.ts +47 -0
  51. package/dist/services/gopass-secrets.js +97 -0
  52. package/dist/services/in-memory-ai-run-state-service.d.ts +15 -0
  53. package/dist/services/in-memory-ai-run-state-service.js +44 -0
  54. package/dist/services/in-memory-trigger-service.d.ts +22 -0
  55. package/dist/services/in-memory-trigger-service.js +48 -0
  56. package/dist/services/in-memory-workflow-service.d.ts +59 -0
  57. package/dist/services/in-memory-workflow-service.js +287 -0
  58. package/dist/services/index.d.ts +26 -11
  59. package/dist/services/index.js +11 -13
  60. package/dist/services/local-content.d.ts +2 -1
  61. package/dist/services/local-content.js +6 -1
  62. package/dist/services/local-secrets.d.ts +25 -2
  63. package/dist/services/local-secrets.js +45 -0
  64. package/dist/services/local-variables.d.ts +6 -1
  65. package/dist/services/local-variables.js +18 -0
  66. package/dist/services/logger-console.d.ts +2 -1
  67. package/dist/services/scheduler-service.d.ts +9 -1
  68. package/dist/services/scheduler-service.js +8 -0
  69. package/dist/services/schema-service.d.ts +6 -0
  70. package/dist/services/scoped-secret-service.d.ts +16 -0
  71. package/dist/services/scoped-secret-service.js +35 -0
  72. package/dist/services/secret-service.d.ts +25 -4
  73. package/dist/services/trigger-service.d.ts +16 -0
  74. package/dist/services/trigger-service.js +1 -0
  75. package/dist/services/typed-secret-service.d.ts +30 -0
  76. package/dist/services/typed-secret-service.js +40 -0
  77. package/dist/services/typed-variables-service.d.ts +27 -0
  78. package/dist/services/typed-variables-service.js +46 -0
  79. package/dist/services/user-session-service.d.ts +15 -2
  80. package/dist/services/user-session-service.js +17 -0
  81. package/dist/services/variables-service.d.ts +5 -0
  82. package/dist/services/workflow-service.d.ts +9 -5
  83. package/dist/types/core.types.d.ts +67 -25
  84. package/dist/types/core.types.js +8 -1
  85. package/dist/types/state.types.d.ts +53 -59
  86. package/dist/utils.d.ts +7 -6
  87. package/dist/utils.js +27 -12
  88. package/dist/version.d.ts +6 -0
  89. package/dist/version.js +19 -0
  90. package/dist/wirings/ai-agent/ai-agent-assistant-ui.d.ts +5 -0
  91. package/dist/wirings/ai-agent/ai-agent-assistant-ui.js +146 -0
  92. package/dist/wirings/ai-agent/ai-agent-helpers.d.ts +28 -0
  93. package/dist/wirings/ai-agent/ai-agent-helpers.js +40 -0
  94. package/dist/wirings/ai-agent/ai-agent-memory.d.ts +29 -0
  95. package/dist/wirings/ai-agent/ai-agent-memory.js +199 -0
  96. package/dist/wirings/ai-agent/ai-agent-model-config.d.ts +9 -0
  97. package/dist/wirings/ai-agent/ai-agent-model-config.js +36 -0
  98. package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +59 -0
  99. package/dist/wirings/ai-agent/ai-agent-prepare.js +348 -0
  100. package/dist/wirings/ai-agent/ai-agent-registry.d.ts +15 -0
  101. package/dist/wirings/ai-agent/ai-agent-registry.js +47 -0
  102. package/dist/wirings/ai-agent/ai-agent-runner.d.ts +3 -0
  103. package/dist/wirings/ai-agent/ai-agent-runner.js +195 -0
  104. package/dist/wirings/ai-agent/ai-agent-stream.d.ts +12 -0
  105. package/dist/wirings/ai-agent/ai-agent-stream.js +662 -0
  106. package/dist/wirings/ai-agent/ai-agent.types.d.ts +313 -0
  107. package/dist/wirings/ai-agent/ai-agent.types.js +1 -0
  108. package/dist/wirings/ai-agent/index.d.ts +7 -0
  109. package/dist/wirings/ai-agent/index.js +6 -0
  110. package/dist/wirings/channel/channel-common.d.ts +8 -5
  111. package/dist/wirings/channel/channel-common.js +15 -8
  112. package/dist/wirings/channel/channel-handler.d.ts +3 -3
  113. package/dist/wirings/channel/channel-handler.js +15 -6
  114. package/dist/wirings/channel/channel-middleware-runner.d.ts +9 -0
  115. package/dist/wirings/channel/channel-middleware-runner.js +73 -0
  116. package/dist/wirings/channel/channel-runner.d.ts +5 -5
  117. package/dist/wirings/channel/channel-runner.js +10 -9
  118. package/dist/wirings/channel/channel-store.d.ts +1 -1
  119. package/dist/wirings/channel/channel.types.d.ts +9 -7
  120. package/dist/wirings/channel/define-channel-routes.d.ts +21 -0
  121. package/dist/wirings/channel/define-channel-routes.js +23 -0
  122. package/dist/wirings/channel/index.d.ts +10 -8
  123. package/dist/wirings/channel/index.js +7 -8
  124. package/dist/wirings/channel/local/index.d.ts +3 -3
  125. package/dist/wirings/channel/local/index.js +3 -3
  126. package/dist/wirings/channel/local/local-channel-runner.d.ts +7 -2
  127. package/dist/wirings/channel/local/local-channel-runner.js +22 -9
  128. package/dist/wirings/channel/local/local-eventhub-service.d.ts +2 -2
  129. package/dist/wirings/channel/log-channels.d.ts +1 -1
  130. package/dist/wirings/channel/pikku-abstract-channel-handler.d.ts +1 -1
  131. package/dist/wirings/channel/serverless/index.d.ts +1 -1
  132. package/dist/wirings/channel/serverless/index.js +1 -1
  133. package/dist/wirings/channel/serverless/serverless-channel-runner.d.ts +4 -4
  134. package/dist/wirings/channel/serverless/serverless-channel-runner.js +40 -23
  135. package/dist/wirings/cli/channel/cli-channel-runner.d.ts +1 -1
  136. package/dist/wirings/cli/channel/index.d.ts +1 -1
  137. package/dist/wirings/cli/channel/index.js +1 -1
  138. package/dist/wirings/cli/cli-runner.d.ts +3 -3
  139. package/dist/wirings/cli/cli-runner.js +12 -8
  140. package/dist/wirings/cli/cli.types.d.ts +9 -9
  141. package/dist/wirings/cli/command-parser.d.ts +1 -1
  142. package/dist/wirings/cli/define-cli-commands.d.ts +19 -0
  143. package/dist/wirings/cli/define-cli-commands.js +20 -0
  144. package/dist/wirings/cli/index.d.ts +2 -3
  145. package/dist/wirings/cli/index.js +1 -3
  146. package/dist/wirings/http/http-routes.d.ts +69 -0
  147. package/dist/wirings/http/http-routes.js +139 -0
  148. package/dist/wirings/http/http-runner.d.ts +9 -9
  149. package/dist/wirings/http/http-runner.js +79 -34
  150. package/dist/wirings/http/http.types.d.ts +77 -41
  151. package/dist/wirings/http/index.d.ts +5 -4
  152. package/dist/wirings/http/index.js +4 -3
  153. package/dist/wirings/http/log-http-routes.d.ts +1 -1
  154. package/dist/wirings/http/pikku-fetch-http-request.d.ts +1 -1
  155. package/dist/wirings/http/pikku-fetch-http-response.d.ts +2 -2
  156. package/dist/wirings/http/pikku-fetch-http-response.js +6 -1
  157. package/dist/wirings/http/routers/http-router.d.ts +1 -1
  158. package/dist/wirings/http/routers/path-to-regex.d.ts +2 -2
  159. package/dist/wirings/mcp/index.d.ts +4 -3
  160. package/dist/wirings/mcp/index.js +3 -3
  161. package/dist/wirings/mcp/mcp-endpoint-registry.d.ts +1 -1
  162. package/dist/wirings/mcp/mcp-runner.d.ts +1 -6
  163. package/dist/wirings/mcp/mcp-runner.js +28 -40
  164. package/dist/wirings/mcp/mcp.types.d.ts +5 -5
  165. package/dist/wirings/node/index.d.ts +2 -0
  166. package/dist/wirings/node/index.js +1 -0
  167. package/dist/wirings/node/node.types.d.ts +20 -0
  168. package/dist/wirings/node/node.types.js +1 -0
  169. package/dist/wirings/oauth2/index.d.ts +3 -0
  170. package/dist/wirings/oauth2/index.js +2 -0
  171. package/dist/wirings/oauth2/oauth2-client.d.ts +47 -0
  172. package/dist/wirings/oauth2/oauth2-client.js +267 -0
  173. package/dist/wirings/oauth2/oauth2.types.d.ts +65 -0
  174. package/dist/wirings/oauth2/oauth2.types.js +4 -0
  175. package/dist/wirings/oauth2/wire-oauth2-credential.d.ts +20 -0
  176. package/dist/wirings/oauth2/wire-oauth2-credential.js +21 -0
  177. package/dist/wirings/queue/index.d.ts +1 -1
  178. package/dist/wirings/queue/queue-runner.d.ts +1 -5
  179. package/dist/wirings/queue/queue-runner.js +11 -8
  180. package/dist/wirings/queue/queue.types.d.ts +5 -5
  181. package/dist/wirings/queue/register-queue-helper.d.ts +2 -2
  182. package/dist/wirings/queue/register-queue-helper.js +1 -1
  183. package/dist/wirings/queue/validate-worker-config.d.ts +1 -1
  184. package/dist/wirings/rpc/index.d.ts +3 -1
  185. package/dist/wirings/rpc/index.js +2 -1
  186. package/dist/wirings/rpc/rpc-runner.d.ts +58 -8
  187. package/dist/wirings/rpc/rpc-runner.js +177 -31
  188. package/dist/wirings/rpc/rpc-types.d.ts +20 -4
  189. package/dist/wirings/rpc/wire-addon.d.ts +10 -0
  190. package/dist/wirings/rpc/wire-addon.js +9 -0
  191. package/dist/wirings/scheduler/index.d.ts +2 -2
  192. package/dist/wirings/scheduler/index.js +1 -2
  193. package/dist/wirings/scheduler/log-schedulers.d.ts +1 -1
  194. package/dist/wirings/scheduler/scheduler-runner.d.ts +3 -5
  195. package/dist/wirings/scheduler/scheduler-runner.js +11 -14
  196. package/dist/wirings/scheduler/scheduler.types.d.ts +2 -2
  197. package/dist/wirings/secret/index.d.ts +3 -0
  198. package/dist/wirings/secret/index.js +2 -0
  199. package/dist/wirings/secret/secret.types.d.ts +27 -0
  200. package/dist/wirings/secret/secret.types.js +1 -0
  201. package/dist/wirings/secret/validate-secret-definitions.d.ts +6 -0
  202. package/dist/wirings/secret/validate-secret-definitions.js +55 -0
  203. package/dist/wirings/trigger/index.d.ts +3 -2
  204. package/dist/wirings/trigger/index.js +2 -2
  205. package/dist/wirings/trigger/pikku-trigger-service.d.ts +22 -0
  206. package/dist/wirings/trigger/pikku-trigger-service.js +70 -0
  207. package/dist/wirings/trigger/trigger-runner.d.ts +18 -9
  208. package/dist/wirings/trigger/trigger-runner.js +48 -19
  209. package/dist/wirings/trigger/trigger.types.d.ts +99 -11
  210. package/dist/wirings/trigger/trigger.types.js +40 -1
  211. package/dist/wirings/variable/index.d.ts +3 -0
  212. package/dist/wirings/variable/index.js +2 -0
  213. package/dist/wirings/variable/validate-variable-definitions.d.ts +6 -0
  214. package/dist/wirings/variable/validate-variable-definitions.js +45 -0
  215. package/dist/wirings/variable/variable.types.d.ts +18 -0
  216. package/dist/wirings/variable/variable.types.js +1 -0
  217. package/dist/wirings/workflow/dsl/index.d.ts +1 -1
  218. package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +7 -1
  219. package/dist/wirings/workflow/dsl/workflow-runner.js +3 -2
  220. package/dist/wirings/workflow/graph/graph-node.d.ts +1 -6
  221. package/dist/wirings/workflow/graph/graph-runner.d.ts +5 -31
  222. package/dist/wirings/workflow/graph/graph-runner.js +384 -296
  223. package/dist/wirings/workflow/graph/index.d.ts +4 -3
  224. package/dist/wirings/workflow/graph/index.js +3 -3
  225. package/dist/wirings/workflow/graph/template.d.ts +15 -0
  226. package/dist/wirings/workflow/graph/template.js +27 -0
  227. package/dist/wirings/workflow/graph/wire-workflow-graph.d.ts +15 -0
  228. package/dist/wirings/workflow/graph/wire-workflow-graph.js +8 -0
  229. package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +0 -13
  230. package/dist/wirings/workflow/index.d.ts +6 -6
  231. package/dist/wirings/workflow/index.js +6 -4
  232. package/dist/wirings/workflow/pikku-workflow-service.d.ts +29 -19
  233. package/dist/wirings/workflow/pikku-workflow-service.js +217 -82
  234. package/dist/wirings/workflow/workflow-helpers.d.ts +36 -0
  235. package/dist/wirings/workflow/workflow-helpers.js +38 -0
  236. package/dist/wirings/workflow/workflow.types.d.ts +42 -95
  237. package/package.json +14 -15
  238. package/run-tests.sh +4 -1
  239. package/src/crypto-utils.test.ts +116 -0
  240. package/src/crypto-utils.ts +99 -0
  241. package/src/errors/error.test.ts +143 -2
  242. package/src/errors/errors.ts +27 -0
  243. package/src/errors/index.ts +1 -1
  244. package/src/factory-functions.test.ts +42 -2
  245. package/src/function/function-runner.test.ts +6 -13
  246. package/src/function/function-runner.ts +161 -53
  247. package/src/function/functions.types.ts +63 -132
  248. package/src/function/index.ts +5 -2
  249. package/src/handle-error.test.ts +361 -0
  250. package/src/handle-error.ts +6 -14
  251. package/src/index.ts +97 -30
  252. package/src/internal.ts +6 -0
  253. package/src/middleware/auth-apikey.test.ts +16 -13
  254. package/src/middleware/auth-apikey.ts +3 -3
  255. package/src/middleware/auth-bearer.test.ts +18 -15
  256. package/src/middleware/auth-bearer.ts +27 -32
  257. package/src/middleware/auth-cookie.test.ts +17 -14
  258. package/src/middleware/auth-cookie.ts +9 -13
  259. package/src/middleware/cors.test.ts +424 -0
  260. package/src/middleware/cors.ts +104 -0
  261. package/src/middleware/index.ts +5 -3
  262. package/src/middleware/remote-auth.test.ts +488 -0
  263. package/src/middleware/remote-auth.ts +68 -0
  264. package/src/middleware-runner.test.ts +123 -1
  265. package/src/middleware-runner.ts +13 -13
  266. package/src/permissions.test.ts +83 -9
  267. package/src/permissions.ts +17 -19
  268. package/src/pikku-state.test.ts +224 -0
  269. package/src/pikku-state.ts +59 -35
  270. package/src/run-tests-script.test.ts +49 -0
  271. package/src/schema.test.ts +198 -6
  272. package/src/schema.ts +28 -21
  273. package/src/services/ai-agent-runner-service.ts +41 -0
  274. package/src/services/ai-run-state-service.ts +20 -0
  275. package/src/services/ai-storage-service.ts +39 -0
  276. package/src/services/content-service.ts +7 -0
  277. package/src/services/deployment-service.ts +22 -0
  278. package/src/services/gopass-secrets.ts +98 -0
  279. package/src/services/in-memory-ai-run-state-service.ts +73 -0
  280. package/src/services/in-memory-trigger-service.ts +64 -0
  281. package/src/services/in-memory-workflow-service.test.ts +351 -0
  282. package/src/services/in-memory-workflow-service.ts +395 -0
  283. package/src/services/index.ts +50 -14
  284. package/src/services/local-content.ts +9 -3
  285. package/src/services/local-secrets.test.ts +80 -0
  286. package/src/services/local-secrets.ts +53 -2
  287. package/src/services/local-variables.test.ts +61 -0
  288. package/src/services/local-variables.ts +23 -1
  289. package/src/services/logger-console.test.ts +118 -0
  290. package/src/services/logger-console.ts +2 -1
  291. package/src/services/scheduler-service.ts +11 -1
  292. package/src/services/schema-service.ts +7 -0
  293. package/src/services/scoped-secret-service.test.ts +74 -0
  294. package/src/services/scoped-secret-service.ts +41 -0
  295. package/src/services/secret-service.ts +25 -4
  296. package/src/services/trigger-service.ts +17 -0
  297. package/src/services/typed-secret-service.test.ts +93 -0
  298. package/src/services/typed-secret-service.ts +70 -0
  299. package/src/services/typed-variables-service.test.ts +73 -0
  300. package/src/services/typed-variables-service.ts +81 -0
  301. package/src/services/user-session-service.test.ts +113 -0
  302. package/src/services/user-session-service.ts +25 -2
  303. package/src/services/variables-service.ts +5 -0
  304. package/src/services/workflow-service.ts +23 -13
  305. package/src/time-utils.test.ts +1 -1
  306. package/src/types/core.types.ts +95 -24
  307. package/src/types/state.types.ts +56 -73
  308. package/src/utils.test.ts +350 -0
  309. package/src/utils.ts +33 -18
  310. package/src/version.test.ts +80 -0
  311. package/src/version.ts +25 -0
  312. package/src/wirings/ai-agent/ai-agent-assistant-ui.test.ts +363 -0
  313. package/src/wirings/ai-agent/ai-agent-assistant-ui.ts +214 -0
  314. package/src/wirings/ai-agent/ai-agent-helpers.test.ts +152 -0
  315. package/src/wirings/ai-agent/ai-agent-helpers.ts +76 -0
  316. package/src/wirings/ai-agent/ai-agent-memory.ts +300 -0
  317. package/src/wirings/ai-agent/ai-agent-model-config.test.ts +115 -0
  318. package/src/wirings/ai-agent/ai-agent-model-config.ts +43 -0
  319. package/src/wirings/ai-agent/ai-agent-prepare.ts +504 -0
  320. package/src/wirings/ai-agent/ai-agent-registry.test.ts +37 -0
  321. package/src/wirings/ai-agent/ai-agent-registry.ts +82 -0
  322. package/src/wirings/ai-agent/ai-agent-runner.test.ts +317 -0
  323. package/src/wirings/ai-agent/ai-agent-runner.ts +251 -0
  324. package/src/wirings/ai-agent/ai-agent-stream.test.ts +652 -0
  325. package/src/wirings/ai-agent/ai-agent-stream.ts +1049 -0
  326. package/src/wirings/ai-agent/ai-agent.types.ts +328 -0
  327. package/src/wirings/ai-agent/index.ts +39 -0
  328. package/src/wirings/channel/channel-common.ts +33 -20
  329. package/src/wirings/channel/channel-handler.ts +26 -9
  330. package/src/wirings/channel/channel-middleware-runner.ts +119 -0
  331. package/src/wirings/channel/channel-runner.ts +15 -16
  332. package/src/wirings/channel/channel-store.ts +1 -1
  333. package/src/wirings/channel/channel.types.ts +21 -9
  334. package/src/wirings/channel/define-channel-routes.ts +25 -0
  335. package/src/wirings/channel/index.ts +23 -8
  336. package/src/wirings/channel/local/index.ts +3 -3
  337. package/src/wirings/channel/local/local-channel-runner.test.ts +57 -7
  338. package/src/wirings/channel/local/local-channel-runner.ts +40 -19
  339. package/src/wirings/channel/local/local-eventhub-service.test.ts +2 -2
  340. package/src/wirings/channel/local/local-eventhub-service.ts +2 -2
  341. package/src/wirings/channel/log-channels.ts +1 -1
  342. package/src/wirings/channel/pikku-abstract-channel-handler.ts +1 -1
  343. package/src/wirings/channel/serverless/index.ts +5 -1
  344. package/src/wirings/channel/serverless/serverless-channel-runner.ts +57 -36
  345. package/src/wirings/cli/channel/cli-channel-runner.ts +1 -1
  346. package/src/wirings/cli/channel/index.ts +1 -1
  347. package/src/wirings/cli/cli-runner.test.ts +10 -10
  348. package/src/wirings/cli/cli-runner.ts +28 -14
  349. package/src/wirings/cli/cli.types.ts +14 -14
  350. package/src/wirings/cli/command-parser.test.ts +9 -9
  351. package/src/wirings/cli/command-parser.ts +1 -1
  352. package/src/wirings/cli/define-cli-commands.ts +24 -0
  353. package/src/wirings/cli/index.ts +10 -4
  354. package/src/wirings/http/http-routes.test.ts +322 -0
  355. package/src/wirings/http/http-routes.ts +198 -0
  356. package/src/wirings/http/http-runner.test.ts +12 -20
  357. package/src/wirings/http/http-runner.ts +101 -51
  358. package/src/wirings/http/http.types.ts +119 -48
  359. package/src/wirings/http/index.ts +16 -4
  360. package/src/wirings/http/log-http-routes.ts +1 -1
  361. package/src/wirings/http/pikku-fetch-http-request.ts +1 -1
  362. package/src/wirings/http/pikku-fetch-http-response.ts +12 -5
  363. package/src/wirings/http/routers/http-router.ts +1 -1
  364. package/src/wirings/http/routers/path-to-regex.test.ts +1 -1
  365. package/src/wirings/http/routers/path-to-regex.ts +4 -3
  366. package/src/wirings/mcp/index.ts +27 -3
  367. package/src/wirings/mcp/mcp-endpoint-registry.test.ts +3 -3
  368. package/src/wirings/mcp/mcp-endpoint-registry.ts +5 -1
  369. package/src/wirings/mcp/mcp-runner.ts +48 -65
  370. package/src/wirings/mcp/mcp.types.ts +5 -5
  371. package/src/wirings/node/index.ts +2 -0
  372. package/src/wirings/node/node.types.ts +23 -0
  373. package/src/wirings/oauth2/index.ts +3 -0
  374. package/src/wirings/oauth2/oauth2-client.test.ts +929 -0
  375. package/src/wirings/oauth2/oauth2-client.ts +335 -0
  376. package/src/wirings/oauth2/oauth2.types.ts +69 -0
  377. package/src/wirings/oauth2/wire-oauth2-credential.ts +23 -0
  378. package/src/wirings/queue/index.ts +13 -2
  379. package/src/wirings/queue/queue-runner.test.ts +142 -73
  380. package/src/wirings/queue/queue-runner.ts +18 -14
  381. package/src/wirings/queue/queue.types.ts +5 -5
  382. package/src/wirings/queue/register-queue-helper.ts +3 -5
  383. package/src/wirings/queue/validate-worker-config.test.ts +108 -0
  384. package/src/wirings/queue/validate-worker-config.ts +4 -1
  385. package/src/wirings/rpc/index.ts +3 -1
  386. package/src/wirings/rpc/rpc-runner.ts +254 -41
  387. package/src/wirings/rpc/rpc-types.ts +28 -4
  388. package/src/wirings/rpc/wire-addon.ts +20 -0
  389. package/src/wirings/scheduler/index.ts +6 -4
  390. package/src/wirings/scheduler/log-schedulers.ts +1 -1
  391. package/src/wirings/scheduler/scheduler-runner.test.ts +78 -45
  392. package/src/wirings/scheduler/scheduler-runner.ts +20 -27
  393. package/src/wirings/scheduler/scheduler.types.ts +2 -2
  394. package/src/wirings/secret/index.ts +9 -0
  395. package/src/wirings/secret/secret.types.ts +32 -0
  396. package/src/wirings/secret/validate-secret-definitions.test.ts +140 -0
  397. package/src/wirings/secret/validate-secret-definitions.ts +82 -0
  398. package/src/wirings/trigger/index.ts +10 -2
  399. package/src/wirings/trigger/pikku-trigger-service.ts +112 -0
  400. package/src/wirings/trigger/trigger-runner.test.ts +79 -0
  401. package/src/wirings/trigger/trigger-runner.ts +80 -41
  402. package/src/wirings/trigger/trigger.types.ts +144 -22
  403. package/src/wirings/variable/index.ts +8 -0
  404. package/src/wirings/variable/validate-variable-definitions.test.ts +91 -0
  405. package/src/wirings/variable/validate-variable-definitions.ts +69 -0
  406. package/src/wirings/variable/variable.types.ts +22 -0
  407. package/src/wirings/workflow/dsl/index.ts +1 -0
  408. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +9 -1
  409. package/src/wirings/workflow/dsl/workflow-runner.ts +4 -3
  410. package/src/wirings/workflow/graph/graph-node.ts +1 -6
  411. package/src/wirings/workflow/graph/graph-runner.test.ts +487 -0
  412. package/src/wirings/workflow/graph/graph-runner.ts +496 -374
  413. package/src/wirings/workflow/graph/index.ts +14 -3
  414. package/src/wirings/workflow/graph/template.test.ts +49 -0
  415. package/src/wirings/workflow/graph/template.ts +42 -0
  416. package/src/wirings/workflow/graph/wire-workflow-graph.ts +29 -0
  417. package/src/wirings/workflow/graph/workflow-graph.types.ts +0 -22
  418. package/src/wirings/workflow/index.ts +21 -10
  419. package/src/wirings/workflow/pikku-workflow-service.test.ts +200 -0
  420. package/src/wirings/workflow/pikku-workflow-service.ts +308 -105
  421. package/src/wirings/workflow/workflow-helpers.test.ts +129 -0
  422. package/src/wirings/workflow/workflow-helpers.ts +79 -0
  423. package/src/wirings/workflow/workflow.types.ts +47 -92
  424. package/tsconfig.tsbuildinfo +1 -1
  425. package/dist/wirings/forge-node/forge-node.types.d.ts +0 -120
  426. package/dist/wirings/forge-node/forge-node.types.js +0 -38
  427. package/dist/wirings/forge-node/index.d.ts +0 -1
  428. package/dist/wirings/forge-node/index.js +0 -1
  429. package/dist/wirings/workflow/wire-workflow.d.ts +0 -42
  430. package/dist/wirings/workflow/wire-workflow.js +0 -53
  431. package/dist/wirings/workflow/workflow-utils.d.ts +0 -23
  432. package/dist/wirings/workflow/workflow-utils.js +0 -66
  433. package/src/wirings/forge-node/forge-node.types.ts +0 -135
  434. package/src/wirings/forge-node/index.ts +0 -1
  435. package/src/wirings/workflow/wire-workflow.ts +0 -94
  436. package/src/wirings/workflow/workflow-utils.ts +0 -120
@@ -1,75 +1,89 @@
1
1
  import type { PikkuWorkflowService } from '../pikku-workflow-service.js'
2
- import type {
3
- GraphNodeConfig,
4
- RefValue,
5
- RefFn,
6
- NextConfig,
7
- GraphWireState,
8
- PikkuGraphWire,
9
- WorkflowGraphDefinition,
10
- } from './workflow-graph.types.js'
11
- import { createRef, isRef } from './workflow-graph.types.js'
2
+ import type { GraphWireState, PikkuGraphWire } from './workflow-graph.types.js'
12
3
  import { pikkuState } from '../../../pikku-state.js'
4
+ import type { WorkflowRuntimeMeta, WorkflowRunWire } from '../workflow.types.js'
5
+ import { RPCNotFoundError } from '../../rpc/rpc-runner.js'
6
+
7
+ function buildTemplateRegex(nodeId: string): RegExp | null {
8
+ if (!nodeId.includes('${')) return null
9
+ const escaped = nodeId
10
+ .split(/\$\{[^}]+\}/)
11
+ .map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
12
+ .join('.+')
13
+ return new RegExp(`^${escaped}$`)
14
+ }
13
15
 
14
- /**
15
- * Add a workflow graph to the system
16
- * This is called by the generated workflow wirings
17
- */
18
- export const addWorkflowGraph = (
19
- workflowName: string,
20
- graphResult: { graph: Record<string, GraphNodeConfig<string>>; wires?: any }
21
- ) => {
22
- // Get workflow metadata from inspector
23
- const meta = pikkuState(null, 'workflows', 'meta')
24
- const workflowMeta = meta[workflowName]
25
- if (!workflowMeta) {
26
- throw new Error(
27
- `Workflow metadata not found for '${workflowName}'. Make sure to run the CLI to generate metadata.`
28
- )
16
+ function remapStepNamesToNodeIds(
17
+ stepNames: string[],
18
+ nodes: Record<string, any>,
19
+ graphName: string
20
+ ): string[] {
21
+ const templatePatterns = new Map<string, RegExp>()
22
+ for (const nodeId of Object.keys(nodes)) {
23
+ const regex = buildTemplateRegex(nodeId)
24
+ if (regex) templatePatterns.set(nodeId, regex)
29
25
  }
30
-
31
- // Store workflow graph definition in state
32
- const registrations = pikkuState(null, 'workflows', 'graphRegistrations')
33
- registrations.set(workflowName, {
34
- name: workflowName,
35
- wires: graphResult.wires || {},
36
- graph: graphResult.graph,
26
+ if (templatePatterns.size === 0) return stepNames
27
+ return stepNames.map((name) => {
28
+ if (nodes[name]) return name
29
+ const matches: string[] = []
30
+ for (const [nodeId, regex] of templatePatterns) {
31
+ if (regex.test(name)) matches.push(nodeId)
32
+ }
33
+ if (matches.length > 1) {
34
+ throw new Error(
35
+ `Workflow graph '${graphName}': ambiguous template node match for '${name}' (${matches.join(', ')})`
36
+ )
37
+ }
38
+ if (matches.length === 1) {
39
+ return matches[0]!
40
+ }
41
+ return name
37
42
  })
38
43
  }
39
44
 
40
- /**
41
- * Get a registered workflow graph by name
42
- */
43
- export function getWorkflowGraph(
44
- name: string
45
- ): WorkflowGraphDefinition<any> | undefined {
46
- const registrations = pikkuState(null, 'workflows', 'graphRegistrations')
47
- return registrations.get(name)
45
+ function remapBranchKeys(
46
+ branchKeys: Record<string, string>,
47
+ nodes: Record<string, any>,
48
+ graphName: string
49
+ ): Record<string, string> {
50
+ const templatePatterns = new Map<string, RegExp>()
51
+ for (const nodeId of Object.keys(nodes)) {
52
+ const regex = buildTemplateRegex(nodeId)
53
+ if (regex) templatePatterns.set(nodeId, regex)
54
+ }
55
+ if (templatePatterns.size === 0) return branchKeys
56
+ const remapped: Record<string, string> = {}
57
+ for (const [key, value] of Object.entries(branchKeys)) {
58
+ let mappedKey = key
59
+ if (!nodes[key]) {
60
+ const matches: string[] = []
61
+ for (const [nodeId, regex] of templatePatterns) {
62
+ if (regex.test(key)) matches.push(nodeId)
63
+ }
64
+ if (matches.length > 1) {
65
+ throw new Error(
66
+ `Workflow graph '${graphName}': ambiguous template branch key match for '${key}' (${matches.join(', ')})`
67
+ )
68
+ }
69
+ if (matches.length === 1) {
70
+ mappedKey = matches[0]!
71
+ }
72
+ }
73
+ remapped[mappedKey] = value
74
+ }
75
+ return remapped
48
76
  }
49
77
 
50
- /**
51
- * Resolve next config to array of node IDs
52
- * For branching (Record), uses the branch key from step
53
- */
54
- function resolveNextFromConfig(
55
- next: NextConfig<string> | undefined,
56
- branchKey?: string
57
- ): string[] {
58
- if (!next) return []
59
-
60
- if (typeof next === 'string') return [next]
61
- if (Array.isArray(next)) return next
62
-
63
- // Record (branching) - use branch key set by graph.branch()
64
- if (!branchKey || !(branchKey in next)) return []
65
-
66
- const branchNext = next[branchKey]
67
- return Array.isArray(branchNext) ? branchNext : [branchNext]
78
+ function isDataRef(value: unknown): value is { $ref: string; path?: string } {
79
+ return (
80
+ typeof value === 'object' &&
81
+ value !== null &&
82
+ '$ref' in value &&
83
+ typeof (value as any).$ref === 'string'
84
+ )
68
85
  }
69
86
 
70
- /**
71
- * Template value - represents a string template with variable interpolation
72
- */
73
87
  interface TemplateValue {
74
88
  $template: {
75
89
  parts: string[]
@@ -77,9 +91,6 @@ interface TemplateValue {
77
91
  }
78
92
  }
79
93
 
80
- /**
81
- * Check if a value is a template
82
- */
83
94
  function isTemplate(value: unknown): value is TemplateValue {
84
95
  return (
85
96
  typeof value === 'object' &&
@@ -89,73 +100,37 @@ function isTemplate(value: unknown): value is TemplateValue {
89
100
  )
90
101
  }
91
102
 
92
- /**
93
- * Create a template function for use in input callbacks
94
- */
95
- function createTemplate(templateStr: string, refs: RefValue[]): TemplateValue {
96
- const parts: string[] = []
97
- const expressions: Array<{ $ref: string; path?: string }> = []
98
-
99
- const regex = /\$(\d+)/g
100
- let lastIndex = 0
101
- let match
102
-
103
- while ((match = regex.exec(templateStr)) !== null) {
104
- parts.push(templateStr.slice(lastIndex, match.index))
105
- const refIndex = parseInt(match[1]!, 10)
106
- const refValue = refs[refIndex]
107
- if (refValue) {
108
- expressions.push({ $ref: refValue.nodeId, path: refValue.path })
109
- } else {
110
- expressions.push({ $ref: 'unknown' })
111
- }
112
- lastIndex = regex.lastIndex
113
- }
114
- parts.push(templateStr.slice(lastIndex))
115
-
116
- return { $template: { parts, expressions } }
103
+ function getWorkflowMeta(name: string): WorkflowRuntimeMeta | undefined {
104
+ const meta = pikkuState(null, 'workflows', 'meta')
105
+ return meta[name]
117
106
  }
118
107
 
119
- /**
120
- * Evaluate a node's input callback to get the input mapping
121
- */
122
- function evaluateInputCallback(
123
- node: GraphNodeConfig
124
- ): Record<string, unknown | RefValue | TemplateValue> {
125
- if (!node.input) return {}
108
+ function resolveNextFromConfig(next: unknown, branchKey?: string): string[] {
109
+ if (!next) return []
126
110
 
127
- const ref: RefFn<string> = (targetNodeId: string, path?: string) =>
128
- createRef(targetNodeId, path)
111
+ if (typeof next === 'string') return [next]
112
+ if (Array.isArray(next)) return next
129
113
 
130
- const template = (templateStr: string, refs: RefValue[]) =>
131
- createTemplate(templateStr, refs)
114
+ if (typeof next === 'object' && next !== null) {
115
+ if (!branchKey || !(branchKey in next)) return []
116
+ const branchNext = (next as Record<string, string | string[]>)[branchKey]!
117
+ return Array.isArray(branchNext) ? branchNext : [branchNext]
118
+ }
132
119
 
133
- // Call with both ref and template - input callback may accept 1 or 2 params
134
- return (node.input as any)(ref, template)
120
+ return []
135
121
  }
136
122
 
137
- /**
138
- * Extract node IDs referenced in an input mapping (including from templates)
139
- */
140
- function extractReferencedNodeIds(
141
- inputMapping: Record<string, unknown | RefValue | TemplateValue>
142
- ): string[] {
143
- const nodeIds: string[] = []
144
- for (const value of Object.values(inputMapping)) {
145
- if (isRef(value)) {
146
- nodeIds.push(value.nodeId)
147
- } else if (isTemplate(value)) {
148
- for (const expr of value.$template.expressions) {
149
- nodeIds.push(expr.$ref)
150
- }
151
- }
123
+ function getValueAtPath(obj: any, path: string): any {
124
+ if (!path) return obj
125
+ const parts = path.split('.')
126
+ let current = obj
127
+ for (const part of parts) {
128
+ if (current == null) return undefined
129
+ current = current[part]
152
130
  }
153
- return [...new Set(nodeIds)]
131
+ return current
154
132
  }
155
133
 
156
- /**
157
- * Resolve a template value using node results
158
- */
159
134
  function resolveTemplate(
160
135
  template: TemplateValue,
161
136
  nodeResults: Record<string, any>
@@ -165,7 +140,7 @@ function resolveTemplate(
165
140
  for (let i = 0; i < parts.length; i++) {
166
141
  result += parts[i]
167
142
  if (i < expressions.length) {
168
- const expr = expressions[i]
143
+ const expr = expressions[i]!
169
144
  const nodeResult = nodeResults[expr.$ref]
170
145
  const value = expr.path
171
146
  ? getValueAtPath(nodeResult, expr.path)
@@ -176,62 +151,144 @@ function resolveTemplate(
176
151
  return result
177
152
  }
178
153
 
179
- /**
180
- * Resolve input mapping using node results
181
- */
182
- function resolveInputMapping(
183
- inputMapping: Record<string, unknown | RefValue | TemplateValue>,
154
+ function resolveValue(value: unknown, nodeResults: Record<string, any>): any {
155
+ if (isDataRef(value)) {
156
+ const source = nodeResults[value.$ref]
157
+ return value.path ? getValueAtPath(source, value.path) : source
158
+ }
159
+ if (isTemplate(value)) {
160
+ return resolveTemplate(value, nodeResults)
161
+ }
162
+ if (Array.isArray(value)) {
163
+ return value.map((item) => resolveValue(item, nodeResults))
164
+ }
165
+ if (typeof value === 'object' && value !== null) {
166
+ const resolved: Record<string, any> = {}
167
+ for (const [k, v] of Object.entries(value)) {
168
+ resolved[k] = resolveValue(v, nodeResults)
169
+ }
170
+ return resolved
171
+ }
172
+ return value
173
+ }
174
+
175
+ function resolveSerializedInput(
176
+ input: Record<string, unknown> | undefined,
184
177
  nodeResults: Record<string, any>
185
178
  ): Record<string, any> {
179
+ if (!input || Object.keys(input).length === 0) return {}
180
+
186
181
  const resolved: Record<string, any> = {}
182
+ for (const [key, value] of Object.entries(input)) {
183
+ resolved[key] = resolveValue(value, nodeResults)
184
+ }
185
+ return resolved
186
+ }
187
187
 
188
- for (const [key, value] of Object.entries(inputMapping)) {
189
- if (isRef(value)) {
190
- const nodeResult = nodeResults[value.nodeId]
191
- resolved[key] = value.path
192
- ? getValueAtPath(nodeResult, value.path)
193
- : nodeResult
194
- } else if (isTemplate(value)) {
195
- resolved[key] = resolveTemplate(value, nodeResults)
196
- } else {
197
- resolved[key] = value
188
+ function collectReferencedNodeIds(value: unknown, nodeIds: string[]): void {
189
+ if (isDataRef(value)) {
190
+ nodeIds.push(value.$ref)
191
+ } else if (isTemplate(value)) {
192
+ for (const expr of value.$template.expressions) {
193
+ nodeIds.push(expr.$ref)
194
+ }
195
+ } else if (Array.isArray(value)) {
196
+ for (const item of value) {
197
+ collectReferencedNodeIds(item, nodeIds)
198
+ }
199
+ } else if (typeof value === 'object' && value !== null) {
200
+ for (const v of Object.values(value)) {
201
+ collectReferencedNodeIds(v, nodeIds)
198
202
  }
199
203
  }
204
+ }
200
205
 
201
- return resolved
206
+ function extractReferencedNodeIds(
207
+ input: Record<string, unknown> | undefined
208
+ ): string[] {
209
+ if (!input) return []
210
+ const nodeIds: string[] = []
211
+ for (const value of Object.values(input)) {
212
+ collectReferencedNodeIds(value, nodeIds)
213
+ }
214
+ return [...new Set(nodeIds)]
202
215
  }
203
216
 
204
- /**
205
- * Get value at a dot-notation path from an object
206
- */
207
- function getValueAtPath(obj: any, path: string): any {
208
- if (!path) return obj
209
- const parts = path.split('.')
210
- let current = obj
211
- for (const part of parts) {
212
- if (current == null) return undefined
213
- current = current[part]
217
+ const IGNORED_REFS = new Set(['trigger', '$item', 'unknown'])
218
+
219
+ function normalizeNodeTargets(value: unknown): string[] {
220
+ if (!value) return []
221
+ if (typeof value === 'string') return [value]
222
+ if (Array.isArray(value)) return value.filter((v) => typeof v === 'string')
223
+ if (typeof value === 'object') {
224
+ const targets: string[] = []
225
+ for (const branchTarget of Object.values(
226
+ value as Record<string, unknown>
227
+ )) {
228
+ targets.push(...normalizeNodeTargets(branchTarget))
229
+ }
230
+ return targets
214
231
  }
215
- return current
232
+ return []
216
233
  }
217
234
 
218
- /**
219
- * Get the RPC name from a graph node's func
220
- * Supports both function references (func.name) and string RPC names
221
- */
222
- function getRpcName(node: GraphNodeConfig): string {
223
- const func = node.func as any
224
- // If func is a string, use it directly as the RPC name
225
- if (typeof func === 'string') {
226
- return func
227
- }
228
- // Otherwise, it's a function reference - use its name
229
- return func?.name || 'unknown'
235
+ function validateGraphReferences(
236
+ graphName: string,
237
+ nodes: Record<string, any>,
238
+ entryNodes: string[]
239
+ ): void {
240
+ const nodeIds = new Set(Object.keys(nodes))
241
+
242
+ for (const entryId of entryNodes) {
243
+ if (!nodeIds.has(entryId)) {
244
+ throw new Error(
245
+ `Workflow graph '${graphName}': entry node '${entryId}' is not defined`
246
+ )
247
+ }
248
+ }
249
+
250
+ for (const [nodeId, node] of Object.entries(nodes)) {
251
+ const inputRefs = extractReferencedNodeIds(node.input).filter(
252
+ (id) => !IGNORED_REFS.has(id)
253
+ )
254
+ for (const refId of inputRefs) {
255
+ if (!nodeIds.has(refId)) {
256
+ throw new Error(
257
+ `Workflow graph '${graphName}': node '${nodeId}' references unknown node '${refId}' in input`
258
+ )
259
+ }
260
+ }
261
+
262
+ const nextTargets = normalizeNodeTargets(node.next)
263
+ for (const nextId of nextTargets) {
264
+ if (!nodeIds.has(nextId)) {
265
+ throw new Error(
266
+ `Workflow graph '${graphName}': node '${nodeId}' routes to unknown node '${nextId}'`
267
+ )
268
+ }
269
+ }
270
+
271
+ const errorTargets = normalizeNodeTargets(node.onError)
272
+ for (const errorId of errorTargets) {
273
+ if (!nodeIds.has(errorId)) {
274
+ throw new Error(
275
+ `Workflow graph '${graphName}': node '${nodeId}' onError targets unknown node '${errorId}'`
276
+ )
277
+ }
278
+ }
279
+ }
280
+ }
281
+
282
+ function areDependenciesSatisfied(
283
+ node: { input?: Record<string, unknown> },
284
+ completedNodeIds: Set<string>
285
+ ): boolean {
286
+ const deps = extractReferencedNodeIds(node.input).filter(
287
+ (id) => !IGNORED_REFS.has(id)
288
+ )
289
+ return deps.every((dep) => completedNodeIds.has(dep))
230
290
  }
231
291
 
232
- /**
233
- * Queue a graph node for execution
234
- */
235
292
  async function queueGraphNode(
236
293
  workflowService: PikkuWorkflowService,
237
294
  runId: string,
@@ -240,105 +297,137 @@ async function queueGraphNode(
240
297
  rpcName: string,
241
298
  input: any
242
299
  ): Promise<void> {
243
- // Step name convention: node:<nodeId>
244
- // Graph name is stored as the workflow name on the run
245
- await workflowService.insertStepState(
246
- runId,
247
- `node:${nodeId}`,
248
- rpcName,
249
- input,
250
- { retries: 3 }
251
- )
252
- await workflowService.resumeWorkflow(runId)
300
+ await workflowService.insertStepState(runId, nodeId, rpcName, input, {
301
+ retries: 0,
302
+ })
303
+ await workflowService.queueStepWorker(runId, nodeId, rpcName, input)
253
304
  }
254
305
 
255
- /**
256
- * Continue graph execution
257
- * Non-blocking - finds pending nodes, resolves inputs, queues them for execution
258
- */
259
306
  export async function continueGraph(
260
307
  workflowService: PikkuWorkflowService,
261
308
  runId: string,
262
- graphName: string
309
+ graphName: string,
310
+ overrideMeta?: WorkflowRuntimeMeta
263
311
  ): Promise<void> {
264
- const definition = getWorkflowGraph(graphName)
265
- if (!definition) {
266
- throw new Error(`Workflow graph '${graphName}' not found`)
312
+ const meta = overrideMeta ?? getWorkflowMeta(graphName)
313
+ if (!meta?.nodes) {
314
+ throw new Error(`Workflow graph meta '${graphName}' not found`)
267
315
  }
268
316
 
269
- const graph = definition.graph
317
+ const nodes = meta.nodes
318
+ validateGraphReferences(graphName, nodes, meta.entryNodeIds ?? [])
319
+
320
+ const {
321
+ completedNodeIds: rawCompleted,
322
+ failedNodeIds: rawFailed,
323
+ branchKeys: rawBranch,
324
+ } = await workflowService.getCompletedGraphState(runId)
325
+ const completedNodeIds = remapStepNamesToNodeIds(
326
+ rawCompleted,
327
+ nodes,
328
+ graphName
329
+ )
330
+ const completedNodeIdSet = new Set(completedNodeIds)
331
+ const failedNodeIds = remapStepNamesToNodeIds(rawFailed, nodes, graphName)
332
+ const branchKeys = remapBranchKeys(rawBranch, nodes, graphName)
333
+
334
+ if (failedNodeIds.length > 0) {
335
+ const failedNode = failedNodeIds[0]!
336
+ await workflowService.updateRunStatus(runId, 'failed', undefined, {
337
+ message: `Graph node '${failedNode}' failed after exhausting retries`,
338
+ stack: '',
339
+ code: 'GRAPH_NODE_FAILED',
340
+ })
341
+ return
342
+ }
270
343
 
271
- // Get completed node IDs + branch keys (lightweight, no results)
272
- const { completedNodeIds, branchKeys } =
273
- await workflowService.getCompletedGraphState(runId)
344
+ const currentRun = await workflowService.getRun(runId)
345
+ if (currentRun?.status === 'suspended') {
346
+ return
347
+ }
274
348
 
275
- // Find candidate next nodes from completed nodes
276
- const candidateNodes: string[] = []
349
+ const candidateNodes = new Set<string>()
277
350
 
278
351
  for (const nodeId of completedNodeIds) {
279
- const node = graph[nodeId]
352
+ const node = nodes[nodeId]
280
353
  if (!node?.next) continue
281
354
 
282
355
  const nextNodes = resolveNextFromConfig(node.next, branchKeys[nodeId])
283
- candidateNodes.push(...nextNodes)
356
+ for (const nextNode of nextNodes) {
357
+ candidateNodes.add(nextNode)
358
+ }
359
+ }
360
+
361
+ for (const entryId of meta.entryNodeIds ?? []) {
362
+ candidateNodes.add(entryId)
284
363
  }
285
364
 
286
- if (candidateNodes.length === 0 && completedNodeIds.length > 0) {
287
- // No more nodes to run - graph complete
365
+ if (candidateNodes.size === 0 && completedNodeIds.length > 0) {
288
366
  await workflowService.updateRunStatus(runId, 'completed')
289
367
  return
290
368
  }
291
369
 
292
- // Filter to only nodes that don't have a step yet
293
- const nodesToQueue = await workflowService.getNodesWithoutSteps(
294
- runId,
295
- candidateNodes
296
- )
370
+ const unstartedNodes = await workflowService.getNodesWithoutSteps(runId, [
371
+ ...candidateNodes,
372
+ ])
297
373
 
298
- // Queue each node for execution
299
- for (const nodeId of nodesToQueue) {
300
- const node = graph[nodeId]
301
- if (!node) continue
374
+ const nodesToQueue = unstartedNodes.filter((nodeId) => {
375
+ const node = nodes[nodeId]
376
+ return node && areDependenciesSatisfied(node, completedNodeIdSet)
377
+ })
302
378
 
303
- // Evaluate input callback to get the mapping
304
- const inputMapping = evaluateInputCallback(node)
379
+ if (nodesToQueue.length === 0) {
380
+ const allRpcNodes = Object.entries(nodes)
381
+ .filter(([_, n]) => n.rpcName)
382
+ .map(([id]) => id)
383
+ const allRpcCompleted = allRpcNodes.every((id) =>
384
+ completedNodeIdSet.has(id)
385
+ )
386
+ if (allRpcCompleted) {
387
+ await workflowService.updateRunStatus(runId, 'completed')
388
+ }
389
+ return
390
+ }
391
+
392
+ const run = await workflowService.getRun(runId)
393
+ const triggerInput = run?.input
394
+
395
+ for (const nodeId of nodesToQueue) {
396
+ const node = nodes[nodeId]
397
+ if (!node?.rpcName) continue
305
398
 
306
- // Only fetch results for nodes referenced in this node's input
307
- const referencedNodeIds = extractReferencedNodeIds(inputMapping)
308
- const nodeResults = await workflowService.getNodeResults(
399
+ const referencedNodeIds = extractReferencedNodeIds(node.input).filter(
400
+ (id) => !IGNORED_REFS.has(id)
401
+ )
402
+ const fetchedResults = await workflowService.getNodeResults(
309
403
  runId,
310
404
  referencedNodeIds
311
405
  )
312
406
 
313
- const resolvedInput = resolveInputMapping(inputMapping, nodeResults)
314
- const rpcName = getRpcName(node)
407
+ const nodeResults = { trigger: triggerInput, ...fetchedResults }
408
+ const resolvedInput = resolveSerializedInput(node.input, nodeResults)
315
409
 
316
410
  await queueGraphNode(
317
411
  workflowService,
318
412
  runId,
319
413
  graphName,
320
414
  nodeId,
321
- rpcName,
415
+ node.rpcName,
322
416
  resolvedInput
323
417
  )
324
418
  }
325
419
  }
326
420
 
327
- /**
328
- * Execute a graph step with wire context.
329
- * Called by the step worker when executing graph nodes.
330
- */
331
421
  export async function executeGraphStep(
332
422
  workflowService: PikkuWorkflowService,
333
423
  rpcService: any,
334
424
  runId: string,
335
425
  stepId: string,
336
- stepName: string,
426
+ nodeId: string,
337
427
  rpcName: string,
338
428
  data: any,
339
429
  graphName: string
340
430
  ): Promise<any> {
341
- const nodeId = stepName.replace(/^node:/, '')
342
431
  const wireState: GraphWireState = {}
343
432
  const graphWire: PikkuGraphWire = {
344
433
  runId,
@@ -353,54 +442,50 @@ export async function executeGraphStep(
353
442
  }
354
443
 
355
444
  try {
356
- // Execute the RPC with graph wire context
357
445
  const result = await rpcService.rpcWithWire(rpcName, data, {
358
446
  graph: graphWire,
359
447
  })
360
448
 
361
- // If branch was called, store the branch key
362
449
  if (wireState.branchKey) {
363
450
  await workflowService.setBranchTaken(stepId, wireState.branchKey)
364
451
  }
365
452
 
366
453
  return result
367
454
  } catch (error) {
368
- // Check if this node has an onError handler
369
- const definition = getWorkflowGraph(graphName)
370
- if (definition) {
371
- const node = definition.graph[nodeId]
455
+ if (error instanceof RPCNotFoundError) {
456
+ await workflowService.updateRunStatus(runId, 'suspended', undefined, {
457
+ message: `RPC '${rpcName}' not found. Deploy the missing function and resume.`,
458
+ code: 'RPC_NOT_FOUND',
459
+ })
460
+ throw error
461
+ }
462
+ const meta = getWorkflowMeta(graphName)
463
+ if (meta?.nodes) {
464
+ const node = meta.nodes[nodeId]
372
465
  if (node?.onError) {
373
- // Route to error handler nodes
374
466
  const errorNodes = Array.isArray(node.onError)
375
467
  ? node.onError
376
468
  : [node.onError]
377
469
  for (const errorNodeId of errorNodes) {
378
- const errorNode = definition.graph[errorNodeId]
470
+ const errorNode = meta.nodes[errorNodeId]
379
471
  if (errorNode) {
380
- const errorRpcName = getRpcName(errorNode)
381
- // Queue error node with the error as input
382
472
  await queueGraphNode(
383
473
  workflowService,
384
474
  runId,
385
475
  graphName,
386
476
  errorNodeId,
387
- errorRpcName,
477
+ errorNode.rpcName,
388
478
  { error: { message: (error as Error).message } }
389
479
  )
390
480
  }
391
481
  }
392
- // Don't rethrow - error was handled by routing to error nodes
393
- return
482
+ throw error
394
483
  }
395
484
  }
396
- // No error handler - rethrow
397
485
  throw error
398
486
  }
399
487
  }
400
488
 
401
- /**
402
- * Handle node completion - re-triggers graph continuation
403
- */
404
489
  export async function onGraphNodeComplete(
405
490
  workflowService: PikkuWorkflowService,
406
491
  runId: string,
@@ -409,9 +494,15 @@ export async function onGraphNodeComplete(
409
494
  await continueGraph(workflowService, runId, graphName)
410
495
  }
411
496
 
412
- /**
413
- * Execute a graph node inline (without queue)
414
- */
497
+ export async function runFromMeta(
498
+ workflowService: PikkuWorkflowService,
499
+ runId: string,
500
+ meta: WorkflowRuntimeMeta,
501
+ _rpcService: any
502
+ ): Promise<void> {
503
+ await continueGraph(workflowService, runId, meta.name, meta)
504
+ }
505
+
415
506
  async function executeGraphNodeInline(
416
507
  workflowService: PikkuWorkflowService,
417
508
  rpcService: any,
@@ -419,18 +510,16 @@ async function executeGraphNodeInline(
419
510
  graphName: string,
420
511
  nodeId: string,
421
512
  input: any,
422
- graph: Record<string, GraphNodeConfig>
513
+ nodes: Record<string, any>
423
514
  ): Promise<void> {
424
- const node = graph[nodeId]
515
+ const node = nodes[nodeId]
425
516
  if (!node) return
426
517
 
427
- const rpcName = getRpcName(node)
428
- const stepName = `node:${nodeId}`
518
+ const rpcName = node.rpcName
429
519
 
430
- // Insert step state
431
520
  const stepState = await workflowService.insertStepState(
432
521
  runId,
433
- stepName,
522
+ nodeId,
434
523
  rpcName,
435
524
  input,
436
525
  { retries: 3 }
@@ -438,7 +527,6 @@ async function executeGraphNodeInline(
438
527
 
439
528
  await workflowService.setStepRunning(stepState.stepId)
440
529
 
441
- // Execute with graph wire context
442
530
  const wireState: GraphWireState = {}
443
531
  const graphWire: PikkuGraphWire = {
444
532
  runId,
@@ -457,7 +545,6 @@ async function executeGraphNodeInline(
457
545
  graph: graphWire,
458
546
  })
459
547
 
460
- // If branch was called, store the branch key
461
548
  if (wireState.branchKey) {
462
549
  await workflowService.setBranchTaken(
463
550
  stepState.stepId,
@@ -467,109 +554,125 @@ async function executeGraphNodeInline(
467
554
 
468
555
  await workflowService.setStepResult(stepState.stepId, result)
469
556
  } catch (error) {
557
+ if (error instanceof RPCNotFoundError) {
558
+ await workflowService.setStepError(stepState.stepId, error as Error)
559
+ await workflowService.updateRunStatus(runId, 'suspended', undefined, {
560
+ message: `RPC '${rpcName}' not found. Deploy the missing function and resume.`,
561
+ code: 'RPC_NOT_FOUND',
562
+ })
563
+ return
564
+ }
470
565
  await workflowService.setStepError(stepState.stepId, error as Error)
471
566
 
472
- // Check if this node has an onError handler
473
- const definition = getWorkflowGraph(graphName)
474
- if (definition) {
475
- const node = definition.graph[nodeId]
476
- if (node?.onError) {
477
- // Route to error handler nodes (inline)
478
- const errorNodes = Array.isArray(node.onError)
479
- ? node.onError
480
- : [node.onError]
481
- await Promise.all(
482
- errorNodes.map((errorNodeId) =>
483
- executeGraphNodeInline(
484
- workflowService,
485
- rpcService,
486
- runId,
487
- graphName,
488
- errorNodeId,
489
- { error: { message: (error as Error).message } },
490
- graph
491
- )
567
+ if (node?.onError) {
568
+ const errorNodes = Array.isArray(node.onError)
569
+ ? node.onError
570
+ : [node.onError]
571
+ await Promise.all(
572
+ errorNodes.map((errorNodeId: string) =>
573
+ executeGraphNodeInline(
574
+ workflowService,
575
+ rpcService,
576
+ runId,
577
+ graphName,
578
+ errorNodeId,
579
+ { error: { message: (error as Error).message } },
580
+ nodes
492
581
  )
493
582
  )
494
- return
495
- }
583
+ )
584
+ return
496
585
  }
497
- // No error handler - rethrow
498
586
  throw error
499
587
  }
500
588
  }
501
589
 
502
- /**
503
- * Continue graph execution inline (without queue)
504
- * Executes nodes in parallel where possible using Promise.all
505
- */
506
590
  async function continueGraphInline(
507
591
  workflowService: PikkuWorkflowService,
508
592
  rpcService: any,
509
593
  runId: string,
510
594
  graphName: string,
511
- graph: Record<string, GraphNodeConfig>,
512
- triggerInput: any
595
+ nodes: Record<string, any>,
596
+ triggerInput: any,
597
+ entryNodeIds: string[]
513
598
  ): Promise<void> {
514
599
  while (true) {
515
- // Get completed node IDs + branch keys (lightweight, no results)
516
- const { completedNodeIds, branchKeys } =
517
- await workflowService.getCompletedGraphState(runId)
600
+ const {
601
+ completedNodeIds: rawCompleted,
602
+ failedNodeIds: rawFailed,
603
+ branchKeys: rawBranch,
604
+ } = await workflowService.getCompletedGraphState(runId)
605
+ const completedNodeIds = remapStepNamesToNodeIds(
606
+ rawCompleted,
607
+ nodes,
608
+ graphName
609
+ )
610
+ const completedNodeIdSet = new Set(completedNodeIds)
611
+ const failedNodeIds = remapStepNamesToNodeIds(rawFailed, nodes, graphName)
612
+ const branchKeys = remapBranchKeys(rawBranch, nodes, graphName)
613
+
614
+ if (failedNodeIds.length > 0) {
615
+ const failedNode = failedNodeIds[0]!
616
+ await workflowService.updateRunStatus(runId, 'failed', undefined, {
617
+ message: `Graph node '${failedNode}' failed after exhausting retries`,
618
+ stack: '',
619
+ code: 'GRAPH_NODE_FAILED',
620
+ })
621
+ return
622
+ }
518
623
 
519
- // Find candidate next nodes from completed nodes
520
- const candidateNodes: string[] = []
624
+ const candidateNodes = new Set<string>()
521
625
 
522
626
  for (const nodeId of completedNodeIds) {
523
- const node = graph[nodeId]
627
+ const node = nodes[nodeId]
524
628
  if (!node?.next) continue
525
629
 
526
630
  const nextNodes = resolveNextFromConfig(node.next, branchKeys[nodeId])
527
- candidateNodes.push(...nextNodes)
631
+ for (const nextNode of nextNodes) {
632
+ candidateNodes.add(nextNode)
633
+ }
634
+ }
635
+
636
+ for (const entryId of entryNodeIds) {
637
+ candidateNodes.add(entryId)
528
638
  }
529
639
 
530
- if (candidateNodes.length === 0 && completedNodeIds.length > 0) {
531
- // No more nodes to run - graph complete
640
+ if (candidateNodes.size === 0 && completedNodeIds.length > 0) {
532
641
  await workflowService.updateRunStatus(runId, 'completed')
533
642
  return
534
643
  }
535
644
 
536
- // Filter to only nodes that don't have a step yet
537
- const nodesToExecute = await workflowService.getNodesWithoutSteps(
538
- runId,
539
- candidateNodes
540
- )
645
+ const unstartedNodes = await workflowService.getNodesWithoutSteps(runId, [
646
+ ...candidateNodes,
647
+ ])
648
+
649
+ const nodesToExecute = unstartedNodes.filter((nodeId) => {
650
+ const node = nodes[nodeId]
651
+ return node && areDependenciesSatisfied(node, completedNodeIdSet)
652
+ })
541
653
 
542
654
  if (nodesToExecute.length === 0) {
543
- // No more nodes to execute - if we've completed at least one node, we're done
544
- // This handles branching where only some paths are taken
545
- if (completedNodeIds.length > 0) {
655
+ if (completedNodeIds.length > 0 && unstartedNodes.length === 0) {
546
656
  await workflowService.updateRunStatus(runId, 'completed')
547
657
  }
548
658
  return
549
659
  }
550
660
 
551
- // Execute all nodes in parallel
552
661
  await Promise.all(
553
662
  nodesToExecute.map(async (nodeId) => {
554
- const node = graph[nodeId]
555
- if (!node) return
663
+ const node = nodes[nodeId]
664
+ if (!node?.rpcName) return
556
665
 
557
- // Evaluate input callback to get the mapping
558
- const inputMapping = evaluateInputCallback(node)
559
-
560
- // Only fetch results for nodes referenced in this node's input (excluding trigger)
561
- const referencedNodeIds = extractReferencedNodeIds(inputMapping).filter(
562
- (id) => id !== 'trigger'
666
+ const referencedNodeIds = extractReferencedNodeIds(node.input).filter(
667
+ (id) => !IGNORED_REFS.has(id)
563
668
  )
564
669
  const fetchedResults = await workflowService.getNodeResults(
565
670
  runId,
566
671
  referencedNodeIds
567
672
  )
568
673
 
569
- // Merge fetched results with trigger input
570
674
  const nodeResults = { trigger: triggerInput, ...fetchedResults }
571
-
572
- const resolvedInput = resolveInputMapping(inputMapping, nodeResults)
675
+ const resolvedInput = resolveSerializedInput(node.input, nodeResults)
573
676
 
574
677
  await executeGraphNodeInline(
575
678
  workflowService,
@@ -578,117 +681,136 @@ async function continueGraphInline(
578
681
  graphName,
579
682
  nodeId,
580
683
  resolvedInput,
581
- graph
684
+ nodes
582
685
  )
583
686
  })
584
687
  )
585
688
  }
586
689
  }
587
690
 
588
- /**
589
- * Start a workflow graph execution
590
- * @param startNode - Optional starting node ID (from wire config). If not provided, uses wires.api from graph definition.
591
- */
592
691
  export async function runWorkflowGraph(
593
692
  workflowService: PikkuWorkflowService,
594
693
  graphName: string,
595
694
  triggerInput: any,
596
695
  rpcService?: any,
597
696
  inline?: boolean,
598
- startNode?: string
697
+ startNode?: string,
698
+ wire?: WorkflowRunWire
599
699
  ): Promise<{ runId: string }> {
600
- const definition = getWorkflowGraph(graphName)
601
- if (!definition) {
700
+ const meta = getWorkflowMeta(graphName)
701
+ if (!meta?.nodes) {
602
702
  throw new Error(`Workflow graph '${graphName}' not found`)
603
703
  }
604
704
 
605
- // Use startNode from caller if provided, otherwise use wires.api from graph definition
606
- const apiStartNode = startNode ?? definition.wires?.api
607
- const entryNodes: string[] = apiStartNode ? [apiStartNode] : []
705
+ const nodes = meta.nodes
706
+ const entryNodes: string[] = startNode
707
+ ? [startNode]
708
+ : (meta.entryNodeIds ?? [])
709
+ validateGraphReferences(graphName, nodes, entryNodes)
608
710
 
609
711
  if (entryNodes.length === 0) {
610
712
  throw new Error(
611
- `Workflow graph '${graphName}' has no wires.api defined and no startNode was provided`
713
+ `Workflow graph '${graphName}': no entry nodes found in meta or startNode`
714
+ )
715
+ }
716
+
717
+ const readyEntryNodes = entryNodes.filter((nodeId) => {
718
+ const node = nodes[nodeId]
719
+ return node && areDependenciesSatisfied(node, new Set())
720
+ })
721
+
722
+ if (readyEntryNodes.length === 0) {
723
+ throw new Error(
724
+ `Workflow graph '${graphName}': no entry nodes have satisfied dependencies`
612
725
  )
613
726
  }
614
727
 
615
- const graph = definition.graph
616
- const runId = await workflowService.createRun(graphName, triggerInput, inline)
728
+ if (!meta.graphHash) {
729
+ throw new Error(`Workflow graph '${graphName}': missing graphHash in meta`)
730
+ }
731
+
732
+ const runId = await workflowService.createRun(
733
+ graphName,
734
+ triggerInput,
735
+ inline ?? false,
736
+ meta.graphHash,
737
+ wire ?? { type: 'unknown' }
738
+ )
617
739
 
618
- // Register as inline for fast lookup
619
740
  if (inline) {
620
741
  workflowService.registerInlineRun(runId)
621
742
  }
622
743
 
623
- // Create nodeResults with trigger for resolving entry node inputs
624
744
  const triggerNodeResults = { trigger: triggerInput }
625
745
 
626
- try {
627
- if (inline && rpcService) {
628
- // Inline mode - execute entry nodes in parallel
629
- await Promise.all(
630
- entryNodes.map(async (nodeId) => {
631
- const node = graph[nodeId]
632
- if (!node) return
633
-
634
- // Evaluate and resolve entry node input
635
- const inputMapping = evaluateInputCallback(node)
636
- const resolvedInput =
637
- Object.keys(inputMapping).length > 0
638
- ? resolveInputMapping(inputMapping, triggerNodeResults)
639
- : triggerInput
640
-
641
- await executeGraphNodeInline(
642
- workflowService,
643
- rpcService,
644
- runId,
645
- graphName,
646
- nodeId,
647
- resolvedInput,
648
- graph
649
- )
650
- })
651
- )
746
+ if (inline && rpcService) {
747
+ const executeInline = async () => {
748
+ try {
749
+ await Promise.all(
750
+ readyEntryNodes.map(async (nodeId) => {
751
+ const node = nodes[nodeId]
752
+ if (!node?.rpcName) return
652
753
 
653
- // Continue executing remaining nodes inline
654
- await continueGraphInline(
655
- workflowService,
656
- rpcService,
657
- runId,
658
- graphName,
659
- graph,
660
- triggerInput
661
- )
662
- } else {
663
- // Queue-based mode
664
- for (const nodeId of entryNodes) {
665
- const node = graph[nodeId]
666
- if (!node) continue
667
-
668
- // Evaluate and resolve entry node input
669
- const inputMapping = evaluateInputCallback(node)
670
- const resolvedInput =
671
- Object.keys(inputMapping).length > 0
672
- ? resolveInputMapping(inputMapping, triggerNodeResults)
673
- : triggerInput
674
-
675
- const rpcName = getRpcName(node)
676
- await queueGraphNode(
754
+ const resolvedInput =
755
+ node.input && Object.keys(node.input).length > 0
756
+ ? resolveSerializedInput(node.input, triggerNodeResults)
757
+ : triggerInput
758
+
759
+ await executeGraphNodeInline(
760
+ workflowService,
761
+ rpcService,
762
+ runId,
763
+ graphName,
764
+ nodeId,
765
+ resolvedInput,
766
+ nodes
767
+ )
768
+ })
769
+ )
770
+
771
+ await continueGraphInline(
677
772
  workflowService,
773
+ rpcService,
678
774
  runId,
679
775
  graphName,
680
- nodeId,
681
- rpcName,
682
- resolvedInput
776
+ nodes,
777
+ triggerInput,
778
+ entryNodes
683
779
  )
780
+ } catch (error) {
781
+ await workflowService.updateRunStatus(runId, 'failed', undefined, {
782
+ message: (error as Error).message,
783
+ stack: (error as Error).stack || '',
784
+ code: 'GRAPH_NODE_FAILED',
785
+ })
786
+ } finally {
787
+ workflowService.unregisterInlineRun(runId)
684
788
  }
685
789
  }
686
-
687
- return { runId }
688
- } finally {
689
- // Clean up inline tracking
790
+ await executeInline()
791
+ } else {
792
+ for (const nodeId of readyEntryNodes) {
793
+ const node = nodes[nodeId]
794
+ if (!node?.rpcName) continue
795
+
796
+ const resolvedInput =
797
+ node.input && Object.keys(node.input).length > 0
798
+ ? resolveSerializedInput(node.input, triggerNodeResults)
799
+ : triggerInput
800
+
801
+ await queueGraphNode(
802
+ workflowService,
803
+ runId,
804
+ graphName,
805
+ nodeId,
806
+ node.rpcName,
807
+ resolvedInput
808
+ )
809
+ }
690
810
  if (inline) {
691
811
  workflowService.unregisterInlineRun(runId)
692
812
  }
693
813
  }
814
+
815
+ return { runId }
694
816
  }