@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,76 @@
1
+ import type {
2
+ PikkuAIScorer,
3
+ ScorerInput,
4
+ ScorerOutput,
5
+ } from './ai-scorer.types.js'
6
+
7
+ const assertSampleRate = (name: string, sampleRate: number | undefined) => {
8
+ if (sampleRate === undefined) return 1
9
+ if (sampleRate < 0 || sampleRate > 1) {
10
+ throw new Error(
11
+ `Scorer '${name}' has a sampleRate of ${sampleRate} — it is a fraction of live runs to grade, so it must be between 0 and 1`
12
+ )
13
+ }
14
+ return sampleRate
15
+ }
16
+
17
+ /**
18
+ * A heuristic scorer: pure code over the finished run, no model call, so it
19
+ * grades on the fast lane.
20
+ */
21
+ export const pikkuAIScorer = <Services = any>(config: {
22
+ name: string
23
+ description: string
24
+ /** 0..1 fraction of live runs to grade. Defaults to all of them. */
25
+ sampleRate?: number
26
+ /**
27
+ * Grades against a known-correct answer. Such a scorer is test-only — live
28
+ * traffic has no answer key, so the runtime never samples it.
29
+ */
30
+ requiresReference?: boolean
31
+ score: (
32
+ input: ScorerInput,
33
+ services: Services
34
+ ) => ScorerOutput | Promise<ScorerOutput>
35
+ }): PikkuAIScorer<Services> => ({
36
+ name: config.name,
37
+ description: config.description,
38
+ lane: 'fast',
39
+ sampleRate: assertSampleRate(config.name, config.sampleRate),
40
+ requiresReference: config.requiresReference ?? false,
41
+ score: config.score,
42
+ })
43
+
44
+ /**
45
+ * An LLM-judge scorer: the runtime makes the model call and forces a structured
46
+ * `{ score, reason }`, so a judge is a rubric rather than a prompt to parse.
47
+ *
48
+ * The rubric field is `goal`, matching `pikkuAIAgent`'s prompt vocabulary — a
49
+ * judge is a degenerate agent, and should use the same word for the same thing.
50
+ * `prompt` is the escape hatch for non-standard framing.
51
+ */
52
+ export const pikkuAIJudge = <Services = any>(config: {
53
+ name: string
54
+ description: string
55
+ /** 0..1 fraction of live runs to grade. Defaults to all of them. */
56
+ sampleRate?: number
57
+ /**
58
+ * Grades against a known-correct answer. Such a judge is test-only — live
59
+ * traffic has no answer key, so the runtime never samples it.
60
+ */
61
+ requiresReference?: boolean
62
+ model: string
63
+ goal: string
64
+ prompt?: (input: ScorerInput) => string
65
+ }): PikkuAIScorer<Services> => ({
66
+ name: config.name,
67
+ description: config.description,
68
+ lane: 'slow',
69
+ sampleRate: assertSampleRate(config.name, config.sampleRate),
70
+ requiresReference: config.requiresReference ?? false,
71
+ judge: {
72
+ model: config.model,
73
+ goal: config.goal,
74
+ ...(config.prompt ? { prompt: config.prompt } : {}),
75
+ },
76
+ })
@@ -0,0 +1,107 @@
1
+ /**
2
+ * What a scorer is shown about a finished run.
3
+ *
4
+ * One snapshot, taken after the output middleware has resolved, so a scorer and
5
+ * the persisted run record see the same thing — including the redactions.
6
+ */
7
+ export interface ScorerInput {
8
+ runId: string
9
+ agentName: string
10
+ threadId?: string
11
+ resourceId?: string
12
+ /** The user prompt the run answered. */
13
+ input: string
14
+ /** The agent's final text answer. */
15
+ output: string
16
+ /**
17
+ * The known-correct answer. Supplied by a scenario for a reference-based
18
+ * judge; never set on live traffic, which has no answer key.
19
+ */
20
+ reference?: string
21
+ toolCalls: {
22
+ name: string
23
+ args: unknown
24
+ result?: unknown
25
+ error?: string
26
+ }[]
27
+ usage: {
28
+ inputTokens: number
29
+ outputTokens: number
30
+ model?: string
31
+ }
32
+ }
33
+
34
+ export interface ScorerOutput {
35
+ /** 0..1, so grades are comparable across scorers. */
36
+ score: number
37
+ reason?: string
38
+ metadata?: Record<string, unknown>
39
+ }
40
+
41
+ /**
42
+ * The queue a scorer's jobs go to. Two lanes exist only so a flood of slow
43
+ * LLM-judge jobs cannot starve the cheap heuristic ones; within a lane it is
44
+ * plain FIFO.
45
+ */
46
+ export type ScorerLane = 'fast' | 'slow'
47
+
48
+ export const SCORER_LANE_QUEUES: Record<ScorerLane, string> = {
49
+ fast: 'ai-score-fast',
50
+ slow: 'ai-score-slow',
51
+ }
52
+
53
+ /**
54
+ * A judge's model call. Present only on a scorer built with `pikkuAIJudge`;
55
+ * `score` is present only on one built with `pikkuAIScorer`. Exactly one of the
56
+ * two is set, which is what the two constructors exist to guarantee.
57
+ */
58
+ export type ScorerJudgeConfig = {
59
+ model: string
60
+ goal: string
61
+ prompt?: (input: ScorerInput) => string
62
+ }
63
+
64
+ export type PikkuAIScorer<Services = any> = {
65
+ name: string
66
+ description: string
67
+ lane: ScorerLane
68
+ /** 0..1 fraction of live runs to grade. */
69
+ sampleRate: number
70
+ /**
71
+ * Grades against a known-correct answer, so it is test-only: live traffic has
72
+ * no answer key and the runtime never samples it.
73
+ */
74
+ requiresReference: boolean
75
+ score?: (
76
+ input: ScorerInput,
77
+ services: Services
78
+ ) => ScorerOutput | Promise<ScorerOutput>
79
+ judge?: ScorerJudgeConfig
80
+ }
81
+
82
+ export type ScorerMeta = Record<
83
+ string,
84
+ {
85
+ name: string
86
+ description: string
87
+ lane: ScorerLane
88
+ sampleRate: number
89
+ requiresReference: boolean
90
+ sourceFile?: string
91
+ exportedName?: string
92
+ }
93
+ >
94
+
95
+ /** A single scorer's job on a lane queue. */
96
+ export type ScoreJob = ScorerInput & {
97
+ scorerName: string
98
+ }
99
+
100
+ export type AIRunScore = {
101
+ runId: string
102
+ scorerName: string
103
+ score: number
104
+ reason?: string
105
+ metadata?: Record<string, unknown>
106
+ createdAt: Date
107
+ }
@@ -0,0 +1,24 @@
1
+ export { pikkuAIScorer, pikkuAIJudge } from './ai-scorer.js'
2
+ export {
3
+ addAIScorer,
4
+ getAIScorers,
5
+ getAIScorersMeta,
6
+ } from './ai-scorer-registry.js'
7
+ export { gradeRun } from './ai-scorer-grade.js'
8
+ export {
9
+ enableScoreSnapshots,
10
+ getScoreSnapshot,
11
+ } from './ai-scorer-snapshots.js'
12
+ export {
13
+ wireAIScorerQueueWorkers,
14
+ } from './ai-scorer-worker.js'
15
+ export type {
16
+ AIRunScore,
17
+ PikkuAIScorer,
18
+ ScoreJob,
19
+ ScorerInput,
20
+ ScorerJudgeConfig,
21
+ ScorerLane,
22
+ ScorerMeta,
23
+ ScorerOutput,
24
+ } from './ai-scorer.types.js'
@@ -1,3 +1,4 @@
1
+ import type { CorePikkuChannelMiddleware } from './channel.types.js'
1
2
  import type {
2
3
  CoreSingletonServices,
3
4
  CorePikkuMiddleware,
@@ -61,7 +62,9 @@ export const runChannelLifecycleWithMiddleware = async ({
61
62
  `${channelConfig.name}:${lifecycleType}:cm`,
62
63
  {
63
64
  wireInheritedChannelMiddleware: channelMiddlewareMeta,
64
- wireChannelMiddleware: channelConfig.channelMiddleware as any,
65
+ // knowledge: questions/channel-middleware-accepts-bare-factories-that-nothing-resolves.md
66
+ wireChannelMiddleware:
67
+ channelConfig.channelMiddleware as CorePikkuChannelMiddleware[],
65
68
  }
66
69
  )
67
70
 
@@ -76,7 +79,7 @@ export const runChannelLifecycleWithMiddleware = async ({
76
79
  const runLifecycle = async () => {
77
80
  return await runPikkuFunc('channel', channelConfig.name, meta.pikkuFuncId, {
78
81
  singletonServices: services,
79
- data: () => data as any,
82
+ data: () => data,
80
83
  wire,
81
84
  sessionService: userSession,
82
85
  tags: meta.tags ?? [],
@@ -0,0 +1,72 @@
1
+ import { describe, test } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+
4
+ import { pikkuState, resetPikkuState } from '../../pikku-state.js'
5
+ import { wireChannel } from './channel-runner.js'
6
+
7
+ const registeredFunc = (funcId: string) =>
8
+ pikkuState(null, 'function', 'functions').get(funcId)
9
+
10
+ const wire = (channel: any) => {
11
+ resetPikkuState()
12
+ pikkuState(null, 'channel', 'meta', {
13
+ shapes: {
14
+ name: 'shapes',
15
+ route: '/shapes',
16
+ input: null,
17
+ connect: { pikkuFuncId: 'shapes__connect' },
18
+ disconnect: { pikkuFuncId: 'shapes__disconnect' },
19
+ message: { pikkuFuncId: 'shapes__message' },
20
+ messageWirings: {},
21
+ },
22
+ } as any)
23
+ wireChannel({ name: 'shapes', route: '/shapes', ...channel })
24
+ }
25
+
26
+ /**
27
+ * knowledge: decisions/internals/channel-message-handlers-accept-three-config-shapes.md
28
+ */
29
+ describe('every channel handler shape registers a callable function config', () => {
30
+ const handler = async () => undefined
31
+
32
+ test('a direct function config registers as-is', () => {
33
+ wire({ onConnect: { func: handler } })
34
+
35
+ assert.equal(typeof registeredFunc('shapes__connect')?.func, 'function')
36
+ })
37
+
38
+ test('a wrapper around a function config registers the inner config', () => {
39
+ wire({ onConnect: { func: { func: handler }, middleware: [] } })
40
+
41
+ // Registering the wrapper itself leaves `.func` an object, and the function
42
+ // runner calls `.func(...)` — that is a TypeError at connect time.
43
+ assert.equal(
44
+ typeof registeredFunc('shapes__connect')?.func,
45
+ 'function',
46
+ 'the wrapper was registered instead of the config it wraps'
47
+ )
48
+ })
49
+
50
+ test('onDisconnect unwraps the same way', () => {
51
+ wire({ onDisconnect: { func: { func: handler }, middleware: [] } })
52
+
53
+ assert.equal(typeof registeredFunc('shapes__disconnect')?.func, 'function')
54
+ })
55
+
56
+ test('onMessage unwraps the same way', () => {
57
+ wire({ onMessage: { func: { func: handler } } })
58
+
59
+ assert.equal(typeof registeredFunc('shapes__message')?.func, 'function')
60
+ })
61
+
62
+ test('a simple wrapper — a function plus sibling middleware — stays whole', () => {
63
+ wire({ onConnect: { func: handler, middleware: [] } })
64
+
65
+ const registered = registeredFunc('shapes__connect')
66
+ assert.equal(typeof registered?.func, 'function')
67
+ assert.ok(
68
+ 'middleware' in (registered as object),
69
+ 'the sibling middleware must survive registration'
70
+ )
71
+ })
72
+ })
@@ -4,6 +4,7 @@ import type {
4
4
  CoreUserSession,
5
5
  } from '../../types/core.types.js'
6
6
  import type {
7
+ CorePikkuChannelMiddleware,
7
8
  BinaryData,
8
9
  ChannelMessageMeta,
9
10
  CoreChannel,
@@ -94,7 +95,7 @@ export const processMessageHandlers = (
94
95
  logger.error(
95
96
  `Channel ${channelConfig.name} with id ${channelHandler.getChannel().channelId} requires a session for ${routeMessage}. No session is attached to this websocket connection. Ensure auth middleware establishes a session during websocket upgrade, and configure sessionStore when the runtime needs persisted sessions.`
96
97
  )
97
- // TODO: Send error message back breaks typescript, but should be implemented somehow
98
+ // knowledge: questions/unauthorized-channel-replies-escape-the-declared-out-type.md
98
99
  channelHandler.getChannel().send(`Unauthorized for ${routeMessage}`)
99
100
  return
100
101
  }
@@ -111,10 +112,7 @@ export const processMessageHandlers = (
111
112
  // Get wire middleware: channel-level middleware + message-specific middleware
112
113
  const channelWireMiddleware = channelConfig.middleware || []
113
114
 
114
- // Check if onMessage is a wrapper object vs direct function config:
115
- // - Direct config: onMessage.func is a plain Function
116
- // - Wrapper: onMessage.func is a CorePikkuFunctionConfig (has onMessage.func.func)
117
- // - Simple wrapper: onMessage has both func (plain Function) and middleware properties
115
+ // knowledge: decisions/internals/channel-message-handlers-accept-three-config-shapes.md
118
116
  const isWrapper =
119
117
  onMessage &&
120
118
  typeof onMessage === 'object' &&
@@ -145,7 +143,9 @@ export const processMessageHandlers = (
145
143
  inheritedMiddleware,
146
144
  wireMiddleware,
147
145
  inheritedChannelMiddleware: channelMeta?.channelMiddleware,
148
- wireChannelMiddleware: wireChannelMiddleware as any,
146
+ // knowledge: questions/channel-middleware-accepts-bare-factories-that-nothing-resolves.md
147
+ wireChannelMiddleware:
148
+ wireChannelMiddleware as CorePikkuChannelMiddleware[],
149
149
  coerceDataFromSchema: true,
150
150
  tags: channelConfig.tags,
151
151
  sessionService: userSession,
@@ -191,7 +191,7 @@ export const processMessageHandlers = (
191
191
 
192
192
  processed = true
193
193
  const routeResult = await processMessage(
194
- data as any,
194
+ data,
195
195
  routes[routerValue],
196
196
  routingProperty,
197
197
  routerValue
@@ -10,20 +10,6 @@ import {
10
10
  type ChannelRPCValidator,
11
11
  } from './channel-rpc.types.js'
12
12
 
13
- /**
14
- * `remote` for channels that only ever flow one way — a server-sent stream, an
15
- * agent's output, a local CLI. Nothing is listening for a request on them, so
16
- * the call is refused rather than waiting out a timeout.
17
- */
18
- export const unsupportedChannelRemote = async (
19
- funcName: string
20
- ): Promise<never> => {
21
- throw new ChannelRPCError(
22
- `Cannot call "${funcName}" remotely: this channel has no peer that answers`,
23
- 'unsupported'
24
- )
25
- }
26
-
27
13
  /**
28
14
  * A `DeploymentService` whose transport is an open channel rather than an
29
15
  * address, so a server-side function can call back into a peer that has no
@@ -109,21 +109,42 @@ describe('ChannelRPCRegistry', () => {
109
109
  })
110
110
  })
111
111
 
112
- test('times out instead of hanging forever', async () => {
112
+ /**
113
+ * The registry unrefs its timeout timer so an in-flight call can never hold
114
+ * a process open. That makes the timeout unawaitable in a test: when the
115
+ * pending call is the only thing left, the loop drains before the timer
116
+ * fires, and node:test cancels the file — taking every later test with it.
117
+ *
118
+ * Driving the clock by hand removes the dependency on real time entirely.
119
+ * `t.mock.timers` is scoped to the test and resets itself, so the tests that
120
+ * do want a real clock are unaffected.
121
+ *
122
+ * knowledge: decisions/internals/an-unref-d-timer-cannot-be-awaited-under-node-test.md
123
+ */
124
+ test('times out instead of hanging forever', async (t) => {
125
+ t.mock.timers.enable({ apis: ['setTimeout'] })
126
+
113
127
  const registry = new ChannelRPCRegistry(10)
114
128
  const call = registry.register()
115
129
 
116
- await assert.rejects(call.promise, (e: ChannelRPCError) => {
130
+ const rejected = assert.rejects(call.promise, (e: ChannelRPCError) => {
117
131
  assert.equal(e.reason, 'timeout')
118
132
  return true
119
133
  })
134
+ t.mock.timers.tick(10)
135
+ await rejected
136
+
120
137
  assert.equal(registry.inFlight, 0, 'a timed out call is not leaked')
121
138
  })
122
139
 
123
- test('drops a late response for an already-timed-out call', async () => {
140
+ test('drops a late response for an already-timed-out call', async (t) => {
141
+ t.mock.timers.enable({ apis: ['setTimeout'] })
142
+
124
143
  const registry = new ChannelRPCRegistry(10)
125
144
  const call = registry.register()
126
- await assert.rejects(call.promise)
145
+ const rejected = assert.rejects(call.promise)
146
+ t.mock.timers.tick(10)
147
+ await rejected
127
148
 
128
149
  const settled = registry.settle({
129
150
  action: CHANNEL_RPC_RESPONSE,
@@ -148,3 +148,17 @@ export type ChannelRPCValidator = (
148
148
  funcName: string,
149
149
  value: unknown
150
150
  ) => Promise<void> | void
151
+
152
+ /**
153
+ * `remote` for wires with no peer that answers — a server-sent stream, an
154
+ * agent's output, a local CLI. Nothing is listening for a request on them, so
155
+ * the call is refused rather than waiting out a timeout.
156
+ */
157
+ export const unsupportedChannelRemote = async (
158
+ funcName: string
159
+ ): Promise<never> => {
160
+ throw new ChannelRPCError(
161
+ `Cannot call "${funcName}" remotely: this channel has no peer that answers`,
162
+ 'unsupported'
163
+ )
164
+ }
@@ -1,10 +1,9 @@
1
1
  import { NotFoundError } from '../../errors/errors.js'
2
2
  import { addFunction } from '../../function/function-runner.js'
3
- import type { CorePikkuPermission } from '../../function/functions.types.js'
3
+ import type { CorePikkuFunctionConfig } from '../../function/functions.types.js'
4
4
  import { pikkuState, getSingletonServices } from '../../pikku-state.js'
5
5
  import { coerceTopLevelDataFromSchema, validateSchema } from '../../schema.js'
6
6
  import type { SessionService } from '../../services/user-session-service.js'
7
- import type { CorePikkuMiddleware } from '../../types/core.types.js'
8
7
  import { httpRouter } from '../http/routers/http-router.js'
9
8
  import type {
10
9
  ChannelMeta,
@@ -13,19 +12,34 @@ import type {
13
12
  RunChannelParams,
14
13
  } from './channel.types.js'
15
14
 
15
+ type HandlerConfig = CorePikkuFunctionConfig<any, any>
16
+
17
+ /**
18
+ * The config to register for a channel handler: the handler itself when it is a
19
+ * direct function config, or its inner `func` when it is a wrapper.
20
+ *
21
+ * knowledge: decisions/internals/channel-message-handlers-accept-three-config-shapes.md
22
+ */
23
+ const registeredHandler = (handler: unknown): HandlerConfig => {
24
+ const candidate = handler as { func?: unknown }
25
+ return typeof candidate.func === 'function'
26
+ ? (handler as HandlerConfig)
27
+ : (candidate.func as HandlerConfig)
28
+ }
29
+
16
30
  export const wireChannel = <
17
31
  In,
18
32
  Channel extends string,
19
- PikkuPermission extends CorePikkuPermission<In>,
20
- PikkuMiddleware extends CorePikkuMiddleware,
21
- ChannelFunction,
33
+ ChannelConnect,
34
+ ChannelDisconnect,
35
+ ChannelFunctionMessage,
22
36
  >(
23
37
  channel: CoreChannel<
24
38
  In,
25
39
  Channel,
26
- PikkuPermission,
27
- PikkuMiddleware,
28
- ChannelFunction
40
+ ChannelConnect,
41
+ ChannelDisconnect,
42
+ ChannelFunctionMessage
29
43
  >
30
44
  ) => {
31
45
  const channelsMeta = pikkuState(null, 'channel', 'meta')
@@ -38,19 +52,23 @@ export const wireChannel = <
38
52
  }
39
53
 
40
54
  if (channel.onConnect && channelMeta.connect) {
41
- addFunction(channelMeta.connect.pikkuFuncId, channel.onConnect as any)
55
+ addFunction(
56
+ channelMeta.connect.pikkuFuncId,
57
+ registeredHandler(channel.onConnect)
58
+ )
42
59
  }
43
60
 
44
61
  if (channel.onDisconnect && channelMeta.disconnect) {
45
- addFunction(channelMeta.disconnect.pikkuFuncId, channel.onDisconnect as any)
62
+ addFunction(
63
+ channelMeta.disconnect.pikkuFuncId,
64
+ registeredHandler(channel.onDisconnect)
65
+ )
46
66
  }
47
67
 
48
68
  if (channel.onMessage && channelMeta.message?.pikkuFuncId) {
49
69
  addFunction(
50
70
  channelMeta.message.pikkuFuncId,
51
- (channel.onMessage as any).func instanceof Function
52
- ? channel.onMessage
53
- : (channel.onMessage as any).func
71
+ registeredHandler(channel.onMessage)
54
72
  )
55
73
  }
56
74
 
@@ -63,17 +81,12 @@ export const wireChannel = <
63
81
  const wiringMeta = channelWirings[wiringKey]
64
82
  if (!wiringMeta) return
65
83
 
66
- addFunction(
67
- wiringMeta.pikkuFuncId,
68
- (handler as any).func instanceof Function
69
- ? handler
70
- : (handler as any).func
71
- )
84
+ addFunction(wiringMeta.pikkuFuncId, registeredHandler(handler))
72
85
  })
73
86
  })
74
87
  }
75
88
 
76
- pikkuState(null, 'channel', 'channels').set(channel.name, channel as any)
89
+ pikkuState(null, 'channel', 'channels').set(channel.name, channel)
77
90
  }
78
91
 
79
92
  const getMatchingChannelConfig = (path: string) => {
@@ -64,6 +64,8 @@ export interface ChannelMeta {
64
64
  message: ChannelMessageMeta | null
65
65
  messageWirings: Record<string, Record<string, ChannelMessageMeta>>
66
66
  binary?: boolean | null
67
+ /** Set when the channel was wired by a gateway rather than declared directly. */
68
+ gateway?: boolean
67
69
  summary?: string
68
70
  description?: string
69
71
  errors?: string[]
@@ -1,8 +1,7 @@
1
- export { wireChannel, openChannel } from './channel-runner.js'
1
+ export { wireChannel } from './channel-runner.js'
2
2
  export {
3
3
  addChannelMiddleware,
4
4
  combineChannelMiddleware,
5
- wrapChannelWithMiddleware,
6
5
  } from './channel-middleware-runner.js'
7
6
  export { logChannels } from './log-channels.js'
8
7
  export { PikkuAbstractChannelHandler } from './pikku-abstract-channel-handler.js'
@@ -18,25 +17,14 @@ export type {
18
17
  CorePikkuChannelMiddlewareFactory,
19
18
  ChannelMessageMeta,
20
19
  ChannelMeta,
21
- ChannelRemote,
22
20
  PikkuChannel,
23
21
  PikkuChannelHandlerFactory,
24
22
  } from './channel.types.js'
25
23
  export { defineChannelRoutes } from './define-channel-routes.js'
26
24
  export {
27
- CHANNEL_RPC_PENDING,
28
- CHANNEL_RPC_REQUEST,
29
- CHANNEL_RPC_RESPONSE,
30
25
  ChannelDeploymentService,
31
26
  ChannelRPCError,
32
27
  ChannelRPCRegistry,
33
- createChannelRPCResponder,
34
- createChannelRPCInputValidator,
35
- createChannelRPCResultValidator,
36
- isChannelRPCPending,
37
- isChannelRPCRequest,
38
- isChannelRPCResponse,
39
- resolveCapability,
40
28
  unsupportedChannelRemote,
41
29
  } from './channel-rpc.js'
42
30
  export type {
@@ -44,16 +32,9 @@ export type {
44
32
  ApprovalRequester,
45
33
  Capabilities,
46
34
  Capability,
47
- CapabilityDef,
48
35
  CapabilityHandler,
49
36
  ChannelRPCPending,
50
37
  ChannelRPCRequest,
51
38
  ChannelRPCResponse,
52
39
  ChannelRPCValidator,
53
40
  } from './channel-rpc.js'
54
- export {
55
- channelRemote,
56
- getChannelHostRPC,
57
- handleChannelRPCResponse,
58
- releaseChannelHostRPC,
59
- } from './channel-host-rpc.js'
@@ -3,6 +3,7 @@ import * as assert from 'node:assert/strict'
3
3
  import { runLocalChannel } from './local-channel-runner.js'
4
4
  import { pikkuState, resetPikkuState } from '../../../pikku-state.js'
5
5
  import { wireChannel } from '../channel-runner.js'
6
+ import { addFunction } from '../../../function/function-runner.js'
6
7
  import { addHTTPMiddleware } from '../../http/http-runner.js'
7
8
  import type {
8
9
  HTTPMethod,
@@ -270,3 +271,70 @@ test('runChannel should run HTTP middleware on websocket upgrade and establish s
270
271
  await result.message(JSON.stringify({ action: 'ping' }))
271
272
  assert.deepEqual(sent, { action: 'ping' })
272
273
  })
274
+
275
+ test('a message handler that returns nothing does not attempt to send', async () => {
276
+ // The connect path already guards this; the message path did not, so a handler
277
+ // with nothing to say produced `send requires a non-empty message` on every
278
+ // message. Gateway websockets hit it every time — their generated message
279
+ // handler returns undefined by design — which is why a chat gateway could
280
+ // accept a connection and never deliver anything to its handler.
281
+ resetPikkuState()
282
+ pikkuState(null, 'package', 'singletonServices', mockSingletonServices as any)
283
+ pikkuState(null, 'package', 'factories', {
284
+ createWireServices: mockCreateWireServices,
285
+ } as any)
286
+
287
+ const sent: unknown[] = []
288
+ let handled = 0
289
+
290
+ pikkuState(null, 'channel', 'meta', {
291
+ quiet: {
292
+ name: 'quiet',
293
+ route: '/quiet-channel',
294
+ message: { pikkuFuncId: 'quietMessage' },
295
+ messageWirings: {},
296
+ },
297
+ } as any)
298
+ pikkuState(null, 'function', 'meta')['quietMessage'] = {
299
+ pikkuFuncId: 'quietMessage',
300
+ inputSchemaName: null,
301
+ outputSchemaName: null,
302
+ sessionless: true,
303
+ } as any
304
+ const quietMessage = {
305
+ auth: false,
306
+ func: async () => {
307
+ handled++
308
+ return undefined
309
+ },
310
+ }
311
+ wireChannel({
312
+ name: 'quiet',
313
+ route: '/quiet-channel',
314
+ auth: false,
315
+ onMessage: quietMessage as any,
316
+ } as any)
317
+
318
+ httpRouter.initialize()
319
+
320
+ const handler = await runLocalChannel({
321
+ channelId: 'quiet-channel-id',
322
+ request: new PikkuMockRequest('/quiet-channel', 'get'),
323
+ response: new PikkuMockResponse(),
324
+ route: '/quiet-channel',
325
+ })
326
+ assert.ok(handler)
327
+ handler.registerOnSend(async (message: unknown) => {
328
+ sent.push(message)
329
+ })
330
+ handler.open()
331
+
332
+ await handler.message(JSON.stringify({ text: 'hello' }))
333
+
334
+ assert.equal(handled, 1, 'the handler should have run')
335
+ assert.deepEqual(
336
+ sent,
337
+ [],
338
+ 'nothing should have been sent for an empty result'
339
+ )
340
+ })