stim 1.14.0 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/README.md +2 -0
  2. package/dist/activity-BxwdD81S.mjs +609 -0
  3. package/dist/agent-device-usage-output-Dpih1Tfa.mjs +411 -0
  4. package/dist/{android-d6rGq1pA.mjs → android-B_NumVyQ.mjs} +169 -81
  5. package/dist/{android-LbPFRWVL.mjs → android-Ift4kVvt.mjs} +517 -413
  6. package/dist/android-cas-8U4DPqeR.mjs +167 -0
  7. package/dist/android-cas-compiler.mjs +2 -2
  8. package/dist/{android-XpvCrV7q.mjs → android-r2rBL8kV.mjs} +2 -2
  9. package/dist/{app-install-BpgSgkJj.mjs → app-install-CKWdbXIX.mjs} +128 -151
  10. package/dist/attempt-Bk2ouQVL.mjs +39 -0
  11. package/dist/budget-DjrURfQn.mjs +2700 -0
  12. package/dist/{build-plan-Dcr-3ie0.mjs → build-plan-DjeSb2Xl.mjs} +192 -653
  13. package/dist/build-slots-rUk77xqh.mjs +132 -0
  14. package/dist/{cache-manifest-C68QQ0ua.mjs → cache-manifest-DnUHBJaU.mjs} +2 -2
  15. package/dist/cache-manifest.mjs +1 -1
  16. package/dist/{chrome-BPCiC3ZX.mjs → chrome-BrcVWQyD.mjs} +1 -1
  17. package/dist/cli-BkYZ_6hy.mjs +51 -0
  18. package/dist/cli.mjs +6 -42
  19. package/dist/client-BTY686R6.mjs +1368 -0
  20. package/dist/collector-run.mjs +9 -195
  21. package/dist/{command-output-DVcpw6eE.mjs → command-output-DnJMdEY-.mjs} +1 -1
  22. package/dist/{config-lvc7d3C2.mjs → config-D0DbpZtM.mjs} +28 -29
  23. package/dist/{created-devices-CllH_8eb.mjs → created-devices-D5owjN4C.mjs} +2 -2
  24. package/dist/{dependency-state-DsT7aKeo.mjs → dependency-state-B6cMXZab.mjs} +2 -2
  25. package/dist/{deps-D9IcKqXS.mjs → deps-urbmfXig.mjs} +3 -3
  26. package/dist/{detached-entry-DJHoca49.mjs → detached-entry-CiTEgMR1.mjs} +1 -1
  27. package/dist/dev-client-CroAIjjj.mjs +43 -0
  28. package/dist/{device-CjuuAJjQ.mjs → device-DZQjF7H5.mjs} +8 -7
  29. package/dist/device-capacity-BHwxNBlu.mjs +136 -0
  30. package/dist/device-host-worker.d.mts +1 -0
  31. package/dist/device-host-worker.mjs +964 -0
  32. package/dist/device-ios-BadmeL2V.mjs +381 -0
  33. package/dist/{device-lease-5c3WrVRm.mjs → device-lease-D-hLqx1b.mjs} +43 -36
  34. package/dist/{device-pool-BiyDyPP-.mjs → device-lease-run-CqjShqWW.mjs} +6 -126
  35. package/dist/device-pool-CUaEWMMm.mjs +125 -0
  36. package/dist/{device-remote-D87zEzaq.mjs → device-remote-D5WVWqOq.mjs} +69 -163
  37. package/dist/{doctor-x9pnU9fq.mjs → doctor-Cimf-gGB.mjs} +241 -103
  38. package/dist/{error-diagnostics-C5HzcwCW.mjs → error-diagnostics-B9L4Ae0x.mjs} +15 -18
  39. package/dist/{exec-DK8KaVHK.mjs → exec-CaErVv7t.mjs} +5 -1
  40. package/dist/{gc-ko6mUAYD.mjs → gc-Bqy4bjEu.mjs} +599 -108
  41. package/dist/{android-cas-iZK3DOCr.mjs → gradle-BsO1MdsH.mjs} +8 -294
  42. package/dist/{guide-6R0VNFqI.mjs → guide-B_uyKTxW.mjs} +2511 -239
  43. package/dist/{guide-status-BZrFPIA_.mjs → guide-status-CjEZMAD4.mjs} +3 -3
  44. package/dist/hosted-client-a-WmqE2L.mjs +197 -0
  45. package/dist/hosted-ios-CIWelIqf.mjs +220 -0
  46. package/dist/hosted-logs-D5g041Q3.mjs +187 -0
  47. package/dist/hosted-macos-BSZyIb3y.mjs +135 -0
  48. package/dist/idle-DYVymG92.mjs +1099 -0
  49. package/dist/idle-shutdown-CTsDnAde.mjs +122 -0
  50. package/dist/in-use-DvBw0Kxo.mjs +130 -0
  51. package/dist/{ios-BwDPrnYQ.mjs → ios-BaInn3sW.mjs} +201 -10
  52. package/dist/ios-Cr1h5gnO.mjs +189 -0
  53. package/dist/{ios-P1f5nm2n.mjs → ios-aq3QoGI3.mjs} +1159 -633
  54. package/dist/{ios-device-BiOsSVzd.mjs → ios-device-Ee7sDXfp.mjs} +1 -1
  55. package/dist/{ios-device-DGglU3E_.mjs → ios-device-FfCN_C8p.mjs} +12 -7
  56. package/dist/ios-state-DS5UpYTH.mjs +48 -0
  57. package/dist/{launch-verify-Deg4tB3Z.mjs → launch-verify-2DBRizjo.mjs} +5 -5
  58. package/dist/logs-CHEVK8zD.mjs +371 -0
  59. package/dist/machines-BIBUdhpj.mjs +265 -0
  60. package/dist/macos-CSYEAFmF.mjs +473 -0
  61. package/dist/macos-run.d.mts +5 -0
  62. package/dist/macos-run.mjs +2 -0
  63. package/dist/maintenance-run.d.mts +4 -0
  64. package/dist/maintenance-run.mjs +254 -0
  65. package/dist/{metro-CcEo8Fl8.mjs → metro-BeK1ZPz1.mjs} +20 -120
  66. package/dist/metro-gateway-BJ-rxQAB.mjs +84 -0
  67. package/dist/{named-ports-Blg8IxOh.mjs → named-ports-Bk_YXEmE.mjs} +6 -59
  68. package/dist/native-run-NaBrRFtQ.mjs +147 -0
  69. package/dist/{native-runtime-DjBHRg5m.mjs → native-runtime-DBzRnEKS.mjs} +9 -5
  70. package/dist/{ndjson-CDY9IkVC.mjs → ndjson-PclOIwVY.mjs} +2 -2
  71. package/dist/offload-worker.d.mts +122 -0
  72. package/dist/offload-worker.mjs +606 -0
  73. package/dist/{ownership-DyYDF5ww.mjs → ownership-DiA16AsN.mjs} +3 -2
  74. package/dist/ownership-hCkPzGmr.mjs +571 -0
  75. package/dist/{page-BHUzTDFt.mjs → page-QF_Ke10s.mjs} +7 -2
  76. package/dist/{ports-BguS83ex.mjs → ports-R2k4Knz3.mjs} +3 -3
  77. package/dist/prebuild-D2fsca_Z.mjs +213 -0
  78. package/dist/preview-DhVbvPAZ.mjs +318 -0
  79. package/dist/process-identity-BmRm1qV0.mjs +12 -0
  80. package/dist/{project-CWlvEaeN.mjs → project-Du91Ilvc.mjs} +74 -19
  81. package/dist/pull-request-pxnDCQzn.mjs +322 -0
  82. package/dist/pull-requests.d.mts +120 -0
  83. package/dist/pull-requests.mjs +2 -0
  84. package/dist/recordings-BIXOzVnr.mjs +37 -0
  85. package/dist/{reload-BiniwsSZ.mjs → reload-rM9jV1fz.mjs} +109 -24
  86. package/dist/{remote-cache-CcR6o6lI.mjs → remote-cache-C8-V13XN.mjs} +5 -5
  87. package/dist/run-f4vI6EnK.mjs +119 -0
  88. package/dist/{server-bare-DGfgyJ5J.mjs → server-bare-CpPGmnmO.mjs} +33 -15
  89. package/dist/server-expo-CVqO2c35.mjs +2 -0
  90. package/dist/{server-expo-BVuwQ65v.mjs → server-expo-MC0rHlOC.mjs} +8 -12
  91. package/dist/settings-BBpMcDbD.mjs +25 -0
  92. package/dist/{settings-B-fsC-Om.mjs → settings-ZkDzkzvB.mjs} +22 -10
  93. package/dist/settings-oTSkkNrM.mjs +1439 -0
  94. package/dist/settings.schema.json +845 -97
  95. package/dist/{simslim-DrDN4WuV.mjs → simslim-B8-7AoTI.mjs} +8 -132
  96. package/dist/{slot-launch-D5t8EiMf.mjs → slot-launch-B8VrHxn1.mjs} +11 -8
  97. package/dist/{spawn-claims-Drw_o-Fb.mjs → spawn-claims-D0XfSe8u.mjs} +1 -1
  98. package/dist/spawn-entry-jw19m8Nl.mjs +17 -0
  99. package/dist/stage-CzaiwFsI.mjs +143 -0
  100. package/dist/start-CX8Ig-U2.mjs +2 -0
  101. package/dist/{start-s4RXp1wy.mjs → start-ClDfwXa4.mjs} +116 -65
  102. package/dist/state-3DwSQIBK.mjs +41 -0
  103. package/dist/state-B4PQFXMW.d.mts +12 -0
  104. package/dist/{state-Dk9RVXI7.mjs → state-C1jB7Q8q.mjs} +70 -8
  105. package/dist/{state-DHqvsbqC.mjs → state-DcW6vOd_.mjs} +7 -4
  106. package/dist/stats-CEZ_i5je.mjs +77 -0
  107. package/dist/{status-BgAuxESg.mjs → status-BuvBFeII.mjs} +118 -20
  108. package/dist/{status-BsPAnfOI.mjs → status-Cs7VLg9D.mjs} +542 -283
  109. package/dist/status-DlKvVJBE.mjs +49 -0
  110. package/dist/stdout-reader-C8yjre1x.mjs +23 -0
  111. package/dist/stim-desktop-Cnz8CPnl.mjs +63 -0
  112. package/dist/stim-footprint.swift +32 -0
  113. package/dist/{stim-installations-CINXy_su.mjs → stim-installations-QMEI32v1.mjs} +1 -1
  114. package/dist/stop-B0Yxtmiy.mjs +48 -0
  115. package/dist/{stop-BzreOWJp.mjs → stop-Bffozsac.mjs} +83 -16
  116. package/dist/{stop-BWIF_6BK.mjs → stop-Dt_dM16A.mjs} +2 -2
  117. package/dist/stop-cause-CidxIqup.mjs +97 -0
  118. package/dist/supervisor-run.d.mts +43 -11
  119. package/dist/supervisor-run.mjs +363 -24
  120. package/dist/support-CAP8qDUF.mjs +316 -0
  121. package/dist/support-D4aYJniO.mjs +201 -0
  122. package/dist/teardown-CL6vDs2Y.mjs +727 -0
  123. package/dist/{xcode-Cp5KvK9x.mjs → toolchain-DkUflduf.mjs} +364 -14
  124. package/dist/trigger-Bemu8ytw.mjs +65 -0
  125. package/dist/trigger-CXts6qt1.mjs +2 -0
  126. package/dist/warm-progress-CpGv3Wb6.mjs +732 -0
  127. package/dist/{web-BsQs1WlO.mjs → web-DWUaMp8P.mjs} +37 -21
  128. package/dist/web-run.mjs +310 -13
  129. package/dist/workspace-process-lock-BL7Q0HCO.mjs +57 -0
  130. package/dist/workspace-state-B8-aJ1_5.mjs +371 -0
  131. package/dist/workspaces-BV4pdHFz.mjs +306 -0
  132. package/dist/{worktree-UrQWXSeb.mjs → worktree-K-xj9Db0.mjs} +625 -256
  133. package/dist/worktree-jnG8Iph5.mjs +2 -0
  134. package/package.json +10 -5
  135. package/shim/bundle-response.cjs +28 -12
  136. package/dist/activity-CYVDcrt3.mjs +0 -356
  137. package/dist/budget-CGDkhvv_.mjs +0 -1745
  138. package/dist/build-progress-DxKpwnaS.mjs +0 -247
  139. package/dist/idle-DXtx3RbX.mjs +0 -191
  140. package/dist/in-use-DlqBDKqY.mjs +0 -249
  141. package/dist/logs-Civ4gbBH.mjs +0 -486
  142. package/dist/merge-state-ytFQCeh4.mjs +0 -262
  143. package/dist/server-expo-DIHDhvGg.mjs +0 -2
  144. package/dist/settings-D61iNOsB.mjs +0 -629
  145. package/dist/start-Cetfvy8l.mjs +0 -2
  146. package/dist/stats-CFpFGlJV.mjs +0 -62
  147. package/dist/stats-CX1Z8Tca.mjs +0 -368
  148. package/dist/stim-desktop-CXt8-Z3J.mjs +0 -40
  149. package/dist/stop-cause-hjGBOZTG.mjs +0 -1231
  150. package/dist/worktree-BVrHsYZG.mjs +0 -752
  151. package/dist/worktree-CHWWvm5v.mjs +0 -2
  152. package/dist/{metro-store-DzeWkfqW.mjs → metro-store-CDDZHtPl.mjs} +1 -1
