@jevitate/cli 0.1.0 → 0.2.0

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 (375) hide show
  1. package/README.md +23 -0
  2. package/dist/ai-cli.js +40 -30
  3. package/dist/ai-cli.js.map +1 -1
  4. package/dist/author-journey-cli.d.ts +4 -0
  5. package/dist/author-journey-cli.js +109 -0
  6. package/dist/author-journey-cli.js.map +1 -0
  7. package/dist/bin.js +58077 -22454
  8. package/dist/bin.js.map +1 -1
  9. package/dist/browser-run-options.d.ts +58 -0
  10. package/dist/browser-run-options.js +106 -0
  11. package/dist/browser-run-options.js.map +1 -0
  12. package/dist/build-info.generated.d.ts +2 -0
  13. package/dist/build-info.generated.js +10 -0
  14. package/dist/build-info.generated.js.map +1 -0
  15. package/dist/check-api.d.ts +5 -0
  16. package/dist/check-api.js +233 -0
  17. package/dist/check-api.js.map +1 -0
  18. package/dist/check-budget.d.ts +26 -0
  19. package/dist/check-budget.js +89 -0
  20. package/dist/check-budget.js.map +1 -0
  21. package/dist/check-cli.d.ts +31 -0
  22. package/dist/check-cli.js +109 -0
  23. package/dist/check-cli.js.map +1 -0
  24. package/dist/check-execute.d.ts +25 -0
  25. package/dist/check-execute.js +385 -0
  26. package/dist/check-execute.js.map +1 -0
  27. package/dist/check-plan.d.ts +113 -0
  28. package/dist/check-plan.js +448 -0
  29. package/dist/check-plan.js.map +1 -0
  30. package/dist/check-suite.d.ts +171 -0
  31. package/dist/check-suite.js +515 -0
  32. package/dist/check-suite.js.map +1 -0
  33. package/dist/check-types.d.ts +181 -0
  34. package/dist/check-types.js +35 -0
  35. package/dist/check-types.js.map +1 -0
  36. package/dist/cli-args.d.ts +26 -0
  37. package/dist/cli-args.js +50 -0
  38. package/dist/cli-args.js.map +1 -0
  39. package/dist/cli-output.d.ts +102 -0
  40. package/dist/cli-output.js +516 -0
  41. package/dist/cli-output.js.map +1 -0
  42. package/dist/cli-reference.d.ts +3 -0
  43. package/dist/cli-reference.js +81 -0
  44. package/dist/cli-reference.js.map +1 -0
  45. package/dist/cli-refusal.d.ts +12 -0
  46. package/dist/cli-refusal.js +52 -0
  47. package/dist/cli-refusal.js.map +1 -0
  48. package/dist/cli-shared.d.ts +330 -0
  49. package/dist/cli-shared.js +492 -0
  50. package/dist/cli-shared.js.map +1 -0
  51. package/dist/cli-surface.d.ts +33 -0
  52. package/dist/cli-surface.js +48 -0
  53. package/dist/cli-surface.js.map +1 -0
  54. package/dist/conversation-options.d.ts +15 -0
  55. package/dist/conversation-options.js +10 -0
  56. package/dist/conversation-options.js.map +1 -0
  57. package/dist/credentials-file.d.ts +15 -0
  58. package/dist/credentials-file.js +56 -0
  59. package/dist/credentials-file.js.map +1 -0
  60. package/dist/data-dir.d.ts +1 -1
  61. package/dist/data-dir.js +1 -1
  62. package/dist/defect-evidence.d.ts +141 -0
  63. package/dist/defect-evidence.js +502 -0
  64. package/dist/defect-evidence.js.map +1 -0
  65. package/dist/demo-aspect-api.d.ts +209 -0
  66. package/dist/demo-aspect-api.js +397 -0
  67. package/dist/demo-aspect-api.js.map +1 -0
  68. package/dist/demo-aspect-cli.d.ts +3 -0
  69. package/dist/demo-aspect-cli.js +241 -0
  70. package/dist/demo-aspect-cli.js.map +1 -0
  71. package/dist/demo-capture.d.ts +108 -0
  72. package/dist/demo-capture.js +438 -0
  73. package/dist/demo-capture.js.map +1 -0
  74. package/dist/engine.d.ts +35 -0
  75. package/dist/engine.js +25 -0
  76. package/dist/engine.js.map +1 -0
  77. package/dist/environments.d.ts +155 -0
  78. package/dist/environments.js +364 -0
  79. package/dist/environments.js.map +1 -0
  80. package/dist/exit-codes.d.ts +46 -0
  81. package/dist/exit-codes.js +91 -0
  82. package/dist/exit-codes.js.map +1 -0
  83. package/dist/explore-adversarial.d.ts +137 -0
  84. package/dist/explore-adversarial.js +218 -0
  85. package/dist/explore-adversarial.js.map +1 -0
  86. package/dist/explore-api.d.ts +7 -187
  87. package/dist/explore-api.js +7 -301
  88. package/dist/explore-api.js.map +1 -1
  89. package/dist/explore-author.d.ts +79 -0
  90. package/dist/explore-author.js +86 -0
  91. package/dist/explore-author.js.map +1 -0
  92. package/dist/explore-cli.d.ts +7 -0
  93. package/dist/explore-cli.js +963 -0
  94. package/dist/explore-cli.js.map +1 -0
  95. package/dist/explore-coverage.d.ts +158 -0
  96. package/dist/explore-coverage.js +240 -0
  97. package/dist/explore-coverage.js.map +1 -0
  98. package/dist/explore-feature.d.ts +127 -0
  99. package/dist/explore-feature.js +220 -0
  100. package/dist/explore-feature.js.map +1 -0
  101. package/dist/explore-goal.d.ts +253 -0
  102. package/dist/explore-goal.js +374 -0
  103. package/dist/explore-goal.js.map +1 -0
  104. package/dist/explore-shared.d.ts +152 -0
  105. package/dist/explore-shared.js +129 -0
  106. package/dist/explore-shared.js.map +1 -0
  107. package/dist/explore-specs.d.ts +53 -0
  108. package/dist/explore-specs.js +333 -0
  109. package/dist/explore-specs.js.map +1 -0
  110. package/dist/findings-filing.d.ts +64 -0
  111. package/dist/findings-filing.js +108 -0
  112. package/dist/findings-filing.js.map +1 -0
  113. package/dist/fixture-auth.d.ts +52 -0
  114. package/dist/fixture-auth.js +82 -0
  115. package/dist/fixture-auth.js.map +1 -0
  116. package/dist/fixture-cli.d.ts +70 -0
  117. package/dist/fixture-cli.js +178 -0
  118. package/dist/fixture-cli.js.map +1 -0
  119. package/dist/github-issue-filer.d.ts +62 -0
  120. package/dist/github-issue-filer.js +188 -0
  121. package/dist/github-issue-filer.js.map +1 -0
  122. package/dist/host-health-run.d.ts +24 -0
  123. package/dist/host-health-run.js +27 -0
  124. package/dist/host-health-run.js.map +1 -0
  125. package/dist/http-5xx-outcome.d.ts +6 -0
  126. package/dist/http-5xx-outcome.js +39 -0
  127. package/dist/http-5xx-outcome.js.map +1 -0
  128. package/dist/inbox-cli.d.ts +7 -0
  129. package/dist/inbox-cli.js +197 -0
  130. package/dist/inbox-cli.js.map +1 -0
  131. package/dist/index.d.ts +1 -0
  132. package/dist/index.js +1 -0
  133. package/dist/index.js.map +1 -1
  134. package/dist/init-cli.d.ts +4 -0
  135. package/dist/init-cli.js +127 -0
  136. package/dist/init-cli.js.map +1 -0
  137. package/dist/init-keys.d.ts +19 -2
  138. package/dist/init-keys.js +15 -4
  139. package/dist/init-keys.js.map +1 -1
  140. package/dist/init-next-steps.d.ts +35 -0
  141. package/dist/init-next-steps.js +79 -0
  142. package/dist/init-next-steps.js.map +1 -0
  143. package/dist/invariants-file.d.ts +34 -0
  144. package/dist/invariants-file.js +68 -0
  145. package/dist/invariants-file.js.map +1 -0
  146. package/dist/invariants-validate.d.ts +30 -0
  147. package/dist/invariants-validate.js +105 -0
  148. package/dist/invariants-validate.js.map +1 -0
  149. package/dist/journey-annotate-api.d.ts +76 -0
  150. package/dist/journey-annotate-api.js +256 -0
  151. package/dist/journey-annotate-api.js.map +1 -0
  152. package/dist/journey-annotate-cli.d.ts +9 -0
  153. package/dist/journey-annotate-cli.js +179 -0
  154. package/dist/journey-annotate-cli.js.map +1 -0
  155. package/dist/journey-api.d.ts +79 -2
  156. package/dist/journey-api.js +136 -19
  157. package/dist/journey-api.js.map +1 -1
  158. package/dist/journey-cli.d.ts +4 -0
  159. package/dist/journey-cli.js +370 -0
  160. package/dist/journey-cli.js.map +1 -0
  161. package/dist/journey-demo-api.d.ts +112 -0
  162. package/dist/journey-demo-api.js +392 -0
  163. package/dist/journey-demo-api.js.map +1 -0
  164. package/dist/journey-demo-cli.d.ts +10 -0
  165. package/dist/journey-demo-cli.js +155 -0
  166. package/dist/journey-demo-cli.js.map +1 -0
  167. package/dist/kill-signal.d.ts +193 -0
  168. package/dist/kill-signal.js +284 -0
  169. package/dist/kill-signal.js.map +1 -0
  170. package/dist/launch-armed.d.ts +37 -0
  171. package/dist/launch-armed.js +31 -0
  172. package/dist/launch-armed.js.map +1 -0
  173. package/dist/ledger-api.d.ts +106 -0
  174. package/dist/ledger-api.js +237 -0
  175. package/dist/ledger-api.js.map +1 -0
  176. package/dist/ledger-cli.d.ts +18 -0
  177. package/dist/ledger-cli.js +107 -0
  178. package/dist/ledger-cli.js.map +1 -0
  179. package/dist/load-api.d.ts +29 -11
  180. package/dist/load-api.js +23 -15
  181. package/dist/load-api.js.map +1 -1
  182. package/dist/load-cli.d.ts +4 -0
  183. package/dist/load-cli.js +97 -0
  184. package/dist/load-cli.js.map +1 -0
  185. package/dist/log-correlation.d.ts +147 -0
  186. package/dist/log-correlation.js +325 -0
  187. package/dist/log-correlation.js.map +1 -0
  188. package/dist/log-lines.d.ts +131 -0
  189. package/dist/log-lines.js +389 -0
  190. package/dist/log-lines.js.map +1 -0
  191. package/dist/log-sources.d.ts +71 -0
  192. package/dist/log-sources.js +235 -0
  193. package/dist/log-sources.js.map +1 -0
  194. package/dist/logs-cli.d.ts +3 -0
  195. package/dist/logs-cli.js +51 -0
  196. package/dist/logs-cli.js.map +1 -0
  197. package/dist/logs-retention.d.ts +28 -0
  198. package/dist/logs-retention.js +84 -0
  199. package/dist/logs-retention.js.map +1 -0
  200. package/dist/mcp-api.d.ts +89 -1
  201. package/dist/mcp-api.js +472 -18
  202. package/dist/mcp-api.js.map +1 -1
  203. package/dist/mcp-args.d.ts +38 -0
  204. package/dist/mcp-args.js +131 -0
  205. package/dist/mcp-args.js.map +1 -0
  206. package/dist/mcp-cli-bridge.d.ts +34 -0
  207. package/dist/mcp-cli-bridge.js +62 -0
  208. package/dist/mcp-cli-bridge.js.map +1 -0
  209. package/dist/mcp-cli-runner.d.ts +29 -0
  210. package/dist/mcp-cli-runner.js +44 -0
  211. package/dist/mcp-cli-runner.js.map +1 -0
  212. package/dist/mcp-cli-tools.d.ts +125 -0
  213. package/dist/mcp-cli-tools.js +680 -0
  214. package/dist/mcp-cli-tools.js.map +1 -0
  215. package/dist/mcp-paths.d.ts +28 -0
  216. package/dist/mcp-paths.js +76 -0
  217. package/dist/mcp-paths.js.map +1 -0
  218. package/dist/mission-actors.d.ts +40 -0
  219. package/dist/mission-actors.js +72 -0
  220. package/dist/mission-actors.js.map +1 -0
  221. package/dist/mission-api.d.ts +35 -0
  222. package/dist/mission-api.js +58 -2
  223. package/dist/mission-api.js.map +1 -1
  224. package/dist/mission-cli.d.ts +4 -0
  225. package/dist/mission-cli.js +247 -0
  226. package/dist/mission-cli.js.map +1 -0
  227. package/dist/mission-exit.d.ts +22 -0
  228. package/dist/mission-exit.js +26 -0
  229. package/dist/mission-exit.js.map +1 -0
  230. package/dist/mission-fixtures.d.ts +225 -0
  231. package/dist/mission-fixtures.js +828 -0
  232. package/dist/mission-fixtures.js.map +1 -0
  233. package/dist/mission-journal.d.ts +60 -0
  234. package/dist/mission-journal.js +115 -0
  235. package/dist/mission-journal.js.map +1 -0
  236. package/dist/mission-queue-cli.d.ts +10 -0
  237. package/dist/mission-queue-cli.js +198 -0
  238. package/dist/mission-queue-cli.js.map +1 -0
  239. package/dist/mission-queue-runner.d.ts +126 -0
  240. package/dist/mission-queue-runner.js +397 -0
  241. package/dist/mission-queue-runner.js.map +1 -0
  242. package/dist/multi-run-cli.d.ts +37 -0
  243. package/dist/multi-run-cli.js +110 -0
  244. package/dist/multi-run-cli.js.map +1 -0
  245. package/dist/multi-run.d.ts +339 -0
  246. package/dist/multi-run.js +619 -0
  247. package/dist/multi-run.js.map +1 -0
  248. package/dist/openrouter-call.d.ts +22 -0
  249. package/dist/openrouter-call.js +63 -0
  250. package/dist/openrouter-call.js.map +1 -0
  251. package/dist/profile-cli.d.ts +4 -0
  252. package/dist/profile-cli.js +57 -0
  253. package/dist/profile-cli.js.map +1 -0
  254. package/dist/program.d.ts +3 -75
  255. package/dist/program.js +119 -1921
  256. package/dist/program.js.map +1 -1
  257. package/dist/project-dir.d.ts +96 -0
  258. package/dist/project-dir.js +220 -0
  259. package/dist/project-dir.js.map +1 -0
  260. package/dist/record-api.d.ts +10 -6
  261. package/dist/record-api.js +39 -28
  262. package/dist/record-api.js.map +1 -1
  263. package/dist/record-cli.d.ts +4 -0
  264. package/dist/record-cli.js +64 -0
  265. package/dist/record-cli.js.map +1 -0
  266. package/dist/recording-cli.d.ts +4 -0
  267. package/dist/recording-cli.js +248 -0
  268. package/dist/recording-cli.js.map +1 -0
  269. package/dist/regression-api.d.ts +101 -0
  270. package/dist/regression-api.js +384 -5
  271. package/dist/regression-api.js.map +1 -1
  272. package/dist/regression-cli.d.ts +4 -0
  273. package/dist/regression-cli.js +232 -0
  274. package/dist/regression-cli.js.map +1 -0
  275. package/dist/report-api.d.ts +146 -0
  276. package/dist/report-api.js +430 -0
  277. package/dist/report-api.js.map +1 -0
  278. package/dist/report-cli.d.ts +12 -0
  279. package/dist/report-cli.js +147 -0
  280. package/dist/report-cli.js.map +1 -0
  281. package/dist/result-schema.d.ts +22 -0
  282. package/dist/result-schema.js +28 -0
  283. package/dist/result-schema.js.map +1 -0
  284. package/dist/run-index.d.ts +29 -0
  285. package/dist/run-index.js +70 -0
  286. package/dist/run-index.js.map +1 -0
  287. package/dist/run-screenshots.d.ts +123 -0
  288. package/dist/run-screenshots.js +229 -0
  289. package/dist/run-screenshots.js.map +1 -0
  290. package/dist/runtime-env.d.ts +8 -0
  291. package/dist/runtime-env.js +22 -0
  292. package/dist/runtime-env.js.map +1 -0
  293. package/dist/secret-args.d.ts +23 -0
  294. package/dist/secret-args.js +45 -0
  295. package/dist/secret-args.js.map +1 -0
  296. package/dist/serve-cli.d.ts +4 -0
  297. package/dist/serve-cli.js +95 -0
  298. package/dist/serve-cli.js.map +1 -0
  299. package/dist/server-log-verify.d.ts +34 -0
  300. package/dist/server-log-verify.js +115 -0
  301. package/dist/server-log-verify.js.map +1 -0
  302. package/dist/session-check.d.ts +14 -0
  303. package/dist/session-check.js +44 -0
  304. package/dist/session-check.js.map +1 -0
  305. package/dist/site-cli.d.ts +4 -0
  306. package/dist/site-cli.js +127 -0
  307. package/dist/site-cli.js.map +1 -0
  308. package/dist/site-gate-cli.d.ts +28 -0
  309. package/dist/site-gate-cli.js +58 -0
  310. package/dist/site-gate-cli.js.map +1 -0
  311. package/dist/source-api.js +18 -3
  312. package/dist/source-api.js.map +1 -1
  313. package/dist/source-cli.d.ts +4 -0
  314. package/dist/source-cli.js +284 -0
  315. package/dist/source-cli.js.map +1 -0
  316. package/dist/source-run-api.d.ts +27 -1
  317. package/dist/source-run-api.js +22 -11
  318. package/dist/source-run-api.js.map +1 -1
  319. package/dist/storage-state-snapshot.d.ts +50 -0
  320. package/dist/storage-state-snapshot.js +87 -0
  321. package/dist/storage-state-snapshot.js.map +1 -0
  322. package/dist/suite-explore-options.d.ts +289 -0
  323. package/dist/suite-explore-options.js +140 -0
  324. package/dist/suite-explore-options.js.map +1 -0
  325. package/dist/target-config.d.ts +109 -0
  326. package/dist/target-config.js +216 -0
  327. package/dist/target-config.js.map +1 -0
  328. package/dist/transcript-file.d.ts +12 -0
  329. package/dist/transcript-file.js +23 -0
  330. package/dist/transcript-file.js.map +1 -0
  331. package/dist/ui-api.d.ts +3 -0
  332. package/dist/ui-api.js +7 -1
  333. package/dist/ui-api.js.map +1 -1
  334. package/dist/usability-capture.d.ts +43 -0
  335. package/dist/usability-capture.js +326 -0
  336. package/dist/usability-capture.js.map +1 -0
  337. package/dist/usage-config.d.ts +36 -0
  338. package/dist/usage-config.js +114 -0
  339. package/dist/usage-config.js.map +1 -0
  340. package/dist/ux-api.d.ts +3 -75
  341. package/dist/ux-api.js +3 -220
  342. package/dist/ux-api.js.map +1 -1
  343. package/dist/ux-cli.d.ts +4 -0
  344. package/dist/ux-cli.js +102 -0
  345. package/dist/ux-cli.js.map +1 -0
  346. package/dist/ux-config.d.ts +29 -0
  347. package/dist/ux-config.js +103 -0
  348. package/dist/ux-config.js.map +1 -0
  349. package/dist/ux-evidence.d.ts +54 -0
  350. package/dist/ux-evidence.js +197 -0
  351. package/dist/ux-evidence.js.map +1 -0
  352. package/dist/ux-review.d.ts +112 -0
  353. package/dist/ux-review.js +151 -0
  354. package/dist/ux-review.js.map +1 -0
  355. package/dist/ux-usability.d.ts +238 -0
  356. package/dist/ux-usability.js +494 -0
  357. package/dist/ux-usability.js.map +1 -0
  358. package/dist/verify-fix-api.d.ts +173 -0
  359. package/dist/verify-fix-api.js +454 -0
  360. package/dist/verify-fix-api.js.map +1 -0
  361. package/dist/verify-fix-cli.d.ts +4 -0
  362. package/dist/verify-fix-cli.js +110 -0
  363. package/dist/verify-fix-cli.js.map +1 -0
  364. package/package.json +19 -14
  365. package/skills/jevitate-ci-check/SKILL.md +77 -0
  366. package/skills/jevitate-demo/SKILL.md +61 -0
  367. package/skills/jevitate-explore/SKILL.md +88 -87
  368. package/skills/jevitate-getting-started/SKILL.md +88 -0
  369. package/skills/jevitate-load-test/SKILL.md +11 -2
  370. package/skills/jevitate-mission-scope/SKILL.md +19 -11
  371. package/skills/jevitate-record/SKILL.md +23 -5
  372. package/skills/jevitate-run-journey/SKILL.md +61 -17
  373. package/skills/jevitate-sources/SKILL.md +57 -14
  374. package/skills/jevitate-ux-review/SKILL.md +106 -16
  375. package/skills/jevitate-verify-fix/SKILL.md +76 -0
