@stim-cli/core 1.15.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 +3 -0
- package/dist/{core-BwaxUjLX.mjs → core-DWOUn9xp.mjs} +58 -12
- package/dist/{index-DgLuTAvh.d.mts → index-DPo8Bexj.d.mts} +563 -161
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +1 -1
- package/dist/phone-protocol.d.mts +28 -4
- package/dist/{protocol-BjnE921M.d.mts → protocol-B0KcaUee.d.mts} +35 -12
- package/dist/protocol.d.mts +2 -2
- package/dist/protocol.mjs +8 -1
- package/dist/receive-protocol.mjs +7303 -4115
- package/dist/state.d.mts +2 -2
- package/dist/state.mjs +1055 -81
- package/package.json +1 -1
package/dist/state.mjs
CHANGED
|
@@ -1,12 +1,276 @@
|
|
|
1
1
|
import { _ as releaseClaim, f as isClaimRefusal, g as readClaimSet, x as tryAcquireClaim } from "./ownership-claim-O31Y5iIb.mjs";
|
|
2
2
|
import { inspectProcessIdentity } from "./process-identity.mjs";
|
|
3
|
-
import { C as workspaceStateDir, h as rotatedLogPath, j as withDirLock, l as configDir, x as workspaceName } from "./core-
|
|
4
|
-
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
3
|
+
import { C as workspaceStateDir, h as rotatedLogPath, j as withDirLock, l as configDir, x as workspaceName } from "./core-DWOUn9xp.mjs";
|
|
4
|
+
import { closeSync, existsSync, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
5
5
|
import { availableParallelism, homedir, loadavg } from "node:os";
|
|
6
6
|
import { basename, dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
|
+
import { isIP } from "node:net";
|
|
8
9
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
9
10
|
import { StringDecoder } from "node:string_decoder";
|
|
11
|
+
//#region state/agent-actions.ts
|
|
12
|
+
const EVENTS_VERSION = 1;
|
|
13
|
+
function object$1(value) {
|
|
14
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
15
|
+
}
|
|
16
|
+
function parseAgentEvents(lines) {
|
|
17
|
+
const events = [];
|
|
18
|
+
const started = [];
|
|
19
|
+
const finished = [];
|
|
20
|
+
let session = null;
|
|
21
|
+
let unknownVersion = null;
|
|
22
|
+
for (const line of lines) {
|
|
23
|
+
let entry;
|
|
24
|
+
try {
|
|
25
|
+
entry = object$1(JSON.parse(line));
|
|
26
|
+
} catch {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (!entry) continue;
|
|
30
|
+
const ts = typeof entry.ts === "string" ? Date.parse(entry.ts) : NaN;
|
|
31
|
+
if (entry.version !== EVENTS_VERSION) {
|
|
32
|
+
unknownVersion ??= {
|
|
33
|
+
version: entry.version,
|
|
34
|
+
ts: Number.isFinite(ts) ? ts : null
|
|
35
|
+
};
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (typeof entry.session === "string") session ??= entry.session;
|
|
39
|
+
const { kind, command, summary } = entry;
|
|
40
|
+
const requestId = typeof entry.requestId === "string" ? entry.requestId : null;
|
|
41
|
+
if (kind === "request.started" && requestId && Number.isFinite(ts)) started.push([requestId, ts]);
|
|
42
|
+
if (kind !== "action.recorded" && kind !== "request.finished") continue;
|
|
43
|
+
const failed = kind === "request.finished" && entry.status === "error";
|
|
44
|
+
if (kind === "request.finished" && !failed) {
|
|
45
|
+
if (requestId) finished.push(requestId);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (!Number.isFinite(ts) || typeof entry.session !== "string" || typeof command !== "string") continue;
|
|
49
|
+
if (typeof summary !== "string") continue;
|
|
50
|
+
events.push({
|
|
51
|
+
ts,
|
|
52
|
+
session: entry.session,
|
|
53
|
+
kind,
|
|
54
|
+
command,
|
|
55
|
+
summary,
|
|
56
|
+
failed,
|
|
57
|
+
details: object$1(entry.details),
|
|
58
|
+
requestId
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
events,
|
|
63
|
+
started,
|
|
64
|
+
finished,
|
|
65
|
+
session,
|
|
66
|
+
unknownVersion
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const RUNNER_DEVICE = /-destination\s+"?platform=iOS Simulator,id=([^",\s]+)|AgentDeviceRunner\.env\.session-(.+?)-owner-/;
|
|
70
|
+
const LOG_TIME = /^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2})(\.\d+)?([+-]\d{2})?(\d{2})?\b/;
|
|
71
|
+
function logTime(line) {
|
|
72
|
+
const match = LOG_TIME.exec(line);
|
|
73
|
+
if (!match) return null;
|
|
74
|
+
const [, date, time, fraction = "", offsetHours, offsetMinutes] = match;
|
|
75
|
+
const offset = offsetHours && offsetMinutes ? `${offsetHours}:${offsetMinutes}` : "";
|
|
76
|
+
const at = Date.parse(`${date}T${time}${fraction.slice(0, 4)}${offset}`);
|
|
77
|
+
return Number.isFinite(at) ? at : null;
|
|
78
|
+
}
|
|
79
|
+
function parseRunnerSpans(text) {
|
|
80
|
+
const spans = [];
|
|
81
|
+
let pending = null;
|
|
82
|
+
for (const line of text.split("\n")) {
|
|
83
|
+
const device = RUNNER_DEVICE.exec(line);
|
|
84
|
+
if (device) {
|
|
85
|
+
pending = device[1] ?? device[2] ?? null;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (pending === null) continue;
|
|
89
|
+
const at = logTime(line);
|
|
90
|
+
if (at === null) continue;
|
|
91
|
+
if (spans.at(-1)?.deviceId !== pending) spans.push({
|
|
92
|
+
deviceId: pending,
|
|
93
|
+
from: spans.length ? at : -Infinity
|
|
94
|
+
});
|
|
95
|
+
pending = null;
|
|
96
|
+
}
|
|
97
|
+
return spans;
|
|
98
|
+
}
|
|
99
|
+
function sessionSpans(runner, closes) {
|
|
100
|
+
return runner.map((span, i) => {
|
|
101
|
+
const previous = runner[i - 1]?.from ?? -Infinity;
|
|
102
|
+
const close = closes.findLast((at) => at > previous && at < span.from);
|
|
103
|
+
return close === void 0 ? span : {
|
|
104
|
+
...span,
|
|
105
|
+
from: close + 1
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
function spanDevice(spans, ts) {
|
|
110
|
+
let device = null;
|
|
111
|
+
for (const span of spans) if (span.from <= ts) device = span.deviceId;
|
|
112
|
+
return device;
|
|
113
|
+
}
|
|
114
|
+
function platformOf(event) {
|
|
115
|
+
const platform = event.details?.platform;
|
|
116
|
+
return platform === "ios" || platform === "android" ? platform : void 0;
|
|
117
|
+
}
|
|
118
|
+
function agentRecord(event, deviceId, target, startedAt) {
|
|
119
|
+
return {
|
|
120
|
+
ts: event.ts,
|
|
121
|
+
...startedAt !== void 0 && startedAt <= event.ts ? { startedAt } : {},
|
|
122
|
+
src: "agent",
|
|
123
|
+
level: event.failed ? "error" : "info",
|
|
124
|
+
msg: event.summary,
|
|
125
|
+
event: event.failed ? "agent_failed" : "agent_action",
|
|
126
|
+
command: event.command,
|
|
127
|
+
session: event.session,
|
|
128
|
+
...target.platform ? { platform: target.platform } : {},
|
|
129
|
+
...deviceId ? { deviceId } : {},
|
|
130
|
+
...target.slot === "default" ? {} : { slot: target.slot },
|
|
131
|
+
...event.details ? { details: event.details } : {}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function formatUnknownRecord(session, unknown, target, now) {
|
|
135
|
+
return {
|
|
136
|
+
ts: unknown.ts ?? now,
|
|
137
|
+
src: "agent",
|
|
138
|
+
level: "warn",
|
|
139
|
+
msg: `agent-device session ${session} records events in an unrecognized format (version ${JSON.stringify(unknown.version) ?? "missing"}); its actions are not shown.`,
|
|
140
|
+
event: "agent_format_unknown",
|
|
141
|
+
session,
|
|
142
|
+
...target.platform ? { platform: target.platform } : {},
|
|
143
|
+
...target.slot === "default" ? {} : { slot: target.slot }
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function readCompleteLines(path, start) {
|
|
147
|
+
let fd;
|
|
148
|
+
try {
|
|
149
|
+
fd = openSync(path, "r");
|
|
150
|
+
const size = fstatSync(fd).size;
|
|
151
|
+
if (size <= start) return {
|
|
152
|
+
text: "",
|
|
153
|
+
next: start,
|
|
154
|
+
size
|
|
155
|
+
};
|
|
156
|
+
const buffer = Buffer.alloc(size - start);
|
|
157
|
+
const read = readSync(fd, buffer, 0, buffer.length, start);
|
|
158
|
+
const end = buffer.subarray(0, read).lastIndexOf(10);
|
|
159
|
+
return {
|
|
160
|
+
text: end < 0 ? "" : buffer.toString("utf8", 0, end + 1),
|
|
161
|
+
next: start + end + 1,
|
|
162
|
+
size
|
|
163
|
+
};
|
|
164
|
+
} catch {
|
|
165
|
+
return null;
|
|
166
|
+
} finally {
|
|
167
|
+
if (fd !== void 0) closeSync(fd);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Reads agent-device session actions incrementally from the supplied sessions directories. With targets,
|
|
172
|
+
* records are restricted to those devices; without targets, retained session metadata supplies attribution.
|
|
173
|
+
* Missing directories and unreadable files yield no records.
|
|
174
|
+
*/
|
|
175
|
+
function createAgentActionReader({ sessionsDirs, targets, sinceTs = -Infinity, now = Date.now, claimedDevice = () => null }) {
|
|
176
|
+
const cursors = /* @__PURE__ */ new Map();
|
|
177
|
+
const byId = new Map(targets?.map((target) => [target.id, target]));
|
|
178
|
+
return () => {
|
|
179
|
+
if (targets && !targets.length) return [];
|
|
180
|
+
const sessionDirs = (typeof sessionsDirs === "function" ? sessionsDirs() : sessionsDirs).flatMap((sessionsDir) => {
|
|
181
|
+
try {
|
|
182
|
+
return readdirSync(sessionsDir).map((name) => ({
|
|
183
|
+
name,
|
|
184
|
+
sessionsDir,
|
|
185
|
+
dir: join(sessionsDir, name)
|
|
186
|
+
}));
|
|
187
|
+
} catch {
|
|
188
|
+
return [];
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
const out = [];
|
|
192
|
+
for (const { name, sessionsDir, dir } of sessionDirs) {
|
|
193
|
+
const eventsPath = join(dir, "events.ndjson");
|
|
194
|
+
const lines = [];
|
|
195
|
+
let cursor = cursors.get(dir);
|
|
196
|
+
if (!cursor) {
|
|
197
|
+
try {
|
|
198
|
+
if (statSync(eventsPath).mtimeMs < sinceTs) continue;
|
|
199
|
+
} catch {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
cursor = {
|
|
203
|
+
offset: 0,
|
|
204
|
+
closes: [],
|
|
205
|
+
runner: null,
|
|
206
|
+
session: null,
|
|
207
|
+
unknownReported: false,
|
|
208
|
+
started: /* @__PURE__ */ new Map()
|
|
209
|
+
};
|
|
210
|
+
cursors.set(dir, cursor);
|
|
211
|
+
try {
|
|
212
|
+
lines.push(...readFileSync(`${eventsPath}.1`, "utf8").split("\n"));
|
|
213
|
+
} catch {}
|
|
214
|
+
}
|
|
215
|
+
let chunk = readCompleteLines(eventsPath, cursor.offset);
|
|
216
|
+
if (chunk && chunk.size < cursor.offset) chunk = readCompleteLines(eventsPath, 0);
|
|
217
|
+
if (!chunk) continue;
|
|
218
|
+
cursor.offset = chunk.next;
|
|
219
|
+
lines.push(...chunk.text.split("\n"));
|
|
220
|
+
const parsed = parseAgentEvents(lines);
|
|
221
|
+
for (const [requestId, at] of parsed.started) cursor.started.set(requestId, at);
|
|
222
|
+
cursor.session ??= parsed.session;
|
|
223
|
+
const session = cursor.session;
|
|
224
|
+
const matched = new Map(parsed.events.flatMap((event) => event.requestId && cursor.started.has(event.requestId) ? [[event.requestId, cursor.started.get(event.requestId)]] : []));
|
|
225
|
+
const failed = parsed.events.flatMap((event) => event.failed && event.requestId ? [event.requestId] : []);
|
|
226
|
+
for (const requestId of [...parsed.finished, ...failed]) cursor.started.delete(requestId);
|
|
227
|
+
for (const requestId of [...cursor.started.keys()].slice(0, -64)) cursor.started.delete(requestId);
|
|
228
|
+
if (!parsed.events.length && !parsed.unknownVersion) continue;
|
|
229
|
+
const runnerPath = join(dir, "runner.log");
|
|
230
|
+
let runnerSize = -1;
|
|
231
|
+
try {
|
|
232
|
+
runnerSize = statSync(runnerPath).size;
|
|
233
|
+
} catch {}
|
|
234
|
+
if (cursor.runner?.size !== runnerSize) {
|
|
235
|
+
let text = "";
|
|
236
|
+
try {
|
|
237
|
+
text = readFileSync(runnerPath, "utf8");
|
|
238
|
+
} catch {}
|
|
239
|
+
cursor.runner = {
|
|
240
|
+
size: runnerSize,
|
|
241
|
+
spans: parseRunnerSpans(text)
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
for (const event of parsed.events) if (event.command === "close" && !event.failed) cursor.closes.push(event.ts);
|
|
245
|
+
const spans = sessionSpans(cursor.runner.spans, cursor.closes);
|
|
246
|
+
const deviceAt = (ts) => spans.length ? spanDevice(spans, ts) : session && claimedDevice(session, sessionsDir);
|
|
247
|
+
cursor.platform ??= parsed.events.map(platformOf).find((platform) => platform !== void 0);
|
|
248
|
+
cursor.platform ??= spans.length ? "ios" : void 0;
|
|
249
|
+
const retainedTarget = () => ({
|
|
250
|
+
platform: cursor.platform,
|
|
251
|
+
slot: "default"
|
|
252
|
+
});
|
|
253
|
+
if (parsed.unknownVersion && !cursor.unknownReported) {
|
|
254
|
+
const deviceId = spans.at(-1)?.deviceId ?? (session && claimedDevice(session, sessionsDir));
|
|
255
|
+
const target = targets ? deviceId ? byId.get(deviceId) : void 0 : retainedTarget();
|
|
256
|
+
if (target) {
|
|
257
|
+
cursor.unknownReported = true;
|
|
258
|
+
out.push(formatUnknownRecord(session ?? name, parsed.unknownVersion, target, now()));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
for (const event of parsed.events) {
|
|
262
|
+
if (event.ts < sinceTs) continue;
|
|
263
|
+
if (!targets) cursor.platform = platformOf(event) ?? cursor.platform;
|
|
264
|
+
const deviceId = !targets && cursor.platform === "android" ? null : deviceAt(event.ts);
|
|
265
|
+
const target = targets ? deviceId ? byId.get(deviceId) : void 0 : retainedTarget();
|
|
266
|
+
const startedAt = event.requestId ? matched.get(event.requestId) : void 0;
|
|
267
|
+
if (target) out.push(agentRecord(event, deviceId || null, target, startedAt));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return out.toSorted((a, b) => a.ts - b.ts);
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
//#endregion
|
|
10
274
|
//#region state/json-file.ts
|
|
11
275
|
const readScope = new AsyncLocalStorage();
|
|
12
276
|
/**
|
|
@@ -49,6 +313,64 @@ function readJsonObject(path) {
|
|
|
49
313
|
}
|
|
50
314
|
}
|
|
51
315
|
//#endregion
|
|
316
|
+
//#region state/agent-device-usage.ts
|
|
317
|
+
function agentDeviceUsageFile() {
|
|
318
|
+
return join(configDir(), "agent-device-usage.json");
|
|
319
|
+
}
|
|
320
|
+
function readAgentDeviceUsageCache() {
|
|
321
|
+
const value = readJsonObject(agentDeviceUsageFile());
|
|
322
|
+
if (!value || value.version !== 1) return null;
|
|
323
|
+
const { roots, ...usage } = value;
|
|
324
|
+
if (!validUsage(usage) || !validRoots(roots)) return null;
|
|
325
|
+
return {
|
|
326
|
+
usage,
|
|
327
|
+
roots
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
function readAgentDeviceUsage() {
|
|
331
|
+
const value = readJsonObject(agentDeviceUsageFile());
|
|
332
|
+
if (!value) return null;
|
|
333
|
+
const { roots: _roots, ...usage } = value;
|
|
334
|
+
return validUsage(usage) ? usage : null;
|
|
335
|
+
}
|
|
336
|
+
function object(value) {
|
|
337
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
338
|
+
}
|
|
339
|
+
function bytes(value) {
|
|
340
|
+
return value === null || typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
341
|
+
}
|
|
342
|
+
function sized(value) {
|
|
343
|
+
return object(value) && typeof value.dir === "string" && bytes(value.bytes);
|
|
344
|
+
}
|
|
345
|
+
function count$1(value) {
|
|
346
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
347
|
+
}
|
|
348
|
+
function nullableString(value) {
|
|
349
|
+
return value === null || typeof value === "string";
|
|
350
|
+
}
|
|
351
|
+
function validRoots(value) {
|
|
352
|
+
return object(value) && typeof value.stateDir === "string" && typeof value.runnerRoot === "string" && typeof value.hostedDir === "string" && Array.isArray(value.workspaceDirs) && value.workspaceDirs.every((dir) => typeof dir === "string");
|
|
353
|
+
}
|
|
354
|
+
function validUsage(value) {
|
|
355
|
+
const { stateDir: state, runnerBuilds: runner, workspaces, hosted } = value;
|
|
356
|
+
return value.version === 1 && typeof value.measuredAt === "string" && Number.isFinite(Date.parse(value.measuredAt)) && typeof value.bytes === "number" && bytes(value.bytes) && typeof value.complete === "boolean" && sized(state) && typeof state.present === "boolean" && sized(state.sessions) && count$1(state.sessions.count) && sized(state.logs) && object(state.other) && bytes(state.other.bytes) && Array.isArray(state.other.largest) && state.other.largest.every((entry) => object(entry) && typeof entry.name === "string" && bytes(entry.bytes)) && sized(runner) && typeof runner.present === "boolean" && bytes(runner.sharedBytes) && Array.isArray(runner.platforms) && runner.platforms.every((platform) => sized(platform) && typeof platform.platform === "string" && Array.isArray(platform.entries) && platform.entries.every((entry) => sized(entry) && typeof entry.name === "string" && nullableString(entry.lastUsedAt) && nullableString(entry.packageVersion) && nullableString(entry.xcodeBuildVersion) && typeof entry.inUse === "boolean" && [
|
|
357
|
+
null,
|
|
358
|
+
"lease",
|
|
359
|
+
"lock",
|
|
360
|
+
"unreadable"
|
|
361
|
+
].includes(entry.inUseReason))) && Array.isArray(workspaces) && workspaces.every((entry) => sized(entry) && nullableString(entry.projectRoot)) && (hosted === null || sized(hosted) && count$1(hosted.sessions));
|
|
362
|
+
}
|
|
363
|
+
//#endregion
|
|
364
|
+
//#region state/swiftpm-cache-usage.ts
|
|
365
|
+
function swiftpmCacheUsageFile() {
|
|
366
|
+
return join(configDir(), "swiftpm-cache-usage.json");
|
|
367
|
+
}
|
|
368
|
+
function readSwiftpmCacheUsage() {
|
|
369
|
+
const value = readJsonObject(swiftpmCacheUsageFile());
|
|
370
|
+
if (!value || value.version !== 1 || typeof value.measuredAt !== "string" || !Number.isFinite(Date.parse(value.measuredAt)) || typeof value.dir !== "string" || typeof value.present !== "boolean" || typeof value.complete !== "boolean" || !(value.bytes === null || typeof value.bytes === "number" && Number.isFinite(value.bytes) && value.bytes >= 0)) return null;
|
|
371
|
+
return value;
|
|
372
|
+
}
|
|
373
|
+
//#endregion
|
|
52
374
|
//#region state/paths.ts
|
|
53
375
|
function getConfigPath() {
|
|
54
376
|
return join(configDir(), "config.json");
|
|
@@ -59,6 +381,9 @@ function configLockPath() {
|
|
|
59
381
|
function workspaceMetadataFile(projectRoot) {
|
|
60
382
|
return join(workspaceStateDir(projectRoot), "workspace.json");
|
|
61
383
|
}
|
|
384
|
+
function workspaceAgentDeviceDir(projectRoot) {
|
|
385
|
+
return join(workspaceStateDir(projectRoot), "agent-device");
|
|
386
|
+
}
|
|
62
387
|
function workspaceLogsDir(projectRoot) {
|
|
63
388
|
return join(workspaceStateDir(projectRoot), "logs");
|
|
64
389
|
}
|
|
@@ -164,6 +489,39 @@ function deviceViewersDir() {
|
|
|
164
489
|
function statsFile() {
|
|
165
490
|
return join(configDir(), "stats.json");
|
|
166
491
|
}
|
|
492
|
+
function maintenanceDir() {
|
|
493
|
+
return join(configDir(), "maintenance");
|
|
494
|
+
}
|
|
495
|
+
function maintenanceStateFile() {
|
|
496
|
+
return join(maintenanceDir(), "state.json");
|
|
497
|
+
}
|
|
498
|
+
function maintenanceAttemptFile() {
|
|
499
|
+
return join(maintenanceDir(), "attempt.json");
|
|
500
|
+
}
|
|
501
|
+
function maintenanceNdjsonFile() {
|
|
502
|
+
return join(maintenanceDir(), "maintenance.ndjson");
|
|
503
|
+
}
|
|
504
|
+
function maintenanceChildLogFile() {
|
|
505
|
+
return join(maintenanceDir(), "child.log");
|
|
506
|
+
}
|
|
507
|
+
function maintenanceRunClaims() {
|
|
508
|
+
return join(maintenanceDir(), "run.claims");
|
|
509
|
+
}
|
|
510
|
+
function archiveRoot() {
|
|
511
|
+
return join(configDir(), "archive");
|
|
512
|
+
}
|
|
513
|
+
function archiveDir(id) {
|
|
514
|
+
return join(archiveRoot(), id);
|
|
515
|
+
}
|
|
516
|
+
function archiveLock() {
|
|
517
|
+
return join(archiveRoot(), ".lock");
|
|
518
|
+
}
|
|
519
|
+
function serverSetupDir() {
|
|
520
|
+
return join(configDir(), "server", "setup");
|
|
521
|
+
}
|
|
522
|
+
function setupJournalFile(hash) {
|
|
523
|
+
return hash.length === 64 && /^[a-f0-9]{64}$/.test(hash) ? join(serverSetupDir(), `${hash}.json`) : null;
|
|
524
|
+
}
|
|
167
525
|
//#endregion
|
|
168
526
|
//#region state/config.ts
|
|
169
527
|
function withConfigLock(fn) {
|
|
@@ -288,6 +646,66 @@ function findProjectByMetroPort(port) {
|
|
|
288
646
|
return null;
|
|
289
647
|
}
|
|
290
648
|
//#endregion
|
|
649
|
+
//#region state/tailnet.ts
|
|
650
|
+
const DEFAULT_SERVE_PORT = 7443;
|
|
651
|
+
const TAILNET_MACHINE_PATTERN = "^[A-Za-z0-9][A-Za-z0-9.-]*(?::(?!0+$)(?:[0-5]?[0-9]{1,4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]))?$";
|
|
652
|
+
function parseMachine(entry) {
|
|
653
|
+
if (!(/* @__PURE__ */ new RegExp("^[A-Za-z0-9][A-Za-z0-9.-]*(?::(?!0+$)(?:[0-5]?[0-9]{1,4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]))?$")).test(entry.trim())) return null;
|
|
654
|
+
const [name, port] = entry.trim().split(":");
|
|
655
|
+
return {
|
|
656
|
+
name: name.toLowerCase(),
|
|
657
|
+
port: port === void 0 ? DEFAULT_SERVE_PORT : Number(port)
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* The one peer in `tailscale status --json` whose MagicDNS name is `name` or starts with `name.`. `ID` there is
|
|
662
|
+
* the node's StableID, the same value `tailscale whois` reports as `Node.StableID`.
|
|
663
|
+
*/
|
|
664
|
+
function findPeer(status, name) {
|
|
665
|
+
const found = (isJsonObject(status) && isJsonObject(status.Peer) ? Object.values(status.Peer) : []).flatMap((peer) => {
|
|
666
|
+
if (!isJsonObject(peer) || typeof peer.ID !== "string" || typeof peer.DNSName !== "string") return [];
|
|
667
|
+
const dnsName = peer.DNSName.replace(/\.$/, "").toLowerCase();
|
|
668
|
+
const addresses = Array.isArray(peer.TailscaleIPs) ? peer.TailscaleIPs.filter((ip) => typeof ip === "string") : [];
|
|
669
|
+
const address = addresses.find((ip) => !ip.includes(":")) ?? addresses[0];
|
|
670
|
+
if (!address || !(dnsName === name || dnsName.startsWith(`${name}.`))) return [];
|
|
671
|
+
return [{
|
|
672
|
+
nodeId: peer.ID,
|
|
673
|
+
dnsName,
|
|
674
|
+
address
|
|
675
|
+
}];
|
|
676
|
+
});
|
|
677
|
+
if (found.length === 0) return "missing";
|
|
678
|
+
return found.length === 1 ? found[0] : "ambiguous";
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Connects to the pinned node's own tailnet address rather than resolving its name, so the socket reaches the
|
|
682
|
+
* node whose ID was just checked. `tailscale serve` still needs the MagicDNS name for TLS (SNI and certificate)
|
|
683
|
+
* and for its Host routing.
|
|
684
|
+
*/
|
|
685
|
+
function endpoint(peer, port) {
|
|
686
|
+
const address = peer.address.includes(":") ? `[${peer.address}]` : peer.address;
|
|
687
|
+
const suffix = port === 443 ? "" : `:${port}`;
|
|
688
|
+
return {
|
|
689
|
+
url: `wss://${address}${suffix}`,
|
|
690
|
+
servername: peer.dnsName,
|
|
691
|
+
host: `${peer.dnsName}${suffix}`
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* The endpoint of a paired machine's pinned node, or why Stim does not connect to it. The name must still
|
|
696
|
+
* resolve to exactly the pinned node; the token is never sent anywhere else.
|
|
697
|
+
*/
|
|
698
|
+
function pinnedEndpoint(credential, current) {
|
|
699
|
+
const parsed = parseMachine(credential.machine);
|
|
700
|
+
if (!parsed) return `${credential.machine} is not a tailnet name`;
|
|
701
|
+
if (!isJsonObject(current)) return "Tailscale is not running";
|
|
702
|
+
const peer = findPeer(current, parsed.name);
|
|
703
|
+
if (peer === "missing") return `no peer on this tailnet is named ${parsed.name}`;
|
|
704
|
+
if (peer === "ambiguous") return `several tailnet peers match ${parsed.name}`;
|
|
705
|
+
if (peer.nodeId !== credential.nodeId) return `${peer.dnsName} is now node ${peer.nodeId}, not the pinned ${credential.nodeId}; run stim doctor`;
|
|
706
|
+
return endpoint(peer, parsed.port);
|
|
707
|
+
}
|
|
708
|
+
//#endregion
|
|
291
709
|
//#region state/hosted-macos.ts
|
|
292
710
|
/** App slots one host offers; a slot names the host-assigned bundle id `<bundleId>.hosted<slot>`. */
|
|
293
711
|
const HOSTED_MACOS_APP_SLOTS = 64;
|
|
@@ -317,11 +735,11 @@ const AGENT_PATH = /^\/device-host\/agent\/[a-f0-9-]{36}\/$/;
|
|
|
317
735
|
const LEASE_NAME = /^[A-Za-z0-9._-]{1,128}$/;
|
|
318
736
|
const LEASE_DEVICE_KEY = /^[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)+@[1-9][0-9]{0,9}$/;
|
|
319
737
|
function parseHostedAgentLease(value) {
|
|
320
|
-
if (!isJsonObject(value) || Object.keys(value).length !== 4 || ![
|
|
738
|
+
if (!isJsonObject(value) || Object.keys(value).length !== (value.backend === "ios-instance" ? 5 : 4) || ![
|
|
321
739
|
value.tenant,
|
|
322
740
|
value.runId,
|
|
323
741
|
value.clientId
|
|
324
|
-
].every((name) => typeof name === "string" && LEASE_NAME.test(name)) || typeof value.deviceKey !== "string" || !LEASE_DEVICE_KEY.test(value.deviceKey)) return null;
|
|
742
|
+
].every((name) => typeof name === "string" && LEASE_NAME.test(name)) || typeof value.deviceKey !== "string" || !(value.backend === "ios-instance" ? /^ios:mobile:[a-fA-F0-9-]{36}$/.test(value.deviceKey) : value.backend === void 0 && LEASE_DEVICE_KEY.test(value.deviceKey))) return null;
|
|
325
743
|
return value;
|
|
326
744
|
}
|
|
327
745
|
/** A grant naming a driver this client does not know is not usable; it parses as null. */
|
|
@@ -345,7 +763,7 @@ function parseHostedAgentAccess(value) {
|
|
|
345
763
|
driver: "none",
|
|
346
764
|
setting: "hosting.agentDriver"
|
|
347
765
|
} : null;
|
|
348
|
-
if (value.driver !== "agent-device" || typeof value.remoteConfig !== "string" || !value.remoteConfig
|
|
766
|
+
if (value.driver !== "agent-device" || typeof value.remoteConfig !== "string" || !isAbsolute(value.remoteConfig) || typeof value.command !== "string" || !value.command || Object.keys(value).length !== 3) return null;
|
|
349
767
|
return {
|
|
350
768
|
driver: "agent-device",
|
|
351
769
|
remoteConfig: value.remoteConfig,
|
|
@@ -516,9 +934,13 @@ const SETTINGS = [
|
|
|
516
934
|
},
|
|
517
935
|
{
|
|
518
936
|
key: "ios.remote",
|
|
519
|
-
type:
|
|
937
|
+
type: {
|
|
938
|
+
kind: "string",
|
|
939
|
+
pattern: TAILNET_MACHINE_PATTERN,
|
|
940
|
+
patternHelp: "eas, proxy, auto, or a tailnet machine name"
|
|
941
|
+
},
|
|
520
942
|
scopes: PROJECT,
|
|
521
|
-
description: "Default remote
|
|
943
|
+
description: "Default iOS remote target: eas, proxy, or an approved Mac in hosting.machines; auto is not available yet"
|
|
522
944
|
},
|
|
523
945
|
{
|
|
524
946
|
key: "ios.simslimProfile",
|
|
@@ -751,6 +1173,79 @@ const SETTINGS = [
|
|
|
751
1173
|
env: "STIM_RECORDING",
|
|
752
1174
|
description: "Let stim-server record device screens for replay; false stops it and deletes the recordings"
|
|
753
1175
|
},
|
|
1176
|
+
{
|
|
1177
|
+
key: "archive.enabled",
|
|
1178
|
+
type: BOOLEAN,
|
|
1179
|
+
scopes: EVERY,
|
|
1180
|
+
default: true,
|
|
1181
|
+
env: "STIM_ARCHIVE_ENABLED",
|
|
1182
|
+
scopedHomeValue: false,
|
|
1183
|
+
description: "Keep workspace history on removal; false under STIM_HOME unless explicitly enabled"
|
|
1184
|
+
},
|
|
1185
|
+
{
|
|
1186
|
+
key: "archive.maxAgeDays",
|
|
1187
|
+
type: CAPACITY,
|
|
1188
|
+
scopes: MACHINE,
|
|
1189
|
+
default: 30,
|
|
1190
|
+
env: "STIM_ARCHIVE_MAX_AGE_DAYS",
|
|
1191
|
+
description: "Days to keep archived workspace records; 0 keeps nothing"
|
|
1192
|
+
},
|
|
1193
|
+
{
|
|
1194
|
+
key: "archive.maxCount",
|
|
1195
|
+
type: CAPACITY,
|
|
1196
|
+
scopes: MACHINE,
|
|
1197
|
+
default: 200,
|
|
1198
|
+
env: "STIM_ARCHIVE_MAX_COUNT",
|
|
1199
|
+
description: "Archived workspace count; oldest records go first, 0 keeps nothing"
|
|
1200
|
+
},
|
|
1201
|
+
{
|
|
1202
|
+
key: "archive.maxTotalGb",
|
|
1203
|
+
type: GIGABYTES,
|
|
1204
|
+
scopes: MACHINE,
|
|
1205
|
+
default: 5,
|
|
1206
|
+
env: "STIM_ARCHIVE_MAX_TOTAL_GB",
|
|
1207
|
+
description: "Total archived bytes in binary GB; 0 keeps no artifacts"
|
|
1208
|
+
},
|
|
1209
|
+
{
|
|
1210
|
+
key: "archive.logs.maxAgeDays",
|
|
1211
|
+
type: CAPACITY,
|
|
1212
|
+
scopes: MACHINE,
|
|
1213
|
+
default: 14,
|
|
1214
|
+
env: "STIM_ARCHIVE_LOGS_MAX_AGE_DAYS",
|
|
1215
|
+
description: "Days to keep archived logs; 0 keeps none"
|
|
1216
|
+
},
|
|
1217
|
+
{
|
|
1218
|
+
key: "archive.logs.maxMbPerWorkspace",
|
|
1219
|
+
type: GIGABYTES,
|
|
1220
|
+
scopes: MACHINE,
|
|
1221
|
+
default: 100,
|
|
1222
|
+
env: "STIM_ARCHIVE_LOGS_MAX_MB_PER_WORKSPACE",
|
|
1223
|
+
description: "Archived logs per workspace in binary MB; 0 keeps none"
|
|
1224
|
+
},
|
|
1225
|
+
{
|
|
1226
|
+
key: "archive.recordings.maxAgeDays",
|
|
1227
|
+
type: CAPACITY,
|
|
1228
|
+
scopes: MACHINE,
|
|
1229
|
+
default: 3,
|
|
1230
|
+
env: "STIM_ARCHIVE_RECORDINGS_MAX_AGE_DAYS",
|
|
1231
|
+
description: "Days to keep archived recordings; 0 keeps none"
|
|
1232
|
+
},
|
|
1233
|
+
{
|
|
1234
|
+
key: "archive.recordings.maxTotalGb",
|
|
1235
|
+
type: GIGABYTES,
|
|
1236
|
+
scopes: MACHINE,
|
|
1237
|
+
default: 2,
|
|
1238
|
+
env: "STIM_ARCHIVE_RECORDINGS_MAX_TOTAL_GB",
|
|
1239
|
+
description: "Archived recordings in binary GB; 0 keeps none"
|
|
1240
|
+
},
|
|
1241
|
+
{
|
|
1242
|
+
key: "archive.agentActions.maxAgeDays",
|
|
1243
|
+
type: CAPACITY,
|
|
1244
|
+
scopes: MACHINE,
|
|
1245
|
+
default: 7,
|
|
1246
|
+
env: "STIM_ARCHIVE_AGENT_ACTIONS_MAX_AGE_DAYS",
|
|
1247
|
+
description: "Days to keep archived agent-device sessions; 0 keeps none"
|
|
1248
|
+
},
|
|
754
1249
|
{
|
|
755
1250
|
key: "worktree.exclude",
|
|
756
1251
|
type: { kind: "strings" },
|
|
@@ -849,6 +1344,179 @@ const SETTINGS = [
|
|
|
849
1344
|
scopedHomeValue: 0,
|
|
850
1345
|
description: "Live workspaces before idle ones are reclaimed; unset or 0 means no limit"
|
|
851
1346
|
},
|
|
1347
|
+
{
|
|
1348
|
+
key: "maintenance.mode",
|
|
1349
|
+
type: {
|
|
1350
|
+
kind: "choice",
|
|
1351
|
+
choices: ["off", "report"]
|
|
1352
|
+
},
|
|
1353
|
+
scopes: MACHINE,
|
|
1354
|
+
default: "report",
|
|
1355
|
+
env: "STIM_MAINTENANCE",
|
|
1356
|
+
scopedHomeValue: "off",
|
|
1357
|
+
ciValue: "off",
|
|
1358
|
+
description: "Automatic resource maintenance. report measures, plans and logs what it would do and never deletes or stops anything; off disables it. Off under STIM_HOME or CI unless STIM_MAINTENANCE is set"
|
|
1359
|
+
},
|
|
1360
|
+
{
|
|
1361
|
+
key: "maintenance.pressureCheckMinutes",
|
|
1362
|
+
type: {
|
|
1363
|
+
kind: "number",
|
|
1364
|
+
integer: true,
|
|
1365
|
+
minimum: 1
|
|
1366
|
+
},
|
|
1367
|
+
scopes: MACHINE,
|
|
1368
|
+
default: 1,
|
|
1369
|
+
env: "STIM_MAINTENANCE_PRESSURE_CHECK_MINUTES",
|
|
1370
|
+
description: "Disk and memory check interval in minutes"
|
|
1371
|
+
},
|
|
1372
|
+
{
|
|
1373
|
+
key: "maintenance.sizeCheckMinutes",
|
|
1374
|
+
type: {
|
|
1375
|
+
kind: "number",
|
|
1376
|
+
integer: true,
|
|
1377
|
+
minimum: 1
|
|
1378
|
+
},
|
|
1379
|
+
scopes: MACHINE,
|
|
1380
|
+
default: 60,
|
|
1381
|
+
env: "STIM_MAINTENANCE_SIZE_CHECK_MINUTES",
|
|
1382
|
+
description: "Directory size check interval in minutes"
|
|
1383
|
+
},
|
|
1384
|
+
{
|
|
1385
|
+
key: "maintenance.maxLoadPerCore",
|
|
1386
|
+
type: {
|
|
1387
|
+
kind: "number",
|
|
1388
|
+
exclusiveMinimum: 0
|
|
1389
|
+
},
|
|
1390
|
+
scopes: MACHINE,
|
|
1391
|
+
default: 4,
|
|
1392
|
+
env: "STIM_MAINTENANCE_MAX_LOAD_PER_CORE",
|
|
1393
|
+
description: "Defer size scans above this one-minute load per CPU"
|
|
1394
|
+
},
|
|
1395
|
+
{
|
|
1396
|
+
key: "maintenance.logMaxMb",
|
|
1397
|
+
type: {
|
|
1398
|
+
kind: "number",
|
|
1399
|
+
exclusiveMinimum: 0
|
|
1400
|
+
},
|
|
1401
|
+
scopes: MACHINE,
|
|
1402
|
+
default: 1,
|
|
1403
|
+
env: "STIM_MAINTENANCE_LOG_MAX_MB",
|
|
1404
|
+
description: "Machine maintenance log rotation size in MiB"
|
|
1405
|
+
},
|
|
1406
|
+
{
|
|
1407
|
+
key: "maintenance.logRetentionDays",
|
|
1408
|
+
type: {
|
|
1409
|
+
kind: "number",
|
|
1410
|
+
integer: true,
|
|
1411
|
+
minimum: 1
|
|
1412
|
+
},
|
|
1413
|
+
scopes: MACHINE,
|
|
1414
|
+
default: 30,
|
|
1415
|
+
env: "STIM_MAINTENANCE_LOG_RETENTION_DAYS",
|
|
1416
|
+
description: "Retention of the rotated machine maintenance log in days"
|
|
1417
|
+
},
|
|
1418
|
+
{
|
|
1419
|
+
key: "maintenance.logChecks",
|
|
1420
|
+
type: BOOLEAN,
|
|
1421
|
+
scopes: MACHINE,
|
|
1422
|
+
default: false,
|
|
1423
|
+
env: "STIM_MAINTENANCE_LOG_CHECKS",
|
|
1424
|
+
description: "Write debug check records and routine skips"
|
|
1425
|
+
},
|
|
1426
|
+
{
|
|
1427
|
+
key: "maintenance.memoryPressureLevel",
|
|
1428
|
+
type: {
|
|
1429
|
+
kind: "choice",
|
|
1430
|
+
choices: [
|
|
1431
|
+
"warning",
|
|
1432
|
+
"critical",
|
|
1433
|
+
"off"
|
|
1434
|
+
]
|
|
1435
|
+
},
|
|
1436
|
+
scopes: MACHINE,
|
|
1437
|
+
default: "warning",
|
|
1438
|
+
env: "STIM_MAINTENANCE_MEMORY_PRESSURE_LEVEL",
|
|
1439
|
+
description: "Lowest macOS memory pressure level considered; off disables memory pressure reporting"
|
|
1440
|
+
},
|
|
1441
|
+
{
|
|
1442
|
+
key: "maintenance.memoryWarningMinutes",
|
|
1443
|
+
type: {
|
|
1444
|
+
kind: "number",
|
|
1445
|
+
integer: true,
|
|
1446
|
+
minimum: 0
|
|
1447
|
+
},
|
|
1448
|
+
scopes: MACHINE,
|
|
1449
|
+
default: 10,
|
|
1450
|
+
env: "STIM_MAINTENANCE_MEMORY_WARNING_MINUTES",
|
|
1451
|
+
description: "Consecutive warning minutes before memory counts as pressure"
|
|
1452
|
+
},
|
|
1453
|
+
{
|
|
1454
|
+
key: "maintenance.minAvailableMemoryGb",
|
|
1455
|
+
type: {
|
|
1456
|
+
kind: "number",
|
|
1457
|
+
minimum: 0
|
|
1458
|
+
},
|
|
1459
|
+
scopes: MACHINE,
|
|
1460
|
+
env: "STIM_MAINTENANCE_MIN_AVAILABLE_MEMORY_GB",
|
|
1461
|
+
description: "Available-memory threshold in GiB; unset is 10% of RAM"
|
|
1462
|
+
},
|
|
1463
|
+
{
|
|
1464
|
+
key: "maintenance.capTargetPercent",
|
|
1465
|
+
type: {
|
|
1466
|
+
kind: "number",
|
|
1467
|
+
integer: true,
|
|
1468
|
+
minimum: 10,
|
|
1469
|
+
maximum: 100
|
|
1470
|
+
},
|
|
1471
|
+
scopes: MACHINE,
|
|
1472
|
+
default: 80,
|
|
1473
|
+
env: "STIM_MAINTENANCE_CAP_TARGET_PERCENT",
|
|
1474
|
+
description: "Plan toward this percentage of each size cap"
|
|
1475
|
+
},
|
|
1476
|
+
{
|
|
1477
|
+
key: "maintenance.workspaceOutputsMaxGb",
|
|
1478
|
+
type: {
|
|
1479
|
+
kind: "number",
|
|
1480
|
+
minimum: 0
|
|
1481
|
+
},
|
|
1482
|
+
scopes: MACHINE,
|
|
1483
|
+
default: 20,
|
|
1484
|
+
env: "STIM_MAINTENANCE_WORKSPACE_OUTPUTS_MAX_GB",
|
|
1485
|
+
description: "Workspace build-output cap in GiB; 0 means no cap"
|
|
1486
|
+
},
|
|
1487
|
+
{
|
|
1488
|
+
key: "caches.buildCacheMaxGb",
|
|
1489
|
+
type: {
|
|
1490
|
+
kind: "number",
|
|
1491
|
+
minimum: 0
|
|
1492
|
+
},
|
|
1493
|
+
scopes: MACHINE,
|
|
1494
|
+
default: 10,
|
|
1495
|
+
env: "STIM_CACHES_BUILD_CACHE_MAX_GB",
|
|
1496
|
+
description: "Shared native build-cache cap in GiB; 0 means no cap"
|
|
1497
|
+
},
|
|
1498
|
+
{
|
|
1499
|
+
key: "caches.metroCacheMaxGb",
|
|
1500
|
+
type: {
|
|
1501
|
+
kind: "number",
|
|
1502
|
+
minimum: 0
|
|
1503
|
+
},
|
|
1504
|
+
scopes: MACHINE,
|
|
1505
|
+
default: 5,
|
|
1506
|
+
env: "STIM_CACHES_METRO_CACHE_MAX_GB",
|
|
1507
|
+
description: "Combined Metro transform-cache cap in GiB; 0 means no cap"
|
|
1508
|
+
},
|
|
1509
|
+
{
|
|
1510
|
+
key: "caches.swiftCompilationCacheMaxGb",
|
|
1511
|
+
type: {
|
|
1512
|
+
kind: "number",
|
|
1513
|
+
minimum: 0
|
|
1514
|
+
},
|
|
1515
|
+
scopes: MACHINE,
|
|
1516
|
+
default: 15,
|
|
1517
|
+
env: "STIM_CACHES_SWIFT_COMPILATION_CACHE_MAX_GB",
|
|
1518
|
+
description: "Swift compilation-cache cap in GiB; planned emptying is whole; 0 means no cap"
|
|
1519
|
+
},
|
|
852
1520
|
{
|
|
853
1521
|
key: "gc.worktreeGraceMinutes",
|
|
854
1522
|
type: CAPACITY,
|
|
@@ -889,7 +1557,7 @@ const SETTINGS = [
|
|
|
889
1557
|
},
|
|
890
1558
|
scopes: MACHINE,
|
|
891
1559
|
default: "none",
|
|
892
|
-
description: "Tool this Mac starts so a client's coding agent can drive the macOS apps it hosts for that client; none starts nothing"
|
|
1560
|
+
description: "Tool this Mac starts so a client's coding agent can drive the macOS apps and iOS simulators it hosts for that client; none starts nothing"
|
|
893
1561
|
},
|
|
894
1562
|
{
|
|
895
1563
|
key: "server.acceptClientBuilds",
|
|
@@ -908,7 +1576,7 @@ const SETTINGS = [
|
|
|
908
1576
|
key: "offload.machine",
|
|
909
1577
|
type: {
|
|
910
1578
|
kind: "string",
|
|
911
|
-
pattern:
|
|
1579
|
+
pattern: TAILNET_MACHINE_PATTERN,
|
|
912
1580
|
patternHelp: "auto, local, or a tailnet machine name"
|
|
913
1581
|
},
|
|
914
1582
|
scopes: MACHINE,
|
|
@@ -1025,6 +1693,7 @@ function acceptsShape(setting, value) {
|
|
|
1025
1693
|
function numberBounds(type) {
|
|
1026
1694
|
const noun = type.integer ? "a whole number" : "a number";
|
|
1027
1695
|
if (type.minimum !== void 0 && type.maximum !== void 0) return `${noun} from ${type.minimum} through ${type.maximum}`;
|
|
1696
|
+
if (type.exclusiveMinimum !== void 0) return `${noun}, greater than ${type.exclusiveMinimum}`;
|
|
1028
1697
|
if (type.minimum !== void 0) return `${noun}, ${type.minimum} or more`;
|
|
1029
1698
|
return noun;
|
|
1030
1699
|
}
|
|
@@ -1033,7 +1702,7 @@ function settingValueError(setting, value) {
|
|
|
1033
1702
|
if (!acceptsShape(setting, value)) return type.kind === "number" ? numberBounds(type) : expectedShape(type);
|
|
1034
1703
|
if (type.kind === "number") {
|
|
1035
1704
|
const number = value;
|
|
1036
|
-
if (!Number.isFinite(number) || type.integer && !Number.isSafeInteger(number) || type.minimum !== void 0 && number < type.minimum || type.maximum !== void 0 && number > type.maximum) return numberBounds(type);
|
|
1705
|
+
if (!Number.isFinite(number) || type.integer && !Number.isSafeInteger(number) || type.minimum !== void 0 && number < type.minimum || type.exclusiveMinimum !== void 0 && number <= type.exclusiveMinimum || type.maximum !== void 0 && number > type.maximum) return numberBounds(type);
|
|
1037
1706
|
}
|
|
1038
1707
|
if (type.kind === "path") {
|
|
1039
1708
|
const path = value;
|
|
@@ -1207,7 +1876,8 @@ function parseCredential(value) {
|
|
|
1207
1876
|
deviceId,
|
|
1208
1877
|
deviceToken,
|
|
1209
1878
|
state,
|
|
1210
|
-
requestedAt
|
|
1879
|
+
requestedAt,
|
|
1880
|
+
...typeof value.ticketHash === "string" ? { ticketHash: value.ticketHash } : {}
|
|
1211
1881
|
};
|
|
1212
1882
|
}
|
|
1213
1883
|
function readBuildMachines() {
|
|
@@ -1375,7 +2045,8 @@ const SOURCES = [
|
|
|
1375
2045
|
"client",
|
|
1376
2046
|
"device",
|
|
1377
2047
|
"build",
|
|
1378
|
-
"agent"
|
|
2048
|
+
"agent",
|
|
2049
|
+
"maintenance"
|
|
1379
2050
|
];
|
|
1380
2051
|
function levelRank(level) {
|
|
1381
2052
|
const i = LEVELS.indexOf(level);
|
|
@@ -1486,7 +2157,7 @@ function bundleMarkerPlatform(record) {
|
|
|
1486
2157
|
return (/^RSC\((.+)\)$/.exec(tag)?.[1] ?? tag).toLowerCase();
|
|
1487
2158
|
}
|
|
1488
2159
|
function isAppDeviceRecord(record) {
|
|
1489
|
-
return record.src === "device" && (record.event === "native_crash" || record.platform === "web");
|
|
2160
|
+
return record.src === "device" && (record.event === "native_crash" || record.event === "hosted_native_log" || record.platform === "web");
|
|
1490
2161
|
}
|
|
1491
2162
|
function recordMatches(record, criteria = {}) {
|
|
1492
2163
|
if (!record) return false;
|
|
@@ -1495,6 +2166,7 @@ function recordMatches(record, criteria = {}) {
|
|
|
1495
2166
|
if (sources && sources.length > 0 && !sources.includes(record.src) && !(criteria.includeAppDeviceErrors && isAppDeviceRecord(record))) return false;
|
|
1496
2167
|
if (minLevel && levelRank(record.level) < levelRank(minLevel)) return false;
|
|
1497
2168
|
if (errorsOnly) {
|
|
2169
|
+
if (record.src === "maintenance" && record.event !== "maintenance_failure") return false;
|
|
1498
2170
|
if (record.level !== "error" && record.level !== "fatal") return false;
|
|
1499
2171
|
if (typeof markerTs === "number") {
|
|
1500
2172
|
const ts = tsOf(record);
|
|
@@ -1559,6 +2231,11 @@ function readLogRecords(dir, include = () => true) {
|
|
|
1559
2231
|
for (const name of logFiles(dir).filter(include)) for (const record of readNdjsonGenerations(join(dir, name))) all.push(record);
|
|
1560
2232
|
return sortByTs(all);
|
|
1561
2233
|
}
|
|
2234
|
+
const NATIVE_CRASH_LOG_PREFIX = "native-crash-";
|
|
2235
|
+
/** Reads the CLI timeline with retained native crash records after other records at the same timestamp. */
|
|
2236
|
+
function readLogTimeline(dir, records) {
|
|
2237
|
+
return sortByTs([...records ?? readLogRecords(dir, (name) => !name.startsWith("native-crash-")), ...readLogRecords(dir, (name) => name.startsWith(NATIVE_CRASH_LOG_PREFIX))]);
|
|
2238
|
+
}
|
|
1562
2239
|
function isExpoErrorContext(record, event) {
|
|
1563
2240
|
if (record.src !== "metro" || record.raw !== true || record.event !== event || typeof record.msg !== "string") return false;
|
|
1564
2241
|
return /^Code: \S/.test(record.msg) || /^\s*>?\s*\d+\s*\|/.test(record.msg) || /^\s*\|\s*\^/.test(record.msg) || /^Call Stack$/.test(record.msg) || /^\s+.+\([^()]+:\d+:\d+\)\s*$/.test(record.msg) || /^\s+at\s+\S+:\d+:\d+\s*$/.test(record.msg);
|
|
@@ -1739,6 +2416,16 @@ function queryLogs({ slot, dir, records, sources, minLevel, since, grep, tail, e
|
|
|
1739
2416
|
if (typeof tail === "number" && tail >= 0 && matched.length > tail) matched = matched.slice(matched.length - tail);
|
|
1740
2417
|
return errorContext ? includeBareErrorContext(all, attachExpoErrorContext(all, matched), launchTs, criteria.sinceTs) : matched;
|
|
1741
2418
|
}
|
|
2419
|
+
/** Queries the JSON timeline, including Expo context fields on errors as `stim logs --json` does. */
|
|
2420
|
+
function queryJsonLogs(options = {}) {
|
|
2421
|
+
const records = options.records ?? readLogTimeline(options.dir);
|
|
2422
|
+
const matched = queryLogs({
|
|
2423
|
+
...options,
|
|
2424
|
+
records,
|
|
2425
|
+
errorContext: false
|
|
2426
|
+
});
|
|
2427
|
+
return options.errorsOnly ? attachExpoErrorContext(records, matched, "field") : matched;
|
|
2428
|
+
}
|
|
1742
2429
|
function tailRead(prev, size) {
|
|
1743
2430
|
const state = prev && typeof prev.offset === "number" ? prev : {
|
|
1744
2431
|
offset: 0,
|
|
@@ -1882,13 +2569,13 @@ function listDir(dir) {
|
|
|
1882
2569
|
return [];
|
|
1883
2570
|
}
|
|
1884
2571
|
}
|
|
1885
|
-
/** The segments in one device directory, oldest first. Files that are not segments are left out. */
|
|
1886
|
-
function listSegments(dir) {
|
|
2572
|
+
/** The segments in one device directory, oldest first. Files that are not segments are left out; `closedOnly` excludes files still being written. */
|
|
2573
|
+
function listSegments(dir, closedOnly = false) {
|
|
1887
2574
|
const segments = [];
|
|
1888
2575
|
for (const name of listDir(dir)) {
|
|
1889
2576
|
const closed = SEGMENT.exec(name);
|
|
1890
2577
|
const part = closed ? null : PART.exec(name);
|
|
1891
|
-
if (!closed && !part) continue;
|
|
2578
|
+
if (!closed && (closedOnly || !part)) continue;
|
|
1892
2579
|
let stat;
|
|
1893
2580
|
try {
|
|
1894
2581
|
stat = statSync(join(dir, name));
|
|
@@ -1993,6 +2680,7 @@ function leafSchema(setting, scope) {
|
|
|
1993
2680
|
case "number":
|
|
1994
2681
|
schema.type = type.integer ? "integer" : "number";
|
|
1995
2682
|
if (type.minimum !== void 0) schema.minimum = type.minimum;
|
|
2683
|
+
if (type.exclusiveMinimum !== void 0) schema.exclusiveMinimum = type.exclusiveMinimum;
|
|
1996
2684
|
if (type.maximum !== void 0) schema.maximum = type.maximum;
|
|
1997
2685
|
break;
|
|
1998
2686
|
case "boolean":
|
|
@@ -2835,8 +3523,8 @@ const PLATFORMS = ["ios", "android"];
|
|
|
2835
3523
|
const OUTCOMES = ["hit", "cold"];
|
|
2836
3524
|
const HISTORY_LIMIT = 10;
|
|
2837
3525
|
function statsProjectKey({ root, commonDir, repoRoot }) {
|
|
2838
|
-
if (commonDir && repoRoot && basename(commonDir) === ".git") return canonical(join(canonical(dirname(commonDir)), relative(canonical(repoRoot), canonical(root))));
|
|
2839
|
-
return canonical(root);
|
|
3526
|
+
if (commonDir && repoRoot && basename(commonDir) === ".git") return canonical$1(join(canonical$1(dirname(commonDir)), relative(canonical$1(repoRoot), canonical$1(root))));
|
|
3527
|
+
return canonical$1(root);
|
|
2840
3528
|
}
|
|
2841
3529
|
/** Placement counts for the local calendar day of `now`, per-machine totals, and the retained placements. */
|
|
2842
3530
|
function offloadSummary(record, now) {
|
|
@@ -2988,6 +3676,7 @@ function normalizePlacement(value) {
|
|
|
2988
3676
|
if (!PLACEMENT_DECISIONS.includes(decision) || !PLATFORMS.includes(platform)) return [];
|
|
2989
3677
|
if (typeof reason !== "string" || typeof project !== "string") return [];
|
|
2990
3678
|
const buildMs = wholeMs(value.buildMs);
|
|
3679
|
+
const slotWaitMs = wholeMs(value.slotWaitMs);
|
|
2991
3680
|
const localEstimateMs = wholeMs(value.localEstimateMs);
|
|
2992
3681
|
return [{
|
|
2993
3682
|
at: timestamp(value.at),
|
|
@@ -2997,6 +3686,7 @@ function normalizePlacement(value) {
|
|
|
2997
3686
|
reason,
|
|
2998
3687
|
...typeof machine === "string" && machine !== "" ? { machine } : {},
|
|
2999
3688
|
...buildMs > 0 ? { buildMs } : {},
|
|
3689
|
+
...slotWaitMs > 0 ? { slotWaitMs } : {},
|
|
3000
3690
|
...localEstimateMs > 0 ? { localEstimateMs } : {},
|
|
3001
3691
|
...value.failed === true ? { failed: true } : {}
|
|
3002
3692
|
}];
|
|
@@ -3095,7 +3785,7 @@ function count(value) {
|
|
|
3095
3785
|
function timestamp(value) {
|
|
3096
3786
|
return typeof value === "string" && value !== "" ? value : (/* @__PURE__ */ new Date(0)).toISOString();
|
|
3097
3787
|
}
|
|
3098
|
-
function canonical(path) {
|
|
3788
|
+
function canonical$1(path) {
|
|
3099
3789
|
try {
|
|
3100
3790
|
return realpathSync.native(path);
|
|
3101
3791
|
} catch {
|
|
@@ -3132,7 +3822,9 @@ function readStatsReport(key, now) {
|
|
|
3132
3822
|
ios: record?.machine.ios ?? null,
|
|
3133
3823
|
android: record?.machine.android ?? null
|
|
3134
3824
|
},
|
|
3135
|
-
offload: offloadSummary(record, now)
|
|
3825
|
+
offload: offloadSummary(record, now),
|
|
3826
|
+
agentDevice: readAgentDeviceUsage(),
|
|
3827
|
+
swiftpmCache: readSwiftpmCacheUsage()
|
|
3136
3828
|
},
|
|
3137
3829
|
note
|
|
3138
3830
|
};
|
|
@@ -3309,16 +4001,30 @@ function readHostedDevice(home, platform = "ios") {
|
|
|
3309
4001
|
if (!device) throw new Error("The hosted device record is malformed.");
|
|
3310
4002
|
return device;
|
|
3311
4003
|
}
|
|
3312
|
-
|
|
3313
|
-
function assertHostedDeviceLedger(home, id, platform = "ios") {
|
|
4004
|
+
function readHostedDeviceLedger(home, options = {}) {
|
|
3314
4005
|
let value;
|
|
3315
4006
|
try {
|
|
3316
4007
|
value = JSON.parse(readFileSync(join(home, "created-devices.json"), "utf8"));
|
|
3317
4008
|
} catch (error) {
|
|
3318
|
-
if (
|
|
4009
|
+
if (!options.required && error.code === "ENOENT") return null;
|
|
3319
4010
|
throw error;
|
|
3320
4011
|
}
|
|
3321
|
-
if (!isJsonObject(value) || value.version !== 1 || !
|
|
4012
|
+
if (!isJsonObject(value) || value.version !== 1 || ![
|
|
4013
|
+
"ios",
|
|
4014
|
+
"android",
|
|
4015
|
+
"web"
|
|
4016
|
+
].every((key) => Array.isArray(value[key]) && value[key].every((id) => typeof id === "string"))) throw new Error("The hosted device ownership ledger is malformed.");
|
|
4017
|
+
return value;
|
|
4018
|
+
}
|
|
4019
|
+
/** Hosted sessions use isolated homes: only one exact device of their platform may appear in this ledger. */
|
|
4020
|
+
function assertHostedDeviceLedger(home, id, platform = "ios", options = {}) {
|
|
4021
|
+
const value = readHostedDeviceLedger(home, { required: platform !== "macos" });
|
|
4022
|
+
if (!value && platform === "macos") return "empty";
|
|
4023
|
+
if (value && !value.ios.length && !value.android.length && !value.web.length) {
|
|
4024
|
+
if (platform === "macos" || options.allowEmpty) return "empty";
|
|
4025
|
+
}
|
|
4026
|
+
if (!value || value.ios.length !== (platform === "ios" ? 1 : 0) || platform === "ios" && value.ios[0] !== id || value.android.length !== (platform === "android" ? 1 : 0) || platform === "android" && value.android[0] !== id || value.web.length !== 0) throw new Error("The hosted device ownership ledger is missing, malformed or names another device.");
|
|
4027
|
+
return "listed";
|
|
3322
4028
|
}
|
|
3323
4029
|
//#endregion
|
|
3324
4030
|
//#region state/hosted-app.ts
|
|
@@ -3331,6 +4037,41 @@ function hostedAppArea(session, attempt, workerHome) {
|
|
|
3331
4037
|
if (workerHome) return join(workerHome, "..", "apps", attempt);
|
|
3332
4038
|
return join(deviceHostArea(session), "apps", attempt);
|
|
3333
4039
|
}
|
|
4040
|
+
function hostedAppBlobs(session, workerHome) {
|
|
4041
|
+
return join(workerHome ? join(workerHome, "..") : deviceHostArea(session), "blobs");
|
|
4042
|
+
}
|
|
4043
|
+
const verifiedBlobs = /* @__PURE__ */ new Map();
|
|
4044
|
+
/** Transfers cached verification after a rename, only for the same inode, size, mtime and digest. */
|
|
4045
|
+
function rememberHostedAppBlobRename(from, to, file) {
|
|
4046
|
+
const previous = verifiedBlobs.get(from);
|
|
4047
|
+
verifiedBlobs.delete(from);
|
|
4048
|
+
const stat = lstatSync(to, { throwIfNoEntry: false });
|
|
4049
|
+
if (!previous || !stat?.isFile() || stat.size !== file.size) return;
|
|
4050
|
+
const prefix = `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeMs}:`;
|
|
4051
|
+
if (previous.startsWith(prefix) && previous.endsWith(`:${file.sha256}`)) verifiedBlobs.set(to, `${prefix}${stat.ctimeMs}:${file.sha256}`);
|
|
4052
|
+
}
|
|
4053
|
+
/** Checks regular blob bytes, including their size, before content-addressed reuse. */
|
|
4054
|
+
function validHostedAppBlob(path, file) {
|
|
4055
|
+
const stat = lstatSync(path, { throwIfNoEntry: false });
|
|
4056
|
+
if (!stat?.isFile() || stat.size !== file.size) return false;
|
|
4057
|
+
const key = `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeMs}:${stat.ctimeMs}:${file.sha256}`;
|
|
4058
|
+
if (verifiedBlobs.get(path) === key) return true;
|
|
4059
|
+
const hash = createHash("sha256");
|
|
4060
|
+
const fd = openSync(path, "r");
|
|
4061
|
+
try {
|
|
4062
|
+
const buffer = Buffer.alloc(65536);
|
|
4063
|
+
let count;
|
|
4064
|
+
while (count = readSync(fd, buffer, 0, buffer.length, null)) hash.update(buffer.subarray(0, count));
|
|
4065
|
+
} finally {
|
|
4066
|
+
closeSync(fd);
|
|
4067
|
+
}
|
|
4068
|
+
const valid = hash.digest("hex") === file.sha256;
|
|
4069
|
+
if (valid) {
|
|
4070
|
+
if (verifiedBlobs.size >= 20001) verifiedBlobs.delete(verifiedBlobs.keys().next().value);
|
|
4071
|
+
verifiedBlobs.set(path, key);
|
|
4072
|
+
} else verifiedBlobs.delete(path);
|
|
4073
|
+
return valid;
|
|
4074
|
+
}
|
|
3334
4075
|
/** At most 32 plain arguments of 1024 characters (8192 in total), with no NUL, CR or LF. */
|
|
3335
4076
|
function validHostedAppArguments(value) {
|
|
3336
4077
|
return Array.isArray(value) && value.length <= 32 && value.every((argument) => typeof argument === "string" && argument.length <= 1024 && !/[\0\r\n]/.test(argument)) && value.reduce((total, argument) => total + argument.length, 0) <= 8192;
|
|
@@ -3405,7 +4146,8 @@ function readHostedAppMetadata(session, attempt, workerHome) {
|
|
|
3405
4146
|
}
|
|
3406
4147
|
function readHostedApp(session, attempt, workerHome) {
|
|
3407
4148
|
const record = readHostedAppMetadata(session, attempt, workerHome);
|
|
3408
|
-
const manifest = join(
|
|
4149
|
+
const manifest = join(hostedAppBlobs(session, workerHome), record.manifest.sha256);
|
|
4150
|
+
if (existsSync(manifest) && !validHostedAppBlob(manifest, record.manifest)) throw new Error("The hosted app manifest differs from its digest.");
|
|
3409
4151
|
const files = existsSync(manifest) ? parseHostedAppManifest(JSON.parse(readFileSync(manifest, "utf8"))) : [];
|
|
3410
4152
|
if (!files || !files.length && record.state !== "receiving") throw new Error("The hosted app manifest is missing or malformed.");
|
|
3411
4153
|
return {
|
|
@@ -3435,7 +4177,11 @@ function readDeviceHostMachines() {
|
|
|
3435
4177
|
"deviceToken",
|
|
3436
4178
|
"requestedAt"
|
|
3437
4179
|
].every((key) => typeof entry[key] === "string" && entry[key]) || entry.state !== "pending" && entry.state !== "approved" || machines.some((each) => each.machine === entry.machine)) throw new Error("Invalid device-host machine credential store");
|
|
3438
|
-
|
|
4180
|
+
const { ticketHash, ...credential } = entry;
|
|
4181
|
+
machines.push({
|
|
4182
|
+
...credential,
|
|
4183
|
+
...typeof ticketHash === "string" ? { ticketHash } : {}
|
|
4184
|
+
});
|
|
3439
4185
|
}
|
|
3440
4186
|
return machines;
|
|
3441
4187
|
}
|
|
@@ -3524,64 +4270,72 @@ function macosAppState(record) {
|
|
|
3524
4270
|
};
|
|
3525
4271
|
}
|
|
3526
4272
|
//#endregion
|
|
3527
|
-
//#region state/
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
const
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
function findPeer(status, name) {
|
|
3543
|
-
const found = (isJsonObject(status) && isJsonObject(status.Peer) ? Object.values(status.Peer) : []).flatMap((peer) => {
|
|
3544
|
-
if (!isJsonObject(peer) || typeof peer.ID !== "string" || typeof peer.DNSName !== "string") return [];
|
|
3545
|
-
const dnsName = peer.DNSName.replace(/\.$/, "").toLowerCase();
|
|
3546
|
-
const addresses = Array.isArray(peer.TailscaleIPs) ? peer.TailscaleIPs.filter((ip) => typeof ip === "string") : [];
|
|
3547
|
-
const address = addresses.find((ip) => !ip.includes(":")) ?? addresses[0];
|
|
3548
|
-
if (!address || !(dnsName === name || dnsName.startsWith(`${name}.`))) return [];
|
|
3549
|
-
return [{
|
|
3550
|
-
nodeId: peer.ID,
|
|
3551
|
-
dnsName,
|
|
3552
|
-
address
|
|
3553
|
-
}];
|
|
3554
|
-
});
|
|
3555
|
-
if (found.length === 0) return "missing";
|
|
3556
|
-
return found.length === 1 ? found[0] : "ambiguous";
|
|
4273
|
+
//#region state/hosted-ios.ts
|
|
4274
|
+
function parseHostedIosPlacement(value) {
|
|
4275
|
+
if (!isJsonObject(value) || typeof value.machine !== "string" || !parseMachine(value.machine) || typeof value.session !== "string" || !/^[a-f0-9-]{36}$/.test(value.session)) return null;
|
|
4276
|
+
const device = parseHostedDevice(value.device);
|
|
4277
|
+
return {
|
|
4278
|
+
machine: value.machine,
|
|
4279
|
+
session: value.session,
|
|
4280
|
+
appAttempt: typeof value.appAttempt === "string" && /^[a-zA-Z0-9_-]{1,128}$/.test(value.appAttempt) ? value.appAttempt : "",
|
|
4281
|
+
selected: value.machine,
|
|
4282
|
+
device,
|
|
4283
|
+
agent: parseHostedAgentAccess(value.agent) ?? {
|
|
4284
|
+
driver: "none",
|
|
4285
|
+
setting: "hosting.agentDriver"
|
|
4286
|
+
}
|
|
4287
|
+
};
|
|
3557
4288
|
}
|
|
3558
|
-
|
|
3559
|
-
* Connects to the pinned node's own tailnet address rather than resolving its name, so the socket reaches the
|
|
3560
|
-
* node whose ID was just checked. `tailscale serve` still needs the MagicDNS name for TLS (SNI and certificate)
|
|
3561
|
-
* and for its Host routing.
|
|
3562
|
-
*/
|
|
3563
|
-
function endpoint(peer, port) {
|
|
3564
|
-
const address = peer.address.includes(":") ? `[${peer.address}]` : peer.address;
|
|
3565
|
-
const suffix = port === 443 ? "" : `:${port}`;
|
|
4289
|
+
function hostedIosStatus(placement) {
|
|
3566
4290
|
return {
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
4291
|
+
machine: placement.machine,
|
|
4292
|
+
session: placement.session,
|
|
4293
|
+
selected: placement.selected,
|
|
4294
|
+
agent: placement.agent,
|
|
4295
|
+
device: placement.device ? {
|
|
4296
|
+
name: placement.device.deviceType,
|
|
4297
|
+
runtime: placement.device.runtime
|
|
4298
|
+
} : null
|
|
3570
4299
|
};
|
|
3571
4300
|
}
|
|
3572
|
-
/**
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
4301
|
+
/** Includes unreadable placements so callers retain every recorded hosting slot. */
|
|
4302
|
+
function hostedIosRecords(state) {
|
|
4303
|
+
const slots = isJsonObject(state?.deviceSlots) ? state.deviceSlots : {};
|
|
4304
|
+
const records = {
|
|
4305
|
+
default: state?.ios,
|
|
4306
|
+
...Object.fromEntries(Object.entries(slots).map(([slot, value]) => [slot, isJsonObject(value) ? value.ios : void 0]))
|
|
4307
|
+
};
|
|
4308
|
+
return Object.fromEntries(Object.entries(records).flatMap(([slot, record]) => isJsonObject(record) && record.host !== void 0 ? [[slot, record.host]] : []));
|
|
4309
|
+
}
|
|
4310
|
+
function unreadableHostedIos(slot) {
|
|
4311
|
+
const key = slot === "default" ? "ios.host" : `deviceSlots.${slot}.ios.host`;
|
|
4312
|
+
return `The hosted iOS placement at workspace-state key ${key} is unreadable. Inspect ${key}.machine and ${key}.session before reconciling it.`;
|
|
4313
|
+
}
|
|
4314
|
+
function hostedIosPlacements(state) {
|
|
4315
|
+
return Object.assign(Object.create(null), Object.fromEntries(Object.entries(hostedIosRecords(state)).flatMap(([slot, record]) => {
|
|
4316
|
+
const placement = parseHostedIosPlacement(record);
|
|
4317
|
+
return placement ? [[slot, placement]] : [];
|
|
4318
|
+
})));
|
|
4319
|
+
}
|
|
4320
|
+
//#endregion
|
|
4321
|
+
//#region state/hosted-metro.ts
|
|
4322
|
+
const HOSTED_METRO_REQUESTS_KEY = "hostedMetroRequests";
|
|
4323
|
+
const HOSTED_METRO_GATEWAYS_KEY = "hostedMetroGateways";
|
|
4324
|
+
function hostedMetroRequests(state) {
|
|
4325
|
+
const raw = state?.[HOSTED_METRO_REQUESTS_KEY];
|
|
4326
|
+
if (!isJsonObject(raw)) return {};
|
|
4327
|
+
return Object.fromEntries(Object.entries(raw).filter((entry) => {
|
|
4328
|
+
const [session, value] = entry;
|
|
4329
|
+
return /^[a-f0-9-]{36}$/.test(session) && isJsonObject(value) && typeof value.id === "string" && typeof value.machine === "string" && typeof value.address === "string" && isIP(value.address) !== 0 && typeof value.peer === "string" && isIP(value.peer) !== 0 && typeof value.secret === "string" && /^[a-f0-9]{64}$/.test(value.secret);
|
|
4330
|
+
}));
|
|
4331
|
+
}
|
|
4332
|
+
function hostedMetroGateways(state) {
|
|
4333
|
+
const raw = state?.[HOSTED_METRO_GATEWAYS_KEY];
|
|
4334
|
+
if (!isJsonObject(raw)) return {};
|
|
4335
|
+
return Object.fromEntries(Object.entries(raw).filter((entry) => {
|
|
4336
|
+
const value = entry[1];
|
|
4337
|
+
return isJsonObject(value) && typeof value.id === "string" && typeof value.processToken === "string" && (value.port === void 0 || typeof value.port === "number" && Number.isInteger(value.port) && value.port > 0 && value.port <= 65535) && (value.error === void 0 || typeof value.error === "string");
|
|
4338
|
+
}));
|
|
3585
4339
|
}
|
|
3586
4340
|
//#endregion
|
|
3587
4341
|
//#region state/hosted-logs.ts
|
|
@@ -3606,6 +4360,21 @@ function hostedMacosLogsDir(home) {
|
|
|
3606
4360
|
return null;
|
|
3607
4361
|
}
|
|
3608
4362
|
}
|
|
4363
|
+
const hostedIosLogsDir = (home) => join(home, "ios-logs");
|
|
4364
|
+
/** The completed query window and overlap digests, or null for a missing, malformed or unreadable checkpoint. */
|
|
4365
|
+
function readHostedIosLogsCheckpoint(home) {
|
|
4366
|
+
try {
|
|
4367
|
+
const value = JSON.parse(readFileSync(join(hostedIosLogsDir(home), "checkpoint.json"), "utf8"));
|
|
4368
|
+
if (!isJsonObject(value) || !Number.isSafeInteger(value.until) || value.until < 0 || value.windowMs !== void 0 && (!Number.isSafeInteger(value.windowMs) || value.windowMs < 1e3) || !Array.isArray(value.boundary) || !value.boundary.every((each) => typeof each === "string" && /^[a-f0-9]{64}$/.test(each))) return null;
|
|
4369
|
+
return {
|
|
4370
|
+
until: value.until,
|
|
4371
|
+
boundary: value.boundary,
|
|
4372
|
+
...typeof value.windowMs === "number" ? { windowMs: value.windowMs } : {}
|
|
4373
|
+
};
|
|
4374
|
+
} catch {
|
|
4375
|
+
return null;
|
|
4376
|
+
}
|
|
4377
|
+
}
|
|
3609
4378
|
function readSpan(path, from, to) {
|
|
3610
4379
|
const fd = openSync(path, "r");
|
|
3611
4380
|
try {
|
|
@@ -3675,4 +4444,209 @@ function readLogsSince(dir, cursor) {
|
|
|
3675
4444
|
};
|
|
3676
4445
|
}
|
|
3677
4446
|
//#endregion
|
|
3678
|
-
|
|
4447
|
+
//#region state/maintenance.ts
|
|
4448
|
+
const finite = (value) => typeof value === "number" && Number.isFinite(value);
|
|
4449
|
+
const nullableNumber = (value) => value === null || finite(value);
|
|
4450
|
+
const strings = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
4451
|
+
const KINDS = [
|
|
4452
|
+
"would-clear-outputs",
|
|
4453
|
+
"would-trim-cache",
|
|
4454
|
+
"would-empty-cache",
|
|
4455
|
+
"would-shutdown-device",
|
|
4456
|
+
"would-stop-workspace"
|
|
4457
|
+
];
|
|
4458
|
+
/** Rejects missing, corrupt or unsupported payloads without throwing. */
|
|
4459
|
+
function parseMaintenanceState(value) {
|
|
4460
|
+
if (!isJsonObject(value) || value.version !== 1 || !isJsonObject(value.lastAt)) return null;
|
|
4461
|
+
if (!Object.values(value.lastAt).every(finite)) return null;
|
|
4462
|
+
if (value.skipKeys !== void 0 && !strings(value.skipKeys)) return null;
|
|
4463
|
+
if (value.deferredAt !== void 0 && (!isJsonObject(value.deferredAt) || !Object.values(value.deferredAt).every(finite))) return null;
|
|
4464
|
+
if (!Array.isArray(value.sizes) || !value.sizes.every((size) => isJsonObject(size) && typeof size.name === "string" && typeof size.dir === "string" && finite(size.bytes) && finite(size.measuredAt) && [
|
|
4465
|
+
"workspace-outputs",
|
|
4466
|
+
"build-cache",
|
|
4467
|
+
"metro-cache",
|
|
4468
|
+
"compilation-cache",
|
|
4469
|
+
"ccache",
|
|
4470
|
+
"other"
|
|
4471
|
+
].includes(String(size.category)) && (size.workspace === void 0 || typeof size.workspace === "string") && (size.blocked === void 0 || typeof size.blocked === "string") && (size.idleDays === void 0 || nullableNumber(size.idleDays)))) return null;
|
|
4472
|
+
if (!Array.isArray(value.plan) || !value.plan.every((action) => isJsonObject(action) && KINDS.includes(String(action.kind)) && typeof action.target === "string" && finite(action.bytes) && typeof action.reason === "string" && (action.workspace === void 0 || typeof action.workspace === "string"))) return null;
|
|
4473
|
+
if (!Array.isArray(value.recent) || !value.recent.every((record) => isJsonObject(record) && record.src === "maintenance" && finite(record.ts) && typeof record.msg === "string" && [
|
|
4474
|
+
"debug",
|
|
4475
|
+
"info",
|
|
4476
|
+
"warn",
|
|
4477
|
+
"error"
|
|
4478
|
+
].includes(String(record.level)) && [
|
|
4479
|
+
"maintenance_pass",
|
|
4480
|
+
"maintenance_action",
|
|
4481
|
+
"maintenance_skip",
|
|
4482
|
+
"maintenance_failure",
|
|
4483
|
+
"maintenance_check"
|
|
4484
|
+
].includes(String(record.event)) && typeof record.pass === "string" && typeof record.trigger === "string" && record.mode === "report")) return null;
|
|
4485
|
+
if (value.lastPass !== null && (!isJsonObject(value.lastPass) || !finite(value.lastPass.startedAt) || !finite(value.lastPass.durationMs) || typeof value.lastPass.trigger !== "string" || value.lastPass.mode !== "report" || value.lastPass.freedBytes !== 0 || value.lastPass.stopped !== 0 || !finite(value.lastPass.actions) || !strings(value.lastPass.blocked))) return null;
|
|
4486
|
+
if (value.pressure !== null) {
|
|
4487
|
+
const pressure = value.pressure;
|
|
4488
|
+
if (!isJsonObject(pressure) || !Array.isArray(pressure.disk) || !pressure.disk.every((disk) => isJsonObject(disk) && typeof disk.volume === "string" && finite(disk.freeMb)) || !nullableNumber(pressure.warningSince) || !isJsonObject(pressure.memory) || ![
|
|
4489
|
+
null,
|
|
4490
|
+
"normal",
|
|
4491
|
+
"warning",
|
|
4492
|
+
"critical"
|
|
4493
|
+
].includes(pressure.memory.level) || !nullableNumber(pressure.memory.availableBytes) || typeof pressure.memory.pressured !== "boolean") return null;
|
|
4494
|
+
}
|
|
4495
|
+
return value;
|
|
4496
|
+
}
|
|
4497
|
+
function readMaintenanceState() {
|
|
4498
|
+
return parseMaintenanceState(readJsonObject(maintenanceStateFile()));
|
|
4499
|
+
}
|
|
4500
|
+
function readMaintenanceAttempt() {
|
|
4501
|
+
const value = readJsonObject(maintenanceAttemptFile())?.attemptedAt;
|
|
4502
|
+
return finite(value) ? value : void 0;
|
|
4503
|
+
}
|
|
4504
|
+
//#endregion
|
|
4505
|
+
//#region state/agent-device.ts
|
|
4506
|
+
function agentDeviceStateDirs(home = homedir()) {
|
|
4507
|
+
const override = process.env.AGENT_DEVICE_STATE_DIR?.trim();
|
|
4508
|
+
const dirs = /* @__PURE__ */ new Set([override ? resolve(override) : join(home, ".agent-device")]);
|
|
4509
|
+
const workspaces = join(configDir(), "workspaces");
|
|
4510
|
+
try {
|
|
4511
|
+
for (const name of readdirSync(workspaces)) {
|
|
4512
|
+
const dir = join(workspaces, name, "agent-device");
|
|
4513
|
+
try {
|
|
4514
|
+
if (statSync(dir).isDirectory()) dirs.add(dir);
|
|
4515
|
+
} catch {}
|
|
4516
|
+
}
|
|
4517
|
+
} catch {}
|
|
4518
|
+
return [...dirs];
|
|
4519
|
+
}
|
|
4520
|
+
//#endregion
|
|
4521
|
+
//#region state/archive.ts
|
|
4522
|
+
function readArchives() {
|
|
4523
|
+
let names;
|
|
4524
|
+
try {
|
|
4525
|
+
names = readdirSync(archiveRoot());
|
|
4526
|
+
} catch {
|
|
4527
|
+
return [];
|
|
4528
|
+
}
|
|
4529
|
+
return names.flatMap((name) => {
|
|
4530
|
+
const archive = readArchive(name);
|
|
4531
|
+
return archive ? [archive] : [];
|
|
4532
|
+
}).toSorted((a, b) => Date.parse(b.removedAt) - Date.parse(a.removedAt) || b.id.localeCompare(a.id));
|
|
4533
|
+
}
|
|
4534
|
+
function readArchive(id) {
|
|
4535
|
+
if (!id || id.startsWith(".") || basename(id) !== id || id.includes("\0")) return null;
|
|
4536
|
+
try {
|
|
4537
|
+
if (!lstatSync(join(archiveRoot(), id)).isDirectory()) return null;
|
|
4538
|
+
} catch {
|
|
4539
|
+
return null;
|
|
4540
|
+
}
|
|
4541
|
+
const record = readJsonObject(join(archiveRoot(), id, "archive.json"));
|
|
4542
|
+
if (!record || record.id !== id || [
|
|
4543
|
+
"projectRoot",
|
|
4544
|
+
"project",
|
|
4545
|
+
"workspace",
|
|
4546
|
+
"removedAt",
|
|
4547
|
+
"removedBy"
|
|
4548
|
+
].some((key) => typeof record[key] !== "string") || !Number.isFinite(Date.parse(record.removedAt)) || !isJsonObject(record.bytes) || !isJsonObject(record.expires) || !isJsonObject(record.worktree) || !isJsonObject(record.builds) || !Array.isArray(record.agents) || typeof record.version !== "number") return null;
|
|
4549
|
+
const bytes = record.bytes;
|
|
4550
|
+
if ([
|
|
4551
|
+
"logs",
|
|
4552
|
+
"recordings",
|
|
4553
|
+
"agentActions",
|
|
4554
|
+
"record",
|
|
4555
|
+
"total"
|
|
4556
|
+
].some((key) => typeof bytes[key] !== "number" || !Number.isFinite(bytes[key]) || bytes[key] < 0)) return null;
|
|
4557
|
+
return record;
|
|
4558
|
+
}
|
|
4559
|
+
function archivedUsage(archives) {
|
|
4560
|
+
const usage = {
|
|
4561
|
+
count: archives.length,
|
|
4562
|
+
bytes: 0,
|
|
4563
|
+
byKind: {
|
|
4564
|
+
logs: 0,
|
|
4565
|
+
recordings: 0,
|
|
4566
|
+
agentActions: 0,
|
|
4567
|
+
record: 0
|
|
4568
|
+
}
|
|
4569
|
+
};
|
|
4570
|
+
for (const archive of archives) {
|
|
4571
|
+
usage.bytes += archive.bytes.total;
|
|
4572
|
+
for (const kind of [
|
|
4573
|
+
"logs",
|
|
4574
|
+
"recordings",
|
|
4575
|
+
"agentActions",
|
|
4576
|
+
"record"
|
|
4577
|
+
]) usage.byKind[kind] += archive.bytes[kind];
|
|
4578
|
+
}
|
|
4579
|
+
return usage;
|
|
4580
|
+
}
|
|
4581
|
+
function canonical(path) {
|
|
4582
|
+
try {
|
|
4583
|
+
return realpathSync(path);
|
|
4584
|
+
} catch {
|
|
4585
|
+
return path;
|
|
4586
|
+
}
|
|
4587
|
+
}
|
|
4588
|
+
function linkReplacedArchives(archives, paths) {
|
|
4589
|
+
const live = new Map(paths.map((path) => [canonical(path), path]));
|
|
4590
|
+
return archives.map((archive) => {
|
|
4591
|
+
const replacedBy = live.get(canonical(archive.projectRoot));
|
|
4592
|
+
return replacedBy ? {
|
|
4593
|
+
...archive,
|
|
4594
|
+
replacedBy
|
|
4595
|
+
} : archive;
|
|
4596
|
+
});
|
|
4597
|
+
}
|
|
4598
|
+
function archiveEnabledValue(layer) {
|
|
4599
|
+
if (!layer || typeof layer !== "object") return void 0;
|
|
4600
|
+
return layer.archive?.enabled;
|
|
4601
|
+
}
|
|
4602
|
+
function archiveEnabled(env, layers) {
|
|
4603
|
+
const definition = settingDefinition("archive.enabled");
|
|
4604
|
+
const override = env[definition.env];
|
|
4605
|
+
if ((!override || override === "") && env.STIM_HOME) return definition.scopedHomeValue;
|
|
4606
|
+
const value = override !== void 0 && override !== "" ? coerceSettingText(definition, override) : layers.map(archiveEnabledValue).find((candidate) => candidate !== void 0) ?? definition.default;
|
|
4607
|
+
const error = settingValueError(definition, value);
|
|
4608
|
+
if (error) throw new Error(`Invalid ${override !== void 0 && override !== "" ? definition.env : definition.key}: ${error}`);
|
|
4609
|
+
return value;
|
|
4610
|
+
}
|
|
4611
|
+
//#endregion
|
|
4612
|
+
//#region state/setup-journal.ts
|
|
4613
|
+
const capability = (value) => value === "build" || value === "device-host";
|
|
4614
|
+
const text = (value) => typeof value === "string" && value.length > 0;
|
|
4615
|
+
const knownKeys = (value, keys) => Object.keys(value).every((key) => keys.includes(key));
|
|
4616
|
+
function parseSetupJournal(value) {
|
|
4617
|
+
if (!isJsonObject(value) || !knownKeys(value, [
|
|
4618
|
+
"v",
|
|
4619
|
+
"client",
|
|
4620
|
+
"expiresAt",
|
|
4621
|
+
"capabilities",
|
|
4622
|
+
"steps",
|
|
4623
|
+
"granted",
|
|
4624
|
+
"done",
|
|
4625
|
+
"exit"
|
|
4626
|
+
]) || value.v !== 1 || !isJsonObject(value.client) || !knownKeys(value.client, ["nodeId"]) || !text(value.client.nodeId) || typeof value.expiresAt !== "string" || !Number.isFinite(Date.parse(value.expiresAt)) || !Array.isArray(value.capabilities) || !value.capabilities.every(capability) || !Array.isArray(value.steps) || !value.steps.every((step) => isJsonObject(step) && knownKeys(step, [
|
|
4627
|
+
"id",
|
|
4628
|
+
"state",
|
|
4629
|
+
"title",
|
|
4630
|
+
"detail",
|
|
4631
|
+
"fix"
|
|
4632
|
+
]) && text(step.id) && typeof step.state === "string" && [
|
|
4633
|
+
"pending",
|
|
4634
|
+
"running",
|
|
4635
|
+
"ok",
|
|
4636
|
+
"skipped",
|
|
4637
|
+
"failed"
|
|
4638
|
+
].includes(step.state) && text(step.title) && (step.detail === void 0 || typeof step.detail === "string") && (step.fix === void 0 || typeof step.fix === "string")) || !Array.isArray(value.granted) || !value.granted.every((grant) => isJsonObject(grant) && knownKeys(grant, ["capability", "id"]) && capability(grant.capability) && text(grant.id)) || typeof value.done !== "boolean" || value.exit !== void 0 && (!Number.isInteger(value.exit) || value.exit < 0)) return null;
|
|
4639
|
+
return value;
|
|
4640
|
+
}
|
|
4641
|
+
function readSetupJournal(hash) {
|
|
4642
|
+
const file = setupJournalFile(hash);
|
|
4643
|
+
return file === null ? null : parseSetupJournal(readJsonObject(file));
|
|
4644
|
+
}
|
|
4645
|
+
function isJournalExpired(journal, now = Date.now()) {
|
|
4646
|
+
return Date.parse(journal.expiresAt) < now;
|
|
4647
|
+
}
|
|
4648
|
+
function isSetupJournalPrunable(journal, now) {
|
|
4649
|
+
return Date.parse(journal.expiresAt) + 36e5 < now;
|
|
4650
|
+
}
|
|
4651
|
+
//#endregion
|
|
4652
|
+
export { ACTIVITY_RECENCY_BASES, AGENT_TOOLS, ANDROID_COMPILER_CACHE_CHOICES, ANDROID_EMULATOR_APPS, ANDROID_PCH_CHOICES, APP_PROCESS_STATES, BUILD_HISTORY_KEY, BUILD_HISTORY_LIMIT, BUILD_PHASES, BUILD_RESULTS, DEVICE_IDLE_SHUTDOWN_KEY, DEV_SERVER_STOP_REQUEST_KEY, ENDED_AGENT_RETENTION_MS, ERROR_SOURCES, HISTORY_LIMIT, HOSTED_AGENT_DRIVERS, HOSTED_APP_CHUNK_BYTES, HOSTED_LOGS_FIRST_BYTES, HOSTED_LOGS_PAGE_BYTES, HOSTED_MACOS_APP_SLOTS, HOSTED_METRO_GATEWAYS_KEY, HOSTED_METRO_REQUESTS_KEY, IDLE_STOP_KEY, IOS_SIMULATOR_APPS, LAST_BUILD_KEYS, LEVELS, MACHINE_OWNER_KINDS, MEMORY_SOURCES, NATIVE_BUILD_STEPS, NATIVE_CRASH_LOG_PREFIX, OFFLOAD_MODES, PHYSICAL_DEVICE_CONNECTIONS, PLACEMENT_DECISIONS, READY_PHASE_MS, RECORDING_CAP_BYTES, RECORDING_FOOTAGE_MS, RECORDING_PLATFORMS, REMOTE_DEVICE_BACKENDS, SETTINGS, SETTINGS_SCHEMA_FILE, SETTINGS_SCHEMA_URL, SETTING_GROUPS, SETTING_SCOPES, SOURCES, STATS_VERSION, STATUS_ISSUE_CODES, TAILNET_MACHINE_PATTERN, TUNNEL_MODES, WARM_KEY, WEB_PAGE_STATES, WEB_VIEWPORTS, WORKSPACE_AGENT_KEY, WORKSPACE_PHASES, WORKSPACE_STAGE_KINDS, acceptsShape, advanceTail, agentDeviceStateDirs, agentDeviceUsageFile, agentSessionOf, agentSessionsCacheFile, allMetroPorts, archiveDir, archiveEnabled, archiveLock, archiveRoot, archivedUsage, assertHostedDeviceLedger, attachExpoErrorContext, buildCriteria, buildDiagnostics, buildMachinesFile, buildMachinesLock, buildSlotPath, buildSlotsDir, buildWorkerRoot, bundleMarkerPlatform, capDrops, closedSegmentName, coerceSettingText, compileGrep, configCorruptRepair, configLockPath, countErrorEntries, createAgentActionReader, createdDevicesFile, createdDevicesLock, decodeStats, deviceHostArea, deviceHostMachinesClaims, deviceHostMachinesFile, deviceHostMachinesLock, deviceHostRoot, deviceViewersDir, diskUsageCacheDir, diskUsageCacheFile, easMachineStateRoot, easSessionLedgerFile, easSessionLedgerLock, emulatorLogFile, endedAgentSessionOf, endpoint, expectedShape, fileSizes, findEnclosingWorktreeRoot, findPeer, findProjectByMetroPort, findProjectRoot, followLogs, footageDrops, getConcurrencyLimits, getConfigPath, getProject, getProjectSetting, getProjectSettings, getRepoSettings, gitMergeCacheDir, hostedAppArea, hostedAppAttempt, hostedAppBlobs, hostedConsolePort, hostedDeviceId, hostedIosLogsDir, hostedIosPlacements, hostedIosRecords, hostedIosStatus, hostedMacosAppSlot, hostedMacosBundleId, hostedMacosLogsDir, hostedMetroGateways, hostedMetroRequests, isJournalExpired, isJsonObject, isLayeredSetting, isPathPrefix, isSensitiveReference, isSetupJournalPrunable, lastUseFrom, levelRank, linkReplacedArchives, listRecordedDevices, listSegments, listStatusCacheEntries, loadConfig, logFiles, machineCapacity, macosAppState, maintenanceAttemptFile, maintenanceChildLogFile, maintenanceDir, maintenanceNdjsonFile, maintenanceRunClaims, maintenanceStateFile, markerWindow, offloadSummary, openSegmentName, parseHostedAgentAccess, parseHostedAgentGrant, parseHostedAndroidDevice, parseHostedAppManifest, parseHostedAppOffer, parseHostedChoice, parseHostedDevice, parseHostedIosPlacement, parseHostedLogsCursor, parseHostedMacosChoice, parseHostedMacosDevice, parseHostedMacosPlacement, parseHostedNativeOffer, parseHostedOfferRequest, parseHostedPlatformDevice, parseHostedRequest, parseMachine, parseMacosRecord, parseMaintenanceState, parseMissReason, parseNdjsonLine, parseNdjsonText, parseSetupJournal, parseSince, pinnedEndpoint, pullRequestCacheDir, pullRequestCacheFile, queryJsonLogs, queryLogs, readAgentDeviceUsage, readAgentDeviceUsageCache, readAgentSessionsCache, readArchive, readArchives, readBuildDetail, readBuildHistory, readBuildMachines, readCreatedDevices, readDevServerStop, readDevServerStopRequest, readDeviceHostMachines, readDeviceIdleShutdowns, readDiskUsage, readEasSessionLedger, readEndedAgentSessions, readHostedApp, readHostedAppMetadata, readHostedDevice, readHostedDeviceLedger, readHostedIosLogsCheckpoint, readHostedMacosApp, readHostedSessions, readIdleStop, readJsonFile, readJsonObject, readLastBuilds, readLogRecords, readLogTimeline, readLogsSince, readMacosRecord, readMaintenanceAttempt, readMaintenanceState, readNdjsonGenerations, readPullRequestCache, readRunEstimates, readSetupJournal, readStateFile, readStats, readStatsReport, readSwiftpmCacheUsage, readViewedDevices, readWarmRecord, readWorkspaceAgent, readWorkspaceState, recordMatches, recordedBytes, recordingDeviceName, recordingEnabled, rememberHostedAppBlobRename, saturation, serverSetupDir, settingDefinition, settingValueError, settingsJsonSchema, setupJournalFile, sharedCcache, sharedCompilationCache, sharedGradle, sharedPods, sortByTs, statsFile, statsProjectKey, stimBuildDigest, supervisorLogFile, supervisorPidFile, swiftpmCacheUsageFile, tailRead, trimSamples, tryAcquireBuildSlotClaim, unreadableHostedIos, validEasSessionClaim, validHostedAppArguments, validHostedAppBlob, wholePhases, withConfigLock, withDerivedFacts, withGitChip, withStateReadCache, workspaceAgentDeviceDir, workspaceBuildDetailFile, workspaceDerivedData, workspaceEndedAgentsFile, workspaceEndedAgentsLock, workspaceGradleBuild, workspaceLastUsed, workspaceLogErrorIndex, workspaceLogsDir, workspaceMetadataFile, workspaceRecordingsDir, workspaceStateFile, workspaceStateLock };
|