@jevitate/cli 0.26.1 → 0.27.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 (52) hide show
  1. package/contracts/jevitate-catalog/v2/manifest.json +4 -4
  2. package/contracts/jevitate-catalog/v2/schemas/journey.schema.json +528 -0
  3. package/dist/author-journey-cli.js +54 -3
  4. package/dist/author-journey-cli.js.map +1 -1
  5. package/dist/bin.js +950 -161
  6. package/dist/build-info.generated.js +3 -3
  7. package/dist/campaign-api.d.ts +5 -0
  8. package/dist/campaign-api.js +23 -2
  9. package/dist/campaign-api.js.map +1 -1
  10. package/dist/campaign-cli.js +8 -3
  11. package/dist/campaign-cli.js.map +1 -1
  12. package/dist/campaign-preflight.d.ts +35 -8
  13. package/dist/campaign-preflight.js +66 -7
  14. package/dist/campaign-preflight.js.map +1 -1
  15. package/dist/catalog-contract-identity.d.ts +1 -1
  16. package/dist/catalog-contract-identity.js +1 -1
  17. package/dist/check-plan.js +2 -1
  18. package/dist/check-plan.js.map +1 -1
  19. package/dist/explore-cli/options.js +1 -0
  20. package/dist/explore-cli/options.js.map +1 -1
  21. package/dist/explore-cli/shared.d.ts +1 -0
  22. package/dist/explore-cli/shared.js.map +1 -1
  23. package/dist/explore-goal.d.ts +3 -1
  24. package/dist/explore-goal.js +1 -0
  25. package/dist/explore-goal.js.map +1 -1
  26. package/dist/explore-specs.d.ts +2 -2
  27. package/dist/explore-specs.js +69 -7
  28. package/dist/explore-specs.js.map +1 -1
  29. package/dist/fixtures/multi-workspace-app.d.ts +35 -0
  30. package/dist/fixtures/multi-workspace-app.js +107 -0
  31. package/dist/fixtures/multi-workspace-app.js.map +1 -0
  32. package/dist/goal-run-flags.d.ts +2 -0
  33. package/dist/goal-run-flags.js +2 -0
  34. package/dist/goal-run-flags.js.map +1 -1
  35. package/dist/login-cli.d.ts +3 -0
  36. package/dist/login-cli.js +11 -2
  37. package/dist/login-cli.js.map +1 -1
  38. package/dist/mcp-cli-tools.js +4 -0
  39. package/dist/mcp-cli-tools.js.map +1 -1
  40. package/dist/multi-run.d.ts +7 -0
  41. package/dist/multi-run.js +30 -4
  42. package/dist/multi-run.js.map +1 -1
  43. package/dist/persona-login.d.ts +37 -0
  44. package/dist/persona-login.js +167 -4
  45. package/dist/persona-login.js.map +1 -1
  46. package/dist/suite-explore-options.d.ts +4 -0
  47. package/dist/suite-explore-options.js +1 -0
  48. package/dist/suite-explore-options.js.map +1 -1
  49. package/dist/target-config.d.ts +2 -0
  50. package/dist/target-config.js +7 -1
  51. package/dist/target-config.js.map +1 -1
  52. package/package.json +10 -10
package/dist/bin.js CHANGED
@@ -26622,6 +26622,33 @@ function goalReasonOf(i) {
26622
26622
  return "defects";
26623
26623
  }
26624
26624
  }
26625
+ var STARTUP_ATTEMPT_ERROR_CLASSES = [
26626
+ /** The host name did not resolve (`net::ERR_NAME_NOT_RESOLVED`, `ENOTFOUND`). */
26627
+ "dns",
26628
+ /** The host resolved but nothing accepted the connection (refused, reset, unreachable). */
26629
+ "connectivity",
26630
+ /** The navigation itself did not answer in time (`Timeout …ms exceeded`). */
26631
+ "navigation-timeout",
26632
+ /** The browser or its context could not be launched. */
26633
+ "browser-launch",
26634
+ /** The browser navigation failed although the origin answered the readiness probe. */
26635
+ "origin-readiness",
26636
+ /** No more specific cause could be attributed. */
26637
+ "unclassified"
26638
+ ];
26639
+ var StartupAttemptSchema = external_exports.object({
26640
+ /** 0-based position in the attempt sequence. */
26641
+ index: external_exports.number().int().nonnegative(),
26642
+ /** The browser error class, or null when this attempt reached the target. */
26643
+ errorClass: external_exports.enum(STARTUP_ATTEMPT_ERROR_CLASSES).nullable(),
26644
+ /** Plain-words, redacted reason (empty for a successful attempt). */
26645
+ message: external_exports.string(),
26646
+ /** Epoch milliseconds the attempt began (from `clock.now()`). */
26647
+ startedAt: external_exports.number().int().nonnegative(),
26648
+ /** Epoch milliseconds the attempt ended. */
26649
+ endedAt: external_exports.number().int().nonnegative()
26650
+ });
26651
+ var StartupAttemptsSchema = external_exports.array(StartupAttemptSchema);
26625
26652
 
26626
26653
  // ../domain/dist/journey-outcome.js
26627
26654
  var JOURNEY_RUN_OUTCOMES = ["ok", "healed-pending-review", "heal-exhausted", "quarantined"];
