stim 1.14.0 → 1.16.0

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 (152) hide show
  1. package/README.md +2 -0
  2. package/dist/activity-BxwdD81S.mjs +609 -0
  3. package/dist/agent-device-usage-output-Dpih1Tfa.mjs +411 -0
  4. package/dist/{android-d6rGq1pA.mjs → android-B_NumVyQ.mjs} +169 -81
  5. package/dist/{android-LbPFRWVL.mjs → android-Ift4kVvt.mjs} +517 -413
  6. package/dist/android-cas-8U4DPqeR.mjs +167 -0
  7. package/dist/android-cas-compiler.mjs +2 -2
  8. package/dist/{android-XpvCrV7q.mjs → android-r2rBL8kV.mjs} +2 -2
  9. package/dist/{app-install-BpgSgkJj.mjs → app-install-CKWdbXIX.mjs} +128 -151
  10. package/dist/attempt-Bk2ouQVL.mjs +39 -0
  11. package/dist/budget-DjrURfQn.mjs +2700 -0
  12. package/dist/{build-plan-Dcr-3ie0.mjs → build-plan-DjeSb2Xl.mjs} +192 -653
  13. package/dist/build-slots-rUk77xqh.mjs +132 -0
  14. package/dist/{cache-manifest-C68QQ0ua.mjs → cache-manifest-DnUHBJaU.mjs} +2 -2
  15. package/dist/cache-manifest.mjs +1 -1
  16. package/dist/{chrome-BPCiC3ZX.mjs → chrome-BrcVWQyD.mjs} +1 -1
  17. package/dist/cli-BkYZ_6hy.mjs +51 -0
  18. package/dist/cli.mjs +6 -42
  19. package/dist/client-BTY686R6.mjs +1368 -0
  20. package/dist/collector-run.mjs +9 -195
  21. package/dist/{command-output-DVcpw6eE.mjs → command-output-DnJMdEY-.mjs} +1 -1
  22. package/dist/{config-lvc7d3C2.mjs → config-D0DbpZtM.mjs} +28 -29
  23. package/dist/{created-devices-CllH_8eb.mjs → created-devices-D5owjN4C.mjs} +2 -2
  24. package/dist/{dependency-state-DsT7aKeo.mjs → dependency-state-B6cMXZab.mjs} +2 -2
  25. package/dist/{deps-D9IcKqXS.mjs → deps-urbmfXig.mjs} +3 -3
  26. package/dist/{detached-entry-DJHoca49.mjs → detached-entry-CiTEgMR1.mjs} +1 -1
  27. package/dist/dev-client-CroAIjjj.mjs +43 -0
  28. package/dist/{device-CjuuAJjQ.mjs → device-DZQjF7H5.mjs} +8 -7
  29. package/dist/device-capacity-BHwxNBlu.mjs +136 -0
  30. package/dist/device-host-worker.d.mts +1 -0
  31. package/dist/device-host-worker.mjs +964 -0
  32. package/dist/device-ios-BadmeL2V.mjs +381 -0
  33. package/dist/{device-lease-5c3WrVRm.mjs → device-lease-D-hLqx1b.mjs} +43 -36
  34. package/dist/{device-pool-BiyDyPP-.mjs → device-lease-run-CqjShqWW.mjs} +6 -126
  35. package/dist/device-pool-CUaEWMMm.mjs +125 -0
  36. package/dist/{device-remote-D87zEzaq.mjs → device-remote-D5WVWqOq.mjs} +69 -163
  37. package/dist/{doctor-x9pnU9fq.mjs → doctor-Cimf-gGB.mjs} +241 -103
  38. package/dist/{error-diagnostics-C5HzcwCW.mjs → error-diagnostics-B9L4Ae0x.mjs} +15 -18
  39. package/dist/{exec-DK8KaVHK.mjs → exec-CaErVv7t.mjs} +5 -1
  40. package/dist/{gc-ko6mUAYD.mjs → gc-Bqy4bjEu.mjs} +599 -108
  41. package/dist/{android-cas-iZK3DOCr.mjs → gradle-BsO1MdsH.mjs} +8 -294
  42. package/dist/{guide-6R0VNFqI.mjs → guide-B_uyKTxW.mjs} +2511 -239
  43. package/dist/{guide-status-BZrFPIA_.mjs → guide-status-CjEZMAD4.mjs} +3 -3
  44. package/dist/hosted-client-a-WmqE2L.mjs +197 -0
  45. package/dist/hosted-ios-CIWelIqf.mjs +220 -0
  46. package/dist/hosted-logs-D5g041Q3.mjs +187 -0
  47. package/dist/hosted-macos-BSZyIb3y.mjs +135 -0
  48. package/dist/idle-DYVymG92.mjs +1099 -0
  49. package/dist/idle-shutdown-CTsDnAde.mjs +122 -0
  50. package/dist/in-use-DvBw0Kxo.mjs +130 -0
  51. package/dist/{ios-BwDPrnYQ.mjs → ios-BaInn3sW.mjs} +201 -10
  52. package/dist/ios-Cr1h5gnO.mjs +189 -0
  53. package/dist/{ios-P1f5nm2n.mjs → ios-aq3QoGI3.mjs} +1159 -633
  54. package/dist/{ios-device-BiOsSVzd.mjs → ios-device-Ee7sDXfp.mjs} +1 -1
  55. package/dist/{ios-device-DGglU3E_.mjs → ios-device-FfCN_C8p.mjs} +12 -7
  56. package/dist/ios-state-DS5UpYTH.mjs +48 -0
  57. package/dist/{launch-verify-Deg4tB3Z.mjs → launch-verify-2DBRizjo.mjs} +5 -5
  58. package/dist/logs-CHEVK8zD.mjs +371 -0
  59. package/dist/machines-BIBUdhpj.mjs +265 -0
  60. package/dist/macos-CSYEAFmF.mjs +473 -0
  61. package/dist/macos-run.d.mts +5 -0
  62. package/dist/macos-run.mjs +2 -0
  63. package/dist/maintenance-run.d.mts +4 -0
  64. package/dist/maintenance-run.mjs +254 -0
  65. package/dist/{metro-CcEo8Fl8.mjs → metro-BeK1ZPz1.mjs} +20 -120
  66. package/dist/metro-gateway-BJ-rxQAB.mjs +84 -0
  67. package/dist/{named-ports-Blg8IxOh.mjs → named-ports-Bk_YXEmE.mjs} +6 -59
  68. package/dist/native-run-NaBrRFtQ.mjs +147 -0
  69. package/dist/{native-runtime-DjBHRg5m.mjs → native-runtime-DBzRnEKS.mjs} +9 -5
  70. package/dist/{ndjson-CDY9IkVC.mjs → ndjson-PclOIwVY.mjs} +2 -2
  71. package/dist/offload-worker.d.mts +122 -0
  72. package/dist/offload-worker.mjs +606 -0
  73. package/dist/{ownership-DyYDF5ww.mjs → ownership-DiA16AsN.mjs} +3 -2
  74. package/dist/ownership-hCkPzGmr.mjs +571 -0
  75. package/dist/{page-BHUzTDFt.mjs → page-QF_Ke10s.mjs} +7 -2
  76. package/dist/{ports-BguS83ex.mjs → ports-R2k4Knz3.mjs} +3 -3
  77. package/dist/prebuild-D2fsca_Z.mjs +213 -0
  78. package/dist/preview-DhVbvPAZ.mjs +318 -0
  79. package/dist/process-identity-BmRm1qV0.mjs +12 -0
  80. package/dist/{project-CWlvEaeN.mjs → project-Du91Ilvc.mjs} +74 -19
  81. package/dist/pull-request-pxnDCQzn.mjs +322 -0
  82. package/dist/pull-requests.d.mts +120 -0
  83. package/dist/pull-requests.mjs +2 -0
  84. package/dist/recordings-BIXOzVnr.mjs +37 -0
  85. package/dist/{reload-BiniwsSZ.mjs → reload-rM9jV1fz.mjs} +109 -24
  86. package/dist/{remote-cache-CcR6o6lI.mjs → remote-cache-C8-V13XN.mjs} +5 -5
  87. package/dist/run-f4vI6EnK.mjs +119 -0
  88. package/dist/{server-bare-DGfgyJ5J.mjs → server-bare-CpPGmnmO.mjs} +33 -15
  89. package/dist/server-expo-CVqO2c35.mjs +2 -0
  90. package/dist/{server-expo-BVuwQ65v.mjs → server-expo-MC0rHlOC.mjs} +8 -12
  91. package/dist/settings-BBpMcDbD.mjs +25 -0
  92. package/dist/{settings-B-fsC-Om.mjs → settings-ZkDzkzvB.mjs} +22 -10
  93. package/dist/settings-oTSkkNrM.mjs +1439 -0
  94. package/dist/settings.schema.json +845 -97
  95. package/dist/{simslim-DrDN4WuV.mjs → simslim-B8-7AoTI.mjs} +8 -132
  96. package/dist/{slot-launch-D5t8EiMf.mjs → slot-launch-B8VrHxn1.mjs} +11 -8
  97. package/dist/{spawn-claims-Drw_o-Fb.mjs → spawn-claims-D0XfSe8u.mjs} +1 -1
  98. package/dist/spawn-entry-jw19m8Nl.mjs +17 -0
  99. package/dist/stage-CzaiwFsI.mjs +143 -0
  100. package/dist/start-CX8Ig-U2.mjs +2 -0
  101. package/dist/{start-s4RXp1wy.mjs → start-ClDfwXa4.mjs} +116 -65
  102. package/dist/state-3DwSQIBK.mjs +41 -0
  103. package/dist/state-B4PQFXMW.d.mts +12 -0
  104. package/dist/{state-Dk9RVXI7.mjs → state-C1jB7Q8q.mjs} +70 -8
  105. package/dist/{state-DHqvsbqC.mjs → state-DcW6vOd_.mjs} +7 -4
  106. package/dist/stats-CEZ_i5je.mjs +77 -0
  107. package/dist/{status-BgAuxESg.mjs → status-BuvBFeII.mjs} +118 -20
  108. package/dist/{status-BsPAnfOI.mjs → status-Cs7VLg9D.mjs} +542 -283
  109. package/dist/status-DlKvVJBE.mjs +49 -0
  110. package/dist/stdout-reader-C8yjre1x.mjs +23 -0
  111. package/dist/stim-desktop-Cnz8CPnl.mjs +63 -0
  112. package/dist/stim-footprint.swift +32 -0
  113. package/dist/{stim-installations-CINXy_su.mjs → stim-installations-QMEI32v1.mjs} +1 -1
  114. package/dist/stop-B0Yxtmiy.mjs +48 -0
  115. package/dist/{stop-BzreOWJp.mjs → stop-Bffozsac.mjs} +83 -16
  116. package/dist/{stop-BWIF_6BK.mjs → stop-Dt_dM16A.mjs} +2 -2
  117. package/dist/stop-cause-CidxIqup.mjs +97 -0
  118. package/dist/supervisor-run.d.mts +43 -11
  119. package/dist/supervisor-run.mjs +363 -24
  120. package/dist/support-CAP8qDUF.mjs +316 -0
  121. package/dist/support-D4aYJniO.mjs +201 -0
  122. package/dist/teardown-CL6vDs2Y.mjs +727 -0
  123. package/dist/{xcode-Cp5KvK9x.mjs → toolchain-DkUflduf.mjs} +364 -14
  124. package/dist/trigger-Bemu8ytw.mjs +65 -0
  125. package/dist/trigger-CXts6qt1.mjs +2 -0
  126. package/dist/warm-progress-CpGv3Wb6.mjs +732 -0
  127. package/dist/{web-BsQs1WlO.mjs → web-DWUaMp8P.mjs} +37 -21
  128. package/dist/web-run.mjs +310 -13
  129. package/dist/workspace-process-lock-BL7Q0HCO.mjs +57 -0
  130. package/dist/workspace-state-B8-aJ1_5.mjs +371 -0
  131. package/dist/workspaces-BV4pdHFz.mjs +306 -0
  132. package/dist/{worktree-UrQWXSeb.mjs → worktree-K-xj9Db0.mjs} +625 -256
  133. package/dist/worktree-jnG8Iph5.mjs +2 -0
  134. package/package.json +10 -5
  135. package/shim/bundle-response.cjs +28 -12
  136. package/dist/activity-CYVDcrt3.mjs +0 -356
  137. package/dist/budget-CGDkhvv_.mjs +0 -1745
  138. package/dist/build-progress-DxKpwnaS.mjs +0 -247
  139. package/dist/idle-DXtx3RbX.mjs +0 -191
  140. package/dist/in-use-DlqBDKqY.mjs +0 -249
  141. package/dist/logs-Civ4gbBH.mjs +0 -486
  142. package/dist/merge-state-ytFQCeh4.mjs +0 -262
  143. package/dist/server-expo-DIHDhvGg.mjs +0 -2
  144. package/dist/settings-D61iNOsB.mjs +0 -629
  145. package/dist/start-Cetfvy8l.mjs +0 -2
  146. package/dist/stats-CFpFGlJV.mjs +0 -62
  147. package/dist/stats-CX1Z8Tca.mjs +0 -368
  148. package/dist/stim-desktop-CXt8-Z3J.mjs +0 -40
  149. package/dist/stop-cause-hjGBOZTG.mjs +0 -1231
  150. package/dist/worktree-BVrHsYZG.mjs +0 -752
  151. package/dist/worktree-CHWWvm5v.mjs +0 -2
  152. package/dist/{metro-store-DzeWkfqW.mjs → metro-store-CDDZHtPl.mjs} +1 -1
