stim 1.14.0 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/README.md +2 -0
  2. package/dist/{activity-CYVDcrt3.mjs → activity-B2-kb7k0.mjs} +281 -41
  3. package/dist/{android-XpvCrV7q.mjs → android-CYmq0jNi.mjs} +2 -2
  4. package/dist/{android-LbPFRWVL.mjs → android-Cu_L4RpL.mjs} +502 -407
  5. package/dist/{android-d6rGq1pA.mjs → android-Db2eEMaf.mjs} +152 -65
  6. package/dist/android-cas-PnKfAdB2.mjs +167 -0
  7. package/dist/android-cas-compiler.mjs +2 -2
  8. package/dist/{app-install-BpgSgkJj.mjs → app-install-CAAplXAt.mjs} +95 -161
  9. package/dist/{budget-CGDkhvv_.mjs → budget-CMiqghig.mjs} +1038 -73
  10. package/dist/{build-plan-Dcr-3ie0.mjs → build-plan-aeGMFX7e.mjs} +193 -653
  11. package/dist/build-progress-C2Jv1AtA.mjs +872 -0
  12. package/dist/build-slots-Df1ApSuH.mjs +127 -0
  13. package/dist/{cache-manifest-C68QQ0ua.mjs → cache-manifest-DnUHBJaU.mjs} +2 -2
  14. package/dist/cache-manifest.mjs +1 -1
  15. package/dist/{chrome-BPCiC3ZX.mjs → chrome-BrcVWQyD.mjs} +1 -1
  16. package/dist/cli-0cX5s4NM.mjs +45 -0
  17. package/dist/cli.mjs +6 -42
  18. package/dist/client-C2IS36Qf.mjs +1333 -0
  19. package/dist/collector-run.mjs +8 -7
  20. package/dist/{command-output-DVcpw6eE.mjs → command-output-CliHHn7a.mjs} +1 -1
  21. package/dist/{config-lvc7d3C2.mjs → config-D0DbpZtM.mjs} +28 -29
  22. package/dist/{created-devices-CllH_8eb.mjs → created-devices-BwVb3liH.mjs} +2 -2
  23. package/dist/{dependency-state-DsT7aKeo.mjs → dependency-state-BmsjKZ2H.mjs} +2 -2
  24. package/dist/{deps-D9IcKqXS.mjs → deps-BFlKbOIp.mjs} +3 -3
  25. package/dist/{detached-entry-DJHoca49.mjs → detached-entry-CiTEgMR1.mjs} +1 -1
  26. package/dist/dev-client-CjzLoNs0.mjs +80 -0
  27. package/dist/{device-CjuuAJjQ.mjs → device-BZ1IyS5A.mjs} +7 -7
  28. package/dist/device-capacity-DMuKC7nx.mjs +136 -0
  29. package/dist/device-host-worker.d.mts +1 -0
  30. package/dist/device-host-worker.mjs +769 -0
  31. package/dist/device-ios-DMQBUPus.mjs +381 -0
  32. package/dist/{device-lease-5c3WrVRm.mjs → device-lease-W5Pi1q38.mjs} +43 -36
  33. package/dist/{device-pool-BiyDyPP-.mjs → device-pool-B2r38YjS.mjs} +4 -4
  34. package/dist/{device-remote-D87zEzaq.mjs → device-remote-9ulPsdhY.mjs} +68 -162
  35. package/dist/{doctor-x9pnU9fq.mjs → doctor-BcE5xsBn.mjs} +191 -99
  36. package/dist/{error-diagnostics-C5HzcwCW.mjs → error-diagnostics-Cb0P1WLS.mjs} +12 -11
  37. package/dist/{exec-DK8KaVHK.mjs → exec-CaErVv7t.mjs} +5 -1
  38. package/dist/{gc-ko6mUAYD.mjs → gc-BuL1Genr.mjs} +429 -101
  39. package/dist/{android-cas-iZK3DOCr.mjs → gradle-CmdOBq1d.mjs} +8 -294
  40. package/dist/{guide-6R0VNFqI.mjs → guide-cCSGRFd3.mjs} +1959 -222
  41. package/dist/{guide-status-BZrFPIA_.mjs → guide-status-CIoWK6mG.mjs} +3 -3
  42. package/dist/hosted-macos-DwowNU7W.mjs +447 -0
  43. package/dist/idle-shutdown-B5LPaNyY.mjs +122 -0
  44. package/dist/in-use-CndPMilN.mjs +110 -0
  45. package/dist/{ios-BwDPrnYQ.mjs → ios-3CtCRhwr.mjs} +201 -10
  46. package/dist/{ios-P1f5nm2n.mjs → ios-Chob8ny7.mjs} +512 -263
  47. package/dist/{ios-device-DGglU3E_.mjs → ios-device-DERmMmk-.mjs} +12 -7
  48. package/dist/{ios-device-BiOsSVzd.mjs → ios-device-Ee7sDXfp.mjs} +1 -1
  49. package/dist/{launch-verify-Deg4tB3Z.mjs → launch-verify-CLlofxRt.mjs} +4 -4
  50. package/dist/{logs-Civ4gbBH.mjs → logs-h9h6MUvE.mjs} +95 -13
  51. package/dist/machine-usage-D_YYMzfR.mjs +283 -0
  52. package/dist/machines-CYtOCqcj.mjs +259 -0
  53. package/dist/macos-BwtbUtTN.mjs +468 -0
  54. package/dist/macos-run.d.mts +5 -0
  55. package/dist/macos-run.mjs +2 -0
  56. package/dist/{merge-state-ytFQCeh4.mjs → merge-state-BnxLNQiM.mjs} +22 -12
  57. package/dist/{metro-CcEo8Fl8.mjs → metro-Di8EN2Tb.mjs} +20 -120
  58. package/dist/{named-ports-Blg8IxOh.mjs → named-ports-Dj_XBNaL.mjs} +6 -59
  59. package/dist/native-run-DFDoIRbY.mjs +147 -0
  60. package/dist/{native-runtime-DjBHRg5m.mjs → native-runtime-i6OkVg-w.mjs} +9 -5
  61. package/dist/offload-worker.d.mts +121 -0
  62. package/dist/offload-worker.mjs +601 -0
  63. package/dist/{ownership-DyYDF5ww.mjs → ownership-BkYxUtuj.mjs} +3 -2
  64. package/dist/{page-BHUzTDFt.mjs → page-QF_Ke10s.mjs} +7 -2
  65. package/dist/{ports-BguS83ex.mjs → ports-DddwttKM.mjs} +3 -3
  66. package/dist/prebuild-BvxDlD6C.mjs +213 -0
  67. package/dist/process-identity-BmRm1qV0.mjs +12 -0
  68. package/dist/{project-CWlvEaeN.mjs → project-CV3tDFHl.mjs} +9 -18
  69. package/dist/pull-request-pxnDCQzn.mjs +322 -0
  70. package/dist/pull-requests.d.mts +120 -0
  71. package/dist/pull-requests.mjs +2 -0
  72. package/dist/recordings-GEXp4iBT.mjs +37 -0
  73. package/dist/{reload-BiniwsSZ.mjs → reload-ChPiwvot.mjs} +70 -22
  74. package/dist/{remote-cache-CcR6o6lI.mjs → remote-cache-CO2elm_b.mjs} +4 -4
  75. package/dist/run-B-xqRrsg.mjs +119 -0
  76. package/dist/{server-bare-DGfgyJ5J.mjs → server-bare-X7PUgizL.mjs} +33 -15
  77. package/dist/{server-expo-BVuwQ65v.mjs → server-expo-DhtX6bom.mjs} +6 -10
  78. package/dist/server-expo-KJeD109I.mjs +2 -0
  79. package/dist/{settings-D61iNOsB.mjs → settings-CduVcoxj.mjs} +77 -26
  80. package/dist/{settings-B-fsC-Om.mjs → settings-DbL6uhH8.mjs} +16 -10
  81. package/dist/settings.schema.json +357 -2
  82. package/dist/{simslim-DrDN4WuV.mjs → simslim-Ciluxlq4.mjs} +8 -132
  83. package/dist/{slot-launch-D5t8EiMf.mjs → slot-launch-BY0BrOLh.mjs} +6 -6
  84. package/dist/{spawn-claims-Drw_o-Fb.mjs → spawn-claims-D0XfSe8u.mjs} +1 -1
  85. package/dist/spawn-entry-DncpJvWL.mjs +16 -0
  86. package/dist/stage-CzaiwFsI.mjs +143 -0
  87. package/dist/start-CPO4wRg7.mjs +2 -0
  88. package/dist/{start-s4RXp1wy.mjs → start-CUD9nasA.mjs} +108 -61
  89. package/dist/state-B4PQFXMW.d.mts +12 -0
  90. package/dist/state-BxCgeTQu.mjs +41 -0
  91. package/dist/{state-DHqvsbqC.mjs → state-CaeaHbDA.mjs} +7 -4
  92. package/dist/{state-Dk9RVXI7.mjs → state-DFm77wRn.mjs} +67 -5
  93. package/dist/stats-DBLLFmKd.mjs +66 -0
  94. package/dist/{status-BsPAnfOI.mjs → status-DYXxNOT0.mjs} +423 -268
  95. package/dist/{status-BgAuxESg.mjs → status-l84Gbvp2.mjs} +116 -20
  96. package/dist/stdout-reader-C8yjre1x.mjs +23 -0
  97. package/dist/stim-desktop-Cnz8CPnl.mjs +63 -0
  98. package/dist/stim-footprint.swift +32 -0
  99. package/dist/{stim-installations-CINXy_su.mjs → stim-installations-QMEI32v1.mjs} +1 -1
  100. package/dist/{stop-BzreOWJp.mjs → stop-Bad61z_2.mjs} +38 -12
  101. package/dist/stop-Bvf1MflS.mjs +48 -0
  102. package/dist/{stop-BWIF_6BK.mjs → stop-CceYh1yG.mjs} +2 -2
  103. package/dist/stop-cause-CZ_Mr-v9.mjs +665 -0
  104. package/dist/supervisor-run.d.mts +43 -11
  105. package/dist/supervisor-run.mjs +227 -19
  106. package/dist/support-CDQbDkL5.mjs +315 -0
  107. package/dist/support-WLOzVKUv.mjs +176 -0
  108. package/dist/teardown-BYuQafiA.mjs +718 -0
  109. package/dist/{xcode-Cp5KvK9x.mjs → toolchain-DmeYjXD9.mjs} +363 -13
  110. package/dist/warm-progress-DVHLN4uC.mjs +463 -0
  111. package/dist/{idle-DXtx3RbX.mjs → watchman-Bx0wceoF.mjs} +55 -8
  112. package/dist/{web-BsQs1WlO.mjs → web-k6w8-HnE.mjs} +37 -20
  113. package/dist/web-run.mjs +309 -12
  114. package/dist/workspace-process-lock-BL7Q0HCO.mjs +57 -0
  115. package/dist/workspace-state-CL2V2Ons.mjs +371 -0
  116. package/dist/worktree-BT_uULFx.mjs +2 -0
  117. package/dist/{worktree-BVrHsYZG.mjs → worktree-C0c5Udwa.mjs} +55 -54
  118. package/dist/{worktree-UrQWXSeb.mjs → worktree-qXKzADSC.mjs} +154 -252
  119. package/package.json +10 -5
  120. package/shim/bundle-response.cjs +28 -12
  121. package/dist/build-progress-DxKpwnaS.mjs +0 -247
  122. package/dist/in-use-DlqBDKqY.mjs +0 -249
  123. package/dist/server-expo-DIHDhvGg.mjs +0 -2
  124. package/dist/start-Cetfvy8l.mjs +0 -2
  125. package/dist/stats-CFpFGlJV.mjs +0 -62
  126. package/dist/stats-CX1Z8Tca.mjs +0 -368
  127. package/dist/stim-desktop-CXt8-Z3J.mjs +0 -40
  128. package/dist/stop-cause-hjGBOZTG.mjs +0 -1231
  129. package/dist/worktree-CHWWvm5v.mjs +0 -2
  130. package/dist/{metro-store-DzeWkfqW.mjs → metro-store-CDDZHtPl.mjs} +1 -1
