@pikku/core 0.12.79 → 0.12.82

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (479) hide show
  1. package/CHANGELOG.md +473 -0
  2. package/dist/dev/hot-reload.js +1 -6
  3. package/dist/ecosystem.d.ts +27 -0
  4. package/dist/ecosystem.js +26 -0
  5. package/dist/errors/error-handler.js +4 -2
  6. package/dist/errors/index.d.ts +1 -1
  7. package/dist/errors/index.js +1 -1
  8. package/dist/function/function-runner.js +22 -47
  9. package/dist/function/functions.types.d.ts +9 -5
  10. package/dist/function/index.d.ts +1 -1
  11. package/dist/index.d.ts +14 -19
  12. package/dist/index.js +6 -11
  13. package/dist/middleware/auth-cookie.d.ts +0 -4
  14. package/dist/middleware/auth-cookie.js +38 -2
  15. package/dist/middleware/cors.js +1 -0
  16. package/dist/middleware/index.d.ts +1 -1
  17. package/dist/middleware/index.js +1 -1
  18. package/dist/middleware/remote-auth.js +14 -3
  19. package/dist/permissions.d.ts +8 -10
  20. package/dist/permissions.js +0 -11
  21. package/dist/pikku-state.js +16 -5
  22. package/dist/schema.js +35 -1
  23. package/dist/services/ai-agent-runner-service.d.ts +7 -0
  24. package/dist/services/ai-run-state-service.d.ts +17 -1
  25. package/dist/services/in-memory-ai-run-state-service.d.ts +6 -2
  26. package/dist/services/in-memory-ai-run-state-service.js +11 -1
  27. package/dist/services/in-memory-workflow-service.js +2 -0
  28. package/dist/services/index.d.ts +15 -15
  29. package/dist/services/index.js +5 -5
  30. package/dist/services/local-content-request-handler.d.ts +21 -0
  31. package/dist/services/local-content-request-handler.js +72 -53
  32. package/dist/services/local-content.d.ts +6 -0
  33. package/dist/services/local-content.js +14 -1
  34. package/dist/services/scoped-credential-service.d.ts +21 -0
  35. package/dist/services/scoped-credential-service.js +53 -0
  36. package/dist/services/workflow-service.d.ts +4 -2
  37. package/dist/testing/service-tests/agent-run-service-tests.d.ts +10 -0
  38. package/dist/testing/service-tests/agent-run-service-tests.js +72 -0
  39. package/dist/testing/service-tests/ai-storage-service-tests.d.ts +3 -0
  40. package/dist/testing/service-tests/ai-storage-service-tests.js +302 -0
  41. package/dist/testing/service-tests/channel-store-tests.d.ts +3 -0
  42. package/dist/testing/service-tests/channel-store-tests.js +72 -0
  43. package/dist/testing/service-tests/credential-service-tests.d.ts +3 -0
  44. package/dist/testing/service-tests/credential-service-tests.js +109 -0
  45. package/dist/testing/service-tests/deployment-service-tests.d.ts +3 -0
  46. package/dist/testing/service-tests/deployment-service-tests.js +21 -0
  47. package/dist/testing/service-tests/event-hub-store-tests.d.ts +3 -0
  48. package/dist/testing/service-tests/event-hub-store-tests.js +34 -0
  49. package/dist/testing/service-tests/secret-service-tests.d.ts +3 -0
  50. package/dist/testing/service-tests/secret-service-tests.js +80 -0
  51. package/dist/testing/service-tests/session-store-tests.d.ts +3 -0
  52. package/dist/testing/service-tests/session-store-tests.js +43 -0
  53. package/dist/testing/service-tests/workflow-run-service-tests.d.ts +3 -0
  54. package/dist/testing/service-tests/workflow-run-service-tests.js +42 -0
  55. package/dist/testing/service-tests/workflow-service-tests.d.ts +3 -0
  56. package/dist/testing/service-tests/workflow-service-tests.js +150 -0
  57. package/dist/testing/service-tests.d.ts +6 -0
  58. package/dist/testing/service-tests.js +26 -791
  59. package/dist/types/core.types.d.ts +11 -3
  60. package/dist/types/state.types.d.ts +14 -1
  61. package/dist/wirings/actor-flow/index.d.ts +1 -1
  62. package/dist/wirings/actor-flow/run-conversation.js +1 -6
  63. package/dist/wirings/ai-agent/agent-rpc.d.ts +15 -0
  64. package/dist/wirings/ai-agent/agent-rpc.js +53 -0
  65. package/dist/wirings/ai-agent/ai-agent-agui.js +1 -5
  66. package/dist/wirings/ai-agent/ai-agent-finalize.d.ts +58 -0
  67. package/dist/wirings/ai-agent/ai-agent-finalize.js +138 -0
  68. package/dist/wirings/ai-agent/ai-agent-interrupt.js +1 -0
  69. package/dist/wirings/ai-agent/ai-agent-memory.d.ts +2 -8
  70. package/dist/wirings/ai-agent/ai-agent-memory.js +37 -18
  71. package/dist/wirings/ai-agent/ai-agent-model-config.d.ts +7 -0
  72. package/dist/wirings/ai-agent/ai-agent-model-config.js +44 -1
  73. package/dist/wirings/ai-agent/ai-agent-prepare.js +5 -9
  74. package/dist/wirings/ai-agent/ai-agent-runner.js +89 -147
  75. package/dist/wirings/ai-agent/ai-agent-stream.js +109 -97
  76. package/dist/wirings/ai-agent/ai-agent-turn.d.ts +57 -0
  77. package/dist/wirings/ai-agent/ai-agent-turn.js +82 -0
  78. package/dist/wirings/ai-agent/ai-agent.types.d.ts +47 -2
  79. package/dist/wirings/ai-agent/index.d.ts +8 -7
  80. package/dist/wirings/ai-agent/index.js +5 -4
  81. package/dist/wirings/ai-agent/voice-input.js +1 -6
  82. package/dist/wirings/ai-agent/voice-output.js +2 -12
  83. package/dist/wirings/ai-scorer/ai-scorer-grade.d.ts +26 -0
  84. package/dist/wirings/ai-scorer/ai-scorer-grade.js +33 -0
  85. package/dist/wirings/ai-scorer/ai-scorer-judge.d.ts +17 -0
  86. package/dist/wirings/ai-scorer/ai-scorer-judge.js +92 -0
  87. package/dist/wirings/ai-scorer/ai-scorer-live.d.ts +15 -0
  88. package/dist/wirings/ai-scorer/ai-scorer-live.js +38 -0
  89. package/dist/wirings/ai-scorer/ai-scorer-registry.d.ts +18 -0
  90. package/dist/wirings/ai-scorer/ai-scorer-registry.js +46 -0
  91. package/dist/wirings/ai-scorer/ai-scorer-sampling.d.ts +8 -0
  92. package/dist/wirings/ai-scorer/ai-scorer-sampling.js +31 -0
  93. package/dist/wirings/ai-scorer/ai-scorer-snapshots.d.ts +10 -0
  94. package/dist/wirings/ai-scorer/ai-scorer-snapshots.js +40 -0
  95. package/dist/wirings/ai-scorer/ai-scorer-worker.d.ts +15 -0
  96. package/dist/wirings/ai-scorer/ai-scorer-worker.js +58 -0
  97. package/dist/wirings/ai-scorer/ai-scorer.d.ts +39 -0
  98. package/dist/wirings/ai-scorer/ai-scorer.js +40 -0
  99. package/dist/wirings/ai-scorer/ai-scorer.types.d.ts +90 -0
  100. package/dist/wirings/ai-scorer/ai-scorer.types.js +4 -0
  101. package/dist/wirings/ai-scorer/index.d.ts +6 -0
  102. package/dist/wirings/ai-scorer/index.js +5 -0
  103. package/dist/wirings/channel/channel-common.js +1 -0
  104. package/dist/wirings/channel/channel-handler.js +3 -5
  105. package/dist/wirings/channel/channel-rpc-service.d.ts +0 -6
  106. package/dist/wirings/channel/channel-rpc-service.js +0 -8
  107. package/dist/wirings/channel/channel-rpc.types.d.ts +6 -0
  108. package/dist/wirings/channel/channel-rpc.types.js +8 -0
  109. package/dist/wirings/channel/channel-runner.d.ts +1 -3
  110. package/dist/wirings/channel/channel-runner.js +16 -8
  111. package/dist/wirings/channel/channel.types.d.ts +2 -0
  112. package/dist/wirings/channel/index.d.ts +5 -6
  113. package/dist/wirings/channel/index.js +3 -4
  114. package/dist/wirings/channel/local/local-channel-runner.js +8 -1
  115. package/dist/wirings/channel/pikku-abstract-channel-handler.js +1 -0
  116. package/dist/wirings/channel/serverless/serverless-channel-runner.js +3 -0
  117. package/dist/wirings/cli/channel/cli-channel-runner.js +2 -0
  118. package/dist/wirings/cli/channel/cli-raw-channel-runner.js +9 -1
  119. package/dist/wirings/cli/channel/index.d.ts +1 -2
  120. package/dist/wirings/cli/channel/index.js +0 -1
  121. package/dist/wirings/cli/cli-runner.js +17 -3
  122. package/dist/wirings/cli/cli.types.d.ts +0 -8
  123. package/dist/wirings/cli/command-parser.js +13 -0
  124. package/dist/wirings/credential/index.d.ts +1 -1
  125. package/dist/wirings/gateway/gateway-runner.js +9 -2
  126. package/dist/wirings/gateway/index.d.ts +1 -1
  127. package/dist/wirings/http/http-routes.js +2 -0
  128. package/dist/wirings/http/http-runner.d.ts +0 -10
  129. package/dist/wirings/http/http-runner.js +14 -15
  130. package/dist/wirings/http/http.types.d.ts +0 -10
  131. package/dist/wirings/http/index.d.ts +2 -3
  132. package/dist/wirings/http/index.js +1 -1
  133. package/dist/wirings/mcp/index.d.ts +1 -1
  134. package/dist/wirings/mcp/mcp-runner.d.ts +15 -7
  135. package/dist/wirings/mcp/mcp-runner.js +18 -11
  136. package/dist/wirings/persona/index.d.ts +3 -4
  137. package/dist/wirings/persona/index.js +2 -3
  138. package/dist/wirings/queue/index.d.ts +1 -3
  139. package/dist/wirings/queue/index.js +1 -3
  140. package/dist/wirings/rpc/addon-runner.d.ts +4 -0
  141. package/dist/wirings/rpc/addon-runner.js +19 -3
  142. package/dist/wirings/rpc/rpc-runner.d.ts +8 -51
  143. package/dist/wirings/rpc/rpc-runner.js +55 -86
  144. package/dist/wirings/rpc/rpc-types.d.ts +7 -0
  145. package/dist/wirings/rpc/wire-addon.d.ts +13 -0
  146. package/dist/wirings/rpc/wire-addon.js +4 -0
  147. package/dist/wirings/scheduler/index.d.ts +1 -1
  148. package/dist/wirings/secret/validate-secret-definitions.js +2 -2
  149. package/dist/wirings/trigger/index.d.ts +1 -1
  150. package/dist/wirings/trigger/pikku-trigger-service.d.ts +0 -4
  151. package/dist/wirings/trigger/trigger-runner.js +1 -0
  152. package/dist/wirings/virtual-user/index.d.ts +5 -6
  153. package/dist/wirings/virtual-user/index.js +2 -4
  154. package/dist/wirings/virtual-user/run-virtual-user.js +11 -11
  155. package/dist/wirings/virtual-user/virtual-user-derive.js +4 -25
  156. package/dist/wirings/virtual-user/virtual-user-dispositions.js +1 -4
  157. package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +100 -3
  158. package/dist/wirings/workflow/feature.js +7 -4
  159. package/dist/wirings/workflow/graph/graph-runner.d.ts +1 -2
  160. package/dist/wirings/workflow/graph/graph-runner.js +8 -7
  161. package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +0 -4
  162. package/dist/wirings/workflow/index.d.ts +12 -7
  163. package/dist/wirings/workflow/index.js +7 -3
  164. package/dist/wirings/workflow/pikku-scenario-service.d.ts +17 -3
  165. package/dist/wirings/workflow/pikku-scenario-service.js +64 -10
  166. package/dist/wirings/workflow/pikku-workflow-service.d.ts +28 -147
  167. package/dist/wirings/workflow/pikku-workflow-service.js +85 -493
  168. package/dist/wirings/workflow/scenario-step.types.d.ts +8 -0
  169. package/dist/wirings/workflow/workflow-approval-audit.d.ts +16 -0
  170. package/dist/wirings/workflow/workflow-approval-audit.js +40 -0
  171. package/dist/wirings/workflow/workflow-approval-policy.d.ts +20 -0
  172. package/dist/wirings/workflow/workflow-approval-policy.js +48 -0
  173. package/dist/wirings/workflow/workflow-approval.d.ts +67 -0
  174. package/dist/wirings/workflow/workflow-approval.js +177 -0
  175. package/dist/wirings/workflow/workflow-constants.d.ts +26 -0
  176. package/dist/wirings/workflow/workflow-constants.js +35 -0
  177. package/dist/wirings/workflow/workflow-errors.d.ts +58 -0
  178. package/dist/wirings/workflow/workflow-errors.js +112 -0
  179. package/dist/wirings/workflow/workflow-meta-resolver.d.ts +11 -0
  180. package/dist/wirings/workflow/workflow-meta-resolver.js +31 -0
  181. package/dist/wirings/workflow/workflow-queue-routing.d.ts +8 -0
  182. package/dist/wirings/workflow/workflow-queue-routing.js +38 -0
  183. package/dist/wirings/workflow/workflow-queue-wiring.d.ts +20 -0
  184. package/dist/wirings/workflow/workflow-queue-wiring.js +79 -0
  185. package/dist/wirings/workflow/workflow-recovery.d.ts +68 -0
  186. package/dist/wirings/workflow/workflow-recovery.js +101 -0
  187. package/dist/wirings/workflow/workflow-run-engine.types.d.ts +54 -0
  188. package/dist/wirings/workflow/workflow-run-ownership.d.ts +17 -0
  189. package/dist/wirings/workflow/workflow-run-ownership.js +30 -0
  190. package/dist/wirings/workflow/workflow-suspend.d.ts +12 -0
  191. package/dist/wirings/workflow/workflow-suspend.js +33 -0
  192. package/dist/wirings/workflow/workflow.types.d.ts +8 -1
  193. package/knowledge/decisions/internals/a-non-streaming-agent-run-registers-with-airunstate-too.md +22 -0
  194. package/knowledge/decisions/internals/a-resumed-agent-turn-is-as-interruptible-as-the-first.md +20 -0
  195. package/knowledge/decisions/internals/a-scenario-step-template-is-offered-unfilled.md +21 -0
  196. package/knowledge/decisions/internals/a-virtual-user-decides-whether-to-trust-memory-once-per-turn.md +21 -0
  197. package/knowledge/decisions/internals/a-wall-clock-threshold-is-a-load-test-in-disguise.md +46 -0
  198. package/knowledge/decisions/internals/a-workflow-wire-is-built-from-the-run-not-from-the-rpc-service.md +45 -0
  199. package/knowledge/decisions/internals/addon-pikku-meta-ships-at-the-package-root-or-under-dist.md +32 -0
  200. package/knowledge/decisions/internals/agent-context-waits-for-a-tool-result-still-being-written.md +21 -0
  201. package/knowledge/decisions/internals/agent-speech-travels-as-a-custom-agui-event.md +22 -0
  202. package/knowledge/decisions/internals/an-addon-scope-root-loses-to-a-root-the-host-already-declares.md +39 -0
  203. package/knowledge/decisions/internals/an-agent-interrupt-is-not-a-failure.md +23 -0
  204. package/knowledge/decisions/internals/an-agent-run-owned-by-another-instance-says-so.md +25 -0
  205. package/knowledge/decisions/internals/an-agent-stream-send-must-return-the-inner-sends-promise.md +22 -0
  206. package/knowledge/decisions/internals/an-empty-text-part-is-omitted-from-an-agent-message.md +20 -0
  207. package/knowledge/decisions/internals/an-empty-transcript-is-not-recorded.md +22 -0
  208. package/knowledge/decisions/internals/an-unref-d-timer-cannot-be-awaited-under-node-test.md +66 -0
  209. package/knowledge/decisions/internals/channel-state-accessors-are-unsound-generics-that-every-implementation-asserts.md +33 -0
  210. package/knowledge/decisions/internals/gateway-listener-middleware-runs-without-an-rpc-on-the-wire.md +43 -0
  211. package/knowledge/decisions/internals/hot-reload-writes-into-the-function-map-captured-at-startup.md +26 -0
  212. package/knowledge/decisions/internals/index.md +42 -3
  213. package/knowledge/decisions/internals/only-exposed-functions-enter-a-virtual-user-catalogue.md +21 -0
  214. package/knowledge/decisions/internals/scenario-given-and-when-are-sugar-but-then-is-not.md +24 -0
  215. package/knowledge/decisions/internals/side-effects-are-an-allowlist-not-a-boolean.md +34 -0
  216. package/knowledge/decisions/internals/speech-synthesis-picks-a-voice-per-sentence-and-warns-once.md +24 -0
  217. package/knowledge/decisions/internals/the-actor-prompt-says-json-because-of-json-object-mode.md +22 -0
  218. package/knowledge/decisions/internals/the-agent-done-event-goes-through-the-middleware-and-is-awaited.md +26 -0
  219. package/knowledge/decisions/internals/the-api-report-pins-members-not-just-names.md +43 -0
  220. package/knowledge/decisions/internals/the-ecosystem-entry-point-carries-the-adapter-surface.md +58 -0
  221. package/knowledge/decisions/internals/the-middleware-resolution-cache-is-deliberately-unbounded.md +40 -0
  222. package/knowledge/decisions/internals/the-per-invocation-rpc-view-is-a-class.md +40 -0
  223. package/knowledge/decisions/internals/the-persona-runtime-is-exported-from-the-persona-entry-point.md +28 -0
  224. package/knowledge/decisions/internals/the-transcript-event-is-sent-ahead-of-the-run.md +25 -0
  225. package/knowledge/decisions/internals/the-virtual-user-catalogue-is-the-only-gate-on-what-may-be-called.md +21 -0
  226. package/knowledge/decisions/internals/the-worker-disposition-is-the-one-that-is-not-testing.md +22 -0
  227. package/knowledge/decisions/internals/thread-history-records-the-transcript-not-the-audio.md +25 -0
  228. package/knowledge/decisions/internals/validate-runs-checks-by-precondition.md +115 -0
  229. package/knowledge/decisions/internals/virtual-user-step-order-comes-from-insertion-order.md +21 -0
  230. package/knowledge/decisions/internals/voice-output-speaks-unless-voice-input-explicitly-says-otherwise.md +24 -0
  231. package/knowledge/decisions/internals/wiring-registries-erase-the-generics-their-wire-functions-capture.md +35 -0
  232. package/knowledge/decisions/security/a-dropped-audit-write-is-always-logged.md +4 -2
  233. package/knowledge/decisions/security/a-function-never-receives-the-secret-service.md +37 -0
  234. package/knowledge/decisions/security/a-graph-run-starts-at-an-entry-node-the-graph-declared.md +33 -0
  235. package/knowledge/decisions/security/a-permission-gets-a-wire-it-cannot-reply-on.md +33 -0
  236. package/knowledge/decisions/security/a-step-runs-the-function-the-workflow-dispatched-it-with.md +37 -0
  237. package/knowledge/decisions/security/a-virtual-user-is-never-offered-a-step-that-would-forge-its-own-oracle.md +28 -0
  238. package/knowledge/decisions/security/a-workflow-run-is-read-and-approved-by-its-owner.md +50 -0
  239. package/knowledge/decisions/security/an-agent-approval-is-claimed-before-the-tool-runs.md +33 -0
  240. package/knowledge/decisions/security/an-approval-answer-outlives-the-run-it-answered.md +59 -0
  241. package/knowledge/decisions/security/an-upload-is-counted-as-it-arrives-not-buffered-then-measured.md +24 -0
  242. package/knowledge/decisions/security/index.md +9 -0
  243. package/knowledge/questions/channel-middleware-accepts-bare-factories-that-nothing-resolves.md +35 -0
  244. package/knowledge/questions/index.md +2 -1
  245. package/knowledge/questions/unauthorized-channel-replies-escape-the-declared-out-type.md +44 -0
  246. package/package.json +15 -3
  247. package/scripts/generate-api-report.d.mts +1 -0
  248. package/scripts/generate-api-report.mjs +89 -0
  249. package/scripts/generate-api-report.mts +343 -0
  250. package/src/api-report.test.ts +69 -0
  251. package/src/column-form.ts +1 -4
  252. package/src/crypto-utils.test.ts +15 -2
  253. package/src/dev/hot-reload.ts +2 -7
  254. package/src/ecosystem.ts +40 -0
  255. package/src/errors/error-handler.ts +5 -3
  256. package/src/errors/error.test.ts +4 -1
  257. package/src/errors/index.ts +1 -1
  258. package/src/function/function-runner.test.ts +53 -1
  259. package/src/function/function-runner.ts +33 -48
  260. package/src/function/functions.types.ts +15 -9
  261. package/src/function/index.ts +0 -2
  262. package/src/handle-error.ts +1 -1
  263. package/src/index.ts +3 -57
  264. package/src/middleware/auth-cookie.test.ts +50 -0
  265. package/src/middleware/auth-cookie.ts +38 -2
  266. package/src/middleware/cors.ts +2 -1
  267. package/src/middleware/index.ts +1 -1
  268. package/src/middleware/remote-auth.test.ts +43 -0
  269. package/src/middleware/remote-auth.ts +17 -3
  270. package/src/middleware-runner.ts +4 -2
  271. package/src/no-any-casts.test.ts +57 -0
  272. package/src/permissions.test.ts +3 -1
  273. package/src/permissions.ts +22 -24
  274. package/src/pikku-state.ts +22 -6
  275. package/src/public-surface.json +554 -0
  276. package/src/public-surface.json.README +25 -0
  277. package/src/public-surface.test.ts +105 -0
  278. package/src/removed-legacy-exports.test.ts +62 -0
  279. package/src/schema.test.ts +78 -0
  280. package/src/schema.ts +36 -1
  281. package/src/services/ai-agent-runner-service.ts +12 -1
  282. package/src/services/ai-run-state-service.ts +18 -1
  283. package/src/services/audit-service.ts +2 -2
  284. package/src/services/in-memory-ai-run-state-service.ts +16 -2
  285. package/src/services/in-memory-workflow-service.ts +2 -0
  286. package/src/services/index.ts +4 -47
  287. package/src/services/local-content-request-handler.test.ts +43 -5
  288. package/src/services/local-content-request-handler.ts +103 -74
  289. package/src/services/local-content.ts +15 -1
  290. package/src/services/local-email-service.ts +5 -1
  291. package/src/services/scoped-credential-service.test.ts +86 -0
  292. package/src/services/scoped-credential-service.ts +63 -0
  293. package/src/services/system-role-guard.test.ts +4 -1
  294. package/src/services/workflow-service.ts +9 -2
  295. package/src/side-effects-are-declared.test.ts +84 -0
  296. package/src/source-files-stay-composable.test.ts +41 -0
  297. package/src/testing/service-tests/agent-run-service-tests.ts +98 -0
  298. package/src/testing/service-tests/ai-storage-service-tests.ts +379 -0
  299. package/src/testing/service-tests/channel-store-tests.ts +98 -0
  300. package/src/testing/service-tests/credential-service-tests.ts +143 -0
  301. package/src/testing/service-tests/deployment-service-tests.ts +33 -0
  302. package/src/testing/service-tests/event-hub-store-tests.ts +48 -0
  303. package/src/testing/service-tests/secret-service-tests.ts +105 -0
  304. package/src/testing/service-tests/session-store-tests.ts +62 -0
  305. package/src/testing/service-tests/workflow-run-service-tests.ts +59 -0
  306. package/src/testing/service-tests/workflow-service-tests.ts +308 -0
  307. package/src/testing/service-tests.ts +31 -1111
  308. package/src/types/core.types.ts +14 -3
  309. package/src/types/state.types.ts +17 -1
  310. package/src/wirings/actor-flow/index.ts +0 -3
  311. package/src/wirings/actor-flow/run-conversation.ts +1 -6
  312. package/src/wirings/ai-agent/agent-rpc.ts +120 -0
  313. package/src/wirings/ai-agent/ai-agent-agui.ts +1 -5
  314. package/src/wirings/ai-agent/ai-agent-finalize.test.ts +186 -0
  315. package/src/wirings/ai-agent/ai-agent-finalize.ts +197 -0
  316. package/src/wirings/ai-agent/ai-agent-interrupt.test.ts +2 -1
  317. package/src/wirings/ai-agent/ai-agent-interrupt.ts +1 -0
  318. package/src/wirings/ai-agent/ai-agent-memory.ts +62 -39
  319. package/src/wirings/ai-agent/ai-agent-model-config.test.ts +72 -3
  320. package/src/wirings/ai-agent/ai-agent-model-config.ts +49 -1
  321. package/src/wirings/ai-agent/ai-agent-prepare.ts +12 -12
  322. package/src/wirings/ai-agent/ai-agent-resume-authorization.test.ts +1 -0
  323. package/src/wirings/ai-agent/ai-agent-runner.test.ts +82 -2
  324. package/src/wirings/ai-agent/ai-agent-runner.ts +120 -160
  325. package/src/wirings/ai-agent/ai-agent-stream-output-hooks.test.ts +353 -0
  326. package/src/wirings/ai-agent/ai-agent-stream.test.ts +2 -0
  327. package/src/wirings/ai-agent/ai-agent-stream.ts +152 -117
  328. package/src/wirings/ai-agent/ai-agent-thread-ownership.test.ts +1 -1
  329. package/src/wirings/ai-agent/ai-agent-turn.test.ts +67 -0
  330. package/src/wirings/ai-agent/ai-agent-turn.ts +122 -0
  331. package/src/wirings/ai-agent/ai-agent.types.ts +65 -5
  332. package/src/wirings/ai-agent/index.ts +2 -16
  333. package/src/wirings/ai-agent/voice-input.ts +1 -6
  334. package/src/wirings/ai-agent/voice-output.ts +2 -12
  335. package/src/wirings/ai-scorer/ai-scorer-grade.test.ts +106 -0
  336. package/src/wirings/ai-scorer/ai-scorer-grade.ts +55 -0
  337. package/src/wirings/ai-scorer/ai-scorer-judge.test.ts +143 -0
  338. package/src/wirings/ai-scorer/ai-scorer-judge.ts +120 -0
  339. package/src/wirings/ai-scorer/ai-scorer-live.test.ts +174 -0
  340. package/src/wirings/ai-scorer/ai-scorer-live.ts +56 -0
  341. package/src/wirings/ai-scorer/ai-scorer-registry.ts +63 -0
  342. package/src/wirings/ai-scorer/ai-scorer-sampling.test.ts +34 -0
  343. package/src/wirings/ai-scorer/ai-scorer-sampling.ts +36 -0
  344. package/src/wirings/ai-scorer/ai-scorer-snapshots.test.ts +49 -0
  345. package/src/wirings/ai-scorer/ai-scorer-snapshots.ts +46 -0
  346. package/src/wirings/ai-scorer/ai-scorer-worker.test.ts +122 -0
  347. package/src/wirings/ai-scorer/ai-scorer-worker.ts +69 -0
  348. package/src/wirings/ai-scorer/ai-scorer.ts +76 -0
  349. package/src/wirings/ai-scorer/ai-scorer.types.ts +107 -0
  350. package/src/wirings/ai-scorer/index.ts +24 -0
  351. package/src/wirings/channel/channel-common.ts +5 -2
  352. package/src/wirings/channel/channel-handler-shapes.test.ts +72 -0
  353. package/src/wirings/channel/channel-handler.ts +7 -7
  354. package/src/wirings/channel/channel-rpc-service.ts +0 -14
  355. package/src/wirings/channel/channel-rpc.test.ts +25 -4
  356. package/src/wirings/channel/channel-rpc.types.ts +14 -0
  357. package/src/wirings/channel/channel-runner.ts +33 -20
  358. package/src/wirings/channel/channel.types.ts +2 -0
  359. package/src/wirings/channel/index.ts +1 -20
  360. package/src/wirings/channel/local/local-channel-runner.test.ts +68 -0
  361. package/src/wirings/channel/local/local-channel-runner.ts +9 -2
  362. package/src/wirings/channel/pikku-abstract-channel-handler.ts +2 -1
  363. package/src/wirings/channel/serverless/serverless-channel-runner.ts +6 -3
  364. package/src/wirings/cli/channel/cli-channel-runner.ts +3 -1
  365. package/src/wirings/cli/channel/cli-raw-channel-runner.test.ts +23 -0
  366. package/src/wirings/cli/channel/cli-raw-channel-runner.ts +12 -1
  367. package/src/wirings/cli/channel/cli-raw-client-runner.ts +4 -1
  368. package/src/wirings/cli/channel/index.ts +0 -7
  369. package/src/wirings/cli/cli-runner.test.ts +68 -0
  370. package/src/wirings/cli/cli-runner.ts +25 -5
  371. package/src/wirings/cli/cli.types.ts +0 -39
  372. package/src/wirings/cli/command-parser.test.ts +19 -0
  373. package/src/wirings/cli/command-parser.ts +17 -1
  374. package/src/wirings/credential/index.ts +0 -1
  375. package/src/wirings/gateway/gateway-channel-meta.test.ts +44 -0
  376. package/src/wirings/gateway/gateway-runner.ts +27 -19
  377. package/src/wirings/gateway/index.ts +0 -3
  378. package/src/wirings/http/http-routes.ts +4 -1
  379. package/src/wirings/http/http-runner.test.ts +66 -0
  380. package/src/wirings/http/http-runner.ts +21 -28
  381. package/src/wirings/http/http.types.ts +0 -15
  382. package/src/wirings/http/index.ts +2 -8
  383. package/src/wirings/http/pikku-fetch-http-request.ts +2 -2
  384. package/src/wirings/http/web-request.ts +1 -1
  385. package/src/wirings/mcp/index.ts +0 -1
  386. package/src/wirings/mcp/mcp-runner.test.ts +181 -0
  387. package/src/wirings/mcp/mcp-runner.ts +37 -21
  388. package/src/wirings/persona/index.ts +0 -8
  389. package/src/wirings/persona/persona-environments.test.ts +14 -3
  390. package/src/wirings/persona/persona.test.ts +13 -3
  391. package/src/wirings/persona/validate-personas.ts +5 -1
  392. package/src/wirings/queue/index.ts +0 -14
  393. package/src/wirings/queue/queue-runner.ts +1 -1
  394. package/src/wirings/rpc/addon-auth-tags.test.ts +1 -5
  395. package/src/wirings/rpc/addon-runner.ts +34 -3
  396. package/src/wirings/rpc/addon-secrets.test.ts +261 -0
  397. package/src/wirings/rpc/rpc-runner.test.ts +2 -0
  398. package/src/wirings/rpc/rpc-runner.ts +60 -136
  399. package/src/wirings/rpc/rpc-types.ts +11 -0
  400. package/src/wirings/rpc/wire-addon.ts +20 -1
  401. package/src/wirings/scheduler/index.ts +0 -1
  402. package/src/wirings/secret/validate-secret-definitions.test.ts +22 -0
  403. package/src/wirings/secret/validate-secret-definitions.ts +2 -2
  404. package/src/wirings/trigger/index.ts +0 -1
  405. package/src/wirings/trigger/pikku-trigger-service.ts +0 -5
  406. package/src/wirings/trigger/trigger-runner.ts +7 -5
  407. package/src/wirings/virtual-user/index.ts +0 -16
  408. package/src/wirings/virtual-user/run-virtual-user.test.ts +18 -9
  409. package/src/wirings/virtual-user/run-virtual-user.ts +28 -15
  410. package/src/wirings/virtual-user/virtual-user-agents.test.ts +4 -1
  411. package/src/wirings/virtual-user/virtual-user-derive.ts +4 -25
  412. package/src/wirings/virtual-user/virtual-user-dispositions.test.ts +7 -2
  413. package/src/wirings/virtual-user/virtual-user-dispositions.ts +1 -4
  414. package/src/wirings/virtual-user/virtual-user-intents.test.ts +13 -3
  415. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +117 -4
  416. package/src/wirings/workflow/feature.ts +7 -4
  417. package/src/wirings/workflow/graph/graph-node.ts +1 -1
  418. package/src/wirings/workflow/graph/graph-runner.test.ts +76 -2
  419. package/src/wirings/workflow/graph/graph-runner.ts +13 -16
  420. package/src/wirings/workflow/graph/workflow-graph.types.ts +0 -5
  421. package/src/wirings/workflow/index.ts +14 -24
  422. package/src/wirings/workflow/pikku-scenario-service.ts +101 -27
  423. package/src/wirings/workflow/pikku-workflow-service.test.ts +15 -14
  424. package/src/wirings/workflow/pikku-workflow-service.ts +214 -743
  425. package/src/wirings/workflow/scenario-expectations.test.ts +75 -0
  426. package/src/wirings/workflow/scenario-hooks.test.ts +52 -0
  427. package/src/wirings/workflow/scenario-step.types.ts +8 -0
  428. package/src/wirings/workflow/workflow-approval-audit.ts +47 -0
  429. package/src/wirings/workflow/workflow-approval-policy.test.ts +524 -0
  430. package/src/wirings/workflow/workflow-approval-policy.ts +68 -0
  431. package/src/wirings/workflow/workflow-approval.ts +289 -0
  432. package/src/wirings/workflow/workflow-child-run-session.test.ts +89 -0
  433. package/src/wirings/workflow/workflow-constants.ts +47 -0
  434. package/src/wirings/workflow/workflow-dispatch-durability.test.ts +1 -1
  435. package/src/wirings/workflow/workflow-errors.ts +128 -0
  436. package/src/wirings/workflow/workflow-inline-authority.test.ts +12 -6
  437. package/src/wirings/workflow/workflow-meta-resolver.ts +45 -0
  438. package/src/wirings/workflow/workflow-missing-meta.test.ts +92 -0
  439. package/src/wirings/workflow/workflow-queue-routing.ts +85 -0
  440. package/src/wirings/workflow/workflow-queue-wiring.ts +149 -0
  441. package/src/wirings/workflow/workflow-recovery.ts +162 -0
  442. package/src/wirings/workflow/workflow-retry-policy.test.ts +1 -1
  443. package/src/wirings/workflow/workflow-run-authority.test.ts +212 -0
  444. package/src/wirings/workflow/workflow-run-engine.types.ts +91 -0
  445. package/src/wirings/workflow/workflow-run-ownership.ts +37 -0
  446. package/src/wirings/workflow/workflow-suspend.ts +61 -0
  447. package/src/wirings/workflow/workflow.types.ts +7 -9
  448. package/src/wirings-stay-decoupled.test.ts +127 -0
  449. package/tsconfig.json +1 -1
  450. package/tsconfig.tsbuildinfo +1 -1
  451. package/dist/internal.d.ts +0 -3
  452. package/dist/internal.js +0 -2
  453. package/dist/middleware/timeout.d.ts +0 -9
  454. package/dist/middleware/timeout.js +0 -15
  455. package/dist/pikku-response.d.ts +0 -6
  456. package/dist/pikku-response.js +0 -6
  457. package/dist/services/gopass-secrets.d.ts +0 -15
  458. package/dist/services/gopass-secrets.js +0 -76
  459. package/dist/services/http-scenario-actors.d.ts +0 -75
  460. package/dist/services/http-scenario-actors.js +0 -195
  461. package/dist/services/http-user-flow-actors.d.ts +0 -67
  462. package/dist/services/http-user-flow-actors.js +0 -193
  463. package/dist/services/scenario-actors-service.d.ts +0 -127
  464. package/dist/services/scenario-actors-service.js +0 -40
  465. package/dist/services/user-flow-actors-service.d.ts +0 -39
  466. package/dist/wirings/credential/wire-credential.d.ts +0 -48
  467. package/dist/wirings/credential/wire-credential.js +0 -47
  468. package/dist/wirings/oauth2/oauth2-client.d.ts +0 -47
  469. package/dist/wirings/oauth2/oauth2-client.js +0 -263
  470. package/dist/wirings/oauth2/oauth2-routes.d.ts +0 -35
  471. package/dist/wirings/oauth2/oauth2-routes.js +0 -146
  472. package/dist/wirings/scope/wire-scope.d.ts +0 -33
  473. package/dist/wirings/scope/wire-scope.js +0 -32
  474. package/dist/wirings/workflow/dsl/index.d.ts +0 -5
  475. package/dist/wirings/workflow/dsl/index.js +0 -4
  476. package/dist/wirings/workflow/graph/index.d.ts +0 -5
  477. package/dist/wirings/workflow/graph/index.js +0 -4
  478. package/src/internal.ts +0 -10
  479. /package/dist/{services/user-flow-actors-service.js → wirings/workflow/workflow-run-engine.types.js} +0 -0
