@validate.qa/runner 1.0.37 → 1.0.38

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 (3) hide show
  1. package/dist/cli.js +497 -28
  2. package/dist/cli.mjs +497 -28
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -241,6 +241,9 @@ var require_ai_provider = __commonJS({
241
241
  exports2.getAvailableModels = getAvailableModels;
242
242
  exports2.getAvailableOrchestratorModels = getAvailableOrchestratorModels;
243
243
  exports2.isAvailableModelId = isAvailableModelId;
244
+ exports2.resolveWhisperProvider = resolveWhisperProvider;
245
+ exports2.isWhisperConfigured = isWhisperConfigured;
246
+ exports2.getWhisperModel = getWhisperModel;
244
247
  exports2.getOpenAIWhisperClient = getOpenAIWhisperClient;
245
248
  exports2.resetAIClient = resetAIClient;
246
249
  exports2.getServerProxiedAIClient = getServerProxiedAIClient;
@@ -570,20 +573,70 @@ var require_ai_provider = __commonJS({
570
573
  function isAvailableModelId(modelId) {
571
574
  return getAvailableModels().some((model) => model.id === modelId);
572
575
  }
576
+ var GROQ_API_BASE_DEFAULT = "https://api.groq.com/openai/v1";
577
+ var GROQ_WHISPER_DEFAULT = "whisper-large-v3-turbo";
578
+ var OPENAI_WHISPER_DEFAULT = "whisper-1";
579
+ function isUsableApiKey(value) {
580
+ if (!value)
581
+ return false;
582
+ const trimmed = value.trim();
583
+ if (!trimmed)
584
+ return false;
585
+ if (/your[-_]?.*(key|api|secret)/i.test(trimmed))
586
+ return false;
587
+ if (trimmed === "sk-..." || trimmed === "gsk_...")
588
+ return false;
589
+ return true;
590
+ }
591
+ function resolveWhisperProvider() {
592
+ const forced = (process.env.STT_PROVIDER ?? process.env.WHISPER_PROVIDER ?? "").trim().toLowerCase();
593
+ const hasGroq = isUsableApiKey(process.env.GROQ_API_KEY);
594
+ const hasOpenAI = isUsableApiKey(process.env.OPENAI_API_KEY);
595
+ if (forced === "groq")
596
+ return hasGroq ? "groq" : null;
597
+ if (forced === "openai")
598
+ return hasOpenAI ? "openai" : null;
599
+ if (hasGroq)
600
+ return "groq";
601
+ if (hasOpenAI)
602
+ return "openai";
603
+ return null;
604
+ }
605
+ function isWhisperConfigured() {
606
+ return resolveWhisperProvider() !== null;
607
+ }
608
+ function getWhisperModel(provider) {
609
+ const p = provider ?? resolveWhisperProvider();
610
+ if (p === "groq") {
611
+ return process.env.GROQ_WHISPER_MODEL?.trim() || process.env.OPENAI_WHISPER_MODEL?.trim() || GROQ_WHISPER_DEFAULT;
612
+ }
613
+ return process.env.OPENAI_WHISPER_MODEL?.trim() || OPENAI_WHISPER_DEFAULT;
614
+ }
573
615
  var _whisperClient = null;
616
+ var _whisperClientProvider = null;
574
617
  function getOpenAIWhisperClient() {
575
- if (_whisperClient)
618
+ const provider = resolveWhisperProvider();
619
+ if (!provider) {
620
+ throw new Error("Speech transcription is not configured (set GROQ_API_KEY or OPENAI_API_KEY)");
621
+ }
622
+ if (_whisperClient && _whisperClientProvider === provider) {
576
623
  return _whisperClient;
577
- const apiKey = process.env.OPENAI_API_KEY;
578
- if (!apiKey)
579
- throw new Error("OPENAI_API_KEY is required for Whisper transcription");
580
- _whisperClient = new openai_1.default({ apiKey });
624
+ }
625
+ if (provider === "groq") {
626
+ const apiKey = process.env.GROQ_API_KEY;
627
+ const baseURL = process.env.GROQ_API_BASE?.trim() || GROQ_API_BASE_DEFAULT;
628
+ _whisperClient = new openai_1.default({ apiKey, baseURL });
629
+ } else {
630
+ _whisperClient = new openai_1.default({ apiKey: process.env.OPENAI_API_KEY });
631
+ }
632
+ _whisperClientProvider = provider;
581
633
  return _whisperClient;
582
634
  }
583
635
  var _serverProxiedClient = null;
584
636
  function resetAIClient() {
585
637
  _clientCache.clear();
586
638
  _whisperClient = null;
639
+ _whisperClientProvider = null;
587
640
  _serverProxiedClient = null;
588
641
  }
589
642
  function buildServerProxiedClient() {
@@ -1682,6 +1735,7 @@ var init_constants = __esm({
1682
1735
  "DATABASE_URL",
1683
1736
  "JWT_SECRET",
1684
1737
  "OPENAI_API_KEY",
1738
+ "GROQ_API_KEY",
1685
1739
  "GEMINI_API_KEY",
1686
1740
  "XAI_API_KEY",
1687
1741
  "GROK_API_KEY",
@@ -3248,8 +3302,10 @@ var init_audit_pricing = __esm({
3248
3302
  "MiniMax-M2.7-highspeed": { input: 0.6, output: 2.4 },
3249
3303
  "MiniMax-M2": { input: 0.3, output: 1.2 },
3250
3304
  "deepseek-v4-flash": { input: 0.14, output: 0.28 },
3251
- // Whisper bills per-minute, not per-token
3305
+ // Whisper bills per-minute, not per-token (OpenAI + Groq)
3252
3306
  "whisper-1": null,
3307
+ "whisper-large-v3": null,
3308
+ "whisper-large-v3-turbo": null,
3253
3309
  // Historical / deprecated (kept so old audit rows still show a cost).
3254
3310
  // Deprecated Grok variants priced at the legacy authoritative Grok rate
3255
3311
  // ($0.20/$0.80) those rows were generated under.
@@ -3860,6 +3916,43 @@ function assertedText(stmt) {
3860
3916
  }
3861
3917
  return out.join(" ").toLowerCase();
3862
3918
  }
3919
+ function clickedAdditiveAffordance(lines) {
3920
+ const nameRe = /name\s*:\s*(?:['"`]([^'"`]+)['"`]|\/([^/]+)\/)/g;
3921
+ for (const ln of lines) {
3922
+ if (!/\.click\s*\(/.test(ln))
3923
+ continue;
3924
+ nameRe.lastIndex = 0;
3925
+ let m;
3926
+ while (m = nameRe.exec(ln)) {
3927
+ const name = (m[1] ?? m[2]).toLowerCase();
3928
+ if (EXCLUDE_AFFORDANCE.test(name))
3929
+ continue;
3930
+ if (ADDITIVE_AFFORDANCE.test(name))
3931
+ return true;
3932
+ }
3933
+ }
3934
+ return false;
3935
+ }
3936
+ function matcherText(stmt) {
3937
+ const out = [];
3938
+ const re = /\.(?:toContainText|toHaveText)\s*\(\s*(?:['"`]([^'"`]+)['"`]|\/([^/]+)\/)/g;
3939
+ let m;
3940
+ while (m = re.exec(stmt))
3941
+ out.push(m[1] ?? m[2]);
3942
+ return out.join(" ").toLowerCase();
3943
+ }
3944
+ function isEmptyStateProof(stmt) {
3945
+ if (NEGATED_ASSERT.test(stmt))
3946
+ return false;
3947
+ if (/\.toHaveCount\s*\(\s*0\s*\)/.test(stmt))
3948
+ return true;
3949
+ if (/\.toBe\s*\(\s*0\s*\)/.test(stmt) && !isChromeTarget(stmt))
3950
+ return true;
3951
+ if (VISIBILITY_MATCHER.test(stmt) || TEXT_CONTENT_MATCHER.test(stmt)) {
3952
+ return EMPTY_STATE_TEXT.test(`${assertedText(stmt)} ${matcherText(stmt)}`);
3953
+ }
3954
+ return false;
3955
+ }
3863
3956
  function hasAttributeTransition(stmts) {
3864
3957
  const byAttr = /* @__PURE__ */ new Map();
3865
3958
  for (const { stmt } of stmts) {
@@ -3966,6 +4059,21 @@ function validateAssertionStrength(code, opts = {}) {
3966
4059
  const interacted = mutated || navClicks > 0;
3967
4060
  navOnly = !mutated && navClicks > 0;
3968
4061
  const reloaded = RELOAD.test(body);
4062
+ if (opts.flagContradictedAdditions && mutated && clickedAdditiveAffordance(interactionStmts)) {
4063
+ const empties = stmts.filter(({ stmt }) => isEmptyStateProof(stmt));
4064
+ if (empties.length > 0) {
4065
+ const others = stmts.filter(({ stmt }) => !isEmptyStateProof(stmt));
4066
+ const provesSomethingElse = detectOutcomeProof(others, mutated, false, clickedControlNames(interactionStmts), typedValues(interactionStmts));
4067
+ if (!provesSomethingElse) {
4068
+ return {
4069
+ ok: false,
4070
+ severity: "block",
4071
+ reason: "additive action (add/create/save/favourite) proves no positive outcome \u2014 its only asserted result is that the target collection is still empty; hold @needs-review, do not ship as a passing regression",
4072
+ signals: base2({ interacted, mutated, terminal })
4073
+ };
4074
+ }
4075
+ }
4076
+ }
3969
4077
  const hasOutcomeProof = detectOutcomeProof(stmts, mutated, reloaded, clickedControlNames(interactionStmts), typedValues(interactionStmts));
3970
4078
  const signals = base2({ interacted, mutated, navOnly, hasOutcomeProof, terminal });
3971
4079
  if (hasOutcomeProof) {
@@ -4020,7 +4128,7 @@ function validateAssertionStrength(code, opts = {}) {
4020
4128
  signals
4021
4129
  };
4022
4130
  }
4023
- var ERROR_BANNER, OUTCOME_ALWAYS, FORM_INPUT, ANY_CLICK, NAV_LOCATOR, URL_PROOF, RELOAD, EXPECT_STMT, BLOCK_SCAFFOLD, LOCATOR_BINDING, STATE_MATCHER, CHROME_LOCATOR, CONTENT_ROLE, GET_BY_TEXT, HAS_TEXT_FILTER, OUTCOME_TEXT, MESSAGE_SURFACE, TEXT_CONTENT_MATCHER, VISIBILITY_MATCHER, AUTH_START, AUTH_END, LOGIN_CALL_LINE, NEGATED_ASSERT, TYPED_INPUT, TITLE_LINE, ROLE_NAME_RE, INTERACT_VERB, ROLE_SIG_RE, TRANSIENT_CONTROL_ROLES;
4131
+ var ERROR_BANNER, OUTCOME_ALWAYS, FORM_INPUT, ANY_CLICK, NAV_LOCATOR, URL_PROOF, RELOAD, EXPECT_STMT, BLOCK_SCAFFOLD, LOCATOR_BINDING, STATE_MATCHER, CHROME_LOCATOR, CONTENT_ROLE, GET_BY_TEXT, HAS_TEXT_FILTER, OUTCOME_TEXT, MESSAGE_SURFACE, TEXT_CONTENT_MATCHER, VISIBILITY_MATCHER, ADDITIVE_AFFORDANCE, EXCLUDE_AFFORDANCE, EMPTY_STATE_TEXT, AUTH_START, AUTH_END, LOGIN_CALL_LINE, NEGATED_ASSERT, TYPED_INPUT, TITLE_LINE, ROLE_NAME_RE, INTERACT_VERB, ROLE_SIG_RE, TRANSIENT_CONTROL_ROLES;
4024
4132
  var init_assertion_strength = __esm({
4025
4133
  "../shared/dist/assertion-strength.js"() {
4026
4134
  "use strict";
@@ -4064,6 +4172,9 @@ var init_assertion_strength = __esm({
4064
4172
  MESSAGE_SURFACE = /getByText\s*\(|\.filter\s*\(\s*\{\s*hasText\s*:|getByRole\s*\(\s*['"`](?:alert|alertdialog|status|heading|dialog)['"`]/i;
4065
4173
  TEXT_CONTENT_MATCHER = /\.(toHaveText|toContainText)\s*\(/;
4066
4174
  VISIBILITY_MATCHER = /\.toBeVisible\s*\(/;
4175
+ ADDITIVE_AFFORDANCE = /\b(add|adds|added|create|creates|save|saves|favou?rite|favou?rites|invite|invites|upload|uploads|insert|append|bookmark|follow|subscribe|post|posts|new)\b/i;
4176
+ EXCLUDE_AFFORDANCE = /\b(filter|filters|search|searches|sort|view|views|tab|tabs|window|windows|toggle|expand|collapse|column|columns|zoom|layout)\b/i;
4177
+ EMPTY_STATE_TEXT = /\bno\b[\s\S]{0,24}?\b(yet|found|results?|items?|favou?rites?|entries|records?|matches|posts?|comments?)\b|nothing (?:here|yet|to show|found)|\bempty\b|\(\s*0\s*\)|\b0\s+(?:favou?rites?|results?|items?|entries|records?|matches|posts?|comments?)\b/i;
4067
4178
  AUTH_START = /^\s*\/\/\s*>>>\s*VALIDATE_QA_AUTH/;
4068
4179
  AUTH_END = /^\s*\/\/\s*<<<\s*VALIDATE_QA_AUTH/;
4069
4180
  LOGIN_CALL_LINE = /^\s*await\s+__vqaLogin\s*\(\s*page\s*\)\s*;?\s*$/;
@@ -4086,6 +4197,24 @@ var init_assertion_strength = __esm({
4086
4197
  });
4087
4198
 
4088
4199
  // ../shared/dist/run-classification.js
4200
+ function pickLatestHealthRun(runs) {
4201
+ for (const run2 of runs) {
4202
+ if (run2.mode === "auth-setup")
4203
+ continue;
4204
+ if (run2.status === "PASSED" || run2.status === "FAILED" || run2.status === "ERROR") {
4205
+ return run2;
4206
+ }
4207
+ }
4208
+ return null;
4209
+ }
4210
+ function pickDisplayRun(runs) {
4211
+ if (runs.length === 0)
4212
+ return null;
4213
+ const latest = runs[0];
4214
+ if (latest.status === "PENDING" || latest.status === "RUNNING")
4215
+ return latest;
4216
+ return pickLatestHealthRun(runs) ?? latest;
4217
+ }
4089
4218
  function isEnvironmentalError(run2) {
4090
4219
  if (!run2)
4091
4220
  return false;
@@ -4547,6 +4676,262 @@ var init_origin_domain = __esm({
4547
4676
  }
4548
4677
  });
4549
4678
 
4679
+ // ../shared/dist/voice-config.js
4680
+ function normalizeTtsVoiceId(voiceId) {
4681
+ if (!voiceId)
4682
+ return null;
4683
+ const trimmed = voiceId.trim();
4684
+ if (!trimmed)
4685
+ return null;
4686
+ return LEGACY_TTS_VOICE_ID_MAP[trimmed] ?? trimmed;
4687
+ }
4688
+ function sttModelsForProvider(provider) {
4689
+ if (provider === "openai") {
4690
+ return STT_MODEL_OPTIONS.filter((m) => m.provider === "openai");
4691
+ }
4692
+ if (provider === "groq") {
4693
+ return STT_MODEL_OPTIONS.filter((m) => m.provider === "groq");
4694
+ }
4695
+ return STT_MODEL_OPTIONS;
4696
+ }
4697
+ function isValidSttProvider(value) {
4698
+ return value === "groq" || value === "openai";
4699
+ }
4700
+ function isSttModelForProvider(modelId, provider) {
4701
+ if (!modelId)
4702
+ return false;
4703
+ return STT_MODEL_OPTIONS.some((m) => m.id === modelId && m.provider === provider);
4704
+ }
4705
+ function resolveSttModelForProvider(provider, configuredModel) {
4706
+ if (configuredModel && isSttModelForProvider(configuredModel, provider)) {
4707
+ return configuredModel;
4708
+ }
4709
+ return DEFAULT_STT_MODEL_BY_PROVIDER[provider];
4710
+ }
4711
+ function sanitizeSttErrorMessage(raw) {
4712
+ if (!raw)
4713
+ return "Speech transcription failed.";
4714
+ let msg = String(raw).replace(/\b(sk-proj-|sk-|gsk_|sk-svcacct-)[A-Za-z0-9_-]{8,}/gi, "$1***").replace(/Incorrect API key provided:\s*\S+/gi, "Incorrect API key provided").replace(/You can find your API key at https?:\/\/\S+/gi, "").replace(/\s+/g, " ").trim();
4715
+ if (/401|incorrect api key|invalid.?api.?key|authentication|unauthorized/i.test(msg)) {
4716
+ return "Speech transcription failed: the speech API key was rejected. Prefer GROQ_API_KEY (same as Copilot voice) or a valid OPENAI_API_KEY on the org server.";
4717
+ }
4718
+ if (/429|rate.?limit|too many requests/i.test(msg)) {
4719
+ return "Speech transcription failed: the speech provider rate-limited the request. Try again in a moment.";
4720
+ }
4721
+ if (/not configured|GROQ_API_KEY or OPENAI_API_KEY/i.test(msg)) {
4722
+ return "Speech transcription is not configured. Set GROQ_API_KEY (preferred) or OPENAI_API_KEY on the org server.";
4723
+ }
4724
+ if (msg.length > 280)
4725
+ msg = `${msg.slice(0, 277)}\u2026`;
4726
+ return msg || "Speech transcription failed.";
4727
+ }
4728
+ function isValidTtsVoiceId(value) {
4729
+ const normalized = normalizeTtsVoiceId(value);
4730
+ if (!normalized)
4731
+ return false;
4732
+ return TTS_VOICE_OPTIONS.some((v) => v.id === normalized);
4733
+ }
4734
+ function isValidTtsModel(value) {
4735
+ if (!value)
4736
+ return false;
4737
+ return TTS_MODEL_OPTIONS.some((m) => m.id === value);
4738
+ }
4739
+ var VOICE_PREVIEW_SAMPLE, DEFAULT_TTS_MODEL, DEFAULT_TTS_VOICE_ID, DEFAULT_TTS_SPEED, LEGACY_TTS_VOICE_ID_MAP, DEFAULT_STT_MODEL_BY_PROVIDER, STT_PROVIDER_OPTIONS, STT_MODEL_OPTIONS, TTS_MODEL_OPTIONS, TTS_VOICE_OPTIONS;
4740
+ var init_voice_config = __esm({
4741
+ "../shared/dist/voice-config.js"() {
4742
+ "use strict";
4743
+ VOICE_PREVIEW_SAMPLE = "Hello! I am your Validate Copilot assistant. I can help you run tests, review failures, and generate new coverage for your project.";
4744
+ DEFAULT_TTS_MODEL = "speech-2.6-hd";
4745
+ DEFAULT_TTS_VOICE_ID = "English_Graceful_Lady";
4746
+ DEFAULT_TTS_SPEED = 1;
4747
+ LEGACY_TTS_VOICE_ID_MAP = {
4748
+ English_LivelyGirl: "English_PlayfulGirl",
4749
+ English_Insightful_Man: "English_Insightful_Speaker",
4750
+ English_Deep_Voice_Man: "English_ManWithDeepVoice",
4751
+ English_CasualMan: "English_Jovialman"
4752
+ };
4753
+ DEFAULT_STT_MODEL_BY_PROVIDER = {
4754
+ groq: "whisper-large-v3-turbo",
4755
+ openai: "whisper-1"
4756
+ };
4757
+ STT_PROVIDER_OPTIONS = [
4758
+ {
4759
+ id: "auto",
4760
+ label: "Auto (prefer Groq)",
4761
+ description: "Use Groq when GROQ_API_KEY is set, otherwise OpenAI Whisper."
4762
+ },
4763
+ {
4764
+ id: "groq",
4765
+ label: "Groq Whisper",
4766
+ description: "Free-tier OpenAI-compatible Whisper via Groq. Requires GROQ_API_KEY on org servers."
4767
+ },
4768
+ {
4769
+ id: "openai",
4770
+ label: "OpenAI Whisper",
4771
+ description: "OpenAI whisper-1 API. Requires OPENAI_API_KEY on org servers."
4772
+ }
4773
+ ];
4774
+ STT_MODEL_OPTIONS = [
4775
+ {
4776
+ id: "whisper-large-v3-turbo",
4777
+ label: "Whisper Large V3 Turbo",
4778
+ provider: "groq",
4779
+ description: "Fast, accurate default for Groq (recommended)."
4780
+ },
4781
+ {
4782
+ id: "whisper-large-v3",
4783
+ label: "Whisper Large V3",
4784
+ provider: "groq",
4785
+ description: "Full-size Groq Whisper model \u2014 higher quality, slower."
4786
+ },
4787
+ {
4788
+ id: "distil-whisper-large-v3-en",
4789
+ label: "Distil Whisper Large V3 (EN)",
4790
+ provider: "groq",
4791
+ description: "English-optimized distilled model \u2014 fastest Groq option."
4792
+ },
4793
+ {
4794
+ id: "whisper-1",
4795
+ label: "Whisper 1",
4796
+ provider: "openai",
4797
+ description: "OpenAI hosted Whisper model."
4798
+ }
4799
+ ];
4800
+ TTS_MODEL_OPTIONS = [
4801
+ {
4802
+ id: "speech-2.8-hd",
4803
+ label: "Speech 2.8 HD",
4804
+ provider: "minimax",
4805
+ description: "Latest high-definition model with interjection tags (recommended)."
4806
+ },
4807
+ {
4808
+ id: "speech-2.8-turbo",
4809
+ label: "Speech 2.8 Turbo",
4810
+ provider: "minimax",
4811
+ description: "Latest low-latency model with interjection tags."
4812
+ },
4813
+ {
4814
+ id: "speech-2.6-hd",
4815
+ label: "Speech 2.6 HD",
4816
+ provider: "minimax",
4817
+ description: "High-definition MiniMax voice model."
4818
+ },
4819
+ {
4820
+ id: "speech-2.6-turbo",
4821
+ label: "Speech 2.6 Turbo",
4822
+ provider: "minimax",
4823
+ description: "Lower-latency Speech 2.6 variant."
4824
+ },
4825
+ {
4826
+ id: "speech-02-hd",
4827
+ label: "Speech 02 HD",
4828
+ provider: "minimax",
4829
+ description: "Previous-generation HD model."
4830
+ },
4831
+ {
4832
+ id: "speech-02-turbo",
4833
+ label: "Speech 02 Turbo",
4834
+ provider: "minimax",
4835
+ description: "Lower-latency Speech 02 variant."
4836
+ },
4837
+ {
4838
+ id: "speech-01-hd",
4839
+ label: "Speech 01 HD",
4840
+ provider: "minimax",
4841
+ description: "Legacy HD model."
4842
+ },
4843
+ {
4844
+ id: "speech-01-turbo",
4845
+ label: "Speech 01 Turbo",
4846
+ provider: "minimax",
4847
+ description: "Legacy fast model."
4848
+ }
4849
+ ];
4850
+ TTS_VOICE_OPTIONS = [
4851
+ {
4852
+ id: "English_Graceful_Lady",
4853
+ label: "Graceful Lady",
4854
+ description: "Warm, polished professional tone.",
4855
+ gender: "female",
4856
+ accent: "English",
4857
+ sampleText: VOICE_PREVIEW_SAMPLE
4858
+ },
4859
+ {
4860
+ id: "English_ConfidentWoman",
4861
+ label: "Confident Woman",
4862
+ description: "Assured and direct \u2014 good for technical guidance.",
4863
+ gender: "female",
4864
+ accent: "English",
4865
+ sampleText: VOICE_PREVIEW_SAMPLE
4866
+ },
4867
+ {
4868
+ id: "English_SereneWoman",
4869
+ label: "Serene Woman",
4870
+ description: "Calm and measured delivery.",
4871
+ gender: "female",
4872
+ accent: "English",
4873
+ sampleText: VOICE_PREVIEW_SAMPLE
4874
+ },
4875
+ {
4876
+ id: "English_PlayfulGirl",
4877
+ label: "Playful Girl",
4878
+ description: "Upbeat and energetic.",
4879
+ gender: "female",
4880
+ accent: "English",
4881
+ sampleText: VOICE_PREVIEW_SAMPLE
4882
+ },
4883
+ {
4884
+ id: "English_CalmWoman",
4885
+ label: "Calm Woman",
4886
+ description: "Soft, reassuring cadence.",
4887
+ gender: "female",
4888
+ accent: "English",
4889
+ sampleText: VOICE_PREVIEW_SAMPLE
4890
+ },
4891
+ {
4892
+ id: "English_Insightful_Speaker",
4893
+ label: "Insightful Speaker",
4894
+ description: "Clear analyst-style narration.",
4895
+ gender: "male",
4896
+ accent: "English",
4897
+ sampleText: VOICE_PREVIEW_SAMPLE
4898
+ },
4899
+ {
4900
+ id: "English_ManWithDeepVoice",
4901
+ label: "Deep Voice Man",
4902
+ description: "Rich, authoritative baritone.",
4903
+ gender: "male",
4904
+ accent: "English",
4905
+ sampleText: VOICE_PREVIEW_SAMPLE
4906
+ },
4907
+ {
4908
+ id: "English_Jovialman",
4909
+ label: "Jovial Man",
4910
+ description: "Relaxed conversational style.",
4911
+ gender: "male",
4912
+ accent: "English",
4913
+ sampleText: VOICE_PREVIEW_SAMPLE
4914
+ },
4915
+ {
4916
+ id: "English_PatientMan",
4917
+ label: "Patient Man",
4918
+ description: "Steady, tutorial-friendly pacing.",
4919
+ gender: "male",
4920
+ accent: "English",
4921
+ sampleText: VOICE_PREVIEW_SAMPLE
4922
+ },
4923
+ {
4924
+ id: "English_FriendlyPerson",
4925
+ label: "Friendly Person",
4926
+ description: "Neutral, approachable assistant voice.",
4927
+ gender: "neutral",
4928
+ accent: "English",
4929
+ sampleText: VOICE_PREVIEW_SAMPLE
4930
+ }
4931
+ ];
4932
+ }
4933
+ });
4934
+
4550
4935
  // ../shared/dist/index.js
4551
4936
  var dist_exports = {};
4552
4937
  __export(dist_exports, {
@@ -4561,8 +4946,13 @@ __export(dist_exports, {
4561
4946
  CREDIT_PRICES_CENTS: () => CREDIT_PRICES_CENTS,
4562
4947
  DANGEROUS_DEEPLINK_SCHEMES: () => DANGEROUS_DEEPLINK_SCHEMES,
4563
4948
  DB_PRICING_PLAN_MAP: () => DB_PRICING_PLAN_MAP,
4949
+ DEFAULT_STT_MODEL_BY_PROVIDER: () => DEFAULT_STT_MODEL_BY_PROVIDER,
4950
+ DEFAULT_TTS_MODEL: () => DEFAULT_TTS_MODEL,
4951
+ DEFAULT_TTS_SPEED: () => DEFAULT_TTS_SPEED,
4952
+ DEFAULT_TTS_VOICE_ID: () => DEFAULT_TTS_VOICE_ID,
4564
4953
  DEFAULT_UI_VARIANT: () => DEFAULT_UI_VARIANT,
4565
4954
  ENVIRONMENTAL_ERROR_PREFIXES: () => ENVIRONMENTAL_ERROR_PREFIXES,
4955
+ LEGACY_TTS_VOICE_ID_MAP: () => LEGACY_TTS_VOICE_ID_MAP,
4566
4956
  MAX_CREDIT_TOPUP_CENTS: () => MAX_CREDIT_TOPUP_CENTS,
4567
4957
  MAX_DEEPLINK_LENGTH: () => MAX_DEEPLINK_LENGTH,
4568
4958
  MIN_CREDIT_TOPUP_CENTS: () => MIN_CREDIT_TOPUP_CENTS,
@@ -4584,10 +4974,15 @@ __export(dist_exports, {
4584
4974
  PLATFORM_ADMIN_EMAIL: () => PLATFORM_ADMIN_EMAIL,
4585
4975
  SELECTOR_PRIORITIES: () => SELECTOR_PRIORITIES,
4586
4976
  SHARED_PRICING_PLAN_MAP: () => SHARED_PRICING_PLAN_MAP,
4977
+ STT_MODEL_OPTIONS: () => STT_MODEL_OPTIONS,
4978
+ STT_PROVIDER_OPTIONS: () => STT_PROVIDER_OPTIONS,
4587
4979
  SUPPORT_EMAIL: () => SUPPORT_EMAIL,
4588
4980
  TRANSIENT_CONTROL_ROLES: () => TRANSIENT_CONTROL_ROLES,
4589
4981
  TRIAL_DURATION_DAYS: () => TRIAL_DURATION_DAYS,
4982
+ TTS_MODEL_OPTIONS: () => TTS_MODEL_OPTIONS,
4983
+ TTS_VOICE_OPTIONS: () => TTS_VOICE_OPTIONS,
4590
4984
  UI_VARIANTS: () => UI_VARIANTS,
4985
+ VOICE_PREVIEW_SAMPLE: () => VOICE_PREVIEW_SAMPLE,
4591
4986
  accumulateStatements: () => accumulateStatements,
4592
4987
  aiCostToCreditCents: () => aiCostToCreditCents,
4593
4988
  analyzeMobileSelectorQuality: () => analyzeMobileSelectorQuality,
@@ -4650,16 +5045,23 @@ __export(dist_exports, {
4650
5045
  isRunnableStep: () => isRunnableStep,
4651
5046
  isSafeDeepLink: () => isSafeDeepLink,
4652
5047
  isSameRegistrableDomainHost: () => isSameRegistrableDomainHost,
5048
+ isSttModelForProvider: () => isSttModelForProvider,
4653
5049
  isTestReadyForPlatform: () => isTestReadyForPlatform,
4654
5050
  isTrialExpired: () => isTrialExpired,
4655
5051
  isUIVariant: () => isUIVariant,
4656
5052
  isValidMobileAppId: () => isValidMobileAppId,
5053
+ isValidSttProvider: () => isValidSttProvider,
5054
+ isValidTtsModel: () => isValidTtsModel,
5055
+ isValidTtsVoiceId: () => isValidTtsVoiceId,
4657
5056
  mediumForMobilePlatform: () => mediumForMobilePlatform,
4658
5057
  mobileCredentialPlaceholder: () => mobileCredentialPlaceholder,
4659
5058
  mobilePlatformFromMedium: () => mobilePlatformFromMedium,
4660
5059
  normalizeControlName: () => normalizeControlName,
5060
+ normalizeTtsVoiceId: () => normalizeTtsVoiceId,
4661
5061
  otherMobilePlatform: () => otherMobilePlatform,
4662
5062
  parenDepthDelta: () => parenDepthDelta,
5063
+ pickDisplayRun: () => pickDisplayRun,
5064
+ pickLatestHealthRun: () => pickLatestHealthRun,
4663
5065
  placeholderizeRecordedMobileAction: () => placeholderizeRecordedMobileAction,
4664
5066
  placeholderizeRecordedTypeValue: () => placeholderizeRecordedTypeValue,
4665
5067
  planHasBillingPortal: () => planHasBillingPortal,
@@ -4669,7 +5071,9 @@ __export(dist_exports, {
4669
5071
  resolveCrossOriginConfigOverride: () => resolveCrossOriginConfigOverride,
4670
5072
  resolveMobileStepValuePlaceholders: () => resolveMobileStepValuePlaceholders,
4671
5073
  resolvePlatformLocatorBundle: () => resolvePlatformLocatorBundle,
5074
+ resolveSttModelForProvider: () => resolveSttModelForProvider,
4672
5075
  safeOrigin: () => safeOrigin,
5076
+ sanitizeSttErrorMessage: () => sanitizeSttErrorMessage,
4673
5077
  sanitizeTestVariables: () => sanitizeTestVariables,
4674
5078
  selectorCandidatesForStep: () => selectorCandidatesForStep,
4675
5079
  selectorForValue: () => selectorForValue,
@@ -4679,6 +5083,7 @@ __export(dist_exports, {
4679
5083
  stepsNeedingReviewForPlatform: () => stepsNeedingReviewForPlatform,
4680
5084
  stripEnvironmentalPrefix: () => stripEnvironmentalPrefix,
4681
5085
  stripLoginScaffolding: () => stripLoginScaffolding,
5086
+ sttModelsForProvider: () => sttModelsForProvider,
4682
5087
  summarizeHealth: () => summarizeHealth,
4683
5088
  summarizeMobileReadiness: () => summarizeMobileReadiness,
4684
5089
  summarizeRunHealth: () => summarizeRunHealth,
@@ -4703,6 +5108,7 @@ var init_dist = __esm({
4703
5108
  init_agent_env();
4704
5109
  init_test_counting();
4705
5110
  init_origin_domain();
5111
+ init_voice_config();
4706
5112
  }
4707
5113
  });
4708
5114
 
@@ -79521,7 +79927,7 @@ var require_agentic_discovery_prompts = __commonJS({
79521
79927
  "## Test-quality bar",
79522
79928
  "",
79523
79929
  "- ONE `test.describe(<featureName>)` block per file with ONE `test(...)` call inside.",
79524
- "- Use `await page.goto(<relativePath>)` \u2014 baseURL is set in the runner config; never hardcode the host.",
79930
+ "- Use `await page.goto(<relativePath>)` \u2014 baseURL is set in the runner config; never hardcode the host. If the base URL includes a path (e.g. `https://host/app`), every goto path MUST start with that path: Playwright only prefixes relative paths, so `page.goto('/')` lands on the ORIGIN ROOT outside the app \u2014 the app home is `page.goto('/app')`.",
79525
79931
  "- Every test must contain at least one `expect(...)` assertion.",
79526
79932
  "- **The LAST statement inside the test body MUST be `await expect(...)` that PROVES THE OUTCOME of the test's primary action.** A test ending in `.click()`, `.fill()`, `.press()`, or a `console.log` is INVALID. For an INTERACTION test, re-asserting that the control you just clicked is visible or `aria-selected=true` proves NOTHING (the toggle could be broken and the test would still pass) and is rejected by the assertion-strength gate. Instead verify the post-action state: a before\u2192after count/value change, new content/value that appeared (`getByText` of the revealed value, `toHaveText`/`toContainText`), a created item present or a removed item gone (`toHaveCount`/`not.toBeVisible`), a field value (`toHaveValue`), or a control now disabled (`toBeDisabled`). EXEMPT: a pure navigation/routing test may end on `toHaveURL` + one landmark `toBeVisible`, and the mandatory post-login wait ends on a landmark `toBeVisible` \u2014 those are correct and required. The parent validator REJECTS tests whose last non-trivial line is anything other than `await expect(...)`.",
79527
79933
  " Prefer: `expect(locator).toBeVisible()`, `expect(page).toHaveURL(/regex/)`, `expect(locator).toHaveText(...)`.",
@@ -81757,7 +82163,7 @@ var require_agentic_discovery_prompts = __commonJS({
81757
82163
  lines.push("## Test-quality bar");
81758
82164
  lines.push("");
81759
82165
  lines.push("- ONE `test.describe(<featureName>)` block per file with ONE `test(...)` inside.");
81760
- lines.push("- Use `await page.goto(<relativePath>)` \u2014 never hardcode the host.");
82166
+ lines.push("- Use `await page.goto(<relativePath>)` \u2014 never hardcode the host. If the base URL includes a path (e.g. `https://host/app`), every goto path MUST start with that path (`page.goto('/')` escapes to the origin root \u2014 the app home is `page.goto('/app')`).");
81761
82167
  lines.push("- Every test MUST include the `@generated` tag.");
81762
82168
  lines.push("- Each test creates its OWN run-scoped data; never depend on pre-seeded data.");
81763
82169
  lines.push("");
@@ -82311,6 +82717,21 @@ var require_agentic_discovery_prompts = __commonJS({
82311
82717
  lines.push(`Use \`browser_navigate\` with the full URL (prepend ${baseUrl}). In the`);
82312
82718
  lines.push("emitted test code keep URLs RELATIVE (`page.goto('/path')`) \u2014 the Playwright");
82313
82719
  lines.push("config sets `baseURL`.");
82720
+ {
82721
+ const gotoBasePath = (() => {
82722
+ try {
82723
+ const p = new URL(baseUrl).pathname.replace(/\/+$/, "");
82724
+ return p && p !== "/" ? p : void 0;
82725
+ } catch {
82726
+ return void 0;
82727
+ }
82728
+ })();
82729
+ if (gotoBasePath) {
82730
+ lines.push(`IMPORTANT: the base URL has a PATH (\`${gotoBasePath}\`) and a leading-slash path escapes`);
82731
+ lines.push(`it to the origin root. Every \`page.goto\` path MUST start with \`${gotoBasePath}\` \u2014 the`);
82732
+ lines.push(`app home is \`page.goto('${gotoBasePath}')\`, NOT \`page.goto('/')\`.`);
82733
+ }
82734
+ }
82314
82735
  lines.push("");
82315
82736
  lines.push("## The scenario");
82316
82737
  lines.push("");
@@ -82994,12 +83415,14 @@ var require_discover_generator = __commonJS({
82994
83415
  exports2.detectScenarioSignals = detectScenarioSignals;
82995
83416
  exports2.scenarioStepsCoverAuth = scenarioStepsCoverAuth;
82996
83417
  exports2.getLoginCommands = getLoginCommands;
83418
+ exports2.hasConfiguredLoginCredentials = hasConfiguredLoginCredentials;
82997
83419
  exports2.computeSkeletonTags = computeSkeletonTags;
82998
83420
  exports2.shouldReplayAuthRecipe = shouldReplayAuthRecipe;
82999
83421
  exports2.buildPageContext = buildPageContext;
83000
83422
  exports2.stripTypescriptSyntax = stripTypescriptSyntax;
83001
83423
  exports2.stripImportDeclarations = stripImportDeclarations;
83002
83424
  exports2.stripStringsAndComments = stripStringsAndComments;
83425
+ exports2.gotoBasePathFromBaseUrl = gotoBasePathFromBaseUrl;
83003
83426
  exports2.sanitizeCode = sanitizeCode;
83004
83427
  exports2.validateTests = validateTests;
83005
83428
  exports2.formatEvidenceForLiveGenerate = formatEvidenceForLiveGenerate;
@@ -83586,11 +84009,15 @@ var require_discover_generator = __commonJS({
83586
84009
  const replayPlan = (0, auth_recipe_js_1.buildReplayPlan)(login);
83587
84010
  return { ...login, replayPlan };
83588
84011
  }
83589
- function computeSkeletonTags(targetPages, pages) {
84012
+ function hasConfiguredLoginCredentials(credentials) {
84013
+ return ["TEST_USER_EMAIL", "TEST_USER_PASSWORD"].some((k) => typeof credentials?.[k] === "string" && credentials[k].trim().length > 0);
84014
+ }
84015
+ function computeSkeletonTags(targetPages, pages, opts) {
83590
84016
  const isAuthPage = (p) => p.pageType === "AUTH" || /\/(auth|login|sign-?in)\b/i.test(p.route);
84017
+ const siteHasAuthEvidence = pages.some(isAuthPage) || opts?.extraAuthEvidence === true;
83591
84018
  const targetPagesInSiteMap = pages.filter((p) => targetPages.includes(p.route));
83592
84019
  const hasAuthPage = targetPagesInSiteMap.some(isAuthPage);
83593
- const hasAuthRequiredPage = targetPagesInSiteMap.some((p) => p.requiresAuth && !isAuthPage(p));
84020
+ const hasAuthRequiredPage = siteHasAuthEvidence && targetPagesInSiteMap.some((p) => p.requiresAuth && !isAuthPage(p));
83594
84021
  if (hasAuthPage && !hasAuthRequiredPage)
83595
84022
  return ["@auth", "@discovered"];
83596
84023
  if (hasAuthRequiredPage)
@@ -83598,7 +84025,7 @@ var require_discover_generator = __commonJS({
83598
84025
  return ["@discovered", "@regression"];
83599
84026
  }
83600
84027
  function shouldReplayAuthRecipe(targetPages, siteMapPages, recipe) {
83601
- return !!recipe && computeSkeletonTags(targetPages, siteMapPages).includes("@needs-auth");
84028
+ return !!recipe && computeSkeletonTags(targetPages, siteMapPages, { extraAuthEvidence: true }).includes("@needs-auth");
83602
84029
  }
83603
84030
  function isLowConfidenceSnapshotKind(kind) {
83604
84031
  return kind === "missing" || kind === "error" || kind === "timeout" || kind === "empty" || kind === "ai-capture_fallback";
@@ -84044,6 +84471,16 @@ ${elementContext}${formContext}${observationContext}${interactionContext}${lowCo
84044
84471
  function findSensitiveLiteralLeaks(code, testVariables) {
84045
84472
  return buildSensitiveEnvReplacements(testVariables).filter((replacement) => code.includes(replacement.value)).map((replacement) => replacement.key);
84046
84473
  }
84474
+ function gotoBasePathFromBaseUrl(baseUrl) {
84475
+ if (!baseUrl)
84476
+ return void 0;
84477
+ try {
84478
+ const path = new URL(baseUrl).pathname.replace(/\/+$/, "");
84479
+ return path && path !== "/" ? path : void 0;
84480
+ } catch {
84481
+ return void 0;
84482
+ }
84483
+ }
84047
84484
  function sanitizeCode(code, options) {
84048
84485
  let result = code;
84049
84486
  const isNetworkIdleWaitLine = (line) => /^\s*await page\.waitForLoadState\((['"])networkidle\1\);\s*$/.test(line);
@@ -84066,12 +84503,24 @@ ${elementContext}${formContext}${observationContext}${interactionContext}${lowCo
84066
84503
  result = result.replace(/\s*await page\.waitForTimeout\(\d+\);\s*/g, "\n");
84067
84504
  result = result.replace(/(await page\.goto\((?:[^()()]|\([^()]*\))*\);)\s*\n\s*await expect\(page\)\.toHaveURL\((?:[^()()]|\([^()]*\))*\);/g, (_match, gotoLine) => gotoLine);
84068
84505
  result = result.replace(/const baseUrl\s*=\s*process\.env\.BASE_URL[^;]*;\s*\n/g, "");
84069
- result = result.replace(/page\.goto\(`\$\{baseUrl\}([^`]*)`/g, (_match, path) => `page.goto('${path}'`);
84506
+ result = result.replace(
84507
+ /page\.goto\(`\$\{baseUrl\}([^`]*)`/g,
84508
+ // Empty template path (goto(`${baseUrl}`)) means the app home → '/', which the
84509
+ // path-scoped basePath pass below re-joins to the real base (e.g. '/wing').
84510
+ (_match, path) => `page.goto('${path || "/"}'`
84511
+ );
84070
84512
  result = result.replace(/test\s*\(\s*(['"`].*?['"`])\s*,\s*\{\s*tags\s*:\s*\[/g, (match) => match.replace("tags", "tag"));
84071
84513
  result = result.replace(/page\.goto\(\s*['"]https?:\/\/[^/'"]+(\/[^'"]*)?['"]/g, (_match, path) => `page.goto('${path || "/"}'`);
84072
84514
  result = result.replace(/waitForURL\(\s*['"]https?:\/\/[^/'"]+(\/.+?)['"]\s*\)/g, (_match, path) => `waitForURL(/${path.replace(/\//g, "\\/")}/)`);
84073
84515
  result = result.replace(/toHaveURL\(\s*['"]https?:\/\/[^/'"]+(\/.+?)['"]\s*\)/g, (_match, path) => `toHaveURL(/${path.replace(/\//g, "\\/")}/)`);
84074
84516
  result = result.replace(/(waitForURL|toHaveURL)\(\/(.*?)\\\/\/\)/g, (_match, fn, inner) => `${fn}(/${inner}/)`);
84517
+ const basePath = options?.basePath && options.basePath !== "/" ? options.basePath.replace(/\/+$/, "") : void 0;
84518
+ if (basePath) {
84519
+ result = result.replace(/\b(goto|waitForURL|toHaveURL)\(\s*(['"])(\/(?!\/)[^'"]*)\2/g, (_match, fn, _quote, path) => {
84520
+ const joined = path === basePath || path.startsWith(basePath + "/") ? path : basePath + (path === "/" ? "" : path);
84521
+ return `${fn}('${joined}'`;
84522
+ });
84523
+ }
84075
84524
  result = result.replace(/\)\)\.(first\(\)|nth\(\d+\)|last\(\))\./g, (match, method) => `).${method}).`);
84076
84525
  result = result.replace(/^(\s+)expect\(page\b/gm, "$1await expect(page");
84077
84526
  result = result.replace(/^(\s+)page\.waitForURL\(/gm, "$1await page.waitForURL(");
@@ -84138,7 +84587,8 @@ ${testOpen}`;
84138
84587
  } : void 0;
84139
84588
  const playwrightCode = sanitizeCode(String(test.playwrightCode || ""), {
84140
84589
  sensitiveEnvMap: options?.sensitiveEnvMap,
84141
- authStamp
84590
+ authStamp,
84591
+ basePath: options?.basePath
84142
84592
  });
84143
84593
  const leakedSensitiveKeys = findSensitiveLiteralLeaks(playwrightCode, options?.sensitiveEnvMap);
84144
84594
  const authGuardStamped = !!options?.authRecipe && rawTags.includes("@needs-auth") && !rawTags.includes("@auth") && process.env.SHARED_AUTH_INJECT === "true";
@@ -85540,7 +85990,11 @@ ${outcomesText}
85540
85990
  ${hasVerifiedRecipe ? "The browser is ALREADY authenticated \u2014 `__vqaLogin(page)` ran first; cookies/session are populated. Do NOT log in again; begin from the authenticated app." : "The browser starts on about:blank with NO cookies, NO session, NO localStorage. You are NOT logged in."}
85541
85991
  The base URL is: ${baseUrl}
85542
85992
  When navigating, use \`browser_navigate\` with the full URL (prepend ${baseUrl}).
85543
- In your generated test code, keep URLs RELATIVE (e.g. \`page.goto('/path')\`) \u2014 Playwright config has baseURL set.
85993
+ In your generated test code, keep URLs RELATIVE (e.g. \`page.goto('/path')\`) \u2014 Playwright config has baseURL set.${(() => {
85994
+ const basePath = gotoBasePathFromBaseUrl(baseUrl);
85995
+ return basePath ? `
85996
+ IMPORTANT: the base URL has a PATH (\`${basePath}\`), and Playwright's baseURL only prefixes relative paths \u2014 a leading-slash path escapes to the origin root. Every \`page.goto\` path MUST start with \`${basePath}\` (the app home is \`page.goto('${basePath}')\`, NOT \`page.goto('/')\`).` : "";
85997
+ })()}
85544
85998
 
85545
85999
  ${evidenceBlock}${credentialNote}
85546
86000
  ## ASSERTION VERIFICATION WORKFLOW
@@ -85748,7 +86202,9 @@ ${tailBlock}`;
85748
86202
  }
85749
86203
  }
85750
86204
  });
85751
- const tags = computeSkeletonTags(scenario.targetPages, siteMap.pages);
86205
+ const tags = computeSkeletonTags(scenario.targetPages, siteMap.pages, {
86206
+ extraAuthEvidence: !!opts?.recipe || hasConfiguredLoginCredentials(credentials)
86207
+ });
85752
86208
  const baseSkeletonHadRegression = tags.includes("@regression");
85753
86209
  const hasVerifiedRecipe = !!opts?.recipe && tags.includes("@needs-auth");
85754
86210
  const systemPrompt = buildScenarioSystemPrompt(scenario, baseUrl, credentials, tags, siteMap.pages, { appBrief: opts?.appBrief, availableVariableKeys: opts?.availableVariableKeys, hasVerifiedRecipe, knowledgeContext: opts?.knowledgeContext });
@@ -85957,7 +86413,7 @@ Do NOT add steps you did not execute. Do NOT skip steps from the recording.${led
85957
86413
  const suspectedBugDeclared = toolArgs.suspectedBug === true;
85958
86414
  const bugDescription = String(toolArgs.bugDescription || "").slice(0, 500).replace(/[\u0000-\u001F\u007F]+/g, " ").replace(/\s+/g, " ").trim();
85959
86415
  const testObj = { name, description, playwrightCode: code, tags };
85960
- const validated = validateTests([testObj], logs2, { sensitiveEnvMap: credentials, authRecipe: opts?.recipe ?? null });
86416
+ const validated = validateTests([testObj], logs2, { sensitiveEnvMap: credentials, authRecipe: opts?.recipe ?? null, basePath: gotoBasePathFromBaseUrl(baseUrl) });
85961
86417
  if (validated.length > 0) {
85962
86418
  if (actionLedger.length > lastRenderedLedgerEntryCount && actionLedger.length > 0) {
85963
86419
  const renderedLedger = renderSessionRecording(actionLedger, credentials);
@@ -86076,7 +86532,12 @@ Do NOT add steps you did not execute. Do NOT skip steps from the recording.${led
86076
86532
  const creditedControlSig = revealVerdict === "revealed" ? assertedSigs.find((s) => revealedControls.has(s)) ?? null : null;
86077
86533
  let strengthVerdict = null;
86078
86534
  try {
86079
- strengthVerdict = (0, shared_1.validateAssertionStrength)(validated[0].playwrightCode, { revealedControls });
86535
+ strengthVerdict = (0, shared_1.validateAssertionStrength)(validated[0].playwrightCode, {
86536
+ revealedControls,
86537
+ // Opt-in additive-contradiction gate (see assertion-strength.ts). On Fargate this
86538
+ // env must be dispatcher-forwarded to take effect here; off → byte-identical.
86539
+ flagContradictedAdditions: process.env.STRENGTH_ADDITIVE_CONTRADICTION_GATE === "true"
86540
+ });
86080
86541
  } catch (err) {
86081
86542
  logs2.push(` Strength gate errored \u2014 skipping (fail-open): ${String(err)}`);
86082
86543
  }
@@ -301288,7 +301749,8 @@ ${currentError.slice(0, 2e3)}`
301288
301749
  }
301289
301750
  lastHealedCode = (0, discover_generator_js_1.sanitizeCode)(stripCodeFences(fixedCode), {
301290
301751
  sensitiveEnvMap: testVariables,
301291
- authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(playwrightCode, lastFailureError, logs2)
301752
+ authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(playwrightCode, lastFailureError, logs2),
301753
+ basePath: (0, discover_generator_js_1.gotoBasePathFromBaseUrl)(baseUrl)
301292
301754
  });
301293
301755
  logs2.push("\u{1F680} Running fixed code natively...");
301294
301756
  let verifyResult;
@@ -302742,7 +303204,8 @@ ${errorContent}`;
302742
303204
  }
302743
303205
  const candidateCode = (0, discover_generator_js_1.sanitizeCode)(stripCodeFences(fixedCode), {
302744
303206
  sensitiveEnvMap: testVariables,
302745
- authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(playwrightCode, options?.lastFailureError, logs2)
303207
+ authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(playwrightCode, options?.lastFailureError, logs2),
303208
+ basePath: (0, discover_generator_js_1.gotoBasePathFromBaseUrl)(baseUrl)
302746
303209
  });
302747
303210
  {
302748
303211
  const syntaxCheckCode = (0, discover_generator_js_1.stripTypescriptSyntax)((0, discover_generator_js_1.stripImportDeclarations)(candidateCode));
@@ -304489,7 +304952,8 @@ var require_grok_per_test_healer = __commonJS({
304489
304952
  }
304490
304953
  const healedCode = (0, discover_generator_js_1.sanitizeCode)(payload.playwrightCode, {
304491
304954
  sensitiveEnvMap: secrets,
304492
- authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(playwrightCode, options.lastFailureError, logs2)
304955
+ authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(playwrightCode, options.lastFailureError, logs2),
304956
+ basePath: (0, discover_generator_js_1.gotoBasePathFromBaseUrl)(baseUrl)
304493
304957
  });
304494
304958
  const leakedKey = findLeakedCredential(healedCode, secrets);
304495
304959
  if (leakedKey) {
@@ -305662,7 +306126,8 @@ var require_grok_batch_healer = __commonJS({
305662
306126
  }
305663
306127
  const healedCode = (0, discover_generator_js_1.sanitizeCode)(verdict.playwrightCode, {
305664
306128
  sensitiveEnvMap: secrets,
305665
- authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(test.playwrightCode, test.lastFailureError, logs2)
306129
+ authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(test.playwrightCode, test.lastFailureError, logs2),
306130
+ basePath: (0, discover_generator_js_1.gotoBasePathFromBaseUrl)(baseUrl)
305666
306131
  });
305667
306132
  const leakedKey = findLeakedCredential(healedCode, secrets);
305668
306133
  if (leakedKey) {
@@ -310674,7 +311139,7 @@ var require_discover_explorer = __commonJS({
310674
311139
  },
310675
311140
  requiresAuth: {
310676
311141
  type: "boolean",
310677
- description: "Whether this page requires authentication"
311142
+ description: "OBSERVED evidence only: true if reaching this page logged-out redirected to a login page, returned 401/403, or showed an explicit sign-in wall. Never infer from content looking account-ish \u2014 a page you read without logging in does NOT require auth."
310678
311143
  },
310679
311144
  observations: {
310680
311145
  type: "array",
@@ -310903,7 +311368,7 @@ You do NOT have a \`record_journey\` tool - your job is mapping, not testing. Do
310903
311368
  2. Action buttons: submit, save, delete, create, edit - include label, triggersSubmit
310904
311369
  3. Navigation links to OTHER pages within the app - include label, triggersNavigation
310905
311370
  4. Skip repeated navigation chrome you already cataloged (sidebar/header links appear on every page)
310906
- - **requiresAuth**: whether the page needs login
311371
+ - **requiresAuth**: OBSERVED evidence only \u2014 you were redirected to a login page, got 401/403, or hit a sign-in wall. If you are reading the page without having logged in, it does NOT require auth (account-ish CONTENT is not evidence)
310907
311372
  - **observations**: empty array (you are mapping, not interacting)
310908
311373
 
310909
311374
  ## Loading and readiness
@@ -314601,9 +315066,13 @@ var require_agentic_survey_prompts = __commonJS({
314601
315066
  ' route: string; // path-only, e.g. "/dashboard/profile"',
314602
315067
  ' type: string; // "AUTH" | "DASHBOARD" | "FORM" | "LIST" | ...',
314603
315068
  " summary: string; // 1-line of key elements/affordances",
314604
- " requiresAuth?: boolean; // true if reaching this page logged-out",
314605
- " // redirected to /login OR content was",
314606
- " // visibly gated behind authentication",
315069
+ " requiresAuth?: boolean; // OBSERVED evidence ONLY: you were redirected",
315070
+ " // to a login page, got 401/403, or hit an",
315071
+ " // explicit sign-in wall. NEVER infer it from",
315072
+ " // content merely LOOKING account-ish \u2014 if you",
315073
+ " // read the page without logging in, it does",
315074
+ " // NOT require auth. A site with no login at",
315075
+ " // all has requiresAuth=false everywhere.",
314607
315076
  " }>;",
314608
315077
  ' flows?: string[]; // ["/login \u2192 /dashboard (submit form)"]',
314609
315078
  " observations?: string[]; // things YOU noticed in the loop",
@@ -336171,7 +336640,7 @@ var require_package4 = __commonJS({
336171
336640
  "package.json"(exports2, module2) {
336172
336641
  module2.exports = {
336173
336642
  name: "@validate.qa/runner",
336174
- version: "1.0.37",
336643
+ version: "1.0.38",
336175
336644
  description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
336176
336645
  bin: {
336177
336646
  "validate-runner": "dist/cli.js"