@pikku/core 0.12.71 → 0.12.74

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 (819) hide show
  1. package/CHANGELOG.md +848 -0
  2. package/dist/crypto-utils.d.ts +30 -5
  3. package/dist/crypto-utils.js +146 -41
  4. package/dist/dev/hot-reload.js +11 -30
  5. package/dist/dev/module-runner.d.ts +3 -7
  6. package/dist/dev/module-runner.js +4 -10
  7. package/dist/dev/reload-meta.d.ts +8 -20
  8. package/dist/dev/reload-meta.js +9 -29
  9. package/dist/errors/error-handler.d.ts +5 -30
  10. package/dist/errors/error-handler.js +16 -32
  11. package/dist/errors/errors.d.ts +32 -151
  12. package/dist/errors/errors.js +55 -157
  13. package/dist/function/abort-scope.d.ts +47 -0
  14. package/dist/function/abort-scope.js +63 -0
  15. package/dist/function/function-runner.js +40 -30
  16. package/dist/function/functions.types.d.ts +44 -136
  17. package/dist/function/functions.types.js +0 -58
  18. package/dist/function/list.types.d.ts +12 -62
  19. package/dist/function/list.types.js +4 -25
  20. package/dist/handle-error.d.ts +0 -11
  21. package/dist/handle-error.js +6 -18
  22. package/dist/index.d.ts +5 -2
  23. package/dist/index.js +4 -1
  24. package/dist/middleware/auth-apikey.d.ts +3 -18
  25. package/dist/middleware/auth-apikey.js +0 -17
  26. package/dist/middleware/auth-bearer.d.ts +6 -41
  27. package/dist/middleware/auth-bearer.js +3 -40
  28. package/dist/middleware/auth-cookie.d.ts +5 -27
  29. package/dist/middleware/auth-cookie.js +2 -26
  30. package/dist/middleware/cors.d.ts +7 -34
  31. package/dist/middleware/cors.js +7 -34
  32. package/dist/middleware/remote-auth.d.ts +3 -1
  33. package/dist/middleware/remote-auth.js +3 -2
  34. package/dist/middleware/telemetry.d.ts +8 -33
  35. package/dist/middleware/telemetry.js +2 -31
  36. package/dist/middleware-runner.d.ts +4 -55
  37. package/dist/middleware-runner.js +5 -74
  38. package/dist/permissions.d.ts +3 -44
  39. package/dist/permissions.js +19 -71
  40. package/dist/pikku-request.d.ts +0 -6
  41. package/dist/pikku-request.js +0 -6
  42. package/dist/pikku-state.d.ts +0 -26
  43. package/dist/pikku-state.js +2 -30
  44. package/dist/remote.d.ts +3 -5
  45. package/dist/remote.js +8 -7
  46. package/dist/schema.d.ts +7 -17
  47. package/dist/schema.js +30 -18
  48. package/dist/scopes.d.ts +4 -23
  49. package/dist/scopes.js +7 -48
  50. package/dist/services/ai-agent-runner-service.d.ts +20 -0
  51. package/dist/services/ai-embedding-service.d.ts +2 -25
  52. package/dist/services/audit-service.js +1 -2
  53. package/dist/services/content-service.d.ts +1 -46
  54. package/dist/services/credential-service.d.ts +3 -40
  55. package/dist/services/deployment-service.d.ts +3 -9
  56. package/dist/services/gateway-service.d.ts +0 -15
  57. package/dist/services/http-personas.d.ts +80 -0
  58. package/dist/services/http-personas.js +233 -0
  59. package/dist/services/in-memory-queue-service.d.ts +0 -14
  60. package/dist/services/in-memory-queue-service.js +1 -15
  61. package/dist/services/in-memory-trigger-service.d.ts +0 -18
  62. package/dist/services/in-memory-trigger-service.js +1 -18
  63. package/dist/services/in-memory-workflow-service.d.ts +0 -16
  64. package/dist/services/in-memory-workflow-service.js +4 -33
  65. package/dist/services/index.d.ts +5 -6
  66. package/dist/services/index.js +2 -5
  67. package/dist/services/istanbul-coverage-service.d.ts +1 -5
  68. package/dist/services/istanbul-coverage-service.js +2 -8
  69. package/dist/services/jwt-service.d.ts +1 -16
  70. package/dist/services/local-content.d.ts +13 -2
  71. package/dist/services/local-content.js +40 -13
  72. package/dist/services/local-gateway-service.d.ts +0 -16
  73. package/dist/services/local-gateway-service.js +2 -17
  74. package/dist/services/local-secrets.d.ts +0 -4
  75. package/dist/services/local-secrets.js +0 -4
  76. package/dist/services/logger-console.d.ts +3 -7
  77. package/dist/services/logger-console.js +3 -7
  78. package/dist/services/logger.d.ts +2 -37
  79. package/dist/services/meta-service.d.ts +23 -26
  80. package/dist/services/meta-service.js +22 -36
  81. package/dist/services/personas-service.d.ts +134 -0
  82. package/dist/services/personas-service.js +40 -0
  83. package/dist/services/pikku-user-id.js +0 -4
  84. package/dist/services/queue-webhook-service.d.ts +2 -36
  85. package/dist/services/queue-webhook-service.js +9 -41
  86. package/dist/services/scheduler-service.d.ts +1 -50
  87. package/dist/services/scheduler-service.js +0 -10
  88. package/dist/services/schema-service.d.ts +1 -24
  89. package/dist/services/scope-service.d.ts +49 -34
  90. package/dist/services/scoped-secret-service.d.ts +0 -4
  91. package/dist/services/scoped-secret-service.js +0 -4
  92. package/dist/services/secret-host-binding.d.ts +8 -0
  93. package/dist/services/secret-host-binding.js +36 -0
  94. package/dist/services/secret-service.d.ts +5 -33
  95. package/dist/services/secretless.d.ts +6 -0
  96. package/dist/services/secretless.js +21 -0
  97. package/dist/services/stub-tracker.d.ts +7 -18
  98. package/dist/services/stub-tracker.js +8 -18
  99. package/dist/services/system-role-guard.d.ts +33 -0
  100. package/dist/services/system-role-guard.js +38 -0
  101. package/dist/services/trigger-service.d.ts +0 -12
  102. package/dist/services/typed-secret-service.d.ts +0 -7
  103. package/dist/services/typed-secret-service.js +1 -7
  104. package/dist/services/v8-coverage-service.d.ts +2 -3
  105. package/dist/services/v8-coverage-service.js +1 -2
  106. package/dist/services/variables-service.d.ts +1 -8
  107. package/dist/services/webhook-service.d.ts +19 -63
  108. package/dist/services/webhook-service.js +6 -20
  109. package/dist/services/workflow-service.d.ts +3 -15
  110. package/dist/testing/service-tests.js +0 -17
  111. package/dist/time-utils.d.ts +0 -16
  112. package/dist/time-utils.js +1 -19
  113. package/dist/types/core.types.d.ts +99 -219
  114. package/dist/types/core.types.js +0 -42
  115. package/dist/types/state.types.d.ts +4 -9
  116. package/dist/utils/hmac.d.ts +4 -10
  117. package/dist/utils/hmac.js +4 -10
  118. package/dist/utils/safe-fetch.d.ts +7 -35
  119. package/dist/utils/safe-fetch.js +13 -53
  120. package/dist/utils.d.ts +1 -6
  121. package/dist/utils.js +6 -15
  122. package/dist/wirings/actor-flow/actor-flow.types.d.ts +1 -34
  123. package/dist/wirings/actor-flow/index.d.ts +0 -9
  124. package/dist/wirings/actor-flow/run-conversation.d.ts +5 -5
  125. package/dist/wirings/actor-flow/run-conversation.js +14 -7
  126. package/dist/wirings/ai-agent/ai-agent-agui.d.ts +0 -5
  127. package/dist/wirings/ai-agent/ai-agent-agui.js +34 -12
  128. package/dist/wirings/ai-agent/ai-agent-helpers.d.ts +7 -0
  129. package/dist/wirings/ai-agent/ai-agent-helpers.js +7 -0
  130. package/dist/wirings/ai-agent/ai-agent-interrupt.d.ts +153 -0
  131. package/dist/wirings/ai-agent/ai-agent-interrupt.js +256 -0
  132. package/dist/wirings/ai-agent/ai-agent-memory.js +0 -2
  133. package/dist/wirings/ai-agent/ai-agent-model-config.d.ts +0 -9
  134. package/dist/wirings/ai-agent/ai-agent-model-config.js +1 -9
  135. package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +10 -99
  136. package/dist/wirings/ai-agent/ai-agent-prepare.js +58 -131
  137. package/dist/wirings/ai-agent/ai-agent-registry.d.ts +2 -1
  138. package/dist/wirings/ai-agent/ai-agent-registry.js +5 -1
  139. package/dist/wirings/ai-agent/ai-agent-runner.js +49 -20
  140. package/dist/wirings/ai-agent/ai-agent-stream.d.ts +25 -2
  141. package/dist/wirings/ai-agent/ai-agent-stream.js +153 -63
  142. package/dist/wirings/ai-agent/ai-agent.types.d.ts +84 -4
  143. package/dist/wirings/ai-agent/index.d.ts +6 -4
  144. package/dist/wirings/ai-agent/index.js +5 -4
  145. package/dist/wirings/ai-agent/voice-input.d.ts +39 -1
  146. package/dist/wirings/ai-agent/voice-input.js +46 -3
  147. package/dist/wirings/ai-agent/voice-output.d.ts +54 -1
  148. package/dist/wirings/ai-agent/voice-output.js +153 -50
  149. package/dist/wirings/channel/channel-common.d.ts +7 -20
  150. package/dist/wirings/channel/channel-common.js +7 -21
  151. package/dist/wirings/channel/channel-handler.js +25 -6
  152. package/dist/wirings/channel/channel-host-rpc.d.ts +25 -0
  153. package/dist/wirings/channel/channel-host-rpc.js +38 -0
  154. package/dist/wirings/channel/channel-middleware-runner.d.ts +0 -12
  155. package/dist/wirings/channel/channel-middleware-runner.js +0 -12
  156. package/dist/wirings/channel/channel-rpc-registry.d.ts +31 -0
  157. package/dist/wirings/channel/channel-rpc-registry.js +89 -0
  158. package/dist/wirings/channel/channel-rpc-responder.d.ts +15 -0
  159. package/dist/wirings/channel/channel-rpc-responder.js +71 -0
  160. package/dist/wirings/channel/channel-rpc-service.d.ts +40 -0
  161. package/dist/wirings/channel/channel-rpc-service.js +106 -0
  162. package/dist/wirings/channel/channel-rpc-validators.d.ts +14 -0
  163. package/dist/wirings/channel/channel-rpc-validators.js +30 -0
  164. package/dist/wirings/channel/channel-rpc.d.ts +5 -0
  165. package/dist/wirings/channel/channel-rpc.js +5 -0
  166. package/dist/wirings/channel/channel-rpc.types.d.ts +90 -0
  167. package/dist/wirings/channel/channel-rpc.types.js +50 -0
  168. package/dist/wirings/channel/channel-runner.d.ts +0 -4
  169. package/dist/wirings/channel/channel-runner.js +0 -14
  170. package/dist/wirings/channel/channel-store.d.ts +0 -10
  171. package/dist/wirings/channel/channel.types.d.ts +12 -1
  172. package/dist/wirings/channel/define-channel-routes.d.ts +0 -20
  173. package/dist/wirings/channel/define-channel-routes.js +0 -20
  174. package/dist/wirings/channel/eventhub-service.d.ts +0 -18
  175. package/dist/wirings/channel/index.d.ts +4 -1
  176. package/dist/wirings/channel/index.js +2 -0
  177. package/dist/wirings/channel/local/local-channel-runner.js +3 -1
  178. package/dist/wirings/channel/local/local-eventhub-service.d.ts +0 -33
  179. package/dist/wirings/channel/local/local-eventhub-service.js +2 -36
  180. package/dist/wirings/channel/log-channels.d.ts +0 -4
  181. package/dist/wirings/channel/log-channels.js +0 -4
  182. package/dist/wirings/channel/pikku-abstract-channel-handler.js +6 -0
  183. package/dist/wirings/channel/serverless/serverless-channel-runner.js +2 -5
  184. package/dist/wirings/cli/channel/cli-approval.d.ts +41 -0
  185. package/dist/wirings/cli/channel/cli-approval.js +81 -0
  186. package/dist/wirings/cli/channel/cli-channel-runner.d.ts +0 -4
  187. package/dist/wirings/cli/channel/cli-channel-runner.js +3 -25
  188. package/dist/wirings/cli/channel/cli-raw-channel-runner.d.ts +47 -9
  189. package/dist/wirings/cli/channel/cli-raw-channel-runner.js +24 -16
  190. package/dist/wirings/cli/channel/cli-raw-client-runner.d.ts +20 -0
  191. package/dist/wirings/cli/channel/cli-raw-client-runner.js +121 -0
  192. package/dist/wirings/cli/channel/index.d.ts +4 -0
  193. package/dist/wirings/cli/channel/index.js +2 -0
  194. package/dist/wirings/cli/cli-runner.d.ts +20 -20
  195. package/dist/wirings/cli/cli-runner.js +28 -89
  196. package/dist/wirings/cli/cli.types.d.ts +14 -3
  197. package/dist/wirings/cli/command-parser.d.ts +1 -10
  198. package/dist/wirings/cli/command-parser.js +10 -87
  199. package/dist/wirings/cli/define-cli-commands.d.ts +1 -17
  200. package/dist/wirings/cli/define-cli-commands.js +1 -17
  201. package/dist/wirings/credential/credential.types.d.ts +0 -12
  202. package/dist/wirings/credential/define-credential.d.ts +48 -0
  203. package/dist/wirings/credential/define-credential.js +47 -0
  204. package/dist/wirings/credential/index.d.ts +1 -1
  205. package/dist/wirings/credential/index.js +1 -1
  206. package/dist/wirings/credential/validate-credential-definitions.d.ts +2 -4
  207. package/dist/wirings/gateway/gateway-runner.d.ts +1 -20
  208. package/dist/wirings/gateway/gateway-runner.js +8 -105
  209. package/dist/wirings/gateway/gateway.types.d.ts +7 -80
  210. package/dist/wirings/http/http-routes.d.ts +0 -63
  211. package/dist/wirings/http/http-routes.js +0 -63
  212. package/dist/wirings/http/http-runner.d.ts +1 -100
  213. package/dist/wirings/http/http-runner.js +12 -166
  214. package/dist/wirings/http/http.types.d.ts +16 -55
  215. package/dist/wirings/http/index.d.ts +2 -1
  216. package/dist/wirings/http/index.js +1 -1
  217. package/dist/wirings/http/log-http-routes.d.ts +0 -4
  218. package/dist/wirings/http/log-http-routes.js +0 -4
  219. package/dist/wirings/http/pikku-fetch-http-request.d.ts +6 -36
  220. package/dist/wirings/http/pikku-fetch-http-request.js +56 -50
  221. package/dist/wirings/http/pikku-fetch-http-response.js +0 -3
  222. package/dist/wirings/http/routers/path-to-regex.js +2 -13
  223. package/dist/wirings/http/web-request.d.ts +0 -8
  224. package/dist/wirings/http/web-request.js +25 -17
  225. package/dist/wirings/mcp/mcp-runner.d.ts +2 -0
  226. package/dist/wirings/mcp/mcp-runner.js +6 -16
  227. package/dist/wirings/mcp/mcp.types.d.ts +2 -35
  228. package/dist/wirings/oauth2/oauth2.types.d.ts +0 -28
  229. package/dist/wirings/oauth2/oauth2.types.js +0 -3
  230. package/dist/wirings/persona/define-personas.d.ts +28 -0
  231. package/dist/wirings/persona/define-personas.js +27 -0
  232. package/dist/wirings/persona/index.d.ts +21 -0
  233. package/dist/wirings/persona/index.js +17 -0
  234. package/dist/wirings/persona/persona-email.d.ts +37 -0
  235. package/dist/wirings/persona/persona-email.js +69 -0
  236. package/dist/wirings/persona/persona-environments.d.ts +45 -0
  237. package/dist/wirings/persona/persona-environments.js +81 -0
  238. package/dist/wirings/persona/persona-mailbox.d.ts +101 -0
  239. package/dist/wirings/persona/persona-mailbox.js +53 -0
  240. package/dist/wirings/persona/persona.types.d.ts +125 -0
  241. package/dist/wirings/persona/persona.types.js +1 -0
  242. package/dist/wirings/persona/validate-personas.d.ts +53 -0
  243. package/dist/wirings/persona/validate-personas.js +94 -0
  244. package/dist/wirings/queue/index.d.ts +3 -0
  245. package/dist/wirings/queue/index.js +2 -3
  246. package/dist/wirings/queue/queue-identity.d.ts +28 -0
  247. package/dist/wirings/queue/queue-identity.js +102 -0
  248. package/dist/wirings/queue/queue-runner.d.ts +0 -19
  249. package/dist/wirings/queue/queue-runner.js +9 -30
  250. package/dist/wirings/queue/queue.types.d.ts +18 -89
  251. package/dist/wirings/queue/register-queue-helper.d.ts +0 -12
  252. package/dist/wirings/queue/register-queue-helper.js +0 -11
  253. package/dist/wirings/queue/signed-queue-service.d.ts +16 -0
  254. package/dist/wirings/queue/signed-queue-service.js +42 -0
  255. package/dist/wirings/queue/validate-worker-config.d.ts +2 -23
  256. package/dist/wirings/queue/validate-worker-config.js +0 -14
  257. package/dist/wirings/role/define-system-role.d.ts +32 -0
  258. package/dist/wirings/role/define-system-role.js +31 -0
  259. package/dist/wirings/role/index.d.ts +3 -0
  260. package/dist/wirings/role/index.js +2 -0
  261. package/dist/wirings/role/role.types.d.ts +43 -0
  262. package/dist/wirings/role/role.types.js +1 -0
  263. package/dist/wirings/role/validate-role-definitions.d.ts +21 -0
  264. package/dist/wirings/role/validate-role-definitions.js +71 -0
  265. package/dist/wirings/rpc/addon-runner.d.ts +0 -19
  266. package/dist/wirings/rpc/addon-runner.js +0 -51
  267. package/dist/wirings/rpc/remote-addon-auth.d.ts +1 -12
  268. package/dist/wirings/rpc/remote-addon-auth.js +1 -9
  269. package/dist/wirings/rpc/rpc-runner.d.ts +11 -18
  270. package/dist/wirings/rpc/rpc-runner.js +88 -105
  271. package/dist/wirings/rpc/rpc-types.d.ts +7 -6
  272. package/dist/wirings/rpc/wire-addon.d.ts +25 -0
  273. package/dist/wirings/rpc/wire-addon.js +62 -0
  274. package/dist/wirings/rpc/wire-remote-addon.d.ts +3 -28
  275. package/dist/wirings/rpc/wire-remote-addon.js +0 -8
  276. package/dist/wirings/scheduler/log-schedulers.d.ts +0 -4
  277. package/dist/wirings/scheduler/log-schedulers.js +0 -4
  278. package/dist/wirings/scheduler/scheduler-runner.d.ts +0 -1
  279. package/dist/wirings/scheduler/scheduler-runner.js +0 -1
  280. package/dist/wirings/scheduler/scheduler.types.d.ts +1 -14
  281. package/dist/wirings/scope/define-scope.d.ts +32 -0
  282. package/dist/wirings/scope/define-scope.js +31 -0
  283. package/dist/wirings/scope/index.d.ts +1 -1
  284. package/dist/wirings/scope/index.js +1 -1
  285. package/dist/wirings/scope/scope.types.d.ts +7 -9
  286. package/dist/wirings/scope/validate-scope-definitions.d.ts +2 -21
  287. package/dist/wirings/scope/validate-scope-definitions.js +3 -21
  288. package/dist/wirings/secret/index.d.ts +1 -1
  289. package/dist/wirings/secret/index.js +1 -1
  290. package/dist/wirings/secret/secret.types.d.ts +19 -15
  291. package/dist/wirings/secret/secret.types.js +1 -1
  292. package/dist/wirings/secret/validate-secret-definitions.d.ts +2 -4
  293. package/dist/wirings/trigger/trigger-runner.d.ts +0 -27
  294. package/dist/wirings/trigger/trigger-runner.js +1 -24
  295. package/dist/wirings/trigger/trigger.types.d.ts +1 -82
  296. package/dist/wirings/trigger/trigger.types.js +0 -34
  297. package/dist/wirings/variable/index.d.ts +1 -1
  298. package/dist/wirings/variable/index.js +1 -1
  299. package/dist/wirings/variable/validate-variable-definitions.d.ts +2 -4
  300. package/dist/wirings/variable/variable.types.d.ts +1 -13
  301. package/dist/wirings/variable/variable.types.js +1 -1
  302. package/dist/wirings/virtual-user/index.d.ts +27 -0
  303. package/dist/wirings/virtual-user/index.js +8 -0
  304. package/dist/wirings/virtual-user/run-virtual-user.d.ts +92 -0
  305. package/dist/wirings/virtual-user/run-virtual-user.js +478 -0
  306. package/dist/wirings/virtual-user/virtual-user-agents.d.ts +38 -0
  307. package/dist/wirings/virtual-user/virtual-user-agents.js +24 -0
  308. package/dist/wirings/virtual-user/virtual-user-catalogue.d.ts +92 -0
  309. package/dist/wirings/virtual-user/virtual-user-catalogue.js +134 -0
  310. package/dist/wirings/virtual-user/virtual-user-derive.d.ts +26 -0
  311. package/dist/wirings/virtual-user/virtual-user-derive.js +137 -0
  312. package/dist/wirings/virtual-user/virtual-user-dispositions.d.ts +79 -0
  313. package/dist/wirings/virtual-user/virtual-user-dispositions.js +128 -0
  314. package/dist/wirings/virtual-user/virtual-user-intents.d.ts +78 -0
  315. package/dist/wirings/virtual-user/virtual-user-intents.js +142 -0
  316. package/dist/wirings/virtual-user/virtual-user-rng.d.ts +24 -0
  317. package/dist/wirings/virtual-user/virtual-user-rng.js +44 -0
  318. package/dist/wirings/virtual-user/virtual-user-target.d.ts +21 -0
  319. package/dist/wirings/virtual-user/virtual-user-target.js +34 -0
  320. package/dist/wirings/virtual-user/virtual-user.types.d.ts +199 -0
  321. package/dist/wirings/virtual-user/virtual-user.types.js +8 -0
  322. package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +5 -5
  323. package/dist/wirings/workflow/dsl/workflow-runner.d.ts +0 -4
  324. package/dist/wirings/workflow/dsl/workflow-runner.js +0 -4
  325. package/dist/wirings/workflow/feature.d.ts +0 -19
  326. package/dist/wirings/workflow/feature.js +0 -19
  327. package/dist/wirings/workflow/graph/graph-node.d.ts +0 -98
  328. package/dist/wirings/workflow/graph/graph-node.js +0 -34
  329. package/dist/wirings/workflow/graph/graph-runner.js +6 -41
  330. package/dist/wirings/workflow/graph/wire-workflow-graph.d.ts +0 -4
  331. package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +0 -58
  332. package/dist/wirings/workflow/graph/workflow-graph.types.js +0 -6
  333. package/dist/wirings/workflow/index.d.ts +5 -7
  334. package/dist/wirings/workflow/index.js +3 -17
  335. package/dist/wirings/workflow/pikku-scenario-service.d.ts +87 -5
  336. package/dist/wirings/workflow/pikku-scenario-service.js +204 -42
  337. package/dist/wirings/workflow/pikku-workflow-service.d.ts +7 -459
  338. package/dist/wirings/workflow/pikku-workflow-service.js +58 -551
  339. package/dist/wirings/workflow/run-timeline.d.ts +0 -47
  340. package/dist/wirings/workflow/run-timeline.js +0 -22
  341. package/dist/wirings/workflow/scenario-cookie-jar.d.ts +0 -23
  342. package/dist/wirings/workflow/scenario-cookie-jar.js +0 -16
  343. package/dist/wirings/workflow/scenario-poll.d.ts +0 -15
  344. package/dist/wirings/workflow/scenario-poll.js +0 -12
  345. package/dist/wirings/workflow/scenario-prose.d.ts +0 -28
  346. package/dist/wirings/workflow/scenario-prose.js +0 -18
  347. package/dist/wirings/workflow/scenario-step-guards.d.ts +0 -13
  348. package/dist/wirings/workflow/scenario-step-guards.js +1 -14
  349. package/dist/wirings/workflow/scenario-step.types.d.ts +72 -6
  350. package/dist/wirings/workflow/scenario-step.types.js +5 -1
  351. package/dist/wirings/workflow/scenario-surface.d.ts +16 -0
  352. package/dist/wirings/workflow/scenario-surface.js +56 -0
  353. package/dist/wirings/workflow/workflow-invocation-id.d.ts +0 -18
  354. package/dist/wirings/workflow/workflow-invocation-id.js +2 -22
  355. package/dist/wirings/workflow/workflow-queue-workers.d.ts +0 -20
  356. package/dist/wirings/workflow/workflow-queue-workers.js +0 -19
  357. package/dist/wirings/workflow/workflow.types.d.ts +0 -197
  358. package/knowledge/decisions/index.md +19 -0
  359. package/knowledge/decisions/internals/a-secret-that-fails-to-decrypt-fails-the-whole-read.md +49 -0
  360. package/knowledge/decisions/internals/actor-flow-conversations-seed-a-hidden-kickoff-message.md +23 -0
  361. package/knowledge/decisions/internals/actor-flow-drives-the-target-through-a-transport-seam.md +24 -0
  362. package/knowledge/decisions/internals/actor-flow-verdicts-are-llm-self-evaluations.md +25 -0
  363. package/knowledge/decisions/internals/addon-package-roots-resolve-by-walking-node-module-search-paths.md +26 -0
  364. package/knowledge/decisions/internals/addon-singleton-services-are-cached-per-namespace-not-per-package.md +33 -0
  365. package/knowledge/decisions/internals/addon-workflow-names-are-prefixed-with-the-consumer-namespace.md +28 -0
  366. package/knowledge/decisions/internals/ai-agent-agui-bridge-obeys-the-client-ordering-contract.md +29 -0
  367. package/knowledge/decisions/internals/ai-agent-audio-chunks-carry-the-format-the-provider-returned.md +20 -0
  368. package/knowledge/decisions/internals/ai-agent-credential-suspensions-hide-the-tool-result.md +26 -0
  369. package/knowledge/decisions/internals/ai-agent-delegate-and-supervise-hide-different-text.md +26 -0
  370. package/knowledge/decisions/internals/ai-agent-llm-tool-arguments-have-nulls-stripped.md +23 -0
  371. package/knowledge/decisions/internals/ai-agent-model-config-stays-a-single-resolution-seam.md +25 -0
  372. package/knowledge/decisions/internals/ai-agent-onerror-hooks-cannot-change-the-failure.md +22 -0
  373. package/knowledge/decisions/internals/ai-agent-runner-methods-must-keep-their-receiver.md +22 -0
  374. package/knowledge/decisions/internals/ai-agent-stream-persistence-is-best-effort.md +27 -0
  375. package/knowledge/decisions/internals/ai-agent-sub-agents-inherit-the-parent-context-block.md +26 -0
  376. package/knowledge/decisions/internals/ai-agent-tool-execute-failures-are-logged-unconditionally.md +25 -0
  377. package/knowledge/decisions/internals/ai-agent-voice-input-transcribes-audio-parts-in-place.md +22 -0
  378. package/knowledge/decisions/internals/ai-agent-working-memory-is-persisted-only-when-valid.md +25 -0
  379. package/knowledge/decisions/internals/channel-message-handlers-accept-three-config-shapes.md +30 -0
  380. package/knowledge/decisions/internals/channel-middleware-caches-only-statically-resolved-middleware.md +31 -0
  381. package/knowledge/decisions/internals/channel-state-is-per-socket-session-state-is-per-user.md +29 -0
  382. package/knowledge/decisions/internals/channel-user-id-is-persisted-after-onconnect-middleware-runs.md +28 -0
  383. package/knowledge/decisions/internals/cli-option-names-are-camelcase-in-state-and-kebab-on-the-command-line.md +27 -0
  384. package/knowledge/decisions/internals/cli-parse-errors-are-routed-by-message-prefix.md +28 -0
  385. package/knowledge/decisions/internals/cli-stdout-is-reserved-for-machine-readable-output.md +34 -0
  386. package/knowledge/decisions/internals/cli-unknown-long-options-warn-instead-of-failing.md +29 -0
  387. package/knowledge/decisions/internals/core-data-classification-brand-is-an-optional-property.md +34 -0
  388. package/knowledge/decisions/internals/core-function-runner-restores-the-wire-fields-it-overwrites.md +44 -0
  389. package/knowledge/decisions/internals/core-hot-reload-merges-generated-meta-never-replaces-it.md +39 -0
  390. package/knowledge/decisions/internals/core-hot-reload-owns-its-module-registry.md +42 -0
  391. package/knowledge/decisions/internals/core-middleware-order-is-scope-then-priority.md +39 -0
  392. package/knowledge/decisions/internals/core-schema-defaults-apply-on-every-transport.md +43 -0
  393. package/knowledge/decisions/internals/core-scopes-are-an-and-gate-separate-from-permissions.md +38 -0
  394. package/knowledge/decisions/internals/core-state-is-a-global-map-written-only-at-registration-time.md +44 -0
  395. package/knowledge/decisions/internals/email-meta-is-read-uncached-because-codegen-rewrites-it-mid-session.md +27 -0
  396. package/knowledge/decisions/internals/gateway-adapters-resolve-lazily-and-are-promise-cached.md +32 -0
  397. package/knowledge/decisions/internals/gateway-webhook-challenges-echo-bytes-not-json.md +28 -0
  398. package/knowledge/decisions/internals/gateway-wiring-is-a-meta-wiring-over-http-and-channels.md +31 -0
  399. package/knowledge/decisions/internals/generated-src-paths-in-pikku-meta-are-absolute.md +26 -0
  400. package/knowledge/decisions/internals/http-request-bodies-are-read-once-and-shared.md +32 -0
  401. package/knowledge/decisions/internals/http-route-groups-cascade-config-in-a-fixed-order.md +28 -0
  402. package/knowledge/decisions/internals/http-router-matches-normalized-paths-but-returns-registered-ones.md +30 -0
  403. package/knowledge/decisions/internals/http-runner-logs-through-a-trace-scoped-logger-functions-do-not.md +26 -0
  404. package/knowledge/decisions/internals/http-set-cookie-headers-are-appended-never-joined.md +28 -0
  405. package/knowledge/decisions/internals/http-sse-streams-flush-headers-only-after-middleware.md +32 -0
  406. package/knowledge/decisions/internals/http-wiring-without-metadata-is-skipped-not-fatal.md +26 -0
  407. package/knowledge/decisions/internals/in-a-scenario-a-4xx-is-data-not-an-exception.md +25 -0
  408. package/knowledge/decisions/internals/in-memory-workflow-history-aliases-the-live-step-object.md +26 -0
  409. package/knowledge/decisions/internals/index.md +113 -0
  410. package/knowledge/decisions/internals/istanbul-statement-counts-attach-to-the-start-line-only.md +25 -0
  411. package/knowledge/decisions/internals/local-trigger-and-gateway-services-assume-a-single-process.md +26 -0
  412. package/knowledge/decisions/internals/node-only-builtins-are-imported-dynamically.md +24 -0
  413. package/knowledge/decisions/internals/queue-group-concurrency-keeps-one-shared-queue-fair.md +28 -0
  414. package/knowledge/decisions/internals/queue-jobs-always-carry-an-explicit-attempts-count.md +27 -0
  415. package/knowledge/decisions/internals/remote-addons-dispatch-over-http-instead-of-local-meta.md +31 -0
  416. package/knowledge/decisions/internals/rpc-names-resolve-through-package-scope-before-root.md +32 -0
  417. package/knowledge/decisions/internals/scenario-agent-calls-sign-in-on-401-only.md +27 -0
  418. package/knowledge/decisions/internals/scenario-meta-lives-apart-from-app-meta-but-merges-when-read-off-disk.md +26 -0
  419. package/knowledge/decisions/internals/scenario-steps-return-drained-response-records.md +27 -0
  420. package/knowledge/decisions/internals/scope-roots-may-be-co-declared-by-an-addon-and-its-host-app.md +30 -0
  421. package/knowledge/decisions/internals/serverless-channel-disconnect-must-tolerate-a-missing-channel.md +28 -0
  422. package/knowledge/decisions/internals/the-dev-queue-copies-prod-timing-and-serialization-semantics.md +30 -0
  423. package/knowledge/decisions/internals/the-embedding-model-is-pinned-per-service-and-doc-query-embedding-is-split.md +29 -0
  424. package/knowledge/decisions/internals/the-in-memory-workflow-service-is-inline-only-and-single-process.md +27 -0
  425. package/knowledge/decisions/internals/the-kek-salt-is-scoped-to-the-key-version.md +40 -0
  426. package/knowledge/decisions/internals/the-schema-service-is-never-stubbed.md +26 -0
  427. package/knowledge/decisions/internals/trigger-declaration-is-split-from-trigger-source.md +33 -0
  428. package/knowledge/decisions/internals/typed-secret-service-caches-for-the-process-lifetime.md +26 -0
  429. package/knowledge/decisions/internals/webhook-delivery-history-records-every-attempt-best-effort.md +26 -0
  430. package/knowledge/decisions/internals/webhook-service-collaborators-are-constructor-args-not-locator-lookups.md +25 -0
  431. package/knowledge/decisions/internals/whether-a-run-is-inline-is-read-from-the-run-record.md +58 -0
  432. package/knowledge/decisions/internals/workflow-approval-expiry-is-decided-from-a-recorded-deadline.md +34 -0
  433. package/knowledge/decisions/internals/workflow-core-never-imports-a-browser-driver.md +42 -0
  434. package/knowledge/decisions/internals/workflow-dsl-meta-separates-runtime-expressions-from-literals.md +38 -0
  435. package/knowledge/decisions/internals/workflow-features-resolve-scenarios-by-object-identity.md +29 -0
  436. package/knowledge/decisions/internals/workflow-graph-inline-and-queued-runs-share-one-planner.md +42 -0
  437. package/knowledge/decisions/internals/workflow-graph-node-notes-are-excluded-from-the-graph-hash.md +25 -0
  438. package/knowledge/decisions/internals/workflow-inline-runs-report-their-run-id-before-they-can-fail.md +29 -0
  439. package/knowledge/decisions/internals/workflow-invocation-id-is-the-dedupe-key-not-step-id.md +43 -0
  440. package/knowledge/decisions/internals/workflow-queued-step-dispatch-requires-an-explicit-opt-in.md +29 -0
  441. package/knowledge/decisions/internals/workflow-queues-are-per-workflow-by-default.md +42 -0
  442. package/knowledge/decisions/internals/workflow-repeated-step-names-get-an-ordinal-suffix.md +33 -0
  443. package/knowledge/decisions/internals/workflow-replay-reads-its-steps-once-and-caches-only-the-immutable-half.md +32 -0
  444. package/knowledge/decisions/internals/workflow-retries-are-owned-by-the-workflow-not-the-queue.md +31 -0
  445. package/knowledge/decisions/internals/workflow-run-capabilities-are-extensions-not-subclasses.md +39 -0
  446. package/knowledge/decisions/internals/workflow-run-mirror-is-never-a-source-of-truth.md +29 -0
  447. package/knowledge/decisions/internals/workflow-run-polling-backs-off-to-the-callers-ceiling.md +33 -0
  448. package/knowledge/decisions/internals/workflow-run-timeline-is-a-pure-fold-over-durable-history.md +37 -0
  449. package/knowledge/decisions/internals/workflow-scenario-assertions-never-retry-and-record-one-step.md +50 -0
  450. package/knowledge/decisions/internals/workflow-scenario-hooks-are-a-scenario-only-affordance.md +43 -0
  451. package/knowledge/decisions/internals/workflow-scenario-prose-is-rendered-from-typed-calls-not-parsed-from-english.md +32 -0
  452. package/knowledge/decisions/internals/workflow-scenario-quarantine-reason-lives-in-code.md +18 -0
  453. package/knowledge/decisions/internals/workflow-scenario-step-targets-are-string-literals-for-the-inspector.md +34 -0
  454. package/knowledge/decisions/internals/workflow-step-compensation-runs-as-its-own-durable-step.md +26 -0
  455. package/knowledge/decisions/internals/workflow-step-dispatch-failure-is-transient-not-a-run-failure.md +33 -0
  456. package/knowledge/decisions/internals/workflow-step-lock-is-held-only-to-claim-the-step.md +27 -0
  457. package/knowledge/decisions/internals/workflow-step-rpc-name-is-provenance-only.md +34 -0
  458. package/knowledge/decisions/internals/workflow-suspend-and-approval-reasons-are-durable-step-identities.md +38 -0
  459. package/knowledge/decisions/internals/workflow-suspended-runs-keep-their-in-process-context.md +30 -0
  460. package/knowledge/decisions/security/a-dropped-audit-write-is-always-logged.md +26 -0
  461. package/knowledge/decisions/security/actor-flow-missing-approval-decisions-default-to-denied.md +22 -0
  462. package/knowledge/decisions/security/actor-sign-in-is-proven-by-set-cookie-not-a-non-empty-jar.md +27 -0
  463. package/knowledge/decisions/security/actor-sign-in-only-works-for-actor-flagged-users.md +27 -0
  464. package/knowledge/decisions/security/addon-auth-and-tags-only-tighten.md +43 -0
  465. package/knowledge/decisions/security/addon-config-gates-apply-only-at-the-namespaced-rpc-boundary.md +52 -0
  466. package/knowledge/decisions/security/addon-scopes-are-resolved-where-the-function-runs.md +46 -0
  467. package/knowledge/decisions/security/ai-agent-approval-forwarding-requires-a-symbol-brand.md +27 -0
  468. package/knowledge/decisions/security/ai-agent-credential-requests-are-symbol-branded.md +37 -0
  469. package/knowledge/decisions/security/ai-agent-gate-requires-a-session-only-when-auth-is-true.md +31 -0
  470. package/knowledge/decisions/security/ai-agent-ownership-failures-never-echo-the-resource.md +23 -0
  471. package/knowledge/decisions/security/ai-agent-resume-re-runs-the-authorization-gate.md +22 -0
  472. package/knowledge/decisions/security/ai-agent-sessionless-deployments-have-no-thread-ownership.md +39 -0
  473. package/knowledge/decisions/security/ai-agent-thread-ownership-composes-the-session-principal.md +30 -0
  474. package/knowledge/decisions/security/ai-agent-tool-filtering-reads-the-live-function-config.md +24 -0
  475. package/knowledge/decisions/security/an-empty-owners-constraint-matches-nothing.md +30 -0
  476. package/knowledge/decisions/security/an-exposed-ungated-function-is-a-codegen-warning.md +49 -0
  477. package/knowledge/decisions/security/console-addon-privileged-functions-gate-themselves.md +76 -0
  478. package/knowledge/decisions/security/core-safe-fetch-blocks-ssrf-by-host-literal-not-dns.md +39 -0
  479. package/knowledge/decisions/security/core-secrets-use-a-per-secret-dek-wrapped-by-a-kek.md +37 -0
  480. package/knowledge/decisions/security/gateway-handlers-run-through-the-function-runner-gate.md +31 -0
  481. package/knowledge/decisions/security/gateway-middleware-sessions-must-be-bridged-onto-the-wire.md +30 -0
  482. package/knowledge/decisions/security/global-permissions-and-function-permissions-are-independent-gates.md +40 -0
  483. package/knowledge/decisions/security/http-error-detail-is-withheld-from-clients-in-production.md +33 -0
  484. package/knowledge/decisions/security/http-request-bodies-are-bounded-before-they-are-buffered.md +46 -0
  485. package/knowledge/decisions/security/index.md +55 -0
  486. package/knowledge/decisions/security/mcp-internal-error-details-are-double-gated-on-production.md +27 -0
  487. package/knowledge/decisions/security/passphrases-are-stretched-key-material-is-expanded.md +40 -0
  488. package/knowledge/decisions/security/permission-auth-filtering-requires-live-permission-functions.md +31 -0
  489. package/knowledge/decisions/security/pikku-carries-actor-scopes-as-data-and-the-app-grants-them.md +26 -0
  490. package/knowledge/decisions/security/queue-job-identities-are-signed-at-enqueue.md +69 -0
  491. package/knowledge/decisions/security/queue-jobs-carry-the-producers-pikku-user-id.md +39 -0
  492. package/knowledge/decisions/security/remote-addon-tokens-are-client-credentials-not-mesh-trust.md +34 -0
  493. package/knowledge/decisions/security/scaffold-features-are-authenticated-unless-opted-out.md +49 -0
  494. package/knowledge/decisions/security/scenario-step-functions-are-never-externally-invocable.md +30 -0
  495. package/knowledge/decisions/security/scope-resolution-happens-at-the-session-boundary-and-sync-never-deletes.md +28 -0
  496. package/knowledge/decisions/security/self-authentication-is-declared-not-detected.md +34 -0
  497. package/knowledge/decisions/security/signed-content-urls-bind-the-request-path.md +37 -0
  498. package/knowledge/decisions/security/webhook-bodies-are-signed-before-they-are-enqueued.md +25 -0
  499. package/knowledge/decisions/security/workflow-actor-steps-always-use-the-real-transport.md +34 -0
  500. package/knowledge/decisions/security/workflow-approval-payloads-are-validated-on-replay-inside-the-workflow.md +40 -0
  501. package/knowledge/decisions/security/workflow-queued-steps-rehydrate-their-session-from-the-run-wire.md +32 -0
  502. package/knowledge/decisions/security/workflow-scenario-sessions-are-isolated-per-actor-and-per-scenario.md +32 -0
  503. package/knowledge/decisions/security/workflow-scenario-steps-are-never-network-invocable.md +31 -0
  504. package/knowledge/index.md +24 -0
  505. package/knowledge/questions/index.md +15 -0
  506. package/package.json +4 -2
  507. package/run-tests.sh +0 -0
  508. package/src/crypto-utils.test.ts +460 -19
  509. package/src/crypto-utils.ts +283 -55
  510. package/src/data-classification.ts +1 -7
  511. package/src/dev/hot-reload.test.ts +0 -4
  512. package/src/dev/hot-reload.ts +11 -30
  513. package/src/dev/module-runner.ts +7 -32
  514. package/src/dev/reload-meta.ts +9 -29
  515. package/src/errors/error-handler.ts +20 -35
  516. package/src/errors/error.test.ts +30 -1
  517. package/src/errors/errors.ts +73 -157
  518. package/src/function/abort-scope.test.ts +97 -0
  519. package/src/function/abort-scope.ts +80 -0
  520. package/src/function/function-runner.test.ts +75 -7
  521. package/src/function/function-runner.ts +73 -30
  522. package/src/function/functions.types.ts +56 -136
  523. package/src/function/list.types.test.ts +3 -25
  524. package/src/function/list.types.ts +12 -62
  525. package/src/gopass-secrets-removed.test.ts +51 -0
  526. package/src/handle-error.test.ts +108 -1
  527. package/src/handle-error.ts +8 -18
  528. package/src/index.ts +60 -1
  529. package/src/middleware/auth-apikey.test.ts +0 -1
  530. package/src/middleware/auth-apikey.ts +0 -17
  531. package/src/middleware/auth-bearer.test.ts +0 -3
  532. package/src/middleware/auth-bearer.ts +3 -40
  533. package/src/middleware/auth-cookie.test.ts +0 -6
  534. package/src/middleware/auth-cookie.ts +2 -26
  535. package/src/middleware/cors.test.ts +34 -0
  536. package/src/middleware/cors.ts +12 -33
  537. package/src/middleware/remote-auth.test.ts +24 -9
  538. package/src/middleware/remote-auth.ts +10 -2
  539. package/src/middleware/telemetry.ts +2 -31
  540. package/src/middleware-runner.test.ts +0 -2
  541. package/src/middleware-runner.ts +5 -74
  542. package/src/permissions.test.ts +30 -0
  543. package/src/permissions.ts +24 -74
  544. package/src/pikku-request.ts +0 -6
  545. package/src/pikku-state.ts +2 -30
  546. package/src/production-barrels-stay-lean.test.ts +110 -0
  547. package/src/remote.test.ts +172 -0
  548. package/src/remote.ts +17 -7
  549. package/src/schema.test.ts +103 -0
  550. package/src/schema.ts +35 -18
  551. package/src/scopes.ts +7 -48
  552. package/src/services/ai-agent-runner-service.ts +20 -0
  553. package/src/services/ai-embedding-service.ts +3 -25
  554. package/src/services/audit-service.ts +1 -2
  555. package/src/services/content-service.ts +1 -46
  556. package/src/services/credential-service.ts +3 -40
  557. package/src/services/credential-wire-service.test.ts +0 -2
  558. package/src/services/deployment-service.ts +3 -9
  559. package/src/services/gateway-service.ts +0 -15
  560. package/src/services/{http-scenario-actors-converse.test.ts → http-personas-converse.test.ts} +38 -9
  561. package/src/services/{http-scenario-actors.test.ts → http-personas.test.ts} +39 -22
  562. package/src/services/{http-scenario-actors.ts → http-personas.ts} +85 -45
  563. package/src/services/in-memory-queue-service.ts +1 -15
  564. package/src/services/in-memory-trigger-service.ts +1 -18
  565. package/src/services/in-memory-workflow-service.test.ts +0 -13
  566. package/src/services/in-memory-workflow-service.ts +4 -38
  567. package/src/services/index.ts +20 -15
  568. package/src/services/istanbul-coverage-service.ts +2 -8
  569. package/src/services/jwt-service.ts +1 -16
  570. package/src/services/local-content.test.ts +159 -27
  571. package/src/services/local-content.ts +55 -23
  572. package/src/services/local-gateway-service.ts +2 -17
  573. package/src/services/local-secrets.ts +0 -4
  574. package/src/services/logger-console.test.ts +0 -1
  575. package/src/services/logger-console.ts +3 -7
  576. package/src/services/logger.ts +2 -37
  577. package/src/services/meta-service.test.ts +1 -5
  578. package/src/services/meta-service.ts +41 -61
  579. package/src/services/{scenario-actors-service.ts → personas-service.ts} +48 -43
  580. package/src/services/pikku-user-id.ts +0 -4
  581. package/src/services/queue-webhook-service.ts +9 -41
  582. package/src/services/scheduler-service.ts +1 -50
  583. package/src/services/schema-service.ts +1 -24
  584. package/src/services/scope-service.ts +50 -34
  585. package/src/services/scoped-secret-service.ts +0 -4
  586. package/src/services/secret-host-binding.test.ts +138 -0
  587. package/src/services/secret-host-binding.ts +51 -0
  588. package/src/services/secret-service.ts +5 -33
  589. package/src/services/secretless.test.ts +54 -0
  590. package/src/services/secretless.ts +29 -0
  591. package/src/services/stub-tracker.ts +8 -18
  592. package/src/services/system-role-guard.test.ts +93 -0
  593. package/src/services/system-role-guard.ts +71 -0
  594. package/src/services/trigger-service.ts +0 -12
  595. package/src/services/typed-secret-service.ts +1 -7
  596. package/src/services/v8-coverage-service.ts +3 -6
  597. package/src/services/variables-service.ts +1 -8
  598. package/src/services/webhook-service.ts +19 -63
  599. package/src/services/workflow-service.ts +3 -20
  600. package/src/testing/service-tests.ts +0 -26
  601. package/src/time-utils.ts +1 -19
  602. package/src/types/core.types.ts +116 -229
  603. package/src/types/state.types.ts +7 -9
  604. package/src/utils/hmac.ts +4 -10
  605. package/src/utils/safe-fetch.ts +13 -54
  606. package/src/utils.test.ts +11 -2
  607. package/src/utils.ts +6 -15
  608. package/src/wirings/actor-flow/actor-flow.types.ts +1 -34
  609. package/src/wirings/actor-flow/index.ts +0 -9
  610. package/src/wirings/actor-flow/run-conversation.test.ts +11 -6
  611. package/src/wirings/actor-flow/run-conversation.ts +19 -12
  612. package/src/wirings/ai-agent/ai-agent-agui.test.ts +75 -10
  613. package/src/wirings/ai-agent/ai-agent-agui.ts +36 -17
  614. package/src/wirings/ai-agent/ai-agent-helpers.ts +20 -0
  615. package/src/wirings/ai-agent/ai-agent-interrupt.test.ts +842 -0
  616. package/src/wirings/ai-agent/ai-agent-interrupt.ts +399 -0
  617. package/src/wirings/ai-agent/ai-agent-memory.ts +0 -2
  618. package/src/wirings/ai-agent/ai-agent-model-config.ts +1 -9
  619. package/src/wirings/ai-agent/ai-agent-prepare.test.ts +202 -31
  620. package/src/wirings/ai-agent/ai-agent-prepare.ts +82 -138
  621. package/src/wirings/ai-agent/ai-agent-registry.test.ts +191 -6
  622. package/src/wirings/ai-agent/ai-agent-registry.ts +18 -1
  623. package/src/wirings/ai-agent/ai-agent-resume-authorization.test.ts +0 -2
  624. package/src/wirings/ai-agent/ai-agent-runner.test.ts +11 -9
  625. package/src/wirings/ai-agent/ai-agent-runner.ts +67 -33
  626. package/src/wirings/ai-agent/ai-agent-stream.test.ts +205 -103
  627. package/src/wirings/ai-agent/ai-agent-stream.ts +192 -75
  628. package/src/wirings/ai-agent/ai-agent-thread-ownership.test.ts +301 -0
  629. package/src/wirings/ai-agent/ai-agent.types.ts +85 -4
  630. package/src/wirings/ai-agent/index.ts +35 -3
  631. package/src/wirings/ai-agent/voice-input.test.ts +72 -7
  632. package/src/wirings/ai-agent/voice-input.ts +48 -3
  633. package/src/wirings/ai-agent/voice-output.test.ts +422 -0
  634. package/src/wirings/ai-agent/voice-output.ts +216 -56
  635. package/src/wirings/channel/channel-common.ts +15 -20
  636. package/src/wirings/channel/channel-handler.test.ts +50 -0
  637. package/src/wirings/channel/channel-handler.ts +32 -11
  638. package/src/wirings/channel/channel-host-rpc.test.ts +150 -0
  639. package/src/wirings/channel/channel-host-rpc.ts +69 -0
  640. package/src/wirings/channel/channel-middleware-runner.test.ts +0 -1
  641. package/src/wirings/channel/channel-middleware-runner.ts +0 -12
  642. package/src/wirings/channel/channel-rpc-registry.ts +116 -0
  643. package/src/wirings/channel/channel-rpc-responder.ts +117 -0
  644. package/src/wirings/channel/channel-rpc-service.ts +146 -0
  645. package/src/wirings/channel/channel-rpc-validators.ts +65 -0
  646. package/src/wirings/channel/channel-rpc.test.ts +820 -0
  647. package/src/wirings/channel/channel-rpc.ts +5 -0
  648. package/src/wirings/channel/channel-rpc.types.ts +150 -0
  649. package/src/wirings/channel/channel-runner.ts +0 -14
  650. package/src/wirings/channel/channel-store.ts +0 -10
  651. package/src/wirings/channel/channel.types.ts +19 -8
  652. package/src/wirings/channel/define-channel-routes.ts +0 -20
  653. package/src/wirings/channel/eventhub-service.ts +0 -18
  654. package/src/wirings/channel/index.ts +35 -0
  655. package/src/wirings/channel/local/local-channel-handler.ts +3 -1
  656. package/src/wirings/channel/local/local-channel-runner.test.ts +0 -10
  657. package/src/wirings/channel/local/local-channel-runner.ts +3 -1
  658. package/src/wirings/channel/local/local-eventhub-service.test.ts +0 -13
  659. package/src/wirings/channel/local/local-eventhub-service.ts +2 -37
  660. package/src/wirings/channel/log-channels.ts +0 -4
  661. package/src/wirings/channel/pikku-abstract-channel-handler.test.ts +83 -2
  662. package/src/wirings/channel/pikku-abstract-channel-handler.ts +7 -0
  663. package/src/wirings/channel/serverless/serverless-channel-runner.ts +2 -5
  664. package/src/wirings/cli/channel/cli-approval.test.ts +177 -0
  665. package/src/wirings/cli/channel/cli-approval.ts +135 -0
  666. package/src/wirings/cli/channel/cli-channel-runner.ts +4 -26
  667. package/src/wirings/cli/channel/cli-raw-channel-runner.test.ts +169 -0
  668. package/src/wirings/cli/channel/cli-raw-channel-runner.ts +59 -16
  669. package/src/wirings/cli/channel/cli-raw-client-runner.test.ts +480 -0
  670. package/src/wirings/cli/channel/cli-raw-client-runner.ts +155 -0
  671. package/src/wirings/cli/channel/index.ts +9 -0
  672. package/src/wirings/cli/cli-runner.test.ts +0 -1
  673. package/src/wirings/cli/cli-runner.ts +46 -88
  674. package/src/wirings/cli/cli.types.ts +14 -3
  675. package/src/wirings/cli/command-parser.test.ts +0 -4
  676. package/src/wirings/cli/command-parser.ts +11 -91
  677. package/src/wirings/cli/define-cli-commands.ts +1 -17
  678. package/src/wirings/credential/credential.types.ts +0 -12
  679. package/src/wirings/credential/{wire-credential.ts → define-credential.ts} +7 -7
  680. package/src/wirings/credential/index.ts +1 -1
  681. package/src/wirings/credential/validate-credential-definitions.ts +2 -4
  682. package/src/wirings/gateway/gateway-runner.test.ts +1 -21
  683. package/src/wirings/gateway/gateway-runner.ts +8 -110
  684. package/src/wirings/gateway/gateway.types.ts +7 -80
  685. package/src/wirings/http/http-routes.test.ts +0 -3
  686. package/src/wirings/http/http-routes.ts +0 -86
  687. package/src/wirings/http/http-runner.test.ts +0 -1
  688. package/src/wirings/http/http-runner.ts +12 -168
  689. package/src/wirings/http/http.types.ts +17 -62
  690. package/src/wirings/http/index.ts +5 -1
  691. package/src/wirings/http/log-http-routes.ts +0 -4
  692. package/src/wirings/http/pikku-fetch-http-request.test.ts +88 -7
  693. package/src/wirings/http/pikku-fetch-http-request.ts +74 -50
  694. package/src/wirings/http/pikku-fetch-http-response.test.ts +1 -1
  695. package/src/wirings/http/pikku-fetch-http-response.ts +0 -3
  696. package/src/wirings/http/routers/path-to-regex.test.ts +4 -17
  697. package/src/wirings/http/routers/path-to-regex.ts +2 -13
  698. package/src/wirings/http/web-request.test.ts +33 -2
  699. package/src/wirings/http/web-request.ts +30 -17
  700. package/src/wirings/mcp/mcp-endpoint-registry.test.ts +0 -1
  701. package/src/wirings/mcp/mcp-runner.test.ts +40 -0
  702. package/src/wirings/mcp/mcp-runner.ts +9 -15
  703. package/src/wirings/mcp/mcp.types.ts +7 -42
  704. package/src/wirings/oauth2/oauth2.types.ts +0 -30
  705. package/src/wirings/persona/define-personas.ts +29 -0
  706. package/src/wirings/persona/index.ts +62 -0
  707. package/src/wirings/persona/persona-email.ts +87 -0
  708. package/src/wirings/persona/persona-environments.test.ts +183 -0
  709. package/src/wirings/persona/persona-environments.ts +138 -0
  710. package/src/wirings/persona/persona-mailbox.ts +156 -0
  711. package/src/wirings/persona/persona.test.ts +220 -0
  712. package/src/wirings/persona/persona.types.ts +131 -0
  713. package/src/wirings/persona/validate-personas.ts +133 -0
  714. package/src/wirings/queue/index.ts +13 -3
  715. package/src/wirings/queue/queue-identity.test.ts +453 -0
  716. package/src/wirings/queue/queue-identity.ts +173 -0
  717. package/src/wirings/queue/queue-runner.ts +12 -31
  718. package/src/wirings/queue/queue.types.ts +19 -89
  719. package/src/wirings/queue/register-queue-helper.ts +0 -14
  720. package/src/wirings/queue/signed-queue-service.ts +59 -0
  721. package/src/wirings/queue/validate-worker-config.ts +2 -28
  722. package/src/wirings/role/define-system-role.ts +33 -0
  723. package/src/wirings/role/index.ts +13 -0
  724. package/src/wirings/role/role.test.ts +104 -0
  725. package/src/wirings/role/role.types.ts +47 -0
  726. package/src/wirings/role/validate-role-definitions.ts +93 -0
  727. package/src/wirings/rpc/addon-auth-tags.test.ts +223 -0
  728. package/src/wirings/rpc/addon-runner.ts +0 -56
  729. package/src/wirings/rpc/addon-scopes.test.ts +225 -0
  730. package/src/wirings/rpc/remote-addon-auth.ts +1 -13
  731. package/src/wirings/rpc/rpc-runner.test.ts +186 -2
  732. package/src/wirings/rpc/rpc-runner.ts +145 -127
  733. package/src/wirings/rpc/rpc-types.ts +11 -6
  734. package/src/wirings/rpc/wire-addon.test.ts +43 -1
  735. package/src/wirings/rpc/wire-addon.ts +99 -0
  736. package/src/wirings/rpc/wire-remote-addon.ts +9 -29
  737. package/src/wirings/scheduler/log-schedulers.ts +0 -4
  738. package/src/wirings/scheduler/scheduler-runner.test.ts +1 -8
  739. package/src/wirings/scheduler/scheduler-runner.ts +0 -2
  740. package/src/wirings/scheduler/scheduler.types.ts +1 -14
  741. package/src/wirings/scope/{wire-scope.ts → define-scope.ts} +5 -6
  742. package/src/wirings/scope/index.ts +1 -1
  743. package/src/wirings/scope/scope.test.ts +1 -2
  744. package/src/wirings/scope/scope.types.ts +7 -9
  745. package/src/wirings/scope/validate-scope-definitions.ts +3 -21
  746. package/src/wirings/secret/index.ts +1 -1
  747. package/src/wirings/secret/secret.types.ts +19 -15
  748. package/src/wirings/secret/validate-secret-definitions.ts +2 -4
  749. package/src/wirings/trigger/trigger-runner.ts +1 -27
  750. package/src/wirings/trigger/trigger.types.ts +1 -82
  751. package/src/wirings/variable/index.ts +1 -1
  752. package/src/wirings/variable/validate-variable-definitions.ts +2 -4
  753. package/src/wirings/variable/variable.types.ts +1 -13
  754. package/src/wirings/virtual-user/index.ts +76 -0
  755. package/src/wirings/virtual-user/run-virtual-user.test.ts +765 -0
  756. package/src/wirings/virtual-user/run-virtual-user.ts +671 -0
  757. package/src/wirings/virtual-user/virtual-user-agents.test.ts +65 -0
  758. package/src/wirings/virtual-user/virtual-user-agents.ts +57 -0
  759. package/src/wirings/virtual-user/virtual-user-catalogue.test.ts +215 -0
  760. package/src/wirings/virtual-user/virtual-user-catalogue.ts +184 -0
  761. package/src/wirings/virtual-user/virtual-user-derive.test.ts +398 -0
  762. package/src/wirings/virtual-user/virtual-user-derive.ts +173 -0
  763. package/src/wirings/virtual-user/virtual-user-dispositions.test.ts +63 -0
  764. package/src/wirings/virtual-user/virtual-user-dispositions.ts +213 -0
  765. package/src/wirings/virtual-user/virtual-user-intents.test.ts +208 -0
  766. package/src/wirings/virtual-user/virtual-user-intents.ts +185 -0
  767. package/src/wirings/virtual-user/virtual-user-rng.test.ts +72 -0
  768. package/src/wirings/virtual-user/virtual-user-rng.ts +50 -0
  769. package/src/wirings/virtual-user/virtual-user-target.ts +47 -0
  770. package/src/wirings/virtual-user/virtual-user.types.ts +219 -0
  771. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +5 -4
  772. package/src/wirings/workflow/dsl/workflow-runner.ts +0 -4
  773. package/src/wirings/workflow/feature.ts +0 -19
  774. package/src/wirings/workflow/graph/graph-node.ts +0 -136
  775. package/src/wirings/workflow/graph/graph-runner.test.ts +20 -19
  776. package/src/wirings/workflow/graph/graph-runner.ts +6 -41
  777. package/src/wirings/workflow/graph/wire-workflow-graph.ts +0 -4
  778. package/src/wirings/workflow/graph/workflow-graph.types.ts +0 -58
  779. package/src/wirings/workflow/index.ts +10 -44
  780. package/src/wirings/workflow/pikku-scenario-service.ts +235 -52
  781. package/src/wirings/workflow/pikku-workflow-service.test.ts +0 -39
  782. package/src/wirings/workflow/pikku-workflow-service.ts +77 -674
  783. package/src/wirings/workflow/run-timeline.test.ts +7 -19
  784. package/src/wirings/workflow/run-timeline.ts +0 -56
  785. package/src/wirings/workflow/scenario-cookie-jar.test.ts +0 -1
  786. package/src/wirings/workflow/scenario-cookie-jar.ts +0 -25
  787. package/src/wirings/workflow/scenario-expectations.test.ts +2 -7
  788. package/src/wirings/workflow/scenario-hooks.test.ts +2 -7
  789. package/src/wirings/workflow/scenario-poll.test.ts +0 -2
  790. package/src/wirings/workflow/scenario-poll.ts +0 -15
  791. package/src/wirings/workflow/scenario-prose.ts +0 -28
  792. package/src/wirings/workflow/scenario-service.test.ts +2 -9
  793. package/src/wirings/workflow/scenario-step-guards.ts +1 -14
  794. package/src/wirings/workflow/scenario-step.test.ts +159 -14
  795. package/src/wirings/workflow/scenario-step.types.ts +82 -6
  796. package/src/wirings/workflow/scenario-surface.test.ts +145 -0
  797. package/src/wirings/workflow/scenario-surface.ts +71 -0
  798. package/src/wirings/workflow/workflow-dispatch-durability.test.ts +14 -15
  799. package/src/wirings/workflow/workflow-dispatch-payload.test.ts +0 -4
  800. package/src/wirings/workflow/workflow-inline-authority.test.ts +169 -0
  801. package/src/wirings/workflow/workflow-invocation-id.test.ts +0 -2
  802. package/src/wirings/workflow/workflow-invocation-id.ts +2 -22
  803. package/src/wirings/workflow/workflow-mirror.test.ts +0 -7
  804. package/src/wirings/workflow/workflow-on-error.test.ts +0 -9
  805. package/src/wirings/workflow/workflow-queue-workers.ts +0 -21
  806. package/src/wirings/workflow/workflow-replay-snapshot.test.ts +8 -7
  807. package/src/wirings/workflow/workflow-retry-policy.test.ts +0 -5
  808. package/src/wirings/workflow/workflow-run-context.test.ts +5 -10
  809. package/src/wirings/workflow/workflow-run-polling.test.ts +0 -5
  810. package/src/wirings/workflow/workflow-step-ordinal.test.ts +19 -4
  811. package/src/wirings/workflow/workflow-step-session.test.ts +0 -7
  812. package/src/wirings/workflow/workflow.types.ts +0 -203
  813. package/tsconfig.tsbuildinfo +1 -1
  814. package/src/middleware/timeout.ts +0 -22
  815. package/src/pikku-response.ts +0 -5
  816. package/src/services/gopass-secrets.ts +0 -78
  817. package/src/wirings/mcp/mcp-endpoint-registry.test.d.ts +0 -1
  818. package/src/wirings/workflow/dsl/index.ts +0 -36
  819. package/src/wirings/workflow/graph/index.ts +0 -15
