@swmansion/argent 0.15.1-next.13 → 0.15.1-next.2

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.
@@ -314,10 +314,10 @@ var init_failure_codes = __esm({
314
314
  ANDROID_UIAUTOMATOR_PARSE_FAILED: "ANDROID_UIAUTOMATOR_PARSE_FAILED",
315
315
  ANDROID_UIAUTOMATOR_CAPTURE_FAILED: "ANDROID_UIAUTOMATOR_CAPTURE_FAILED",
316
316
  DEBUGGER_METRO_NOT_RUNNING: "DEBUGGER_METRO_NOT_RUNNING",
317
+ DEBUGGER_METRO_PROJECT_ROOT_MISSING: "DEBUGGER_METRO_PROJECT_ROOT_MISSING",
317
318
  DEBUGGER_METRO_NO_TARGETS: "DEBUGGER_METRO_NO_TARGETS",
318
319
  DEBUGGER_CDP_RUNTIME_EXCEPTION: "DEBUGGER_CDP_RUNTIME_EXCEPTION",
319
320
  DEBUGGER_CDP_BINDING_TIMEOUT: "DEBUGGER_CDP_BINDING_TIMEOUT",
320
- DEBUGGER_CDP_BINDING_UNAVAILABLE: "DEBUGGER_CDP_BINDING_UNAVAILABLE",
321
321
  DEBUGGER_CDP_PROTOCOL_ERROR: "DEBUGGER_CDP_PROTOCOL_ERROR",
322
322
  DEBUGGER_RELOAD_FAILED: "DEBUGGER_RELOAD_FAILED",
323
323
  JS_RUNTIME_CONSOLE_SERVER_BIND_FAILED: "JS_RUNTIME_CONSOLE_SERVER_BIND_FAILED",
@@ -404,7 +404,6 @@ var init_failure_codes = __esm({
404
404
  CHROMIUM_ELECTRON_EXITED_BEFORE_READY: "CHROMIUM_ELECTRON_EXITED_BEFORE_READY",
405
405
  KEYBOARD_KEY_UNSUPPORTED: "KEYBOARD_KEY_UNSUPPORTED",
406
406
  KEYBOARD_CHARACTER_UNSUPPORTED: "KEYBOARD_CHARACTER_UNSUPPORTED",
407
- SECRET_PLACEHOLDER_UNKNOWN: "SECRET_PLACEHOLDER_UNKNOWN",
408
407
  SCREENSHOT_DIFF_INPUT_INVALID: "SCREENSHOT_DIFF_INPUT_INVALID",
409
408
  BOOT_DEVICE_TARGET_SELECTION_INVALID: "BOOT_DEVICE_TARGET_SELECTION_INVALID",
410
409
  BOOT_IOS_UNSUPPORTED_HOST: "BOOT_IOS_UNSUPPORTED_HOST",
@@ -103836,8 +103835,6 @@ var ALLOWED = {
103836
103835
  action: PACKAGE_ACTION,
103837
103836
  is_success: bool,
103838
103837
  duration_ms: DURATION_MS,
103839
- retry_count: COUNT,
103840
- last_attempt_duration_ms: DURATION_MS,
103841
103838
  ...FAILURE_SIGNAL2
103842
103839
  },
103843
103840
  "installation:cli_update_start": {},
@@ -104399,7 +104396,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
104399
104396
  var SESSION_ID = (0, import_node_crypto3.randomUUID)();
104400
104397
  function readCliVersion() {
104401
104398
  if (true) {
104402
- return "0.16.0";
104399
+ return "0.15.0";
104403
104400
  }
104404
104401
  return "0.0.0";
104405
104402
  }
@@ -105764,7 +105761,7 @@ var import_node_path4 = __toESM(require("node:path"));
105764
105761
  var import_semver = __toESM(require_semver2());
105765
105762
 
105766
105763
  // ../tool-server/package.json
105767
- var version3 = "0.16.0";
105764
+ var version3 = "0.15.0";
105768
105765
 
105769
105766
  // ../tool-server/src/utils/update-checker.ts
105770
105767
  var import_update_core = __toESM(require_dist4());
@@ -114409,8 +114406,6 @@ var CDPClient = class {
114409
114406
  nextId = 1;
114410
114407
  pending = /* @__PURE__ */ new Map();
114411
114408
  pendingBindings = /* @__PURE__ */ new Map();
114412
- /** Set by addBinding when the runtime ACKs the command but installs nothing. */
114413
- bindingUnavailable = false;
114414
114409
  scripts = /* @__PURE__ */ new Map();
114415
114410
  enabledDomains = /* @__PURE__ */ new Set();
114416
114411
  wsUrl;
@@ -114543,8 +114538,6 @@ var CDPClient = class {
114543
114538
  }
114544
114539
  async addBinding(name) {
114545
114540
  await this.send("Runtime.addBinding", { name });
114546
- const probe3 = await this.evaluate(`typeof ${name}`).catch(() => void 0);
114547
- this.bindingUnavailable = probe3 === "undefined";
114548
114541
  }
114549
114542
  /**
114550
114543
  * Inject a script that will push a result via the binding using a unique requestId.
@@ -114553,19 +114546,6 @@ var CDPClient = class {
114553
114546
  evaluateWithBinding(expression, requestId, options) {
114554
114547
  const id = requestId ?? crypto3.randomUUID();
114555
114548
  const timeout = options?.timeout ?? DEFAULT_TIMEOUT_MS2;
114556
- if (this.bindingUnavailable) {
114557
- return Promise.reject(
114558
- new FailureError(
114559
- "This JS runtime acknowledges Runtime.addBinding but never installs the binding (legacy Hermes, React Native <= 0.72), so it cannot deliver a result over the binding channel. Tools that read the React tree this way are unavailable here; use `describe` to read on-screen structure.",
114560
- {
114561
- error_code: FAILURE_CODES.DEBUGGER_CDP_BINDING_UNAVAILABLE,
114562
- failure_stage: "debugger_cdp_binding",
114563
- failure_area: "tool_server",
114564
- error_kind: "unsupported"
114565
- }
114566
- )
114567
- );
114568
- }
114569
114549
  return new Promise((resolve7, reject) => {
114570
114550
  const timer = setTimeout(() => {
114571
114551
  this.pendingBindings.delete(id);
@@ -122002,14 +121982,12 @@ init_src();
122002
121982
 
122003
121983
  // ../tool-server/src/utils/debugger/discovery.ts
122004
121984
  init_src();
122005
- var DECOY_VM = "don't use";
122006
121985
  async function discoverMetro(port) {
122007
- let statusRes;
122008
- try {
122009
- statusRes = await fetch(`http://localhost:${port}/status`);
122010
- } catch (err) {
121986
+ const statusRes = await fetch(`http://localhost:${port}/status`);
121987
+ const statusText = await statusRes.text();
121988
+ if (!statusText.includes("packager-status:running")) {
122011
121989
  throw new FailureError(
122012
- `Metro at port ${port} is not running (got: ${err instanceof Error ? err.message : String(err)})`,
121990
+ `Metro at port ${port} is not running (got: ${statusText.slice(0, 100)})`,
122013
121991
  {
122014
121992
  error_code: FAILURE_CODES.DEBUGGER_METRO_NOT_RUNNING,
122015
121993
  failure_stage: "debugger_discover_metro_status",
@@ -122018,25 +121996,21 @@ async function discoverMetro(port) {
122018
121996
  }
122019
121997
  );
122020
121998
  }
122021
- const statusText = await statusRes.text();
122022
- if (!statusText.includes("packager-status:running")) {
121999
+ const projectRoot = statusRes.headers.get("X-React-Native-Project-Root") ?? "";
122000
+ if (!projectRoot) {
122023
122001
  throw new FailureError(
122024
- `Metro at port ${port} is not running (got: ${statusText.slice(0, 100)})`,
122002
+ `Metro at port ${port} did not return X-React-Native-Project-Root header`,
122025
122003
  {
122026
- error_code: FAILURE_CODES.DEBUGGER_METRO_NOT_RUNNING,
122027
- failure_stage: "debugger_discover_metro_status",
122004
+ error_code: FAILURE_CODES.DEBUGGER_METRO_PROJECT_ROOT_MISSING,
122005
+ failure_stage: "debugger_discover_metro_project_root",
122028
122006
  failure_area: "tool_server",
122029
122007
  error_kind: "network"
122030
122008
  }
122031
122009
  );
122032
122010
  }
122033
- const projectRoot = statusRes.headers.get("X-React-Native-Project-Root") ?? "";
122034
122011
  const listRes = await fetch(`http://localhost:${port}/json/list`);
122035
- const parsed = await listRes.json().catch(() => null);
122036
- const targets = (Array.isArray(parsed) ? parsed : []).filter(
122037
- (t) => t?.vm !== DECOY_VM
122038
- );
122039
- if (!targets.length) {
122012
+ const targets = await listRes.json();
122013
+ if (!targets?.length) {
122040
122014
  throw new FailureError(
122041
122015
  `Metro at port ${port} has no CDP targets \u2014 is a React Native app connected?`,
122042
122016
  {
@@ -122051,19 +122025,8 @@ async function discoverMetro(port) {
122051
122025
  }
122052
122026
 
122053
122027
  // ../tool-server/src/utils/debugger/target-selection.ts
122054
- function deviceKey(target) {
122055
- const logicalId = target.reactNative?.logicalDeviceId;
122056
- if (logicalId) return logicalId;
122057
- try {
122058
- const device = new URL(target.webSocketDebuggerUrl).searchParams.get("device");
122059
- if (device) return `device=${device}`;
122060
- } catch {
122061
- }
122062
- return target.deviceName;
122063
- }
122064
122028
  function selectTarget(targets, port, options) {
122065
- const pool = targets;
122066
- let candidates = pool;
122029
+ let candidates = targets;
122067
122030
  if (typeof options?.deviceId === "string" && options.deviceId) {
122068
122031
  const deviceId = options.deviceId;
122069
122032
  const filtered = candidates.filter((t) => t.reactNative?.logicalDeviceId === deviceId);
@@ -122071,21 +122034,16 @@ function selectTarget(targets, port, options) {
122071
122034
  candidates = filtered;
122072
122035
  } else {
122073
122036
  const distinctDevices = /* @__PURE__ */ new Map();
122074
- for (const t of pool) {
122075
- const key = deviceKey(t);
122076
- if (key && !distinctDevices.has(key)) {
122077
- distinctDevices.set(key, {
122078
- name: t.deviceName,
122079
- logicalId: t.reactNative?.logicalDeviceId
122080
- });
122037
+ for (const t of targets) {
122038
+ const id = t.reactNative?.logicalDeviceId;
122039
+ if (id !== void 0 && id !== "" && !distinctDevices.has(id)) {
122040
+ distinctDevices.set(id, t.deviceName);
122081
122041
  }
122082
122042
  }
122083
122043
  if (distinctDevices.size > 1) {
122084
- const listed = [...distinctDevices.values()].map(
122085
- (d) => d.logicalId ? `${d.name ?? "unknown"} (${d.logicalId})` : `${d.name ?? "unknown"} (legacy inspector \u2014 no logicalDeviceId)`
122086
- ).join(", ");
122044
+ const listed = [...distinctDevices.entries()].map(([id, name]) => name ? `${name} (${id})` : id).join(", ");
122087
122045
  throw new Error(
122088
- `No debugger target matches device_id "${deviceId}". ${distinctDevices.size} devices are connected to Metro on port ${port}: ${listed}. Re-target with the logicalDeviceId in parentheses \u2014 that is what debugger-connect returns and what subsequent debugger-* calls must pass. A legacy-inspector device (RN 0.72 / Vega) reports none and cannot be singled out of a shared Metro: give it its own Metro port.`
122046
+ `No debugger target matches device_id "${deviceId}". ${distinctDevices.size} devices are connected to Metro on port ${port}: ${listed}. Pass the logicalDeviceId (in parentheses) of the desired device as device_id (the logicalDeviceId returned by debugger-connect).`
122089
122047
  );
122090
122048
  }
122091
122049
  }
@@ -122226,7 +122184,6 @@ function parseNode(line) {
122226
122184
  // ../tool-server/src/utils/debugger/source-resolver.ts
122227
122185
  var ALLOWED_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs"]);
122228
122186
  function isInsideProject(absFile, projectRoot) {
122229
- if (!projectRoot) return false;
122230
122187
  const resolvedRoot = path17.resolve(projectRoot);
122231
122188
  const resolvedFile = path17.resolve(absFile);
122232
122189
  const rel = path17.relative(resolvedRoot, resolvedFile);
@@ -122269,7 +122226,7 @@ function createSourceResolver(port, projectRoot) {
122269
122226
  const frame = data.stack?.[0];
122270
122227
  if (!frame?.file) return null;
122271
122228
  if (/^https?:\/\//.test(frame.file)) return null;
122272
- const relFile = projectRoot ? frame.file.replace(projectRoot + "/", "").replace(/^\/+/, "") : frame.file;
122229
+ const relFile = frame.file.replace(projectRoot + "/", "").replace(/^\/+/, "");
122273
122230
  return {
122274
122231
  file: relFile,
122275
122232
  line: frame.lineNumber ?? 0,
@@ -122288,7 +122245,6 @@ function createSourceResolver(port, projectRoot) {
122288
122245
  },
122289
122246
  symbolicate: symbolicateFrame,
122290
122247
  async readSourceFragment(location, contextLines = 3) {
122291
- if (!projectRoot) return null;
122292
122248
  try {
122293
122249
  const absPath = path17.isAbsolute(location.file) ? path17.resolve(location.file) : path17.resolve(projectRoot, location.file);
122294
122250
  const realRoot = await fs18.realpath(projectRoot);
@@ -126146,10 +126102,7 @@ var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
126146
126102
  var zodSchema16 = external_exports.object({
126147
126103
  udid: external_exports.string().describe("Target device id from `list-devices` (iOS UDID, Android serial, or Chromium id)."),
126148
126104
  x: external_exports.number().describe("Normalized horizontal position 0.0\u20131.0 (left=0, right=1), not pixels"),
126149
- y: external_exports.number().describe("Normalized vertical position 0.0\u20131.0 (top=0, bottom=1), not pixels"),
126150
- clickCount: external_exports.number().int().min(1).max(10).optional().describe(
126151
- "Number of taps/clicks dispatched as ONE multi-tap gesture (2 = double-tap / double-click). The taps land inside the OS double-tap window; on Chromium each click carries an escalating CDP clickCount so dblclick actually fires. Default 1."
126152
- )
126105
+ y: external_exports.number().describe("Normalized vertical position 0.0\u20131.0 (top=0, bottom=1), not pixels")
126153
126106
  });
126154
126107
  var capability8 = {
126155
126108
  apple: { simulator: true, device: true },
@@ -126157,25 +126110,19 @@ var capability8 = {
126157
126110
  android: { emulator: true, device: true, unknown: true },
126158
126111
  chromium: { app: true }
126159
126112
  };
126160
- var TAP_HOLD_MS = 50;
126161
- var MULTI_TAP_GAP_MS = 100;
126162
- async function tapChromium(api, x, y, clickCount) {
126113
+ async function tapChromium(api, x, y) {
126163
126114
  const vp = api.getViewport();
126164
126115
  const pxX = Math.max(0, Math.min(vp.width, x * vp.width));
126165
126116
  const pxY = Math.max(0, Math.min(vp.height, y * vp.height));
126166
126117
  await api.dispatchMouseEvent({ type: "mouseMoved", x: pxX, y: pxY });
126167
- for (let i = 1; i <= clickCount; i++) {
126168
- if (i > 1) await sleep2(MULTI_TAP_GAP_MS);
126169
- await api.dispatchMouseEvent({ type: "mousePressed", x: pxX, y: pxY, clickCount: i });
126170
- await sleep2(TAP_HOLD_MS);
126171
- await api.dispatchMouseEvent({ type: "mouseReleased", x: pxX, y: pxY, clickCount: i });
126172
- }
126118
+ await api.dispatchMouseEvent({ type: "mousePressed", x: pxX, y: pxY, clickCount: 1 });
126119
+ await sleep2(50);
126120
+ await api.dispatchMouseEvent({ type: "mouseReleased", x: pxX, y: pxY, clickCount: 1 });
126173
126121
  }
126174
126122
  var gestureTapTool = {
126175
126123
  id: "gesture-tap",
126176
126124
  description: `Press the device screen (iOS simulator, Android emulator, or Chromium app) at normalized coordinates: x and y are fractions of screen width and height in 0.0\u20131.0 (not pixels).
126177
126125
  Sends a Down event followed by an Up event at the same point. For Chromium, this dispatches a CDP mouse-press/release on the renderer.
126178
- Set clickCount: 2 for a double-tap / double-click \u2014 the taps are dispatched as one gesture with proper click counting, which two separate tap calls cannot guarantee.
126179
126126
  Use when you need to tap a button, link, or any tappable element on the screen.
126180
126127
  Returns { tapped: true, timestampMs }. Fails if the simulator-server / emulator backend / Chromium CDP is not reachable for the given device.
126181
126128
  Before tapping, determine the correct coordinates by using discovery tools \u2014 pick by platform: iOS / Android use \`describe\`, \`native-describe-screen\`, or \`debugger-component-tree\`; Chromium uses \`describe\` (the DOM walker), since the native and RN-specific discovery tools don't apply. More information in \`argent-device-interact\` skill`,
@@ -126193,33 +126140,29 @@ Before tapping, determine the correct coordinates by using discovery tools \u201
126193
126140
  async execute(services, params) {
126194
126141
  const device = resolveDevice(params.udid);
126195
126142
  const timestampMs = Date.now();
126196
- const clickCount = params.clickCount ?? 1;
126197
126143
  if (device.platform === "chromium") {
126198
126144
  const chromium = services.chromium;
126199
- await tapChromium(chromium, params.x, params.y, clickCount);
126145
+ await tapChromium(chromium, params.x, params.y);
126200
126146
  return { tapped: true, timestampMs };
126201
126147
  }
126202
126148
  const api = services.simulatorServer;
126203
- for (let i = 1; i <= clickCount; i++) {
126204
- if (i > 1) await sleep2(MULTI_TAP_GAP_MS);
126205
- sendCommand(api, {
126206
- cmd: "touch",
126207
- type: "Down",
126208
- x: params.x,
126209
- y: params.y,
126210
- second_x: null,
126211
- second_y: null
126212
- });
126213
- await sleep2(TAP_HOLD_MS);
126214
- sendCommand(api, {
126215
- cmd: "touch",
126216
- type: "Up",
126217
- x: params.x,
126218
- y: params.y,
126219
- second_x: null,
126220
- second_y: null
126221
- });
126222
- }
126149
+ sendCommand(api, {
126150
+ cmd: "touch",
126151
+ type: "Down",
126152
+ x: params.x,
126153
+ y: params.y,
126154
+ second_x: null,
126155
+ second_y: null
126156
+ });
126157
+ await sleep2(50);
126158
+ sendCommand(api, {
126159
+ cmd: "touch",
126160
+ type: "Up",
126161
+ x: params.x,
126162
+ y: params.y,
126163
+ second_x: null,
126164
+ second_y: null
126165
+ });
126223
126166
  return { tapped: true, timestampMs };
126224
126167
  }
126225
126168
  };
@@ -126731,54 +126674,6 @@ Fails if the device backend is not reachable \u2014 the simulator-server for iOS
126731
126674
  // ../tool-server/src/tools/keyboard/index.ts
126732
126675
  init_zod();
126733
126676
 
126734
- // ../tool-server/src/utils/secrets.ts
126735
- init_src();
126736
- var SECRET_ENV_PREFIX = "ARGENT_SECRET_";
126737
- var SECRET_PLACEHOLDER_MARKER = "{{secret:";
126738
- var PLACEHOLDER_RE = /\{\{secret:([A-Za-z_][A-Za-z0-9_]*)\}\}/g;
126739
- function availableSecretNames(env = process.env) {
126740
- return Object.keys(env).filter((k) => k.startsWith(SECRET_ENV_PREFIX) && env[k] !== void 0).map((k) => k.slice(SECRET_ENV_PREFIX.length)).sort();
126741
- }
126742
- var REDUNDANT_PREFIX_RE = /^argent_secret_/i;
126743
- function resolveSecretPlaceholders(text, env = process.env) {
126744
- const secrets = [];
126745
- const resolved = text.replace(PLACEHOLDER_RE, (placeholder, rawName) => {
126746
- let name = rawName;
126747
- let value = env[SECRET_ENV_PREFIX + name];
126748
- if (value === void 0 && REDUNDANT_PREFIX_RE.test(name)) {
126749
- name = name.replace(REDUNDANT_PREFIX_RE, "");
126750
- value = env[SECRET_ENV_PREFIX + name];
126751
- }
126752
- if (value === void 0) {
126753
- const names = availableSecretNames(env);
126754
- throw new InvalidToolInputError(
126755
- `Unknown secret "${rawName}" \u2014 no ${SECRET_ENV_PREFIX}${name} environment variable is set on the machine running the tool-server. Available secrets: ${names.length ? names.join(", ") : "(none)"}. To make it available, ask the user to export ${SECRET_ENV_PREFIX}${name} in the tool-server's environment \u2014 never ask the user for the secret value itself.`,
126756
- {
126757
- error_code: FAILURE_CODES.SECRET_PLACEHOLDER_UNKNOWN,
126758
- failure_stage: "secret_placeholder_resolution",
126759
- error_kind: "validation"
126760
- }
126761
- );
126762
- }
126763
- if (!secrets.some((s) => s.name === name)) secrets.push({ name, value });
126764
- return value;
126765
- });
126766
- return { text: resolved, secrets };
126767
- }
126768
- function redactSecretsFromError(err, secrets) {
126769
- const scrub = (s) => secrets.reduce(
126770
- (acc, { name, value }) => value ? acc.split(value).join(`${SECRET_PLACEHOLDER_MARKER}${name}}}`) : acc,
126771
- s
126772
- );
126773
- if (err instanceof Error) {
126774
- err.message = scrub(err.message);
126775
- if (err.stack) err.stack = scrub(err.stack);
126776
- return err;
126777
- }
126778
- if (typeof err === "string") return scrub(err);
126779
- return err;
126780
- }
126781
-
126782
126677
  // ../tool-server/src/tools/keyboard/simulator-server-keys.ts
126783
126678
  init_src();
126784
126679
 
@@ -126889,11 +126784,10 @@ async function typeSimulatorServer(registry2, device, params) {
126889
126784
  }
126890
126785
  keysPressed++;
126891
126786
  };
126892
- let namedKeyCode;
126893
126787
  if (params.key) {
126894
126788
  const lower = params.key.toLowerCase();
126895
- namedKeyCode = Object.hasOwn(NAMED_KEYS, lower) ? NAMED_KEYS[lower] : void 0;
126896
- if (namedKeyCode == null) {
126789
+ const code = Object.hasOwn(NAMED_KEYS, lower) ? NAMED_KEYS[lower] : void 0;
126790
+ if (code == null) {
126897
126791
  throw new InvalidToolInputError(
126898
126792
  `Unknown key "${params.key}". Supported: ${Object.keys(NAMED_KEYS).join(", ")}`,
126899
126793
  {
@@ -126903,6 +126797,7 @@ async function typeSimulatorServer(registry2, device, params) {
126903
126797
  }
126904
126798
  );
126905
126799
  }
126800
+ await pressKeyCode(code);
126906
126801
  }
126907
126802
  if (params.text) {
126908
126803
  for (const char of params.text) {
@@ -126917,9 +126812,6 @@ async function typeSimulatorServer(registry2, device, params) {
126917
126812
  await sleep8(delay2);
126918
126813
  }
126919
126814
  }
126920
- if (namedKeyCode != null) {
126921
- await pressKeyCode(namedKeyCode);
126922
- }
126923
126815
  return { typed: params.text ?? params.key ?? "", keys: keysPressed };
126924
126816
  }
126925
126817
 
@@ -127063,10 +126955,9 @@ var sleep9 = (ms) => new Promise((r) => setTimeout(r, ms));
127063
126955
  async function runChromium(api, params) {
127064
126956
  const delay2 = params.delayMs ?? 50;
127065
126957
  let keysPressed = 0;
127066
- let named;
127067
126958
  if (params.key) {
127068
126959
  const lower = params.key.toLowerCase();
127069
- named = Object.hasOwn(CHROMIUM_NAMED_KEYS, lower) ? CHROMIUM_NAMED_KEYS[lower] : void 0;
126960
+ const named = Object.hasOwn(CHROMIUM_NAMED_KEYS, lower) ? CHROMIUM_NAMED_KEYS[lower] : void 0;
127070
126961
  if (!named) {
127071
126962
  throw new InvalidToolInputError(
127072
126963
  `Unknown key "${params.key}". Supported: ${Object.keys(CHROMIUM_NAMED_KEYS).join(", ")}`,
@@ -127077,6 +126968,20 @@ async function runChromium(api, params) {
127077
126968
  }
127078
126969
  );
127079
126970
  }
126971
+ await api.dispatchKeyEvent({
126972
+ type: "keyDown",
126973
+ key: named.key,
126974
+ code: named.code,
126975
+ windowsVirtualKeyCode: named.windowsVirtualKeyCode
126976
+ });
126977
+ await sleep9(delay2);
126978
+ await api.dispatchKeyEvent({
126979
+ type: "keyUp",
126980
+ key: named.key,
126981
+ code: named.code,
126982
+ windowsVirtualKeyCode: named.windowsVirtualKeyCode
126983
+ });
126984
+ keysPressed++;
127080
126985
  }
127081
126986
  if (params.text) {
127082
126987
  for (const char of params.text) {
@@ -127105,22 +127010,6 @@ async function runChromium(api, params) {
127105
127010
  await sleep9(delay2);
127106
127011
  }
127107
127012
  }
127108
- if (named) {
127109
- await api.dispatchKeyEvent({
127110
- type: "keyDown",
127111
- key: named.key,
127112
- code: named.code,
127113
- windowsVirtualKeyCode: named.windowsVirtualKeyCode
127114
- });
127115
- await sleep9(delay2);
127116
- await api.dispatchKeyEvent({
127117
- type: "keyUp",
127118
- key: named.key,
127119
- code: named.code,
127120
- windowsVirtualKeyCode: named.windowsVirtualKeyCode
127121
- });
127122
- keysPressed++;
127123
- }
127124
127013
  return { typed: params.text ?? params.key ?? "", keys: keysPressed };
127125
127014
  }
127126
127015
  function makeChromiumImpl(registry2) {
@@ -127204,7 +127093,7 @@ async function injectViaInputd(subcommands) {
127204
127093
  async function injectVegaButtons(buttons) {
127205
127094
  await injectViaInputd(remoteButtonsToKeycodes(buttons).map((code) => `button_press ${code}`));
127206
127095
  }
127207
- function resolveVegaNamedKeycode(name) {
127096
+ async function injectVegaNamedKey(name) {
127208
127097
  const lower = name.toLowerCase();
127209
127098
  const code = Object.hasOwn(NAMED_KEYCODES, lower) ? NAMED_KEYCODES[lower] : void 0;
127210
127099
  if (!code) {
@@ -127217,10 +127106,7 @@ function resolveVegaNamedKeycode(name) {
127217
127106
  }
127218
127107
  );
127219
127108
  }
127220
- return code;
127221
- }
127222
- async function injectVegaNamedKey(name) {
127223
- await injectViaInputd([`button_press ${resolveVegaNamedKeycode(name)}`]);
127109
+ await injectViaInputd([`button_press ${code}`]);
127224
127110
  }
127225
127111
  async function injectVegaText(text) {
127226
127112
  if (/[\n\r]/.test(text)) {
@@ -127236,15 +127122,14 @@ async function injectVegaText(text) {
127236
127122
  // ../tool-server/src/tools/keyboard/platforms/vega.ts
127237
127123
  async function runVega2(params) {
127238
127124
  let keysPressed = 0;
127239
- if (params.key) resolveVegaNamedKeycode(params.key);
127240
- if (params.text) {
127241
- await injectVegaText(params.text);
127242
- keysPressed += [...params.text].length;
127243
- }
127244
127125
  if (params.key) {
127245
127126
  await injectVegaNamedKey(params.key);
127246
127127
  keysPressed++;
127247
127128
  }
127129
+ if (params.text) {
127130
+ await injectVegaText(params.text);
127131
+ keysPressed += [...params.text].length;
127132
+ }
127248
127133
  return { typed: params.text ?? params.key ?? "", keys: keysPressed };
127249
127134
  }
127250
127135
  var vegaImpl4 = {
@@ -127257,11 +127142,9 @@ var zodSchema24 = external_exports.object({
127257
127142
  udid: external_exports.string().describe(
127258
127143
  "Target device id from `list-devices` (iOS UDID, Android serial, Vega serial, or Chromium id)."
127259
127144
  ),
127260
- text: external_exports.string().optional().describe(
127261
- 'Text to type character by character. Handles uppercase and common punctuation. To type a credential without its plaintext ever entering your context, use a secret placeholder: `{{secret:<NAME>}}` types the value of the `ARGENT_SECRET_<NAME>` environment variable set on the machine running the tool-server \u2014 e.g. text: "{{secret:APP_PASSWORD}}" types the value of `ARGENT_SECRET_APP_PASSWORD`. Only env vars with the `ARGENT_SECRET_` prefix are resolvable. Placeholders can be embedded in longer text and are never echoed back resolved. If the secret you need is not set, ask the user to export it as `ARGENT_SECRET_<NAME>` and restart the session \u2014 NEVER ask the user to paste the secret value into the conversation.'
127262
- ),
127145
+ text: external_exports.string().optional().describe("Text to type character by character. Handles uppercase and common punctuation."),
127263
127146
  key: external_exports.string().optional().describe(
127264
- "Named key to press: enter, escape, backspace, tab, space, arrow-up, arrow-down, arrow-left, arrow-right, f1\u2013f12. When combined with `text`, the key is pressed AFTER the text is typed (so text + enter types and submits). Not supported on TV targets \u2014 move focus with `tv-remote` (up/down/left/right) instead."
127147
+ "Named key to press: enter, escape, backspace, tab, space, arrow-up, arrow-down, arrow-left, arrow-right, f1\u2013f12. Not supported on TV targets \u2014 move focus with `tv-remote` (up/down/left/right) instead."
127265
127148
  ),
127266
127149
  delayMs: external_exports.number().optional().describe(
127267
127150
  "Delay in ms between key presses (default 50). Ignored on Android phones/tablets (typed via `adb input text`, which has no per-key cadence), on Vega (text/keys injected in a single shot), and on TV targets (Apple TV / Android TV type the whole string at the daemon's own cadence)."
@@ -127275,24 +127158,15 @@ var capability16 = {
127275
127158
  vega: { vvd: true }
127276
127159
  };
127277
127160
  function createKeyboardTool(registry2) {
127278
- const dispatch = dispatchByPlatform({
127279
- toolId: "keyboard",
127280
- capability: capability16,
127281
- ios: makeIosImpl3(registry2),
127282
- iosRemote: makeIosRemoteImpl(registry2),
127283
- android: makeAndroidImpl(registry2),
127284
- chromium: makeChromiumImpl(registry2),
127285
- vega: vegaImpl4
127286
- });
127287
127161
  return {
127288
127162
  id: "keyboard",
127289
127163
  description: `Type text or press special keys on the device (iOS simulator, Android emulator or device, Chromium app, Vega Virtual Device, or Apple TV / Android TV) using keyboard events.
127290
127164
  Use when you need to enter text or trigger a named key such as enter, escape, or arrow keys. On Vega and Apple TV / Android TV, prefer the remote tools for D-pad navigation; use keyboard to type into a focused text field (e.g. a search or login box).
127291
127165
  Returns { typed: string, keys: number }. Fails if an unsupported key name is provided or the device's input backend is not reachable.
127292
- - text: types a string (supports uppercase, digits, common punctuation). To type a credential, use \`{{secret:<NAME>}}\` \u2014 resolved server-side from the \`ARGENT_SECRET_<NAME>\` env var (prefix mandatory; \`{{secret:APP_PASSWORD}}\` \u2194 \`ARGENT_SECRET_APP_PASSWORD\`), so the plaintext never enters agent context; the result echoes the placeholder, not the value, and the after-typing auto-screenshot is skipped.
127166
+ - text: types a string (supports uppercase, digits, common punctuation)
127293
127167
  - key: presses a single named key (enter, escape, backspace, tab, arrow-up/down/left/right, f1\u2013f12) \u2014 NOT supported on TV targets; move focus with \`tv-remote\` instead.
127294
127168
  On a TV target (runtimeKind 'tv') only \`text\` applies \u2014 focus a text field first (with \`tv-remote\`), then type into it (injected HID keyboard on Apple TV, \`adb input text\` on Android TV).
127295
- Provide text, key, or both \u2014 when both are given, the text is typed first and the key is pressed after it (text + key:"enter" types and submits).`,
127169
+ Provide text, key, or both.`,
127296
127170
  zodSchema: zodSchema24,
127297
127171
  capability: capability16,
127298
127172
  searchHint: "type text keyboard input named key enter escape arrow tv vega fire tv search field hid leanback",
@@ -127300,17 +127174,15 @@ Provide text, key, or both \u2014 when both are given, the text is typed first a
127300
127174
  // simulator-server, CDP, or Vega adb), since distinguishing a TV target is
127301
127175
  // async and a tvOS udid must never resolve simulator-server.
127302
127176
  services: () => ({}),
127303
- execute: async (services, params, options) => {
127304
- if (params.text === void 0) return dispatch(services, params, options);
127305
- const { text, secrets } = resolveSecretPlaceholders(params.text);
127306
- if (secrets.length === 0) return dispatch(services, params, options);
127307
- try {
127308
- const result = await dispatch(services, { ...params, text }, options);
127309
- return { ...result, typed: params.text };
127310
- } catch (err) {
127311
- throw redactSecretsFromError(err, secrets);
127312
- }
127313
- }
127177
+ execute: dispatchByPlatform({
127178
+ toolId: "keyboard",
127179
+ capability: capability16,
127180
+ ios: makeIosImpl3(registry2),
127181
+ iosRemote: makeIosRemoteImpl(registry2),
127182
+ android: makeAndroidImpl(registry2),
127183
+ chromium: makeChromiumImpl(registry2),
127184
+ vega: vegaImpl4
127185
+ })
127314
127186
  };
127315
127187
  }
127316
127188
 
@@ -133273,26 +133145,17 @@ async function describeVega(_serial) {
133273
133145
 
133274
133146
  // ../tool-server/src/utils/ui-tree-match.ts
133275
133147
  init_zod();
133276
- function hasVisibleText(text) {
133277
- return text.replace(/[\p{Cf}\p{Co}\p{Cc}]/gu, "").trim().length > 0;
133278
- }
133279
- var selectorFieldsSchema = external_exports.object({
133280
- text: external_exports.string().min(1).refine(hasVisibleText, {
133281
- message: "text must contain at least one visible character (icon-font/private-use and zero-width characters render as nothing) \u2014 select by identifier or role instead"
133282
- }).optional().describe("Case-insensitive substring of the element's visible label or value."),
133148
+ var selectorSchema = external_exports.object({
133149
+ text: external_exports.string().min(1).optional().describe("Case-insensitive substring of the element's visible label or value."),
133283
133150
  identifier: external_exports.string().min(1).optional().describe(
133284
133151
  "The element's identifier (accessibilityIdentifier / resource-id / testid), matched case-insensitively as the exact identifier or the unqualified resource-id name ('submit' matches 'com.example.app:id/submit')."
133285
133152
  ),
133286
133153
  role: external_exports.string().min(1).optional().describe(
133287
133154
  "Case-insensitive substring of the element's role (e.g. AXButton, button, TextView)."
133288
133155
  )
133289
- }).strict();
133290
- var selectorSchema = selectorFieldsSchema.refine(
133291
- (s) => Boolean(s.text || s.identifier || s.role),
133292
- {
133293
- message: "selector needs at least one of text, identifier, or role"
133294
- }
133295
- );
133156
+ }).refine((s) => Boolean(s.text || s.identifier || s.role), {
133157
+ message: "selector needs at least one of text, identifier, or role"
133158
+ });
133296
133159
  function nodeText(node) {
133297
133160
  return [node.label, node.value].filter(Boolean).join(" ");
133298
133161
  }
@@ -133309,32 +133172,15 @@ function identifierMatches(actual, needle) {
133309
133172
  if (!actual) return false;
133310
133173
  return equalsCI(actual, needle) || actual.toLowerCase().endsWith(`:id/${needle.toLowerCase()}`);
133311
133174
  }
133312
- var uiTreeMatchInternals = {
133313
- createRegExp(pattern) {
133314
- return new RegExp(pattern);
133315
- }
133316
- };
133317
- function regexMatchesNonEmpty(regex, actual) {
133318
- if (!actual) return false;
133319
- return regex.test(actual);
133320
- }
133321
133175
  function textMatches(actual, expected, mode) {
133322
- if (mode === "matches") {
133323
- return regexMatchesNonEmpty(uiTreeMatchInternals.createRegExp(expected), actual);
133324
- }
133325
133176
  return mode === "equals" ? equalsCI(actual, expected) : includesCI(actual, expected);
133326
133177
  }
133327
- function matchNodeWithRegex(node, selector, textRegex) {
133178
+ function matchNode(node, selector) {
133328
133179
  if (selector.text !== void 0) {
133329
133180
  if (!includesCI(node.label, selector.text) && !includesCI(node.value, selector.text)) {
133330
133181
  return false;
133331
133182
  }
133332
133183
  }
133333
- if (textRegex !== void 0) {
133334
- if (!regexMatchesNonEmpty(textRegex, node.label) && !regexMatchesNonEmpty(textRegex, node.value)) {
133335
- return false;
133336
- }
133337
- }
133338
133184
  if (selector.identifier !== void 0 && !identifierMatches(node.identifier, selector.identifier)) {
133339
133185
  return false;
133340
133186
  }
@@ -133343,17 +133189,13 @@ function matchNodeWithRegex(node, selector, textRegex) {
133343
133189
  }
133344
133190
  return true;
133345
133191
  }
133346
- function selectorTextRegex(selector) {
133347
- return selector.textMatches === void 0 ? void 0 : uiTreeMatchInternals.createRegExp(selector.textMatches);
133348
- }
133349
- function collectMatches(node, selector, textRegex, acc) {
133350
- if (matchNodeWithRegex(node, selector, textRegex)) acc.push(node);
133351
- for (const child of node.children) collectMatches(child, selector, textRegex, acc);
133192
+ function collectMatches(node, selector, acc) {
133193
+ if (matchNode(node, selector)) acc.push(node);
133194
+ for (const child of node.children) collectMatches(child, selector, acc);
133352
133195
  }
133353
133196
  function findAll(root2, selector) {
133354
133197
  const acc = [];
133355
- const textRegex = selectorTextRegex(selector);
133356
- for (const child of root2.children) collectMatches(child, selector, textRegex, acc);
133198
+ for (const child of root2.children) collectMatches(child, selector, acc);
133357
133199
  return acc;
133358
133200
  }
133359
133201
  function isVisible(node) {
@@ -133417,29 +133259,21 @@ function nodeAtPoint(root2, point) {
133417
133259
  for (const child of root2.children) walk(child);
133418
133260
  return best;
133419
133261
  }
133420
- function fullConsumptionRegex(selector) {
133421
- return selector.textMatches === void 0 ? void 0 : uiTreeMatchInternals.createRegExp(`^(?:${selector.textMatches})$`);
133422
- }
133423
- function exactFieldCount(node, selector, fullTextRegex) {
133262
+ function exactFieldCount(node, selector) {
133424
133263
  let count = 0;
133425
133264
  if (selector.text !== void 0 && (equalsCI(node.label, selector.text) || equalsCI(node.value, selector.text))) {
133426
133265
  count++;
133427
133266
  }
133428
- if (fullTextRegex !== void 0 && (regexMatchesNonEmpty(fullTextRegex, node.label) || regexMatchesNonEmpty(fullTextRegex, node.value))) {
133429
- count++;
133430
- }
133431
133267
  if (selector.identifier !== void 0 && equalsCI(node.identifier, selector.identifier)) count++;
133432
133268
  if (selector.role !== void 0 && equalsCI(node.role, selector.role)) count++;
133433
133269
  return count;
133434
133270
  }
133435
133271
  function selectorToFrame(root2, selector) {
133436
133272
  const visible = findAll(root2, selector).filter(isVisible);
133437
- if (visible.length === 0) return void 0;
133438
- const fullTextRegex = fullConsumptionRegex(selector);
133439
133273
  let best;
133440
133274
  let bestExact = -1;
133441
133275
  for (const n of visible) {
133442
- const exact = exactFieldCount(n, selector, fullTextRegex);
133276
+ const exact = exactFieldCount(n, selector);
133443
133277
  if (best === void 0 || exact !== bestExact) {
133444
133278
  if (exact > bestExact) {
133445
133279
  best = n;
@@ -133467,7 +133301,7 @@ var GENERIC_ROLES = /* @__PURE__ */ new Set([
133467
133301
  ]);
133468
133302
  function deriveSelector(node) {
133469
133303
  if (node.identifier && node.identifier.trim()) return { identifier: node.identifier };
133470
- const text = [node.label, node.value].map((t) => t?.trim()).find((t) => t && hasVisibleText(t));
133304
+ const text = [node.label, node.value].map((t) => t?.trim()).find(Boolean);
133471
133305
  if (text) return { text };
133472
133306
  if (node.role && !GENERIC_ROLES.has(node.role.toLowerCase())) return { role: node.role };
133473
133307
  return null;
@@ -133723,7 +133557,7 @@ a prior tap), use individual tool calls instead.
133723
133557
 
133724
133558
  Allowed tools and their args (udid is auto-injected, do NOT include it in args):
133725
133559
 
133726
- gesture-tap: { x: number, y: number, clickCount?: number } [ios/android/chromium]
133560
+ gesture-tap: { x: number, y: number } [ios/android/chromium]
133727
133561
  gesture-swipe: { fromX: number, fromY: number, toX: number, toY: number, durationMs?: number } [ios/android]
133728
133562
  gesture-scroll: { x: number, y: number, deltaX?: number, deltaY?: number, durationMs?: number } [chromium only]
133729
133563
  gesture-drag: { fromX: number, fromY: number, toX: number, toY: number, durationMs?: number } [chromium only]
@@ -133731,8 +133565,7 @@ Allowed tools and their args (udid is auto-injected, do NOT include it in args):
133731
133565
  gesture-pinch: { centerX: number, centerY: number, startDistance: number, endDistance: number, angle?: number, durationMs?: number } [ios only]
133732
133566
  gesture-rotate: { centerX: number, centerY: number, radius: number, startAngle: number, endAngle: number, durationMs?: number } [ios only]
133733
133567
  button: { button: "home"|"back"|"power"|"volumeUp"|"volumeDown"|"appSwitch"|"actionButton" } [ios/android]
133734
- keyboard: { text?: string, key?: string, delayMs?: number } (key pressed after text; TV: text only) [ios/android/chromium/vega/tv]
133735
- text supports {{secret:<NAME>}} placeholders, resolved server-side from ARGENT_SECRET_<NAME> env vars (prefix mandatory) \u2014 credentials never enter agent context
133568
+ keyboard: { text?: string, key?: string, delayMs?: number } (TV: text only) [ios/android/chromium/vega/tv]
133736
133569
  rotate: { orientation: "Portrait"|"LandscapeLeft"|"LandscapeRight"|"PortraitUpsideDown" } [ios/android]
133737
133570
  tv-remote: { button: <remote button | array of them>, repeat?: number } [apple tv/android tv/vega]
133738
133571
  buttons: up/down/left/right/select/back/home/menu/playPause (+ rewind/fastForward/next/previous/volumeUp/volumeDown/mute \u2014 work on Android TV and Vega; rejected on the Apple TV simulator)
@@ -133745,9 +133578,10 @@ Example \u2014 scroll down three times (use gesture-scroll with positive deltaY
133745
133578
  { "tool": "gesture-swipe", "args": { "fromX": 0.5, "fromY": 0.7, "toX": 0.5, "toY": 0.3 } }
133746
133579
  ]}
133747
133580
 
133748
- Example \u2014 type text and submit (one step: the key is pressed after the text is typed):
133581
+ Example \u2014 type text and submit:
133749
133582
  { "udid": "<UDID>", "steps": [
133750
- { "tool": "keyboard", "args": { "text": "hello world", "key": "enter" } }
133583
+ { "tool": "keyboard", "args": { "text": "hello world" } },
133584
+ { "tool": "keyboard", "args": { "key": "enter" } }
133751
133585
  ]}
133752
133586
 
133753
133587
  Example \u2014 TV: move focus right twice then activate (one tv-remote step with a path is cheaper):
@@ -133845,8 +133679,7 @@ var DEBUGGER_TOOL_CAPABILITY = {
133845
133679
  apple: { simulator: true, device: true },
133846
133680
  appleRemote: { simulator: true },
133847
133681
  android: { emulator: true, device: true, unknown: true },
133848
- chromium: { app: true },
133849
- vega: { vvd: true }
133682
+ chromium: { app: true }
133850
133683
  };
133851
133684
  var RN_ONLY_TOOL_CAPABILITY = {
133852
133685
  apple: { simulator: true, device: true },
@@ -133865,14 +133698,14 @@ function debuggerServiceRef(params) {
133865
133698
  var zodSchema29 = external_exports.object({
133866
133699
  port: external_exports.coerce.number().default(8081).describe("Metro server port (ignored for Chromium \u2014 its CDP port is encoded in device_id)"),
133867
133700
  device_id: external_exports.string().describe(
133868
- "Device id: iOS simulator UDID, Android logicalDeviceId returned by Metro, Vega serial (amazon-...), or Chromium device id (chromium-cdp-<port>) from list-devices. When a logicalDeviceId is returned, forward it as device_id to all subsequent debugger-* calls to pin them to this device; when none is returned (Vega), keep passing the id you connected with."
133701
+ "Device id: iOS simulator UDID, Android logicalDeviceId returned by Metro, or Chromium device id (chromium-cdp-<port>) from list-devices. The returned logicalDeviceId must be forwarded as device_id to all subsequent debugger-* calls to pin them to this device."
133869
133702
  )
133870
133703
  });
133871
133704
  var debuggerConnectTool = {
133872
133705
  id: "debugger-connect",
133873
133706
  description: `Connect to a JS runtime CDP debugger.
133874
- iOS / Android / Vega: connects to Metro's CDP endpoint on the given port. Chromium: re-uses the page CDP session opened by boot-device \u2014 port is ignored.
133875
- Returns connection info including port, projectRoot (empty on Chromium and on legacy Metro, e.g. Vega), deviceName, appName, logicalDeviceId (absent on Vega), and isNewDebugger. If already connected, returns the existing connection.
133707
+ iOS / Android: connects to Metro's CDP endpoint on the given port. Chromium: re-uses the page CDP session opened by boot-device \u2014 port is ignored.
133708
+ Returns connection info including port, projectRoot (empty on Chromium), deviceName, appName, logicalDeviceId, and isNewDebugger. If already connected, returns the existing connection.
133876
133709
  Use when starting a debug session or before calling other debugger-* tools. Fails if the runtime is unreachable (Metro down, or Chromium CDP terminated).`,
133877
133710
  zodSchema: zodSchema29,
133878
133711
  capability: DEBUGGER_TOOL_CAPABILITY,
@@ -133898,13 +133731,13 @@ init_zod();
133898
133731
  var zodSchema30 = external_exports.object({
133899
133732
  port: external_exports.coerce.number().default(8081).describe("Metro server port (ignored for Chromium)"),
133900
133733
  device_id: external_exports.string().describe(
133901
- "Device id from debugger-connect (iOS simulator UDID, Android logicalDeviceId, Vega serial, or Chromium device id)."
133734
+ "Device id from debugger-connect (iOS simulator UDID, Android logicalDeviceId, or Chromium device id)."
133902
133735
  )
133903
133736
  });
133904
133737
  var debuggerStatusTool = {
133905
133738
  id: "debugger-status",
133906
133739
  description: `Get JS runtime debugger connection status and diagnostic info.
133907
- Use when you need to verify connectivity before using other debugger tools. Returns port, projectRoot (empty on Chromium and on legacy Metro, e.g. Vega), deviceName, appName, logicalDeviceId (absent on Vega), isNewDebugger (false on the legacy inspector), connected flag, loadedScripts count, and sourceMapReady (always true \u2014 waits for pending source maps before returning; no-op on Chromium). Fails if the runtime is unreachable.`,
133740
+ Use when you need to verify connectivity before using other debugger tools. Returns port, projectRoot (empty on Chromium), deviceName, appName, logicalDeviceId, connected flag, loadedScripts count, and sourceMapReady (always true \u2014 waits for pending source maps before returning; no-op on Chromium). Fails if the runtime is unreachable.`,
133908
133741
  zodSchema: zodSchema30,
133909
133742
  capability: DEBUGGER_TOOL_CAPABILITY,
133910
133743
  services: (params) => ({
@@ -133933,13 +133766,13 @@ init_zod();
133933
133766
  var zodSchema31 = external_exports.object({
133934
133767
  port: external_exports.coerce.number().default(8081).describe("Metro server port (ignored for Chromium)"),
133935
133768
  device_id: external_exports.string().describe(
133936
- "Device id from debugger-connect (iOS simulator UDID, Android logicalDeviceId, Vega serial, or Chromium device id)."
133769
+ "Device id from debugger-connect (iOS simulator UDID, Android logicalDeviceId, or Chromium device id)."
133937
133770
  ),
133938
133771
  expression: external_exports.string().describe("JavaScript expression to evaluate in the app runtime")
133939
133772
  });
133940
133773
  var debuggerEvaluateTool = {
133941
133774
  id: "debugger-evaluate",
133942
- description: `Execute arbitrary JavaScript in the app's JS runtime via CDP \u2014 Hermes on iOS / Android / Vega, V8 on Chromium.
133775
+ description: `Execute arbitrary JavaScript in the app's JS runtime via CDP \u2014 Hermes on iOS / Android, V8 on Chromium.
133943
133776
  Returns the evaluation result as a JSON-serializable value, along with deviceName, appName, and logicalDeviceId for context. Use when you need to read app state, call app functions, or test logic at runtime. The result is serialized by value, so cyclic objects (many RN runtime values \u2014 fiber nodes, navigation refs, global \u2014 are cyclic) fail with a serialization error rather than returning silently. Fails if the expression throws or the runtime is not connected.`,
133944
133777
  zodSchema: zodSchema31,
133945
133778
  capability: DEBUGGER_TOOL_CAPABILITY,
@@ -135423,13 +135256,13 @@ init_zod();
135423
135256
  var zodSchema35 = external_exports.object({
135424
135257
  port: external_exports.coerce.number().default(8081).describe("Metro server port (ignored for Chromium)"),
135425
135258
  device_id: external_exports.string().describe(
135426
- "Device id from debugger-connect (iOS simulator UDID, Android logicalDeviceId, Vega serial, or Chromium device id)."
135259
+ "Device id from debugger-connect (iOS simulator UDID, Android logicalDeviceId, or Chromium device id)."
135427
135260
  )
135428
135261
  });
135429
135262
  var debuggerLogRegistryTool = {
135430
135263
  id: "debugger-log-registry",
135431
135264
  description: `Get a summary of all console logs captured from the app's JS runtime.
135432
- Returns the log file path, entry counts by level, and message clusters (grouped by similarity). Works against Hermes (iOS / Android / Vega) and V8 (Chromium).
135265
+ Returns the log file path, entry counts by level, and message clusters (grouped by similarity). Works against Hermes (iOS / Android) and V8 (Chromium).
135433
135266
  Use when investigating warnings, errors, or unexpected output \u2014 call this first for an overview, then read the returned file for details. Returns empty stats if no log data has been captured yet.`,
135434
135267
  zodSchema: zodSchema35,
135435
135268
  capability: DEBUGGER_TOOL_CAPABILITY,
@@ -135521,7 +135354,7 @@ var networkLogsTool = {
135521
135354
  description: `Retrieve captured network (HTTP) requests from the running app.
135522
135355
  Returns a paginated list of requests with method, URL, status, resource type, size, and duration.
135523
135356
  Each entry includes a requestId that can be passed to view-network-request-details for full details.
135524
- On React Native (iOS / Android / Vega) interception is injected into the JS runtime \u2014 it captures fetch() calls. On Chromium it reads the browser's native CDP Network domain (the active tab; all request types).
135357
+ On React Native (iOS/Android) interception is injected into the JS runtime \u2014 it captures fetch() calls. On Chromium it reads the browser's native CDP Network domain (the active tab; all request types).
135525
135358
  Use when inspecting outbound HTTP traffic or debugging API calls in the running app.
135526
135359
  Fails if the app is not connected (RN) or the device is not reachable (Chromium).`,
135527
135360
  zodSchema: zodSchema36,
@@ -144664,67 +144497,14 @@ function chromiumLaunchSpec(launch) {
144664
144497
  return typeof c === "string" ? { path: c } : { path: c.path, args: c.args };
144665
144498
  }
144666
144499
  function selectorToYaml(sel) {
144667
- if (sel.text !== void 0 && sel.textMatches !== void 0) {
144668
- throw new Error(
144669
- 'Cannot serialize flow selector without losing constraints: both `text` and `textMatches` are set, but flow YAML can represent only one `text` constraint (a literal string or `{ matches: "<regex>" }`). Use either literal or regex text matching.'
144670
- );
144671
- }
144672
- if (sel.text !== void 0 && (typeof sel.text !== "string" || !hasVisibleText(sel.text))) {
144673
- throw new Error(
144674
- "Cannot serialize flow selector: `text` must contain at least one visible character (icon-font/private-use and zero-width characters render as nothing). Select by identifier or role, or use a coordinate tap."
144675
- );
144676
- }
144677
- if (sel.loose && (sel.text === void 0 || sel.textMatches !== void 0 || sel.identifier !== void 0 || sel.role !== void 0)) {
144678
- const incompatible = [
144679
- sel.textMatches !== void 0 ? "textMatches" : void 0,
144680
- sel.identifier !== void 0 ? "identifier" : void 0,
144681
- sel.role !== void 0 ? "role" : void 0
144682
- ].filter((field) => field !== void 0);
144683
- throw new Error(
144684
- "Cannot serialize loose flow selector without changing its meaning: bare-string YAML can represent only a loose text-only selector" + (incompatible.length > 0 ? `; incompatible fields: ${incompatible.join(", ")}` : "") + "."
144685
- );
144686
- }
144687
144500
  if (sel.loose && sel.text !== void 0 && sel.identifier === void 0 && sel.role === void 0) {
144688
144501
  return sel.text;
144689
144502
  }
144690
- const { loose: _loose, identifier, textMatches: textMatches2, ...rest } = sel;
144691
- const out = { ...rest };
144692
- if (textMatches2 !== void 0) out.text = { matches: textMatches2 };
144693
- if (identifier !== void 0) out.id = identifier;
144694
- return out;
144503
+ const { loose: _loose, identifier, ...rest } = sel;
144504
+ return identifier === void 0 ? { ...rest } : { ...rest, id: identifier };
144695
144505
  }
144696
144506
  function describeSelector(s) {
144697
- return Object.entries(s).filter(([k]) => k !== "loose").map(
144698
- ([k, v]) => k === "textMatches" ? `text=/${v}/` : `${k === "identifier" ? "id" : k}="${v}"`
144699
- ).join(" ");
144700
- }
144701
- function describeTextExpectation(expectedText, textMatch, verbForm = "mode") {
144702
- const expected = expectedText ?? "";
144703
- const mode = textMatch ?? "contains";
144704
- switch (mode) {
144705
- case "contains":
144706
- return `${verbForm === "infinitive" ? "contain" : mode} ${JSON.stringify(expected)}`;
144707
- case "equals":
144708
- return `${verbForm === "infinitive" ? "equal" : mode} ${JSON.stringify(expected)}`;
144709
- case "matches":
144710
- return `${verbForm === "infinitive" ? "match" : mode} /${expected}/`;
144711
- }
144712
- }
144713
- function textWaitToYaml(selector, expectedText, textMatch) {
144714
- const expected = expectedText ?? "";
144715
- const mode = textMatch ?? "contains";
144716
- switch (mode) {
144717
- case "contains":
144718
- return { text: { in: selector, contains: expected } };
144719
- case "equals":
144720
- return { text: { in: selector, equals: expected } };
144721
- case "matches":
144722
- return { text: { in: selector, matches: expected } };
144723
- default: {
144724
- const exhaustive = mode;
144725
- throw new Error(`Unsupported text match mode: ${exhaustive}`);
144726
- }
144727
- }
144507
+ return Object.entries(s).filter(([k]) => k !== "loose").map(([k, v]) => `${k === "identifier" ? "id" : k}="${v}"`).join(" ");
144728
144508
  }
144729
144509
  function waitToYaml(condition, selector, expectedText, textMatch, timeoutMs) {
144730
144510
  const sel = selectorToYaml(selector);
@@ -144740,7 +144520,7 @@ function waitToYaml(condition, selector, expectedText, textMatch, timeoutMs) {
144740
144520
  body = { hidden: sel };
144741
144521
  break;
144742
144522
  case "text":
144743
- body = textWaitToYaml(sel, expectedText, textMatch);
144523
+ body = textMatch === "equals" ? { text: { in: sel, equals: expectedText ?? "" } } : { text: { in: sel, contains: expectedText ?? "" } };
144744
144524
  break;
144745
144525
  }
144746
144526
  if (timeoutMs !== void 0) body.timeout = timeoutMs;
@@ -144755,20 +144535,9 @@ function toYamlStep(step) {
144755
144535
  case "run":
144756
144536
  return { run: step.flow };
144757
144537
  case "tap": {
144758
- if (step.selector) {
144759
- const sel = selectorToYaml(step.selector);
144760
- return { tap: step.times !== void 0 ? { on: sel, times: step.times } : sel };
144761
- }
144762
- const body = { x: step.x, y: step.y };
144763
- if (step.times !== void 0) body.times = step.times;
144538
+ const body = step.selector ? selectorToYaml(step.selector) : { x: step.x, y: step.y };
144764
144539
  return { tap: body };
144765
144540
  }
144766
- case "long-press": {
144767
- const sel = selectorToYaml(step.selector);
144768
- return {
144769
- "long-press": step.duration !== void 0 ? { on: sel, duration: step.duration } : sel
144770
- };
144771
- }
144772
144541
  case "type": {
144773
144542
  const body = {
144774
144543
  into: selectorToYaml(step.into),
@@ -144833,16 +144602,6 @@ function badEntry(raw, detail) {
144833
144602
  error_kind: "validation"
144834
144603
  });
144835
144604
  }
144836
- function validatePattern(raw, pattern, where) {
144837
- try {
144838
- new RegExp(pattern);
144839
- } catch (err) {
144840
- badEntry(
144841
- raw,
144842
- `${where} \`matches\` is not a valid regular expression: ${err instanceof Error ? err.message : String(err)}`
144843
- );
144844
- }
144845
- }
144846
144605
  function editDistance(a, b) {
144847
144606
  let prevPrev = new Array(b.length + 1);
144848
144607
  let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
@@ -144906,46 +144665,11 @@ function parseSelector(raw, where) {
144906
144665
  }
144907
144666
  normalized = { ...rest, identifier: id };
144908
144667
  }
144909
- if (normalized !== null && typeof normalized === "object") {
144910
- const { text, ...rest } = normalized;
144911
- if (text !== null && typeof text === "object") {
144912
- const keys = Object.keys(text);
144913
- if (!Array.isArray(text)) {
144914
- rejectUnknownKeys(
144915
- raw,
144916
- text,
144917
- ["matches"],
144918
- `${where}: text matcher`
144919
- );
144920
- }
144921
- const pattern = text.matches;
144922
- if (keys.length !== 1 || keys[0] !== "matches") {
144923
- badEntry(
144924
- raw,
144925
- `${where}: a text matcher takes exactly { matches: '<regex>' } \u2014 for a substring, use the plain-string form (text: "\u2026")`
144926
- );
144927
- }
144928
- if (typeof pattern !== "string" || pattern.length === 0) {
144929
- badEntry(raw, `${where}: text matcher needs a non-empty \`matches\` pattern`);
144930
- }
144931
- validatePattern(raw, pattern, `${where}: text`);
144932
- const fields = selectorFieldsSchema.safeParse(rest);
144933
- if (!fields.success) {
144934
- badEntry(raw, `${where}: ${fields.error.issues[0]?.message ?? "invalid selector"}`);
144935
- }
144936
- return { ...fields.data, textMatches: pattern };
144937
- }
144938
- }
144939
144668
  const r = selectorSchema.safeParse(normalized);
144940
144669
  if (!r.success) badEntry(raw, `${where}: ${r.error.issues[0]?.message ?? "invalid selector"}`);
144941
144670
  return r.data;
144942
144671
  }
144943
144672
  var WAIT_CONDITIONS = ["exists", "visible", "hidden", "text"];
144944
- var TEXT_MATCH_MODES = Object.keys({
144945
- contains: true,
144946
- equals: true,
144947
- matches: true
144948
- });
144949
144673
  var SCROLL_DIRECTIONS = ["up", "down", "left", "right"];
144950
144674
  function parseWaitFields(raw, kind) {
144951
144675
  if (raw === null || typeof raw !== "object") {
@@ -144985,30 +144709,22 @@ function parseWaitFields(raw, kind) {
144985
144709
  if (condition === "text") {
144986
144710
  const t = b.text;
144987
144711
  if (t === null || typeof t !== "object") {
144988
- badEntry(
144989
- { [kind]: b },
144990
- `${kind} text needs { in: <selector>, contains|equals|matches: <string> }`
144991
- );
144712
+ badEntry({ [kind]: b }, `${kind} text needs { in: <selector>, contains|equals: <string> }`);
144992
144713
  }
144993
144714
  const tb = t;
144994
144715
  if (!Array.isArray(tb)) {
144995
- rejectUnknownKeys({ [kind]: b }, tb, ["in", ...TEXT_MATCH_MODES], `${kind}.text`);
144716
+ rejectUnknownKeys({ [kind]: b }, tb, ["in", "contains", "equals"], `${kind}.text`);
144996
144717
  }
144997
- const comparators = TEXT_MATCH_MODES.filter((mode) => mode in tb);
144998
- if (comparators.length !== 1) {
144999
- badEntry(
145000
- { [kind]: b },
145001
- `${kind} text needs exactly one of \`contains\`, \`equals\`, or \`matches\``
145002
- );
144718
+ const hasContains = "contains" in tb;
144719
+ const hasEquals = "equals" in tb;
144720
+ if (hasContains === hasEquals) {
144721
+ badEntry({ [kind]: b }, `${kind} text needs exactly one of \`contains\` or \`equals\``);
145003
144722
  }
145004
- const textMatch = comparators[0];
145005
- const expected = tb[textMatch];
144723
+ const textMatch = hasEquals ? "equals" : "contains";
144724
+ const expected = hasEquals ? tb.equals : tb.contains;
145006
144725
  if (typeof expected !== "string" || expected.length === 0) {
145007
144726
  badEntry({ [kind]: b }, `${kind} text needs a non-empty \`${textMatch}\``);
145008
144727
  }
145009
- if (textMatch === "matches") {
145010
- validatePattern({ [kind]: b }, expected, `${kind} text`);
145011
- }
145012
144728
  return {
145013
144729
  condition: "text",
145014
144730
  selector: parseSelector(tb.in, `${kind}.text.in`),
@@ -145071,7 +144787,6 @@ var STEP_DIRECTIVE_KEYS = [
145071
144787
  "run",
145072
144788
  "tool",
145073
144789
  "tap",
145074
- "long-press",
145075
144790
  "type",
145076
144791
  "await",
145077
144792
  "assert",
@@ -145079,79 +144794,6 @@ var STEP_DIRECTIVE_KEYS = [
145079
144794
  "scroll-to",
145080
144795
  "snapshot"
145081
144796
  ];
145082
- function parseLongPress(body, entry) {
145083
- const obj = body !== null && typeof body === "object" ? body : {};
145084
- if (obj.on !== void 0 || obj.duration !== void 0) {
145085
- if (obj.text !== void 0 || obj.id !== void 0 || obj.identifier !== void 0 || obj.role !== void 0) {
145086
- badEntry(
145087
- entry,
145088
- 'the long-press options form takes a nested selector \u2014 e.g. long-press: { on: { text: "Row" }, duration: 1200 }'
145089
- );
145090
- }
145091
- if (!Object.keys(obj).every((k) => k === "on" || k === "duration")) {
145092
- badEntry(entry, "the long-press options form accepts only { on, duration }");
145093
- }
145094
- if (obj.on === void 0) {
145095
- badEntry(entry, 'long-press needs a target \u2014 e.g. long-press: { on: "Row", duration: 1200 }');
145096
- }
145097
- const step = { kind: "long-press", selector: parseSelector(obj.on, "long-press.on") };
145098
- if (obj.duration !== void 0) {
145099
- if (typeof obj.duration !== "number" || !Number.isFinite(obj.duration) || obj.duration <= 0) {
145100
- badEntry(
145101
- entry,
145102
- "long-press.duration needs a positive number of milliseconds (e.g. `duration: 1200`)"
145103
- );
145104
- }
145105
- step.duration = obj.duration;
145106
- }
145107
- return step;
145108
- }
145109
- return { kind: "long-press", selector: parseSelector(body, "long-press") };
145110
- }
145111
- function parseTapTimes(raw, entry) {
145112
- if (raw === void 0) return void 0;
145113
- if (typeof raw !== "number" || !Number.isInteger(raw) || raw < 1 || raw > 10) {
145114
- badEntry(entry, "tap.times must be an integer between 1 and 10 (2 = double-tap)");
145115
- }
145116
- return raw === 1 ? void 0 : raw;
145117
- }
145118
- function parseTap(body, entry) {
145119
- const obj = body !== null && typeof body === "object" ? body : {};
145120
- if (obj.x !== void 0 || obj.y !== void 0) {
145121
- if (obj.on !== void 0 || obj.text !== void 0 || obj.id !== void 0 || obj.identifier !== void 0 || obj.role !== void 0) {
145122
- badEntry(entry, "tap takes a selector or x/y coordinates, not both");
145123
- }
145124
- if (typeof obj.x !== "number" || typeof obj.y !== "number") {
145125
- badEntry(entry, "a coordinate tap needs numeric x and y");
145126
- }
145127
- if (!Object.keys(obj).every((k) => k === "x" || k === "y" || k === "times")) {
145128
- badEntry(entry, "a coordinate tap takes only { x, y, times }");
145129
- }
145130
- const step = { kind: "tap", x: obj.x, y: obj.y };
145131
- const times = parseTapTimes(obj.times, entry);
145132
- if (times !== void 0) step.times = times;
145133
- return step;
145134
- }
145135
- if (obj.on !== void 0 || obj.times !== void 0) {
145136
- if (obj.text !== void 0 || obj.id !== void 0 || obj.identifier !== void 0 || obj.role !== void 0) {
145137
- badEntry(
145138
- entry,
145139
- 'the tap options form takes a nested selector \u2014 e.g. tap: { on: { text: "Photo" }, times: 2 }'
145140
- );
145141
- }
145142
- if (!Object.keys(obj).every((k) => k === "on" || k === "times")) {
145143
- badEntry(entry, "the tap options form accepts only { on, times }");
145144
- }
145145
- if (obj.on === void 0) {
145146
- badEntry(entry, 'tap with times needs a target \u2014 e.g. tap: { on: "Photo", times: 2 }');
145147
- }
145148
- const step = { kind: "tap", selector: parseSelector(obj.on, "tap.on") };
145149
- const times = parseTapTimes(obj.times, entry);
145150
- if (times !== void 0) step.times = times;
145151
- return step;
145152
- }
145153
- return { kind: "tap", selector: parseSelector(body, "tap") };
145154
- }
145155
144797
  function fromYamlStep(raw) {
145156
144798
  const entry = raw;
145157
144799
  const kinds = STEP_DIRECTIVE_KEYS.filter((k) => k in entry);
@@ -145177,9 +144819,22 @@ function fromYamlStep(raw) {
145177
144819
  if ("echo" in raw) return { kind: "echo", message: String(raw.echo) };
145178
144820
  if ("launch" in raw) return { kind: "launch", app: parseLaunch(raw.launch) };
145179
144821
  if ("run" in raw) return { kind: "run", flow: String(raw.run) };
145180
- if ("tap" in raw) return parseTap(raw.tap, raw);
145181
- if ("long-press" in raw) {
145182
- return parseLongPress(raw["long-press"], raw);
144822
+ if ("tap" in raw) {
144823
+ const body = raw.tap;
144824
+ const obj = body !== null && typeof body === "object" ? body : {};
144825
+ if (body !== null && typeof body === "object" && !Array.isArray(body)) {
144826
+ rejectUnknownKeys(raw, obj, [...SELECTOR_KEYS, "x", "y"], "tap");
144827
+ }
144828
+ if (obj.x !== void 0 || obj.y !== void 0) {
144829
+ if (obj.text !== void 0 || obj.id !== void 0 || obj.identifier !== void 0 || obj.role !== void 0) {
144830
+ badEntry(raw, "tap takes a selector or x/y coordinates, not both");
144831
+ }
144832
+ if (typeof obj.x !== "number" || typeof obj.y !== "number") {
144833
+ badEntry(raw, "a coordinate tap needs numeric x and y");
144834
+ }
144835
+ return { kind: "tap", x: obj.x, y: obj.y };
144836
+ }
144837
+ return { kind: "tap", selector: parseSelector(body, "tap") };
145183
144838
  }
145184
144839
  if ("type" in raw) {
145185
144840
  const body = raw.type;
@@ -146016,15 +145671,13 @@ If a step was recorded by mistake, edit the .yaml file directly to remove it.`,
146016
145671
  const runTarget = params.command === RUN_TARGET_COMMAND && params.delayMs === void 0 ? await captureRunTarget(session, args) : void 0;
146017
145672
  const strippedArgs = stripDeviceKeys(args);
146018
145673
  const isLaunch = params.command === "restart-app" && params.delayMs === void 0 && typeof strippedArgs.bundleId === "string" && Object.keys(strippedArgs).length === 1;
146019
- const cc = args.clickCount;
146020
- const tapTimes = isTap && typeof cc === "number" && Number.isInteger(cc) && cc >= 2 && cc <= 10 ? { times: cc } : {};
146021
145674
  let step;
146022
145675
  let warning;
146023
145676
  if (captured?.selector) {
146024
- step = { kind: "tap", selector: captured.selector, ...tapTimes };
145677
+ step = { kind: "tap", selector: captured.selector };
146025
145678
  warning = captured.warning;
146026
145679
  } else if (isTap) {
146027
- step = { kind: "tap", x: args.x, y: args.y, ...tapTimes };
145680
+ step = { kind: "tap", x: args.x, y: args.y };
146028
145681
  warning = captured?.warning;
146029
145682
  } else if (isLaunch) {
146030
145683
  step = { kind: "launch", app: strippedArgs.bundleId };
@@ -146114,20 +145767,11 @@ You can still edit the .yaml file directly afterwards to remove or reorder steps
146114
145767
  return `${n}. run: ${step.flow}`;
146115
145768
  case "tap":
146116
145769
  return `${n}. tap: ${step.selector ? selectorLabel(step.selector) : `(${step.x}, ${step.y})`}`;
146117
- case "long-press":
146118
- return `${n}. long-press: ${selectorLabel(step.selector)}`;
146119
145770
  case "type":
146120
145771
  return `${n}. type: ${selectorLabel(step.into)} \u2190 "${step.text}"`;
146121
145772
  case "await":
146122
145773
  case "assert": {
146123
- let tail;
146124
- if (step.condition !== "text") {
146125
- tail = `${step.condition} ${selectorLabel(step.selector)}`;
146126
- } else {
146127
- const selector = selectorLabel(step.selector);
146128
- const expected = step.expectedText ?? "";
146129
- tail = step.textMatch === "matches" ? `text ${selector} matches /${expected}/` : step.textMatch === "equals" ? `text ${selector} == ${JSON.stringify(expected)}` : `text ${selector} contains ${JSON.stringify(expected)}`;
146130
- }
145774
+ const tail = step.condition === "text" ? `text ${selectorLabel(step.selector)} == "${step.expectedText ?? ""}"` : `${step.condition} ${selectorLabel(step.selector)}`;
146131
145775
  return `${n}. ${step.kind}: ${tail}`;
146132
145776
  }
146133
145777
  case "wait":
@@ -146369,7 +146013,7 @@ function offscreenHint(sel) {
146369
146013
  return `no visible element matched selector ${describeSelector(sel)} \u2014 if it is off-screen, add a scroll-to step before this one`;
146370
146014
  }
146371
146015
  async function runDirective(env, step) {
146372
- if (env.device.platform === "vega" && (step.kind === "tap" || step.kind === "long-press" || step.kind === "type" || step.kind === "scroll-to")) {
146016
+ if (env.device.platform === "vega" && (step.kind === "tap" || step.kind === "type" || step.kind === "scroll-to")) {
146373
146017
  return {
146374
146018
  ok: false,
146375
146019
  reason: `${step.kind} is a touch directive and Vega is remote-driven \u2014 move focus with \`tool: tv-remote\` steps (and type via \`tool: keyboard\`) instead`
@@ -146378,8 +146022,6 @@ async function runDirective(env, step) {
146378
146022
  switch (step.kind) {
146379
146023
  case "tap":
146380
146024
  return runTap(env, step);
146381
- case "long-press":
146382
- return runLongPress(env, step);
146383
146025
  case "type":
146384
146026
  return runType(env, step);
146385
146027
  case "await":
@@ -146407,37 +146049,7 @@ async function runTap(env, target) {
146407
146049
  } else {
146408
146050
  return { ok: false, reason: "tap needs a selector or x/y coordinates" };
146409
146051
  }
146410
- await invokeOnDevice(env, "gesture-tap", {
146411
- ...point,
146412
- ...target.times !== void 0 ? { clickCount: target.times } : {}
146413
- });
146414
- return { ok: true };
146415
- }
146416
- var DEFAULT_LONG_PRESS_MS = 800;
146417
- async function runLongPress(env, step) {
146418
- const frame = await waitForFrame(env, step.selector);
146419
- if (frame === "aborted") return ABORTED_OUTCOME;
146420
- if (!frame) {
146421
- return { ok: false, reason: offscreenHint(step.selector) };
146422
- }
146423
- const point = getDescribeTapPoint(frame);
146424
- const duration3 = step.duration ?? DEFAULT_LONG_PRESS_MS;
146425
- if (env.device.platform === "chromium") {
146426
- await invokeOnDevice(env, "gesture-drag", {
146427
- fromX: point.x,
146428
- fromY: point.y,
146429
- toX: point.x,
146430
- toY: point.y,
146431
- durationMs: duration3
146432
- });
146433
- } else {
146434
- await invokeOnDevice(env, "gesture-custom", {
146435
- events: [
146436
- { type: "Down", x: point.x, y: point.y, delayMs: 0 },
146437
- { type: "Up", x: point.x, y: point.y, delayMs: duration3 }
146438
- ]
146439
- });
146440
- }
146052
+ await invokeOnDevice(env, "gesture-tap", point);
146441
146053
  return { ok: true };
146442
146054
  }
146443
146055
  async function runType(env, step) {
@@ -146521,11 +146133,11 @@ function assertReason(condition, selector, expectedText, textMatch, matches2) {
146521
146133
  case "text": {
146522
146134
  const first = firstInReadingOrder(matches2.filter(isVisible)) ?? firstInReadingOrder(matches2);
146523
146135
  if (!first) return `no element matched selector ${sel}`;
146524
- const wanted = describeTextExpectation(expectedText, textMatch, "infinitive");
146136
+ const wanted = textMatch === "equals" ? "equal" : "contain";
146525
146137
  const shown = assertText(first);
146526
146138
  const own = nodeText(first);
146527
146139
  const ownNote = own && own !== shown ? ` (own text "${own}")` : "";
146528
- return `element matched ${sel} but its text was "${shown}"${ownNote} (wanted to ${wanted})`;
146140
+ return `element matched ${sel} but its text was "${shown}"${ownNote} (wanted to ${wanted} "${expectedText}")`;
146529
146141
  }
146530
146142
  default:
146531
146143
  return `assertion failed for selector ${sel}`;
@@ -148874,9 +148486,8 @@ function createRunFlowTool(registry2) {
148874
148486
  id: "flow-execute",
148875
148487
  description: `Run a saved flow from the .argent/flows/ directory.
148876
148488
  Steps run in order: \`launch\` starts an app from scratch (terminate + relaunch) and waits until it is
148877
- ready; \`tool\` calls dispatch through the registry; \`tap\`/\`long-press\`/\`type\` resolve a selector to an
148878
- element and act on it (\`long-press: { on, duration }\` presses and holds); \`scroll-to\` scrolls
148879
- (momentum-free) until a target is visible; \`await\` waits for a UI
148489
+ ready; \`tool\` calls dispatch through the registry; \`tap\`/\`type\` resolve a selector to an element and
148490
+ act on it; \`scroll-to\` scrolls (momentum-free) until a target is visible; \`await\` waits for a UI
148880
148491
  condition; \`wait\` pauses for a fixed number of milliseconds; \`assert\` checks one now; \`snapshot\`
148881
148492
  diffs a screenshot against a stored baseline (a missing baseline fails the step \u2014 set updateBaselines
148882
148493
  to adopt the current screen); \`echo\` annotates; \`run\` executes a referenced fragment inline.
@@ -149021,7 +148632,6 @@ function pushReport(state3, report) {
149021
148632
  function selectorLabel2(sel) {
149022
148633
  const parts = [];
149023
148634
  if (sel.text !== void 0) parts.push(`"${sel.text}"`);
149024
- if (sel.textMatches !== void 0) parts.push(`/${sel.textMatches}/`);
149025
148635
  if (sel.identifier) parts.push(`id=${sel.identifier}`);
149026
148636
  if (sel.role) parts.push(`role=${sel.role}`);
149027
148637
  return parts.join(" ");
@@ -149032,15 +148642,13 @@ function stepTarget(step) {
149032
148642
  if (step.selector) return selectorLabel2(step.selector);
149033
148643
  if (step.x !== void 0 && step.y !== void 0) return `(${step.x}, ${step.y})`;
149034
148644
  return void 0;
149035
- case "long-press":
149036
- return selectorLabel2(step.selector);
149037
148645
  case "type":
149038
148646
  return `into ${selectorLabel2(step.into)}`;
149039
148647
  case "await":
149040
148648
  case "assert": {
149041
148649
  const sel = selectorLabel2(step.selector);
149042
148650
  if (step.condition === "text") {
149043
- return `${sel} ${describeTextExpectation(step.expectedText, step.textMatch)}`;
148651
+ return `${sel} ${step.textMatch ?? "contains"} "${step.expectedText ?? ""}"`;
149044
148652
  }
149045
148653
  return `${step.condition} ${sel}`;
149046
148654
  }
@@ -149129,7 +148737,6 @@ async function execLeafStep(state3, step, index, sourceFlow) {
149129
148737
  return { ...base, status: r.ok ? "pass" : "error", reason: r.reason };
149130
148738
  }
149131
148739
  case "tap":
149132
- case "long-press":
149133
148740
  case "type":
149134
148741
  case "await":
149135
148742
  case "assert":
@@ -149941,7 +149548,7 @@ function normLabel(s) {
149941
149548
  return (s || "").toLowerCase().replace(/-/g, "").replace(/[\s,]+/g, " ").trim();
149942
149549
  }
149943
149550
  var MAX_FRAME_AREA = 0.85;
149944
- function matchNode(n, match, needle) {
149551
+ function matchNode2(n, match, needle) {
149945
149552
  const label = normLabel(n.label);
149946
149553
  const ident = normLabel(n.identifier);
149947
149554
  const value = normLabel(n.value);
@@ -149968,7 +149575,7 @@ function findElementMatch(tree, match) {
149968
149575
  const candidates = [];
149969
149576
  const walk = (n) => {
149970
149577
  if (!n || typeof n !== "object") return;
149971
- const m = matchNode(n, match, needle);
149578
+ const m = matchNode2(n, match, needle);
149972
149579
  if (m && n.frame) {
149973
149580
  const f = n.frame;
149974
149581
  const cx = f.x + f.width / 2;