@swmansion/argent 0.30.2-next.17 → 0.30.2-next.19

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.
package/dist/cli-cmds.mjs CHANGED
@@ -35594,7 +35594,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
35594
35594
  var SESSION_ID2 = randomUUID5();
35595
35595
  function readCliVersion() {
35596
35596
  if (true) {
35597
- return "0.30.2-next.17";
35597
+ return "0.30.2-next.19";
35598
35598
  }
35599
35599
  return "0.0.0";
35600
35600
  }
@@ -16900,7 +16900,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
16900
16900
  var SESSION_ID = randomUUID4();
16901
16901
  function readCliVersion() {
16902
16902
  if (true) {
16903
- return "0.30.2-next.17";
16903
+ return "0.30.2-next.19";
16904
16904
  }
16905
16905
  return "0.0.0";
16906
16906
  }
@@ -53,6 +53,8 @@ export interface ArgentToolArgs {
53
53
  force?: boolean;
54
54
  /** Android only: boot the emulator with audio output enabled. Defaults to false: argent boots emulators MUTED so several agent-driven devices don't all play sound on the host machine; pass `true` when the task involves playing, hearing, or testing audio. Takes effect at boot: if the emulator is already running muted, add `force: true` to reboot it with sound. A boot snapshot saved in the other audio mode can't be reused, so the first boot after toggling is a slower cold boot. The `boot-sound` argent flag flips this default to true. Ignored on iOS/Vega/HarmonyOS/Electron, which argent never mutes. */
55
55
  sound?: boolean;
56
+ /** Android on a macOS host only: boot the emulator with guest Vulkan. Defaults to false: guest Vulkan crashes Chrome's GPU process, which breaks Chrome and Custom Tab sign-in flows. Without it apps find no Vulkan device, although the guest still lists the Vulkan features, so a Vulkan-only renderer such as WebGPU (react-native-wgpu) draws nothing; pass `true` to test one. Takes effect at boot: an emulator already running in the other mode is reused with a `note`, and `force: true` reboots it. A boot snapshot saved in the other mode can't be reused, so the first boot after toggling is a slower cold boot. The `ARGENT_EMULATOR_GUEST_VULKAN` env var flips this default to true. Ignored on other hosts and platforms. */
57
+ guestVulkan?: boolean;
56
58
  /** iOS only: boot the simulator core WITHOUT opening its GUI window (Simulator.app, or Device Hub on Xcode 27+). The device still streams via simulator-server; used by Argent Lens. Set the `ARGENT_SIMULATOR_NO_WINDOW` env var (1/true/yes) to force this host-wide without passing the flag per call (the iOS analog of `ARGENT_EMULATOR_NO_WINDOW`). Ignored on Android/Vega/HarmonyOS/Electron, which have no equivalent GUI step. */
57
59
  headless?: boolean;
58
60
  /** Electron: path to the Electron app to launch. Either a packaged .app bundle / executable, or a project directory whose package.json points the Electron binary at the entry script. Provide exactly one of `udid`, `avdName`, `vvdImage`, `harmonyInstance`, or `electronAppPath`. */
@@ -100945,7 +100945,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
100945
100945
  var SESSION_ID = (0, import_node_crypto3.randomUUID)();
100946
100946
  function readCliVersion() {
100947
100947
  if (true) {
100948
- return "0.30.2-next.17";
100948
+ return "0.30.2-next.19";
100949
100949
  }
100950
100950
  return "0.0.0";
100951
100951
  }
@@ -137046,6 +137046,9 @@ function iosHeadlessFromEnv() {
137046
137046
  function androidHeadlessFromEnv() {
137047
137047
  return truthyEnv("ARGENT_EMULATOR_NO_WINDOW");
137048
137048
  }
137049
+ function androidGuestVulkanFromEnv() {
137050
+ return truthyEnv("ARGENT_EMULATOR_GUEST_VULKAN");
137051
+ }
137049
137052
 
137050
137053
  // ../tool-server/src/tools/devices/boot-device.ts
137051
137054
  init_device_info();
@@ -137798,6 +137801,9 @@ var zodSchema9 = external_exports.object({
137798
137801
  sound: external_exports.boolean().optional().describe(
137799
137802
  "Android only: boot the emulator with audio output enabled. Defaults to false: argent boots emulators MUTED so several agent-driven devices don't all play sound on the host machine; pass `true` when the task involves playing, hearing, or testing audio. Takes effect at boot: if the emulator is already running muted, add `force: true` to reboot it with sound. A boot snapshot saved in the other audio mode can't be reused, so the first boot after toggling is a slower cold boot. The `boot-sound` argent flag flips this default to true. Ignored on iOS/Vega<flag:harmonyos>/HarmonyOS</flag:harmonyos>/Electron, which argent never mutes."
137800
137803
  ),
137804
+ guestVulkan: external_exports.boolean().optional().describe(
137805
+ "Android on a macOS host only: boot the emulator with guest Vulkan. Defaults to false: guest Vulkan crashes Chrome's GPU process, which breaks Chrome and Custom Tab sign-in flows. Without it apps find no Vulkan device, although the guest still lists the Vulkan features, so a Vulkan-only renderer such as WebGPU (react-native-wgpu) draws nothing; pass `true` to test one. Takes effect at boot: an emulator already running in the other mode is reused with a `note`, and `force: true` reboots it. A boot snapshot saved in the other mode can't be reused, so the first boot after toggling is a slower cold boot. The `ARGENT_EMULATOR_GUEST_VULKAN` env var flips this default to true. Ignored on other hosts and platforms."
137806
+ ),
137801
137807
  headless: external_exports.boolean().optional().describe(
137802
137808
  "iOS only: boot the simulator core WITHOUT opening its GUI window (Simulator.app, or Device Hub on Xcode 27+). The device still streams via simulator-server; used by Argent Lens. Set the `ARGENT_SIMULATOR_NO_WINDOW` env var (1/true/yes) to force this host-wide without passing the flag per call (the iOS analog of `ARGENT_EMULATOR_NO_WINDOW`). Ignored on Android/Vega<flag:harmonyos>/HarmonyOS</flag:harmonyos>/Electron, which have no equivalent GUI step."
137803
137809
  ),
@@ -137865,14 +137871,27 @@ function selectGpuMode() {
137865
137871
  }
137866
137872
  return process.platform === "linux" ? "swiftshader" : "auto";
137867
137873
  }
137874
+ function guestVulkanArgs(guestVulkan) {
137875
+ if (process.platform !== "darwin") return [];
137876
+ return ["-feature", guestVulkan ? "Vulkan" : "-Vulkan"];
137877
+ }
137878
+ async function runningGuestVulkanNote(serial, guestVulkan) {
137879
+ if (process.platform !== "darwin") return void 0;
137880
+ const prop = await adbShell(serial, "getprop ro.boot.hardware.vulkan", { timeoutMs: 5e3 }).then((out) => out.trim()).catch(() => null);
137881
+ if (prop === null || prop !== "" === guestVulkan) return void 0;
137882
+ return guestVulkan ? "This emulator was already running without guest Vulkan, so apps find no Vulkan device. Pass `force: true` to reboot it with guest Vulkan." : "This emulator was already running with guest Vulkan, which can crash Chrome's GPU process. Pass `force: true` to reboot it without guest Vulkan.";
137883
+ }
137868
137884
  var BOOT_POLL_INTERVALS_MS = {
137869
137885
  serialByAvd: 1500,
137870
137886
  // findSerialByAvdName: re-scan when >1 new emulator appeared
137871
137887
  adbRegister: 1e3,
137872
137888
  // attemptBoot stage 2: re-scan adb devices for the new serial
137873
- earlyExit: 500
137889
+ earlyExit: 500,
137874
137890
  // createEarlyExitRacer: re-check the crash latch during a blocking adb call
137891
+ serialGone: 500
137892
+ // awaitEmulatorsGone: re-scan adb devices for a killed emulator
137875
137893
  };
137894
+ var EMULATOR_GONE_BUDGET_MS = 3e4;
137876
137895
  var FRAME_PROBE = "screencap -p 2>/dev/null | wc -c | awk '$1>20000{print 1;exit} {print 0}'";
137877
137896
  async function killEmulatorQuietly(serial, child) {
137878
137897
  if (serial) {
@@ -137952,18 +137971,28 @@ async function findSerialByAvdName(avdName, deadline) {
137952
137971
  }
137953
137972
  return null;
137954
137973
  }
137955
- async function listNewEmulatorSerials(before) {
137974
+ async function listEmulatorSerials() {
137956
137975
  const { stdout } = await runAdb(["devices"], { timeoutMs: 3e3 }).catch(() => ({
137957
137976
  stdout: "",
137958
137977
  stderr: ""
137959
137978
  }));
137960
- const lines = stdout.split("\n");
137961
137979
  const now = [];
137962
- for (const line of lines) {
137980
+ for (const line of stdout.split("\n")) {
137963
137981
  const m = line.match(/^(emulator-\d+)\s+/);
137964
137982
  if (m) now.push(m[1]);
137965
137983
  }
137966
- return now.filter((s) => !before.has(s));
137984
+ return now;
137985
+ }
137986
+ async function listNewEmulatorSerials(before) {
137987
+ return (await listEmulatorSerials()).filter((s) => !before.has(s));
137988
+ }
137989
+ async function awaitEmulatorsGone(serials) {
137990
+ const deadline = Date.now() + EMULATOR_GONE_BUDGET_MS;
137991
+ while (serials.length > 0 && Date.now() < deadline) {
137992
+ const listed = await listEmulatorSerials();
137993
+ if (!serials.some((s) => listed.includes(s))) return;
137994
+ await new Promise((r) => setTimeout(r, BOOT_POLL_INTERVALS_MS.serialGone));
137995
+ }
137967
137996
  }
137968
137997
  async function bootIos(udid, registry2, force, headless) {
137969
137998
  if (process.platform !== "darwin") {
@@ -138263,7 +138292,10 @@ async function bootAndroidImpl(params) {
138263
138292
  await ensureDep("adb");
138264
138293
  await ensureDep("emulator");
138265
138294
  const gpuMode = selectGpuMode();
138266
- const extraEmulatorArgs = androidHeadlessFromEnv() ? ["-no-window"] : [];
138295
+ const extraEmulatorArgs = [
138296
+ ...androidHeadlessFromEnv() ? ["-no-window"] : [],
138297
+ ...guestVulkanArgs(params.guestVulkan)
138298
+ ];
138267
138299
  const hardeningArgs = launchHardeningArgs(params.sound);
138268
138300
  for (const msg of linuxBootDiagnostics(params.avdName) ?? []) {
138269
138301
  console.warn(`[boot-device:linux] ${msg}`);
@@ -138312,26 +138344,31 @@ async function bootAndroidImpl(params) {
138312
138344
  });
138313
138345
  const existingDevices = await listAndroidDevices().catch(() => []);
138314
138346
  let hotBootFailureReason;
138347
+ const dropKilledEmulator = async (serial) => {
138348
+ await awaitEmulatorsGone([serial]);
138349
+ const refreshed = await listAndroidDevices().catch(() => existingDevices);
138350
+ existingDevices.splice(0, existingDevices.length, ...refreshed);
138351
+ };
138315
138352
  const alreadyRunning = existingDevices.find(
138316
138353
  (d) => d.isEmulator && d.avdName === params.avdName && d.state === "device"
138317
138354
  );
138318
138355
  if (alreadyRunning) {
138319
138356
  if (params.force) {
138320
138357
  await killEmulatorQuietly(alreadyRunning.serial);
138321
- const refreshed = await listAndroidDevices().catch(() => existingDevices);
138322
- existingDevices.splice(0, existingDevices.length, ...refreshed);
138358
+ await dropKilledEmulator(alreadyRunning.serial);
138323
138359
  } else {
138324
138360
  try {
138325
138361
  await assertScreencapAlive(alreadyRunning.serial);
138362
+ const note = await runningGuestVulkanNote(alreadyRunning.serial, params.guestVulkan);
138326
138363
  return {
138327
138364
  platform: "android",
138328
138365
  serial: alreadyRunning.serial,
138329
138366
  avdName: params.avdName,
138330
- booted: true
138367
+ booted: true,
138368
+ ...note ? { note } : {}
138331
138369
  };
138332
138370
  } catch (_err) {
138333
- const refreshed = await listAndroidDevices().catch(() => existingDevices);
138334
- existingDevices.splice(0, existingDevices.length, ...refreshed);
138371
+ await dropKilledEmulator(alreadyRunning.serial);
138335
138372
  }
138336
138373
  }
138337
138374
  }
@@ -138384,6 +138421,7 @@ async function bootAndroidImpl(params) {
138384
138421
  };
138385
138422
  } catch (err) {
138386
138423
  hotBootFailureReason = err instanceof Error ? err.message : String(err);
138424
+ await awaitEmulatorsGone(await listNewEmulatorSerials(serialsBefore));
138387
138425
  const refreshed = new Set(
138388
138426
  (await listAndroidDevices().catch(() => [])).map((d) => d.serial)
138389
138427
  );
@@ -138949,7 +138987,7 @@ function createBootDeviceTool(registry2) {
138949
138987
  description: `Start an iOS simulator, launch an Android emulator, start a Vega (Fire TV) Virtual Device, <flag:harmonyos>start a HarmonyOS emulator, </flag:harmonyos>or spawn an Electron app and wait until it is ready to accept interactions.
138950
138988
  Pick the platform by which argument you pass: 'udid' for an iOS simulator from list-devices, 'avdName' for an Android AVD (a serial is assigned automatically), 'vvdImage' for a Vega VVD (the 'vvdImage' of a vega device from list-devices, e.g. 'tv'), <flag:harmonyos>'harmonyInstance' for a HarmonyOS emulator instance (the 'name' of a harmony device from list-devices), </flag:harmonyos>or 'electronAppPath' for an Electron app (a CDP remote-debugging port is picked automatically, or pass 'electronPort' to fix one).
138951
138989
  Use at the start of a session once you have picked a target.
138952
- Returns a tagged payload: { platform: 'ios', udid, booted } or { platform: 'android', serial, avdName, booted } or { platform: 'vega', serial, vvdImage, booted } or <flag:harmonyos>{ platform: 'harmony', udid, instanceName, booted, note? } or </flag:harmonyos>{ platform: 'chromium', id, port, pid, booted } (an Electron app boots as a Chromium/CDP device).
138990
+ Returns a tagged payload: { platform: 'ios', udid, booted } or { platform: 'android', serial, avdName, booted, note? } or { platform: 'vega', serial, vvdImage, booted } or <flag:harmonyos>{ platform: 'harmony', udid, instanceName, booted, note? } or </flag:harmonyos>{ platform: 'chromium', id, port, pid, booted } (an Electron app boots as a Chromium/CDP device).
138953
138991
  Android boots take 2\u201310 minutes depending on machine and cold/warm state; the tool transparently hot-boots from the AVD's default_boot snapshot when usable and falls back to cold boot otherwise. Vega starts the single SDK-managed VVD via the vega CLI (~10s) and returns once it reports running. If an Android/Electron boot stage fails, the tool terminates the device it spawned so the next retry starts clean.<flag:harmonyos>
138954
138992
  HarmonyOS hands the instance to DevEco Studio's Emulator manager, then waits for it to register with 'hdc' and returns its connect key as 'udid', the id the interaction tools drive. The manager itself reports no readiness signal and never names a port, so an instance that is already running is left alone unless you pass 'force: true'; when the key could not be resolved the payload names the instance instead, and a 'note' is present whenever the boot left something unproven: no key and why, or a key that is not yet answering or could not be checked against the instance. Huawei serves the emulator images only within mainland China, so a host outside it usually has no instance to start; one that already has an image is started like any other.</flag:harmonyos>`,
138955
138993
  alwaysLoad: true,
@@ -139012,7 +139050,8 @@ HarmonyOS hands the instance to DevEco Studio's Emulator manager, then waits for
139012
139050
  // An explicit argument always wins; the `boot-sound` flag only moves
139013
139051
  // the default when the caller left `sound` unset. Read live per call
139014
139052
  // so `argent enable/disable boot-sound` applies without a restart.
139015
- sound: params.sound ?? isFlagEnabled("boot-sound")
139053
+ sound: params.sound ?? isFlagEnabled("boot-sound"),
139054
+ guestVulkan: params.guestVulkan ?? androidGuestVulkanFromEnv()
139016
139055
  });
139017
139056
  }
139018
139057
  if (hasVega) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swmansion/argent",
3
- "version": "0.30.2-next.17",
3
+ "version": "0.30.2-next.19",
4
4
  "mcpName": "io.github.software-mansion/argent",
5
5
  "description": "MCP server for iOS Simulator and Android Emulator control",
6
6
  "license": "Apache-2.0",