package/dist/mcp-api.js CHANGED
@@ -1,13 +1,31 @@
1
1
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { SiteGateRefusedError } from "@jevitate/runtime";
3
+ import { withSiteGate } from "./site-gate-cli.js";
2
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
5
  import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
4
- import { ALLOWED_TOOLS, FORBIDDEN_TOOLS, findCapabilities, queueExploration as facadeQueueExploration, aiGenerateText as facadeAiGenerateText, facadeListIncoming, facadeGetCommand, facadeGetThread, facadeQueueAction, facadeQueueRetrieval, facadeApproveAction, facadeCancelCommand, facadeGetSiteHealth, } from "@jevitate/mcp-facade";
6
+ import { ALLOWED_TOOLS, FORBIDDEN_TOOLS, findCapabilities, queueExploration as facadeQueueExploration, aiGenerateText as facadeAiGenerateText, facadeListIncoming, facadeGetCommand, facadeGetThread, facadeQueueAction, facadeQueueRetrieval, facadeApproveAction, facadeCancelCommand, facadeGetSiteHealth, isMissionResultId, isQueuedMissionId, missionResultFileName, missionStatus, parseResultOutcome, } from "@jevitate/mcp-facade";
5
7
  import { FsJourneyStore, JourneyRegistry } from "@jevitate/journey";
