@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,24 @@
1
+ ---
2
+ type: decision
3
+ title: `given` and `when` are sugar for each other; `then` is not
4
+ description: The phase decides whether a step's bindings are alternatives or witnesses, so the same step function called as `when` and as `then` runs differently
5
+ tags: core, workflow
6
+ ---
7
+
8
+ # `given` and `when` are sugar for each other; `then` is not
9
+
10
+ All three run a named `pikkuScenarioStep` as one durable step. `given` and
11
+ `when` differ only in the prose a reporter renders — the step behaves
12
+ identically either way.
13
+
14
+ `then` is a different operation. The phase is what decides how a step's surface
15
+ bindings are treated: for an action, the bindings are *alternatives* and one is
16
+ chosen; for an assertion, they are *witnesses* and every applicable one runs and
17
+ must agree. So the same step function invoked as `when` and as `then` executes a
18
+ different number of times against a different number of surfaces.
19
+
20
+ See `resolveScenarioSurfaces` for the resolution itself.
21
+
22
+ **What this rules out:** collapsing the three into one call with a cosmetic
23
+ label, or letting a caller pass the phase as data. The phase changes execution,
24
+ so it has to be visible at the call site.
@@ -0,0 +1,34 @@
1
+ ---
2
+ type: decision
3
+ title: sideEffects is an allowlist, because core genuinely has some
4
+ description: The error registry is built by addError calls that run on import, so claiming sideEffects:false would let a bundler drop it and leave getErrorResponse unable to find any error
5
+ tags: core, packaging
6
+ ---
7
+
8
+ # `sideEffects` is an allowlist, not `false`
9
+
10
+ Without a `sideEffects` field a bundler must assume every module in the package
11
+ does something merely by being imported, so it cannot drop any of them. That is
12
+ the state core shipped in, and it costs every bundled consumer the whole package.
13
+
14
+ The reflex fix — `"sideEffects": false` — would be untrue. Five modules run code
15
+ at import, 59 top-level calls between them, and every one is `addError(...)`
16
+ registering an error class in the runtime registry:
17
+
18
+ - `errors/errors.js` (44)
19
+ - `wirings/workflow/pikku-scenario-service.js` (6)
20
+ - `wirings/workflow/workflow-errors.js` (5)
21
+ - `wirings/rpc/rpc-runner.js` (3)
22
+ - `wirings/rpc/remote-addon-auth.js` (1)
23
+
24
+ A bundler that dropped `errors/errors.js` because nothing imported a binding
25
+ from it would leave `getErrorResponse` unable to map any error to a status. The
26
+ app would build, and every error would come back as a generic 500.
27
+
28
+ So the field names those five exactly. Everything else can be tree-shaken.
29
+
30
+ **What this rules out:** flipping this to `false` as a performance change, and
31
+ adding a module-level `addError` (or any other registration) without adding the
32
+ module here. `side-effects-are-declared.test.ts` walks the source for top-level
33
+ calls and fails in both directions — a module with side effects missing from the
34
+ list, and a listed module that no longer has any.
@@ -0,0 +1,24 @@
1
+ ---
2
+ type: decision
3
+ title: Speech synthesis picks a voice per sentence but announces a limitation once
4
+ description: A bilingual reply should speak the half it can, and repeating the notice for every sentence would bury the reply itself
5
+ tags: core, ai-agent
6
+ ---
7
+
8
+ # Speech synthesis picks a voice per sentence, and warns once per reply
9
+
10
+ `voiceOutput` checks the script of each sentence separately, but emits any
11
+ "cannot speak this" notice only once for the whole reply.
12
+
13
+ Per sentence is what makes the voice correct. A reply that answers in English
14
+ and then quotes a Chinese title is two sentences in two scripts, and each is
15
+ synthesized in the voice its own script needs. Checking once for the whole reply
16
+ would pick one voice and mispronounce the other half.
17
+
18
+ Announcing once is what keeps the reply audible. A bilingual answer should still
19
+ speak the part it can, and repeating the notice for every sentence of a long
20
+ reply buries the answer under its own caveats.
21
+
22
+ **What this rules out:** moving the script check up to the reply level to save
23
+ work, and moving the notice down to the sentence level for consistency with it.
24
+ The two belong at different granularities on purpose.
@@ -0,0 +1,22 @@
1
+ ---
2
+ type: decision
3
+ title: The actor's instructions say the word "json" because a degraded gateway demands it
4
+ description: A gateway that cannot take a JSON schema falls back to OpenAI's json_object mode, which refuses any request whose prompt does not contain the word
5
+ tags: core, actor-flow
6
+ ---
7
+
8
+ # The actor's instructions say the word "json"
9
+
10
+ Every call the actor makes wants a schema'd object back. A gateway that cannot
11
+ accept a JSON *schema* degrades to OpenAI's `json_object` response mode, and
12
+ that mode refuses the request outright — a hard API error, not a bad answer —
13
+ unless the literal word "json" appears somewhere in the prompt.
14
+
15
+ The word is therefore in the shared instruction block, once. All three call
16
+ sites (turn, approvals, verdict) build on that block, so one mention covers
17
+ them, and it costs nothing on providers that never needed telling.
18
+
19
+ **What this rules out:** tidying the instructions by removing a word that reads
20
+ as redundant. It is load-bearing for exactly one provider configuration, and its
21
+ absence fails the request rather than degrading the output — so the failure will
22
+ not look like a prompt problem.
@@ -0,0 +1,26 @@
1
+ ---
2
+ type: decision
3
+ title: The agent `done` event goes through the middleware and is awaited
4
+ description: `done` is the only end-of-reply signal a stream hook gets, and buffering hooks flush on it — sending it raw discards work already paid for
5
+ tags: core, ai-agent
6
+ ---
7
+
8
+ # The agent `done` event goes through the middleware and is awaited
9
+
10
+ Both the first turn and the post-approval resume send their terminating `done`
11
+ event through the stream middleware rather than straight at the channel, and
12
+ await it.
13
+
14
+ `done` is the only signal a stream hook receives that the reply is over. The
15
+ hooks that buffer need it to flush: `voiceOutput` holds a trailing fragment that
16
+ never reached a full stop, and waits on audio it has already asked the provider
17
+ to synthesize and already been billed for. Sending `done` raw skips those hooks
18
+ entirely, and the `close()` immediately after throws the buffered work away.
19
+
20
+ It matters more on the resume path than the first turn. After an approval, most
21
+ of what gets spoken is the agent describing what it just did — so a dropped
22
+ flush silences the larger half of the reply.
23
+
24
+ **What this rules out:** treating the terminating event as a special case that
25
+ can bypass the chain because "nothing comes after it". Something does: the
26
+ flush.
@@ -0,0 +1,43 @@
1
+ ---
2
+ type: decision
3
+ title: The API report pins members, because the export list only pins names
4
+ description: public-surface.json catches an export appearing or vanishing; it cannot see a method added to an interface, which is the change that breaks a consumer's build
5
+ tags: core, api
6
+ ---
7
+
8
+ # The API report pins members, not just names
9
+
10
+ `public-surface.json` records `Object.keys(module)` for every entry point. That
11
+ is a real guard — it caught a new `./node-host-resolver` subpath and five
12
+ renames during a rebase — but it only sees *names*.
13
+
14
+ It cannot see:
15
+
16
+ - a method added to `MetaService`, which has 32 members
17
+ - a field on `ChannelMeta` becoming required
18
+ - a parameter added to a method on `PikkuWorkflowService`, which has 104
19
+ - any change at all to an `interface`, since types are erased before
20
+ `Object.keys` can enumerate them
21
+
22
+ Those are the changes that break a consumer's build, and there are far more of
23
+ them than there are exports — roughly two members for every name. The report's
24
+ own summary counts both, live, so the figure is not repeated here to rot: an
25
+ earlier hand-count in this note was already wrong by 40 members within a day of
26
+ being written.
27
+
28
+ Demonstrated rather than assumed: adding `probeAddedMember(): void` to
29
+ `MetaService` leaves `public-surface.test.ts` passing 3/0, and fails
30
+ `api-report.test.ts`.
31
+
32
+ So `api-report.md` is generated from the type checker — every exported symbol
33
+ with its full signature, interfaces and classes rendered as their declarations
34
+ so member changes land in the diff. It is committed, and the test fails when
35
+ the code and the report disagree. Regenerate with `yarn api-report`.
36
+
37
+ **What this rules out:** treating `public-surface.json` as the API guard. It
38
+ guards the front door; this guards the rooms. Both are needed, and the export
39
+ list is the cheaper of the two to run — which is why it stayed.
40
+
41
+ Re-exports are resolved through `getAliasedSymbol` before the signature is
42
+ taken. Without that, 624 of the symbols reported as `any`, which would have
43
+ pinned nothing while looking like it did.
@@ -0,0 +1,58 @@
1
+ ---
2
+ type: decision
3
+ title: The ecosystem entry point carries the adapter surface, so the root can promise stability
4
+ description: runPikkuFunc and the singleton-service accessors are what a runtime adapter reaches for; their signatures move, and the package root should not promise otherwise
5
+ tags: core, api
6
+ ---
7
+
8
+ # The ecosystem entry point carries the adapter surface
9
+
10
+ `@pikku/core` has two kinds of consumer. An **application** writes functions and
11
+ wires them: `pikkuFunc`, `wireHTTP`, `PikkuWire`, the error catalogue. A
12
+ **runtime adapter** — the express, fastify, uWS, Next, Cloudflare and Lambda
13
+ packages, plus tests — reaches past that to drive the runner itself.
14
+
15
+ Those two surfaces have measurably different stability. Over the nine months
16
+ since `PikkuWire` was introduced, its app-facing shape took 22 additions and
17
+ **zero removals** — every original field is still there. Over the same period
18
+ `runPikkuFunc` was reshaped: `allServices`/`getAllServices` became
19
+ `singletonServices`/`createWireServices`, the positional arguments became a
20
+ `wireType`/`wireId` pair plus an options object, and `session` became `auth`.
21
+
22
+ Both were exported from the package root. A compatibility promise made there
23
+ would therefore have been a promise about the weaker of the two.
24
+
25
+ So the adapter surface moved to `@pikku/core/ecosystem`: `runPikkuFunc`, the
26
+ singleton-service accessors, and the registration calls the code generator emits
27
+ — `addFunction`, `addGlobalMiddleware`, `addMiddleware` — alongside `pikkuState`
28
+ and `httpRouter`, which were already separated for the same reason. The wire
29
+ runners (`runQueueJob`, `runScheduledTask`, `runCLICommand`, `runMCP*`) left the
30
+ root too, but for a different reason: they already lived on their wire subpaths,
31
+ which is where every runtime package imports them from, so the root copies were
32
+ redundant. 108 runtime exports became 95.
33
+
34
+ **The name took two attempts.** Not `/internal`: the generated bootstrap
35
+ imports from here, so the specifier appears in the user's own `.pikku`
36
+ directory, and telling someone they are touching internals when the code
37
+ generator put it there is both wrong and self-defeating — it could never be
38
+ broken anyway. Not `/runtime` either: that reads as runtime-versus-compile-time,
39
+ i.e. _the real API_, which is the opposite of the intended signal, and
40
+ `packages/runtimes/*` already claims the word while `packages/cli` is the
41
+ largest consumer here at 22 files.
42
+
43
+ `/ecosystem` says the true thing: you are building a package in the Pikku
44
+ ecosystem — a runtime, a service, an addon, the CLI. `/internal` remains as an
45
+ alias to the same module because the pinned bootstrap CLI still emits it.
46
+
47
+ Nothing was deleted. What stayed public is what applications actually
48
+ hand-write: `addTagMiddleware` (8 files), `addGlobalPermission`, `fetch` (31),
49
+ `wireAddon`, and the authoring helpers.
50
+
51
+ **What this rules out:** re-exporting anything from `/internal` at the package
52
+ root for convenience. The split is the whole point — `/internal` may change in
53
+ any release, and the root may not. It also rules out treating `/internal` as
54
+ private: it is a published entry point that runtime authors are expected to use,
55
+ just without the compatibility guarantee.
56
+
57
+ `public-surface.json` pins both, so moving a symbol across the line is a visible
58
+ diff rather than an accident.
@@ -0,0 +1,40 @@
1
+ ---
2
+ type: decision
3
+ title: The middleware resolution cache is deliberately unbounded
4
+ description: Its keyspace is the set of registered wires, not request traffic, and middleware is dynamic — so eviction would buy nothing and cost the dedupe guarantee
5
+ tags: core, middleware
6
+ ---
7
+
8
+ # The middleware resolution cache is deliberately unbounded
9
+
10
+ `middlewareCache` in `packages/core/src/middleware-runner.ts` is a plain
11
+ `Record<PikkuWiringTypes, Record<string, readonly CorePikkuMiddleware[]>>` with
12
+ no size limit and no eviction. That reads like an unbounded-growth bug and is
13
+ not one.
14
+
15
+ **The keyspace is finite by construction.** The cache is keyed by wire type and
16
+ wire id, and wire ids come from registration, not from traffic — an HTTP route
17
+ pattern, a channel name, a queue name. A million requests to the same route
18
+ produce one entry. Nothing a caller sends creates a key: a channel message key
19
+ is `${channel}:${routingProperty}:${routerValue}`, and `routerValue` is only
20
+ reached after matching `routes[routerValue]`, so an unregistered value never
21
+ gets that far.
22
+
23
+ **Middleware is dynamic, and the cache is invalidated rather than aged out.**
24
+ `addGlobalMiddleware` and `addTagMiddleware` can run after startup, and dev
25
+ hot-reload rewires wholesale — so correctness comes from `clearMiddlewareCache()`
26
+ at the points where the middleware set actually changes, not from entries
27
+ expiring. An LRU would evict entries that are still current while doing nothing
28
+ about entries that are stale.
29
+
30
+ **Eviction would also weaken the chain itself.** `combineMiddleware` returns a
31
+ `freezeDedupe`d array, and dedupe is by function identity — a middleware
32
+ reachable through both a tag group and a direct wire registration runs exactly
33
+ once. Callers hold that array. Recomputing it under memory pressure hands out a
34
+ second array for the same wire, which is churn at best and, for anything
35
+ comparing chains by identity, a silent behaviour change.
36
+
37
+ **What this rules out:** adding a `MIDDLEWARE_CACHE_MAX` with LRU eviction as a
38
+ denial-of-service mitigation. There is no traffic-driven growth to mitigate. If
39
+ a future change ever keys this cache by something a caller controls, that is the
40
+ bug — bound the key, not the cache.
@@ -0,0 +1,40 @@
1
+ ---
2
+ type: decision
3
+ title: The per-invocation rpc view is a class, because an object literal with a getter is slow to build
4
+ description: An accessor declared on an object literal is defined per instance, which drops the literal off V8's fast construction path — measured at 1.15µs against 0.47µs, on every request
5
+ tags: core, rpc, performance
6
+ ---
7
+
8
+ # The per-invocation rpc view is a class
9
+
10
+ `getContextRPCService` builds `wire.rpc` once per invocation. It used to return
11
+ an object literal, and that literal declared `get agent()` so the agent facade
12
+ stayed lazy — most requests never touch it, and reading it builds five closures.
13
+
14
+ The laziness is right. Declaring the accessor *on the literal* was not: a
15
+ literal containing an accessor needs a real property descriptor per instance,
16
+ which takes it off V8's fast object-literal construction path and slows the
17
+ whole object, not just the accessor.
18
+
19
+ Measured with `benchmarks/bench-profile-granular.ts`, three runs each:
20
+
21
+ | shape | per call |
22
+ | --- | --- |
23
+ | object literal with `get agent()` | 1.106 / 1.433 / 1.147 µs |
24
+ | class with `agent` on the prototype | 0.523 / 0.461 / 0.525 µs |
25
+
26
+ Roughly 2.4×, well outside the run-to-run variance, on a path every request
27
+ takes. On the same machine a full `fetchData` measures 12–19µs, so this was on
28
+ the order of a tenth of a request spent constructing one object.
29
+
30
+ The obvious alternative is worse. Making `agent` an eager property removes the
31
+ accessor but builds those five closures unconditionally, and measured *slower*
32
+ than the original at 1.967µs. A prototype accessor is the only shape that keeps
33
+ the laziness and the fast construction path.
34
+
35
+ **What this rules out:** "simplifying" `ContextRPCView` back to an object
36
+ literal. It reads as the plainer option and costs double. It also rules out
37
+ trusting a synthetic microbenchmark here — a standalone benchmark of these
38
+ shapes pointed at the eager version, because its stand-in for the agent getter
39
+ was too cheap to represent the real one. The numbers above come from the
40
+ profiler running the real code.
@@ -0,0 +1,28 @@
1
+ ---
2
+ type: decision
3
+ title: The persona runtime is exported from @pikku/core/persona, never from services
4
+ description: Those values reach the actor-flow and agent runners, which no production server runs — and an unbundled deploy loads whatever the import graph names
5
+ tags: core, services
6
+ ---
7
+
8
+ # The persona runtime lives on `@pikku/core/persona`
9
+
10
+ `HttpPersona`, `createHttpPersonas`, `readScenarioHttpResponse` and
11
+ `postScenarioJson` are exported from `@pikku/core/persona`, not from
12
+ `@pikku/core/services`. Their *types* are re-exported freely — TypeScript erases
13
+ those and they cost a bundle nothing.
14
+
15
+ The values are different. They reach `http-personas`, which reaches the
16
+ actor-flow conversation runner, which reaches the agent runner: an entire
17
+ scenario and virtual-user runtime that no production server executes. An
18
+ unbundled Node or Lambda deploy does no tree-shaking — it loads whatever the
19
+ import graph names — so exporting one value from the services barrel pulls all
20
+ of it into every application that imports a service.
21
+
22
+ This is enforced, not merely intended: `production-barrels-stay-lean.test.ts`
23
+ walks the value-import graph from `services/index.ts` and
24
+ `wirings/workflow/index.ts` and fails if any of those modules is reachable.
25
+
26
+ **What this rules out:** re-exporting a persona value from the services barrel
27
+ for convenience, and "just one small helper" — the graph is transitive, and one
28
+ value is enough to pull the whole runtime.
@@ -0,0 +1,25 @@
1
+ ---
2
+ type: decision
3
+ title: A voice turn's transcript is sent before the run starts, on the raw channel
4
+ description: The client does not know what it said, and an answer starts streaming within a few hundred milliseconds — a question arriving after its answer reads as the wrong question
5
+ tags: core, ai-agent
6
+ ---
7
+
8
+ # A voice turn's transcript is sent ahead of the run
9
+
10
+ A voice client sends audio, so it does not know what it said. Until the
11
+ transcript reaches it, its own message renders as a blank bubble.
12
+
13
+ The event is sent *before* the run rather than alongside it because the answer
14
+ begins streaming within a few hundred milliseconds. Sent concurrently, the
15
+ transcript routinely lands after the first tokens of its own answer — and a
16
+ question that appears beneath its answer reads as a question about something
17
+ else entirely.
18
+
19
+ It goes on the raw channel rather than through the stream middleware because it
20
+ is not part of the reply: it is what the user said, and the stream hooks are
21
+ built to transform what the agent says.
22
+
23
+ **What this rules out:** folding the transcript into the run's event stream for
24
+ uniformity. Ordering is the whole point, and the stream is where ordering is
25
+ least under this code's control.
@@ -0,0 +1,21 @@
1
+ ---
2
+ type: decision
3
+ title: The catalogue is the only gate on what a virtual user may call
4
+ description: Narrowing happens once at derivation, so there is no second check in the run loop that could drift out of step with it
5
+ tags: core, virtual-user
6
+ ---
7
+
8
+ # The catalogue is the only gate on what a virtual user may call
9
+
10
+ What a virtual user may invoke is decided once, when the catalogue is narrowed
11
+ for its disposition: a read-only disposition is never offered a mutation, and an
12
+ approval-gated endpoint is never offered at all.
13
+
14
+ The run loop then indexes into that catalogue and calls what it finds. There is
15
+ deliberately no second permission check at call time, because a second check is
16
+ a second thing to keep correct — and the failure mode of the two disagreeing is
17
+ that one of them silently stops mattering.
18
+
19
+ **What this rules out:** adding a "defence in depth" guard in the run loop. It
20
+ would not be defence in depth; it would be a duplicate of the derivation rules,
21
+ maintained separately, and the one that runs first wins.
@@ -0,0 +1,22 @@
1
+ ---
2
+ type: decision
3
+ title: The worker disposition is the only one not testing anything
4
+ description: It is doing the job, so it abandons rarely, runs cool, and is told to stop and say so rather than guess
5
+ tags: core, virtual-user
6
+ ---
7
+
8
+ # The worker disposition is the only one not testing anything
9
+
10
+ Every other disposition exists to probe the product. The worker exists to *use*
11
+ it, which changes every parameter: it abandons rarely, runs at a low temperature,
12
+ and is instructed to stop and report rather than guess its way past an obstacle.
13
+
14
+ The failure mode being guarded against is different too. For the probing
15
+ dispositions the risk is a missed bug. For this one it is a wrong action nobody
16
+ asked for — a real mutation performed because the model improvised when it
17
+ should have stopped.
18
+
19
+ **What this rules out:** tuning this disposition toward the others for
20
+ consistency, in particular raising its temperature or its tolerance for
21
+ guessing. Those settings are what make the others useful and what would make this
22
+ one dangerous.
@@ -0,0 +1,25 @@
1
+ ---
2
+ type: decision
3
+ title: Thread history records what the model was asked, which for a spoken turn is the transcript
4
+ description: The wire carried a base64 audio blob; persisting it writes megabytes of unreadable data and loses the only readable record of the turn
5
+ tags: core, ai-agent
6
+ ---
7
+
8
+ # Thread history records the transcript, not the audio that arrived
9
+
10
+ What goes into thread history is what the model was actually asked. For a typed
11
+ turn that is what arrived over the wire. For a spoken turn it is not: the wire
12
+ carried a base64 audio blob, and `voiceInput` replaced it with a transcript
13
+ before the model ever saw it.
14
+
15
+ Persisting the blob would write megabytes of unreadable data into the history
16
+ *and* discard the only readable record of what was said — the worst of both.
17
+
18
+ Both the streaming and non-streaming paths do this, and both check identity
19
+ rather than assuming: a middleware is free to rewrite the message list into
20
+ something with no relation to this turn, and in that case there is no transcript
21
+ to substitute and the list is persisted as-is.
22
+
23
+ **What this rules out:** persisting the inbound message verbatim on the grounds
24
+ that it is the ground truth. For voice it is the least useful representation
25
+ available, and the substitution is exactly what makes the history readable.
@@ -0,0 +1,115 @@
1
+ ---
2
+ type: decision
3
+ title: pikku validate runs every applicable check, it does not detect a project kind
4
+ description: one `pikku validate` replaces `pikku workspace validate`; checks declare a precondition and run wherever it holds, because the repos that matter are an app and a pile of addons at the same time
5
+ tags: cli, validate, addons
6
+ ---
7
+
8
+ # pikku validate runs every applicable check, it does not detect a project kind
9
+
10
+ `pikku workspace validate` was named after one of the things a repo can be.
11
+ Adding a second — "is this addon publishable" — invited a second command,
12
+ `pikku addon validate`, and that is where the naming falls apart: the addons
13
+ repo is a workspace *containing* 217 publishable addons. Standing at its root,
14
+ `pikku addon validate` would have to refuse or sweep every package, at which
15
+ point it is doing the workspace thing anyway. The two nouns were never
16
+ alternatives.
17
+
18
+ So there is one command, and it does not detect a kind and dispatch. Each check
19
+ declares the condition under which it means anything, and runs wherever that
20
+ condition holds:
21
+
22
+ - `app-project` — a `pikku.config.json` with no `types/application-types.d.ts`
23
+ beside it. The marker matters: an addon carries a `pikku.config.json` too, and
24
+ the app-shaped checks would report every app convention it has no reason to
25
+ follow, starting with a `packages/functions/` it will never have.
26
+ - `addon-package` — a non-private package that ships generated pikku output.
27
+ Having a `.pikku` directory is not enough on its own: an app's
28
+ `packages/functions` has one, and it is codegen for that app rather than
29
+ something anyone installs. The signal is a `files`/`exports` that carries it
30
+ into the tarball.
31
+
32
+ Detection-as-dispatch fails on the first repo that is two things at once.
33
+ Detection-as-precondition composes: the addons repo plans 217 addon checks and
34
+ no app check; the online-shop template plans one app check and no addon check;
35
+ a repo that is both plans both.
36
+
37
+ ## Targets come from walking, not from `workspaces`
38
+
39
+ The field is an array in one repo and `{ packages: [...] }` in the next, spells
40
+ the same layout as `packages/**` or as six explicit globs, and a package that is
41
+ real but unlisted is exactly the kind of thing worth validating. So discovery
42
+ walks the tree for `package.json` files and lets the preconditions decide.
43
+ Over-collecting targets is free; guessing the glob dialect is not.
44
+
45
+ ## A run that checked nothing must not print a tick
46
+
47
+ The failure mode of auto-detection is that finding nothing looks exactly like
48
+ finding everything and liking it. When no check applies, the renderer says so
49
+ and names what it looked for, rather than reporting success.
50
+
51
+ ## The check the addon case exists for
52
+
53
+ Every relative import in a shipped generated file must resolve to a file that is
54
+ itself shipped — stated as a property, not as "the build script must copy
55
+ `types/`". The build script is not the only way to get this wrong and will not
56
+ always be a `cp`; the generated files already declare what they need.
57
+
58
+ That property was false in all 217 published addons. `cp -r .pikku dist/`
59
+ shipped the generated output but not the `types/application-types.d.ts` it
60
+ imports, and `tsc` never emits a hand-written `.d.ts` to `outDir`, so nothing
61
+ put it in `dist`. Consumers got 8 unresolved-module errors plus 6
62
+ `RequiredServices does not satisfy CoreSecretlessSingletonServices` follow-ons
63
+ inside `node_modules`, for merely depending on an addon. The scaffold template
64
+ had been right the whole time — `templates/function-addon` copies `types/` —
65
+ and the published packages had drifted from it with nothing watching.
66
+
67
+ ## The check walks whichever generated directory a package ships
68
+
69
+ `dist/.pikku` is the target shape (below), but the check cannot assume it: the
70
+ shape is what this work moved addons *to*, and a package that has not moved —
71
+ or was published before it did — still carries `.pikku` at the root, listed in
72
+ `files` and mapped by `exports` as `./.pikku/*`. There it is a public entry
73
+ point rather than build input, and its imports climb one level fewer — to
74
+ `<pkg>/src` and `<pkg>/types` — where the copy under `dist` reaches
75
+ `<pkg>/dist/src` and `<pkg>/dist/types`. Two roots, two ways to fall outside
76
+ the tarball, so the check walks whichever ones are actually shipped rather than
77
+ the one it would prefer to find.
78
+
79
+ `exports` and `imports` get the same treatment one level up: a target outside
80
+ the published file set is the same defect, and the one the import walk cannot
81
+ see, because nothing inside `dist/.pikku` mentions it. That is how a `#pikku`
82
+ still pointing at `./.pikku/pikku-types.gen.ts` hides.
83
+
84
+ ## An addon's entry points all resolve under dist
85
+
86
+ Everything an installed package reaches for lives under `dist`; the addon's own
87
+ build resolves `#pikku` through tsconfig `paths`, so nothing in `exports` or
88
+ `imports` has to point into the source tree, and `files` is just `["dist"]`.
89
+ The alternative — shipping `src/` and `types/` at the root so the existing paths
90
+ resolve as written — publishes TypeScript source and a second copy of
91
+ everything `dist` already has.
92
+
93
+ Checking only `dist` is what let the root `.pikku` stay broken through the first
94
+ round of fixes: in the published `@pikku/addon-assemblyai@0.1.4` tarball, `.pikku`
95
+ ships `.gen.ts` files importing a `../../src/` and `../types/` that the tarball
96
+ does not contain, and the `pikku-bootstrap.gen.js` that consumers import through
97
+ that subpath exists only under `dist`. Everything resolved locally through the
98
+ workspace link and none of it resolved on install.
99
+
100
+ ## Only a package that publishes gets the dist shape
101
+
102
+ The shape describes a tarball, so it means nothing for a `private` package —
103
+ and applying it there actively breaks: `exports` *is* enforced across a
104
+ workspace link, so repointing a private fixture at `dist` makes every consumer
105
+ demand a directory that only a build produces. The three `verifiers/db-schema`
106
+ and `verifiers/addon-registry` fixtures have no build script at all, so `dist`
107
+ never exists for them; the five `e2e/packages` addons build, but their metadata
108
+ is read straight from the source tree before any build has run. Repointing all
109
+ eight left the db-schema verifier unable to resolve
110
+ `dist/.pikku/db/pikku-db-meta.gen.json` and every e2e addon reporting "no
111
+ function metadata".
112
+
113
+ `isAddonPackage` already draws this line — it returns false for `private` — so
114
+ the checks and the shape agree: a package the registry never sees is consumed
115
+ from source.
@@ -0,0 +1,21 @@
1
+ ---
2
+ type: decision
3
+ title: A scenario's step order for a virtual user is insertion order, not a graph traversal
4
+ description: The CLI writes nodes in declaration order, so following `next` would buy an ordering that is already true and cost a traversal that has to interpret branches
5
+ tags: core, virtual-user
6
+ ---
7
+
8
+ # Step order comes from insertion order, not a graph traversal
9
+
10
+ When a scenario becomes catalogue entries, its steps are read in the order the
11
+ nodes appear — which is the order the CLI wrote them, which is the order the
12
+ scenario declares them.
13
+
14
+ Following `next` edges instead would produce the same ordering in the ordinary
15
+ case, and in the branching case it would force a decision nobody needs: which
16
+ arm of a branch to present to a reader who will never take one. The traversal
17
+ costs work to reproduce a fact that already holds.
18
+
19
+ **What this rules out:** "correcting" this to a topological walk because a graph
20
+ is present. The graph describes execution; the catalogue describes what the
21
+ scenario *says*, and those are read in declaration order.
@@ -0,0 +1,24 @@
1
+ ---
2
+ type: decision
3
+ title: Only an explicit `false` silences a spoken reply
4
+ description: The key being absent means no voice input is wired and nothing reported either way, so those callers keep the behaviour they had before the option existed
5
+ tags: core, ai-agent
6
+ ---
7
+
8
+ # Only an explicit `false` silences a spoken reply
9
+
10
+ `voiceOutput` speaks a reply unless the turn is explicitly marked as not having
11
+ arrived by voice. The distinction is between three states, not two:
12
+
13
+ - `true` — `voiceInput` handled this turn and it carried speech.
14
+ - `false` — `voiceInput` handled this turn and a real user really typed it.
15
+ - absent — no voice input is wired at all, so nothing reported either way.
16
+
17
+ Treating absent as `false` would silence every caller who wired `voiceOutput`
18
+ without `voiceInput`, changing behaviour that worked before the option existed.
19
+ Treating absent as `true` would speak replies to typists on any stack where the
20
+ flag never gets set.
21
+
22
+ **What this rules out:** normalising the flag to a boolean at any point between
23
+ `voiceInput` and `voiceOutput` — that collapses absent into one of the other two
24
+ and picks a wrong answer for somebody.
@@ -0,0 +1,35 @@
1
+ ---
2
+ type: decision
3
+ title: Wiring registries erase the generics their wire* functions capture
4
+ description: A registry is typed at unknown so every wiring shares one map; storing a generic config in it needs an assertion, because function parameters are contravariant
5
+ tags: core
6
+ ---
7
+
8
+ # Wiring registries erase the generics their `wire*` functions capture
9
+
10
+ Every `wire*` entry point — `wireTrigger`, `wireTriggerSource`, `wireChannel`,
11
+ `wireQueueWorker`, and the rest — is generic in the shape a caller declares, so
12
+ the caller gets a checked `func`. The registry each one writes to is not:
13
+ `pikkuState(null, 'trigger', 'triggers')` is a
14
+ `Map<string, CoreTriggerSource>` where `CoreTriggerSource` fixes input and
15
+ output at `unknown`, because one map has to hold every trigger in the app.
16
+
17
+ Handing `CoreTriggerSource<TInput, TOutput>` to a slot typed
18
+ `CoreTriggerSource<unknown, unknown>` is not an upcast. `func` takes its input
19
+ as a *parameter*, and parameters are contravariant: a function that accepts
20
+ `TInput` cannot stand in for one that accepts `unknown`, since `unknown` admits
21
+ values `TInput` does not. TypeScript is right to reject it, and no variance
22
+ annotation makes it go away — the registry genuinely holds functions whose input
23
+ types it cannot name.
24
+
25
+ The runtime is nonetheless sound, because the only thing that ever invokes a
26
+ registered `func` is the function runner, which validates the incoming data
27
+ against that function's own generated schema before the call. The type the
28
+ registry lost is re-established at the call boundary by the schema, not by the
29
+ type system.
30
+
31
+ **What this rules out:** deleting these assertions as if they were noise — the
32
+ code does not compile without them. It equally rules out `as any` in their
33
+ place: `as any` discards the target type too, so a genuinely wrong config would
34
+ also slip through. Assert to the registry's own element type and let everything
35
+ except the erased generic stay checked.