rnxsim 0.1.313 → 0.1.314
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/LICENSE +11 -10
- package/README.md +5 -0
- package/cli/app-config.ts +65 -0
- package/cli/app-fonts.ts +408 -0
- package/cli/app-project.ts +231 -0
- package/cli/app-splash.ts +185 -0
- package/cli/app-state-reset.ts +24 -0
- package/cli/auth.ts +155 -0
- package/cli/bin.ts +594 -0
- package/cli/bridge-diagnostics.ts +226 -0
- package/cli/bridge-flow-runner.ts +2830 -0
- package/cli/browser-evals.ts +96 -0
- package/cli/commands/agent-wrapper.ts +986 -0
- package/cli/commands/agent.ts +423 -0
- package/cli/commands/app-fonts.ts +98 -0
- package/cli/commands/assert.ts +541 -0
- package/cli/commands/auth.ts +59 -0
- package/cli/commands/camera.ts +266 -0
- package/cli/commands/cleanup.ts +169 -0
- package/cli/commands/compat.ts +87 -0
- package/cli/commands/config.ts +32 -0
- package/cli/commands/control.ts +2142 -0
- package/cli/commands/cpu-profile.ts +269 -0
- package/cli/commands/daemon-mac-app.ts +169 -0
- package/cli/commands/daemon.ts +874 -0
- package/cli/commands/debug.ts +719 -0
- package/cli/commands/desktop.ts +39 -0
- package/cli/commands/detect.ts +197 -0
- package/cli/commands/detox.ts +385 -0
- package/cli/commands/device.ts +133 -0
- package/cli/commands/diagnose.ts +589 -0
- package/cli/commands/electron.ts +95 -0
- package/cli/commands/film.ts +379 -0
- package/cli/commands/flow.ts +1124 -0
- package/cli/commands/inspect/actions.ts +622 -0
- package/cli/commands/inspect/core.ts +2405 -0
- package/cli/commands/inspect/count.ts +17 -0
- package/cli/commands/inspect/describe.ts +192 -0
- package/cli/commands/inspect/env.ts +23 -0
- package/cli/commands/inspect/find.ts +171 -0
- package/cli/commands/inspect/get-layout.ts +39 -0
- package/cli/commands/inspect/keyboard.ts +52 -0
- package/cli/commands/inspect/list.ts +58 -0
- package/cli/commands/inspect/memory.ts +215 -0
- package/cli/commands/inspect/redaction.ts +39 -0
- package/cli/commands/inspect/resolve-target.ts +82 -0
- package/cli/commands/inspect/screens.ts +78 -0
- package/cli/commands/inspect/settle.ts +22 -0
- package/cli/commands/inspect/settling.ts +158 -0
- package/cli/commands/inspect/shared.ts +353 -0
- package/cli/commands/inspect/sleep.ts +14 -0
- package/cli/commands/inspect/tree.ts +32 -0
- package/cli/commands/inspect/url.ts +17 -0
- package/cli/commands/inspect/wait-event.ts +210 -0
- package/cli/commands/inspect/wait-idle.ts +24 -0
- package/cli/commands/inspect/wait-ready.ts +74 -0
- package/cli/commands/inspect/wait-selector.ts +54 -0
- package/cli/commands/inspect/wait.ts +31 -0
- package/cli/commands/inspect.ts +4519 -0
- package/cli/commands/install-desktop.ts +351 -0
- package/cli/commands/login.ts +331 -0
- package/cli/commands/logout.ts +31 -0
- package/cli/commands/maestro-generate.ts +361 -0
- package/cli/commands/maestro.ts +453 -0
- package/cli/commands/mode.ts +57 -0
- package/cli/commands/no-bridge-hint.ts +80 -0
- package/cli/commands/perf.ts +66 -0
- package/cli/commands/permissions.ts +203 -0
- package/cli/commands/profile.ts +108 -0
- package/cli/commands/react.ts +353 -0
- package/cli/commands/record.ts +1434 -0
- package/cli/commands/report-issue.ts +305 -0
- package/cli/commands/reset.ts +85 -0
- package/cli/commands/runtime.ts +351 -0
- package/cli/commands/screenshot-command.ts +106 -0
- package/cli/commands/screenshot-layers.ts +143 -0
- package/cli/commands/screenshot-mode.ts +37 -0
- package/cli/commands/screenshot.ts +488 -0
- package/cli/commands/screenshots-capture.ts +607 -0
- package/cli/commands/screenshots.ts +127 -0
- package/cli/commands/serve.ts +168 -0
- package/cli/commands/setup.ts +545 -0
- package/cli/commands/shell-boolean-mode.ts +81 -0
- package/cli/commands/skills.ts +467 -0
- package/cli/commands/slides.ts +361 -0
- package/cli/commands/state.ts +87 -0
- package/cli/commands/storage.ts +58 -0
- package/cli/commands/telemetry.ts +54 -0
- package/cli/commands/three-mode.ts +763 -0
- package/cli/commands/timeline.ts +122 -0
- package/cli/commands/upgrade.ts +208 -0
- package/cli/commands/upload.ts +1225 -0
- package/cli/commands/version.ts +54 -0
- package/cli/commands/what-happened.ts +327 -0
- package/cli/current-sim.ts +204 -0
- package/cli/desktop-companion.ts +300 -0
- package/cli/drivers/electron.ts +70 -0
- package/cli/drivers/index.ts +20 -0
- package/cli/drivers/playwright-provisioning.ts +180 -0
- package/cli/drivers/playwright.ts +698 -0
- package/cli/drivers/registry.ts +65 -0
- package/cli/drivers/types.ts +102 -0
- package/cli/flow-file.ts +142 -0
- package/cli/flow-live-status.ts +120 -0
- package/cli/flow-session.ts +187 -0
- package/cli/help.ts +80 -0
- package/cli/hidden-runtime-alias.ts +19 -0
- package/cli/hints.ts +216 -0
- package/cli/inspect-notice-state.ts +114 -0
- package/cli/maestro-js.ts +334 -0
- package/cli/open-url.ts +8 -0
- package/cli/parent-pid.ts +204 -0
- package/cli/parse-args.ts +211 -0
- package/cli/prompt.ts +51 -0
- package/cli/recording-access.ts +107 -0
- package/cli/registry.ts +1 -0
- package/cli/resolve-assets.ts +63 -0
- package/cli/run-registry.ts +226 -0
- package/cli/runtime-notes.ts +66 -0
- package/cli/runtime-summary.ts +25 -0
- package/cli/setup-repository.ts +187 -0
- package/cli/telemetry.ts +187 -0
- package/cli/ws-bridge.ts +798 -0
- package/dist-cli/bin.js +5 -5
- package/dist-cli/chunks/{agent-XZ2KTPCU.js → agent-7YBDCYMA.js} +2 -2
- package/dist-cli/chunks/{agent-wrapper-JJYYW2WH.js → agent-wrapper-2GHFBHCR.js} +2 -2
- package/dist-cli/chunks/{app-fonts-IXRNQG6B.js → app-fonts-RSNVPQSU.js} +2 -2
- package/dist-cli/chunks/{assert-54T5SK5F.js → assert-XCMX3XJX.js} +2 -2
- package/dist-cli/chunks/{auth-FI5UDI45.js → auth-B442HRAX.js} +2 -2
- package/dist-cli/chunks/{beta-JV6UKADW.js → beta-XJ55JK3M.js} +2 -2
- package/dist-cli/chunks/camera-UGYSSLIK.js +33 -0
- package/dist-cli/chunks/{chunk-3NV2NCNX.js → chunk-277AEQZX.js} +2 -2
- package/dist-cli/chunks/{chunk-2YR5BGA5.js → chunk-2JNSK774.js} +2 -2
- package/dist-cli/chunks/{chunk-RSZWCKNT.js → chunk-32WOTSTR.js} +3 -3
- package/dist-cli/chunks/{chunk-WEXDAC74.js → chunk-3S753SNQ.js} +2 -2
- package/dist-cli/chunks/{chunk-IJO63TDP.js → chunk-46ZOLOYA.js} +2 -2
- package/dist-cli/chunks/{chunk-WUSWBCWA.js → chunk-5L7ELDQL.js} +8 -9
- package/dist-cli/chunks/{chunk-WF3T4SVI.js → chunk-7OOPFSQS.js} +2 -2
- package/dist-cli/chunks/{chunk-TZFFR3SD.js → chunk-7SV3RPRW.js} +2 -2
- package/dist-cli/chunks/{chunk-WWZIXIRD.js → chunk-7ZC35MOU.js} +1 -1
- package/dist-cli/chunks/chunk-AMG5E6CC.js +9 -0
- package/dist-cli/chunks/{chunk-WINYQ44O.js → chunk-APWNH3A4.js} +1 -1
- package/dist-cli/chunks/chunk-B57XUKY3.js +4 -0
- package/dist-cli/chunks/{chunk-YDGQTMQL.js → chunk-C2NL26TD.js} +1 -1
- package/dist-cli/chunks/{chunk-NMF2ZMZQ.js → chunk-D2FNUWAB.js} +4 -4
- package/dist-cli/chunks/{chunk-YIFT42WN.js → chunk-E5T4XSJ3.js} +2 -2
- package/dist-cli/chunks/{chunk-46EUUFJ5.js → chunk-EAC34EQS.js} +1 -1
- package/dist-cli/chunks/{chunk-OVFJFXUD.js → chunk-EG32ML36.js} +2 -2
- package/dist-cli/chunks/{chunk-5YJCOWCH.js → chunk-FXUAC6D5.js} +1 -1
- package/dist-cli/chunks/chunk-GQSL4USA.js +6 -0
- package/dist-cli/chunks/{chunk-VFCMSYZK.js → chunk-HAXW27SS.js} +2 -2
- package/dist-cli/chunks/{chunk-2D2UPBBR.js → chunk-IZAHPAN6.js} +1 -1
- package/dist-cli/chunks/{chunk-7GN3LVWB.js → chunk-J62KM5TB.js} +2 -2
- package/dist-cli/chunks/{chunk-BTWORNNG.js → chunk-JEMCD5E4.js} +1 -1
- package/dist-cli/chunks/{chunk-DCEMHR2Y.js → chunk-JZS3Q37N.js} +2 -2
- package/dist-cli/chunks/{chunk-D4FFVGI5.js → chunk-KR4JON7D.js} +1 -1
- package/dist-cli/chunks/chunk-KWCYKQIQ.js +4 -0
- package/dist-cli/chunks/{chunk-VNQEB4L7.js → chunk-MCWPL644.js} +2 -2
- package/dist-cli/chunks/chunk-MJYK3N2I.js +4 -0
- package/dist-cli/chunks/{chunk-5TEF3ET3.js → chunk-O6TRIZNS.js} +2 -2
- package/dist-cli/chunks/{chunk-QKDWYITG.js → chunk-P7XL2E73.js} +3 -3
- package/dist-cli/chunks/{chunk-BBULZ7CG.js → chunk-PFQTUKQ4.js} +62 -87
- package/dist-cli/chunks/{chunk-GGRX24GF.js → chunk-PG5RZCTN.js} +2 -2
- package/dist-cli/chunks/{chunk-W6K4EFPH.js → chunk-QGRI2Z4M.js} +2 -2
- package/dist-cli/chunks/{chunk-ZMJD5GEC.js → chunk-QLXXE7GE.js} +1 -1
- package/dist-cli/chunks/{chunk-VZXWHRUZ.js → chunk-QOJJJWJE.js} +89 -133
- package/dist-cli/chunks/{chunk-OZSSI4WN.js → chunk-RWZY5427.js} +2 -2
- package/dist-cli/chunks/{chunk-UC6U3MML.js → chunk-RZKU2K3J.js} +2 -2
- package/dist-cli/chunks/{chunk-IJ5CAZZC.js → chunk-SBV4IK4H.js} +1 -1
- package/dist-cli/chunks/{chunk-DZS6WPUI.js → chunk-SGMVFFMK.js} +1 -1
- package/dist-cli/chunks/{chunk-OHAZNXLK.js → chunk-TAX4UT2N.js} +1 -1
- package/dist-cli/chunks/{chunk-5DHC6KHQ.js → chunk-TUOFAWXT.js} +1 -1
- package/dist-cli/chunks/{chunk-GASE6UBA.js → chunk-UHZLOHGP.js} +1 -1
- package/dist-cli/chunks/{chunk-F5ZRSS3C.js → chunk-VC7V76U3.js} +1 -1
- package/dist-cli/chunks/{chunk-RTN5C5RL.js → chunk-VQVMLW4U.js} +1 -1
- package/dist-cli/chunks/{chunk-WMIIKMGK.js → chunk-VUKKYPZN.js} +2 -2
- package/dist-cli/chunks/{chunk-5TPRP5QT.js → chunk-WGGRJDRE.js} +1 -1
- package/dist-cli/chunks/chunk-X6H76EKP.js +15 -0
- package/dist-cli/chunks/chunk-XLZ5FNRT.js +27 -0
- package/dist-cli/chunks/{chunk-HI5TFJWN.js → chunk-XULEACM4.js} +2 -2
- package/dist-cli/chunks/{chunk-MJRLLB4R.js → chunk-YFSDM7AX.js} +4 -4
- package/dist-cli/chunks/chunk-YWI3UEVX.js +5 -0
- package/dist-cli/chunks/{chunk-XEVZYVIW.js → chunk-ZBSJO4NB.js} +10 -9
- package/dist-cli/chunks/{cleanup-P27PA6JI.js → cleanup-EYLGCA6Z.js} +2 -2
- package/dist-cli/chunks/cli-version-LL2UGIHE.js +4 -0
- package/dist-cli/chunks/{compat-ZD65FED3.js → compat-TLJYHB4E.js} +2 -2
- package/dist-cli/chunks/{config-XMJRNM2A.js → config-4JWUOEXK.js} +2 -2
- package/dist-cli/chunks/{control-KMIQT3QP.js → control-HPAOYF4N.js} +2 -2
- package/dist-cli/chunks/daemon-OYLASXLE.js +4 -0
- package/dist-cli/chunks/{debug-PT4HOP7N.js → debug-4BKXF6KI.js} +5 -5
- package/dist-cli/chunks/{desktop-S3FG72AK.js → desktop-ZQ6ZD2S6.js} +3 -3
- package/dist-cli/chunks/{detox-B3D4IFCN.js → detox-WNPASSS3.js} +2 -2
- package/dist-cli/chunks/{device-XBNDSB2R.js → device-BXLXVG3V.js} +2 -2
- package/dist-cli/chunks/{diagnose-HMQXJE5N.js → diagnose-3QX7W5RW.js} +2 -2
- package/dist-cli/chunks/{disk-cleanup-BLCZ5BSZ.js → disk-cleanup-KSWKI7WB.js} +2 -2
- package/dist-cli/chunks/drivers-EPIEFF7P.js +4 -0
- package/dist-cli/chunks/{film-BJGTBYZB.js → film-GDMR33VO.js} +3 -3
- package/dist-cli/chunks/flow-NKMPBYCJ.js +4 -0
- package/dist-cli/chunks/help-OGCBHOKA.js +4 -0
- package/dist-cli/chunks/{hidden-runtime-alias-ANOYADHM.js → hidden-runtime-alias-S2GTDX3T.js} +2 -2
- package/dist-cli/chunks/home-paths-QRCDLTTV.js +4 -0
- package/dist-cli/chunks/inspect-FUYMOZPY.js +4 -0
- package/dist-cli/chunks/install-desktop-FR6YKW7Y.js +4 -0
- package/dist-cli/chunks/{login-FJ737MWG.js → login-IJAPUZHI.js} +4 -4
- package/dist-cli/chunks/{logout-ZCNMMHMY.js → logout-QMXDFU2X.js} +2 -2
- package/dist-cli/chunks/{maestro-SZTNKLDF.js → maestro-ZXU3YCVX.js} +3 -3
- package/dist-cli/chunks/{maestro-generate-DCFAIZ4H.js → maestro-generate-PYB5QY7K.js} +3 -3
- package/dist-cli/chunks/{mode-GRMQCRXR.js → mode-WGUCL5FZ.js} +2 -2
- package/dist-cli/chunks/{optional-demo-registry-W36EWFFB.js → optional-demo-registry-WH2O6H36.js} +2 -2
- package/dist-cli/chunks/{perf-QYBAAUZG.js → perf-TOD3UFAH.js} +2 -2
- package/dist-cli/chunks/{permissions-3QCQ6VF4.js → permissions-I5BRJGTB.js} +2 -2
- package/dist-cli/chunks/{record-QPPC2S4E.js → record-ZYL2FYSK.js} +3 -3
- package/dist-cli/chunks/{report-issue-7NMFP4HK.js → report-issue-TAI6DYZO.js} +2 -2
- package/dist-cli/chunks/reset-7YGYKQPQ.js +4 -0
- package/dist-cli/chunks/runtime-B4JO6QRI.js +4 -0
- package/dist-cli/chunks/{screenshot-command-67AECJFB.js → screenshot-command-7ANLODZY.js} +7 -7
- package/dist-cli/chunks/{screenshot-layers-ASWBYPJL.js → screenshot-layers-A7FYXSVU.js} +3 -3
- package/dist-cli/chunks/{screenshots-capture-PXA3HFQK.js → screenshots-capture-WT2ZY6CB.js} +2 -2
- package/dist-cli/chunks/serve-TG5WKA4V.js +44 -0
- package/dist-cli/chunks/{setup-7DWPMRSB.js → setup-PCWLD22V.js} +2 -2
- package/dist-cli/chunks/{skills-S3Y22TUA.js → skills-27ZHWCQS.js} +2 -2
- package/dist-cli/chunks/state-ZQVY46ZO.js +14 -0
- package/dist-cli/chunks/{storage-XUIMJWAJ.js → storage-OYC57C4X.js} +6 -6
- package/dist-cli/chunks/store-32HSPZHI.js +4 -0
- package/dist-cli/chunks/telemetry-PBJR7XHR.js +4 -0
- package/dist-cli/chunks/{timeline-TMPLQPSP.js → timeline-RAJJFQT6.js} +2 -2
- package/dist-cli/chunks/{upgrade-7HDSIM7K.js → upgrade-OEFNZRIP.js} +2 -2
- package/dist-cli/chunks/upload-LQJITLKK.js +4 -0
- package/dist-cli/chunks/version-NIXTY6PL.js +6 -0
- package/dist-cli/chunks/{web-DG3WBYD3.js → web-XBUTBVGR.js} +2 -2
- package/dist-cli/chunks/{what-happened-XFVUTZR7.js → what-happened-YNHRFUDX.js} +3 -3
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.cjs +1 -1
- package/dist-lib/agent-sessions.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +1 -1
- package/dist-lib/backend-origin.cjs +1 -1
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +1 -1
- package/dist-lib/bridge-contract.cjs +20 -0
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +1 -1
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +67 -28
- package/dist-lib/host/bridge-host.cjs +140 -12
- package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +2815 -40
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/menu.cjs +1 -1
- package/dist-lib/menu.mjs +1 -1
- package/dist-lib/metro.cjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
- package/dist-lib/sdk.cjs +2549 -2061
- package/dist-lib/sdk.mjs +2543 -2061
- package/dist-lib/skills.cjs +480 -280
- package/dist-lib/vite.cjs +1 -1
- package/package.json +8 -2
- package/src/bridge-constants.ts +3 -4
- package/src/bridge-contract.ts +251 -0
- package/src/connect.ts +83 -0
- package/src/disk-cleanup.ts +30 -0
- package/src/home-paths.ts +81 -38
- package/src/host/bridge-host.ts +134 -6
- package/src/index.ts +27 -1
- package/src/sdk.ts +8 -0
- package/src/sim-client.ts +660 -0
- package/dist-cli/chunks/camera-VL73YIKP.js +0 -22
- package/dist-cli/chunks/chunk-4NPPOV2N.js +0 -5
- package/dist-cli/chunks/chunk-FSUYIVJ6.js +0 -9
- package/dist-cli/chunks/chunk-G2WW6L2C.js +0 -23
- package/dist-cli/chunks/chunk-KTHV3RUS.js +0 -26
- package/dist-cli/chunks/chunk-LF2ZVT7O.js +0 -6
- package/dist-cli/chunks/chunk-NFK7T35W.js +0 -4
- package/dist-cli/chunks/chunk-TIVZIMMW.js +0 -4
- package/dist-cli/chunks/cli-version-WWLPBDQ7.js +0 -4
- package/dist-cli/chunks/daemon-G2ME7NLB.js +0 -4
- package/dist-cli/chunks/drivers-LDECZGP2.js +0 -4
- package/dist-cli/chunks/flow-UEQNVTU7.js +0 -4
- package/dist-cli/chunks/help-T5FYSVGB.js +0 -4
- package/dist-cli/chunks/home-paths-GT3LFNOR.js +0 -4
- package/dist-cli/chunks/inspect-ZA6XF5LD.js +0 -4
- package/dist-cli/chunks/install-desktop-TIMUDHPL.js +0 -4
- package/dist-cli/chunks/runtime-XOAXMSTU.js +0 -4
- package/dist-cli/chunks/serve-BI2NBAXG.js +0 -44
- package/dist-cli/chunks/store-JTHEJLAZ.js +0 -4
- package/dist-cli/chunks/telemetry-ZYJGD2DB.js +0 -4
- package/dist-cli/chunks/upload-GMSZPWM6.js +0 -4
- package/dist-cli/chunks/version-HOCHZ37L.js +0 -6
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
// shared helpers for inspect subcommands.
|
|
2
|
+
//
|
|
3
|
+
// pulled out of the monolithic inspect.ts so each subcommand can live in its
|
|
4
|
+
// own file without reaching back into a 4000-line switch. everything here is
|
|
5
|
+
// call-agnostic: just bridge-facing helpers that any subcommand can use.
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
callInBridge,
|
|
9
|
+
callInBridgeWrite,
|
|
10
|
+
createBridge,
|
|
11
|
+
type ParsedBridgeCliArgs,
|
|
12
|
+
type WsBridge,
|
|
13
|
+
} from '../../ws-bridge'
|
|
14
|
+
import {
|
|
15
|
+
getShellState,
|
|
16
|
+
isShellCommandUnavailable,
|
|
17
|
+
readyProbeHasContent,
|
|
18
|
+
READY_NODE_STABLE_MS,
|
|
19
|
+
WAIT_READY_PROBE,
|
|
20
|
+
type ReadyProbe,
|
|
21
|
+
} from './core'
|
|
22
|
+
import type { SootSimScreenTransitionWaitResult } from '@sootsim/globals'
|
|
23
|
+
|
|
24
|
+
// shell-state helpers live in the transport-agnostic kernel module so the CLI
|
|
25
|
+
// and the in-browser agent share one implementation. re-exported here so
|
|
26
|
+
// existing subcommand imports (`./shared`) keep working.
|
|
27
|
+
export { getShellState, isShellCommandUnavailable }
|
|
28
|
+
|
|
29
|
+
export const SCREEN_TRANSITION_PREFLIGHT = {
|
|
30
|
+
timeoutMs: 1800,
|
|
31
|
+
settleMs: 48,
|
|
32
|
+
startWindowMs: 64,
|
|
33
|
+
} as const
|
|
34
|
+
|
|
35
|
+
export function sleep(ms: number) {
|
|
36
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// true when the caller passed --json. read verbs use this to route their
|
|
40
|
+
// payload to stdout-as-json and send everything else (headers, hints,
|
|
41
|
+
// warnings) to stderr so `jq` on stdout never sees framing noise.
|
|
42
|
+
export function wantsJson(args: string[]): boolean {
|
|
43
|
+
return args.includes('--json')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// emit a stable JSON document on stdout. no trailing prose, no ANSI,
|
|
47
|
+
// no colors. the 2-space indent matches the rest of the CLI and is
|
|
48
|
+
// cheap enough that pretty-printing by default is fine for bisect
|
|
49
|
+
// scripts — consumers who care pipe through `jq -c`.
|
|
50
|
+
export function printJson(payload: unknown): void {
|
|
51
|
+
process.stdout.write(`${JSON.stringify(payload ?? null, null, 2)}\n`)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// warnings, hints, "screen transition still active" messages — anything
|
|
55
|
+
// that isn't the payload. always goes to stderr so stdout stays parseable.
|
|
56
|
+
export function printWarn(msg: string): void {
|
|
57
|
+
process.stderr.write(`${msg}\n`)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 24-hour wall-clock + relative offset for log/error/network timestamps.
|
|
61
|
+
// agents reading these care most about "is this new or stale" — append
|
|
62
|
+
// "+<n>s" for things younger than 60s. older entries keep the relative
|
|
63
|
+
// stamp in m/h/d so it's still scannable without diffing wall-clock times.
|
|
64
|
+
//
|
|
65
|
+
// returns "HH:MM:SS +Xs" where wall clock is locale-independent 24-hour.
|
|
66
|
+
// callers can wrap it in their own padding/dim ANSI.
|
|
67
|
+
export function formatLogTimestamp(ts: number, nowMs: number = Date.now()): string {
|
|
68
|
+
const d = new Date(ts)
|
|
69
|
+
const wall = `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`
|
|
70
|
+
const deltaMs = nowMs - ts
|
|
71
|
+
if (deltaMs < 0) return wall
|
|
72
|
+
const sec = Math.round(deltaMs / 1000)
|
|
73
|
+
if (sec < 60) return `${wall} +${sec}s`
|
|
74
|
+
if (sec < 3600) return `${wall} +${Math.round(sec / 60)}m`
|
|
75
|
+
if (sec < 86400) return `${wall} +${Math.round(sec / 3600)}h`
|
|
76
|
+
return `${wall} +${Math.round(sec / 86400)}d`
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function pad2(n: number): string {
|
|
80
|
+
return n < 10 ? `0${n}` : String(n)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function callTestBridge<T = unknown>(
|
|
84
|
+
bridge: WsBridge,
|
|
85
|
+
method: string,
|
|
86
|
+
...args: unknown[]
|
|
87
|
+
): Promise<T> {
|
|
88
|
+
return callInBridge<T>(bridge, `__sootsimTest.${method}`, ...args)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function callShellCommand<T = unknown>(
|
|
92
|
+
bridge: WsBridge,
|
|
93
|
+
method: 'launchApp' | 'goHome' | 'openSwitcher' | 'openSwitcherCard',
|
|
94
|
+
...args: unknown[]
|
|
95
|
+
): Promise<T> {
|
|
96
|
+
return callInBridgeWrite<T>(bridge, `SootSim.bridges.mainShell.${method}`, ...args)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function maybeWaitForStartedScreenTransitions(
|
|
100
|
+
bridge: WsBridge,
|
|
101
|
+
opts: { verbose?: boolean } = {},
|
|
102
|
+
) {
|
|
103
|
+
try {
|
|
104
|
+
const result = await callTestBridge<SootSimScreenTransitionWaitResult>(
|
|
105
|
+
bridge,
|
|
106
|
+
'waitForScreenTransitions',
|
|
107
|
+
SCREEN_TRANSITION_PREFLIGHT,
|
|
108
|
+
)
|
|
109
|
+
if (!opts.verbose || !result?.started) return
|
|
110
|
+
if (result.timedOut) {
|
|
111
|
+
console.log(
|
|
112
|
+
` screen transition still active after ${result.waitedMs}ms; continuing`,
|
|
113
|
+
)
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
if (result.waitedMs > 0) {
|
|
117
|
+
console.log(` waited ${result.waitedMs}ms for screen transition settle`)
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
// best-effort preflight only — older runtimes or boot races should not
|
|
121
|
+
// block the actual command.
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export async function callShellCommandWhenReady<T = unknown>(
|
|
126
|
+
bridge: WsBridge,
|
|
127
|
+
method: 'launchApp' | 'goHome' | 'openSwitcher' | 'openSwitcherCard',
|
|
128
|
+
readyTimeoutMs: number,
|
|
129
|
+
...args: unknown[]
|
|
130
|
+
): Promise<T> {
|
|
131
|
+
const deadline = Date.now() + Math.max(0, readyTimeoutMs)
|
|
132
|
+
while (true) {
|
|
133
|
+
try {
|
|
134
|
+
return await callShellCommand<T>(bridge, method, ...args)
|
|
135
|
+
} catch (error) {
|
|
136
|
+
if (!isShellCommandUnavailable(error) || Date.now() >= deadline) throw error
|
|
137
|
+
await sleep(50)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export async function waitForSimReady(
|
|
143
|
+
wsPort: number,
|
|
144
|
+
commandTimeoutMs: number,
|
|
145
|
+
simId?: string,
|
|
146
|
+
opts: {
|
|
147
|
+
attempts?: number
|
|
148
|
+
intervalMs?: number
|
|
149
|
+
minNodeCount?: number
|
|
150
|
+
simIdSource?: ParsedBridgeCliArgs['simIdSource']
|
|
151
|
+
} = {},
|
|
152
|
+
) {
|
|
153
|
+
const attempts = opts.attempts ?? 30
|
|
154
|
+
const intervalMs = opts.intervalMs ?? 500
|
|
155
|
+
const minNodeCount = opts.minNodeCount ?? 10
|
|
156
|
+
|
|
157
|
+
for (let i = 0; i < attempts; i++) {
|
|
158
|
+
const bridge = createBridge(wsPort, {
|
|
159
|
+
commandTimeoutMs,
|
|
160
|
+
simId,
|
|
161
|
+
simIdSource: opts.simIdSource,
|
|
162
|
+
})
|
|
163
|
+
try {
|
|
164
|
+
const count = await bridge.send({
|
|
165
|
+
type: 'evaluate',
|
|
166
|
+
code: '(async () => (await window.__sootsimTest?.getNodeCount()) || 0)()',
|
|
167
|
+
})
|
|
168
|
+
if (typeof count === 'number' && count > minNodeCount) {
|
|
169
|
+
return { bridge, count }
|
|
170
|
+
}
|
|
171
|
+
} catch {
|
|
172
|
+
// still loading or disconnected
|
|
173
|
+
}
|
|
174
|
+
bridge.close()
|
|
175
|
+
await sleep(intervalMs)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return null
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// waits until a bridge can evaluate a trivial expression. used by the reload
|
|
182
|
+
// full-page path after window.location.reload() to re-establish a bridge
|
|
183
|
+
// before handing off to pollForReloadReady. intentionally does not gate on
|
|
184
|
+
// node count — that check belongs in the subsequent ready poll, which uses
|
|
185
|
+
// the __sootsimExternalAppReady flag as its primary signal.
|
|
186
|
+
export async function waitForBridgeConnected(
|
|
187
|
+
wsPort: number,
|
|
188
|
+
commandTimeoutMs: number,
|
|
189
|
+
simId: string | undefined,
|
|
190
|
+
opts: {
|
|
191
|
+
timeoutMs?: number
|
|
192
|
+
intervalMs?: number
|
|
193
|
+
simIdSource?: ParsedBridgeCliArgs['simIdSource']
|
|
194
|
+
} = {},
|
|
195
|
+
): Promise<WsBridge | null> {
|
|
196
|
+
const timeoutMs = opts.timeoutMs ?? 8000
|
|
197
|
+
const intervalMs = opts.intervalMs ?? 250
|
|
198
|
+
const deadline = Date.now() + timeoutMs
|
|
199
|
+
while (Date.now() < deadline) {
|
|
200
|
+
const bridge = createBridge(wsPort, {
|
|
201
|
+
commandTimeoutMs,
|
|
202
|
+
simId,
|
|
203
|
+
simIdSource: opts.simIdSource,
|
|
204
|
+
})
|
|
205
|
+
try {
|
|
206
|
+
await bridge.send({ type: 'evaluate', code: '1' })
|
|
207
|
+
return bridge
|
|
208
|
+
} catch {
|
|
209
|
+
bridge.close()
|
|
210
|
+
}
|
|
211
|
+
await sleep(intervalMs)
|
|
212
|
+
}
|
|
213
|
+
return null
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export interface ReloadReadyResult {
|
|
217
|
+
ready: boolean
|
|
218
|
+
source: 'flag' | 'nodes-fallback' | 'error-bail' | 'timeout'
|
|
219
|
+
elapsedMs: number
|
|
220
|
+
nodes: number
|
|
221
|
+
targets: number
|
|
222
|
+
liveFrameActive: boolean
|
|
223
|
+
liveFrameChannels: number
|
|
224
|
+
liveFramePublishes: number
|
|
225
|
+
flag: unknown
|
|
226
|
+
loadingText: string
|
|
227
|
+
externalReady: boolean | null
|
|
228
|
+
externalStatus: string
|
|
229
|
+
externalError: string
|
|
230
|
+
errors: number
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// drives the post-reload wait with short bridge-side probes. used by
|
|
234
|
+
// `sootsim reload` (both the in-place hot-remount and the full-page reload
|
|
235
|
+
// paths). primary ready signal is window.__sootsimExternalAppReady, set by
|
|
236
|
+
// shell-events-bridge on the `sootsim:externalAppReady` event, plus visible
|
|
237
|
+
// inspectable content (or a larger non-placeholder tree) and a brief stable
|
|
238
|
+
// node count. a single long in-page evaluate can hold a stale shell-owned tree
|
|
239
|
+
// snapshot until it returns; separate probes let the host process pending
|
|
240
|
+
// shellTreeUpdate traffic between reads. bails early if the console picks up
|
|
241
|
+
// errors after a grace period with the flag still unset — fatal bundle/eval
|
|
242
|
+
// failures pile up errors fast and never emit ready, so waiting the full
|
|
243
|
+
// budget just wastes wall time. falls back to the same content gate on timeout
|
|
244
|
+
// so apps that never wire up the shell-events bridge don't hard-fail reload.
|
|
245
|
+
export async function pollForReloadReady(
|
|
246
|
+
bridge: WsBridge,
|
|
247
|
+
opts: {
|
|
248
|
+
timeoutMs?: number
|
|
249
|
+
errorGraceMs?: number
|
|
250
|
+
pollIntervalMs?: number
|
|
251
|
+
} = {},
|
|
252
|
+
): Promise<ReloadReadyResult> {
|
|
253
|
+
const timeoutMs = opts.timeoutMs ?? 10000
|
|
254
|
+
const errorGraceMs = opts.errorGraceMs ?? 3000
|
|
255
|
+
const pollIntervalMs = opts.pollIntervalMs ?? 200
|
|
256
|
+
const start = Date.now()
|
|
257
|
+
const deadline = start + timeoutMs
|
|
258
|
+
let lastNodes = -1
|
|
259
|
+
let nodeStableSince = start
|
|
260
|
+
let lastExternalReady: unknown = null
|
|
261
|
+
let lastExternalErrored = false
|
|
262
|
+
let last: ReloadReadyResult = {
|
|
263
|
+
ready: false,
|
|
264
|
+
source: 'timeout',
|
|
265
|
+
elapsedMs: 0,
|
|
266
|
+
nodes: 0,
|
|
267
|
+
targets: 0,
|
|
268
|
+
liveFrameActive: false,
|
|
269
|
+
liveFrameChannels: 0,
|
|
270
|
+
liveFramePublishes: 0,
|
|
271
|
+
flag: undefined,
|
|
272
|
+
loadingText: '',
|
|
273
|
+
externalReady: null,
|
|
274
|
+
externalStatus: '',
|
|
275
|
+
externalError: '',
|
|
276
|
+
errors: 0,
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
while (Date.now() < deadline) {
|
|
280
|
+
try {
|
|
281
|
+
const probe = (await bridge.send({
|
|
282
|
+
type: 'evaluate',
|
|
283
|
+
code: WAIT_READY_PROBE,
|
|
284
|
+
})) as Partial<ReadyProbe> | null
|
|
285
|
+
const elapsedMs = Date.now() - start
|
|
286
|
+
const nodes = Number(probe?.nodes) || 0
|
|
287
|
+
const targets = Number(probe?.targets) || 0
|
|
288
|
+
const liveFrameActive = probe?.liveFrameActive === true
|
|
289
|
+
const liveFrameChannels = Number(probe?.liveFrameChannels) || 0
|
|
290
|
+
const liveFramePublishes = Number(probe?.liveFramePublishes) || 0
|
|
291
|
+
const errors = Number(probe?.errors) || 0
|
|
292
|
+
const readyFlag = probe?.flag === true
|
|
293
|
+
const externalReady = probe?.externalReady
|
|
294
|
+
const loadingText =
|
|
295
|
+
typeof probe?.loadingText === 'string' ? probe.loadingText.trim() : ''
|
|
296
|
+
const externalStatus =
|
|
297
|
+
typeof probe?.externalStatus === 'string' ? probe.externalStatus.trim() : ''
|
|
298
|
+
const externalError =
|
|
299
|
+
typeof probe?.externalError === 'string' ? probe.externalError.trim() : ''
|
|
300
|
+
const externalErrored =
|
|
301
|
+
typeof probe?.externalError === 'string' && externalError.length > 0
|
|
302
|
+
lastExternalReady = externalReady
|
|
303
|
+
lastExternalErrored = externalErrored
|
|
304
|
+
if (nodes !== lastNodes) {
|
|
305
|
+
lastNodes = nodes
|
|
306
|
+
nodeStableSince = elapsedMs
|
|
307
|
+
}
|
|
308
|
+
last = {
|
|
309
|
+
ready: false,
|
|
310
|
+
source: 'timeout',
|
|
311
|
+
elapsedMs,
|
|
312
|
+
nodes,
|
|
313
|
+
targets,
|
|
314
|
+
liveFrameActive,
|
|
315
|
+
liveFrameChannels,
|
|
316
|
+
liveFramePublishes,
|
|
317
|
+
flag: probe?.flag,
|
|
318
|
+
loadingText,
|
|
319
|
+
externalReady: typeof externalReady === 'boolean' ? externalReady : null,
|
|
320
|
+
externalStatus,
|
|
321
|
+
externalError,
|
|
322
|
+
errors,
|
|
323
|
+
}
|
|
324
|
+
if (
|
|
325
|
+
readyFlag &&
|
|
326
|
+
externalReady !== false &&
|
|
327
|
+
!externalErrored &&
|
|
328
|
+
!loadingText &&
|
|
329
|
+
readyProbeHasContent({ liveFrameActive, nodes, targets })
|
|
330
|
+
) {
|
|
331
|
+
if (elapsedMs - nodeStableSince >= READY_NODE_STABLE_MS) {
|
|
332
|
+
return { ...last, ready: true, source: 'flag' }
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (elapsedMs >= errorGraceMs && errors > 0 && !readyFlag) {
|
|
336
|
+
return { ...last, ready: false, source: 'error-bail' }
|
|
337
|
+
}
|
|
338
|
+
} catch {
|
|
339
|
+
// transient bridge disconnect during full-page reload — keep polling.
|
|
340
|
+
}
|
|
341
|
+
await sleep(pollIntervalMs)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (
|
|
345
|
+
lastExternalReady !== false &&
|
|
346
|
+
!lastExternalErrored &&
|
|
347
|
+
!last.loadingText &&
|
|
348
|
+
readyProbeHasContent(last)
|
|
349
|
+
) {
|
|
350
|
+
return { ...last, ready: true, source: 'nodes-fallback' }
|
|
351
|
+
}
|
|
352
|
+
return { ...last, ready: false, source: 'timeout', elapsedMs: Date.now() - start }
|
|
353
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { sleep } from './shared'
|
|
2
|
+
|
|
3
|
+
export async function runSleepSubcommand(opts: {
|
|
4
|
+
positional: string[]
|
|
5
|
+
inspectUsage: (name: string, tail: string) => string
|
|
6
|
+
}): Promise<void> {
|
|
7
|
+
const seconds = opts.positional[1] ? Number(opts.positional[1]) : 0.5
|
|
8
|
+
if (!Number.isFinite(seconds) || seconds < 0) {
|
|
9
|
+
console.error(opts.inspectUsage('sleep', '[seconds]'))
|
|
10
|
+
process.exit(1)
|
|
11
|
+
}
|
|
12
|
+
await sleep(seconds * 1000)
|
|
13
|
+
console.log(` slept ${seconds}s`)
|
|
14
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// `sootsim get tree` — raw node tree dump, default depth 5.
|
|
2
|
+
//
|
|
3
|
+
// goes through the merged test bridge (shell + tenant) rather than the raw
|
|
4
|
+
// tree message, which only walks the tenant root.
|
|
5
|
+
|
|
6
|
+
import { inspectTree } from './core'
|
|
7
|
+
import { printJson, wantsJson } from './shared'
|
|
8
|
+
import type { WsBridge } from '../../ws-bridge'
|
|
9
|
+
|
|
10
|
+
export async function runTreeSubcommand(opts: {
|
|
11
|
+
bridge: WsBridge
|
|
12
|
+
args: string[]
|
|
13
|
+
positional: string[]
|
|
14
|
+
}): Promise<void> {
|
|
15
|
+
const { bridge, args, positional } = opts
|
|
16
|
+
// --boxes: structured nested rects ({ type, testID?, text?, x, y, w, h,
|
|
17
|
+
// bg?, fg?, br?, children? }) in absolute device coords — for layout
|
|
18
|
+
// tooling that needs real geometry, not the rendered text dump. deeper
|
|
19
|
+
// default depth because geometry consumers want the whole tree.
|
|
20
|
+
const boxes = args.includes('--boxes')
|
|
21
|
+
const depth = positional[1] ? Number(positional[1]) : boxes ? 50 : 5
|
|
22
|
+
const { tree } = await inspectTree(
|
|
23
|
+
bridge,
|
|
24
|
+
depth,
|
|
25
|
+
boxes ? { format: 'boxes' } : undefined,
|
|
26
|
+
)
|
|
27
|
+
if (wantsJson(args)) {
|
|
28
|
+
printJson({ depth, tree: tree ?? null })
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
console.log(typeof tree === 'string' ? tree : JSON.stringify(tree, null, 2))
|
|
32
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// `sootsim get url` — prints the current page URL the bridge is attached to.
|
|
2
|
+
|
|
3
|
+
import { inspectUrl } from './core'
|
|
4
|
+
import { printJson, wantsJson } from './shared'
|
|
5
|
+
import type { WsBridge } from '../../ws-bridge'
|
|
6
|
+
|
|
7
|
+
export async function runUrlSubcommand(
|
|
8
|
+
bridge: WsBridge,
|
|
9
|
+
opts: { args: string[] } = { args: [] },
|
|
10
|
+
): Promise<void> {
|
|
11
|
+
const result = await inspectUrl(bridge)
|
|
12
|
+
if (wantsJson(opts.args)) {
|
|
13
|
+
printJson(result)
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
console.log(result.url)
|
|
17
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// sootsim wait event — block until a semantic timeline event of the
|
|
2
|
+
// given kind appears, or timeout.
|
|
3
|
+
//
|
|
4
|
+
// usage:
|
|
5
|
+
// sootsim wait event <kind> [--max-ms 5000] [--since now|cursor]
|
|
6
|
+
// sootsim wait event fetch [--filter <substring>]
|
|
7
|
+
// sootsim wait event screen [--equals SignInPage]
|
|
8
|
+
//
|
|
9
|
+
// the existing wait family is layout-driven: `wait selector` polls
|
|
10
|
+
// for a testID, `wait idle` polls for rAF + animation settling,
|
|
11
|
+
// `wait ready` polls for bundle paint. `wait event` is the
|
|
12
|
+
// timeline-driven companion: agents that want to drive "tap → wait
|
|
13
|
+
// for the BeginSignIn fetch to land → tap next" no longer need to
|
|
14
|
+
// chain `do tap-text Continue` + `sleep 4` + `what-happened --kinds
|
|
15
|
+
// fetch --since 4s | grep BeginSignIn`. one read returns when the
|
|
16
|
+
// next event of the requested kind appears.
|
|
17
|
+
//
|
|
18
|
+
// the implementation polls timeline.recent() — server-side
|
|
19
|
+
// subscription would be cheaper but a 200ms poll is plenty for the
|
|
20
|
+
// human-paced UI events we care about and stays compatible with the
|
|
21
|
+
// existing query surface.
|
|
22
|
+
|
|
23
|
+
import type { WsBridge } from '../../ws-bridge'
|
|
24
|
+
import type {
|
|
25
|
+
SootSimTimelineEvent,
|
|
26
|
+
SootSimTimelineQuery,
|
|
27
|
+
SootSimTimelineQueryResult,
|
|
28
|
+
} from '@sootsim/globals'
|
|
29
|
+
|
|
30
|
+
interface WaitEventOptions {
|
|
31
|
+
bridge: WsBridge
|
|
32
|
+
args: string[]
|
|
33
|
+
positional: string[]
|
|
34
|
+
inspectUsage: (name: string, tail: string) => string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// every timeline event kind the cli currently knows about. matching
|
|
38
|
+
// the type on `SootSimTimelineEvent['kind']` would be more strict
|
|
39
|
+
// but the timeline can grow new kinds and we don't want this
|
|
40
|
+
// command to refuse an unknown one — the worst case is the wait
|
|
41
|
+
// just times out, which is exactly the right behaviour.
|
|
42
|
+
const KNOWN_KINDS = new Set([
|
|
43
|
+
'app-launch',
|
|
44
|
+
'toast',
|
|
45
|
+
'keyboard',
|
|
46
|
+
'screen',
|
|
47
|
+
'flow-step',
|
|
48
|
+
'route',
|
|
49
|
+
'alert',
|
|
50
|
+
'actionsheet',
|
|
51
|
+
'picker',
|
|
52
|
+
'notification',
|
|
53
|
+
'fetch',
|
|
54
|
+
'console',
|
|
55
|
+
'shell',
|
|
56
|
+
'scroll',
|
|
57
|
+
'gesture',
|
|
58
|
+
'text-input',
|
|
59
|
+
'react-commit',
|
|
60
|
+
'animation',
|
|
61
|
+
'reanimated',
|
|
62
|
+
])
|
|
63
|
+
|
|
64
|
+
function findFlagValue(args: string[], flag: string): string | undefined {
|
|
65
|
+
const idx = args.indexOf(flag)
|
|
66
|
+
if (idx >= 0 && idx + 1 < args.length) return args[idx + 1]
|
|
67
|
+
return undefined
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function eventMatches(
|
|
71
|
+
ev: SootSimTimelineEvent,
|
|
72
|
+
opts: { filter?: string; equals?: string },
|
|
73
|
+
): boolean {
|
|
74
|
+
if (!opts.filter && !opts.equals) return true
|
|
75
|
+
const data = ev.data as Record<string, unknown> | null
|
|
76
|
+
// build a one-string haystack from the most-likely-relevant fields
|
|
77
|
+
// per kind. agents asking for `wait event fetch --filter BeginSignIn`
|
|
78
|
+
// expect to match the URL; `wait event screen --equals Inbox`
|
|
79
|
+
// expects to match the screen name.
|
|
80
|
+
const haystack: string[] = []
|
|
81
|
+
if (data && typeof data === 'object') {
|
|
82
|
+
for (const key of [
|
|
83
|
+
'url',
|
|
84
|
+
'displayUrl',
|
|
85
|
+
'message',
|
|
86
|
+
'name',
|
|
87
|
+
'activeName',
|
|
88
|
+
'path',
|
|
89
|
+
'pathname',
|
|
90
|
+
'title',
|
|
91
|
+
'phase',
|
|
92
|
+
'event',
|
|
93
|
+
'type',
|
|
94
|
+
'kind',
|
|
95
|
+
]) {
|
|
96
|
+
const v = data[key]
|
|
97
|
+
if (typeof v === 'string' && v.length > 0) haystack.push(v)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const joined = haystack.join(' ')
|
|
101
|
+
if (opts.equals) {
|
|
102
|
+
return haystack.some((s) => s === opts.equals)
|
|
103
|
+
}
|
|
104
|
+
if (opts.filter) {
|
|
105
|
+
return joined.toLowerCase().includes(opts.filter.toLowerCase())
|
|
106
|
+
}
|
|
107
|
+
return true
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function runWaitEventSubcommand(opts: WaitEventOptions): Promise<void> {
|
|
111
|
+
const { bridge, args, positional, inspectUsage } = opts
|
|
112
|
+
// positional shape after verb-prefix strip and stripBooleanFlags:
|
|
113
|
+
// sootsim wait event <kind> → ['event', '<kind>']
|
|
114
|
+
// so positional[1] is the kind.
|
|
115
|
+
const kind = positional[1]
|
|
116
|
+
if (!kind) {
|
|
117
|
+
console.error(
|
|
118
|
+
inspectUsage(
|
|
119
|
+
'wait event',
|
|
120
|
+
'<kind> [--max-ms 5000] [--filter <substring>] [--equals <exact>] [--since now|cursor]',
|
|
121
|
+
),
|
|
122
|
+
)
|
|
123
|
+
process.exit(1)
|
|
124
|
+
}
|
|
125
|
+
if (!KNOWN_KINDS.has(kind)) {
|
|
126
|
+
console.error(
|
|
127
|
+
` warning: '${kind}' is not a known timeline kind — waiting anyway. known: ${[...KNOWN_KINDS].sort().join(', ')}`,
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const maxMsRaw = findFlagValue(args, '--max-ms')
|
|
132
|
+
const maxMs =
|
|
133
|
+
maxMsRaw && Number.isFinite(Number(maxMsRaw)) ? Math.max(100, Number(maxMsRaw)) : 5000
|
|
134
|
+
|
|
135
|
+
const filter = findFlagValue(args, '--filter')
|
|
136
|
+
const equals = findFlagValue(args, '--equals')
|
|
137
|
+
const sinceMode = findFlagValue(args, '--since') ?? 'now'
|
|
138
|
+
const jsonOut = args.includes('--json')
|
|
139
|
+
|
|
140
|
+
// capture the start watermark so we don't match an event that
|
|
141
|
+
// happened before this command. agents calling `wait event fetch`
|
|
142
|
+
// after a tap mean "the *next* fetch", not "any fetch since the
|
|
143
|
+
// session started". `--since cursor` falls back to the
|
|
144
|
+
// what-happened cursor semantics for callers that explicitly want
|
|
145
|
+
// that.
|
|
146
|
+
const startedAt = Date.now()
|
|
147
|
+
const deadline = startedAt + maxMs
|
|
148
|
+
const pollMs = 200
|
|
149
|
+
|
|
150
|
+
let lastWatermark = startedAt
|
|
151
|
+
|
|
152
|
+
while (Date.now() < deadline) {
|
|
153
|
+
const query: SootSimTimelineQuery = {
|
|
154
|
+
kinds: [kind] as SootSimTimelineQuery['kinds'],
|
|
155
|
+
since: sinceMode === 'cursor' ? undefined : lastWatermark,
|
|
156
|
+
limit: 50,
|
|
157
|
+
}
|
|
158
|
+
const result = (await bridge.send({
|
|
159
|
+
type: 'evaluate',
|
|
160
|
+
// hand-marshal the query through evaluate — the bridge already
|
|
161
|
+
// exposes `SootSim.bridges.timeline.recent` as a sync method,
|
|
162
|
+
// but evaluate is the universally available shape and the cost
|
|
163
|
+
// of a string-baked function is negligible at 5Hz.
|
|
164
|
+
code: `(async () => {
|
|
165
|
+
const t = window.SootSim?.bridges?.timeline
|
|
166
|
+
if (!t) return { ok: false, error: 'timeline bridge missing' }
|
|
167
|
+
return { ok: true, result: await t.recent(${JSON.stringify(query)}) }
|
|
168
|
+
})()`,
|
|
169
|
+
})) as { ok: true; result: SootSimTimelineQueryResult } | { ok: false; error: string }
|
|
170
|
+
if (!result || !result.ok) {
|
|
171
|
+
console.error(
|
|
172
|
+
` could not query timeline: ${result && 'error' in result ? result.error : 'unknown'}`,
|
|
173
|
+
)
|
|
174
|
+
process.exit(1)
|
|
175
|
+
}
|
|
176
|
+
const evts = result.result.events ?? []
|
|
177
|
+
for (const ev of evts) {
|
|
178
|
+
if (eventMatches(ev, { filter, equals })) {
|
|
179
|
+
const elapsed = Date.now() - startedAt
|
|
180
|
+
if (jsonOut) {
|
|
181
|
+
console.log(JSON.stringify({ found: true, elapsedMs: elapsed, event: ev }))
|
|
182
|
+
} else {
|
|
183
|
+
console.log(
|
|
184
|
+
` ${kind} event after ${elapsed}ms${filter ? ` (filter: ${filter})` : ''}${equals ? ` (equals: ${equals})` : ''}`,
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// advance the local watermark to the high-water mark we just
|
|
191
|
+
// saw so the next poll doesn't redundantly re-scan the same
|
|
192
|
+
// events.
|
|
193
|
+
if (result.result.watermark && result.result.watermark > lastWatermark) {
|
|
194
|
+
lastWatermark = result.result.watermark
|
|
195
|
+
}
|
|
196
|
+
await new Promise((r) => setTimeout(r, pollMs))
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const elapsed = Date.now() - startedAt
|
|
200
|
+
if (jsonOut) {
|
|
201
|
+
console.log(
|
|
202
|
+
JSON.stringify({ found: false, elapsedMs: elapsed, kind, filter, equals }),
|
|
203
|
+
)
|
|
204
|
+
} else {
|
|
205
|
+
console.error(
|
|
206
|
+
` ⚠ wait event ${kind} timed out after ${elapsed}ms${filter ? ` (filter: ${filter})` : ''}${equals ? ` (equals: ${equals})` : ''}`,
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
process.exit(1)
|
|
210
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { resolveMaxMsFlag } from './core'
|
|
2
|
+
import { waitForSootsimIdle } from './settling'
|
|
3
|
+
import type { WsBridge } from '../../ws-bridge'
|
|
4
|
+
|
|
5
|
+
// alias for `do settle` under the discoverable `wait` verb grouping.
|
|
6
|
+
// layout hash stability across 3 polls; --strict also requires worker
|
|
7
|
+
// animation flags to be clear.
|
|
8
|
+
export async function runWaitIdleSubcommand(opts: {
|
|
9
|
+
bridge: WsBridge
|
|
10
|
+
args: string[]
|
|
11
|
+
positional: string[]
|
|
12
|
+
}): Promise<void> {
|
|
13
|
+
const { bridge, args } = opts
|
|
14
|
+
const strict = args.includes('--strict')
|
|
15
|
+
const maxMs = resolveMaxMsFlag(args, 3000)
|
|
16
|
+
const { elapsed, settled } = await waitForSootsimIdle({ bridge, maxMs, strict })
|
|
17
|
+
|
|
18
|
+
if (settled) {
|
|
19
|
+
console.log(` idle in ${elapsed}ms`)
|
|
20
|
+
} else {
|
|
21
|
+
console.error(` ⚠ wait idle timed out after ${elapsed}ms (may still be animating)`)
|
|
22
|
+
process.exit(1)
|
|
23
|
+
}
|
|
24
|
+
}
|