@@ -1,22 +1,26 @@
1
- import { T as upsertProject, p as getProject } from "./config-lvc7d3C2.mjs";
2
- import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
- import { o as readNdjsonGenerations } from "./ndjson-CDY9IkVC.mjs";
4
- import { M as workspaceLogsDir, c as resolveProjectMetro, f as reserveMetroPort, g as recordWorkspaceUse, h as readWorkspaceState, k as workspaceDir } from "./metro-CcEo8Fl8.mjs";
5
- import { s as gitCommonDir, y as repoRoot } from "./worktree-BVrHsYZG.mjs";
6
- import { i as findCommandWorkspace, r as detectIsExpo, s as isPackageResolvable } from "./project-CWlvEaeN.mjs";
7
- import { n as windowsLauncherArgs } from "./detached-entry-DJHoca49.mjs";
8
- import { a as withWorkspaceProcessLock, i as reserveBrowserPort, n as getNamedPort } from "./named-ports-Blg8IxOh.mjs";
9
- import { f as refuseNoProject, l as phaseLine } from "./command-output-DVcpw6eE.mjs";
10
- import { P as settingShapeErrors, R as webSettings, a as SETTING_SHAPE_REMEDY, j as resolveSettings } from "./settings-D61iNOsB.mjs";
11
- import { b as teardownBrowserHeld, u as resolveSupervisorTarget } from "./stop-cause-hjGBOZTG.mjs";
12
- import { a as readWebRecord, f as webSupervisorLogFile, o as updateWebRecord, r as cdpEndpoint, u as webLogFile } from "./state-Dk9RVXI7.mjs";
13
- import { a as findChrome, t as CHROME_INSTALL_REMEDY } from "./chrome-BPCiC3ZX.mjs";
14
- import { c as readMetroRecords } from "./launch-verify-Deg4tB3Z.mjs";
15
- import { f as spawnEntry } from "./start-s4RXp1wy.mjs";
16
- import { a as sleep, n as ensureWorkspaceStorageSafely } from "./native-runtime-DjBHRg5m.mjs";
17
- import { n as sendToOwnedPage, t as liveWebRecord } from "./page-BHUzTDFt.mjs";
18
- import { mkdirSync, openSync } from "node:fs";
1
+ import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
+ import { E as upsertProject, p as getProject } from "./config-D0DbpZtM.mjs";
3
+ import { n as windowsLauncherArgs } from "./detached-entry-CiTEgMR1.mjs";
4
+ import { f as refuseNoProject, l as phaseLine } from "./command-output-DnJMdEY-.mjs";
5
+ import { P as resolveSettings, R as settingShapeErrors, S as metroPortSetting, U as webSettings, a as SETTING_SHAPE_REMEDY, gt as repoRoot, it as gitCommonDir } from "./settings-oTSkkNrM.mjs";
6
+ import { a as findCommandWorkspace, c as isPackageResolvable, r as detectIsExpo } from "./project-Du91Ilvc.mjs";
7
+ import { l as resolveProjectMetro } from "./metro-BeK1ZPz1.mjs";
8
+ import { i as recordWorkspaceUse, r as readWorkspaceState, v as workspaceDir, x as workspaceLogsDir } from "./workspace-state-B8-aJ1_5.mjs";
9
+ import { d as webLogFile, o as readWebRecord, p as webSupervisorLogFile, r as cdpEndpoint, s as updateWebRecord } from "./state-C1jB7Q8q.mjs";
10
+ import { a as workspaceLinks, i as workspaceLinkLine } from "./stim-desktop-Cnz8CPnl.mjs";
11
+ import { r as teardownBrowserHeld } from "./teardown-CL6vDs2Y.mjs";
12
+ import { t as withWorkspaceProcessLock } from "./workspace-process-lock-BL7Q0HCO.mjs";
13
+ import { i as reserveBrowserPort, n as getNamedPort } from "./named-ports-Bk_YXEmE.mjs";
14
+ import { t as resolveSupervisorTarget } from "./ownership-hCkPzGmr.mjs";
15
+ import { o as readNdjsonGenerations } from "./ndjson-PclOIwVY.mjs";
16
+ import { a as findChrome, t as CHROME_INSTALL_REMEDY } from "./chrome-BrcVWQyD.mjs";
17
+ import { t as spawnEntry } from "./spawn-entry-jw19m8Nl.mjs";
18
+ import { c as readMetroRecords } from "./launch-verify-2DBRizjo.mjs";
19
+ import { o as resolveWorkspaceMetroPort } from "./start-ClDfwXa4.mjs";
20
+ import { a as sleep, n as ensureWorkspaceStorageSafely } from "./native-runtime-DBzRnEKS.mjs";
21
+ import { n as sendToOwnedPage, t as liveWebRecord } from "./page-QF_Ke10s.mjs";
19
22
  import { randomUUID } from "node:crypto";
