@pikku/core 0.12.79 → 0.12.82

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 (479) hide show
  1. package/CHANGELOG.md +473 -0
  2. package/dist/dev/hot-reload.js +1 -6
  3. package/dist/ecosystem.d.ts +27 -0
  4. package/dist/ecosystem.js +26 -0
  5. package/dist/errors/error-handler.js +4 -2
  6. package/dist/errors/index.d.ts +1 -1
  7. package/dist/errors/index.js +1 -1
  8. package/dist/function/function-runner.js +22 -47
  9. package/dist/function/functions.types.d.ts +9 -5
  10. package/dist/function/index.d.ts +1 -1
  11. package/dist/index.d.ts +14 -19
  12. package/dist/index.js +6 -11
  13. package/dist/middleware/auth-cookie.d.ts +0 -4
  14. package/dist/middleware/auth-cookie.js +38 -2
  15. package/dist/middleware/cors.js +1 -0
  16. package/dist/middleware/index.d.ts +1 -1
  17. package/dist/middleware/index.js +1 -1
  18. package/dist/middleware/remote-auth.js +14 -3
  19. package/dist/permissions.d.ts +8 -10
  20. package/dist/permissions.js +0 -11
  21. package/dist/pikku-state.js +16 -5
  22. package/dist/schema.js +35 -1
  23. package/dist/services/ai-agent-runner-service.d.ts +7 -0
  24. package/dist/services/ai-run-state-service.d.ts +17 -1
  25. package/dist/services/in-memory-ai-run-state-service.d.ts +6 -2
  26. package/dist/services/in-memory-ai-run-state-service.js +11 -1
  27. package/dist/services/in-memory-workflow-service.js +2 -0
  28. package/dist/services/index.d.ts +15 -15
  29. package/dist/services/index.js +5 -5
  30. package/dist/services/local-content-request-handler.d.ts +21 -0
  31. package/dist/services/local-content-request-handler.js +72 -53
  32. package/dist/services/local-content.d.ts +6 -0
  33. package/dist/services/local-content.js +14 -1
  34. package/dist/services/scoped-credential-service.d.ts +21 -0
  35. package/dist/services/scoped-credential-service.js +53 -0
  36. package/dist/services/workflow-service.d.ts +4 -2
  37. package/dist/testing/service-tests/agent-run-service-tests.d.ts +10 -0
  38. package/dist/testing/service-tests/agent-run-service-tests.js +72 -0
  39. package/dist/testing/service-tests/ai-storage-service-tests.d.ts +3 -0
  40. package/dist/testing/service-tests/ai-storage-service-tests.js +302 -0
  41. package/dist/testing/service-tests/channel-store-tests.d.ts +3 -0
  42. package/dist/testing/service-tests/channel-store-tests.js +72 -0
  43. package/dist/testing/service-tests/credential-service-tests.d.ts +3 -0
  44. package/dist/testing/service-tests/credential-service-tests.js +109 -0
  45. package/dist/testing/service-tests/deployment-service-tests.d.ts +3 -0
  46. package/dist/testing/service-tests/deployment-service-tests.js +21 -0
  47. package/dist/testing/service-tests/event-hub-store-tests.d.ts +3 -0
  48. package/dist/testing/service-tests/event-hub-store-tests.js +34 -0
  49. package/dist/testing/service-tests/secret-service-tests.d.ts +3 -0
  50. package/dist/testing/service-tests/secret-service-tests.js +80 -0
  51. package/dist/testing/service-tests/session-store-tests.d.ts +3 -0
  52. package/dist/testing/service-tests/session-store-tests.js +43 -0
  53. package/dist/testing/service-tests/workflow-run-service-tests.d.ts +3 -0
  54. package/dist/testing/service-tests/workflow-run-service-tests.js +42 -0
  55. package/dist/testing/service-tests/workflow-service-tests.d.ts +3 -0
  56. package/dist/testing/service-tests/workflow-service-tests.js +150 -0
  57. package/dist/testing/service-tests.d.ts +6 -0
  58. package/dist/testing/service-tests.js +26 -791
  59. package/dist/types/core.types.d.ts +11 -3
  60. package/dist/types/state.types.d.ts +14 -1
  61. package/dist/wirings/actor-flow/index.d.ts +1 -1
  62. package/dist/wirings/actor-flow/run-conversation.js +1 -6
  63. package/dist/wirings/ai-agent/agent-rpc.d.ts +15 -0
  64. package/dist/wirings/ai-agent/agent-rpc.js +53 -0
  65. package/dist/wirings/ai-agent/ai-agent-agui.js +1 -5
  66. package/dist/wirings/ai-agent/ai-agent-finalize.d.ts +58 -0
  67. package/dist/wirings/ai-agent/ai-agent-finalize.js +138 -0
  68. package/dist/wirings/ai-agent/ai-agent-interrupt.js +1 -0
  69. package/dist/wirings/ai-agent/ai-agent-memory.d.ts +2 -8
  70. package/dist/wirings/ai-agent/ai-agent-memory.js +37 -18
  71. package/dist/wirings/ai-agent/ai-agent-model-config.d.ts +7 -0
  72. package/dist/wirings/ai-agent/ai-agent-model-config.js +44 -1
  73. package/dist/wirings/ai-agent/ai-agent-prepare.js +5 -9
  74. package/dist/wirings/ai-agent/ai-agent-runner.js +89 -147
  75. package/dist/wirings/ai-agent/ai-agent-stream.js +109 -97
  76. package/dist/wirings/ai-agent/ai-agent-turn.d.ts +57 -0
  77. package/dist/wirings/ai-agent/ai-agent-turn.js +82 -0
  78. package/dist/wirings/ai-agent/ai-agent.types.d.ts +47 -2
  79. package/dist/wirings/ai-agent/index.d.ts +8 -7
  80. package/dist/wirings/ai-agent/index.js +5 -4
  81. package/dist/wirings/ai-agent/voice-input.js +1 -6
  82. package/dist/wirings/ai-agent/voice-output.js +2 -12
  83. package/dist/wirings/ai-scorer/ai-scorer-grade.d.ts +26 -0
  84. package/dist/wirings/ai-scorer/ai-scorer-grade.js +33 -0
  85. package/dist/wirings/ai-scorer/ai-scorer-judge.d.ts +17 -0
  86. package/dist/wirings/ai-scorer/ai-scorer-judge.js +92 -0
  87. package/dist/wirings/ai-scorer/ai-scorer-live.d.ts +15 -0
  88. package/dist/wirings/ai-scorer/ai-scorer-live.js +38 -0
  89. package/dist/wirings/ai-scorer/ai-scorer-registry.d.ts +18 -0
  90. package/dist/wirings/ai-scorer/ai-scorer-registry.js +46 -0
  91. package/dist/wirings/ai-scorer/ai-scorer-sampling.d.ts +8 -0
  92. package/dist/wirings/ai-scorer/ai-scorer-sampling.js +31 -0
  93. package/dist/wirings/ai-scorer/ai-scorer-snapshots.d.ts +10 -0
  94. package/dist/wirings/ai-scorer/ai-scorer-snapshots.js +40 -0
  95. package/dist/wirings/ai-scorer/ai-scorer-worker.d.ts +15 -0
  96. package/dist/wirings/ai-scorer/ai-scorer-worker.js +58 -0
  97. package/dist/wirings/ai-scorer/ai-scorer.d.ts +39 -0
  98. package/dist/wirings/ai-scorer/ai-scorer.js +40 -0
  99. package/dist/wirings/ai-scorer/ai-scorer.types.d.ts +90 -0
  100. package/dist/wirings/ai-scorer/ai-scorer.types.js +4 -0
  101. package/dist/wirings/ai-scorer/index.d.ts +6 -0
  102. package/dist/wirings/ai-scorer/index.js +5 -0
  103. package/dist/wirings/channel/channel-common.js +1 -0
  104. package/dist/wirings/channel/channel-handler.js +3 -5
  105. package/dist/wirings/channel/channel-rpc-service.d.ts +0 -6
  106. package/dist/wirings/channel/channel-rpc-service.js +0 -8
  107. package/dist/wirings/channel/channel-rpc.types.d.ts +6 -0
  108. package/dist/wirings/channel/channel-rpc.types.js +8 -0
  109. package/dist/wirings/channel/channel-runner.d.ts +1 -3
  110. package/dist/wirings/channel/channel-runner.js +16 -8
  111. package/dist/wirings/channel/channel.types.d.ts +2 -0
  112. package/dist/wirings/channel/index.d.ts +5 -6
  113. package/dist/wirings/channel/index.js +3 -4
  114. package/dist/wirings/channel/local/local-channel-runner.js +8 -1
  115. package/dist/wirings/channel/pikku-abstract-channel-handler.js +1 -0
  116. package/dist/wirings/channel/serverless/serverless-channel-runner.js +3 -0
  117. package/dist/wirings/cli/channel/cli-channel-runner.js +2 -0
  118. package/dist/wirings/cli/channel/cli-raw-channel-runner.js +9 -1
  119. package/dist/wirings/cli/channel/index.d.ts +1 -2
  120. package/dist/wirings/cli/channel/index.js +0 -1
  121. package/dist/wirings/cli/cli-runner.js +17 -3
  122. package/dist/wirings/cli/cli.types.d.ts +0 -8
  123. package/dist/wirings/cli/command-parser.js +13 -0
  124. package/dist/wirings/credential/index.d.ts +1 -1
  125. package/dist/wirings/gateway/gateway-runner.js +9 -2
  126. package/dist/wirings/gateway/index.d.ts +1 -1
  127. package/dist/wirings/http/http-routes.js +2 -0
  128. package/dist/wirings/http/http-runner.d.ts +0 -10
  129. package/dist/wirings/http/http-runner.js +14 -15
  130. package/dist/wirings/http/http.types.d.ts +0 -10
  131. package/dist/wirings/http/index.d.ts +2 -3
  132. package/dist/wirings/http/index.js +1 -1
  133. package/dist/wirings/mcp/index.d.ts +1 -1
  134. package/dist/wirings/mcp/mcp-runner.d.ts +15 -7
  135. package/dist/wirings/mcp/mcp-runner.js +18 -11
  136. package/dist/wirings/persona/index.d.ts +3 -4
  137. package/dist/wirings/persona/index.js +2 -3
  138. package/dist/wirings/queue/index.d.ts +1 -3
  139. package/dist/wirings/queue/index.js +1 -3
  140. package/dist/wirings/rpc/addon-runner.d.ts +4 -0
  141. package/dist/wirings/rpc/addon-runner.js +19 -3
  142. package/dist/wirings/rpc/rpc-runner.d.ts +8 -51
  143. package/dist/wirings/rpc/rpc-runner.js +55 -86
  144. package/dist/wirings/rpc/rpc-types.d.ts +7 -0
  145. package/dist/wirings/rpc/wire-addon.d.ts +13 -0
  146. package/dist/wirings/rpc/wire-addon.js +4 -0
  147. package/dist/wirings/scheduler/index.d.ts +1 -1
  148. package/dist/wirings/secret/validate-secret-definitions.js +2 -2
  149. package/dist/wirings/trigger/index.d.ts +1 -1
  150. package/dist/wirings/trigger/pikku-trigger-service.d.ts +0 -4
  151. package/dist/wirings/trigger/trigger-runner.js +1 -0
  152. package/dist/wirings/virtual-user/index.d.ts +5 -6
  153. package/dist/wirings/virtual-user/index.js +2 -4
  154. package/dist/wirings/virtual-user/run-virtual-user.js +11 -11
  155. package/dist/wirings/virtual-user/virtual-user-derive.js +4 -25
  156. package/dist/wirings/virtual-user/virtual-user-dispositions.js +1 -4
  157. package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +100 -3
  158. package/dist/wirings/workflow/feature.js +7 -4
  159. package/dist/wirings/workflow/graph/graph-runner.d.ts +1 -2
  160. package/dist/wirings/workflow/graph/graph-runner.js +8 -7
  161. package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +0 -4
  162. package/dist/wirings/workflow/index.d.ts +12 -7
  163. package/dist/wirings/workflow/index.js +7 -3
  164. package/dist/wirings/workflow/pikku-scenario-service.d.ts +17 -3
  165. package/dist/wirings/workflow/pikku-scenario-service.js +64 -10
  166. package/dist/wirings/workflow/pikku-workflow-service.d.ts +28 -147
  167. package/dist/wirings/workflow/pikku-workflow-service.js +85 -493
  168. package/dist/wirings/workflow/scenario-step.types.d.ts +8 -0
  169. package/dist/wirings/workflow/workflow-approval-audit.d.ts +16 -0
  170. package/dist/wirings/workflow/workflow-approval-audit.js +40 -0
  171. package/dist/wirings/workflow/workflow-approval-policy.d.ts +20 -0
  172. package/dist/wirings/workflow/workflow-approval-policy.js +48 -0
  173. package/dist/wirings/workflow/workflow-approval.d.ts +67 -0
  174. package/dist/wirings/workflow/workflow-approval.js +177 -0
  175. package/dist/wirings/workflow/workflow-constants.d.ts +26 -0
  176. package/dist/wirings/workflow/workflow-constants.js +35 -0
  177. package/dist/wirings/workflow/workflow-errors.d.ts +58 -0
  178. package/dist/wirings/workflow/workflow-errors.js +112 -0
  179. package/dist/wirings/workflow/workflow-meta-resolver.d.ts +11 -0
  180. package/dist/wirings/workflow/workflow-meta-resolver.js +31 -0
  181. package/dist/wirings/workflow/workflow-queue-routing.d.ts +8 -0
  182. package/dist/wirings/workflow/workflow-queue-routing.js +38 -0
  183. package/dist/wirings/workflow/workflow-queue-wiring.d.ts +20 -0
  184. package/dist/wirings/workflow/workflow-queue-wiring.js +79 -0
  185. package/dist/wirings/workflow/workflow-recovery.d.ts +68 -0
  186. package/dist/wirings/workflow/workflow-recovery.js +101 -0
  187. package/dist/wirings/workflow/workflow-run-engine.types.d.ts +54 -0
  188. package/dist/wirings/workflow/workflow-run-ownership.d.ts +17 -0
  189. package/dist/wirings/workflow/workflow-run-ownership.js +30 -0
  190. package/dist/wirings/workflow/workflow-suspend.d.ts +12 -0
  191. package/dist/wirings/workflow/workflow-suspend.js +33 -0
  192. package/dist/wirings/workflow/workflow.types.d.ts +8 -1
  193. package/knowledge/decisions/internals/a-non-streaming-agent-run-registers-with-airunstate-too.md +22 -0
  194. package/knowledge/decisions/internals/a-resumed-agent-turn-is-as-interruptible-as-the-first.md +20 -0
  195. package/knowledge/decisions/internals/a-scenario-step-template-is-offered-unfilled.md +21 -0
  196. package/knowledge/decisions/internals/a-virtual-user-decides-whether-to-trust-memory-once-per-turn.md +21 -0
  197. package/knowledge/decisions/internals/a-wall-clock-threshold-is-a-load-test-in-disguise.md +46 -0
  198. package/knowledge/decisions/internals/a-workflow-wire-is-built-from-the-run-not-from-the-rpc-service.md +45 -0
  199. package/knowledge/decisions/internals/addon-pikku-meta-ships-at-the-package-root-or-under-dist.md +32 -0
  200. package/knowledge/decisions/internals/agent-context-waits-for-a-tool-result-still-being-written.md +21 -0
  201. package/knowledge/decisions/internals/agent-speech-travels-as-a-custom-agui-event.md +22 -0
  202. package/knowledge/decisions/internals/an-addon-scope-root-loses-to-a-root-the-host-already-declares.md +39 -0
  203. package/knowledge/decisions/internals/an-agent-interrupt-is-not-a-failure.md +23 -0
  204. package/knowledge/decisions/internals/an-agent-run-owned-by-another-instance-says-so.md +25 -0
  205. package/knowledge/decisions/internals/an-agent-stream-send-must-return-the-inner-sends-promise.md +22 -0
  206. package/knowledge/decisions/internals/an-empty-text-part-is-omitted-from-an-agent-message.md +20 -0
  207. package/knowledge/decisions/internals/an-empty-transcript-is-not-recorded.md +22 -0
  208. package/knowledge/decisions/internals/an-unref-d-timer-cannot-be-awaited-under-node-test.md +66 -0
  209. package/knowledge/decisions/internals/channel-state-accessors-are-unsound-generics-that-every-implementation-asserts.md +33 -0
  210. package/knowledge/decisions/internals/gateway-listener-middleware-runs-without-an-rpc-on-the-wire.md +43 -0
  211. package/knowledge/decisions/internals/hot-reload-writes-into-the-function-map-captured-at-startup.md +26 -0
  212. package/knowledge/decisions/internals/index.md +42 -3
  213. package/knowledge/decisions/internals/only-exposed-functions-enter-a-virtual-user-catalogue.md +21 -0
  214. package/knowledge/decisions/internals/scenario-given-and-when-are-sugar-but-then-is-not.md +24 -0
  215. package/knowledge/decisions/internals/side-effects-are-an-allowlist-not-a-boolean.md +34 -0
  216. package/knowledge/decisions/internals/speech-synthesis-picks-a-voice-per-sentence-and-warns-once.md +24 -0
  217. package/knowledge/decisions/internals/the-actor-prompt-says-json-because-of-json-object-mode.md +22 -0
  218. package/knowledge/decisions/internals/the-agent-done-event-goes-through-the-middleware-and-is-awaited.md +26 -0
  219. package/knowledge/decisions/internals/the-api-report-pins-members-not-just-names.md +43 -0
  220. package/knowledge/decisions/internals/the-ecosystem-entry-point-carries-the-adapter-surface.md +58 -0
  221. package/knowledge/decisions/internals/the-middleware-resolution-cache-is-deliberately-unbounded.md +40 -0
  222. package/knowledge/decisions/internals/the-per-invocation-rpc-view-is-a-class.md +40 -0
  223. package/knowledge/decisions/internals/the-persona-runtime-is-exported-from-the-persona-entry-point.md +28 -0
  224. package/knowledge/decisions/internals/the-transcript-event-is-sent-ahead-of-the-run.md +25 -0
  225. package/knowledge/decisions/internals/the-virtual-user-catalogue-is-the-only-gate-on-what-may-be-called.md +21 -0
  226. package/knowledge/decisions/internals/the-worker-disposition-is-the-one-that-is-not-testing.md +22 -0
  227. package/knowledge/decisions/internals/thread-history-records-the-transcript-not-the-audio.md +25 -0
  228. package/knowledge/decisions/internals/validate-runs-checks-by-precondition.md +115 -0
  229. package/knowledge/decisions/internals/virtual-user-step-order-comes-from-insertion-order.md +21 -0
  230. package/knowledge/decisions/internals/voice-output-speaks-unless-voice-input-explicitly-says-otherwise.md +24 -0
  231. package/knowledge/decisions/internals/wiring-registries-erase-the-generics-their-wire-functions-capture.md +35 -0
  232. package/knowledge/decisions/security/a-dropped-audit-write-is-always-logged.md +4 -2
  233. package/knowledge/decisions/security/a-function-never-receives-the-secret-service.md +37 -0
  234. package/knowledge/decisions/security/a-graph-run-starts-at-an-entry-node-the-graph-declared.md +33 -0
  235. package/knowledge/decisions/security/a-permission-gets-a-wire-it-cannot-reply-on.md +33 -0
  236. package/knowledge/decisions/security/a-step-runs-the-function-the-workflow-dispatched-it-with.md +37 -0
  237. package/knowledge/decisions/security/a-virtual-user-is-never-offered-a-step-that-would-forge-its-own-oracle.md +28 -0
  238. package/knowledge/decisions/security/a-workflow-run-is-read-and-approved-by-its-owner.md +50 -0
  239. package/knowledge/decisions/security/an-agent-approval-is-claimed-before-the-tool-runs.md +33 -0
  240. package/knowledge/decisions/security/an-approval-answer-outlives-the-run-it-answered.md +59 -0
  241. package/knowledge/decisions/security/an-upload-is-counted-as-it-arrives-not-buffered-then-measured.md +24 -0
  242. package/knowledge/decisions/security/index.md +9 -0
  243. package/knowledge/questions/channel-middleware-accepts-bare-factories-that-nothing-resolves.md +35 -0
  244. package/knowledge/questions/index.md +2 -1
  245. package/knowledge/questions/unauthorized-channel-replies-escape-the-declared-out-type.md +44 -0
  246. package/package.json +15 -3
  247. package/scripts/generate-api-report.d.mts +1 -0
  248. package/scripts/generate-api-report.mjs +89 -0
  249. package/scripts/generate-api-report.mts +343 -0
  250. package/src/api-report.test.ts +69 -0
  251. package/src/column-form.ts +1 -4
  252. package/src/crypto-utils.test.ts +15 -2
  253. package/src/dev/hot-reload.ts +2 -7
  254. package/src/ecosystem.ts +40 -0
  255. package/src/errors/error-handler.ts +5 -3
  256. package/src/errors/error.test.ts +4 -1
  257. package/src/errors/index.ts +1 -1
  258. package/src/function/function-runner.test.ts +53 -1
  259. package/src/function/function-runner.ts +33 -48
  260. package/src/function/functions.types.ts +15 -9
  261. package/src/function/index.ts +0 -2
  262. package/src/handle-error.ts +1 -1
  263. package/src/index.ts +3 -57
  264. package/src/middleware/auth-cookie.test.ts +50 -0
  265. package/src/middleware/auth-cookie.ts +38 -2
  266. package/src/middleware/cors.ts +2 -1
  267. package/src/middleware/index.ts +1 -1
  268. package/src/middleware/remote-auth.test.ts +43 -0
  269. package/src/middleware/remote-auth.ts +17 -3
  270. package/src/middleware-runner.ts +4 -2
  271. package/src/no-any-casts.test.ts +57 -0
  272. package/src/permissions.test.ts +3 -1
  273. package/src/permissions.ts +22 -24
  274. package/src/pikku-state.ts +22 -6
  275. package/src/public-surface.json +554 -0
  276. package/src/public-surface.json.README +25 -0
  277. package/src/public-surface.test.ts +105 -0
  278. package/src/removed-legacy-exports.test.ts +62 -0
  279. package/src/schema.test.ts +78 -0
  280. package/src/schema.ts +36 -1
  281. package/src/services/ai-agent-runner-service.ts +12 -1
  282. package/src/services/ai-run-state-service.ts +18 -1
  283. package/src/services/audit-service.ts +2 -2
  284. package/src/services/in-memory-ai-run-state-service.ts +16 -2
  285. package/src/services/in-memory-workflow-service.ts +2 -0
  286. package/src/services/index.ts +4 -47
  287. package/src/services/local-content-request-handler.test.ts +43 -5
  288. package/src/services/local-content-request-handler.ts +103 -74
  289. package/src/services/local-content.ts +15 -1
  290. package/src/services/local-email-service.ts +5 -1
  291. package/src/services/scoped-credential-service.test.ts +86 -0
  292. package/src/services/scoped-credential-service.ts +63 -0
  293. package/src/services/system-role-guard.test.ts +4 -1
  294. package/src/services/workflow-service.ts +9 -2
  295. package/src/side-effects-are-declared.test.ts +84 -0
  296. package/src/source-files-stay-composable.test.ts +41 -0
  297. package/src/testing/service-tests/agent-run-service-tests.ts +98 -0
  298. package/src/testing/service-tests/ai-storage-service-tests.ts +379 -0
  299. package/src/testing/service-tests/channel-store-tests.ts +98 -0
  300. package/src/testing/service-tests/credential-service-tests.ts +143 -0
  301. package/src/testing/service-tests/deployment-service-tests.ts +33 -0
  302. package/src/testing/service-tests/event-hub-store-tests.ts +48 -0
  303. package/src/testing/service-tests/secret-service-tests.ts +105 -0
  304. package/src/testing/service-tests/session-store-tests.ts +62 -0
  305. package/src/testing/service-tests/workflow-run-service-tests.ts +59 -0
  306. package/src/testing/service-tests/workflow-service-tests.ts +308 -0
  307. package/src/testing/service-tests.ts +31 -1111
  308. package/src/types/core.types.ts +14 -3
  309. package/src/types/state.types.ts +17 -1
  310. package/src/wirings/actor-flow/index.ts +0 -3
  311. package/src/wirings/actor-flow/run-conversation.ts +1 -6
  312. package/src/wirings/ai-agent/agent-rpc.ts +120 -0
  313. package/src/wirings/ai-agent/ai-agent-agui.ts +1 -5
  314. package/src/wirings/ai-agent/ai-agent-finalize.test.ts +186 -0
  315. package/src/wirings/ai-agent/ai-agent-finalize.ts +197 -0
  316. package/src/wirings/ai-agent/ai-agent-interrupt.test.ts +2 -1
  317. package/src/wirings/ai-agent/ai-agent-interrupt.ts +1 -0
  318. package/src/wirings/ai-agent/ai-agent-memory.ts +62 -39
  319. package/src/wirings/ai-agent/ai-agent-model-config.test.ts +72 -3
  320. package/src/wirings/ai-agent/ai-agent-model-config.ts +49 -1
  321. package/src/wirings/ai-agent/ai-agent-prepare.ts +12 -12
  322. package/src/wirings/ai-agent/ai-agent-resume-authorization.test.ts +1 -0
  323. package/src/wirings/ai-agent/ai-agent-runner.test.ts +82 -2
  324. package/src/wirings/ai-agent/ai-agent-runner.ts +120 -160
  325. package/src/wirings/ai-agent/ai-agent-stream-output-hooks.test.ts +353 -0
  326. package/src/wirings/ai-agent/ai-agent-stream.test.ts +2 -0
  327. package/src/wirings/ai-agent/ai-agent-stream.ts +152 -117
  328. package/src/wirings/ai-agent/ai-agent-thread-ownership.test.ts +1 -1
  329. package/src/wirings/ai-agent/ai-agent-turn.test.ts +67 -0
  330. package/src/wirings/ai-agent/ai-agent-turn.ts +122 -0
  331. package/src/wirings/ai-agent/ai-agent.types.ts +65 -5
  332. package/src/wirings/ai-agent/index.ts +2 -16
  333. package/src/wirings/ai-agent/voice-input.ts +1 -6
  334. package/src/wirings/ai-agent/voice-output.ts +2 -12
  335. package/src/wirings/ai-scorer/ai-scorer-grade.test.ts +106 -0
  336. package/src/wirings/ai-scorer/ai-scorer-grade.ts +55 -0
  337. package/src/wirings/ai-scorer/ai-scorer-judge.test.ts +143 -0
  338. package/src/wirings/ai-scorer/ai-scorer-judge.ts +120 -0
  339. package/src/wirings/ai-scorer/ai-scorer-live.test.ts +174 -0
  340. package/src/wirings/ai-scorer/ai-scorer-live.ts +56 -0
  341. package/src/wirings/ai-scorer/ai-scorer-registry.ts +63 -0
  342. package/src/wirings/ai-scorer/ai-scorer-sampling.test.ts +34 -0
  343. package/src/wirings/ai-scorer/ai-scorer-sampling.ts +36 -0
  344. package/src/wirings/ai-scorer/ai-scorer-snapshots.test.ts +49 -0
  345. package/src/wirings/ai-scorer/ai-scorer-snapshots.ts +46 -0
  346. package/src/wirings/ai-scorer/ai-scorer-worker.test.ts +122 -0
  347. package/src/wirings/ai-scorer/ai-scorer-worker.ts +69 -0
  348. package/src/wirings/ai-scorer/ai-scorer.ts +76 -0
  349. package/src/wirings/ai-scorer/ai-scorer.types.ts +107 -0
  350. package/src/wirings/ai-scorer/index.ts +24 -0
  351. package/src/wirings/channel/channel-common.ts +5 -2
  352. package/src/wirings/channel/channel-handler-shapes.test.ts +72 -0
  353. package/src/wirings/channel/channel-handler.ts +7 -7
  354. package/src/wirings/channel/channel-rpc-service.ts +0 -14
  355. package/src/wirings/channel/channel-rpc.test.ts +25 -4
  356. package/src/wirings/channel/channel-rpc.types.ts +14 -0
  357. package/src/wirings/channel/channel-runner.ts +33 -20
  358. package/src/wirings/channel/channel.types.ts +2 -0
  359. package/src/wirings/channel/index.ts +1 -20
  360. package/src/wirings/channel/local/local-channel-runner.test.ts +68 -0
  361. package/src/wirings/channel/local/local-channel-runner.ts +9 -2
  362. package/src/wirings/channel/pikku-abstract-channel-handler.ts +2 -1
  363. package/src/wirings/channel/serverless/serverless-channel-runner.ts +6 -3
  364. package/src/wirings/cli/channel/cli-channel-runner.ts +3 -1
  365. package/src/wirings/cli/channel/cli-raw-channel-runner.test.ts +23 -0
  366. package/src/wirings/cli/channel/cli-raw-channel-runner.ts +12 -1
  367. package/src/wirings/cli/channel/cli-raw-client-runner.ts +4 -1
  368. package/src/wirings/cli/channel/index.ts +0 -7
  369. package/src/wirings/cli/cli-runner.test.ts +68 -0
  370. package/src/wirings/cli/cli-runner.ts +25 -5
  371. package/src/wirings/cli/cli.types.ts +0 -39
  372. package/src/wirings/cli/command-parser.test.ts +19 -0
  373. package/src/wirings/cli/command-parser.ts +17 -1
  374. package/src/wirings/credential/index.ts +0 -1
  375. package/src/wirings/gateway/gateway-channel-meta.test.ts +44 -0
  376. package/src/wirings/gateway/gateway-runner.ts +27 -19
  377. package/src/wirings/gateway/index.ts +0 -3
  378. package/src/wirings/http/http-routes.ts +4 -1
  379. package/src/wirings/http/http-runner.test.ts +66 -0
  380. package/src/wirings/http/http-runner.ts +21 -28
  381. package/src/wirings/http/http.types.ts +0 -15
  382. package/src/wirings/http/index.ts +2 -8
  383. package/src/wirings/http/pikku-fetch-http-request.ts +2 -2
  384. package/src/wirings/http/web-request.ts +1 -1
  385. package/src/wirings/mcp/index.ts +0 -1
  386. package/src/wirings/mcp/mcp-runner.test.ts +181 -0
  387. package/src/wirings/mcp/mcp-runner.ts +37 -21
  388. package/src/wirings/persona/index.ts +0 -8
  389. package/src/wirings/persona/persona-environments.test.ts +14 -3
  390. package/src/wirings/persona/persona.test.ts +13 -3
  391. package/src/wirings/persona/validate-personas.ts +5 -1
  392. package/src/wirings/queue/index.ts +0 -14
  393. package/src/wirings/queue/queue-runner.ts +1 -1
  394. package/src/wirings/rpc/addon-auth-tags.test.ts +1 -5
  395. package/src/wirings/rpc/addon-runner.ts +34 -3
  396. package/src/wirings/rpc/addon-secrets.test.ts +261 -0
  397. package/src/wirings/rpc/rpc-runner.test.ts +2 -0
  398. package/src/wirings/rpc/rpc-runner.ts +60 -136
  399. package/src/wirings/rpc/rpc-types.ts +11 -0
  400. package/src/wirings/rpc/wire-addon.ts +20 -1
  401. package/src/wirings/scheduler/index.ts +0 -1
  402. package/src/wirings/secret/validate-secret-definitions.test.ts +22 -0
  403. package/src/wirings/secret/validate-secret-definitions.ts +2 -2
  404. package/src/wirings/trigger/index.ts +0 -1
  405. package/src/wirings/trigger/pikku-trigger-service.ts +0 -5
  406. package/src/wirings/trigger/trigger-runner.ts +7 -5
  407. package/src/wirings/virtual-user/index.ts +0 -16
  408. package/src/wirings/virtual-user/run-virtual-user.test.ts +18 -9
  409. package/src/wirings/virtual-user/run-virtual-user.ts +28 -15
  410. package/src/wirings/virtual-user/virtual-user-agents.test.ts +4 -1
  411. package/src/wirings/virtual-user/virtual-user-derive.ts +4 -25
  412. package/src/wirings/virtual-user/virtual-user-dispositions.test.ts +7 -2
  413. package/src/wirings/virtual-user/virtual-user-dispositions.ts +1 -4
  414. package/src/wirings/virtual-user/virtual-user-intents.test.ts +13 -3
  415. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +117 -4
  416. package/src/wirings/workflow/feature.ts +7 -4
  417. package/src/wirings/workflow/graph/graph-node.ts +1 -1
  418. package/src/wirings/workflow/graph/graph-runner.test.ts +76 -2
  419. package/src/wirings/workflow/graph/graph-runner.ts +13 -16
  420. package/src/wirings/workflow/graph/workflow-graph.types.ts +0 -5
  421. package/src/wirings/workflow/index.ts +14 -24
  422. package/src/wirings/workflow/pikku-scenario-service.ts +101 -27
  423. package/src/wirings/workflow/pikku-workflow-service.test.ts +15 -14
  424. package/src/wirings/workflow/pikku-workflow-service.ts +214 -743
  425. package/src/wirings/workflow/scenario-expectations.test.ts +75 -0
  426. package/src/wirings/workflow/scenario-hooks.test.ts +52 -0
  427. package/src/wirings/workflow/scenario-step.types.ts +8 -0
  428. package/src/wirings/workflow/workflow-approval-audit.ts +47 -0
  429. package/src/wirings/workflow/workflow-approval-policy.test.ts +524 -0
  430. package/src/wirings/workflow/workflow-approval-policy.ts +68 -0
  431. package/src/wirings/workflow/workflow-approval.ts +289 -0
  432. package/src/wirings/workflow/workflow-child-run-session.test.ts +89 -0
  433. package/src/wirings/workflow/workflow-constants.ts +47 -0
  434. package/src/wirings/workflow/workflow-dispatch-durability.test.ts +1 -1
  435. package/src/wirings/workflow/workflow-errors.ts +128 -0
  436. package/src/wirings/workflow/workflow-inline-authority.test.ts +12 -6
  437. package/src/wirings/workflow/workflow-meta-resolver.ts +45 -0
  438. package/src/wirings/workflow/workflow-missing-meta.test.ts +92 -0
  439. package/src/wirings/workflow/workflow-queue-routing.ts +85 -0
  440. package/src/wirings/workflow/workflow-queue-wiring.ts +149 -0
  441. package/src/wirings/workflow/workflow-recovery.ts +162 -0
  442. package/src/wirings/workflow/workflow-retry-policy.test.ts +1 -1
  443. package/src/wirings/workflow/workflow-run-authority.test.ts +212 -0
  444. package/src/wirings/workflow/workflow-run-engine.types.ts +91 -0
  445. package/src/wirings/workflow/workflow-run-ownership.ts +37 -0
  446. package/src/wirings/workflow/workflow-suspend.ts +61 -0
  447. package/src/wirings/workflow/workflow.types.ts +7 -9
  448. package/src/wirings-stay-decoupled.test.ts +127 -0
  449. package/tsconfig.json +1 -1
  450. package/tsconfig.tsbuildinfo +1 -1
  451. package/dist/internal.d.ts +0 -3
  452. package/dist/internal.js +0 -2
  453. package/dist/middleware/timeout.d.ts +0 -9
  454. package/dist/middleware/timeout.js +0 -15
  455. package/dist/pikku-response.d.ts +0 -6
  456. package/dist/pikku-response.js +0 -6
  457. package/dist/services/gopass-secrets.d.ts +0 -15
  458. package/dist/services/gopass-secrets.js +0 -76
  459. package/dist/services/http-scenario-actors.d.ts +0 -75
  460. package/dist/services/http-scenario-actors.js +0 -195
  461. package/dist/services/http-user-flow-actors.d.ts +0 -67
  462. package/dist/services/http-user-flow-actors.js +0 -193
  463. package/dist/services/scenario-actors-service.d.ts +0 -127
  464. package/dist/services/scenario-actors-service.js +0 -40
  465. package/dist/services/user-flow-actors-service.d.ts +0 -39
  466. package/dist/wirings/credential/wire-credential.d.ts +0 -48
  467. package/dist/wirings/credential/wire-credential.js +0 -47
  468. package/dist/wirings/oauth2/oauth2-client.d.ts +0 -47
  469. package/dist/wirings/oauth2/oauth2-client.js +0 -263
  470. package/dist/wirings/oauth2/oauth2-routes.d.ts +0 -35
  471. package/dist/wirings/oauth2/oauth2-routes.js +0 -146
  472. package/dist/wirings/scope/wire-scope.d.ts +0 -33
  473. package/dist/wirings/scope/wire-scope.js +0 -32
  474. package/dist/wirings/workflow/dsl/index.d.ts +0 -5
  475. package/dist/wirings/workflow/dsl/index.js +0 -4
  476. package/dist/wirings/workflow/graph/index.d.ts +0 -5
  477. package/dist/wirings/workflow/graph/index.js +0 -4
  478. package/src/internal.ts +0 -10
  479. /package/dist/{services/user-flow-actors-service.js → wirings/workflow/workflow-run-engine.types.js} +0 -0
