@validate.qa/runner 1.0.5 → 1.0.8

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 +496 -158
  2. package/dist/cli.mjs +496 -158
  3. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -1042,6 +1042,65 @@ var require_mobile_source_parser = __commonJS({
1042
1042
  }
1043
1043
  });
1044
1044
 
1045
+ // ../runner-core/dist/services/mobile/mobile-boundary.js
1046
+ var require_mobile_boundary = __commonJS({
1047
+ "../runner-core/dist/services/mobile/mobile-boundary.js"(exports) {
1048
+ "use strict";
1049
+ Object.defineProperty(exports, "__esModule", { value: true });
1050
+ exports.mobileOwnerFromScreenSignal = mobileOwnerFromScreenSignal;
1051
+ exports.isTransientMobileSystemOwner = isTransientMobileSystemOwner;
1052
+ exports.classifyMobileScreenBoundary = classifyMobileScreenBoundary2;
1053
+ exports.isTargetLikeMobileBoundary = isTargetLikeMobileBoundary2;
1054
+ exports.mobileBoundaryNote = mobileBoundaryNote;
1055
+ var ANDROID_TRANSIENT_SYSTEM_APP_IDS = /* @__PURE__ */ new Set([
1056
+ "com.android.permissioncontroller",
1057
+ "com.google.android.permissioncontroller",
1058
+ "com.android.packageinstaller",
1059
+ "com.google.android.packageinstaller"
1060
+ ]);
1061
+ var IOS_TRANSIENT_SYSTEM_APP_IDS = /* @__PURE__ */ new Set([
1062
+ "com.apple.springboard"
1063
+ ]);
1064
+ function mobileOwnerFromScreenSignal(signal, targetAppId) {
1065
+ const bundleId = signal.bundleId?.trim();
1066
+ if (bundleId)
1067
+ return bundleId;
1068
+ const activity = signal.activity?.trim();
1069
+ if (activity && targetAppId && (activity === targetAppId || activity.startsWith(`${targetAppId}.`))) {
1070
+ return targetAppId;
1071
+ }
1072
+ return null;
1073
+ }
1074
+ function isTransientMobileSystemOwner(owner, platform3) {
1075
+ return platform3 === "ANDROID" ? ANDROID_TRANSIENT_SYSTEM_APP_IDS.has(owner) : IOS_TRANSIENT_SYSTEM_APP_IDS.has(owner);
1076
+ }
1077
+ function classifyMobileScreenBoundary2(signal, targetAppId, platform3) {
1078
+ if (!targetAppId)
1079
+ return { kind: "target" };
1080
+ const owner = mobileOwnerFromScreenSignal(signal, targetAppId);
1081
+ if (!owner)
1082
+ return { kind: "unknown" };
1083
+ if (owner === targetAppId)
1084
+ return { kind: "target" };
1085
+ if (isTransientMobileSystemOwner(owner, platform3)) {
1086
+ return { kind: "transient_external", owner, targetAppId };
1087
+ }
1088
+ return { kind: "external", owner, targetAppId };
1089
+ }
1090
+ function isTargetLikeMobileBoundary2(boundary) {
1091
+ return boundary.kind === "target" || boundary.kind === "unknown";
1092
+ }
1093
+ function mobileBoundaryNote(boundary) {
1094
+ return [
1095
+ "",
1096
+ "## App Boundary",
1097
+ `Foreground app is ${boundary.owner}, outside target app ${boundary.targetAppId}.`,
1098
+ boundary.kind === "transient_external" ? "This looks like a system overlay. Handle or dismiss it if needed, but do not capture it as an app screen." : "The runner excluded this external app screen from the target app evidence and relaunched the target app."
1099
+ ].join("\n");
1100
+ }
1101
+ }
1102
+ });
1103
+
1045
1104
  // ../shared/dist/types.js