@@ -1,4 +1,6 @@
1
+ import { finalizeAgentRun, lastUserMessageText, } from './ai-agent-finalize.js';
1
2
  import { pikkuState, getSingletonServices } from '../../pikku-state.js';
3
+ import { applyInputMiddleware } from './ai-agent-turn.js';
2
4
  import { AIProviderNotConfiguredError } from '../../errors/errors.js';
3
5
  import { randomUUID } from './ai-agent-utils.js';
4
6
  import { SPOKEN_TRANSCRIPT } from './voice-input.js';
@@ -17,6 +19,9 @@ function createPersistingChannel(parent, storage, threadId, logger) {
17
19
  inputTokens: 0,
18
20
  outputTokens: 0,
19
21
  };
22
+ // Survives the per-step flush below, which clears its own buffers: the run
23
+ // record needs every call the run made, not just the last step's.
24
+ const runToolCalls = [];
20
25
  const flushStep = async (opts) => {
21
26
  if (!storage)
22
27
  return;
@@ -62,6 +67,7 @@ function createPersistingChannel(parent, storage, threadId, logger) {
62
67
  });
63
68
  });
64
69
  };
70
+ const runToolCallIndex = new Map();
65
71
  const channel = {
66
72
  channelId: parent.channelId,
67
73
  openingData: parent.openingData,
@@ -74,6 +80,9 @@ function createPersistingChannel(parent, storage, threadId, logger) {
74
80
  get totalUsage() {
75
81
  return totalUsage;
76
82
  },
83
+ get runToolCalls() {
84
+ return runToolCalls;
85
+ },
77
86
  flush: flushStep,
78
87
  close: () => parent.close(),
79
88
  sendBinary: (data) => parent.sendBinary(data),
@@ -83,6 +92,26 @@ function createPersistingChannel(parent, storage, threadId, logger) {
83
92
  // report the fragment it got through even with persistence turned off.
84
93
  if (event.type === 'text-delta')
85
94
  fullText += event.text;
95
+ if (event.type === 'tool-call') {
96
+ runToolCallIndex.set(event.toolCallId, runToolCalls.length);
97
+ runToolCalls.push({
98
+ name: event.toolName,
99
+ args: event.args,
100
+ result: '',
101
+ });
102
+ }
103
+ if (event.type === 'tool-result') {
104
+ const index = runToolCallIndex.get(event.toolCallId);
105
+ const result = typeof event.result === 'string'
106
+ ? event.result
107
+ : JSON.stringify(event.result);
108
+ const call = index === undefined ? undefined : runToolCalls[index];
109
+ if (call) {
110
+ call.result = result;
111
+ if (event.error)
112
+ call.error = event.error;
113
+ }
114
+ }
86
115
  if (storage) {
87
116
  switch (event.type) {
88
117
  case 'text-delta':
@@ -102,6 +131,7 @@ function createPersistingChannel(parent, storage, threadId, logger) {
102
131
  result: typeof event.result === 'string'
103
132
  ? event.result
104
133
  : JSON.stringify(event.result),
134
+ ...(event.error ? { error: event.error } : {}),
105
135
  });
106
136
  break;
107
137
  case 'generative-ui':
@@ -128,36 +158,49 @@ function createPersistingChannel(parent, storage, threadId, logger) {
128
158
  };
129
159
  return channel;
130
160
  }
131
- async function postStreamCleanup(persistingChannel, aiMiddlewares, singletonServices, messages, aiRunState, runId) {
132
- const usage = persistingChannel.totalUsage;
133
- let outputText = persistingChannel.fullText;
134
- let outputMessages = messages;
135
- for (let i = aiMiddlewares.length - 1; i >= 0; i--) {
136
- const mw = aiMiddlewares[i];
137
- if (mw.modifyOutput) {
138
- const result = await mw.modifyOutput(singletonServices, {
139
- text: outputText,
140
- messages: outputMessages,
141
- usage: {
142
- inputTokens: usage.inputTokens,
143
- outputTokens: usage.outputTokens,
144
- },
145
- });
146
- outputText = result.text;
147
- outputMessages = result.messages;
148
- }
149
- }
150
- await aiRunState.updateRun(runId, {
151
- status: 'completed',
152
- ...(usage.model
153
- ? {
154
- usage: {
155
- inputTokens: usage.inputTokens,
156
- outputTokens: usage.outputTokens,
157
- model: usage.model,
158
- },
159
- }
160
- : {}),
161
+ /**
162
+ * Agents already warned about, so a per-request hook does not become a
163
+ * per-request log line.
164
+ */
165
+ const warnedUnstreamedOutputHooks = new Set();
166
+ /**
167
+ * `modifyOutput` does not run on a streamed run at all. Nothing here could act
168
+ * on what it returns — the text has already reached the client, and
169
+ * `createPersistingChannel` flushes each step to storage as it goes, so by the
170
+ * time the run ends the transcript is already written.
171
+ *
172
+ * Rewriting on this path belongs to `modifyOutputStream`, which genuinely
173
+ * works: the stream middleware wraps the persisting channel, so what is stored
174
+ * and accumulated is already what the client was sent. A middleware that
175
+ * rewrites in `modifyOutput` only — a redaction hook, typically — is therefore
176
+ * silently ineffective when the agent is streamed, and is told so once.
177
+ */
178
+ const warnUnstreamedOutputHooks = (agentName, aiMiddlewares, logger) => {
179
+ if (warnedUnstreamedOutputHooks.has(agentName))
180
+ return;
181
+ const unstreamed = aiMiddlewares.some((mw) => mw.modifyOutput && !mw.modifyOutputStream);
182
+ if (!unstreamed)
183
+ return;
184
+ warnedUnstreamedOutputHooks.add(agentName);
185
+ logger?.warn(`Agent '${agentName}' has AI middleware with modifyOutput but no modifyOutputStream — modifyOutput does not apply to streamed runs. Implement modifyOutputStream to affect a streamed reply.`);
186
+ };
187
+ async function postStreamCleanup(persistingChannel, aiRunState, runId, run) {
188
+ await finalizeAgentRun(aiRunState, {
189
+ runId,
190
+ agentName: run.agentName,
191
+ threadId: run.threadId,
192
+ resourceId: run.resourceId,
193
+ input: run.input,
194
+ // Already what the client received: the stream middleware wraps the
195
+ // persisting channel, so both were accumulated post-rewrite.
196
+ text: persistingChannel.fullText,
197
+ steps: [
198
+ {
199
+ usage: persistingChannel.totalUsage,
200
+ toolCalls: persistingChannel.runToolCalls,
201
+ },
202
+ ],
203
+ usage: persistingChannel.totalUsage,
161
204
  });
162
205
  }
163
206
  async function runStreamStepLoop(params) {
@@ -414,36 +457,18 @@ export async function streamAIAgent(agentName, input, channel, params, agentSess
414
457
  ];
415
458
  // One bag per run, shared by every middleware — see PikkuAIMiddlewareHooks.
416
459
  const sharedNotes = {};
417
- let modifiedMessages = runnerParams.messages;
418
- let modifiedInstructions = runnerParams.instructions;
419
- for (const mw of aiMiddlewares) {
420
- if (mw.modifyInput) {
421
- const result = await mw.modifyInput(singletonServices, {
422
- messages: modifiedMessages,
423
- instructions: modifiedInstructions,
424
- shared: sharedNotes,
425
- });
426
- modifiedMessages = result.messages;
427
- modifiedInstructions = result.instructions;
428
- }
429
- }
460
+ const { messages: modifiedMessages, instructions: modifiedInstructions } = await applyInputMiddleware(aiMiddlewares, singletonServices, {
461
+ messages: runnerParams.messages,
462
+ instructions: runnerParams.instructions,
463
+ }, sharedNotes);
430
464
  runnerParams.messages = modifiedMessages;
431
465
  runnerParams.instructions = modifiedInstructions;
432
- // Sent on the raw channel, ahead of the run. A voice client sent audio and so
433
- // has no idea what it said; until this arrives its own message is a blank
434
- // bubble. Ahead of the run rather than alongside it because the answer starts
435
- // streaming within a few hundred milliseconds, and a question that appears
436
- // after its answer reads as the wrong question.
466
+ // knowledge: decisions/internals/the-transcript-event-is-sent-ahead-of-the-run.md
437
467
  const transcript = sharedNotes[SPOKEN_TRANSCRIPT];
438
468
  if (typeof transcript === 'string') {
439
469
  channel.send({ type: 'transcript', text: transcript });
440
470
  }
441
- // What goes into thread history is what the model was actually asked. For a
442
- // spoken turn that is not what arrived over the wire: the wire carried a
443
- // base64 audio blob, and persisting it would write megabytes of unreadable
444
- // data into the history while losing the only readable record of the turn.
445
- // Identity-checked rather than assumed — a middleware is free to rewrite the
446
- // message list into something with no relation to the turn.
471
+ // knowledge: decisions/internals/thread-history-records-the-transcript-not-the-audio.md
447
472
  const lastModified = modifiedMessages[modifiedMessages.length - 1];
448
473
  const persistedUserMessage = lastModified?.id === userMessage.id ? lastModified : userMessage;
449
474
  const runId = await aiRunState.createRun({
@@ -465,6 +490,7 @@ export async function streamAIAgent(agentName, input, channel, params, agentSess
465
490
  if (storage) {
466
491
  await storage.saveMessages(threadId, [persistedUserMessage]);
467
492
  }
493
+ warnUnstreamedOutputHooks(agentName, aiMiddlewares, singletonServices.logger);
468
494
  const streamMiddleware = aiMiddlewares
469
495
  .filter((mw) => mw.modifyOutputStream)
470
496
  .map((mw) => {
@@ -508,10 +534,7 @@ export async function streamAIAgent(agentName, input, channel, params, agentSess
508
534
  : persistingChannel;
509
535
  const credentialFilteredChannel = {
510
536
  ...wrappedChannel,
511
- // Returns what the inner send returns. Middleware runs asynchronously, so
512
- // swallowing the promise here makes every `await channel.send(...)` upstream
513
- // a no-op — including the one that waits for a buffering hook to flush
514
- // before the channel closes.
537
+ // knowledge: decisions/internals/an-agent-stream-send-must-return-the-inner-sends-promise.md
515
538
  send: (event) => {
516
539
  if (event.type === 'tool-result' &&
517
540
  event.result !== null &&
@@ -558,12 +581,13 @@ export async function streamAIAgent(agentName, input, channel, params, agentSess
558
581
  await handleCredentialRequests(loopResult.credentialRequests, runId, channel, aiRunState, persistingChannel);
559
582
  return persistingChannel.fullText;
560
583
  }
561
- await postStreamCleanup(persistingChannel, aiMiddlewares, singletonServices, runnerParams.messages, aiRunState, runId);
562
- // Through the middleware rather than straight at the channel, and awaited.
563
- // `done` is the only signal a stream hook gets that the reply is over, and
564
- // the ones that buffer need it: `voiceOutput` speaks a trailing fragment
565
- // that never reached a full stop, and waits for audio it has already paid
566
- // to synthesize. Sent raw, that work is discarded by the `close()` below.
584
+ await postStreamCleanup(persistingChannel, aiRunState, runId, {
585
+ agentName,
586
+ threadId,
587
+ resourceId: input.resourceId,
588
+ input: lastUserMessageText(runnerParams.messages),
589
+ });
590
+ // knowledge: decisions/internals/the-agent-done-event-goes-through-the-middleware-and-is-awaited.md
567
591
  await outputChannel.send({ type: 'done' });
568
592
  channel.close();
569
593
  return persistingChannel.fullText;
@@ -650,12 +674,7 @@ export async function interruptAIAgent(input, params) {
650
674
  const stopped = signalRunInterrupt(input.runId, {
651
675
  reason: input.reason ?? 'user',
652
676
  });
653
- // A run still marked `running` that this process cannot abort is executing on
654
- // another instance. Returning a bare `false` there is indistinguishable from
655
- // "it already finished", so the one deployment shape this registry does not
656
- // cover fails silently — an agent that simply refuses to shut up, with nothing
657
- // in the logs. Say so instead. See `signalRunInterrupt` for the fix (fan the
658
- // interrupt out over eventHub so every instance tries locally).
677
+ // knowledge: decisions/internals/an-agent-run-owned-by-another-instance-says-so.md
659
678
  if (!stopped && run.status === 'running') {
660
679
  logger?.warn(`Could not interrupt run ${input.runId}: it is running in another process. ` +
661
680
  'Interrupts are process-local; fan them out over eventHub to support multiple instances.');
@@ -681,7 +700,13 @@ export async function resumeAIAgent(input, channel, params, options) {
681
700
  }
682
701
  const { agent, packageName, resolvedName } = resolveAgent(run.agentName);
683
702
  await assertAgentAuthorized(agent, params, packageName);
684
- await aiRunState.resolveApproval(input.toolCallId, input.approved ? 'approved' : 'denied');
703
+ // The read above is not a claim — concurrent resumes all see the same pending
704
+ // approval. `resolveApproval` is the claim, and the loser must not go on to
705
+ // run the tool a second time.
706
+ const claimed = await aiRunState.resolveApproval(input.toolCallId, input.approved ? 'approved' : 'denied');
707
+ if (!claimed) {
708
+ throw new Error(`Approval for toolCallId ${input.toolCallId} was already resolved by another caller`);
709
+ }
685
710
  const { storage } = resolveMemoryServices(agent, singletonServices);
686
711
  const memoryConfig = agent.memory;
687
712
  const agentRunner = singletonServices.aiAgentRunner;
@@ -789,18 +814,19 @@ export async function resumeAIAgent(input, channel, params, options) {
789
814
  }
790
815
  const toolArgs = typeof pending.args === 'string' ? JSON.parse(pending.args) : pending.args;
791
816
  let toolResult;
792
- let isError = false;
817
+ let toolError;
793
818
  try {
794
819
  toolResult = await matchingTool.execute(toolArgs);
795
820
  }
796
821
  catch (execErr) {
797
822
  if (execErr?.payload?.error === 'missing_credential') {
798
823
  toolResult = execErr.payload;
824
+ toolError = 'missing_credential';
799
825
  }
800
826
  else {
801
- toolResult = `Error: ${execErr instanceof Error ? execErr.message : String(execErr)}`;
827
+ toolError = execErr instanceof Error ? execErr.message : String(execErr);
828
+ toolResult = `Error: ${toolError}`;
802
829
  }
803
- isError = true;
804
830
  }
805
831
  const resultStr = typeof toolResult === 'string' ? toolResult : JSON.stringify(toolResult);
806
832
  if (storage) {
@@ -824,7 +850,7 @@ export async function resumeAIAgent(input, channel, params, options) {
824
850
  toolCallId: input.toolCallId,
825
851
  toolName: pending.toolName,
826
852
  result: toolResult,
827
- ...(isError ? { isError: true } : {}),
853
+ ...(toolError ? { error: toolError } : {}),
828
854
  });
829
855
  }
830
856
  const updatedRun = await aiRunState.getRun(run.runId);
@@ -865,24 +891,10 @@ async function continueAfterToolResult(run, agent, packageName, resolvedName, st
865
891
  ];
866
892
  // One bag per run, shared by every middleware — see PikkuAIMiddlewareHooks.
867
893
  const sharedNotes = {};
868
- let modifiedMessages = trimmedMessages;
869
- let modifiedInstructions = instructions;
870
- for (const mw of aiMiddlewares) {
871
- if (mw.modifyInput) {
872
- const result = await mw.modifyInput(singletonServices, {
873
- messages: modifiedMessages,
874
- instructions: modifiedInstructions,
875
- shared: sharedNotes,
876
- });
877
- modifiedMessages = result.messages;
878
- modifiedInstructions = result.instructions;
879
- }
880
- }
881
- // Resuming is as interruptible as the first turn. It is the same person
882
- // listening to the same voice, and after an approval it is where most of the
883
- // reply actually gets spoken — an approved delete is followed by the agent
884
- // talking about it, and that is a normal thing to talk over.
894
+ const { messages: modifiedMessages, instructions: modifiedInstructions } = await applyInputMiddleware(aiMiddlewares, singletonServices, { messages: trimmedMessages, instructions: instructions }, sharedNotes);
895
+ // knowledge: decisions/internals/a-resumed-agent-turn-is-as-interruptible-as-the-first.md
885
896
  const interruptHandle = registerInterruptibleRun(run.runId);
897
+ warnUnstreamedOutputHooks(run.agentName, aiMiddlewares, singletonServices.logger);
886
898
  const streamMiddleware = aiMiddlewares
887
899
  .filter((mw) => mw.modifyOutputStream)
888
900
  .map((mw) => {
@@ -960,11 +972,11 @@ async function continueAfterToolResult(run, agent, packageName, resolvedName, st
960
972
  await handleCredentialRequests(loopResult.credentialRequests, run.runId, channel, aiRunState, persistingChannel);
961
973
  return;
962
974
  }
963
- await postStreamCleanup(persistingChannel, aiMiddlewares, singletonServices, runnerParams.messages, aiRunState, run.runId);
964
- // Through the middleware, for the same reason as the first turn — and it
965
- // matters more here. After an approval, most of what gets spoken is the
966
- // agent talking about what it just did, so this is the half of the reply a
967
- // dropped flush would silence.
975
+ await postStreamCleanup(persistingChannel, aiRunState, run.runId, {
976
+ ...run,
977
+ input: lastUserMessageText(runnerParams.messages),
978
+ });
979
+ // knowledge: decisions/internals/the-agent-done-event-goes-through-the-middleware-and-is-awaited.md
968
980
  await wrappedChannel.send({ type: 'done' });
969
981
  channel.close();
970
982
  }
@@ -0,0 +1,57 @@
1
+ import type { CoreSingletonServices } from '../../types/core.types.js';
2
+ import type { PikkuAIMiddlewareHooks } from './ai-agent.types.js';
3
+ /** Exactly what `modifyInput` accepts and returns, taken from the hook itself. */
4
+ type TurnInput = Pick<Parameters<NonNullable<PikkuAIMiddlewareHooks['modifyInput']>>[1], 'messages' | 'instructions'>;
5
+ /**
6
+ * Run every `modifyInput` hook in order, threading each result into the next.
7
+ *
8
+ * Shared by the four places a turn starts — first turn and resume, on both the
9
+ * streaming and non-streaming paths. `sharedNotes` is one bag per run that the
10
+ * caller keeps, because hooks use it to pass things forward (`voiceInput`
11
+ * leaves the transcript there for `voiceOutput`).
12
+ */
13
+ export declare const applyInputMiddleware: (aiMiddlewares: PikkuAIMiddlewareHooks[], singletonServices: CoreSingletonServices, input: TurnInput, sharedNotes: Record<string, unknown>) => Promise<TurnInput>;
14
+ /** The step fields both agent paths hand to `afterStep`, taken from the hook. */
15
+ type StepResult = Omit<Parameters<NonNullable<PikkuAIMiddlewareHooks['afterStep']>>[1], 'stepNumber'>;
16
+ /**
17
+ * Notify every `afterStep` hook. The payload is the step verbatim plus its
18
+ * ordinal — a hook that wants the run's totals accumulates them itself.
19
+ */
20
+ export declare const notifyAfterStep: (aiMiddlewares: PikkuAIMiddlewareHooks[], singletonServices: CoreSingletonServices, stepNumber: number, stepResult: StepResult) => Promise<void>;
21
+ /**
22
+ * The step shape kept on the run: each tool call paired with its result.
23
+ *
24
+ * A result is stringified unless it already is a string, because this is what
25
+ * gets persisted and read back as history — an object there is unreadable.
26
+ */
27
+ export declare const toAccumulatedStep: (stepResult: StepResult) => {
28
+ usage: {
29
+ inputTokens: number;
30
+ outputTokens: number;
31
+ };
32
+ toolCalls: {
33
+ error?: string | undefined;
34
+ name: string;
35
+ args: Record<string, unknown>;
36
+ result: string;
37
+ }[];
38
+ };
39
+ type PendingApproval = {
40
+ toolName: string;
41
+ args: unknown;
42
+ reason?: string;
43
+ };
44
+ type ToolDef = {
45
+ name: string;
46
+ approvalDescriptionFn?: (args: any) => Promise<string> | string;
47
+ };
48
+ /**
49
+ * Fill in each approval's human-readable reason from the tool's own
50
+ * `approvalDescriptionFn`, where it did not supply one.
51
+ *
52
+ * A description that throws is swallowed: the approval still has to be raised,
53
+ * and a gate that disappears because its label failed to render is the worst
54
+ * available outcome.
55
+ */
56
+ export declare const describeApprovals: (approvals: PendingApproval[], tools: ToolDef[]) => Promise<void>;
57
+ export {};
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Run every `modifyInput` hook in order, threading each result into the next.
3
+ *
4
+ * Shared by the four places a turn starts — first turn and resume, on both the
5
+ * streaming and non-streaming paths. `sharedNotes` is one bag per run that the
6
+ * caller keeps, because hooks use it to pass things forward (`voiceInput`
7
+ * leaves the transcript there for `voiceOutput`).
8
+ */
9
+ export const applyInputMiddleware = async (aiMiddlewares, singletonServices, input, sharedNotes) => {
10
+ let { messages, instructions } = input;
11
+ for (const mw of aiMiddlewares) {
12
+ if (!mw.modifyInput)
13
+ continue;
14
+ const result = await mw.modifyInput(singletonServices, {
15
+ messages,
16
+ instructions,
17
+ shared: sharedNotes,
18
+ });
19
+ messages = result.messages;
20
+ instructions = result.instructions;
21
+ }
22
+ return { messages, instructions };
23
+ };
24
+ /**
25
+ * Notify every `afterStep` hook. The payload is the step verbatim plus its
26
+ * ordinal — a hook that wants the run's totals accumulates them itself.
27
+ */
28
+ export const notifyAfterStep = async (aiMiddlewares, singletonServices, stepNumber, stepResult) => {
29
+ for (const mw of aiMiddlewares) {
30
+ if (!mw.afterStep)
31
+ continue;
32
+ await mw.afterStep(singletonServices, {
33
+ stepNumber,
34
+ text: stepResult.text,
35
+ toolCalls: stepResult.toolCalls,
36
+ toolResults: stepResult.toolResults,
37
+ usage: stepResult.usage,
38
+ finishReason: stepResult.finishReason,
39
+ });
40
+ }
41
+ };
42
+ /**
43
+ * The step shape kept on the run: each tool call paired with its result.
44
+ *
45
+ * A result is stringified unless it already is a string, because this is what
46
+ * gets persisted and read back as history — an object there is unreadable.
47
+ */
48
+ export const toAccumulatedStep = (stepResult) => ({
49
+ usage: stepResult.usage,
50
+ toolCalls: stepResult.toolCalls.map((tc) => {
51
+ const tr = stepResult.toolResults.find((r) => r.toolCallId === tc.toolCallId);
52
+ return {
53
+ name: tc.toolName,
54
+ args: tc.args,
55
+ result: typeof tr?.result === 'string'
56
+ ? tr.result
57
+ : JSON.stringify(tr?.result ?? ''),
58
+ ...(tr?.error ? { error: tr.error } : {}),
59
+ };
60
+ }),
61
+ });
62
+ /**
63
+ * Fill in each approval's human-readable reason from the tool's own
64
+ * `approvalDescriptionFn`, where it did not supply one.
65
+ *
66
+ * A description that throws is swallowed: the approval still has to be raised,
67
+ * and a gate that disappears because its label failed to render is the worst
68
+ * available outcome.
69
+ */
70
+ export const describeApprovals = async (approvals, tools) => {
71
+ for (const approval of approvals) {
72
+ if (approval.reason)
73
+ continue;
74
+ const toolDef = tools.find((t) => t.name === approval.toolName);
75
+ if (!toolDef?.approvalDescriptionFn)
76
+ continue;
77
+ try {
78
+ approval.reason = await toolDef.approvalDescriptionFn(approval.args);
79
+ }
80
+ catch { }
81
+ }
82
+ };
@@ -43,6 +43,13 @@ export interface AIToolResult {
43
43
  id: string;
44
44
  name: string;
45
45
  result: string;
46
+ /**
47
+ * Set when the tool threw rather than returned. Carried separately from
48
+ * `result`, which is a rendered string by the time it is persisted — a tool
49
+ * may legitimately return text beginning `Error:`, so the prefix cannot be
50
+ * read as a failure signal.
51
+ */
52
+ error?: string;
46
53
  }
47
54
  export interface AIMessage {
48
55
  id: string;
@@ -79,6 +86,8 @@ export interface AIAgentStep {
79
86
  name: string;
80
87
  args: Record<string, unknown>;
81
88
  result: string;
89
+ /** The failure message, when the tool threw rather than returned. */
90
+ error?: string;
82
91
  }[];
83
92
  }
84
93
  export interface AIAgentInputAttachment {
@@ -216,6 +225,21 @@ export interface PikkuAIMiddlewareHooks<State extends Record<string, unknown> =
216
225
  */
217
226
  signal?: AbortSignal;
218
227
  }) => Promise<AIStreamEvent | AIStreamEvent[] | null> | AIStreamEvent | AIStreamEvent[] | null;
228
+ /**
229
+ * The last chance to rewrite what the run produced, before it is persisted
230
+ * and returned.
231
+ *
232
+ * It does **not** run on a streamed run: there the text has already reached
233
+ * the client and each step is flushed to storage as it goes, so nothing could
234
+ * act on what this returned. Use {@link modifyOutputStream} to rewrite a
235
+ * streamed reply — a middleware that implements only this one is warned about
236
+ * when an agent it is attached to streams.
237
+ *
238
+ * `toolCalls` is here so a redaction pass covers the whole run record rather
239
+ * than just the visible answer: the tool arguments and results are persisted
240
+ * and handed to anything that grades the run, and scrubbing the reply alone
241
+ * leaves them untouched.
242
+ */
219
243
  modifyOutput?: (services: Services, ctx: {
220
244
  text: string;
221
245
  messages: AIMessage[];
@@ -223,12 +247,15 @@ export interface PikkuAIMiddlewareHooks<State extends Record<string, unknown> =
223
247
  inputTokens: number;
224
248
  outputTokens: number;
225
249
  };
250
+ toolCalls: NonNullable<AIAgentStep['toolCalls']>;
226
251
  }) => Promise<{
227
252
  text: string;
228
253
  messages: AIMessage[];
254
+ toolCalls?: NonNullable<AIAgentStep['toolCalls']>;
229
255
  }> | {
230
256
  text: string;
231
257
  messages: AIMessage[];
258
+ toolCalls?: NonNullable<AIAgentStep['toolCalls']>;
232
259
  };
233
260
  beforeToolCall?: (services: Services, ctx: {
234
261
  toolName: string;
@@ -262,6 +289,8 @@ export interface PikkuAIMiddlewareHooks<State extends Record<string, unknown> =
262
289
  toolCallId: string;
263
290
  toolName: string;
264
291
  result: unknown;
292
+ /** Set when the tool threw rather than returned. */
293
+ error?: string;
265
294
  }[];
266
295
  usage: {
267
296
  inputTokens: number;
@@ -282,7 +311,7 @@ export type AIAgentMemoryConfig = {
282
311
  lastMessages?: number;
283
312
  workingMemory?: unknown;
284
313
  };
285
- export type CoreAIAgent<PikkuPermission = CorePikkuPermission<any, any>, PikkuMiddleware = CorePikkuMiddleware<any>, Scope extends string = string> = {
314
+ export type CoreAIAgent<PikkuPermission = CorePikkuPermission<any, any>, PikkuMiddleware = CorePikkuMiddleware<any>, Scope extends string = string, Scorer extends string = string> = {
286
315
  name: string;
287
316
  description: string;
288
317
  summary?: string;
@@ -308,6 +337,16 @@ export type CoreAIAgent<PikkuPermission = CorePikkuPermission<any, any>, PikkuMi
308
337
  tools?: unknown[];
309
338
  agents?: unknown[];
310
339
  workflows?: unknown[];
340
+ /**
341
+ * Grades this agent's finished runs on live traffic, named by the generated
342
+ * `ScorerName` union rather than by `ref()` — a scorer is not a function, so
343
+ * there is nothing in the function map for a ref to resolve against.
344
+ *
345
+ * A reference-based judge listed here is never sampled: live traffic has no
346
+ * answer key. Scenarios name scorers directly and may grade with scorers an
347
+ * agent does not ship with.
348
+ */
349
+ scorers?: Scorer[];
311
350
  agentMode?: 'delegate' | 'supervise';
312
351
  memory?: AIAgentMemoryConfig;
313
352
  maxSteps?: number;
@@ -382,6 +421,12 @@ export type AIStreamEvent = {
382
421
  toolCallId: string;
383
422
  toolName: string;
384
423
  result: unknown;
424
+ /**
425
+ * The failure message, set when the tool threw rather than returned.
426
+ * Carried explicitly because a tool may legitimately return text that
427
+ * reads like an error, so `result` cannot be matched on to tell.
428
+ */
429
+ error?: string;
385
430
  agent?: string;
386
431
  session?: string;
387
432
  } | {
@@ -400,7 +445,7 @@ export type AIStreamEvent = {
400
445
  toolName: string;
401
446
  args: unknown;
402
447
  reason?: string;
403
- runId?: string;
448
+ runId: string;
404
449
  agent?: string;
405
450
  session?: string;
406
451
  } | {
@@ -1,11 +1,12 @@
1
1
  export { agent, agentStream, agentResume, agentApprove, agentInterrupt, } from './ai-agent-helpers.js';
2
- export { wrapChannelWithAGUI, type AGUIEvent } from './ai-agent-agui.js';
2
+ export { wrapChannelWithAGUI } from './ai-agent-agui.js';
3
3
  export { runAIAgent, resumeAIAgentSync } from './ai-agent-runner.js';
4
+ export { resolveModelAlias } from './ai-agent-model-config.js';
4
5
  export { streamAIAgent, resumeAIAgent, interruptAIAgent, } from './ai-agent-stream.js';
5
- export { voiceInput, readsAsNonSpeech, NoSpeechDetectedError, SPOKEN_TURN, SPOKEN_TRANSCRIPT, } from './voice-input.js';
6
+ export { voiceInput, NoSpeechDetectedError, SPOKEN_TURN, SPOKEN_TRANSCRIPT, } from './voice-input.js';
6
7
  export { voiceOutput, unspeakableScripts, voiceForText, type SpeakableScripts, } from './voice-output.js';
7
- export { AgentInterruptedError, awaitPendingInterruptNote, getInFlightTools, isAbortError, isRunInterruptible, persistOrphanedToolResults, registerInterruptibleRun, signalRunInterrupt, trackInterruptNote, trackToolExecution, } from './ai-agent-interrupt.js';
8
- export type { AgentInterruption, AgentInterruptResult, InterruptibleRunHandle, OrphanedToolResult, } from './ai-agent-interrupt.js';
9
- export { type RunAIAgentParams, type StreamAIAgentOptions, ToolApprovalRequired, ToolCredentialRequired, canAccessThread, isOwnedByPrincipal, sessionPrincipals, threadOwnerConstraint, } from './ai-agent-prepare.js';
10
- export { addAIAgent, approveAIAgent, getAIAgents, getAIAgentsMeta, } from './ai-agent-registry.js';
11
- export type { AIAgentInput, AIAgentInputAttachment, AIAgentMeta, AIAgentMemoryConfig, AIAgentStep, AIContentPart, AgentRunRow, AgentRunService, AgentRunState, AIMessage, AIStreamChannel, AIStreamEvent, AIThread, CoreAIAgent, PendingApproval, PikkuAIMiddlewareHooks, } from './ai-agent.types.js';
8
+ export { AgentInterruptedError, signalRunInterrupt, } from './ai-agent-interrupt.js';
9
+ export type { AgentInterruption, AgentInterruptResult, InterruptibleRunHandle, } from './ai-agent-interrupt.js';
10
+ export { type RunAIAgentParams, type StreamAIAgentOptions, ToolApprovalRequired, ToolCredentialRequired, canAccessThread, isOwnedByPrincipal, threadOwnerConstraint, } from './ai-agent-prepare.js';
11
+ export { addAIAgent, } from './ai-agent-registry.js';
12
+ export type { AIAgentInput, AIAgentMeta, AIAgentMemoryConfig, AIAgentStep, AIContentPart, AgentRunRow, AgentRunService, AgentRunState, AIMessage, AIStreamChannel, AIStreamEvent, AIThread, CoreAIAgent, PendingApproval, PikkuAIMiddlewareHooks, } from './ai-agent.types.js';
@@ -1,9 +1,10 @@
1
1
  export { agent, agentStream, agentResume, agentApprove, agentInterrupt, } from './ai-agent-helpers.js';
2
2
  export { wrapChannelWithAGUI } from './ai-agent-agui.js';
3
3
  export { runAIAgent, resumeAIAgentSync } from './ai-agent-runner.js';
4
+ export { resolveModelAlias } from './ai-agent-model-config.js';
4
5
  export { streamAIAgent, resumeAIAgent, interruptAIAgent, } from './ai-agent-stream.js';
5
- export { voiceInput, readsAsNonSpeech, NoSpeechDetectedError, SPOKEN_TURN, SPOKEN_TRANSCRIPT, } from './voice-input.js';
6
+ export { voiceInput, NoSpeechDetectedError, SPOKEN_TURN, SPOKEN_TRANSCRIPT, } from './voice-input.js';
6
7
  export { voiceOutput, unspeakableScripts, voiceForText, } from './voice-output.js';
7
- export { AgentInterruptedError, awaitPendingInterruptNote, getInFlightTools, isAbortError, isRunInterruptible, persistOrphanedToolResults, registerInterruptibleRun, signalRunInterrupt, trackInterruptNote, trackToolExecution, } from './ai-agent-interrupt.js';
8
- export { ToolApprovalRequired, ToolCredentialRequired, canAccessThread, isOwnedByPrincipal, sessionPrincipals, threadOwnerConstraint, } from './ai-agent-prepare.js';
9
- export { addAIAgent, approveAIAgent, getAIAgents, getAIAgentsMeta, } from './ai-agent-registry.js';
8
+ export { AgentInterruptedError, signalRunInterrupt, } from './ai-agent-interrupt.js';
9
+ export { ToolApprovalRequired, ToolCredentialRequired, canAccessThread, isOwnedByPrincipal, threadOwnerConstraint, } from './ai-agent-prepare.js';
10
+ export { addAIAgent, } from './ai-agent-registry.js';
@@ -135,12 +135,7 @@ export const voiceInput = (config) => pikkuAIMiddleware({
135
135
  // to send, and a message with no content is not a question.
136
136
  if (updatedContent.length === 0)
137
137
  throw new NoSpeechDetectedError();
138
- // Only when something was actually heard. A turn can carry audio that all
139
- // reads as non-speech and still have content — an image with a silent
140
- // caption clip — which leaves nothing above to throw. Recording `''` here
141
- // would send a transcript event saying the user said nothing, and a client
142
- // that tells "not transcribed yet" from "transcribed" by the key being
143
- // absent would render the turn as an empty bubble rather than a pending one.
138
+ // knowledge: decisions/internals/an-empty-transcript-is-not-recorded.md
144
139
  if (heard.length > 0) {
145
140
  shared[SPOKEN_TRANSCRIPT] = heard.join(' ');
146
141
  }