@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
@@ -16,15 +16,14 @@
16
16
  * {@link VirtualUserTarget}, which in production is an `HttpPersona`
17
17
  * signed in as a real user against staging or production.
18
18
  */
19
- export type { ApiCatalogueEntry, IntentRecord, IntentSource, IntentStatus, StepRecord, VirtualUserAction, VirtualUserBudget, VirtualUserDisposition, VirtualUserFinding, VirtualUserFindingKind, VirtualUserRunResult, VirtualUserTally, VirtualUserTarget, } from './virtual-user.types.js';
19
+ export type { ApiCatalogueEntry, IntentSource, VirtualUserDisposition, VirtualUserFinding, VirtualUserRunResult, VirtualUserTarget, } from './virtual-user.types.js';
20
20
  export { PRODUCTION_DISPOSITION } from './virtual-user.types.js';
21
- export { runVirtualUser, rememberIds, type RunVirtualUserParams, type VirtualUserCallContext, } from './run-virtual-user.js';
21
+ export { runVirtualUser, type RunVirtualUserParams, } from './run-virtual-user.js';
22
22
  export { personaScopes, prepareVirtualUserRun, type VirtualUserPreparation, } from './prepare-virtual-user-run.js';
23
23
  export type { VirtualUserRunOutcome, VirtualUserRunRecord, VirtualUserRunStart, VirtualUserRunStore, } from './virtual-user-run-store.js';
24
24
  export { DISPOSITIONS, dispositionProfile, type DispositionProfile, type VirtualUserTuning, } from './virtual-user-dispositions.js';
25
- export { catalogueClassification, catalogueIndex, catalogueLookup, describeEntry, isReadOnly, reachableCatalogue, renderCatalogue, unreachableCatalogue, } from './virtual-user-catalogue.js';
26
- export { reachableAgents, type AgentReachability, type ReachableAgent, } from './virtual-user-agents.js';
27
- export { IntentStack, intentsForPersona, type IntentMove, type ScheduledTick, } from './virtual-user-intents.js';
28
- export { createRng, type VirtualUserRng } from './virtual-user-rng.js';
25
+ export { catalogueClassification, catalogueLookup, isReadOnly, reachableCatalogue, unreachableCatalogue, } from './virtual-user-catalogue.js';
26
+ export { type AgentReachability, type ReachableAgent, } from './virtual-user-agents.js';
27
+ export { IntentStack, intentsForPersona, } from './virtual-user-intents.js';
29
28
  export { deriveCatalogue, deriveIntents, type SchemaMap, } from './virtual-user-derive.js';
30
29
  export { personaVirtualUserTarget, type PersonaTargetOptions, } from './virtual-user-target.js';
@@ -1,10 +1,8 @@
1
1
  export { PRODUCTION_DISPOSITION } from './virtual-user.types.js';
2
- export { runVirtualUser, rememberIds, } from './run-virtual-user.js';
2
+ export { runVirtualUser, } from './run-virtual-user.js';
3
3
  export { personaScopes, prepareVirtualUserRun, } from './prepare-virtual-user-run.js';
4
4
  export { DISPOSITIONS, dispositionProfile, } from './virtual-user-dispositions.js';
5
- export { catalogueClassification, catalogueIndex, catalogueLookup, describeEntry, isReadOnly, reachableCatalogue, renderCatalogue, unreachableCatalogue, } from './virtual-user-catalogue.js';
6
- export { reachableAgents, } from './virtual-user-agents.js';
5
+ export { catalogueClassification, catalogueLookup, isReadOnly, reachableCatalogue, unreachableCatalogue, } from './virtual-user-catalogue.js';
7
6
  export { IntentStack, intentsForPersona, } from './virtual-user-intents.js';
8
- export { createRng } from './virtual-user-rng.js';
9
7
  export { deriveCatalogue, deriveIntents, } from './virtual-user-derive.js';
10
8
  export { personaVirtualUserTarget, } from './virtual-user-target.js';
@@ -97,7 +97,9 @@ export const rememberIds = (body, into, depth = 0) => {
97
97
  return into;
98
98
  }
