@pikku/cli 0.12.120 → 0.12.121

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 (225) hide show
  1. package/CHANGELOG.md +192 -0
  2. package/console-app/assets/{abnfDiagram-N423BO3Z-Df3BCQUl.js → abnfDiagram-N423BO3Z-Dfq94a7u.js} +1 -1
  3. package/console-app/assets/architecture-TIHT7OUA-CA-Di-O3.js +1 -0
  4. package/console-app/assets/{architectureDiagram-T3A2C74G-BlV2VA0B.js → architectureDiagram-T3A2C74G-C9MjP0IU.js} +1 -1
  5. package/console-app/assets/{blockDiagram-VBNYF7ZC-BZ7GFw7a.js → blockDiagram-VBNYF7ZC-CGgHSlOO.js} +1 -1
  6. package/console-app/assets/{c4Diagram-5PPSVZJV-uXLYnA16.js → c4Diagram-5PPSVZJV-3kzckivq.js} +1 -1
  7. package/console-app/assets/channel-UOxM22B3.js +1 -0
  8. package/console-app/assets/{chunk-2GRJ4B5K-CU6Szqvt.js → chunk-2GRJ4B5K-UZ1q29J_.js} +1 -1
  9. package/console-app/assets/{chunk-4I5QYGJK-Bf8ARw9p.js → chunk-4I5QYGJK-bq4iSS6S.js} +1 -1
  10. package/console-app/assets/{chunk-5RXB4S5H-CuRjyaQR.js → chunk-5RXB4S5H-BCeU8nXq.js} +1 -1
  11. package/console-app/assets/{chunk-6Q2QTUOP-DMFott3W.js → chunk-6Q2QTUOP-DpsTVhit.js} +1 -1
  12. package/console-app/assets/{chunk-7Z6QIM7H-YJMK1J3W.js → chunk-7Z6QIM7H-I6XKkv2A.js} +1 -1
  13. package/console-app/assets/{chunk-GF5L2VYU-Co4slpk_.js → chunk-GF5L2VYU-aYODFCZ6.js} +1 -1
  14. package/console-app/assets/{chunk-I66GZJ75-CE6Jk-ML.js → chunk-I66GZJ75-CWdPrY4a.js} +1 -1
  15. package/console-app/assets/{chunk-JQJVKLGR-BjkITZJq.js → chunk-JQJVKLGR-D5bHP4bt.js} +1 -1
  16. package/console-app/assets/{chunk-KBJHAD2P-DdHZUbE2.js → chunk-KBJHAD2P-B-Spac9o.js} +1 -1
  17. package/console-app/assets/{chunk-NSK5VX7P-BB0gp55h.js → chunk-NSK5VX7P-0jZVLBhi.js} +1 -1
  18. package/console-app/assets/{chunk-QR6OTTB3-BhAzJcX6.js → chunk-QR6OTTB3-BurXFOtV.js} +1 -1
  19. package/console-app/assets/{chunk-RYQCIY6F-BaRFYCcu.js → chunk-RYQCIY6F-C4zacxhH.js} +1 -1
  20. package/console-app/assets/{chunk-UBXNYLIW-DN_ayb-W.js → chunk-UBXNYLIW-7RthekQ-.js} +1 -1
  21. package/console-app/assets/{chunk-W5SLKNZC-DQ0C1K0m.js → chunk-W5SLKNZC-rq3UcMTy.js} +1 -1
  22. package/console-app/assets/{chunk-WRU74C26-Dej4aDDK.js → chunk-WRU74C26-DUX-oZjQ.js} +1 -1
  23. package/console-app/assets/chunk-XXDRQBXY-gIL1rRSY.js +1 -0
  24. package/console-app/assets/classDiagram-JCYQIIEL-BwV1w5At.js +1 -0
  25. package/console-app/assets/classDiagram-v2-OCEON4UE-BwV1w5At.js +1 -0
  26. package/console-app/assets/{cose-bilkent-JH36ORCC-CaCBDIGn.js → cose-bilkent-JH36ORCC-DZn35HFs.js} +1 -1
  27. package/console-app/assets/{cynefin-VYW2F7L2-DvNj3Ktd.js → cynefin-VYW2F7L2-mZlOMKNi.js} +1 -1
  28. package/console-app/assets/{cynefinDiagram-MW4NZA55-2M1I4KVc.js → cynefinDiagram-MW4NZA55-uUAfUoag.js} +1 -1
  29. package/console-app/assets/{dagre-BX6AcUT6.js → dagre-DfTmIhGN.js} +1 -1
  30. package/console-app/assets/{dagre-VZM6K2ZE-CeiMExQ0.js → dagre-VZM6K2ZE-7oTddCss.js} +1 -1
  31. package/console-app/assets/{diagram-7IWD3JNH-DFb1QCit.js → diagram-7IWD3JNH-D6TR4TlN.js} +1 -1
  32. package/console-app/assets/{diagram-B4RE2ZJO-CcmxcWm0.js → diagram-B4RE2ZJO-BZwUFLzC.js} +1 -1
  33. package/console-app/assets/{diagram-LBJQPF4R-DpxWXRoq.js → diagram-LBJQPF4R-B4Ug2xRo.js} +1 -1
  34. package/console-app/assets/{diagram-Q27KOJAE-Cu_MNubg.js → diagram-Q27KOJAE-Fpr_Cahs.js} +1 -1
  35. package/console-app/assets/{diagram-UB23O5K3-B-R4JZVn.js → diagram-UB23O5K3-d0X7PA-5.js} +1 -1
  36. package/console-app/assets/{ebnfDiagram-BXEA7PRR-C26G-5vz.js → ebnfDiagram-BXEA7PRR-DExP954c.js} +1 -1
  37. package/console-app/assets/{erDiagram-JOGREHBK-DJaz9wds.js → erDiagram-JOGREHBK-WdnXmG8f.js} +1 -1
  38. package/console-app/assets/eventmodeling-45OFAUF4-DXnDrAdV.js +1 -0
  39. package/console-app/assets/flowDiagram-UKHOOZJN-UixAvy5Z.js +1 -0
  40. package/console-app/assets/{ganttDiagram-PKOTCBZU-BmHvLxmw.js → ganttDiagram-PKOTCBZU-ADJHCyvl.js} +1 -1
  41. package/console-app/assets/{gitGraph-TEB2WS4Q-gjOfZX1j.js → gitGraph-TEB2WS4Q-BPcXt1vK.js} +1 -1
  42. package/console-app/assets/{gitGraphDiagram-DS77QQ5N-Cn2aQfgw.js → gitGraphDiagram-DS77QQ5N-DFhenfUX.js} +1 -1
  43. package/console-app/assets/{index-Cx39I_BR.js → index-BwP6EcJr.js} +3 -3
  44. package/console-app/assets/{info-DKCQHKI2-Dxvvr3Du.js → info-DKCQHKI2-BsE7egna.js} +1 -1
  45. package/console-app/assets/{infoDiagram-6WML65LV-CU2DYprj.js → infoDiagram-6WML65LV-BmVqXMw0.js} +1 -1
  46. package/console-app/assets/{ishikawaDiagram-WSZJBQD7-CEIhGAXe.js → ishikawaDiagram-WSZJBQD7-CdFTPIY5.js} +1 -1
  47. package/console-app/assets/{journeyDiagram-NVQOT4AX-vDLEtm6u.js → journeyDiagram-NVQOT4AX-DC8CKdJk.js} +1 -1
  48. package/console-app/assets/{kanban-definition-27J2QSJJ-rfZdVBY0.js → kanban-definition-27J2QSJJ-Bo7kRcLN.js} +1 -1
  49. package/console-app/assets/{line-CK7XHoXo.js → line-5nUqh22u.js} +1 -1
  50. package/console-app/assets/{linear-DeA9Np_F.js → linear-82KoPtTH.js} +1 -1
  51. package/console-app/assets/{mermaid-parser.core-Bkwm3osM.js → mermaid-parser.core-R240ylPw.js} +3 -3
  52. package/console-app/assets/{mermaid.core-DvVwOMEV.js → mermaid.core-BZ3jSM6D.js} +4 -4
  53. package/console-app/assets/{mindmap-definition-FAOFIHXS-DYMAQz7X.js → mindmap-definition-FAOFIHXS-x1K54VeO.js} +1 -1
  54. package/console-app/assets/{packet-7NZHBO7P-CtoDJZ2L.js → packet-7NZHBO7P-CE2Lun0t.js} +1 -1
  55. package/console-app/assets/{pegDiagram-VL7TDLO6-BRxy7Znf.js → pegDiagram-VL7TDLO6-CpGZYdUz.js} +1 -1
  56. package/console-app/assets/{pie-RZYD4A2V-NEqmtqwU.js → pie-RZYD4A2V-Hf5MOyY8.js} +1 -1
  57. package/console-app/assets/{pieDiagram-7S7Q4E2Y-eekm9APm.js → pieDiagram-7S7Q4E2Y-0J5H7r7g.js} +1 -1
  58. package/console-app/assets/{quadrantDiagram-CIZ2JOQS-BSew_APH.js → quadrantDiagram-CIZ2JOQS-DWtYi8O-.js} +1 -1
  59. package/console-app/assets/{radar-I7S5WNFK-BgvnQqhv.js → radar-I7S5WNFK-BgtvrX4w.js} +1 -1
  60. package/console-app/assets/{railroad-3IZDKUUU-DqG1o6ll.js → railroad-3IZDKUUU-CeR0NhLL.js} +1 -1
  61. package/console-app/assets/railroad-abnf-AHOZXSZD-BEVU16-y.js +1 -0
  62. package/console-app/assets/railroad-ebnf-EBAXGLYW-WT4rrGXf.js +1 -0
  63. package/console-app/assets/railroad-peg-LSFZ7HO6-BnObCMjn.js +1 -0
  64. package/console-app/assets/{railroadDiagram-AXF67PYL-Rb5V_7ZT.js → railroadDiagram-AXF67PYL-CUIn_gPf.js} +1 -1
  65. package/console-app/assets/{requirementDiagram-LRYGKXZP-BONrWtQh.js → requirementDiagram-LRYGKXZP-Ds9Aqcts.js} +1 -1
  66. package/console-app/assets/{sankeyDiagram-W5VNT64P-CUPWhFm-.js → sankeyDiagram-W5VNT64P-CKhuya4d.js} +1 -1
  67. package/console-app/assets/{sequenceDiagram-SI44F4Z6-DXwluQXT.js → sequenceDiagram-SI44F4Z6-DKgOmdzz.js} +1 -1
  68. package/console-app/assets/{src-CyNQB37R.js → src-DwHVY--Z.js} +1 -1
  69. package/console-app/assets/{stateDiagram-OKZ733FA-DXopCa6U.js → stateDiagram-OKZ733FA-CPIfaZie.js} +1 -1
  70. package/console-app/assets/stateDiagram-v2-UEYNNEHI-DHcCmJ1C.js +1 -0
  71. package/console-app/assets/{swimlanes-SLNWSIFB-C_oImvSp.js → swimlanes-SLNWSIFB-8q3lK4RS.js} +1 -1
  72. package/console-app/assets/swimlanesDiagram-ULZ7WXOC-B1uT0NiL.js +8 -0
  73. package/console-app/assets/{timeline-definition-Z64GVDOM-BEx4FmJK.js → timeline-definition-Z64GVDOM-CHHi2Rpq.js} +1 -1
  74. package/console-app/assets/{treeView-QDETBFTQ-DIQFLe_6.js → treeView-QDETBFTQ-DrLHlsII.js} +1 -1
  75. package/console-app/assets/{treemap-6X3UGDF4-Dz8_vGjV.js → treemap-6X3UGDF4-C3saYKuK.js} +1 -1
  76. package/console-app/assets/{vennDiagram-T6HMQDX7-_sMSuxYe.js → vennDiagram-T6HMQDX7-CdyzNiAu.js} +1 -1
  77. package/console-app/assets/{wardley-OPB4EBWU-BkFD9f0r.js → wardley-OPB4EBWU-BSrymyse.js} +1 -1
  78. package/console-app/assets/{wardleyDiagram-T6FBY63Y-DhqBDV2O.js → wardleyDiagram-T6FBY63Y-CCW6rhEo.js} +1 -1
  79. package/console-app/assets/{xychartDiagram-ELKLHX3M-D8Jav5f0.js → xychartDiagram-ELKLHX3M-BQvPaxzg.js} +1 -1
  80. package/console-app/index.html +1 -1
  81. package/dist/.pikku/addon/index.d.ts +1 -1
  82. package/dist/.pikku/addon/index.js +1 -1
  83. package/dist/.pikku/addon/pikku-addon-types.gen.d.ts +1 -1
  84. package/dist/.pikku/addon/pikku-addon-types.gen.js +1 -1
  85. package/dist/.pikku/agent/index.d.ts +1 -1
  86. package/dist/.pikku/agent/index.js +1 -1
  87. package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
  88. package/dist/.pikku/agent/pikku-model-aliases.gen.js +1 -1
  89. package/dist/.pikku/auth/index.d.ts +1 -1
  90. package/dist/.pikku/auth/index.js +1 -1
  91. package/dist/.pikku/auth/pikku-auth-types.gen.d.ts +1 -1
  92. package/dist/.pikku/auth/pikku-auth-types.gen.js +1 -1
  93. package/dist/.pikku/channel/index.d.ts +1 -1
  94. package/dist/.pikku/channel/index.js +1 -1
  95. package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
  96. package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
  97. package/dist/.pikku/cli/index.d.ts +1 -1
  98. package/dist/.pikku/cli/index.js +1 -1
  99. package/dist/.pikku/cli/pikku-cli-channel.js +1 -1
  100. package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +1 -1
  101. package/dist/.pikku/cli/pikku-cli-client.gen.js +1 -1
  102. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.d.ts +1 -1
  103. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.js +1 -1
  104. package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
  105. package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
  106. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
  107. package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
  108. package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
  109. package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
  110. package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
  111. package/dist/.pikku/error/index.d.ts +1 -1
  112. package/dist/.pikku/error/index.js +1 -1
  113. package/dist/.pikku/error/pikku-error-types.gen.d.ts +1 -1
  114. package/dist/.pikku/error/pikku-error-types.gen.js +1 -1
  115. package/dist/.pikku/function/index.d.ts +1 -1
  116. package/dist/.pikku/function/index.js +1 -1
  117. package/dist/.pikku/function/pikku-function-types.gen.d.ts +1 -1
  118. package/dist/.pikku/function/pikku-function-types.gen.js +1 -1
  119. package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
  120. package/dist/.pikku/function/pikku-functions-meta.gen.json +1 -1
  121. package/dist/.pikku/function/pikku-functions.gen.js +1 -1
  122. package/dist/.pikku/gateway/index.d.ts +1 -1
  123. package/dist/.pikku/gateway/index.js +1 -1
  124. package/dist/.pikku/gateway/pikku-gateway-types.gen.d.ts +1 -1
  125. package/dist/.pikku/gateway/pikku-gateway-types.gen.js +1 -1
  126. package/dist/.pikku/http/index.d.ts +1 -1
  127. package/dist/.pikku/http/index.js +1 -1
  128. package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
  129. package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
  130. package/dist/.pikku/mcp/index.d.ts +1 -1
  131. package/dist/.pikku/mcp/index.js +1 -1
  132. package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
  133. package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
  134. package/dist/.pikku/middleware/index.d.ts +1 -1
  135. package/dist/.pikku/middleware/index.js +1 -1
  136. package/dist/.pikku/middleware/pikku-middleware-types.gen.d.ts +1 -1
  137. package/dist/.pikku/middleware/pikku-middleware-types.gen.js +1 -1
  138. package/dist/.pikku/pikku-bootstrap-scenarios.gen.d.ts +1 -1
  139. package/dist/.pikku/pikku-bootstrap-scenarios.gen.js +1 -1
  140. package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
  141. package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
  142. package/dist/.pikku/pikku-services.gen.d.ts +1 -1
  143. package/dist/.pikku/queue/index.d.ts +1 -1
  144. package/dist/.pikku/queue/index.js +1 -1
  145. package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
  146. package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
  147. package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +1 -1
  148. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
  149. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
  150. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
  151. package/dist/.pikku/scenarios/index.d.ts +1 -1
  152. package/dist/.pikku/scenarios/index.js +1 -1
  153. package/dist/.pikku/scenarios/pikku-personas.gen.js +1 -1
  154. package/dist/.pikku/scenarios/pikku-scenario-functions-meta.gen.js +1 -1
  155. package/dist/.pikku/scenarios/pikku-scenario-functions.gen.d.ts +1 -1
  156. package/dist/.pikku/scenarios/pikku-scenario-types.gen.d.ts +1 -1
  157. package/dist/.pikku/scenarios/pikku-scenario-wirings-meta.gen.js +1 -1
  158. package/dist/.pikku/scenarios/pikku-scenario-wirings.gen.d.ts +1 -1
  159. package/dist/.pikku/scenarios/schemas/register.gen.d.ts +1 -1
  160. package/dist/.pikku/scenarios/schemas/register.gen.js +1 -1
  161. package/dist/.pikku/scheduler/index.d.ts +1 -1
  162. package/dist/.pikku/scheduler/index.js +1 -1
  163. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
  164. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
  165. package/dist/.pikku/schemas/register.gen.js +1 -1
  166. package/dist/.pikku/scopes/index.d.ts +1 -1
  167. package/dist/.pikku/scopes/index.js +1 -1
  168. package/dist/.pikku/scopes/pikku-personas.gen.js +1 -1
  169. package/dist/.pikku/scopes/pikku-roles.gen.d.ts +1 -1
  170. package/dist/.pikku/scopes/pikku-scope-types.gen.d.ts +1 -1
  171. package/dist/.pikku/scopes/pikku-scope-types.gen.js +1 -1
  172. package/dist/.pikku/scopes/pikku-scopes.gen.d.ts +1 -1
  173. package/dist/.pikku/secrets/index.d.ts +1 -1
  174. package/dist/.pikku/secrets/index.js +1 -1
  175. package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
  176. package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
  177. package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
  178. package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
  179. package/dist/.pikku/services/pikku-meta-service.gen.d.ts +1 -1
  180. package/dist/.pikku/services/pikku-meta-service.gen.js +1 -1
  181. package/dist/.pikku/setup/index.d.ts +1 -1
  182. package/dist/.pikku/setup/index.js +1 -1
  183. package/dist/.pikku/setup/pikku-setup-types.gen.d.ts +1 -1
  184. package/dist/.pikku/setup/pikku-setup-types.gen.js +1 -1
  185. package/dist/.pikku/trigger/index.d.ts +1 -1
  186. package/dist/.pikku/trigger/index.js +1 -1
  187. package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
  188. package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
  189. package/dist/.pikku/variables/index.d.ts +1 -1
  190. package/dist/.pikku/variables/index.js +1 -1
  191. package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
  192. package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
  193. package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
  194. package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
  195. package/dist/.pikku/workflow/index.d.ts +1 -1
  196. package/dist/.pikku/workflow/index.js +1 -1
  197. package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -1
  198. package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
  199. package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
  200. package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
  201. package/dist/bin/pikku-bin.mjs +2 -2
  202. package/dist/src/fabric/functions/validate.function.js +22 -2
  203. package/dist/src/functions/wirings/agent/serialize-public-agent.js +7 -19
  204. package/dist/src/functions/wirings/console/serialize-console-functions.js +3 -12
  205. package/dist/src/functions/wirings/emails/serialize-emails.d.ts +10 -0
  206. package/dist/src/functions/wirings/emails/serialize-emails.js +30 -173
  207. package/dist/src/functions/wirings/virtual-user/serialize-virtual-user-functions.js +158 -438
  208. package/dist/src/functions/wirings/workflow/pikku-command-workflow.js +7 -7
  209. package/dist/src/functions/wirings/workflow/serialize-workflow-routes.js +11 -148
  210. package/dist/src/utils/pikku-cli-config.d.ts +18 -0
  211. package/dist/src/utils/pikku-cli-config.js +39 -0
  212. package/package.json +6 -6
  213. package/surface.json +1 -1
  214. package/console-app/assets/architecture-TIHT7OUA-i2kvkWiG.js +0 -1
  215. package/console-app/assets/channel-BjjIBjrV.js +0 -1
  216. package/console-app/assets/chunk-XXDRQBXY-COHoQA_g.js +0 -1
  217. package/console-app/assets/classDiagram-JCYQIIEL-BkQzFYJq.js +0 -1
  218. package/console-app/assets/classDiagram-v2-OCEON4UE-BkQzFYJq.js +0 -1
  219. package/console-app/assets/eventmodeling-45OFAUF4-Cge15uFd.js +0 -1
  220. package/console-app/assets/flowDiagram-UKHOOZJN-DN8OJeAP.js +0 -1
  221. package/console-app/assets/railroad-abnf-AHOZXSZD-DQHwBMhW.js +0 -1
  222. package/console-app/assets/railroad-ebnf-EBAXGLYW-cu_kDg2r.js +0 -1
  223. package/console-app/assets/railroad-peg-LSFZ7HO6-CJh8d3dM.js +0 -1
  224. package/console-app/assets/stateDiagram-v2-UEYNNEHI-BCi_SQzZ.js +0 -1
  225. package/console-app/assets/swimlanesDiagram-ULZ7WXOC-C01TBFdM.js +0 -8