6
- import { FsMissionTargetStore, MissionTargetRegistry, FsMissionQueueStore, } from "@jevitate/missions";
8
+ import { FsMissionTargetStore, MissionTargetRegistry, FsMissionQueueStore, MISSION_STRATEGIES, QUEUED_SCREENSHOT_MODES, } from "@jevitate/missions";
7
9
  import { FsInboxStore } from "@jevitate/inbox";
8
- import { envCredentialStore, } from "@jevitate/ai-core";
9
- import { safeRunPolicy } from "@jevitate/domain";
10
- import { runJourneyProgrammatically } from "./journey-api.js";
10
+ import { ALL_CREDENTIAL_KEYS, envCredentialStore, } from "@jevitate/ai-core";
11
+ import { existsSync } from "node:fs";
12
+ import { access, readFile } from "node:fs/promises";
13
+ import { join } from "node:path";
14
+ import { ParamValidationError } from "@jevitate/journey";
15
+ import { safeRunPolicy as defaultRunPolicy } from "@jevitate/domain";
16
+ import { JourneyRequiresAuthError, UnknownJourneyError, runJourneyProgrammatically } from "./journey-api.js";
17
+ import { runVerifyFix } from "./verify-fix-api.js";
18
+ import { browserRunFromFlags, emulationFromFlags } from "./cli-shared.js";
19
+ import { environmentFromFlags, isEnvironmentError } from "./environments.js";
20
+ import { buildMissionFixtures, checkSetupRefs } from "./fixture-cli.js";
21
+ import { FixtureSetupError, FixtureSpecError, UnboundSetupRefError } from "./mission-fixtures.js";
22
+ import { parseScreenshotsArg } from "./run-screenshots.js";
23
+ import { makeExploreSelfHealer } from "./self-heal-adapter.js";
24
+ import { McpArgError, argErrorBody, optBool, optEnum, optInt, optPath, optRecordVideo, optScreenshots, optString, optStringArray, optStringMap, optViewport } from "./mcp-args.js";
25
+ import { defaultMcpPathRoots } from "./mcp-paths.js";
26
+ import { CLI_TOOL_SPECS, cliToolInputSchema, runCliTool } from "./mcp-cli-tools.js";
27
+ import { currentEngineInfo } from "./engine.js";
28
+ import { loadTargetsFile } from "./target-config.js";
11
29
  const ALLOWED = new Set(ALLOWED_TOOLS);
