@jevitate/cli 0.4.0 → 0.6.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/dist/author-journey-cli.d.ts +2 -0
- package/dist/author-journey-cli.js +106 -11
- package/dist/author-journey-cli.js.map +1 -1
- package/dist/bin.js +2988 -776
- package/dist/build-info.generated.js +2 -2
- package/dist/check-execute.js +1 -0
- package/dist/check-execute.js.map +1 -1
- package/dist/check-plan.js +2 -1
- package/dist/check-plan.js.map +1 -1
- package/dist/check-suite.d.ts +6 -6
- package/dist/check-suite.js +18 -12
- package/dist/check-suite.js.map +1 -1
- package/dist/cli-shared.d.ts +5 -2
- package/dist/cli-shared.js +10 -3
- package/dist/cli-shared.js.map +1 -1
- package/dist/conversation-options.d.ts +3 -0
- package/dist/conversation-options.js +1 -0
- package/dist/conversation-options.js.map +1 -1
- package/dist/demo-aspect-api.js +2 -0
- package/dist/demo-aspect-api.js.map +1 -1
- package/dist/demo-capture.d.ts +16 -4
- package/dist/demo-capture.js +82 -15
- package/dist/demo-capture.js.map +1 -1
- package/dist/exit-codes.d.ts +1 -1
- package/dist/exit-codes.js +1 -1
- package/dist/explore-author.d.ts +28 -6
- package/dist/explore-author.js +66 -32
- package/dist/explore-author.js.map +1 -1
- package/dist/explore-cli.js +68 -79
- package/dist/explore-cli.js.map +1 -1
- package/dist/explore-goal.d.ts +5 -1
- package/dist/explore-goal.js +19 -1
- package/dist/explore-goal.js.map +1 -1
- package/dist/explore-specs.js +12 -2
- package/dist/explore-specs.js.map +1 -1
- package/dist/goal-run-flags.d.ts +120 -0
- package/dist/goal-run-flags.js +220 -0
- package/dist/goal-run-flags.js.map +1 -0
- package/dist/journey-api.js +4 -1
- package/dist/journey-api.js.map +1 -1
- package/dist/journey-network-checks.d.ts +15 -0
- package/dist/journey-network-checks.js +36 -0
- package/dist/journey-network-checks.js.map +1 -0
- package/dist/kill-signal.d.ts +30 -3
- package/dist/kill-signal.js +51 -6
- package/dist/kill-signal.js.map +1 -1
- package/dist/mcp-api.js +8 -4
- package/dist/mcp-api.js.map +1 -1
- package/dist/mcp-cli-tools.js +43 -5
- package/dist/mcp-cli-tools.js.map +1 -1
- package/dist/mission-queue-cli.js +3 -1
- package/dist/mission-queue-cli.js.map +1 -1
- package/dist/secret-command.d.ts +7 -0
- package/dist/secret-command.js +52 -0
- package/dist/secret-command.js.map +1 -0
- package/dist/signal-triage.js +5 -1
- package/dist/signal-triage.js.map +1 -1
- package/dist/source-run-api.js +3 -1
- package/dist/source-run-api.js.map +1 -1
- package/dist/suite-explore-options.d.ts +9 -0
- package/dist/suite-explore-options.js +5 -0
- package/dist/suite-explore-options.js.map +1 -1
- package/dist/target-config.d.ts +3 -1
- package/dist/target-config.js +6 -0
- package/dist/target-config.js.map +1 -1
- package/dist/usability-capture.js +21 -3
- package/dist/usability-capture.js.map +1 -1
- package/dist/ux-usability.d.ts +5 -1
- package/dist/ux-usability.js +19 -2
- package/dist/ux-usability.js.map +1 -1
- package/package.json +7 -7
- package/skills/jevitate-explore/SKILL.md +7 -0
package/dist/explore-author.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { PlaywrightBrowserPort } from "@jevitate/playwright";
|
|
2
|
-
import { sessionLaunchOptions } from "./browser-run-options.js";
|
|
3
|
-
import { CastActor, BrowseTheWeb } from "@jevitate/screenplay";
|
|
4
1
|
import { authorJourney, assertAuthorizedExploreTarget, } from "@jevitate/explore";
|
|
5
2
|
import { FsJourneyStore } from "@jevitate/journey";
|
|
3
|
+
import { runExploration } from "./explore-goal.js";
|
|
4
|
+
import { screenshotsDirFor } from "./run-screenshots.js";
|
|
6
5
|
/**
|
|
7
6
|
* The programmatic surface behind `jevitate explore author-journey` — drives
|
|
8
7
|
* the goal-based exploration mission and feeds its take(s) through RxD's
|
|
@@ -23,7 +22,8 @@ export async function runAuthorJourney(opts) {
|
|
|
23
22
|
url: opts.url,
|
|
24
23
|
origin,
|
|
25
24
|
goal: opts.goal,
|
|
26
|
-
successAssertion: opts.successAssertion,
|
|
25
|
+
...(opts.successAssertion === undefined ? {} : { successAssertion: opts.successAssertion }),
|
|
26
|
+
...(opts.successChecks === undefined ? {} : { successChecks: opts.successChecks }),
|
|
27
27
|
allowlist: opts.allowlist,
|
|
28
28
|
judge: opts.judge,
|
|
29
29
|
gen: opts.gen,
|
|
@@ -35,6 +35,9 @@ export async function runAuthorJourney(opts) {
|
|
|
35
35
|
browser: opts.browser,
|
|
36
36
|
...(opts.storageState !== undefined ? { storageState: opts.storageState } : {}),
|
|
37
37
|
...(opts.safety === undefined ? {} : { safety: opts.safety }),
|
|
38
|
+
...(opts.goalRun === undefined ? {} : { goalRun: opts.goalRun }),
|
|
39
|
+
...(opts.outDir === undefined ? {} : { outDir: opts.outDir }),
|
|
40
|
+
...(opts.usage === undefined ? {} : { usage: opts.usage }),
|
|
38
41
|
});
|
|
39
42
|
if (result.outcome === "authored") {
|
|
40
43
|
// #170: a Journey authored behind a login (--storage-state) declares it, so a run without a
|
|
@@ -47,40 +50,71 @@ export async function runAuthorJourney(opts) {
|
|
|
47
50
|
}
|
|
48
51
|
return result;
|
|
49
52
|
}
|
|
50
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* Default authoring step (#369): every take — the discovery and each corroborating one — is a full
|
|
55
|
+
* goal run (`runExploration`, exactly what `jevitate explore --strategy goal` runs, with the same
|
|
56
|
+
* run-shaping options), in its own browser, writing its own result, transcript and Recording; its
|
|
57
|
+
* verdict (`goalOutcome`) decides whether the take reached the goal.
|
|
58
|
+
*/
|
|
51
59
|
async function authorViaBrowser(args) {
|
|
52
60
|
if (!args.judge || !args.gen) {
|
|
53
61
|
throw new Error("runAuthorJourney: judge and gen gateways are required to drive the authoring mission");
|
|
54
62
|
}
|
|
55
63
|
const judge = args.judge;
|
|
56
64
|
const gen = args.gen;
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
...(args.
|
|
65
|
+
const g = args.goalRun ?? {};
|
|
66
|
+
// targets.json's safety (`goalRun.target`) wins; a caller's `safety` (#249) applies without one.
|
|
67
|
+
const target = g.target ?? (args.safety === undefined ? undefined : { safety: args.safety });
|
|
68
|
+
return authorJourney({
|
|
69
|
+
goal: args.goal,
|
|
70
|
+
...(args.successAssertion === undefined ? {} : { successAssertion: args.successAssertion }),
|
|
71
|
+
...(args.successChecks === undefined ? {} : { successChecks: args.successChecks }),
|
|
72
|
+
allowlist: args.allowlist,
|
|
73
|
+
startUrl: args.url,
|
|
74
|
+
bounds: args.bounds,
|
|
75
|
+
takes: args.takes,
|
|
76
|
+
journeyId: args.journeyId,
|
|
77
|
+
journeyName: args.journeyName,
|
|
78
|
+
runTake: async ({ goal, successChecks }) => {
|
|
79
|
+
const r = await runExploration({
|
|
80
|
+
...g,
|
|
81
|
+
...(target === undefined ? {} : { target }),
|
|
82
|
+
url: args.url,
|
|
83
|
+
goal,
|
|
84
|
+
successChecks,
|
|
85
|
+
allowlist: args.allowlist,
|
|
86
|
+
judge,
|
|
87
|
+
gen,
|
|
88
|
+
...(args.usage === undefined ? {} : { usage: args.usage }),
|
|
89
|
+
bounds: args.bounds,
|
|
90
|
+
...(args.outDir === undefined ? {} : { outDir: args.outDir }),
|
|
91
|
+
...(args.browserPortFactory === undefined ? {} : { browserPortFactory: args.browserPortFactory }),
|
|
92
|
+
...(args.browser === undefined ? {} : { browser: args.browser }),
|
|
93
|
+
...(args.storageState === undefined ? {} : { storageState: args.storageState }),
|
|
94
|
+
});
|
|
95
|
+
return goalRunTake(r, g);
|
|
96
|
+
},
|
|
64
97
|
});
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
98
|
+
}
|
|
99
|
+
/** #369: one goal run as an authoring take — its Recording and where everything it wrote is. */
|
|
100
|
+
export function goalRunTake(r, g = {}) {
|
|
101
|
+
const reason = r.reason ?? r.failure?.message;
|
|
102
|
+
const recordingPath = r.recordingPaths[0];
|
|
103
|
+
const diagnostics = {
|
|
104
|
+
outcome: r.goalOutcome,
|
|
105
|
+
...(reason === undefined ? {} : { reason }),
|
|
106
|
+
stop: r.stop,
|
|
107
|
+
runOutcome: r.runOutcome,
|
|
108
|
+
...(r.failure === undefined ? {} : { failure: { kind: r.failure.kind, message: r.failure.message } }),
|
|
109
|
+
checks: r.checks,
|
|
110
|
+
decisions: r.decisions,
|
|
111
|
+
actions: r.actions,
|
|
112
|
+
finalUrl: r.finalUrl,
|
|
113
|
+
resultPath: r.resultPath,
|
|
114
|
+
transcriptPath: r.transcriptPath,
|
|
115
|
+
recordingPaths: r.recordingPaths,
|
|
116
|
+
...(g.screenshots === undefined || recordingPath === undefined ? {} : { screenshotsDir: screenshotsDirFor(g.screenshots, recordingPath) }),
|
|
117
|
+
};
|
|
118
|
+
return { outcome: r.goalOutcome, recording: r.recording, diagnostics };
|
|
85
119
|
}
|
|
86
120
|
//# sourceMappingURL=explore-author.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"explore-author.js","sourceRoot":"","sources":["../src/explore-author.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"explore-author.js","sourceRoot":"","sources":["../src/explore-author.ts"],"names":[],"mappings":"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"}
|
package/dist/explore-cli.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { envCredentialStore, MissingCredentialError } from "@jevitate/ai-core";
|
|
3
3
|
import { loadLocalCredentials } from "./credentials-file.js";
|
|
4
|
-
import { FixtureNotFoundError, ScopeUnderivableError, UnauthorizedExploreTargetError, resolveCoverageThresholds,
|
|
4
|
+
import { FixtureNotFoundError, ScopeUnderivableError, UnauthorizedExploreTargetError, resolveCoverageThresholds, TypeFixtureSpecError, validateDenyPatterns, } from "@jevitate/explore";
|
|
5
5
|
import { loadTypeFixtures } from "./type-fixture-file.js";
|
|
6
|
+
import { secretCommandRunner } from "./secret-command.js";
|
|
6
7
|
import { ok, fail } from "./envelope.js";
|
|
7
8
|
import { SessionFileInProjectError, assertSessionFileOutsideProject } from "./project-dir.js";
|
|
8
9
|
import { InvariantsFileError, loadInvariantFiles, resolveInvariantAuthTokens } from "./invariants-file.js";
|
|
@@ -17,7 +18,7 @@ import { LITERAL_SECRET_WARNING, SecretArgError, resolveSecretArgs } from "./sec
|
|
|
17
18
|
import { withEngine } from "./engine.js";
|
|
18
19
|
import { setKillSwitchOutput } from "./kill-signal.js";
|
|
19
20
|
import { EXIT_CODES } from "./exit-codes.js";
|
|
20
|
-
import {
|
|
21
|
+
import { positiveNumberArg, nonNegativeIntArg, positiveIntArg, ratioArg } from "./cli-args.js";
|
|
21
22
|
import { formatMissionHuman, formatMultiRunHuman } from "./cli-output.js";
|
|
22
23
|
import { runExploration, runCoverageMission, runAdversarialCliMission, CLI_ADVERSARIAL_STRATEGIES, runFeatureCliMission, parseSuccessSpec, resolveExploreAllowlist, } from "./explore-api.js";
|
|
23
24
|
import { parseLogSourceSpecs, LogSourceSpecError } from "./log-sources.js";
|
|
@@ -44,6 +45,7 @@ import { formatCampaignHuman } from "./campaign-cli.js";
|
|
|
44
45
|
import { forwardedArgv } from "./multi-run-cli.js";
|
|
45
46
|
import { ANCHORED_STRATEGIES, JourneyPrefixArgsError, JourneyPrefixStaleError, journeyStaleResult, resolveJourneyPrefix, } from "./journey-prefix.js";
|
|
46
47
|
import { clock } from "@jevitate/domain";
|
|
48
|
+
import { GOAL_RUN_OPTIONS, GoalRunFlagError, conversationFromFlags, dialogsFromFlags, secretFieldsFromFlags, successWhenFromFlags, targetFlagsFromFlags, } from "./goal-run-flags.js";
|
|
47
49
|
/**
|
|
48
50
|
* #293: the flags a sweep sets on each of its missions itself (the rest of the command line is
|
|
49
51
|
* forwarded to every mission as given).
|
|
@@ -94,71 +96,59 @@ export function registerExploreCommands(program, deps, buildProgram) {
|
|
|
94
96
|
"Omit it for a find-out goal (e.g. \"find out how many contacts... report the answer\"): the run must then end",
|
|
95
97
|
"with the model's own `report` op, and the grounded answer (#101) is the verdict — no page/network check needed.",
|
|
96
98
|
].join(" "), (v, prev) => [...prev, v], [])
|
|
97
|
-
.
|
|
98
|
-
.
|
|
99
|
-
|
|
100
|
-
.option("--action-deltas", "opt-in (#303; every --strategy, not --feature): record what each action changed on the page — an accessibility snapshot before and after, announcements, " +
|
|
101
|
-
"the action's requests — redacted, with a code verdict per step (no-change | relevant-change | inconclusive) used by the goal loop's no-progress check and a persistence re-check after writes (goal), and as defect evidence (adversarial, coverage); " +
|
|
102
|
-
"adds `delta` to every transcript step (and Recording step, goal) and `actionDeltas` to the result. Costs about 50-100 ms per action on a small page, 0.3-0.5 s on a large one")
|
|
99
|
+
.addOption(GOAL_RUN_OPTIONS.successWhen())
|
|
100
|
+
.addOption(GOAL_RUN_OPTIONS.allowVacuousChecks())
|
|
101
|
+
.addOption(GOAL_RUN_OPTIONS.actionDeltas())
|
|
103
102
|
.option("--feature <name>", "run the capability-scoped feature-testing mission (instead of --goal/--success)")
|
|
104
103
|
.option("--route <glob>", "in-scope route glob (repeatable), e.g. /thread/** — for --feature it replaces the default scope (the start URL's route and everything under it); it widens --strategy adversarial/coverage/exploratory beyond the start URL's route", (v, prev) => [...prev, v], [])
|
|
105
104
|
.option("--scope <mode>", "--strategy coverage/exploratory: 'app' widens containment to the whole app (same as --route '/**'); default: the start URL's route plus --route globs")
|
|
106
105
|
.option("--allow <origin>", "authorized origin (repeatable); REPLACES the default allowlist when given (the URL's own origin is used only when --allow is omitted entirely) -- include the URL's own origin explicitly if you still need it", (v, prev) => [...prev, v], [])
|
|
107
|
-
.
|
|
108
|
-
.
|
|
109
|
-
.
|
|
110
|
-
.
|
|
111
|
-
.
|
|
106
|
+
.addOption(GOAL_RUN_OPTIONS.secret())
|
|
107
|
+
.addOption(GOAL_RUN_OPTIONS.secretField())
|
|
108
|
+
.addOption(GOAL_RUN_OPTIONS.totp())
|
|
109
|
+
.addOption(GOAL_RUN_OPTIONS.typeFixture())
|
|
110
|
+
.addOption(GOAL_RUN_OPTIONS.fixture())
|
|
112
111
|
.option("--storage-state <file>", "Playwright storageState JSON to start the session authenticated (deterministic login pre-step); must exist")
|
|
113
112
|
.option("--actor <name=storageState>", "multi-actor mission (#147, goal only; repeatable): the FIRST actor is the primary (the only one the model drives, " +
|
|
114
113
|
"from its own storageState); every other actor is an observer in its OWN fresh context that only runs the " +
|
|
115
114
|
"--invariants' cross-actor checks (capture + probe as:/deniedAs) — never clicks or types. Replaces --storage-state", (v, prev) => [...prev, v], [])
|
|
116
|
-
.
|
|
117
|
-
"Useful with a rotating refresh token: --storage-state's file goes stale after one authenticated run refreshes it, " +
|
|
118
|
-
"so point --save-storage-state at the SAME file (or a new one) to keep it usable for the next run. " +
|
|
119
|
-
"Written on every exit path -- a crash or a SIGTERM/SIGINT kill included (#159), not only a clean end -- but " +
|
|
120
|
-
"never over a good file with a session that already looks lost/logged-out; the last known-good state is used " +
|
|
121
|
-
"instead, or nothing is written if none was ever captured.")
|
|
115
|
+
.addOption(GOAL_RUN_OPTIONS.saveStorageState())
|
|
122
116
|
.option("--max-actions <n>", "hard cap on executed actions", positiveIntArg)
|
|
123
117
|
.option("--max-decisions <n>", "hard cap on model decisions", positiveIntArg)
|
|
124
118
|
.option("--stall-timeout <seconds>", "--strategy coverage/exploratory and --feature: end the run inconclusive (stalled) when no step completes within this many seconds (default 120)", positiveNumberArg)
|
|
125
|
-
.
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
.
|
|
129
|
-
.
|
|
130
|
-
.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
.option("--deny <pattern>", "a control no mission may click (repeatable): an accessible-name regex (/Archive/i or Archive) or a descriptor role=button;name=Archive. " +
|
|
135
|
-
"Session-ending (Sign out), destructive (Delete, Revoke, Rotate) and paid (Buy, Run simulation, Generate, Send invite) controls are refused by default", (v, prev) => [...prev, v], [])
|
|
136
|
-
.option("--paid <pattern>", "an app control that costs money or credits (repeatable; same syntax as --deny), e.g. /^(Analyze|Draft|Improve)\\b/i: treated like the built-in paid " +
|
|
137
|
-
"vocabulary — the budget guard sees it, hang replays never repeat it, and a goal that asks for it may still click it", (v, prev) => [...prev, v], [])
|
|
138
|
-
.option("--allow-destructive", "let missions click session-ending, destructive and paid controls (a --deny pattern still holds). A goal run already may click one its goal asks for")
|
|
119
|
+
.addOption(GOAL_RUN_OPTIONS.replyWaitMs())
|
|
120
|
+
.addOption(GOAL_RUN_OPTIONS.replyQuietMs())
|
|
121
|
+
.addOption(GOAL_RUN_OPTIONS.replyCeilingMs())
|
|
122
|
+
.addOption(GOAL_RUN_OPTIONS.replyMaxChars())
|
|
123
|
+
.addOption(GOAL_RUN_OPTIONS.jobWaitMs())
|
|
124
|
+
.addOption(GOAL_RUN_OPTIONS.deny())
|
|
125
|
+
.addOption(GOAL_RUN_OPTIONS.paid())
|
|
126
|
+
.addOption(GOAL_RUN_OPTIONS.allowDestructive())
|
|
127
|
+
.addOption(GOAL_RUN_OPTIONS.dialogs())
|
|
139
128
|
.option("--allow-writes", "let a find-out goal (no --success check, ended by report) change the app. By default it is read-only: controls that start a write flow " +
|
|
140
129
|
"(checkout, upgrade, create, save, submit…) are refused and the write requests an action fires are blocked, unless the goal itself asks for a change")
|
|
141
130
|
.option("--allow-write <glob>", "a write-request path a read-only find-out goal never blocks (repeatable; ** spans segments; a glob starting with https:// matches " +
|
|
142
131
|
"origin + path, e.g. https://abc.supabase.co/rest/v1/**), beyond the built-in auth-refresh ones " +
|
|
143
132
|
"(**/refresh*, **/token*, **/oauth/**, **/auth/**/refresh*). The app's background writes outside an action always pass", (v, prev) => [...prev, v], [])
|
|
144
|
-
.
|
|
145
|
-
"gRPC-web/Connect Get*/List*/Search*/Find*/Watch*/Stream*/Count*/Describe*/Read* methods are reads already. Reads are never guarded or reported as duplicate writes", (v, prev) => [...prev, v], [])
|
|
133
|
+
.addOption(GOAL_RUN_OPTIONS.readRpc())
|
|
146
134
|
.option("--real", "use live Jev + OpenRouter gateways (requires keys)", false)
|
|
147
135
|
.option("--fake-ai", "use deterministic fake gateways (pipeline smoke only)", false)
|
|
148
136
|
.option("--out <dir>", "directory to write the emitted Recording")
|
|
149
137
|
.option("--file-issues", "file findings as issues (needs a repo: --issue-repo or ~/.jevitate/filing.json); default: drafts only")
|
|
150
138
|
.option("--issue-repo <owner/name>", "the system-under-test repo findings for THIS target are filed to")
|
|
151
|
-
.
|
|
139
|
+
.addOption(GOAL_RUN_OPTIONS.hangReplays())
|
|
152
140
|
.option("--hang-replay-writes", "let hang replays re-send a paid/destructive write the run sent (default: such a hang is reported inconclusive, never replayed)")
|
|
153
|
-
.
|
|
154
|
-
.
|
|
155
|
-
.
|
|
156
|
-
.
|
|
141
|
+
.addOption(GOAL_RUN_OPTIONS.settleIgnore())
|
|
142
|
+
.addOption(GOAL_RUN_OPTIONS.longPollMs())
|
|
143
|
+
.addOption(GOAL_RUN_OPTIONS.apiPrefix())
|
|
144
|
+
.addOption(GOAL_RUN_OPTIONS.ignoreNoProgress())
|
|
157
145
|
.option("--jevitate-repo <owner/name>", "where jevitate engine findings are filed (default matt-cochran/jevitate)")
|
|
158
146
|
.option("--min-control-coverage <ratio>", "adversarial: share of the target's controls (0..1) a run must exercise before 'found nothing' is clean (default 0.25); below it the run is inconclusive", ratioArg)
|
|
159
147
|
.option("--no-require-form-submit", "adversarial: do not require a submitted form for a clean result (default: required when the target has a form)")
|
|
160
148
|
.option("--invariants <file>", "app-declared invariants JSON (repeatable; goal, coverage, exploratory, adversarial, --feature): checked around every action, a violation is a defect (exit 1). Validated before any browser opens; probes are GET/HEAD on an --allow origin only", (v, prev) => [...prev, v], [])
|
|
161
149
|
.option("--log-source <spec>", "backend log source (repeatable; every strategy, incl. usability): file:<path> (tailed from its current end) | docker:<container> (docker logs -f --since 0s) | cmd:<command> (needs --allow-log-cmd). Read-only, operator-declared, never the model's choice. Error/warning lines are correlated to the step they landed during and attached to its transcript evidence, redacted", (v, prev) => [...prev, v], [])
|
|
150
|
+
.addOption(GOAL_RUN_OPTIONS.allowSecretCmd())
|
|
151
|
+
.addOption(GOAL_RUN_OPTIONS.secretCmdAttempts())
|
|
162
152
|
.option("--allow-log-cmd", "opt-in: a --log-source cmd:<command> may run as a subprocess (operator-declared only; refused otherwise)", false)
|
|
163
153
|
.option("--log-defect <level|/regex/>", "backend log lines matching this (repeatable) become a server-log defect: a level (error|warn|info|debug, matched as level>=this) or a /regex/flags/ over the raw line. Its fingerprint is the normalized message (ids/numbers/uuids/timestamps stripped) plus the correlated route; verify-fix re-checks it by re-tailing the same --log-source(s)", (v, prev) => [...prev, v], [])
|
|
164
154
|
.option("--log-quiet-ok <spec>", "declares a --log-source spec (exact match, repeatable) as legitimately quiet: zero lines from it does not make the --log-defect oracle unhealthy (#169). Without it, a declared source that opened but delivered not one line makes an otherwise-clean run inconclusive, same as one that failed to open", (v, prev) => [...prev, v], [])
|
|
@@ -210,6 +200,17 @@ export function registerExploreCommands(program, deps, buildProgram) {
|
|
|
210
200
|
emitExplore(fail("E_EXPLORE_ARGS", `unknown strategy ${JSON.stringify(o.strategy)} (one of ${EXPLORE_STRATEGIES.join(", ")})`));
|
|
211
201
|
return;
|
|
212
202
|
}
|
|
203
|
+
// #334: a mistyped --dialogs never silently means "dismiss".
|
|
204
|
+
let dialogs;
|
|
205
|
+
try {
|
|
206
|
+
dialogs = dialogsFromFlags(o);
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
if (!(err instanceof GoalRunFlagError))
|
|
210
|
+
throw err;
|
|
211
|
+
emitExplore(fail(err.code, err.message));
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
213
214
|
// #195: a session file never lands in the repo's .jevitate/ (refused before any run, multi-runs included).
|
|
214
215
|
if (o.saveStorageState !== undefined) {
|
|
215
216
|
try {
|
|
@@ -432,22 +433,14 @@ export function registerExploreCommands(program, deps, buildProgram) {
|
|
|
432
433
|
// #120: a killed run prints what this command would have printed — the envelope with --json,
|
|
433
434
|
// else the human summary (#210) — before it exits.
|
|
434
435
|
setKillSwitchOutput(o.json === true ? "envelope" : "human");
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
...(o.replyMaxChars === undefined ? {} : { replyMaxChars: Number(o.replyMaxChars) }),
|
|
439
|
-
...(o.jobWaitMs === undefined ? {} : { jobWaitMs: Number(o.jobWaitMs) }),
|
|
440
|
-
};
|
|
441
|
-
if ((conversation.replyWaitMs !== undefined && !(Number.isInteger(conversation.replyWaitMs) && conversation.replyWaitMs > 0)) ||
|
|
442
|
-
(conversation.replyCeilingMs !== undefined &&
|
|
443
|
-
!(Number.isInteger(conversation.replyCeilingMs) && conversation.replyCeilingMs > 0)) ||
|
|
444
|
-
(conversation.replyMaxChars !== undefined &&
|
|
445
|
-
!(Number.isInteger(conversation.replyMaxChars) && conversation.replyMaxChars >= 20 && conversation.replyMaxChars <= 2000))) {
|
|
446
|
-
emitExplore(fail("E_EXPLORE_ARGS", "--reply-wait-ms and --reply-ceiling-ms must be positive integers; --reply-max-chars an integer in 20..2000"));
|
|
447
|
-
return;
|
|
436
|
+
let conversation;
|
|
437
|
+
try {
|
|
438
|
+
conversation = conversationFromFlags(o);
|
|
448
439
|
}
|
|
449
|
-
|
|
450
|
-
|
|
440
|
+
catch (err) {
|
|
441
|
+
if (!(err instanceof GoalRunFlagError))
|
|
442
|
+
throw err;
|
|
443
|
+
emitExplore(fail(err.code, err.message));
|
|
451
444
|
return;
|
|
452
445
|
}
|
|
453
446
|
// #154: refused BEFORE any browser opens — `nonNegativeIntArg` already rejects a bad value at
|
|
@@ -495,19 +488,7 @@ export function registerExploreCommands(program, deps, buildProgram) {
|
|
|
495
488
|
let target;
|
|
496
489
|
if (o.url !== undefined) {
|
|
497
490
|
try {
|
|
498
|
-
target = resolveTargetConfig(loadTargetsFile(deps.explore?.targetsConfigPath), new URL(setupRefFreeUrl(o.url)).origin,
|
|
499
|
-
settleIgnore: o.settleIgnore,
|
|
500
|
-
ignoreNoProgress: o.ignoreNoProgress,
|
|
501
|
-
apiPrefixes: o.apiPrefix,
|
|
502
|
-
deny: o.deny,
|
|
503
|
-
paid: o.paid,
|
|
504
|
-
readRpc: o.readRpc,
|
|
505
|
-
...(o.allowDestructive === true ? { allowDestructive: true } : {}),
|
|
506
|
-
...(o.allowWrites === true ? { allowWrites: true } : {}),
|
|
507
|
-
allowWrite: o.allowWrite,
|
|
508
|
-
...(o.hangReplayWrites === true ? { hangReplayWrites: true } : {}),
|
|
509
|
-
...(o.longPollMs === undefined ? {} : { longPollMs: Number(o.longPollMs) }),
|
|
510
|
-
});
|
|
491
|
+
target = resolveTargetConfig(loadTargetsFile(deps.explore?.targetsConfigPath), new URL(setupRefFreeUrl(o.url)).origin, targetFlagsFromFlags(o, dialogs));
|
|
511
492
|
}
|
|
512
493
|
catch (err) {
|
|
513
494
|
if (err instanceof TargetConfigError) {
|
|
@@ -672,18 +653,17 @@ export function registerExploreCommands(program, deps, buildProgram) {
|
|
|
672
653
|
return;
|
|
673
654
|
}
|
|
674
655
|
try {
|
|
675
|
-
secretFields =
|
|
676
|
-
...o.secretField.map((s) => parseSecretField(s, "value", process.env)),
|
|
677
|
-
...o.totp.map((s) => parseSecretField(s, "totp", process.env)),
|
|
678
|
-
];
|
|
656
|
+
secretFields = secretFieldsFromFlags(o);
|
|
679
657
|
}
|
|
680
658
|
catch (err) {
|
|
681
|
-
if (!(err instanceof
|
|
659
|
+
if (!(err instanceof GoalRunFlagError))
|
|
682
660
|
throw err;
|
|
683
661
|
emitExplore(fail(err.code, err.message));
|
|
684
662
|
return;
|
|
685
663
|
}
|
|
686
664
|
}
|
|
665
|
+
// #324: a cmd: binding's command runs at type time; its output becomes a run secret at once.
|
|
666
|
+
const secretCommand = secretFields.some((f) => f.kind === "cmd") ? secretCommandRunner() : undefined;
|
|
687
667
|
// #281: fields typed with a file's exact text — read (and checked) before any browser opens.
|
|
688
668
|
let typeFixtures = [];
|
|
689
669
|
if (o.typeFixture.length > 0) {
|
|
@@ -910,8 +890,8 @@ export function registerExploreCommands(program, deps, buildProgram) {
|
|
|
910
890
|
emitExplore(fail("E_EXPLORE_ASSERTION", String(err instanceof Error ? err.message : err)));
|
|
911
891
|
return;
|
|
912
892
|
}
|
|
913
|
-
if (o.successWhen !== undefined && o.successWhen !== "held" && o.successWhen !== "final") {
|
|
914
|
-
emitExplore(fail("E_EXPLORE_ARGS", `--success-when must be "held" or "
|
|
893
|
+
if (o.successWhen !== undefined && o.successWhen !== "held" && o.successWhen !== "final" && o.successWhen !== "each") {
|
|
894
|
+
emitExplore(fail("E_EXPLORE_ARGS", `--success-when must be "final", "held" or "each", got ${JSON.stringify(o.successWhen)}`));
|
|
915
895
|
return;
|
|
916
896
|
}
|
|
917
897
|
if (uxSuccessChecks.length === 0 && (o.successWhen !== undefined || o.allowVacuousChecks === true)) {
|
|
@@ -962,6 +942,8 @@ export function registerExploreCommands(program, deps, buildProgram) {
|
|
|
962
942
|
conversation,
|
|
963
943
|
secrets: o.secret.length > 0 ? o.secret : undefined,
|
|
964
944
|
...(secretFields.length > 0 ? { secretFields } : {}),
|
|
945
|
+
...(secretCommand === undefined ? {} : { secretCommand }),
|
|
946
|
+
...(o.secretCmdAttempts === undefined ? {} : { secretCommandAttempts: o.secretCmdAttempts }),
|
|
965
947
|
fixture: o.fixture,
|
|
966
948
|
outDir: o.out,
|
|
967
949
|
browserPortFactory: deps.explore?.browserPortFactory,
|
|
@@ -974,7 +956,7 @@ export function registerExploreCommands(program, deps, buildProgram) {
|
|
|
974
956
|
...triagedServerLog(serverLog, uxJudge, o.real === true),
|
|
975
957
|
...withInvariants,
|
|
976
958
|
...(uxSuccessChecks.length === 0 ? {} : { successChecks: uxSuccessChecks }),
|
|
977
|
-
...(o.successWhen === "held" || o.successWhen === "final" ? { successWhen: o.successWhen } : {}),
|
|
959
|
+
...(o.successWhen === "held" || o.successWhen === "final" || o.successWhen === "each" ? { successWhen: o.successWhen } : {}),
|
|
978
960
|
...(o.allowVacuousChecks === true ? { allowVacuousChecks: true } : {}),
|
|
979
961
|
...withPrefix,
|
|
980
962
|
...(o.actionDeltas === true ? { actionDeltas: true } : {}),
|
|
@@ -1080,11 +1062,16 @@ export function registerExploreCommands(program, deps, buildProgram) {
|
|
|
1080
1062
|
emitExplore(fail("E_EXPLORE_ASSERTION", String(err instanceof Error ? err.message : err)));
|
|
1081
1063
|
return;
|
|
1082
1064
|
}
|
|
1083
|
-
|
|
1084
|
-
|
|
1065
|
+
let successWhen;
|
|
1066
|
+
try {
|
|
1067
|
+
successWhen = successWhenFromFlags(o);
|
|
1068
|
+
}
|
|
1069
|
+
catch (err) {
|
|
1070
|
+
if (!(err instanceof GoalRunFlagError))
|
|
1071
|
+
throw err;
|
|
1072
|
+
emitExplore(fail(err.code, err.message));
|
|
1085
1073
|
return;
|
|
1086
1074
|
}
|
|
1087
|
-
const successWhen = o.successWhen === "held" || o.successWhen === "final" ? o.successWhen : undefined;
|
|
1088
1075
|
const allowlist = resolveExploreAllowlist(setupRefFreeUrl(o.url), o.allow);
|
|
1089
1076
|
// Fixtures (#140/#144): the spec and every ${setup.x} reference are validated here, before any
|
|
1090
1077
|
// browser or request; the setup itself runs just before the mission (below).
|
|
@@ -1170,6 +1157,8 @@ export function registerExploreCommands(program, deps, buildProgram) {
|
|
|
1170
1157
|
bounds: Object.keys(bounds).length > 0 ? bounds : undefined,
|
|
1171
1158
|
secrets: o.secret.length > 0 ? o.secret : undefined,
|
|
1172
1159
|
...(secretFields.length > 0 ? { secretFields } : {}),
|
|
1160
|
+
...(secretCommand === undefined ? {} : { secretCommand }),
|
|
1161
|
+
...(o.secretCmdAttempts === undefined ? {} : { secretCommandAttempts: o.secretCmdAttempts }),
|
|
1173
1162
|
...(typeFixtures.length > 0 ? { typeFixtures } : {}),
|
|
1174
1163
|
fixture: o.fixture,
|
|
1175
1164
|
outDir: o.out,
|