1046
1105
  function isUIVariant(value) {
1047
1106
  return typeof value === "string" && UI_VARIANTS.includes(value);
@@ -1210,7 +1269,8 @@ var init_constants = __esm({
1210
1269
  "GROK_API_KEY",
1211
1270
  "MINIMAX_API_KEY",
1212
1271
  "ANTHROPIC_API_KEY",
1213
- "NVIDIA_API_KEY"
1272
+ "NVIDIA_API_KEY",
1273
+ "DEEPSEEK_API_KEY"
1214
1274
  ]);
1215
1275
  SPAWNED_ENV_ALLOWLIST = /* @__PURE__ */ new Set([
1216
1276
  // Runtime essentials - Node.js and Playwright need these to function
@@ -1477,6 +1537,18 @@ function buildAppiumCode(testName, target, steps) {
1477
1537
  ` }`,
1478
1538
  ` throw new Error('No locator resolved. Tried: ' + JSON.stringify(selectors) + (__lastError ? ' (last error: ' + String(__lastError) + ')' : ''));`,
1479
1539
  `}`,
1540
+ ``,
1541
+ `async function __tapAt(driver: WebdriverIO.Browser, x: number, y: number) {`,
1542
+ ` await driver.performActions([{ type: 'pointer', id: 'finger1', parameters: { pointerType: 'touch' }, actions: [{ type: 'pointerMove', duration: 0, x, y }, { type: 'pointerDown', button: 0 }, { type: 'pause', duration: 100 }, { type: 'pointerUp', button: 0 }] }]);`,
1543
+ `}`,
1544
+ ``,
1545
+ `async function __typeIntoFocused(driver: WebdriverIO.Browser, text: string) {`,
1546
+ ` try {`,
1547
+ ` await (await driver.getActiveElement() as unknown as WebdriverIO.Element).addValue(text);`,
1548
+ ` } catch {`,
1549
+ ` await driver.keys(text);`,
1550
+ ` }`,
1551
+ `}`,
1480
1552
  ...hasSwipe ? [
1481
1553
  ``,
1482
1554
  `// Read the device window with a few retries (matches the runtime executor).`,
@@ -1535,13 +1607,25 @@ function buildAppiumCode(testName, target, steps) {
1535
1607
  const y = Math.round(bounds.y + bounds.height / 2);
1536
1608
  lines.push(` await driver.performActions([{ type: 'pointer', id: 'finger1', parameters: { pointerType: 'touch' }, actions: [{ type: 'pointerMove', duration: 0, x: ${x}, y: ${y} }, { type: 'pointerDown', button: 0 }, { type: 'pause', duration: 100 }, { type: 'pointerUp', button: 0 }] }]);`);
1537
1609
  } else if (step.action === "type" && hasSelector) {
1538
- lines.push(` await (await findFirst(driver, ${candidatesLiteral})).addValue(${toJsStringLiteral(step.value ?? "")});`);
1610
+ if (bounds) {
1611
+ const x = Math.round(bounds.x + bounds.width / 2);
1612
+ const y = Math.round(bounds.y + bounds.height / 2);
1613
+ lines.push(` try {`);
1614
+ lines.push(` await __tapAt(driver, ${x}, ${y});`);
1615
+ lines.push(` await driver.pause(${MOBILE_FOCUS_SETTLE_MS});`);
1616
+ lines.push(` await __typeIntoFocused(driver, ${toJsStringLiteral(step.value ?? "")});`);
1617
+ lines.push(` } catch {`);
1618
+ lines.push(` await (await findFirst(driver, ${candidatesLiteral})).addValue(${toJsStringLiteral(step.value ?? "")});`);
1619
+ lines.push(` }`);
1620
+ } else {
1621
+ lines.push(` await (await findFirst(driver, ${candidatesLiteral})).addValue(${toJsStringLiteral(step.value ?? "")});`);
1622
+ }
1539
1623
  } else if (step.action === "type" && bounds) {
1540
1624
  const x = Math.round(bounds.x + bounds.width / 2);
1541
1625
  const y = Math.round(bounds.y + bounds.height / 2);
1542
- lines.push(` await driver.performActions([{ type: 'pointer', id: 'finger1', parameters: { pointerType: 'touch' }, actions: [{ type: 'pointerMove', duration: 0, x: ${x}, y: ${y} }, { type: 'pointerDown', button: 0 }, { type: 'pause', duration: 100 }, { type: 'pointerUp', button: 0 }] }]);`);
1626
+ lines.push(` await __tapAt(driver, ${x}, ${y});`);
1543
1627
  lines.push(` await driver.pause(${MOBILE_FOCUS_SETTLE_MS});`);
1544
- lines.push(` await (await driver.getActiveElement() as unknown as WebdriverIO.Element).addValue(${toJsStringLiteral(step.value ?? "")});`);
1628
+ lines.push(` await __typeIntoFocused(driver, ${toJsStringLiteral(step.value ?? "")});`);
1545
1629
  } else if (step.action === "clear" && hasSelector) {
1546
1630
  lines.push(` await (await findFirst(driver, ${candidatesLiteral})).clearValue();`);
1547
1631
  } else if (step.action === "assertVisible" && hasSelector) {
@@ -2055,6 +2139,7 @@ var init_audit_pricing = __esm({
2055
2139
  "MiniMax-M2.7": { input: 0.3, output: 1.2 },
2056
2140
  "MiniMax-M2.7-highspeed": { input: 0.6, output: 2.4 },
2057
2141
  "MiniMax-M2": { input: 0.3, output: 1.2 },
2142
+ "deepseek-v4-flash": { input: 0.14, output: 0.28 },
2058
2143
  // Whisper bills per-minute, not per-token
2059
2144
  "whisper-1": null,
2060
2145
  // Historical / deprecated (kept so old audit rows still show a cost).
@@ -4438,21 +4523,13 @@ var require_mobile_explorer = __commonJS({
4438
4523
  var ai_retry_js_1 = require_ai_retry();
4439
4524
  var mobile_source_parser_js_1 = require_mobile_source_parser();
4440
4525
  var mobile_observation_js_1 = require_mobile_observation();
4526
+ var mobile_boundary_js_1 = require_mobile_boundary();
4441
4527
  var MAX_MOBILE_ITERATIONS_SURVEY = 40;
4442
4528
  var MAX_MOBILE_ITERATIONS_DEEP = 60;
4443
4529
  var MAX_SCREENSHOTS = 15;
4444
4530
  var MAX_RECENT_EXCHANGES = 24;
4445
4531
  var OBSERVATION_MAX_ELEMENTS = 50;
4446
4532
  var NO_SNAPSHOT_NUDGE_THRESHOLD = 4;
4447
- var ANDROID_TRANSIENT_SYSTEM_APP_IDS = /* @__PURE__ */ new Set([
4448
- "com.android.permissioncontroller",
4449
- "com.google.android.permissioncontroller",
4450
- "com.android.packageinstaller",
4451
- "com.google.android.packageinstaller"
4452
- ]);
4453
- var IOS_TRANSIENT_SYSTEM_APP_IDS = /* @__PURE__ */ new Set([
4454
- "com.apple.springboard"
4455
- ]);
4456
4533
  var MOBILE_SNAPSHOT_TOOL = {
4457
4534
  type: "function",
4458
4535
  function: {
@@ -4675,40 +4752,6 @@ var require_mobile_explorer = __commonJS({
4675
4752
  return "timeout";
4676
4753
  return "ai_error";
4677
4754
  }
4678
- function ownerFromScreenSignal(signal, targetAppId) {
4679
- const bundleId = signal.bundleId?.trim();
4680
- if (bundleId)
4681
- return bundleId;
4682
- const activity = signal.activity?.trim();
4683
- if (activity && targetAppId && (activity === targetAppId || activity.startsWith(`${targetAppId}.`))) {
4684
- return targetAppId;
4685
- }
4686
- return null;
4687
- }
4688
- function isTransientSystemOwner(owner, platform3) {
4689
- return platform3 === "ANDROID" ? ANDROID_TRANSIENT_SYSTEM_APP_IDS.has(owner) : IOS_TRANSIENT_SYSTEM_APP_IDS.has(owner);
4690
- }
4691
- function classifyMobileScreenBoundary(signal, targetAppId, platform3) {
4692
- if (!targetAppId)
4693
- return { kind: "target" };
4694
- const owner = ownerFromScreenSignal(signal, targetAppId);
4695
- if (!owner)
4696
- return { kind: "unknown" };
4697
- if (owner === targetAppId)
4698
- return { kind: "target" };
4699
- if (isTransientSystemOwner(owner, platform3)) {
4700
- return { kind: "transient_external", owner, targetAppId };
4701
- }
4702
- return { kind: "external", owner, targetAppId };
4703
- }
4704
- function boundaryNote(boundary) {
4705
- return [
4706
- "",
4707
- "## App Boundary",
4708
- `Foreground app is ${boundary.owner}, outside target app ${boundary.targetAppId}.`,
4709
- boundary.kind === "transient_external" ? "This looks like a system overlay. Handle or dismiss it if needed, but do not capture it as an app screen." : "The runner excluded this external app screen from the survey evidence."
4710
- ].join("\n");
4711
- }
4712
4755
  function resolveSnapshot(xml, windowSize, driverSignal, platform3) {
4713
4756
  const parsed = (0, mobile_source_parser_js_1.parseMobileSource)(xml, platform3);
4714
4757
  const signal = {
@@ -4906,10 +4949,10 @@ Use this to prioritize which screens and flows to cover.` : "";
4906
4949
  };
4907
4950
  const currentBoundary = () => {
4908
4951
  const current = getLatest();
4909
- return current ? classifyMobileScreenBoundary(current.signal, targetAppId, platform3) : { kind: "unknown" };
4952
+ return current ? (0, mobile_boundary_js_1.classifyMobileScreenBoundary)(current.signal, targetAppId, platform3) : { kind: "unknown" };
4910
4953
  };
4911
4954
  const absorbResolvedObservation = async (resolved, screenshot, iterationForObservation, source) => {
4912
- const boundary = classifyMobileScreenBoundary(resolved.signal, targetAppId, platform3);
4955
+ const boundary = (0, mobile_boundary_js_1.classifyMobileScreenBoundary)(resolved.signal, targetAppId, platform3);
4913
4956
  if (boundary.kind === "target" || boundary.kind === "unknown") {
4914
4957
  const screenId = ingestSnapshot(resolved, iterationForObservation);
4915
4958
  captureScreenshot(screenshot, screenId);
@@ -4920,7 +4963,7 @@ Use this to prioritize which screens and flows to cover.` : "";
4920
4963
  if (boundary.kind === "transient_external") {
4921
4964
  log2(` System overlay detected from ${source}: ${boundary.owner}; not adding it to target-app survey evidence.`);
4922
4965
  return {
4923
- observation: `${resolved.observation}${boundaryNote(boundary)}`,
4966
+ observation: `${resolved.observation}${(0, mobile_boundary_js_1.mobileBoundaryNote)(boundary)}`,
4924
4967
  screenId: null,
4925
4968
  transientExternal: true,
4926
4969
  externalOwner: boundary.owner
@@ -4939,7 +4982,7 @@ Use this to prioritize which screens and flows to cover.` : "";
4939
4982
  externalOwner: boundary.owner
4940
4983
  };
4941
4984
  }
4942
- const recoveredBoundary = classifyMobileScreenBoundary(recovered.signal, targetAppId, platform3);
4985
+ const recoveredBoundary = (0, mobile_boundary_js_1.classifyMobileScreenBoundary)(recovered.signal, targetAppId, platform3);
4943
4986
  latest = recovered;
4944
4987
  lastSnapshotIteration = iterationForObservation;
4945
4988
  if (recoveredBoundary.kind === "target" || recoveredBoundary.kind === "unknown") {
@@ -4955,7 +4998,7 @@ Use this to prioritize which screens and flows to cover.` : "";
4955
4998
  if (recoveredBoundary.kind === "transient_external") {
4956
4999
  log2(` Relaunch surfaced system overlay ${recoveredBoundary.owner}; not adding it to target-app survey evidence.`);
4957
5000
  return {
4958
- observation: `${recovered.observation}${boundaryNote(recoveredBoundary)}`,
5001
+ observation: `${recovered.observation}${(0, mobile_boundary_js_1.mobileBoundaryNote)(recoveredBoundary)}`,
4959
5002
  screenId: null,
4960
5003
  externalBlocked: true,
4961
5004
  transientExternal: true,
@@ -8599,6 +8642,7 @@ var require_mobile_generator = __commonJS({
8599
8642
  var mobile_source_parser_js_1 = require_mobile_source_parser();
8600
8643
  var mobile_source_parser_js_2 = require_mobile_source_parser();
8601
8644
  var mobile_observation_js_1 = require_mobile_observation();
8645
+ var mobile_boundary_js_1 = require_mobile_boundary();
8602
8646
  var MAX_SCENARIO_ITERATIONS = 30;
8603
8647
  var MAX_RECENT_EXCHANGES = 24;
8604
8648
  var OBSERVATION_MAX_ELEMENTS = 50;
@@ -8784,8 +8828,9 @@ var require_mobile_generator = __commonJS({
8784
8828
  function hasStableId(element) {
8785
8829
  return !!nonEmpty2(element.accessibilityId) || !!nonEmpty2(element.resourceId);
8786
8830
  }
8787
- function buildSystemPrompt(platform3) {
8831
+ function buildSystemPrompt(platform3, targetAppId) {
8788
8832
  const platformLabel = platform3 === "IOS" ? "iOS" : "Android";
8833
+ const targetLine = targetAppId ? ` Target app package/bundle: ${targetAppId}.` : "";
8789
8834
  return `You are an expert mobile QA agent generating ONE automated test by driving a real ${platformLabel} app on a connected device. The app has just been relaunched into a clean state.
8790
8835
 
8791
8836
  ## How the loop works
@@ -8805,7 +8850,8 @@ var require_mobile_generator = __commonJS({
8805
8850
  - Re-snapshot before every assertion so the ref is fresh.
8806
8851
  - Assert the OUTCOME of the flow (the new screen / new content), not the control you just tapped.
8807
8852
  - Do NOT trigger irreversible side effects (real payments, sending messages to others) \u2014 note them and assert what you safely can.
8808
- - Stay inside this app. Finish promptly once the outcome is proven.
8853
+ - Stay inside this app.${targetLine} Do not open or use app stores, browsers, email apps, settings, or any other app. If an action leaves the target app, the runner will relaunch the target app and reject that action.
8854
+ - Finish promptly once the outcome is proven.
8809
8855
 
8810
8856
  Max iterations: ${MAX_SCENARIO_ITERATIONS}. You will be warned near the end.`;
8811
8857
  }
@@ -8878,7 +8924,7 @@ Start by calling mobile_snapshot, then execute the steps. Finish with at least o
8878
8924
  }
8879
8925
  }
8880
8926
  async function runScenarioLoop(args) {
8881
- const { scenario, driver, platform: platform3, maxIterations, agentModel, provider, aiClient, auditGroupId, onAICall, log: log2 } = args;
8927
+ const { scenario, driver, platform: platform3, maxIterations, agentModel, provider, aiClient, auditGroupId, onAICall, onScreenshot, targetAppId, log: log2 } = args;
8882
8928
  const actions = [];
8883
8929
  let assertCount = 0;
8884
8930
  let finish = null;
@@ -8892,14 +8938,98 @@ Start by calling mobile_snapshot, then execute the steps. Finish with at least o
8892
8938
  const fromActivity = signal.activity ? signal.activity.split(".").pop() : void 0;
8893
8939
  return nonEmpty2(fromActivity) ?? nonEmpty2(signal.bundleId) ?? `screen-${signal.screenHash.slice(0, 8)}`;
8894
8940
  };
8895
- const ingest = (xml, driverSignal, windowSize) => {
8896
- if (!xml)
8897
- return null;
8898
- const resolved = resolveSnapshot(xml, windowSize ?? latest?.screen.windowSize ?? null, driverSignal, platform3);
8941
+ const setLatest = (resolved) => {
8899
8942
  latest = resolved;
8900
8943
  lastScreenName = screenNameFor(resolved);
8901
8944
  return resolved;
8902
8945
  };
8946
+ const buildResolved = (xml, driverSignal, windowSize) => {
8947
+ if (!xml)
8948
+ return null;
8949
+ return resolveSnapshot(xml, windowSize ?? latest?.screen.windowSize ?? null, driverSignal, platform3);
8950
+ };
8951
+ const emitScreenshot = (base64) => {
8952
+ if (!base64)
8953
+ return;
8954
+ try {
8955
+ onScreenshot?.(base64);
8956
+ } catch {
8957
+ }
8958
+ };
8959
+ const absorbResolvedObservation = async (resolved, screenshot, source) => {
8960
+ const boundary = (0, mobile_boundary_js_1.classifyMobileScreenBoundary)(resolved.signal, targetAppId, platform3);
8961
+ if ((0, mobile_boundary_js_1.isTargetLikeMobileBoundary)(boundary)) {
8962
+ setLatest(resolved);
8963
+ emitScreenshot(screenshot);
8964
+ return { resolved, observation: resolved.observation };
8965
+ }
8966
+ setLatest(resolved);
8967
+ if (boundary.kind === "transient_external") {
8968
+ log2(` system overlay detected from ${source}: ${boundary.owner}; not recording it as a target-app step.`);
8969
+ return {
8970
+ resolved,
8971
+ observation: `${resolved.observation}${(0, mobile_boundary_js_1.mobileBoundaryNote)(boundary)}`,
8972
+ transientExternal: true,
8973
+ externalOwner: boundary.owner
8974
+ };
8975
+ }
8976
+ log2(` external app detected from ${source}: ${boundary.owner}; rejecting the action and relaunching ${boundary.targetAppId}.`);
8977
+ try {
8978
+ await driver.relaunch();
8979
+ const snap = await driver.snapshot();
8980
+ const recovered = buildResolved(snap.xml, snap.screenSignal, snap.windowSize);
8981
+ if (!recovered) {
8982
+ return {
8983
+ resolved: null,
8984
+ observation: `(left target app ${boundary.targetAppId} for ${boundary.owner}; relaunched target app, but no UI tree was returned)`,
8985
+ externalBlocked: true,
8986
+ externalOwner: boundary.owner
8987
+ };
8988
+ }
8989
+ const recoveredBoundary = (0, mobile_boundary_js_1.classifyMobileScreenBoundary)(recovered.signal, targetAppId, platform3);
8990
+ setLatest(recovered);
8991
+ if ((0, mobile_boundary_js_1.isTargetLikeMobileBoundary)(recoveredBoundary)) {
8992
+ emitScreenshot(snap.screenshot);
8993
+ return {
8994
+ resolved: recovered,
8995
+ observation: recovered.observation,
8996
+ externalBlocked: true,
8997
+ externalOwner: boundary.owner
8998
+ };
8999
+ }
9000
+ if (recoveredBoundary.kind === "transient_external") {
9001
+ log2(` relaunch surfaced system overlay ${recoveredBoundary.owner}; not recording it as a target-app step.`);
9002
+ return {
9003
+ resolved: recovered,
9004
+ observation: `${recovered.observation}${(0, mobile_boundary_js_1.mobileBoundaryNote)(recoveredBoundary)}`,
9005
+ externalBlocked: true,
9006
+ transientExternal: true,
9007
+ externalOwner: boundary.owner
9008
+ };
9009
+ }
9010
+ log2(` relaunch still outside target app (${recoveredBoundary.owner}); no target-app step recorded.`);
9011
+ return {
9012
+ resolved: recovered,
9013
+ observation: `(left target app ${boundary.targetAppId} for ${boundary.owner}; relaunch still showed ${recoveredBoundary.owner}, so no target-app step was recorded)`,
9014
+ externalBlocked: true,
9015
+ externalOwner: boundary.owner
9016
+ };
9017
+ } catch (err) {
9018
+ const message = err instanceof Error ? err.message : String(err);
9019
+ return {
9020
+ resolved: null,
9021
+ observation: `(left target app ${boundary.targetAppId} for ${boundary.owner}; failed to relaunch target app: ${message})`,
9022
+ externalBlocked: true,
9023
+ externalOwner: boundary.owner
9024
+ };
9025
+ }
9026
+ };
9027
+ const absorbObservation = async (xml, driverSignal, windowSize, screenshot, source) => {
9028
+ const resolved = buildResolved(xml, driverSignal, windowSize);
9029
+ if (!resolved)
9030
+ return { resolved: null, observation: null };
9031
+ return absorbResolvedObservation(resolved, screenshot, source);
9032
+ };
8903
9033
  const resolveRef = (ref) => {
8904
9034
  if (typeof ref !== "string" || !latest)
8905
9035
  return null;
@@ -8907,11 +9037,11 @@ Start by calling mobile_snapshot, then execute the steps. Finish with at least o
8907
9037
  };
8908
9038
  try {
8909
9039
  const seed = await driver.snapshot();
8910
- ingest(seed.xml, seed.screenSignal, seed.windowSize);
9040
+ await absorbObservation(seed.xml, seed.screenSignal, seed.windowSize, seed.screenshot, "seed snapshot");
8911
9041
  } catch (err) {
8912
9042
  log2(` seed snapshot failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
8913
9043
  }
8914
- const systemPrompt = buildSystemPrompt(platform3);
9044
+ const systemPrompt = buildSystemPrompt(platform3, targetAppId);
8915
9045
  const kickoff = buildScenarioKickoff(scenario);
8916
9046
  const recentExchanges = [];
8917
9047
  let iteration = 0;
@@ -9022,7 +9152,7 @@ ${statePacket}` },
9022
9152
  driver,
9023
9153
  platform: platform3,
9024
9154
  latest: getLatest,
9025
- ingest,
9155
+ absorbObservation,
9026
9156
  resolveRef,
9027
9157
  screenName: () => lastScreenName || "UnknownScreen",
9028
9158
  recordAction: (action) => {
@@ -9045,60 +9175,85 @@ ${statePacket}` },
9045
9175
  return { actions, assertCount, finish, lastSnapshot: getLatest(), lastScreenName };
9046
9176
  }
9047
9177
  async function dispatchGenerateTool(args) {
9048
- const { toolName, toolArgs, driver, platform: platform3, latest, ingest, resolveRef, screenName, recordAction, log: log2 } = args;
9178
+ const { toolName, toolArgs, driver, platform: platform3, latest, absorbObservation, resolveRef, screenName, recordAction, log: log2 } = args;
9049
9179
  const unknownRef = (ref) => ({
9050
9180
  ok: false,
9051
9181
  error: `Unknown element ref "${String(ref)}". Call mobile_snapshot first; refs are only valid for the latest snapshot.`
9052
9182
  });
9183
+ const actionPayload = (result, absorbed) => {
9184
+ const blocked = Boolean(absorbed.externalBlocked || absorbed.transientExternal);
9185
+ return {
9186
+ ok: result.ok && !blocked,
9187
+ ...result.error ? { error: result.error } : {},
9188
+ ...blocked ? { error: "Action left the target app and was not recorded. Continue from the relaunched target app." } : {},
9189
+ observation: absorbed.observation ?? void 0,
9190
+ ...absorbed.externalBlocked ? { blockedExternalApp: true, externalApp: absorbed.externalOwner } : {},
9191
+ ...absorbed.transientExternal ? { transientExternalApp: true, externalApp: absorbed.externalOwner } : {}
9192
+ };
9193
+ };
9194
+ const shouldRecordAction = (result, absorbed) => result.ok && !absorbed.externalBlocked && !absorbed.transientExternal;
9053
9195
  switch (toolName) {
9054
9196
  case "mobile_snapshot": {
9055
9197
  const snap = await driver.snapshot();
9056
- const resolved = ingest(snap.xml, snap.screenSignal, snap.windowSize);
9057
- if (!resolved)
9198
+ const absorbed = await absorbObservation(snap.xml, snap.screenSignal, snap.windowSize, snap.screenshot, "mobile_snapshot");
9199
+ if (!absorbed.resolved)
9058
9200
  return { ok: false, error: "snapshot returned no UI tree" };
9059
- log2(` snapshot: ${resolved.screen.elements.length} elements`);
9060
- return { observation: resolved.observation };
9201
+ log2(` snapshot: ${absorbed.resolved.screen.elements.length} elements`);
9202
+ return {
9203
+ observation: absorbed.observation,
9204
+ ...absorbed.externalBlocked ? { blockedExternalApp: true, externalApp: absorbed.externalOwner } : {},
9205
+ ...absorbed.transientExternal ? { transientExternalApp: true, externalApp: absorbed.externalOwner } : {}
9206
+ };
9061
9207
  }
9062
9208
  case "mobile_tap": {
9063
9209
  const element = resolveRef(toolArgs.ref);
9064
9210
  if (!element)
9065
9211
  return unknownRef(toolArgs.ref);
9212
+ const beforeScreenName = screenName();
9066
9213
  const result = await driver.tap(element.bounds);
9067
- recordAction({
9068
- type: "TAP",
9069
- selector: (0, mobile_source_parser_js_2.preferredLocator)(element),
9070
- metadata: metadataForElement(element, screenName(), platform3)
9071
- });
9072
- const resolved = ingest(result.source, void 0, latest()?.screen.windowSize ?? null);
9073
- return { ok: result.ok, ...result.error ? { error: result.error } : {}, observation: resolved?.observation };
9214
+ const absorbed = await absorbObservation(result.source, result.screenSignal, latest()?.screen.windowSize ?? null, result.screenshot, "mobile_tap");
9215
+ if (shouldRecordAction(result, absorbed)) {
9216
+ recordAction({
9217
+ type: "TAP",
9218
+ selector: (0, mobile_source_parser_js_2.preferredLocator)(element),
9219
+ metadata: metadataForElement(element, beforeScreenName, platform3)
9220
+ });
9221
+ }
9222
+ return actionPayload(result, absorbed);
9074
9223
  }
9075
9224
  case "mobile_type": {
9076
9225
  const element = resolveRef(toolArgs.ref);
9077
9226
  if (!element)
9078
9227
  return unknownRef(toolArgs.ref);
9079
9228
  const value = typeof toolArgs.value === "string" ? toolArgs.value : String(toolArgs.value ?? "");
9229
+ const beforeScreenName = screenName();
9080
9230
  const result = await driver.type(value, element.bounds);
9081
- recordAction({
9082
- type: "TYPE",
9083
- value,
9084
- selector: (0, mobile_source_parser_js_2.preferredLocator)(element),
9085
- metadata: metadataForElement(element, screenName(), platform3)
9086
- });
9087
- const resolved = ingest(result.source, void 0, latest()?.screen.windowSize ?? null);
9088
- return { ok: result.ok, ...result.error ? { error: result.error } : {}, observation: resolved?.observation };
9231
+ const absorbed = await absorbObservation(result.source, result.screenSignal, latest()?.screen.windowSize ?? null, result.screenshot, "mobile_type");
9232
+ if (shouldRecordAction(result, absorbed)) {
9233
+ recordAction({
9234
+ type: "TYPE",
9235
+ value,
9236
+ selector: (0, mobile_source_parser_js_2.preferredLocator)(element),
9237
+ metadata: metadataForElement(element, beforeScreenName, platform3)
9238
+ });
9239
+ }
9240
+ return actionPayload(result, absorbed);
9089
9241
  }
9090
9242
  case "mobile_clear": {
9091
9243
  const element = resolveRef(toolArgs.ref);
9092
9244
  if (!element)
9093
9245
  return unknownRef(toolArgs.ref);
9246
+ const beforeScreenName = screenName();
9094
9247
  const result = await driver.clear(element.bounds);
9095
- recordAction({
9096
- type: "CLEAR",
9097
- selector: (0, mobile_source_parser_js_2.preferredLocator)(element),
9098
- metadata: metadataForElement(element, screenName(), platform3)
9099
- });
9100
- const resolved = ingest(result.source, void 0, latest()?.screen.windowSize ?? null);
9101
- return { ok: result.ok, ...result.error ? { error: result.error } : {}, observation: resolved?.observation };
9248
+ const absorbed = await absorbObservation(result.source, result.screenSignal, latest()?.screen.windowSize ?? null, result.screenshot, "mobile_clear");
9249
+ if (shouldRecordAction(result, absorbed)) {
9250
+ recordAction({
9251
+ type: "CLEAR",
9252
+ selector: (0, mobile_source_parser_js_2.preferredLocator)(element),
9253
+ metadata: metadataForElement(element, beforeScreenName, platform3)
9254
+ });
9255
+ }
9256
+ return actionPayload(result, absorbed);
9102
9257
  }
9103
9258
  case "mobile_swipe": {
9104
9259
  const direction = toolArgs.direction;
@@ -9106,30 +9261,42 @@ ${statePacket}` },
9106
9261
  return { ok: false, error: "direction must be one of up|down|left|right" };
9107
9262
  }
9108
9263
  const distance = typeof toolArgs.distance === "number" ? toolArgs.distance : void 0;
9264
+ const beforeScreenName = screenName();
9109
9265
  const result = await driver.swipe(direction, distance);
9110
- recordAction({
9111
- type: "SWIPE",
9112
- metadata: {
9113
- screenName: screenName(),
9114
- direction,
9115
- ...distance !== void 0 ? { distance } : {}
9116
- }
9117
- });
9118
- const resolved = ingest(result.source, void 0, latest()?.screen.windowSize ?? null);
9119
- return { ok: result.ok, ...result.error ? { error: result.error } : {}, observation: resolved?.observation };
9266
+ const absorbed = await absorbObservation(result.source, result.screenSignal, latest()?.screen.windowSize ?? null, result.screenshot, "mobile_swipe");
9267
+ if (shouldRecordAction(result, absorbed)) {
9268
+ recordAction({
9269
+ type: "SWIPE",
9270
+ metadata: {
9271
+ screenName: beforeScreenName,
9272
+ direction,
9273
+ ...distance !== void 0 ? { distance } : {}
9274
+ }
9275
+ });
9276
+ }
9277
+ return actionPayload(result, absorbed);
9120
9278
  }
9121
9279
  case "mobile_back": {
9280
+ const beforeScreenName = screenName();
9122
9281
  const result = await driver.back();
9123
- recordAction({ type: "BACK", metadata: { screenName: screenName() } });
9124
- const resolved = ingest(result.source, void 0, latest()?.screen.windowSize ?? null);
9125
- return { ok: result.ok, ...result.error ? { error: result.error } : {}, observation: resolved?.observation };
9282
+ const absorbed = await absorbObservation(result.source, result.screenSignal, latest()?.screen.windowSize ?? null, result.screenshot, "mobile_back");
9283
+ if (shouldRecordAction(result, absorbed)) {
9284
+ recordAction({ type: "BACK", metadata: { screenName: beforeScreenName } });
9285
+ }
9286
+ return actionPayload(result, absorbed);
9126
9287
  }
9127
9288
  case "mobile_relaunch": {
9128
9289
  await driver.relaunch();
9129
9290
  const snap = await driver.snapshot();
9130
- const resolved = ingest(snap.xml, snap.screenSignal, snap.windowSize);
9291
+ const absorbed = await absorbObservation(snap.xml, snap.screenSignal, snap.windowSize, snap.screenshot, "mobile_relaunch");
9131
9292
  log2(" relaunched app for hermetic reset.");
9132
- return { ok: true, observation: resolved?.observation, note: "App relaunched (clean state). Not recorded as a step." };
9293
+ return {
9294
+ ok: true,
9295
+ observation: absorbed.observation,
9296
+ ...absorbed.externalBlocked ? { blockedExternalApp: true, externalApp: absorbed.externalOwner } : {},
9297
+ ...absorbed.transientExternal ? { transientExternalApp: true, externalApp: absorbed.externalOwner } : {},
9298
+ note: "App relaunched (clean state). Not recorded as a step."
9299
+ };
9133
9300
  }
9134
9301
  case "mobile_assert_visible": {
9135
9302
  const recorded = recordAssertVisible(toolArgs, { resolveRef, screenName, platform: platform3, recordAction });
@@ -9267,7 +9434,7 @@ ${statePacket}` },
9267
9434
  return test;
9268
9435
  }
9269
9436
  async function runMobileGeneratePhase(args) {
9270
- const { scenarios, driver, ctx, config, onLog, onAICall, onTestGenerated } = args;
9437
+ const { scenarios, driver, ctx, config, onLog, onAICall, onTestGenerated, onScreenshot } = args;
9271
9438
  const log2 = (line) => {
9272
9439
  try {
9273
9440
  onLog?.(line);
@@ -9281,6 +9448,7 @@ ${statePacket}` },
9281
9448
  const provider = (0, ai_provider_js_1.getProviderForModel)(agentModel);
9282
9449
  const aiClient = modelOverride ? (0, ai_provider_js_1.getMobileClientForModel)(modelOverride) : (0, ai_provider_js_1.getMobileAIClient)();
9283
9450
  const auditGroupId = `mobile-generate-${(0, crypto_1.randomUUID)().slice(0, 8)}`;
9451
+ const targetAppId = ctx.target?.appId?.trim() || void 0;
9284
9452
  log2(`Mobile generator starting \u2014 platform: ${platform3}, provider: ${provider}, model: ${agentModel}, scenarios: ${scenarios.length}`);
9285
9453
  const tests = [];
9286
9454
  for (let i = 0; i < scenarios.length; i++) {
@@ -9299,6 +9467,8 @@ ${statePacket}` },
9299
9467
  aiClient,
9300
9468
  auditGroupId,
9301
9469
  onAICall,
9470
+ onScreenshot,
9471
+ targetAppId,
9302
9472
  log: log2
9303
9473
  });
9304
9474
  const test = assembleTest({ scenario, recording, ctx, platform: platform3, log: log2 });
@@ -311428,7 +311598,9 @@ var require_mobile_discovery_agent = __commonJS({
311428
311598
  return {
311429
311599
  collectedPages: explore.collectedPages,
311430
311600
  collectedTransitions,
311431
- pageScreenshots: explore.pageScreenshots,
311601
+ // Native screenshots are multi-megabyte PNGs. The live mirror streams them
311602
+ // through heartbeat telemetry; discovery checkpoints keep the screen graph
311603
+ // and transitions so large Android surveys cannot exceed server body limits.
311432
311604
  exploreStats: explore.exploreStats,
311433
311605
  healthObservations: explore.healthObservations
311434
311606
  };
@@ -311586,6 +311758,7 @@ Plan: ${planResult.scenarios.length} scenarios, ${planResult.featureGroups.lengt
311586
311758
  ctx,
311587
311759
  onLog: (line) => log2(line),
311588
311760
  onAICall,
311761
+ onScreenshot,
311589
311762
  onTestGenerated: (test) => callbacks?.onTestGenerated?.(test, { phase: "verified" })
311590
311763
  });
311591
311764
  const generateElapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
@@ -325291,7 +325464,7 @@ var require_live_browser_registry = __commonJS({
325291
325464
  var THUMBNAIL_INTERVAL_MS = parseInt(process.env.LIVE_BROWSER_THUMBNAIL_INTERVAL_MS ?? "4000", 10) || 4e3;
325292
325465
  var THUMBNAIL_QUALITY = parseInt(process.env.LIVE_BROWSER_THUMBNAIL_QUALITY ?? "60", 10) || 60;
325293
325466
  var BROWSER_THUMBNAIL_MAX_BASE64_BYTES = parseInt(process.env.LIVE_BROWSER_THUMBNAIL_MAX_BASE64_BYTES ?? "400000", 10) || 4e5;
325294
- var MOBILE_THUMBNAIL_MAX_BASE64_BYTES = parseInt(process.env.LIVE_MOBILE_THUMBNAIL_MAX_BASE64_BYTES ?? "2000000", 10) || 2e6;
325467
+ var MOBILE_THUMBNAIL_MAX_BASE64_BYTES = parseInt(process.env.LIVE_MOBILE_THUMBNAIL_MAX_BASE64_BYTES ?? "4500000", 10) || 45e5;
325295
325468
  var STALE_SESSION_MS = parseInt(process.env.LIVE_BROWSER_STALE_MS ?? `${10 * 60 * 1e3}`, 10) || 10 * 60 * 1e3;
325296
325469
  var MAX_SESSIONS = parseInt(process.env.LIVE_BROWSER_MAX_SESSIONS ?? "32", 10) || 32;
325297
325470
  var LiveBrowserRegistry = class {
@@ -325787,6 +325960,7 @@ var require_dist2 = __commonJS({
325787
325960
  __exportStar(require_ai_provider(), exports);
325788
325961
  __exportStar(require_mobile_types(), exports);
325789
325962
  __exportStar(require_mobile_source_parser(), exports);
325963
+ __exportStar(require_mobile_boundary(), exports);
325790
325964
  var mobile_explorer_js_1 = require_mobile_explorer();
325791
325965
  Object.defineProperty(exports, "runMobileExplorePhase", { enumerable: true, get: function() {
325792
325966
  return mobile_explorer_js_1.runMobileExplorePhase;
@@ -326119,7 +326293,7 @@ var require_package4 = __commonJS({
326119
326293
  "package.json"(exports, module) {
326120
326294
  module.exports = {
326121
326295
  name: "@validate.qa/runner",
326122
- version: "1.0.5",
326296
+ version: "1.0.8",
326123
326297
  description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
326124
326298
  bin: {
326125
326299
  "validate-runner": "dist/cli.js"
@@ -326217,7 +326391,7 @@ var import_runner_core = __toESM(require_dist2());
326217
326391
  var import_runner_core2 = __toESM(require_dist2());
326218
326392
 
326219
326393
  // src/runner.ts
326220
- var import_runner_core9 = __toESM(require_dist2());
326394
+ var import_runner_core10 = __toESM(require_dist2());
326221
326395
 
326222
326396
  // src/services/regression-runner.ts
326223
326397
  var import_runner_core3 = __toESM(require_dist2());
@@ -326232,7 +326406,7 @@ var import_runner_core5 = __toESM(require_dist2());
326232
326406
  var import_runner_core6 = __toESM(require_dist2());
326233
326407
 
326234
326408
  // src/runner.ts
326235
- var import_runner_core10 = __toESM(require_dist2());
326409
+ var import_runner_core11 = __toESM(require_dist2());
326236
326410
 
326237
326411
  // src/services/auth-state.ts
326238
326412
  var INLINE_LOGIN_TAG = "@inline-login";
@@ -326252,6 +326426,7 @@ function shouldUseAuthState(tags, playwrightCode) {
326252
326426
 
326253
326427
  // src/services/appium-executor.ts
326254
326428
  init_dist();
326429
+ var import_runner_core8 = __toESM(require_dist2());
326255
326430
 
326256
326431
  // src/services/appium-lifecycle.ts
326257
326432
  import { spawn, execFileSync } from "child_process";
@@ -327543,6 +327718,86 @@ async function openDeepLink(sessionId, platform3, deeplink, appId) {
327543
327718
  body: JSON.stringify({ script: "mobile: deepLink", args })
327544
327719
  });
327545
327720
  }
327721
+ function readExecuteStringValue(result) {
327722
+ if (result && typeof result === "object" && "value" in result) {
327723
+ const value = result.value;
327724
+ if (typeof value === "string" && value.trim()) return value.trim();
327725
+ }
327726
+ return void 0;
327727
+ }
327728
+ function readActiveBundleId(result) {
327729
+ if (result && typeof result === "object" && "value" in result) {
327730
+ const value = result.value;
327731
+ if (value && typeof value === "object" && "bundleId" in value) {
327732
+ const bundleId = value.bundleId;
327733
+ if (typeof bundleId === "string" && bundleId.trim()) return bundleId.trim();
327734
+ }
327735
+ }
327736
+ return void 0;
327737
+ }
327738
+ function normalizeAndroidActivity(activity, pkg2) {
327739
+ const trimmed = activity?.trim();
327740
+ if (!trimmed) return void 0;
327741
+ const packageName = pkg2?.trim();
327742
+ if (!packageName) return trimmed;
327743
+ if (trimmed.startsWith(".")) return `${packageName}${trimmed}`;
327744
+ if (!trimmed.includes(".")) return `${packageName}.${trimmed}`;
327745
+ return trimmed;
327746
+ }
327747
+ async function executeMobileScript(sessionId, script, args) {
327748
+ return wdRequest(`/session/${sessionId}/execute/sync`, {
327749
+ method: "POST",
327750
+ body: JSON.stringify({ script, args })
327751
+ });
327752
+ }
327753
+ async function readForegroundScreenSignal(sessionId, platform3) {
327754
+ if (platform3 === "ANDROID") {
327755
+ const [packageResult, activityResult] = await Promise.all([
327756
+ executeMobileScript(sessionId, "mobile: getCurrentPackage", []),
327757
+ executeMobileScript(sessionId, "mobile: getCurrentActivity", []).catch(() => void 0)
327758
+ ]);
327759
+ const pkg2 = readExecuteStringValue(packageResult);
327760
+ if (!pkg2) return null;
327761
+ const activity = normalizeAndroidActivity(readExecuteStringValue(activityResult), pkg2);
327762
+ return {
327763
+ platform: platform3,
327764
+ bundleId: pkg2,
327765
+ ...activity ? { activity } : {},
327766
+ screenHash: "foreground"
327767
+ };
327768
+ }
327769
+ const infoResult = await executeMobileScript(sessionId, "mobile: activeAppInfo", []);
327770
+ const bundleId = readActiveBundleId(infoResult);
327771
+ if (!bundleId) return null;
327772
+ return { platform: platform3, bundleId, screenHash: "foreground" };
327773
+ }
327774
+ async function activateTargetApp(sessionId, platform3, appId) {
327775
+ const args = platform3 === "IOS" ? [{ bundleId: appId }] : [{ appId }];
327776
+ await executeMobileScript(sessionId, "mobile: activateApp", args);
327777
+ }
327778
+ async function restoreTargetAppIfExternal(sessionId, target, log2, source, failOnExternal) {
327779
+ let boundary = null;
327780
+ try {
327781
+ const signal = await readForegroundScreenSignal(sessionId, target.platform);
327782
+ boundary = signal ? (0, import_runner_core8.classifyMobileScreenBoundary)(signal, target.appId, target.platform) : null;
327783
+ } catch (err) {
327784
+ log2(` App boundary check skipped after ${source}: ${err instanceof Error ? err.message : String(err)}`);
327785
+ return;
327786
+ }
327787
+ if (!boundary || (0, import_runner_core8.isTargetLikeMobileBoundary)(boundary) || boundary.kind === "transient_external") {
327788
+ return;
327789
+ }
327790
+ const message = `Foreground app changed to ${boundary.owner}, outside target app ${boundary.targetAppId}`;
327791
+ log2(` ${message}; activating target app.`);
327792
+ try {
327793
+ await activateTargetApp(sessionId, target.platform, target.appId);
327794
+ } catch (err) {
327795
+ log2(` Could not reactivate ${target.appId}: ${err instanceof Error ? err.message : String(err)}`);
327796
+ }
327797
+ if (failOnExternal) {
327798
+ throw new Error(`${message}; relaunched target app and failed the step so mobile tests do not continue in another app.`);
327799
+ }
327800
+ }
327546
327801
  async function deleteSession(sessionId) {
327547
327802
  try {
327548
327803
  await wdRequest(`/session/${sessionId}`, { method: "DELETE" });
@@ -327669,6 +327924,41 @@ async function getActiveElementId(sessionId) {
327669
327924
  return null;
327670
327925
  }
327671
327926
  }
327927
+ async function sendKeysToFocusedInput(sessionId, value) {
327928
+ await wdRequest(`/session/${sessionId}/keys`, {
327929
+ method: "POST",
327930
+ body: JSON.stringify({ text: value, value: Array.from(value) })
327931
+ });
327932
+ }
327933
+ async function trySendElementValue(sessionId, elementId, value) {
327934
+ try {
327935
+ await wdRequest(`/session/${sessionId}/element/${elementId}/value`, {
327936
+ method: "POST",
327937
+ body: JSON.stringify({ text: value })
327938
+ });
327939
+ return null;
327940
+ } catch (error2) {
327941
+ if (isTransportError(error2)) throw error2;
327942
+ return error2;
327943
+ }
327944
+ }
327945
+ function shouldAbortAfterStepFailure(action) {
327946
+ switch (action) {
327947
+ case "launchApp":
327948
+ case "tap":
327949
+ case "type":
327950
+ case "clear":
327951
+ case "swipe":
327952
+ case "scroll":
327953
+ case "back":
327954
+ case "home":
327955
+ case "waitForElement":
327956
+ return true;
327957
+ case "assertVisible":
327958
+ case "assertText":
327959
+ return false;
327960
+ }
327961
+ }
327672
327962
  async function executeTap(sessionId, step) {
327673
327963
  const bounds = boundsFromStep(step);
327674
327964
  if (step.target) {
@@ -327690,30 +327980,64 @@ async function executeTap(sessionId, step) {
327690
327980
  }
327691
327981
  async function executeType(sessionId, step) {
327692
327982
  if (step.value === void 0 || step.value === null || step.value === "") return;
327983
+ const value = String(step.value);
327693
327984
  const bounds = boundsFromStep(step);
327694
327985
  let elementId = null;
327986
+ let lastInputError = null;
327987
+ let tappedBounds = false;
327988
+ if (bounds) {
327989
+ await tapCoordinates(sessionId, bounds);
327990
+ tappedBounds = true;
327991
+ await new Promise((resolve) => setTimeout(resolve, MOBILE_FOCUS_SETTLE_MS));
327992
+ elementId = await getActiveElementId(sessionId);
327993
+ if (elementId) {
327994
+ lastInputError = await trySendElementValue(sessionId, elementId, value);
327995
+ if (!lastInputError) return;
327996
+ elementId = null;
327997
+ }
327998
+ }
327695
327999
  if (step.target) {
327696
328000
  try {
327697
328001
  elementId = await findElement(sessionId, step.target, readStepTimeout(step));
328002
+ lastInputError = await trySendElementValue(sessionId, elementId, value);
328003
+ if (!lastInputError) return;
328004
+ elementId = null;
327698
328005
  } catch (error2) {
327699
328006
  if (!bounds) throw error2;
328007
+ lastInputError = error2;
327700
328008
  }
327701
328009
  }
327702
328010
  if (!elementId && bounds) {
327703
328011
  await tapCoordinates(sessionId, bounds);
328012
+ tappedBounds = true;
327704
328013
  await new Promise((resolve) => setTimeout(resolve, MOBILE_FOCUS_SETTLE_MS));
327705
328014
  elementId = await getActiveElementId(sessionId);
327706
328015
  }
327707
328016
  if (!elementId) {
327708
328017
  elementId = await getActiveElementId(sessionId);
327709
328018
  }
327710
- if (!elementId) {
327711
- throw new Error("TYPE step could not resolve a target or focused element");
328019
+ if (elementId) {
328020
+ lastInputError = await trySendElementValue(sessionId, elementId, value);
328021
+ if (!lastInputError) return;
328022
+ elementId = null;
328023
+ if (!bounds) {
328024
+ throw lastInputError instanceof Error ? lastInputError : new Error(String(lastInputError));
328025
+ }
327712
328026
  }
327713
- await wdRequest(`/session/${sessionId}/element/${elementId}/value`, {
327714
- method: "POST",
327715
- body: JSON.stringify({ text: step.value })
327716
- });
328027
+ if (bounds) {
328028
+ if (!tappedBounds) {
328029
+ await tapCoordinates(sessionId, bounds);
328030
+ await new Promise((resolve) => setTimeout(resolve, MOBILE_FOCUS_SETTLE_MS));
328031
+ }
328032
+ try {
328033
+ await sendKeysToFocusedInput(sessionId, value);
328034
+ return;
328035
+ } catch (error2) {
328036
+ if (isTransportError(error2)) throw error2;
328037
+ lastInputError = error2;
328038
+ }
328039
+ }
328040
+ throw new Error(`TYPE step could not send text to the focused target${lastInputError instanceof Error ? `: ${lastInputError.message}` : ""}`);
327717
328041
  }
327718
328042
  async function executeClear(sessionId, step) {
327719
328043
  const bounds = boundsFromStep(step);
@@ -327917,6 +328241,7 @@ async function executeMobileTest(options) {
327917
328241
  log2(` \u2717 Deep link failed: ${err instanceof Error ? err.message : String(err)}`);
327918
328242
  }
327919
328243
  }
328244
+ await restoreTargetAppIfExternal(sessionId, options.target, log2, "session start", false);
327920
328245
  let environmental = null;
327921
328246
  for (const step of options.steps) {
327922
328247
  const stepStart = Date.now();
@@ -327924,6 +328249,7 @@ async function executeMobileTest(options) {
327924
328249
  let status = "passed";
327925
328250
  let stepError;
327926
328251
  try {
328252
+ await restoreTargetAppIfExternal(sessionId, options.target, log2, `step ${step.order} pre-check`, false);
327927
328253
  switch (step.action) {
327928
328254
  case "launchApp":
327929
328255
  log2(" App launched (handled by session creation)");
@@ -327963,6 +328289,7 @@ async function executeMobileTest(options) {
327963
328289
  break;
327964
328290
  }
327965
328291
  }
328292
+ await restoreTargetAppIfExternal(sessionId, options.target, log2, `step ${step.order} (${step.action})`, true);
327966
328293
  } catch (err) {
327967
328294
  status = "failed";
327968
328295
  stepError = err instanceof Error ? err.message : String(err);
@@ -327983,6 +328310,10 @@ async function executeMobileTest(options) {
327983
328310
  log2(`Aborting remaining steps \u2014 Appium session is no longer usable: ${environmental}`);
327984
328311
  break;
327985
328312
  }
328313
+ if (status === "failed" && shouldAbortAfterStepFailure(step.action)) {
328314
+ log2(`Aborting remaining steps \u2014 ${step.action} failed, so downstream mobile steps are no longer reliable.`);
328315
+ break;
328316
+ }
327986
328317
  }
327987
328318
  const allPassed = !environmental && stepResults.every((r) => r.status === "passed" || r.status === "skipped");
327988
328319
  const firstError = environmental ?? (stepResults.find((r) => r.status === "failed")?.error ?? null);
@@ -329248,7 +329579,7 @@ function detachMirrorSession(sessionId) {
329248
329579
  }
329249
329580
 
329250
329581
  // src/services/mobile/mobile-bridge-driver.ts
329251
- var import_runner_core8 = __toESM(require_dist2());
329582
+ var import_runner_core9 = __toESM(require_dist2());
329252
329583
  init_dist();
329253
329584
  var APPIUM_SESSION_ID_PATTERN2 = /^[a-f0-9-]{1,64}$/i;
329254
329585
  function readStringValue(result) {
@@ -329258,7 +329589,7 @@ function readStringValue(result) {
329258
329589
  }
329259
329590
  return void 0;
329260
329591
  }
329261
- function normalizeAndroidActivity(activity, pkg2) {
329592
+ function normalizeAndroidActivity2(activity, pkg2) {
329262
329593
  const trimmed = activity?.trim();
329263
329594
  if (!trimmed) return void 0;
329264
329595
  const packageName = pkg2?.trim();
@@ -329294,7 +329625,7 @@ var MobileBridgeDriver = class {
329294
329625
  return this.platform === "ANDROID" ? { appId: this.appId } : { bundleId: this.appId };
329295
329626
  }
329296
329627
  baseScreenSignal(source) {
329297
- const parsed = (0, import_runner_core8.parseMobileSource)(source, this.platform);
329628
+ const parsed = (0, import_runner_core9.parseMobileSource)(source, this.platform);
329298
329629
  return {
329299
329630
  platform: this.platform,
329300
329631
  screenHash: parsed.screenSignal.screenHash,
@@ -329313,11 +329644,11 @@ var MobileBridgeDriver = class {
329313
329644
  const activity = readStringValue(activityRes);
329314
329645
  const pkg2 = readStringValue(packageRes);
329315
329646
  if (pkg2) signal.bundleId = pkg2;
329316
- const normalizedActivity = normalizeAndroidActivity(activity, pkg2);
329647
+ const normalizedActivity = normalizeAndroidActivity2(activity, pkg2);
329317
329648
  if (normalizedActivity) signal.activity = normalizedActivity;
329318
329649
  } else {
329319
329650
  const infoRes = await this.executeScript("mobile: activeAppInfo", []).catch(() => void 0);
329320
- const bundleId = readActiveBundleId(infoRes);
329651
+ const bundleId = readActiveBundleId2(infoRes);
329321
329652
  if (bundleId) signal.bundleId = bundleId;
329322
329653
  }
329323
329654
  } catch {
@@ -329459,7 +329790,7 @@ var MobileBridgeDriver = class {
329459
329790
  return this.enrichScreenSignal(source);
329460
329791
  }
329461
329792
  };
329462
- function readActiveBundleId(result) {
329793
+ function readActiveBundleId2(result) {
329463
329794
  if (result && typeof result === "object" && "value" in result) {
329464
329795
  const value = result.value;
329465
329796
  if (value && typeof value === "object" && "bundleId" in value) {
@@ -329685,7 +330016,7 @@ function printDoctorReport(report) {
329685
330016
 
329686
330017
  // src/runner.ts
329687
330018
  init_dist();
329688
- var import_runner_core11 = __toESM(require_dist2());
330019
+ var import_runner_core12 = __toESM(require_dist2());
329689
330020
  function fingerprintTest(test) {
329690
330021
  const code = test.framework === "APPIUM" ? [test.appiumCode ?? "", JSON.stringify(test.steps ?? [])].join("\n--steps--\n") : test.playwrightCode ?? "";
329691
330022
  return createHash("sha256").update(test.name).update("\n--suite--\n").update(test.suiteName ?? "").update("\n--type--\n").update(test.testType ?? "").update("\n--framework--\n").update(test.framework ?? "").update("\n--code--\n").update(code).digest("hex").slice(0, 24);
@@ -330130,7 +330461,7 @@ function pruneCollectedPageForPost(page) {
330130
330461
  if (Array.isArray(out.observations)) out.observations = out.observations.slice(-200).map((obs) => compactForAudit(obs));
330131
330462
  return out;
330132
330463
  }
330133
- function prepareDiscoveryResultForPost(result, logs2) {
330464
+ function prepareDiscoveryResultForPost(result, logs2, options = {}) {
330134
330465
  const safe = { ...result, logs: truncateTextTail(logs2, RESULT_LOG_MAX_CHARS, "discovery result log") };
330135
330466
  if (Array.isArray(safe.collectedPages)) safe.collectedPages = safe.collectedPages.map(pruneCollectedPageForPost);
330136
330467
  if (Array.isArray(safe.apiCalls)) safe.apiCalls = safe.apiCalls.map((call) => compactForAudit(call));
@@ -330146,6 +330477,10 @@ function prepareDiscoveryResultForPost(result, logs2) {
330146
330477
  if (safe.snapshotEvidence) safe.snapshotEvidence = compactForAudit(safe.snapshotEvidence);
330147
330478
  if (safe.discoveryCheckpoints) safe.discoveryCheckpoints = compactForAudit(safe.discoveryCheckpoints);
330148
330479
  if (safe.pageScreenshotKeys && safe.pageScreenshots) delete safe.pageScreenshots;
330480
+ if (options.omitScreenshots) {
330481
+ if (Array.isArray(safe.screenshots)) delete safe.screenshots;
330482
+ if (safe.pageScreenshots) delete safe.pageScreenshots;
330483
+ }
330149
330484
  if (safe.exploreAlreadySaved === true) {
330150
330485
  if (Array.isArray(safe.screenshots)) delete safe.screenshots;
330151
330486
  if (safe.pageScreenshots) delete safe.pageScreenshots;
@@ -330245,7 +330580,7 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
330245
330580
  const runMode = run2.mode ?? "run";
330246
330581
  const RUNNER_MODE = opts.runnerMode ?? "playwright-native";
330247
330582
  log.info(`Running test: ${run2.testCaseId ?? "auth-setup"} (run ${run2.runId}) [mode: ${runMode === "heal" ? "heal" : runMode === "auth-setup" ? "auth-setup" : RUNNER_MODE}]`);
330248
- const liveBrowserHandle = import_runner_core10.liveBrowserRegistry.register({
330583
+ const liveBrowserHandle = import_runner_core11.liveBrowserRegistry.register({
330249
330584
  sessionId: `${run2.runId}`,
330250
330585
  mode: toLiveBrowserMode(runMode),
330251
330586
  runId: run2.runId,
@@ -330254,7 +330589,10 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
330254
330589
  testCaseId: run2.testCaseId ?? void 0
330255
330590
  });
330256
330591
  requestLiveBrowserHeartbeat(true);
330257
- const liveBrowserHeartbeatInterval = setInterval(() => requestLiveBrowserHeartbeat(), 5e3);
330592
+ const liveBrowserHeartbeatInterval = setInterval(() => {
330593
+ liveBrowserHandle.patch({});
330594
+ requestLiveBrowserHeartbeat();
330595
+ }, 5e3);
330258
330596
  liveBrowserHeartbeatInterval.unref?.();
330259
330597
  const runNativeWithLiveBrowser = (nativeOptions) => (0, import_runner_core.executeTestViaNativePlaywright)({
330260
330598
  ...nativeOptions,
@@ -330281,7 +330619,7 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
330281
330619
  log.fail(`PRE-FLIGHT FAILED - ${preflightError}`);
330282
330620
  return;
330283
330621
  }
330284
- const preflight = await (0, import_runner_core11.resolveBaseUrlPreflight)(run2.baseUrl);
330622
+ const preflight = await (0, import_runner_core12.resolveBaseUrlPreflight)(run2.baseUrl);
330285
330623
  if (preflight.error) {
330286
330624
  await postResult(opts.serverUrl, headers, run2.runId, { status: "ERROR", error: preflight.error });
330287
330625
  log.fail(`PRE-FLIGHT FAILED - ${preflight.error}`);
@@ -330338,7 +330676,7 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
330338
330676
  return;
330339
330677
  }
330340
330678
  const healLogStream = createHealLogStreamer(opts.serverUrl, headers, run2.runId);
330341
- const batchResult = await (0, import_runner_core9.executeGrokPerBatchHeal)(tests, run2.baseUrl, {
330679
+ const batchResult = await (0, import_runner_core10.executeGrokPerBatchHeal)(tests, run2.baseUrl, {
330342
330680
  testVariables: run2.testVariables,
330343
330681
  runNativeTest: runNativeWithLiveBrowser,
330344
330682
  validateContext: { runId: run2.runId, serverUrl: opts.serverUrl, authHeader: headers.Authorization },
@@ -330359,16 +330697,7 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
330359
330697
  }
330360
330698
  return;
330361
330699
  }
330362
- if (runMode === "heal") {
330363
- if (run2.framework === "APPIUM") {
330364
- await postResult(opts.serverUrl, headers, run2.runId, {
330365
- status: "ERROR",
330366
- error: "CONFIG_ISSUE: Appium (mobile) tests are not auto-healable; skipped heal.",
330367
- duration: Date.now() - startTime
330368
- });
330369
- log.warn("Skipped heal for APPIUM test (mobile tests are not healable)");
330370
- return;
330371
- }
330700
+ if (runMode === "heal" && run2.framework !== "APPIUM") {
330372
330701
  const isApiTest2 = Array.isArray(run2.tags) && run2.tags.includes("@api");
330373
330702
  if (isApiTest2) {
330374
330703
  const testCode = run2.playwrightCode ?? "";
@@ -330572,7 +330901,7 @@ ${finalVerifyResult.logs ?? ""}`;
330572
330901
  if (classifyErrorMessage(composedError ?? null) === "config_issue") {
330573
330902
  apiHealStatus = "ERROR";
330574
330903
  } else {
330575
- const rlDetection = (0, import_runner_core11.detectRateLimitBlock)({
330904
+ const rlDetection = (0, import_runner_core12.detectRateLimitBlock)({
330576
330905
  testName: run2.testName ?? run2.testCaseId ?? void 0,
330577
330906
  tags: run2.tags ?? [],
330578
330907
  error: composedError ?? finalVerifyResult?.error ?? null,
@@ -330679,7 +331008,7 @@ ${finalVerifyResult.logs ?? ""}`;
330679
331008
  log.info(`Heal engine: GROK (${source})`);
330680
331009
  }
330681
331010
  const grokHealLogStream = useGrokHeal ? createHealLogStreamer(opts.serverUrl, headers, run2.runId) : null;
330682
- const result2 = useGrokHeal ? await (0, import_runner_core9.executeGrokPerTestHeal)(run2.playwrightCode, run2.steps, run2.baseUrl, {
331011
+ const result2 = useGrokHeal ? await (0, import_runner_core10.executeGrokPerTestHeal)(run2.playwrightCode, run2.steps, run2.baseUrl, {
330683
331012
  ...healOpts,
330684
331013
  onLog: grokHealLogStream.onLog
330685
331014
  }) : await (0, import_runner_core2.executeTestWithHealing)(run2.playwrightCode, run2.steps, run2.baseUrl, healOpts);
@@ -330712,7 +331041,7 @@ ${finalVerifyResult.logs ?? ""}`;
330712
331041
  let healStatus = result2.passed ? "PASSED" : isConfigIssue ? "ERROR" : "FAILED";
330713
331042
  let healError = result2.error;
330714
331043
  if (!result2.passed && !isConfigIssue) {
330715
- const rlDetection = (0, import_runner_core11.detectRateLimitBlock)({
331044
+ const rlDetection = (0, import_runner_core12.detectRateLimitBlock)({
330716
331045
  testName: run2.testName ?? run2.testCaseId ?? void 0,
330717
331046
  tags: run2.tags ?? [],
330718
331047
  error: result2.error,
@@ -330906,7 +331235,7 @@ ${finalVerifyResult.logs ?? ""}`;
330906
331235
  liveBrowserHandle,
330907
331236
  runNativeTest: runNativeWithLiveBrowser
330908
331237
  };
330909
- const healResult = useGrokHealSuite ? await (0, import_runner_core9.executeGrokPerTestHeal)(
331238
+ const healResult = useGrokHealSuite ? await (0, import_runner_core10.executeGrokPerTestHeal)(
330910
331239
  failedTest.playwrightCode,
330911
331240
  failedTest.steps ?? [],
330912
331241
  run2.baseUrl,
@@ -331194,9 +331523,16 @@ ${finalVerifyResult.logs ?? ""}`;
331194
331523
  signal: AbortSignal.timeout(3e4)
331195
331524
  });
331196
331525
  if (res.ok) return;
331197
- log.warn(`[stream] discovery-plan attempt ${attempt + 1} returned ${res.status}`);
331526
+ const responseText = await res.text().catch(() => "");
331527
+ const statusMessage = responseText ? `${res.status}: ${responseText.slice(0, 500)}` : String(res.status);
331528
+ if (res.status === 409) {
331529
+ throw new Error(`Discovery plan checkpoint rejected: ${statusMessage}`);
331530
+ }
331531
+ log.warn(`[stream] discovery-plan attempt ${attempt + 1} returned ${statusMessage}`);
331198
331532
  } catch (err) {
331199
- log.warn(`[stream] discovery-plan attempt ${attempt + 1} failed: ${err instanceof Error ? err.message : String(err)}`);
331533
+ const message = err instanceof Error ? err.message : String(err);
331534
+ if (/Discovery plan checkpoint rejected/i.test(message)) throw err;
331535
+ log.warn(`[stream] discovery-plan attempt ${attempt + 1} failed: ${message}`);
331200
331536
  }
331201
331537
  if (attempt < 2) await new Promise((r) => setTimeout(r, 3e3 * (attempt + 1)));
331202
331538
  }
@@ -331204,6 +331540,7 @@ ${finalVerifyResult.logs ?? ""}`;
331204
331540
  };
331205
331541
  let discoveryResult;
331206
331542
  const buildFinalBody = (r, overrideError, auditWarning) => {
331543
+ const isNativeDiscovery = ctx.medium === "IOS_NATIVE" || ctx.medium === "ANDROID_NATIVE";
331207
331544
  const { exploreFinalMessages: _exploreFinalMessages, ...rest } = r;
331208
331545
  void _exploreFinalMessages;
331209
331546
  const logs2 = auditWarning ? [rest.logs, auditWarning].filter(Boolean).join("\n") : rest.logs;
@@ -331253,7 +331590,7 @@ ${finalVerifyResult.logs ?? ""}`;
331253
331590
  resolvedAppOrigin: rest.resolvedAppOrigin,
331254
331591
  plans: rest.plans,
331255
331592
  exploreStats: rest.exploreStats,
331256
- screenshots: rest.screenshots,
331593
+ ...isNativeDiscovery ? {} : { screenshots: rest.screenshots },
331257
331594
  snapshotEvidence: rest.snapshotEvidence,
331258
331595
  discoveryCheckpoints: rest.discoveryCheckpoints,
331259
331596
  streamedTestCount: streamedTestKeys.size,
@@ -331262,8 +331599,10 @@ ${finalVerifyResult.logs ?? ""}`;
331262
331599
  ...explorePosted ? {} : {
331263
331600
  collectedPages: rest.collectedPages,
331264
331601
  collectedTransitions: rest.collectedTransitions,
331265
- pageScreenshots: rest.pageScreenshots,
331266
- pageScreenshotKeys: rest.pageScreenshotKeys
331602
+ ...isNativeDiscovery ? {} : {
331603
+ pageScreenshots: rest.pageScreenshots,
331604
+ pageScreenshotKeys: rest.pageScreenshotKeys
331605
+ }
331267
331606
  },
331268
331607
  ...harPosted ? {} : {
331269
331608
  apiCalls: rest.apiCalls,
@@ -331271,7 +331610,7 @@ ${finalVerifyResult.logs ?? ""}`;
331271
331610
  },
331272
331611
  // Only send tests that were NOT confirmed streamed to avoid duplicates
331273
331612
  tests: rest.tests.filter((t) => !streamedTestKeys.has(fingerprintTest(t)))
331274
- }, logs2);
331613
+ }, logs2, { omitScreenshots: isNativeDiscovery });
331275
331614
  };
331276
331615
  const acquireLoginBrowser = async () => {
331277
331616
  const browser = await browserPool.acquireAsync(12e3);
@@ -331375,7 +331714,6 @@ ${finalVerifyResult.logs ?? ""}`;
331375
331714
  thumbnailCapturedAt: Date.now()
331376
331715
  });
331377
331716
  requestLiveBrowserHeartbeat();
331378
- void onScreenshot("mobile", base64);
331379
331717
  },
331380
331718
  onExploreComplete,
331381
331719
  onTestGenerated,
@@ -331402,7 +331740,7 @@ ${finalVerifyResult.logs ?? ""}`;
331402
331740
  setExecutorBusy(false);
331403
331741
  }
331404
331742
  } else if (ctx.mode === "import") {
331405
- const runRepoImport = ctx.frameworkImport?.kind === "repo-analysis" ? import_runner_core9.runRepoAnalysisOnRunner : import_runner_core9.runFrameworkImportOnRunner;
331743
+ const runRepoImport = ctx.frameworkImport?.kind === "repo-analysis" ? import_runner_core10.runRepoAnalysisOnRunner : import_runner_core10.runFrameworkImportOnRunner;
331406
331744
  discoveryResult = await runRepoImport(ctx, { onTestGenerated, acquireLoginBrowser, onAudit: discoveryAudit }, {
331407
331745
  archiveUrl: ctx.frameworkImport?.source === "upload" ? `${opts.serverUrl}/api/runner/runs/${run2.runId}/import-archive` : void 0,
331408
331746
  archiveAuthHeader: headers.Authorization,
@@ -331732,7 +332070,7 @@ ${finalVerifyResult.logs ?? ""}`;
331732
332070
  let runStatus = result.passed ? "PASSED" : "FAILED";
331733
332071
  let runError = result.error;
331734
332072
  if (!result.passed) {
331735
- const rlDetection = (0, import_runner_core11.detectRateLimitBlock)({
332073
+ const rlDetection = (0, import_runner_core12.detectRateLimitBlock)({
331736
332074
  testName: run2.testName ?? run2.testCaseId ?? void 0,
331737
332075
  tags: run2.tags ?? [],
331738
332076
  error: result.error,
@@ -331870,7 +332208,7 @@ async function startRunner(opts) {
331870
332208
  capture: envInt("MAX_CAPTURE", DEFAULT_TYPE_LIMITS.capture),
331871
332209
  run: envInt("MAX_RUN", DEFAULT_TYPE_LIMITS.run)
331872
332210
  };
331873
- const RESOURCE_GOVERNOR_OPTIONS = (0, import_runner_core11.createResourceGovernorOptions)({
332211
+ const RESOURCE_GOVERNOR_OPTIONS = (0, import_runner_core12.createResourceGovernorOptions)({
331874
332212
  isCloud: IS_CLOUD,
331875
332213
  maxConcurrent: MAX_CONCURRENT,
331876
332214
  typeLimits: TYPE_LIMITS
@@ -332021,8 +332359,8 @@ async function startRunner(opts) {
332021
332359
  android: devices.some((d) => d.platform === "ANDROID" && d.isAvailable && d.state === "BOOTED")
332022
332360
  } : { web: true, ios: false, android: false };
332023
332361
  const bridge = enableMobile ? getBridgeState() : void 0;
332024
- const resourceStatus = (0, import_runner_core11.getResourceGovernorStatus)(RESOURCE_GOVERNOR_OPTIONS, Object.fromEntries(activeByType));
332025
- const processInventory = await (0, import_runner_core11.getBrowserProcessInventory)();
332362
+ const resourceStatus = (0, import_runner_core12.getResourceGovernorStatus)(RESOURCE_GOVERNOR_OPTIONS, Object.fromEntries(activeByType));
332363
+ const processInventory = await (0, import_runner_core12.getBrowserProcessInventory)();
332026
332364
  lastInventoryPids = new Set(processInventory.processes.map((p) => p.pid));
332027
332365
  lastInventoryKinds.clear();
332028
332366
  for (const p of processInventory.processes) lastInventoryKinds.set(p.pid, p.kind);
@@ -332049,8 +332387,8 @@ async function startRunner(opts) {
332049
332387
  resourceSnapshot: resourceStatus.snapshot,
332050
332388
  resourceGovernor: resourceStatus.governor,
332051
332389
  processInventory,
332052
- aiQueue: (0, import_runner_core11.getRunnerAIQueueStats)(),
332053
- liveBrowsers: import_runner_core10.liveBrowserRegistry.snapshotForHeartbeat(),
332390
+ aiQueue: (0, import_runner_core12.getRunnerAIQueueStats)(),
332391
+ liveBrowsers: import_runner_core11.liveBrowserRegistry.snapshotForHeartbeat(),
332054
332392
  // Pre-flight checks (ANDROID_HOME, Xcode, Appium drivers, adb, etc).
332055
332393
  // Cached & cheap — see getCachedDoctor above.
332056
332394
  doctor: getCachedDoctor()
@@ -332144,7 +332482,7 @@ async function startRunner(opts) {
332144
332482
  let pidsWatchdogWarned = false;
332145
332483
  const pidsWatchdog = setInterval(() => {
332146
332484
  if (shuttingDown) return;
332147
- const snapshot = (0, import_runner_core11.getResourceSnapshot)();
332485
+ const snapshot = (0, import_runner_core12.getResourceSnapshot)();
332148
332486
  const pct = snapshot.pidsPct;
332149
332487
  if (pct == null || !Number.isFinite(pct)) return;
332150
332488
  if (pct >= PIDS_RECYCLE_PCT) {
@@ -332180,7 +332518,7 @@ async function startRunner(opts) {
332180
332518
  log.dim(` Deferred ${run2.runId} (project ${runProject} at capacity ${getActiveForProject(runProject)}/${projectLimit})`);
332181
332519
  continue;
332182
332520
  }
332183
- const admission = (0, import_runner_core11.evaluateRunAdmission)(RESOURCE_GOVERNOR_OPTIONS, {
332521
+ const admission = (0, import_runner_core12.evaluateRunAdmission)(RESOURCE_GOVERNOR_OPTIONS, {
332184
332522
  mode: runMode,
332185
332523
  activeRuns,
332186
332524
  activeByType: Object.fromEntries(activeByType),