@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.mjs CHANGED
@@ -246,6 +246,9 @@ var require_ai_provider = __commonJS({
246
246
  exports.getAvailableModels = getAvailableModels;
247
247
  exports.getAvailableOrchestratorModels = getAvailableOrchestratorModels;
248
248
  exports.isAvailableModelId = isAvailableModelId;
249
+ exports.resolveWhisperProvider = resolveWhisperProvider;
250
+ exports.isWhisperConfigured = isWhisperConfigured;
251
+ exports.getWhisperModel = getWhisperModel;
249
252
  exports.getOpenAIWhisperClient = getOpenAIWhisperClient;
250
253
  exports.resetAIClient = resetAIClient;
251
254
  exports.getServerProxiedAIClient = getServerProxiedAIClient;
@@ -575,20 +578,70 @@ var require_ai_provider = __commonJS({
575
578
  function isAvailableModelId(modelId) {
576
579
  return getAvailableModels().some((model) => model.id === modelId);
577
580
  }
581
+ var GROQ_API_BASE_DEFAULT = "https://api.groq.com/openai/v1";
582
+ var GROQ_WHISPER_DEFAULT = "whisper-large-v3-turbo";
583
+ var OPENAI_WHISPER_DEFAULT = "whisper-1";
584
+ function isUsableApiKey(value) {
585
+ if (!value)
586
+ return false;
587
+ const trimmed = value.trim();
588
+ if (!trimmed)
589
+ return false;
590
+ if (/your[-_]?.*(key|api|secret)/i.test(trimmed))
591
+ return false;
592
+ if (trimmed === "sk-..." || trimmed === "gsk_...")
593
+ return false;
594
+ return true;
595
+ }
596
+ function resolveWhisperProvider() {
597
+ const forced = (process.env.STT_PROVIDER ?? process.env.WHISPER_PROVIDER ?? "").trim().toLowerCase();
598
+ const hasGroq = isUsableApiKey(process.env.GROQ_API_KEY);
599
+ const hasOpenAI = isUsableApiKey(process.env.OPENAI_API_KEY);
600
+ if (forced === "groq")
601
+ return hasGroq ? "groq" : null;
602
+ if (forced === "openai")
603
+ return hasOpenAI ? "openai" : null;
604
+ if (hasGroq)
605
+ return "groq";
606
+ if (hasOpenAI)
607
+ return "openai";
608
+ return null;
609
+ }
610
+ function isWhisperConfigured() {
611
+ return resolveWhisperProvider() !== null;
612
+ }
613
+ function getWhisperModel(provider) {
614
+ const p = provider ?? resolveWhisperProvider();
615
+ if (p === "groq") {
616
+ return process.env.GROQ_WHISPER_MODEL?.trim() || process.env.OPENAI_WHISPER_MODEL?.trim() || GROQ_WHISPER_DEFAULT;
617
+ }
618
+ return process.env.OPENAI_WHISPER_MODEL?.trim() || OPENAI_WHISPER_DEFAULT;
619
+ }
578
620
  var _whisperClient = null;
621
+ var _whisperClientProvider = null;
579
622
  function getOpenAIWhisperClient() {
580
- if (_whisperClient)
623
+ const provider = resolveWhisperProvider();
624
+ if (!provider) {
625
+ throw new Error("Speech transcription is not configured (set GROQ_API_KEY or OPENAI_API_KEY)");
626
+ }
627
+ if (_whisperClient && _whisperClientProvider === provider) {
581
628
  return _whisperClient;
582
- const apiKey = process.env.OPENAI_API_KEY;
583
- if (!apiKey)
584
- throw new Error("OPENAI_API_KEY is required for Whisper transcription");
585
- _whisperClient = new openai_1.default({ apiKey });
629
+ }
630
+ if (provider === "groq") {
631
+ const apiKey = process.env.GROQ_API_KEY;
632
+ const baseURL = process.env.GROQ_API_BASE?.trim() || GROQ_API_BASE_DEFAULT;
633
+ _whisperClient = new openai_1.default({ apiKey, baseURL });
634
+ } else {
635
+ _whisperClient = new openai_1.default({ apiKey: process.env.OPENAI_API_KEY });
636
+ }
637
+ _whisperClientProvider = provider;
586
638
  return _whisperClient;
587
639
  }
588
640
  var _serverProxiedClient = null;
589
641
  function resetAIClient() {
590
642
  _clientCache.clear();
591
643
  _whisperClient = null;
644
+ _whisperClientProvider = null;
592
645
  _serverProxiedClient = null;
593
646
  }
594
647
  function buildServerProxiedClient() {
@@ -1687,6 +1740,7 @@ var init_constants = __esm({
1687
1740
  "DATABASE_URL",
1688
1741
  "JWT_SECRET",
1689
1742
  "OPENAI_API_KEY",
1743
+ "GROQ_API_KEY",
1690
1744
  "GEMINI_API_KEY",
1691
1745
  "XAI_API_KEY",
1692
1746
  "GROK_API_KEY",
@@ -3253,8 +3307,10 @@ var init_audit_pricing = __esm({
3253
3307
  "MiniMax-M2.7-highspeed": { input: 0.6, output: 2.4 },
3254
3308
  "MiniMax-M2": { input: 0.3, output: 1.2 },
3255
3309
  "deepseek-v4-flash": { input: 0.14, output: 0.28 },
3256
- // Whisper bills per-minute, not per-token
3310
+ // Whisper bills per-minute, not per-token (OpenAI + Groq)
3257
3311
  "whisper-1": null,
3312
+ "whisper-large-v3": null,
3313
+ "whisper-large-v3-turbo": null,
3258
3314
  // Historical / deprecated (kept so old audit rows still show a cost).
3259
3315
  // Deprecated Grok variants priced at the legacy authoritative Grok rate
3260
3316
  // ($0.20/$0.80) those rows were generated under.
@@ -3865,6 +3921,43 @@ function assertedText(stmt) {
3865
3921
  }
3866
3922
  return out.join(" ").toLowerCase();
3867
3923
  }
3924
+ function clickedAdditiveAffordance(lines) {
3925
+ const nameRe = /name\s*:\s*(?:['"`]([^'"`]+)['"`]|\/([^/]+)\/)/g;
3926
+ for (const ln of lines) {
3927
+ if (!/\.click\s*\(/.test(ln))
3928
+ continue;
3929
+ nameRe.lastIndex = 0;
3930
+ let m;
3931
+ while (m = nameRe.exec(ln)) {
3932
+ const name = (m[1] ?? m[2]).toLowerCase();
3933
+ if (EXCLUDE_AFFORDANCE.test(name))
3934
+ continue;
3935
+ if (ADDITIVE_AFFORDANCE.test(name))
3936
+ return true;
3937
+ }
3938
+ }
3939
+ return false;
3940
+ }
3941
+ function matcherText(stmt) {
3942
+ const out = [];
3943
+ const re = /\.(?:toContainText|toHaveText)\s*\(\s*(?:['"`]([^'"`]+)['"`]|\/([^/]+)\/)/g;
3944
+ let m;
3945
+ while (m = re.exec(stmt))
3946
+ out.push(m[1] ?? m[2]);
3947
+ return out.join(" ").toLowerCase();
3948
+ }
3949
+ function isEmptyStateProof(stmt) {
3950
+ if (NEGATED_ASSERT.test(stmt))
3951
+ return false;
3952
+ if (/\.toHaveCount\s*\(\s*0\s*\)/.test(stmt))
3953
+ return true;
3954
+ if (/\.toBe\s*\(\s*0\s*\)/.test(stmt) && !isChromeTarget(stmt))
3955
+ return true;
3956
+ if (VISIBILITY_MATCHER.test(stmt) || TEXT_CONTENT_MATCHER.test(stmt)) {
3957
+ return EMPTY_STATE_TEXT.test(`${assertedText(stmt)} ${matcherText(stmt)}`);
3958
+ }
3959
+ return false;
3960
+ }
3868
3961
  function hasAttributeTransition(stmts) {
3869
3962
  const byAttr = /* @__PURE__ */ new Map();
3870
3963
  for (const { stmt } of stmts) {
@@ -3971,6 +4064,21 @@ function validateAssertionStrength(code, opts = {}) {
3971
4064
  const interacted = mutated || navClicks > 0;
3972
4065
  navOnly = !mutated && navClicks > 0;
3973
4066
  const reloaded = RELOAD.test(body);
4067
+ if (opts.flagContradictedAdditions && mutated && clickedAdditiveAffordance(interactionStmts)) {
4068
+ const empties = stmts.filter(({ stmt }) => isEmptyStateProof(stmt));
4069
+ if (empties.length > 0) {
4070
+ const others = stmts.filter(({ stmt }) => !isEmptyStateProof(stmt));
4071
+ const provesSomethingElse = detectOutcomeProof(others, mutated, false, clickedControlNames(interactionStmts), typedValues(interactionStmts));
4072
+ if (!provesSomethingElse) {
4073
+ return {
4074
+ ok: false,
4075
+ severity: "block",
4076
+ 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",
4077
+ signals: base2({ interacted, mutated, terminal })
4078
+ };
4079
+ }
4080
+ }
4081
+ }
3974
4082
  const hasOutcomeProof = detectOutcomeProof(stmts, mutated, reloaded, clickedControlNames(interactionStmts), typedValues(interactionStmts));
3975
4083
  const signals = base2({ interacted, mutated, navOnly, hasOutcomeProof, terminal });
3976
4084
  if (hasOutcomeProof) {
@@ -4025,7 +4133,7 @@ function validateAssertionStrength(code, opts = {}) {
4025
4133
  signals
4026
4134
  };
4027
4135
  }
4028
- 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;
4136
+ 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;
4029
4137
  var init_assertion_strength = __esm({
4030
4138
  "../shared/dist/assertion-strength.js"() {
4031
4139
  "use strict";
@@ -4069,6 +4177,9 @@ var init_assertion_strength = __esm({
4069
4177
  MESSAGE_SURFACE = /getByText\s*\(|\.filter\s*\(\s*\{\s*hasText\s*:|getByRole\s*\(\s*['"`](?:alert|alertdialog|status|heading|dialog)['"`]/i;
4070
4178
  TEXT_CONTENT_MATCHER = /\.(toHaveText|toContainText)\s*\(/;
4071
4179
  VISIBILITY_MATCHER = /\.toBeVisible\s*\(/;
4180
+ 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;
4181
+ EXCLUDE_AFFORDANCE = /\b(filter|filters|search|searches|sort|view|views|tab|tabs|window|windows|toggle|expand|collapse|column|columns|zoom|layout)\b/i;
4182
+ 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;
4072
4183
  AUTH_START = /^\s*\/\/\s*>>>\s*VALIDATE_QA_AUTH/;
4073
4184
  AUTH_END = /^\s*\/\/\s*<<<\s*VALIDATE_QA_AUTH/;
4074
4185
  LOGIN_CALL_LINE = /^\s*await\s+__vqaLogin\s*\(\s*page\s*\)\s*;?\s*$/;
@@ -4091,6 +4202,24 @@ var init_assertion_strength = __esm({
4091
4202
  });
4092
4203
 
4093
4204
  // ../shared/dist/run-classification.js
4205
+ function pickLatestHealthRun(runs) {
4206
+ for (const run2 of runs) {
4207
+ if (run2.mode === "auth-setup")
4208
+ continue;
4209
+ if (run2.status === "PASSED" || run2.status === "FAILED" || run2.status === "ERROR") {
4210
+ return run2;
4211
+ }
4212
+ }
4213
+ return null;
4214
+ }
4215
+ function pickDisplayRun(runs) {
4216
+ if (runs.length === 0)
4217
+ return null;
4218
+ const latest = runs[0];
4219
+ if (latest.status === "PENDING" || latest.status === "RUNNING")
4220
+ return latest;
4221
+ return pickLatestHealthRun(runs) ?? latest;
4222
+ }
4094
4223
  function isEnvironmentalError(run2) {
4095
4224
  if (!run2)
4096
4225
  return false;
@@ -4552,6 +4681,262 @@ var init_origin_domain = __esm({
4552
4681
  }
4553
4682
  });
4554
4683
 
4684
+ // ../shared/dist/voice-config.js
4685
+ function normalizeTtsVoiceId(voiceId) {
4686
+ if (!voiceId)
4687
+ return null;
4688
+ const trimmed = voiceId.trim();
4689
+ if (!trimmed)
4690
+ return null;
4691
+ return LEGACY_TTS_VOICE_ID_MAP[trimmed] ?? trimmed;
4692
+ }
4693
+ function sttModelsForProvider(provider) {
4694
+ if (provider === "openai") {
4695
+ return STT_MODEL_OPTIONS.filter((m) => m.provider === "openai");
4696
+ }
4697
+ if (provider === "groq") {
4698
+ return STT_MODEL_OPTIONS.filter((m) => m.provider === "groq");
4699
+ }
4700
+ return STT_MODEL_OPTIONS;
4701
+ }
4702
+ function isValidSttProvider(value) {
4703
+ return value === "groq" || value === "openai";
4704
+ }
4705
+ function isSttModelForProvider(modelId, provider) {
4706
+ if (!modelId)
4707
+ return false;
4708
+ return STT_MODEL_OPTIONS.some((m) => m.id === modelId && m.provider === provider);
4709
+ }
4710
+ function resolveSttModelForProvider(provider, configuredModel) {
4711
+ if (configuredModel && isSttModelForProvider(configuredModel, provider)) {
4712
+ return configuredModel;
4713
+ }
4714
+ return DEFAULT_STT_MODEL_BY_PROVIDER[provider];
4715
+ }
4716
+ function sanitizeSttErrorMessage(raw) {
4717
+ if (!raw)
4718
+ return "Speech transcription failed.";
4719
+ 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();
4720
+ if (/401|incorrect api key|invalid.?api.?key|authentication|unauthorized/i.test(msg)) {
4721
+ 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.";
4722
+ }
4723
+ if (/429|rate.?limit|too many requests/i.test(msg)) {
4724
+ return "Speech transcription failed: the speech provider rate-limited the request. Try again in a moment.";
4725
+ }
4726
+ if (/not configured|GROQ_API_KEY or OPENAI_API_KEY/i.test(msg)) {
4727
+ return "Speech transcription is not configured. Set GROQ_API_KEY (preferred) or OPENAI_API_KEY on the org server.";
4728
+ }
4729
+ if (msg.length > 280)
4730
+ msg = `${msg.slice(0, 277)}\u2026`;
4731
+ return msg || "Speech transcription failed.";
4732
+ }
4733
+ function isValidTtsVoiceId(value) {
4734
+ const normalized = normalizeTtsVoiceId(value);
4735
+ if (!normalized)
4736
+ return false;
4737
+ return TTS_VOICE_OPTIONS.some((v) => v.id === normalized);
4738
+ }
4739
+ function isValidTtsModel(value) {
4740
+ if (!value)
4741
+ return false;
4742
+ return TTS_MODEL_OPTIONS.some((m) => m.id === value);
4743
+ }
4744
+ 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;
4745
+ var init_voice_config = __esm({
4746
+ "../shared/dist/voice-config.js"() {
4747
+ "use strict";
4748
+ 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.";
4749
+ DEFAULT_TTS_MODEL = "speech-2.6-hd";
4750
+ DEFAULT_TTS_VOICE_ID = "English_Graceful_Lady";
4751
+ DEFAULT_TTS_SPEED = 1;
4752
+ LEGACY_TTS_VOICE_ID_MAP = {
4753
+ English_LivelyGirl: "English_PlayfulGirl",
4754
+ English_Insightful_Man: "English_Insightful_Speaker",
4755
+ English_Deep_Voice_Man: "English_ManWithDeepVoice",
4756
+ English_CasualMan: "English_Jovialman"
4757
+ };
4758
+ DEFAULT_STT_MODEL_BY_PROVIDER = {
4759
+ groq: "whisper-large-v3-turbo",
4760
+ openai: "whisper-1"
4761
+ };
4762
+ STT_PROVIDER_OPTIONS = [
4763
+ {
4764
+ id: "auto",
4765
+ label: "Auto (prefer Groq)",
4766
+ description: "Use Groq when GROQ_API_KEY is set, otherwise OpenAI Whisper."
4767
+ },
4768
+ {
4769
+ id: "groq",
4770
+ label: "Groq Whisper",
4771
+ description: "Free-tier OpenAI-compatible Whisper via Groq. Requires GROQ_API_KEY on org servers."
4772
+ },
4773
+ {
4774
+ id: "openai",
4775
+ label: "OpenAI Whisper",
4776
+ description: "OpenAI whisper-1 API. Requires OPENAI_API_KEY on org servers."
4777
+ }
4778
+ ];
4779
+ STT_MODEL_OPTIONS = [
4780
+ {
4781
+ id: "whisper-large-v3-turbo",
4782
+ label: "Whisper Large V3 Turbo",
4783
+ provider: "groq",
4784
+ description: "Fast, accurate default for Groq (recommended)."
4785
+ },
4786
+ {
4787
+ id: "whisper-large-v3",
4788
+ label: "Whisper Large V3",
4789
+ provider: "groq",
4790
+ description: "Full-size Groq Whisper model \u2014 higher quality, slower."
4791
+ },
4792
+ {
4793
+ id: "distil-whisper-large-v3-en",
4794
+ label: "Distil Whisper Large V3 (EN)",
4795
+ provider: "groq",
4796
+ description: "English-optimized distilled model \u2014 fastest Groq option."
4797
+ },
4798
+ {
4799
+ id: "whisper-1",
4800
+ label: "Whisper 1",
4801
+ provider: "openai",
4802
+ description: "OpenAI hosted Whisper model."
4803
+ }
4804
+ ];
4805
+ TTS_MODEL_OPTIONS = [
4806
+ {
4807
+ id: "speech-2.8-hd",
4808
+ label: "Speech 2.8 HD",
4809
+ provider: "minimax",
4810
+ description: "Latest high-definition model with interjection tags (recommended)."
4811
+ },
4812
+ {
4813
+ id: "speech-2.8-turbo",
4814
+ label: "Speech 2.8 Turbo",
4815
+ provider: "minimax",
4816
+ description: "Latest low-latency model with interjection tags."
4817
+ },
4818
+ {
4819
+ id: "speech-2.6-hd",
4820
+ label: "Speech 2.6 HD",
4821
+ provider: "minimax",
4822
+ description: "High-definition MiniMax voice model."
4823
+ },
4824
+ {
4825
+ id: "speech-2.6-turbo",
4826
+ label: "Speech 2.6 Turbo",
4827
+ provider: "minimax",
4828
+ description: "Lower-latency Speech 2.6 variant."
4829
+ },
4830
+ {
4831
+ id: "speech-02-hd",
4832
+ label: "Speech 02 HD",
4833
+ provider: "minimax",
4834
+ description: "Previous-generation HD model."
4835
+ },
4836
+ {
4837
+ id: "speech-02-turbo",
4838
+ label: "Speech 02 Turbo",
4839
+ provider: "minimax",
4840
+ description: "Lower-latency Speech 02 variant."
4841
+ },
4842
+ {
4843
+ id: "speech-01-hd",
4844
+ label: "Speech 01 HD",
4845
+ provider: "minimax",
4846
+ description: "Legacy HD model."
4847
+ },
4848
+ {
4849
+ id: "speech-01-turbo",
4850
+ label: "Speech 01 Turbo",
4851
+ provider: "minimax",
4852
+ description: "Legacy fast model."
4853
+ }
4854
+ ];
4855
+ TTS_VOICE_OPTIONS = [
4856
+ {
4857
+ id: "English_Graceful_Lady",
4858
+ label: "Graceful Lady",
4859
+ description: "Warm, polished professional tone.",
4860
+ gender: "female",
4861
+ accent: "English",
4862
+ sampleText: VOICE_PREVIEW_SAMPLE
4863
+ },
4864
+ {
4865
+ id: "English_ConfidentWoman",
4866
+ label: "Confident Woman",
4867
+ description: "Assured and direct \u2014 good for technical guidance.",
4868
+ gender: "female",
4869
+ accent: "English",
4870
+ sampleText: VOICE_PREVIEW_SAMPLE
4871
+ },
4872
+ {
4873
+ id: "English_SereneWoman",
4874
+ label: "Serene Woman",
4875
+ description: "Calm and measured delivery.",
4876
+ gender: "female",
4877
+ accent: "English",
4878
+ sampleText: VOICE_PREVIEW_SAMPLE
4879
+ },
4880
+ {
4881
+ id: "English_PlayfulGirl",
4882
+ label: "Playful Girl",
4883
+ description: "Upbeat and energetic.",
4884
+ gender: "female",
4885
+ accent: "English",
4886
+ sampleText: VOICE_PREVIEW_SAMPLE
4887
+ },
4888
+ {
4889
+ id: "English_CalmWoman",
4890
+ label: "Calm Woman",
4891
+ description: "Soft, reassuring cadence.",
4892
+ gender: "female",
4893
+ accent: "English",
4894
+ sampleText: VOICE_PREVIEW_SAMPLE
4895
+ },
4896
+ {
4897
+ id: "English_Insightful_Speaker",
4898
+ label: "Insightful Speaker",
4899
+ description: "Clear analyst-style narration.",
4900
+ gender: "male",
4901
+ accent: "English",
4902
+ sampleText: VOICE_PREVIEW_SAMPLE
4903
+ },
4904
+ {
4905
+ id: "English_ManWithDeepVoice",
4906
+ label: "Deep Voice Man",
4907
+ description: "Rich, authoritative baritone.",
4908
+ gender: "male",
4909
+ accent: "English",
4910
+ sampleText: VOICE_PREVIEW_SAMPLE
4911
+ },
4912
+ {
4913
+ id: "English_Jovialman",
4914
+ label: "Jovial Man",
4915
+ description: "Relaxed conversational style.",
4916
+ gender: "male",
4917
+ accent: "English",
4918
+ sampleText: VOICE_PREVIEW_SAMPLE
4919
+ },
4920
+ {
4921
+ id: "English_PatientMan",
4922
+ label: "Patient Man",
4923
+ description: "Steady, tutorial-friendly pacing.",
4924
+ gender: "male",
4925
+ accent: "English",
4926
+ sampleText: VOICE_PREVIEW_SAMPLE
4927
+ },
4928
+ {
4929
+ id: "English_FriendlyPerson",
4930
+ label: "Friendly Person",
4931
+ description: "Neutral, approachable assistant voice.",
4932
+ gender: "neutral",
4933
+ accent: "English",
4934
+ sampleText: VOICE_PREVIEW_SAMPLE
4935
+ }
4936
+ ];
4937
+ }
4938
+ });
4939
+
4555
4940
  // ../shared/dist/index.js
4556
4941
  var dist_exports = {};
4557
4942
  __export(dist_exports, {
@@ -4566,8 +4951,13 @@ __export(dist_exports, {
4566
4951
  CREDIT_PRICES_CENTS: () => CREDIT_PRICES_CENTS,
4567
4952
  DANGEROUS_DEEPLINK_SCHEMES: () => DANGEROUS_DEEPLINK_SCHEMES,
4568
4953
  DB_PRICING_PLAN_MAP: () => DB_PRICING_PLAN_MAP,
4954
+ DEFAULT_STT_MODEL_BY_PROVIDER: () => DEFAULT_STT_MODEL_BY_PROVIDER,
4955
+ DEFAULT_TTS_MODEL: () => DEFAULT_TTS_MODEL,
4956
+ DEFAULT_TTS_SPEED: () => DEFAULT_TTS_SPEED,
4957
+ DEFAULT_TTS_VOICE_ID: () => DEFAULT_TTS_VOICE_ID,
4569
4958
  DEFAULT_UI_VARIANT: () => DEFAULT_UI_VARIANT,
4570
4959
  ENVIRONMENTAL_ERROR_PREFIXES: () => ENVIRONMENTAL_ERROR_PREFIXES,
4960
+ LEGACY_TTS_VOICE_ID_MAP: () => LEGACY_TTS_VOICE_ID_MAP,
4571
4961
  MAX_CREDIT_TOPUP_CENTS: () => MAX_CREDIT_TOPUP_CENTS,
4572
4962
  MAX_DEEPLINK_LENGTH: () => MAX_DEEPLINK_LENGTH,
4573
4963
  MIN_CREDIT_TOPUP_CENTS: () => MIN_CREDIT_TOPUP_CENTS,
@@ -4589,10 +4979,15 @@ __export(dist_exports, {
4589
4979
  PLATFORM_ADMIN_EMAIL: () => PLATFORM_ADMIN_EMAIL,
4590
4980
  SELECTOR_PRIORITIES: () => SELECTOR_PRIORITIES,
4591
4981
  SHARED_PRICING_PLAN_MAP: () => SHARED_PRICING_PLAN_MAP,
4982
+ STT_MODEL_OPTIONS: () => STT_MODEL_OPTIONS,
4983
+ STT_PROVIDER_OPTIONS: () => STT_PROVIDER_OPTIONS,
4592
4984
  SUPPORT_EMAIL: () => SUPPORT_EMAIL,
4593
4985
  TRANSIENT_CONTROL_ROLES: () => TRANSIENT_CONTROL_ROLES,
4594
4986
  TRIAL_DURATION_DAYS: () => TRIAL_DURATION_DAYS,
4987
+ TTS_MODEL_OPTIONS: () => TTS_MODEL_OPTIONS,
4988
+ TTS_VOICE_OPTIONS: () => TTS_VOICE_OPTIONS,
4595
4989
  UI_VARIANTS: () => UI_VARIANTS,
4990
+ VOICE_PREVIEW_SAMPLE: () => VOICE_PREVIEW_SAMPLE,
4596
4991
  accumulateStatements: () => accumulateStatements,
4597
4992
  aiCostToCreditCents: () => aiCostToCreditCents,
4598
4993
  analyzeMobileSelectorQuality: () => analyzeMobileSelectorQuality,
@@ -4655,16 +5050,23 @@ __export(dist_exports, {
4655
5050
  isRunnableStep: () => isRunnableStep,
4656
5051
  isSafeDeepLink: () => isSafeDeepLink,
4657
5052
  isSameRegistrableDomainHost: () => isSameRegistrableDomainHost,
5053
+ isSttModelForProvider: () => isSttModelForProvider,
4658
5054
  isTestReadyForPlatform: () => isTestReadyForPlatform,
4659
5055
  isTrialExpired: () => isTrialExpired,
4660
5056
  isUIVariant: () => isUIVariant,
4661
5057
  isValidMobileAppId: () => isValidMobileAppId,
5058
+ isValidSttProvider: () => isValidSttProvider,
5059
+ isValidTtsModel: () => isValidTtsModel,
5060
+ isValidTtsVoiceId: () => isValidTtsVoiceId,
4662
5061
  mediumForMobilePlatform: () => mediumForMobilePlatform,
4663
5062
  mobileCredentialPlaceholder: () => mobileCredentialPlaceholder,
4664
5063
  mobilePlatformFromMedium: () => mobilePlatformFromMedium,
4665
5064
  normalizeControlName: () => normalizeControlName,
5065
+ normalizeTtsVoiceId: () => normalizeTtsVoiceId,
4666
5066
  otherMobilePlatform: () => otherMobilePlatform,
4667
5067
  parenDepthDelta: () => parenDepthDelta,
5068
+ pickDisplayRun: () => pickDisplayRun,
5069
+ pickLatestHealthRun: () => pickLatestHealthRun,
4668
5070
  placeholderizeRecordedMobileAction: () => placeholderizeRecordedMobileAction,
4669
5071
  placeholderizeRecordedTypeValue: () => placeholderizeRecordedTypeValue,
4670
5072
  planHasBillingPortal: () => planHasBillingPortal,
@@ -4674,7 +5076,9 @@ __export(dist_exports, {
4674
5076
  resolveCrossOriginConfigOverride: () => resolveCrossOriginConfigOverride,
4675
5077
  resolveMobileStepValuePlaceholders: () => resolveMobileStepValuePlaceholders,
4676
5078
  resolvePlatformLocatorBundle: () => resolvePlatformLocatorBundle,
5079
+ resolveSttModelForProvider: () => resolveSttModelForProvider,
4677
5080
  safeOrigin: () => safeOrigin,
5081
+ sanitizeSttErrorMessage: () => sanitizeSttErrorMessage,
4678
5082
  sanitizeTestVariables: () => sanitizeTestVariables,
4679
5083
  selectorCandidatesForStep: () => selectorCandidatesForStep,
4680
5084
  selectorForValue: () => selectorForValue,
@@ -4684,6 +5088,7 @@ __export(dist_exports, {
4684
5088
  stepsNeedingReviewForPlatform: () => stepsNeedingReviewForPlatform,
4685
5089
  stripEnvironmentalPrefix: () => stripEnvironmentalPrefix,
4686
5090
  stripLoginScaffolding: () => stripLoginScaffolding,
5091
+ sttModelsForProvider: () => sttModelsForProvider,
4687
5092
  summarizeHealth: () => summarizeHealth,
4688
5093
  summarizeMobileReadiness: () => summarizeMobileReadiness,
4689
5094
  summarizeRunHealth: () => summarizeRunHealth,
@@ -4708,6 +5113,7 @@ var init_dist = __esm({
4708
5113
  init_agent_env();
4709
5114
  init_test_counting();
4710
5115
  init_origin_domain();
5116
+ init_voice_config();
4711
5117
  }
4712
5118
  });
4713
5119
 
@@ -79526,7 +79932,7 @@ var require_agentic_discovery_prompts = __commonJS({
79526
79932
  "## Test-quality bar",
79527
79933
  "",
79528
79934
  "- ONE `test.describe(<featureName>)` block per file with ONE `test(...)` call inside.",
79529
- "- Use `await page.goto(<relativePath>)` \u2014 baseURL is set in the runner config; never hardcode the host.",
79935
+ "- 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')`.",
79530
79936
  "- Every test must contain at least one `expect(...)` assertion.",
79531
79937
  "- **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(...)`.",
79532
79938
  " Prefer: `expect(locator).toBeVisible()`, `expect(page).toHaveURL(/regex/)`, `expect(locator).toHaveText(...)`.",
@@ -81762,7 +82168,7 @@ var require_agentic_discovery_prompts = __commonJS({
81762
82168
  lines.push("## Test-quality bar");
81763
82169
  lines.push("");
81764
82170
  lines.push("- ONE `test.describe(<featureName>)` block per file with ONE `test(...)` inside.");
81765
- lines.push("- Use `await page.goto(<relativePath>)` \u2014 never hardcode the host.");
82171
+ 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')`).");
81766
82172
  lines.push("- Every test MUST include the `@generated` tag.");
81767
82173
  lines.push("- Each test creates its OWN run-scoped data; never depend on pre-seeded data.");
81768
82174
  lines.push("");
@@ -82316,6 +82722,21 @@ var require_agentic_discovery_prompts = __commonJS({
82316
82722
  lines.push(`Use \`browser_navigate\` with the full URL (prepend ${baseUrl}). In the`);
82317
82723
  lines.push("emitted test code keep URLs RELATIVE (`page.goto('/path')`) \u2014 the Playwright");
82318
82724
  lines.push("config sets `baseURL`.");
82725
+ {
82726
+ const gotoBasePath = (() => {
82727
+ try {
82728
+ const p = new URL(baseUrl).pathname.replace(/\/+$/, "");
82729
+ return p && p !== "/" ? p : void 0;
82730
+ } catch {
82731
+ return void 0;
82732
+ }
82733
+ })();
82734
+ if (gotoBasePath) {
82735
+ lines.push(`IMPORTANT: the base URL has a PATH (\`${gotoBasePath}\`) and a leading-slash path escapes`);
82736
+ lines.push(`it to the origin root. Every \`page.goto\` path MUST start with \`${gotoBasePath}\` \u2014 the`);
82737
+ lines.push(`app home is \`page.goto('${gotoBasePath}')\`, NOT \`page.goto('/')\`.`);
82738
+ }
82739
+ }
82319
82740
  lines.push("");
82320
82741
  lines.push("## The scenario");
82321
82742
  lines.push("");
@@ -82999,12 +83420,14 @@ var require_discover_generator = __commonJS({
82999
83420
  exports.detectScenarioSignals = detectScenarioSignals;
83000
83421
  exports.scenarioStepsCoverAuth = scenarioStepsCoverAuth;
83001
83422
  exports.getLoginCommands = getLoginCommands;
83423
+ exports.hasConfiguredLoginCredentials = hasConfiguredLoginCredentials;
83002
83424
  exports.computeSkeletonTags = computeSkeletonTags;
83003
83425
  exports.shouldReplayAuthRecipe = shouldReplayAuthRecipe;
83004
83426
  exports.buildPageContext = buildPageContext;
83005
83427
  exports.stripTypescriptSyntax = stripTypescriptSyntax;
83006
83428
  exports.stripImportDeclarations = stripImportDeclarations;
83007
83429
  exports.stripStringsAndComments = stripStringsAndComments;
83430
+ exports.gotoBasePathFromBaseUrl = gotoBasePathFromBaseUrl;
83008
83431
  exports.sanitizeCode = sanitizeCode;
83009
83432
  exports.validateTests = validateTests;
83010
83433
  exports.formatEvidenceForLiveGenerate = formatEvidenceForLiveGenerate;
@@ -83591,11 +84014,15 @@ var require_discover_generator = __commonJS({
83591
84014
  const replayPlan = (0, auth_recipe_js_1.buildReplayPlan)(login);
83592
84015
  return { ...login, replayPlan };
83593
84016
  }
83594
- function computeSkeletonTags(targetPages, pages) {
84017
+ function hasConfiguredLoginCredentials(credentials) {
84018
+ return ["TEST_USER_EMAIL", "TEST_USER_PASSWORD"].some((k) => typeof credentials?.[k] === "string" && credentials[k].trim().length > 0);
84019
+ }
84020
+ function computeSkeletonTags(targetPages, pages, opts) {
83595
84021
  const isAuthPage = (p) => p.pageType === "AUTH" || /\/(auth|login|sign-?in)\b/i.test(p.route);
84022
+ const siteHasAuthEvidence = pages.some(isAuthPage) || opts?.extraAuthEvidence === true;
83596
84023
  const targetPagesInSiteMap = pages.filter((p) => targetPages.includes(p.route));
83597
84024
  const hasAuthPage = targetPagesInSiteMap.some(isAuthPage);
83598
- const hasAuthRequiredPage = targetPagesInSiteMap.some((p) => p.requiresAuth && !isAuthPage(p));
84025
+ const hasAuthRequiredPage = siteHasAuthEvidence && targetPagesInSiteMap.some((p) => p.requiresAuth && !isAuthPage(p));
83599
84026
  if (hasAuthPage && !hasAuthRequiredPage)
83600
84027
  return ["@auth", "@discovered"];
83601
84028
  if (hasAuthRequiredPage)
@@ -83603,7 +84030,7 @@ var require_discover_generator = __commonJS({
83603
84030
  return ["@discovered", "@regression"];
83604
84031
  }
83605
84032
  function shouldReplayAuthRecipe(targetPages, siteMapPages, recipe) {
83606
- return !!recipe && computeSkeletonTags(targetPages, siteMapPages).includes("@needs-auth");
84033
+ return !!recipe && computeSkeletonTags(targetPages, siteMapPages, { extraAuthEvidence: true }).includes("@needs-auth");
83607
84034
  }
83608
84035
  function isLowConfidenceSnapshotKind(kind) {
83609
84036
  return kind === "missing" || kind === "error" || kind === "timeout" || kind === "empty" || kind === "ai-capture_fallback";
@@ -84049,6 +84476,16 @@ ${elementContext}${formContext}${observationContext}${interactionContext}${lowCo
84049
84476
  function findSensitiveLiteralLeaks(code, testVariables) {
84050
84477
  return buildSensitiveEnvReplacements(testVariables).filter((replacement) => code.includes(replacement.value)).map((replacement) => replacement.key);
84051
84478
  }
84479
+ function gotoBasePathFromBaseUrl(baseUrl) {
84480
+ if (!baseUrl)
84481
+ return void 0;
84482
+ try {
84483
+ const path = new URL(baseUrl).pathname.replace(/\/+$/, "");
84484
+ return path && path !== "/" ? path : void 0;
84485
+ } catch {
84486
+ return void 0;
84487
+ }
84488
+ }
84052
84489
  function sanitizeCode(code, options) {
84053
84490
  let result = code;
84054
84491
  const isNetworkIdleWaitLine = (line) => /^\s*await page\.waitForLoadState\((['"])networkidle\1\);\s*$/.test(line);
@@ -84071,12 +84508,24 @@ ${elementContext}${formContext}${observationContext}${interactionContext}${lowCo
84071
84508
  result = result.replace(/\s*await page\.waitForTimeout\(\d+\);\s*/g, "\n");
84072
84509
  result = result.replace(/(await page\.goto\((?:[^()()]|\([^()]*\))*\);)\s*\n\s*await expect\(page\)\.toHaveURL\((?:[^()()]|\([^()]*\))*\);/g, (_match, gotoLine) => gotoLine);
84073
84510
  result = result.replace(/const baseUrl\s*=\s*process\.env\.BASE_URL[^;]*;\s*\n/g, "");
84074
- result = result.replace(/page\.goto\(`\$\{baseUrl\}([^`]*)`/g, (_match, path) => `page.goto('${path}'`);
84511
+ result = result.replace(
84512
+ /page\.goto\(`\$\{baseUrl\}([^`]*)`/g,
84513
+ // Empty template path (goto(`${baseUrl}`)) means the app home → '/', which the
84514
+ // path-scoped basePath pass below re-joins to the real base (e.g. '/wing').
84515
+ (_match, path) => `page.goto('${path || "/"}'`
84516
+ );
84075
84517
  result = result.replace(/test\s*\(\s*(['"`].*?['"`])\s*,\s*\{\s*tags\s*:\s*\[/g, (match) => match.replace("tags", "tag"));
84076
84518
  result = result.replace(/page\.goto\(\s*['"]https?:\/\/[^/'"]+(\/[^'"]*)?['"]/g, (_match, path) => `page.goto('${path || "/"}'`);
84077
84519
  result = result.replace(/waitForURL\(\s*['"]https?:\/\/[^/'"]+(\/.+?)['"]\s*\)/g, (_match, path) => `waitForURL(/${path.replace(/\//g, "\\/")}/)`);
84078
84520
  result = result.replace(/toHaveURL\(\s*['"]https?:\/\/[^/'"]+(\/.+?)['"]\s*\)/g, (_match, path) => `toHaveURL(/${path.replace(/\//g, "\\/")}/)`);
84079
84521
  result = result.replace(/(waitForURL|toHaveURL)\(\/(.*?)\\\/\/\)/g, (_match, fn, inner) => `${fn}(/${inner}/)`);
84522
+ const basePath = options?.basePath && options.basePath !== "/" ? options.basePath.replace(/\/+$/, "") : void 0;
84523
+ if (basePath) {
84524
+ result = result.replace(/\b(goto|waitForURL|toHaveURL)\(\s*(['"])(\/(?!\/)[^'"]*)\2/g, (_match, fn, _quote, path) => {
84525
+ const joined = path === basePath || path.startsWith(basePath + "/") ? path : basePath + (path === "/" ? "" : path);
84526
+ return `${fn}('${joined}'`;
84527
+ });
84528
+ }
84080
84529
  result = result.replace(/\)\)\.(first\(\)|nth\(\d+\)|last\(\))\./g, (match, method) => `).${method}).`);
84081
84530
  result = result.replace(/^(\s+)expect\(page\b/gm, "$1await expect(page");
84082
84531
  result = result.replace(/^(\s+)page\.waitForURL\(/gm, "$1await page.waitForURL(");
@@ -84143,7 +84592,8 @@ ${testOpen}`;
84143
84592
  } : void 0;
84144
84593
  const playwrightCode = sanitizeCode(String(test.playwrightCode || ""), {
84145
84594
  sensitiveEnvMap: options?.sensitiveEnvMap,
84146
- authStamp
84595
+ authStamp,
84596
+ basePath: options?.basePath
84147
84597
  });
84148
84598
  const leakedSensitiveKeys = findSensitiveLiteralLeaks(playwrightCode, options?.sensitiveEnvMap);
84149
84599
  const authGuardStamped = !!options?.authRecipe && rawTags.includes("@needs-auth") && !rawTags.includes("@auth") && process.env.SHARED_AUTH_INJECT === "true";
@@ -85545,7 +85995,11 @@ ${outcomesText}
85545
85995
  ${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."}
85546
85996
  The base URL is: ${baseUrl}
85547
85997
  When navigating, use \`browser_navigate\` with the full URL (prepend ${baseUrl}).
85548
- In your generated test code, keep URLs RELATIVE (e.g. \`page.goto('/path')\`) \u2014 Playwright config has baseURL set.
85998
+ In your generated test code, keep URLs RELATIVE (e.g. \`page.goto('/path')\`) \u2014 Playwright config has baseURL set.${(() => {
85999
+ const basePath = gotoBasePathFromBaseUrl(baseUrl);
86000
+ return basePath ? `
86001
+ 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('/')\`).` : "";
86002
+ })()}
85549
86003
 
85550
86004
  ${evidenceBlock}${credentialNote}
85551
86005
  ## ASSERTION VERIFICATION WORKFLOW
@@ -85753,7 +86207,9 @@ ${tailBlock}`;
85753
86207
  }
85754
86208
  }
85755
86209
  });
85756
- const tags = computeSkeletonTags(scenario.targetPages, siteMap.pages);
86210
+ const tags = computeSkeletonTags(scenario.targetPages, siteMap.pages, {
86211
+ extraAuthEvidence: !!opts?.recipe || hasConfiguredLoginCredentials(credentials)
86212
+ });
85757
86213
  const baseSkeletonHadRegression = tags.includes("@regression");
85758
86214
  const hasVerifiedRecipe = !!opts?.recipe && tags.includes("@needs-auth");
85759
86215
  const systemPrompt = buildScenarioSystemPrompt(scenario, baseUrl, credentials, tags, siteMap.pages, { appBrief: opts?.appBrief, availableVariableKeys: opts?.availableVariableKeys, hasVerifiedRecipe, knowledgeContext: opts?.knowledgeContext });
@@ -85962,7 +86418,7 @@ Do NOT add steps you did not execute. Do NOT skip steps from the recording.${led
85962
86418
  const suspectedBugDeclared = toolArgs.suspectedBug === true;
85963
86419
  const bugDescription = String(toolArgs.bugDescription || "").slice(0, 500).replace(/[\u0000-\u001F\u007F]+/g, " ").replace(/\s+/g, " ").trim();
85964
86420
  const testObj = { name, description, playwrightCode: code, tags };
85965
- const validated = validateTests([testObj], logs2, { sensitiveEnvMap: credentials, authRecipe: opts?.recipe ?? null });
86421
+ const validated = validateTests([testObj], logs2, { sensitiveEnvMap: credentials, authRecipe: opts?.recipe ?? null, basePath: gotoBasePathFromBaseUrl(baseUrl) });
85966
86422
  if (validated.length > 0) {
85967
86423
  if (actionLedger.length > lastRenderedLedgerEntryCount && actionLedger.length > 0) {
85968
86424
  const renderedLedger = renderSessionRecording(actionLedger, credentials);
@@ -86081,7 +86537,12 @@ Do NOT add steps you did not execute. Do NOT skip steps from the recording.${led
86081
86537
  const creditedControlSig = revealVerdict === "revealed" ? assertedSigs.find((s) => revealedControls.has(s)) ?? null : null;
86082
86538
  let strengthVerdict = null;
86083
86539
  try {
86084
- strengthVerdict = (0, shared_1.validateAssertionStrength)(validated[0].playwrightCode, { revealedControls });
86540
+ strengthVerdict = (0, shared_1.validateAssertionStrength)(validated[0].playwrightCode, {
86541
+ revealedControls,
86542
+ // Opt-in additive-contradiction gate (see assertion-strength.ts). On Fargate this
86543
+ // env must be dispatcher-forwarded to take effect here; off → byte-identical.
86544
+ flagContradictedAdditions: process.env.STRENGTH_ADDITIVE_CONTRADICTION_GATE === "true"
86545
+ });
86085
86546
  } catch (err) {
86086
86547
  logs2.push(` Strength gate errored \u2014 skipping (fail-open): ${String(err)}`);
86087
86548
  }
@@ -301293,7 +301754,8 @@ ${currentError.slice(0, 2e3)}`
301293
301754
  }
301294
301755
  lastHealedCode = (0, discover_generator_js_1.sanitizeCode)(stripCodeFences(fixedCode), {
301295
301756
  sensitiveEnvMap: testVariables,
301296
- authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(playwrightCode, lastFailureError, logs2)
301757
+ authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(playwrightCode, lastFailureError, logs2),
301758
+ basePath: (0, discover_generator_js_1.gotoBasePathFromBaseUrl)(baseUrl)
301297
301759
  });
301298
301760
  logs2.push("\u{1F680} Running fixed code natively...");
301299
301761
  let verifyResult;
@@ -302747,7 +303209,8 @@ ${errorContent}`;
302747
303209
  }
302748
303210
  const candidateCode = (0, discover_generator_js_1.sanitizeCode)(stripCodeFences(fixedCode), {
302749
303211
  sensitiveEnvMap: testVariables,
302750
- authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(playwrightCode, options?.lastFailureError, logs2)
303212
+ authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(playwrightCode, options?.lastFailureError, logs2),
303213
+ basePath: (0, discover_generator_js_1.gotoBasePathFromBaseUrl)(baseUrl)
302751
303214
  });
302752
303215
  {
302753
303216
  const syntaxCheckCode = (0, discover_generator_js_1.stripTypescriptSyntax)((0, discover_generator_js_1.stripImportDeclarations)(candidateCode));
@@ -304494,7 +304957,8 @@ var require_grok_per_test_healer = __commonJS({
304494
304957
  }
304495
304958
  const healedCode = (0, discover_generator_js_1.sanitizeCode)(payload.playwrightCode, {
304496
304959
  sensitiveEnvMap: secrets,
304497
- authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(playwrightCode, options.lastFailureError, logs2)
304960
+ authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(playwrightCode, options.lastFailureError, logs2),
304961
+ basePath: (0, discover_generator_js_1.gotoBasePathFromBaseUrl)(baseUrl)
304498
304962
  });
304499
304963
  const leakedKey = findLeakedCredential(healedCode, secrets);
304500
304964
  if (leakedKey) {
@@ -305667,7 +306131,8 @@ var require_grok_batch_healer = __commonJS({
305667
306131
  }
305668
306132
  const healedCode = (0, discover_generator_js_1.sanitizeCode)(verdict.playwrightCode, {
305669
306133
  sensitiveEnvMap: secrets,
305670
- authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(test.playwrightCode, test.lastFailureError, logs2)
306134
+ authStamp: (0, auth_recipe_js_1.buildHealAuthStamp)(test.playwrightCode, test.lastFailureError, logs2),
306135
+ basePath: (0, discover_generator_js_1.gotoBasePathFromBaseUrl)(baseUrl)
305671
306136
  });
305672
306137
  const leakedKey = findLeakedCredential(healedCode, secrets);
305673
306138
  if (leakedKey) {
@@ -310679,7 +311144,7 @@ var require_discover_explorer = __commonJS({
310679
311144
  },
310680
311145
  requiresAuth: {
310681
311146
  type: "boolean",
310682
- description: "Whether this page requires authentication"
311147
+ 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."
310683
311148
  },
310684
311149
  observations: {
310685
311150
  type: "array",
@@ -310908,7 +311373,7 @@ You do NOT have a \`record_journey\` tool - your job is mapping, not testing. Do
310908
311373
  2. Action buttons: submit, save, delete, create, edit - include label, triggersSubmit
310909
311374
  3. Navigation links to OTHER pages within the app - include label, triggersNavigation
310910
311375
  4. Skip repeated navigation chrome you already cataloged (sidebar/header links appear on every page)
310911
- - **requiresAuth**: whether the page needs login
311376
+ - **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)
310912
311377
  - **observations**: empty array (you are mapping, not interacting)
310913
311378
 
310914
311379
  ## Loading and readiness
@@ -314606,9 +315071,13 @@ var require_agentic_survey_prompts = __commonJS({
314606
315071
  ' route: string; // path-only, e.g. "/dashboard/profile"',
314607
315072
  ' type: string; // "AUTH" | "DASHBOARD" | "FORM" | "LIST" | ...',
314608
315073
  " summary: string; // 1-line of key elements/affordances",
314609
- " requiresAuth?: boolean; // true if reaching this page logged-out",
314610
- " // redirected to /login OR content was",
314611
- " // visibly gated behind authentication",
315074
+ " requiresAuth?: boolean; // OBSERVED evidence ONLY: you were redirected",
315075
+ " // to a login page, got 401/403, or hit an",
315076
+ " // explicit sign-in wall. NEVER infer it from",
315077
+ " // content merely LOOKING account-ish \u2014 if you",
315078
+ " // read the page without logging in, it does",
315079
+ " // NOT require auth. A site with no login at",
315080
+ " // all has requiresAuth=false everywhere.",
314612
315081
  " }>;",
314613
315082
  ' flows?: string[]; // ["/login \u2192 /dashboard (submit form)"]',
314614
315083
  " observations?: string[]; // things YOU noticed in the loop",
@@ -336176,7 +336645,7 @@ var require_package4 = __commonJS({
336176
336645
  "package.json"(exports, module) {
336177
336646
  module.exports = {
336178
336647
  name: "@validate.qa/runner",
336179
- version: "1.0.37",
336648
+ version: "1.0.38",
336180
336649
  description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
336181
336650
  bin: {
336182
336651
  "validate-runner": "dist/cli.js"