@@ -27977,6 +28004,8 @@ function createResourceSignals(platform2 = process.platform, host = defaultHostS
27977
28004
  // ../playwright/dist/reachability.js
27978
28005
  import { connect as netConnect } from "node:net";
27979
28006
  import { lookup as dnsLookup } from "node:dns/promises";
28007
+ import { get as httpGet } from "node:http";
28008
+ import { get as httpsGet } from "node:https";
27980
28009
  var CHROMIUM_UNSAFE_PORTS = /* @__PURE__ */ new Set([
27981
28010
  1,
27982
28011
  7,
@@ -28060,7 +28089,7 @@ var CHROMIUM_UNSAFE_PORTS = /* @__PURE__ */ new Set([
28060
28089
  10080
28061
28090
  ]);
28062
28091
  var LOOPBACK = /^(localhost|127\.\d+\.\d+\.\d+|\[?::1\]?|0\.0\.0\.0)$/i;
28063
- async function probeReachable(url2, deps = {}) {
28092
+ function probeTarget(url2, deps) {
28064
28093
  let u;
28065
28094
  try {
28066
28095
  u = new URL(url2);
@@ -28073,22 +28102,24 @@ async function probeReachable(url2, deps = {}) {
28073
28102
  const port = u.port !== "" ? Number(u.port) : u.protocol === "https:" ? 443 : 80;
28074
28103
  if (CHROMIUM_UNSAFE_PORTS.has(port))
28075
28104
  return null;
28076
- const where = `is the app running at ${u.origin}?`;
28077
28105
  const loopback = LOOPBACK.test(u.hostname);
28078
- if (!loopback) {
28106
+ const timeoutMs = loopback ? deps.loopbackTimeoutMs ?? 3e3 : deps.remoteTimeoutMs ?? 1e4;
28107
+ return { host, port, origin: `${u.origin}/`, loopback, timeoutMs, where: `is the app running at ${u.origin}?` };
28108
+ }
28109
+ async function probeTransport(t, deps) {
28110
+ if (!t.loopback) {
28079
28111
  try {
28080
- await (deps.lookup ?? ((h) => dnsLookup(h)))(host);
28112
+ await (deps.lookup ?? ((h) => dnsLookup(h)))(t.host);
28081
28113
  } catch (e) {
28082
28114
  const code = e.code;
28083
28115
  if (code === "ENOTFOUND" || code === "EAI_NONAME" || code === "EAI_FAIL")
28084
- return `host not found (${host}) \u2014 ${where}`;
28085
- return null;
28116
+ return { kind: "dns", detail: `host not found (${t.host}) \u2014 ${t.where}` };
28117
+ return { kind: "reachable" };
28086
28118
  }
28087
28119
  }
28088
- const timeoutMs = loopback ? deps.loopbackTimeoutMs ?? 3e3 : deps.remoteTimeoutMs ?? 1e4;
28089
- return new Promise((resolve25) => {
28120
+ const detail = await new Promise((resolve25) => {
28090
28121
  let settled = false;
28091
- const socket = (deps.connect ?? ((p, h) => netConnect(p, h)))(port, host);
28122
+ const socket = (deps.connect ?? ((p, h) => netConnect(p, h)))(t.port, t.host);
28092
28123
  const done = (reason) => {
28093
28124
  if (settled)
28094
28125
  return;
@@ -28097,18 +28128,62 @@ async function probeReachable(url2, deps = {}) {
28097
28128
  socket.destroy();
28098
28129
  resolve25(reason);
28099
28130
  };
28100
- const timer = clock.setTimeout(() => done(loopback ? `connection refused (nothing answered a connect within ${Math.round(timeoutMs / 1e3)}s) \u2014 ${where}` : `no answer to a connect within ${Math.round(timeoutMs / 1e3)}s \u2014 ${where}`), timeoutMs);
28131
+ const timer = clock.setTimeout(() => done(t.loopback ? `connection refused (nothing answered a connect within ${Math.round(t.timeoutMs / 1e3)}s) \u2014 ${t.where}` : `no answer to a connect within ${Math.round(t.timeoutMs / 1e3)}s \u2014 ${t.where}`), t.timeoutMs);
28101
28132
  socket.once("connect", () => done(null));
28102
28133
  socket.once("error", (e) => {
28103
28134
  if (e.code === "ECONNREFUSED")
28104
- return done(`connection refused \u2014 ${where}`);
28135
+ return done(`connection refused \u2014 ${t.where}`);
28105
28136
  if (e.code === "ENOTFOUND" || e.code === "EAI_NONAME")
28106
- return done(`host not found (${host}) \u2014 ${where}`);
28137
+ return done(`host not found (${t.host}) \u2014 ${t.where}`);
28107
28138
  if (e.code === "EHOSTUNREACH" || e.code === "ENETUNREACH")
28108
- return done(`host unreachable (${host}) \u2014 ${where}`);
28139
+ return done(`host unreachable (${t.host}) \u2014 ${t.where}`);
28109
28140
  done(null);
28110
28141
  });
28111
28142
  });
28143
+ return detail === null ? { kind: "reachable" } : { kind: "connect", detail };
28144
+ }
28145
+ async function probeReachable(url2, deps = {}) {
28146
+ const t = probeTarget(url2, deps);
28147
+ if (t === null)
28148
+ return null;
28149
+ const transport = await probeTransport(t, deps);
28150
+ return transport.kind === "reachable" ? null : transport.detail;
28151
+ }
28152
+ var READY_STATUS = (status) => status >= 200 && status < 400;
28153
+ function defaultHttpRequest(url2, timeoutMs) {
28154
+ return new Promise((resolve25) => {
28155
+ let settled = false;
28156
+ const get = new URL(url2).protocol === "https:" ? httpsGet : httpGet;
28157
+ let timer;
28158
+ const done = (result) => {
28159
+ if (settled)
28160
+ return;
28161
+ settled = true;
28162
+ clock.clearTimeout(timer);
28163
+ resolve25(result);
28164
+ };
28165
+ const req = get(url2, { method: "GET", headers: { accept: "*/*" } }, (res) => {
28166
+ res.resume();
28167
+ done({ status: res.statusCode ?? 0 });
28168
+ });
28169
+ timer = clock.setTimeout(() => {
28170
+ req.destroy();
28171
+ done({ error: `no response within ${Math.round(timeoutMs / 1e3)}s` });
28172
+ }, timeoutMs);
28173
+ req.once("error", (e) => done({ error: e.message.split("\n")[0] ?? e.message }));
28174
+ });
28175
+ }
28176
+ async function probeOriginReadiness(url2, deps = {}) {
28177
+ const t = probeTarget(url2, deps);
28178
+ if (t === null)
28179
+ return { class: "ready", detail: `${url2} is not an http(s) origin \u2014 not probed` };
28180
+ const transport = await probeTransport(t, deps);
28181
+ if (transport.kind !== "reachable")
28182
+ return { class: transport.kind, detail: transport.detail };
28183
+ const res = await (deps.httpRequest ?? defaultHttpRequest)(t.origin, t.timeoutMs);
28184
+ if ("error" in res)
28185
+ return { class: "http", detail: `no HTTP response (${res.error}) \u2014 ${t.where}` };
28186
+ return READY_STATUS(res.status) ? { class: "ready", status: res.status, detail: `origin answered HTTP ${res.status} \u2014 ${t.where}` } : { class: "http", status: res.status, detail: `origin answered HTTP ${res.status} (not ready) \u2014 ${t.where}` };
28112
28187
  }
28113
28188
 
28114
28189
  // ../playwright/dist/extensions.js
@@ -29372,6 +29447,7 @@ var PlaywrightBrowserPort = class {
29372
29447
  #liveness;
29373
29448
  #openTimeoutMs;
29374
29449
  #probe;
29450
+ #originProbe;
29375
29451
  #governor;
29376
29452
  constructor(deps = {}) {
29377
29453
  this.#governor = deps.governor;
@@ -29382,6 +29458,7 @@ var PlaywrightBrowserPort = class {
29382
29458
  this.#platform = deps.platform ?? process.platform;
29383
29459
  this.#pool = deps.pool;
29384
29460
  this.#probe = deps.probe ?? ((url2) => probeReachable(url2));
29461
+ this.#originProbe = deps.originProbe ?? ((url2) => probeOriginReadiness(url2));
29385
29462
  }
29386
29463
  async open(opts) {
29387
29464
  const emulation = resolveEmulation({ viewport: opts.viewport, device: opts.device });
@@ -29442,7 +29519,7 @@ var PlaywrightBrowserPort = class {
29442
29519
  await lease.release().catch(() => void 0);
29443
29520
  throw err;
29444
29521
  }
29445
- return pooledSession(lease, page, this.#watch(page), this.#probe, await videoPathOf(page), navGuard);
29522
+ return pooledSession(lease, page, this.#watch(page), this.#probe, this.#originProbe, await videoPathOf(page), navGuard);
29446
29523
  }
29447
29524
  /**
29448
29525
  * A persistent-context session: the caller's `persistentProfile`, or (#256) a throwaway profile
@@ -29504,6 +29581,7 @@ var PlaywrightBrowserPort = class {
29504
29581
  return JSON.stringify(await context.storageState());
29505
29582
  },
29506
29583
  probeReachable: this.#probe,
29584
+ probeOriginReadiness: this.#originProbe,
29507
29585
  drainBlockedNavigations: () => navGuard?.drain() ?? [],
29508
29586
  async close() {
29509
29587
  watchdog?.stop();
@@ -29580,7 +29658,7 @@ function governed(session2, governor, ticket, limits) {
29580
29658
  }
29581
29659
  };
29582
29660
  }
29583
- function pooledSession(lease, page, watchdog, probe, videoPath, navGuard) {
29661
+ function pooledSession(lease, page, watchdog, probe, originProbe, videoPath, navGuard) {
29584
29662
  const context = lease.context;
29585
29663
  const alive = () => {
29586
29664
  if (lease.crash !== void 0)
@@ -29607,6 +29685,7 @@ function pooledSession(lease, page, watchdog, probe, videoPath, navGuard) {
29607
29685
  return JSON.stringify(await context.storageState());
29608
29686
  },
29609
29687
  probeReachable: probe,
29688
+ probeOriginReadiness: originProbe,
29610
29689
  drainBlockedNavigations: () => navGuard?.drain() ?? [],
29611
29690
  async close() {
29612
29691
  watchdog?.stop();
@@ -32934,6 +33013,86 @@ function selectJsonValues(doc, path2) {
32934
33013
  return cur.filter((v) => v !== void 0);
32935
33014
  }
32936
33015
 
33016
+ // ../recording/dist/assertion-refs.js
33017
+ var SECRET_PARAM_NAME_RE = /pass(word|code|phrase)?|secret|token|api[_-]?key|otp|pin\b|credential|session|cookie/i;
33018
+ var PLACEHOLDER_RE = /\$\{([^}]*)\}/g;
33019
+ var E_ASSERTION_REF_UNBOUND = "E_ASSERTION_REF_UNBOUND";
33020
+ var E_ASSERTION_REF_SECRET = "E_ASSERTION_REF_SECRET";
33021
+ var E_ASSERTION_REF_TYPE = "E_ASSERTION_REF_TYPE";
33022
+ var AssertionRefError = class extends Error {
33023
+ code;
33024
+ ref;
33025
+ constructor(code, ref, detail) {
33026
+ super(`${code}: \${${ref}} ${detail}`);
33027
+ this.code = code;
33028
+ this.ref = ref;
33029
+ this.name = "AssertionRefError";
33030
+ }
33031
+ };
33032
+ function placeholderNames(text2) {
33033
+ return [...text2.matchAll(PLACEHOLDER_RE)].map((m) => m[1] ?? "");
33034
+ }
33035
+ function expandPlaceholders(text2, resolve25) {
33036
+ return text2.replace(PLACEHOLDER_RE, (_m, name) => resolve25(name));
33037
+ }
33038
+ function operands(a) {
33039
+ switch (a.kind) {
33040
+ case "textIncludes":
33041
+ return [a.text];
33042
+ case "valueEquals":
33043
+ return [a.value];
33044
+ case "attr":
33045
+ return a.value === void 0 ? [] : [a.value];
33046
+ case "urlIncludes":
33047
+ return [a.text];
33048
+ default:
33049
+ return [];
33050
+ }
33051
+ }
33052
+ function operandRefs(op) {
33053
+ return typeof op === "string" ? placeholderNames(op) : [op.var];
33054
+ }
33055
+ function assertionRefs(a) {
33056
+ return [...new Set(operands(a).flatMap(operandRefs))];
33057
+ }
33058
+ function describeOperand(op) {
33059
+ return typeof op === "string" ? op : `\${${op.var}}`;
33060
+ }
33061
+ function literalOperand(op) {
33062
+ if (typeof op === "string")
33063
+ return op;
33064
+ throw new AssertionRefError(E_ASSERTION_REF_UNBOUND, op.var, "was not resolved before the check ran");
33065
+ }
33066
+ function bound(name, lookup) {
33067
+ const b2 = lookup(name);
33068
+ if (b2 === void 0)
33069
+ throw new AssertionRefError(E_ASSERTION_REF_UNBOUND, name, "is not bound in this run (declare it as a parameter, or extract it in an earlier step)");
33070
+ if (b2.secret)
33071
+ throw new AssertionRefError(E_ASSERTION_REF_SECRET, name, "is a secret \u2014 an assertion's expected text is model-visible and persisted, so it may not use one");
33072
+ if (typeof b2.value !== "string")
33073
+ throw new AssertionRefError(E_ASSERTION_REF_TYPE, name, `is bound to a ${b2.value === null ? "null" : typeof b2.value}, not a string`);
33074
+ return b2.value;
33075
+ }
33076
+ function resolveOperand(op, lookup) {
33077
+ return typeof op === "string" ? expandPlaceholders(op, (n3) => bound(n3, lookup)) : bound(op.var, lookup);
33078
+ }
33079
+ function resolveAssertionRefs(a, lookup) {
33080
+ if (assertionRefs(a).length === 0)
33081
+ return a;
33082
+ switch (a.kind) {
33083
+ case "textIncludes":
33084
+ return { ...a, text: resolveOperand(a.text, lookup) };
33085
+ case "valueEquals":
33086
+ return { ...a, value: resolveOperand(a.value, lookup) };
33087
+ case "attr":
33088
+ return a.value === void 0 ? a : { ...a, value: resolveOperand(a.value, lookup) };
33089
+ case "urlIncludes":
33090
+ return { ...a, text: expandPlaceholders(a.text, (n3) => encodeUrlParamValue(bound(n3, lookup))) };
33091
+ default:
33092
+ return a;
33093
+ }
33094
+ }
33095
+
32937
33096
  // ../recording/dist/schema.js
32938
33097
  function targetDescriptorSelectorProblem(d) {
32939
33098
  if (d.testId || d.role && d.name || d.label || d.text || d.css)
@@ -33261,8 +33420,33 @@ var StatusSpecSchema = external_exports.union([
33261
33420
  ]);
33262
33421
  var METHOD = external_exports.string().min(1).max(20);
33263
33422
  var PATH_GLOB = external_exports.string().min(1).max(2e3);
33264
- var RequestMadeSchema = external_exports.object({ kind: external_exports.literal("requestMade"), method: METHOD, pathGlob: PATH_GLOB }).strict();
33265
- var ResponseStatusSchema = external_exports.object({ kind: external_exports.literal("responseStatus"), method: METHOD, pathGlob: PATH_GLOB, status: StatusSpecSchema }).strict();
33423
+ var COUNT_BOUND = external_exports.number().int().min(0).max(1e3);
33424
+ var NetworkCheckCountSchema = external_exports.union([
33425
+ external_exports.object({ exact: COUNT_BOUND }).strict(),
33426
+ external_exports.object({ min: COUNT_BOUND }).strict(),
33427
+ external_exports.object({ max: COUNT_BOUND }).strict()
33428
+ ]);
33429
+ var BodyFieldShapeSchema = external_exports.object({ name: external_exports.string().min(1).max(200), predicate: external_exports.enum(["exists", "absent", "string", "number", "boolean", "null", "array", "object"]) }).strict();
33430
+ var RequestBodyShapeSchema = external_exports.object({ fields: external_exports.array(BodyFieldShapeSchema).min(1).max(50) }).strict().superRefine((shape3, ctx) => {
33431
+ shape3.fields.forEach((field3, index) => {
33432
+ if (SECRET_PARAM_NAME_RE.test(field3.name)) {
33433
+ ctx.addIssue({
33434
+ code: "custom",
33435
+ message: `bodyShape field ${JSON.stringify(field3.name)} names a credential \u2014 a body shape declares field names only`,
33436
+ path: ["fields", index, "name"]
33437
+ });
33438
+ }
33439
+ });
33440
+ });
33441
+ var RequestMadeSchema = external_exports.object({ kind: external_exports.literal("requestMade"), method: METHOD, pathGlob: PATH_GLOB, count: NetworkCheckCountSchema.optional(), bodyShape: RequestBodyShapeSchema.optional() }).strict();
33442
+ var ResponseStatusSchema = external_exports.object({
33443
+ kind: external_exports.literal("responseStatus"),
33444
+ method: METHOD,
33445
+ pathGlob: PATH_GLOB,
33446
+ status: StatusSpecSchema,
33447
+ count: NetworkCheckCountSchema.optional(),
33448
+ bodyShape: RequestBodyShapeSchema.optional()
33449
+ }).strict();
33266
33450
  var NetworkCheckSchema = external_exports.discriminatedUnion("kind", [RequestMadeSchema, ResponseStatusSchema]);
33267
33451
  var HTTP_CHECK_TEXT = external_exports.string().min(1).max(2e3);
33268
33452
  var HttpCheckUrlSchema = external_exports.string().min(1).max(4096).refine((url2) => /^https?:\/\//i.test(url2), { message: "http check url must be an absolute http(s):// URL" }).refine((url2) => !/[\t\n\r\s]/.test(url2), { message: "http check url must not contain whitespace" }).superRefine((url2, ctx) => {
@@ -35178,86 +35362,6 @@ function fixtureEscapesRoot(raw, root2) {
35178
35362
  return { escapes: !isInsideRoot(real, realRoot), candidate, real };
35179
35363
  }
35180
35364
 
35181
- // ../recording/dist/assertion-refs.js
35182
- var SECRET_PARAM_NAME_RE = /pass(word|code|phrase)?|secret|token|api[_-]?key|otp|pin\b|credential|session|cookie/i;
35183
- var PLACEHOLDER_RE = /\$\{([^}]*)\}/g;
35184
- var E_ASSERTION_REF_UNBOUND = "E_ASSERTION_REF_UNBOUND";
35185
- var E_ASSERTION_REF_SECRET = "E_ASSERTION_REF_SECRET";
35186
- var E_ASSERTION_REF_TYPE = "E_ASSERTION_REF_TYPE";
35187
- var AssertionRefError = class extends Error {
35188
- code;
35189
- ref;
35190
- constructor(code, ref, detail) {
35191
- super(`${code}: \${${ref}} ${detail}`);
35192
- this.code = code;
35193
- this.ref = ref;
35194
- this.name = "AssertionRefError";
35195
- }
35196
- };
35197
- function placeholderNames(text2) {
35198
- return [...text2.matchAll(PLACEHOLDER_RE)].map((m) => m[1] ?? "");
35199
- }
35200
- function expandPlaceholders(text2, resolve25) {
35201
- return text2.replace(PLACEHOLDER_RE, (_m, name) => resolve25(name));
35202
- }
35203
- function operands(a) {
35204
- switch (a.kind) {
35205
- case "textIncludes":
35206
- return [a.text];
35207
- case "valueEquals":
35208
- return [a.value];
35209
- case "attr":
35210
- return a.value === void 0 ? [] : [a.value];
35211
- case "urlIncludes":
35212
- return [a.text];
35213
- default:
35214
- return [];
35215
- }
35216
- }
35217
- function operandRefs(op) {
35218
- return typeof op === "string" ? placeholderNames(op) : [op.var];
35219
- }
35220
- function assertionRefs(a) {
35221
- return [...new Set(operands(a).flatMap(operandRefs))];
35222
- }
35223
- function describeOperand(op) {
35224
- return typeof op === "string" ? op : `\${${op.var}}`;
35225
- }
35226
- function literalOperand(op) {
35227
- if (typeof op === "string")
35228
- return op;
35229
- throw new AssertionRefError(E_ASSERTION_REF_UNBOUND, op.var, "was not resolved before the check ran");
35230
- }
35231
- function bound(name, lookup) {
35232
- const b2 = lookup(name);
35233
- if (b2 === void 0)
35234
- throw new AssertionRefError(E_ASSERTION_REF_UNBOUND, name, "is not bound in this run (declare it as a parameter, or extract it in an earlier step)");
35235
- if (b2.secret)
35236
- throw new AssertionRefError(E_ASSERTION_REF_SECRET, name, "is a secret \u2014 an assertion's expected text is model-visible and persisted, so it may not use one");
35237
- if (typeof b2.value !== "string")
35238
- throw new AssertionRefError(E_ASSERTION_REF_TYPE, name, `is bound to a ${b2.value === null ? "null" : typeof b2.value}, not a string`);
35239
- return b2.value;
35240
- }
35241
- function resolveOperand(op, lookup) {
35242
- return typeof op === "string" ? expandPlaceholders(op, (n3) => bound(n3, lookup)) : bound(op.var, lookup);
35243
- }
35244
- function resolveAssertionRefs(a, lookup) {
35245
- if (assertionRefs(a).length === 0)
35246
- return a;
35247
- switch (a.kind) {
35248
- case "textIncludes":
35249
- return { ...a, text: resolveOperand(a.text, lookup) };
35250
- case "valueEquals":
35251
- return { ...a, value: resolveOperand(a.value, lookup) };
35252
- case "attr":
35253
- return a.value === void 0 ? a : { ...a, value: resolveOperand(a.value, lookup) };
35254
- case "urlIncludes":
35255
- return { ...a, text: expandPlaceholders(a.text, (n3) => encodeUrlParamValue(bound(n3, lookup))) };
35256
- default:
35257
- return a;
35258
- }
35259
- }
35260
-
35261
35365
  // ../interpreter/dist/resolve-target.js
35262
35366
  var ReplayTargetError = class extends Error {
35263
35367
  kind;
@@ -39362,6 +39466,12 @@ function classifyRequest(r, apiPrefixes = []) {
39362
39466
  }
39363
39467
  return "other";
39364
39468
  }
39469
+ function isDevServerAssetRead(r, apiPrefixes = []) {
39470
+ const method = r.method.toUpperCase();
39471
+ if (method !== "GET" && method !== "HEAD")
39472
+ return false;
39473
+ return classifyRequest({ url: r.url, resourceType: r.resourceType }, apiPrefixes) === "asset";
39474
+ }
39365
39475
  var SLOWEST = 3;
39366
39476
  function endpointOf(method, url2) {
39367
39477
  return `${method.toUpperCase()} ${normalizeRoute(redactUrl(url2))}`;
@@ -39688,6 +39798,33 @@ function walk(out, key2, node2) {
39688
39798
  }
39689
39799
  }
39690
39800
  }
39801
+ function predicateSatisfied(predicate, type) {
39802
+ if (predicate === "exists")
39803
+ return type !== void 0;
39804
+ if (predicate === "absent")
39805
+ return type === void 0;
39806
+ return type === predicate;
39807
+ }
39808
+ function bodyShapeMismatches(shape3, fields) {
39809
+ const types = /* @__PURE__ */ new Map();
39810
+ for (const f2 of fields)
39811
+ types.set(f2.name, f2.type);
39812
+ const out = [];
39813
+ for (const declared of shape3) {
39814
+ if (sensitive(declared.name))
39815
+ continue;
39816
+ const type = types.get(declared.name);
39817
+ if (predicateSatisfied(declared.predicate, type))
39818
+ continue;
39819
+ if (declared.predicate === "absent")
39820
+ out.push(`${declared.name} (expected absent, got ${type})`);
39821
+ else if (type === void 0)
39822
+ out.push(`${declared.name} (missing)`);
39823
+ else
39824
+ out.push(`${declared.name} (expected ${declared.predicate}, got ${type})`);
39825
+ }
39826
+ return out;
39827
+ }
39691
39828
  function bodyValues(body, contentType) {
39692
39829
  if (body === null || body === "" || body.length > MAX_BODY_CHARS)
39693
39830
  return [];
@@ -39829,6 +39966,69 @@ var INSTRUMENT = `(() => {
39829
39966
  } catch (e) { /* LCP not exposed by this browser */ }
39830
39967
  })();`;
39831
39968
  var MAX_CAPTURED = 2e4;
39969
+ var MAX_DECLARED_BODY_CHARS = 64e3;
39970
+ var MAX_DECLARED_FIELDS = 50;
39971
+ function declaredFieldType(value) {
39972
+ if (value === null)
39973
+ return "null";
39974
+ if (Array.isArray(value))
39975
+ return "array";
39976
+ if (typeof value === "string")
39977
+ return "string";
39978
+ if (typeof value === "number")
39979
+ return "number";
39980
+ if (typeof value === "boolean")
39981
+ return "boolean";
39982
+ return "object";
39983
+ }
39984
+ function pushDeclaredField(out, name, value) {
39985
+ if (out.length >= MAX_DECLARED_FIELDS || SECRET_PARAM_NAME_RE.test(name))
39986
+ return;
39987
+ out.push({ name, type: declaredFieldType(value) });
39988
+ }
39989
+ function walkDeclaredFields(out, path2, node2) {
39990
+ if (out.length >= MAX_DECLARED_FIELDS || node2 === null || typeof node2 !== "object")
39991
+ return;
39992
+ if (Array.isArray(node2)) {
39993
+ for (let i = 0; i < node2.length && out.length < MAX_DECLARED_FIELDS; i++) {
39994
+ const child = path2 === "" ? String(i) : `${path2}.${i}`;
39995
+ pushDeclaredField(out, child, node2[i]);
39996
+ walkDeclaredFields(out, child, node2[i]);
39997
+ }
39998
+ return;
39999
+ }
40000
+ for (const [key2, value] of Object.entries(node2)) {
40001
+ if (out.length >= MAX_DECLARED_FIELDS)
40002
+ return;
40003
+ const child = path2 === "" ? key2 : `${path2}.${key2}`;
40004
+ pushDeclaredField(out, child, value);
40005
+ walkDeclaredFields(out, child, value);
40006
+ }
40007
+ }
40008
+ function declaredFields(body, contentType) {
40009
+ if (body === null || body === "" || body.length > MAX_DECLARED_BODY_CHARS)
40010
+ return [];
40011
+ const type = (contentType ?? "").toLowerCase();
40012
+ const out = [];
40013
+ if (type.includes("application/x-www-form-urlencoded")) {
40014
+ for (const [key2] of new URLSearchParams(body)) {
40015
+ if (out.length >= MAX_DECLARED_FIELDS)
40016
+ break;
40017
+ if (out.some((f2) => f2.name === key2) || SECRET_PARAM_NAME_RE.test(key2))
40018
+ continue;
40019
+ out.push({ name: key2, type: "string" });
40020
+ }
40021
+ return out;
40022
+ }
40023
+ if (type === "" || type.includes("json")) {
40024
+ try {
40025
+ walkDeclaredFields(out, "", JSON.parse(body));
40026
+ } catch {
40027
+ return [];
40028
+ }
40029
+ }
40030
+ return out;
40031
+ }
39832
40032
  var RequestCapture = class {
39833
40033
  /**
39834
40034
  * #617: every request that finished during the capture (its whole completion history), each
@@ -39944,9 +40144,15 @@ var PageMonitor = class {
39944
40144
  #background = /* @__PURE__ */ new Map();
39945
40145
  #ignore = () => false;
39946
40146
  #longPollMs = DEFAULT_LONG_POLL_MS;
40147
+ /** #745: the target opted into the dev-server (Vite/HMR) settle policy. */
40148
+ #devServer = false;
40149
+ /** #745: `timing.apiPrefixes` — a configured API path is never read as a dev-server asset. */
40150
+ #apiPrefixes = [];
39947
40151
  #documentNavStartedAt = null;
39948
40152
  #instrumented;
39949
40153
  #captures = /* @__PURE__ */ new Set();
40154
+ /** Consumers observing the declared field names and JSON types of each write request. */
40155
+ #declaredHooks = /* @__PURE__ */ new Set();
39950
40156
  constructor(page, now2 = clock.now) {
39951
40157
  this.#page = page;
39952
40158
  this.#now = now2;
@@ -39977,6 +40183,19 @@ var PageMonitor = class {
39977
40183
  if (this.#writeBodies.length > MAX_WRITE_BODIES)
39978
40184
  this.#writeBodies.shift();
39979
40185
  }
40186
+ if (this.#declaredHooks.size > 0) {
40187
+ let fields = [];
40188
+ try {
40189
+ fields = declaredFields(r.postData(), requestContentType2);
40190
+ } catch {
40191
+ }
40192
+ for (const hook of this.#declaredHooks) {
40193
+ try {
40194
+ hook({ method: r.method().toUpperCase(), url: redactUrl(r.url()), path: pathOf3(r.url()), fields });
40195
+ } catch {
40196
+ }
40197
+ }
40198
+ }
39980
40199
  }
39981
40200
  for (const c of this.#captures) {
39982
40201
  c.began(r, {
@@ -40116,6 +40335,17 @@ var PageMonitor = class {
40116
40335
  })();
40117
40336
  return this.#instrumented;
40118
40337
  }
40338
+ /**
40339
+ * Registers a per-request hook fed the declared field names and JSON types of each write's body
40340
+ * (never values; credential-named fields are dropped by `SECRET_PARAM_NAME_RE`). Returns an
40341
+ * unsubscribe function. Nothing is added to the capture's `CapturedRequest` records.
40342
+ */
40343
+ onDeclaredFields(hook) {
40344
+ this.#declaredHooks.add(hook);
40345
+ return () => {
40346
+ this.#declaredHooks.delete(hook);
40347
+ };
40348
+ }
40119
40349
  /** Starts keeping every request that finishes from now on (until `stopCapture`). */
40120
40350
  startCapture() {
40121
40351
  const c = new RequestCapture();
@@ -40126,10 +40356,17 @@ var PageMonitor = class {
40126
40356
  stopCapture(capture2) {
40127
40357
  this.#captures.delete(capture2);
40128
40358
  }
40129
- /** Applies the target's settle configuration (idempotent; the latest call wins). */
40130
- configure(cfg) {
40359
+ /**
40360
+ * Applies the target's settle configuration (idempotent; the latest call wins). `apiPrefixes` is
40361
+ * `timing.apiPrefixes` (#745): perceive re-applies only the settle config before every wait, so
40362
+ * the prefixes a caller with the timing config sets here are kept rather than cleared.
40363
+ */
40364
+ configure(cfg, apiPrefixes) {
40131
40365
  this.#ignore = urlMatcher(cfg?.ignoreRequests);
40132
40366
  this.#longPollMs = cfg?.longPollMs ?? DEFAULT_LONG_POLL_MS;
40367
+ this.#devServer = cfg?.devServer === true;
40368
+ if (apiPrefixes !== void 0)
40369
+ this.#apiPrefixes = apiPrefixes;
40133
40370
  for (const [r] of this.#inflight)
40134
40371
  if (this.#ignore(r.url()))
40135
40372
  this.#background.set(r, "ignored");
@@ -40361,7 +40598,9 @@ var PageMonitor = class {
40361
40598
  */
40362
40599
  async waitSettled(opts) {
40363
40600
  await this.instrument();
40364
- const ignore = opts.ignoreRequest;
40601
+ const ignoreRequest = opts.ignoreRequest;
40602
+ const discountAssets = opts.discountAssets === true || this.#devServer;
40603
+ const ignore = ignoreRequest === void 0 && !discountAssets ? void 0 : (r) => (ignoreRequest?.(r) ?? false) || discountAssets && isDevServerAssetRead(r, this.#apiPrefixes);
40365
40604
  const quietMs = opts.quietMs ?? SETTLE_QUIET_MS;
40366
40605
  const start = this.#now();
40367
40606
  const remaining = () => opts.ceilingMs - (this.#now() - start);
@@ -44048,6 +44287,22 @@ function describeUnreachable(message2, netErrorText) {
44048
44287
  return "timed out before any response";
44049
44288
  return messageOf(new Error(message2));
44050
44289
  }
44290
+ function startupErrorClass(input2) {
44291
+ const text2 = `${input2.message}
44292
+ ${input2.netErrorText ?? ""}`;
44293
+ if (input2.originReady === true && isUnreachableTarget(input2.message))
44294
+ return "origin-readiness";
44295
+ const code = netErrorCode(text2);
44296
+ if (code === "ERR_NAME_NOT_RESOLVED" || /ENOTFOUND|EAI_NONAME|EAI_FAIL/i.test(text2))
44297
+ return "dns";
44298
+ if (code === "ERR_CONNECTION_REFUSED" || code === "ERR_CONNECTION_RESET" || code === "ERR_CONNECTION_CLOSED" || code === "ERR_CONNECTION_TIMED_OUT" || code === "ERR_ADDRESS_UNREACHABLE" || /ECONNREFUSED|ECONNRESET|EHOSTUNREACH|ENETUNREACH/i.test(text2))
44299
+ return "connectivity";
44300
+ if (/Timeout \d+ms exceeded|timed out/i.test(text2))
44301
+ return "navigation-timeout";
44302
+ if (/browserType\.launch|Executable doesn't exist|Failed to launch/i.test(text2))
44303
+ return "browser-launch";
44304
+ return "unclassified";
44305
+ }
44051
44306
  async function tryTriage(generation, input2) {
44052
44307
  try {
44053
44308
  const r = await generation.generate("triage.narrative", input2);
@@ -44189,6 +44444,7 @@ var MAX_QUIET_WAITS = 3;
44189
44444
  var MAX_MOVING_SCROLLS = 12;
44190
44445
  var LAST_CHANCE_NOTE = "no progress: the last steps left the page unchanged and you have seen the whole page \u2014 act on a visible control, report the answer, or say done/blocked now";
44191
44446
  var LAST_CHANCE_NO_WRITE_NOTE = "no progress: the last steps left the page unchanged and you have seen the whole page, but no write of this run has succeeded yet \u2014 the goal asks for a change the app must receive: go back and press the control that submits it (Import, Save, Send\u2026), or say blocked if the app offers none";
44447
+ var NO_PROGRESS_RECOVERY_NOTE = (name, locator) => `no progress: the last steps left the page unchanged \u2014 the unique safe forward control ${JSON.stringify(name)} (${locator}) is the way on: act through it now, or the run ends`;
44192
44448
 
44193
44449
  // ../explore/dist/run-depth.js
44194
44450
  var OPEN_ENDED_MIN_ACTIONS = 12;
@@ -48201,6 +48457,7 @@ var PageDeltas = class {
48201
48457
  // ../explore/dist/goal-loop/context/observation.js
48202
48458
  function initObservation(ctx, cfg) {
48203
48459
  ctx.timings = [];
48460
+ monitorFor(ctx.page).configure(cfg.settle, cfg.timingConfig?.apiPrefixes);
48204
48461
  ctx.isWrite = writeClassifier(cfg.safety?.readRequests === void 0 ? {} : { readRequests: cfg.safety.readRequests });
48205
48462
  ctx.safety = new SafetyPolicy(cfg.safety, { goal: cfg.goal });
48206
48463
  ctx.firstParty = new FirstPartyOrigins(cfg.allowlist);
@@ -48700,6 +48957,13 @@ function takeRecoveryTurn(ctx) {
48700
48957
  turn.pending = false;
48701
48958
  return turn.safe;
48702
48959
  }
48960
+ function armRecoveryTurn(ctx, safe) {
48961
+ const turn = RECOVERY_TURNS.get(ctx);
48962
+ if (turn !== void 0 && turn.given)
48963
+ return false;
48964
+ RECOVERY_TURNS.set(ctx, { safe, given: true, pending: true });
48965
+ return true;
48966
+ }
48703
48967
  function resumeIdentity(e) {
48704
48968
  return JSON.stringify(e.resume);
48705
48969
  }
@@ -48725,14 +48989,15 @@ function instructionText(controls, visibleText) {
48725
48989
  }
48726
48990
  return text2;
48727
48991
  }
48728
- function primaryForwardControl(ctx, step) {
48992
+ function forwardControls(ctx, step) {
48729
48993
  const goalTerms = terms(ctx.goalText);
48730
48994
  if (goalTerms.size === 0)
48731
- return null;
48995
+ return [];
48732
48996
  const instruction = instructionText(step.modelControls, step.visibleText);
48733
48997
  const instructionTerms = terms(instruction);
48734
48998
  if (instructionTerms.size === 0 || !DIRECTIVE.test(instruction))
48735
- return null;
48999
+ return [];
49000
+ const out = [];
48736
49001
  for (const control of step.modelControls) {
48737
49002
  if (!control.enabled)
48738
49003
  continue;
@@ -48745,9 +49010,12 @@ function primaryForwardControl(ctx, step) {
48745
49010
  if (controlTerms.size === 0)
48746
49011
  continue;
48747
49012
  if ([...controlTerms].some((t) => goalTerms.has(t)) && [...controlTerms].every((t) => instructionTerms.has(t)))
48748
- return control;
49013
+ out.push(control);
48749
49014
  }
48750
- return null;
49015
+ return out;
49016
+ }
49017
+ function primaryForwardControl(ctx, step) {
49018
+ return forwardControls(ctx, step)[0] ?? null;
48751
49019
  }
48752
49020
  async function handleBlocked(ctx, step) {
48753
49021
  const { modelControls, record: record3 } = step;
@@ -48822,15 +49090,14 @@ async function handleBlocked(ctx, step) {
48822
49090
  }
48823
49091
  const evidence = await collectActionableControlEvidence(ctx.page, { screenshots: false }).catch(() => []);
48824
49092
  const safe = evidence.filter((e) => e.kind === "safe" && e.name !== "");
48825
- const turn = RECOVERY_TURNS.get(ctx);
48826
- if ((turn === void 0 || !turn.given) && safe.length > 0) {
48827
- RECOVERY_TURNS.set(ctx, { safe, given: true, pending: true });
49093
+ if (safe.length > 0 && armRecoveryTurn(ctx, safe)) {
48828
49094
  const note = `blocked: one recovery turn is available through the safe control(s) ${safe.map((e) => `${quote(e.name, 40)} (${e.locator})`).join(", ")} \u2014 act through one of them or the run ends blocked`;
48829
49095
  ctx.history.push(note);
48830
49096
  record3(false, note, { origin: "engine" });
48831
49097
  return "continue";
48832
49098
  }
48833
49099
  record3(true, "model blocked");
49100
+ const turn = RECOVERY_TURNS.get(ctx);
48834
49101
  const turnSafe = turn !== void 0 && turn.given && turn.safe.length > 0 ? turn.safe : null;
48835
49102
  const excluded = evidence.filter((e) => e.kind !== "safe");
48836
49103
  const classes = [...new Set(excluded.map((e) => e.kind))].join(", ");
@@ -50378,7 +50645,8 @@ async function checkProgress(ctx, step) {
50378
50645
  if (!ctx.lastChanceGiven) {
50379
50646
  ctx.lastChanceGiven = true;
50380
50647
  ctx.lastChanceTurn = true;
50381
- ctx.history.push(ctx.writeGoal && !ctx.wroteOk ? LAST_CHANCE_NO_WRITE_NOTE : LAST_CHANCE_NOTE);
50648
+ const targeted = await targetedRecoveryNote(ctx, snap, text2);
50649
+ ctx.history.push(targeted ?? (ctx.writeGoal && !ctx.wroteOk ? LAST_CHANCE_NO_WRITE_NOTE : LAST_CHANCE_NOTE));
50382
50650
  } else {
50383
50651
  ctx.stop = "no-progress";
50384
50652
  return "stop";
@@ -50396,6 +50664,19 @@ async function checkProgress(ctx, step) {
50396
50664
  ctx.seen.add(snap.signature);
50397
50665
  return "next";
50398
50666
  }
50667
+ async function targetedRecoveryNote(ctx, snap, text2) {
50668
+ const evidence = await collectActionableControlEvidence(ctx.page, { screenshots: false }).catch(() => []);
50669
+ const safeNames = new Set(evidence.filter((e) => e.kind === "safe" && e.name !== "").map((e) => e.name));
50670
+ if (safeNames.size === 0)
50671
+ return null;
50672
+ const forward = forwardControls(ctx, { snap, modelControls: snap.controls, visibleText: text2 }).filter((c) => safeNames.has(c.name.trim()));
50673
+ if (forward.length !== 1)
50674
+ return null;
50675
+ const target = evidence.find((e) => e.kind === "safe" && e.name === forward[0].name.trim());
50676
+ if (target === void 0 || !armRecoveryTurn(ctx, [target]))
50677
+ return null;
50678
+ return NO_PROGRESS_RECOVERY_NOTE(target.name, target.locator);
50679
+ }
50399
50680
  async function noteCycle(ctx, snap, text2) {
50400
50681
  const step = ctx.cycleAction;
50401
50682
  ctx.cycleAction = null;
@@ -50844,6 +51125,99 @@ async function finishRun(ctx) {
50844
51125
  };
50845
51126
  }
50846
51127
 
51128
+ // ../explore/dist/startup-retry.js
51129
+ var DEFAULT_STARTUP_RETRY = Object.freeze({
51130
+ maxAttempts: 3,
51131
+ perAttemptTimeoutMs: 15e3,
51132
+ backoffMs: 500
51133
+ });
51134
+ var STARTUP_RETRY_BUDGET_MS = 6e4;
51135
+ function startupRetryBudgetMs(policy) {
51136
+ return policy.maxAttempts * policy.perAttemptTimeoutMs + (policy.maxAttempts - 1) * policy.backoffMs;
51137
+ }
51138
+ var StartupRetryExhaustedError = class extends Error {
51139
+ name = "StartupRetryExhaustedError";
51140
+ attempts;
51141
+ cause;
51142
+ constructor(cause, attempts) {
51143
+ super(cause instanceof Error ? cause.message : String(cause));
51144
+ this.attempts = attempts;
51145
+ this.cause = cause;
51146
+ }
51147
+ };
51148
+ function messageOf2(e) {
51149
+ return e instanceof Error ? e.message.split("\n")[0] ?? e.message : String(e);
51150
+ }
51151
+ function defaultNavigate(actor, url2, timeoutMs) {
51152
+ return async () => {
51153
+ await actor.ability(BrowseTheWebToken).session.page.goto(url2, { timeout: timeoutMs });
51154
+ };
51155
+ }
51156
+ var attemptsByActor = /* @__PURE__ */ new WeakMap();
51157
+ function startupAttemptsFor(actor) {
51158
+ return attemptsByActor.get(actor) ?? [];
51159
+ }
51160
+ async function navigateWithStartupRetry(actor, url2, options = {}) {
51161
+ const policy = { ...DEFAULT_STARTUP_RETRY, ...options.policy };
51162
+ if (!Number.isInteger(policy.maxAttempts) || policy.maxAttempts < 1) {
51163
+ throw new RangeError(`startup retry maxAttempts must be a positive integer, got ${JSON.stringify(policy.maxAttempts)}`);
51164
+ }
51165
+ const budget = startupRetryBudgetMs(policy);
51166
+ if (budget > STARTUP_RETRY_BUDGET_MS) {
51167
+ throw new RangeError(`startup retry worst case ${budget}ms exceeds the ${STARTUP_RETRY_BUDGET_MS}ms bound`);
51168
+ }
51169
+ const session2 = tryAbility(actor, BrowseTheWebToken)?.session;
51170
+ const page = session2?.page;
51171
+ const probe = options.probeOriginReadiness ?? session2?.probeOriginReadiness?.bind(session2);
51172
+ const navigate = options.navigate ?? defaultNavigate(actor, url2, policy.perAttemptTimeoutMs);
51173
+ let netErrorText = null;
51174
+ const onRequestFailed = (req) => {
51175
+ const text2 = req.failure()?.errorText;
51176
+ if (text2 !== void 0)
51177
+ netErrorText = text2;
51178
+ };
51179
+ page?.on("requestfailed", onRequestFailed);
51180
+ const attempts = [];
51181
+ attemptsByActor.set(actor, attempts);
51182
+ const record3 = (attempt2) => {
51183
+ attempts.push(attempt2);
51184
+ options.onAttempt?.(attempt2);
51185
+ };
51186
+ try {
51187
+ for (let index = 0; index < policy.maxAttempts; index += 1) {
51188
+ netErrorText = null;
51189
+ const startedAt = clock.now();
51190
+ try {
51191
+ await navigate();
51192
+ record3({ index, errorClass: null, message: "", startedAt, endedAt: clock.now() });
51193
+ return attempts;
51194
+ } catch (e) {
51195
+ const message2 = messageOf2(e);
51196
+ if (peekBlockedDeparture(actor) !== void 0) {
51197
+ record3({ index, errorClass: "unclassified", message: describeUnreachable(message2, netErrorText), startedAt, endedAt: clock.now() });
51198
+ throw e;
51199
+ }
51200
+ let originReady = false;
51201
+ if (probe !== void 0) {
51202
+ try {
51203
+ originReady = (await probe(url2)).class === "ready";
51204
+ } catch {
51205
+ originReady = false;
51206
+ }
51207
+ }
51208
+ const errorClass = startupErrorClass({ message: message2, netErrorText, originReady });
51209
+ record3({ index, errorClass, message: describeUnreachable(message2, netErrorText), startedAt, endedAt: clock.now() });
51210
+ if (index === policy.maxAttempts - 1)
51211
+ throw new StartupRetryExhaustedError(e, attempts);
51212
+ await clock.sleep(policy.backoffMs);
51213
+ }
51214
+ }
51215
+ return attempts;
51216
+ } finally {
51217
+ page?.off("requestfailed", onRequestFailed);
51218
+ }
51219
+ }
51220
+
50847
51221
  // ../explore/dist/goal-loop/start.js
50848
51222
  async function openRun(ctx) {
50849
51223
  const { cfg } = ctx;
@@ -50855,7 +51229,7 @@ async function openRun(ctx) {
50855
51229
  try {
50856
51230
  if (cfg.startInPlace !== true) {
50857
51231
  await assertSeedReachable(cfg.actor, cfg.startUrl);
50858
- await Navigate.to(cfg.startUrl).performAs(cfg.actor);
51232
+ await navigateWithStartupRetry(cfg.actor, cfg.startUrl, cfg.startupRetry === void 0 ? {} : { policy: cfg.startupRetry });
50859
51233
  }
50860
51234
  } catch (e) {
50861
51235
  const message2 = firstLine(e);
@@ -51195,13 +51569,31 @@ function describeCheck(c) {
51195
51569
  case "reloadThen":
51196
51570
  return `reloadThen:${describeAssertionSpec(c.assertion)}`;
51197
51571
  case "requestMade":
51198
- return `requestMade:${c.method} ${c.pathGlob}`;
51199
51572
  case "responseStatus":
51200
- return `responseStatus:${c.method} ${c.pathGlob}=${describeStatus2(c.status)}`;
51573
+ return describeNetworkCheck(c);
51201
51574
  case "http":
51202
51575
  return describeHttpCheck(c);
51203
51576
  }
51204
51577
  }
51578
+ function describeCount(count) {
51579
+ if ("exact" in count)
51580
+ return `exact:${count.exact}`;
51581
+ if ("min" in count)
51582
+ return `min:${count.min}`;
51583
+ return `max:${count.max}`;
51584
+ }
51585
+ function describeBodyShape(shape3) {
51586
+ return shape3.fields.map((f2) => `${f2.name}:${f2.predicate}`).join(",");
51587
+ }
51588
+ function describeNetworkCheck(c) {
51589
+ const base = c.kind === "requestMade" ? `requestMade:${c.method} ${c.pathGlob}` : `responseStatus:${c.method} ${c.pathGlob}=${describeStatus2(c.status)}`;
51590
+ const extras = [];
51591
+ if (c.count !== void 0)
51592
+ extras.push(`count=${describeCount(c.count)}`);
51593
+ if (c.bodyShape !== void 0)
51594
+ extras.push(`body=${describeBodyShape(c.bodyShape)}`);
51595
+ return [base, ...extras].join("|");
51596
+ }
51205
51597
  function escapeHttpValue(v) {
51206
51598
  return v.replace(/\\/g, "\\\\").replace(/\|/g, "\\|");
51207
51599
  }
@@ -51222,6 +51614,13 @@ function describeHttpCheck(c) {
51222
51614
  function statusMatches(status, spec) {
51223
51615
  return "class" in spec ? Math.floor(status / 100) === spec.class : status === spec.code;
51224
51616
  }
51617
+ function countHolds(count, n3) {
51618
+ if ("exact" in count)
51619
+ return n3 === count.exact;
51620
+ if ("min" in count)
51621
+ return n3 >= count.min;
51622
+ return n3 <= count.max;
51623
+ }
51225
51624
  function matching(requests, method, pathGlob2) {
51226
51625
  const m = method.toUpperCase();
51227
51626
  return requests.filter((r) => (m === "*" || r.method === m) && matchGlob(pathGlob2, r.path));
@@ -51272,7 +51671,24 @@ function nearMissHint(requests, method, pathGlob2) {
51272
51671
  return null;
51273
51672
  return `saw ${top.method} ${top.path} \u2192 ${top.status} (${top.count}\xD7)`;
51274
51673
  }
51275
- function evaluateNetworkCheck(check3, requests, truncated = false) {
51674
+ function declaredMatching(declared, method, pathGlob2) {
51675
+ const m = method.toUpperCase();
51676
+ return declared.filter((d) => (m === "*" || d.method === m) && matchGlob(pathGlob2, d.path));
51677
+ }
51678
+ function describeCountExpectation(count) {
51679
+ if ("exact" in count)
51680
+ return `exactly ${count.exact}`;
51681
+ if ("min" in count)
51682
+ return `at least ${count.min}`;
51683
+ return `at most ${count.max}`;
51684
+ }
51685
+ function missingBodyFields(shape3, candidates) {
51686
+ if (candidates.length === 0)
51687
+ return bodyShapeMismatches(shape3.fields, []);
51688
+ const lists = candidates.map((d) => bodyShapeMismatches(shape3.fields, d.fields));
51689
+ return lists.reduce((best, cur) => cur.length < best.length ? cur : best);
51690
+ }
51691
+ function evaluateNetworkCheck(check3, requests, truncated = false, declared = []) {
51276
51692
  const spec = describeCheck(check3);
51277
51693
  const hits = matching(requests, check3.method, check3.pathGlob);
51278
51694
  const note = truncated ? " (the capture dropped its oldest requests)" : "";
@@ -51285,6 +51701,19 @@ function evaluateNetworkCheck(check3, requests, truncated = false) {
51285
51701
  detail: `no ${check3.method.toUpperCase()} request matched ${check3.pathGlob} (${captured} requests captured)${note}${hint2 === null ? "" : `; ${hint2}`}`
51286
51702
  };
51287
51703
  }
51704
+ if (check3.count !== void 0 && !countHolds(check3.count, hits.length)) {
51705
+ return { check: spec, passed: false, detail: `expected ${describeCountExpectation(check3.count)} matching request(s), saw ${hits.length}${note}` };
51706
+ }
51707
+ if (check3.bodyShape !== void 0) {
51708
+ const candidates = declaredMatching(declared, check3.method, check3.pathGlob);
51709
+ if (!candidates.some((d) => bodyShapeMismatches(check3.bodyShape.fields, d.fields).length === 0)) {
51710
+ return {
51711
+ check: spec,
51712
+ passed: false,
51713
+ detail: `none of the ${hits.length} matching request(s) declared the required body shape: ${missingBodyFields(check3.bodyShape, candidates).join("; ")}${note}`
51714
+ };
51715
+ }
51716
+ }
51288
51717
  const inFlight = hits.filter((r) => r.pending === true).length;
51289
51718
  if (check3.kind === "requestMade") {
51290
51719
  return {
@@ -52855,9 +53284,29 @@ function whyNot(run, results) {
52855
53284
  function sentSinceFirstAction(requests, firstActionAt) {
52856
53285
  return firstActionAt === null ? [] : requests.filter((r) => r.startedAt !== void 0 && r.startedAt >= firstActionAt);
52857
53286
  }
52858
- function judgeNetworkCheck(c, requests, truncated, scope2, onVacuous) {
52859
- const after = evaluateNetworkCheck(c, sentSinceFirstAction(requests, scope2.firstActionAt), truncated);
52860
- const all = evaluateNetworkCheck(c, requests, truncated);
53287
+ var MAX_DECLARED_FIELDS2 = 500;
53288
+ var declaredFieldsByPage = /* @__PURE__ */ new WeakMap();
53289
+ function captureDeclaredBodyFields(page) {
53290
+ if (declaredFieldsByPage.has(page))
53291
+ return;
53292
+ const fields = [];
53293
+ declaredFieldsByPage.set(page, fields);
53294
+ monitorFor(page).onDeclaredFields((declared) => {
53295
+ fields.push(declared);
53296
+ if (fields.length > MAX_DECLARED_FIELDS2)
53297
+ fields.shift();
53298
+ });
53299
+ }
53300
+ function declaredFieldsFor(page) {
53301
+ return declaredFieldsByPage.get(page) ?? [];
53302
+ }
53303
+ function declaredWithin(declared, requests) {
53304
+ return declared.filter((d) => requests.some((r) => r.method === d.method && r.path === d.path));
53305
+ }
53306
+ function judgeNetworkCheck(c, requests, truncated, scope2, onVacuous, declared = []) {
53307
+ const scoped = sentSinceFirstAction(requests, scope2.firstActionAt);
53308
+ const after = evaluateNetworkCheck(c, scoped, truncated, declaredWithin(declared, scoped));
53309
+ const all = evaluateNetworkCheck(c, requests, truncated, declaredWithin(declared, requests));
52861
53310
  const vacuous = !after.passed && all.passed;
52862
53311
  if (vacuous)
52863
53312
  onVacuous?.(after.check);
@@ -52869,6 +53318,8 @@ function judgeNetworkCheck(c, requests, truncated, scope2, onVacuous) {
52869
53318
  } : after;
52870
53319
  }
52871
53320
  async function evaluateChecks(cfg, checks, page, capture2, scope2, onVacuous) {
53321
+ captureDeclaredBodyFields(page);
53322
+ const declared = declaredFieldsFor(page);
52872
53323
  return evaluateOutcomeChecks(checks, {
52873
53324
  actor: cfg.actor,
52874
53325
  page,
@@ -52876,7 +53327,7 @@ async function evaluateChecks(cfg, checks, page, capture2, scope2, onVacuous) {
52876
53327
  ...cfg.secrets === void 0 ? {} : { secrets: cfg.secrets },
52877
53328
  timeoutMs: cfg.oracleTimeoutMs ?? 3e3,
52878
53329
  settleMs: cfg.oracleSettleMs ?? DEFAULT_ORACLE_SETTLE_MS,
52879
- judgeNetwork: (c, requests, truncated) => judgeNetworkCheck(c, requests, truncated, scope2, onVacuous),
53330
+ judgeNetwork: (c, requests, truncated) => judgeNetworkCheck(c, requests, truncated, scope2, onVacuous, declared),
52880
53331
  ...cfg.httpProbe === void 0 ? {} : { httpProbe: cfg.httpProbe },
52881
53332
  ...cfg.httpRefs === void 0 ? {} : { resolveRef: cfg.httpRefs }
52882
53333
  });
@@ -53286,6 +53737,8 @@ function setupAdjudication(cfg, checks, page, capture2, declared) {
53286
53737
  const httpChecks = [...checks.entries()].filter((e) => e[1].kind === "http");
53287
53738
  const holdsNow = (a) => checkAssertion(cfg.actor, a, { timeoutMs: HELD_CHECK_TIMEOUT_MS }).catch(() => false);
53288
53739
  const networkChecks = checks.filter((c) => c.kind === "requestMade" || c.kind === "responseStatus");
53740
+ if (networkChecks.some((c) => c.bodyShape !== void 0))
53741
+ captureDeclaredBodyFields(page);
53289
53742
  const answerRequired = checks.length > 0 && goalAsksForReport(cfg.goal);
53290
53743
  const stopWhenHeld = (cfg.successWhen === "held" || cfg.successWhen === "each") && checks.length > 0 && !checks.some(isDeferredCheck) && !answerRequired;
53291
53744
  const hasChecks = checks.length > 0;
@@ -53463,6 +53916,11 @@ function declaredInvariants(cfg, page) {
53463
53916
  };
53464
53917
  }
53465
53918
  async function adjudicatedRun(cfg, checks, page, capture2, declared) {
53919
+ const result = await assembleGoalResult(cfg, checks, page, capture2, declared);
53920
+ const attempts = startupAttemptsFor(cfg.actor);
53921
+ return attempts.length === 0 ? result : { ...result, attempts: [...attempts] };
53922
+ }
53923
+ async function assembleGoalResult(cfg, checks, page, capture2, declared) {
53466
53924
  const ctx = setupAdjudication(cfg, checks, page, capture2, declared);
53467
53925
  const acted = await act2(ctx);
53468
53926
  if (acted.kind === "ended")
@@ -58986,7 +59444,7 @@ async function loadSeed(ctx) {
58986
59444
  try {
58987
59445
  if (params.startInPlace !== true) {
58988
59446
  await ctx.guard(assertSeedReachable(ctx.sessions.actor, params.seedUrl));
58989
- await ctx.guard(ctx.sessions.actor.attemptsTo(Navigate.to(params.seedUrl)));
59447
+ await ctx.guard(navigateWithStartupRetry(ctx.sessions.actor, params.seedUrl));
58990
59448
  }
58991
59449
  } catch (e) {
58992
59450
  const message2 = e instanceof Error ? e.message.split("\n")[0] ?? e.message : String(e);
@@ -61935,7 +62393,7 @@ async function loadSeed2(ctx, params) {
61935
62393
  try {
61936
62394
  if (params.startInPlace !== true) {
61937
62395
  await assertSeedReachable(ctx.sessions.actor, params.seedUrl);
61938
- await Navigate.to(params.seedUrl).performAs(ctx.sessions.actor);
62396
+ await navigateWithStartupRetry(ctx.sessions.actor, params.seedUrl);
61939
62397
  }
61940
62398
  } catch (e) {
61941
62399
  const message2 = e instanceof Error ? e.message.split("\n")[0] ?? e.message : String(e);
@@ -62761,7 +63219,7 @@ async function runFeatureFrontier(params, declared, safety, budget, overlay = nu
62761
63219
  sessions.page.on("requestfailed", onFirstNavRequestFailed);
62762
63220
  try {
62763
63221
  await guard(assertSeedReachable(sessions.actor, params.seedUrl));
62764
- await guard(sessions.actor.attemptsTo(Navigate.to(params.seedUrl)));
63222
+ await guard(navigateWithStartupRetry(sessions.actor, params.seedUrl));
62765
63223
  } catch (e) {
62766
63224
  const message2 = e instanceof Error ? e.message.split("\n")[0] ?? e.message : String(e);
62767
63225
  if (peekBlockedDeparture(sessions.actor) === void 0) {
@@ -63608,9 +64066,9 @@ function hostStarvedFailure(failure3, health) {
63608
64066
  }
63609
64067
 
63610
64068
  // src/build-info.generated.ts
63611
- var GENERATED_COMMIT = "e527356";
63612
- var GENERATED_TREE = "edd7d1c";
63613
- var GENERATED_BUILT_AT = "2026-10-27T16:27:12.402Z";
64069
+ var GENERATED_COMMIT = "b4bfd12";
64070
+ var GENERATED_TREE = "f8e0769";
64071
+ var GENERATED_BUILT_AT = "2026-10-30T20:01:06.112Z";
63614
64072
 
63615
64073
  // src/version.ts
63616
64074
  import { readFileSync as readFileSync5 } from "node:fs";
@@ -63632,13 +64090,13 @@ function readCliVersion() {
63632
64090
 
63633
64091
  // src/engine.ts
63634
64092
  function bundledCommit() {
63635
- return true ? "e527356" : void 0;
64093
+ return true ? "b4bfd12" : void 0;
63636
64094
  }
63637
64095
  function bundledBuiltAt() {
63638
- return true ? "2026-10-27T16:27:12.402Z" : void 0;
64096
+ return true ? "2026-10-30T20:01:06.112Z" : void 0;
63639
64097
  }
63640
64098
  function bundledTree() {
63641
- return true ? "edd7d1c" : void 0;
64099
+ return true ? "f8e0769" : void 0;
63642
64100
  }
63643
64101
  function currentBuildTree() {
63644
64102
  return bundledTree() ?? GENERATED_TREE;
@@ -64816,6 +65274,51 @@ function parseStatusSpec(text2) {
64816
65274
  if (/^[1-5]\d\d$/.test(text2)) return { code: Number(text2) };
64817
65275
  throw new Error(`responseStatus expects 2xx, 4xx \u2026 or a status code, got ${JSON.stringify(text2)}`);
64818
65276
  }
65277
+ var BODY_PREDICATES = /* @__PURE__ */ new Set(["exists", "absent", "string", "number", "boolean", "null", "array", "object"]);
65278
+ function parseCountSpec(kind, value) {
65279
+ const m = /^(exact|min|max):(\d+)$/.exec(value);
65280
+ if (m === null) throw new Error(`${kind}: count expects exact:<n>, min:<n> or max:<n>, got ${JSON.stringify(value)}`);
65281
+ const n3 = Number(m[2]);
65282
+ if (n3 > 1e3) throw new Error(`${kind}: count bound must be 0..1000, got ${n3}`);
65283
+ const bound2 = m[1];
65284
+ return bound2 === "exact" ? { exact: n3 } : bound2 === "min" ? { min: n3 } : { max: n3 };
65285
+ }
65286
+ function parseBodyShapeSpec(kind, value) {
65287
+ const fields = value.split(",").map((part) => {
65288
+ const idx = part.lastIndexOf(":");
65289
+ if (idx <= 0) throw new Error(`${kind}: body expects name:predicate, got ${JSON.stringify(part)}`);
65290
+ const name = part.slice(0, idx);
65291
+ const predicate = part.slice(idx + 1);
65292
+ if (!BODY_PREDICATES.has(predicate)) throw new Error(`${kind}: body predicate must be one of ${[...BODY_PREDICATES].join(", ")}, got ${JSON.stringify(predicate)}`);
65293
+ if (SECRET_PARAM_NAME_RE.test(name)) {
65294
+ throw new Error(`${kind}: body field ${JSON.stringify(name)} names a credential \u2014 a body shape declares field names and predicates only`);
65295
+ }
65296
+ return { name, predicate };
65297
+ });
65298
+ return { fields };
65299
+ }
65300
+ function splitNetworkSuffixes(kind, text2) {
65301
+ const parts2 = text2.split("|");
65302
+ const core = parts2[0] ?? "";
65303
+ const out = { core };
65304
+ for (const part of parts2.slice(1)) {
65305
+ const eq = part.indexOf("=");
65306
+ const key2 = eq === -1 ? part : part.slice(0, eq);
65307
+ const value = eq === -1 ? void 0 : part.slice(eq + 1);
65308
+ if (key2 === "count") {
65309
+ if (out.count !== void 0) throw new Error(`${kind}: count declared more than once`);
65310
+ if (value === void 0) throw new Error(`${kind}: count expects exact:<n>, min:<n> or max:<n>`);
65311
+ out.count = parseCountSpec(kind, value);
65312
+ } else if (key2 === "body") {
65313
+ if (out.bodyShape !== void 0) throw new Error(`${kind}: body declared more than once`);
65314
+ if (value === void 0) throw new Error(`${kind}: body expects name:predicate,\u2026`);
65315
+ out.bodyShape = parseBodyShapeSpec(kind, value);
65316
+ } else {
65317
+ throw new Error(`${kind}: unknown option ${JSON.stringify(part)} (expected count=\u2026 or body=\u2026)`);
65318
+ }
65319
+ }
65320
+ return out;
65321
+ }
64819
65322
  function parseSuccessSpec(spec) {
64820
65323
  const ci = spec.indexOf(":");
64821
65324
  const kind = ci === -1 ? spec : spec.slice(0, ci);
@@ -64824,14 +65327,17 @@ function parseSuccessSpec(spec) {
64824
65327
  case "reloadThen":
64825
65328
  if (rest.startsWith("reloadThen:")) throw new Error("reloadThen cannot be nested");
64826
65329
  return { kind: "reloadThen", assertion: parseAssertionSpec(rest) };
64827
- case "requestMade":
64828
- return { kind: "requestMade", ...parseRequestSpec(kind, rest) };
65330
+ case "requestMade": {
65331
+ const { core, ...extras } = splitNetworkSuffixes(kind, rest);
65332
+ return { kind: "requestMade", ...parseRequestSpec(kind, core), ...extras };
65333
+ }
64829
65334
  case "http":
64830
65335
  return parseHttpCheckSpec(rest);
64831
65336
  case "responseStatus": {
64832
- const eq = rest.lastIndexOf("=");
65337
+ const { core, ...extras } = splitNetworkSuffixes(kind, rest);
65338
+ const eq = core.lastIndexOf("=");
64833
65339
  if (eq === -1) throw new Error('responseStatus requires "<METHOD> <path-glob>=<2xx|4xx|code>"');
64834
- return { kind: "responseStatus", ...parseRequestSpec(kind, rest.slice(0, eq)), status: parseStatusSpec(rest.slice(eq + 1)) };
65340
+ return { kind: "responseStatus", ...parseRequestSpec(kind, core.slice(0, eq)), status: parseStatusSpec(core.slice(eq + 1)), ...extras };
64835
65341
  }
64836
65342
  default:
64837
65343
  return { kind: "page", assertion: parseAssertionSpec(spec) };
@@ -84848,7 +85354,7 @@ async function readChangeScope(o) {
84848
85354
  try {
84849
85355
  ({ stdout } = await exec(diffArgs, { cwd: o.cwd, env: process.env }));
84850
85356
  } catch (error62) {
84851
- throw new ChangesInputError(`git diff failed: ${messageOf2(error62)}`);
85357
+ throw new ChangesInputError(`git diff failed: ${messageOf3(error62)}`);
84852
85358
  }
84853
85359
  if (Buffer.byteLength(stdout, "utf8") > maxBytes) {
84854
85360
  throw new ChangesInputError(`change diff exceeds ${maxBytes} bytes`);
@@ -84871,7 +85377,7 @@ async function resolveCommit(exec, cwd, rev) {
84871
85377
  try {
84872
85378
  ({ stdout } = await exec(args, { cwd, env: process.env }));
84873
85379
  } catch (error62) {
84874
- throw new ChangesInputError(`cannot resolve change revision ${JSON.stringify(rev)}: ${messageOf2(error62)}`);
85380
+ throw new ChangesInputError(`cannot resolve change revision ${JSON.stringify(rev)}: ${messageOf3(error62)}`);
84875
85381
  }
84876
85382
  const sha = stdout.trim();
84877
85383
  if (!SHA_RE2.test(sha)) throw new ChangesInputError(`cannot resolve change revision ${JSON.stringify(rev)}`);
@@ -84882,7 +85388,7 @@ async function mergeBase(exec, cwd, shaA, shaB) {
84882
85388
  try {
84883
85389
  ({ stdout } = await exec(["merge-base", shaA, shaB], { cwd, env: process.env }));
84884
85390
  } catch (error62) {
84885
- throw new ChangesInputError(`cannot find merge base: ${messageOf2(error62)}`);
85391
+ throw new ChangesInputError(`cannot find merge base: ${messageOf3(error62)}`);
84886
85392
  }
84887
85393
  const sha = stdout.trim();
84888
85394
  if (!SHA_RE2.test(sha)) throw new ChangesInputError(`cannot find merge base for ${shaA}...${shaB}`);
@@ -84916,7 +85422,7 @@ function unquotePath(value) {
84916
85422
  if (trimmed.length >= 2 && trimmed.startsWith('"') && trimmed.endsWith('"')) return trimmed.slice(1, -1);
84917
85423
  return trimmed;
84918
85424
  }
84919
- function messageOf2(error62) {
85425
+ function messageOf3(error62) {
84920
85426
  return error62 instanceof Error ? error62.message : String(error62);
84921
85427
  }
84922
85428
 
@@ -88753,6 +89259,13 @@ var LoginFailedError = class extends Error {
88753
89259
  this.name = "LoginFailedError";
88754
89260
  }
88755
89261
  };
89262
+ var LoginContextError = class extends Error {
89263
+ code = "E_LOGIN_CONTEXT";
89264
+ constructor(message2) {
89265
+ super(message2);
89266
+ this.name = "LoginContextError";
89267
+ }
89268
+ };
88756
89269
  var VAR_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
88757
89270
  var RESERVED_VARS = /* @__PURE__ */ new Set(["OPENROUTER_API_KEY", "TYPESAFE_API_KEY", "GITHUB_TOKEN"]);
88758
89271
  function assertEnvVarName(name, what) {
@@ -88774,8 +89287,8 @@ function httpUrl2(v, what) {
88774
89287
  if (u.username !== "" || u.password !== "") throw new LoginArgsError(`${what} must not carry credentials in the URL`);
88775
89288
  return u.href;
88776
89289
  }
88777
- var LOGIN_KEYS = ["url", "api", "userEnv", "passwordEnv", "userField", "passwordField", "submit", "success"];
88778
- var FORM_ONLY_KEYS = ["url", "userField", "passwordField", "submit", "success"];
89290
+ var LOGIN_KEYS = ["url", "api", "userEnv", "passwordEnv", "userField", "passwordField", "submit", "success", "organizationField", "roleField"];
89291
+ var FORM_ONLY_KEYS = ["url", "userField", "passwordField", "submit", "success", "organizationField", "roleField"];
88779
89292
  var API_KEYS = ["url", "userKey", "passwordKey", "tokenPath", "storageKey", "storage", "verifyUrl"];
88780
89293
  var BODY_KEY = /^[A-Za-z_][A-Za-z0-9_-]{0,63}$/;
88781
89294
  var PATH_SEGMENT = /^(?:[A-Za-z_][A-Za-z0-9_-]{0,63}|0|[1-9][0-9]{0,5})$/;
@@ -88882,7 +89395,9 @@ function parsePersonaLogin(v, where) {
88882
89395
  ...optional2("userField", str7(o, "userField", `${where}.userField`, false)),
88883
89396
  ...optional2("passwordField", str7(o, "passwordField", `${where}.passwordField`, false)),
88884
89397
  ...optional2("submit", str7(o, "submit", `${where}.submit`, false)),
88885
- ...optional2("success", str7(o, "success", `${where}.success`, false))
89398
+ ...optional2("success", str7(o, "success", `${where}.success`, false)),
89399
+ ...optional2("organizationField", str7(o, "organizationField", `${where}.organizationField`, false)),
89400
+ ...optional2("roleField", str7(o, "roleField", `${where}.roleField`, false))
88886
89401
  };
88887
89402
  }
88888
89403
  assertEnvVarName(login.userEnv, `${where}.userEnv`);
@@ -88900,7 +89415,9 @@ function loginFromFlags(f2) {
88900
89415
  ["--user-field", f2.userField],
88901
89416
  ["--password-field", f2.passwordField],
88902
89417
  ["--submit", f2.submit],
88903
- ["--success", f2.success]
89418
+ ["--success", f2.success],
89419
+ ["--organization-field", f2.organizationField],
89420
+ ["--role-field", f2.roleField]
88904
89421
  ].filter(([, v]) => v !== void 0).map(([k]) => k);
88905
89422
  if (formFlags.length > 0) throw new LoginArgsError(`--api signs in through an HTTP endpoint, not a form: ${formFlags.join(", ")} ${formFlags.length === 1 ? "does" : "do"} not apply`);
88906
89423
  const login2 = { api: parseApiLogin(f2.api), userEnv: f2.userEnv, passwordEnv: f2.passwordEnv };
@@ -88916,7 +89433,9 @@ function loginFromFlags(f2) {
88916
89433
  ...optional2("userField", f2.userField),
88917
89434
  ...optional2("passwordField", f2.passwordField),
88918
89435
  ...optional2("submit", f2.submit),
88919
- ...optional2("success", f2.success)
89436
+ ...optional2("success", f2.success),
89437
+ ...optional2("organizationField", f2.organizationField),
89438
+ ...optional2("roleField", f2.roleField)
88920
89439
  };
88921
89440
  assertEnvVarName(login.userEnv, "--user-env");
88922
89441
  assertEnvVarName(login.passwordEnv, "--password-env");
@@ -89132,6 +89651,103 @@ async function performLogin(page, login, secrets, opts) {
89132
89651
  throw new LoginFailedError(scrub2(`the sign-in failed: ${message2}`, secrets));
89133
89652
  }
89134
89653
  }
89654
+ var CONTEXT_CONTROL_LABEL = {
89655
+ organization: /organization|organisation|\borg\b|workspace|tenant|account/i,
89656
+ role: /\brole\b/i
89657
+ };
89658
+ var CHOOSER_ROLES = ["button", "link", "option", "radio"];
89659
+ var CHOOSER_SETTLE_MS = 3e3;
89660
+ function escapeRegExp(text2) {
89661
+ return text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
89662
+ }
89663
+ async function chooserMatches(page, name, exact = false) {
89664
+ const out = [];
89665
+ const query = exact && typeof name === "string" ? { name, exact: true } : { name };
89666
+ for (const role of CHOOSER_ROLES) {
89667
+ const found = page.getByRole(role, query).filter({ visible: true });
89668
+ const count = await found.count().catch(() => 0);
89669
+ for (let i = 0; i < count; i++) {
89670
+ const item = found.nth(i);
89671
+ if (await item.isEnabled().catch(() => true)) out.push(item);
89672
+ }
89673
+ }
89674
+ return out;
89675
+ }
89676
+ async function chooserOffered(page) {
89677
+ const names = [];
89678
+ for (const role of CHOOSER_ROLES) {
89679
+ const found = page.getByRole(role).filter({ visible: true });
89680
+ const count = await found.count().catch(() => 0);
89681
+ for (let i = 0; i < count; i++) {
89682
+ const item = found.nth(i);
89683
+ if (!await item.isEnabled().catch(() => true)) continue;
89684
+ const name = await item.evaluate((el) => (el.getAttribute("aria-label") ?? el.textContent ?? "").replace(/\s+/g, " ").trim()).catch(() => "") || "";
89685
+ names.push(name);
89686
+ }
89687
+ }
89688
+ return names;
89689
+ }
89690
+ async function chooserCandidates(page, value) {
89691
+ const exact = await chooserMatches(page, value, true);
89692
+ if (exact.length > 0) return exact;
89693
+ const loose = await chooserMatches(page, new RegExp(`\\b${escapeRegExp(value)}\\b`, "i"));
89694
+ const safe = [];
89695
+ for (const card of loose) {
89696
+ const name = await card.evaluate((el) => (el.getAttribute("aria-label") ?? el.textContent ?? "").replace(/\s+/g, " ").trim()).catch(() => "") || "";
89697
+ if (!SESSION_END.test(name) && !DESTRUCTIVE.test(name)) safe.push(card);
89698
+ }
89699
+ return safe;
89700
+ }
89701
+ async function selectContextCard(page, what, value, timeoutMs) {
89702
+ const before = page.url();
89703
+ const matches2 = await chooserCandidates(page, value);
89704
+ if (matches2.length === 0) {
89705
+ const offered = await chooserOffered(page);
89706
+ throw new LoginContextError(`the declared ${what} ${JSON.stringify(value)} is unavailable on ${pathOf12(before)}: the chooser offers ${offered.length === 0 ? "nothing" : offered.map((n3) => JSON.stringify(n3)).join(", ")}`);
89707
+ }
89708
+ if (matches2.length > 1) {
89709
+ const offered = await Promise.all(matches2.map(async (m) => await m.evaluate((el) => (el.getAttribute("aria-label") ?? el.textContent ?? "").replace(/\s+/g, " ").trim()).catch(() => "") || ""));
89710
+ throw new LoginContextError(`the declared ${what} ${JSON.stringify(value)} is ambiguous on ${pathOf12(before)}: ${matches2.length} chooser cards match (${offered.map((n3) => JSON.stringify(n3)).join(", ")})`);
89711
+ }
89712
+ await matches2[0].click({ timeout: timeoutMs });
89713
+ await page.waitForLoadState("load", { timeout: timeoutMs }).catch(() => void 0);
89714
+ await page.waitForLoadState("networkidle", { timeout: Math.min(timeoutMs, CHOOSER_SETTLE_MS) }).catch(() => void 0);
89715
+ if (page.url() !== before) return;
89716
+ if ((await chooserCandidates(page, value)).length > 0) {
89717
+ throw new LoginContextError(`the declared ${what} ${JSON.stringify(value)} was clicked on ${pathOf12(page.url())}, but the chooser still offers it \u2014 the selection did not take`);
89718
+ }
89719
+ }
89720
+ async function selectLoginContext(page, context, opts) {
89721
+ await selectContextValue(page, "organization", context.organization, opts.login.organizationField, opts.timeoutMs);
89722
+ await selectContextValue(page, "role", context.role, opts.login.roleField, opts.timeoutMs);
89723
+ }
89724
+ async function selectContextValue(page, what, value, field3, timeoutMs) {
89725
+ if (value === void 0 || value === "") return;
89726
+ const candidates = field3 === void 0 ? [page.getByLabel(CONTEXT_CONTROL_LABEL[what]), page.getByRole("combobox", { name: CONTEXT_CONTROL_LABEL[what] })] : fieldBySpec(page, field3);
89727
+ const control = await firstVisible(candidates);
89728
+ if (control === void 0) {
89729
+ if (field3 !== void 0) throw new LoginContextError(`the declared ${what} ${JSON.stringify(value)} is unavailable: no ${what} control on ${pathOf12(page.url())} matching ${JSON.stringify(field3)}`);
89730
+ await selectContextCard(page, what, value, timeoutMs);
89731
+ return;
89732
+ }
89733
+ const labels = await control.evaluate((el) => el.tagName === "SELECT" ? Array.from(el.options).map((o) => (o.textContent ?? "").trim()) : null).catch(() => void 0);
89734
+ if (labels == null) {
89735
+ await control.click({ timeout: timeoutMs });
89736
+ const option = page.getByRole("option", { name: value, exact: true }).first();
89737
+ const visible3 = await option.waitFor({ state: "visible", timeout: timeoutMs }).then(() => true).catch(() => false);
89738
+ if (!visible3) throw new LoginContextError(`the declared ${what} ${JSON.stringify(value)} is unavailable on ${pathOf12(page.url())}: the ${what} control does not offer it`);
89739
+ await option.click({ timeout: timeoutMs });
89740
+ return;
89741
+ }
89742
+ if (!labels.includes(value)) {
89743
+ throw new LoginContextError(`the declared ${what} ${JSON.stringify(value)} is unavailable on ${pathOf12(page.url())}: the ${what} control offers ${labels.length === 0 ? "nothing" : labels.map((l2) => JSON.stringify(l2)).join(", ")}`);
89744
+ }
89745
+ await control.selectOption({ label: value }, { timeout: timeoutMs });
89746
+ const selected = await control.evaluate((el) => el.selectedOptions[0]?.textContent?.trim() ?? null);
89747
+ if (selected !== value) {
89748
+ throw new LoginContextError(`the declared ${what} ${JSON.stringify(value)} was not selected: the ${what} control shows ${JSON.stringify(selected)}`);
89749
+ }
89750
+ }
89135
89751
  function checkSaveTarget(file2, what) {
89136
89752
  const path2 = resolve10(file2);
89137
89753
  const refusal = sessionFileInProjectRefusal(path2, what);
@@ -89191,11 +89807,13 @@ async function mintStorageState(o) {
89191
89807
  try {
89192
89808
  session2 = await o.port.open({ ...sessionLaunchOptions(launch), allowedOrigins: [...o.allowlist], baseUrl: o.login.url });
89193
89809
  const landed = await performLogin(session2.page, o.login, secrets, { allowlist: o.allowlist, timeoutMs: o.timeoutMs });
89810
+ if (o.context !== void 0) await selectLoginContext(session2.page, o.context, { login: o.login, timeoutMs: o.timeoutMs });
89194
89811
  const state = session2.captureStorageState !== void 0 ? await session2.captureStorageState() : JSON.stringify(await session2.page.context().storageState());
89195
89812
  writeStorageStateFile(save, state);
89196
89813
  return { saved: save, landedUrl: scrub2(redactUrl(landed), secrets) };
89197
89814
  } catch (err) {
89198
89815
  if (err instanceof LoginFailedError || err instanceof LoginArgsError) throw err;
89816
+ if (err instanceof LoginContextError) throw new LoginContextError(scrub2(err.message, secrets));
89199
89817
  throw new LoginFailedError(scrub2(`the sign-in failed: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`, secrets));
89200
89818
  } finally {
89201
89819
  await session2?.close().catch(() => void 0);
@@ -89260,6 +89878,15 @@ async function mintApiStorageState(o, api) {
89260
89878
  else if (typeof v === "string" && v !== "") token = v;
89261
89879
  else throw new LoginFailedError(`the sign-in endpoint's JSON response has no ${v === void 0 || v === null ? "value" : "string value"} at --token-path ${api.tokenPath}`);
89262
89880
  }
89881
+ if (o.context !== void 0) {
89882
+ await session2.page.goto(appUrl, { waitUntil: "load", timeout: o.timeoutMs });
89883
+ if (token !== void 0 && api.storageKey !== void 0) {
89884
+ await session2.page.evaluate((args) => window.localStorage.setItem(args[0], args[1]), [api.storageKey, token]);
89885
+ await session2.page.reload({ waitUntil: "load", timeout: o.timeoutMs });
89886
+ }
89887
+ assertPageAuthorized(session2.page, o.allowlist);
89888
+ await selectLoginContext(session2.page, o.context, { login: o.login, timeoutMs: o.timeoutMs });
89889
+ }
89263
89890
  const raw = session2.captureStorageState !== void 0 ? await session2.captureStorageState() : JSON.stringify(await context.storageState());
89264
89891
  const state = JSON.parse(raw);
89265
89892
  if (token === void 0 && (state.cookies ?? []).length === 0) {
@@ -89291,6 +89918,7 @@ async function mintApiStorageState(o, api) {
89291
89918
  } catch (err) {
89292
89919
  if (err instanceof LoginFailedError) throw new LoginFailedError(scrub2(err.message, secrets, token));
89293
89920
  if (err instanceof LoginArgsError) throw new LoginArgsError(scrub2(err.message, secrets, token));
89921
+ if (err instanceof LoginContextError) throw new LoginContextError(scrub2(err.message, secrets, token));
89294
89922
  throw new LoginFailedError(scrub2(`the sign-in failed: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`, secrets, token));
89295
89923
  } finally {
89296
89924
  if (tmp !== void 0) rmSync(tmp, { force: true });
@@ -89390,7 +90018,7 @@ async function ensurePersonaSession(o) {
89390
90018
  const landedUrl = first !== void 0 && !first.ok ? first.landedUrl : void 0;
89391
90019
  if (o.login === void 0) return { ok: false, reason: expired, ...landedUrl === void 0 ? {} : { landedUrl } };
89392
90020
  try {
89393
- await mintStorageState({ login: o.login, save: o.storageState, allowlist: o.allowlist, env: o.env, port: o.port, ...o.browser === void 0 ? {} : { browser: o.browser }, timeoutMs: o.timeoutMs });
90021
+ await mintStorageState({ login: o.login, save: o.storageState, allowlist: o.allowlist, env: o.env, port: o.port, ...o.browser === void 0 ? {} : { browser: o.browser }, ...o.context === void 0 ? {} : { context: o.context }, timeoutMs: o.timeoutMs });
89394
90022
  } catch (err) {
89395
90023
  if (!(err instanceof LoginFailedError) && !(err instanceof LoginArgsError)) throw err;
89396
90024
  return { ok: false, reason: `${expired}; re-signing in ${who} failed: ${err.message}`, ...landedUrl === void 0 ? {} : { landedUrl } };
@@ -89429,14 +90057,35 @@ var MultiRunArgsError = class extends Error {
89429
90057
  };
89430
90058
  var MAX_REPEAT = 20;
89431
90059
  var PERSONA_NAME = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$/;
89432
- function persona(name, storageState, base, noun = "persona", login) {
90060
+ function persona(name, storageState, base, noun = "persona", login, context) {
89433
90061
  if (!PERSONA_NAME.test(name)) {
89434
90062
  throw new MultiRunArgsError(`${noun} name ${JSON.stringify(name)} must be 1-64 of [A-Za-z0-9_.-], starting alphanumeric`);
89435
90063
  }
89436
90064
  if (storageState.trim() === "") throw new MultiRunArgsError(`${noun} ${name}: storage state path is empty`);
89437
90065
  const path2 = isAbsolute9(storageState) ? storageState : resolve11(base, storageState);
89438
90066
  if (login === void 0 && !existsSync11(path2)) throw new MultiRunArgsError(`${noun} ${name}: storage state not found: ${path2}`);
89439
- return { name, storageState: path2, ...login === void 0 ? {} : { login } };
90067
+ return {
90068
+ name,
90069
+ storageState: path2,
90070
+ ...login === void 0 ? {} : { login },
90071
+ ...context === void 0 ? {} : { context }
90072
+ };
90073
+ }
90074
+ function entryContext(v, where) {
90075
+ if (v === void 0) return void 0;
90076
+ if (!isRecord(v)) throw new MultiRunArgsError(`${where} must be an object {"organization"?: string, "role"?: string}`);
90077
+ for (const key2 of Object.keys(v)) {
90078
+ if (key2 !== "organization" && key2 !== "role") {
90079
+ throw new MultiRunArgsError(`${where} has unknown key ${JSON.stringify(key2)} \u2014 allowed keys are "organization" and "role"`);
90080
+ }
90081
+ }
90082
+ const { organization, role } = v;
90083
+ if (organization !== void 0 && typeof organization !== "string") throw new MultiRunArgsError(`${where}: organization must be text`);
90084
+ if (role !== void 0 && typeof role !== "string") throw new MultiRunArgsError(`${where}: role must be text`);
90085
+ return {
90086
+ ...organization === void 0 ? {} : { organization },
90087
+ ...role === void 0 ? {} : { role }
90088
+ };
89440
90089
  }
89441
90090
  function entryLogin(v, where) {
89442
90091
  if (v === void 0) return void 0;
@@ -89514,7 +90163,16 @@ function loadPersonasFile(path2, opts = {}) {
89514
90163
  continue;
89515
90164
  }
89516
90165
  if (typeof value.storageState === "string") {
89517
- out.push(persona(name, value.storageState, base, "persona", entryLogin(value.login, `personas file ${path2}: ${name}.login`)));
90166
+ out.push(
90167
+ persona(
90168
+ name,
90169
+ value.storageState,
90170
+ base,
90171
+ "persona",
90172
+ entryLogin(value.login, `personas file ${path2}: ${name}.login`),
90173
+ entryContext(value.context, `personas file ${path2}: ${name}.context`)
90174
+ )
90175
+ );
89518
90176
  continue;
89519
90177
  }
89520
90178
  if (value.storageState === void 0 && value.login === void 0 && CATALOG_ONLY_KEYS.some((k) => value[k] !== void 0)) continue;
@@ -89527,7 +90185,7 @@ function loadPersonasFile(path2, opts = {}) {
89527
90185
  }
89528
90186
  return out;
89529
90187
  }
89530
- var CATALOG_ONLY_KEYS = ["description", "role", "approval"];
90188
+ var CATALOG_ONLY_KEYS = ["description", "role", "approval", "context"];
89531
90189
  function wantsMultiRun(f2) {
89532
90190
  return f2.repeat !== void 0 || f2.minAgreement !== void 0 || f2.persona.length > 0 || f2.personas !== void 0;
89533
90191
  }
@@ -90048,6 +90706,7 @@ var SUITE_EXPLORE_OPTIONS = {
90048
90706
  hangReplayWrites: { shape: "boolean", appliesTo: ALL_KINDS },
90049
90707
  settleIgnore: { shape: "strings", appliesTo: ALL_KINDS },
90050
90708
  longPollMs: { shape: "integer", appliesTo: ALL_KINDS },
90709
+ devServer: { shape: "boolean", appliesTo: ALL_KINDS },
90051
90710
  apiPrefix: { shape: "strings", appliesTo: ALL_KINDS },
90052
90711
  ignoreNoProgress: { shape: "strings", appliesTo: ALL_KINDS },
90053
90712
  hangReplays: { shape: "count", appliesTo: ["goal", "adversarial"] },
@@ -92280,6 +92939,10 @@ function parseTarget(v, where, baseDir) {
92280
92939
  }
92281
92940
  settle2.longPollMs = s3.longPollMs;
92282
92941
  }
92942
+ if (s3.devServer !== void 0) {
92943
+ if (typeof s3.devServer !== "boolean") throw new TargetConfigError(`${where}.settle.devServer must be a boolean`);
92944
+ settle2.devServer = s3.devServer;
92945
+ }
92283
92946
  out.settle = settle2;
92284
92947
  }
92285
92948
  if (o.hangs !== void 0) {
@@ -92355,6 +93018,7 @@ function resolveTargetConfig(file2, origin, flags = {}) {
92355
93018
  const base = file2[origin] ?? {};
92356
93019
  const ignoreRequests = [...base.settle?.ignoreRequests ?? [], ...flags.settleIgnore ?? []];
92357
93020
  const longPollMs = flags.longPollMs ?? base.settle?.longPollMs;
93021
+ const devServer = flags.devServer === true || base.settle?.devServer === true;
92358
93022
  const ignoreNoProgress = [...base.hangs?.ignoreNoProgress ?? [], ...flags.ignoreNoProgress ?? []];
92359
93023
  const apiPrefixes = [...base.timing?.apiPrefixes ?? [], ...flags.apiPrefixes ?? []];
92360
93024
  const deny = [...base.safety?.deny ?? [], ...flags.deny ?? []];
@@ -92380,7 +93044,7 @@ function resolveTargetConfig(file2, origin, flags = {}) {
92380
93044
  return {
92381
93045
  ...Object.keys(safety).length === 0 ? {} : { safety },
92382
93046
  timing: apiPrefixes.length === 0 ? {} : { apiPrefixes },
92383
- settle: { ...ignoreRequests.length === 0 ? {} : { ignoreRequests }, ...longPollMs === void 0 ? {} : { longPollMs } },
93047
+ settle: { ...ignoreRequests.length === 0 ? {} : { ignoreRequests }, ...longPollMs === void 0 ? {} : { longPollMs }, ...devServer ? { devServer } : {} },
92384
93048
  hangs: ignoreNoProgress.length === 0 ? {} : { ignoreNoProgress },
92385
93049
  ...base.fixtures === void 0 ? {} : { fixtures: base.fixtures },
92386
93050
  // #142 follow-up: pass-through only — no CLI flag merges into these (a `--log-source` on the
@@ -93280,6 +93944,7 @@ async function runExploration(opts) {
93280
93944
  assertionPassed: mission.assertionPassed,
93281
93945
  checks: mission.checks,
93282
93946
  ...mission.warnings === void 0 ? {} : { checkWarnings: mission.warnings },
93947
+ ...mission.attempts === void 0 ? {} : { attempts: mission.attempts },
93283
93948
  ...(() => {
93284
93949
  const lost = sessionLostReason({
93285
93950
  target: primaryState === void 0 ? {} : { storageStatePath: resolvePath7(primaryState) },
@@ -95008,6 +95673,7 @@ var TARGET_FLAG_KEYS = [
95008
95673
  "hangReplayWrites",
95009
95674
  "settleIgnore",
95010
95675
  "longPollMs",
95676
+ "devServer",
95011
95677
  "apiPrefix",
95012
95678
  "ignoreNoProgress"
95013
95679
  ];
@@ -95048,7 +95714,8 @@ function itemSetup(p, item, kind, opts, refs, prefix) {
95048
95714
  ...x.allowWrites === true ? { allowWrites: true } : {},
95049
95715
  ...x.allowWrite === void 0 ? {} : { allowWrite: x.allowWrite },
95050
95716
  ...x.hangReplayWrites === true ? { hangReplayWrites: true } : {},
95051
- ...x.longPollMs === void 0 ? {} : { longPollMs: x.longPollMs }
95717
+ ...x.longPollMs === void 0 ? {} : { longPollMs: x.longPollMs },
95718
+ ...x.devServer === true ? { devServer: true } : {}
95052
95719
  });
95053
95720
  }
95054
95721
  let serverLog = p.serverLog;
@@ -105137,7 +105804,7 @@ import { basename as basename16, dirname as dirname24, join as join56, resolve a
105137
105804
  var CATALOG_CONTRACT_IDENTITY = {
105138
105805
  "kind": "jevitate.catalog-contract",
105139
105806
  "version": "2.0.0",
105140
- "manifestSha256": "c1292a8727d7dc0dbe1d63dc768d34b6cc94ae60fab3a67aeb0a53837e1735ad"
105807
+ "manifestSha256": "c0fa630296bfb654da15d3949b77e7ce19a984846baa147ea9d4a0ce3b2d438a"
105141
105808
  };
105142
105809
 
105143
105810
  // src/catalog-bundle.ts
@@ -114498,13 +115165,39 @@ import { dirname as dirname31, isAbsolute as isAbsolute13, join as join71, resol
114498
115165
 
114499
115166
  // src/campaign-preflight.ts
114500
115167
  import { basename as basename18 } from "node:path";
115168
+ var CampaignHostStarvedError = class extends Error {
115169
+ code = "E_HOST_STARVED";
115170
+ constructor(message2) {
115171
+ super(message2);
115172
+ this.name = "CampaignHostStarvedError";
115173
+ }
115174
+ };
115175
+ function readinessReason(r) {
115176
+ if (r.class === "ready") return null;
115177
+ if (r.class === "http") return r.status !== void 0 && r.status < 500 ? null : r.detail;
115178
+ return r.detail;
115179
+ }
115180
+ function contextMismatch(declared, session2) {
115181
+ for (const field3 of ["organization", "role"]) {
115182
+ const d = declared[field3];
115183
+ const s3 = session2[field3];
115184
+ if (d !== void 0 && s3 !== void 0 && d !== s3) return `its ${field3} ${JSON.stringify(s3)} does not match the Journey's ${field3} ${JSON.stringify(d)}`;
115185
+ }
115186
+ return null;
115187
+ }
114501
115188
  var SESSION_CHECK_TIMEOUT_MS = 3e4;
114502
- function defaultPreflightDeps(portFactory = () => new PlaywrightBrowserPort()) {
115189
+ function defaultPreflightDeps(portFactory = () => new PlaywrightBrowserPort(), opts = {}) {
114503
115190
  return {
114504
- probe: (url2) => probeReachable(url2),
115191
+ // #746: an HTTP origin-readiness probe (dns/connect/http/ready); 4xx on the origin root still means up.
115192
+ readiness: (url2) => probeOriginReadiness(url2),
114505
115193
  checkSession: async (o) => {
114506
115194
  const r = await ensurePersonaSession({ ...o, check: { mode: "auto" }, port: portFactory(), env: {}, timeoutMs: SESSION_CHECK_TIMEOUT_MS });
114507
115195
  return r.ok ? { ok: true } : { ok: false, reason: r.reason, landedUrl: r.landedUrl ?? "" };
115196
+ },
115197
+ personaContext: (storageState) => projectPersonaFor(storageState)?.context,
115198
+ hostReadiness: async () => {
115199
+ const refusal = await resourcePreflight({ ...opts.ignoreHostLoad === true ? { ignoreHostLoad: true } : {} });
115200
+ return refusal === null ? null : refusal.error?.message ?? "the host is starved";
114508
115201
  }
114509
115202
  };
114510
115203
  }
@@ -114517,7 +115210,7 @@ function checkableUrl(url2) {
114517
115210
  }
114518
115211
  }
114519
115212
  async function checkCampaignTargets(jobs, deps) {
114520
- const probe = deps.probe ?? ((url2) => probeReachable(url2));
115213
+ const probe = deps.probe ?? (async (url2) => readinessReason(await (deps.readiness ?? probeOriginReadiness)(url2)));
114521
115214
  const problems = [];
114522
115215
  const probed = /* @__PURE__ */ new Map();
114523
115216
  const sessions = /* @__PURE__ */ new Map();
@@ -114564,6 +115257,19 @@ ${url2}`;
114564
115257
  });
114565
115258
  }
114566
115259
  }
115260
+ for (const job of jobs) {
115261
+ if (job.storageState === void 0 || job.declaredContext === void 0) continue;
115262
+ const session2 = deps.personaContext?.(job.storageState);
115263
+ if (session2 === void 0) continue;
115264
+ const mismatch = contextMismatch(job.declaredContext, session2);
115265
+ if (mismatch !== null) {
115266
+ problems.push({
115267
+ check: "context",
115268
+ job: job.id,
115269
+ message: `job ${job.id}: the persona session in ${basename18(job.storageState)} declares ${mismatch} \u2014 every mission would run as the wrong persona; re-save it with \`jevitate login\` for the Journey's persona (or point the job's storageState at that session)`
115270
+ });
115271
+ }
115272
+ }
114567
115273
  return { problems };
114568
115274
  }
114569
115275
  function allowOriginProblems(job, writeOrigins) {
@@ -114732,6 +115438,7 @@ async function validateCampaignSpec(raw, specPath, opts) {
114732
115438
  }
114733
115439
  }
114734
115440
  totalRuns += anchors.length * job.strategies.length;
115441
+ const declaredContext = declaredContextOf(full.metadata);
114735
115442
  jobs.push({
114736
115443
  id: job.id,
114737
115444
  journey: job.journey,
@@ -114739,6 +115446,7 @@ async function validateCampaignSpec(raw, specPath, opts) {
114739
115446
  ...session2 === void 0 ? {} : { storageState: session2 },
114740
115447
  startUrl: journeyStartUrl(full.recording),
114741
115448
  allowlist: environment === void 0 ? [new URL(full.recording.site).origin] : [...environment.allowedOrigins],
115449
+ ...declaredContext === void 0 ? {} : { declaredContext },
114742
115450
  anchors,
114743
115451
  strategies: job.strategies,
114744
115452
  ...job.goal === void 0 ? {} : { goal: job.goal },
@@ -114770,6 +115478,13 @@ function journeyStartUrl(recording) {
114770
115478
  return recording.site;
114771
115479
  }
114772
115480
  }
115481
+ function declaredContextOf(metadata) {
115482
+ if (metadata.persona !== void 0) {
115483
+ const context = projectPersonas().find((p) => p.name === metadata.persona)?.context;
115484
+ if (context !== void 0) return context;
115485
+ }
115486
+ return metadata.role === void 0 ? void 0 : { role: metadata.role };
115487
+ }
114773
115488
  async function runChild(newProgram, argv) {
114774
115489
  const lines = [];
114775
115490
  const child = newProgram();
@@ -114834,9 +115549,12 @@ async function runCampaign(plan2, opts) {
114834
115549
  journey: job.journey,
114835
115550
  startUrl: job.startUrl ?? "",
114836
115551
  allowlist: job.allowlist ?? [],
114837
- ...job.storageState === void 0 ? {} : { storageState: job.storageState }
115552
+ ...job.storageState === void 0 ? {} : { storageState: job.storageState },
115553
+ ...job.declaredContext === void 0 ? {} : { declaredContext: job.declaredContext }
114838
115554
  }));
114839
115555
  if (opts.preflight !== void 0 && opts.preflight !== "skip") {
115556
+ const starved = opts.ignoreHostLoad === true || opts.preflight.hostReadiness === void 0 ? null : await opts.preflight.hostReadiness();
115557
+ if (starved !== null) throw new CampaignHostStarvedError(`the host is starved, so no mission would give a trustworthy result: ${starved}`);
114840
115558
  const targets = await checkCampaignTargets(preflightJobs.filter((j) => j.startUrl !== ""), opts.preflight);
114841
115559
  preflightProblems.push(...targets.problems);
114842
115560
  blocked = new Set(targets.problems.map((p) => p.job));
@@ -115002,7 +115720,7 @@ function campaignMarkdown(r, reportMarkdown) {
115002
115720
  "",
115003
115721
  `${r.missions.length} anchored mission(s) \xB7 outcome ${r.missionOutcome} (exit ${r.exitCode}) \xB7 ${r.report.summary.defects} deduped defect(s), ${r.report.summary.advisory} advisory.`,
115004
115722
  "",
115005
- ...r.preflight === void 0 ? [] : ["## Preflight", "", r.preflight.skipped === true ? "Skipped (--skip-preflight)." : "Passed: every start URL reachable, every session signed in, no allow-list mismatch.", ""],
115723
+ ...r.preflight === void 0 ? [] : ["## Preflight", "", r.preflight.skipped === true ? "Skipped (--skip-preflight)." : "Passed: every start URL ready, every session signed in and from the declared persona context, no allow-list mismatch.", ""],
115006
115724
  "## Discovery",
115007
115725
  "",
115008
115726
  ...r.discovery.length === 0 ? ["Skipped (discovery: false)."] : ["| job | journey | outcome | reason |", "| --- | --- | --- | --- |", ...r.discovery.map((d) => `| ${d.job} | ${d.journeyId} | ${d.outcome} | ${(d.reason ?? "").replace(/\|/g, "\\|")} |`)],
@@ -115070,6 +115788,9 @@ function registerCampaignCommands(program2, deps, buildProgram2) {
115070
115788
  "--skip-preflight",
115071
115789
  "do not check the campaign's preconditions first (start URLs reachable, sessions signed in, no allow-list origin mismatch in discovery) \u2014 by default a failed check refuses the campaign (exit 64) before any mission",
115072
115790
  false
115791
+ ).option(
115792
+ "--ignore-host-load",
115793
+ "start even when the host is starved (the campaign-start check and every mission) instead of refusing with E_HOST_STARVED; the runs are throttled and their results record it"
115073
115794
  ).option("--real", "use live Jev + OpenRouter gateways for the missions (requires keys)", false).option("--fake-ai", "use deterministic fake gateways (pipeline smoke only)", false).option("--jev-provider <provider>", JEV_PROVIDER_FLAG_HELP, jevProviderArg).option("--json", "emit the JSON envelope (default: a human summary)").option(TAG_FLAG, TAG_HELP, collectTag, []).action(taggedAction(program2, "campaign run", async function(spec) {
115074
115795
  const o = this.opts();
115075
115796
  const emit5 = (envelope, exitCode) => emitCommandResult(program2, envelope, { json: o.json === true, command: "campaign run", human: formatCampaignHuman, ...exitCode === void 0 ? {} : { exitCode } });
@@ -115093,12 +115814,14 @@ function registerCampaignCommands(program2, deps, buildProgram2) {
115093
115814
  ...o.real === true ? { real: true } : {},
115094
115815
  ...o.fakeAi === true ? { fakeAi: true } : {},
115095
115816
  missionArgs: campaignMissionArgv(this),
115817
+ // #746: --ignore-host-load starts the campaign on a starved host (and is forwarded to every mission).
115818
+ ...o.ignoreHostLoad === true ? { ignoreHostLoad: true } : {},
115096
115819
  // #485: the preflight's checks open the same browser port the missions use.
115097
- preflight: o.skipPreflight === true ? "skip" : defaultPreflightDeps(deps.explore?.browserPortFactory)
115820
+ preflight: o.skipPreflight === true ? "skip" : defaultPreflightDeps(deps.explore?.browserPortFactory, { ...o.ignoreHostLoad === true ? { ignoreHostLoad: true } : {} })
115098
115821
  });
115099
115822
  emit5(ok(withEngine(result)), result.exitCode);
115100
115823
  } catch (err) {
115101
- if (err instanceof CampaignSpecError || err instanceof CampaignPreflightError) emit5(fail(err.code, err.message));
115824
+ if (err instanceof CampaignSpecError || err instanceof CampaignPreflightError || err instanceof CampaignHostStarvedError) emit5(fail(err.code, err.message));
115102
115825
  else emit5(fail("E_CAMPAIGN_RUN", String(err instanceof Error ? err.message : err)));
115103
115826
  }
115104
115827
  }));
@@ -115197,6 +115920,10 @@ var GOAL_RUN_OPTIONS = {
115197
115920
  ),
115198
115921
  settleIgnore: () => new Option("--settle-ignore <pattern>", "a request URL pattern the target marks as background (never pending work; repeatable, * wildcard)").default([]).argParser(collect7),
115199
115922
  longPollMs: () => new Option("--long-poll-ms <n>", "a request pending this long on an interactive page is a long-poll (default 5000)").argParser(nonNegativeIntArg),
115923
+ devServer: () => new Option(
115924
+ "--dev-server",
115925
+ "opt-in (#745): a Vite/HMR dev target discounts asset-classified reads (its own modules, scripts, styles, images) from settling. A non-asset write still keeps settle waiting and stays tracked, and a configured --api-prefix still wins over a Vite-shaped path"
115926
+ ).default(false),
115200
115927
  apiPrefix: () => new Option("--api-prefix <path>", "a path prefix whose requests are the app's API in the timing summary (repeatable), e.g. /api/").default([]).argParser(collect7),
115201
115928
  ignoreNoProgress: () => new Option("--ignore-no-progress <pattern>", "a route / action label / busy indicator (its description or visible words) where ui-no-progress is expected (repeatable, * wildcard)").default([]).argParser(collect7),
115202
115929
  allowSecretCmd: () => new Option(
@@ -115274,7 +116001,8 @@ function targetFlagsFromFlags(o, dialogs) {
115274
116001
  ...o.allowWrites === true ? { allowWrites: true } : {},
115275
116002
  allowWrite: o.allowWrite ?? [],
115276
116003
  ...o.hangReplayWrites === true ? { hangReplayWrites: true } : {},
115277
- ...o.longPollMs === void 0 ? {} : { longPollMs: Number(o.longPollMs) }
116004
+ ...o.longPollMs === void 0 ? {} : { longPollMs: Number(o.longPollMs) },
116005
+ ...o.devServer === true ? { devServer: true } : {}
115278
116006
  };
115279
116007
  }
115280
116008
  function resolveGoalRunFlags(o, url2, deps = {}) {
@@ -115489,7 +116217,7 @@ function declareExploreCommand(program2) {
115489
116217
  ).option("--issue-repo <owner/name>", "the system-under-test repo findings for THIS target are filed to").addOption(GOAL_RUN_OPTIONS.hangReplays()).option(
115490
116218
  "--hang-replay-writes",
115491
116219
  "let hang replays re-send a paid/destructive write the run sent (default: such a hang is reported inconclusive, never replayed)"
115492
- ).addOption(GOAL_RUN_OPTIONS.settleIgnore()).addOption(GOAL_RUN_OPTIONS.longPollMs()).addOption(GOAL_RUN_OPTIONS.apiPrefix()).addOption(GOAL_RUN_OPTIONS.ignoreNoProgress()).option("--jevitate-repo <owner/name>", "where jevitate engine findings are filed (default matt-cochran/jevitate)").option(
116220
+ ).addOption(GOAL_RUN_OPTIONS.settleIgnore()).addOption(GOAL_RUN_OPTIONS.longPollMs()).addOption(GOAL_RUN_OPTIONS.devServer()).addOption(GOAL_RUN_OPTIONS.apiPrefix()).addOption(GOAL_RUN_OPTIONS.ignoreNoProgress()).option("--jevitate-repo <owner/name>", "where jevitate engine findings are filed (default matt-cochran/jevitate)").option(
115493
116221
  "--min-control-coverage <ratio>",
115494
116222
  "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",
115495
116223
  ratioArg
@@ -116631,18 +117359,19 @@ function declaredPersona(name, personasFile) {
116631
117359
  function registerLoginCommand(program2, deps) {
116632
117360
  program2.command("login").description(
116633
117361
  "#427: sign in as a persona with credentials from environment variables and save its Playwright storage state (mode 0600) \u2014 the session `explore --storage-state/--persona` starts from. Credentials are never accepted as values, never printed or recorded"
116634
- ).option("--persona <name>", "the persona being signed in (names it in the result); with a declared persona (--personas or .jevitate/personas.json) its login parameters and storage state path are the defaults").option("--personas <file>", "personas JSON to read --persona's login parameters from (default: the project's .jevitate/personas.json)").option("--url <loginUrl>", "the sign-in page (must be an authorized origin: its own, or --allow)").option("--api <url>", "#449: sign in through this HTTP endpoint instead of a form: the credentials are POSTed as JSON (must be an authorized origin; redirects are never followed)").option("--api-user-key <key>", "with --api: the JSON body key the username is sent under (default username)").option("--api-password-key <key>", "with --api: the JSON body key the password is sent under (default password)").option("--token-path <path>", "with --api: a dotted path into the JSON response whose value is the session token (e.g. token, data.accessToken); without it the response's cookies are the session").option("--storage-key <key>", "with --token-path: the localStorage key the token is written under (default: the path's last segment)").option("--storage <where>", "with --token-path: local (the only choice \u2014 a Playwright storage state has no sessionStorage)").option("--verify-url <url>", "with --api: the app page the new session is proven on, and whose origin receives the token (default: the endpoint's origin root)").option("--auth-check <check>", "with --api: how --verify-url proves the session: off | auto | urlExcludes:<text> | selector:<css> (default auto)").option("--user-env <VAR>", "environment variable holding the username (its NAME \u2014 the value is read from the environment)").option("--password-env <VAR>", "environment variable holding the password (its NAME \u2014 the value is read from the environment)").option("--user-field <field>", "the username field: its label, else a CSS selector (default: found by autocomplete/type/name)").option("--password-field <field>", "the password field: its label, else a CSS selector (default: the visible password input)").option("--submit <name>", "the submit button's accessible name (default: the form's submit button, else Enter)").option("--success <check>", "how a successful sign-in is recognised: urlIncludes:<text> | selector:<css> | text:<text> (default: the page leaves the sign-in form \u2014 no login-like URL, no password field)").option("--save <file>", "where to write the storage state (parent directory created; mode 0600; never inside a repo's .jevitate/)").option("--allow <origin>", "an origin credentials may be typed into (repeatable; default: the sign-in page's own) \u2014 e.g. an SSO provider", (v, prev) => [...prev, v], []).option("--timeout <seconds>", `how long each step of the sign-in may take (default ${DEFAULT_TIMEOUT_S})`, positiveIntArg).option("--json", "emit a JSON envelope").action(async function() {
117362
+ ).option("--persona <name>", "the persona being signed in (names it in the result); with a declared persona (--personas or .jevitate/personas.json) its login parameters and storage state path are the defaults").option("--personas <file>", "personas JSON to read --persona's login parameters from (default: the project's .jevitate/personas.json)").option("--url <loginUrl>", "the sign-in page (must be an authorized origin: its own, or --allow)").option("--api <url>", "#449: sign in through this HTTP endpoint instead of a form: the credentials are POSTed as JSON (must be an authorized origin; redirects are never followed)").option("--api-user-key <key>", "with --api: the JSON body key the username is sent under (default username)").option("--api-password-key <key>", "with --api: the JSON body key the password is sent under (default password)").option("--token-path <path>", "with --api: a dotted path into the JSON response whose value is the session token (e.g. token, data.accessToken); without it the response's cookies are the session").option("--storage-key <key>", "with --token-path: the localStorage key the token is written under (default: the path's last segment)").option("--storage <where>", "with --token-path: local (the only choice \u2014 a Playwright storage state has no sessionStorage)").option("--verify-url <url>", "with --api: the app page the new session is proven on, and whose origin receives the token (default: the endpoint's origin root)").option("--auth-check <check>", "with --api: how --verify-url proves the session: off | auto | urlExcludes:<text> | selector:<css> (default auto)").option("--user-env <VAR>", "environment variable holding the username (its NAME \u2014 the value is read from the environment)").option("--password-env <VAR>", "environment variable holding the password (its NAME \u2014 the value is read from the environment)").option("--user-field <field>", "the username field: its label, else a CSS selector (default: found by autocomplete/type/name)").option("--password-field <field>", "the password field: its label, else a CSS selector (default: the visible password input)").option("--organization-field <field>", "#741: the organization selection control: its label, else a CSS selector (default: a control labelled organization/workspace/tenant/account, else a chooser card)").option("--role-field <field>", "#741: the role selection control: its label, else a CSS selector (default: a control labelled role, else a chooser card)").option("--submit <name>", "the submit button's accessible name (default: the form's submit button, else Enter)").option("--success <check>", "how a successful sign-in is recognised: urlIncludes:<text> | selector:<css> | text:<text> (default: the page leaves the sign-in form \u2014 no login-like URL, no password field)").option("--save <file>", "where to write the storage state (parent directory created; mode 0600; never inside a repo's .jevitate/)").option("--allow <origin>", "an origin credentials may be typed into (repeatable; default: the sign-in page's own) \u2014 e.g. an SSO provider", (v, prev) => [...prev, v], []).option("--timeout <seconds>", `how long each step of the sign-in may take (default ${DEFAULT_TIMEOUT_S})`, positiveIntArg).option("--json", "emit a JSON envelope").action(async function() {
116635
117363
  const o = this.opts();
116636
117364
  const emit5 = (envelope) => emitCommandResult(program2, envelope, { json: o.json === true, command: "login", human: formatLoginHuman });
116637
117365
  let login;
116638
117366
  let save;
116639
117367
  let warnings;
116640
117368
  let authCheck;
117369
+ let declared;
116641
117370
  try {
116642
117371
  if (o.persona !== void 0 && !PERSONA_NAME2.test(o.persona)) {
116643
117372
  throw new LoginArgsError(`--persona ${JSON.stringify(o.persona)} must be 1-64 of [A-Za-z0-9_.-], starting alphanumeric`);
116644
117373
  }
116645
- const declared = o.persona === void 0 ? void 0 : declaredPersona(o.persona, o.personas);
117374
+ declared = o.persona === void 0 ? void 0 : declaredPersona(o.persona, o.personas);
116646
117375
  if (o.personas !== void 0 && o.persona === void 0) throw new LoginArgsError("--personas reads a declared persona's login parameters: pass --persona <name> too");
116647
117376
  if (o.personas !== void 0 && declared === void 0) throw new LoginArgsError(`persona ${o.persona} is not declared in ${o.personas}`);
116648
117377
  const base = declared?.login;
@@ -116690,7 +117419,9 @@ function registerLoginCommand(program2, deps) {
116690
117419
  ...pick3("userField", o.userField ?? (viaApi ? void 0 : base?.userField)),
116691
117420
  ...pick3("passwordField", o.passwordField ?? (viaApi ? void 0 : base?.passwordField)),
116692
117421
  ...pick3("submit", o.submit ?? (viaApi ? void 0 : base?.submit)),
116693
- ...pick3("success", o.success ?? (viaApi ? void 0 : base?.success))
117422
+ ...pick3("success", o.success ?? (viaApi ? void 0 : base?.success)),
117423
+ ...pick3("organizationField", viaApi ? void 0 : base?.organizationField),
117424
+ ...pick3("roleField", viaApi ? void 0 : base?.roleField)
116694
117425
  });
116695
117426
  ({ path: save, warnings } = checkSaveTarget(target, "--save"));
116696
117427
  } catch (err) {
@@ -116712,6 +117443,7 @@ function registerLoginCommand(program2, deps) {
116712
117443
  env: deps.explore?.env ?? process.env,
116713
117444
  port: (deps.explore?.browserPortFactory ?? (() => new PlaywrightBrowserPort()))(),
116714
117445
  timeoutMs: (o.timeout ?? DEFAULT_TIMEOUT_S) * 1e3,
117446
+ ...declared?.context === void 0 ? {} : { context: declared.context },
116715
117447
  ...login.api === void 0 ? {} : { verify: { url: apiVerifyUrl(login.api), check: authCheck } }
116716
117448
  });
116717
117449
  const result = {
@@ -116722,11 +117454,13 @@ function registerLoginCommand(program2, deps) {
116722
117454
  landedUrl: minted.landedUrl,
116723
117455
  userEnv: login.userEnv,
116724
117456
  passwordEnv: login.passwordEnv,
117457
+ ...declared?.context === void 0 ? {} : { context: declared.context },
116725
117458
  warnings
116726
117459
  };
116727
117460
  emit5(ok(result));
116728
117461
  } catch (err) {
116729
117462
  if (err instanceof LoginArgsError) emit5(fail("E_LOGIN_ARGS", err.message));
117463
+ else if (err instanceof LoginContextError) emit5(fail("E_LOGIN_CONTEXT", err.message));
116730
117464
  else if (err instanceof LoginFailedError) emit5(fail("E_LOGIN_FAILED", err.message));
116731
117465
  else throw err;
116732
117466
  }
@@ -117329,6 +118063,7 @@ var CLI_TOOL_SPECS = [
117329
118063
  checkOverflow: b("--check-overflow"),
117330
118064
  apiPrefix: many("--api-prefix"),
117331
118065
  longPollMs: n2("--long-poll-ms"),
118066
+ devServer: b("--dev-server"),
117332
118067
  jobWaitMs: n2("--job-wait-ms"),
117333
118068
  replyWaitMs: n2("--reply-wait-ms"),
117334
118069
  replyQuietMs: n2("--reply-quiet-ms"),
@@ -117394,6 +118129,8 @@ var CLI_TOOL_SPECS = [
117394
118129
  evidenceVideo: b("--evidence-video"),
117395
118130
  // #485: like run_exploration authCheck "off" — an explicit opt-out, recorded on the report (preflight.skipped).
117396
118131
  skipPreflight: b("--skip-preflight"),
118132
+ // #205/#746: start on a starved host (the campaign-start check and every mission) instead of E_HOST_STARVED.
118133
+ ignoreHostLoad: b("--ignore-host-load"),
117397
118134
  ...JEV_AI
117398
118135
  },
117399
118136
  omitted: {
@@ -117504,6 +118241,7 @@ var CLI_TOOL_SPECS = [
117504
118241
  hangReplays: n2("--hang-replays"),
117505
118242
  settleIgnore: many("--settle-ignore"),
117506
118243
  longPollMs: n2("--long-poll-ms"),
118244
+ devServer: b("--dev-server"),
117507
118245
  apiPrefix: many("--api-prefix"),
117508
118246
  ignoreNoProgress: many("--ignore-no-progress"),
117509
118247
  // #544: mission fixtures set up before / restored after every take; the Journey records the file.
@@ -118954,6 +119692,7 @@ var AUTHOR_GOAL_RUN_FLAGS = [
118954
119692
  "hangReplays",
118955
119693
  "settleIgnore",
118956
119694
  "longPollMs",
119695
+ "devServer",
118957
119696
  "apiPrefix",
118958
119697
  "ignoreNoProgress",
118959
119698
  "allowSecretCmd",
@@ -118991,6 +119730,42 @@ function formatAuthorJourneyHuman(data) {
118991
119730
  return `${lines.join("\n")}
118992
119731
  `;
118993
119732
  }
119733
+ var REQUIRED_AUTHOR_ARGS = [
119734
+ { flag: "--url", attr: "url", placeholder: "<url>" },
119735
+ { flag: "--goal", attr: "goal", placeholder: "<text>" },
119736
+ { flag: "--success", attr: "success", placeholder: "<spec>" },
119737
+ { flag: "--id", attr: "id", placeholder: "<id>" },
119738
+ { flag: "--name", attr: "name", placeholder: "<name>" }
119739
+ ];
119740
+ var SECRET_ARG_FLAGS = /* @__PURE__ */ new Set(["--secret", "--secret-field", "--totp"]);
119741
+ function quoteArg(v) {
119742
+ return /[\s"'`$\\|&;<>(){}*?!#]/.test(v) ? JSON.stringify(v) : v;
119743
+ }
119744
+ function missingAuthorArgs(opts) {
119745
+ return REQUIRED_AUTHOR_ARGS.filter((a) => {
119746
+ const v = opts[a.attr];
119747
+ return v === void 0 || v === "" || Array.isArray(v) && v.length === 0;
119748
+ });
119749
+ }
119750
+ function authorCorrectedInvocation(cmd, opts, missing) {
119751
+ const parts2 = ["jevitate", "explore-author-journey"];
119752
+ for (const opt of cmd.options) {
119753
+ if (opt.long === void 0 || opt.long === "--json") continue;
119754
+ const value = opts[opt.attributeName()];
119755
+ if (value === void 0 || value === false) continue;
119756
+ if (Array.isArray(value)) {
119757
+ for (const item of value) parts2.push(opt.long, SECRET_ARG_FLAGS.has(opt.long) ? "<redacted>" : quoteArg(String(item)));
119758
+ continue;
119759
+ }
119760
+ if (value === true) {
119761
+ parts2.push(opt.long);
119762
+ continue;
119763
+ }
119764
+ parts2.push(opt.long, SECRET_ARG_FLAGS.has(opt.long) ? "<redacted>" : quoteArg(String(value)));
119765
+ }
119766
+ for (const m of missing) parts2.push(m.flag, m.placeholder);
119767
+ return parts2.join(" ");
119768
+ }
118994
119769
  function registerAuthorJourneyCommands(program2, deps) {
118995
119770
  const cmd = withServerLogFlags(
118996
119771
  withBrowserLaunchFlags(
@@ -119026,12 +119801,26 @@ function registerAuthorJourneyCommands(program2, deps) {
119026
119801
  ].join("\n")
119027
119802
  ).action(async function() {
119028
119803
  const o = this.opts();
119804
+ const optsRecord = o;
119029
119805
  if (o.storageState !== void 0 && !existsSync41(o.storageState)) {
119030
- emitJson(program2, fail("E_EXPLORE_ARGS", `storage state not found: ${o.storageState}`));
119806
+ emitJson(
119807
+ program2,
119808
+ fail(
119809
+ "E_EXPLORE_AUTHOR_JOURNEY_ARGS",
119810
+ `--storage-state <file> not found: ${o.storageState} \u2014 corrected invocation: ${authorCorrectedInvocation(this, optsRecord, missingAuthorArgs(optsRecord))}`
119811
+ )
119812
+ );
119031
119813
  return;
119032
119814
  }
119033
119815
  if (!o.url || !o.goal || o.success.length === 0 || !o.id || !o.name) {
119034
- emitJson(program2, fail("E_AUTHOR_ARGS", "--url, --goal, --success, --id and --name are all required"));
119816
+ const missingArgs = missingAuthorArgs(optsRecord);
119817
+ emitJson(
119818
+ program2,
119819
+ fail(
119820
+ "E_EXPLORE_AUTHOR_JOURNEY_ARGS",
119821
+ `${missingArgs.map((m) => m.flag).join(", ")} ${missingArgs.length === 1 ? "is" : "are"} required \u2014 corrected invocation: ${authorCorrectedInvocation(this, optsRecord, missingArgs)}`
119822
+ )
119823
+ );
119035
119824
  return;
119036
119825
  }
119037
119826
  const successChecks = [];