package/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`,
@@ -1,16 +1,130 @@
1
- import { O as process_identity_exports } from "./config-lvc7d3C2.mjs";
2
- import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
- import { M as workspaceLogsDir, h as readWorkspaceState } from "./metro-CcEo8Fl8.mjs";
4
- import { i as listLeaseFiles, r as leaseIsExpired } from "./device-lease-5c3WrVRm.mjs";
5
- import { closeSync, fstatSync, openSync, readFileSync, readSync, readdirSync } from "node:fs";
1
+ import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
+ import { t as process_identity_exports } from "./process-identity-BmRm1qV0.mjs";
3
+ import { C as workspaceLogsDir, r as readWorkspaceState$1 } from "./workspace-state-CL2V2Ons.mjs";
4
+ import { i as listLeaseFiles, r as leaseIsExpired } from "./device-lease-W5Pi1q38.mjs";
5
+ import { closeSync, fstatSync, openSync, readFileSync, readSync, readdirSync, statSync } from "node:fs";
6
6
  import { basename, join, resolve } from "node:path";
7
+ import { readViewedDevices } from "@stim-cli/core/state";
7
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
8
117
  //#region src/devices/activity.ts
9
118
  const ACTIVE_WINDOW_MS = 6e5;
10
119
  const LOG_TAIL_BYTES = 262144;
11
120
  function classifyActivity(evidence, now) {
12
121
  const last = evidence.recency.reduce((best, entry) => Number.isFinite(entry.at) && (!best || entry.at > best.at) ? entry : best, null);
13
- const lastActivityAt = last ? { lastActivityAt: new Date(last.at).toISOString() } : {};
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
+ } : {};
14
128
  const [first] = evidence.drivers;
15
129
  if (first) {
16
130
  const { basis: _basis, ...driver } = first;
@@ -72,6 +186,7 @@ function parseAgentDeviceRecord(kind, path, raw) {
72
186
  readable: Boolean(entry && deviceId),
73
187
  owner: entry ? field(entry, "ownerPid", "ownerStartTime") : null,
74
188
  runner: entry && kind === "runner-lease" ? field(entry, "runnerPid", "runnerStartTime") : null,
189
+ claimProtocol: kind === "runner-lease" && entry?.deviceClaimProtocol === 1,
75
190
  createdAtMs
76
191
  };
77
192
  }
@@ -107,34 +222,15 @@ function parseProcessTable(output) {
107
222
  }
108
223
  return rows;
109
224
  }
110
- function namesDevice(command, id) {
111
- const escaped = id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
112
- return new RegExp(`(^|[^A-Za-z0-9])${escaped}([^A-Za-z0-9]|$)`).test(command);
113
- }
114
- function driverTool(command, id) {
115
- if (!namesDevice(command, id)) return null;
116
- if (/\bsimctl\s+spawn\b.*\blog\s+stream\b/.test(command) || /\blogcat\b/.test(command)) return null;
117
- if (/agent-device/i.test(command)) return "agent-device";
118
- if (/idb_companion/.test(command)) return "idb";
119
- if (/maestro/i.test(command)) return "maestro";
120
- if (/appium|WebDriverAgent/i.test(command)) return "appium";
121
- if (/\bxcodebuild\b.*\btest(-without-building)?\b/.test(command)) return "xcodebuild";
122
- if (/\bsimctl\s+(io|spawn)\b/.test(command)) return "simctl";
123
- return null;
124
- }
125
225
  function parseAndroidInstrumentation(output) {
126
226
  const found = [];
127
227
  for (const line of output.split("\n")) {
128
228
  const match = /^\s*(\d+)\s+(.*)$/.exec(line);
129
229
  if (!match) continue;
130
- const args = match[2];
131
- if (/uiautomator/.test(args)) found.push({
132
- pid: Number(match[1]),
133
- tool: "uiautomator"
134
- });
135
- else if (/androidx\.test|\binstrument\b/.test(args)) found.push({
230
+ const tool = instrumentationTool(match[2]);
231
+ if (tool) found.push({
136
232
  pid: Number(match[1]),
137
- tool: "instrumentation"
233
+ tool
138
234
  });
139
235
  }
140
236
  return found;
@@ -159,6 +255,7 @@ function latestRecordAt(lines, matches) {
159
255
  function isDeviceRecord(record) {
160
256
  return !(typeof record.event === "string" && record.event.startsWith("collector_"));
161
257
  }
258
+ /** The last lines of the file at `path`, at most its final 256 KiB, or null when it cannot be read. */
162
259
  function tailLines(path) {
163
260
  let fd;
164
261
  try {
@@ -190,6 +287,18 @@ function agentDeviceDirs(home) {
190
287
  dir
191
288
  }))];
192
289
  }
290
+ /**
291
+ * When an agent-device session last recorded an event: its events file's modification time. agent-device names a
292
+ * session's directory after the session with every character outside `[a-zA-Z0-9._-]` replaced by `_`.
293
+ */
294
+ function agentSessionActedAt(home, session) {
295
+ const root = envDir("AGENT_DEVICE_STATE_DIR") ?? join(home, ".agent-device");
296
+ try {
297
+ return statSync(join(root, "sessions", session.replaceAll(/[^a-zA-Z0-9._-]/g, "_"), "events.ndjson")).mtimeMs;
298
+ } catch {
299
+ return null;
300
+ }
301
+ }
193
302
  function readAgentDeviceRecords(home) {
194
303
  return agentDeviceDirs(home).flatMap(({ kind, dir }) => {
195
304
  let names;
@@ -208,18 +317,48 @@ function readAgentDeviceRecords(home) {
208
317
  });
209
318
  });
210
319
  }
320
+ function isClaimProtocolRunner(record, row) {
321
+ if (!record.claimProtocol || record.runner?.pid !== row.pid || row.startedAt === null) return false;
322
+ const recorded = Date.parse(record.runner.startTime);
323
+ return Math.floor(recorded / 1e3) === Math.floor(Date.parse(row.startedAt) / 1e3);
324
+ }
325
+ function lsofArgs(port) {
326
+ return [
327
+ "-nP",
328
+ `-iTCP:${port}`,
329
+ "-sTCP:ESTABLISHED",
330
+ "-Fpn"
331
+ ];
332
+ }
333
+ function lsofNoMatch(error) {
334
+ const stdout = error.stdout;
335
+ return error.status === 1 && (stdout === "" || stdout === void 0) ? "" : null;
336
+ }
211
337
  function createDeviceProcessTables() {
212
338
  const exec = getExecutor();
213
339
  let host;
214
340
  const android = /* @__PURE__ */ new Map();
341
+ const tcp = /* @__PURE__ */ new Map();
215
342
  return {
343
+ tcpConnections(port) {
344
+ if (!tcp.has(port)) {
345
+ let output;
346
+ try {
347
+ output = exec.runFile("lsof", lsofArgs(port), { timeoutMs: 5e3 });
348
+ } catch (error) {
349
+ output = lsofNoMatch(error);
350
+ }
351
+ tcp.set(port, output);
352
+ }
353
+ return tcp.get(port) ?? null;
354
+ },
216
355
  host() {
217
356
  if (host === void 0) {
218
357
  const output = exec.runFileQuiet("ps", [
219
358
  "-axww",
220
359
  "-o",
221
360
  HOST_PROCESS_COLUMNS
222
- ], { timeoutMs: 5e3 });
361
+ ], { timeoutMs: 1e4 });
223
362
  host = output === null ? null : parseProcessTable(output);
224
363
  }
225
364
  return host;
@@ -238,9 +377,10 @@ function createDeviceProcessTables() {
238
377
  }
239
378
  };
240
379
  }
241
- function createActivityReader({ now = Date.now(), home = homedir(), startOf = process_identity_exports.inspectProcessStart, leaseFiles = listLeaseFiles, tables = createDeviceProcessTables() } = {}) {
380
+ function createActivityReader({ now = Date.now(), home = homedir(), startOf = process_identity_exports.inspectProcessStart, leaseFiles = listLeaseFiles, tables = createDeviceProcessTables(), viewers = readViewedDevices } = {}) {
242
381
  let agentDevice;
243
382
  let stimLeases;
383
+ let viewed;
244
384
  const logs = /* @__PURE__ */ new Map();
245
385
  const log = (path) => {
246
386
  if (!logs.has(path)) logs.set(path, tailLines(path));
@@ -258,14 +398,21 @@ function createActivityReader({ now = Date.now(), home = homedir(), startOf = pr
258
398
  }));
259
399
  for (const { record, liveness } of agentDevice) {
260
400
  if (record.deviceId !== null && record.deviceId !== target.id) continue;
401
+ if (record.claimProtocol) continue;
261
402
  const basis = record.kind === "claim" ? "agent-device-claim" : "agent-device-lease";
262
- if (liveness === "live") evidence.drivers.push({
263
- basis,
264
- tool: "agent-device",
265
- pid: record.owner?.pid ?? null,
266
- since: record.createdAtMs !== null ? new Date(record.createdAtMs).toISOString() : null
267
- });
268
- else if (liveness === "unknown") evidence.unknown.push(basis);
403
+ if (liveness === "live") {
404
+ evidence.drivers.push({
405
+ basis,
406
+ tool: "agent-device",
407
+ pid: record.owner?.pid ?? null,
408
+ since: record.createdAtMs !== null ? new Date(record.createdAtMs).toISOString() : null
409
+ });
410
+ const actedAt = record.session ? agentSessionActedAt(home, record.session) : null;
411
+ if (actedAt !== null) evidence.recency.push({
412
+ basis: "agent-action",
413
+ at: actedAt
414
+ });
415
+ } else if (liveness === "unknown") evidence.unknown.push(basis);
269
416
  }
270
417
  try {
271
418
  stimLeases ??= leaseFiles();
@@ -283,10 +430,16 @@ function createActivityReader({ now = Date.now(), home = homedir(), startOf = pr
283
430
  since: entry.lease.grantedAt
284
431
  });
285
432
  }
433
+ viewed ??= viewers();
434
+ if (viewed.some((device) => device.platform === target.platform && device.id === target.id)) evidence.recency.push({
435
+ basis: "viewer",
436
+ at: now
437
+ });
286
438
  const processes = tables.host();
287
439
  if (processes === null) evidence.unknown.push("driver-process");
288
440
  for (const row of processes ?? []) {
289
- const tool = driverTool(row.command, target.id);
441
+ if (agentDevice.some(({ record }) => isClaimProtocolRunner(record, row))) continue;
442
+ const tool = hostDriverTool(row.command, target.platform, target.id);
290
443
  if (tool) evidence.drivers.push({
291
444
  basis: "driver-process",
292
445
  tool,
@@ -316,7 +469,7 @@ function createActivityReader({ now = Date.now(), home = homedir(), startOf = pr
316
469
  basis: "metro-bundle",
317
470
  at: bundleAt
318
471
  });
319
- const usedAt = Date.parse(String(readWorkspaceState(target.workspace)?.lastUsedAt ?? ""));
472
+ const usedAt = Date.parse(String(readWorkspaceState$1(target.workspace)?.lastUsedAt ?? ""));
320
473
  if (Number.isFinite(usedAt)) evidence.recency.push({
321
474
  basis: "workspace-use",
322
475
  at: usedAt
@@ -327,7 +480,7 @@ function createActivityReader({ now = Date.now(), home = homedir(), startOf = pr
327
480
  }
328
481
  function workspaceActivity(workspace, now = Date.now()) {
329
482
  const dir = workspaceLogsDir(workspace);
330
- const state = readWorkspaceState(workspace);
483
+ const state = readWorkspaceState$1(workspace);
331
484
  const recency = [];
332
485
  const deviceAt = latestRecordAt(tailLines(join(dir, "device.ndjson")) ?? [], (record) => record.src === "device" && isDeviceRecord(record));
333
486
  if (deviceAt !== null) recency.push({
@@ -352,5 +505,92 @@ function workspaceActivity(workspace, now = Date.now()) {
352
505
  recency
353
506
  }, now);
354
507
  }
508
+ /**
509
+ * The pids of processes connected to loopback `port` as clients, from `lsof -Fpn` output: a connection whose remote
510
+ * end is the port. Chrome's own accepted sockets have the port as their local end and are skipped.
511
+ */
512
+ function parseTcpClients(output, port) {
513
+ const pids = /* @__PURE__ */ new Set();
514
+ let pid = null;
515
+ for (const line of output.split("\n")) if (line.startsWith("p")) pid = Number(line.slice(1));
516
+ else if (line.startsWith("n") && pid !== null && Number.isInteger(pid)) {
517
+ const remote = line.slice(1).split("->")[1];
518
+ if (remote && Number(remote.slice(remote.lastIndexOf(":") + 1)) === port) pids.add(pid);
519
+ }
520
+ return [...pids];
521
+ }
522
+ function webClientPids(connections, target) {
523
+ return parseTcpClients(connections, target.port).filter((pid) => !target.ownPids.includes(pid));
524
+ }
525
+ function webDrivers(clients, processes) {
526
+ return clients.flatMap((pid) => {
527
+ const row = processes.find((candidate) => candidate.pid === pid);
528
+ const tool = row ? cdpClientTool(row.command) : "unknown DevTools client";
529
+ return tool ? [{
530
+ tool,
531
+ pid,
532
+ since: row?.startedAt ?? null
533
+ }] : [];
534
+ });
535
+ }
536
+ /**
537
+ * The DevTools clients of the owned browser that are not Stim's, read without blocking the event loop; null when
538
+ * lsof or ps could not be read.
539
+ */
540
+ async function probeWebDrivers(target) {
541
+ const exec = getExecutor();
542
+ let connections;
543
+ try {
544
+ connections = await exec.runFileAsync("lsof", lsofArgs(target.port), { timeoutMs: 5e3 });
545
+ } catch (error) {
546
+ connections = lsofNoMatch(error);
547
+ }
548
+ if (connections === null) return null;
549
+ const clients = webClientPids(connections, target);
550
+ if (!clients.length) return [];
551
+ let table;
552
+ try {
553
+ table = await exec.runFileAsync("ps", [
554
+ "-axww",
555
+ "-o",
556
+ HOST_PROCESS_COLUMNS
557
+ ], { timeoutMs: 5e3 });
558
+ } catch {
559
+ return null;
560
+ }
561
+ return webDrivers(clients, parseProcessTable(table));
562
+ }
563
+ /**
564
+ * The owned browser's activity: a DevTools client other than Stim's is a driver (basis `cdp-client`). Recency comes
565
+ * from the newest agent action the browser supervisor recorded (basis `agent-action`) and the newest other page
566
+ * record (basis `page-log`).
567
+ */
568
+ function readWebActivity(target, tables, now = Date.now()) {
569
+ const evidence = {
570
+ drivers: [],
571
+ unknown: [],
572
+ recency: []
573
+ };
574
+ const connections = tables.tcpConnections(target.port);
575
+ const clients = connections === null ? [] : webClientPids(connections, target);
576
+ const processes = clients.length ? (tables.clients ?? tables.host)() : [];
577
+ if (connections === null || processes === null) evidence.unknown.push("cdp-client");
578
+ else for (const driver of webDrivers(clients, processes)) evidence.drivers.push({
579
+ ...driver,
580
+ basis: "cdp-client"
581
+ });
582
+ const lines = tailLines(join(workspaceLogsDir(target.workspace), "web.ndjson")) ?? [];
583
+ const actionAt = latestRecordAt(lines, (record) => record.src === "agent");
584
+ if (actionAt !== null) evidence.recency.push({
585
+ basis: "agent-action",
586
+ at: actionAt
587
+ });
588
+ const pageAt = latestRecordAt(lines, (record) => record.src !== "agent");
589
+ if (pageAt !== null) evidence.recency.push({
590
+ basis: "page-log",
591
+ at: pageAt
592
+ });
593
+ return classifyActivity(evidence, now);
594
+ }
355
595
  //#endregion
356
- export { readAgentDeviceRecords as a, idleForMs as i, createActivityReader as n, workspaceActivity as o, createDeviceProcessTables as r, agentDeviceLiveness as t };
596
+ export { probeWebDrivers as a, tailLines as c, idleForMs as i, workspaceActivity as l, createActivityReader as n, readAgentDeviceRecords as o, createDeviceProcessTables as r, readWebActivity as s, agentDeviceLiveness as t };
@@ -1,5 +1,5 @@
1
- import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
2
- import { l as androidToolCwd } from "./android-d6rGq1pA.mjs";
1
+ import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
+ import { u as androidToolCwd } from "./android-Db2eEMaf.mjs";
3
3
  //#region src/collector/android.ts
4
4
  const LEVEL_BY_LETTER = {
5
5
  V: "debug",