@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.
- package/README.md +23 -0
- package/dist/ai-cli.js +40 -30
- package/dist/ai-cli.js.map +1 -1
- package/dist/author-journey-cli.d.ts +4 -0
- package/dist/author-journey-cli.js +109 -0
- package/dist/author-journey-cli.js.map +1 -0
- package/dist/bin.js +58077 -22454
- package/dist/bin.js.map +1 -1
- package/dist/browser-run-options.d.ts +58 -0
- package/dist/browser-run-options.js +106 -0
- package/dist/browser-run-options.js.map +1 -0
- package/dist/build-info.generated.d.ts +2 -0
- package/dist/build-info.generated.js +10 -0
- package/dist/build-info.generated.js.map +1 -0
- package/dist/check-api.d.ts +5 -0
- package/dist/check-api.js +233 -0
- package/dist/check-api.js.map +1 -0
- package/dist/check-budget.d.ts +26 -0
- package/dist/check-budget.js +89 -0
- package/dist/check-budget.js.map +1 -0
- package/dist/check-cli.d.ts +31 -0
- package/dist/check-cli.js +109 -0
- package/dist/check-cli.js.map +1 -0
- package/dist/check-execute.d.ts +25 -0
- package/dist/check-execute.js +385 -0
- package/dist/check-execute.js.map +1 -0
- package/dist/check-plan.d.ts +113 -0
- package/dist/check-plan.js +448 -0
- package/dist/check-plan.js.map +1 -0
- package/dist/check-suite.d.ts +171 -0
- package/dist/check-suite.js +515 -0
- package/dist/check-suite.js.map +1 -0
- package/dist/check-types.d.ts +181 -0
- package/dist/check-types.js +35 -0
- package/dist/check-types.js.map +1 -0
- package/dist/cli-args.d.ts +26 -0
- package/dist/cli-args.js +50 -0
- package/dist/cli-args.js.map +1 -0
- package/dist/cli-output.d.ts +102 -0
- package/dist/cli-output.js +516 -0
- package/dist/cli-output.js.map +1 -0
- package/dist/cli-reference.d.ts +3 -0
- package/dist/cli-reference.js +81 -0
- package/dist/cli-reference.js.map +1 -0
- package/dist/cli-refusal.d.ts +12 -0
- package/dist/cli-refusal.js +52 -0
- package/dist/cli-refusal.js.map +1 -0
- package/dist/cli-shared.d.ts +330 -0
- package/dist/cli-shared.js +492 -0
- package/dist/cli-shared.js.map +1 -0
- package/dist/cli-surface.d.ts +33 -0
- package/dist/cli-surface.js +48 -0
- package/dist/cli-surface.js.map +1 -0
- package/dist/conversation-options.d.ts +15 -0
- package/dist/conversation-options.js +10 -0
- package/dist/conversation-options.js.map +1 -0
- package/dist/credentials-file.d.ts +15 -0
- package/dist/credentials-file.js +56 -0
- package/dist/credentials-file.js.map +1 -0
- package/dist/data-dir.d.ts +1 -1
- package/dist/data-dir.js +1 -1
- package/dist/defect-evidence.d.ts +141 -0
- package/dist/defect-evidence.js +502 -0
- package/dist/defect-evidence.js.map +1 -0
- package/dist/demo-aspect-api.d.ts +209 -0
- package/dist/demo-aspect-api.js +397 -0
- package/dist/demo-aspect-api.js.map +1 -0
- package/dist/demo-aspect-cli.d.ts +3 -0
- package/dist/demo-aspect-cli.js +241 -0
- package/dist/demo-aspect-cli.js.map +1 -0
- package/dist/demo-capture.d.ts +108 -0
- package/dist/demo-capture.js +438 -0
- package/dist/demo-capture.js.map +1 -0
- package/dist/engine.d.ts +35 -0
- package/dist/engine.js +25 -0
- package/dist/engine.js.map +1 -0
- package/dist/environments.d.ts +155 -0
- package/dist/environments.js +364 -0
- package/dist/environments.js.map +1 -0
- package/dist/exit-codes.d.ts +46 -0
- package/dist/exit-codes.js +91 -0
- package/dist/exit-codes.js.map +1 -0
- package/dist/explore-adversarial.d.ts +137 -0
- package/dist/explore-adversarial.js +218 -0
- package/dist/explore-adversarial.js.map +1 -0
- package/dist/explore-api.d.ts +7 -187
- package/dist/explore-api.js +7 -301
- package/dist/explore-api.js.map +1 -1
- package/dist/explore-author.d.ts +79 -0
- package/dist/explore-author.js +86 -0
- package/dist/explore-author.js.map +1 -0
- package/dist/explore-cli.d.ts +7 -0
- package/dist/explore-cli.js +963 -0
- package/dist/explore-cli.js.map +1 -0
- package/dist/explore-coverage.d.ts +158 -0
- package/dist/explore-coverage.js +240 -0
- package/dist/explore-coverage.js.map +1 -0
- package/dist/explore-feature.d.ts +127 -0
- package/dist/explore-feature.js +220 -0
- package/dist/explore-feature.js.map +1 -0
- package/dist/explore-goal.d.ts +253 -0
- package/dist/explore-goal.js +374 -0
- package/dist/explore-goal.js.map +1 -0
- package/dist/explore-shared.d.ts +152 -0
- package/dist/explore-shared.js +129 -0
- package/dist/explore-shared.js.map +1 -0
- package/dist/explore-specs.d.ts +53 -0
- package/dist/explore-specs.js +333 -0
- package/dist/explore-specs.js.map +1 -0
- package/dist/findings-filing.d.ts +64 -0
- package/dist/findings-filing.js +108 -0
- package/dist/findings-filing.js.map +1 -0
- package/dist/fixture-auth.d.ts +52 -0
- package/dist/fixture-auth.js +82 -0
- package/dist/fixture-auth.js.map +1 -0
- package/dist/fixture-cli.d.ts +70 -0
- package/dist/fixture-cli.js +178 -0
- package/dist/fixture-cli.js.map +1 -0
- package/dist/github-issue-filer.d.ts +62 -0
- package/dist/github-issue-filer.js +188 -0
- package/dist/github-issue-filer.js.map +1 -0
- package/dist/host-health-run.d.ts +24 -0
- package/dist/host-health-run.js +27 -0
- package/dist/host-health-run.js.map +1 -0
- package/dist/http-5xx-outcome.d.ts +6 -0
- package/dist/http-5xx-outcome.js +39 -0
- package/dist/http-5xx-outcome.js.map +1 -0
- package/dist/inbox-cli.d.ts +7 -0
- package/dist/inbox-cli.js +197 -0
- package/dist/inbox-cli.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/init-cli.d.ts +4 -0
- package/dist/init-cli.js +127 -0
- package/dist/init-cli.js.map +1 -0
- package/dist/init-keys.d.ts +19 -2
- package/dist/init-keys.js +15 -4
- package/dist/init-keys.js.map +1 -1
- package/dist/init-next-steps.d.ts +35 -0
- package/dist/init-next-steps.js +79 -0
- package/dist/init-next-steps.js.map +1 -0
- package/dist/invariants-file.d.ts +34 -0
- package/dist/invariants-file.js +68 -0
- package/dist/invariants-file.js.map +1 -0
- package/dist/invariants-validate.d.ts +30 -0
- package/dist/invariants-validate.js +105 -0
- package/dist/invariants-validate.js.map +1 -0
- package/dist/journey-annotate-api.d.ts +76 -0
- package/dist/journey-annotate-api.js +256 -0
- package/dist/journey-annotate-api.js.map +1 -0
- package/dist/journey-annotate-cli.d.ts +9 -0
- package/dist/journey-annotate-cli.js +179 -0
- package/dist/journey-annotate-cli.js.map +1 -0
- package/dist/journey-api.d.ts +79 -2
- package/dist/journey-api.js +136 -19
- package/dist/journey-api.js.map +1 -1
- package/dist/journey-cli.d.ts +4 -0
- package/dist/journey-cli.js +370 -0
- package/dist/journey-cli.js.map +1 -0
- package/dist/journey-demo-api.d.ts +112 -0
- package/dist/journey-demo-api.js +392 -0
- package/dist/journey-demo-api.js.map +1 -0
- package/dist/journey-demo-cli.d.ts +10 -0
- package/dist/journey-demo-cli.js +155 -0
- package/dist/journey-demo-cli.js.map +1 -0
- package/dist/kill-signal.d.ts +193 -0
- package/dist/kill-signal.js +284 -0
- package/dist/kill-signal.js.map +1 -0
- package/dist/launch-armed.d.ts +37 -0
- package/dist/launch-armed.js +31 -0
- package/dist/launch-armed.js.map +1 -0
- package/dist/ledger-api.d.ts +106 -0
- package/dist/ledger-api.js +237 -0
- package/dist/ledger-api.js.map +1 -0
- package/dist/ledger-cli.d.ts +18 -0
- package/dist/ledger-cli.js +107 -0
- package/dist/ledger-cli.js.map +1 -0
- package/dist/load-api.d.ts +29 -11
- package/dist/load-api.js +23 -15
- package/dist/load-api.js.map +1 -1
- package/dist/load-cli.d.ts +4 -0
- package/dist/load-cli.js +97 -0
- package/dist/load-cli.js.map +1 -0
- package/dist/log-correlation.d.ts +147 -0
- package/dist/log-correlation.js +325 -0
- package/dist/log-correlation.js.map +1 -0
- package/dist/log-lines.d.ts +131 -0
- package/dist/log-lines.js +389 -0
- package/dist/log-lines.js.map +1 -0
- package/dist/log-sources.d.ts +71 -0
- package/dist/log-sources.js +235 -0
- package/dist/log-sources.js.map +1 -0
- package/dist/logs-cli.d.ts +3 -0
- package/dist/logs-cli.js +51 -0
- package/dist/logs-cli.js.map +1 -0
- package/dist/logs-retention.d.ts +28 -0
- package/dist/logs-retention.js +84 -0
- package/dist/logs-retention.js.map +1 -0
- package/dist/mcp-api.d.ts +89 -1
- package/dist/mcp-api.js +472 -18
- package/dist/mcp-api.js.map +1 -1
- package/dist/mcp-args.d.ts +38 -0
- package/dist/mcp-args.js +131 -0
- package/dist/mcp-args.js.map +1 -0
- package/dist/mcp-cli-bridge.d.ts +34 -0
- package/dist/mcp-cli-bridge.js +62 -0
- package/dist/mcp-cli-bridge.js.map +1 -0
- package/dist/mcp-cli-runner.d.ts +29 -0
- package/dist/mcp-cli-runner.js +44 -0
- package/dist/mcp-cli-runner.js.map +1 -0
- package/dist/mcp-cli-tools.d.ts +125 -0
- package/dist/mcp-cli-tools.js +680 -0
- package/dist/mcp-cli-tools.js.map +1 -0
- package/dist/mcp-paths.d.ts +28 -0
- package/dist/mcp-paths.js +76 -0
- package/dist/mcp-paths.js.map +1 -0
- package/dist/mission-actors.d.ts +40 -0
- package/dist/mission-actors.js +72 -0
- package/dist/mission-actors.js.map +1 -0
- package/dist/mission-api.d.ts +35 -0
- package/dist/mission-api.js +58 -2
- package/dist/mission-api.js.map +1 -1
- package/dist/mission-cli.d.ts +4 -0
- package/dist/mission-cli.js +247 -0
- package/dist/mission-cli.js.map +1 -0
- package/dist/mission-exit.d.ts +22 -0
- package/dist/mission-exit.js +26 -0
- package/dist/mission-exit.js.map +1 -0
- package/dist/mission-fixtures.d.ts +225 -0
- package/dist/mission-fixtures.js +828 -0
- package/dist/mission-fixtures.js.map +1 -0
- package/dist/mission-journal.d.ts +60 -0
- package/dist/mission-journal.js +115 -0
- package/dist/mission-journal.js.map +1 -0
- package/dist/mission-queue-cli.d.ts +10 -0
- package/dist/mission-queue-cli.js +198 -0
- package/dist/mission-queue-cli.js.map +1 -0
- package/dist/mission-queue-runner.d.ts +126 -0
- package/dist/mission-queue-runner.js +397 -0
- package/dist/mission-queue-runner.js.map +1 -0
- package/dist/multi-run-cli.d.ts +37 -0
- package/dist/multi-run-cli.js +110 -0
- package/dist/multi-run-cli.js.map +1 -0
- package/dist/multi-run.d.ts +339 -0
- package/dist/multi-run.js +619 -0
- package/dist/multi-run.js.map +1 -0
- package/dist/openrouter-call.d.ts +22 -0
- package/dist/openrouter-call.js +63 -0
- package/dist/openrouter-call.js.map +1 -0
- package/dist/profile-cli.d.ts +4 -0
- package/dist/profile-cli.js +57 -0
- package/dist/profile-cli.js.map +1 -0
- package/dist/program.d.ts +3 -75
- package/dist/program.js +119 -1921
- package/dist/program.js.map +1 -1
- package/dist/project-dir.d.ts +96 -0
- package/dist/project-dir.js +220 -0
- package/dist/project-dir.js.map +1 -0
- package/dist/record-api.d.ts +10 -6
- package/dist/record-api.js +39 -28
- package/dist/record-api.js.map +1 -1
- package/dist/record-cli.d.ts +4 -0
- package/dist/record-cli.js +64 -0
- package/dist/record-cli.js.map +1 -0
- package/dist/recording-cli.d.ts +4 -0
- package/dist/recording-cli.js +248 -0
- package/dist/recording-cli.js.map +1 -0
- package/dist/regression-api.d.ts +101 -0
- package/dist/regression-api.js +384 -5
- package/dist/regression-api.js.map +1 -1
- package/dist/regression-cli.d.ts +4 -0
- package/dist/regression-cli.js +232 -0
- package/dist/regression-cli.js.map +1 -0
- package/dist/report-api.d.ts +146 -0
- package/dist/report-api.js +430 -0
- package/dist/report-api.js.map +1 -0
- package/dist/report-cli.d.ts +12 -0
- package/dist/report-cli.js +147 -0
- package/dist/report-cli.js.map +1 -0
- package/dist/result-schema.d.ts +22 -0
- package/dist/result-schema.js +28 -0
- package/dist/result-schema.js.map +1 -0
- package/dist/run-index.d.ts +29 -0
- package/dist/run-index.js +70 -0
- package/dist/run-index.js.map +1 -0
- package/dist/run-screenshots.d.ts +123 -0
- package/dist/run-screenshots.js +229 -0
- package/dist/run-screenshots.js.map +1 -0
- package/dist/runtime-env.d.ts +8 -0
- package/dist/runtime-env.js +22 -0
- package/dist/runtime-env.js.map +1 -0
- package/dist/secret-args.d.ts +23 -0
- package/dist/secret-args.js +45 -0
- package/dist/secret-args.js.map +1 -0
- package/dist/serve-cli.d.ts +4 -0
- package/dist/serve-cli.js +95 -0
- package/dist/serve-cli.js.map +1 -0
- package/dist/server-log-verify.d.ts +34 -0
- package/dist/server-log-verify.js +115 -0
- package/dist/server-log-verify.js.map +1 -0
- package/dist/session-check.d.ts +14 -0
- package/dist/session-check.js +44 -0
- package/dist/session-check.js.map +1 -0
- package/dist/site-cli.d.ts +4 -0
- package/dist/site-cli.js +127 -0
- package/dist/site-cli.js.map +1 -0
- package/dist/site-gate-cli.d.ts +28 -0
- package/dist/site-gate-cli.js +58 -0
- package/dist/site-gate-cli.js.map +1 -0
- package/dist/source-api.js +18 -3
- package/dist/source-api.js.map +1 -1
- package/dist/source-cli.d.ts +4 -0
- package/dist/source-cli.js +284 -0
- package/dist/source-cli.js.map +1 -0
- package/dist/source-run-api.d.ts +27 -1
- package/dist/source-run-api.js +22 -11
- package/dist/source-run-api.js.map +1 -1
- package/dist/storage-state-snapshot.d.ts +50 -0
- package/dist/storage-state-snapshot.js +87 -0
- package/dist/storage-state-snapshot.js.map +1 -0
- package/dist/suite-explore-options.d.ts +289 -0
- package/dist/suite-explore-options.js +140 -0
- package/dist/suite-explore-options.js.map +1 -0
- package/dist/target-config.d.ts +109 -0
- package/dist/target-config.js +216 -0
- package/dist/target-config.js.map +1 -0
- package/dist/transcript-file.d.ts +12 -0
- package/dist/transcript-file.js +23 -0
- package/dist/transcript-file.js.map +1 -0
- package/dist/ui-api.d.ts +3 -0
- package/dist/ui-api.js +7 -1
- package/dist/ui-api.js.map +1 -1
- package/dist/usability-capture.d.ts +43 -0
- package/dist/usability-capture.js +326 -0
- package/dist/usability-capture.js.map +1 -0
- package/dist/usage-config.d.ts +36 -0
- package/dist/usage-config.js +114 -0
- package/dist/usage-config.js.map +1 -0
- package/dist/ux-api.d.ts +3 -75
- package/dist/ux-api.js +3 -220
- package/dist/ux-api.js.map +1 -1
- package/dist/ux-cli.d.ts +4 -0
- package/dist/ux-cli.js +102 -0
- package/dist/ux-cli.js.map +1 -0
- package/dist/ux-config.d.ts +29 -0
- package/dist/ux-config.js +103 -0
- package/dist/ux-config.js.map +1 -0
- package/dist/ux-evidence.d.ts +54 -0
- package/dist/ux-evidence.js +197 -0
- package/dist/ux-evidence.js.map +1 -0
- package/dist/ux-review.d.ts +112 -0
- package/dist/ux-review.js +151 -0
- package/dist/ux-review.js.map +1 -0
- package/dist/ux-usability.d.ts +238 -0
- package/dist/ux-usability.js +494 -0
- package/dist/ux-usability.js.map +1 -0
- package/dist/verify-fix-api.d.ts +173 -0
- package/dist/verify-fix-api.js +454 -0
- package/dist/verify-fix-api.js.map +1 -0
- package/dist/verify-fix-cli.d.ts +4 -0
- package/dist/verify-fix-cli.js +110 -0
- package/dist/verify-fix-cli.js.map +1 -0
- package/package.json +19 -14
- package/skills/jevitate-ci-check/SKILL.md +77 -0
- package/skills/jevitate-demo/SKILL.md +61 -0
- package/skills/jevitate-explore/SKILL.md +88 -87
- package/skills/jevitate-getting-started/SKILL.md +88 -0
- package/skills/jevitate-load-test/SKILL.md +11 -2
- package/skills/jevitate-mission-scope/SKILL.md +19 -11
- package/skills/jevitate-record/SKILL.md +23 -5
- package/skills/jevitate-run-journey/SKILL.md +61 -17
- package/skills/jevitate-sources/SKILL.md +57 -14
- package/skills/jevitate-ux-review/SKILL.md +106 -16
- 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 {
|
|
10
|
-
import {
|
|
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
|
|
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) =>
|
|
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)
|
|
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
|
|
155
|
-
// `steps`/`recording` in the arguments is deliberately ignored.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
|
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
|
-
|
|
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
|
}));
|