@@ -3,6 +3,18 @@ import { pikkuAIMiddleware } from '../../types/core.types.js'
3
3
 
4
4
  type VoiceOutputState = {
5
5
  textBuffer?: string
6
+ /**
7
+ * The audio emitted so far, as a chain.
8
+ *
9
+ * Synthesis for every finished sentence starts immediately, so the second
10
+ * sentence is being generated while the first is still being spoken. That
11
+ * also means the second can finish first — a short sentence after a long one
12
+ * routinely does. Chaining the *emissions* keeps the client receiving audio
13
+ * in the order it was written while leaving the generation overlapped.
14
+ */
15
+ tail?: Promise<void>
16
+ /** Whether this reply has already said it cannot be spoken aloud. */
17
+ reportedUnspeakable?: boolean
6
18
  }
7
19
 
8
20
  function bufferToBase64(data: Uint8Array): string {
@@ -19,6 +31,102 @@ function isSentenceBoundary(text: string): boolean {
19
31
  return SENTENCE_BOUNDARY.test(text)
20
32
  }
21
33
 
34
+ /**
35
+ * Scripts a speech model can pronounce, as named by {@link voiceOutput}'s
36
+ * `speakableScripts`.
37
+ *
38
+ * Script rather than language because script is what is decidable from the text
39
+ * alone. Spanish, French and English are one test, not three — and the models
40
+ * that handle one Latin-script language generally handle the others. What a
41
+ * model cannot do is invent a phoneme set it was never given.
42
+ */
43
+ const SCRIPT_RANGES: Record<string, RegExp> = {
44
+ latin: /[A-ɏ]/,
45
+ devanagari: /[ऀ-ॿ]/,
46
+ han: /[㐀-鿿]/,
47
+ kana: /[぀-ヿ]/,
48
+ arabic: /[؀-ۿݐ-ݿ]/,
49
+ cyrillic: /[Ѐ-ӿ]/,
50
+ hangul: /[가-힯ᄀ-ᇿ]/,
51
+ hebrew: /[֐-׿]/,
52
+ greek: /[Ͱ-Ͽ]/,
53
+ thai: /[฀-๿]/,
54
+ }
55
+
56
+ /**
57
+ * The scripts a model can pronounce, and optionally the voice each one needs.
58
+ *
59
+ * The array form is for a model whose voice is the same whatever the script.
60
+ * The record form exists because that is not the common case: Kokoro speaks
61
+ * Mandarin, Japanese and Hindi well, but only through a voice belonging to that
62
+ * language. Asked for Chinese in its default American-English voice it spells
63
+ * the characters out — 9.9 seconds of "Chinese letter, Chinese letter" for a
64
+ * sentence the right voice says in 3.5. Same model, same text, same price.
65
+ */
66
+ export type SpeakableScripts = string[] | Record<string, string>
67
+
68
+ /**
69
+ * Which script decides the voice when a sentence contains several.
70
+ *
71
+ * The ordering is by how much a script narrows down the language, not by
72
+ * preference. Kana appears only in Japanese, so a sentence holding any settles
73
+ * it — while Han is written in both Chinese and Japanese and so cannot, which
74
+ * matters because ordinary Japanese is mostly kanji. Latin comes last for the
75
+ * same reason from the other end: it turns up inside sentences in every other
76
+ * script, and it is the one whose voice is least often the one that counts. A
77
+ * Chinese voice reading an English word is accented; an English voice reading
78
+ * Chinese spells it out.
79
+ *
80
+ * Everything else — Han included — sits between the two in config order.
81
+ */
82
+ const SCRIPT_PRECEDENCE: Record<string, number> = { kana: -1, latin: 1 }
83
+
84
+ const scriptRank = (name: string): number => SCRIPT_PRECEDENCE[name] ?? 0
85
+
86
+ const scriptNames = (speakable: SpeakableScripts): string[] =>
87
+ Array.isArray(speakable) ? speakable : Object.keys(speakable)
88
+
89
+ /**
90
+ * The scripts present in `text` that the model was not said to handle.
91
+ *
92
+ * Silence would be the wrong answer here and so would a best effort. Handed
93
+ * Arabic — which it has no voice for at all — Kokoro does not fail and does not
94
+ * stay quiet: it reads out the *letter names*, twenty-four seconds of "Arabic
95
+ * meem, Arabic ra" for a one-line sentence, which is worse than either. Naming
96
+ * the gap lets the caller say so instead.
97
+ */
98
+ export const unspeakableScripts = (
99
+ text: string,
100
+ speakable: SpeakableScripts
101
+ ): string[] => {
102
+ const allowed = new Set(scriptNames(speakable))
103
+ return Object.entries(SCRIPT_RANGES)
104
+ .filter(([name, range]) => !allowed.has(name) && range.test(text))
105
+ .map(([name]) => name)
106
+ }
107
+
108
+ /**
109
+ * The voice to speak `text` in, or `fallback` if no mapped script appears.
110
+ *
111
+ * A sentence is regularly in more than one script, so which one decides is
112
+ * settled by {@link SCRIPT_PRECEDENCE} rather than by the order the config
113
+ * happens to be written in.
114
+ */
115
+ export const voiceForText = (
116
+ text: string,
117
+ speakable: SpeakableScripts,
118
+ fallback?: string
119
+ ): string | undefined => {
120
+ if (Array.isArray(speakable)) return fallback
121
+ const mapped = Object.entries(speakable).sort(
122
+ ([a], [b]) => scriptRank(a) - scriptRank(b)
123
+ )
124
+ for (const [name, voice] of mapped) {
125
+ if (SCRIPT_RANGES[name]?.test(text)) return voice
126
+ }
127
+ return fallback
128
+ }
129
+
22
130
  async function synthesizeAudio(
23
131
  aiAgentRunner: AIAgentRunnerService,
24
132
  input: {
@@ -29,6 +137,7 @@ async function synthesizeAudio(
29
137
  instructions?: string
30
138
  speed?: number
31
139
  language?: string
140
+ abortSignal?: AbortSignal
32
141
  }
33
142
  ): Promise<{ bytes: Uint8Array; format: string }> {
34
143
  const result = await aiAgentRunner.generateSpeech?.({
@@ -39,14 +148,13 @@ async function synthesizeAudio(
39
148
  instructions: input.instructions,
40
149
  speed: input.speed,
41
150
  language: input.language,
151
+ abortSignal: input.abortSignal,
42
152
  })
43
153
  if (!result) {
44
154
  throw new Error(
45
155
  'voiceOutput requires an aiAgentRunner with generateSpeech support'
46
156
  )
47
157
  }
48
- // Label chunks with the format the provider actually returned (config.format
49
- // is only a request), falling back to the requested format then pcm16.
50
158
  return {
51
159
  bytes: result.audio.uint8Array,
52
160
  format: result.audio.format || input.format || 'pcm16',
@@ -60,75 +168,127 @@ export const voiceOutput = (config?: {
60
168
  instructions?: string
61
169
  speed?: number
62
170
  language?: string
171
+ /**
172
+ * Scripts the configured model can actually pronounce, and the voice each
173
+ * needs — see {@link SpeakableScripts}. Anything written in another script is
174
+ * left unspoken and reported once, as a `voice-unsupported` data event,
175
+ * rather than sent to a model that will mangle it.
176
+ *
177
+ * Omitted means no check: every model is trusted with every script, which is
178
+ * the right default for a provider that genuinely is multilingual.
179
+ */
180
+ speakableScripts?: SpeakableScripts
63
181
  }) =>
64
182
  pikkuAIMiddleware<VoiceOutputState>({
65
- modifyOutputStream: async (services, { event, state }) => {
66
- const aiAgentRunner = (services as {
183
+ modifyOutputStream: async (services, { event, state, emit, signal }) => {
184
+ const { aiAgentRunner, logger } = services as {
67
185
  aiAgentRunner?: AIAgentRunnerService
68
- }).aiAgentRunner
186
+ logger?: { error: (message: string) => void }
187
+ }
69
188
  if (!aiAgentRunner?.generateSpeech) return event
70
189
 
71
- if (event.type === 'done') {
72
- const remaining = state.textBuffer ?? ''
73
- if (remaining) {
74
- state.textBuffer = ''
75
- if (!config?.model) {
76
- throw new Error(
77
- 'voiceOutput requires a speech model (e.g. openai/tts-1)'
190
+ /**
191
+ * Queue a finished sentence for speech.
192
+ *
193
+ * Nothing here is awaited by the caller. Synthesis is kicked off at once
194
+ * and its emission is chained behind the previous sentence, which is
195
+ * what keeps the text flowing at the speed the model writes it instead
196
+ * of the speed the speech provider answers.
197
+ */
198
+ const speak = (text: string) => {
199
+ if (!config?.model) {
200
+ throw new Error(
201
+ 'voiceOutput requires a speech model (e.g. openai/tts-1)'
202
+ )
203
+ }
204
+
205
+ // Checked per sentence but announced once per reply: a bilingual answer
206
+ // should still speak the half it can, and repeating the notice for
207
+ // every sentence of a long one would bury the reply itself.
208
+ //
209
+ // Per sentence is also what makes the voice right. A reply that answers
210
+ // in English and then quotes a Chinese title is two sentences in two
211
+ // scripts, and each is synthesized in the voice its own script needs.
212
+ let voice = config.voice
213
+ if (config.speakableScripts) {
214
+ const unspeakable = unspeakableScripts(text, config.speakableScripts)
215
+ voice = voiceForText(text, config.speakableScripts, config.voice)
216
+ if (unspeakable.length > 0) {
217
+ if (!state.reportedUnspeakable) {
218
+ state.reportedUnspeakable = true
219
+ state.tail = (state.tail ?? Promise.resolve()).then(() =>
220
+ emit({
221
+ type: 'data',
222
+ name: 'voice-unsupported',
223
+ data: { model: config.model, scripts: unspeakable },
224
+ })
225
+ )
226
+ }
227
+ return
228
+ }
229
+ }
230
+ // Settled into a value rather than left as a rejectable promise: it
231
+ // sits unobserved until the chain reaches it, and an unhandled
232
+ // rejection in that window would take the process down.
233
+ const synthesis = synthesizeAudio(aiAgentRunner, {
234
+ model: config.model,
235
+ text,
236
+ voice,
237
+ format: config?.format,
238
+ instructions: config?.instructions,
239
+ speed: config?.speed,
240
+ language: config?.language,
241
+ // Barge-in should stop the bill, not just the playback. Without this
242
+ // every sentence already in flight is synthesized in full and paid
243
+ // for after the user has stopped listening.
244
+ abortSignal: signal,
245
+ }).then(
246
+ (audio) => ({ ok: true as const, audio }),
247
+ (error: unknown) => ({ ok: false as const, error })
248
+ )
249
+
250
+ state.tail = (state.tail ?? Promise.resolve()).then(async () => {
251
+ const result = await synthesis
252
+ if (!result.ok) {
253
+ // One sentence going unspoken is better than the rest of the reply
254
+ // never arriving, so the chain carries on.
255
+ logger?.error(
256
+ `voiceOutput could not synthesize a sentence: ${String(result.error)}`
78
257
  )
258
+ return
79
259
  }
80
- const audio = await synthesizeAudio(aiAgentRunner, {
81
- model: config.model,
82
- text: remaining,
83
- voice: config?.voice,
84
- format: config?.format,
85
- instructions: config?.instructions,
86
- speed: config?.speed,
87
- language: config?.language,
260
+ await emit({
261
+ type: 'audio-delta',
262
+ data: bufferToBase64(result.audio.bytes),
263
+ format: result.audio.format,
88
264
  })
89
- return [
90
- {
91
- type: 'audio-delta' as const,
92
- data: bufferToBase64(audio.bytes),
93
- format: audio.format,
94
- },
95
- { type: 'audio-done' as const },
96
- event,
97
- ]
265
+ })
266
+ }
267
+
268
+ if (event.type === 'done') {
269
+ const remaining = state.textBuffer ?? ''
270
+ state.textBuffer = ''
271
+ if (remaining.trim()) {
272
+ speak(remaining)
98
273
  }
274
+ // The only place this hook waits. The reply is already over, so the
275
+ // wait costs the reader nothing, and it is what makes `audio-done`
276
+ // mean what it says rather than racing the last chunk.
277
+ await state.tail
278
+ state.tail = undefined
99
279
  return [{ type: 'audio-done' as const }, event]
100
280
  }
101
281
 
102
282
  if (event.type !== 'text-delta') return event
103
283
 
104
284
  state.textBuffer = `${state.textBuffer ?? ''}${event.text}`
105
- if (!isSentenceBoundary(state.textBuffer)) return event
106
-
107
- const text = state.textBuffer
108
- state.textBuffer = ''
109
-
110
- if (!config?.model) {
111
- throw new Error(
112
- 'voiceOutput requires a speech model (e.g. openai/tts-1)'
113
- )
285
+ if (isSentenceBoundary(state.textBuffer)) {
286
+ const text = state.textBuffer
287
+ state.textBuffer = ''
288
+ speak(text)
114
289
  }
115
- const audio = await synthesizeAudio(aiAgentRunner, {
116
- model: config.model,
117
- text,
118
- voice: config?.voice,
119
- format: config?.format,
120
- instructions: config?.instructions,
121
- speed: config?.speed,
122
- language: config?.language,
123
- })
124
-
125
- return [
126
- event,
127
- {
128
- type: 'audio-delta' as const,
129
- data: bufferToBase64(audio.bytes),
130
- format: audio.format,
131
- },
132
- ]
290
+
291
+ // Returned without waiting for any audio — the entire point.
292
+ return event
133
293
  },
134
294
  })
@@ -8,30 +8,15 @@ import type {
8
8
  import type { CoreChannel, ChannelMessageMeta } from './channel.types.js'
9
9
  import { combineMiddleware, runMiddleware } from '../../middleware-runner.js'
10
10
  import { runPikkuFunc } from '../../function/function-runner.js'
11
+ import {
12
+ type PikkuSessionService,
13
+ createMiddlewareSessionWireProps,
14
+ } from '../../services/user-session-service.js'
11
15
  import {
12
16
  combineChannelMiddleware,
13
17
  wrapChannelWithMiddleware,
14
18
  } from './channel-middleware-runner.js'
15
19
 
16
- /**
17
- * Runs a channel lifecycle function (onConnect or onDisconnect) with proper middleware handling.
18
- *
19
- * This function:
20
- * 1. Extracts inline middleware from the lifecycle config if present
21
- * 2. Combines metadata middleware with inline middleware using combineMiddleware()
22
- * 3. Wraps the channel with channel middleware if present
23
- * 4. Runs the lifecycle function with middleware support
24
- *
25
- * @param channelConfig - The channel configuration
26
- * @param meta - Metadata for the lifecycle function (connect or disconnect)
27
- * @param lifecycleConfig - The onConnect or onDisconnect config (can be function or object with middleware)
28
- * @param lifecycleType - Type of lifecycle for cache key ('connect' or 'disconnect')
29
- * @param services - All services (singleton + session)
30
- * @param channel - The channel instance
31
- * @param data - Optional data to pass to the lifecycle function (for onConnect)
32
- * @param channelMiddlewareMeta - Optional channel middleware metadata for wrapping channel.send()
33
- * @returns Promise<unknown> - Result from the lifecycle function (if any)
34
- */
35
20
  export const runChannelLifecycleWithMiddleware = async ({
36
21
  channelConfig,
37
22
  meta,
@@ -41,6 +26,7 @@ export const runChannelLifecycleWithMiddleware = async ({
41
26
  channel,
42
27
  data,
43
28
  channelMiddlewareMeta,
29
+ userSession,
44
30
  }: {
45
31
  channelConfig: CoreChannel<unknown, any>
46
32
  meta: ChannelMessageMeta
@@ -50,6 +36,11 @@ export const runChannelLifecycleWithMiddleware = async ({
50
36
  channel: any
51
37
  data?: unknown
52
38
  channelMiddlewareMeta?: MiddlewareMetadata[]
39
+ /**
40
+ * Established while the socket was upgrading, so `onConnect` can tell who
41
+ * just connected.
42
+ */
43
+ userSession?: PikkuSessionService<any>
53
44
  }): Promise<unknown> => {
54
45
  const lifecycleMiddleware =
55
46
  typeof lifecycleConfig === 'object' && 'middleware' in lifecycleConfig
@@ -74,7 +65,10 @@ export const runChannelLifecycleWithMiddleware = async ({
74
65
  }
75
66
  )
76
67
 
77
- let wire: PikkuRawWire = { channel }
68
+ let wire: PikkuRawWire = {
69
+ channel,
70
+ ...(userSession ? createMiddlewareSessionWireProps(userSession) : {}),
71
+ }
78
72
  if (allChannelMiddleware.length > 0) {
79
73
  wire = wrapChannelWithMiddleware(wire, services, allChannelMiddleware)
80
74
  }
@@ -84,6 +78,7 @@ export const runChannelLifecycleWithMiddleware = async ({
84
78
  singletonServices: services,
85
79
  data: () => data as any,
86
80
  wire,
81
+ sessionService: userSession,
87
82
  tags: meta.tags ?? [],
88
83
  packageName: meta.packageName ?? null,
89
84
  })
@@ -4,6 +4,8 @@ import assert from 'node:assert/strict'
4
4
  import { addFunction } from '../../function/function-runner.js'
5
5
  import { pikkuState, resetPikkuState } from '../../pikku-state.js'
6
6
  import { processMessageHandlers } from './channel-handler.js'
7
+ import { getChannelHostRPC, releaseChannelHostRPC } from './channel-host-rpc.js'
8
+ import { CHANNEL_RPC_RESPONSE } from './channel-rpc.js'
7
9
 
8
10
  const createLogger = () => {
9
11
  const errors: string[] = []
@@ -240,6 +242,54 @@ describe('processMessageHandlers', () => {
240
242
  assert.match(errors[1] || '', /Channel config name: chat/)
241
243
  })
242
244
 
245
+ test('takes a reverse-RPC answer off the socket before routing', async () => {
246
+ const { errors, logger } = createLogger()
247
+ const { sent, handler } = createChannelHandler()
248
+
249
+ pikkuState(null, 'channel', 'meta').chat = {
250
+ name: 'chat',
251
+ route: '/chat',
252
+ input: null,
253
+ connect: null,
254
+ disconnect: null,
255
+ message: { pikkuFuncId: 'defaultFunc', packageName: null },
256
+ messageWirings: {},
257
+ } as never
258
+ registerFunction('defaultFunc', async () => ({ ok: true }))
259
+
260
+ const { onMessage } = processMessageHandlers(
261
+ { logger } as never,
262
+ {
263
+ name: 'chat',
264
+ route: '/chat',
265
+ auth: false,
266
+ onMessage: { func: async () => ({ ok: true }) } as never,
267
+ } as never,
268
+ handler as never
269
+ )
270
+
271
+ const transport = getChannelHostRPC(handler.getChannel() as never)
272
+ const call = transport.invoke('localCheckout', {})
273
+ const request = sent[0] as { id: string }
274
+
275
+ const result = await onMessage(
276
+ JSON.stringify({
277
+ action: CHANNEL_RPC_RESPONSE,
278
+ id: request.id,
279
+ ok: true,
280
+ result: { sha: 'deadbeef' },
281
+ })
282
+ )
283
+
284
+ assert.deepEqual(await call, { sha: 'deadbeef' })
285
+ // The channel declares no route for replies and its default handler is
286
+ // never reached — the answer belongs to the waiting caller, not the app.
287
+ assert.equal(result, undefined)
288
+ assert.deepEqual(errors, [])
289
+
290
+ await releaseChannelHostRPC(handler.getChannel().channelId)
291
+ })
292
+
243
293
  test('blocks binary messages when a session is required and missing', async () => {
244
294
  const { errors, logger } = createLogger()
245
295
  const { handler } = createChannelHandler()
@@ -11,6 +11,8 @@ import type {
11
11
  } from './channel.types.js'
12
12
  import { pikkuState } from '../../pikku-state.js'
13
13
  import { runPikkuFunc } from '../../function/function-runner.js'
14
+ import { isChannelRPCPending, isChannelRPCResponse } from './channel-rpc.js'
15
+ import { handleChannelRPCResponse } from './channel-host-rpc.js'
14
16
  import type { SessionService } from '../../services/user-session-service.js'
15
17
  import { createMiddlewareSessionWireProps } from '../../services/user-session-service.js'
16
18
  const getChannelMeta = (channelName: string) => {
@@ -159,14 +161,26 @@ export const processMessageHandlers = (
159
161
  let result: unknown
160
162
  let processed = false
161
163
 
162
- // Route-specific handling
163
- if (typeof rawData === 'string' && channelConfig.onMessageWiring) {
164
- let messageData: any
164
+ let frame: any
165
+ if (typeof rawData === 'string') {
165
166
  try {
166
- messageData = JSON.parse(rawData)
167
+ frame = JSON.parse(rawData)
167
168
  } catch {
168
169
  // Most likely a json error.. ignore
169
170
  }
171
+ }
172
+
173
+ // Taken ahead of routing so every channel gets reverse RPC without
174
+ // declaring a route for replies, and so an answer to a call someone is
175
+ // still awaiting can never be mistaken for a new message.
176
+ if (isChannelRPCResponse(frame) || isChannelRPCPending(frame)) {
177
+ handleChannelRPCResponse(channelHandler.getChannel().channelId, frame)
178
+ return
179
+ }
180
+
181
+ // Route-specific handling
182
+ if (typeof rawData === 'string' && channelConfig.onMessageWiring) {
183
+ const messageData: any = frame
170
184
 
171
185
  if (messageData) {
172
186
  const entries = Object.entries(channelConfig.onMessageWiring)
@@ -176,14 +190,21 @@ export const processMessageHandlers = (
176
190
  const { [routingProperty]: _, ...data } = messageData
177
191
 
178
192
  processed = true
193
+ const routeResult = await processMessage(
194
+ data as any,
195
+ routes[routerValue],
196
+ routingProperty,
197
+ routerValue
198
+ )
199
+ // The routing key is echoed so the client can match the reply.
200
+ // Assigning it to a primitive throws under ESM strict mode, so a
201
+ // primitive result is wrapped rather than mutated.
179
202
  result =
180
- (await processMessage(
181
- data as any,
182
- routes[routerValue],
183
- routingProperty,
184
- routerValue
185
- )) || {}
186
- ;(result as any)[routingProperty] = routerValue
203
+ routeResult !== null && typeof routeResult === 'object'
204
+ ? Object.assign(routeResult, { [routingProperty]: routerValue })
205
+ : routeResult === undefined
206
+ ? { [routingProperty]: routerValue }
207
+ : { [routingProperty]: routerValue, result: routeResult }
187
208
  break
188
209
  }
189
210
  }
@@ -0,0 +1,150 @@
1
+ import { describe, test } from 'node:test'
2
+ import * as assert from 'node:assert/strict'
3
+
4
+ import { CHANNEL_RPC_RESPONSE } from './channel-rpc.js'
5
+ import {
6
+ getChannelHostRPC,
7
+ handleChannelRPCResponse,
8
+ releaseChannelHostRPC,
9
+ } from './channel-host-rpc.js'
10
+ import type { PikkuChannel } from './channel.types.js'
11
+
12
+ /**
13
+ * A channel stripped to what the host RPC registry touches: an id to key the
14
+ * transport by, and a send it writes requests to.
15
+ */
16
+ const fakeChannel = (channelId: string) => {
17
+ const sent: unknown[] = []
18
+ const channel = {
19
+ channelId,
20
+ send: async (data: unknown) => {
21
+ sent.push(data)
22
+ },
23
+ } as unknown as PikkuChannel<unknown, any>
24
+ return { channel, sent }
25
+ }
26
+
27
+ describe('channel host RPC', () => {
28
+ test('one transport per connection, reused across commands', async () => {
29
+ const { channel } = fakeChannel('reuse')
30
+ try {
31
+ assert.equal(getChannelHostRPC(channel), getChannelHostRPC(channel))
32
+ } finally {
33
+ await releaseChannelHostRPC('reuse')
34
+ }
35
+ })
36
+
37
+ test('separate connections get separate transports', async () => {
38
+ const a = fakeChannel('a')
39
+ const b = fakeChannel('b')
40
+ try {
41
+ assert.notEqual(
42
+ getChannelHostRPC(a.channel),
43
+ getChannelHostRPC(b.channel)
44
+ )
45
+
46
+ // Requests must land on their own socket, not the other connection's.
47
+ // Nothing answers this one, so it is rejected by the release below.
48
+ getChannelHostRPC(a.channel)
49
+ .invoke('localCheckout', {})
50
+ .catch(() => {})
51
+ assert.equal(a.sent.length, 1)
52
+ assert.equal(b.sent.length, 0)
53
+ } finally {
54
+ await releaseChannelHostRPC('a')
55
+ await releaseChannelHostRPC('b')
56
+ }
57
+ })
58
+
59
+ test('a response is routed back to the call it belongs to', async () => {
60
+ const { channel, sent } = fakeChannel('routed')
61
+ try {
62
+ const service = getChannelHostRPC(channel)
63
+ const call = service.invoke('localCheckout', {})
64
+ const { id } = sent[0] as { id: string }
65
+
66
+ assert.equal(
67
+ handleChannelRPCResponse('routed', {
68
+ action: CHANNEL_RPC_RESPONSE,
69
+ id,
70
+ ok: true,
71
+ result: { sha: 'deadbeef' },
72
+ }),
73
+ true
74
+ )
75
+ assert.deepEqual(await call, { sha: 'deadbeef' })
76
+ } finally {
77
+ await releaseChannelHostRPC('routed')
78
+ }
79
+ })
80
+
81
+ test('a frame for an unknown connection or id is dropped, not thrown', async () => {
82
+ const { channel, sent } = fakeChannel('dropped')
83
+ try {
84
+ const service = getChannelHostRPC(channel)
85
+ const call = service.invoke('localCheckout', {})
86
+ const { id } = sent[0] as { id: string }
87
+
88
+ // No transport for that connection at all.
89
+ assert.equal(
90
+ handleChannelRPCResponse('never-connected', {
91
+ action: CHANNEL_RPC_RESPONSE,
92
+ id,
93
+ ok: true,
94
+ result: null,
95
+ }),
96
+ false
97
+ )
98
+ // Right connection, but an id that was never registered — a late frame
99
+ // from a call that already timed out looks exactly like this.
100
+ assert.equal(
101
+ handleChannelRPCResponse('dropped', {
102
+ action: CHANNEL_RPC_RESPONSE,
103
+ id: 'not-an-id',
104
+ ok: true,
105
+ result: null,
106
+ }),
107
+ false
108
+ )
109
+
110
+ handleChannelRPCResponse('dropped', {
111
+ action: CHANNEL_RPC_RESPONSE,
112
+ id,
113
+ ok: true,
114
+ result: 'ok',
115
+ })
116
+ assert.equal(await call, 'ok')
117
+ } finally {
118
+ await releaseChannelHostRPC('dropped')
119
+ }
120
+ })
121
+
122
+ test('releasing rejects everything still in flight', async () => {
123
+ const { channel } = fakeChannel('released')
124
+ const call = getChannelHostRPC(channel).invoke('localCheckout', {})
125
+
126
+ await releaseChannelHostRPC('released')
127
+
128
+ // Without this a caller waits out the full timeout on a socket that is
129
+ // already gone.
130
+ await assert.rejects(call)
131
+ })
132
+
133
+ test('releasing forgets the connection', async () => {
134
+ const { channel } = fakeChannel('forgotten')
135
+ const first = getChannelHostRPC(channel)
136
+ await releaseChannelHostRPC('forgotten')
137
+
138
+ try {
139
+ // A stale entry would hand a reconnecting channel a stopped transport,
140
+ // and the map would grow for the life of the process.
141
+ assert.notEqual(getChannelHostRPC(channel), first)
142
+ } finally {
143
+ await releaseChannelHostRPC('forgotten')
144
+ }
145
+ })
146
+
147
+ test('releasing an unknown connection is a no-op', async () => {
148
+ await releaseChannelHostRPC('was-never-here')
149
+ })
150
+ })