@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
@@ -0,0 +1,38 @@
1
+ import { getSingletonServices, pikkuState } from '../../pikku-state.js';
2
+ import { getDurationInMilliseconds } from '../../time-utils.js';
3
+ import { DEFAULT_STEP_RETRIES } from './workflow-constants.js';
4
+ const toKebab = (s) => s.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
5
+ export const resolveWorkflowConfig = () => {
6
+ const workflow = getSingletonServices().config?.workflow;
7
+ return {
8
+ retries: workflow?.retries ?? DEFAULT_STEP_RETRIES,
9
+ retryDelay: workflow?.retryDelay ?? 0,
10
+ orchestratorQueueName: workflow?.orchestratorQueueName ?? 'pikku-workflow-orchestrator',
11
+ stepWorkerQueueName: workflow?.stepWorkerQueueName ?? 'pikku-workflow-step-worker',
12
+ sleeperRPCName: workflow?.sleeperRPCName ?? 'pikkuWorkflowSleeper',
13
+ };
14
+ };
15
+ /**
16
+ * Falls back to the shared queue whenever the dedicated one was not generated
17
+ * into queue meta, so a workflow added without a rebuild still runs.
18
+ */
19
+ const dedicatedQueueName = (prefix, name, strategy, fallback) => {
20
+ if (!name || strategy === 'shared-groups') {
21
+ return fallback;
22
+ }
23
+ const dedicated = `${prefix}${toKebab(name)}`;
24
+ return pikkuState(null, 'queue', 'meta')[dedicated] ? dedicated : fallback;
25
+ };
26
+ export const orchestratorQueueName = (strategy, workflowName) => dedicatedQueueName('wf-orchestrator-', workflowName, strategy, resolveWorkflowConfig().orchestratorQueueName);
27
+ export const stepWorkerQueueName = (strategy, rpcName) => dedicatedQueueName('wf-step-', rpcName, strategy, resolveWorkflowConfig().stepWorkerQueueName);
28
+ export const jobGroupFor = (strategy, id) => id && strategy === 'shared-groups' ? { id, tier: id } : undefined;
29
+ export const stepJobOptions = (stepOptions) => {
30
+ const retries = stepOptions?.retries ?? DEFAULT_STEP_RETRIES;
31
+ const retryDelay = stepOptions?.retryDelay;
32
+ const backoff = retryDelay !== undefined && retryDelay !== 'exponential'
33
+ ? { type: 'fixed', delay: getDurationInMilliseconds(retryDelay) }
34
+ : retries > 0 || retryDelay === 'exponential'
35
+ ? 'exponential'
36
+ : undefined;
37
+ return { attempts: retries + 1, ...(backoff ? { backoff } : {}) };
38
+ };
@@ -0,0 +1,20 @@
1
+ import type { GroupConcurrencyConfig } from '../queue/queue.types.js';
2
+ import type { Logger } from '../../services/logger.js';
3
+ import type { WorkflowQueueStrategy } from './workflow-queue-routing.js';
4
+ export type WorkflowQueueWiringOptions = {
5
+ strategy: WorkflowQueueStrategy;
6
+ concurrency: number;
7
+ groupConcurrency: number | GroupConcurrencyConfig;
8
+ /**
9
+ * Resolved only when there is something to warn about: reading the logger
10
+ * throws while singleton services are uninitialized, and wiring queues before
11
+ * that point is legitimate.
12
+ */
13
+ getLogger: () => Logger | undefined;
14
+ };
15
+ /**
16
+ * Registers the orchestrator, step-worker and sleeper functions and binds them
17
+ * to their queues. Idempotent: a queue already carrying a registered function
18
+ * is left alone.
19
+ */
20
+ export declare const wireWorkflowQueueWorkers: ({ strategy, concurrency, groupConcurrency, getLogger, }: WorkflowQueueWiringOptions) => void;
@@ -0,0 +1,79 @@
1
+ import { addFunction } from '../../function/function-runner.js';
2
+ import { pikkuState } from '../../pikku-state.js';
3
+ import { wireQueueWorker } from '../queue/queue-runner.js';
4
+ import { pikkuWorkflowOrchestratorFunc, pikkuWorkflowSleeperFunc, pikkuWorkflowWorkerFunc, } from './workflow-queue-workers.js';
5
+ const ORCHESTRATOR_QUEUE_PREFIX = 'wf-orchestrator-';
6
+ const STEP_QUEUE_PREFIX = 'wf-step-';
7
+ const helperFunctionMeta = (funcId) => ({
8
+ pikkuFuncId: funcId,
9
+ sessionless: true,
10
+ functionType: 'helper',
11
+ inputSchemaName: null,
12
+ outputSchemaName: null,
13
+ });
14
+ /**
15
+ * Registers the orchestrator, step-worker and sleeper functions and binds them
16
+ * to their queues. Idempotent: a queue already carrying a registered function
17
+ * is left alone.
18
+ */
19
+ export const wireWorkflowQueueWorkers = ({ strategy, concurrency, groupConcurrency, getLogger, }) => {
20
+ const functions = pikkuState(null, 'function', 'functions');
21
+ const functionsMeta = pikkuState(null, 'function', 'meta');
22
+ const queueMeta = pikkuState(null, 'queue', 'meta');
23
+ const registerWorkflowFunc = (funcId, func, queueName, config) => {
24
+ if (functions.has(funcId))
25
+ return;
26
+ addFunction(funcId, func);
27
+ if (!queueMeta[queueName]) {
28
+ queueMeta[queueName] = { pikkuFuncId: funcId, name: queueName };
29
+ }
30
+ wireQueueWorker({ name: queueName, func, config });
31
+ if (!functionsMeta[funcId]) {
32
+ functionsMeta[funcId] = helperFunctionMeta(funcId);
33
+ }
34
+ };
35
+ const sharedGroups = strategy === 'shared-groups';
36
+ const sharedQueueConfig = sharedGroups
37
+ ? { batchSize: concurrency, groupConcurrency }
38
+ : undefined;
39
+ registerWorkflowFunc('pikkuWorkflowOrchestrator', { func: pikkuWorkflowOrchestratorFunc }, 'pikku-workflow-orchestrator', sharedQueueConfig);
40
+ registerWorkflowFunc('pikkuWorkflowStepWorker', { func: pikkuWorkflowWorkerFunc }, 'pikku-workflow-step-worker', sharedQueueConfig);
41
+ const registerDedicatedQueues = (meta) => {
42
+ for (const [queueName, entry] of Object.entries(meta)) {
43
+ if (functions.has(entry.pikkuFuncId))
44
+ continue;
45
+ if (queueName.startsWith(ORCHESTRATOR_QUEUE_PREFIX)) {
46
+ registerWorkflowFunc(entry.pikkuFuncId, { func: pikkuWorkflowOrchestratorFunc }, queueName);
47
+ }
48
+ else if (queueName.startsWith(STEP_QUEUE_PREFIX)) {
49
+ registerWorkflowFunc(entry.pikkuFuncId, { func: pikkuWorkflowWorkerFunc }, queueName);
50
+ }
51
+ }
52
+ };
53
+ if (!sharedGroups) {
54
+ registerDedicatedQueues(pikkuState(null, 'queue', 'meta'));
55
+ const addons = pikkuState(null, 'addons', 'packages');
56
+ for (const [, addon] of addons ?? []) {
57
+ const addonQueueMeta = pikkuState(addon.package, 'queue', 'meta');
58
+ if (addonQueueMeta) {
59
+ registerDedicatedQueues(addonQueueMeta);
60
+ }
61
+ }
62
+ }
63
+ warnOnMissingDedicatedQueues(queueMeta, getLogger);
64
+ if (!functions.has('pikkuWorkflowSleeper')) {
65
+ addFunction('pikkuWorkflowSleeper', { func: pikkuWorkflowSleeperFunc });
66
+ }
67
+ if (!functionsMeta.pikkuWorkflowSleeper) {
68
+ functionsMeta.pikkuWorkflowSleeper = helperFunctionMeta('pikkuWorkflowSleeper');
69
+ }
70
+ };
71
+ const warnOnMissingDedicatedQueues = (queueMeta, getLogger) => {
72
+ const workflowCount = Object.keys(pikkuState(null, 'workflows', 'meta') ?? {}).length;
73
+ const dedicatedQueues = Object.keys(queueMeta).filter((name) => name.startsWith(ORCHESTRATOR_QUEUE_PREFIX)).length;
74
+ if (workflowCount > 0 && dedicatedQueues === 0) {
75
+ getLogger()?.warn?.(`[pikku] ${workflowCount} workflow(s) registered but no per-workflow orchestrator queues were found in queue meta. ` +
76
+ `All workflows will share a single orchestrator queue, where one slow step blocks every other workflow behind it. ` +
77
+ `Check that the generated bootstrap imports the queue-workers meta (pikku-queue-workers-wirings-meta.gen.js).`);
78
+ }
79
+ };
@@ -0,0 +1,68 @@
1
+ import type { Logger } from '../../services/logger.js';
2
+ /**
3
+ * Per-process, advisory record of when a run may next be re-dispatched.
4
+ *
5
+ * Keyed by run rather than by step because the run is the unit of re-drive:
6
+ * `resumeWorkflow` replays the whole run and re-dispatches every step still
7
+ * owed a job, so holding off a single step while resuming its run would
8
+ * suppress nothing.
9
+ *
10
+ * Losing this on restart costs extra dispatches, never correctness.
11
+ */
12
+ export declare class RedispatchBackoff {
13
+ private readonly eligibleAt;
14
+ private readonly delays;
15
+ isEligible(runId: string, now: number): boolean;
16
+ note(runId: string, now: number): void;
17
+ }
18
+ type SweepDeps = {
19
+ resume: (runId: string) => Promise<void>;
20
+ logger?: Logger;
21
+ };
22
+ /**
23
+ * Re-drive runs whose next move was lost, and report which were resumed.
24
+ *
25
+ * Arming a step is two writes to two systems — the step row, then the queue or
26
+ * scheduler job — so a process that dies between them leaves a run that is
27
+ * `running` with nothing in flight. Nothing notices: the run parks on a step
28
+ * that will never complete and never error, so it neither finishes nor fails.
29
+ * (Seen on a `workflow.sleep()`: a deploy restart landed between the sleep
30
+ * step's insert and its timer, parking the run permanently.)
31
+ *
32
+ * Replay is the recovery — `resumeWorkflow` re-orchestrates from persisted step
33
+ * state, and every settled step is memoized, so resuming a run that was not
34
+ * actually stuck costs an orchestration pass and changes nothing. That
35
+ * idempotence is what makes an idle-time heuristic safe here; a run that is
36
+ * legitimately mid-sleep is excluded anyway, since its step is `scheduled`.
37
+ */
38
+ export declare const sweepStalledRuns: (findStalledRunIds: (before: Date, limit: number) => Promise<string[]>, options: {
39
+ stalledAfterMs?: number;
40
+ limit?: number;
41
+ } | undefined, deps: SweepDeps) => Promise<{
42
+ resumed: string[];
43
+ }>;
44
+ /**
45
+ * Re-drive steps whose dispatch was lost, and report which runs were nudged.
46
+ *
47
+ * The step row is the outbox record and this is the relay. Age is the only
48
+ * signal available — a step `pending` because its dispatch was lost is
49
+ * indistinguishable from one whose job is merely still queued — so a step past
50
+ * `undispatchedAfterMs` is re-dispatched regardless, and correctness rests on
51
+ * the claim in `executeWorkflowStepInner` rather than on the guess being right.
52
+ * A redundant dispatch costs one queue message: the loser reads `running` and
53
+ * returns without invoking anything.
54
+ *
55
+ * Re-dispatches back off per run (doubling from 30s, capped at 10m) so a
56
+ * genuine queue backlog is not amplified by a tick that keeps firing at the
57
+ * steps the backlog is already delaying.
58
+ */
59
+ export declare const sweepUndispatchedSteps: (findUndispatchedSteps: (before: Date, limit: number) => Promise<Array<{
60
+ runId: string;
61
+ stepId: string;
62
+ }>>, backoff: RedispatchBackoff, options: {
63
+ undispatchedAfterMs?: number;
64
+ limit?: number;
65
+ } | undefined, deps: SweepDeps) => Promise<{
66
+ redispatched: string[];
67
+ }>;
68
+ export {};
@@ -0,0 +1,101 @@
1
+ import { DEFAULT_STALLED_RUN_LIMIT, DEFAULT_STALLED_RUN_MS, DEFAULT_UNDISPATCHED_STEP_LIMIT, DEFAULT_UNDISPATCHED_STEP_MS, REDISPATCH_BACKOFF_MAX_ENTRIES, REDISPATCH_BACKOFF_MAX_MS, REDISPATCH_BACKOFF_MS, } from './workflow-constants.js';
2
+ /**
3
+ * Per-process, advisory record of when a run may next be re-dispatched.
4
+ *
5
+ * Keyed by run rather than by step because the run is the unit of re-drive:
6
+ * `resumeWorkflow` replays the whole run and re-dispatches every step still
7
+ * owed a job, so holding off a single step while resuming its run would
8
+ * suppress nothing.
9
+ *
10
+ * Losing this on restart costs extra dispatches, never correctness.
11
+ */
12
+ export class RedispatchBackoff {
13
+ eligibleAt = new Map();
14
+ delays = new Map();
15
+ isEligible(runId, now) {
16
+ const at = this.eligibleAt.get(runId);
17
+ return at === undefined || at <= now;
18
+ }
19
+ note(runId, now) {
20
+ const previous = this.delays.get(runId);
21
+ const delay = Math.min(previous === undefined ? REDISPATCH_BACKOFF_MS : previous * 2, REDISPATCH_BACKOFF_MAX_MS);
22
+ // A run that settles is never returned again, so entries are only evicted
23
+ // by this bound — oldest first, which is also least recently re-dispatched.
24
+ if (this.eligibleAt.size >= REDISPATCH_BACKOFF_MAX_ENTRIES) {
25
+ const oldest = this.eligibleAt.keys().next();
26
+ if (!oldest.done) {
27
+ this.eligibleAt.delete(oldest.value);
28
+ this.delays.delete(oldest.value);
29
+ }
30
+ }
31
+ this.delays.set(runId, delay);
32
+ this.eligibleAt.set(runId, now + delay);
33
+ }
34
+ }
35
+ const resumeEach = async (runIds, { resume, logger }, failure) => {
36
+ const succeeded = [];
37
+ for (const runId of runIds) {
38
+ try {
39
+ await resume(runId);
40
+ succeeded.push(runId);
41
+ }
42
+ catch (err) {
43
+ // One unresumable run must not stop the sweep from recovering the rest.
44
+ logger?.error(failure(runId, err instanceof Error ? err.message : String(err)));
45
+ }
46
+ }
47
+ return succeeded;
48
+ };
49
+ /**
50
+ * Re-drive runs whose next move was lost, and report which were resumed.
51
+ *
52
+ * Arming a step is two writes to two systems — the step row, then the queue or
53
+ * scheduler job — so a process that dies between them leaves a run that is
54
+ * `running` with nothing in flight. Nothing notices: the run parks on a step
55
+ * that will never complete and never error, so it neither finishes nor fails.
56
+ * (Seen on a `workflow.sleep()`: a deploy restart landed between the sleep
57
+ * step's insert and its timer, parking the run permanently.)
58
+ *
59
+ * Replay is the recovery — `resumeWorkflow` re-orchestrates from persisted step
60
+ * state, and every settled step is memoized, so resuming a run that was not
61
+ * actually stuck costs an orchestration pass and changes nothing. That
62
+ * idempotence is what makes an idle-time heuristic safe here; a run that is
63
+ * legitimately mid-sleep is excluded anyway, since its step is `scheduled`.
64
+ */
65
+ export const sweepStalledRuns = async (findStalledRunIds, options, deps) => {
66
+ const before = new Date(Date.now() - (options?.stalledAfterMs ?? DEFAULT_STALLED_RUN_MS));
67
+ const runIds = await findStalledRunIds(before, options?.limit ?? DEFAULT_STALLED_RUN_LIMIT);
68
+ return {
69
+ resumed: await resumeEach(runIds, deps, (runId, detail) => `Failed to resume stalled workflow run ${runId}: ${detail}`),
70
+ };
71
+ };
72
+ /**
73
+ * Re-drive steps whose dispatch was lost, and report which runs were nudged.
74
+ *
75
+ * The step row is the outbox record and this is the relay. Age is the only
76
+ * signal available — a step `pending` because its dispatch was lost is
77
+ * indistinguishable from one whose job is merely still queued — so a step past
78
+ * `undispatchedAfterMs` is re-dispatched regardless, and correctness rests on
79
+ * the claim in `executeWorkflowStepInner` rather than on the guess being right.
80
+ * A redundant dispatch costs one queue message: the loser reads `running` and
81
+ * returns without invoking anything.
82
+ *
83
+ * Re-dispatches back off per run (doubling from 30s, capped at 10m) so a
84
+ * genuine queue backlog is not amplified by a tick that keeps firing at the
85
+ * steps the backlog is already delaying.
86
+ */
87
+ export const sweepUndispatchedSteps = async (findUndispatchedSteps, backoff, options, deps) => {
88
+ const before = new Date(Date.now() - (options?.undispatchedAfterMs ?? DEFAULT_UNDISPATCHED_STEP_MS));
89
+ const steps = await findUndispatchedSteps(before, options?.limit ?? DEFAULT_UNDISPATCHED_STEP_LIMIT);
90
+ const now = Date.now();
91
+ const runIds = new Set();
92
+ for (const { runId } of steps) {
93
+ if (!backoff.isEligible(runId, now))
94
+ continue;
95
+ backoff.note(runId, now);
96
+ runIds.add(runId);
97
+ }
98
+ return {
99
+ redispatched: await resumeEach(runIds, deps, (runId, detail) => `Failed to re-dispatch workflow run ${runId}: ${detail}`),
100
+ };
101
+ };
@@ -0,0 +1,54 @@
1
+ import type { PikkuRawWire, SerializedError } from '../../types/core.types.js';
2
+ import type { CoreWorkflow, PikkuWorkflowWire, StepState, WorkflowRun, WorkflowStatus, WorkflowStepOptions } from './workflow.types.js';
3
+ export interface RunLifecycleContext {
4
+ runId: string;
5
+ run: WorkflowRun;
6
+ workflowMeta: any;
7
+ workflow: CoreWorkflow;
8
+ wire: PikkuRawWire;
9
+ packageName: string | null;
10
+ }
11
+ /**
12
+ * The subset of the workflow service a step executor is allowed to call back
13
+ * into.
14
+ */
15
+ export interface WorkflowRunEngine {
16
+ inlineStep(runId: string, logicalStepName: string, fn: Function, stepOptions?: WorkflowStepOptions, data?: any, funcName?: string): Promise<any>;
17
+ updateRunStatus(runId: string, status: WorkflowStatus, output?: any, error?: SerializedError): Promise<void>;
18
+ onChildWorkflowFailed(run: WorkflowRun, error: unknown): Promise<void>;
19
+ verifyStepName(stepName: unknown): void;
20
+ }
21
+ /**
22
+ * Hooks a host may install to decorate runs it did not start. Used by the
23
+ * scenario service to attach its own per-run state.
24
+ */
25
+ export interface WorkflowRunExtension {
26
+ attachRunContext(runId: string, workflowMeta: any, options?: Record<string, any>): Promise<void>;
27
+ detachRunContext(runId: string): void;
28
+ decorateRunWire(wire: PikkuRawWire, context: {
29
+ runId: string;
30
+ workflowMeta: any;
31
+ workflowWire: PikkuWorkflowWire;
32
+ }): void;
33
+ decorateWorkflowWire(workflowWire: PikkuWorkflowWire, context: {
34
+ name: string;
35
+ runId: string;
36
+ rpcService: any;
37
+ addonNamespace?: string | null;
38
+ }): void;
39
+ onBeforeRunFunc(context: RunLifecycleContext): Promise<void>;
40
+ onAfterRunFunc(context: RunLifecycleContext, outcome: 'completed' | 'failed' | 'interrupted', failure: unknown): Promise<void>;
41
+ }
42
+ /**
43
+ * Per-run bookkeeping held only for the lifetime of an in-process execution.
44
+ */
45
+ export type RunContext = {
46
+ activeExecutions: number;
47
+ inline?: boolean;
48
+ ordinals: Map<string, number>;
49
+ lastStep?: string;
50
+ replay?: {
51
+ steps?: Map<string, StepState>;
52
+ run?: WorkflowRun;
53
+ };
54
+ };
@@ -0,0 +1,17 @@
1
+ import { ForbiddenError } from '../../errors/errors.js';
2
+ import type { CoreUserSession } from '../../types/core.types.js';
3
+ import type { WorkflowRunWire } from './workflow.types.js';
4
+ export declare class WorkflowRunForbiddenError extends ForbiddenError {
5
+ constructor();
6
+ }
7
+ /**
8
+ * A run started through a session records that session's user as its owner, and
9
+ * only that user may read it. Who may *answer* a run's approval gates is the
10
+ * gate's own declaration — see `approvers` on `workflow.approval()`.
11
+ *
12
+ * A run with no recorded owner — started by a trigger, a scheduler, or a route
13
+ * wired without auth — has nobody to compare a caller against, so ownership is
14
+ * not a control that exists for it. Gate those with `auth` or `permissions` on
15
+ * the entrypoint instead.
16
+ */
17
+ export declare const assertWorkflowRunOwner: (wire: WorkflowRunWire | undefined, session: CoreUserSession | undefined) => void;
@@ -0,0 +1,30 @@
1
+ import { ForbiddenError } from '../../errors/errors.js';
2
+ import { addError } from '../../errors/error-handler.js';
3
+ export class WorkflowRunForbiddenError extends ForbiddenError {
4
+ constructor() {
5
+ super('Not authorized to access this workflow run');
6
+ }
7
+ }
8
+ addError(WorkflowRunForbiddenError, {
9
+ status: 403,
10
+ message: 'Not authorized to access this workflow run.',
11
+ });
12
+ /**
13
+ * A run started through a session records that session's user as its owner, and
14
+ * only that user may read it. Who may *answer* a run's approval gates is the
15
+ * gate's own declaration — see `approvers` on `workflow.approval()`.
16
+ *
17
+ * A run with no recorded owner — started by a trigger, a scheduler, or a route
18
+ * wired without auth — has nobody to compare a caller against, so ownership is
19
+ * not a control that exists for it. Gate those with `auth` or `permissions` on
20
+ * the entrypoint instead.
21
+ */
22
+ export const assertWorkflowRunOwner = (wire, session) => {
23
+ const owner = wire?.pikkuUserId;
24
+ if (!owner) {
25
+ return;
26
+ }
27
+ if (!session?.userId || session.userId !== owner) {
28
+ throw new WorkflowRunForbiddenError();
29
+ }
30
+ };
@@ -0,0 +1,12 @@
1
+ import type { ApprovalStore } from './workflow-approval.js';
2
+ /** The durable step name a suspension point is recorded under. */
3
+ export declare const suspendStepNameFor: (reason: string) => string;
4
+ /** What the suspend gate needs from the workflow service. */
5
+ export type SuspendStore = Pick<ApprovalStore, 'getStepState' | 'insertStepState' | 'setStepRunning' | 'setStepResult'>;
6
+ /**
7
+ * Record a suspension point and unwind the run.
8
+ *
9
+ * A suspension that has already succeeded returns instead of throwing, so a
10
+ * replay walks past a gate the run has already passed through.
11
+ */
12
+ export declare const recordSuspension: (store: SuspendStore, runId: string, reason: string, stepName: string, fromStepName: string | undefined) => Promise<void>;
@@ -0,0 +1,33 @@
1
+ import { WorkflowSuspendedException } from './workflow-errors.js';
2
+ /** The durable step name a suspension point is recorded under. */
3
+ export const suspendStepNameFor = (reason) => `__workflow_suspend:${reason}`;
4
+ /**
5
+ * Record a suspension point and unwind the run.
6
+ *
7
+ * A suspension that has already succeeded returns instead of throwing, so a
8
+ * replay walks past a gate the run has already passed through.
9
+ */
10
+ export const recordSuspension = async (store, runId, reason, stepName, fromStepName) => {
11
+ const insert = () => store.insertStepState(runId, stepName, 'pikkuWorkflowSuspend', { reason }, undefined, fromStepName);
12
+ let stepState;
13
+ try {
14
+ stepState = await store.getStepState(runId, stepName);
15
+ }
16
+ catch {
17
+ stepState = await insert();
18
+ }
19
+ if (!stepState.stepId) {
20
+ stepState = await insert();
21
+ }
22
+ if (stepState.status === 'succeeded') {
23
+ return;
24
+ }
25
+ if (stepState.status === 'pending') {
26
+ await store.setStepRunning(stepState.stepId);
27
+ }
28
+ await store.setStepResult(stepState.stepId, {
29
+ reason,
30
+ suspendedAt: new Date().toISOString(),
31
+ });
32
+ throw new WorkflowSuspendedException(runId, reason);
33
+ };
@@ -2,7 +2,7 @@ import type { SerializedError, CommonWireMeta } from '../../types/core.types.js'
2
2
  import type { CorePikkuFunctionConfig, CorePikkuFunctionHook } from '../../function/functions.types.js';
