@pikku/cli 0.12.119 → 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 +225 -0
  2. package/console-app/assets/{abnfDiagram-N423BO3Z-CiK5F2ml.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-g4Elcs_0.js → architectureDiagram-T3A2C74G-C9MjP0IU.js} +1 -1
  5. package/console-app/assets/{blockDiagram-VBNYF7ZC-CI6muc3M.js → blockDiagram-VBNYF7ZC-CGgHSlOO.js} +1 -1
  6. package/console-app/assets/{c4Diagram-5PPSVZJV-CjzL9yGp.js → c4Diagram-5PPSVZJV-3kzckivq.js} +1 -1
  7. package/console-app/assets/channel-UOxM22B3.js +1 -0
  8. package/console-app/assets/{chunk-2GRJ4B5K-C68TKOLb.js → chunk-2GRJ4B5K-UZ1q29J_.js} +1 -1
  9. package/console-app/assets/{chunk-4I5QYGJK-D_DINIPO.js → chunk-4I5QYGJK-bq4iSS6S.js} +1 -1
  10. package/console-app/assets/{chunk-5RXB4S5H-D8mEuiea.js → chunk-5RXB4S5H-BCeU8nXq.js} +1 -1
  11. package/console-app/assets/{chunk-6Q2QTUOP-CVNqLnb8.js → chunk-6Q2QTUOP-DpsTVhit.js} +1 -1
  12. package/console-app/assets/{chunk-7Z6QIM7H-Bnih6HyS.js → chunk-7Z6QIM7H-I6XKkv2A.js} +1 -1
  13. package/console-app/assets/{chunk-GF5L2VYU-C5efrbFt.js → chunk-GF5L2VYU-aYODFCZ6.js} +1 -1
  14. package/console-app/assets/{chunk-I66GZJ75-dckv50K3.js → chunk-I66GZJ75-CWdPrY4a.js} +1 -1
  15. package/console-app/assets/{chunk-JQJVKLGR-HirBo6LQ.js → chunk-JQJVKLGR-D5bHP4bt.js} +1 -1
  16. package/console-app/assets/{chunk-KBJHAD2P-lknhmPj0.js → chunk-KBJHAD2P-B-Spac9o.js} +1 -1
  17. package/console-app/assets/{chunk-NSK5VX7P-tGAl7zxF.js → chunk-NSK5VX7P-0jZVLBhi.js} +1 -1
  18. package/console-app/assets/{chunk-QR6OTTB3-BlSKCrEq.js → chunk-QR6OTTB3-BurXFOtV.js} +1 -1
  19. package/console-app/assets/{chunk-RYQCIY6F-CwQmwEV-.js → chunk-RYQCIY6F-C4zacxhH.js} +1 -1
  20. package/console-app/assets/{chunk-UBXNYLIW-D1Dn6ZK0.js → chunk-UBXNYLIW-7RthekQ-.js} +1 -1
  21. package/console-app/assets/{chunk-W5SLKNZC-DOkZHhST.js → chunk-W5SLKNZC-rq3UcMTy.js} +1 -1
  22. package/console-app/assets/{chunk-WRU74C26-CB4orIKm.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-stBhaqpU.js → cose-bilkent-JH36ORCC-DZn35HFs.js} +1 -1
  27. package/console-app/assets/{cynefin-VYW2F7L2-Bb1nmKWZ.js → cynefin-VYW2F7L2-mZlOMKNi.js} +1 -1
  28. package/console-app/assets/{cynefinDiagram-MW4NZA55-Bp8fMJi-.js → cynefinDiagram-MW4NZA55-uUAfUoag.js} +1 -1
  29. package/console-app/assets/{dagre-DshmzimC.js → dagre-DfTmIhGN.js} +1 -1
  30. package/console-app/assets/{dagre-VZM6K2ZE-DIl1klf5.js → dagre-VZM6K2ZE-7oTddCss.js} +1 -1
  31. package/console-app/assets/{diagram-7IWD3JNH-DdkSOxKT.js → diagram-7IWD3JNH-D6TR4TlN.js} +1 -1
  32. package/console-app/assets/{diagram-B4RE2ZJO-9Dj9O4L8.js → diagram-B4RE2ZJO-BZwUFLzC.js} +1 -1
  33. package/console-app/assets/{diagram-LBJQPF4R-CxBb73tU.js → diagram-LBJQPF4R-B4Ug2xRo.js} +1 -1
  34. package/console-app/assets/{diagram-Q27KOJAE-CPpUmnvb.js → diagram-Q27KOJAE-Fpr_Cahs.js} +1 -1
  35. package/console-app/assets/{diagram-UB23O5K3-7wV_Db6W.js → diagram-UB23O5K3-d0X7PA-5.js} +1 -1
  36. package/console-app/assets/{ebnfDiagram-BXEA7PRR-Cxzbf8pk.js → ebnfDiagram-BXEA7PRR-DExP954c.js} +1 -1
  37. package/console-app/assets/{erDiagram-JOGREHBK-BBPLq83P.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-Erh7WK4H.js → ganttDiagram-PKOTCBZU-ADJHCyvl.js} +1 -1
  41. package/console-app/assets/{gitGraph-TEB2WS4Q-o5FLRF57.js → gitGraph-TEB2WS4Q-BPcXt1vK.js} +1 -1
  42. package/console-app/assets/{gitGraphDiagram-DS77QQ5N-BFbsaenX.js → gitGraphDiagram-DS77QQ5N-DFhenfUX.js} +1 -1
  43. package/console-app/assets/{index-w2ZoJr8X.js → index-BwP6EcJr.js} +184 -184
  44. package/console-app/assets/{info-DKCQHKI2-Dkcrdr1w.js → info-DKCQHKI2-BsE7egna.js} +1 -1
  45. package/console-app/assets/{infoDiagram-6WML65LV-iZVC5Wzy.js → infoDiagram-6WML65LV-BmVqXMw0.js} +1 -1
  46. package/console-app/assets/{ishikawaDiagram-WSZJBQD7-BjV8LLeA.js → ishikawaDiagram-WSZJBQD7-CdFTPIY5.js} +1 -1
  47. package/console-app/assets/{journeyDiagram-NVQOT4AX-D3rpLjYi.js → journeyDiagram-NVQOT4AX-DC8CKdJk.js} +1 -1
  48. package/console-app/assets/{kanban-definition-27J2QSJJ-Dds5TFyv.js → kanban-definition-27J2QSJJ-Bo7kRcLN.js} +1 -1
  49. package/console-app/assets/{line-D1rUAT18.js → line-5nUqh22u.js} +1 -1
  50. package/console-app/assets/{linear-fQ_6Sc79.js → linear-82KoPtTH.js} +1 -1
  51. package/console-app/assets/{mermaid-parser.core-B9Q-4IXF.js → mermaid-parser.core-R240ylPw.js} +3 -3
  52. package/console-app/assets/{mermaid.core-bPhADShw.js → mermaid.core-BZ3jSM6D.js} +4 -4
  53. package/console-app/assets/{mindmap-definition-FAOFIHXS-CULlU6Fy.js → mindmap-definition-FAOFIHXS-x1K54VeO.js} +1 -1
  54. package/console-app/assets/{packet-7NZHBO7P-DpwzoQpc.js → packet-7NZHBO7P-CE2Lun0t.js} +1 -1
  55. package/console-app/assets/{pegDiagram-VL7TDLO6-B9fKKc4F.js → pegDiagram-VL7TDLO6-CpGZYdUz.js} +1 -1
  56. package/console-app/assets/{pie-RZYD4A2V-DkXpqWAI.js → pie-RZYD4A2V-Hf5MOyY8.js} +1 -1
  57. package/console-app/assets/{pieDiagram-7S7Q4E2Y-Bcpn2rUM.js → pieDiagram-7S7Q4E2Y-0J5H7r7g.js} +1 -1
  58. package/console-app/assets/{quadrantDiagram-CIZ2JOQS-Bd5wWFj_.js → quadrantDiagram-CIZ2JOQS-DWtYi8O-.js} +1 -1
  59. package/console-app/assets/{radar-I7S5WNFK-ylw-u3HI.js → radar-I7S5WNFK-BgtvrX4w.js} +1 -1
  60. package/console-app/assets/{railroad-3IZDKUUU-D4LMucL-.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-CZukQql9.js → railroadDiagram-AXF67PYL-CUIn_gPf.js} +1 -1
  65. package/console-app/assets/{requirementDiagram-LRYGKXZP-Ou4nRENK.js → requirementDiagram-LRYGKXZP-Ds9Aqcts.js} +1 -1
  66. package/console-app/assets/{sankeyDiagram-W5VNT64P-BKdG-c_o.js → sankeyDiagram-W5VNT64P-CKhuya4d.js} +1 -1
  67. package/console-app/assets/{sequenceDiagram-SI44F4Z6-Dc7d5obe.js → sequenceDiagram-SI44F4Z6-DKgOmdzz.js} +1 -1
  68. package/console-app/assets/{src-BFb2oeKx.js → src-DwHVY--Z.js} +1 -1
  69. package/console-app/assets/{stateDiagram-OKZ733FA-BA9fuN7b.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-l-ETgM83.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-Uj_ByeVr.js → timeline-definition-Z64GVDOM-CHHi2Rpq.js} +1 -1
  74. package/console-app/assets/{treeView-QDETBFTQ-BvFRkVGY.js → treeView-QDETBFTQ-DrLHlsII.js} +1 -1
  75. package/console-app/assets/{treemap-6X3UGDF4-BV5T7Dw9.js → treemap-6X3UGDF4-C3saYKuK.js} +1 -1
  76. package/console-app/assets/{vennDiagram-T6HMQDX7-DAFU8gAo.js → vennDiagram-T6HMQDX7-CdyzNiAu.js} +1 -1
  77. package/console-app/assets/{wardley-OPB4EBWU-MLNuhuiA.js → wardley-OPB4EBWU-BSrymyse.js} +1 -1
  78. package/console-app/assets/{wardleyDiagram-T6FBY63Y-B1lCTfi1.js → wardleyDiagram-T6FBY63Y-CCW6rhEo.js} +1 -1
  79. package/console-app/assets/{xychartDiagram-ELKLHX3M-Bw3zhuWc.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 +171 -405
  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 +2 -2
  214. package/console-app/assets/architecture-TIHT7OUA-FMO1RByN.js +0 -1
  215. package/console-app/assets/channel-Dp8A5NJg.js +0 -1
  216. package/console-app/assets/chunk-XXDRQBXY-BMybY6E-.js +0 -1
  217. package/console-app/assets/classDiagram-JCYQIIEL-C-K-UAUR.js +0 -1
  218. package/console-app/assets/classDiagram-v2-OCEON4UE-C-K-UAUR.js +0 -1
  219. package/console-app/assets/eventmodeling-45OFAUF4-CLUnygIv.js +0 -1
  220. package/console-app/assets/flowDiagram-UKHOOZJN-D3mDcWWx.js +0 -1
  221. package/console-app/assets/railroad-abnf-AHOZXSZD-CRTVzK1g.js +0 -1
  222. package/console-app/assets/railroad-ebnf-EBAXGLYW-CB13ugAF.js +0 -1
  223. package/console-app/assets/railroad-peg-LSFZ7HO6-AN8zyQYp.js +0 -1
  224. package/console-app/assets/stateDiagram-v2-UEYNNEHI-D13a6mUd.js +0 -1
  225. package/console-app/assets/swimlanesDiagram-ULZ7WXOC-BsC-B2Z9.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({
@@ -206,6 +215,19 @@ const virtualUserScheduleFields = {
206
215
  nextRunAt: z.string(),
207
216
  lastRunId: z.string().nullable(),
208
217
  lastRunAt: z.string().nullable(),
218
+ /**
219
+ * What \`definePersonas()\` says about this persona now, alongside what the
220
+ * row is actually running.
221
+ *
222
+ * A schedule is turned on once and then outlives the declaration it was
223
+ * written from: someone edits \`personas.ts\`, redeploys, and the row keeps
224
+ * running last month's goals with nothing anywhere to say so. Both sides
225
+ * cross the wire so that difference is readable rather than inferred.
226
+ */
227
+ declared: z.object({
228
+ disposition: Disposition,
229
+ goals: z.array(z.string()),
230
+ }),
209
231
  }
210
232
 
211
233
  export const SetVirtualUserScheduleOutput = z.object(virtualUserScheduleFields)
@@ -229,6 +251,8 @@ export const ExecuteVirtualUserRunInput = z.object({
229
251
  })
230
252
  .optional(),
231
253
  seed: z.number(),
254
+ /** Carried from the caller for the duration of the dispatch, never stored. */
255
+ operatorToken: z.string().optional(),
232
256
  })
233
257
 
234
258
  export const ExecuteVirtualUserRunOutput = z.object({
@@ -239,16 +263,20 @@ export const ExecuteVirtualUserRunOutput = z.object({
239
263
  * Auto-generated virtual user functions
240
264
  * Do not edit manually - regenerate with 'npx pikku'
241
265
  */
242
- import { pikkuFunc, pikkuSessionlessFunc } from '${leaf('function')}'
266
+ import { pikkuFunc, pikkuSessionlessFunc, type Session } from '${leaf('function')}'
267
+ import { pikkuMiddleware } from '${leaf('middleware')}'
243
268
  import { defineScope } from '${leaf('scopes')}'
244
- import { personaVirtualUserTarget, runVirtualUser as runVirtualUserEngine, type SchemaMap, type VirtualUserDisposition } from '@pikku/core/virtual-user'
245
269
  import {
246
- prepareVirtualUserRun,
247
- PRODUCTION_DISPOSITION,
270
+ executeVirtualUserRun as executeRun,
271
+ logVirtualUserTick,
272
+ requireVirtualUserRunStore,
273
+ serializeVirtualUserRun,
274
+ serializeVirtualUserSchedule,
275
+ serializeVirtualUserSteps,
276
+ startVirtualUserRun,
248
277
  tickVirtualUserSchedules as tick,
249
- type VirtualUserRunRecord,
250
- type VirtualUserRunStore,
251
- type VirtualUserScheduleRecord,
278
+ virtualUserScheduleRunInput,
279
+ writeVirtualUserSchedule,
252
280
  } from '@pikku/core/virtual-user'
253
281
  import { createPersonas, personaConfigs } from '${pathToPersonas}'
254
282
  import {
@@ -267,6 +295,14 @@ import {
267
295
  SetVirtualUserScheduleOutput,
268
296
  } from './virtual-user.schemas.gen.js'
269
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
+ */
270
306
  defineScope({
271
307
  virtualUser: {
272
308
  displayName: 'Virtual Users',
@@ -281,151 +317,6 @@ defineScope({
281
317
  },
282
318
  })
283
319
 
284
- /**
285
- * Where the virtual user signs in. Its own variable rather than a guess at the
286
- * host's origin: a run drives real traffic through the real front door, and a
287
- * server that cannot name its own public URL would be signing in somewhere it
288
- * only assumed was itself.
289
- */
290
- const API_URL_VARIABLE = 'VIRTUAL_USER_API_URL'
291
- const SECRET_VARIABLE = 'SCENARIO_ACTOR_SECRET'
292
- const MODEL_VARIABLE = 'VIRTUAL_USER_MODEL'
293
-
294
- /**
295
- * One run on the wire. Findings and intents are free-form by design — the
296
- * engine records what it noticed, not a fixed row shape — so they cross as the
297
- * schema's open objects rather than being narrowed to whatever kinds exist
298
- * today.
299
- */
300
- const serializeRun = (run: VirtualUserRunRecord) => ({
301
- runId: run.runId,
302
- persona: run.persona,
303
- disposition: run.disposition,
304
- seed: run.seed,
305
- status: run.status,
306
- goals: run.goals,
307
- memory: run.memory,
308
- findings: run.findings.map((finding) => ({
309
- kind: finding.kind as string,
310
- detail: finding.detail,
311
- rpcName: finding.rpcName,
312
- status: finding.status,
313
- intentId: finding.intentId,
314
- step: finding.step,
315
- })),
316
- intents: run.intents.map((intent) => ({
317
- id: intent.id,
318
- sourceId: intent.sourceId,
319
- title: intent.title,
320
- status: intent.status as string,
321
- steps: intent.steps,
322
- suspensions: intent.suspensions,
323
- summary: intent.summary,
324
- })),
325
- tally: (run.tally ?? null) as Record<string, unknown> | null,
326
- stoppedBy: run.stoppedBy,
327
- error: run.error,
328
- createdAt: run.createdAt.toISOString(),
329
- finishedAt: run.finishedAt ? run.finishedAt.toISOString() : null,
330
- })
331
-
332
- /**
333
- * Resolves a persona down to a run and kicks it off, for the two callers that
334
- * start one: a person with the scope, and the tick acting on a schedule.
335
- *
336
- * Shared rather than duplicated because the checks are the point — an acted-upon
337
- * persona has no session, and only one disposition may ever touch production.
338
- * A second copy of that would eventually disagree with this one.
339
- */
340
- const startVirtualUserRun = async (
341
- services: {
342
- virtualUserRunStore?: VirtualUserRunStore
343
- config: { nodeEnv?: string }
344
- },
345
- rpc: { invoke: (name: string, data: any) => Promise<unknown> },
346
- input: {
347
- persona: string
348
- disposition?: string
349
- goals?: string[]
350
- memory?: Record<string, string>
351
- budget?: { steps?: number; mutations?: number; durationMs?: number }
352
- seed?: number
353
- startedBy?: string | null
354
- }
355
- ): Promise<string> => {
356
- const { virtualUserRunStore, config } = services
357
- if (!virtualUserRunStore) {
358
- throw new Error(
359
- 'No virtualUserRunStore is wired — a run has nowhere to be recorded. ' +
360
- 'Wire KyselyVirtualUserRunStore from @pikku/kysely, or your own implementation of VirtualUserRunStore.'
361
- )
362
- }
363
-
364
- const persona = personaConfigs[input.persona as keyof typeof personaConfigs] as
365
- | { id: string; runnable: boolean; disposition?: string }
366
- | undefined
367
- if (!persona) {
368
- throw new Error(
369
- \`Unknown persona "\${input.persona}" — declare it with definePersonas()\`
370
- )
371
- }
372
- // An acted-upon persona has no session of its own, and running one would
373
- // race whatever scenario acts on it.
374
- if (!persona.runnable) {
375
- throw new Error(
376
- \`Persona "\${input.persona}" is declared as acted upon, never run\`
377
- )
378
- }
379
-
380
- const disposition = (input.disposition ??
381
- persona.disposition ??
382
- 'realistic') as VirtualUserDisposition
383
- // Every disposition other than this one exists to find out what the product
384
- // does wrong, which is not a thing to do to real customers' data. Checked
385
- // against the effective disposition, so the override cannot smuggle one in.
386
- if (
387
- config.nodeEnv === 'production' &&
388
- disposition !== PRODUCTION_DISPOSITION
389
- ) {
390
- throw new Error(
391
- \`Only the '\${PRODUCTION_DISPOSITION}' disposition may run against production; "\${input.persona}" is \${disposition}\`
392
- )
393
- }
394
-
395
- // Seeded here rather than inside the engine so the record carries the seed
396
- // even if the run dies before returning — an unreproducible crash costs the
397
- // most.
398
- const seed = input.seed ?? Math.floor(Math.random() * 2_147_483_647)
399
-
400
- const runId = await virtualUserRunStore.start({
401
- persona: persona.id,
402
- disposition,
403
- seed,
404
- goals: input.goals ?? [],
405
- memory: input.memory ?? {},
406
- startedBy: input.startedBy ?? null,
407
- })
408
-
409
- // Deliberately not awaited: a run takes minutes, and a held-open request
410
- // survives neither a rollout nor a proxy timeout. executeVirtualUserRun
411
- // writes both outcomes to the record itself, so the only thing left to
412
- // handle here is a rejection escaping the promise — without the catch it
413
- // becomes an unhandled rejection and takes the process with it.
414
- void rpc
415
- .invoke('executeVirtualUserRun', {
416
- runId,
417
- persona: persona.id,
418
- disposition,
419
- goals: input.goals ?? [],
420
- memory: input.memory ?? {},
421
- budget: input.budget,
422
- seed,
423
- })
424
- .catch(() => {})
425
-
426
- return runId
427
- }
428
-
429
320
  export const runVirtualUser = pikkuFunc({
430
321
  tags: ['pikku'],
431
322
  title: 'Run a Virtual User',
@@ -436,11 +327,42 @@ export const runVirtualUser = pikkuFunc({
436
327
  input: RunVirtualUserInput,
437
328
  output: RunVirtualUserOutput,
438
329
  func: async ({ virtualUserRunStore, config }, input, { session, rpc }) => {
439
- const runId = await startVirtualUserRun(
440
- { virtualUserRunStore, config },
441
- rpc!,
442
- { ...input, startedBy: session?.userId ?? null }
443
- )
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
+
444
366
  return { runId }
445
367
  },
446
368
  })
@@ -458,14 +380,14 @@ export const getVirtualUserRun = pikkuFunc({
458
380
  input: GetVirtualUserRunInput,
459
381
  output: GetVirtualUserRunOutput,
460
382
  func: async ({ virtualUserRunStore }, { runId }) => {
461
- if (!virtualUserRunStore) {
462
- throw new Error('No virtualUserRunStore is wired — there are no runs to read.')
463
- }
464
- const run = await virtualUserRunStore.get(runId)
383
+ const run = await requireVirtualUserRunStore(
384
+ virtualUserRunStore,
385
+ true
386
+ ).get(runId)
465
387
  if (!run) {
466
388
  throw new Error(\`No virtual user run \${runId}\`)
467
389
  }
468
- return serializeRun(run)
390
+ return serializeVirtualUserRun(run)
469
391
  },
470
392
  })
471
393
 
@@ -479,11 +401,11 @@ export const listVirtualUserRuns = pikkuFunc({
479
401
  input: ListVirtualUserRunsInput,
480
402
  output: ListVirtualUserRunsOutput,
481
403
  func: async ({ virtualUserRunStore }, { persona, limit, offset }) => {
482
- if (!virtualUserRunStore) {
483
- throw new Error('No virtualUserRunStore is wired — there are no runs to read.')
484
- }
485
- const runs = await virtualUserRunStore.list({ persona, limit, offset })
486
- 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) }
487
409
  },
488
410
  })
489
411
 
@@ -500,53 +422,14 @@ export const getVirtualUserRunSteps = pikkuFunc({
500
422
  input: GetVirtualUserRunStepsInput,
501
423
  output: GetVirtualUserRunStepsOutput,
502
424
  func: async ({ virtualUserRunStore }, { runId, limit, offset }) => {
503
- if (!virtualUserRunStore) {
504
- throw new Error('No virtualUserRunStore is wired — there are no runs to read.')
505
- }
506
- const steps = await virtualUserRunStore.steps(runId, { limit, offset })
507
- return {
508
- steps: steps.map((step) => ({
509
- index: step.index,
510
- intentId: step.intentId,
511
- action: step.action as unknown as Record<string, unknown>,
512
- status: step.status,
513
- ok: step.ok,
514
- response: step.response,
515
- findingKinds: step.findingKinds as string[] | undefined,
516
- tokensIn: step.tokensIn,
517
- tokensOut: step.tokensOut,
518
- })),
519
- }
425
+ const steps = await requireVirtualUserRunStore(
426
+ virtualUserRunStore,
427
+ true
428
+ ).steps(runId, { limit, offset })
429
+ return { steps: serializeVirtualUserSteps(steps) }
520
430
  },
521
431
  })
522
432
 
523
- /**
524
- * One schedule on the wire. The budget crosses as \`durationMs\` because that is
525
- * what every other call here takes; the engine's own duration also accepts
526
- * \`'30m'\`, which nothing on this side ever writes.
527
- */
528
- const serializeSchedule = (schedule: VirtualUserScheduleRecord) => ({
529
- persona: schedule.persona,
530
- enabled: schedule.enabled,
531
- disposition: schedule.disposition,
532
- goals: schedule.goals,
533
- budget: schedule.budget
534
- ? {
535
- steps: schedule.budget.steps,
536
- mutations: schedule.budget.mutations,
537
- durationMs:
538
- typeof schedule.budget.duration === 'number'
539
- ? schedule.budget.duration
540
- : undefined,
541
- }
542
- : null,
543
- minIntervalMs: schedule.minIntervalMs,
544
- maxIntervalMs: schedule.maxIntervalMs,
545
- nextRunAt: schedule.nextRunAt.toISOString(),
546
- lastRunId: schedule.lastRunId,
547
- lastRunAt: schedule.lastRunAt ? schedule.lastRunAt.toISOString() : null,
548
- })
549
-
550
433
  export const setVirtualUserSchedule = pikkuFunc({
551
434
  tags: ['pikku'],
552
435
  title: 'Put a Virtual User on a Clock',
@@ -560,48 +443,12 @@ export const setVirtualUserSchedule = pikkuFunc({
560
443
  input: SetVirtualUserScheduleInput,
561
444
  output: SetVirtualUserScheduleOutput,
562
445
  func: async ({ virtualUserScheduleStore }, input) => {
563
- if (!virtualUserScheduleStore) {
564
- throw new Error(
565
- 'No virtualUserScheduleStore is wired — a cadence has nowhere to live. ' +
566
- 'Wire KyselyVirtualUserScheduleStore from @pikku/kysely, or your own implementation of VirtualUserScheduleStore.'
567
- )
568
- }
569
- const persona = personaConfigs[
570
- input.persona as keyof typeof personaConfigs
571
- ] as { runnable: boolean } | undefined
572
- if (!persona) {
573
- throw new Error(
574
- \`Unknown persona "\${input.persona}" — declare it with definePersonas()\`
575
- )
576
- }
577
- // The same rule \`runVirtualUser\` enforces, applied at the point the row is
578
- // written rather than every hour afterwards: an acted-upon persona has no
579
- // session, so a cadence for one is a tick that can only ever fail to start.
580
- if (!persona.runnable) {
581
- throw new Error(
582
- \`Persona "\${input.persona}" is declared as acted upon, never run\`
583
- )
584
- }
585
- const schedule = await virtualUserScheduleStore.set({
586
- persona: input.persona,
587
- enabled: input.enabled,
588
- disposition: input.disposition as VirtualUserDisposition | undefined,
589
- goals: input.goals,
590
- budget:
591
- input.budget === undefined
592
- ? undefined
593
- : input.budget === null
594
- ? null
595
- : {
596
- steps: input.budget.steps,
597
- mutations: input.budget.mutations,
598
- duration: input.budget.durationMs,
599
- },
600
- minIntervalMs: input.minIntervalMs,
601
- maxIntervalMs: input.maxIntervalMs,
602
- nextRunAt: input.nextRunAt ? new Date(input.nextRunAt) : undefined,
446
+ const schedule = await writeVirtualUserSchedule({
447
+ store: virtualUserScheduleStore,
448
+ personas: personaConfigs,
449
+ ...input,
603
450
  })
604
- return serializeSchedule(schedule)
451
+ return serializeVirtualUserSchedule(schedule, personaConfigs)
605
452
  },
606
453
  })
607
454
 
@@ -612,17 +459,50 @@ export const listVirtualUserSchedules = pikkuFunc({
612
459
  'Which personas are on a clock, how often they run, and when each is next due.',
613
460
  expose: true,
614
461
  scopes: ['virtualUser:read'],
615
- input: null,
616
462
  output: ListVirtualUserSchedulesOutput,
617
463
  func: async ({ virtualUserScheduleStore }) => {
618
464
  if (!virtualUserScheduleStore) {
619
465
  return { schedules: [] }
620
466
  }
621
467
  const schedules = await virtualUserScheduleStore.list()
622
- return { schedules: schedules.map(serializeSchedule) }
468
+ return {
469
+ schedules: schedules.map((schedule) =>
470
+ serializeVirtualUserSchedule(schedule, personaConfigs)
471
+ ),
472
+ }
623
473
  },
624
474
  })
625
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
+
626
506
  /**
627
507
  * Acts on whichever personas are due, once.
628
508
  *
@@ -635,13 +515,21 @@ export const listVirtualUserSchedules = pikkuFunc({
635
515
  * wireScheduler({
636
516
  * name: 'virtualUsers',
637
517
  * schedule: '0 * * * *',
518
+ * middleware: [virtualUserPlatformSession],
638
519
  * func: tickVirtualUserSchedules,
639
520
  * })
640
521
  * \`\`\`
641
522
  *
642
- * An hourly tick is plenty for intervals measured in hours: the tick
643
- * decides nothing except which rows are already due, so running it more often
644
- * 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.
645
533
  */
646
534
  export const tickVirtualUserSchedules = pikkuSessionlessFunc<void, void>({
647
535
  tags: ['pikku'],
@@ -650,50 +538,26 @@ export const tickVirtualUserSchedules = pikkuSessionlessFunc<void, void>({
650
538
  if (!virtualUserScheduleStore || !virtualUserRunStore) {
651
539
  return
652
540
  }
653
- const result = await tick({
654
- schedules: virtualUserScheduleStore,
655
- runs: virtualUserRunStore,
656
- dispatch: (schedule) =>
657
- startVirtualUserRun(services, rpc!, {
658
- persona: schedule.persona,
659
- disposition: schedule.disposition,
660
- goals: schedule.goals,
661
- budget: schedule.budget
662
- ? {
663
- steps: schedule.budget.steps,
664
- mutations: schedule.budget.mutations,
665
- durationMs:
666
- typeof schedule.budget.duration === 'number'
667
- ? schedule.budget.duration
668
- : undefined,
669
- }
670
- : undefined,
671
- }),
672
- })
673
- // Logged rather than returned: the caller is a cron, and a run this started
674
- // is otherwise the only trace that a persona is still out there working.
675
- for (const { persona, runId } of result.dispatched) {
676
- logger.info(\`Virtual user \${persona} started run \${runId} on schedule\`)
677
- }
678
- for (const runId of result.reaped) {
679
- logger.warn(
680
- \`Virtual user run \${runId} was abandoned — marked failed so its persona can run again\`
681
- )
682
- }
683
- for (const { persona, reason } of result.skipped) {
684
- logger.info(\`Virtual user \${persona} skipped this tick: \${reason}\`)
685
- }
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
+ )
686
555
  },
687
556
  })
688
557
 
689
558
  /**
690
559
  * The run itself. Not exposed: it is dispatched by \`runVirtualUser\` and has no
691
560
  * caller of its own.
692
- *
693
- * Everything it needs is derived through \`metaService\` and the generated
694
- * personas — the same public surface any consumer has. Nothing reaches into
695
- * pikku's internals, because an app could not, and a feature built on what only
696
- * the framework can see would not be this feature.
697
561
  */
698
562
  export const executeVirtualUserRun = pikkuSessionlessFunc({
699
563
  tags: ['pikku'],
@@ -701,116 +565,18 @@ export const executeVirtualUserRun = pikkuSessionlessFunc({
701
565
  output: ExecuteVirtualUserRunOutput,
702
566
  func: async (
703
567
  { virtualUserRunStore, metaService, agentRunner, variables, logger },
704
- { runId, persona: personaId, disposition, goals, memory, budget, seed }
705
- ) => {
706
- if (!virtualUserRunStore) {
707
- throw new Error('No virtualUserRunStore is wired.')
708
- }
709
- try {
710
- if (!metaService) {
711
- throw new Error('metaService is not wired — there is no catalogue to derive')
712
- }
713
- if (!agentRunner) {
714
- throw new Error('agentRunner is not wired — there is nothing to think with')
715
- }
716
-
717
- const apiUrl = await variables.get(API_URL_VARIABLE)
718
- if (!apiUrl) {
719
- throw new Error(
720
- \`\${API_URL_VARIABLE} is not set — a virtual user has no address to sign in at.\`
721
- )
722
- }
723
- const secret = await variables.get(SECRET_VARIABLE)
724
- if (!secret) {
725
- throw new Error(
726
- \`\${SECRET_VARIABLE} is not set — actor sign-in is disabled, so there is nobody for the virtual user to be.\`
727
- )
728
- }
729
- const model = await variables.get(MODEL_VARIABLE)
730
- if (!model) {
731
- throw new Error(\`\${MODEL_VARIABLE} is not set — no model to think with.\`)
732
- }
733
-
734
- const functionsMeta = await metaService.getFunctionsMeta()
735
- // Only the schemas the catalogue can actually refer to, so a large app
736
- // does not pull every schema it has ever generated into one run.
737
- const schemaNames = [
738
- ...new Set(
739
- Object.values(functionsMeta).flatMap((meta: any) =>
740
- [meta.inputSchemaName, meta.outputSchemaName].filter(
741
- (name: unknown): name is string => !!name
742
- )
743
- )
744
- ),
745
- ]
746
- const schemas = (await metaService.getSchemas(schemaNames)) as SchemaMap
747
-
748
- const persona = personaConfigs[personaId as keyof typeof personaConfigs]
749
- if (!persona) {
750
- throw new Error(\`Persona "\${personaId}" is no longer declared\`)
751
- }
752
-
753
- const { catalogue, intents, agents } = prepareVirtualUserRun({
754
- persona,
755
- functionsMeta,
756
- schemas,
757
- workflowsMeta: await metaService.getWorkflowMeta(),
758
- systemRoles: await metaService.getSystemRolesMeta(),
759
- agentsMeta: await metaService.getAgentsMeta(),
760
- })
761
-
762
- const signedIn = createPersonas({ apiUrl, secret, model })
763
- const target = signedIn[personaId as keyof typeof signedIn]
764
- if (!target) {
765
- throw new Error(\`Persona "\${personaId}" cannot sign in\`)
766
- }
767
-
768
- const result = await runVirtualUserEngine({
769
- persona,
770
- personaId,
771
- disposition: disposition as VirtualUserDisposition,
772
- catalogue,
773
- intents,
774
- goals,
775
- memory,
776
- seed,
777
- agents,
778
- target: personaVirtualUserTarget(target, {
779
- model,
780
- agents: agents.map((agent) => agent.name),
781
- }),
782
- // \`AgentRunnerService.run\` IS the engine's \`ActorLLM\` — same params,
783
- // same result — so a virtual user thinks through the same runner every
784
- // agent in the app does, provider quirks and all.
785
- llm: (params) => agentRunner.run(params),
786
- model,
787
- budget: {
788
- steps: budget?.steps,
789
- mutations: budget?.mutations,
790
- duration: budget?.durationMs,
791
- },
792
- })
793
-
794
- await virtualUserRunStore.complete(runId, {
795
- findings: result.findings,
796
- tally: result.tally,
797
- memory: result.memory,
798
- stoppedBy: result.stoppedBy ?? null,
799
- intents: result.intents,
800
- steps: result.steps,
801
- })
802
-
803
- return { findings: result.findings.length }
804
- } catch (error) {
805
- // A crashed run and a run that found nothing are different states, and the
806
- // record is the only place that distinction survives — leaving it at
807
- // 'running' forever is what \`fail\` exists to prevent.
808
- const message = error instanceof Error ? error.message : String(error)
809
- logger.error(\`Virtual user run \${runId} (\${personaId}) failed: \${message}\`)
810
- await virtualUserRunStore.fail(runId, message)
811
- throw error
812
- }
813
- },
568
+ input
569
+ ) =>
570
+ executeRun({
571
+ runStore: virtualUserRunStore,
572
+ metaService,
573
+ agentRunner,
574
+ variables,
575
+ logger,
576
+ personas: personaConfigs,
577
+ createPersonas,
578
+ ...input,
579
+ }),
814
580
  })
815
581
  `;
816
582
  return { schemas, functions };