sootsim 0.0.3 → 0.1.36
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/LICENSE +21 -0
- package/README.md +4 -4
- package/dist-cli/bin.js +12 -12
- package/dist-cli/chunks/{agent-D5NBV32O.js → agent-YZB6D3DR.js} +4 -4
- package/dist-cli/chunks/agent-wrapper-VHCVS22I.js +15 -0
- package/dist-cli/chunks/{assert-EJ7DQS2H.js → assert-AIVCKKLG.js} +2 -2
- package/dist-cli/chunks/auto-bootstrap-MLNTX23H.js +2 -0
- package/dist-cli/chunks/chunk-27P763IZ.js +61 -0
- package/dist-cli/chunks/chunk-3UIWOHC2.js +62 -0
- package/dist-cli/chunks/chunk-5KGFHWVR.js +1 -0
- package/dist-cli/chunks/chunk-5QIUJNT3.js +5 -0
- package/dist-cli/chunks/{chunk-RLS6PHBW.js → chunk-6GGMKFWJ.js} +1 -1
- package/dist-cli/chunks/{chunk-CQ6PX2EU.js → chunk-6Z275LCY.js} +2 -2
- package/dist-cli/chunks/chunk-75LBYBKW.js +11 -0
- package/dist-cli/chunks/chunk-A5BRCXYE.js +2 -0
- package/dist-cli/chunks/{chunk-FTRI7SVV.js → chunk-CYCXOAVZ.js} +2 -2
- package/dist-cli/chunks/{chunk-5IPP4HAW.js → chunk-DFN3GGH7.js} +2 -2
- package/dist-cli/chunks/chunk-EBEHZJRG.js +117 -0
- package/dist-cli/chunks/{chunk-3SLEIN6B.js → chunk-EJLNUMMP.js} +1 -1
- package/dist-cli/chunks/{chunk-3K6VDPVD.js → chunk-EWSQSALM.js} +2 -2
- package/dist-cli/chunks/{chunk-NKJLTISU.js → chunk-FE7UI3MT.js} +4 -4
- package/dist-cli/chunks/chunk-G663654J.js +1 -0
- package/dist-cli/chunks/chunk-G7XQD4KC.js +4 -0
- package/dist-cli/chunks/chunk-GW7XY5KC.js +2 -0
- package/dist-cli/chunks/{chunk-O2HBPZW5.js → chunk-H2QO4TDV.js} +2 -2
- package/dist-cli/chunks/{chunk-UZL5ZZ4E.js → chunk-HWCKZXNJ.js} +2 -2
- package/dist-cli/chunks/{chunk-BYLX2DO4.js → chunk-HWFHBMAQ.js} +2 -2
- package/dist-cli/chunks/chunk-IJMYFYDZ.js +2 -0
- package/dist-cli/chunks/chunk-J7CTD37P.js +1 -0
- package/dist-cli/chunks/{chunk-44CBTM22.js → chunk-KAXZHEKM.js} +1 -1
- package/dist-cli/chunks/{chunk-P5C3UASK.js → chunk-LHDWH7VS.js} +1 -1
- package/dist-cli/chunks/{chunk-H3JVJXOC.js → chunk-N32NCVL2.js} +2 -2
- package/dist-cli/chunks/{chunk-D4JFMCXD.js → chunk-NIZBR7EK.js} +2 -2
- package/dist-cli/chunks/{chunk-XJBPH4JR.js → chunk-NYY36OKU.js} +12 -12
- package/dist-cli/chunks/{chunk-SUZR2SZZ.js → chunk-OXN2PEB7.js} +1 -1
- package/dist-cli/chunks/{chunk-46LRF7PH.js → chunk-PJL25JQV.js} +1 -1
- package/dist-cli/chunks/{chunk-OG5CKIPC.js → chunk-RMW5BO3S.js} +2 -2
- package/dist-cli/chunks/chunk-SHO54NET.js +2 -0
- package/dist-cli/chunks/chunk-SMVJOWSV.js +16 -0
- package/dist-cli/chunks/chunk-TC6V7YFC.js +3 -0
- package/dist-cli/chunks/{chunk-VI3VW5BL.js → chunk-VFDRZNPN.js} +1 -1
- package/dist-cli/chunks/{chunk-AFTHIY3L.js → chunk-YIO6S3R5.js} +1 -1
- package/dist-cli/chunks/{chunk-X2W4IRXK.js → chunk-YLIIVTTQ.js} +2 -2
- package/dist-cli/chunks/chunk-YR7BGGYE.js +2 -0
- package/dist-cli/chunks/chunk-ZEW3RF5Q.js +1 -0
- package/dist-cli/chunks/{compat-5KSMOWLB.js → compat-Y2O2U7FL.js} +2 -2
- package/dist-cli/chunks/{config-NJB6PQHU.js → config-SRBOFUCI.js} +2 -2
- package/dist-cli/chunks/control-PL2V2O6S.js +2 -0
- package/dist-cli/chunks/daemon-IZC32PZW.js +50 -0
- package/dist-cli/chunks/{debug-QVOBTTLP.js → debug-BIDMW2PE.js} +3 -3
- package/dist-cli/chunks/demo-app-registry-5JFOUU3D.js +2 -0
- package/dist-cli/chunks/{detox-ZZSNZL4T.js → detox-B3FDOIS3.js} +2 -2
- package/dist-cli/chunks/{device-PQB3YGHN.js → device-ZZSI363W.js} +2 -2
- package/dist-cli/chunks/drivers-S4NGK4DB.js +2 -0
- package/dist-cli/chunks/{electron-JB26VHOO.js → electron-5YFHXEOI.js} +3 -3
- package/dist-cli/chunks/flow-JJBO6TFY.js +2 -0
- package/dist-cli/chunks/{hints-IGYDXXDS.js → hints-G5HBBV2O.js} +2 -2
- package/dist-cli/chunks/home-paths-VWC3FWA3.js +2 -0
- package/dist-cli/chunks/{inspect-DSU6ELRM.js → inspect-POOPWUQI.js} +66 -62
- package/dist-cli/chunks/install-MP6FHXNZ.js +2 -0
- package/dist-cli/chunks/install-desktop-2MYEI4FM.js +23 -0
- package/dist-cli/chunks/{install-dev-desktop-4DP3UY2X.js → install-dev-desktop-SKH3KEHY.js} +2 -2
- package/dist-cli/chunks/{keys-R5LAPAAL.js → keys-7PNASIQR.js} +2 -2
- package/dist-cli/chunks/{launch-K3WJV4QA.js → launch-JNS47LAQ.js} +3 -3
- package/dist-cli/chunks/login-YWZWUHBS.js +26 -0
- package/dist-cli/chunks/logout-O6SXMSBP.js +2 -0
- package/dist-cli/chunks/{maestro-YALWKKGU.js → maestro-CW6XVUKV.js} +3 -3
- package/dist-cli/chunks/{preview-D35EEONY.js → preview-WGKJO5FS.js} +2 -2
- package/dist-cli/chunks/{profile-MAF7NM5Q.js → profile-SUOBRPIC.js} +2 -2
- package/dist-cli/chunks/{record-ZCPQNGFW.js → record-QPWLYH5R.js} +2 -2
- package/dist-cli/chunks/runtime-KEMO2MSB.js +25 -0
- package/dist-cli/chunks/{screenshot-NQVZYC3C.js → screenshot-JTY46V7G.js} +2 -2
- package/dist-cli/chunks/{screenshot-mode-E45D2ZFH.js → screenshot-mode-7OYBBX6D.js} +2 -2
- package/dist-cli/chunks/{screenshots-I4SQI4DA.js → screenshots-QISKC4GD.js} +2 -2
- package/dist-cli/chunks/server-YSFJAKAV.js +34 -0
- package/dist-cli/chunks/setup-repo-LFB3HBEO.js +2 -0
- package/dist-cli/chunks/{skills-N4U63E5W.js → skills-MO7BFNVM.js} +2 -2
- package/dist-cli/chunks/store-6MFL53I4.js +2 -0
- package/dist-cli/chunks/telemetry-CN42GMVC.js +2 -0
- package/dist-cli/chunks/{test-VBD6N3AR.js → test-XUI3KNNQ.js} +3 -3
- package/dist-cli/chunks/upload-6FUT7AX5.js +2 -0
- package/dist-cli/chunks/{whoami-4K6JGMWH.js → whoami-TQFHY42N.js} +2 -2
- package/dist-lib/agent-daemon-client.cjs +3 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-sessions.cjs +2 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +1 -1
- package/dist-lib/backend-origin.cjs +1 -1
- package/dist-lib/bridge-constants.cjs +1 -1
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/dev-bundle-resolution.cjs +3 -1
- package/dist-lib/home-paths.cjs +29 -3
- package/dist-lib/host/bridge-host.cjs +499 -59
- package/dist-lib/index.cjs +2 -2
- package/dist-lib/metro.cjs +2 -2
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/vite-base.cjs +800 -102
- package/dist-lib/vite.cjs +1 -1
- package/package.json +5 -3
- package/dist-cli/chunks/agent-wrapper-Y7I5QGHM.js +0 -15
- package/dist-cli/chunks/auto-bootstrap-Q7GNLISM.js +0 -2
- package/dist-cli/chunks/chunk-2FPPPJE5.js +0 -2
- package/dist-cli/chunks/chunk-3WPAEUOO.js +0 -1
- package/dist-cli/chunks/chunk-4RYT6AQV.js +0 -16
- package/dist-cli/chunks/chunk-5AG24UFX.js +0 -119
- package/dist-cli/chunks/chunk-BU3TZP4Y.js +0 -11
- package/dist-cli/chunks/chunk-CPMW2QLM.js +0 -1
- package/dist-cli/chunks/chunk-EEBR5YP5.js +0 -62
- package/dist-cli/chunks/chunk-EQ7G3UHS.js +0 -4
- package/dist-cli/chunks/chunk-LV5U7TI4.js +0 -1
- package/dist-cli/chunks/chunk-REYWQVAH.js +0 -2
- package/dist-cli/chunks/chunk-USRNDVQ3.js +0 -2
- package/dist-cli/chunks/chunk-WUYJFYOW.js +0 -2
- package/dist-cli/chunks/chunk-ZSRMXBGK.js +0 -2
- package/dist-cli/chunks/control-2F3AGZAO.js +0 -2
- package/dist-cli/chunks/daemon-MLG65V4S.js +0 -49
- package/dist-cli/chunks/demo-app-registry-XRYNJ4GC.js +0 -2
- package/dist-cli/chunks/drivers-GWDQEGWD.js +0 -2
- package/dist-cli/chunks/flow-7JRQXMFV.js +0 -2
- package/dist-cli/chunks/home-paths-CEGSGQTD.js +0 -2
- package/dist-cli/chunks/install-K6IJKADG.js +0 -65
- package/dist-cli/chunks/install-desktop-SC3LNFFF.js +0 -23
- package/dist-cli/chunks/login-A23PYJAW.js +0 -26
- package/dist-cli/chunks/logout-AJ24PH5O.js +0 -2
- package/dist-cli/chunks/runtime-Z2WIXYUN.js +0 -25
- package/dist-cli/chunks/server-ZUXKJRR5.js +0 -29
- package/dist-cli/chunks/store-4A6X4GBJ.js +0 -2
- package/dist-cli/chunks/upload-Y6FZ5XF2.js +0 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! sootsim v0.
|
|
1
|
+
/*! sootsim v0.1.36 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
|
@@ -35,10 +35,10 @@ __export(bridge_host_exports, {
|
|
|
35
35
|
SootSimBridgeHost: () => SootSimBridgeHost
|
|
36
36
|
});
|
|
37
37
|
module.exports = __toCommonJS(bridge_host_exports);
|
|
38
|
-
var
|
|
39
|
-
var
|
|
38
|
+
var import_child_process4 = require("child_process");
|
|
39
|
+
var import_fs4 = __toESM(require("fs"), 1);
|
|
40
40
|
var import_http2 = require("http");
|
|
41
|
-
var
|
|
41
|
+
var import_path4 = __toESM(require("path"), 1);
|
|
42
42
|
var import_ws = require("ws");
|
|
43
43
|
|
|
44
44
|
// src/bridge-constants.ts
|
|
@@ -51,6 +51,7 @@ var import_path = __toESM(require("path"), 1);
|
|
|
51
51
|
var SOOTSIM_HOME_ENV = "SOOTSIM_HOME";
|
|
52
52
|
var ACTIVE_RUNTIME_FILE = "active";
|
|
53
53
|
var DAEMON_LOCKFILE = "daemon.json";
|
|
54
|
+
var CONFIG_FILE = "config.json";
|
|
54
55
|
var DAEMON_HEARTBEAT_STALE_MS = 3e4;
|
|
55
56
|
function sootsimHomeDir() {
|
|
56
57
|
const override = process.env[SOOTSIM_HOME_ENV];
|
|
@@ -72,6 +73,9 @@ function cacheDir() {
|
|
|
72
73
|
function daemonLockfilePath() {
|
|
73
74
|
return import_path.default.join(sootsimHomeDir(), DAEMON_LOCKFILE);
|
|
74
75
|
}
|
|
76
|
+
function configFilePath() {
|
|
77
|
+
return import_path.default.join(sootsimHomeDir(), CONFIG_FILE);
|
|
78
|
+
}
|
|
75
79
|
function ensureSootsimHome() {
|
|
76
80
|
import_fs.default.mkdirSync(sootsimHomeDir(), { recursive: true });
|
|
77
81
|
import_fs.default.mkdirSync(runtimesDir(), { recursive: true });
|
|
@@ -181,12 +185,223 @@ function removeDaemonLockfile() {
|
|
|
181
185
|
}
|
|
182
186
|
}
|
|
183
187
|
|
|
188
|
+
// src/runtime-delivery.ts
|
|
189
|
+
var import_child_process = require("child_process");
|
|
190
|
+
var import_crypto = __toESM(require("crypto"), 1);
|
|
191
|
+
var import_fs2 = __toESM(require("fs"), 1);
|
|
192
|
+
var import_path2 = __toESM(require("path"), 1);
|
|
193
|
+
var import_stream = require("stream");
|
|
194
|
+
var import_promises = require("stream/promises");
|
|
195
|
+
var DEFAULT_RUNTIME_CDN_ORIGIN = "https://sootbean.com";
|
|
196
|
+
var RUNTIME_CDN_ORIGIN_ENV = "SOOTSIM_CDN_ORIGIN";
|
|
197
|
+
var RUNTIME_CHANNEL_ENV = "SOOTSIM_RUNTIME_CHANNEL";
|
|
198
|
+
function readConfig() {
|
|
199
|
+
try {
|
|
200
|
+
const parsed = JSON.parse(import_fs2.default.readFileSync(configFilePath(), "utf8"));
|
|
201
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
202
|
+
} catch {
|
|
203
|
+
return {};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function resolveRuntimeCdnOrigin(input) {
|
|
207
|
+
const config = readConfig();
|
|
208
|
+
const value = input || process.env[RUNTIME_CDN_ORIGIN_ENV] || config.cdnOrigin || DEFAULT_RUNTIME_CDN_ORIGIN;
|
|
209
|
+
return value.replace(/\/+$/, "");
|
|
210
|
+
}
|
|
211
|
+
function resolveRuntimeChannel(input) {
|
|
212
|
+
const config = readConfig();
|
|
213
|
+
return input || process.env[RUNTIME_CHANNEL_ENV] || config.runtimeChannel || "stable";
|
|
214
|
+
}
|
|
215
|
+
function runtimeManifestUrl(cdnOrigin) {
|
|
216
|
+
const url = new URL(`${resolveRuntimeCdnOrigin(cdnOrigin)}/runtimes/manifest.json`);
|
|
217
|
+
url.searchParams.set("t", String(Date.now()));
|
|
218
|
+
return url.toString();
|
|
219
|
+
}
|
|
220
|
+
function runtimeTarballUrl(version, cdnOrigin) {
|
|
221
|
+
return `${resolveRuntimeCdnOrigin(cdnOrigin)}/runtimes/sootsim-runtime-${version}.tar.gz`;
|
|
222
|
+
}
|
|
223
|
+
async function fetchRuntimeManifest(cdnOrigin) {
|
|
224
|
+
const url = runtimeManifestUrl(cdnOrigin);
|
|
225
|
+
const res = await fetch(url, { headers: { Accept: "application/json" } });
|
|
226
|
+
if (!res.ok) {
|
|
227
|
+
throw new Error(`manifest fetch failed: ${res.status} ${res.statusText} (${url})`);
|
|
228
|
+
}
|
|
229
|
+
return await res.json();
|
|
230
|
+
}
|
|
231
|
+
function resolveRuntimeVersion(manifest, opts = {}) {
|
|
232
|
+
const channel = resolveRuntimeChannel(opts.channel);
|
|
233
|
+
const version = opts.version || manifest.channels[channel]?.latest;
|
|
234
|
+
if (!version) {
|
|
235
|
+
throw new Error(
|
|
236
|
+
`no version specified and channel '${channel}' has no latest entry in the manifest`
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
const entry = manifest.versions[version];
|
|
240
|
+
if (!entry) {
|
|
241
|
+
throw new Error(
|
|
242
|
+
`version ${version} not found in manifest; available: ${Object.keys(manifest.versions).slice(-10).join(", ") || "(none)"}`
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
return { version, channel, entry };
|
|
246
|
+
}
|
|
247
|
+
async function installRuntime(opts = {}) {
|
|
248
|
+
ensureSootsimHome();
|
|
249
|
+
const cdnOrigin = resolveRuntimeCdnOrigin(opts.cdnOrigin);
|
|
250
|
+
const manifest = await fetchRuntimeManifest(cdnOrigin);
|
|
251
|
+
const { version, channel, entry } = resolveRuntimeVersion(manifest, opts);
|
|
252
|
+
const destDir = runtimeDir(version);
|
|
253
|
+
const setActive = opts.setActive !== false;
|
|
254
|
+
if (!opts.force && import_fs2.default.existsSync(import_path2.default.join(destDir, "index.html"))) {
|
|
255
|
+
if (setActive) writeActiveRuntime(version);
|
|
256
|
+
return {
|
|
257
|
+
version,
|
|
258
|
+
channel,
|
|
259
|
+
cdnOrigin,
|
|
260
|
+
runtimeDir: destDir,
|
|
261
|
+
installed: false,
|
|
262
|
+
activated: setActive,
|
|
263
|
+
manifest
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
const tarUrl = entry.tarball || runtimeTarballUrl(version, cdnOrigin);
|
|
267
|
+
const tarCachePath = import_path2.default.join(cacheDir(), `sootsim-runtime-${version}.tar.gz`);
|
|
268
|
+
process.stderr.write(`sootsim: downloading runtime ${version}\u2026
|
|
269
|
+
`);
|
|
270
|
+
await downloadToFile(tarUrl, tarCachePath);
|
|
271
|
+
process.stderr.write(`sootsim: extracting runtime ${version}\u2026
|
|
272
|
+
`);
|
|
273
|
+
const actualSha = await sha256File(tarCachePath);
|
|
274
|
+
if (actualSha !== entry.sha256) {
|
|
275
|
+
import_fs2.default.rmSync(tarCachePath, { force: true });
|
|
276
|
+
throw new Error(
|
|
277
|
+
`sha256 mismatch for runtime ${version}: expected ${entry.sha256}, actual ${actualSha}`
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
const tmpDir = import_path2.default.join(import_path2.default.dirname(destDir), `.installing-${version}-${process.pid}`);
|
|
281
|
+
import_fs2.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
282
|
+
import_fs2.default.mkdirSync(tmpDir, { recursive: true });
|
|
283
|
+
try {
|
|
284
|
+
await extractTarball(tarCachePath, tmpDir);
|
|
285
|
+
if (!import_fs2.default.existsSync(import_path2.default.join(tmpDir, "index.html"))) {
|
|
286
|
+
throw new Error(`extracted tarball for runtime ${version} is missing index.html`);
|
|
287
|
+
}
|
|
288
|
+
import_fs2.default.rmSync(destDir, { recursive: true, force: true });
|
|
289
|
+
import_fs2.default.renameSync(tmpDir, destDir);
|
|
290
|
+
} catch (err) {
|
|
291
|
+
import_fs2.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
292
|
+
throw err;
|
|
293
|
+
}
|
|
294
|
+
if (setActive) writeActiveRuntime(version);
|
|
295
|
+
return {
|
|
296
|
+
version,
|
|
297
|
+
channel,
|
|
298
|
+
cdnOrigin,
|
|
299
|
+
runtimeDir: destDir,
|
|
300
|
+
installed: true,
|
|
301
|
+
activated: setActive,
|
|
302
|
+
manifest
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
async function updateRuntimeToLatest(opts = {}) {
|
|
306
|
+
ensureSootsimHome();
|
|
307
|
+
const cdnOrigin = resolveRuntimeCdnOrigin(opts.cdnOrigin);
|
|
308
|
+
const channel = resolveRuntimeChannel(opts.channel);
|
|
309
|
+
const manifest = await fetchRuntimeManifest(cdnOrigin);
|
|
310
|
+
const latestVersion = manifest.channels[channel]?.latest;
|
|
311
|
+
if (!latestVersion) {
|
|
312
|
+
return {
|
|
313
|
+
checked: true,
|
|
314
|
+
updated: false,
|
|
315
|
+
reason: `channel '${channel}' has no latest runtime`,
|
|
316
|
+
activeVersion: readActiveRuntime()
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
const entry = manifest.versions[latestVersion];
|
|
320
|
+
if (!entry) {
|
|
321
|
+
return {
|
|
322
|
+
checked: true,
|
|
323
|
+
updated: false,
|
|
324
|
+
reason: `manifest is missing version ${latestVersion}`,
|
|
325
|
+
activeVersion: readActiveRuntime(),
|
|
326
|
+
latestVersion
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
const activeVersion = readActiveRuntime();
|
|
330
|
+
const activeDir = activeVersion ? runtimeDir(activeVersion) : null;
|
|
331
|
+
const activeInstalled = activeDir ? import_fs2.default.existsSync(import_path2.default.join(activeDir, "index.html")) : false;
|
|
332
|
+
const shouldInstall = !activeVersion || !activeInstalled || compareSemver(latestVersion, activeVersion) > 0;
|
|
333
|
+
if (!shouldInstall) {
|
|
334
|
+
return {
|
|
335
|
+
checked: true,
|
|
336
|
+
updated: false,
|
|
337
|
+
reason: "active runtime is current",
|
|
338
|
+
activeVersion,
|
|
339
|
+
latestVersion
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
const install = await installRuntime({
|
|
343
|
+
version: latestVersion,
|
|
344
|
+
channel,
|
|
345
|
+
cdnOrigin,
|
|
346
|
+
setActive: false
|
|
347
|
+
});
|
|
348
|
+
return {
|
|
349
|
+
checked: true,
|
|
350
|
+
updated: true,
|
|
351
|
+
activeVersion: latestVersion,
|
|
352
|
+
latestVersion,
|
|
353
|
+
install
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
async function downloadToFile(url, destPath) {
|
|
357
|
+
const res = await fetch(url);
|
|
358
|
+
if (!res.ok || !res.body) {
|
|
359
|
+
throw new Error(`download failed: ${res.status} ${res.statusText} (${url})`);
|
|
360
|
+
}
|
|
361
|
+
import_fs2.default.mkdirSync(import_path2.default.dirname(destPath), { recursive: true });
|
|
362
|
+
const tmp = `${destPath}.partial`;
|
|
363
|
+
try {
|
|
364
|
+
await (0, import_promises.pipeline)(
|
|
365
|
+
import_stream.Readable.fromWeb(res.body),
|
|
366
|
+
import_fs2.default.createWriteStream(tmp)
|
|
367
|
+
);
|
|
368
|
+
import_fs2.default.renameSync(tmp, destPath);
|
|
369
|
+
} catch (err) {
|
|
370
|
+
try {
|
|
371
|
+
import_fs2.default.unlinkSync(tmp);
|
|
372
|
+
} catch {
|
|
373
|
+
}
|
|
374
|
+
throw err;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
function sha256File(filePath) {
|
|
378
|
+
return new Promise((resolve2, reject) => {
|
|
379
|
+
const hash = import_crypto.default.createHash("sha256");
|
|
380
|
+
const stream = import_fs2.default.createReadStream(filePath);
|
|
381
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
382
|
+
stream.on("error", reject);
|
|
383
|
+
stream.on("end", () => resolve2(hash.digest("hex")));
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
function extractTarball(tarPath, destDir) {
|
|
387
|
+
return new Promise((resolve2, reject) => {
|
|
388
|
+
const child = (0, import_child_process.spawn)("tar", ["-xzf", tarPath, "-C", destDir], {
|
|
389
|
+
stdio: ["ignore", "inherit", "inherit"]
|
|
390
|
+
});
|
|
391
|
+
child.on("error", reject);
|
|
392
|
+
child.on("exit", (code) => {
|
|
393
|
+
if (code === 0) resolve2();
|
|
394
|
+
else reject(new Error(`tar exited with code ${code}`));
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
184
399
|
// src/host/agent-host.ts
|
|
185
400
|
var import_node_fs4 = __toESM(require("node:fs"), 1);
|
|
186
401
|
var import_node_path4 = __toESM(require("node:path"), 1);
|
|
187
402
|
|
|
188
403
|
// scripts/dev-server-scanner.ts
|
|
189
|
-
var
|
|
404
|
+
var import_child_process2 = require("child_process");
|
|
190
405
|
var import_http = __toESM(require("http"), 1);
|
|
191
406
|
var import_net = __toESM(require("net"), 1);
|
|
192
407
|
var import_util = require("util");
|
|
@@ -221,11 +436,13 @@ function normalizeNativeDevBundleUrl(bundleUrl) {
|
|
|
221
436
|
try {
|
|
222
437
|
const isAbsolute = isAbsoluteHttpUrl(bundleUrl);
|
|
223
438
|
const parsed = new URL(bundleUrl, "http://soot.local");
|
|
439
|
+
parsed.pathname = parsed.pathname.replace(/\.\.bundle$/, ".bundle");
|
|
224
440
|
if (!isNativeDevBundlePath(parsed.pathname)) return bundleUrl;
|
|
225
441
|
if (!parsed.searchParams.has("dev")) parsed.searchParams.set("dev", "true");
|
|
226
442
|
if (!parsed.searchParams.has("minify")) {
|
|
227
443
|
parsed.searchParams.set("minify", "false");
|
|
228
444
|
}
|
|
445
|
+
parsed.searchParams.delete("transform.bytecode");
|
|
229
446
|
if (isAbsolute) return parsed.toString();
|
|
230
447
|
return `${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
231
448
|
} catch {
|
|
@@ -610,7 +827,7 @@ var APPS = [
|
|
|
610
827
|
];
|
|
611
828
|
|
|
612
829
|
// scripts/dev-server-scanner.ts
|
|
613
|
-
var execP = (0, import_util.promisify)(
|
|
830
|
+
var execP = (0, import_util.promisify)(import_child_process2.exec);
|
|
614
831
|
var TIMEOUT_MS = 250;
|
|
615
832
|
var TCP_GATE_MS = 120;
|
|
616
833
|
function tcpPing(port, timeout = TCP_GATE_MS) {
|
|
@@ -630,10 +847,10 @@ function tcpPing(port, timeout = TCP_GATE_MS) {
|
|
|
630
847
|
sock.connect(port, "localhost");
|
|
631
848
|
});
|
|
632
849
|
}
|
|
633
|
-
function httpGet(port,
|
|
850
|
+
function httpGet(port, path7, method = "GET", timeout = TIMEOUT_MS, headers = {}) {
|
|
634
851
|
return new Promise((resolve2) => {
|
|
635
852
|
const req = import_http.default.request(
|
|
636
|
-
{ hostname: "localhost", port, path:
|
|
853
|
+
{ hostname: "localhost", port, path: path7, method, timeout, headers },
|
|
637
854
|
(res) => {
|
|
638
855
|
let body = "";
|
|
639
856
|
res.on("data", (c) => body += c.toString());
|
|
@@ -766,9 +983,9 @@ function applyManifest(result, manifestRes, buildIconProxyUrl) {
|
|
|
766
983
|
if (client.name) result.projectName = client.name;
|
|
767
984
|
if (client.ios?.bundleIdentifier) result.bundleId = client.ios.bundleIdentifier;
|
|
768
985
|
if (result.framework === "metro" && client.sdkVersion) result.framework = "expo";
|
|
769
|
-
const
|
|
770
|
-
if (
|
|
771
|
-
result.bundleUrl = withRuntimeConfig(result.port,
|
|
986
|
+
const launchUrl2 = manifest?.launchAsset?.url;
|
|
987
|
+
if (launchUrl2 && !result.patched && !isDirectOneBundleUrl(result.bundleUrl)) {
|
|
988
|
+
result.bundleUrl = withRuntimeConfig(result.port, launchUrl2);
|
|
772
989
|
}
|
|
773
990
|
const rawIconUrl = client.iconUrl || client.ios?.iconUrl || client.icon || client.ios?.icon;
|
|
774
991
|
if (rawIconUrl) {
|
|
@@ -837,13 +1054,13 @@ async function probePort(port, buildIconProxyUrl) {
|
|
|
837
1054
|
const manifest = JSON.parse(manifestRes.body);
|
|
838
1055
|
const client = manifest?.extra?.expoClient || {};
|
|
839
1056
|
if (client.name) {
|
|
840
|
-
const
|
|
1057
|
+
const launchUrl2 = manifest?.launchAsset?.url || `http://localhost:${port}/index.bundle?platform=ios&dev=true&hot=true&minify=false`;
|
|
841
1058
|
knownNonPatched.add(port);
|
|
842
1059
|
return applyManifest(
|
|
843
1060
|
{
|
|
844
1061
|
port,
|
|
845
1062
|
framework: "one",
|
|
846
|
-
bundleUrl: withRuntimeConfig(port,
|
|
1063
|
+
bundleUrl: withRuntimeConfig(port, launchUrl2),
|
|
847
1064
|
hmrUrl: `ws://localhost:${port}/hot`,
|
|
848
1065
|
lastSeen: Date.now()
|
|
849
1066
|
},
|
|
@@ -1556,6 +1773,7 @@ async function startSession(opts) {
|
|
|
1556
1773
|
];
|
|
1557
1774
|
if (opts.codexBin) wrapperArgs.push("--codex-bin", opts.codexBin);
|
|
1558
1775
|
if (opts.claudeBin) wrapperArgs.push("--claude-bin", opts.claudeBin);
|
|
1776
|
+
if (opts.freshThread) wrapperArgs.push("--fresh-thread");
|
|
1559
1777
|
if (claudeSessionUuid) {
|
|
1560
1778
|
wrapperArgs.push("--claude-session-uuid", claudeSessionUuid);
|
|
1561
1779
|
}
|
|
@@ -2157,6 +2375,155 @@ function mapFrameworkToProjectFramework(fw) {
|
|
|
2157
2375
|
return "unknown";
|
|
2158
2376
|
}
|
|
2159
2377
|
|
|
2378
|
+
// src/host/open-url.ts
|
|
2379
|
+
var import_child_process3 = require("child_process");
|
|
2380
|
+
var import_fs3 = require("fs");
|
|
2381
|
+
var import_os2 = require("os");
|
|
2382
|
+
var import_path3 = require("path");
|
|
2383
|
+
var MAC_CHROMIUM_CANDIDATES = [
|
|
2384
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
2385
|
+
"~/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
2386
|
+
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
2387
|
+
"~/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
2388
|
+
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
2389
|
+
"~/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
2390
|
+
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
|
|
2391
|
+
"~/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
|
|
2392
|
+
"/Applications/Arc.app/Contents/MacOS/Arc",
|
|
2393
|
+
"~/Applications/Arc.app/Contents/MacOS/Arc"
|
|
2394
|
+
];
|
|
2395
|
+
var LINUX_CHROMIUM_CANDIDATES = [
|
|
2396
|
+
"/usr/bin/google-chrome",
|
|
2397
|
+
"/usr/bin/chromium",
|
|
2398
|
+
"/usr/bin/chromium-browser",
|
|
2399
|
+
"/usr/bin/microsoft-edge",
|
|
2400
|
+
"/usr/bin/brave-browser",
|
|
2401
|
+
"/snap/bin/chromium"
|
|
2402
|
+
];
|
|
2403
|
+
var WINDOWS_CHROMIUM_CANDIDATES = [
|
|
2404
|
+
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
|
|
2405
|
+
"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
|
|
2406
|
+
"C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
|
|
2407
|
+
"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
|
2408
|
+
"C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe",
|
|
2409
|
+
"C:\\Program Files (x86)\\BraveSoftware\\Brave-Browser\\Application\\brave.exe"
|
|
2410
|
+
];
|
|
2411
|
+
var UNIX_CHROMIUM_COMMANDS = [
|
|
2412
|
+
"google-chrome",
|
|
2413
|
+
"chromium",
|
|
2414
|
+
"chromium-browser",
|
|
2415
|
+
"microsoft-edge",
|
|
2416
|
+
"brave-browser"
|
|
2417
|
+
];
|
|
2418
|
+
var WINDOWS_CHROMIUM_COMMANDS = ["chrome", "msedge", "brave"];
|
|
2419
|
+
function expandHome(candidate) {
|
|
2420
|
+
return candidate.startsWith("~/") ? (0, import_path3.join)((0, import_os2.homedir)(), candidate.slice(2)) : candidate;
|
|
2421
|
+
}
|
|
2422
|
+
function firstExisting(candidates) {
|
|
2423
|
+
for (const candidate of candidates) {
|
|
2424
|
+
const expanded = expandHome(candidate);
|
|
2425
|
+
if ((0, import_fs3.existsSync)(expanded)) return expanded;
|
|
2426
|
+
}
|
|
2427
|
+
return null;
|
|
2428
|
+
}
|
|
2429
|
+
function lookupExecutable(command, platform) {
|
|
2430
|
+
try {
|
|
2431
|
+
const tool = platform === "win32" ? "where" : "which";
|
|
2432
|
+
const out = (0, import_child_process3.execFileSync)(tool, [command], {
|
|
2433
|
+
encoding: "utf8",
|
|
2434
|
+
timeout: 1500,
|
|
2435
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2436
|
+
}).trim();
|
|
2437
|
+
return out ? out.split(/\r?\n/)[0] : null;
|
|
2438
|
+
} catch {
|
|
2439
|
+
return null;
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
function resolveChromiumBinary(platform = process.platform) {
|
|
2443
|
+
const envCandidate = process.env.CHROME_PATH || process.env.CHROMIUM_PATH;
|
|
2444
|
+
if (envCandidate && (0, import_fs3.existsSync)(envCandidate)) return envCandidate;
|
|
2445
|
+
const direct = platform === "darwin" ? firstExisting(MAC_CHROMIUM_CANDIDATES) : platform === "win32" ? firstExisting(WINDOWS_CHROMIUM_CANDIDATES) : firstExisting(LINUX_CHROMIUM_CANDIDATES);
|
|
2446
|
+
if (direct) return direct;
|
|
2447
|
+
const commands = platform === "win32" ? WINDOWS_CHROMIUM_COMMANDS : UNIX_CHROMIUM_COMMANDS;
|
|
2448
|
+
for (const command of commands) {
|
|
2449
|
+
const found = lookupExecutable(command, platform);
|
|
2450
|
+
if (found) return found;
|
|
2451
|
+
}
|
|
2452
|
+
return null;
|
|
2453
|
+
}
|
|
2454
|
+
function buildOpenUrlCommand(url, options = {}) {
|
|
2455
|
+
if (!url) throw new Error("openUrl requires a url");
|
|
2456
|
+
const platform = options.platform ?? process.platform;
|
|
2457
|
+
if (options.newWindow) {
|
|
2458
|
+
return buildChromiumUrlCommand(url, { ...options, platform, newWindow: true });
|
|
2459
|
+
}
|
|
2460
|
+
if (platform === "darwin") {
|
|
2461
|
+
return {
|
|
2462
|
+
command: "open",
|
|
2463
|
+
args: options.background === false ? [url] : ["-g", url],
|
|
2464
|
+
via: "system"
|
|
2465
|
+
};
|
|
2466
|
+
}
|
|
2467
|
+
if (platform === "win32") {
|
|
2468
|
+
return {
|
|
2469
|
+
command: "cmd",
|
|
2470
|
+
args: ["/c", "start", "", url],
|
|
2471
|
+
via: "system"
|
|
2472
|
+
};
|
|
2473
|
+
}
|
|
2474
|
+
return {
|
|
2475
|
+
command: "xdg-open",
|
|
2476
|
+
args: [url],
|
|
2477
|
+
via: "system"
|
|
2478
|
+
};
|
|
2479
|
+
}
|
|
2480
|
+
function buildChromiumUrlCommand(url, options = {}) {
|
|
2481
|
+
if (!url) throw new Error("openUrl requires a url");
|
|
2482
|
+
const platform = options.platform ?? process.platform;
|
|
2483
|
+
const chromiumBinary = "chromiumBinary" in options ? options.chromiumBinary : resolveChromiumBinary(platform);
|
|
2484
|
+
if (!chromiumBinary) {
|
|
2485
|
+
throw new Error("browser launch requires Chrome, Chromium, Edge, Brave, or Arc");
|
|
2486
|
+
}
|
|
2487
|
+
const args = [];
|
|
2488
|
+
if (options.headless) {
|
|
2489
|
+
args.push("--headless=new");
|
|
2490
|
+
} else if (options.newWindow !== false) {
|
|
2491
|
+
args.push("--new-window");
|
|
2492
|
+
}
|
|
2493
|
+
args.push(url);
|
|
2494
|
+
return {
|
|
2495
|
+
command: chromiumBinary,
|
|
2496
|
+
args,
|
|
2497
|
+
via: "chromium",
|
|
2498
|
+
target: chromiumBinary
|
|
2499
|
+
};
|
|
2500
|
+
}
|
|
2501
|
+
async function spawnLaunch(command, args, detached) {
|
|
2502
|
+
return new Promise((resolve2, reject) => {
|
|
2503
|
+
const child = (0, import_child_process3.spawn)(command, args, {
|
|
2504
|
+
detached,
|
|
2505
|
+
stdio: "ignore"
|
|
2506
|
+
});
|
|
2507
|
+
child.once("error", reject);
|
|
2508
|
+
child.once("spawn", () => {
|
|
2509
|
+
if (detached) child.unref();
|
|
2510
|
+
resolve2(child.pid);
|
|
2511
|
+
});
|
|
2512
|
+
});
|
|
2513
|
+
}
|
|
2514
|
+
async function launchUrl(url, options = {}) {
|
|
2515
|
+
const command = buildOpenUrlCommand(url, options);
|
|
2516
|
+
const pid = await spawnLaunch(command.command, command.args, options.detached ?? true);
|
|
2517
|
+
return {
|
|
2518
|
+
...command,
|
|
2519
|
+
pid,
|
|
2520
|
+
attachUrl: url
|
|
2521
|
+
};
|
|
2522
|
+
}
|
|
2523
|
+
async function openUrl(url, options = {}) {
|
|
2524
|
+
await launchUrl(url, options);
|
|
2525
|
+
}
|
|
2526
|
+
|
|
2160
2527
|
// src/host/bridge-host.ts
|
|
2161
2528
|
var WRITE_COMMAND_TYPES = /* @__PURE__ */ new Set(["tap", "keyboard", "close"]);
|
|
2162
2529
|
function shouldAcquireLease(msg) {
|
|
@@ -2166,6 +2533,8 @@ function shouldAcquireLease(msg) {
|
|
|
2166
2533
|
return WRITE_COMMAND_TYPES.has(msg.type);
|
|
2167
2534
|
}
|
|
2168
2535
|
var DAEMON_HEARTBEAT_INTERVAL_MS = 5e3;
|
|
2536
|
+
var DEFAULT_RUNTIME_UPDATE_INTERVAL_MS = 60 * 60 * 1e3;
|
|
2537
|
+
var RUNTIME_UPDATE_INTERVAL_ENV = "SOOTSIM_RUNTIME_UPDATE_INTERVAL_MS";
|
|
2169
2538
|
var HTTP_MIME_TYPES = {
|
|
2170
2539
|
".html": "text/html; charset=utf-8",
|
|
2171
2540
|
".js": "application/javascript",
|
|
@@ -2211,6 +2580,10 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
2211
2580
|
static CLI_IDLE_TIMEOUT_MS = 6e4;
|
|
2212
2581
|
static CLI_LEASE_TTL_MS = 6e5;
|
|
2213
2582
|
static USER_ACTIVE_LEASE_TTL_MS = 8e3;
|
|
2583
|
+
// explicit user actions (clicking Boot, focusing the tab to take it over)
|
|
2584
|
+
// hold the tab longer than passive canvas interaction so reconnecting clis
|
|
2585
|
+
// can't immediately reclaim while the user gets oriented.
|
|
2586
|
+
static USER_BOOT_LEASE_TTL_MS = 6e4;
|
|
2214
2587
|
static BROWSER_RECONNECT_TTL_MS = 3e4;
|
|
2215
2588
|
preferredPort;
|
|
2216
2589
|
portFallbackCount;
|
|
@@ -2218,14 +2591,15 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
2218
2591
|
effectivePort = 0;
|
|
2219
2592
|
startedAt = 0;
|
|
2220
2593
|
heartbeatTimer = null;
|
|
2594
|
+
runtimeUpdateTimer = null;
|
|
2595
|
+
runtimeUpdateInFlight = null;
|
|
2221
2596
|
activeRuntimeVersion = null;
|
|
2222
2597
|
activeRuntimeDirPath = null;
|
|
2223
2598
|
constructor(opts = {}) {
|
|
2224
2599
|
this.preferredPort = opts.port || DEFAULT_SOOTSIM_BRIDGE_PORT;
|
|
2225
2600
|
this.port = this.preferredPort;
|
|
2226
2601
|
this.shouldWriteLockfile = opts.writeLockfile === true;
|
|
2227
|
-
|
|
2228
|
-
this.portFallbackCount = Math.max(1, opts.portFallbackCount ?? defaultFallback);
|
|
2602
|
+
this.portFallbackCount = Math.max(1, opts.portFallbackCount ?? 10);
|
|
2229
2603
|
this.openUrlHandler = opts.openUrl;
|
|
2230
2604
|
this.agentHost = new AgentHost({ getExcludePorts: opts.agentScanExcludes });
|
|
2231
2605
|
}
|
|
@@ -2315,6 +2689,8 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
2315
2689
|
const origin = req.headers.origin;
|
|
2316
2690
|
const role = origin ? "browser" : "cli";
|
|
2317
2691
|
let browser = null;
|
|
2692
|
+
ws.on("error", () => {
|
|
2693
|
+
});
|
|
2318
2694
|
this.agentHost.registerSocket(ws);
|
|
2319
2695
|
if (role === "browser") {
|
|
2320
2696
|
browser = {
|
|
@@ -2458,15 +2834,18 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
2458
2834
|
}
|
|
2459
2835
|
}
|
|
2460
2836
|
const hadLease = !!browser.cliLease;
|
|
2461
|
-
browser.cliLease =
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2837
|
+
browser.cliLease = {
|
|
2838
|
+
kind: "user-active",
|
|
2839
|
+
cliSessionKey: "__user-active__",
|
|
2840
|
+
cliLabel: "active user",
|
|
2841
|
+
expiresAt: Date.now() + _SootSimBridgeHost.USER_BOOT_LEASE_TTL_MS
|
|
2842
|
+
};
|
|
2843
|
+
process.stderr.write(
|
|
2844
|
+
`sootsim booted ${booted.length} cli client(s)${hadLease ? " (overrode prior lease)" : ""}; held tab for user [${browser.id}]
|
|
2465
2845
|
`
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
}
|
|
2846
|
+
);
|
|
2847
|
+
this.recordBrowserAction(browser.id, "browser booted cli clients");
|
|
2848
|
+
this.broadcastBrowserClientStates();
|
|
2470
2849
|
return;
|
|
2471
2850
|
}
|
|
2472
2851
|
const internalPending = this.pendingCommands.get(msg.id);
|
|
@@ -2539,7 +2918,7 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
2539
2918
|
if (typeof msg.url !== "string" || !msg.url) {
|
|
2540
2919
|
throw new Error("bridge:open requires a url");
|
|
2541
2920
|
}
|
|
2542
|
-
await this.openUrl(msg.url);
|
|
2921
|
+
await this.openUrl(msg.url, { newWindow: msg.newWindow === true });
|
|
2543
2922
|
if (ws.readyState === import_ws.WebSocket.OPEN) {
|
|
2544
2923
|
ws.send(
|
|
2545
2924
|
JSON.stringify({
|
|
@@ -2703,9 +3082,11 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
2703
3082
|
}
|
|
2704
3083
|
}, DAEMON_HEARTBEAT_INTERVAL_MS);
|
|
2705
3084
|
this.heartbeatTimer.unref();
|
|
3085
|
+
this.startRuntimeUpdater();
|
|
2706
3086
|
}
|
|
2707
3087
|
void this.agentHost.seedOnBoot();
|
|
2708
3088
|
}
|
|
3089
|
+
bootstrapping = true;
|
|
2709
3090
|
buildLockfileSnapshot() {
|
|
2710
3091
|
return {
|
|
2711
3092
|
schema: 1,
|
|
@@ -2716,7 +3097,8 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
2716
3097
|
activeRuntime: this.activeRuntimeVersion,
|
|
2717
3098
|
activeRuntimeDir: this.activeRuntimeDirPath,
|
|
2718
3099
|
startedAt: this.startedAt,
|
|
2719
|
-
heartbeatAt: Date.now()
|
|
3100
|
+
heartbeatAt: Date.now(),
|
|
3101
|
+
bootstrapping: this.bootstrapping
|
|
2720
3102
|
};
|
|
2721
3103
|
}
|
|
2722
3104
|
writeLockfileSnapshot() {
|
|
@@ -2726,6 +3108,61 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
2726
3108
|
this.activeRuntimeVersion = readActiveRuntime();
|
|
2727
3109
|
this.activeRuntimeDirPath = activeRuntimeDir();
|
|
2728
3110
|
}
|
|
3111
|
+
resolveRuntimeUpdateIntervalMs() {
|
|
3112
|
+
const raw = Number(process.env[RUNTIME_UPDATE_INTERVAL_ENV]);
|
|
3113
|
+
if (Number.isFinite(raw) && raw > 0) return Math.max(100, Math.round(raw));
|
|
3114
|
+
return DEFAULT_RUNTIME_UPDATE_INTERVAL_MS;
|
|
3115
|
+
}
|
|
3116
|
+
startRuntimeUpdater() {
|
|
3117
|
+
if (!this.shouldWriteLockfile || this.runtimeUpdateTimer) return;
|
|
3118
|
+
void this.runRuntimeUpdate("startup");
|
|
3119
|
+
const intervalMs = this.resolveRuntimeUpdateIntervalMs();
|
|
3120
|
+
this.runtimeUpdateTimer = setInterval(() => {
|
|
3121
|
+
void this.runRuntimeUpdate("periodic");
|
|
3122
|
+
}, intervalMs);
|
|
3123
|
+
this.runtimeUpdateTimer.unref();
|
|
3124
|
+
}
|
|
3125
|
+
runRuntimeUpdate(reason) {
|
|
3126
|
+
if (this.runtimeUpdateInFlight) return this.runtimeUpdateInFlight;
|
|
3127
|
+
this.runtimeUpdateInFlight = (async () => {
|
|
3128
|
+
try {
|
|
3129
|
+
if (reason === "startup") {
|
|
3130
|
+
process.stderr.write("sootsim: checking for runtime updates\u2026\n");
|
|
3131
|
+
}
|
|
3132
|
+
const result = await updateRuntimeToLatest();
|
|
3133
|
+
if (!result.updated || !result.latestVersion) {
|
|
3134
|
+
if (reason === "startup") {
|
|
3135
|
+
process.stderr.write(
|
|
3136
|
+
`sootsim: runtime ${this.activeRuntimeVersion ?? "(none)"} is current
|
|
3137
|
+
`
|
|
3138
|
+
);
|
|
3139
|
+
}
|
|
3140
|
+
return;
|
|
3141
|
+
}
|
|
3142
|
+
const active = this.setActiveRuntime(result.latestVersion);
|
|
3143
|
+
process.stderr.write(`sootsim runtime updated to ${active.version} (${reason})
|
|
3144
|
+
`);
|
|
3145
|
+
} catch (err) {
|
|
3146
|
+
process.stderr.write(
|
|
3147
|
+
`sootsim runtime update failed (${reason}): ${err instanceof Error ? err.message : String(err)}
|
|
3148
|
+
`
|
|
3149
|
+
);
|
|
3150
|
+
} finally {
|
|
3151
|
+
this.runtimeUpdateInFlight = null;
|
|
3152
|
+
if (reason === "startup" && this.bootstrapping) {
|
|
3153
|
+
this.bootstrapping = false;
|
|
3154
|
+
if (this.shouldWriteLockfile && this.httpServer) {
|
|
3155
|
+
try {
|
|
3156
|
+
this.writeLockfileSnapshot();
|
|
3157
|
+
} catch {
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
process.stderr.write("sootsim: ready\n");
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
3163
|
+
})();
|
|
3164
|
+
return this.runtimeUpdateInFlight;
|
|
3165
|
+
}
|
|
2729
3166
|
/** update the active runtime on disk + in memory. the caller guarantees
|
|
2730
3167
|
* the version directory exists. pushes a runtime:changed message to all
|
|
2731
3168
|
* connected browsers so electron (or any renderer) can reload. */
|
|
@@ -2883,47 +3320,50 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
2883
3320
|
return;
|
|
2884
3321
|
}
|
|
2885
3322
|
}
|
|
2886
|
-
const resolved =
|
|
2887
|
-
const baseWithSep = baseDir.endsWith(
|
|
3323
|
+
const resolved = import_path4.default.resolve(baseDir, "." + rel);
|
|
3324
|
+
const baseWithSep = baseDir.endsWith(import_path4.default.sep) ? baseDir : baseDir + import_path4.default.sep;
|
|
2888
3325
|
if (!resolved.startsWith(baseWithSep) && resolved !== baseDir) {
|
|
2889
3326
|
res.writeHead(403);
|
|
2890
3327
|
res.end("forbidden");
|
|
2891
3328
|
return;
|
|
2892
3329
|
}
|
|
2893
|
-
|
|
3330
|
+
import_fs4.default.realpath(resolved, (realErr, realResolved) => {
|
|
2894
3331
|
const servePath = realErr ? resolved : realResolved;
|
|
2895
|
-
const servePathWithSep = servePath.endsWith(
|
|
3332
|
+
const servePathWithSep = servePath.endsWith(import_path4.default.sep) ? servePath : servePath + import_path4.default.sep;
|
|
2896
3333
|
if (!realErr) {
|
|
2897
3334
|
const realBaseWithSep = (() => {
|
|
2898
3335
|
try {
|
|
2899
|
-
const rb =
|
|
2900
|
-
return rb.endsWith(
|
|
3336
|
+
const rb = import_fs4.default.realpathSync(baseDir);
|
|
3337
|
+
return rb.endsWith(import_path4.default.sep) ? rb : rb + import_path4.default.sep;
|
|
2901
3338
|
} catch {
|
|
2902
3339
|
return baseWithSep;
|
|
2903
3340
|
}
|
|
2904
3341
|
})();
|
|
2905
|
-
if (!servePathWithSep.startsWith(realBaseWithSep) && servePath +
|
|
3342
|
+
if (!servePathWithSep.startsWith(realBaseWithSep) && servePath + import_path4.default.sep !== realBaseWithSep) {
|
|
2906
3343
|
res.writeHead(403);
|
|
2907
3344
|
res.end("forbidden");
|
|
2908
3345
|
return;
|
|
2909
3346
|
}
|
|
2910
3347
|
}
|
|
2911
|
-
|
|
3348
|
+
import_fs4.default.stat(servePath, (err, stats) => {
|
|
2912
3349
|
if (err || !stats?.isFile()) {
|
|
2913
|
-
const ext2 =
|
|
3350
|
+
const ext2 = import_path4.default.extname(rel).toLowerCase();
|
|
2914
3351
|
if (ext2 && ext2 !== ".html") {
|
|
2915
3352
|
res.writeHead(404);
|
|
2916
3353
|
res.end("not found");
|
|
2917
3354
|
return;
|
|
2918
3355
|
}
|
|
2919
|
-
const indexPath =
|
|
2920
|
-
|
|
3356
|
+
const indexPath = import_path4.default.join(baseDir, "index.html");
|
|
3357
|
+
import_fs4.default.readFile(indexPath, (err2, data) => {
|
|
2921
3358
|
if (err2) {
|
|
2922
3359
|
res.writeHead(404);
|
|
2923
3360
|
res.end("not found");
|
|
2924
3361
|
return;
|
|
2925
3362
|
}
|
|
2926
|
-
res.writeHead(200, {
|
|
3363
|
+
res.writeHead(200, {
|
|
3364
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
3365
|
+
"Cache-Control": "no-store"
|
|
3366
|
+
});
|
|
2927
3367
|
if (method === "HEAD") {
|
|
2928
3368
|
res.end();
|
|
2929
3369
|
return;
|
|
@@ -2932,14 +3372,17 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
2932
3372
|
});
|
|
2933
3373
|
return;
|
|
2934
3374
|
}
|
|
2935
|
-
const ext =
|
|
3375
|
+
const ext = import_path4.default.extname(servePath).toLowerCase();
|
|
2936
3376
|
const contentType = HTTP_MIME_TYPES[ext] || "application/octet-stream";
|
|
2937
|
-
res.writeHead(200, {
|
|
3377
|
+
res.writeHead(200, {
|
|
3378
|
+
"Content-Type": contentType,
|
|
3379
|
+
"Cache-Control": "no-store"
|
|
3380
|
+
});
|
|
2938
3381
|
if (method === "HEAD") {
|
|
2939
3382
|
res.end();
|
|
2940
3383
|
return;
|
|
2941
3384
|
}
|
|
2942
|
-
const stream =
|
|
3385
|
+
const stream = import_fs4.default.createReadStream(servePath);
|
|
2943
3386
|
stream.pipe(res);
|
|
2944
3387
|
stream.on("error", () => {
|
|
2945
3388
|
try {
|
|
@@ -3022,7 +3465,7 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
3022
3465
|
const target = `${filePath}${loc}`;
|
|
3023
3466
|
const trySpawn = (cmd, args) => new Promise((resolve2) => {
|
|
3024
3467
|
try {
|
|
3025
|
-
const child = (0,
|
|
3468
|
+
const child = (0, import_child_process4.spawn)(cmd, args, { detached: true, stdio: "ignore" });
|
|
3026
3469
|
let settled = false;
|
|
3027
3470
|
child.on("error", () => {
|
|
3028
3471
|
if (settled) return;
|
|
@@ -3049,26 +3492,12 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
3049
3492
|
if (await trySpawn("code", ["-g", target])) return;
|
|
3050
3493
|
await this.openUrl(filePath);
|
|
3051
3494
|
}
|
|
3052
|
-
async openUrl(url) {
|
|
3495
|
+
async openUrl(url, options = {}) {
|
|
3053
3496
|
if (this.openUrlHandler) {
|
|
3054
|
-
await this.openUrlHandler(url);
|
|
3497
|
+
await this.openUrlHandler(url, options);
|
|
3055
3498
|
return;
|
|
3056
3499
|
}
|
|
3057
|
-
|
|
3058
|
-
const child2 = (0, import_child_process2.spawn)("open", ["-g", url], { detached: true, stdio: "ignore" });
|
|
3059
|
-
child2.unref();
|
|
3060
|
-
return;
|
|
3061
|
-
}
|
|
3062
|
-
if (process.platform === "win32") {
|
|
3063
|
-
const child2 = (0, import_child_process2.spawn)("cmd", ["/c", "start", "", url], {
|
|
3064
|
-
detached: true,
|
|
3065
|
-
stdio: "ignore"
|
|
3066
|
-
});
|
|
3067
|
-
child2.unref();
|
|
3068
|
-
return;
|
|
3069
|
-
}
|
|
3070
|
-
const child = (0, import_child_process2.spawn)("xdg-open", [url], { detached: true, stdio: "ignore" });
|
|
3071
|
-
child.unref();
|
|
3500
|
+
await openUrl(url, options);
|
|
3072
3501
|
}
|
|
3073
3502
|
async close() {
|
|
3074
3503
|
if (this.cliIdleTimer) {
|
|
@@ -3079,6 +3508,10 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
3079
3508
|
clearInterval(this.heartbeatTimer);
|
|
3080
3509
|
this.heartbeatTimer = null;
|
|
3081
3510
|
}
|
|
3511
|
+
if (this.runtimeUpdateTimer) {
|
|
3512
|
+
clearInterval(this.runtimeUpdateTimer);
|
|
3513
|
+
this.runtimeUpdateTimer = null;
|
|
3514
|
+
}
|
|
3082
3515
|
if (this.shouldWriteLockfile) {
|
|
3083
3516
|
try {
|
|
3084
3517
|
removeDaemonLockfile();
|
|
@@ -3215,11 +3648,14 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
3215
3648
|
if (existing && existing.kind === "cli") {
|
|
3216
3649
|
return;
|
|
3217
3650
|
}
|
|
3651
|
+
const now = Date.now();
|
|
3652
|
+
const refreshed = now + _SootSimBridgeHost.USER_ACTIVE_LEASE_TTL_MS;
|
|
3653
|
+
const expiresAt = existing && existing.kind === "user-active" ? Math.max(existing.expiresAt, refreshed) : refreshed;
|
|
3218
3654
|
browser.cliLease = {
|
|
3219
3655
|
kind: "user-active",
|
|
3220
3656
|
cliSessionKey: "__user-active__",
|
|
3221
3657
|
cliLabel: "active user",
|
|
3222
|
-
expiresAt
|
|
3658
|
+
expiresAt
|
|
3223
3659
|
};
|
|
3224
3660
|
this.broadcastBrowserClientStates();
|
|
3225
3661
|
}
|
|
@@ -3434,6 +3870,10 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
3434
3870
|
clearInterval(this.cliIdleTimer);
|
|
3435
3871
|
this.cliIdleTimer = null;
|
|
3436
3872
|
}
|
|
3873
|
+
if (this.runtimeUpdateTimer) {
|
|
3874
|
+
clearInterval(this.runtimeUpdateTimer);
|
|
3875
|
+
this.runtimeUpdateTimer = null;
|
|
3876
|
+
}
|
|
3437
3877
|
const wss = this.wss;
|
|
3438
3878
|
const httpServer = this.httpServer;
|
|
3439
3879
|
this.wss = null;
|