3
3
  import type { GroupConcurrencyConfig } from '../queue/queue.types.js';
4
4
  export type { WorkflowService } from '../../services/workflow-service.js';
5
- export type { WorkflowStepOptions, WorkflowExpectEventuallyOptions, WorkflowExpectErrorOptions, WorkflowExpectServiceOptions, WorkflowWireDoRPC, WorkflowWireDoInline, WorkflowWireSleep, WorkflowWireSuspend, WorkflowWireApproval, WorkflowApprovalOptions, ApprovalOutcome, InputSource, OutputBinding, RpcStepMeta, SimpleCondition, Condition, BranchCase, BranchStepMeta, ParallelGroupStepMeta, FanoutStepMeta, ReturnStepMeta, InlineStepMeta, SleepStepMeta, CancelStepMeta, SuspendStepMeta, ApprovalStepMeta, SetStepMeta, SwitchCaseMeta, SwitchStepMeta, FilterStepMeta, ArrayPredicateStepMeta, ScenarioStepInvocation, ScenarioStepMeta, WorkflowStepMeta, WorkflowStepWire, PikkuWorkflowWire, PikkuScenarioWire, } from './dsl/workflow-dsl.types.js';
5
+ export type { WorkflowStepOptions, WorkflowWireDoRPC, WorkflowApprovalOptions, ApprovalOutcome, InputSource, OutputBinding, RpcStepMeta, Condition, BranchStepMeta, ParallelGroupStepMeta, FanoutStepMeta, ReturnStepMeta, InlineStepMeta, SleepStepMeta, CancelStepMeta, SuspendStepMeta, ApprovalStepMeta, SetStepMeta, SwitchCaseMeta, SwitchStepMeta, FilterStepMeta, ArrayPredicateStepMeta, ScenarioStepInvocation, ScenarioStepMeta, WorkflowStepMeta, WorkflowStepWire, PikkuWorkflowWire, PikkuScenarioWire, } from './dsl/workflow-dsl.types.js';
6
6
  export type { ScenarioStepPhase, ScenarioStepOptions, PikkuScenarioStepWire, PikkuBrowserWire, ScenarioBrowserProvider, } from './scenario-step.types.js';