23
+ import { mkdirSync, openSync } from "node:fs";
20
24
  import chalk from "chalk";
21
25
  //#region src/web/launch.ts
22
26
  const VERIFY_WAIT_MS = 2e4;
@@ -243,13 +247,19 @@ async function runWeb({ root, headed, note }) {
243
247
  const [shapeError] = settingShapeErrors(settings);
244
248
  if (shapeError) return failure("STIM_BAD_ARG", shapeError, SETTING_SHAPE_REMEDY);
245
249
  const web = webSettings(settings);
250
+ const usesMetro = web.url === null || web.url.includes("{port:metro}");
251
+ const pin = usesMetro ? metroPortSetting(root) : null;
252
+ if (pin?.error) return failure("STIM_BAD_ARG", pin.error, SETTING_SHAPE_REMEDY);
246
253
  const chrome = findChrome();
247
254
  if (!chrome) return failure("STIM_WEB_NO_CHROME", "Google Chrome or Chromium is not installed.", CHROME_INSTALL_REMEDY);
248
- const usesMetro = web.url === null || web.url.includes("{port:metro}");
249
255
  if (web.url === null && !detectIsExpo(root)) return failure("STIM_WEB_NO_URL", "This is not an Expo app, so Stim does not know which page to open.", "Start your web dev server on a named port, then set the page: `stim settings set web.url 'http://localhost:{port:web}/' --scope workspace`. See `stim guide web`.");
250
256
  if (web.url === null && !isPackageResolvable(root, "react-native-web")) return failure("STIM_WEB_DEPS_MISSING", "This Expo app cannot render on the web: react-native-web is not installed.", `Run \`${EXPO_WEB_DEPENDENCIES}\` and \`stim start\`, then run \`stim web\` again.`);
251
257
  let metroPort = getProject(root)?.metroPort ?? null;
252
- if (usesMetro && metroPort === null) metroPort = await reserveMetroPort(root);
258
+ if (usesMetro && (pin?.port !== null || metroPort === null)) {
259
+ const result = await resolveWorkspaceMetroPort(root, note, "web");
260
+ if (typeof result !== "number") return failure(result.code, result.message, result.remedy);
261
+ metroPort = result;
262
+ }
253
263
  const metro = usesMetro && metroPort !== null ? await resolveProjectMetro(metroPort, root) : null;
254
264
  const supervisor = resolveSupervisorTarget({
255
265
  state: readWorkspaceState(root)?.supervisor,
@@ -351,6 +361,7 @@ async function runWeb({ root, headed, note }) {
351
361
  viewport: record.viewport,
352
362
  profile: record.profile,
353
363
  cdpEndpoint: live ? cdpEndpoint(live.cdpPort) : null,
364
+ targetId: live?.targetId ?? null,
354
365
  reused: launch.reused,
355
366
  launched: verdict.launched,
356
367
  metroPort: usesMetro ? metroPort : null,
@@ -382,12 +393,17 @@ function webCommand(program) {
382
393
  }
383
394
  const { facts, remedy } = result;
384
395
  if (remedy) printNote(chalk.yellow(phaseLine("launch", remedy)));
396
+ const links = workspaceLinks(root, { platform: "web" });
385
397
  if (json) {
386
- console.log(JSON.stringify(facts));
398
+ console.log(JSON.stringify(links ? {
399
+ ...facts,
400
+ links
401
+ } : facts));
387
402
  return;
388
403
  }
389
404
  const launched = facts.launched === true ? chalk.green("loaded") : chalk.yellow(facts.launched === "bundling" ? "bundling" : "unverified");
390
405
  console.log(facts.running ? `${facts.url} ${launched} in ${facts.version ?? "Chrome"} (pid ${facts.pid}, ${facts.headless ? "headless" : "headed"}, ${facts.viewport}). DevTools: ${facts.cdpEndpoint}. Logs: stim logs --errors.` : `${facts.url} ${launched}, and the owned Chrome is no longer running. Run stim logs --errors, then stim web again.`);
406
+ if (links) console.error(chalk.dim(workspaceLinkLine(links)));
391
407
  });
392
408
  }
393
409
  //#endregion
package/dist/web-run.mjs CHANGED
@@ -1,16 +1,17 @@
1
- import { A as ownership_claim_exports, O as process_identity_exports } from "./config-lvc7d3C2.mjs";
2
- import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
- import { r as createNdjsonWriter } from "./ndjson-CDY9IkVC.mjs";
4
- import { l as signalProcessTree } from "./metro-CcEo8Fl8.mjs";
5
- import { t as relaunchWithLogFile } from "./detached-entry-DJHoca49.mjs";
6
- import { r as recordCreatedDevice } from "./created-devices-CllH_8eb.mjs";
7
- import { c as webDir, d as webProfileDir, g as removeSingletonFiles, h as liveProfileHolder, i as clearWebRecord, m as chromeProcessState, n as browserLogFile, o as updateWebRecord, p as writeWebRecord, s as webClaimRoot, u as webLogFile } from "./state-Dk9RVXI7.mjs";
1
+ import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
+ import { n as ownership_claim_exports, t as process_identity_exports } from "./process-identity-BmRm1qV0.mjs";
3
+ import { t as relaunchWithLogFile } from "./detached-entry-CiTEgMR1.mjs";
4
+ import { u as signalProcessTree } from "./metro-BeK1ZPz1.mjs";
5
+ import { _ as removeSingletonFiles, c as webClaimRoot, d as webLogFile, f as webProfileDir, g as liveProfileHolder, h as chromeProcessState, i as clearWebRecord, l as webDir, m as writeWebRecord, n as browserLogFile, s as updateWebRecord } from "./state-C1jB7Q8q.mjs";
6
+ import { c as probeWebDrivers } from "./activity-BxwdD81S.mjs";
7
+ import { r as recordCreatedDevice } from "./created-devices-D5owjN4C.mjs";
8
+ import { r as createNdjsonWriter } from "./ndjson-PclOIwVY.mjs";
8
9
  import { t as connectOwnedBrowser } from "./cdp-Dq_ypAlJ.mjs";
9
- import { i as chromeArgs, n as DESKTOP_PAGE, r as PHONE_SCREEN } from "./chrome-BPCiC3ZX.mjs";
10
+ import { i as chromeArgs, n as DESKTOP_PAGE, r as PHONE_SCREEN } from "./chrome-BrcVWQyD.mjs";
10
11
  import { existsSync, mkdirSync, openSync, realpathSync } from "node:fs";
12
+ import { isAbsolute, resolve } from "node:path";
11
13
  import { LOG_ROTATE_BYTES } from "@stim-cli/core";
12
14
  import { WEB_VIEWPORTS } from "@stim-cli/core/state";
13
- import { isAbsolute, resolve } from "node:path";
14
15
  import { fileURLToPath } from "node:url";
15
16
  //#region src/web/events.ts
16
17
  const MAX_FRAMES = 20;
@@ -124,6 +125,220 @@ function networkFailureRecord({ url, method, errorText, status, canceled, docume
124
125
  };
125
126
  }
126
127
  //#endregion
128
+ //#region src/web/input.ts
129
+ /** The isolated world the input listener runs in, so the page's own scripts cannot see or replace it. */
130
+ const INPUT_WORLD = "stim";
131
+ const INPUT_BINDING = "__stimInput";
132
+ /**
133
+ * Observes trusted clicks, key presses, text input and wheel scrolls in the page and reports them through the
134
+ * binding, coalesced: one action per click, per typing burst in a field, per repeated key and per scroll burst.
135
+ * Typed text is never reported, only its length. Input up to 3 seconds after a `stim-takeover` event on the window,
136
+ * which Stim's Take over dispatches while it sends input, is dropped; so is input just before one, since the event
137
+ * and the input reach the page by different routes.
138
+ */
139
+ const INPUT_LISTENER = `(() => {
140
+ const send = globalThis.${INPUT_BINDING};
141
+ if (typeof send !== 'function' || window !== window.top) return;
142
+ const BURST_MS = 1000, FLUSH_MS = 1000, MAX_WAIT_MS = 3000, TAKEOVER_MS = 3000, MAX_ACTIONS = 50;
143
+ let pending = [], marks = [], dropped = 0, lastKeyAt = -Infinity, timer = 0, firstAt = 0;
144
+ const clip = (value, max) => {
145
+ const text = String(value || '').replace(/\\s+/g, ' ').trim();
146
+ return text.length > max ? text.slice(0, max - 3) + '...' : text;
147
+ };
148
+ const ROLES = ['button', 'link', 'tab', 'menuitem', 'checkbox', 'radio', 'switch', 'option', 'textbox', 'combobox', 'searchbox'];
149
+ const INTERACTIVE = 'a[href],button,input,textarea,select,label,summary,[contenteditable=""],[contenteditable=true],[data-testid],'
150
+ + ROLES.map((role) => '[role=' + role + ']').join(',');
151
+ const describe = (node, withText) => {
152
+ if (!(node instanceof Element)) return null;
153
+ const el = node.closest(INTERACTIVE) || node;
154
+ const tag = el.tagName.toLowerCase();
155
+ let out = tag;
156
+ if (el.id) out += '#' + clip(el.id, 40);
157
+ if (tag === 'input') out += '[type=' + (el.getAttribute('type') || 'text') + ']';
158
+ const role = el.getAttribute('role');
159
+ if (role) out += '[role=' + clip(role, 20) + ']';
160
+ const testId = el.getAttribute('data-testid');
161
+ if (testId) out += '[data-testid=' + clip(testId, 40) + ']';
162
+ const field = tag === 'input' || tag === 'textarea' || tag === 'select' || el.isContentEditable;
163
+ const editable = node.isContentEditable || el.querySelector('input,textarea,select,[contenteditable]') !== null;
164
+ const name = el.getAttribute('aria-label') || (field
165
+ ? el.getAttribute('placeholder') || el.getAttribute('name')
166
+ : withText && !editable ? el.textContent : '');
167
+ const label = clip(name, 40);
168
+ return label ? out + ' "' + label.replace(/"/g, "'") + '"' : out;
169
+ };
170
+ const flush = () => {
171
+ clearTimeout(timer);
172
+ timer = 0;
173
+ const now = performance.now();
174
+ marks = marks.filter((at) => at > now - TAKEOVER_MS - MAX_WAIT_MS - BURST_MS);
175
+ const actions = pending
176
+ .filter((action) => !marks.some((at) => at >= action.start - TAKEOVER_MS && at <= action.end + 500))
177
+ .map(({ start, end, ...action }) => ({ ...action, at: Math.round(performance.timeOrigin + start) }));
178
+ const lost = dropped;
179
+ pending = [];
180
+ dropped = 0;
181
+ if (actions.length || lost) send(JSON.stringify({ actions, dropped: lost }));
182
+ };
183
+ const add = (action, merge) => {
184
+ const now = performance.now();
185
+ const last = pending[pending.length - 1];
186
+ if (last && last.command === action.command && now - last.end < BURST_MS && merge(last)) {
187
+ last.end = now;
188
+ } else if (pending.length >= MAX_ACTIONS) {
189
+ dropped += 1;
190
+ } else {
191
+ if (!pending.length) {
192
+ firstAt = now;
193
+ send('started');
194
+ }
195
+ pending.push({ ...action, start: now, end: now });
196
+ }
197
+ clearTimeout(timer);
198
+ timer = setTimeout(flush, Math.max(0, Math.min(FLUSH_MS, firstAt + MAX_WAIT_MS - now)));
199
+ };
200
+ const typed = (target, characters) =>
201
+ add({ command: 'type', target, characters }, (last) => last.target === target && (last.characters += characters, true));
202
+ const origin = (event) => event.composedPath()[0] || event.target;
203
+ addEventListener('stim-takeover', () => marks.push(performance.now()), true);
204
+ addEventListener('beforeunload', flush, true);
205
+ addEventListener('click', (event) => {
206
+ if (!event.isTrusted) return;
207
+ add({ command: 'click', target: describe(origin(event), true), x: Math.round(event.clientX), y: Math.round(event.clientY) }, () => false);
208
+ }, true);
209
+ addEventListener('keydown', (event) => {
210
+ if (!event.isTrusted || event.isComposing) return;
211
+ const key = event.key;
212
+ if (!key || key === 'Shift' || key === 'Control' || key === 'Alt' || key === 'Meta' || key === 'CapsLock') return;
213
+ if ([...key].length === 1 || key === 'Enter') lastKeyAt = performance.now();
214
+ const target = describe(origin(event), false);
215
+ const modifiers = (event.ctrlKey ? 'Control+' : '') + (event.metaKey ? 'Meta+' : '');
216
+ if ([...key].length === 1 && !modifiers) {
217
+ typed(target, 1);
218
+ return;
219
+ }
220
+ const name = modifiers + (event.altKey ? 'Alt+' : '') + (key === ' ' ? 'Space' : key);
221
+ add({ command: 'press', target, key: name, count: 1 }, (last) => last.key === name && last.target === target && (last.count += 1, true));
222
+ }, true);
223
+ addEventListener('input', (event) => {
224
+ if (!event.isTrusted || typeof event.inputType !== 'string' || !event.inputType.startsWith('insert')) return;
225
+ if (performance.now() - lastKeyAt < 100) return;
226
+ typed(describe(origin(event), false), typeof event.data === 'string' && event.data ? event.data.length : 1);
227
+ }, true);
228
+ addEventListener('wheel', (event) => {
229
+ if (!event.isTrusted) return;
230
+ const scale = event.deltaMode === 1 ? 16 : event.deltaMode === 2 ? innerHeight : 1;
231
+ const deltaX = event.deltaX * scale, deltaY = event.deltaY * scale;
232
+ add({ command: 'scroll', deltaX, deltaY, x: Math.round(event.clientX), y: Math.round(event.clientY) }, (last) => {
233
+ last.deltaX += deltaX;
234
+ last.deltaY += deltaY;
235
+ return true;
236
+ });
237
+ }, { capture: true, passive: true });
238
+ })();`;
239
+ const num = (value) => typeof value === "number" && Number.isFinite(value);
240
+ const text = (value) => typeof value === "string" && value ? value.slice(0, 200) : null;
241
+ function parseAction(value) {
242
+ if (!value || typeof value !== "object") return null;
243
+ const entry = value;
244
+ if (!num(entry.at)) return null;
245
+ const { at } = entry;
246
+ switch (entry.command) {
247
+ case "click": return num(entry.x) && num(entry.y) ? {
248
+ command: "click",
249
+ at,
250
+ target: text(entry.target),
251
+ x: entry.x,
252
+ y: entry.y
253
+ } : null;
254
+ case "type": return num(entry.characters) ? {
255
+ command: "type",
256
+ at,
257
+ target: text(entry.target),
258
+ characters: entry.characters
259
+ } : null;
260
+ case "press": {
261
+ const key = text(entry.key);
262
+ return key && num(entry.count) ? {
263
+ command: "press",
264
+ at,
265
+ target: text(entry.target),
266
+ key,
267
+ count: entry.count
268
+ } : null;
269
+ }
270
+ case "scroll": return num(entry.deltaX) && num(entry.deltaY) && num(entry.x) && num(entry.y) ? {
271
+ command: "scroll",
272
+ at,
273
+ deltaX: entry.deltaX,
274
+ deltaY: entry.deltaY,
275
+ x: entry.x,
276
+ y: entry.y
277
+ } : null;
278
+ default: return null;
279
+ }
280
+ }
281
+ /** The batch the input listener sent, keeping only well-formed actions. */
282
+ function parseInputBatch(payload) {
283
+ let parsed;
284
+ try {
285
+ parsed = JSON.parse(payload);
286
+ } catch {
287
+ return {
288
+ actions: [],
289
+ dropped: 0
290
+ };
291
+ }
292
+ const batch = parsed ?? {};
293
+ return {
294
+ actions: Array.isArray(batch.actions) ? batch.actions.map(parseAction).filter((action) => action !== null) : [],
295
+ dropped: num(batch.dropped) && batch.dropped > 0 ? batch.dropped : 0
296
+ };
297
+ }
298
+ function scrollSummary(deltaX, deltaY) {
299
+ const parts = [Math.round(deltaY) ? `${deltaY > 0 ? "down" : "up"} ${Math.abs(Math.round(deltaY))}px` : "", Math.round(deltaX) ? `${deltaX > 0 ? "right" : "left"} ${Math.abs(Math.round(deltaX))}px` : ""].filter(Boolean);
300
+ return parts.length ? `Scrolled ${parts.join(" and ")}` : "Scrolled";
301
+ }
302
+ function summary(action) {
303
+ switch (action.command) {
304
+ case "click": return `Clicked ${action.target ?? "the page"}`;
305
+ case "type": return `Typed ${action.characters} character${action.characters === 1 ? "" : "s"} into ${action.target ?? "the page"}`;
306
+ case "press": return `Pressed ${action.key}${action.count > 1 ? ` x${action.count}` : ""}${action.target ? ` in ${action.target}` : ""}`;
307
+ case "scroll": return scrollSummary(action.deltaX, action.deltaY);
308
+ }
309
+ }
310
+ /**
311
+ * The `agent` records for input the DevTools client `driver` sent to the owned page `targetId`, in the shape of
312
+ * agent-device's action records.
313
+ */
314
+ function webAgentRecords(batch, targetId, driver, now) {
315
+ const base = {
316
+ src: "agent",
317
+ level: "info",
318
+ event: "agent_action",
319
+ platform: "web",
320
+ deviceId: targetId,
321
+ driver
322
+ };
323
+ const records = batch.actions.map((action) => {
324
+ const { at, command, ...details } = action;
325
+ return {
326
+ ts: at,
327
+ ...base,
328
+ msg: summary(action),
329
+ command,
330
+ details
331
+ };
332
+ });
333
+ if (batch.dropped) records.push({
334
+ ts: now,
335
+ ...base,
336
+ msg: `${batch.dropped} more input event${batch.dropped === 1 ? " was" : "s were"} not recorded`,
337
+ command: "input"
338
+ });
339
+ return records;
340
+ }
341
+ //#endregion
127
342
  //#region src/web/run.ts
128
343
  const USAGE = "Usage: web-run --root <path> --chrome <path> --url <url> --port <n> [--launch-id <id>] [--headed] [--viewport desktop|phone] [--ignore-certificate-errors]";
129
344
  function parseArgs(argv) {
@@ -167,6 +382,7 @@ function parseArgs(argv) {
167
382
  };
168
383
  }
169
384
  const DEVTOOLS_WAIT_MS = 2e4;
385
+ const ROUTE_THROTTLE_MS = 250;
170
386
  const CHROME_EXIT_WAIT_MS = 5e3;
171
387
  const POLL_MS = 50;
172
388
  const sleep = (ms) => new Promise((done) => setTimeout(done, ms));
@@ -176,6 +392,19 @@ function describe(error) {
176
392
  function isWebBundle(url) {
177
393
  return /\.bundle(?:\?|$)/.test(url) && /[?&]platform=web(?:&|$)/.test(url);
178
394
  }
395
+ function createDriverProbe(port, ownPids) {
396
+ let running = null;
397
+ return () => {
398
+ running ??= probeWebDrivers({
399
+ port,
400
+ ownPids
401
+ }).catch(() => null).then((drivers) => {
402
+ running = null;
403
+ return drivers?.[0] ?? null;
404
+ });
405
+ return running;
406
+ };
407
+ }
179
408
  async function runWebSupervisor(options, { onExit = (code) => process.exit(code), stderr = (line) => console.error(line) } = {}) {
180
409
  const root = realpathSync(options.root);
181
410
  mkdirSync(webDir(root), { recursive: true });
@@ -346,6 +575,22 @@ async function runWebSupervisor(options, { onExit = (code) => process.exit(code)
346
575
  flatten: true
347
576
  });
348
577
  const requests = /* @__PURE__ */ new Map();
578
+ const driver = createDriverProbe(options.port, [process.pid, pid]);
579
+ let batchDriver = null;
580
+ let reloading = false;
581
+ let route = {
582
+ written: null,
583
+ latest: null,
584
+ at: 0,
585
+ timer: null
586
+ };
587
+ const writeRoute = () => {
588
+ route.timer = null;
589
+ if (route.latest === null || route.latest === route.written) return;
590
+ route.written = route.latest;
591
+ route.at = Date.now();
592
+ log("info", "web_route", `route changed to ${route.latest}`, { url: route.latest });
593
+ };
349
594
  connection.onEvent((event) => {
350
595
  if (event.method === "Target.targetDestroyed" && event.params.targetId === targetId && !stopping) {
351
596
  stopChrome().then(() => finish(0, "info", "web_page_closed", "the owned page was closed; closed Chrome"));
@@ -369,6 +614,35 @@ async function runWebSupervisor(options, { onExit = (code) => process.exit(code)
369
614
  if (record) writer.write(record);
370
615
  return;
371
616
  }
617
+ case "Runtime.bindingCalled": {
618
+ if (params.name !== "__stimInput") return;
619
+ if (params.payload === "started") {
620
+ batchDriver = driver();
621
+ return;
622
+ }
623
+ const batch = parseInputBatch(String(params.payload ?? ""));
624
+ const attributed = batchDriver ?? driver();
625
+ batchDriver = null;
626
+ if (!batch.actions.length && !batch.dropped) return;
627
+ (async () => {
628
+ const attached = await attributed;
629
+ if (!attached) return;
630
+ for (const record of webAgentRecords(batch, targetId, attached.tool, Date.now())) writer.write(record);
631
+ })();
632
+ return;
633
+ }
634
+ case "Page.frameStartedNavigating":
635
+ if (params.frameId === targetId) reloading = String(params.navigationType).startsWith("reload");
636
+ return;
637
+ case "Page.navigatedWithinDocument": {
638
+ if (params.frameId !== targetId || typeof params.url !== "string") return;
639
+ route.latest = params.url;
640
+ if (route.timer) return;
641
+ const wait = route.at + ROUTE_THROTTLE_MS - Date.now();
642
+ if (wait <= 0) writeRoute();
643
+ else route.timer = setTimeout(writeRoute, wait);
644
+ return;
645
+ }
372
646
  case "Page.loadEventFired":
373
647
  log("info", "web_page_loaded", "the page fired its load event");
374
648
  return;
@@ -380,10 +654,21 @@ async function runWebSupervisor(options, { onExit = (code) => process.exit(code)
380
654
  method: request.method,
381
655
  document
382
656
  });
383
- if (document) log("info", "web_navigation", `navigating to ${request.url}`, {
384
- url: request.url,
385
- marker: true
386
- });
657
+ if (document) {
658
+ if (route.timer) clearTimeout(route.timer);
659
+ route = {
660
+ written: null,
661
+ latest: null,
662
+ at: 0,
663
+ timer: null
664
+ };
665
+ log("info", "web_navigation", `${reloading ? "reloading" : "navigating to"} ${request.url}`, {
666
+ url: request.url,
667
+ marker: true,
668
+ ...reloading ? { reload: true } : {}
669
+ });
670
+ reloading = false;
671
+ }
387
672
  return;
388
673
  }
389
674
  case "Network.responseReceived": {
@@ -424,6 +709,18 @@ async function runWebSupervisor(options, { onExit = (code) => process.exit(code)
424
709
  "Network",
425
710
  "Page"
426
711
  ]) await connection.send(`${domain}.enable`, {}, sessionId);
712
+ try {
713
+ await connection.send("Runtime.addBinding", {
714
+ name: INPUT_BINDING,
715
+ executionContextName: INPUT_WORLD
716
+ }, sessionId);
717
+ await connection.send("Page.addScriptToEvaluateOnNewDocument", {
718
+ source: INPUT_LISTENER,
719
+ worldName: INPUT_WORLD
720
+ }, sessionId);
721
+ } catch (error) {
722
+ log("warn", "web_input_unobserved", `agent input on the page is not recorded: ${describe(error)}`);
723
+ }
427
724
  if (options.viewport === "phone") {
428
725
  await connection.send("Emulation.setDeviceMetricsOverride", {
429
726
  ...PHONE_SCREEN,
@@ -0,0 +1,57 @@
1
+ import { n as ownership_claim_exports } from "./process-identity-BmRm1qV0.mjs";
2
+ import { n as declareSpawnsOn, o as stopDeclaringSpawnsOn } from "./spawn-claims-D0XfSe8u.mjs";
3
+ import { join } from "node:path";
4
+ //#region src/engine/workspace-process-lock.ts
5
+ const DEFAULT_WAIT_MS = 6e4;
6
+ const POLL_MS = 25;
7
+ const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
8
+ function workspaceProcessLockError(err) {
9
+ const code = err?.code;
10
+ if (code === "STIM_LOCK_REFUSED") return "refused";
11
+ if (code === "STIM_LOCK_TIMEOUT") return "timeout";
12
+ return null;
13
+ }
14
+ function workspaceProcessLockPath(root, name, external) {
15
+ return external ? join(root, `${name}.lock`) : join(root, ".stim", `${name}.lock`);
16
+ }
17
+ async function withWorkspaceProcessLock(root, name, fn, { now = Date.now, sleep = defaultSleep, waitMs = DEFAULT_WAIT_MS, ownerPurpose, details = {}, onHeld, rejectOwnerPurposes = [], external = false, declareSpawns = false } = {}) {
18
+ const path = workspaceProcessLockPath(root, name, external);
19
+ const deadline = now() + waitMs;
20
+ for (;;) {
21
+ const attempt = (0, ownership_claim_exports.tryAcquireClaim)({
22
+ root: path,
23
+ mode: "exclusive",
24
+ label: `${name} lock`,
25
+ details: ownerPurpose ? {
26
+ ...details,
27
+ purpose: ownerPurpose
28
+ } : details
29
+ });
30
+ if (attempt.pending) (0, ownership_claim_exports.releaseClaim)(attempt.pending);
31
+ if (attempt.acquired) {
32
+ if (declareSpawns) declareSpawnsOn(attempt.acquired);
33
+ try {
34
+ return await fn(attempt.acquired);
35
+ } finally {
36
+ stopDeclaringSpawnsOn(attempt.acquired);
37
+ (0, ownership_claim_exports.releaseClaim)(attempt.acquired);
38
+ }
39
+ }
40
+ const holder = attempt.held ?? attempt.waitingFor?.[0];
41
+ const purpose = holder?.details.purpose;
42
+ if (typeof purpose === "string" && rejectOwnerPurposes.includes(purpose)) {
43
+ const error = /* @__PURE__ */ new Error(`The ${name} lock at ${path} is held for ${purpose}.`);
44
+ error.code = "STIM_LOCK_REFUSED";
45
+ throw error;
46
+ }
47
+ if (holder && onHeld) await onHeld(holder);
48
+ if (now() >= deadline) {
49
+ const error = /* @__PURE__ */ new Error(`Timed out waiting for the ${name} lock at ${path}.`);
50
+ error.code = "STIM_LOCK_TIMEOUT";
51
+ throw error;
52
+ }
53
+ await sleep(POLL_MS);
54
+ }
55
+ }
56
+ //#endregion
57
+ export { workspaceProcessLockError as n, workspaceProcessLockPath as r, withWorkspaceProcessLock as t };