@pikku/core 0.12.78 → 0.12.80

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 (341) hide show
  1. package/CHANGELOG.md +234 -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/function/function-runner.js +20 -42
  7. package/dist/function/functions.types.d.ts +9 -5
  8. package/dist/index.d.ts +4 -9
  9. package/dist/index.js +3 -8
  10. package/dist/middleware/auth-cookie.d.ts +0 -4
  11. package/dist/middleware/auth-cookie.js +38 -2
  12. package/dist/middleware/cors.js +1 -0
  13. package/dist/middleware/index.d.ts +1 -1
  14. package/dist/middleware/index.js +1 -1
  15. package/dist/middleware/remote-auth.js +14 -3
  16. package/dist/permissions.d.ts +8 -10
  17. package/dist/permissions.js +0 -11
  18. package/dist/pikku-state.js +12 -5
  19. package/dist/schema.js +35 -1
  20. package/dist/services/ai-run-state-service.d.ts +7 -1
  21. package/dist/services/in-memory-ai-run-state-service.d.ts +1 -1
  22. package/dist/services/in-memory-ai-run-state-service.js +2 -1
  23. package/dist/services/in-memory-workflow-service.d.ts +10 -0
  24. package/dist/services/in-memory-workflow-service.js +25 -0
  25. package/dist/services/local-content-request-handler.d.ts +21 -0
  26. package/dist/services/local-content-request-handler.js +72 -53
  27. package/dist/services/local-content.d.ts +6 -0
  28. package/dist/services/local-content.js +14 -1
  29. package/dist/services/workflow-service.d.ts +4 -2
  30. package/dist/testing/service-tests/agent-run-service-tests.d.ts +10 -0
  31. package/dist/testing/service-tests/agent-run-service-tests.js +72 -0
  32. package/dist/testing/service-tests/ai-storage-service-tests.d.ts +3 -0
  33. package/dist/testing/service-tests/ai-storage-service-tests.js +226 -0
  34. package/dist/testing/service-tests/channel-store-tests.d.ts +3 -0
  35. package/dist/testing/service-tests/channel-store-tests.js +72 -0
  36. package/dist/testing/service-tests/credential-service-tests.d.ts +3 -0
  37. package/dist/testing/service-tests/credential-service-tests.js +109 -0
  38. package/dist/testing/service-tests/deployment-service-tests.d.ts +3 -0
  39. package/dist/testing/service-tests/deployment-service-tests.js +21 -0
  40. package/dist/testing/service-tests/event-hub-store-tests.d.ts +3 -0
  41. package/dist/testing/service-tests/event-hub-store-tests.js +34 -0
  42. package/dist/testing/service-tests/secret-service-tests.d.ts +3 -0
  43. package/dist/testing/service-tests/secret-service-tests.js +80 -0
  44. package/dist/testing/service-tests/session-store-tests.d.ts +3 -0
  45. package/dist/testing/service-tests/session-store-tests.js +43 -0
  46. package/dist/testing/service-tests/workflow-run-service-tests.d.ts +3 -0
  47. package/dist/testing/service-tests/workflow-run-service-tests.js +42 -0
  48. package/dist/testing/service-tests/workflow-service-tests.d.ts +3 -0
  49. package/dist/testing/service-tests/workflow-service-tests.js +150 -0
  50. package/dist/testing/service-tests.d.ts +6 -0
  51. package/dist/testing/service-tests.js +26 -791
  52. package/dist/types/core.types.d.ts +11 -1
  53. package/dist/types/state.types.d.ts +1 -1
  54. package/dist/utils/node-host-resolver.d.ts +12 -0
  55. package/dist/utils/node-host-resolver.js +16 -0
  56. package/dist/utils/safe-fetch.d.ts +18 -0
  57. package/dist/utils/safe-fetch.js +167 -29
  58. package/dist/wirings/actor-flow/run-conversation.js +1 -6
  59. package/dist/wirings/ai-agent/agent-rpc.d.ts +15 -0
  60. package/dist/wirings/ai-agent/agent-rpc.js +53 -0
  61. package/dist/wirings/ai-agent/ai-agent-agui.js +1 -5
  62. package/dist/wirings/ai-agent/ai-agent-memory.js +3 -1
  63. package/dist/wirings/ai-agent/ai-agent-prepare.js +3 -9
  64. package/dist/wirings/ai-agent/ai-agent-runner.js +28 -107
  65. package/dist/wirings/ai-agent/ai-agent-stream.js +20 -61
  66. package/dist/wirings/ai-agent/ai-agent-turn.d.ts +56 -0
  67. package/dist/wirings/ai-agent/ai-agent-turn.js +81 -0
  68. package/dist/wirings/ai-agent/ai-agent.types.d.ts +1 -1
  69. package/dist/wirings/ai-agent/voice-input.js +1 -6
  70. package/dist/wirings/ai-agent/voice-output.js +2 -12
  71. package/dist/wirings/channel/channel-common.js +1 -0
  72. package/dist/wirings/channel/channel-handler.js +3 -5
  73. package/dist/wirings/channel/channel-rpc-service.d.ts +0 -6
  74. package/dist/wirings/channel/channel-rpc-service.js +0 -8
  75. package/dist/wirings/channel/channel-rpc.types.d.ts +6 -0
  76. package/dist/wirings/channel/channel-rpc.types.js +8 -0
  77. package/dist/wirings/channel/channel-runner.d.ts +1 -3
  78. package/dist/wirings/channel/channel-runner.js +16 -8
  79. package/dist/wirings/channel/channel.types.d.ts +2 -0
  80. package/dist/wirings/channel/pikku-abstract-channel-handler.js +1 -0
  81. package/dist/wirings/channel/serverless/serverless-channel-runner.js +3 -0
  82. package/dist/wirings/cli/channel/cli-channel-runner.js +2 -0
  83. package/dist/wirings/cli/cli-runner.js +4 -2
  84. package/dist/wirings/cli/cli.types.d.ts +0 -8
  85. package/dist/wirings/cli/command-parser.js +13 -0
  86. package/dist/wirings/gateway/gateway-runner.js +41 -5
  87. package/dist/wirings/http/http-routes.js +2 -0
  88. package/dist/wirings/http/http-runner.d.ts +0 -10
  89. package/dist/wirings/http/http-runner.js +6 -13
  90. package/dist/wirings/http/http.types.d.ts +0 -10
  91. package/dist/wirings/http/index.d.ts +1 -1
  92. package/dist/wirings/http/index.js +1 -1
  93. package/dist/wirings/mcp/mcp-runner.d.ts +0 -7
  94. package/dist/wirings/mcp/mcp-runner.js +0 -6
  95. package/dist/wirings/rpc/rpc-runner.d.ts +8 -51
  96. package/dist/wirings/rpc/rpc-runner.js +53 -86
  97. package/dist/wirings/rpc/rpc-types.d.ts +3 -0
  98. package/dist/wirings/secret/validate-secret-definitions.js +2 -0
  99. package/dist/wirings/trigger/pikku-trigger-service.d.ts +0 -4
  100. package/dist/wirings/trigger/trigger-runner.js +1 -0
  101. package/dist/wirings/virtual-user/run-virtual-user.js +11 -11
  102. package/dist/wirings/virtual-user/virtual-user-derive.js +4 -25
  103. package/dist/wirings/virtual-user/virtual-user-dispositions.js +1 -4
  104. package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +29 -2
  105. package/dist/wirings/workflow/feature.js +7 -4
  106. package/dist/wirings/workflow/graph/graph-runner.d.ts +1 -2
  107. package/dist/wirings/workflow/graph/graph-runner.js +8 -7
  108. package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +0 -4
  109. package/dist/wirings/workflow/index.d.ts +7 -2
  110. package/dist/wirings/workflow/index.js +5 -1
  111. package/dist/wirings/workflow/pikku-scenario-service.d.ts +17 -3
  112. package/dist/wirings/workflow/pikku-scenario-service.js +38 -10
  113. package/dist/wirings/workflow/pikku-workflow-service.d.ts +44 -111
  114. package/dist/wirings/workflow/pikku-workflow-service.js +86 -404
  115. package/dist/wirings/workflow/workflow-approval.d.ts +39 -0
  116. package/dist/wirings/workflow/workflow-approval.js +114 -0
  117. package/dist/wirings/workflow/workflow-constants.d.ts +26 -0
  118. package/dist/wirings/workflow/workflow-constants.js +35 -0
  119. package/dist/wirings/workflow/workflow-errors.d.ts +58 -0
  120. package/dist/wirings/workflow/workflow-errors.js +112 -0
  121. package/dist/wirings/workflow/workflow-meta-resolver.d.ts +11 -0
  122. package/dist/wirings/workflow/workflow-meta-resolver.js +31 -0
  123. package/dist/wirings/workflow/workflow-queue-routing.d.ts +8 -0
  124. package/dist/wirings/workflow/workflow-queue-routing.js +38 -0
  125. package/dist/wirings/workflow/workflow-queue-wiring.d.ts +20 -0
  126. package/dist/wirings/workflow/workflow-queue-wiring.js +79 -0
  127. package/dist/wirings/workflow/workflow-recovery.d.ts +68 -0
  128. package/dist/wirings/workflow/workflow-recovery.js +101 -0
  129. package/dist/wirings/workflow/workflow-run-engine.types.d.ts +54 -0
  130. package/dist/wirings/workflow/workflow-run-engine.types.js +1 -0
  131. package/dist/wirings/workflow/workflow-run-ownership.d.ts +16 -0
  132. package/dist/wirings/workflow/workflow-run-ownership.js +29 -0
  133. package/dist/wirings/workflow/workflow-suspend.d.ts +12 -0
  134. package/dist/wirings/workflow/workflow-suspend.js +33 -0
  135. package/dist/wirings/workflow/workflow.types.d.ts +7 -0
  136. package/knowledge/decisions/internals/a-non-streaming-agent-run-registers-with-airunstate-too.md +22 -0
  137. package/knowledge/decisions/internals/a-resumed-agent-turn-is-as-interruptible-as-the-first.md +20 -0
  138. package/knowledge/decisions/internals/a-scenario-step-template-is-offered-unfilled.md +21 -0
  139. package/knowledge/decisions/internals/a-virtual-user-decides-whether-to-trust-memory-once-per-turn.md +21 -0
  140. package/knowledge/decisions/internals/a-wall-clock-threshold-is-a-load-test-in-disguise.md +46 -0
  141. package/knowledge/decisions/internals/a-workflow-wire-is-built-from-the-run-not-from-the-rpc-service.md +45 -0
  142. package/knowledge/decisions/internals/agent-context-waits-for-a-tool-result-still-being-written.md +21 -0
  143. package/knowledge/decisions/internals/agent-speech-travels-as-a-custom-agui-event.md +22 -0
  144. package/knowledge/decisions/internals/an-agent-interrupt-is-not-a-failure.md +23 -0
  145. package/knowledge/decisions/internals/an-agent-run-owned-by-another-instance-says-so.md +25 -0
  146. package/knowledge/decisions/internals/an-agent-stream-send-must-return-the-inner-sends-promise.md +22 -0
  147. package/knowledge/decisions/internals/an-empty-text-part-is-omitted-from-an-agent-message.md +20 -0
  148. package/knowledge/decisions/internals/an-empty-transcript-is-not-recorded.md +22 -0
  149. package/knowledge/decisions/internals/an-unref-d-timer-cannot-be-awaited-under-node-test.md +66 -0
  150. package/knowledge/decisions/internals/channel-state-accessors-are-unsound-generics-that-every-implementation-asserts.md +33 -0
  151. package/knowledge/decisions/internals/gateway-listener-middleware-runs-without-an-rpc-on-the-wire.md +43 -0
  152. package/knowledge/decisions/internals/hot-reload-writes-into-the-function-map-captured-at-startup.md +26 -0
  153. package/knowledge/decisions/internals/index.md +38 -26
  154. package/knowledge/decisions/internals/only-exposed-functions-enter-a-virtual-user-catalogue.md +21 -0
  155. package/knowledge/decisions/internals/scenario-given-and-when-are-sugar-but-then-is-not.md +24 -0
  156. package/knowledge/decisions/internals/side-effects-are-an-allowlist-not-a-boolean.md +34 -0
  157. package/knowledge/decisions/internals/speech-synthesis-picks-a-voice-per-sentence-and-warns-once.md +24 -0
  158. package/knowledge/decisions/internals/the-actor-prompt-says-json-because-of-json-object-mode.md +22 -0
  159. package/knowledge/decisions/internals/the-agent-done-event-goes-through-the-middleware-and-is-awaited.md +26 -0
  160. package/knowledge/decisions/internals/the-api-report-pins-members-not-just-names.md +43 -0
  161. package/knowledge/decisions/internals/the-ecosystem-entry-point-carries-the-adapter-surface.md +58 -0
  162. package/knowledge/decisions/internals/the-middleware-resolution-cache-is-deliberately-unbounded.md +40 -0
  163. package/knowledge/decisions/internals/the-per-invocation-rpc-view-is-a-class.md +40 -0
  164. package/knowledge/decisions/internals/the-persona-runtime-is-exported-from-the-persona-entry-point.md +28 -0
  165. package/knowledge/decisions/internals/the-transcript-event-is-sent-ahead-of-the-run.md +25 -0
  166. package/knowledge/decisions/internals/the-virtual-user-catalogue-is-the-only-gate-on-what-may-be-called.md +21 -0
  167. package/knowledge/decisions/internals/the-worker-disposition-is-the-one-that-is-not-testing.md +22 -0
  168. package/knowledge/decisions/internals/thread-history-records-the-transcript-not-the-audio.md +25 -0
  169. package/knowledge/decisions/internals/virtual-user-step-order-comes-from-insertion-order.md +21 -0
  170. package/knowledge/decisions/internals/voice-output-speaks-unless-voice-input-explicitly-says-otherwise.md +24 -0
  171. package/knowledge/decisions/internals/wiring-registries-erase-the-generics-their-wire-functions-capture.md +35 -0
  172. package/knowledge/decisions/security/a-dropped-audit-write-is-always-logged.md +4 -2
  173. package/knowledge/decisions/security/a-graph-run-starts-at-an-entry-node-the-graph-declared.md +33 -0
  174. package/knowledge/decisions/security/a-permission-gets-a-wire-it-cannot-reply-on.md +33 -0
  175. package/knowledge/decisions/security/a-step-runs-the-function-the-workflow-dispatched-it-with.md +37 -0
  176. package/knowledge/decisions/security/a-virtual-user-is-never-offered-a-step-that-would-forge-its-own-oracle.md +28 -0
  177. package/knowledge/decisions/security/a-workflow-run-is-read-and-approved-by-its-owner.md +34 -0
  178. package/knowledge/decisions/security/an-agent-approval-is-claimed-before-the-tool-runs.md +33 -0
  179. package/knowledge/decisions/security/an-upload-is-counted-as-it-arrives-not-buffered-then-measured.md +24 -0
  180. package/knowledge/decisions/security/gateway-handlers-run-through-the-function-runner-gate.md +13 -3
  181. package/knowledge/decisions/security/index.md +7 -0
  182. package/knowledge/questions/channel-middleware-accepts-bare-factories-that-nothing-resolves.md +35 -0
  183. package/knowledge/questions/index.md +2 -1
  184. package/knowledge/questions/unauthorized-channel-replies-escape-the-declared-out-type.md +44 -0
  185. package/package.json +14 -3
  186. package/scripts/generate-api-report.d.mts +1 -0
  187. package/scripts/generate-api-report.mjs +89 -0
  188. package/scripts/generate-api-report.mts +218 -0
  189. package/src/api-report.test.ts +69 -0
  190. package/src/column-form.ts +1 -4
  191. package/src/crypto-utils.test.ts +15 -2
  192. package/src/dev/hot-reload.ts +2 -7
  193. package/src/ecosystem.ts +40 -0
  194. package/src/errors/error-handler.ts +5 -3
  195. package/src/errors/error.test.ts +4 -1
  196. package/src/function/function-runner.test.ts +1 -1
  197. package/src/function/function-runner.ts +28 -39
  198. package/src/function/functions.types.ts +15 -9
  199. package/src/handle-error.ts +1 -1
  200. package/src/index.ts +3 -22
  201. package/src/middleware/auth-cookie.test.ts +50 -0
  202. package/src/middleware/auth-cookie.ts +38 -2
  203. package/src/middleware/cors.ts +2 -1
  204. package/src/middleware/index.ts +1 -1
  205. package/src/middleware/remote-auth.test.ts +43 -0
  206. package/src/middleware/remote-auth.ts +17 -3
  207. package/src/middleware-runner.ts +4 -2
  208. package/src/no-any-casts.test.ts +57 -0
  209. package/src/permissions.test.ts +3 -1
  210. package/src/permissions.ts +22 -24
  211. package/src/pikku-state.ts +17 -6
  212. package/src/public-surface.json +578 -0
  213. package/src/public-surface.json.README +25 -0
  214. package/src/public-surface.test.ts +105 -0
  215. package/src/removed-legacy-exports.test.ts +62 -0
  216. package/src/schema.test.ts +78 -0
  217. package/src/schema.ts +36 -1
  218. package/src/services/ai-run-state-service.ts +7 -1
  219. package/src/services/audit-service.ts +2 -2
  220. package/src/services/in-memory-ai-run-state-service.ts +3 -2
  221. package/src/services/in-memory-workflow-service.ts +25 -0
  222. package/src/services/index.ts +1 -4
  223. package/src/services/local-content-request-handler.test.ts +43 -5
  224. package/src/services/local-content-request-handler.ts +103 -74
  225. package/src/services/local-content.ts +15 -1
  226. package/src/services/local-email-service.ts +5 -1
  227. package/src/services/system-role-guard.test.ts +4 -1
  228. package/src/services/workflow-service.ts +9 -2
  229. package/src/side-effects-are-declared.test.ts +84 -0
  230. package/src/source-files-stay-composable.test.ts +41 -0
  231. package/src/testing/service-tests/agent-run-service-tests.ts +98 -0
  232. package/src/testing/service-tests/ai-storage-service-tests.ts +286 -0
  233. package/src/testing/service-tests/channel-store-tests.ts +98 -0
  234. package/src/testing/service-tests/credential-service-tests.ts +143 -0
  235. package/src/testing/service-tests/deployment-service-tests.ts +33 -0
  236. package/src/testing/service-tests/event-hub-store-tests.ts +48 -0
  237. package/src/testing/service-tests/secret-service-tests.ts +105 -0
  238. package/src/testing/service-tests/session-store-tests.ts +62 -0
  239. package/src/testing/service-tests/workflow-run-service-tests.ts +59 -0
  240. package/src/testing/service-tests/workflow-service-tests.ts +308 -0
  241. package/src/testing/service-tests.ts +31 -1111
  242. package/src/types/core.types.ts +15 -1
  243. package/src/types/state.types.ts +1 -1
  244. package/src/utils/node-host-resolver.ts +20 -0
  245. package/src/utils/safe-fetch.test.ts +143 -1
  246. package/src/utils/safe-fetch.ts +200 -25
  247. package/src/wirings/actor-flow/run-conversation.ts +1 -6
  248. package/src/wirings/ai-agent/agent-rpc.ts +120 -0
  249. package/src/wirings/ai-agent/ai-agent-agui.ts +1 -5
  250. package/src/wirings/ai-agent/ai-agent-interrupt.test.ts +2 -1
  251. package/src/wirings/ai-agent/ai-agent-memory.ts +8 -1
  252. package/src/wirings/ai-agent/ai-agent-prepare.ts +10 -12
  253. package/src/wirings/ai-agent/ai-agent-resume-authorization.test.ts +1 -0
  254. package/src/wirings/ai-agent/ai-agent-runner.test.ts +82 -2
  255. package/src/wirings/ai-agent/ai-agent-runner.ts +49 -120
  256. package/src/wirings/ai-agent/ai-agent-stream.test.ts +2 -0
  257. package/src/wirings/ai-agent/ai-agent-stream.ts +36 -63
  258. package/src/wirings/ai-agent/ai-agent-thread-ownership.test.ts +1 -1
  259. package/src/wirings/ai-agent/ai-agent-turn.ts +121 -0
  260. package/src/wirings/ai-agent/ai-agent.types.ts +1 -1
  261. package/src/wirings/ai-agent/voice-input.ts +1 -6
  262. package/src/wirings/ai-agent/voice-output.ts +2 -12
  263. package/src/wirings/channel/channel-common.ts +5 -2
  264. package/src/wirings/channel/channel-handler-shapes.test.ts +72 -0
  265. package/src/wirings/channel/channel-handler.ts +7 -7
  266. package/src/wirings/channel/channel-rpc-service.ts +0 -14
  267. package/src/wirings/channel/channel-rpc.test.ts +25 -4
  268. package/src/wirings/channel/channel-rpc.types.ts +14 -0
  269. package/src/wirings/channel/channel-runner.ts +33 -20
  270. package/src/wirings/channel/channel.types.ts +2 -0
  271. package/src/wirings/channel/local/local-channel-runner.ts +1 -1
  272. package/src/wirings/channel/pikku-abstract-channel-handler.ts +2 -1
  273. package/src/wirings/channel/serverless/serverless-channel-runner.ts +6 -3
  274. package/src/wirings/cli/channel/cli-channel-runner.ts +3 -1
  275. package/src/wirings/cli/channel/cli-raw-client-runner.ts +4 -1
  276. package/src/wirings/cli/cli-runner.ts +7 -4
  277. package/src/wirings/cli/cli.types.ts +0 -39
  278. package/src/wirings/cli/command-parser.test.ts +19 -0
  279. package/src/wirings/cli/command-parser.ts +17 -1
  280. package/src/wirings/gateway/gateway-authorization.test.ts +131 -0
  281. package/src/wirings/gateway/gateway-channel-meta.test.ts +44 -0
  282. package/src/wirings/gateway/gateway-runner.ts +62 -22
  283. package/src/wirings/http/http-routes.ts +4 -1
  284. package/src/wirings/http/http-runner.ts +11 -26
  285. package/src/wirings/http/http.types.ts +0 -15
  286. package/src/wirings/http/index.ts +1 -7
  287. package/src/wirings/http/pikku-fetch-http-request.ts +2 -2
  288. package/src/wirings/http/web-request.ts +1 -1
  289. package/src/wirings/mcp/mcp-runner.ts +2 -16
  290. package/src/wirings/persona/persona-environments.test.ts +14 -3
  291. package/src/wirings/persona/persona.test.ts +13 -3
  292. package/src/wirings/persona/validate-personas.ts +5 -1
  293. package/src/wirings/queue/queue-runner.ts +1 -1
  294. package/src/wirings/rpc/addon-auth-tags.test.ts +1 -5
  295. package/src/wirings/rpc/rpc-runner.ts +58 -136
  296. package/src/wirings/rpc/rpc-types.ts +7 -0
  297. package/src/wirings/rpc/wire-addon.ts +3 -1
  298. package/src/wirings/secret/validate-secret-definitions.test.ts +69 -0
  299. package/src/wirings/secret/validate-secret-definitions.ts +2 -0
  300. package/src/wirings/trigger/pikku-trigger-service.ts +0 -5
  301. package/src/wirings/trigger/trigger-runner.ts +7 -5
  302. package/src/wirings/virtual-user/run-virtual-user.test.ts +18 -9
  303. package/src/wirings/virtual-user/run-virtual-user.ts +28 -15
  304. package/src/wirings/virtual-user/virtual-user-agents.test.ts +4 -1
  305. package/src/wirings/virtual-user/virtual-user-derive.ts +4 -25
  306. package/src/wirings/virtual-user/virtual-user-dispositions.test.ts +7 -2
  307. package/src/wirings/virtual-user/virtual-user-dispositions.ts +1 -4
  308. package/src/wirings/virtual-user/virtual-user-intents.test.ts +13 -3
  309. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +35 -2
  310. package/src/wirings/workflow/feature.ts +7 -4
  311. package/src/wirings/workflow/graph/graph-node.ts +1 -1
  312. package/src/wirings/workflow/graph/graph-runner.test.ts +4 -2
  313. package/src/wirings/workflow/graph/graph-runner.ts +13 -16
  314. package/src/wirings/workflow/graph/workflow-graph.types.ts +0 -5
  315. package/src/wirings/workflow/index.ts +12 -4
  316. package/src/wirings/workflow/pikku-scenario-service.ts +54 -25
  317. package/src/wirings/workflow/pikku-workflow-service.test.ts +2 -2
  318. package/src/wirings/workflow/pikku-workflow-service.ts +208 -630
  319. package/src/wirings/workflow/scenario-hooks.test.ts +51 -0
  320. package/src/wirings/workflow/workflow-approval.ts +185 -0
  321. package/src/wirings/workflow/workflow-child-run-session.test.ts +89 -0
  322. package/src/wirings/workflow/workflow-constants.ts +47 -0
  323. package/src/wirings/workflow/workflow-dispatch-durability.test.ts +1 -1
  324. package/src/wirings/workflow/workflow-dispatch-relay.test.ts +128 -0
  325. package/src/wirings/workflow/workflow-errors.ts +128 -0
  326. package/src/wirings/workflow/workflow-inline-authority.test.ts +12 -6
  327. package/src/wirings/workflow/workflow-meta-resolver.ts +45 -0
  328. package/src/wirings/workflow/workflow-missing-meta.test.ts +92 -0
  329. package/src/wirings/workflow/workflow-queue-routing.ts +85 -0
  330. package/src/wirings/workflow/workflow-queue-wiring.ts +149 -0
  331. package/src/wirings/workflow/workflow-recovery.ts +162 -0
  332. package/src/wirings/workflow/workflow-retry-policy.test.ts +1 -1
  333. package/src/wirings/workflow/workflow-run-authority.test.ts +215 -0
  334. package/src/wirings/workflow/workflow-run-engine.types.ts +91 -0
  335. package/src/wirings/workflow/workflow-run-ownership.ts +36 -0
  336. package/src/wirings/workflow/workflow-suspend.ts +61 -0
  337. package/src/wirings/workflow/workflow.types.ts +7 -0
  338. package/src/wirings-stay-decoupled.test.ts +123 -0
  339. package/tsconfig.json +1 -1
  340. package/tsconfig.tsbuildinfo +1 -1
  341. package/src/internal.ts +0 -10