@@ -1,168 +1,22 @@
1
- import { runPikkuFunc, addFunction } from '../../function/function-runner.js';
2
- import { pikkuWorkflowWorkerFunc, pikkuWorkflowOrchestratorFunc, pikkuWorkflowSleeperFunc, } from './workflow-queue-workers.js';
3
- import { wireQueueWorker } from '../queue/queue-runner.js';
1
+ import { runPikkuFunc } from '../../function/function-runner.js';
4
2
  import { getSingletonServices, getCreateWireServices, pikkuState, } from '../../pikku-state.js';
5
3
  import { getDurationInMilliseconds } from '../../time-utils.js';
6
- const resolveWorkflowMeta = (name) => {
7
- const rootMeta = pikkuState(null, 'workflows', 'meta');
8
- if (rootMeta[name]) {
9
- return { meta: rootMeta[name], packageName: null, resolvedName: name };
10
- }
11
- const colonIndex = name.indexOf(':');
12
- if (colonIndex !== -1) {
13
- const namespace = name.substring(0, colonIndex);
14
- const localName = name.substring(colonIndex + 1);
15
- const addons = pikkuState(null, 'addons', 'packages');
16
- const pkgConfig = addons?.get(namespace);
17
- if (pkgConfig) {
18
- const addonMeta = pikkuState(pkgConfig.package, 'workflows', 'meta');
19
- if (addonMeta?.[localName]) {
20
- return {
21
- meta: addonMeta[localName],
22
- packageName: pkgConfig.package,
23
- resolvedName: localName,
24
- };
25
- }
26
- }
27
- }
28
- return null;
29
- };
30
- const toKebab = (s) => s.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
31
- import { continueGraph, executeGraphStep, runWorkflowGraph, runFromMeta, } from './graph/graph-runner.js';
32
- import { PikkuError, addError, isExpectedError, } from '../../errors/error-handler.js';
4
+ import { ChildWorkflowStartedException, continueGraph, executeGraphStep, runWorkflowGraph, runFromMeta, } from './graph/graph-runner.js';
5
+ import { isExpectedError } from '../../errors/error-handler.js';
6
+ import { PikkuMissingMetaError } from '../../errors/errors.js';
33
7
  import { RPCNotFoundError } from '../rpc/rpc-runner.js';
