@stim-cli/server 1.25.0 → 1.26.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 +37 -16
- package/dist/{protocol-VEZO2CyR.mjs → protocol-nY-u8rtw.mjs} +7 -2
- package/dist/protocol.mjs +1 -1
- package/dist/protocol.schema.json +16 -2
- package/dist/{stim-server-DowAx-6v.mjs → stim-server-DU1htjam.mjs} +129 -57
- package/dist/stim-server.mjs +1 -1
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -93,7 +93,7 @@ it appends the default Node of nvm, fnm, Volta, mise or asdf, Homebrew,
|
|
|
93
93
|
`JAVA_HOME` to a Homebrew `openjdk` (17, then 21, then the newest), else Android
|
|
94
94
|
Studio's bundled runtime, when no JDK is installed under
|
|
95
95
|
`/Library/Java/JavaVirtualMachines`. It never replaces a variable it was given,
|
|
96
|
-
and its own Node's directory is always the last `PATH` entry. CocoaPods uses the
|
|
96
|
+
and its own Node's directory is always the last `PATH` entry. The build host repeats this discovery for each build offer, toolchain read and build start, so a tool installed after the server started, such as Homebrew's directory, a JDK or the Android SDK, is found without a restart, within the minute the toolchain report is cached. A `PATH` line added to a shell profile is never read. CocoaPods uses the
|
|
97
97
|
project's `.ruby-version` Ruby, else rvm's or rbenv's default Ruby, and reads the
|
|
98
98
|
login shell only when neither exists. Anything else a shell profile sets, such as
|
|
99
99
|
`STIM_*` variables, reaches the server only through its environment or `--env`.
|
|
@@ -275,7 +275,9 @@ reports without starting a CLI process. `desktop.reports.get` takes
|
|
|
275
275
|
Doctor accepts a selected folder before it is registered with Stim. The result is
|
|
276
276
|
`{ "pending": false, "value": { ... }, "measuredAt": "..." }`; a failed read
|
|
277
277
|
returns `stim-failed`. Doctor never repairs the project or records a CLI run, and
|
|
278
|
-
GC never deletes anything.
|
|
278
|
+
GC never deletes anything. The GC report, here and in `machine.details`, takes
|
|
279
|
+
worktree build-state sizes only from the disk-usage cache and runs no `du` for
|
|
280
|
+
them; a stale size is `null`.
|
|
279
281
|
|
|
280
282
|
The local-only `desktop-report-platform` feature adds an optional `"platform": "ios"`
|
|
281
283
|
or `"platform": "android"` to doctor targets. Omitting it checks all selected platforms.
|
|
@@ -291,12 +293,17 @@ Active subscriptions refresh after 60 seconds; no refresh timer runs without
|
|
|
291
293
|
subscribers. `unsubscribe` and disconnect remove the subscription. Cancelling a
|
|
292
294
|
get request does not cancel another reader's shared work.
|
|
293
295
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
296
|
+
`desktop.reports.invalidate` without parameters invalidates all report targets
|
|
297
|
+
and refreshes active subscriptions. With the local-only
|
|
298
|
+
`desktop-report-invalidate-target` feature it also takes a report target, as
|
|
299
|
+
`desktop.reports.get` does, and invalidates only that target, so a fresh read
|
|
300
|
+
of one report does not restart the others. Results started before
|
|
301
|
+
invalidation cannot replace a newer result or satisfy a waiting get; invalidation
|
|
302
|
+
aborts such a probe, so a waiting get waits only for the fresh one. A waiting get
|
|
303
|
+
does not use one of the connection's request slots: a connection can wait for 16
|
|
304
|
+
reports at a time, and more fail with `limit-exceeded`. The subscription cap,
|
|
305
|
+
report byte quota, API execution and cleanup budgets, and 150-second server limit
|
|
306
|
+
apply. At most four shared report probes run
|
|
300
307
|
at once across connections and targets. A new target above that limit returns a
|
|
301
308
|
busy error without starting or queuing work; invalidate and retry after a probe
|
|
302
309
|
finishes. Unsubscribing does not release a probe's slot before it drains.
|
|
@@ -359,7 +366,10 @@ person approves does setup store the ticket's derived PSK and lookup in
|
|
|
359
366
|
gets `unknown-secret`, and its `stim doctor` reports the machine as pending and
|
|
360
367
|
tries again on every run. The requester's next first-contact handshake with the
|
|
361
368
|
ticket records its public key once per approved capability, which spends the
|
|
362
|
-
ticket.
|
|
369
|
+
ticket. While it waits, setup says so ("Waiting for the requesting machine to
|
|
370
|
+
connect. Stim Desktop sends this automatically.", or the `stim doctor`
|
|
371
|
+
instruction for a Doctor-made command) and confirms each approval when it
|
|
372
|
+
arrives. From then on the ticket admits only that key, until it expires, so a
|
|
363
373
|
requester that lost the reply can retry; setup deletes an unspent secret when it
|
|
364
374
|
ends. Build and
|
|
365
375
|
device-host approvals are separate records. A build grant carries no `read` or
|
|
@@ -1360,7 +1370,10 @@ event loop. Until that read returns, `/health` answers HTTP 503 with
|
|
|
1360
1370
|
requests get 503, and no status follower, native helper, file watcher or
|
|
1361
1371
|
recorder starts. A read that does not return within 10 seconds, plus up to one
|
|
1362
1372
|
second to stop the child, leaves the server listening with
|
|
1363
|
-
`startup: { "state": "degraded", "reason": "..." }`
|
|
1373
|
+
`startup: { "state": "degraded", "reason": "...", "cause": "home-unreadable" }`
|
|
1374
|
+
(`start-failed` when starting failed after the read returned) and HTTP 503 on
|
|
1375
|
+
`/health` (a tailnet peer sees only `{ "version", "startup": { "state",
|
|
1376
|
+
"cause" } }`, because the reason names the Stim home path),
|
|
1364
1377
|
logs the reason once, and reads again every 30 seconds. When a read returns,
|
|
1365
1378
|
the server starts its watchers and recorder, `/health` answers 200 with
|
|
1366
1379
|
`startup: { "state": "ready" }`, and no restart is needed. `service status`
|
|
@@ -1621,9 +1634,14 @@ connected since stim-server started keeps its directory when revoked.
|
|
|
1621
1634
|
`arch`; `xcode`; `simulatorSdk`; `macosSdk` (from
|
|
1622
1635
|
`xcrun --sdk macosx --show-sdk-version`); `cocoapods`; `runtimes`, the simulator
|
|
1623
1636
|
runtimes with an iPhone simulator to build for; `jdk`, the major version of
|
|
1624
|
-
the JDK in `JAVA_HOME` or the macOS default;
|
|
1637
|
+
the JDK in `JAVA_HOME` or the macOS default; `jdks`, the majors of every JDK it finds
|
|
1638
|
+
(`JAVA_HOME`, the system JVM directories, Homebrew, Android Studio's JBR, SDKMAN), the default included; `developerDir` (what `xcode-select -p` prints) and `xcodeApps` (the Xcode apps in
|
|
1639
|
+
`/Applications` with `version`, `build` and, while `xcode` is null, the `license` and `firstLaunch` checks of each), on a Mac; and `androidSdk`, the `ndk`,
|
|
1625
1640
|
`buildTools` and `platforms` directories of the SDK in `ANDROID_HOME`, else `~/Library/Android/sdk` on macOS or
|
|
1626
|
-
`~/Android/Sdk` on Linux, null without one), `
|
|
1641
|
+
`~/Android/Sdk` on Linux, null without one), `androidHome` (that directory) and
|
|
1642
|
+
`androidLicenses` (whether `licenses/android-sdk-license` in it has content, so
|
|
1643
|
+
Gradle can download a missing package; both absent from a server older than
|
|
1644
|
+
them, and from a worker without an SDK), `capacity` and `warm`
|
|
1627
1645
|
(`checkout`, `dependencies`, and the legacy `build` field, now always false)
|
|
1628
1646
|
for that repository. For React Native and Expo builds, clients supporting `build-dependency-cache` can send
|
|
1629
1647
|
`dependencies: { project, packageName }` alongside `lockfile`; the dependency
|
|
@@ -1715,7 +1733,9 @@ connected since stim-server started keeps its directory when revoked.
|
|
|
1715
1733
|
simulator runtime identifier, required for `ios`, null for `android`),
|
|
1716
1734
|
`fingerprint`, `packageName`, `isExpo`, `optimizations`, `android` (for
|
|
1717
1735
|
`android`: `variant`, `abi`, `gradleBuildCache`, `pch` and `compilerCache`,
|
|
1718
|
-
`ccache` or `none`), `stimBuild` and, from a caller that saw `build-compat`, `compat`,
|
|
1736
|
+
`ccache` or `none`), `stimBuild` and, from a caller that saw `build-compat`, `compat`, and, for Android from a caller
|
|
1737
|
+
that saw `build-jdk`, `jdk` (the JDK major its Gradle runs on; the build runs with `JAVA_HOME` at an installed JDK of
|
|
1738
|
+
that major, and fails if there is none), after the whole manifest of `repo` was
|
|
1719
1739
|
synced on this connection. A peer-cache-capable React Native caller can set
|
|
1720
1740
|
`cacheArtifact: true` to retain a completed development archive when its cache
|
|
1721
1741
|
write and remote policies allow it. With the `fingerprint-sources` feature, a
|
|
@@ -1945,7 +1965,7 @@ Events are `{ "event", "subscription", ... }`.
|
|
|
1945
1965
|
retained logs and agent-device session actions locally through core, with the same JSON filters, ordering,
|
|
1946
1966
|
markers and error context. Archive reads are synchronous and queued per
|
|
1947
1967
|
connection, yielding to the event loop between requests. Pending archive
|
|
1948
|
-
reads share the
|
|
1968
|
+
reads share the connection's request limit with commands. Archived session actions
|
|
1949
1969
|
retain available platform and device attribution; device ids and Stim slot
|
|
1950
1970
|
assignments absent from retained files are unavailable after removal. They never follow files or use a hosted relay.
|
|
1951
1971
|
An archive subscription sends its records in the same batches and limits,
|
|
@@ -2355,9 +2375,10 @@ Events are `{ "event", "subscription", ... }`.
|
|
|
2355
2375
|
|
|
2356
2376
|
`workspace` is an environment `path` from a status payload. Any other path is
|
|
2357
2377
|
refused with `unknown-workspace` and runs nothing. A connection holds at most
|
|
2358
|
-
32 subscriptions and runs at most
|
|
2378
|
+
32 subscriptions and runs at most 32 `logs.query`, `stats.get`,
|
|
2359
2379
|
`settings.get`, `build.plan`, `workspace.files` and `workspace.diff` requests at
|
|
2360
|
-
a time
|
|
2380
|
+
a time, actions included. A tailnet device runs at most 16 across all its
|
|
2381
|
+
connections; more fail with `limit-exceeded`. `logs.query`, `stats.get` and `settings.get` fail after 60 seconds,
|
|
2361
2382
|
`build.plan` after 150; these four fail at 32 MiB of output. Closing the
|
|
2362
2383
|
connection stops running requests. When the command refuses with Stim's error
|
|
2363
2384
|
contract on stdout, the `stim-failed` message is its code, message and remedy;
|
|
@@ -2427,10 +2427,10 @@ function protocolJsonSchema() {
|
|
|
2427
2427
|
}),
|
|
2428
2428
|
request("desktop.reports.get", { $ref: "#/$defs/DesktopReportTarget" }),
|
|
2429
2429
|
request("desktop.reports.subscribe", { $ref: "#/$defs/DesktopReportTarget" }),
|
|
2430
|
-
optionalParams("desktop.reports.invalidate", {
|
|
2430
|
+
optionalParams("desktop.reports.invalidate", { oneOf: [{
|
|
2431
2431
|
type: "object",
|
|
2432
2432
|
maxProperties: 0
|
|
2433
|
-
}),
|
|
2433
|
+
}, { $ref: "#/$defs/DesktopReportTarget" }] }),
|
|
2434
2434
|
optionalParams("desktop.settings.schema", {
|
|
2435
2435
|
type: "object",
|
|
2436
2436
|
maxProperties: 0
|
|
@@ -3316,6 +3316,11 @@ function protocolJsonSchema() {
|
|
|
3316
3316
|
compat: {
|
|
3317
3317
|
...buildCompat,
|
|
3318
3318
|
description: "Compared instead of stimBuild by a machine whose build.offer toolchain reports compat."
|
|
3319
|
+
},
|
|
3320
|
+
jdk: {
|
|
3321
|
+
type: "string",
|
|
3322
|
+
pattern: "^[0-9]{1,3}$",
|
|
3323
|
+
description: "The JDK major an Android build runs on, sent to a stim-server with the build-jdk feature."
|
|
3319
3324
|
}
|
|
3320
3325
|
}
|
|
3321
3326
|
}),
|
package/dist/protocol.mjs
CHANGED
|
@@ -1281,9 +1281,11 @@
|
|
|
1281
1281
|
"hosted-metro-ports",
|
|
1282
1282
|
"server-update",
|
|
1283
1283
|
"build-compat",
|
|
1284
|
+
"build-jdk",
|
|
1284
1285
|
"status-views",
|
|
1285
1286
|
"desktop-reports",
|
|
1286
1287
|
"desktop-report-platform",
|
|
1288
|
+
"desktop-report-invalidate-target",
|
|
1287
1289
|
"desktop-settings",
|
|
1288
1290
|
"desktop-settings-schema",
|
|
1289
1291
|
"desktop-settings-write",
|
|
@@ -4030,8 +4032,15 @@
|
|
|
4030
4032
|
"const": "desktop.reports.invalidate"
|
|
4031
4033
|
},
|
|
4032
4034
|
"params": {
|
|
4033
|
-
"
|
|
4034
|
-
|
|
4035
|
+
"oneOf": [
|
|
4036
|
+
{
|
|
4037
|
+
"type": "object",
|
|
4038
|
+
"maxProperties": 0
|
|
4039
|
+
},
|
|
4040
|
+
{
|
|
4041
|
+
"$ref": "#/$defs/DesktopReportTarget"
|
|
4042
|
+
}
|
|
4043
|
+
]
|
|
4035
4044
|
}
|
|
4036
4045
|
}
|
|
4037
4046
|
},
|
|
@@ -7688,6 +7697,11 @@
|
|
|
7688
7697
|
"type": "integer",
|
|
7689
7698
|
"minimum": 1,
|
|
7690
7699
|
"description": "Compared instead of stimBuild by a machine whose build.offer toolchain reports compat."
|
|
7700
|
+
},
|
|
7701
|
+
"jdk": {
|
|
7702
|
+
"type": "string",
|
|
7703
|
+
"pattern": "^[0-9]{1,3}$",
|
|
7704
|
+
"description": "The JDK major an Android build runs on, sent to a stim-server with the build-jdk feature."
|
|
7691
7705
|
}
|
|
7692
7706
|
}
|
|
7693
7707
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as protocol_exports } from "./protocol-
|
|
1
|
+
import { n as protocol_exports } from "./protocol-nY-u8rtw.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { execFile, execFileSync, spawn } from "node:child_process";
|
|
4
4
|
import { appendFileSync, chmodSync, closeSync, constants, copyFileSync, cpSync, createReadStream, existsSync, futimesSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, statfsSync, watch, writeFileSync, writeSync } from "node:fs";
|
|
@@ -661,16 +661,16 @@ function validateServeEnvironment(env, pathPrepend, serving = false) {
|
|
|
661
661
|
* environment launchd gave the process; one that `source` lacks is skipped with a line on stderr. `STIM_HOME` and
|
|
662
662
|
* `SHELL` entries are skipped the same way: the job takes them from the installing process.
|
|
663
663
|
*/
|
|
664
|
-
function applyServeEnvironment(base, env, pathPrepend, source = process.env) {
|
|
664
|
+
function applyServeEnvironment(base, env, pathPrepend, source = process.env, warn = console.error) {
|
|
665
665
|
const result = { ...base };
|
|
666
666
|
for (const entry of env) {
|
|
667
667
|
const parsed = parseEnvAssignment(entry);
|
|
668
668
|
const name = typeof parsed === "string" ? entry : parsed.key;
|
|
669
|
-
if (JOB_KEYS.includes(name))
|
|
669
|
+
if (JOB_KEYS.includes(name)) warn(`stim-server: ignoring --env ${name}; the service takes it from the process that installed it.`);
|
|
670
670
|
else if (typeof parsed !== "string") result[name] = parsed.value;
|
|
671
671
|
else if (ENV_KEY_PATTERN.test(entry)) {
|
|
672
672
|
const value = source[entry];
|
|
673
|
-
if (value === void 0)
|
|
673
|
+
if (value === void 0) warn(`stim-server: --env ${entry} has no value in the service environment; skipping it.`);
|
|
674
674
|
else result[entry] = value;
|
|
675
675
|
}
|
|
676
676
|
}
|
|
@@ -3457,6 +3457,9 @@ var BuildHost = class {
|
|
|
3457
3457
|
} catch {}
|
|
3458
3458
|
}
|
|
3459
3459
|
}
|
|
3460
|
+
environment() {
|
|
3461
|
+
return this.options.currentEnv?.() ?? this.options.env;
|
|
3462
|
+
}
|
|
3460
3463
|
root() {
|
|
3461
3464
|
return buildWorkerRoot();
|
|
3462
3465
|
}
|
|
@@ -3472,7 +3475,7 @@ var BuildHost = class {
|
|
|
3472
3475
|
const value = new Promise((resolve) => {
|
|
3473
3476
|
const args = native ? [`offer-native-${native}`] : ["offer", ...rubyVersion ? [rubyVersion] : []];
|
|
3474
3477
|
const child = spawn(process.execPath, [this.options.worker, ...args], {
|
|
3475
|
-
env: this.
|
|
3478
|
+
env: this.environment(),
|
|
3476
3479
|
stdio: [
|
|
3477
3480
|
"ignore",
|
|
3478
3481
|
"pipe",
|
|
@@ -3548,7 +3551,7 @@ var BuildHost = class {
|
|
|
3548
3551
|
},
|
|
3549
3552
|
warm: {
|
|
3550
3553
|
checkout: existsSync(join(area, "src")),
|
|
3551
|
-
dependencies: typeof params.lockfile === "string" && dependencies !== void 0 && reusableDependencies(area, dependencies.project, params.lockfile, this.
|
|
3554
|
+
dependencies: typeof params.lockfile === "string" && dependencies !== void 0 && reusableDependencies(area, dependencies.project, params.lockfile, this.environment()),
|
|
3552
3555
|
build: false
|
|
3553
3556
|
},
|
|
3554
3557
|
...artifact !== void 0 ? { artifact } : {}
|
|
@@ -3559,7 +3562,7 @@ var BuildHost = class {
|
|
|
3559
3562
|
}
|
|
3560
3563
|
workerEnvironment(clientDir, area) {
|
|
3561
3564
|
return {
|
|
3562
|
-
...this.
|
|
3565
|
+
...this.environment(),
|
|
3563
3566
|
STIM_HOME: join(area, "home"),
|
|
3564
3567
|
CP_HOME_DIR: join(clientDir, "cache", "cocoapods-home"),
|
|
3565
3568
|
CP_CACHE_DIR: join(clientDir, "cache", "cocoapods"),
|
|
@@ -4373,6 +4376,7 @@ var BuildSession = class {
|
|
|
4373
4376
|
cacheArtifact: params.cacheArtifact === true,
|
|
4374
4377
|
stimBuild: params.stimBuild,
|
|
4375
4378
|
...compat !== void 0 && toolchain.compat !== void 0 ? { compat } : {},
|
|
4379
|
+
...typeof params.jdk === "string" ? { jdk: params.jdk } : {},
|
|
4376
4380
|
configuration: optional(params.configuration),
|
|
4377
4381
|
scheme: optional(params.scheme),
|
|
4378
4382
|
iosProjectPath: optional(params.iosProjectPath),
|
|
@@ -4972,7 +4976,7 @@ function toolChecks(tools, stimBuild, platform) {
|
|
|
4972
4976
|
],
|
|
4973
4977
|
[
|
|
4974
4978
|
"Android SDK",
|
|
4975
|
-
!!tools.androidSdk
|
|
4979
|
+
!!tools.androidSdk,
|
|
4976
4980
|
tools.androidSdk ? JSON.stringify(tools.androidSdk) : "Missing Android SDK",
|
|
4977
4981
|
platform === "linux" ? "Install the Android SDK in ~/Android/Sdk, or rerun setup with --env ANDROID_HOME=<sdk>" : platform === "win32" ? "Install the Android SDK in %LOCALAPPDATA%\\Android\\Sdk, or rerun setup with --env ANDROID_HOME=<sdk>" : "Install Android Studio or set ANDROID_HOME"
|
|
4978
4982
|
],
|
|
@@ -5314,7 +5318,8 @@ async function runSetup(args, version, deps) {
|
|
|
5314
5318
|
try {
|
|
5315
5319
|
deps.storeSecret(opts.ticket, approved, opts.expiresAt, deps.now());
|
|
5316
5320
|
secretStored = true;
|
|
5317
|
-
|
|
5321
|
+
const waiting = deps.fromDoctor ? "Waiting for the requesting machine to reconnect. Run `stim doctor` on that machine to finish." : "Waiting for the requesting machine to connect. Stim Desktop sends this automatically.";
|
|
5322
|
+
step("approve", "running", "Access approval", waiting, void 0, { running: waiting });
|
|
5318
5323
|
while (deps.now() < Date.parse(opts.expiresAt)) {
|
|
5319
5324
|
changed = false;
|
|
5320
5325
|
for (const grant of recorded(approved)) note(grant, false);
|
|
@@ -12136,6 +12141,7 @@ var UsageRecorder = class {
|
|
|
12136
12141
|
//#endregion
|
|
12137
12142
|
//#region src/desktop-reports.ts
|
|
12138
12143
|
const MAX_REPORT_READS = 4;
|
|
12144
|
+
const reportKey = (target) => target.report === "gc" ? "gc" : `doctor:${target.platform ?? "all"}:${target.cwd}`;
|
|
12139
12145
|
var DesktopReports = class {
|
|
12140
12146
|
entries = /* @__PURE__ */ new Map();
|
|
12141
12147
|
closed = false;
|
|
@@ -12206,9 +12212,13 @@ var DesktopReports = class {
|
|
|
12206
12212
|
}
|
|
12207
12213
|
};
|
|
12208
12214
|
}
|
|
12209
|
-
|
|
12210
|
-
|
|
12215
|
+
/** Invalidates `target`, or every target without one. */
|
|
12216
|
+
invalidate(target) {
|
|
12217
|
+
const only = target && this.entries.get(reportKey(target));
|
|
12218
|
+
if (target && !only) return;
|
|
12219
|
+
for (const entry of only ? [only] : this.entries.values()) {
|
|
12211
12220
|
entry.generation++;
|
|
12221
|
+
entry.pending?.abort.abort();
|
|
12212
12222
|
clearTimeout(entry.timer);
|
|
12213
12223
|
entry.timer = void 0;
|
|
12214
12224
|
if (entry.listeners.size && !entry.pending) this.start(entry);
|
|
@@ -12224,7 +12234,7 @@ var DesktopReports = class {
|
|
|
12224
12234
|
}
|
|
12225
12235
|
entry(target) {
|
|
12226
12236
|
for (const [key, entry] of this.entries) if (!entry.readers && !entry.listeners.size && !entry.pending && (!entry.settled || this.now() - entry.settled.at >= this.ttlMs)) this.entries.delete(key);
|
|
12227
|
-
const key = target
|
|
12237
|
+
const key = reportKey(target);
|
|
12228
12238
|
let entry = this.entries.get(key);
|
|
12229
12239
|
if (!entry) {
|
|
12230
12240
|
entry = {
|
|
@@ -12247,6 +12257,7 @@ var DesktopReports = class {
|
|
|
12247
12257
|
const admitted = this.activeLoads < MAX_REPORT_READS;
|
|
12248
12258
|
if (admitted) this.activeLoads++;
|
|
12249
12259
|
const generation = entry.generation;
|
|
12260
|
+
const abort = new AbortController();
|
|
12250
12261
|
const pending = {
|
|
12251
12262
|
promise: Promise.resolve().then(() => {
|
|
12252
12263
|
if (this.closed) return {
|
|
@@ -12257,7 +12268,7 @@ var DesktopReports = class {
|
|
|
12257
12268
|
ok: false,
|
|
12258
12269
|
message: `The server is already running ${MAX_REPORT_READS} shared reports. Try again after a report finishes.`
|
|
12259
12270
|
};
|
|
12260
|
-
return this.load(entry.target);
|
|
12271
|
+
return this.load(entry.target, abort.signal);
|
|
12261
12272
|
}).catch((cause) => ({
|
|
12262
12273
|
ok: false,
|
|
12263
12274
|
message: cause instanceof Error ? cause.message : String(cause)
|
|
@@ -12278,7 +12289,8 @@ var DesktopReports = class {
|
|
|
12278
12289
|
this.publish(entry);
|
|
12279
12290
|
this.schedule(entry);
|
|
12280
12291
|
}),
|
|
12281
|
-
generation
|
|
12292
|
+
generation,
|
|
12293
|
+
abort
|
|
12282
12294
|
};
|
|
12283
12295
|
entry.pending = pending;
|
|
12284
12296
|
this.publish(entry);
|
|
@@ -13787,7 +13799,12 @@ function lastCursor(page) {
|
|
|
13787
13799
|
return last ? archiveCursor(last) : null;
|
|
13788
13800
|
}
|
|
13789
13801
|
const STATUS_READ_WAIT_MS = 3e4;
|
|
13790
|
-
const
|
|
13802
|
+
const REQUEST_LIMITS = {
|
|
13803
|
+
connection: 32,
|
|
13804
|
+
device: 16
|
|
13805
|
+
};
|
|
13806
|
+
const MAX_REPORT_WAITS = 16;
|
|
13807
|
+
const MAX_RETAINED_PLANS = 4;
|
|
13791
13808
|
function isWorkspaceChangeMethod(method) {
|
|
13792
13809
|
return method === "workspace.files" || method === "workspace.diff";
|
|
13793
13810
|
}
|
|
@@ -14438,6 +14455,11 @@ async function startServer(options) {
|
|
|
14438
14455
|
return foldBuild;
|
|
14439
14456
|
};
|
|
14440
14457
|
const running = /* @__PURE__ */ new Set();
|
|
14458
|
+
const peerCommands = /* @__PURE__ */ new Map();
|
|
14459
|
+
const requestLimits = {
|
|
14460
|
+
...REQUEST_LIMITS,
|
|
14461
|
+
...options.requestLimits
|
|
14462
|
+
};
|
|
14441
14463
|
const cancelling = new Pending();
|
|
14442
14464
|
const logLimits = {
|
|
14443
14465
|
...LOG_LIMITS,
|
|
@@ -14454,8 +14476,10 @@ async function startServer(options) {
|
|
|
14454
14476
|
const busyWorkspaces = /* @__PURE__ */ new Set();
|
|
14455
14477
|
const retainedLifecycles = /* @__PURE__ */ new Set();
|
|
14456
14478
|
const planQueues = /* @__PURE__ */ new Map();
|
|
14457
|
-
const readDetailsReport = (method, cwd = homedir(), platform) => {
|
|
14479
|
+
const readDetailsReport = (method, cwd = homedir(), platform, signal) => {
|
|
14458
14480
|
const abort = new AbortController();
|
|
14481
|
+
if (signal?.aborted) abort.abort();
|
|
14482
|
+
else signal?.addEventListener("abort", () => abort.abort(), { once: true });
|
|
14459
14483
|
const timeoutMs = options.commandLimits?.timeoutMs ?? DETAILS_TIMEOUT_MS;
|
|
14460
14484
|
let task;
|
|
14461
14485
|
const cancel = async () => {
|
|
@@ -14468,10 +14492,14 @@ async function startServer(options) {
|
|
|
14468
14492
|
projectRoot: cwd,
|
|
14469
14493
|
env: options.env
|
|
14470
14494
|
});
|
|
14471
|
-
|
|
14495
|
+
if (method === "doctor") return await stim.doctor({
|
|
14472
14496
|
signal: abort.signal,
|
|
14473
14497
|
platform
|
|
14474
|
-
})
|
|
14498
|
+
});
|
|
14499
|
+
return method === "gc" ? await stim.gc({
|
|
14500
|
+
signal: abort.signal,
|
|
14501
|
+
measureBuildState: false
|
|
14502
|
+
}) : await stim.stats({ signal: abort.signal });
|
|
14475
14503
|
}).then((report) => {
|
|
14476
14504
|
if (method !== "stats" && Buffer.byteLength(JSON.stringify(report)) > commandLimits.maxOutputBytes) return {
|
|
14477
14505
|
ok: false,
|
|
@@ -14491,7 +14519,7 @@ async function startServer(options) {
|
|
|
14491
14519
|
running.add(cancel);
|
|
14492
14520
|
return task;
|
|
14493
14521
|
};
|
|
14494
|
-
const desktopReports = new DesktopReports((target) => readDetailsReport(target.report, target.report === "doctor" ? target.cwd : void 0, target.report === "doctor" ? target.platform : void 0));
|
|
14522
|
+
const desktopReports = new DesktopReports((target, signal) => readDetailsReport(target.report, target.report === "doctor" ? target.cwd : void 0, target.report === "doctor" ? target.platform : void 0, signal));
|
|
14495
14523
|
const machineDetails = new MachineDetailsCache(() => loadMachineDetails(() => readDetailsReport("gc"), loadAudit, () => readDetailsReport("stats")));
|
|
14496
14524
|
const buildMachines = new BuildMachinesCache();
|
|
14497
14525
|
let recordingTurn = Promise.resolve();
|
|
@@ -14583,6 +14611,7 @@ async function startServer(options) {
|
|
|
14583
14611
|
const builds = new BuildHost({
|
|
14584
14612
|
worker: join(dirname(options.stimCli), "offload-worker.mjs"),
|
|
14585
14613
|
env: options.env,
|
|
14614
|
+
currentEnv: options.currentEnv,
|
|
14586
14615
|
limits: options.buildLimits,
|
|
14587
14616
|
ready: () => startup.state === "ready",
|
|
14588
14617
|
finished: ({ client, repo, ok, error, durationMs }) => auditSafely({
|
|
@@ -14763,6 +14792,19 @@ async function startServer(options) {
|
|
|
14763
14792
|
const replays = /* @__PURE__ */ new Map();
|
|
14764
14793
|
let keyframeReads = 0;
|
|
14765
14794
|
const commands = /* @__PURE__ */ new Set();
|
|
14795
|
+
const reportWaits = /* @__PURE__ */ new Set();
|
|
14796
|
+
const holdSlot = (cancel) => {
|
|
14797
|
+
commands.add(cancel);
|
|
14798
|
+
if (peer !== null) peerCommands.set(peer, (peerCommands.get(peer) ?? 0) + 1);
|
|
14799
|
+
};
|
|
14800
|
+
const releaseSlot = (cancel) => {
|
|
14801
|
+
if (!commands.delete(cancel) || peer === null) return;
|
|
14802
|
+
const left = (peerCommands.get(peer) ?? 1) - 1;
|
|
14803
|
+
if (left > 0) peerCommands.set(peer, left);
|
|
14804
|
+
else peerCommands.delete(peer);
|
|
14805
|
+
};
|
|
14806
|
+
const slotsFull = (needed = 1) => commands.size + needed > requestLimits.connection || peer !== null && (peerCommands.get(peer) ?? 0) + needed > requestLimits.device;
|
|
14807
|
+
const slotsMessage = (needed = 1) => peer !== null && commands.size + needed <= requestLimits.connection ? `A remote device can run ${requestLimits.device} requests at a time.` : `A connection can run ${requestLimits.connection} requests at a time.`;
|
|
14766
14808
|
const planRequests = /* @__PURE__ */ new Map();
|
|
14767
14809
|
const persistentActions = /* @__PURE__ */ new Set();
|
|
14768
14810
|
let nextSubscription = 1;
|
|
@@ -14862,6 +14904,7 @@ async function startServer(options) {
|
|
|
14862
14904
|
"desktop-plan",
|
|
14863
14905
|
"desktop-reports",
|
|
14864
14906
|
"desktop-report-platform",
|
|
14907
|
+
"desktop-report-invalidate-target",
|
|
14865
14908
|
"desktop-settings",
|
|
14866
14909
|
"desktop-settings-schema",
|
|
14867
14910
|
"desktop-settings-write",
|
|
@@ -15555,18 +15598,18 @@ async function startServer(options) {
|
|
|
15555
15598
|
subscriptions.set(subscription, cleanup);
|
|
15556
15599
|
}
|
|
15557
15600
|
function archiveRead(id, read) {
|
|
15558
|
-
if (
|
|
15601
|
+
if (slotsFull()) return error(id, "limit-exceeded", slotsMessage());
|
|
15559
15602
|
let dropped = false;
|
|
15560
15603
|
const cancel = async () => {
|
|
15561
15604
|
dropped = true;
|
|
15562
15605
|
await task;
|
|
15563
15606
|
};
|
|
15564
|
-
|
|
15607
|
+
holdSlot(cancel);
|
|
15565
15608
|
running.add(cancel);
|
|
15566
15609
|
const task = archiveReads.then(() => new Promise((resolve) => setImmediate(resolve))).then(() => {
|
|
15567
15610
|
if (!dropped && socket.readyState === socket.OPEN) return read();
|
|
15568
15611
|
}).finally(() => {
|
|
15569
|
-
|
|
15612
|
+
releaseSlot(cancel);
|
|
15570
15613
|
running.delete(cancel);
|
|
15571
15614
|
});
|
|
15572
15615
|
archiveReads = task.catch(() => socket.close(1011, "internal error"));
|
|
@@ -15598,14 +15641,14 @@ async function startServer(options) {
|
|
|
15598
15641
|
if (!isJsonObject(params) || typeof params.workspace !== "string" || !params.workspace.length || params.workspace.length > 4096 || params.workspace.includes("\0") || !needsPath && params.group !== "changed" && params.group !== "untracked" || needsPath && (typeof params.path !== "string" || !params.path.length || params.path.length > 4096 || params.path.includes("\0")) || Object.keys(params).some((key) => key !== "workspace" && !(needsPath ? key === "path" : key === "group"))) return error(id, "bad-request", "Workspace diff requires workspace and, for a patch, a file path.");
|
|
15599
15642
|
const cwd = workspaceDir(id, params.workspace, true);
|
|
15600
15643
|
if (!cwd) return;
|
|
15601
|
-
if (
|
|
15644
|
+
if (slotsFull()) return error(id, "limit-exceeded", slotsMessage());
|
|
15602
15645
|
const abort = new AbortController();
|
|
15603
15646
|
let task;
|
|
15604
15647
|
const cancel = async () => {
|
|
15605
15648
|
abort.abort();
|
|
15606
15649
|
await task;
|
|
15607
15650
|
};
|
|
15608
|
-
|
|
15651
|
+
holdSlot(cancel);
|
|
15609
15652
|
running.add(cancel);
|
|
15610
15653
|
task = readWorkspaceDiff(cwd, needsPath ? params.path : void 0, options.env, abort.signal, !needsPath ? params.group : void 0).then((result) => {
|
|
15611
15654
|
if (!abort.signal.aborted) send(socket, {
|
|
@@ -15615,13 +15658,13 @@ async function startServer(options) {
|
|
|
15615
15658
|
}, (cause) => {
|
|
15616
15659
|
if (!abort.signal.aborted) error(id, "stim-failed", cause.message);
|
|
15617
15660
|
}).finally(() => {
|
|
15618
|
-
|
|
15661
|
+
releaseSlot(cancel);
|
|
15619
15662
|
running.delete(cancel);
|
|
15620
15663
|
});
|
|
15621
15664
|
}
|
|
15622
|
-
function inProcessRead(id, label, read, timeoutMs = commandLimits.timeoutMs, turn = null) {
|
|
15623
|
-
if (
|
|
15624
|
-
error(id, "limit-exceeded", `A connection can
|
|
15665
|
+
function inProcessRead(id, label, read, timeoutMs = commandLimits.timeoutMs, turn = null, report = false) {
|
|
15666
|
+
if (report ? reportWaits.size >= MAX_REPORT_WAITS : slotsFull()) {
|
|
15667
|
+
error(id, "limit-exceeded", report ? `A connection can wait for ${MAX_REPORT_WAITS} reports at a time.` : slotsMessage());
|
|
15625
15668
|
return null;
|
|
15626
15669
|
}
|
|
15627
15670
|
const abort = new AbortController();
|
|
@@ -15645,14 +15688,16 @@ async function startServer(options) {
|
|
|
15645
15688
|
if (!abort.signal.aborted) error(id, "stim-failed", readFailure(cause));
|
|
15646
15689
|
}).finally(() => {
|
|
15647
15690
|
clearTimeout(deadline);
|
|
15648
|
-
|
|
15691
|
+
releaseSlot(cancel);
|
|
15692
|
+
reportWaits.delete(cancel);
|
|
15649
15693
|
running.delete(cancel);
|
|
15650
15694
|
});
|
|
15651
15695
|
const cancel = async () => {
|
|
15652
15696
|
abort.abort();
|
|
15653
15697
|
await task;
|
|
15654
15698
|
};
|
|
15655
|
-
|
|
15699
|
+
if (report) reportWaits.add(cancel);
|
|
15700
|
+
else holdSlot(cancel);
|
|
15656
15701
|
running.add(cancel);
|
|
15657
15702
|
return {
|
|
15658
15703
|
done: task,
|
|
@@ -15721,7 +15766,7 @@ async function startServer(options) {
|
|
|
15721
15766
|
const { action } = parsed;
|
|
15722
15767
|
const resolved = registeredWorkspace(action.workspace);
|
|
15723
15768
|
if ("code" in resolved) return refuseAction(resolved.code, resolved.message);
|
|
15724
|
-
if (
|
|
15769
|
+
if (slotsFull()) return refuseAction("limit-exceeded", slotsMessage());
|
|
15725
15770
|
if (busyWorkspaces.has(resolved.dir)) return refuseAction("action-busy", `An action is already running in ${resolved.dir}. Try again when it finishes.`);
|
|
15726
15771
|
let admittedCwd = resolved.dir;
|
|
15727
15772
|
let run;
|
|
@@ -15755,7 +15800,7 @@ async function startServer(options) {
|
|
|
15755
15800
|
let finished = false;
|
|
15756
15801
|
const release = (outcome) => {
|
|
15757
15802
|
if (outcome.unresolved) return;
|
|
15758
|
-
|
|
15803
|
+
releaseSlot(cancel);
|
|
15759
15804
|
persistentActions.delete(cancel);
|
|
15760
15805
|
running.delete(cancel);
|
|
15761
15806
|
busyWorkspaces.delete(resolved.dir);
|
|
@@ -15793,7 +15838,7 @@ async function startServer(options) {
|
|
|
15793
15838
|
await run.cancel();
|
|
15794
15839
|
release(await run.drained);
|
|
15795
15840
|
};
|
|
15796
|
-
|
|
15841
|
+
holdSlot(cancel);
|
|
15797
15842
|
persistentActions.add(cancel);
|
|
15798
15843
|
running.add(cancel);
|
|
15799
15844
|
run.outcome.then((outcome) => finish(!outcome.ok && "code" in outcome && outcome.code === "STIM_ACTION_BUSY" ? {
|
|
@@ -15920,7 +15965,7 @@ async function startServer(options) {
|
|
|
15920
15965
|
} catch (cause) {
|
|
15921
15966
|
return error(id, "bad-request", `Cannot reload this workspace: ${readFailure(cause)}`);
|
|
15922
15967
|
}
|
|
15923
|
-
if (
|
|
15968
|
+
if (slotsFull()) return error(id, "limit-exceeded", slotsMessage());
|
|
15924
15969
|
if (busyWorkspaces.has(cwd)) return error(id, "action-busy", `An action is already running in ${cwd}.`);
|
|
15925
15970
|
const subscription = openSubscription(id);
|
|
15926
15971
|
if (!subscription) return;
|
|
@@ -15961,7 +16006,7 @@ async function startServer(options) {
|
|
|
15961
16006
|
}
|
|
15962
16007
|
});
|
|
15963
16008
|
const cancel = () => run.cancel();
|
|
15964
|
-
|
|
16009
|
+
holdSlot(cancel);
|
|
15965
16010
|
running.add(cancel);
|
|
15966
16011
|
busyWorkspaces.add(cwd);
|
|
15967
16012
|
subscriptions.set(subscription, () => {
|
|
@@ -15971,7 +16016,7 @@ async function startServer(options) {
|
|
|
15971
16016
|
run.outcome.then(finish);
|
|
15972
16017
|
run.drained.then((outcome) => {
|
|
15973
16018
|
if (!outcome.unresolved) {
|
|
15974
|
-
|
|
16019
|
+
releaseSlot(cancel);
|
|
15975
16020
|
running.delete(cancel);
|
|
15976
16021
|
busyWorkspaces.delete(cwd);
|
|
15977
16022
|
busyWorkspaces.delete(admittedCwd);
|
|
@@ -16089,7 +16134,7 @@ async function startServer(options) {
|
|
|
16089
16134
|
} catch (cause) {
|
|
16090
16135
|
return error(id, "bad-request", `Cannot ${kind} this workspace: ${readFailure(cause)}`);
|
|
16091
16136
|
}
|
|
16092
|
-
if (
|
|
16137
|
+
if (slotsFull()) return error(id, "limit-exceeded", slotsMessage());
|
|
16093
16138
|
if (busyWorkspaces.has(cwd)) return error(id, "action-busy", `An action is already running in ${cwd}.`);
|
|
16094
16139
|
const subscription = openSubscription(id);
|
|
16095
16140
|
if (!subscription) return;
|
|
@@ -16130,7 +16175,7 @@ async function startServer(options) {
|
|
|
16130
16175
|
const cancel = async () => {
|
|
16131
16176
|
await run.cancel();
|
|
16132
16177
|
};
|
|
16133
|
-
|
|
16178
|
+
holdSlot(cancel);
|
|
16134
16179
|
running.add(cancel);
|
|
16135
16180
|
busyWorkspaces.add(cwd);
|
|
16136
16181
|
subscriptions.set(subscription, async () => {
|
|
@@ -16145,7 +16190,7 @@ async function startServer(options) {
|
|
|
16145
16190
|
run.drained.then((outcome) => {
|
|
16146
16191
|
if (kind === "gc" || kind === "worktree-remove" || kind === "worktree-warm" || kind === "machine-access") desktopReports.invalidate();
|
|
16147
16192
|
const release = () => {
|
|
16148
|
-
|
|
16193
|
+
releaseSlot(cancel);
|
|
16149
16194
|
running.delete(cancel);
|
|
16150
16195
|
busyWorkspaces.delete(cwd);
|
|
16151
16196
|
for (const workspace of admittedWorkspaces) busyWorkspaces.delete(workspace);
|
|
@@ -16199,7 +16244,7 @@ async function startServer(options) {
|
|
|
16199
16244
|
} catch (cause) {
|
|
16200
16245
|
return error(id, "bad-request", `Cannot change settings here: ${readFailure(cause)}`);
|
|
16201
16246
|
}
|
|
16202
|
-
if (
|
|
16247
|
+
if (slotsFull()) return error(id, "limit-exceeded", slotsMessage());
|
|
16203
16248
|
if (busyWorkspaces.has(cwd)) return error(id, "action-busy", `An action is already running in ${cwd}.`);
|
|
16204
16249
|
const { key, action } = params;
|
|
16205
16250
|
const value = typeof params.value === "string" ? params.value : void 0;
|
|
@@ -16287,7 +16332,7 @@ async function startServer(options) {
|
|
|
16287
16332
|
} });
|
|
16288
16333
|
}).finally(() => {
|
|
16289
16334
|
clearTimeout(deadline);
|
|
16290
|
-
|
|
16335
|
+
releaseSlot(cancel);
|
|
16291
16336
|
running.delete(cancel);
|
|
16292
16337
|
busyWorkspaces.delete(cwd);
|
|
16293
16338
|
busyWorkspaces.delete(admittedCwd);
|
|
@@ -16313,7 +16358,7 @@ async function startServer(options) {
|
|
|
16313
16358
|
abort.abort();
|
|
16314
16359
|
await task;
|
|
16315
16360
|
};
|
|
16316
|
-
|
|
16361
|
+
holdSlot(cancel);
|
|
16317
16362
|
running.add(cancel);
|
|
16318
16363
|
busyWorkspaces.add(cwd);
|
|
16319
16364
|
}
|
|
@@ -16325,7 +16370,7 @@ async function startServer(options) {
|
|
|
16325
16370
|
abort.abort();
|
|
16326
16371
|
await task;
|
|
16327
16372
|
};
|
|
16328
|
-
|
|
16373
|
+
holdSlot(cancel);
|
|
16329
16374
|
running.add(cancel);
|
|
16330
16375
|
task = Promise.resolve().then(() => createStim({
|
|
16331
16376
|
projectRoot: cwd,
|
|
@@ -16335,7 +16380,7 @@ async function startServer(options) {
|
|
|
16335
16380
|
signal: abort.signal
|
|
16336
16381
|
})).then((records) => Buffer.byteLength(records.map((record) => JSON.stringify(record)).join("\n")) > commandLimits.maxOutputBytes ? `stim logs printed more than ${commandLimits.maxOutputBytes} bytes.` : records, (cause) => readFailure(cause)).finally(() => {
|
|
16337
16382
|
clearTimeout(deadline);
|
|
16338
|
-
|
|
16383
|
+
releaseSlot(cancel);
|
|
16339
16384
|
running.delete(cancel);
|
|
16340
16385
|
});
|
|
16341
16386
|
return task;
|
|
@@ -16377,7 +16422,7 @@ async function startServer(options) {
|
|
|
16377
16422
|
archive
|
|
16378
16423
|
});
|
|
16379
16424
|
if (!cwd) return;
|
|
16380
|
-
if (archive === void 0 &&
|
|
16425
|
+
if (archive === void 0 && slotsFull(2)) return error(id, "limit-exceeded", slotsMessage(2));
|
|
16381
16426
|
const slotName = slot ?? "default";
|
|
16382
16427
|
const spans = recordedSpans(listSegments(recordingDir(workspace, platform, slotName, archive), archive !== void 0));
|
|
16383
16428
|
const replayTarget = {
|
|
@@ -16568,7 +16613,7 @@ async function startServer(options) {
|
|
|
16568
16613
|
}, "build.plan");
|
|
16569
16614
|
}
|
|
16570
16615
|
function queuePlan(id, cwd, plan, method, request) {
|
|
16571
|
-
if (request !== void 0 && planRequests.size >=
|
|
16616
|
+
if (request !== void 0 && planRequests.size >= MAX_RETAINED_PLANS) {
|
|
16572
16617
|
error(id, "limit-exceeded", "Acknowledge retained plan refusals before requesting more previews.");
|
|
16573
16618
|
return;
|
|
16574
16619
|
}
|
|
@@ -16831,8 +16876,7 @@ async function startServer(options) {
|
|
|
16831
16876
|
}).settings({ signal }));
|
|
16832
16877
|
}
|
|
16833
16878
|
function desktopReport(id, method, params) {
|
|
16834
|
-
if (method === "desktop.reports.invalidate") {
|
|
16835
|
-
if (params !== void 0 && (!isJsonObject(params) || Object.keys(params).length)) return error(id, "bad-request", "desktop.reports.invalidate takes no parameters.");
|
|
16879
|
+
if (method === "desktop.reports.invalidate" && (params === void 0 || isJsonObject(params) && !Object.keys(params).length)) {
|
|
16836
16880
|
desktopReports.invalidate();
|
|
16837
16881
|
return send(socket, {
|
|
16838
16882
|
id,
|
|
@@ -16845,12 +16889,19 @@ async function startServer(options) {
|
|
|
16845
16889
|
cwd: resolve(String(params.cwd)),
|
|
16846
16890
|
...params.platform === "ios" || params.platform === "android" ? { platform: params.platform } : {}
|
|
16847
16891
|
};
|
|
16892
|
+
if (method === "desktop.reports.invalidate") {
|
|
16893
|
+
desktopReports.invalidate(target);
|
|
16894
|
+
return send(socket, {
|
|
16895
|
+
id,
|
|
16896
|
+
result: {}
|
|
16897
|
+
});
|
|
16898
|
+
}
|
|
16848
16899
|
if (method === "desktop.reports.get") {
|
|
16849
16900
|
inProcessRead(id, `stim ${target.report}`, async (signal) => {
|
|
16850
16901
|
const snapshot = await desktopReports.get(target, signal);
|
|
16851
16902
|
if (snapshot.error) throw new Error(snapshot.error);
|
|
16852
16903
|
return snapshot;
|
|
16853
|
-
}, options.commandLimits?.timeoutMs ?? DETAILS_TIMEOUT_MS);
|
|
16904
|
+
}, options.commandLimits?.timeoutMs ?? DETAILS_TIMEOUT_MS, null, true);
|
|
16854
16905
|
return;
|
|
16855
16906
|
}
|
|
16856
16907
|
const subscription = openSubscription(id);
|
|
@@ -17222,12 +17273,14 @@ async function startServer(options) {
|
|
|
17222
17273
|
controllers.delete(socket);
|
|
17223
17274
|
for (const unsubscribe of subscriptions.values()) unsubscribe();
|
|
17224
17275
|
subscriptions.clear();
|
|
17225
|
-
for (const cancel of commands) {
|
|
17226
|
-
if (persistentActions.has(cancel))
|
|
17276
|
+
for (const cancel of [...commands, ...reportWaits]) {
|
|
17277
|
+
if (persistentActions.has(cancel)) {
|
|
17278
|
+
releaseSlot(cancel);
|
|
17279
|
+
continue;
|
|
17280
|
+
}
|
|
17227
17281
|
running.delete(cancel);
|
|
17228
17282
|
cancelling.track(cancel());
|
|
17229
17283
|
}
|
|
17230
|
-
commands.clear();
|
|
17231
17284
|
});
|
|
17232
17285
|
}
|
|
17233
17286
|
function upgrade(request, socket, head) {
|
|
@@ -17415,7 +17468,13 @@ async function startServer(options) {
|
|
|
17415
17468
|
return;
|
|
17416
17469
|
}
|
|
17417
17470
|
if (request.method === "GET" && request.url === "/health" && peerAddress(request) !== null && request.headers.origin === void 0 && request.headers["sec-fetch-site"] === void 0) {
|
|
17418
|
-
const peerHealth = {
|
|
17471
|
+
const peerHealth = {
|
|
17472
|
+
version: health.version,
|
|
17473
|
+
...startup.state === "ready" ? {} : { startup: startup.state === "degraded" ? {
|
|
17474
|
+
state: "degraded",
|
|
17475
|
+
cause: startup.cause
|
|
17476
|
+
} : { state: "pending" } }
|
|
17477
|
+
};
|
|
17419
17478
|
response.writeHead(startup.state === "ready" ? 200 : 503, { "content-type": "application/json" }).end(JSON.stringify(peerHealth));
|
|
17420
17479
|
return;
|
|
17421
17480
|
}
|
|
@@ -17600,6 +17659,7 @@ async function startServer(options) {
|
|
|
17600
17659
|
}
|
|
17601
17660
|
startupProbe = probeDirectories(directories, options.env, options.startupProbeMs ?? 1e4, configDir());
|
|
17602
17661
|
let reason = await startupProbe.result;
|
|
17662
|
+
let cause = "home-unreadable";
|
|
17603
17663
|
startupProbe = null;
|
|
17604
17664
|
if (closing) return settle(startup);
|
|
17605
17665
|
if (reason === null) try {
|
|
@@ -17611,6 +17671,7 @@ async function startServer(options) {
|
|
|
17611
17671
|
becomeReady();
|
|
17612
17672
|
} catch (error) {
|
|
17613
17673
|
reason = error.message;
|
|
17674
|
+
cause = "start-failed";
|
|
17614
17675
|
}
|
|
17615
17676
|
if (reason === null) {
|
|
17616
17677
|
if (startup.state === "degraded") console.error("stim-server: the Stim home answers again; serving clients.");
|
|
@@ -17635,7 +17696,8 @@ async function startServer(options) {
|
|
|
17635
17696
|
if (startup.state !== "degraded" || startup.reason !== reason) console.error(`stim-server: listening, but not serving clients until the Stim home answers. ${reason}`);
|
|
17636
17697
|
startup = {
|
|
17637
17698
|
state: "degraded",
|
|
17638
|
-
reason
|
|
17699
|
+
reason,
|
|
17700
|
+
cause
|
|
17639
17701
|
};
|
|
17640
17702
|
settle(startup);
|
|
17641
17703
|
startupRetry = setTimeout(() => void checkHome(), options.startupRetryMs ?? 3e4);
|
|
@@ -17786,12 +17848,21 @@ async function serve(port, extraEnv, pathPrepend, loopbackOnly) {
|
|
|
17786
17848
|
const desktopInput = bootstrapFd === "0" ? await readDesktopBootstrap(process.stdin).catch(() => fail("Desktop bootstrap input is invalid or unavailable.")) : void 0;
|
|
17787
17849
|
delete process.env.NODE_COMPILE_CACHE;
|
|
17788
17850
|
const captureHost = hostFromExecutable(process.env.STIM_HOST_EXECUTABLE);
|
|
17789
|
-
const
|
|
17790
|
-
|
|
17791
|
-
|
|
17851
|
+
const inherited = { ...process.env };
|
|
17852
|
+
const serveEnvironment = (warn) => {
|
|
17853
|
+
const found = applyServeEnvironment(discoverToolchain(inherited).env, extraEnv, pathPrepend, process.env, warn);
|
|
17854
|
+
delete found.CLAUDE_CODE_SESSION_ID;
|
|
17855
|
+
delete found.CODEX_THREAD_ID;
|
|
17856
|
+
return found;
|
|
17857
|
+
};
|
|
17858
|
+
const env = serveEnvironment();
|
|
17792
17859
|
if (!process.env.STIM_HOME && env.STIM_HOME) process.env.STIM_HOME = env.STIM_HOME;
|
|
17793
17860
|
if (process.env.STIM_HOME) env.STIM_HOME = process.env.STIM_HOME;
|
|
17794
17861
|
Object.assign(process.env, env);
|
|
17862
|
+
const currentEnv = () => ({
|
|
17863
|
+
...serveEnvironment(() => {}),
|
|
17864
|
+
...env.STIM_HOME ? { STIM_HOME: env.STIM_HOME } : {}
|
|
17865
|
+
});
|
|
17795
17866
|
const loaded = await loadTransport({
|
|
17796
17867
|
reference: "@stim-cli/transport-tailscale",
|
|
17797
17868
|
load: () => import("@stim-cli/transport-tailscale"),
|
|
@@ -17818,6 +17889,7 @@ async function serve(port, extraEnv, pathPrepend, loopbackOnly) {
|
|
|
17818
17889
|
stimVersion: stim.version,
|
|
17819
17890
|
serverVersion: pkg.version,
|
|
17820
17891
|
env,
|
|
17892
|
+
currentEnv,
|
|
17821
17893
|
transports: [transport],
|
|
17822
17894
|
service: {
|
|
17823
17895
|
label: serviceManagerOrNull()?.runningLabel(process.env) ?? null,
|
package/dist/stim-server.mjs
CHANGED
|
@@ -4,6 +4,6 @@ const encode = (version) => version.split(".", 3).reduce((sum, part) => sum * 1e
|
|
|
4
4
|
if (encode(process.versions.node) < encode("22.12.0")) {
|
|
5
5
|
process.stderr.write(`STIM_NODE_UNSUPPORTED: stim-server needs Node 22.12.0 or later; this is Node ${process.versions.node} at ${process.execPath}. To run stim-server where a project pins an older Node, see https://stim.appandflow.com/docs/requirements#older-node-pins\n`);
|
|
6
6
|
process.exitCode = 1;
|
|
7
|
-
} else import("./stim-server-
|
|
7
|
+
} else import("./stim-server-DU1htjam.mjs");
|
|
8
8
|
//#endregion
|
|
9
9
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stim-cli/server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.26.0",
|
|
4
4
|
"description": "Serves Stim status to paired clients over Tailscale, and runs reload and stop for clients granted control.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -28,13 +28,13 @@
|
|
|
28
28
|
"./protocol.schema.json": "./dist/protocol.schema.json"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@stim-cli/agent-driver": "1.
|
|
32
|
-
"@stim-cli/agent-driver-agent-device": "1.
|
|
33
|
-
"@stim-cli/cache": "1.
|
|
34
|
-
"@stim-cli/core": "1.
|
|
35
|
-
"@stim-cli/transport": "1.
|
|
36
|
-
"@stim-cli/transport-tailscale": "1.
|
|
37
|
-
"stim": "1.
|
|
31
|
+
"@stim-cli/agent-driver": "1.26.0",
|
|
32
|
+
"@stim-cli/agent-driver-agent-device": "1.26.0",
|
|
33
|
+
"@stim-cli/cache": "1.26.0",
|
|
34
|
+
"@stim-cli/core": "1.26.0",
|
|
35
|
+
"@stim-cli/transport": "1.26.0",
|
|
36
|
+
"@stim-cli/transport-tailscale": "1.26.0",
|
|
37
|
+
"stim": "1.26.0",
|
|
38
38
|
"ws": "^8.21.3"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|