7
7
  import type { WorkflowStepMeta } from './dsl/workflow-dsl.types.js';
8
8
  export interface WorkflowRunWire {
@@ -49,6 +49,13 @@ export interface WorkflowRun {
49
49
  export interface StepState {
50
50
  stepId: string;
51
51
  status: StepStatus;
52
+ /**
53
+ * The function the workflow dispatched this step with, recorded so a worker
54
+ * can reject a queue message naming anything else. `null` is a step with no
55
+ * function of its own (inline work); `undefined` is a store that never
56
+ * recorded one, and cannot be compared against.
57
+ */
58
+ rpcName?: string | null;
52
59
  result?: any;
53
60
  error?: SerializedError;
54
61
  attemptCount: number;
@@ -0,0 +1,22 @@
1
+ ---
2
+ type: decision
3
+ title: A non-streaming agent run registers with aiRunState on the same terms as a streaming one
4
+ description: Otherwise interruptAIAgent finds the run, passes the ownership check, then cannot stop it — and reports that as if the run were on another host
5
+ tags: core, ai-agent
6
+ ---
7
+
8
+ # A non-streaming agent run registers with `aiRunState` too
9
+
10
+ `runAIAgent` registers its run with `aiRunState` exactly as `streamAIAgent`
11
+ does, even though nothing is streaming and there is no channel to interrupt.
12
+
13
+ `interruptAIAgent` resolves a run through `aiRunState` first, checks ownership,
14
+ then looks for a local abort handle. A run that skipped registration is invisible
15
+ at the first step. A run that registered but has no handle is visible, passes the
16
+ ownership check, and then cannot be stopped — which the interrupt path reports as
17
+ "running on another instance". That message would be wrong and actively
18
+ misleading: the run is right here, and the deployment is single-instance.
19
+
20
+ **What this rules out:** treating registration as a streaming concern. It is an
21
+ addressability concern, and the two paths have to be addressable the same way for
22
+ the interrupt path's diagnosis to mean anything.
@@ -0,0 +1,20 @@
1
+ ---
2
+ type: decision
3
+ title: A resumed turn is as interruptible as the first one
4
+ description: It is the same person listening to the same voice, and after an approval it is where most of the reply actually gets spoken
5
+ tags: core, ai-agent
6
+ ---
7
+
8
+ # A resumed turn is as interruptible as the first one
9
+
10
+ Resuming after an approval registers for interruption on the same terms as the
11
+ original turn.
12
+
13
+ It is the same person listening to the same voice, so the reason to allow
14
+ interruption has not changed. And after an approval is precisely where most of
15
+ the reply gets spoken: an approved delete is followed by the agent describing
16
+ what it did, which is a normal thing for a listener to talk over.
17
+
18
+ **What this rules out:** treating the resume as a short continuation not worth
19
+ wiring for interruption. By volume of speech it is usually the larger half of
20
+ the exchange.
@@ -0,0 +1,21 @@
1
+ ---
2
+ type: decision
3
+ title: A scenario step's prose template is offered to a virtual user unfilled
4
+ description: A reporter fills placeholders from a run that happened; there is no run yet, and the filled form would answer the question the user is there to answer
5
+ tags: core, virtual-user
6
+ ---
7
+
8
+ # A scenario step's prose template is offered unfilled
9
+
10
+ When a scenario step becomes a catalogue entry, its prose template goes in with
11
+ its placeholders intact — braces and all.
12
+
13
+ A reporter fills those placeholders from a run that already happened. There is
14
+ no run yet at derivation time, so there is nothing to fill them from. More
15
+ importantly, filling them would answer the wrong question: "invites {email}"
16
+ tells the user to choose someone, where "invites ada@example.com" tells it whom
17
+ — and *whom* is the scenario author's answer, not something the virtual user
18
+ worked out.
19
+
20
+ **What this rules out:** substituting example or fixture values to make the
21
+ catalogue read more naturally. It reads better and tests less.
@@ -0,0 +1,21 @@
1
+ ---
2
+ type: decision
3
+ title: A virtual user decides whether to trust its notes once per turn, by one roll
4
+ description: The difference between the stale, newcomer and auditor dispositions is expressed as a single probability rather than as prose in each prompt
5
+ tags: core, virtual-user
6
+ ---
7
+
8
+ # A virtual user decides whether to trust its notes once per turn
9
+
10
+ Each turn the run makes one weighted decision: does this user act on what it
11
+ already recorded, or go and look again.
12
+
13
+ That single roll is where the dispositions differ. `stale` almost always trusts
14
+ its notes — that is what makes it stale. A `newcomer` has none to trust. An
15
+ `auditor` re-checks nearly everything, which is the entire point of an auditor.
16
+ Expressing it as one probability keeps the difference between those runs in one
17
+ readable place instead of spread through three prompts as English that drifts.
18
+
19
+ **What this rules out:** encoding "you are suspicious of your own notes" into
20
+ each disposition's prompt text, where the behaviour becomes a property of how
21
+ the model reads prose rather than something the run controls and can report.
@@ -0,0 +1,46 @@
1
+ ---
2
+ type: decision
3
+ title: A wall-clock threshold is a load test in disguise
4
+ description: The KEK derivation test asserted a fixed 50ms budget for work that took 10ms, which went red about one run in five once the suite was large enough to compete for the machine
5
+ tags: core, testing, crypto
6
+ ---
7
+
8
+ # A wall-clock threshold is a load test in disguise
9
+
10
+ `crypto-utils.test.ts` guards a real property: unwrapping N secrets must not
11
+ cost N KEK derivations. Deriving a KEK is a deliberately slow KDF, so doing it
12
+ per secret turns a 10ms operation into a 4-second one.
13
+
14
+ It guarded it with `assert.ok(elapsed < 50)`. Measured on this machine:
15
+
16
+ | | |
17
+ | --- | --- |
18
+ | one `deriveKEK` | ~86ms |
19
+ | 50 `envelopeDecrypt` | 10.8ms |
20
+ | the old 50ms threshold | 4.6x headroom |
21
+ | the failure it guards against | ~4281ms — a **396x** separation |
22
+
23
+ So the assertion had 4.6x of margin to detect a 396x regression. Everything
24
+ between those two numbers was noise, and once core's suite reached ~2000 tests
25
+ competing for the same cores, a 10ms window drifting past 50ms became routine:
26
+ roughly one run in five went red, always on a machine where nothing was wrong.
27
+
28
+ **Calibrate against a measurement taken in the same run.** The test already
29
+ derives a KEK, so timing that call is free, and the assertion becomes
30
+ `elapsed < oneDerivation` — unwrapping all fifty must cost less than deriving
31
+ once. Under load both sides slow down together, so the ratio holds.
32
+
33
+ **What this rules out:** raising the constant. 100ms or 200ms buys a smaller
34
+ flake rate and the same class of bug, and it drifts again the next time the
35
+ suite grows or CI moves to a noisier runner. Any assertion of the form
36
+ "operation X takes less than N milliseconds" has this problem; express it as a
37
+ ratio against something measured alongside it.
38
+
39
+ Worth knowing: `envelopeDecrypt(kek: CryptoKey, …)` takes the derived key as a
40
+ parameter, so it *cannot* derive one — the property is already enforced by the
41
+ signature, and [[the-api-report-pins-members-not-just-names]] would catch a
42
+ change to it. The test is defence in depth, which is a reason to make it cheap
43
+ and quiet rather than to delete it.
44
+
45
+ Related: [[an-unref-d-timer-cannot-be-awaited-under-node-test]], the other
46
+ source of nondeterminism found in the same pass.