@@ -69,6 +69,15 @@ export const RunVirtualUserInput = z.object({
69
69
  budget: Budget.optional(),
70
70
  /** Fixed seed, so a run replays into the same finding. */
71
71
  seed: z.number().int().optional(),
72
+ /**
73
+ * A short-lived Fabric operator token, handed in by whoever starts the run.
74
+ *
75
+ * Handed in rather than held: a deployed stage that could ask for a token
76
+ * would be holding a credential able to mint admin sessions for itself, for
77
+ * as long as the box lives. This way it holds one receipt, for one run, and
78
+ * the receipt expires on its own. It is never written to the run record.
79
+ */
80
+ operatorToken: z.string().min(1).optional(),
72
81
  })
73
82
 
74
83
  export const RunVirtualUserOutput = z.object({
@@ -242,6 +251,8 @@ export const ExecuteVirtualUserRunInput = z.object({
242
251
  })
243
252
  .optional(),
244
253
  seed: z.number(),
254
+ /** Carried from the caller for the duration of the dispatch, never stored. */
255
+ operatorToken: z.string().optional(),
245
256
  })
246
257
 
247
258
  export const ExecuteVirtualUserRunOutput = z.object({
@@ -252,16 +263,20 @@ export const ExecuteVirtualUserRunOutput = z.object({
252
263
  * Auto-generated virtual user functions
253
264
  * Do not edit manually - regenerate with 'npx pikku'
254
265
  */
255
- import { pikkuFunc, pikkuSessionlessFunc } from '${leaf('function')}'
266
+ import { pikkuFunc, pikkuSessionlessFunc, type Session } from '${leaf('function')}'
267
+ import { pikkuMiddleware } from '${leaf('middleware')}'
256
268
  import { defineScope } from '${leaf('scopes')}'
257
- import { personaVirtualUserTarget, runVirtualUser as runVirtualUserEngine, type SchemaMap, type VirtualUserDisposition } from '@pikku/core/virtual-user'
258
269
  import {
259
- prepareVirtualUserRun,
260
- PRODUCTION_DISPOSITION,
270
+ executeVirtualUserRun as executeRun,
271
+ logVirtualUserTick,
272
+ requireVirtualUserRunStore,
273
+ serializeVirtualUserRun,
274
+ serializeVirtualUserSchedule,
275
+ serializeVirtualUserSteps,
276
+ startVirtualUserRun,
261
277
  tickVirtualUserSchedules as tick,
262
- type VirtualUserRunRecord,
263
- type VirtualUserRunStore,
264
- type VirtualUserScheduleRecord,
278
+ virtualUserScheduleRunInput,
279
+ writeVirtualUserSchedule,
265
280
  } from '@pikku/core/virtual-user'
266
281
  import { createPersonas, personaConfigs } from '${pathToPersonas}'
267
282
  import {
@@ -280,6 +295,14 @@ import {
280
295
  SetVirtualUserScheduleOutput,
281
296
  } from './virtual-user.schemas.gen.js'
282
297
 
298
+ /**
299
+ * The wirings, and only the wirings.
300
+ *
301
+ * Every body below is one call into \`@pikku/core/virtual-user\`, because none of
302
+ * this work varies by application — what is generated is the part that does:
303
+ * the scopes this project declares, the schemas its RPCs are checked against,
304
+ * and the \`rpc.invoke\` calls typed off its own RPC map.
305
+ */
283
306
  defineScope({
284
307
  virtualUser: {
285
308
  displayName: 'Virtual Users',
@@ -294,168 +317,6 @@ defineScope({
294
317
  },
295
318
  })
296
319
 
297
- /**
298
- * Where the virtual user signs in. Its own variable rather than a guess at the
299
- * host's origin: a run drives real traffic through the real front door, and a
300
- * server that cannot name its own public URL would be signing in somewhere it
301
- * only assumed was itself.
302
- */
303
- const API_URL_VARIABLE = 'VIRTUAL_USER_API_URL'
304
- const SECRET_VARIABLE = 'SCENARIO_ACTOR_SECRET'
305
- const MODEL_VARIABLE = 'VIRTUAL_USER_MODEL'
306
-
307
- /**
308
- * The same two variables a scenario run reads, because a virtual user signs in
309
- * and calls through exactly the doors a scenario does. An app that mounts auth
310
- * somewhere other than the root — \`/api/auth\` is the common one — has no other
311
- * way to say so, and without them the run signs in against a 404 and spends its
312
- * whole budget thinking about why nothing works.
313
- */
314
- const SIGN_IN_PATH_VARIABLE = 'SCENARIO_SIGN_IN_PATH'
315
- const RPC_PATH_VARIABLE = 'SCENARIO_RPC_PATH'
316
-
317
- /**
318
- * One run on the wire. Findings and intents are free-form by design — the
319
- * engine records what it noticed, not a fixed row shape — so they cross as the
320
- * schema's open objects rather than being narrowed to whatever kinds exist
321
- * today.
322
- */
323
- const serializeRun = (run: VirtualUserRunRecord) => ({
324
- runId: run.runId,
325
- persona: run.persona,
326
- disposition: run.disposition,
327
- seed: run.seed,
328
- status: run.status,
329
- goals: run.goals,
330
- memory: run.memory,
331
- findings: run.findings.map((finding) => ({
332
- kind: finding.kind as string,
333
- detail: finding.detail,
334
- rpcName: finding.rpcName,
335
- status: finding.status,
336
- intentId: finding.intentId,
337
- step: finding.step,
338
- })),
339
- intents: run.intents.map((intent) => ({
340
- id: intent.id,
341
- sourceId: intent.sourceId,
342
- title: intent.title,
343
- status: intent.status as string,
344
- steps: intent.steps,
345
- suspensions: intent.suspensions,
346
- summary: intent.summary,
347
- })),
348
- tally: (run.tally ?? null) as Record<string, unknown> | null,
349
- stoppedBy: run.stoppedBy,
350
- error: run.error,
351
- createdAt: run.createdAt.toISOString(),
352
- finishedAt: run.finishedAt ? run.finishedAt.toISOString() : null,
353
- })
354
-
355
- /**
356
- * Resolves a persona down to a run and kicks it off, for the two callers that
357
- * start one: a person with the scope, and the tick acting on a schedule.
358
- *
359
- * Shared rather than duplicated because the checks are the point — an acted-upon
360
- * persona has no session, and only one disposition may ever touch production.
361
- * A second copy of that would eventually disagree with this one.
362
- */
363
- const startVirtualUserRun = async (
364
- services: {
365
- virtualUserRunStore?: VirtualUserRunStore
366
- // \`unknown\` because an application's Config is its own interface and need
367
- // not declare nodeEnv at all. A structural \`{ nodeEnv?: string }\` shares no
368
- // property with such a config and TypeScript rejects the whole call.
369
- config: unknown
370
- },
371
- // Named literally rather than as \`string\`: a project's generated rpc.invoke
372
- // is generic over its own map's keys, and \`string\` is not one of them.
373
- rpc: {
374
- invoke: (name: 'executeVirtualUserRun', data: any) => Promise<unknown>
375
- },
376
- input: {
377
- persona: string
378
- disposition?: string
379
- goals?: string[]
380
- memory?: Record<string, string>
381
- budget?: { steps?: number; mutations?: number; durationMs?: number }
382
- seed?: number
383
- startedBy?: string | null
384
- }
385
- ): Promise<string> => {
386
- const { virtualUserRunStore, config } = services
387
- if (!virtualUserRunStore) {
388
- throw new Error(
389
- 'No virtualUserRunStore is wired — a run has nowhere to be recorded. ' +
390
- 'Wire KyselyVirtualUserRunStore from @pikku/kysely, or your own implementation of VirtualUserRunStore.'
391
- )
392
- }
393
-
394
- const persona = personaConfigs[input.persona as keyof typeof personaConfigs] as
395
- | { id: string; runnable: boolean; disposition?: string }
396
- | undefined
397
- if (!persona) {
398
- throw new Error(
399
- \`Unknown persona "\${input.persona}" — declare it with definePersonas()\`
400
- )
401
- }
402
- // An acted-upon persona has no session of its own, and running one would
403
- // race whatever scenario acts on it.
404
- if (!persona.runnable) {
405
- throw new Error(
406
- \`Persona "\${input.persona}" is declared as acted upon, never run\`
407
- )
408
- }
409
-
410
- const disposition = (input.disposition ??
411
- persona.disposition ??
412
- 'realistic') as VirtualUserDisposition
413
- // Every disposition other than this one exists to find out what the product
414
- // does wrong, which is not a thing to do to real customers' data. Checked
415
- // against the effective disposition, so the override cannot smuggle one in.
416
- if (
417
- (config as { nodeEnv?: string } | undefined)?.nodeEnv === 'production' &&
418
- disposition !== PRODUCTION_DISPOSITION
419
- ) {
420
- throw new Error(
421
- \`Only the '\${PRODUCTION_DISPOSITION}' disposition may run against production; "\${input.persona}" is \${disposition}\`
422
- )
423
- }
424
-
425
- // Seeded here rather than inside the engine so the record carries the seed
426
- // even if the run dies before returning — an unreproducible crash costs the
427
- // most.
428
- const seed = input.seed ?? Math.floor(Math.random() * 2_147_483_647)
429
-
430
- const runId = await virtualUserRunStore.start({
431
- persona: persona.id,
432
- disposition,
433
- seed,
434
- goals: input.goals ?? [],
435
- memory: input.memory ?? {},
436
- startedBy: input.startedBy ?? null,
437
- })
438
-
439
- // Deliberately not awaited: a run takes minutes, and a held-open request
440
- // survives neither a rollout nor a proxy timeout. executeVirtualUserRun
441
- // writes both outcomes to the record itself, so the only thing left to
442
- // handle here is a rejection escaping the promise — without the catch it
443
- // becomes an unhandled rejection and takes the process with it.
444
- void rpc
445
- .invoke('executeVirtualUserRun', {
446
- runId,
447
- persona: persona.id,
448
- disposition,
449
- goals: input.goals ?? [],
450
- memory: input.memory ?? {},
451
- budget: input.budget,
452
- seed,
453
- })
454
- .catch(() => {})
455
-
456
- return runId
457
- }
458
-
459
320
  export const runVirtualUser = pikkuFunc({
460
321
  tags: ['pikku'],
461
322
  title: 'Run a Virtual User',
@@ -466,11 +327,42 @@ export const runVirtualUser = pikkuFunc({
466
327
  input: RunVirtualUserInput,
467
328
  output: RunVirtualUserOutput,
468
329
  func: async ({ virtualUserRunStore, config }, input, { session, rpc }) => {
469
- const runId = await startVirtualUserRun(
470
- { virtualUserRunStore, config },
471
- rpc!,
472
- { ...input, startedBy: session?.userId ?? null }
473
- )
330
+ const { runId, persona, disposition, goals, memory, seed } =
331
+ await startVirtualUserRun({
332
+ store: virtualUserRunStore,
333
+ personas: personaConfigs,
334
+ // Cast because an application's Config is its own interface and need
335
+ // not declare nodeEnv at all.
336
+ config: config as { nodeEnv?: string } | undefined,
337
+ persona: input.persona,
338
+ disposition: input.disposition,
339
+ seed: input.seed,
340
+ goals: input.goals,
341
+ memory: input.memory,
342
+ startedBy: session?.userId ?? null,
343
+ })
344
+
345
+ // Deliberately not awaited: a run takes minutes, and a held-open request
346
+ // survives neither a rollout nor a proxy timeout. executeVirtualUserRun
347
+ // writes both outcomes to the record itself, so the only thing left to
348
+ // handle here is a rejection escaping the promise — without the catch it
349
+ // becomes an unhandled rejection and takes the process with it.
350
+ void rpc!
351
+ .invoke('executeVirtualUserRun', {
352
+ runId,
353
+ persona,
354
+ disposition,
355
+ goals,
356
+ memory,
357
+ seed,
358
+ budget: input.budget,
359
+ // Rides the dispatch and nothing else. The run record is deliberately
360
+ // not given it: a record outlives the run, and a credential in a row
361
+ // somebody can read back is a credential leak.
362
+ operatorToken: input.operatorToken,
363
+ })
364
+ .catch(() => {})
365
+
474
366
  return { runId }
475
367
  },
476
368
  })
@@ -488,14 +380,14 @@ export const getVirtualUserRun = pikkuFunc({
488
380
  input: GetVirtualUserRunInput,
489
381
  output: GetVirtualUserRunOutput,
490
382
  func: async ({ virtualUserRunStore }, { runId }) => {
491
- if (!virtualUserRunStore) {
492
- throw new Error('No virtualUserRunStore is wired — there are no runs to read.')
493
- }
494
- const run = await virtualUserRunStore.get(runId)
383
+ const run = await requireVirtualUserRunStore(
384
+ virtualUserRunStore,
385
+ true
386
+ ).get(runId)
495
387
  if (!run) {
496
388
  throw new Error(\`No virtual user run \${runId}\`)
497
389
  }
498
- return serializeRun(run)
390
+ return serializeVirtualUserRun(run)
499
391
  },
500
392
  })
501
393
 
@@ -509,11 +401,11 @@ export const listVirtualUserRuns = pikkuFunc({
509
401
  input: ListVirtualUserRunsInput,
510
402
  output: ListVirtualUserRunsOutput,
511
403
  func: async ({ virtualUserRunStore }, { persona, limit, offset }) => {
512
- if (!virtualUserRunStore) {
513
- throw new Error('No virtualUserRunStore is wired — there are no runs to read.')
514
- }
515
- const runs = await virtualUserRunStore.list({ persona, limit, offset })
516
- return { runs: runs.map(serializeRun) }
404
+ const runs = await requireVirtualUserRunStore(
405
+ virtualUserRunStore,
406
+ true
407
+ ).list({ persona, limit, offset })
408
+ return { runs: runs.map(serializeVirtualUserRun) }
517
409
  },
518
410
  })
519
411
 
@@ -530,64 +422,14 @@ export const getVirtualUserRunSteps = pikkuFunc({
530
422
  input: GetVirtualUserRunStepsInput,
531
423
  output: GetVirtualUserRunStepsOutput,
532
424
  func: async ({ virtualUserRunStore }, { runId, limit, offset }) => {
533
- if (!virtualUserRunStore) {
534
- throw new Error('No virtualUserRunStore is wired — there are no runs to read.')
535
- }
536
- const steps = await virtualUserRunStore.steps(runId, { limit, offset })
537
- return {
538
- steps: steps.map((step) => ({
539
- index: step.index,
540
- intentId: step.intentId,
541
- action: step.action as unknown as Record<string, unknown>,
542
- status: step.status,
543
- ok: step.ok,
544
- response: step.response,
545
- findingKinds: step.findingKinds as string[] | undefined,
546
- tokensIn: step.tokensIn,
547
- tokensOut: step.tokensOut,
548
- })),
549
- }
425
+ const steps = await requireVirtualUserRunStore(
426
+ virtualUserRunStore,
427
+ true
428
+ ).steps(runId, { limit, offset })
429
+ return { steps: serializeVirtualUserSteps(steps) }
550
430
  },
551
431
  })
552
432
 
553
- /**
554
- * One schedule on the wire. The budget crosses as \`durationMs\` because that is
555
- * what every other call here takes; the engine's own duration also accepts
556
- * \`'30m'\`, which nothing on this side ever writes.
557
- */
558
- const serializeSchedule = (schedule: VirtualUserScheduleRecord) => {
559
- const persona = personaConfigs[
560
- schedule.persona as keyof typeof personaConfigs
561
- ] as { goals?: string[]; disposition?: string } | undefined
562
- const declared = {
563
- disposition: (persona?.disposition ??
564
- 'realistic') as VirtualUserDisposition,
565
- goals: persona?.goals ?? [],
566
- }
567
- return {
568
- persona: schedule.persona,
569
- enabled: schedule.enabled,
570
- disposition: schedule.disposition,
571
- goals: schedule.goals,
572
- budget: schedule.budget
573
- ? {
574
- steps: schedule.budget.steps,
575
- mutations: schedule.budget.mutations,
576
- durationMs:
577
- typeof schedule.budget.duration === 'number'
578
- ? schedule.budget.duration
579
- : undefined,
580
- }
581
- : null,
582
- minIntervalMs: schedule.minIntervalMs,
583
- maxIntervalMs: schedule.maxIntervalMs,
584
- nextRunAt: schedule.nextRunAt.toISOString(),
585
- lastRunId: schedule.lastRunId,
586
- lastRunAt: schedule.lastRunAt ? schedule.lastRunAt.toISOString() : null,
587
- declared,
588
- }
589
- }
590
-
591
433
  export const setVirtualUserSchedule = pikkuFunc({
592
434
  tags: ['pikku'],
593
435
  title: 'Put a Virtual User on a Clock',
@@ -601,48 +443,12 @@ export const setVirtualUserSchedule = pikkuFunc({
601
443
  input: SetVirtualUserScheduleInput,
602
444
  output: SetVirtualUserScheduleOutput,
603
445
  func: async ({ virtualUserScheduleStore }, input) => {
604
- if (!virtualUserScheduleStore) {
605
- throw new Error(
606
- 'No virtualUserScheduleStore is wired — a cadence has nowhere to live. ' +
607
- 'Wire KyselyVirtualUserScheduleStore from @pikku/kysely, or your own implementation of VirtualUserScheduleStore.'
608
- )
609
- }
610
- const persona = personaConfigs[
611
- input.persona as keyof typeof personaConfigs
612
- ] as { runnable: boolean } | undefined
613
- if (!persona) {
614
- throw new Error(
615
- \`Unknown persona "\${input.persona}" — declare it with definePersonas()\`
616
- )
617
- }
618
- // The same rule \`runVirtualUser\` enforces, applied at the point the row is
619
- // written rather than every hour afterwards: an acted-upon persona has no
620
- // session, so a cadence for one is a tick that can only ever fail to start.
621
- if (!persona.runnable) {
622
- throw new Error(
623
- \`Persona "\${input.persona}" is declared as acted upon, never run\`
624
- )
625
- }
626
- const schedule = await virtualUserScheduleStore.set({
627
- persona: input.persona,
628
- enabled: input.enabled,
629
- disposition: input.disposition as VirtualUserDisposition | undefined,
630
- goals: input.goals,
631
- budget:
632
- input.budget === undefined
633
- ? undefined
634
- : input.budget === null
635
- ? null
636
- : {
637
- steps: input.budget.steps,
638
- mutations: input.budget.mutations,
639
- duration: input.budget.durationMs,
640
- },
641
- minIntervalMs: input.minIntervalMs,
642
- maxIntervalMs: input.maxIntervalMs,
643
- nextRunAt: input.nextRunAt ? new Date(input.nextRunAt) : undefined,
446
+ const schedule = await writeVirtualUserSchedule({
447
+ store: virtualUserScheduleStore,
448
+ personas: personaConfigs,
449
+ ...input,
644
450
  })
645
- return serializeSchedule(schedule)
451
+ return serializeVirtualUserSchedule(schedule, personaConfigs)
646
452
  },
647
453
  })
648
454
 
@@ -659,10 +465,44 @@ export const listVirtualUserSchedules = pikkuFunc({
659
465
  return { schedules: [] }
660
466
  }
661
467
  const schedules = await virtualUserScheduleStore.list()
662
- return { schedules: schedules.map(serializeSchedule) }
468
+ return {
469
+ schedules: schedules.map((schedule) =>
470
+ serializeVirtualUserSchedule(schedule, personaConfigs)
471
+ ),
472
+ }
663
473
  },
664
474
  })
665
475
 
476
+ /**
477
+ * The identity a scheduled tick runs as.
478
+ *
479
+ * \`pikku-platform\` is the platform's own principal, and it already exists for
480
+ * exactly this: a reserved user row created with no credential account of any
481
+ * kind, so no sign-in method can resolve it, and one the user directory already
482
+ * filters out — so unlike a seeded service account it costs no phantom member
483
+ * in any list, seat count or bill.
484
+ *
485
+ * Not parameterised, because it is not a decision an application makes: work
486
+ * this scaffold starts on its own clock is the platform's work, and the scope
487
+ * is the one \`runVirtualUser\` gates on.
488
+ *
489
+ * Attached to \`wireScheduler\`'s \`middleware\` rather than declared as tag
490
+ * middleware over \`/rpc\`, which cannot set a session at all: \`runScheduledTask\`
491
+ * builds its wire with a \`sessionService\`, so the session set here is the one
492
+ * the function is frozen with.
493
+ */
494
+ const PLATFORM_USER_ID = 'pikku-platform'
495
+
496
+ export const virtualUserPlatformSession = pikkuMiddleware(
497
+ async (_services, { setSession }, next) => {
498
+ await setSession?.({
499
+ userId: PLATFORM_USER_ID,
500
+ scopes: ['virtualUser:run'],
501
+ } as Session)
502
+ return next()
503
+ }
504
+ )
505
+
666
506
  /**
667
507
  * Acts on whichever personas are due, once.
668
508
  *
@@ -675,13 +515,21 @@ export const listVirtualUserSchedules = pikkuFunc({
675
515
  * wireScheduler({
676
516
  * name: 'virtualUsers',
677
517
  * schedule: '0 * * * *',
518
+ * middleware: [virtualUserPlatformSession],
678
519
  * func: tickVirtualUserSchedules,
679
520
  * })
680
521
  * \`\`\`
681
522
  *
682
- * An hourly tick is plenty for intervals measured in hours: the tick
683
- * decides nothing except which rows are already due, so running it more often
684
- * costs a query and changes no cadence.
523
+ * The middleware is not decoration: the tick starts a run through
524
+ * \`runVirtualUser\`, the same gated entry point a person calls, so without a
525
+ * session carrying \`virtualUser:run\` every dispatch is refused. Going through
526
+ * that front door rather than a shared helper is the point — the persona
527
+ * checks, the production-disposition rule and the record all live in one place,
528
+ * and a second copy of them would eventually disagree with the first.
529
+ *
530
+ * An hourly tick is plenty for intervals measured in hours: the tick decides
531
+ * nothing except which rows are already due, so running it more often costs a
532
+ * query and changes no cadence.
685
533
  */
686
534
  export const tickVirtualUserSchedules = pikkuSessionlessFunc<void, void>({
687
535
  tags: ['pikku'],
@@ -690,50 +538,26 @@ export const tickVirtualUserSchedules = pikkuSessionlessFunc<void, void>({
690
538
  if (!virtualUserScheduleStore || !virtualUserRunStore) {
691
539
  return
692
540
  }
693
- const result = await tick({
694
- schedules: virtualUserScheduleStore,
695
- runs: virtualUserRunStore,
696
- dispatch: (schedule) =>
697
- startVirtualUserRun(services, rpc!, {
698
- persona: schedule.persona,
699
- disposition: schedule.disposition,
700
- goals: schedule.goals,
701
- budget: schedule.budget
702
- ? {
703
- steps: schedule.budget.steps,
704
- mutations: schedule.budget.mutations,
705
- durationMs:
706
- typeof schedule.budget.duration === 'number'
707
- ? schedule.budget.duration
708
- : undefined,
709
- }
710
- : undefined,
711
- }),
712
- })
713
- // Logged rather than returned: the caller is a cron, and a run this started
714
- // is otherwise the only trace that a persona is still out there working.
715
- for (const { persona, runId } of result.dispatched) {
716
- logger.info(\`Virtual user \${persona} started run \${runId} on schedule\`)
717
- }
718
- for (const runId of result.reaped) {
719
- logger.warn(
720
- \`Virtual user run \${runId} was abandoned — marked failed so its persona can run again\`
721
- )
722
- }
723
- for (const { persona, reason } of result.skipped) {
724
- logger.info(\`Virtual user \${persona} skipped this tick: \${reason}\`)
725
- }
541
+ logVirtualUserTick(
542
+ logger,
543
+ await tick({
544
+ schedules: virtualUserScheduleStore,
545
+ runs: virtualUserRunStore,
546
+ dispatch: async (schedule) => {
547
+ const { runId } = await rpc!.invoke(
548
+ 'runVirtualUser',
549
+ virtualUserScheduleRunInput(schedule)
550
+ )
551
+ return runId
552
+ },
553
+ })
554
+ )
726
555
  },
727
556
  })
728
557
 
729
558
  /**
730
559
  * The run itself. Not exposed: it is dispatched by \`runVirtualUser\` and has no
731
560
  * caller of its own.
732
- *
733
- * Everything it needs is derived through \`metaService\` and the generated
734
- * personas — the same public surface any consumer has. Nothing reaches into
735
- * pikku's internals, because an app could not, and a feature built on what only
736
- * the framework can see would not be this feature.
737
561
  */
738
562
  export const executeVirtualUserRun = pikkuSessionlessFunc({
739
563
  tags: ['pikku'],
@@ -741,122 +565,18 @@ export const executeVirtualUserRun = pikkuSessionlessFunc({
741
565
  output: ExecuteVirtualUserRunOutput,
742
566
  func: async (
743
567
  { virtualUserRunStore, metaService, agentRunner, variables, logger },
744
- { runId, persona: personaId, disposition, goals, memory, budget, seed }
745
- ) => {
746
- if (!virtualUserRunStore) {
747
- throw new Error('No virtualUserRunStore is wired.')
748
- }
749
- try {
750
- if (!metaService) {
751
- throw new Error('metaService is not wired — there is no catalogue to derive')
752
- }
753
- if (!agentRunner) {
754
- throw new Error('agentRunner is not wired — there is nothing to think with')
755
- }
756
-
757
- const apiUrl = await variables.get(API_URL_VARIABLE)
758
- if (!apiUrl) {
759
- throw new Error(
760
- \`\${API_URL_VARIABLE} is not set — a virtual user has no address to sign in at.\`
761
- )
762
- }
763
- const secret = await variables.get(SECRET_VARIABLE)
764
- if (!secret) {
765
- throw new Error(
766
- \`\${SECRET_VARIABLE} is not set — actor sign-in is disabled, so there is nobody for the virtual user to be.\`
767
- )
768
- }
769
- const model = await variables.get(MODEL_VARIABLE)
770
- if (!model) {
771
- throw new Error(\`\${MODEL_VARIABLE} is not set — no model to think with.\`)
772
- }
773
-
774
- const functionsMeta = await metaService.getFunctionsMeta()
775
- // Only the schemas the catalogue can actually refer to, so a large app
776
- // does not pull every schema it has ever generated into one run.
777
- const schemaNames = [
778
- ...new Set(
779
- Object.values(functionsMeta).flatMap((meta: any) =>
780
- [meta.inputSchemaName, meta.outputSchemaName].filter(
781
- (name: unknown): name is string => !!name
782
- )
783
- )
784
- ),
785
- ]
786
- const schemas = (await metaService.getSchemas(schemaNames)) as SchemaMap
787
-
788
- const persona = personaConfigs[personaId as keyof typeof personaConfigs]
789
- if (!persona) {
790
- throw new Error(\`Persona "\${personaId}" is no longer declared\`)
791
- }
792
-
793
- const { catalogue, intents, agents } = prepareVirtualUserRun({
794
- persona,
795
- functionsMeta,
796
- schemas,
797
- workflowsMeta: await metaService.getWorkflowMeta(),
798
- systemRoles: await metaService.getSystemRolesMeta(),
799
- agentsMeta: await metaService.getAgentsMeta(),
800
- })
801
-
802
- const signedIn = createPersonas({
803
- apiUrl,
804
- secret,
805
- model,
806
- signInPath: (await variables.get(SIGN_IN_PATH_VARIABLE)) ?? undefined,
807
- rpcPath: (await variables.get(RPC_PATH_VARIABLE)) ?? undefined,
808
- })
809
- const target = signedIn[personaId as keyof typeof signedIn]
810
- if (!target) {
811
- throw new Error(\`Persona "\${personaId}" cannot sign in\`)
812
- }
813
-
814
- const result = await runVirtualUserEngine({
815
- persona,
816
- personaId,
817
- disposition: disposition as VirtualUserDisposition,
818
- catalogue,
819
- intents,
820
- goals,
821
- memory,
822
- seed,
823
- agents,
824
- target: personaVirtualUserTarget(target, {
825
- model,
826
- agents: agents.map((agent) => agent.name),
827
- }),
828
- // \`AgentRunnerService.run\` IS the engine's \`ActorLLM\` — same params,
829
- // same result — so a virtual user thinks through the same runner every
830
- // agent in the app does, provider quirks and all.
831
- llm: (params) => agentRunner.run(params),
832
- model,
833
- budget: {
834
- steps: budget?.steps,
835
- mutations: budget?.mutations,
836
- duration: budget?.durationMs,
837
- },
838
- })
839
-
840
- await virtualUserRunStore.complete(runId, {
841
- findings: result.findings,
842
- tally: result.tally,
843
- memory: result.memory,
844
- stoppedBy: result.stoppedBy ?? null,
845
- intents: result.intents,
846
- steps: result.steps,
847
- })
848
-
849
- return { findings: result.findings.length }
850
- } catch (error) {
851
- // A crashed run and a run that found nothing are different states, and the
852
- // record is the only place that distinction survives — leaving it at
853
- // 'running' forever is what \`fail\` exists to prevent.
854
- const message = error instanceof Error ? error.message : String(error)
855
- logger.error(\`Virtual user run \${runId} (\${personaId}) failed: \${message}\`)
856
- await virtualUserRunStore.fail(runId, message)
857
- throw error
858
- }
859
- },
568
+ input
569
+ ) =>
570
+ executeRun({
571
+ runStore: virtualUserRunStore,
572
+ metaService,
573
+ agentRunner,
574
+ variables,
575
+ logger,
576
+ personas: personaConfigs,
577
+ createPersonas,
578
+ ...input,
579
+ }),
860
580
  })
861
581
  `;
862
582
  return { schemas, functions };