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