34
- import { ChildWorkflowStartedException } from './graph/graph-runner.js';
35
8
  import { deriveInvocationId } from './workflow-invocation-id.js';
9
+ import { approvalDeciderFrom } from './workflow-approval-policy.js';
36
10
  import { buildRunTimeline, reconstructStateAt, } from './run-timeline.js';
37
- export const DEFAULT_STEP_RETRIES = 5;
38
- export class WorkflowAsyncException extends Error {
39
- runId;
40
- stepName;
41
- constructor(runId, stepName) {
42
- super(`Workflow paused at step: ${stepName}`);
43
- this.runId = runId;
44
- this.stepName = stepName;
45
- this.name = 'WorkflowAsyncException';
46
- }
47
- }
48
- export class WorkflowCancelledException extends Error {
49
- runId;
50
- reason;
51
- constructor(runId, reason) {
52
- super(reason || 'Workflow cancelled');
53
- this.runId = runId;
54
- this.reason = reason;
55
- this.name = 'WorkflowCancelledException';
56
- }
57
- }
58
- export class WorkflowSuspendedException extends Error {
59
- runId;
60
- reason;
61
- constructor(runId, reason) {
62
- super(reason || 'Workflow suspended');
63
- this.runId = runId;
64
- this.reason = reason;
65
- this.name = 'WorkflowSuspendedException';
66
- }
67
- }
68
- export class WorkflowDispatchException extends Error {
69
- runId;
70
- stepName;
71
- constructor(runId, stepName, options) {
72
- super(`Failed to dispatch workflow step '${stepName}' (run ${runId})`, options);
73
- this.runId = runId;
74
- this.stepName = stepName;
75
- this.name = 'WorkflowDispatchException';
76
- }
77
- }
78
- export class WorkflowNotFoundError extends PikkuError {
79
- constructor(name) {
80
- super(`Workflow not found: ${name}`);
81
- }
82
- }
83
- addError(WorkflowNotFoundError, {
84
- status: 404,
85
- message: 'Workflow not found.',
86
- });
87
- export class WorkflowRunNotFoundError extends PikkuError {
88
- constructor(runId) {
89
- super(`Workflow run not found: ${runId}`);
90
- }
91
- }
92
- addError(WorkflowRunNotFoundError, {
93
- status: 404,
94
- message: 'Workflow run not found.',
95
- });
96
- export class WorkflowRunFailedError extends PikkuError {
97
- payload;
98
- constructor(message) {
99
- super(`Workflow run failed: ${message ?? 'unknown'}`);
100
- this.payload = { message };
101
- }
102
- }
103
- addError(WorkflowRunFailedError, {
104
- status: 422,
105
- message: 'Workflow run failed.',
106
- });
107
- export class WorkflowRunCancelledError extends PikkuError {
108
- constructor() {
109
- super('Workflow was cancelled');
110
- }
111
- }
112
- addError(WorkflowRunCancelledError, {
113
- status: 409,
114
- message: 'Workflow was cancelled.',
115
- });
116
- export class WorkflowApprovalResolvedError extends PikkuError {
117
- payload;
118
- constructor(reason, outcome) {
119
- super(`Approval already ${outcome}: ${reason}`);
120
- this.payload = { reason, outcome };
121
- }
122
- }
123
- addError(WorkflowApprovalResolvedError, {
124
- status: 409,
125
- message: 'Approval has already been resolved.',
126
- });
127
- export class WorkflowServiceNotInitialized extends Error {
128
- }
129
- export class WorkflowStepNameNotString extends Error {
130
- constructor(stepName) {
131
- super(`Workflow step name must be a string. Received: ${typeof stepName}`);
132
- }
133
- }
134
- const WORKFLOW_END_STATES = new Set([
135
- 'completed',
136
- 'failed',
137
- 'cancelled',
138
- 'suspended',
139
- ]);
140
- const WORKFLOW_TERMINAL_STATES = new Set([
141
- 'completed',
142
- 'failed',
143
- 'cancelled',
144
- ]);
145
- /** Idle window before a `running` run with nothing in flight is treated as stalled. */
146
- const DEFAULT_STALLED_RUN_MS = 5 * 60_000;
147
- /** Runs re-driven per `recoverStalledRuns` call, so one sweep is bounded. */
148
- const DEFAULT_STALLED_RUN_LIMIT = 100;
149
- /**
150
- * How long a step may sit `pending` before the relay assumes its dispatch was
151
- * lost. This is a bet that no healthy queue takes this long to move a job from
152
- * `pending` to `running`; set it above the observed p99 of that latency.
153
- */
154
- const DEFAULT_UNDISPATCHED_STEP_MS = 30_000;
155
- /** Steps re-driven per `relayUndispatchedSteps` call, so one tick is bounded. */
156
- const DEFAULT_UNDISPATCHED_STEP_LIMIT = 100;
157
- /** First wait before a step already re-dispatched once is re-dispatched again. */
158
- const REDISPATCH_BACKOFF_MS = 30_000;
159
- /** Ceiling on the doubling backoff, so a permanently stuck step still gets swept. */
160
- const REDISPATCH_BACKOFF_MAX_MS = 10 * 60_000;
161
- /** Bound on the in-process backoff map, so a long-lived process cannot grow it without bound. */
162
- const REDISPATCH_BACKOFF_MAX_ENTRIES = 10_000;
163
- const WORKFLOW_POLL_MIN_MS = 10;
164
- const WORKFLOW_POLL_FACTOR = 1.6;
165
- const WORKFLOW_CHILD_POLL_MAX_MS = 500;
11
+ import { DEFAULT_STEP_RETRIES, WORKFLOW_CHILD_POLL_MAX_MS, WORKFLOW_END_STATES, WORKFLOW_POLL_FACTOR, WORKFLOW_POLL_MIN_MS, WORKFLOW_TERMINAL_STATES, } from './workflow-constants.js';
12
+ import { WorkflowAsyncException, WorkflowCancelledException, WorkflowDispatchException, WorkflowNotFoundError, WorkflowRunCancelledError, WorkflowRunFailedError, WorkflowRunNotFoundError, WorkflowStepFunctionMismatchError, WorkflowStepNameNotString, WorkflowSuspendedException, } from './workflow-errors.js';
13
+ import { resolveWorkflowMeta } from './workflow-meta-resolver.js';
14
+ import { jobGroupFor, orchestratorQueueName, resolveWorkflowConfig, stepJobOptions, stepWorkerQueueName, } from './workflow-queue-routing.js';
15
+ import { wireWorkflowQueueWorkers } from './workflow-queue-wiring.js';
16
+ import { approvalStepNameFor, evaluateApprovalStep, recordApprovalDecision, } from './workflow-approval.js';
17
+ import { auditApprovalDecision } from './workflow-approval-audit.js';
18
+ import { recordSuspension, suspendStepNameFor } from './workflow-suspend.js';
19
+ import { RedispatchBackoff, sweepStalledRuns, sweepUndispatchedSteps, } from './workflow-recovery.js';
166
20
  export class PikkuWorkflowService {
167
21
  runExtension;
168
22
  runContexts = new Map();
@@ -236,76 +90,12 @@ export class PikkuWorkflowService {
236
90
  return written;
237
91
  }
238
92
  wireQueueWorkers() {
239
- const functions = pikkuState(null, 'function', 'functions');
240
- const functionsMeta = pikkuState(null, 'function', 'meta');
241
- const mkMeta = (funcId) => ({
242
- pikkuFuncId: funcId,
243
- sessionless: true,
244
- functionType: 'helper',
245
- inputSchemaName: null,
246
- outputSchemaName: null,
93
+ wireWorkflowQueueWorkers({
94
+ strategy: this.queueStrategy,
95
+ concurrency: this.queueConcurrency,
96
+ groupConcurrency: this.queueGroupConcurrency,
97
+ getLogger: () => this.logger,
247
98
  });
248
- const queueMeta = pikkuState(null, 'queue', 'meta');
249
- const registerWorkflowFunc = (funcId, func, queueName, config) => {
250
- if (functions.has(funcId))
251
- return;
252
- addFunction(funcId, func);
253
- if (!queueMeta[queueName]) {
254
- queueMeta[queueName] = { pikkuFuncId: funcId, name: queueName };
255
- }
256
- wireQueueWorker({ name: queueName, func, config });
257
- if (!functionsMeta[funcId]) {
258
- functionsMeta[funcId] = mkMeta(funcId);
259
- }
260
- };
261
- const sharedGroups = this.queueStrategy === 'shared-groups';
262
- const sharedQueueConfig = sharedGroups
263
- ? {
264
- batchSize: this.queueConcurrency,
265
- groupConcurrency: this.queueGroupConcurrency,
266
- }
267
- : undefined;
268
- registerWorkflowFunc('pikkuWorkflowOrchestrator', { func: pikkuWorkflowOrchestratorFunc }, 'pikku-workflow-orchestrator', sharedQueueConfig);
269
- registerWorkflowFunc('pikkuWorkflowStepWorker', { func: pikkuWorkflowWorkerFunc }, 'pikku-workflow-step-worker', sharedQueueConfig);
270
- const registerQueueWorkers = (queueMeta) => {
271
- for (const [queueName, meta] of Object.entries(queueMeta)) {
272
- if (functions.has(meta.pikkuFuncId))
273
- continue;
274
- if (queueName.startsWith('wf-orchestrator-')) {
275
- registerWorkflowFunc(meta.pikkuFuncId, { func: pikkuWorkflowOrchestratorFunc }, queueName);
276
- }
277
- else if (queueName.startsWith('wf-step-')) {
278
- registerWorkflowFunc(meta.pikkuFuncId, { func: pikkuWorkflowWorkerFunc }, queueName);
279
- }
280
- }
281
- };
282
- if (!sharedGroups) {
283
- registerQueueWorkers(pikkuState(null, 'queue', 'meta'));
284
- const addons = pikkuState(null, 'addons', 'packages');
285
- if (addons) {
286
- for (const [, addon] of addons) {
287
- const addonQueueMeta = pikkuState(addon.package, 'queue', 'meta');
288
- if (addonQueueMeta) {
289
- registerQueueWorkers(addonQueueMeta);
290
- }
291
- }
292
- }
293
- }
294
- const workflowCount = Object.keys(pikkuState(null, 'workflows', 'meta') ?? {}).length;
295
- const perWorkflowQueues = Object.keys(queueMeta).filter((name) => name.startsWith('wf-orchestrator-')).length;
296
- if (workflowCount > 0 && perWorkflowQueues === 0) {
297
- this.logger?.warn?.(`[pikku] ${workflowCount} workflow(s) registered but no per-workflow orchestrator queues were found in queue meta. ` +
298
- `All workflows will share a single orchestrator queue, where one slow step blocks every other workflow behind it. ` +
299
- `Check that the generated bootstrap imports the queue-workers meta (pikku-queue-workers-wirings-meta.gen.js).`);
300
- }
301
- if (!functions.has('pikkuWorkflowSleeper')) {
302
- addFunction('pikkuWorkflowSleeper', {
303
- func: pikkuWorkflowSleeperFunc,
304
- });
305
- }
306
- if (!functionsMeta.pikkuWorkflowSleeper) {
307
- functionsMeta.pikkuWorkflowSleeper = mkMeta('pikkuWorkflowSleeper');
308
- }
309
99
  }
310
100
  async isInline(runId) {
311
101
  const context = this.runContexts.get(runId);
@@ -465,41 +255,6 @@ export class PikkuWorkflowService {
465
255
  async findStalledRunIds(_before, _limit) {
466
256
  return [];
467
257
  }
468
- /**
469
- * Re-drive runs whose next move was lost, and report which were resumed.
470
- *
471
- * Arming a step is two writes to two systems — the step row, then the queue
472
- * or scheduler job — so a process that dies between them leaves a run that is
473
- * `running` with nothing in flight. Nothing notices: the run parks on a step
474
- * that will never complete and never error, so it neither finishes nor fails.
475
- * (Seen on a `workflow.sleep()`: a deploy restart landed between the sleep
476
- * step's insert and its timer, parking the run permanently.)
477
- *
478
- * Replay is the recovery — `resumeWorkflow` re-orchestrates from persisted
479
- * step state, and every settled step is memoized, so resuming a run that was
480
- * not actually stuck costs an orchestration pass and changes nothing. That
481
- * idempotence is what makes an idle-time heuristic safe here; a run that is
482
- * legitimately mid-sleep is excluded anyway, since its step is `scheduled`.
483
- *
484
- * This is not self-starting. Call it from a scheduled task at whatever
485
- * interval suits the workload.
486
- */
487
- async recoverStalledRuns(options) {
488
- const before = new Date(Date.now() - (options?.stalledAfterMs ?? DEFAULT_STALLED_RUN_MS));
489
- const runIds = await this.findStalledRunIds(before, options?.limit ?? DEFAULT_STALLED_RUN_LIMIT);
490
- const resumed = [];
491
- for (const runId of runIds) {
492
- try {
493
- await this.resumeWorkflow(runId);
494
- resumed.push(runId);
495
- }
496
- catch (err) {
497
- // One unresumable run must not stop the sweep from recovering the rest.
498
- getSingletonServices()?.logger?.error(`Failed to resume stalled workflow run ${runId}: ${err instanceof Error ? err.message : String(err)}`);
499
- }
500
- }
501
- return { resumed };
502
- }
503
258
  /**
504
259
  * Runs holding a step that has sat `pending` since before `before`, paired
505
260
  * with the step that flagged them.
@@ -516,89 +271,30 @@ export class PikkuWorkflowService {
516
271
  async findUndispatchedSteps(_before, _limit) {
517
272
  return [];
518
273
  }
274
+ redispatchBackoff = new RedispatchBackoff();
275
+ get sweepDeps() {
276
+ return {
277
+ resume: (runId) => this.resumeWorkflow(runId),
278
+ logger: getSingletonServices()?.logger,
279
+ };
280
+ }
519
281
  /**
520
- * Re-drive steps whose dispatch was lost, and report which runs were nudged.
521
- *
522
- * Arming a step is two writes to two systems: the step row lands `pending`,
523
- * then a queue or scheduler job is published. Nothing spans both, so a crash
524
- * in between leaves a durable row that nothing will ever pick up — the run
525
- * neither finishes nor fails. (Seen on a `workflow.sleep()`: a deploy restart
526
- * landed between the sleep step's insert and its timer.)
527
- *
528
- * The row is the outbox record and this is the relay. Age is the only signal
529
- * available a step `pending` because its dispatch was lost is
530
- * indistinguishable from one whose job is merely still queued — so a step
531
- * past `undispatchedAfterMs` is re-dispatched regardless, and correctness
532
- * rests on the claim rather than on the guess being right. A redundant
533
- * dispatch costs one queue message: the loser reads `running` and returns
534
- * without invoking anything.
535
- *
536
- * Re-dispatches back off per step (doubling from 30s, capped at 10m) so a
537
- * genuine queue backlog is not amplified by a tick that keeps firing at the
538
- * steps the backlog is already delaying. The backoff is per process and
539
- * advisory — losing it on restart costs extra dispatches, never correctness.
540
- *
541
- * This is not self-starting. Call it from a scheduled task; ~30s suits a
542
- * queue whose `pending`→`running` latency is well under that.
282
+ * Re-drive runs whose next move was lost. Not self-starting call it from a
283
+ * scheduled task at whatever interval suits the workload.
284
+ */
285
+ async recoverStalledRuns(options) {
286
+ return sweepStalledRuns((before, limit) => this.findStalledRunIds(before, limit), options, this.sweepDeps);
287
+ }
288
+ /**
289
+ * Re-drive steps whose dispatch was lost. Not self-starting — call it from a
290
+ * scheduled task; ~30s suits a queue whose `pending`→`running` latency is
291
+ * well under that.
543
292
  */
544
293
  async relayUndispatchedSteps(options) {
545
- const before = new Date(Date.now() -
546
- (options?.undispatchedAfterMs ?? DEFAULT_UNDISPATCHED_STEP_MS));
547
- const steps = await this.findUndispatchedSteps(before, options?.limit ?? DEFAULT_UNDISPATCHED_STEP_LIMIT);
548
- // Backoff is keyed by run, not step, because the run is the unit of
549
- // re-drive: `resumeWorkflow` replays the whole run and re-dispatches every
550
- // step still owed a job. Holding off a single step while resuming its run
551
- // would not suppress anything.
552
- const now = Date.now();
553
- const runIds = new Set();
554
- for (const { runId } of steps) {
555
- const eligibleAt = this.redispatchBackoff.get(runId);
556
- if (eligibleAt !== undefined && eligibleAt > now) {
557
- continue;
558
- }
559
- this.noteRedispatch(runId, now);
560
- runIds.add(runId);
561
- }
562
- const redispatched = [];
563
- for (const runId of runIds) {
564
- try {
565
- await this.resumeWorkflow(runId);
566
- redispatched.push(runId);
567
- }
568
- catch (err) {
569
- // One unresumable run must not stop the tick from relaying the rest.
570
- getSingletonServices()?.logger?.error(`Failed to re-dispatch workflow run ${runId}: ${err instanceof Error ? err.message : String(err)}`);
571
- }
572
- }
573
- return { redispatched };
574
- }
575
- /** Advisory, per-process record of when a run may next be re-dispatched. */
576
- redispatchBackoff = new Map();
577
- redispatchDelays = new Map();
578
- noteRedispatch(runId, now) {
579
- const previous = this.redispatchDelays.get(runId);
580
- const delay = Math.min(previous === undefined ? REDISPATCH_BACKOFF_MS : previous * 2, REDISPATCH_BACKOFF_MAX_MS);
581
- // A run that settles is never returned again, so entries are only evicted by
582
- // this bound — oldest first, which is also least recently re-dispatched.
583
- if (this.redispatchBackoff.size >= REDISPATCH_BACKOFF_MAX_ENTRIES) {
584
- const oldest = this.redispatchBackoff.keys().next();
585
- if (!oldest.done) {
586
- this.redispatchBackoff.delete(oldest.value);
587
- this.redispatchDelays.delete(oldest.value);
588
- }
589
- }
590
- this.redispatchDelays.set(runId, delay);
591
- this.redispatchBackoff.set(runId, now + delay);
294
+ return sweepUndispatchedSteps((before, limit) => this.findUndispatchedSteps(before, limit), this.redispatchBackoff, options, this.sweepDeps);
592
295
  }
593
296
  resolveStepJobOptions(stepOptions) {
594
- const retries = stepOptions?.retries ?? DEFAULT_STEP_RETRIES;
595
- const retryDelay = stepOptions?.retryDelay;
596
- const backoff = retryDelay !== undefined && retryDelay !== 'exponential'
597
- ? { type: 'fixed', delay: getDurationInMilliseconds(retryDelay) }
598
- : retries > 0 || retryDelay === 'exponential'
599
- ? 'exponential'
600
- : undefined;
601
- return { attempts: retries + 1, ...(backoff ? { backoff } : {}) };
297
+ return stepJobOptions(stepOptions);
602
298
  }
603
299
  async queueStepWorker(runId, stepName, rpcName, data, stepOptions, fromStepName) {
604
300
  const queueService = this.verifyQueueService();
@@ -676,6 +372,18 @@ export class PikkuWorkflowService {
676
372
  throw new WorkflowNotFoundError(name);
677
373
  }
678
374
  if (workflowMeta.source === 'graph') {
375
+ // A caller-supplied startNode must be one of the graph's declared entry
376
+ // nodes. startWorkflow is the boundary the public
377
+ // `/workflow/:name/graph/:nodeId` route and triggers enter through, so
378
+ // without this a request could name any dependency-free node — one whose
379
+ // input reads only `trigger` — and fire its RPC directly with
380
+ // attacker-chosen data, skipping every upstream eligibility, validation or
381
+ // approval node. (Internal resume/replay drives runWorkflowGraph directly
382
+ // and is unaffected.)
383
+ if (options?.startNode &&
384
+ !(workflowMeta.entryNodeIds ?? []).includes(options.startNode)) {
385
+ throw new Error(`Workflow graph '${name}': '${options.startNode}' is not a declared entry node`);
386
+ }
679
387
  const shouldInline = options?.inline || !getSingletonServices()?.queueService;
680
388
  return runWorkflowGraph(this, name, input, rpcService, shouldInline, options?.startNode, wire, workflowMeta);
681
389
  }
@@ -859,6 +567,9 @@ export class PikkuWorkflowService {
859
567
  return;
860
568
  }
861
569
  const registrations = pikkuState(pkgName, 'workflows', 'registrations');
570
+ if (!workflowMeta) {
571
+ throw new PikkuMissingMetaError(`Missing generated metadata for workflow '${run.workflow}'`);
572
+ }
862
573
  const workflow = registrations.get(resolved?.resolvedName ?? run.workflow);
863
574
  if (!workflow) {
864
575
  throw new WorkflowNotFoundError(run.workflow);
@@ -869,11 +580,10 @@ export class PikkuWorkflowService {
869
580
  : null;
870
581
  const workflowWire = this.createWorkflowWire(run.workflow, runId, rpcService, addonNs);
871
582
  workflowWire.pikkuUserId = run.wire?.pikkuUserId;
583
+ // No `rpc` yet — runPikkuFunc attaches it lazily for the invocation.
872
584
  const wire = {
873
585
  workflow: workflowWire,
874
586
  pikkuUserId: run.wire?.pikkuUserId,
875
- session: rpcService?.wire?.session,
876
- rpc: rpcService?.wire?.rpc,
877
587
  };
878
588
  this.runExtension?.decorateRunWire(wire, {
879
589
  runId,
@@ -990,6 +700,11 @@ export class PikkuWorkflowService {
990
700
  async executeWorkflowStepInner(runId, stepName, rpcName, data, rpcService) {
991
701
  const claimed = await this.withStepLock(runId, stepName, async () => {
992
702
  const stepState = await this.getStepState(runId, stepName);
703
+ // knowledge: decisions/security/a-step-runs-the-function-the-workflow-dispatched-it-with.md
704
+ if (stepState.rpcName !== undefined &&
705
+ stepState.rpcName !== (rpcName ?? null)) {
706
+ throw new WorkflowStepFunctionMismatchError(runId, stepName);
707
+ }
993
708
  if (stepState.status === 'succeeded' || stepState.status === 'running') {
994
709
  return null;
995
710
  }
@@ -1027,7 +742,7 @@ export class PikkuWorkflowService {
1027
742
  }
1028
743
  }
1029
744
  if (graphNodeId) {
1030
- result = await executeGraphStep(this, rpcService, runId, stepState.stepId, graphNodeId, rpcName, data, run.workflow);
745
+ result = await executeGraphStep(this, rpcService, runId, stepState.stepId, graphNodeId, workflowMeta.nodes[graphNodeId].rpcName, data, run.workflow);
1031
746
  }
1032
747
  else {
1033
748
  const subWorkflowMeta = meta[rpcName];
@@ -1037,7 +752,7 @@ export class PikkuWorkflowService {
1037
752
  id: rpcName,
1038
753
  parentRunId: runId,
1039
754
  parentStepId: stepState.stepId,
1040
- pikkuUserId: rpcService.wire?.pikkuUserId,
755
+ pikkuUserId: run.wire?.pikkuUserId,
1041
756
  };
1042
757
  const shouldInline = !getSingletonServices()?.queueService;
1043
758
  const { runId: childRunId } = await this.startWorkflow(rpcName, data, childWire, rpcService, { inline: shouldInline });
@@ -1202,7 +917,7 @@ export class PikkuWorkflowService {
1202
917
  type: 'workflow',
1203
918
  id: rpcName,
1204
919
  parentRunId: runId,
1205
- pikkuUserId: rpcService.wire?.pikkuUserId,
920
+ pikkuUserId: (await this.getRunIdentity(runId))?.wire?.pikkuUserId,
1206
921
  };
1207
922
  const { runId: childRunId } = await this.startWorkflow(rpcName, data, childWire, rpcService, { inline: true });
1208
923
  await this.setStepChildRunId(currentStepState.stepId, childRunId);
@@ -1281,39 +996,10 @@ export class PikkuWorkflowService {
1281
996
  await new Promise((resolve) => setTimeout(resolve, getDurationInMilliseconds(duration)));
1282
997
  await this.setStepResult(stepState.stepId, null);
1283
998
  }
1284
- getSuspendStepName(reason) {
1285
- return `__workflow_suspend:${reason}`;
1286
- }
1287
999
  async suspendStep(runId, reason) {
1288
1000
  const fromStepName = this.lastStepName(runId);
1289
- const suspendStepName = this.nextStepKey(runId, this.getSuspendStepName(reason));
1290
- await this.withStepLock(runId, suspendStepName, async () => {
1291
- let stepState;
1292
- try {
1293
- stepState = await this.getStepState(runId, suspendStepName);
1294
- }
1295
- catch {
1296
- stepState = await this.insertStepState(runId, suspendStepName, 'pikkuWorkflowSuspend', {
1297
- reason,
1298
- }, undefined, fromStepName);
1299
- }
1300
- if (!stepState.stepId) {
1301
- stepState = await this.insertStepState(runId, suspendStepName, 'pikkuWorkflowSuspend', {
1302
- reason,
1303
- }, undefined, fromStepName);
1304
- }
1305
- if (stepState.status === 'succeeded') {
1306
- return;
1307
- }
1308
- if (stepState.status === 'pending') {
1309
- await this.setStepRunning(stepState.stepId);
1310
- }
1311
- await this.setStepResult(stepState.stepId, {
1312
- reason,
1313
- suspendedAt: new Date().toISOString(),
1314
- });
1315
- throw new WorkflowSuspendedException(runId, reason);
1316
- });
1001
+ const suspendStepName = this.nextStepKey(runId, suspendStepNameFor(reason));
1002
+ await this.withStepLock(runId, suspendStepName, () => recordSuspension(this.approvalStore, runId, reason, suspendStepName, fromStepName));
1317
1003
  }
1318
1004
  async scheduleRunWake(runId, delay) {
1319
1005
  try {
@@ -1331,96 +1017,27 @@ export class PikkuWorkflowService {
1331
1017
  this.logger?.warn(`Failed to schedule approval expiry wake for run ${runId}; expiry will still resolve on the next replay`, error);
1332
1018
  }
1333
1019
  }
1334
- getApprovalStepName(reason) {
1335
- return `__workflow_approval:${reason}`;
1336
- }
1337
- approvalStateKey(stepName) {
1338
- let hex = '';
1339
- for (const byte of new TextEncoder().encode(stepName)) {
1340
- hex += byte.toString(16).padStart(2, '0');
1341
- }
1342
- return `__approval_${hex}`;
1020
+ get approvalStore() {
1021
+ return {
1022
+ getStepState: (runId, stepName) => this.getStepState(runId, stepName),
1023
+ insertStepState: (runId, stepName, rpcName, input, output, fromStepName) => this.insertStepState(runId, stepName, rpcName, input, output, fromStepName),
1024
+ setStepRunning: (stepId) => this.setStepRunning(stepId),
1025
+ setStepResult: (stepId, result) => this.setStepResult(stepId, result),
1026
+ getRunState: (runId) => this.getRunState(runId),
1027
+ updateRunState: (runId, key, value) => this.updateRunState(runId, key, value),
1028
+ resumeWorkflow: (runId) => this.resumeWorkflow(runId),
1029
+ scheduleRunWake: (runId, delay) => this.scheduleRunWake(runId, delay),
1030
+ getRunOwner: async (runId) => (await this.getRunIdentity(runId))?.wire?.pikkuUserId,
1031
+ auditApproval: (event) => auditApprovalDecision(event),
1032
+ };
1343
1033
  }
1344
- async approveStep(runId, reason, decision) {
1345
- const stepName = this.getApprovalStepName(reason);
1346
- const stateKey = this.approvalStateKey(stepName);
1347
- let resolved;
1348
- try {
1349
- resolved = await this.getStepState(runId, stepName);
1350
- }
1351
- catch {
1352
- // knowledge: decisions/security/workflow-approval-payloads-are-validated-on-replay-inside-the-workflow.md
1353
- }
1354
- if (resolved?.stepId && resolved.status === 'succeeded') {
1355
- const outcome = resolved.result;
1356
- throw new WorkflowApprovalResolvedError(reason, outcome?.status ?? 'decided');
1357
- }
1358
- const state = await this.getRunState(runId);
1359
- const record = (state[stateKey] ?? {});
1360
- await this.updateRunState(runId, stateKey, {
1361
- ...record,
1362
- decision,
1363
- decidedAt: new Date().toISOString(),
1364
- error: undefined,
1365
- });
1366
- await this.resumeWorkflow(runId);
1034
+ async approveStep(runId, reason, decision, session) {
1035
+ return recordApprovalDecision(this.approvalStore, runId, reason, decision, approvalDeciderFrom(session));
1367
1036
  }
1368
1037
  async approvalStep(runId, reason, options) {
1369
1038
  const fromStepName = this.lastStepName(runId);
1370
- const approvalStepName = this.nextStepKey(runId, this.getApprovalStepName(reason));
1371
- return await this.withStepLock(runId, approvalStepName, async () => {
1372
- let stepState;
1373
- try {
1374
- stepState = await this.getStepState(runId, approvalStepName);
1375
- }
1376
- catch {
1377
- stepState = await this.insertStepState(runId, approvalStepName, 'pikkuWorkflowApproval', { reason, expiry: options.expiry }, undefined, fromStepName);
1378
- }
1379
- if (!stepState.stepId) {
1380
- stepState = await this.insertStepState(runId, approvalStepName, 'pikkuWorkflowApproval', { reason, expiry: options.expiry }, undefined, fromStepName);
1381
- }
1382
- if (stepState.status === 'succeeded') {
1383
- return stepState.result;
1384
- }
1385
- const stateKey = this.approvalStateKey(approvalStepName);
1386
- let record = ((await this.getRunState(runId))[stateKey] ?? {});
1387
- if (stepState.status === 'pending') {
1388
- await this.setStepRunning(stepState.stepId);
1389
- if (options.expiry !== undefined && !record.expiresAt) {
1390
- const expiresAt = new Date(Date.now() + getDurationInMilliseconds(options.expiry)).toISOString();
1391
- record = { ...record, expiresAt };
1392
- await this.updateRunState(runId, stateKey, record);
1393
- await this.scheduleRunWake(runId, getDurationInMilliseconds(options.expiry));
1394
- }
1395
- }
1396
- if (record.decision !== undefined) {
1397
- const validation = await options.schema['~standard'].validate(record.decision);
1398
- if (validation.issues) {
1399
- await this.updateRunState(runId, stateKey, {
1400
- ...record,
1401
- decision: undefined,
1402
- decidedAt: undefined,
1403
- error: validation.issues.map((issue) => ({
1404
- message: issue.message,
1405
- path: issue.path?.map((segment) => typeof segment === 'object' ? segment.key : segment),
1406
- })),
1407
- });
1408
- throw new WorkflowSuspendedException(runId, reason);
1409
- }
1410
- const outcome = {
1411
- status: 'decided',
1412
- data: validation.value,
1413
- };
1414
- await this.setStepResult(stepState.stepId, outcome);
1415
- return outcome;
1416
- }
1417
- if (record.expiresAt && Date.now() >= Date.parse(record.expiresAt)) {
1418
- const outcome = { status: 'expired' };
1419
- await this.setStepResult(stepState.stepId, outcome);
1420
- return outcome;
1421
- }
1422
- throw new WorkflowSuspendedException(runId, reason);
1423
- });
1039
+ const approvalStepName = this.nextStepKey(runId, approvalStepNameFor(reason));
1040
+ return await this.withStepLock(runId, approvalStepName, () => evaluateApprovalStep(this.approvalStore, runId, reason, approvalStepName, fromStepName, options));
1424
1041
  }
1425
1042
  createWorkflowWire(name, runId, rpcService, addonNamespace) {
1426
1043
  const workflowWire = {
@@ -1466,40 +1083,15 @@ export class PikkuWorkflowService {
1466
1083
  }
1467
1084
  }
1468
1085
  getConfig() {
1469
- const singletonServices = getSingletonServices();
1470
- const workflow = singletonServices.config?.workflow;
1471
- return {
1472
- retries: workflow?.retries ?? DEFAULT_STEP_RETRIES,
1473
- retryDelay: workflow?.retryDelay ?? 0,
1474
- orchestratorQueueName: workflow?.orchestratorQueueName ?? 'pikku-workflow-orchestrator',
1475
- stepWorkerQueueName: workflow?.stepWorkerQueueName ?? 'pikku-workflow-step-worker',
1476
- sleeperRPCName: workflow?.sleeperRPCName ?? 'pikkuWorkflowSleeper',
1477
- };
1086
+ return resolveWorkflowConfig();
1478
1087
  }
1479
1088
  getOrchestratorQueueName(workflowName) {
1480
- if (workflowName && this.queueStrategy !== 'shared-groups') {
1481
- const perWorkflow = `wf-orchestrator-${toKebab(workflowName)}`;
1482
- const meta = pikkuState(null, 'queue', 'meta');
1483
- if (meta[perWorkflow]) {
1484
- return perWorkflow;
1485
- }
1486
- }
1487
- return this.getConfig().orchestratorQueueName;
1089
+ return orchestratorQueueName(this.queueStrategy, workflowName);
1488
1090
  }
1489
1091
  getStepWorkerQueueName(rpcName) {
1490
- if (rpcName && this.queueStrategy !== 'shared-groups') {
1491
- const perStep = `wf-step-${toKebab(rpcName)}`;
1492
- const meta = pikkuState(null, 'queue', 'meta');
1493
- if (meta[perStep]) {
1494
- return perStep;
1495
- }
1496
- }
1497
- return this.getConfig().stepWorkerQueueName;
1092
+ return stepWorkerQueueName(this.queueStrategy, rpcName);
1498
1093
  }
1499
1094
  getJobGroup(id) {
1500
- if (!id || this.queueStrategy !== 'shared-groups') {
1501
- return undefined;
1502
- }
1503
- return { id, tier: id };
1095
+ return jobGroupFor(this.queueStrategy, id);
1504
1096
  }
1505
1097
  }