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.
- package/README.md +2 -0
- package/dist/activity-BxwdD81S.mjs +609 -0
- package/dist/agent-device-usage-output-Dpih1Tfa.mjs +411 -0
- package/dist/{android-d6rGq1pA.mjs → android-B_NumVyQ.mjs} +169 -81
- package/dist/{android-LbPFRWVL.mjs → android-Ift4kVvt.mjs} +517 -413
- package/dist/android-cas-8U4DPqeR.mjs +167 -0
- package/dist/android-cas-compiler.mjs +2 -2
- package/dist/{android-XpvCrV7q.mjs → android-r2rBL8kV.mjs} +2 -2
- package/dist/{app-install-BpgSgkJj.mjs → app-install-CKWdbXIX.mjs} +128 -151
- package/dist/attempt-Bk2ouQVL.mjs +39 -0
- package/dist/budget-DjrURfQn.mjs +2700 -0
- package/dist/{build-plan-Dcr-3ie0.mjs → build-plan-DjeSb2Xl.mjs} +192 -653
- package/dist/build-slots-rUk77xqh.mjs +132 -0
- package/dist/{cache-manifest-C68QQ0ua.mjs → cache-manifest-DnUHBJaU.mjs} +2 -2
- package/dist/cache-manifest.mjs +1 -1
- package/dist/{chrome-BPCiC3ZX.mjs → chrome-BrcVWQyD.mjs} +1 -1
- package/dist/cli-BkYZ_6hy.mjs +51 -0
- package/dist/cli.mjs +6 -42
- package/dist/client-BTY686R6.mjs +1368 -0
- package/dist/collector-run.mjs +9 -195
- package/dist/{command-output-DVcpw6eE.mjs → command-output-DnJMdEY-.mjs} +1 -1
- package/dist/{config-lvc7d3C2.mjs → config-D0DbpZtM.mjs} +28 -29
- package/dist/{created-devices-CllH_8eb.mjs → created-devices-D5owjN4C.mjs} +2 -2
- package/dist/{dependency-state-DsT7aKeo.mjs → dependency-state-B6cMXZab.mjs} +2 -2
- package/dist/{deps-D9IcKqXS.mjs → deps-urbmfXig.mjs} +3 -3
- package/dist/{detached-entry-DJHoca49.mjs → detached-entry-CiTEgMR1.mjs} +1 -1
- package/dist/dev-client-CroAIjjj.mjs +43 -0
- package/dist/{device-CjuuAJjQ.mjs → device-DZQjF7H5.mjs} +8 -7
- package/dist/device-capacity-BHwxNBlu.mjs +136 -0
- package/dist/device-host-worker.d.mts +1 -0
- package/dist/device-host-worker.mjs +964 -0
- package/dist/device-ios-BadmeL2V.mjs +381 -0
- package/dist/{device-lease-5c3WrVRm.mjs → device-lease-D-hLqx1b.mjs} +43 -36
- package/dist/{device-pool-BiyDyPP-.mjs → device-lease-run-CqjShqWW.mjs} +6 -126
- package/dist/device-pool-CUaEWMMm.mjs +125 -0
- package/dist/{device-remote-D87zEzaq.mjs → device-remote-D5WVWqOq.mjs} +69 -163
- package/dist/{doctor-x9pnU9fq.mjs → doctor-Cimf-gGB.mjs} +241 -103
- package/dist/{error-diagnostics-C5HzcwCW.mjs → error-diagnostics-B9L4Ae0x.mjs} +15 -18
- package/dist/{exec-DK8KaVHK.mjs → exec-CaErVv7t.mjs} +5 -1
- package/dist/{gc-ko6mUAYD.mjs → gc-Bqy4bjEu.mjs} +599 -108
- package/dist/{android-cas-iZK3DOCr.mjs → gradle-BsO1MdsH.mjs} +8 -294
- package/dist/{guide-6R0VNFqI.mjs → guide-B_uyKTxW.mjs} +2511 -239
- package/dist/{guide-status-BZrFPIA_.mjs → guide-status-CjEZMAD4.mjs} +3 -3
- package/dist/hosted-client-a-WmqE2L.mjs +197 -0
- package/dist/hosted-ios-CIWelIqf.mjs +220 -0
- package/dist/hosted-logs-D5g041Q3.mjs +187 -0
- package/dist/hosted-macos-BSZyIb3y.mjs +135 -0
- package/dist/idle-DYVymG92.mjs +1099 -0
- package/dist/idle-shutdown-CTsDnAde.mjs +122 -0
- package/dist/in-use-DvBw0Kxo.mjs +130 -0
- package/dist/{ios-BwDPrnYQ.mjs → ios-BaInn3sW.mjs} +201 -10
- package/dist/ios-Cr1h5gnO.mjs +189 -0
- package/dist/{ios-P1f5nm2n.mjs → ios-aq3QoGI3.mjs} +1159 -633
- package/dist/{ios-device-BiOsSVzd.mjs → ios-device-Ee7sDXfp.mjs} +1 -1
- package/dist/{ios-device-DGglU3E_.mjs → ios-device-FfCN_C8p.mjs} +12 -7
- package/dist/ios-state-DS5UpYTH.mjs +48 -0
- package/dist/{launch-verify-Deg4tB3Z.mjs → launch-verify-2DBRizjo.mjs} +5 -5
- package/dist/logs-CHEVK8zD.mjs +371 -0
- package/dist/machines-BIBUdhpj.mjs +265 -0
- package/dist/macos-CSYEAFmF.mjs +473 -0
- package/dist/macos-run.d.mts +5 -0
- package/dist/macos-run.mjs +2 -0
- package/dist/maintenance-run.d.mts +4 -0
- package/dist/maintenance-run.mjs +254 -0
- package/dist/{metro-CcEo8Fl8.mjs → metro-BeK1ZPz1.mjs} +20 -120
- package/dist/metro-gateway-BJ-rxQAB.mjs +84 -0
- package/dist/{named-ports-Blg8IxOh.mjs → named-ports-Bk_YXEmE.mjs} +6 -59
- package/dist/native-run-NaBrRFtQ.mjs +147 -0
- package/dist/{native-runtime-DjBHRg5m.mjs → native-runtime-DBzRnEKS.mjs} +9 -5
- package/dist/{ndjson-CDY9IkVC.mjs → ndjson-PclOIwVY.mjs} +2 -2
- package/dist/offload-worker.d.mts +122 -0
- package/dist/offload-worker.mjs +606 -0
- package/dist/{ownership-DyYDF5ww.mjs → ownership-DiA16AsN.mjs} +3 -2
- package/dist/ownership-hCkPzGmr.mjs +571 -0
- package/dist/{page-BHUzTDFt.mjs → page-QF_Ke10s.mjs} +7 -2
- package/dist/{ports-BguS83ex.mjs → ports-R2k4Knz3.mjs} +3 -3
- package/dist/prebuild-D2fsca_Z.mjs +213 -0
- package/dist/preview-DhVbvPAZ.mjs +318 -0
- package/dist/process-identity-BmRm1qV0.mjs +12 -0
- package/dist/{project-CWlvEaeN.mjs → project-Du91Ilvc.mjs} +74 -19
- package/dist/pull-request-pxnDCQzn.mjs +322 -0
- package/dist/pull-requests.d.mts +120 -0
- package/dist/pull-requests.mjs +2 -0
- package/dist/recordings-BIXOzVnr.mjs +37 -0
- package/dist/{reload-BiniwsSZ.mjs → reload-rM9jV1fz.mjs} +109 -24
- package/dist/{remote-cache-CcR6o6lI.mjs → remote-cache-C8-V13XN.mjs} +5 -5
- package/dist/run-f4vI6EnK.mjs +119 -0
- package/dist/{server-bare-DGfgyJ5J.mjs → server-bare-CpPGmnmO.mjs} +33 -15
- package/dist/server-expo-CVqO2c35.mjs +2 -0
- package/dist/{server-expo-BVuwQ65v.mjs → server-expo-MC0rHlOC.mjs} +8 -12
- package/dist/settings-BBpMcDbD.mjs +25 -0
- package/dist/{settings-B-fsC-Om.mjs → settings-ZkDzkzvB.mjs} +22 -10
- package/dist/settings-oTSkkNrM.mjs +1439 -0
- package/dist/settings.schema.json +845 -97
- package/dist/{simslim-DrDN4WuV.mjs → simslim-B8-7AoTI.mjs} +8 -132
- package/dist/{slot-launch-D5t8EiMf.mjs → slot-launch-B8VrHxn1.mjs} +11 -8
- package/dist/{spawn-claims-Drw_o-Fb.mjs → spawn-claims-D0XfSe8u.mjs} +1 -1
- package/dist/spawn-entry-jw19m8Nl.mjs +17 -0
- package/dist/stage-CzaiwFsI.mjs +143 -0
- package/dist/start-CX8Ig-U2.mjs +2 -0
- package/dist/{start-s4RXp1wy.mjs → start-ClDfwXa4.mjs} +116 -65
- package/dist/state-3DwSQIBK.mjs +41 -0
- package/dist/state-B4PQFXMW.d.mts +12 -0
- package/dist/{state-Dk9RVXI7.mjs → state-C1jB7Q8q.mjs} +70 -8
- package/dist/{state-DHqvsbqC.mjs → state-DcW6vOd_.mjs} +7 -4
- package/dist/stats-CEZ_i5je.mjs +77 -0
- package/dist/{status-BgAuxESg.mjs → status-BuvBFeII.mjs} +118 -20
- package/dist/{status-BsPAnfOI.mjs → status-Cs7VLg9D.mjs} +542 -283
- package/dist/status-DlKvVJBE.mjs +49 -0
- package/dist/stdout-reader-C8yjre1x.mjs +23 -0
- package/dist/stim-desktop-Cnz8CPnl.mjs +63 -0
- package/dist/stim-footprint.swift +32 -0
- package/dist/{stim-installations-CINXy_su.mjs → stim-installations-QMEI32v1.mjs} +1 -1
- package/dist/stop-B0Yxtmiy.mjs +48 -0
- package/dist/{stop-BzreOWJp.mjs → stop-Bffozsac.mjs} +83 -16
- package/dist/{stop-BWIF_6BK.mjs → stop-Dt_dM16A.mjs} +2 -2
- package/dist/stop-cause-CidxIqup.mjs +97 -0
- package/dist/supervisor-run.d.mts +43 -11
- package/dist/supervisor-run.mjs +363 -24
- package/dist/support-CAP8qDUF.mjs +316 -0
- package/dist/support-D4aYJniO.mjs +201 -0
- package/dist/teardown-CL6vDs2Y.mjs +727 -0
- package/dist/{xcode-Cp5KvK9x.mjs → toolchain-DkUflduf.mjs} +364 -14
- package/dist/trigger-Bemu8ytw.mjs +65 -0
- package/dist/trigger-CXts6qt1.mjs +2 -0
- package/dist/warm-progress-CpGv3Wb6.mjs +732 -0
- package/dist/{web-BsQs1WlO.mjs → web-DWUaMp8P.mjs} +37 -21
- package/dist/web-run.mjs +310 -13
- package/dist/workspace-process-lock-BL7Q0HCO.mjs +57 -0
- package/dist/workspace-state-B8-aJ1_5.mjs +371 -0
- package/dist/workspaces-BV4pdHFz.mjs +306 -0
- package/dist/{worktree-UrQWXSeb.mjs → worktree-K-xj9Db0.mjs} +625 -256
- package/dist/worktree-jnG8Iph5.mjs +2 -0
- package/package.json +10 -5
- package/shim/bundle-response.cjs +28 -12
- package/dist/activity-CYVDcrt3.mjs +0 -356
- package/dist/budget-CGDkhvv_.mjs +0 -1745
- package/dist/build-progress-DxKpwnaS.mjs +0 -247
- package/dist/idle-DXtx3RbX.mjs +0 -191
- package/dist/in-use-DlqBDKqY.mjs +0 -249
- package/dist/logs-Civ4gbBH.mjs +0 -486
- package/dist/merge-state-ytFQCeh4.mjs +0 -262
- package/dist/server-expo-DIHDhvGg.mjs +0 -2
- package/dist/settings-D61iNOsB.mjs +0 -629
- package/dist/start-Cetfvy8l.mjs +0 -2
- package/dist/stats-CFpFGlJV.mjs +0 -62
- package/dist/stats-CX1Z8Tca.mjs +0 -368
- package/dist/stim-desktop-CXt8-Z3J.mjs +0 -40
- package/dist/stop-cause-hjGBOZTG.mjs +0 -1231
- package/dist/worktree-BVrHsYZG.mjs +0 -752
- package/dist/worktree-CHWWvm5v.mjs +0 -2
- package/dist/{metro-store-DzeWkfqW.mjs → metro-store-CDDZHtPl.mjs} +1 -1
|
@@ -1,22 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { a as
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
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 &&
|
|
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(
|
|
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 {
|
|
2
|
-
import { t as
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
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-
|
|
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)
|
|
384
|
-
|
|
385
|
-
|
|
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 };
|