package/README.md CHANGED
@@ -43,6 +43,8 @@ reference:
43
43
 
44
44
  - [Getting started](https://stim.appandflow.com/docs/getting-started): terms,
45
45
  the first run, parallel worktrees, and what to do when a run fails.
46
+ - [Native macOS prototype](https://stim.appandflow.com/docs/macos): Swift Package
47
+ Debug apps, workspace logs and local window viewing in Stim Desktop.
46
48
  - [Web in an owned Chrome](https://stim.appandflow.com/docs/web): `stim web`
47
49
  for Expo web and other web servers, page logs, and attaching browser tools.
48
50
  - [Worktrees](https://stim.appandflow.com/docs/worktrees): `worktree warm`,
@@ -0,0 +1,609 @@
1
+ import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
+ import { t as process_identity_exports } from "./process-identity-BmRm1qV0.mjs";
3
+ import { r as readWorkspaceState$1, x as workspaceLogsDir$1 } from "./workspace-state-B8-aJ1_5.mjs";
4
+ import { i as listLeaseFiles, r as leaseIsExpired } from "./device-lease-D-hLqx1b.mjs";
5
+ import { closeSync, fstatSync, openSync, readFileSync, readSync, readdirSync, statSync } from "node:fs";
6
+ import { basename, join, resolve } from "node:path";
7
+ import { agentDeviceStateDirs, readViewedDevices } from "@stim-cli/core/state";
8
+ import { homedir } from "node:os";
9
+ //#region src/devices/automation-tools.ts
10
+ const AUTOMATION_TOOLS = [
11
+ {
12
+ tool: "agent-browser",
13
+ platforms: ["web"],
14
+ cdp: /agent-browser/i
15
+ },
16
+ {
17
+ tool: "agent-device",
18
+ platforms: [
19
+ "ios",
20
+ "android",
21
+ "web"
22
+ ],
23
+ host: /agent-device/i,
24
+ cdp: /agent-device/i
25
+ },
26
+ {
27
+ tool: "argent",
28
+ platforms: [
29
+ "ios",
30
+ "android",
31
+ "web"
32
+ ],
33
+ host: /\/bin\/[\w-]+\/(simulator-server(\.exe)?\s+(ios|android|android_device)|ax-service)\b|com\.argent\.androiddevtools/,
34
+ instrumentation: /com\.argent\.androiddevtools/,
35
+ cdp: /@swmansion\/argent\/dist\/tool-server/
36
+ },
37
+ {
38
+ tool: "idb",
39
+ platforms: ["ios"],
40
+ host: /idb_companion/
41
+ },
42
+ {
43
+ tool: "maestro",
44
+ platforms: ["ios", "android"],
45
+ host: /maestro/i
46
+ },
47
+ {
48
+ tool: "appium",
49
+ platforms: ["ios", "android"],
50
+ host: /appium|WebDriverAgent/i
51
+ },
52
+ {
53
+ tool: "xcodebuild",
54
+ platforms: ["ios"],
55
+ host: /\bxcodebuild\b.*\btest(-without-building)?\b/
56
+ },
57
+ {
58
+ tool: "simctl",
59
+ platforms: ["ios"],
60
+ host: /\bsimctl\s+(io|spawn)\b/
61
+ },
62
+ {
63
+ tool: "uiautomator",
64
+ platforms: ["android"],
65
+ instrumentation: /uiautomator/
66
+ },
67
+ {
68
+ tool: "instrumentation",
69
+ platforms: ["android"],
70
+ instrumentation: /androidx\.test|\binstrument\b/
71
+ },
72
+ {
73
+ tool: "chrome-devtools-mcp",
74
+ platforms: ["web"],
75
+ cdp: /chrome-devtools-mcp/i
76
+ },
77
+ {
78
+ tool: "playwright",
79
+ platforms: ["web"],
80
+ cdp: /playwright/i
81
+ },
82
+ {
83
+ tool: "puppeteer",
84
+ platforms: ["web"],
85
+ cdp: /puppeteer/i
86
+ }
87
+ ];
88
+ const HOST_EXCLUSIONS = [/\bsimctl\s+spawn\b.*\blog\s+stream\b/, /\blogcat\b/];
89
+ /** Stim's own DevTools clients: the browser supervisor, `stim-frames` in stim-server, Stim Desktop, the stim CLI. */
90
+ const CDP_EXCLUSIONS = [/\bstim-(frames|server|web)\b|StimDesktop|\/stim(-cli)?\/(dist|bin)\/|\/bin\/stim(\s|$)|^stim(\s|$)/];
91
+ function namesDevice(command, id) {
92
+ const escaped = id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
93
+ return new RegExp(`(^|[^A-Za-z0-9])${escaped}([^A-Za-z0-9]|$)`).test(command);
94
+ }
95
+ /** The tool a host process drives the device `id` with, or null when the command does not name the device. */
96
+ function hostDriverTool(command, platform, id) {
97
+ if (!namesDevice(command, id) || HOST_EXCLUSIONS.some((pattern) => pattern.test(command))) return null;
98
+ return AUTOMATION_TOOLS.find((entry) => entry.platforms.includes(platform) && entry.host?.test(command))?.tool ?? null;
99
+ }
100
+ /** The tool behind an on-device process's arguments, or null when it is not instrumentation. */
101
+ function instrumentationTool(args) {
102
+ return AUTOMATION_TOOLS.find((entry) => entry.platforms.includes("android") && entry.instrumentation?.test(args))?.tool ?? null;
103
+ }
104
+ /**
105
+ * The tool a DevTools client's command line names, or null for Stim's own clients. A client no entry names is
106
+ * reported by its script or executable name.
107
+ */
108
+ function cdpClientTool(command) {
109
+ if (CDP_EXCLUSIONS.some((pattern) => pattern.test(command))) return null;
110
+ const known = AUTOMATION_TOOLS.find((entry) => entry.platforms.includes("web") && entry.cdp?.test(command));
111
+ if (known) return known.tool;
112
+ const [executable = "", script] = command.trim().split(/\s+/);
113
+ const name = basename(executable);
114
+ return (/^(node|bun|deno|python3?)$/.test(name) && script ? basename(script) : name) || "unknown DevTools client";
115
+ }
116
+ //#endregion
117
+ //#region src/devices/activity.ts
118
+ const ACTIVE_WINDOW_MS = 6e5;
119
+ const LOG_TAIL_BYTES = 262144;
120
+ function classifyActivity(evidence, now) {
121
+ const last = evidence.recency.reduce((best, entry) => Number.isFinite(entry.at) && (!best || entry.at > best.at) ? entry : best, null);
122
+ const recent = {};
123
+ for (const { basis, at } of evidence.recency) if (Number.isFinite(at) && !(Date.parse(recent[basis] ?? "") >= at)) recent[basis] = new Date(at).toISOString();
124
+ const lastActivityAt = last ? {
125
+ lastActivityAt: new Date(last.at).toISOString(),
126
+ recent
127
+ } : {};
128
+ const [first] = evidence.drivers;
129
+ if (first) {
130
+ const { basis: _basis, ...driver } = first;
131
+ return {
132
+ state: "driven",
133
+ driver,
134
+ ...lastActivityAt,
135
+ basis: [...new Set(evidence.drivers.map((entry) => entry.basis))]
136
+ };
137
+ }
138
+ if (evidence.unknown.length) return {
139
+ state: "unknown",
140
+ ...lastActivityAt,
141
+ basis: [...new Set(evidence.unknown)]
142
+ };
143
+ return {
144
+ state: Boolean(last && now - last.at < ACTIVE_WINDOW_MS) ? "active" : "idle",
145
+ ...lastActivityAt,
146
+ basis: [...new Set(evidence.recency.map((entry) => entry.basis))]
147
+ };
148
+ }
149
+ function idleForMs(activity, now) {
150
+ if (activity?.state !== "idle" || !activity.lastActivityAt) return null;
151
+ const at = Date.parse(activity.lastActivityAt);
152
+ return Number.isFinite(at) ? Math.max(0, now - at) : null;
153
+ }
154
+ function field(entry, pidKey, startKey) {
155
+ const pid = entry[pidKey];
156
+ const startTime = entry[startKey];
157
+ if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || typeof startTime !== "string") return null;
158
+ return {
159
+ pid,
160
+ startTime
161
+ };
162
+ }
163
+ function parseObject(raw) {
164
+ if (raw === null) return null;
165
+ try {
166
+ const parsed = JSON.parse(raw);
167
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
168
+ } catch {
169
+ return null;
170
+ }
171
+ }
172
+ function parseAgentDeviceRecord(kind, path, raw) {
173
+ const entry = parseObject(raw);
174
+ const stem = basename(path, ".json");
175
+ const device = entry?.device && typeof entry.device === "object" ? entry.device : null;
176
+ const recordedId = kind === "claim" ? device?.id : entry?.deviceId;
177
+ const deviceId = typeof recordedId === "string" && recordedId ? recordedId : kind === "runner-lease" ? stem : null;
178
+ const createdAtMs = typeof entry?.createdAtMs === "number" ? entry.createdAtMs : null;
179
+ return {
180
+ path,
181
+ kind,
182
+ deviceId,
183
+ session: kind === "claim" && typeof entry?.session === "string" && entry.session ? entry.session : null,
184
+ workspace: kind === "claim" && typeof entry?.workspace === "string" && entry.workspace ? entry.workspace : null,
185
+ stateDir: kind === "claim" && typeof entry?.stateDir === "string" && entry.stateDir ? entry.stateDir : null,
186
+ deviceName: kind === "claim" && typeof device?.name === "string" ? device.name : null,
187
+ readable: Boolean(entry && deviceId),
188
+ owner: entry ? field(entry, "ownerPid", "ownerStartTime") : null,
189
+ runner: entry && kind === "runner-lease" ? field(entry, "runnerPid", "runnerStartTime") : null,
190
+ claimProtocol: kind === "runner-lease" && entry?.deviceClaimProtocol === 1,
191
+ createdAtMs,
192
+ xctestrunPath: typeof entry?.xctestrunPath === "string" ? entry.xctestrunPath : null
193
+ };
194
+ }
195
+ function processMatches(entry, startOf) {
196
+ const start = startOf(entry.pid);
197
+ if (start.status === "gone") return "dead";
198
+ if (start.status === "unknown") return "unknown";
199
+ const recorded = Date.parse(entry.startTime);
200
+ if (!Number.isFinite(recorded)) return "unknown";
201
+ return Math.floor(start.startedAtMs / 1e3) === Math.floor(recorded / 1e3) ? "live" : "dead";
202
+ }
203
+ function agentDeviceDaemon(stateDir, startOf = process_identity_exports.inspectProcessStart) {
204
+ let raw;
205
+ try {
206
+ raw = readFileSync(join(stateDir, "daemon.json"), "utf8");
207
+ } catch (error) {
208
+ return error.code === "ENOENT" ? "none" : "unknown";
209
+ }
210
+ const entry = parseObject(raw);
211
+ const owner = entry && field(entry, "pid", "processStartTime");
212
+ return owner ? processMatches(owner, startOf) : "unknown";
213
+ }
214
+ function agentDeviceLiveness(record, startOf) {
215
+ const results = (record.kind === "claim" ? [record.owner] : [record.owner, record.runner]).map((entry) => entry ? processMatches(entry, startOf) : "unknown");
216
+ if (results.includes("dead")) return "dead";
217
+ if (!record.readable || results.includes("unknown")) return "unknown";
218
+ return "live";
219
+ }
220
+ const HOST_PROCESS_COLUMNS = "pid=,ppid=,rss=,%cpu=,lstart=,command=";
221
+ function parseProcessTable(output) {
222
+ const rows = [];
223
+ for (const line of output.split("\n")) {
224
+ const match = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\d+(?:[.,]\d+)?)\s+(\w{3}\s+\w{3}\s+\d+\s+\d{1,2}:\d{2}:\d{2}\s+\d{4})\s+(.*)$/.exec(line);
225
+ if (!match) continue;
226
+ const at = Date.parse(match[5]);
227
+ rows.push({
228
+ pid: Number(match[1]),
229
+ ppid: Number(match[2]),
230
+ rssKb: Number(match[3]),
231
+ cpuPercent: Number(match[4].replace(",", ".")),
232
+ startedAt: Number.isFinite(at) ? new Date(at).toISOString() : null,
233
+ command: match[6]
234
+ });
235
+ }
236
+ return rows;
237
+ }
238
+ function parseAndroidInstrumentation(output) {
239
+ const found = [];
240
+ for (const line of output.split("\n")) {
241
+ const match = /^\s*(\d+)\s+(.*)$/.exec(line);
242
+ if (!match) continue;
243
+ const tool = instrumentationTool(match[2]);
244
+ if (tool) found.push({
245
+ pid: Number(match[1]),
246
+ tool
247
+ });
248
+ }
249
+ return found;
250
+ }
251
+ function latestDeviceLogAt(lines, target) {
252
+ return latestRecordAt(lines, (record) => {
253
+ if (record.src !== "device" || record.platform !== target.platform || !isDeviceRecord(record)) return false;
254
+ if (target.slot === "default") return record.slot === void 0;
255
+ return record.slot === target.slot && (record.deviceId === void 0 || record.deviceId === target.id);
256
+ });
257
+ }
258
+ function latestBundleRequestAt(lines, platform) {
259
+ return latestRecordAt(lines, (record) => record.event === "bundle_response_started" && record.platform === platform);
260
+ }
261
+ function latestRecordAt(lines, matches) {
262
+ for (let i = lines.length - 1; i >= 0; i--) {
263
+ const record = parseObject(lines[i]);
264
+ if (record && typeof record.ts === "number" && matches(record)) return record.ts;
265
+ }
266
+ return null;
267
+ }
268
+ function isDeviceRecord(record) {
269
+ return !(typeof record.event === "string" && record.event.startsWith("collector_"));
270
+ }
271
+ /** The last lines of the file at `path`, at most its final 256 KiB, or null when it cannot be read. */
272
+ function tailLines(path) {
273
+ let fd;
274
+ try {
275
+ fd = openSync(path, "r");
276
+ const size = fstatSync(fd).size;
277
+ const length = Math.min(size, LOG_TAIL_BYTES);
278
+ const buffer = Buffer.alloc(length);
279
+ readSync(fd, buffer, 0, length, size - length);
280
+ const lines = buffer.toString("utf8").split("\n");
281
+ return length < size ? lines.slice(1) : lines;
282
+ } catch {
283
+ return null;
284
+ } finally {
285
+ if (fd !== void 0) closeSync(fd);
286
+ }
287
+ }
288
+ function envDir(name) {
289
+ const value = process.env[name]?.trim();
290
+ return value ? resolve(value) : null;
291
+ }
292
+ function agentDeviceDirs(home) {
293
+ const root = join(home, ".agent-device");
294
+ const leaseDirs = [envDir("AGENT_DEVICE_IOS_RUNNER_LEASE_DIR") ?? join(root, "apple-runner", "leases"), join(root, "ios-runner", "leases")];
295
+ return [{
296
+ kind: "claim",
297
+ dir: envDir("AGENT_DEVICE_CLAIMS_DIR") ?? join(root, "device-claims")
298
+ }, ...[...new Set(leaseDirs)].map((dir) => ({
299
+ kind: "runner-lease",
300
+ dir
301
+ }))];
302
+ }
303
+ /**
304
+ * When an agent-device session last recorded an event: its events file's modification time. agent-device names a
305
+ * session's directory after the session with every character outside `[a-zA-Z0-9._-]` replaced by `_`.
306
+ */
307
+ function agentSessionActedAt(home, session, stateDir) {
308
+ let latest = null;
309
+ for (const root of stateDir ? [stateDir] : agentDeviceStateDirs(home)) try {
310
+ const at = statSync(join(root, "sessions", session.replaceAll(/[^a-zA-Z0-9._-]/g, "_"), "events.ndjson")).mtimeMs;
311
+ latest = latest === null ? at : Math.max(latest, at);
312
+ } catch {}
313
+ return latest;
314
+ }
315
+ function readAgentDeviceRecords(home) {
316
+ return agentDeviceDirs(home).flatMap(({ kind, dir }) => {
317
+ let names;
318
+ try {
319
+ names = readdirSync(dir).filter((name) => name.endsWith(".json"));
320
+ } catch {
321
+ return [];
322
+ }
323
+ return names.map((name) => {
324
+ const path = join(dir, name);
325
+ let raw = null;
326
+ try {
327
+ raw = readFileSync(path, "utf8");
328
+ } catch {}
329
+ return parseAgentDeviceRecord(kind, path, raw);
330
+ });
331
+ });
332
+ }
333
+ function isClaimProtocolRunner(record, row) {
334
+ if (!record.claimProtocol || record.runner?.pid !== row.pid || row.startedAt === null) return false;
335
+ const recorded = Date.parse(record.runner.startTime);
336
+ return Math.floor(recorded / 1e3) === Math.floor(Date.parse(row.startedAt) / 1e3);
337
+ }
338
+ function lsofArgs(port) {
339
+ return [
340
+ "-nP",
341
+ `-iTCP:${port}`,
342
+ "-sTCP:ESTABLISHED",
343
+ "-Fpn"
344
+ ];
345
+ }
346
+ function lsofNoMatch(error) {
347
+ const stdout = error.stdout;
348
+ return error.status === 1 && (stdout === "" || stdout === void 0) ? "" : null;
349
+ }
350
+ function createDeviceProcessTables() {
351
+ const exec = getExecutor();
352
+ let host;
353
+ const android = /* @__PURE__ */ new Map();
354
+ const tcp = /* @__PURE__ */ new Map();
355
+ return {
356
+ tcpConnections(port) {
357
+ if (!tcp.has(port)) {
358
+ let output;
359
+ try {
360
+ output = exec.runFile("lsof", lsofArgs(port), { timeoutMs: 5e3 });
361
+ } catch (error) {
362
+ output = lsofNoMatch(error);
363
+ }
364
+ tcp.set(port, output);
365
+ }
366
+ return tcp.get(port) ?? null;
367
+ },
368
+ host() {
369
+ if (host === void 0) {
370
+ const output = exec.runFileQuiet("ps", [
371
+ "-axww",
372
+ "-o",
373
+ HOST_PROCESS_COLUMNS
374
+ ], { timeoutMs: 1e4 });
375
+ host = output === null ? null : parseProcessTable(output);
376
+ }
377
+ return host;
378
+ },
379
+ android(serial) {
380
+ if (!android.has(serial)) android.set(serial, exec.runFileQuiet("adb", [
381
+ "-s",
382
+ serial,
383
+ "shell",
384
+ "ps",
385
+ "-A",
386
+ "-o",
387
+ "PID,ARGS"
388
+ ], { timeoutMs: 5e3 }));
389
+ return android.get(serial) ?? null;
390
+ }
391
+ };
392
+ }
393
+ function createActivityReader({ now = Date.now(), home = homedir(), startOf = process_identity_exports.inspectProcessStart, leaseFiles = listLeaseFiles, tables = createDeviceProcessTables(), viewers = readViewedDevices } = {}) {
394
+ let agentDevice;
395
+ let stimLeases;
396
+ let viewed;
397
+ const logs = /* @__PURE__ */ new Map();
398
+ const log = (path) => {
399
+ if (!logs.has(path)) logs.set(path, tailLines(path));
400
+ return logs.get(path) ?? null;
401
+ };
402
+ return (target) => {
403
+ const evidence = {
404
+ drivers: [],
405
+ unknown: [],
406
+ recency: []
407
+ };
408
+ agentDevice ??= readAgentDeviceRecords(home).map((record) => ({
409
+ record,
410
+ liveness: agentDeviceLiveness(record, startOf)
411
+ }));
412
+ for (const { record, liveness } of agentDevice) {
413
+ if (record.deviceId !== null && record.deviceId !== target.id) continue;
414
+ if (record.claimProtocol) continue;
415
+ const basis = record.kind === "claim" ? "agent-device-claim" : "agent-device-lease";
416
+ if (liveness === "live") {
417
+ evidence.drivers.push({
418
+ basis,
419
+ tool: "agent-device",
420
+ pid: record.owner?.pid ?? null,
421
+ since: record.createdAtMs !== null ? new Date(record.createdAtMs).toISOString() : null
422
+ });
423
+ const actedAt = record.session ? agentSessionActedAt(home, record.session, record.stateDir) : null;
424
+ if (actedAt !== null) evidence.recency.push({
425
+ basis: "agent-action",
426
+ at: actedAt
427
+ });
428
+ } else if (liveness === "unknown") evidence.unknown.push(basis);
429
+ }
430
+ try {
431
+ stimLeases ??= leaseFiles();
432
+ } catch {
433
+ stimLeases = [];
434
+ evidence.unknown.push("device-lock");
435
+ }
436
+ for (const entry of stimLeases) {
437
+ if (entry.platform !== target.platform || entry.id !== target.id) continue;
438
+ if (!entry.lease) evidence.unknown.push("device-lock");
439
+ else if (!leaseIsExpired(entry.lease, now)) evidence.drivers.push({
440
+ basis: "device-lock",
441
+ tool: "stim device lock",
442
+ pid: null,
443
+ since: entry.lease.grantedAt
444
+ });
445
+ }
446
+ viewed ??= viewers();
447
+ if (viewed.some((device) => device.platform === target.platform && device.id === target.id)) evidence.recency.push({
448
+ basis: "viewer",
449
+ at: now
450
+ });
451
+ const processes = tables.host();
452
+ if (processes === null) evidence.unknown.push("driver-process");
453
+ for (const row of processes ?? []) {
454
+ if (agentDevice.some(({ record }) => isClaimProtocolRunner(record, row))) continue;
455
+ const tool = hostDriverTool(row.command, target.platform, target.id);
456
+ if (tool) evidence.drivers.push({
457
+ basis: "driver-process",
458
+ tool,
459
+ pid: row.pid,
460
+ since: row.startedAt
461
+ });
462
+ }
463
+ if (target.platform === "android") {
464
+ const output = tables.android(target.id);
465
+ if (output === null) evidence.unknown.push("instrumentation");
466
+ for (const { pid, tool } of parseAndroidInstrumentation(output ?? "")) evidence.drivers.push({
467
+ basis: "instrumentation",
468
+ tool,
469
+ pid,
470
+ since: null
471
+ });
472
+ }
473
+ if (target.workspace) {
474
+ const dir = workspaceLogsDir$1(target.workspace);
475
+ const deviceAt = latestDeviceLogAt(log(join(dir, "device.ndjson")) ?? [], target);
476
+ if (deviceAt !== null) evidence.recency.push({
477
+ basis: "device-log",
478
+ at: deviceAt
479
+ });
480
+ const bundleAt = latestBundleRequestAt(log(join(dir, "metro.ndjson")) ?? [], target.platform);
481
+ if (bundleAt !== null) evidence.recency.push({
482
+ basis: "metro-bundle",
483
+ at: bundleAt
484
+ });
485
+ const usedAt = Date.parse(String(readWorkspaceState$1(target.workspace)?.lastUsedAt ?? ""));
486
+ if (Number.isFinite(usedAt)) evidence.recency.push({
487
+ basis: "workspace-use",
488
+ at: usedAt
489
+ });
490
+ }
491
+ return classifyActivity(evidence, now);
492
+ };
493
+ }
494
+ function workspaceActivity(workspace, now = Date.now()) {
495
+ const dir = workspaceLogsDir$1(workspace);
496
+ const state = readWorkspaceState$1(workspace);
497
+ const recency = [];
498
+ const deviceAt = latestRecordAt(tailLines(join(dir, "device.ndjson")) ?? [], (record) => record.src === "device" && isDeviceRecord(record));
499
+ if (deviceAt !== null) recency.push({
500
+ basis: "device-log",
501
+ at: deviceAt
502
+ });
503
+ const bundleAt = latestRecordAt(tailLines(join(dir, "metro.ndjson")) ?? [], (record) => record.event === "bundle_response_started");
504
+ if (bundleAt !== null) recency.push({
505
+ basis: "metro-bundle",
506
+ at: bundleAt
507
+ });
508
+ for (const [basis, value] of [["workspace-use", state?.lastUsedAt], ["supervisor-start", state?.supervisor?.startedAt]]) {
509
+ const at = Date.parse(String(value ?? ""));
510
+ if (Number.isFinite(at)) recency.push({
511
+ basis,
512
+ at
513
+ });
514
+ }
515
+ return classifyActivity({
516
+ drivers: [],
517
+ unknown: [],
518
+ recency
519
+ }, now);
520
+ }
521
+ /**
522
+ * The pids of processes connected to loopback `port` as clients, from `lsof -Fpn` output: a connection whose remote
523
+ * end is the port. Chrome's own accepted sockets have the port as their local end and are skipped.
524
+ */
525
+ function parseTcpClients(output, port) {
526
+ const pids = /* @__PURE__ */ new Set();
527
+ let pid = null;
528
+ for (const line of output.split("\n")) if (line.startsWith("p")) pid = Number(line.slice(1));
529
+ else if (line.startsWith("n") && pid !== null && Number.isInteger(pid)) {
530
+ const remote = line.slice(1).split("->")[1];
531
+ if (remote && Number(remote.slice(remote.lastIndexOf(":") + 1)) === port) pids.add(pid);
532
+ }
533
+ return [...pids];
534
+ }
535
+ function webClientPids(connections, target) {
536
+ return parseTcpClients(connections, target.port).filter((pid) => !target.ownPids.includes(pid));
537
+ }
538
+ function webDrivers(clients, processes) {
539
+ return clients.flatMap((pid) => {
540
+ const row = processes.find((candidate) => candidate.pid === pid);
541
+ const tool = row ? cdpClientTool(row.command) : "unknown DevTools client";
542
+ return tool ? [{
543
+ tool,
544
+ pid,
545
+ since: row?.startedAt ?? null
546
+ }] : [];
547
+ });
548
+ }
549
+ /**
550
+ * The DevTools clients of the owned browser that are not Stim's, read without blocking the event loop; null when
551
+ * lsof or ps could not be read.
552
+ */
553
+ async function probeWebDrivers(target) {
554
+ const exec = getExecutor();
555
+ let connections;
556
+ try {
557
+ connections = await exec.runFileAsync("lsof", lsofArgs(target.port), { timeoutMs: 5e3 });
558
+ } catch (error) {
559
+ connections = lsofNoMatch(error);
560
+ }
561
+ if (connections === null) return null;
562
+ const clients = webClientPids(connections, target);
563
+ if (!clients.length) return [];
564
+ let table;
565
+ try {
566
+ table = await exec.runFileAsync("ps", [
567
+ "-axww",
568
+ "-o",
569
+ HOST_PROCESS_COLUMNS
570
+ ], { timeoutMs: 5e3 });
571
+ } catch {
572
+ return null;
573
+ }
574
+ return webDrivers(clients, parseProcessTable(table));
575
+ }
576
+ /**
577
+ * The owned browser's activity: a DevTools client other than Stim's is a driver (basis `cdp-client`). Recency comes
578
+ * from the newest agent action the browser supervisor recorded (basis `agent-action`) and the newest other page
579
+ * record (basis `page-log`).
580
+ */
581
+ function readWebActivity(target, tables, now = Date.now()) {
582
+ const evidence = {
583
+ drivers: [],
584
+ unknown: [],
585
+ recency: []
586
+ };
587
+ const connections = tables.tcpConnections(target.port);
588
+ const clients = connections === null ? [] : webClientPids(connections, target);
589
+ const processes = clients.length ? (tables.clients ?? tables.host)() : [];
590
+ if (connections === null || processes === null) evidence.unknown.push("cdp-client");
591
+ else for (const driver of webDrivers(clients, processes)) evidence.drivers.push({
592
+ ...driver,
593
+ basis: "cdp-client"
594
+ });
595
+ const lines = tailLines(join(workspaceLogsDir$1(target.workspace), "web.ndjson")) ?? [];
596
+ const actionAt = latestRecordAt(lines, (record) => record.src === "agent");
597
+ if (actionAt !== null) evidence.recency.push({
598
+ basis: "agent-action",
599
+ at: actionAt
600
+ });
601
+ const pageAt = latestRecordAt(lines, (record) => record.src !== "agent");
602
+ if (pageAt !== null) evidence.recency.push({
603
+ basis: "page-log",
604
+ at: pageAt
605
+ });
606
+ return classifyActivity(evidence, now);
607
+ }
608
+ //#endregion
609
+ export { envDir as a, probeWebDrivers as c, readWebActivity as d, tailLines as f, createDeviceProcessTables as i, processMatches as l, agentDeviceLiveness as n, idleForMs as o, workspaceActivity as p, createActivityReader as r, parseAgentDeviceRecord as s, agentDeviceDaemon as t, readAgentDeviceRecords as u };