@@ -0,0 +1,121 @@
1
+ import type { CoreSingletonServices } from '../../types/core.types.js'
2
+ import type { PikkuAIMiddlewareHooks } from './ai-agent.types.js'
3
+
4
+ /** Exactly what `modifyInput` accepts and returns, taken from the hook itself. */
5
+ type TurnInput = Pick<
6
+ Parameters<NonNullable<PikkuAIMiddlewareHooks['modifyInput']>>[1],
7
+ 'messages' | 'instructions'
8
+ >
9
+
10
+ /**
11
+ * Run every `modifyInput` hook in order, threading each result into the next.
12
+ *
13
+ * Shared by the four places a turn starts — first turn and resume, on both the
14
+ * streaming and non-streaming paths. `sharedNotes` is one bag per run that the
15
+ * caller keeps, because hooks use it to pass things forward (`voiceInput`
16
+ * leaves the transcript there for `voiceOutput`).
17
+ */
18
+ export const applyInputMiddleware = async (
19
+ aiMiddlewares: PikkuAIMiddlewareHooks[],
20
+ singletonServices: CoreSingletonServices,
21
+ input: TurnInput,
22
+ sharedNotes: Record<string, unknown>
23
+ ): Promise<TurnInput> => {
24
+ let { messages, instructions } = input
25
+ for (const mw of aiMiddlewares) {
26
+ if (!mw.modifyInput) continue
27
+ const result = await mw.modifyInput(singletonServices, {
28
+ messages,
29
+ instructions,
30
+ shared: sharedNotes,
31
+ })
32
+ messages = result.messages
33
+ instructions = result.instructions
34
+ }
35
+ return { messages, instructions }
36
+ }
37
+
38
+ /** The step fields both agent paths hand to `afterStep`, taken from the hook. */
39
+ type StepResult = Omit<
40
+ Parameters<NonNullable<PikkuAIMiddlewareHooks['afterStep']>>[1],
41
+ 'stepNumber'
42
+ >
43
+
44
+ /**
45
+ * Notify every `afterStep` hook. The payload is the step verbatim plus its
46
+ * ordinal — a hook that wants the run's totals accumulates them itself.
47
+ */
48
+ export const notifyAfterStep = async (
49
+ aiMiddlewares: PikkuAIMiddlewareHooks[],
50
+ singletonServices: CoreSingletonServices,
51
+ stepNumber: number,
52
+ stepResult: StepResult
53
+ ): Promise<void> => {
54
+ for (const mw of aiMiddlewares) {
55
+ if (!mw.afterStep) continue
56
+ await mw.afterStep(singletonServices, {
57
+ stepNumber,
58
+ text: stepResult.text,
59
+ toolCalls: stepResult.toolCalls,
60
+ toolResults: stepResult.toolResults,
61
+ usage: stepResult.usage,
62
+ finishReason: stepResult.finishReason,
63
+ })
64
+ }
65
+ }
66
+
67
+ /**
68
+ * The step shape kept on the run: each tool call paired with its result.
69
+ *
70
+ * A result is stringified unless it already is a string, because this is what
71
+ * gets persisted and read back as history — an object there is unreadable.
72
+ */
73
+ export const toAccumulatedStep = (stepResult: StepResult) => ({
74
+ usage: stepResult.usage,
75
+ toolCalls: stepResult.toolCalls.map((tc) => {
76
+ const tr = stepResult.toolResults.find(
77
+ (r) => r.toolCallId === tc.toolCallId
78
+ )
79
+ return {
80
+ name: tc.toolName,
81
+ args: tc.args as Record<string, unknown>,
82
+ result:
83
+ typeof tr?.result === 'string'
84
+ ? tr.result
85
+ : JSON.stringify(tr?.result ?? ''),
86
+ }
87
+ }),
88
+ })
89
+
90
+ type PendingApproval = {
91
+ toolName: string
92
+ args: unknown
93
+ reason?: string
94
+ }
95
+
96
+ type ToolDef = {
97
+ name: string
98
+ approvalDescriptionFn?: (args: any) => Promise<string> | string
99
+ }
100
+
101
+ /**
102
+ * Fill in each approval's human-readable reason from the tool's own
103
+ * `approvalDescriptionFn`, where it did not supply one.
104
+ *
105
+ * A description that throws is swallowed: the approval still has to be raised,
106
+ * and a gate that disappears because its label failed to render is the worst
107
+ * available outcome.
108
+ */
109
+ export const describeApprovals = async (
110
+ approvals: PendingApproval[],
111
+ tools: ToolDef[]
112
+ ): Promise<void> => {
113
+ for (const approval of approvals) {
114
+ if (approval.reason) continue
115
+ const toolDef = tools.find((t) => t.name === approval.toolName)
116
+ if (!toolDef?.approvalDescriptionFn) continue
117
+ try {
118
+ approval.reason = await toolDef.approvalDescriptionFn(approval.args)
119
+ } catch {}
120
+ }
121
+ }
@@ -413,7 +413,7 @@ export type AIStreamEvent =
413
413
  toolName: string