12
30
  const FORBIDDEN = new Set(FORBIDDEN_TOOLS);
13
31
  /**
@@ -33,9 +51,17 @@ function errorResult(value) {
33
51
  * SDK error (which can echo request internals verbatim) never surfaces a key
34
52
  * through the MCP result. The message returned to the caller is generic and
35
53
  * key-free; this is belt-and-braces on top of the gateway's outbound guard. */
54
+ /** #217: a persisted goal result's own ending (`result.goalOutcome`), when present. */
55
+ function goalOutcomeOf(file) {
56
+ const result = file.result;
57
+ return result !== null && typeof result === "object" && "goalOutcome" in result ? result.goalOutcome : undefined;
58
+ }
59
+ /** A model-backed option was asked for without usable gateways/keys (typed `setup_required`). */
60
+ class SetupRequired extends Error {
61
+ }
36
62
  function redactCredentials(message, store) {
37
63
  let out = message;
38
- for (const key of ["OPENROUTER_API_KEY", "TYPESAFE_API_KEY"]) {
64
+ for (const key of ALL_CREDENTIAL_KEYS) {
39
65
  const value = store.read(key);
40
66
  if (value)
41
67
  out = out.split(value).join("***REDACTED***");
@@ -57,7 +83,21 @@ export function buildMcpTools(deps) {
57
83
  const findCaps = deps.findCapabilities ??
58
84
  ((query) => findCapabilities(new JourneyRegistry(new FsJourneyStore(deps.journeysDir)), query));
59
85
  const runJourney = deps.runJourney ??
60
- ((id, params) => runJourneyProgrammatically({ dir: deps.journeysDir, id, params, policy: safeRunPolicy() }));
86
+ ((id, params, storageState, o) => withSiteGate(deps.sitePolicyDbPath, (siteGate) => runJourneyProgrammatically({
87
+ dir: deps.journeysDir,
88
+ id,
89
+ params,
90
+ policy: o?.policy ?? defaultRunPolicy(),
91
+ ...(storageState !== undefined ? { storageState } : {}),
92
+ ...(siteGate === undefined ? {} : { siteGate }),
93
+ ...(o?.selfHealer === undefined ? {} : { selfHealer: o.selfHealer }),
94
+ ...(o?.environment === undefined ? {} : { environment: o.environment }),
95
+ ...(o?.browser === undefined ? {} : { browser: o.browser }),
96
+ ...(o?.emulation === undefined ? {} : { emulation: o.emulation }),
97
+ ...(o?.screenshots === undefined ? {} : { screenshots: o.screenshots }),
98
+ ...(o?.fixtures === undefined ? {} : { fixtures: o.fixtures }),
99
+ })));
100
+ const pathRoots = deps.pathRoots ?? defaultMcpPathRoots();
61
101
  // queue_exploration: enqueue over the SAME promoted fs store `jevitate
62
102
  // mission target` writes. The store is built lazily inside the closure so
63
103
  // constructing the tool set never touches disk; a missing dir is a config
@@ -91,7 +131,7 @@ export function buildMcpTools(deps) {
91
131
  if (!deps.inboxDir) {
92
132
  throw new Error("inbox tools require inboxDir to be configured");
93
133
  }
94
- return new FsInboxStore(deps.inboxDir);
134
+ return new FsInboxStore(deps.inboxDir, deps.engine ?? currentEngineInfo());
95
135
  };
96
136
  function inboxHandler(fn) {
97
137
  return async (args) => {
@@ -131,19 +171,364 @@ export function buildMcpTools(deps) {
131
171
  },
132
172
  };
133
173
  const queueCommonRequired = ["run", "journey", "step", "reason", "agent"];
174
+ // get_mission_result: reads a persisted typed mission result by ID only (the artifact stem the
175
+ // CLI wrote) — never a caller-supplied path. The status carries the CLI exit code, and a run that
176
+ // itself broke (inconclusive/crashed) is an MCP error result, so it can never read as a pass.
177
+ //
178
+ // The id is either a result stem (`explore-<stamp>`, `usability-<stamp>.recording`, …) or a
179
+ // `queue_exploration` missionId (#117): the latter resolves through the mission's queue record —
180
+ // `queued`/`running` is reported as such (not an error, not a result), `failed` as an error, and
181
+ // `done` reads the result its drain wrote. Both shapes are closed regexes; nothing caller-shaped
182
+ // ever reaches a path.
183
+ const resolveResultId = async (id, tool) => {
184
+ if (isMissionResultId(id))
185
+ return { resultId: id };
186
+ if (!isQueuedMissionId(id)) {
187
+ return {
188
+ response: errorResult({
189
+ error: "invalid_args",
190
+ message: `${tool} requires a mission result 'id' (explore-|coverage-|exploratory-|adversarial-|feature-|usability-<stamp>) or a queue_exploration missionId`,
191
+ }),
192
+ };
193
+ }
194
+ if (!deps.missionQueueDir) {
195
+ return { response: errorResult({ error: "not_configured", message: `${tool} requires missionQueueDir to resolve a missionId` }) };
196
+ }
197
+ let mission;
198
+ try {
199
+ mission = await new FsMissionQueueStore(deps.missionQueueDir).get(id);
200
+ }
201
+ catch {
202
+ return { response: errorResult({ error: "corrupt_mission", id }) };
203
+ }
204
+ if (mission === null)
205
+ return { response: errorResult({ error: "not_found", id }) };
206
+ if (mission.status === "queued" || mission.status === "running") {
207
+ // Not finished: neither a pass nor a failure — the agent polls again later.
208
+ const pending = {
209
+ id,
210
+ missionId: id,
211
+ status: mission.status,
212
+ pending: true,
213
+ enqueuedAtIso: mission.enqueuedAtIso,
214
+ ...(mission.startedAtIso === undefined ? {} : { startedAtIso: mission.startedAtIso }),
215
+ };
216
+ return { response: jsonResult(pending), pending };
217
+ }
218
+ if (mission.status === "failed") {
219
+ return { response: errorResult({ id, missionId: id, status: "failed", isError: true, error: mission.error ?? "mission failed" }) };
220
+ }
221
+ if (!isMissionResultId(mission.resultId))
222
+ return { response: errorResult({ error: "corrupt_mission", id }) };
223
+ return { resultId: mission.resultId, missionId: id };
224
+ };
225
+ /** The dirs a result may live in: recordings, and (usability reviews) the UX reports dir. */
226
+ const resultPathsFor = (resultId) => {
227
+ const file = missionResultFileName(resultId);
228
+ const dirs = deps.resultDirsFor?.(resultId) ?? [deps.recordingsDir, ...(resultId.startsWith("usability-") ? [deps.uxReportsDir] : [])];
229
+ return dirs.filter((d) => d !== undefined).map((d) => join(d, file));
230
+ };
231
+ const getMissionResult = async (args) => {
232
+ if (!deps.recordingsDir) {
233
+ return errorResult({ error: "not_configured", message: "get_mission_result requires recordingsDir" });
234
+ }
235
+ const ref = await resolveResultId(args.id, "get_mission_result");
236
+ if ("response" in ref)
237
+ return ref.response;
238
+ const ids = { id: args.id, ...(ref.missionId === undefined ? {} : { missionId: ref.missionId, resultId: ref.resultId }) };
239
+ let raw;
240
+ for (const path of resultPathsFor(ref.resultId)) {
241
+ try {
242
+ raw = await readFile(path, "utf8");
243
+ break;
244
+ }
245
+ catch {
246
+ // Try the next candidate dir.
247
+ }
248
+ }
249
+ if (raw === undefined) {
250
+ return errorResult({ error: "not_found", ...ids });
251
+ }
252
+ let parsed;
253
+ try {
254
+ parsed = JSON.parse(raw);
255
+ }
256
+ catch {
257
+ return errorResult({ error: "corrupt_result", ...ids });
258
+ }
259
+ // #217: `missionOutcome` is canonical; a goal run's own ending rides beside it as `goalOutcome`.
260
+ const parsedOutcome = parsed !== null && typeof parsed === "object" && "missionOutcome" in parsed
261
+ ? parseResultOutcome(parsed.missionOutcome, goalOutcomeOf(parsed))
262
+ : null;
263
+ if (parsedOutcome === null) {
264
+ return errorResult({ error: "corrupt_result", ...ids });
265
+ }
266
+ const status = {
267
+ ...missionStatus(parsedOutcome.outcome),
268
+ ...(parsedOutcome.goalOutcome === undefined ? {} : { goalOutcome: parsedOutcome.goalOutcome }),
269
+ };
270
+ const result = parsed.result ?? null;
271
+ // An adversarial run's coverage is surfaced next to the status: an `inconclusive` run says what
272
+ // it did and did not exercise, so an agent can tell "found nothing" from "tried nothing".
273
+ const coverage = result !== null && typeof result === "object" && "coverage" in result ? result.coverage : undefined;
274
+ const body = { ...ids, ...status, ...(coverage === undefined ? {} : { coverage }), result };
275
+ return status.isError ? errorResult(body) : jsonResult(body);
276
+ };
277
+ // verify_fix: replays a persisted finding by (result id, fingerprint) — never a caller-supplied
278
+ // recording or path (invariant #5 analogue). Passes only if the defect signal is absent; an
279
+ // unreachable replay is `inconclusive` and returned as an error result, never as "fixed".
280
+ // #142 follow-up: a `cmd:` server-log source may only be re-run when the OPERATOR opted in for
281
+ // this origin in ~/.jevitate/targets.json (`allowLogCmd: true`) — never via an MCP argument.
282
+ const verifyFixImpl = deps.verifyFix ??
283
+ ((a) => runVerifyFix({
284
+ targets: loadTargetsFile(deps.targetsConfigPath),
285
+ resultPath: a.resultPath,
286
+ fingerprint: a.fingerprint,
287
+ ...(a.storageState === undefined ? {} : { storageState: a.storageState }),
288
+ ...(a.browser === undefined ? {} : { browser: a.browser }),
289
+ ...(a.replays === undefined ? {} : { replays: a.replays }),
290
+ ...(a.invariantFiles === undefined ? {} : { invariantFiles: a.invariantFiles }),
291
+ ...(a.fixtureFlags === undefined ? {} : { fixtureFlags: a.fixtureFlags }),
292
+ ...(a.emulation === undefined ? {} : { emulation: a.emulation }),
293
+ ...(a.allowEmulationOverride === undefined ? {} : { allowEmulationOverride: a.allowEmulationOverride }),
294
+ ...(a.screenshots === undefined ? {} : { screenshots: a.screenshots }),
295
+ }));
296
+ /** #255: verify_fix's CLI-parity replay options, validated like `verify-fix`'s flags (typed, before any browser). */
297
+ const verifyFixOptions = (args) => {
298
+ const storageState = optPath(args, "storageState", pathRoots, { session: true });
299
+ if (storageState !== undefined && !existsSync(storageState))
300
+ throw new McpArgError(`storage state not found: ${storageState}`);
301
+ const replays = optInt(args, "replays", 1);
302
+ const invariantFiles = (optStringArray(args, "invariants") ?? []).map((f, i) => optPath({ [`invariants[${i}]`]: f }, `invariants[${i}]`, pathRoots));
303
+ const fixtures = optPath(args, "fixtures", pathRoots);
304
+ const allowEmulationOverride = optBool(args, "allowEmulationOverride");
305
+ const headed = optBool(args, "headed");
306
+ const slowMo = optInt(args, "slowMo", 0);
307
+ const recordVideo = optRecordVideo(args, pathRoots);
308
+ const shots = optScreenshots(args, pathRoots);
309
+ const viewport = optViewport(args);
310
+ const device = optString(args, "device");
311
+ try {
312
+ const browser = browserRunFromFlags({ browserArg: [], ...(headed === undefined ? {} : { headed }), ...(slowMo === undefined ? {} : { slowMo }), ...(recordVideo === undefined ? {} : { recordVideo }) });
313
+ const screenshots = parseScreenshotsArg(shots);
314
+ const emulation = emulationFromFlags({ ...(viewport === undefined ? {} : { viewport }), ...(device === undefined ? {} : { device }) });
315
+ return {
316
+ ...(storageState === undefined ? {} : { storageState }),
317
+ ...(replays === undefined ? {} : { replays }),
318
+ ...(invariantFiles.length === 0 ? {} : { invariantFiles }),
319
+ ...(fixtures === undefined ? {} : { fixtureFlags: { fixtures } }),
320
+ ...(allowEmulationOverride === undefined ? {} : { allowEmulationOverride }),
321
+ ...(browser === undefined ? {} : { browser }),
322
+ ...(screenshots === undefined ? {} : { screenshots }),
323
+ ...(emulation === undefined ? {} : { emulation }),
324
+ };
325
+ }
326
+ catch (err) {
327
+ throw new McpArgError(err instanceof Error ? err.message : String(err));
328
+ }
329
+ };
330
+ const verifyFixTool = async (args) => {
331
+ if (!deps.recordingsDir) {
332
+ return errorResult({ error: "not_configured", message: "verify_fix requires recordingsDir" });
333
+ }
334
+ if (!(isMissionResultId(args.id) || isQueuedMissionId(args.id)) ||
335
+ typeof args.fingerprint !== "string" ||
336
+ !/^[0-9a-f]{16}$/.test(args.fingerprint)) {
337
+ return errorResult({ error: "invalid_args", message: "verify_fix requires a mission result 'id' (or missionId) and a 16-hex 'fingerprint'" });
338
+ }
339
+ let options;
340
+ try {
341
+ options = verifyFixOptions(args);
342
+ }
343
+ catch (err) {
344
+ const body = argErrorBody(err);
345
+ if (body === undefined)
346
+ throw err;
347
+ return errorResult(body);
348
+ }
349
+ const ref = await resolveResultId(args.id, "verify_fix");
350
+ if ("response" in ref) {
351
+ // A mission still queued/running has nothing to verify yet: never a pass.
352
+ return ref.pending === undefined ? ref.response : errorResult({ error: "not_ready", ...ref.pending });
353
+ }
354
+ // The first candidate that exists (usability results may sit in the UX reports dir).
355
+ const candidates = resultPathsFor(ref.resultId);
356
+ let resultPath = candidates[0];
357
+ for (const path of candidates) {
358
+ try {
359
+ await access(path);
360
+ resultPath = path;
361
+ break;
362
+ }
363
+ catch {
364
+ // Try the next candidate dir.
365
+ }
366
+ }
367
+ try {
368
+ const report = await verifyFixImpl({ ...options, resultPath, fingerprint: args.fingerprint });
369
+ const body = {
370
+ id: args.id,
371
+ ...(ref.missionId === undefined ? {} : { missionId: ref.missionId, resultId: ref.resultId }),
372
+ status: report.verdict,
373
+ ...report,
374
+ };
375
+ // Neither is a pass: `inconclusive` proved nothing either way, `intermittent` (#74) means the
376
+ // signal fired on SOME but not all fresh-context replays — never trustworthy as "fixed".
377
+ return report.verdict === "inconclusive" || report.verdict === "intermittent" ? errorResult(body) : jsonResult(body);
378
+ }
379
+ catch (err) {
380
+ return errorResult({ error: "verify_fix_refused", message: err instanceof Error ? err.message : String(err) });
381
+ }
382
+ };
383
+ /**
384
+ * #255: `run_journey`'s CLI-parity options, resolved exactly as `journey run` resolves its flags
385
+ * (the same helpers: environmentFromFlags, browserRunFromFlags, parseScreenshotsArg,
386
+ * emulationFromFlags, buildMissionFixtures) — every refusal typed, before any browser opens.
387
+ */
388
+ const resolveJourneyRun = async (args) => {
389
+ // Params keep their historical leniency for a non-object (treated as none); a non-string VALUE is refused.
390
+ const params = args.params && typeof args.params === "object" && !Array.isArray(args.params) ? (optStringMap(args, "params") ?? {}) : {};
391
+ const envName = optString(args, "env");
392
+ const baseUrl = optString(args, "baseUrl");
393
+ const environment = environmentFromFlags({ ...(envName === undefined ? {} : { env: envName }), ...(baseUrl === undefined ? {} : { baseUrl }) }, { ...(deps.environmentsFile === undefined ? {} : { environmentsFile: deps.environmentsFile }), ...(deps.targetsConfigPath === undefined ? {} : { targetsFile: deps.targetsConfigPath }) });
394
+ // --storage-state wins; else the environment's own session (~/.jevitate/targets.json[<origin>]).
395
+ const storageState = optPath(args, "storageState", pathRoots, { session: true }) ?? environment?.storageState;
396
+ if (args.storageState !== undefined && storageState !== undefined && !existsSync(storageState))
397
+ throw new McpArgError(`storage state not found: ${storageState}`);
398
+ const headed = optBool(args, "headed");
399
+ const slowMo = optInt(args, "slowMo", 0);
400
+ const recordVideo = optRecordVideo(args, pathRoots);
401
+ let browser;
402
+ let screenshots;
403
+ let emulation;
404
+ try {
405
+ browser = browserRunFromFlags({ browserArg: [], ...(headed === undefined ? {} : { headed }), ...(slowMo === undefined ? {} : { slowMo }), ...(recordVideo === undefined ? {} : { recordVideo }) });
406
+ screenshots = parseScreenshotsArg(optScreenshots(args, pathRoots));
407
+ const viewport = optViewport(args);
408
+ const device = optString(args, "device");
409
+ emulation = emulationFromFlags({ ...(viewport === undefined ? {} : { viewport }), ...(device === undefined ? {} : { device }) });
410
+ }
411
+ catch (err) {
412
+ if (err instanceof McpArgError || argErrorBody(err) !== undefined)
413
+ throw err;
414
+ throw new McpArgError(err instanceof Error ? err.message : String(err));
415
+ }
416
+ const fixturesFile = optPath(args, "fixtures", pathRoots);
417
+ // The environment's hooks apply as on the CLI; they need --allow-shell-hooks, which MCP never sets.
418
+ const fixtureFlags = {
419
+ ...(fixturesFile === undefined ? {} : { fixtures: fixturesFile }),
420
+ ...(environment?.hooks?.before === undefined ? {} : { before: environment.hooks.before }),
421
+ ...(environment?.hooks?.after === undefined ? {} : { after: environment.hooks.after }),
422
+ };
423
+ const fixtures = (site) => {
424
+ const fx = buildMissionFixtures(fixtureFlags, {
425
+ allowlist: environment === undefined ? [site] : environment.allowedOrigins,
426
+ baseUrl: site,
427
+ ...(storageState !== undefined ? { storageState } : {}),
428
+ ...(environment?.fixtures === undefined ? {} : { targetFixtures: environment.fixtures }),
429
+ });
430
+ checkSetupRefs({ "--param": Object.values(params) }, fx);
431
+ return fx;
432
+ };
433
+ const selfHeal = optEnum(args, "selfHeal", ["fail-closed", "hybrid", "full"]) ?? "fail-closed";
434
+ const real = optBool(args, "real") ?? false;
435
+ const fakeAi = optBool(args, "fakeAi") ?? false;
436
+ let selfHealer;
437
+ let policy = defaultRunPolicy();
438
+ let usage;
439
+ if (selfHeal !== "fail-closed") {
440
+ if (deps.selfHealGateways === undefined)
441
+ throw new SetupRequired("run_journey selfHeal needs the model gateways, which this server was not given");
442
+ let judge;
443
+ let gen;
444
+ try {
445
+ ({ judge, gen, usage } = await deps.selfHealGateways({ real, fakeAi }));
446
+ }
447
+ catch (err) {
448
+ throw new SetupRequired(redactCredentials(err instanceof Error ? err.message : String(err), credentialStore));
449
+ }
450
+ selfHealer = makeExploreSelfHealer(judge, gen);
451
+ policy = { ...policy, selfHeal: { mode: selfHeal } };
452
+ }
453
+ return {
454
+ params,
455
+ ...(storageState === undefined ? {} : { storageState }),
456
+ options: {
457
+ policy,
458
+ fixtures,
459
+ ...(selfHealer === undefined ? {} : { selfHealer }),
460
+ ...(environment === undefined ? {} : { environment }),
461
+ ...(browser === undefined ? {} : { browser }),
462
+ ...(emulation === undefined ? {} : { emulation }),
463
+ ...(screenshots === undefined ? {} : { screenshots }),
464
+ },
465
+ ...(usage === undefined ? {} : { usage }),
466
+ };
467
+ };
134
468
  const wired = {
469
+ verify_fix: {
470
+ description: "Replay a finding's reproduction (by mission result id — or a finished queue_exploration missionId — + fingerprint) N times in fresh browsers (default 3) — `jevitate verify-fix`. status: fixed (signal absent on every replay) | still-reproduces | intermittent (fired on some but not all replays — never a pass) | inconclusive (replay could not reach the step — never a pass). " +
471
+ "#255 (same as the CLI flags): 'replays' (>= 1); 'recordVideo' (true or a directory: the before/after evidence pair — `evidence.before` is the run's own clip, `evidence.after` a captioned replay — plus videoPaths); 'screenshots'; 'headed'/'slowMo'; " +
472
+ "'storageState' (a path inside the project or ~/.jevitate, never a repo's .jevitate/; overrides the mission's session); 'viewport' {width,height} or 'device' (refused when it differs from the finding's recorded emulation unless 'allowEmulationOverride'); " +
473
+ "'invariants' (invariant file paths re-checking a declared-invariant defect); 'fixtures' (a fixtures file overriding the mission's saved one). Operator-only settings (shell hooks, cmd: log sources, re-sending paid/destructive hang writes) come from targets.json, never an argument.",
474
+ inputSchema: {
475
+ type: "object",
476
+ properties: {
477
+ id: { type: "string" },
478
+ fingerprint: { type: "string" },
479
+ replays: { type: "integer", minimum: 1 },
480
+ recordVideo: { type: ["boolean", "string"] },
481
+ screenshots: { type: ["boolean", "string"] },
482
+ headed: { type: "boolean" },
483
+ slowMo: { type: "integer", minimum: 0 },
484
+ storageState: { type: "string" },
485
+ viewport: { type: "object", properties: { width: { type: "integer" }, height: { type: "integer" } }, required: ["width", "height"] },
486
+ device: { type: "string" },
487
+ allowEmulationOverride: { type: "boolean" },
488
+ invariants: { type: "array", items: { type: "string" } },
489
+ fixtures: { type: "string" },
490
+ },
491
+ required: ["id", "fingerprint"],
492
+ },
493
+ handler: verifyFixTool,
494
+ },
495
+ get_mission_result: {
496
+ description: "Read a mission's TYPED result by id: a result stem (explore-|coverage-|exploratory-|adversarial-|feature-|usability-<stamp>, e.g. adversarial-2026-09-23T00-00-00-000Z) or a queue_exploration missionId. A queued mission reports status queued | running (pending: true — poll again; `jevitate mission run` drains the queue) or failed (an error: it could not run). A finished one: status is clean | defects-found | hang | intermittent | inconclusive | crashed, with the matching CLI exit code. A broken run (inconclusive/crashed) is returned as an error result — never a pass. An adversarial result carries `coverage` (target controls exercised/total, forms submitted, strategies applied vs found nothing, out-of-scope steps): a run below its coverage thresholds is inconclusive, never clean.",
497
+ inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
498
+ handler: getMissionResult,
499
+ },
135
500
  find_capabilities: {
136
501
  description: "Find promoted Journeys (capabilities) whose metadata matches a query.",
137
502
  inputSchema: { type: "object", properties: { query: { type: "string" } }, required: ["query"] },
138
503
  handler: async (args) => jsonResult(await findCaps(typeof args.query === "string" ? args.query : "")),
139
504
  },
140
505
  run_journey: {
141
- description: "Run a PUBLISHED Journey by id with string params (fail-closed policy). Never accepts inline steps.",
506
+ description: "Run a PUBLISHED Journey by id with string params (fail-closed policy) — `jevitate journey run`. Never accepts inline steps. " +
507
+ "Optional 'storageState': a PATH (on the machine running this MCP server; inside the project or ~/.jevitate, never a repo's .jevitate/) to a Playwright storageState " +
508
+ "JSON file, for a Journey authored behind a login (#118) — the file's contents are read only by the " +
509
+ "server's own browser, never returned or logged. A Journey that declares metadata.requiresAuth refuses " +
510
+ "with a clear error when no storageState is given. #255 (same as the CLI flags): 'env' (a named environment in .jevitate/environments.json; its session from ~/.jevitate/targets.json) and/or 'baseUrl'; " +
511
+ "'headed' (needs a display) and 'slowMo'; 'recordVideo' (true, or a directory) → videoPaths; 'screenshots' (true | screens | steps | mode:<dir> | <dir>) → screenshotPaths; " +
512
+ "'viewport' {width,height} or 'device' (mutually exclusive); 'fixtures' (a fixtures JSON path: setup before, restore after; an environment's shell hooks are never run over MCP — they refuse as the CLI does without --allow-shell-hooks); " +
513
+ "'selfHeal' fail-closed (default) | hybrid | full with 'real' or 'fakeAi' (a write step never self-heals). A site-policy refusal is {error: throttled, retryAfter}.",
142
514
  inputSchema: {
143
515
  type: "object",
144
516
  properties: {
145
517
  id: { type: "string" },
146
518
  params: { type: "object", additionalProperties: { type: "string" } },
519
+ storageState: { type: "string" },
520
+ env: { type: "string" },
521
+ baseUrl: { type: "string" },
522
+ headed: { type: "boolean" },
523
+ slowMo: { type: "integer", minimum: 0 },
524
+ recordVideo: { type: ["boolean", "string"] },
525
+ screenshots: { type: ["boolean", "string"] },
526
+ viewport: { type: "object", properties: { width: { type: "integer" }, height: { type: "integer" } }, required: ["width", "height"] },
527
+ device: { type: "string" },
528
+ fixtures: { type: "string" },
529
+ selfHeal: { type: "string", enum: ["fail-closed", "hybrid", "full"] },
530
+ real: { type: "boolean" },
531
+ fakeAi: { type: "boolean" },
147
532
  },
148
533
  required: ["id"],
149
534
  },
@@ -151,16 +536,49 @@ export function buildMcpTools(deps) {
151
536
  if (typeof args.id !== "string" || args.id.length === 0) {
152
537
  return errorResult({ error: "invalid_args", message: "run_journey requires a non-empty string 'id'" });
153
538
  }
154
- // Invariant #5: only id + params are threaded through — any inline
155
- // `steps`/`recording` in the arguments is deliberately ignored.
156
- const params = args.params && typeof args.params === "object" && !Array.isArray(args.params)
157
- ? args.params
158
- : {};
159
- return jsonResult(await runJourney(args.id, params));
539
+ // Invariant #5: only id + params (+ a storageState PATH and the run options below) are
540
+ // threaded through — any inline `steps`/`recording` in the arguments is deliberately ignored.
541
+ let resolved;
542
+ try {
543
+ resolved = await resolveJourneyRun(args);
544
+ }
545
+ catch (err) {
546
+ const body = argErrorBody(err) ?? (isEnvironmentError(err) ? { error: "invalid_args", code: err.code, message: err.message } : undefined);
547
+ if (body !== undefined)
548
+ return errorResult(body);
549
+ if (err instanceof SetupRequired)
550
+ return errorResult({ error: "setup_required", message: err.message });
551
+ throw err;
552
+ }
553
+ try {
554
+ const result = await runJourney(args.id, resolved.params, resolved.storageState, resolved.options);
555
+ // #163: a self-healing run's model usage lands on its result, as on the CLI.
556
+ return jsonResult(resolved.usage === undefined || result === null || typeof result !== "object" ? result : { ...result, usage: resolved.usage.snapshot() });
557
+ }
558
+ catch (err) {
559
+ // A site-policy refusal (throttle, budget, quiet hours) is an answer the agent acts on — when to retry.
560
+ if (err instanceof SiteGateRefusedError) {
561
+ return errorResult({ error: "throttled", reason: err.reason, retryAfter: err.retryAfter, message: err.message });
562
+ }
563
+ if (err instanceof UnknownJourneyError)
564
+ return errorResult({ error: "not_found", id: args.id, message: err.message });
565
+ if (err instanceof JourneyRequiresAuthError)
566
+ return errorResult({ error: "invalid_args", code: "E_JOURNEY_REQUIRES_AUTH", message: err.message });
567
+ if (err instanceof ParamValidationError)
568
+ return errorResult({ error: "invalid_args", code: "E_INVALID_PARAMS", message: err.message });
569
+ if (isEnvironmentError(err) || err instanceof FixtureSpecError || err instanceof UnboundSetupRefError) {
570
+ return errorResult({ error: "invalid_args", code: err.code, message: err.message });
571
+ }
572
+ // Never run on unknown state: inconclusive (exit 2) — proves nothing, so an error result.
573
+ if (err instanceof FixtureSetupError) {
574
+ return errorResult({ outcome: "inconclusive", reason: err.message, failure: { kind: "configuration", message: err.message }, attribution: "configuration", exitCode: 2 });
575
+ }
576
+ throw err;
577
+ }
160
578
  },
161
579
  },
162
580
  queue_exploration: {
163
- description: "Enqueue an exploration mission against a PROMOTED target. Never runs anything — only queues. Refuses unknown/unpromoted targets and over-ceiling budgets.",
581
+ description: "Enqueue an exploration mission against a PROMOTED target. Never runs anything — only queues; `jevitate mission run` drains the queue, and get_mission_result {id: missionId} reports its status/result. strategy: goal-based (goal|feature|route + successAssertion) | coverage | exploratory (novelty-first coverage) | adversarial (optional in-scope route glob) | feature (feature name, optional route glob); a usability review is not queueable — use run_exploration {strategy: usability}. The target's authorized origin plus its declared apiOrigins are the only reachable origins. Refuses unknown/unpromoted targets, over-ceiling budgets and invalid declared `invariants` (an optional closed spec checked around every action; probes GET/HEAD on the target origin only). Optional 'viewport' ({width,height}) or 'device' (a Playwright devices registry name, e.g. \"iPhone 13\") — mutually exclusive (#149); default: Playwright's own default viewport. An unknown device is refused before any browser opens. #255: 'recordVideo' / 'evidenceVideo' (booleans) and 'screenshots' (screens | steps) write media next to the result (listed in it) — a queued request never names a path; 'persona' names a persona in the operator's ~/.jevitate/targets.json for the target's origin (its session, never the caller's). Same as `jevitate mission queue`.",
164
582
  inputSchema: {
165
583
  type: "object",
166
584
  properties: {
@@ -169,7 +587,7 @@ export function buildMcpTools(deps) {
169
587
  feature: { type: "string" },
170
588
  route: { type: "string" },
171
589
  successAssertion: { type: "object" },
172
- strategy: { type: "string" },
590
+ strategy: { type: "string", enum: [...MISSION_STRATEGIES] },
173
591
  budget: {
174
592
  type: "object",
175
593
  properties: {
@@ -178,6 +596,22 @@ export function buildMcpTools(deps) {
178
596
  maxCandidates: { type: "number" },
179
597
  },
180
598
  },
599
+ // App-declared invariants (#86), inline only: a closed schema (observe: dom|network|probe,
600
+ // invariants: require|never|always). Probes are GET/HEAD on the target's own origin; a spec
601
+ // that does not validate refuses the enqueue.
602
+ invariants: { type: "object" },
603
+ // Per-mission viewport/device emulation (#149) — mutually exclusive.
604
+ viewport: {
605
+ type: "object",
606
+ properties: { width: { type: "number" }, height: { type: "number" } },
607
+ required: ["width", "height"],
608
+ },
609
+ device: { type: "string" },
610
+ // #255: media next to the result (a mode or a boolean — never a path) and a persona NAME.
611
+ recordVideo: { type: "boolean" },
612
+ screenshots: { type: "string", enum: [...QUEUED_SCREENSHOT_MODES] },
613
+ evidenceVideo: { type: "boolean" },
614
+ persona: { type: "string" },
181
615
  },
182
616
  required: ["target"],
183
617
  },
@@ -293,6 +727,19 @@ export function buildMcpTools(deps) {
293
727
  handler: inboxHandler((store) => facadeGetSiteHealth(store)),
294
728
  },
295
729
  };
730
+ // #255: every CLI command without a hand-written tool above — mirrored exactly, run in process.
731
+ for (const spec of CLI_TOOL_SPECS) {
732
+ if (wired[spec.name] !== undefined)
733
+ throw new Error(`MCP tool '${spec.name}' is defined twice`);
734
+ wired[spec.name] = {
735
+ description: spec.description,
736
+ inputSchema: cliToolInputSchema(spec),
737
+ handler: async (args) => {
738
+ const { isError, body } = await runCliTool(spec, args, { runCli: deps.runCli, roots: pathRoots, redact: (text) => redactCredentials(text, credentialStore) });
739
+ return isError ? errorResult(body) : jsonResult(body);
740
+ },
741
+ };
742
+ }
296
743
  const notImplemented = (name) => async () => errorResult({
297
744
  error: "not_implemented",
298
745
  tool: name,
@@ -319,7 +766,14 @@ export function buildMcpTools(deps) {
319
766
  export function createMcpServer(deps) {
320
767
  const tools = buildMcpTools(deps);
321
768
  const byName = new Map(tools.map((t) => [t.name, t]));
322
- const server = new Server({ name: "jevitate", version: "0.0.0" }, { capabilities: { tools: {} } });
769
+ // The real build identity (#112), never a placeholder: `version` is the published semver, and the
770
+ // description names the commit/build time so an MCP client can tie a session to a build.
771
+ const engine = deps.engine ?? currentEngineInfo();
772
+ const server = new Server({
773
+ name: "jevitate",
774
+ version: engine.version,
775
+ description: `jevitate ${engine.version} (commit ${engine.commit}, built ${engine.builtAt})`,
776
+ }, { capabilities: { tools: {} } });
323
777
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
324
778
  tools: tools.map((t) => ({ name: t.name, description: t.description, inputSchema: t.inputSchema })),
325
779
  }));