99
99
  for (const [key, value] of Object.entries(body)) {
100
- if (typeof value === 'string' && MEMORABLE.test(key) && value.length < 200) {
100
+ if (typeof value === 'string' &&
101
+ MEMORABLE.test(key) &&
102
+ value.length < 200) {
101
103
  into[key] = value;
102
104
  }
103
105
  else if (typeof value === 'object') {
@@ -203,7 +205,8 @@ export const runVirtualUser = async (params) => {
203
205
  stoppedBy = 'budget-duration';
204
206
  break;
205
207
  }
206
- if (budget?.mutations !== undefined && tally.mutations >= budget.mutations) {
208
+ if (budget?.mutations !== undefined &&
209
+ tally.mutations >= budget.mutations) {
207
210
  stoppedBy = 'budget-mutations';
208
211
  break;
209
212
  }
@@ -243,10 +246,7 @@ export const runVirtualUser = async (params) => {
243
246
  if (tick.move === 'resume' || tick.move === 'suspend') {
244
247
  messages.push(msg('user', `You were pulled away and have come back to this. Things may have changed since.`));
245
248
  }
246
- // Whether this user trusts its notes this turn or goes and looks again.
247
- // `stale` almost always trusts them, a newcomer has none to trust, and an
248
- // auditor checks nearly everything — which is the whole difference between
249
- // those runs, expressed as one roll rather than as prose.
249
+ // knowledge: decisions/internals/a-virtual-user-decides-whether-to-trust-memory-once-per-turn.md
250
250
  if (Object.keys(memory).length && !rng.chance(profile.reReadRate)) {
251
251
  messages.push(msg('user', `Things you have noted down: ${Object.entries(memory)
252
252
  .slice(-25)
@@ -365,10 +365,7 @@ export const runVirtualUser = async (params) => {
365
365
  steps.push(record);
366
366
  continue;
367
367
  }
368
- // What may be called is decided once, when the catalogue is narrowed: a
369
- // read-only disposition is never offered a mutation and an approval-gated
370
- // endpoint is never offered at all, so `index` is the gate and there is no
371
- // second one here to drift out of step with it.
368
+ // knowledge: decisions/internals/the-virtual-user-catalogue-is-the-only-gate-on-what-may-be-called.md
372
369
  const mutating = !isReadOnly(entry);
373
370
  const args = readArgs(action.args);
374
371
  const repeats = profile.repeatRate > 0 && rng.chance(profile.repeatRate) ? 2 : 1;
@@ -379,7 +376,10 @@ export const runVirtualUser = async (params) => {
379
376
  response = await target.call(entry.name, args);
380
377
  }
381
378
  catch (error) {
382
- record.findingKinds = [...(record.findingKinds ?? []), 'transport-error'];
379
+ record.findingKinds = [
380
+ ...(record.findingKinds ?? []),
381
+ 'transport-error',
382
+ ];
383
383
  addFinding({
384
384
  kind: 'transport-error',
385
385
  detail: `${entry.name} threw: ${error.message}`,
@@ -18,24 +18,10 @@ const topLevelKeys = (schema) => {
18
18
  export const deriveCatalogue = (functions, schemas = {}) => {
19
19
  const entries = [];
20
20
  for (const [name, meta] of Object.entries(functions ?? {})) {
21
- // Scenario bodies and their steps are never network-callable — they are
22
- // held out of every deployed unit — so offering one would only ever waste
23
- // a turn on a 404.
24
- //
25
- // For a platform or addon step this is not an efficiency argument but the
26
- // oracle: a virtual user's findings are worth something only because it
27
- // cannot manufacture the outcomes it is meant to discover. One that could
28
- // invoke "Stripe's webhook arrives" would forge its own payment success,
29
- // and every finding downstream of that is worthless. Same class of argument
30
- // as `allowApprovalRequired` defaulting to false — and enforced here, at
31
- // derivation, rather than by convention.
21
+ // knowledge: decisions/security/a-virtual-user-is-never-offered-a-step-that-would-forge-its-own-oracle.md
32
22
  if (meta.scenario || meta.scenarioStep || meta.scenarioStepKind)
33
23
  continue;
34
- // `expose: true` is what puts a function on the rpc transport, and that is
35
- // what the shipped target calls over. Absent is not permissive: an unexposed
36
- // function 404s, and offering one spends a step to learn nothing about the
37
- // product. Measured on the e2e app, 34 of its 72 functions are in exactly
38
- // that state — nearly half a catalogue that cannot be called.
24
+ // knowledge: decisions/internals/only-exposed-functions-enter-a-virtual-user-catalogue.md
39
25
  if (meta.expose !== true)
40
26
  continue;
41
27
  const inputSchema = meta.inputSchemaName
@@ -76,10 +62,7 @@ const scenarioSteps = (meta) => {
76
62
  ? [{ stepFunc: step.stepFunc, phase: step.phase, actor: step.actor }]
77
63
  : []);
78
64
  }
79
- // Insertion order is the order the CLI wrote the nodes in, which is the order
80
- // the scenario declares them. Following `next` instead would buy an ordering
81
- // that is already true and cost a traversal that has to decide what a branch
82
- // means to a reader who will never take one.
65
+ // knowledge: decisions/internals/virtual-user-step-order-comes-from-insertion-order.md
83
66
  return Object.values(meta.nodes ?? {}).flatMap((node) => {
84
67
  const { rpcName, scenarioStepPhase, actor } = (node ?? {});
85
68
  if (!rpcName || !scenarioStepPhase)
@@ -115,11 +98,7 @@ export const deriveIntents = (workflows, functions = {}) => {
115
98
  continue;
116
99
  steps.push(composeStepProse({
117
100
  phase: step.phase,
118
- // The template goes in unfilled, braces and all. A reporter fills its
119
- // placeholders from a run that happened; there is no run yet, and
120
- // "invites {email}" tells a user to pick someone where the filled-in
121
- // form would have told it whom — which is the scenario's answer, not
122
- // the user's own.
101
+ // knowledge: decisions/internals/a-scenario-step-template-is-offered-unfilled.md
123
102
  description: template ?? description ?? '',
124
103
  actor: step.actor,
125
104
  }));
@@ -78,10 +78,7 @@ export const DISPOSITIONS = {
78
78
  reReadRate: 0.2,
79
79
  invertedOracle: true,
80
80
  },
81
- // The only disposition that is not testing anything. It is doing the job, so
82
- // it abandons rarely, runs cool, and is told to stop and say so rather than
83
- // guess — the failure mode here is a wrong action nobody asked for, not a
84
- // missed bug.
81
+ // knowledge: decisions/internals/the-worker-disposition-is-the-one-that-is-not-testing.md
85
82
  accountable: {
86
83
  ...REALISTIC,
87
84
  instructions: [
@@ -53,6 +53,19 @@ export interface WorkflowExpectServiceOptions extends WorkflowStepOptions {
53
53
  /** Assert the exact number of matching calls. Default: at least one. */
54
54
  times?: number;
55
55
  }
56
+ /** Options for workflow.expectScore() */
57
+ export interface WorkflowExpectScoreOptions extends WorkflowStepOptions {
58
+ /** Fail below this score. Defaults to 0.5 — a scorer that answered at all. */
59
+ atLeast?: number;
60
+ /** Fail above this score, for a scorer where high is the failure. */
61
+ atMost?: number;
62
+ /**
63
+ * The known-correct answer, which is what a reference-based judge grades
64
+ * against. Live traffic has no answer key, so a judge declared
65
+ * `requiresReference` is only ever reachable from a scenario.
66
+ */
67
+ reference?: string;
68
+ }
56
69
  /**
57
70
  * Type signature for workflow.do() RPC form - used by inspector
58
71
  */
@@ -80,10 +93,50 @@ export type WorkflowWireSleep = (stepName: string, duration: string) => Promise<
80
93
  * loops, like dynamic `do()` step names.
81
94
  */
82
95
  export type WorkflowWireSuspend = (reason: string) => Promise<void>;
96
+ /**
97
+ * Who is allowed to answer an approval gate, relative to the user who started
98
+ * the run.
99
+ *
100
+ * - `any` — anyone the approve entrypoint lets through. The gate is a pause for
101
+ * a decision, not an authorization boundary.
102
+ * - `owner` — only the user who started the run, so a gate can be used to
103
+ * confirm an action against the same person who requested it.
104
+ * - `not-initiator` — anyone *except* the user who started the run: four-eyes.
105
+ * A run with no recorded initiator has nobody to exclude, so this degrades to
106
+ * requiring a signed-in decider.
107
+ */
108
+ export type WorkflowApprovalApprovers = 'any' | 'owner' | 'not-initiator';
109
+ /**
110
+ * Who may answer an approval gate. Declared on the gate rather than the
111
+ * entrypoint because it is a property of the decision being made, and because
112
+ * one workflow can hold several gates with different requirements.
113
+ */
114
+ export interface WorkflowApprovalPolicy {
115
+ /**
116
+ * Defaults to `any`. See {@link WorkflowApprovalApprovers}.
117
+ */
118
+ approvers?: WorkflowApprovalApprovers;
119
+ /**
120
+ * Additionally require the decider's session to hold this scope, so
121
+ * "a second pair of eyes" can be narrowed to "a second pair of *senior*
122
+ * eyes". Combines with `approvers` — both must pass.
123
+ */
124
+ approverScope?: string;
125
+ }
126
+ /**
127
+ * The decider, reduced to the two facts a policy can be expressed in terms of.
128
+ * Recorded alongside the decision so the gate can be judged on replay — the
129
+ * session itself is long gone by then — and carried into the settled outcome so
130
+ * the answer keeps its provenance.
131
+ */
132
+ export interface ApprovalDecider {
133
+ userId?: string;
134
+ scopes?: string[];
135
+ }
83
136
  /**
84
137
  * Options for workflow.approval().
85
138
  */
86
- export interface WorkflowApprovalOptions<TSchema extends StandardSchemaV1 = StandardSchemaV1> {
139
+ export interface WorkflowApprovalOptions<TSchema extends StandardSchemaV1 = StandardSchemaV1> extends WorkflowApprovalPolicy {
87
140
  /**
88
141
  * Schema the decision payload is validated against. This is a VALUE, not a
89
142
  * type generic: the payload arrives from an untrusted caller over the approve
@@ -107,6 +160,14 @@ export interface WorkflowApprovalOptions<TSchema extends StandardSchemaV1 = Stan
107
160
  export type ApprovalOutcome<T> = {
108
161
  status: 'decided';
109
162
  data: T;
163
+ /**
164
+ * Who answered, and when. Run state holds the decision only while the gate
165
+ * is open — it is overwritten by the next write and cleared outright when a
166
+ * decision is refused — so the settled answer carries its own provenance
167
+ * into the step result, which is append-only.
168
+ */
169
+ decidedBy?: ApprovalDecider;
170
+ decidedAt?: string;
110
171
  } | {
111
172
  status: 'expired';
112
173
  };
@@ -288,7 +349,7 @@ export interface ReturnStepMeta {
288
349
  spread?: string[];
289
350
  }
290
351
  /**
291
- * Inline step metadata (legacy support)
352
+ * Inline step metadata
292
353
  */
293
354
  export interface InlineStepMeta {
294
355
  /** Inline form - local execution */
@@ -386,6 +447,10 @@ export interface ApprovalStepMeta {
386
447
  outputVar?: string;
387
448
  /** Expiry duration, when one was given */
388
449
  expiry?: string | number;
450
+ /** Who may answer the gate, when the default was not used */
451
+ approvers?: WorkflowApprovalApprovers;
452
+ /** Scope the decider must hold, when one was required */
453
+ approverScope?: string;
389
454
  }
390
455
  /**
391
456
  * Filter step metadata (array.filter)
@@ -473,7 +538,30 @@ export interface PikkuWorkflowWire {
473
538
  /** Suspend workflow until a human records a decision against this gate */
474
539
  approval: WorkflowWireApproval;
475
540
  }
476
- export interface PikkuScenarioWire extends PikkuWorkflowWire {
541
+ /**
542
+ * What a scenario has accumulated so far, shared by its body and its hooks.
543
+ *
544
+ * `Partial` because a scenario can fail at any step: `Out` describes what a
545
+ * *completed* run produces, and the context holds however much of it this run
546
+ * reached.
547
+ *
548
+ * The `0 extends 1 & Out` branch keeps `any` collapsing to `any` rather than to
549
+ * the union of the other two. `PikkuWire` and `PikkuFunctionConfig` both use
550
+ * this type as a generic *constraint*, and a constraint carrying a concrete
551
+ * context would reject every wire whose scenario output differs from it.
552
+ */
553
+ export type ScenarioContext<Out = unknown> = 0 extends 1 & Out ? any : Out extends object ? Partial<Out> : Record<string, unknown>;
554
+ export interface PikkuScenarioWire<Out = unknown> extends PikkuWorkflowWire {
555
+ /**
556
+ * Scratch the body writes and the `before`/`after` hooks read. A hook is a
557
+ * separate function and cannot see the body's locals, so this is how teardown
558
+ * learns the ids the body minted.
559
+ *
560
+ * Deliberately *not* a world: it is scoped to one run, and scenario steps
561
+ * cannot reach it. Steps stay pure functions of their declared inputs, so the
562
+ * ladder in the report remains a complete account of what a step saw.
563
+ */
564
+ context: ScenarioContext<Out>;
477
565
  /**
478
566
  * Durable polling step: invoke `rpcName` (as an actor when `options.actor`
479
567
  * is set) until `predicate` passes or `options.within` elapses.
@@ -483,6 +571,15 @@ export interface PikkuScenarioWire extends PikkuWorkflowWire {
483
571
  expectError: <TInput = any>(stepName: string, rpcName: string, data: TInput, options?: WorkflowExpectErrorOptions) => Promise<string>;
484
572
  /** Stub-assertion step: asserts `service.method` was called on the target server */
485
573
  expectService: (stepName: string, serviceMethod: string, options?: WorkflowExpectServiceOptions) => Promise<void>;
574
+ /**
575
+ * Grade-assertion step: runs one declared scorer against a finished agent run
576
+ * and asserts the score. Returns the grade, so a scenario can report the
577
+ * reason a judge gave rather than only that it fell short.
578
+ */
579
+ expectScore: (stepName: string, runId: string, scorerName: string, options?: WorkflowExpectScoreOptions) => Promise<{
580
+ score: number;
581
+ reason?: string;
582
+ }>;
486
583
  /**
487
584
  * Run a registered scenario step, as the setup the scenario starts from.
488
585
  *
@@ -13,8 +13,10 @@ export const resolveFeatureScenarios = (features, registrations) => {
13
13
  const scenarios = feature.scenarios ?? [];
14
14
  for (let index = 0; index < scenarios.length; index++) {
15
15
  const entry = scenarios[index];
16
- const paired = typeof entry === 'object' && entry !== null && 'scenario' in entry;
17
- const config = paired ? entry.scenario : entry;
16
+ const pairing = typeof entry === 'object' && entry !== null && 'scenario' in entry
17
+ ? entry
18
+ : undefined;
19
+ const config = pairing ? pairing.scenario : entry;
18
20
  const scenarioName = nameByConfig.get(config);
19
21
  if (!scenarioName) {
20
22
  unresolved.push({ featureId, index });
@@ -24,10 +26,11 @@ export const resolveFeatureScenarios = (features, registrations) => {
24
26
  featureId,
25
27
  featureName: feature.name ?? featureId,
26
28
  scenarioName,
27
- data: paired ? entry.data : undefined,
29
+ data: pairing?.data,
28
30
  tags: [
29
31
  ...new Set([
30
- ...(registrations.get(scenarioName)?.func?.tags ?? []),
32
+ ...(registrations.get(scenarioName)?.func
33
+ ?.tags ?? []),
31
34
  ...(feature.tags ?? []),
32
35
  ]),
33
36
  ],
@@ -1,4 +1,4 @@
1
- import { type PikkuWorkflowService } from '../pikku-workflow-service.js';
1
+ import type { PikkuWorkflowService } from '../pikku-workflow-service.js';
2
2
  import type { WorkflowRuntimeMeta, WorkflowRunWire } from '../workflow.types.js';
3
3
  export declare class ChildWorkflowStartedException extends Error {
4
4
  parentRunId: string;
@@ -9,7 +9,6 @@ export declare class ChildWorkflowStartedException extends Error {
9
9
  }
10
10
  export declare function continueGraph(workflowService: PikkuWorkflowService, runId: string, graphName: string, overrideMeta?: WorkflowRuntimeMeta): Promise<void>;
11
11
  export declare function executeGraphStep(workflowService: PikkuWorkflowService, rpcService: any, runId: string, stepId: string, nodeId: string, rpcName: string, data: any, graphName: string): Promise<any>;
12
- export declare function onGraphNodeComplete(workflowService: PikkuWorkflowService, runId: string, graphName: string): Promise<void>;
13
12
  export declare function runFromMeta(workflowService: PikkuWorkflowService, runId: string, meta: WorkflowRuntimeMeta, _rpcService: any): Promise<void>;
14
13
  export declare function runWorkflowGraph(workflowService: PikkuWorkflowService, graphName: string, triggerInput: any, rpcService?: any, inline?: boolean, startNode?: string, wire?: WorkflowRunWire, overrideMeta?: WorkflowRuntimeMeta): Promise<{
15
14
  runId: string;
@@ -1,4 +1,5 @@
1
- import { WorkflowAsyncException, WorkflowSuspendedException, DEFAULT_STEP_RETRIES, } from '../pikku-workflow-service.js';
1
+ import { WorkflowAsyncException, WorkflowSuspendedException, } from '../workflow-errors.js';
2
+ import { DEFAULT_STEP_RETRIES } from '../workflow-constants.js';
2
3
  import { pikkuState, getSingletonServices } from '../../../pikku-state.js';
3
4
  import { RPCNotFoundError } from '../../rpc/rpc-runner.js';
4
5
  export class ChildWorkflowStartedException extends Error {
@@ -491,9 +492,6 @@ export async function executeGraphStep(workflowService, rpcService, runId, stepI
491
492
  throw error;
492
493
  }
493
494
  }
494
- export async function onGraphNodeComplete(workflowService, runId, graphName) {
495
- await continueGraph(workflowService, runId, graphName);
496
- }
497
495
  export async function runFromMeta(workflowService, runId, meta, _rpcService) {
498
496
  await continueGraph(workflowService, runId, meta.name, meta);
499
497
  }
@@ -610,9 +608,12 @@ export async function runWorkflowGraph(workflowService, graphName, triggerInput,
610
608
  throw new Error(`Workflow graph '${graphName}' not found`);
611
609
  }
612
610
  const nodes = meta.nodes;
613
- const entryNodes = startNode
614
- ? [startNode]
615
- : (meta.entryNodeIds ?? []);
611
+ const declaredEntryNodes = meta.entryNodeIds ?? [];
612
+ // knowledge: decisions/security/a-graph-run-starts-at-an-entry-node-the-graph-declared.md
613
+ if (startNode && !declaredEntryNodes.includes(startNode)) {
614
+ throw new Error(`Workflow graph '${graphName}': '${startNode}' is not an entry node`);
615
+ }
616
+ const entryNodes = startNode ? [startNode] : declaredEntryNodes;
616
617
  validateGraphReferences(graphName, nodes, entryNodes);
617
618
  if (entryNodes.length === 0) {
618
619
  throw new Error(`Workflow graph '${graphName}': no entry nodes found in meta or startNode`);
@@ -5,10 +5,6 @@ export interface RefValue {
5
5
  }
6
6
  export declare const createRef: (nodeId: string, path?: string) => RefValue;
7
7
  export declare const isRef: (value: unknown) => value is RefValue;
8
- export interface InputRef {
9
- nodeId: string;
10
- path?: string;
11
- }
12
8
  export type RefFn<NodeIds extends string = string> = (nodeId: NodeIds, path?: string) => RefValue;
13
9
  export type NextConfig<NodeIds extends string = string> = NodeIds | NodeIds[] | Record<string, NodeIds | NodeIds[]>;
14
10
  export interface GraphNodeConfig<NodeIds extends string = string> {
@@ -1,19 +1,24 @@
1
- export { PikkuWorkflowService, WorkflowCancelledException, WorkflowSuspendedException, WorkflowDispatchException, WorkflowNotFoundError, WorkflowRunNotFoundError, WorkflowApprovalResolvedError, DEFAULT_STEP_RETRIES, } from './pikku-workflow-service.js';
2
- export type { RunLifecycleContext, WorkflowRunEngine, WorkflowRunExtension, } from './pikku-workflow-service.js';
1
+ export { PikkuWorkflowService } from './pikku-workflow-service.js';
2
+ export { WorkflowCancelledException, WorkflowSuspendedException, WorkflowDispatchException, WorkflowNotFoundError, WorkflowRunNotFoundError, WorkflowApprovalResolvedError, WorkflowStepFunctionMismatchError, } from './workflow-errors.js';
3
+ export { DEFAULT_STEP_RETRIES } from './workflow-constants.js';
4
+ export { assertWorkflowRunOwner, WorkflowRunForbiddenError, } from './workflow-run-ownership.js';
5
+ export { WorkflowApprovalForbiddenError } from './workflow-approval-policy.js';
6
+ export type { WorkflowRunEngine, WorkflowRunExtension, } from './workflow-run-engine.types.js';
3
7
  export { deriveInvocationId, uuidv5 } from './workflow-invocation-id.js';
8
+ export { isRef } from './graph/workflow-graph.types.js';
9
+ export type { RefValue } from './graph/workflow-graph.types.js';
4
10
  export { buildRunTimeline, reconstructStateAt, reconstructFinalState, } from './run-timeline.js';
5
- export type { RunTimeline, RunTimelineEvent, ReconstructedRunState, ReconstructedStep, RunPhase, } from './run-timeline.js';
11
+ export type { RunTimeline, ReconstructedRunState, } from './run-timeline.js';
6
12
  export { addWorkflow } from './dsl/workflow-runner.js';
7
13
  export { addFeature, resolveFeatureScenarios } from './feature.js';
8
14
  export { template, type TemplateString } from './graph/template.js';
9
15
  export { pikkuWorkflowGraph, type PikkuWorkflowGraphConfig, type PikkuWorkflowGraphResult, } from './graph/wire-workflow-graph.js';
10
- export { pikkuWorkflowWorkerFunc, pikkuWorkflowOrchestratorFunc, pikkuWorkflowSleeperFunc, } from './workflow-queue-workers.js';
11
16
  export type { WorkflowStepInput as WorkflowStepQueueInput, PikkuWorkflowOrchestratorInput, PikkuWorkflowSleeperInput, } from './workflow-queue-workers.js';
12
- export type { WorkflowService, WorkflowQueueOptions, WorkflowServiceConfig, WorkflowPlannedStep, WorkflowRunWire, WorkflowStatus, WorkflowVersionStatus, StepStatus, WorkflowRun, WorkflowRunStatus, StepState, WorkflowRunService, WorkflowRunMirror, CoreWorkflow, CoreFeature, CoreFeatureScenario, FeatureMeta, FeatureMetaEntry, FeaturesMeta, FeaturePlanEntry, PikkuWorkflow, ContextVariable, WorkflowContext, WorkflowsMeta, WorkflowRuntimeMeta, WorkflowsRuntimeMeta, } from './workflow.types.js';
13
- export type { WorkflowStepOptions, WorkflowWireDoRPC, WorkflowWireDoInline, WorkflowWireSleep, WorkflowWireSuspend, WorkflowWireApproval, WorkflowApprovalOptions, ApprovalOutcome, InputSource, OutputBinding, RpcStepMeta, SimpleCondition, Condition, BranchCase, BranchStepMeta, ParallelGroupStepMeta, FanoutStepMeta, ReturnStepMeta, InlineStepMeta, SleepStepMeta, CancelStepMeta, SuspendStepMeta, ApprovalStepMeta, SetStepMeta, SwitchCaseMeta, SwitchStepMeta, FilterStepMeta, ArrayPredicateStepMeta, ScenarioStepInvocation, ScenarioStepMeta, WorkflowStepMeta, WorkflowStepWire, PikkuWorkflowWire, PikkuScenarioWire, } from './workflow.types.js';
17
+ export type { WorkflowService, WorkflowQueueOptions, WorkflowServiceConfig, WorkflowPlannedStep, WorkflowRunWire, WorkflowStatus, WorkflowVersionStatus, StepStatus, WorkflowRun, WorkflowRunStatus, StepState, WorkflowRunService, WorkflowRunMirror, CoreWorkflow, CoreFeature, FeatureMeta, FeaturesMeta, FeaturePlanEntry, PikkuWorkflow, ContextVariable, WorkflowContext, WorkflowsMeta, WorkflowsRuntimeMeta, } from './workflow.types.js';
18
+ export type { WorkflowStepOptions, WorkflowWireDoRPC, WorkflowApprovalOptions, ApprovalOutcome, InputSource, OutputBinding, RpcStepMeta, Condition, BranchStepMeta, ParallelGroupStepMeta, FanoutStepMeta, ReturnStepMeta, InlineStepMeta, SleepStepMeta, CancelStepMeta, SuspendStepMeta, ApprovalStepMeta, SetStepMeta, SwitchCaseMeta, SwitchStepMeta, FilterStepMeta, ArrayPredicateStepMeta, ScenarioStepInvocation, ScenarioStepMeta, WorkflowStepMeta, WorkflowStepWire, PikkuWorkflowWire, PikkuScenarioWire, } from './workflow.types.js';
14
19
  export type { ScenarioStepPhase, ScenarioStepKind, ScenarioStepOptions, PikkuScenarioStepWire, ScenarioEnvironment, ScenarioSurface, ScenarioSurfaceResolution, } from './scenario-step.types.js';
15
20
  export { SCENARIO_SURFACES } from './scenario-step.types.js';
16
- export { resolveScenarioSurfaces, witnessesAgree } from './scenario-surface.js';
21
+ export { resolveScenarioSurfaces } from './scenario-surface.js';
17
22
  export { requireActor, requireScenarioEnv } from './scenario-step-guards.js';
18
23
  export { pollUntil, type PollOptions } from './scenario-poll.js';
19
24
  export { createCookieJar } from './scenario-cookie-jar.js';
@@ -1,14 +1,18 @@
1
- export { PikkuWorkflowService, WorkflowCancelledException, WorkflowSuspendedException, WorkflowDispatchException, WorkflowNotFoundError, WorkflowRunNotFoundError, WorkflowApprovalResolvedError, DEFAULT_STEP_RETRIES, } from './pikku-workflow-service.js';
1
+ export { PikkuWorkflowService } from './pikku-workflow-service.js';
2
+ export { WorkflowCancelledException, WorkflowSuspendedException, WorkflowDispatchException, WorkflowNotFoundError, WorkflowRunNotFoundError, WorkflowApprovalResolvedError, WorkflowStepFunctionMismatchError, } from './workflow-errors.js';
3
+ export { DEFAULT_STEP_RETRIES } from './workflow-constants.js';
4
+ export { assertWorkflowRunOwner, WorkflowRunForbiddenError, } from './workflow-run-ownership.js';
5
+ export { WorkflowApprovalForbiddenError } from './workflow-approval-policy.js';
2
6
  export { deriveInvocationId, uuidv5 } from './workflow-invocation-id.js';
7
+ export { isRef } from './graph/workflow-graph.types.js';
3
8
  export { buildRunTimeline, reconstructStateAt, reconstructFinalState, } from './run-timeline.js';
4
9
  export { addWorkflow } from './dsl/workflow-runner.js';
5
10
  export { addFeature, resolveFeatureScenarios } from './feature.js';
6
11
  export { template } from './graph/template.js';
7
12
  export { pikkuWorkflowGraph, } from './graph/wire-workflow-graph.js';
8
- export { pikkuWorkflowWorkerFunc, pikkuWorkflowOrchestratorFunc, pikkuWorkflowSleeperFunc, } from './workflow-queue-workers.js';
9
13
  export { SCENARIO_SURFACES } from './scenario-step.types.js';
10
14
  // Which of a step's bindings run: one for an action, every witness for a `then`
11
- export { resolveScenarioSurfaces, witnessesAgree } from './scenario-surface.js';
15
+ export { resolveScenarioSurfaces } from './scenario-surface.js';
12
16
  export { requireActor, requireScenarioEnv } from './scenario-step-guards.js';
13
17
  export { pollUntil } from './scenario-poll.js';
14
18
  export { createCookieJar } from './scenario-cookie-jar.js';
@@ -1,7 +1,7 @@
1
1
  import { PikkuError } from '../../errors/error-handler.js';
2
2
  import { InMemoryWorkflowService } from '../../services/in-memory-workflow-service.js';
3
- import { type RunLifecycleContext, type WorkflowRunEngine, type WorkflowRunExtension } from './pikku-workflow-service.js';
4
- import type { PikkuWire } from '../../types/core.types.js';
3
+ import type { RunLifecycleContext, WorkflowRunEngine, WorkflowRunExtension } from './workflow-run-engine.types.js';
4
+ import type { PikkuRawWire } from '../../types/core.types.js';
5
5
  import type { ScenarioPersonas } from '../../services/personas-service.js';
6
6
  import type { ScenarioBrowserProvider, ScenarioEnvironment, ScenarioSurface } from './scenario-step.types.js';
7
7
  import type { PikkuWorkflowWire, WorkflowQueueOptions } from './workflow.types.js';
@@ -126,6 +126,7 @@ export declare class ScenarioNoWitness extends PikkuError {
126
126
  export declare class PikkuScenarioService implements WorkflowRunExtension {
127
127
  private readonly engine;
128
128
  private runActors;
129
+ private runContexts;
129
130
  private scenarioBrowserProvider?;
130
131
  private scenarioEnvironment?;
131
132
  private runSurface;
@@ -154,7 +155,20 @@ export declare class PikkuScenarioService implements WorkflowRunExtension {
154
155
  actors?: ScenarioPersonas;
155
156
  }): Promise<void>;
156
157
  detachRunContext(runId: string): void;
157
- decorateRunWire(wire: PikkuWire, context: {
158
+ /**
159
+ * What the run has accumulated.
160
+ *
161
+ * Undefined only for a run whose wire was never decorated — decoration calls
162
+ * `contextForRun` unconditionally, so a plain workflow that has reached a
163
+ * step holds an empty context rather than none.
164
+ */
165
+ getRunContext(runId: string): Record<string, unknown> | undefined;
166
+ /**
167
+ * The run's context, created on demand because the wire is also built on
168
+ * paths that skip `attachRunContext`.
169
+ */
170
+ private contextForRun;
171
+ decorateRunWire(wire: PikkuRawWire, context: {
158
172
  runId: string;
159
173
  workflowMeta: any;
160
174
  workflowWire: PikkuWorkflowWire;
@@ -5,7 +5,7 @@ import { closeWireServices } from '../../utils.js';
5
5
  import { PikkuError, addError } from '../../errors/error-handler.js';
6
6
  import { InMemoryWorkflowService } from '../../services/in-memory-workflow-service.js';
7
7
  import { runScheduledTask } from '../scheduler/scheduler-runner.js';
8
- import { WorkflowStepNameNotString, } from './pikku-workflow-service.js';
8
+ import { WorkflowStepNameNotString } from './workflow-errors.js';
9
9
  import { resolveScenarioSurfaces, witnessesAgree } from './scenario-surface.js';
10
10
  /**
11
11
  * A workflow service with the scenario capability attached — the two lines
@@ -176,6 +176,10 @@ export class PikkuScenarioService {
176
176
  // Scenario actors per run: live authenticated clients (cookie jars) are
177
177
  // process-local by nature, so they ride this map, never the persisted wire.
178
178
  runActors = new Map();
179
+ // What each run has accumulated so far — process-local like the actors above,
180
+ // so the body and its hooks share one object rather than reading it back off
181
+ // the persisted wire.
182
+ runContexts = new Map();
179
183
  scenarioBrowserProvider;
180
184
  scenarioEnvironment;
181
185
  runSurface = 'default';
@@ -222,9 +226,35 @@ export class PikkuScenarioService {
222
226
  if (actors) {
223
227
  this.runActors.set(runId, actors);
224
228
  }
229
+ if (workflowMeta.source === 'scenario') {
230
+ this.runContexts.set(runId, {});
231
+ }
225
232
  }
226
233
  detachRunContext(runId) {
227
234
  this.runActors.delete(runId);
235
+ this.runContexts.delete(runId);
236
+ }
237
+ /**
238
+ * What the run has accumulated.
239
+ *
240
+ * Undefined only for a run whose wire was never decorated — decoration calls
241
+ * `contextForRun` unconditionally, so a plain workflow that has reached a
242
+ * step holds an empty context rather than none.
243
+ */
244
+ getRunContext(runId) {
245
+ return this.runContexts.get(runId);
246
+ }
247
+ /**
248
+ * The run's context, created on demand because the wire is also built on
249
+ * paths that skip `attachRunContext`.
250
+ */
251
+ contextForRun(runId) {
252
+ let runContext = this.runContexts.get(runId);
253
+ if (!runContext) {
254
+ runContext = {};
255
+ this.runContexts.set(runId, runContext);
256
+ }
257
+ return runContext;
228
258
  }
229
259
  decorateRunWire(wire, context) {
230
260
  wire.scenario =
@@ -367,6 +397,7 @@ export class PikkuScenarioService {
367
397
  rpcService,
368
398
  });
369
399
  Object.assign(workflowWire, {
400
+ context: this.contextForRun(runId),
370
401
  // Durable polling step: invoke an RPC (as an actor when options.as is
371
402
  // set) until the predicate passes or `within` elapses. The whole poll is
372
403
  // ONE recorded step, so replay returns the cached outcome.
@@ -451,12 +482,36 @@ export class PikkuScenarioService {
451
482
  }
452
483
  }, options);
453
484
  },
454
- // Scenario steps: a named `pikkuScenarioStep` run as one durable step.
455
- // `given`/`when` are sugar for each other, differing only in the
456
- // prose a reporter renders. `then` is not: the phase is what decides
457
- // whether the step's bindings are alternatives or witnesses, so the same
458
- // step function called two ways runs differently. See
459
- // {@link resolveScenarioSurfaces}.
485
+ expectScore: async (stepName, agentRunId, scorerName, options) => {
486
+ this.engine.verifyStepName(stepName);
487
+ return await this.engine.inlineStep(runId, stepName, async () => {
488
+ const rpcName = 'pikkuScenarioGradeRun';
489
+ const data = {
490
+ runId: agentRunId,
491
+ scorer: scorerName,
492
+ ...(options?.reference !== undefined
493
+ ? { reference: options.reference }
494
+ : {}),
495
+ };
496
+ const grade = options?.actor
497
+ ? await options.actor.invoke(rpcName, data)
498
+ : await rpcService.rpcWithWire(rpcName, data, {});
499
+ // A scorer that graded is a scorer that answered, so an unstated
500
+ // bound still fails a zero rather than passing anything at all.
501
+ const atLeast = options?.atLeast ?? 0.5;
502
+ const failed = grade.score < atLeast ||
503
+ (options?.atMost !== undefined && grade.score > options.atMost);
504
+ if (failed) {
505
+ const bound = options?.atMost !== undefined
506
+ ? `between ${atLeast} and ${options.atMost}`
507
+ : `at least ${atLeast}`;
508
+ throw new Error(`[workflow] expectScore '${stepName}' expected '${scorerName}' to grade run ${agentRunId} ${bound}, got ${grade.score}` +
509
+ `${grade.reason ? `: ${grade.reason}` : ''}`);
510
+ }
511
+ return grade;
512
+ }, options);
513
+ },
514
+ // knowledge: decisions/internals/scenario-given-and-when-are-sugar-but-then-is-not.md
460
515
  given: (stepName, stepFunc, data, options) => this.scenarioStep('given', scenarioStepContext(), stepName, stepFunc, data, options),
461
516
  when: (stepName, stepFunc, data, options) => this.scenarioStep('when', scenarioStepContext(), stepName, stepFunc, data, options),
462
517
  then: (stepName, stepFunc, data, options) => this.scenarioStep('then', scenarioStepContext(), stepName, stepFunc, data, options),
@@ -466,7 +521,7 @@ export class PikkuScenarioService {
466
521
  });
467
522
  }
468
523
  async scenarioStep(phase, context, stepName, stepFunc, data, options) {
469
- const { runId, workflowName, addonNamespace, workflowWire, rpcService } = context;
524
+ const { runId, workflowName, addonNamespace, workflowWire } = context;
470
525
  // Also the guard for `then` being a wire member: an accidental
471
526
  // `await scenario` calls it with a resolve function, which lands here as a
472
527
  // loud, named error instead of a silent hang.
@@ -486,11 +541,10 @@ export class PikkuScenarioService {
486
541
  const resolution = resolveScenarioSurfaces(phase, declared, this.runSurface);
487
542
  return await this.engine.inlineStep(runId, stepName, async () => {
488
543
  const runOnSurface = async (surface) => {
544
+ // No `rpc` yet — runPikkuFunc attaches it lazily for the invocation.
489
545
  const wire = {
490
546
  workflow: workflowWire,
491
547
  scenario: workflowWire,
492
- rpc: rpcService?.wire?.rpc,
493
- session: rpcService?.wire?.session,
494
548
  pikkuUserId: workflowWire.pikkuUserId,
495
549
  actors: this.runActors.get(runId),
496
550
  scenarioStep: {