414
414
  args: unknown
415
415
  reason?: string
416
- runId?: string
416
+ runId: string
417
417
  agent?: string
418
418
  session?: string
419
419
  }
@@ -164,12 +164,7 @@ export const voiceInput = (config?: {
164
164
  // to send, and a message with no content is not a question.
165
165
  if (updatedContent.length === 0) throw new NoSpeechDetectedError()
166
166
 
167
- // Only when something was actually heard. A turn can carry audio that all
168
- // reads as non-speech and still have content — an image with a silent
169
- // caption clip — which leaves nothing above to throw. Recording `''` here
170
- // would send a transcript event saying the user said nothing, and a client
171
- // that tells "not transcribed yet" from "transcribed" by the key being
172
- // absent would render the turn as an empty bubble rather than a pending one.
167
+ // knowledge: decisions/internals/an-empty-transcript-is-not-recorded.md
173
168
  if (heard.length > 0) {
174
169
  shared[SPOKEN_TRANSCRIPT] = heard.join(' ')
175
170
  }
@@ -206,11 +206,7 @@ export const voiceOutput = (config?: {
206
206
  }
207
207
  if (!aiAgentRunner?.generateSpeech) return event
208
208
 
209
- // Only an explicit `false` silences the reply. `voiceInput` sets this on
210
- // every turn it handles, so `false` means a real user really typed; the
211
- // key being absent means nothing reported either way — no voice input is
212
- // wired — and that caller's replies are spoken exactly as they were before
213
- // this option existed.
209
+ // knowledge: decisions/internals/voice-output-speaks-unless-voice-input-explicitly-says-otherwise.md
214
210
  if (!config?.always && shared[SPOKEN_TURN] === false) return event
215
211
 
216
212
  /**
@@ -228,13 +224,7 @@ export const voiceOutput = (config?: {
228
224
  )
229
225
  }
230
226
 
231
- // Checked per sentence but announced once per reply: a bilingual answer
232
- // should still speak the half it can, and repeating the notice for
233
- // every sentence of a long one would bury the reply itself.
234
- //
235
- // Per sentence is also what makes the voice right. A reply that answers
236
- // in English and then quotes a Chinese title is two sentences in two
237
- // scripts, and each is synthesized in the voice its own script needs.
227
+ // knowledge: decisions/internals/speech-synthesis-picks-a-voice-per-sentence-and-warns-once.md
238
228
  let voice = config.voice
239
229
  if (config.speakableScripts) {
240
230
  const unspeakable = unspeakableScripts(text, config.speakableScripts)
@@ -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[]
@@ -206,7 +206,7 @@ export const runLocalChannel = async ({
206
206
 
207
207
  const { onMessage, onBinaryMessage } = processMessageHandlers(
208
208
  services,
209
- channelConfig as any,
209
+ channelConfig,
210
210
  channelHandler,
211
211
  userSession
212
212
  )
@@ -34,7 +34,8 @@ export abstract class PikkuAbstractChannelHandler<
34
34
  setState: (s) => {
35
35
  channelState = s
36
36
  },
37
- getState: () => channelState as any,
37
+ // knowledge: decisions/internals/channel-state-accessors-are-unsound-generics-that-every-implementation-asserts.md
38
+ getState: () => channelState as never,
38
39
  clearState: () => {
39
40
  channelState = undefined
40
41
  },
@@ -117,7 +117,8 @@ export const runChannelConnect = async ({
117
117
  channelName: channelConfig.name,
118
118
  })
119
119
  channel.setState = (state) => channelStore.setState(channelId, state)
120
- channel.getState = () => channelStore.getState(channelId) as any
120
+ // knowledge: decisions/internals/channel-state-accessors-are-unsound-generics-that-every-implementation-asserts.md
121
+ channel.getState = () => channelStore.getState(channelId) as never
121
122
  channel.clearState = () => channelStore.clearState(channelId)
122
123
 
123
124
  const wire: PikkuRawWire = {
@@ -198,7 +199,8 @@ export const runChannelDisconnect = async ({
198
199
  channelName,
199
200
  })
200
201
  channel.setState = (state) => channelStore.setState(channelId, state)
201
- channel.getState = () => channelStore.getState(channelId) as any
202
+ // knowledge: decisions/internals/channel-state-accessors-are-unsound-generics-that-every-implementation-asserts.md
203
+ channel.getState = () => channelStore.getState(channelId) as never
202
204
  channel.clearState = () => channelStore.clearState(channelId)
203
205
 
204
206
  const userSession = new PikkuSessionService<CoreUserSession>(
@@ -271,7 +273,8 @@ export const runChannelMessage = async (
271
273
  channelName,
272
274
  })
273
275
  channel.setState = (state) => channelStore.setState(channelId, state)
274
- channel.getState = () => channelStore.getState(channelId) as any
276
+ // knowledge: decisions/internals/channel-state-accessors-are-unsound-generics-that-every-implementation-asserts.md
277
+ channel.getState = () => channelStore.getState(channelId) as never
275
278
  channel.clearState = () => channelStore.clearState(channelId)
276
279
 
277
280
  const userSession = new PikkuSessionService<CoreUserSession>(
@@ -90,7 +90,9 @@ export async function executeCLIViaChannel({
90
90
  return
91
91
  }
92
92
 
93
- renderer(null as any, response, undefined)
93
+ // A CLI channel response is rendered off-process: there are no services
94
+ // to hand the renderer, and every CLI renderer ignores the argument.
95
+ renderer(null as never, response, undefined)
94
96
  }
95
97
 
96
98
  commandRoute.subscribe(commandId, responseHandler)
@@ -32,7 +32,10 @@ export type CorePikkuCLIClientRender<Data> = (
32
32
  ) => void | Promise<void>
33
33
 
34
34
  /** The normal case once the server owns the command tree. */
35
- const defaultJSONRenderer: CorePikkuCLIClientRender<any> = (_services, data) => {
35
+ const defaultJSONRenderer: CorePikkuCLIClientRender<any> = (
36
+ _services,
37
+ data
38
+ ) => {
36
39
  console.log(JSON.stringify(data))
37
40
  }
38
41
 
@@ -16,6 +16,7 @@ import type {
16
16
  CLIMeta,
17
17
  } from './cli.types.js'
18
18
  import type {
19
+ CoreConfig,
19
20
  CoreSingletonServices,
20
21
  CoreServices,
21
22
  CreateWireServices,
@@ -24,7 +25,7 @@ import type {
24
25
  CreateSingletonServices,
25
26
  } from '../../types/core.types.js'
26
27
  import type { PikkuChannel } from '../channel/channel.types.js'
27
- import { unsupportedChannelRemote } from '../channel/channel-rpc.js'
28
+ import { unsupportedChannelRemote } from '../channel/channel-rpc.types.js'
28
29
  import {
29
30
  PikkuSessionService,
30
31
  createMiddlewareSessionWireProps,
@@ -321,7 +322,8 @@ export async function runCLICommand({
321
322
  setState: (s) => {
322
323
  cliState = s
323
324
  },
324
- getState: () => cliState as any,
325
+ // knowledge: decisions/internals/channel-state-accessors-are-unsound-generics-that-every-implementation-asserts.md
326
+ getState: () => cliState as never,
325
327
  clearState: () => {
326
328
  cliState = undefined
327
329
  },
@@ -372,7 +374,8 @@ export async function runCLICommand({
372
374
  if (result !== undefined && !onOutput) {
373
375
  const commandRenderer = programData?.renderers[commandId]
374
376
  const jsonMode =
375
- (data as any).json === true || (data as any).output === 'json'
377
+ (data as { json?: unknown; output?: unknown }).json === true ||
378
+ (data as { json?: unknown; output?: unknown }).output === 'json'
376
379
  const finalRenderer: CorePikkuCLIRender<any> | undefined =
377
380
  jsonMode && commandRenderer !== undefined
378
381
  ? defaultJSONRenderer
@@ -491,7 +494,7 @@ export async function executeCLI({
491
494
 
492
495
  const config = createConfig
493
496
  ? await createConfig(new LocalVariablesService(), data)
494
- : ({} as any)
497
+ : ({} as CoreConfig)
495
498
 
496
499
  const singletonServices = await createSingletonServices(config)
497
500
  pikkuState(null, 'package', 'singletonServices', singletonServices)