stim 1.14.0 → 1.15.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 (130) hide show
  1. package/README.md +2 -0
  2. package/dist/{activity-CYVDcrt3.mjs → activity-B2-kb7k0.mjs} +281 -41
  3. package/dist/{android-XpvCrV7q.mjs → android-CYmq0jNi.mjs} +2 -2
  4. package/dist/{android-LbPFRWVL.mjs → android-Cu_L4RpL.mjs} +502 -407
  5. package/dist/{android-d6rGq1pA.mjs → android-Db2eEMaf.mjs} +152 -65
  6. package/dist/android-cas-PnKfAdB2.mjs +167 -0
  7. package/dist/android-cas-compiler.mjs +2 -2
  8. package/dist/{app-install-BpgSgkJj.mjs → app-install-CAAplXAt.mjs} +95 -161
  9. package/dist/{budget-CGDkhvv_.mjs → budget-CMiqghig.mjs} +1038 -73
  10. package/dist/{build-plan-Dcr-3ie0.mjs → build-plan-aeGMFX7e.mjs} +193 -653
  11. package/dist/build-progress-C2Jv1AtA.mjs +872 -0
  12. package/dist/build-slots-Df1ApSuH.mjs +127 -0
  13. package/dist/{cache-manifest-C68QQ0ua.mjs → cache-manifest-DnUHBJaU.mjs} +2 -2
  14. package/dist/cache-manifest.mjs +1 -1
  15. package/dist/{chrome-BPCiC3ZX.mjs → chrome-BrcVWQyD.mjs} +1 -1
  16. package/dist/cli-0cX5s4NM.mjs +45 -0
  17. package/dist/cli.mjs +6 -42
  18. package/dist/client-C2IS36Qf.mjs +1333 -0
  19. package/dist/collector-run.mjs +8 -7
  20. package/dist/{command-output-DVcpw6eE.mjs → command-output-CliHHn7a.mjs} +1 -1
  21. package/dist/{config-lvc7d3C2.mjs → config-D0DbpZtM.mjs} +28 -29
  22. package/dist/{created-devices-CllH_8eb.mjs → created-devices-BwVb3liH.mjs} +2 -2
  23. package/dist/{dependency-state-DsT7aKeo.mjs → dependency-state-BmsjKZ2H.mjs} +2 -2
  24. package/dist/{deps-D9IcKqXS.mjs → deps-BFlKbOIp.mjs} +3 -3
  25. package/dist/{detached-entry-DJHoca49.mjs → detached-entry-CiTEgMR1.mjs} +1 -1
  26. package/dist/dev-client-CjzLoNs0.mjs +80 -0
  27. package/dist/{device-CjuuAJjQ.mjs → device-BZ1IyS5A.mjs} +7 -7
  28. package/dist/device-capacity-DMuKC7nx.mjs +136 -0
  29. package/dist/device-host-worker.d.mts +1 -0
  30. package/dist/device-host-worker.mjs +769 -0
  31. package/dist/device-ios-DMQBUPus.mjs +381 -0
  32. package/dist/{device-lease-5c3WrVRm.mjs → device-lease-W5Pi1q38.mjs} +43 -36
  33. package/dist/{device-pool-BiyDyPP-.mjs → device-pool-B2r38YjS.mjs} +4 -4
  34. package/dist/{device-remote-D87zEzaq.mjs → device-remote-9ulPsdhY.mjs} +68 -162
  35. package/dist/{doctor-x9pnU9fq.mjs → doctor-BcE5xsBn.mjs} +191 -99
  36. package/dist/{error-diagnostics-C5HzcwCW.mjs → error-diagnostics-Cb0P1WLS.mjs} +12 -11
  37. package/dist/{exec-DK8KaVHK.mjs → exec-CaErVv7t.mjs} +5 -1
  38. package/dist/{gc-ko6mUAYD.mjs → gc-BuL1Genr.mjs} +429 -101
  39. package/dist/{android-cas-iZK3DOCr.mjs → gradle-CmdOBq1d.mjs} +8 -294
  40. package/dist/{guide-6R0VNFqI.mjs → guide-cCSGRFd3.mjs} +1959 -222
  41. package/dist/{guide-status-BZrFPIA_.mjs → guide-status-CIoWK6mG.mjs} +3 -3
  42. package/dist/hosted-macos-DwowNU7W.mjs +447 -0
  43. package/dist/idle-shutdown-B5LPaNyY.mjs +122 -0
  44. package/dist/in-use-CndPMilN.mjs +110 -0
  45. package/dist/{ios-BwDPrnYQ.mjs → ios-3CtCRhwr.mjs} +201 -10
  46. package/dist/{ios-P1f5nm2n.mjs → ios-Chob8ny7.mjs} +512 -263
  47. package/dist/{ios-device-DGglU3E_.mjs → ios-device-DERmMmk-.mjs} +12 -7
  48. package/dist/{ios-device-BiOsSVzd.mjs → ios-device-Ee7sDXfp.mjs} +1 -1
  49. package/dist/{launch-verify-Deg4tB3Z.mjs → launch-verify-CLlofxRt.mjs} +4 -4
  50. package/dist/{logs-Civ4gbBH.mjs → logs-h9h6MUvE.mjs} +95 -13
  51. package/dist/machine-usage-D_YYMzfR.mjs +283 -0
  52. package/dist/machines-CYtOCqcj.mjs +259 -0
  53. package/dist/macos-BwtbUtTN.mjs +468 -0
  54. package/dist/macos-run.d.mts +5 -0
  55. package/dist/macos-run.mjs +2 -0
  56. package/dist/{merge-state-ytFQCeh4.mjs → merge-state-BnxLNQiM.mjs} +22 -12
  57. package/dist/{metro-CcEo8Fl8.mjs → metro-Di8EN2Tb.mjs} +20 -120
  58. package/dist/{named-ports-Blg8IxOh.mjs → named-ports-Dj_XBNaL.mjs} +6 -59
  59. package/dist/native-run-DFDoIRbY.mjs +147 -0
  60. package/dist/{native-runtime-DjBHRg5m.mjs → native-runtime-i6OkVg-w.mjs} +9 -5
  61. package/dist/offload-worker.d.mts +121 -0
  62. package/dist/offload-worker.mjs +601 -0
  63. package/dist/{ownership-DyYDF5ww.mjs → ownership-BkYxUtuj.mjs} +3 -2
  64. package/dist/{page-BHUzTDFt.mjs → page-QF_Ke10s.mjs} +7 -2
  65. package/dist/{ports-BguS83ex.mjs → ports-DddwttKM.mjs} +3 -3
  66. package/dist/prebuild-BvxDlD6C.mjs +213 -0
  67. package/dist/process-identity-BmRm1qV0.mjs +12 -0
  68. package/dist/{project-CWlvEaeN.mjs → project-CV3tDFHl.mjs} +9 -18
  69. package/dist/pull-request-pxnDCQzn.mjs +322 -0
  70. package/dist/pull-requests.d.mts +120 -0
  71. package/dist/pull-requests.mjs +2 -0
  72. package/dist/recordings-GEXp4iBT.mjs +37 -0
  73. package/dist/{reload-BiniwsSZ.mjs → reload-ChPiwvot.mjs} +70 -22
  74. package/dist/{remote-cache-CcR6o6lI.mjs → remote-cache-CO2elm_b.mjs} +4 -4
  75. package/dist/run-B-xqRrsg.mjs +119 -0
  76. package/dist/{server-bare-DGfgyJ5J.mjs → server-bare-X7PUgizL.mjs} +33 -15
  77. package/dist/{server-expo-BVuwQ65v.mjs → server-expo-DhtX6bom.mjs} +6 -10
  78. package/dist/server-expo-KJeD109I.mjs +2 -0
  79. package/dist/{settings-D61iNOsB.mjs → settings-CduVcoxj.mjs} +77 -26
  80. package/dist/{settings-B-fsC-Om.mjs → settings-DbL6uhH8.mjs} +16 -10
  81. package/dist/settings.schema.json +357 -2
  82. package/dist/{simslim-DrDN4WuV.mjs → simslim-Ciluxlq4.mjs} +8 -132
  83. package/dist/{slot-launch-D5t8EiMf.mjs → slot-launch-BY0BrOLh.mjs} +6 -6
  84. package/dist/{spawn-claims-Drw_o-Fb.mjs → spawn-claims-D0XfSe8u.mjs} +1 -1
  85. package/dist/spawn-entry-DncpJvWL.mjs +16 -0
  86. package/dist/stage-CzaiwFsI.mjs +143 -0
  87. package/dist/start-CPO4wRg7.mjs +2 -0
  88. package/dist/{start-s4RXp1wy.mjs → start-CUD9nasA.mjs} +108 -61
  89. package/dist/state-B4PQFXMW.d.mts +12 -0
  90. package/dist/state-BxCgeTQu.mjs +41 -0
  91. package/dist/{state-DHqvsbqC.mjs → state-CaeaHbDA.mjs} +7 -4
  92. package/dist/{state-Dk9RVXI7.mjs → state-DFm77wRn.mjs} +67 -5
  93. package/dist/stats-DBLLFmKd.mjs +66 -0
  94. package/dist/{status-BsPAnfOI.mjs → status-DYXxNOT0.mjs} +423 -268
  95. package/dist/{status-BgAuxESg.mjs → status-l84Gbvp2.mjs} +116 -20
  96. package/dist/stdout-reader-C8yjre1x.mjs +23 -0
  97. package/dist/stim-desktop-Cnz8CPnl.mjs +63 -0
  98. package/dist/stim-footprint.swift +32 -0
  99. package/dist/{stim-installations-CINXy_su.mjs → stim-installations-QMEI32v1.mjs} +1 -1
  100. package/dist/{stop-BzreOWJp.mjs → stop-Bad61z_2.mjs} +38 -12
  101. package/dist/stop-Bvf1MflS.mjs +48 -0
  102. package/dist/{stop-BWIF_6BK.mjs → stop-CceYh1yG.mjs} +2 -2
  103. package/dist/stop-cause-CZ_Mr-v9.mjs +665 -0
  104. package/dist/supervisor-run.d.mts +43 -11
  105. package/dist/supervisor-run.mjs +227 -19
  106. package/dist/support-CDQbDkL5.mjs +315 -0
  107. package/dist/support-WLOzVKUv.mjs +176 -0
  108. package/dist/teardown-BYuQafiA.mjs +718 -0
  109. package/dist/{xcode-Cp5KvK9x.mjs → toolchain-DmeYjXD9.mjs} +363 -13
  110. package/dist/warm-progress-DVHLN4uC.mjs +463 -0
  111. package/dist/{idle-DXtx3RbX.mjs → watchman-Bx0wceoF.mjs} +55 -8
  112. package/dist/{web-BsQs1WlO.mjs → web-k6w8-HnE.mjs} +37 -20
  113. package/dist/web-run.mjs +309 -12
  114. package/dist/workspace-process-lock-BL7Q0HCO.mjs +57 -0
  115. package/dist/workspace-state-CL2V2Ons.mjs +371 -0
  116. package/dist/worktree-BT_uULFx.mjs +2 -0
  117. package/dist/{worktree-BVrHsYZG.mjs → worktree-C0c5Udwa.mjs} +55 -54
  118. package/dist/{worktree-UrQWXSeb.mjs → worktree-qXKzADSC.mjs} +154 -252
  119. package/package.json +10 -5
  120. package/shim/bundle-response.cjs +28 -12
  121. package/dist/build-progress-DxKpwnaS.mjs +0 -247
  122. package/dist/in-use-DlqBDKqY.mjs +0 -249
  123. package/dist/server-expo-DIHDhvGg.mjs +0 -2
  124. package/dist/start-Cetfvy8l.mjs +0 -2
  125. package/dist/stats-CFpFGlJV.mjs +0 -62
  126. package/dist/stats-CX1Z8Tca.mjs +0 -368
  127. package/dist/stim-desktop-CXt8-Z3J.mjs +0 -40
  128. package/dist/stop-cause-hjGBOZTG.mjs +0 -1231
  129. package/dist/worktree-CHWWvm5v.mjs +0 -2
  130. package/dist/{metro-store-DzeWkfqW.mjs → metro-store-CDDZHtPl.mjs} +1 -1
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";
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
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";
4
+ import { t as relaunchWithLogFile } from "./detached-entry-CiTEgMR1.mjs";
5
+ import { a as probeWebDrivers } from "./activity-B2-kb7k0.mjs";
6
+ import { r as recordCreatedDevice } from "./created-devices-BwVb3liH.mjs";
7
+ import { u as signalProcessTree } from "./metro-Di8EN2Tb.mjs";
8
+ 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-DFm77wRn.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 };