@centralu/win32-x64 0.1.0-beta.12 → 0.1.0-beta.13
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/Centralu/centralu.exe
CHANGED
|
Binary file
|
|
@@ -42881,12 +42881,12 @@ new Anthropic({ apiKey, dangerouslyAllowBrowser: true });
|
|
|
42881
42881
|
import { parseArgs } from "node:util";
|
|
42882
42882
|
import { randomBytes as randomBytes6 } from "node:crypto";
|
|
42883
42883
|
import { homedir as homedir11 } from "node:os";
|
|
42884
|
-
import { dirname as dirname11, join as
|
|
42884
|
+
import { dirname as dirname11, join as join30 } from "node:path";
|
|
42885
42885
|
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync10, writeSync as writeSync3 } from "node:fs";
|
|
42886
42886
|
|
|
42887
42887
|
// packages/protocol/src/brand.ts
|
|
42888
42888
|
var APP_NAME = "Centralu";
|
|
42889
|
-
var APP_VERSION = "0.1.0-beta.
|
|
42889
|
+
var APP_VERSION = "0.1.0-beta.13";
|
|
42890
42890
|
var APP_SLUG = "centralu";
|
|
42891
42891
|
var CLIENT_INFO = { name: APP_SLUG, title: APP_NAME, version: APP_VERSION };
|
|
42892
42892
|
var DATA_DIR = ".centralu";
|
|
@@ -58125,7 +58125,7 @@ function parseUiPreferences(raw) {
|
|
|
58125
58125
|
// packages/protocol/src/machines.ts
|
|
58126
58126
|
var MACHINE_ID_RE = /^[a-z][a-z0-9-]{0,31}$/;
|
|
58127
58127
|
var MachineId = external_exports.string().regex(MACHINE_ID_RE, "Not a machine id");
|
|
58128
|
-
var MachineStatus = external_exports.enum(["connecting", "connected", "unreachable", "not_running", "versions_differ", "refused"]);
|
|
58128
|
+
var MachineStatus = external_exports.enum(["connecting", "connected", "unreachable", "not_running", "versions_differ", "refused", "starting"]);
|
|
58129
58129
|
var MachineSide = external_exports.object({
|
|
58130
58130
|
version: external_exports.string(),
|
|
58131
58131
|
protocolVersion: external_exports.number().int(),
|
|
@@ -58170,7 +58170,14 @@ var MachineInfo = external_exports.object({
|
|
|
58170
58170
|
* remote machine (phase 2) need them to match (docs/agent-host.md §4.7).
|
|
58171
58171
|
*/
|
|
58172
58172
|
localPort: external_exports.number().int().nullable().default(null),
|
|
58173
|
-
sameLocalPort: external_exports.boolean().default(false)
|
|
58173
|
+
sameLocalPort: external_exports.boolean().default(false),
|
|
58174
|
+
/**
|
|
58175
|
+
* The hub started the host there, because it found none running (plan §10.9, decision 7), and how:
|
|
58176
|
+
* `detached` lives on whatever happens to the link; `link_bound` runs in the link's own ssh session,
|
|
58177
|
+
* because that machine blocks starting a process through WMI, and ends when the link does (`note`
|
|
58178
|
+
* says why). Null when the host was already running. Additive; an older hub sends nothing
|
|
58179
|
+
*/
|
|
58180
|
+
hostStarted: external_exports.object({ how: external_exports.enum(["detached", "link_bound"]).catch("detached"), at: external_exports.number(), note: external_exports.string().nullable().default(null) }).nullable().catch(null).default(null)
|
|
58174
58181
|
});
|
|
58175
58182
|
|
|
58176
58183
|
// packages/protocol/src/events.ts
|
|
@@ -60747,6 +60754,14 @@ var RpcMethods = {
|
|
|
60747
60754
|
* question comes again. Refused when the protocols differ.
|
|
60748
60755
|
*/
|
|
60749
60756
|
"machines.acceptVersions": { params: external_exports.object({ machineId: MachineId }), result: MachineInfo },
|
|
60757
|
+
/**
|
|
60758
|
+
* Stops this host the way a signal would: agents, terminals, command runs and app processes
|
|
60759
|
+
* stopped in order, the store closed, then exit. For `centralu serve --stop` (docs/agent-host.md
|
|
60760
|
+
* §4.7): on Windows a launcher can only be killed, and killing it ends the host without its
|
|
60761
|
+
* shutdown. Answers before the host starts to leave. Refused by a host under the app's keeper,
|
|
60762
|
+
* which the app stops (and which would only start the host again).
|
|
60763
|
+
*/
|
|
60764
|
+
"host.stop": { params: external_exports.object({}), result: external_exports.object({ ok: external_exports.literal(true) }) },
|
|
60750
60765
|
/**
|
|
60751
60766
|
* The trash (#204). Only the person reaches these: the RPC is the UI's channel, and neither the agents' tools nor
|
|
60752
60767
|
* the apps' broker has a trash or purge verb. `agents.deleteSession` is the way in.
|
|
@@ -62354,15 +62369,15 @@ function buildViewCsp(csp) {
|
|
|
62354
62369
|
const frame = sanitizeDomains(csp?.frameDomains);
|
|
62355
62370
|
const base2 = sanitizeDomains(csp?.baseUriDomains);
|
|
62356
62371
|
const r = resource.kept.join(" ");
|
|
62357
|
-
const
|
|
62372
|
+
const join31 = (...parts) => parts.filter(Boolean).join(" ");
|
|
62358
62373
|
const policy = [
|
|
62359
62374
|
"default-src 'none'",
|
|
62360
|
-
`script-src ${
|
|
62361
|
-
`style-src ${
|
|
62362
|
-
`img-src ${
|
|
62363
|
-
`font-src ${
|
|
62364
|
-
`media-src ${
|
|
62365
|
-
`worker-src ${
|
|
62375
|
+
`script-src ${join31("'unsafe-inline'", r)}`,
|
|
62376
|
+
`style-src ${join31("'unsafe-inline'", r)}`,
|
|
62377
|
+
`img-src ${join31("data: blob:", r)}`,
|
|
62378
|
+
`font-src ${join31("data:", r)}`,
|
|
62379
|
+
`media-src ${join31("data: blob:", r)}`,
|
|
62380
|
+
`worker-src ${join31("blob:", r)}`,
|
|
62366
62381
|
`connect-src ${connect2.kept.length ? connect2.kept.join(" ") : "'none'"}`,
|
|
62367
62382
|
`frame-src ${frame.kept.length ? frame.kept.join(" ") : "'none'"}`,
|
|
62368
62383
|
"form-action 'none'",
|
|
@@ -62891,6 +62906,7 @@ import { devNull } from "node:os";
|
|
|
62891
62906
|
import { promisify } from "node:util";
|
|
62892
62907
|
|
|
62893
62908
|
// packages/agent-host/src/tool-launch.ts
|
|
62909
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
62894
62910
|
import { existsSync as existsSync3, readFileSync } from "node:fs";
|
|
62895
62911
|
import { win32 as win323 } from "node:path";
|
|
62896
62912
|
|
|
@@ -62987,7 +63003,14 @@ var realDeps = () => ({
|
|
|
62987
63003
|
platform: process.platform,
|
|
62988
63004
|
read: (path) => readFileSync(path, "utf8"),
|
|
62989
63005
|
exists: existsSync3,
|
|
62990
|
-
node: process.execPath
|
|
63006
|
+
node: process.execPath,
|
|
63007
|
+
runJs: (script) => {
|
|
63008
|
+
try {
|
|
63009
|
+
return execFileSync2(process.execPath, [script], { encoding: "utf8", timeout: 1e4, windowsHide: true });
|
|
63010
|
+
} catch {
|
|
63011
|
+
return null;
|
|
63012
|
+
}
|
|
63013
|
+
}
|
|
62991
63014
|
});
|
|
62992
63015
|
function launchFor(path, deps = realDeps()) {
|
|
62993
63016
|
const plain = { command: path, args: [] };
|
|
@@ -62998,10 +63021,10 @@ function launchFor(path, deps = realDeps()) {
|
|
|
62998
63021
|
} catch {
|
|
62999
63022
|
return plain;
|
|
63000
63023
|
}
|
|
63024
|
+
const dir = win323.dirname(path);
|
|
63001
63025
|
const relative10 = shimTarget(text2);
|
|
63002
|
-
|
|
63003
|
-
|
|
63004
|
-
if (!deps.exists(target)) return plain;
|
|
63026
|
+
const target = relative10 ? win323.resolve(dir, relative10) : nodeShimEntry(text2, dir, deps);
|
|
63027
|
+
if (!target || !deps.exists(target)) return plain;
|
|
63005
63028
|
if (/\.(c|m)?js$/i.test(target)) return { command: deps.node, args: [target] };
|
|
63006
63029
|
if (/\.exe$/i.test(target)) return { command: target, args: [] };
|
|
63007
63030
|
return plain;
|
|
@@ -63014,6 +63037,20 @@ function shimTarget(text2) {
|
|
|
63014
63037
|
}
|
|
63015
63038
|
return null;
|
|
63016
63039
|
}
|
|
63040
|
+
function nodeShimEntry(text2, dir, deps) {
|
|
63041
|
+
const started = /^\s*"%NODE_EXE%"\s+"%(\w+)%"\s+%\*\s*$/m.exec(text2)?.[1];
|
|
63042
|
+
if (!started) return null;
|
|
63043
|
+
const set3 = (name) => new RegExp(`^\\s*SET\\s+"${name}=%~dp0\\\\?([^"%]+)"`, "im").exec(text2)?.[1]?.trim() ?? null;
|
|
63044
|
+
const relative10 = set3(started);
|
|
63045
|
+
if (!relative10) return null;
|
|
63046
|
+
const base2 = win323.resolve(dir, relative10);
|
|
63047
|
+
const prefixJs = set3("NPM_PREFIX_JS");
|
|
63048
|
+
if (!prefixJs || !deps.runJs) return base2;
|
|
63049
|
+
const prefix = deps.runJs(win323.resolve(dir, prefixJs))?.trim().split(/\r?\n/).pop()?.trim();
|
|
63050
|
+
if (!prefix || !win323.isAbsolute(prefix)) return base2;
|
|
63051
|
+
const inPrefix = win323.join(prefix, "node_modules", "npm", "bin", win323.basename(base2));
|
|
63052
|
+
return deps.exists(inPrefix) ? inPrefix : base2;
|
|
63053
|
+
}
|
|
63017
63054
|
function resolveCommand(command, env = process.env, deps = realDeps()) {
|
|
63018
63055
|
if (deps.platform !== "win32") return { command, args: [] };
|
|
63019
63056
|
if (/[\\/]/.test(command)) return launchFor(command, deps);
|
|
@@ -63103,6 +63140,31 @@ async function runGit(cwd, args, opts = {}) {
|
|
|
63103
63140
|
return stdout;
|
|
63104
63141
|
}
|
|
63105
63142
|
|
|
63143
|
+
// packages/agent-host/src/os.ts
|
|
63144
|
+
import { homedir as homedir3 } from "node:os";
|
|
63145
|
+
import { posix as posix2, win32 as win324 } from "node:path";
|
|
63146
|
+
var HOST_PLATFORM = process.platform;
|
|
63147
|
+
function hasPs(platform = HOST_PLATFORM) {
|
|
63148
|
+
return platform !== "win32";
|
|
63149
|
+
}
|
|
63150
|
+
function clonesFiles(platform = HOST_PLATFORM) {
|
|
63151
|
+
return platform === "darwin";
|
|
63152
|
+
}
|
|
63153
|
+
function locksRunningPrograms(platform = HOST_PLATFORM) {
|
|
63154
|
+
return platform === "win32";
|
|
63155
|
+
}
|
|
63156
|
+
function pathsOf(platform = HOST_PLATFORM) {
|
|
63157
|
+
return platform === "win32" ? win324 : posix2;
|
|
63158
|
+
}
|
|
63159
|
+
function installedCopyPath(platform = HOST_PLATFORM, env = process.env, home = homedir3()) {
|
|
63160
|
+
if (platform === "darwin") return `/Applications/${APP_NAME}.app`;
|
|
63161
|
+
if (platform === "win32") {
|
|
63162
|
+
const local = env.LOCALAPPDATA || win324.join(home, "AppData", "Local");
|
|
63163
|
+
return win324.join(local, "Programs", APP_NAME);
|
|
63164
|
+
}
|
|
63165
|
+
return posix2.join(home, ".local/share/applications", `${APP_SLUG}.desktop`);
|
|
63166
|
+
}
|
|
63167
|
+
|
|
63106
63168
|
// packages/agent-host/src/dev-services/fs.ts
|
|
63107
63169
|
var MAX_TEXT = 2e6;
|
|
63108
63170
|
var MAX_IMAGE_PREVIEW = IMAGE_PREVIEW_MAX_BYTES;
|
|
@@ -63276,7 +63338,7 @@ async function copyTree(src, dst, clone2 = cloneTree) {
|
|
|
63276
63338
|
cpSync(src, dst, { recursive: true, verbatimSymlinks: true });
|
|
63277
63339
|
}
|
|
63278
63340
|
async function cloneTree(src, dst) {
|
|
63279
|
-
if (
|
|
63341
|
+
if (!clonesFiles()) return false;
|
|
63280
63342
|
return new Promise((done) => {
|
|
63281
63343
|
const p = spawn("/bin/cp", ["-Rc", src, dst], { stdio: "ignore" });
|
|
63282
63344
|
p.on("error", () => done(false));
|
|
@@ -78556,7 +78618,7 @@ var SseError = class extends Error {
|
|
|
78556
78618
|
};
|
|
78557
78619
|
|
|
78558
78620
|
// packages/agent-host/src/dev-services/kill-tree.ts
|
|
78559
|
-
import { execFile as execFile2, execFileSync as
|
|
78621
|
+
import { execFile as execFile2, execFileSync as execFileSync3 } from "node:child_process";
|
|
78560
78622
|
import { join as join6 } from "node:path";
|
|
78561
78623
|
import { promisify as promisify2 } from "node:util";
|
|
78562
78624
|
var KILL_GRACE_MS = 3e3;
|
|
@@ -78614,16 +78676,19 @@ function survivorTargets(rows, first, self2) {
|
|
|
78614
78676
|
}
|
|
78615
78677
|
function snapshot() {
|
|
78616
78678
|
try {
|
|
78617
|
-
return parsePs(
|
|
78679
|
+
return parsePs(execFileSync3("ps", ["-A", "-o", "pid=,ppid=,pgid="], { encoding: "utf8", timeout: 2e3 }));
|
|
78618
78680
|
} catch {
|
|
78619
78681
|
return [];
|
|
78620
78682
|
}
|
|
78621
78683
|
}
|
|
78684
|
+
function hasProcessGroups(platform = process.platform) {
|
|
78685
|
+
return platform !== "win32";
|
|
78686
|
+
}
|
|
78622
78687
|
var REAL_OS = {
|
|
78623
78688
|
platform: process.platform,
|
|
78624
78689
|
taskkill: (pid) => {
|
|
78625
78690
|
const exe = join6(process.env.SystemRoot ?? "C:\\Windows", "System32", "taskkill.exe");
|
|
78626
|
-
|
|
78691
|
+
execFileSync3(exe, ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", timeout: 5e3, windowsHide: true });
|
|
78627
78692
|
}
|
|
78628
78693
|
};
|
|
78629
78694
|
function shootWindows(handle, os2) {
|
|
@@ -79009,7 +79074,7 @@ var AppProcess = class _AppProcess {
|
|
|
79009
79074
|
* has no groups to gain, and its tree is ended with `taskkill /T` (kill-tree.ts). Attached and
|
|
79010
79075
|
* hidden, the app shares the host's windowless console.
|
|
79011
79076
|
*/
|
|
79012
|
-
detached:
|
|
79077
|
+
detached: hasProcessGroups(),
|
|
79013
79078
|
windowsHide: true
|
|
79014
79079
|
});
|
|
79015
79080
|
const proc = new _AppProcess(child, child.stdio[3] ?? null, log, spec.probeTimeoutMs, spawnedAt);
|
|
@@ -79076,7 +79141,7 @@ var AppProcess = class _AppProcess {
|
|
|
79076
79141
|
this.signalOwnGroup();
|
|
79077
79142
|
}
|
|
79078
79143
|
this.fd3?.destroy();
|
|
79079
|
-
const leftovers =
|
|
79144
|
+
const leftovers = !hasProcessGroups() && !this.alive ? this.collectLeftovers() : null;
|
|
79080
79145
|
this.leftovers = leftovers;
|
|
79081
79146
|
if (leftovers && opts.awaitTree) await leftovers;
|
|
79082
79147
|
if (leftovers && !opts.awaitTree) void leftovers.finally(() => this.log.close());
|
|
@@ -99264,7 +99329,7 @@ function orchestratorMemory(reader, sessionId) {
|
|
|
99264
99329
|
}
|
|
99265
99330
|
|
|
99266
99331
|
// packages/agent-host/src/sessions/manager.ts
|
|
99267
|
-
import { homedir as
|
|
99332
|
+
import { homedir as homedir5 } from "node:os";
|
|
99268
99333
|
import { basename as basename2, isAbsolute as isAbsolute6, join as join20, relative as relative7, resolve as resolve4, sep as sep7 } from "node:path";
|
|
99269
99334
|
import { existsSync as existsSync14, statSync as statSync9 } from "node:fs";
|
|
99270
99335
|
import { exec as exec3 } from "node:child_process";
|
|
@@ -102482,8 +102547,8 @@ async function sweepAttachments(maxBytes = ATTACHMENTS_MAX_BYTES) {
|
|
|
102482
102547
|
// packages/agent-host/src/dev-services/message-image.ts
|
|
102483
102548
|
import { constants as constants5 } from "node:fs";
|
|
102484
102549
|
import { open as open2, realpath as realpath3 } from "node:fs/promises";
|
|
102485
|
-
import { homedir as
|
|
102486
|
-
import { posix as
|
|
102550
|
+
import { homedir as homedir4 } from "node:os";
|
|
102551
|
+
import { posix as posix3, sep as sep6, win32 as win325 } from "node:path";
|
|
102487
102552
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
102488
102553
|
var OPEN_FLAGS = constants5.O_RDONLY | constants5.O_NONBLOCK | (constants5.O_NOFOLLOW ?? 0);
|
|
102489
102554
|
var MESSAGE_IMAGE_KINDS = "PNG, JPEG, GIF or WebP";
|
|
@@ -102518,7 +102583,7 @@ function mentions(text2, written) {
|
|
|
102518
102583
|
}
|
|
102519
102584
|
return false;
|
|
102520
102585
|
}
|
|
102521
|
-
function resolveWrittenPath(written, cwd, home =
|
|
102586
|
+
function resolveWrittenPath(written, cwd, home = homedir4(), p = sep6 === "\\" ? win325 : posix3) {
|
|
102522
102587
|
const windows = p.sep === "\\";
|
|
102523
102588
|
let abs;
|
|
102524
102589
|
if (/^file:/i.test(written)) {
|
|
@@ -102693,7 +102758,7 @@ var EXTERNAL_LIST_LIMIT = 200;
|
|
|
102693
102758
|
var builderKey = (ref) => `${ref.projectId ?? "_user"}/${ref.appId}`;
|
|
102694
102759
|
var APP_BUILDERS_KEY = "apps.builders";
|
|
102695
102760
|
var SessionManager = class _SessionManager {
|
|
102696
|
-
constructor(store2, adapters2, emit, endpoint, worktreeRoot = join20(
|
|
102761
|
+
constructor(store2, adapters2, emit, endpoint, worktreeRoot = join20(homedir5(), DATA_DIR, "worktrees"), keeperOpts = {}) {
|
|
102697
102762
|
this.store = store2;
|
|
102698
102763
|
this.adapters = adapters2;
|
|
102699
102764
|
this.emit = emit;
|
|
@@ -106989,11 +107054,10 @@ import { promisify as promisify5 } from "node:util";
|
|
|
106989
107054
|
// packages/agent-host/src/cli-version.ts
|
|
106990
107055
|
import { execFile as execFile4 } from "node:child_process";
|
|
106991
107056
|
import { readFileSync as readFileSync10, realpathSync as realpathSync5 } from "node:fs";
|
|
106992
|
-
import { posix as posix3, win32 as win326 } from "node:path";
|
|
106993
107057
|
import { promisify as promisify4 } from "node:util";
|
|
106994
107058
|
var exec4 = promisify4(execFile4);
|
|
106995
107059
|
var realDeps2 = () => ({
|
|
106996
|
-
platform:
|
|
107060
|
+
platform: HOST_PLATFORM,
|
|
106997
107061
|
which: (name) => whichTool(name),
|
|
106998
107062
|
launch: (path) => launchFor(path),
|
|
106999
107063
|
realpath: (path) => realpathSync5(path),
|
|
@@ -107002,7 +107066,7 @@ var realDeps2 = () => ({
|
|
|
107002
107066
|
});
|
|
107003
107067
|
var PACKAGE_DEPTH = 3;
|
|
107004
107068
|
function npmPackageVersion(file2, packageName, deps) {
|
|
107005
|
-
const path = deps.platform
|
|
107069
|
+
const path = pathsOf(deps.platform);
|
|
107006
107070
|
let real2 = file2;
|
|
107007
107071
|
try {
|
|
107008
107072
|
real2 = deps.realpath(file2);
|
|
@@ -107028,7 +107092,7 @@ async function installedCliVersion(name, packageName, deps = realDeps2()) {
|
|
|
107028
107092
|
const file2 = launch.args[0] ?? launch.command;
|
|
107029
107093
|
const fromNpm = npmPackageVersion(file2, packageName, deps);
|
|
107030
107094
|
if (fromNpm) return fromNpm;
|
|
107031
|
-
const path = deps.platform
|
|
107095
|
+
const path = pathsOf(deps.platform);
|
|
107032
107096
|
let real2 = file2;
|
|
107033
107097
|
try {
|
|
107034
107098
|
real2 = deps.realpath(file2);
|
|
@@ -107036,7 +107100,7 @@ async function installedCliVersion(name, packageName, deps = realDeps2()) {
|
|
|
107036
107100
|
}
|
|
107037
107101
|
const named = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.exec(path.basename(real2).replace(/\.exe$/i, ""));
|
|
107038
107102
|
if (named) return named[0];
|
|
107039
|
-
if (deps.platform
|
|
107103
|
+
if (locksRunningPrograms(deps.platform)) return null;
|
|
107040
107104
|
try {
|
|
107041
107105
|
return parseCliVersion(await deps.run(launch.command, [...launch.args, "--version"]));
|
|
107042
107106
|
} catch {
|
|
@@ -107108,6 +107172,9 @@ function claudeKey(exe, stat6, fs2 = realExeFs) {
|
|
|
107108
107172
|
const raw = version2 ? `${version2}-${stat6.size}` : `${stat6.size}-${Math.floor(stat6.mtimeMs)}`;
|
|
107109
107173
|
return raw.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
107110
107174
|
}
|
|
107175
|
+
function windowsStart(platform = process.platform) {
|
|
107176
|
+
return platform === "win32";
|
|
107177
|
+
}
|
|
107111
107178
|
var ClaudeLinks = class {
|
|
107112
107179
|
constructor(opts) {
|
|
107113
107180
|
this.opts = opts;
|
|
@@ -107121,7 +107188,7 @@ var ClaudeLinks = class {
|
|
|
107121
107188
|
/** The key new sessions start from: the last one prepared */
|
|
107122
107189
|
current = null;
|
|
107123
107190
|
get enabled() {
|
|
107124
|
-
return this.opts.platform
|
|
107191
|
+
return windowsStart(this.opts.platform);
|
|
107125
107192
|
}
|
|
107126
107193
|
base() {
|
|
107127
107194
|
return join25(this.opts.root(), "tools", "claude");
|
|
@@ -107514,7 +107581,6 @@ var LocalAgentProcess = class _LocalAgentProcess extends EventEmitter3 {
|
|
|
107514
107581
|
exitReported = false;
|
|
107515
107582
|
spawnedAt = Date.now();
|
|
107516
107583
|
static spawn(spec, os2) {
|
|
107517
|
-
const platform = os2?.platform ?? process.platform;
|
|
107518
107584
|
const child = spawn4(spec.command, spec.args, {
|
|
107519
107585
|
cwd: spec.cwd,
|
|
107520
107586
|
env: spec.env,
|
|
@@ -107523,7 +107589,7 @@ var LocalAgentProcess = class _LocalAgentProcess extends EventEmitter3 {
|
|
|
107523
107589
|
* A group of its own, which its helpers join. Not on Windows: there `detached` means a process
|
|
107524
107590
|
* with no console, whose every console child opens a visible window (app-process.ts).
|
|
107525
107591
|
*/
|
|
107526
|
-
detached: platform
|
|
107592
|
+
detached: hasProcessGroups(os2?.platform),
|
|
107527
107593
|
windowsHide: true
|
|
107528
107594
|
});
|
|
107529
107595
|
const p = new _LocalAgentProcess(child, os2);
|
|
@@ -107549,8 +107615,9 @@ var LocalAgentProcess = class _LocalAgentProcess extends EventEmitter3 {
|
|
|
107549
107615
|
get ended() {
|
|
107550
107616
|
return this.child.exitCode !== null || this.child.signalCode !== null;
|
|
107551
107617
|
}
|
|
107552
|
-
|
|
107553
|
-
|
|
107618
|
+
/** Whether the CLI got a group of its own (`hasProcessGroups`) */
|
|
107619
|
+
get grouped() {
|
|
107620
|
+
return hasProcessGroups(this.os?.platform);
|
|
107554
107621
|
}
|
|
107555
107622
|
handle() {
|
|
107556
107623
|
return { pid: this.child.pid, kill: (s) => void this.child.kill(s) };
|
|
@@ -107562,7 +107629,7 @@ var LocalAgentProcess = class _LocalAgentProcess extends EventEmitter3 {
|
|
|
107562
107629
|
kill(signal = "SIGTERM") {
|
|
107563
107630
|
if (this.ended || this.child.pid === void 0) return false;
|
|
107564
107631
|
this.killed = true;
|
|
107565
|
-
if (this.
|
|
107632
|
+
if (!this.grouped || signal === "SIGKILL") killTree(this.handle(), "SIGKILL", this.os);
|
|
107566
107633
|
else if (signal === "SIGTERM") stopTree(this.handle(), KILL_GRACE_MS, () => !this.ended, this.os);
|
|
107567
107634
|
else {
|
|
107568
107635
|
try {
|
|
@@ -107588,7 +107655,7 @@ var LocalAgentProcess = class _LocalAgentProcess extends EventEmitter3 {
|
|
|
107588
107655
|
sweep() {
|
|
107589
107656
|
const pid = this.child.pid;
|
|
107590
107657
|
if (pid === void 0) return;
|
|
107591
|
-
if (this.
|
|
107658
|
+
if (this.grouped) return stopGroup(pid, KILL_GRACE_MS);
|
|
107592
107659
|
const root3 = { pid, spawnedAt: this.spawnedAt, exitedAt: Date.now() };
|
|
107593
107660
|
void collectOrphansWindows(root3, this.os?.listProcesses ? { listProcesses: this.os.listProcesses, taskkill: this.os.taskkill } : void 0).then((ended) => {
|
|
107594
107661
|
if (ended.length > 0) console.error(`[agent] ended ${ended.length} process(es) pid ${pid} left running: ${ended.join(", ")}`);
|
|
@@ -109199,18 +109266,19 @@ var ClaudeAdapter = class _ClaudeAdapter {
|
|
|
109199
109266
|
tool = "claude";
|
|
109200
109267
|
launch;
|
|
109201
109268
|
constructor(options = {}) {
|
|
109202
|
-
const platform = options.platform
|
|
109269
|
+
const platform = options.platform;
|
|
109270
|
+
const windows = windowsStart(platform);
|
|
109203
109271
|
const executable = options.executable ?? (() => toolExecutable("claude"));
|
|
109204
109272
|
this.launch = {
|
|
109205
|
-
|
|
109273
|
+
windows,
|
|
109206
109274
|
executable,
|
|
109207
109275
|
links: new ClaudeLinks({ platform, root: options.dataRoot ?? dataRoot, fs: options.fs }),
|
|
109208
|
-
gate: new StartGate(options.startGapMs ?? (
|
|
109276
|
+
gate: new StartGate(options.startGapMs ?? (windows ? WINDOWS_START_GAP_MS : 0)),
|
|
109209
109277
|
retryDelay: options.retryDelay ?? (() => 3e3 + Math.floor(Math.random() * 3e3)),
|
|
109210
109278
|
closing: /* @__PURE__ */ new Set(),
|
|
109211
109279
|
exitGraceMs: options.exitGraceMs ?? 1500
|
|
109212
109280
|
};
|
|
109213
|
-
if (
|
|
109281
|
+
if (windows) {
|
|
109214
109282
|
this.launch.links.keep(executable());
|
|
109215
109283
|
this.launch.links.sweep();
|
|
109216
109284
|
}
|
|
@@ -109266,7 +109334,7 @@ var ClaudeAdapter = class _ClaudeAdapter {
|
|
|
109266
109334
|
const path = whichTool("claude");
|
|
109267
109335
|
try {
|
|
109268
109336
|
let launch = launchFor(path ?? "claude");
|
|
109269
|
-
const exe = this.launch.
|
|
109337
|
+
const exe = this.launch.windows ? this.launch.executable() : null;
|
|
109270
109338
|
if (exe && /\.exe$/i.test(exe)) {
|
|
109271
109339
|
try {
|
|
109272
109340
|
launch = { command: this.launch.links.prepare(exe).path, args: [] };
|
|
@@ -109343,7 +109411,7 @@ var ClaudeAdapter = class _ClaudeAdapter {
|
|
|
109343
109411
|
* nothing waits: the SDK sends them SIGTERM on the host's exit, which they handle.
|
|
109344
109412
|
*/
|
|
109345
109413
|
async settle() {
|
|
109346
|
-
if (this.launch.
|
|
109414
|
+
if (!this.launch.windows || this.launch.closing.size === 0) return;
|
|
109347
109415
|
let timer;
|
|
109348
109416
|
await Promise.race([
|
|
109349
109417
|
Promise.allSettled([...this.launch.closing]),
|
|
@@ -109373,7 +109441,7 @@ function bridgePath() {
|
|
|
109373
109441
|
|
|
109374
109442
|
// packages/agent-host/src/adapters/codex/index.ts
|
|
109375
109443
|
import { existsSync as existsSync17, realpathSync as realpathSync6 } from "node:fs";
|
|
109376
|
-
import { homedir as
|
|
109444
|
+
import { homedir as homedir10 } from "node:os";
|
|
109377
109445
|
import { dirname as dirname9, join as join27, resolve as resolve9 } from "node:path";
|
|
109378
109446
|
import { promisify as promisify6 } from "node:util";
|
|
109379
109447
|
|
|
@@ -109525,10 +109593,10 @@ var CodexClient = class {
|
|
|
109525
109593
|
import { createReadStream as createReadStream2 } from "node:fs";
|
|
109526
109594
|
import { readdir as readdir6 } from "node:fs/promises";
|
|
109527
109595
|
import { createInterface as createInterface2 } from "node:readline";
|
|
109528
|
-
import { homedir as
|
|
109596
|
+
import { homedir as homedir6 } from "node:os";
|
|
109529
109597
|
import { join as join26 } from "node:path";
|
|
109530
109598
|
var MIN_SUMMARY_CHARS = 200;
|
|
109531
|
-
async function findRolloutPath(threadId, sessionsDir = join26(
|
|
109599
|
+
async function findRolloutPath(threadId, sessionsDir = join26(homedir6(), ".codex", "sessions")) {
|
|
109532
109600
|
try {
|
|
109533
109601
|
const suffix = `-${threadId}.jsonl`;
|
|
109534
109602
|
const names = await readdir6(sessionsDir, { recursive: true });
|
|
@@ -109761,7 +109829,7 @@ async function imageEventFromDisk(sessionId, path, maxBytes = IMAGE_MAX_BYTES) {
|
|
|
109761
109829
|
}
|
|
109762
109830
|
|
|
109763
109831
|
// packages/agent-host/src/adapters/codex/usage-client.ts
|
|
109764
|
-
import { homedir as
|
|
109832
|
+
import { homedir as homedir7 } from "node:os";
|
|
109765
109833
|
|
|
109766
109834
|
// packages/agent-host/src/adapters/codex/usage.ts
|
|
109767
109835
|
var str6 = (v) => typeof v === "string" && v ? v : null;
|
|
@@ -109809,7 +109877,7 @@ async function readCodexUsage(command) {
|
|
|
109809
109877
|
{ onNotification: () => {
|
|
109810
109878
|
}, onServerRequest: (r) => client.respond(r.id, {}), onExit: () => {
|
|
109811
109879
|
} },
|
|
109812
|
-
{ cwd:
|
|
109880
|
+
{ cwd: homedir7(), command }
|
|
109813
109881
|
);
|
|
109814
109882
|
try {
|
|
109815
109883
|
await client.request("initialize", {
|
|
@@ -109830,7 +109898,7 @@ async function readCodexUsage(command) {
|
|
|
109830
109898
|
}
|
|
109831
109899
|
|
|
109832
109900
|
// packages/agent-host/src/adapters/codex/models.ts
|
|
109833
|
-
import { homedir as
|
|
109901
|
+
import { homedir as homedir8 } from "node:os";
|
|
109834
109902
|
var MAX_PAGES = 20;
|
|
109835
109903
|
async function collectModels(fetchPage) {
|
|
109836
109904
|
const out = [];
|
|
@@ -109848,7 +109916,7 @@ async function listCodexModels(command) {
|
|
|
109848
109916
|
{ onNotification: () => {
|
|
109849
109917
|
}, onServerRequest: (r) => client.respond(r.id, {}), onExit: () => {
|
|
109850
109918
|
} },
|
|
109851
|
-
{ cwd:
|
|
109919
|
+
{ cwd: homedir8(), command }
|
|
109852
109920
|
);
|
|
109853
109921
|
try {
|
|
109854
109922
|
await client.request("initialize", {
|
|
@@ -109908,7 +109976,7 @@ function toModelOptions(data) {
|
|
|
109908
109976
|
}
|
|
109909
109977
|
|
|
109910
109978
|
// packages/agent-host/src/adapters/codex/notices.ts
|
|
109911
|
-
import { homedir as
|
|
109979
|
+
import { homedir as homedir9 } from "node:os";
|
|
109912
109980
|
var LABELS = {
|
|
109913
109981
|
configWarning: "config warning",
|
|
109914
109982
|
warning: "warning",
|
|
@@ -109923,7 +109991,7 @@ var IGNORED_HEAD = /^Codex is ignoring \d+ unrecognized configuration settings?\
|
|
|
109923
109991
|
var HYDRATION = /^Full-history hydration is deprecated\b/;
|
|
109924
109992
|
var API_METHOD = /`?\b(thread|turn|review)\/[a-z]/;
|
|
109925
109993
|
function tilde(path) {
|
|
109926
|
-
const home =
|
|
109994
|
+
const home = homedir9();
|
|
109927
109995
|
return home && path.startsWith(`${home}/`) ? `~${path.slice(home.length)}` : path;
|
|
109928
109996
|
}
|
|
109929
109997
|
function ignoredSettings(text2) {
|
|
@@ -111601,7 +111669,7 @@ function threadIdOf(res) {
|
|
|
111601
111669
|
}
|
|
111602
111670
|
function codexHome() {
|
|
111603
111671
|
const custom2 = process.env.CODEX_HOME?.trim();
|
|
111604
|
-
return custom2 ? custom2 : join27(
|
|
111672
|
+
return custom2 ? custom2 : join27(homedir10(), ".codex");
|
|
111605
111673
|
}
|
|
111606
111674
|
var CODEX_VERBOSITIES = ["low", "medium", "high"];
|
|
111607
111675
|
var CodexAdapter = class {
|
|
@@ -112000,7 +112068,7 @@ async function cwdsOf(pids) {
|
|
|
112000
112068
|
}
|
|
112001
112069
|
}
|
|
112002
112070
|
async function findStrays(roots, selfPid = process.pid, held3 = []) {
|
|
112003
|
-
if (
|
|
112071
|
+
if (!hasPs() || roots.length === 0) return [];
|
|
112004
112072
|
const rows = await psRows();
|
|
112005
112073
|
const candidates = rows.filter((r) => r.pid > 1 && noTty(r.tty)).map((r) => r.pid);
|
|
112006
112074
|
const cwdOf = await cwdsOf(candidates);
|
|
@@ -112113,7 +112181,7 @@ var toInfo = (h) => ({
|
|
|
112113
112181
|
function sessionIdsOf(panels) {
|
|
112114
112182
|
return panels.flatMap((p) => p.kind === "session" ? [p.sessionId] : []);
|
|
112115
112183
|
}
|
|
112116
|
-
function createRpcHandler(mgr2, adapters2, { terminals: terminals2, updates: updates2, commands, externalApps: externalApps2, views: views2, inlineViews: inlineViews2, heldPids, themes: themes2, agentVersions: agentVersions2, machines } = {}) {
|
|
112184
|
+
function createRpcHandler(mgr2, adapters2, { terminals: terminals2, updates: updates2, commands, externalApps: externalApps2, views: views2, inlineViews: inlineViews2, heldPids, themes: themes2, agentVersions: agentVersions2, machines, stopHost } = {}) {
|
|
112117
112185
|
const requireThemes = () => {
|
|
112118
112186
|
if (!themes2) throw Object.assign(new Error("Theme files are unavailable"), { code: "internal" });
|
|
112119
112187
|
return themes2;
|
|
@@ -112706,7 +112774,14 @@ function createRpcHandler(mgr2, adapters2, { terminals: terminals2, updates: upd
|
|
|
112706
112774
|
"updates.setAutoApply": async (p) => requireUpdates().setAutoApply(RpcMethods["updates.setAutoApply"].params.parse(p).enabled),
|
|
112707
112775
|
// Answers once the install has started, not once it has finished — see the note on
|
|
112708
112776
|
// `updates.apply` in the protocol. The rest arrives as `update_status` events.
|
|
112709
|
-
"updates.apply": async () => requireUpdates().apply()
|
|
112777
|
+
"updates.apply": async () => requireUpdates().apply(),
|
|
112778
|
+
"host.stop": async () => {
|
|
112779
|
+
if (!stopHost) {
|
|
112780
|
+
throw Object.assign(new Error("This host runs under the Centralu app, which stops it: quit the app instead"), { code: "internal" });
|
|
112781
|
+
}
|
|
112782
|
+
setTimeout(stopHost, 50);
|
|
112783
|
+
return { ok: true };
|
|
112784
|
+
}
|
|
112710
112785
|
};
|
|
112711
112786
|
return async (method, params) => {
|
|
112712
112787
|
const name = method;
|
|
@@ -113243,15 +113318,14 @@ var CommandRunner = class {
|
|
|
113243
113318
|
// packages/agent-host/src/updates.ts
|
|
113244
113319
|
import { execFile as execFile8 } from "node:child_process";
|
|
113245
113320
|
import { existsSync as existsSync19 } from "node:fs";
|
|
113246
|
-
import { homedir as homedir10 } from "node:os";
|
|
113247
|
-
import { join as join29 } from "node:path";
|
|
113248
113321
|
var REGISTRY_URL = `https://registry.npmjs.org/${APP_SLUG}/latest`;
|
|
113249
113322
|
var CHECK_INTERVAL_MS = 6 * 60 * 60 * 1e3;
|
|
113250
113323
|
var SCHEDULED_TIMEOUT_MS = 3e3;
|
|
113251
113324
|
var FORCED_TIMEOUT_MS = 8e3;
|
|
113252
113325
|
var INSTALL_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
113253
|
-
function
|
|
113254
|
-
|
|
113326
|
+
function existingInstalledCopy() {
|
|
113327
|
+
const path = installedCopyPath();
|
|
113328
|
+
return existsSync19(path) ? path : null;
|
|
113255
113329
|
}
|
|
113256
113330
|
async function fetchLatestFromRegistry(timeoutMs) {
|
|
113257
113331
|
try {
|
|
@@ -113265,12 +113339,23 @@ async function fetchLatestFromRegistry(timeoutMs) {
|
|
|
113265
113339
|
return { ok: false, reason: "Could not reach the registry \u2014 check the network" };
|
|
113266
113340
|
}
|
|
113267
113341
|
}
|
|
113268
|
-
function
|
|
113269
|
-
|
|
113270
|
-
|
|
113271
|
-
|
|
113272
|
-
|
|
113273
|
-
|
|
113342
|
+
function commandFor(file2, args, resolve10 = (f) => resolveCommand(f)) {
|
|
113343
|
+
const launch = resolve10(file2);
|
|
113344
|
+
return { file: launch.command, args: [...launch.args, ...args] };
|
|
113345
|
+
}
|
|
113346
|
+
function failureDetail(file2, stderr) {
|
|
113347
|
+
const lines = stderr.split(/\r?\n/).map((l) => l.trim()).filter((l) => l && !/complete log of this run/i.test(l));
|
|
113348
|
+
return (file2 === "npm" ? lines.at(-1) : lines[0]) ?? null;
|
|
113349
|
+
}
|
|
113350
|
+
function runCommand(file2, args, resolve10) {
|
|
113351
|
+
const cmd = commandFor(file2, args, resolve10);
|
|
113352
|
+
return new Promise((done, reject) => {
|
|
113353
|
+
execFile8(cmd.file, cmd.args, { timeout: INSTALL_TIMEOUT_MS, windowsHide: true }, (err, _stdout, stderr) => {
|
|
113354
|
+
if (!err) return done();
|
|
113355
|
+
if (err.code === "ENOENT") {
|
|
113356
|
+
return reject(new Error(`${file2} was not found on PATH (${err.message})`));
|
|
113357
|
+
}
|
|
113358
|
+
reject(new Error(failureDetail(file2, String(stderr)) || err.message));
|
|
113274
113359
|
});
|
|
113275
113360
|
});
|
|
113276
113361
|
}
|
|
@@ -113280,6 +113365,7 @@ var UpdateService = class {
|
|
|
113280
113365
|
this.deps = {
|
|
113281
113366
|
fetchLatest: deps.fetchLatest ?? fetchLatestFromRegistry,
|
|
113282
113367
|
run: deps.run ?? runCommand,
|
|
113368
|
+
installedCopy: deps.installedCopy ?? existingInstalledCopy,
|
|
113283
113369
|
now: deps.now ?? Date.now,
|
|
113284
113370
|
// If no place to persist it is given, treat auto-update as on — the same default that main.ts
|
|
113285
113371
|
// passes in.
|
|
@@ -113444,12 +113530,17 @@ var UpdateService = class {
|
|
|
113444
113530
|
return this.current();
|
|
113445
113531
|
}
|
|
113446
113532
|
async runApply(version2) {
|
|
113533
|
+
let packageInstalled = false;
|
|
113534
|
+
const copy = this.deps.installedCopy();
|
|
113447
113535
|
try {
|
|
113448
113536
|
await this.deps.run("npm", ["i", "-g", `${APP_SLUG}@${version2}`]);
|
|
113449
|
-
|
|
113537
|
+
packageInstalled = true;
|
|
113538
|
+
if (copy) await this.deps.run(APP_SLUG, ["install"]);
|
|
113450
113539
|
this.status = { ...this.status, phase: "restart_required", error: null };
|
|
113451
113540
|
} catch (e) {
|
|
113452
|
-
|
|
113541
|
+
const reason = e.message;
|
|
113542
|
+
const error51 = packageInstalled ? `npm installed ${version2}, but the copy at ${copy} was not replaced (${reason}). Quit ${APP_NAME}, then run "${APP_SLUG} install" in a terminal.` : reason;
|
|
113543
|
+
this.status = { ...this.status, phase: "failed", error: error51 };
|
|
113453
113544
|
}
|
|
113454
113545
|
this.emit();
|
|
113455
113546
|
}
|
|
@@ -113682,9 +113773,9 @@ var AgentVersionService = class {
|
|
|
113682
113773
|
};
|
|
113683
113774
|
|
|
113684
113775
|
// packages/agent-host/src/dev-services/instance-lock.ts
|
|
113685
|
-
import { execFileSync as
|
|
113776
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
113686
113777
|
import { readFileSync as readFileSync12, unlinkSync as unlinkSync3, writeFileSync as writeFileSync7 } from "node:fs";
|
|
113687
|
-
import { dirname as dirname10, join as
|
|
113778
|
+
import { dirname as dirname10, join as join29 } from "node:path";
|
|
113688
113779
|
import Database2 from "better-sqlite3";
|
|
113689
113780
|
var OWNERSHIP_FILE = "host-ownership.sqlite";
|
|
113690
113781
|
var held = /* @__PURE__ */ new Set();
|
|
@@ -113698,9 +113789,9 @@ function alive(pid) {
|
|
|
113698
113789
|
}
|
|
113699
113790
|
}
|
|
113700
113791
|
function processStartTime(pid) {
|
|
113701
|
-
if (
|
|
113792
|
+
if (!hasPs()) return null;
|
|
113702
113793
|
try {
|
|
113703
|
-
const out =
|
|
113794
|
+
const out = execFileSync4("ps", ["-o", "lstart=", "-p", String(pid)], {
|
|
113704
113795
|
encoding: "utf8",
|
|
113705
113796
|
timeout: 2e3,
|
|
113706
113797
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -113730,10 +113821,10 @@ function stillHeld(holder, startOf) {
|
|
|
113730
113821
|
const now = startOf(holder.pid);
|
|
113731
113822
|
return now === null || now === holder.started;
|
|
113732
113823
|
}
|
|
113733
|
-
function acquireInstanceLock(dbPath2, startOf = processStartTime, legacyFile =
|
|
113824
|
+
function acquireInstanceLock(dbPath2, startOf = processStartTime, legacyFile = hasPs()) {
|
|
113734
113825
|
if (dbPath2 === ":memory:") return { ok: true, release: () => {
|
|
113735
113826
|
} };
|
|
113736
|
-
const lockPath =
|
|
113827
|
+
const lockPath = join29(dirname10(dbPath2), "host.lock");
|
|
113737
113828
|
const readHolder = () => {
|
|
113738
113829
|
try {
|
|
113739
113830
|
return parseHolder(readFileSync12(lockPath, "utf8"));
|
|
@@ -113741,7 +113832,7 @@ function acquireInstanceLock(dbPath2, startOf = processStartTime, legacyFile = p
|
|
|
113741
113832
|
return null;
|
|
113742
113833
|
}
|
|
113743
113834
|
};
|
|
113744
|
-
const owner = takeOwnership(
|
|
113835
|
+
const owner = takeOwnership(join29(dirname10(dbPath2), OWNERSHIP_FILE));
|
|
113745
113836
|
if (!owner) {
|
|
113746
113837
|
const holder2 = readHolder();
|
|
113747
113838
|
return { ok: false, heldByPid: holder2 && Number.isInteger(holder2.pid) && holder2.pid > 0 ? holder2.pid : null, lockPath };
|
|
@@ -114443,6 +114534,8 @@ var LinkedMachine = class {
|
|
|
114443
114534
|
error = null;
|
|
114444
114535
|
versions = null;
|
|
114445
114536
|
lastConnectedAt = null;
|
|
114537
|
+
/** The host this link started there, if it did (owner decision 7, plan §10.9) */
|
|
114538
|
+
hostStarted = null;
|
|
114446
114539
|
endpoint = null;
|
|
114447
114540
|
client = null;
|
|
114448
114541
|
/** Sessions of the remote the hub shows / hides, by the remote's id. Events of any other session are not passed on */
|
|
@@ -114476,7 +114569,8 @@ var LinkedMachine = class {
|
|
|
114476
114569
|
versions: this.versions,
|
|
114477
114570
|
lastConnectedAt: this.lastConnectedAt,
|
|
114478
114571
|
localPort: this.endpoint && this.endpoint.localPort > 0 ? this.endpoint.localPort : null,
|
|
114479
|
-
sameLocalPort: this.endpoint ? this.endpoint.localPort === this.endpoint.line.port : false
|
|
114572
|
+
sameLocalPort: this.endpoint ? this.endpoint.localPort === this.endpoint.line.port : false,
|
|
114573
|
+
hostStarted: this.hostStarted
|
|
114480
114574
|
};
|
|
114481
114575
|
}
|
|
114482
114576
|
// ── RoutedMachine ───────────────────────────────────────────────────────────────────────
|
|
@@ -114538,17 +114632,27 @@ var LinkedMachine = class {
|
|
|
114538
114632
|
this.client?.close();
|
|
114539
114633
|
this.client = null;
|
|
114540
114634
|
this.setStatus("connecting", null);
|
|
114541
|
-
let endpoint;
|
|
114542
|
-
|
|
114543
|
-
|
|
114544
|
-
|
|
114635
|
+
let endpoint = await this.openTunnel();
|
|
114636
|
+
if (!endpoint) return;
|
|
114637
|
+
if (!endpoint.line.hostRunning && this.tunnel.startHost) {
|
|
114638
|
+
this.setStatus("starting", null);
|
|
114639
|
+
this.deps.log?.(`[links] ${this.name}: no Centralu host is running there; starting one (centralu serve --detach)`);
|
|
114640
|
+
let started;
|
|
114641
|
+
try {
|
|
114642
|
+
started = await this.tunnel.startHost();
|
|
114643
|
+
} catch (err) {
|
|
114644
|
+
if (this.stopped) return;
|
|
114645
|
+
this.setStatus("not_running", `Centralu could not be started on ${this.name}: ${err.message}`);
|
|
114646
|
+
return this.retryLater();
|
|
114647
|
+
}
|
|
114545
114648
|
if (this.stopped) return;
|
|
114546
|
-
this.
|
|
114547
|
-
|
|
114548
|
-
|
|
114549
|
-
|
|
114550
|
-
|
|
114551
|
-
|
|
114649
|
+
this.hostStarted = { ...started, at: Date.now() };
|
|
114650
|
+
this.deps.log?.(
|
|
114651
|
+
started.how === "detached" ? `[links] ${this.name}: started its host; it keeps running after this link ends` : `[links] ${this.name}: its host runs only while this link is up: ${started.note ?? "it cannot be started in the background there"}`
|
|
114652
|
+
);
|
|
114653
|
+
this.setStatus("starting", null);
|
|
114654
|
+
endpoint = await this.openTunnel();
|
|
114655
|
+
if (!endpoint) return;
|
|
114552
114656
|
}
|
|
114553
114657
|
this.endpoint = endpoint;
|
|
114554
114658
|
if (!endpoint.line.hostRunning) {
|
|
@@ -114559,6 +114663,23 @@ var LinkedMachine = class {
|
|
|
114559
114663
|
if (!mayConnect(versions)) return;
|
|
114560
114664
|
this.connectClient(endpoint);
|
|
114561
114665
|
}
|
|
114666
|
+
/** The tunnel's `open`, or null after reporting why not (and when the link was stopped meanwhile) */
|
|
114667
|
+
async openTunnel() {
|
|
114668
|
+
let endpoint;
|
|
114669
|
+
try {
|
|
114670
|
+
endpoint = await this.tunnel.open();
|
|
114671
|
+
} catch (err) {
|
|
114672
|
+
if (this.stopped) return null;
|
|
114673
|
+
this.setStatus("unreachable", err.message);
|
|
114674
|
+
this.retryLater();
|
|
114675
|
+
return null;
|
|
114676
|
+
}
|
|
114677
|
+
if (this.stopped) {
|
|
114678
|
+
await this.tunnel.close();
|
|
114679
|
+
return null;
|
|
114680
|
+
}
|
|
114681
|
+
return endpoint;
|
|
114682
|
+
}
|
|
114562
114683
|
/** Records the versions, and holds the link at `versions_differ` when they do not allow connecting */
|
|
114563
114684
|
check(remote) {
|
|
114564
114685
|
const pending = compareVersions(this.deps.hub, remote, false);
|
|
@@ -114943,6 +115064,8 @@ var ROUTES = {
|
|
|
114943
115064
|
"machines.add": HUB,
|
|
114944
115065
|
"machines.remove": HUB,
|
|
114945
115066
|
"machines.reconnect": HUB,
|
|
115067
|
+
// Stopping a remote host is `centralu serve --stop` over ssh, never a call through the link
|
|
115068
|
+
"host.stop": HUB,
|
|
114946
115069
|
"machines.acceptVersions": HUB,
|
|
114947
115070
|
// ── Callbacks of the hub's own agents ───────────────────────────────────────────────────
|
|
114948
115071
|
"orchestrator.tools": INTERNAL,
|
|
@@ -115175,6 +115298,7 @@ function parseConnectionLine(stdout) {
|
|
|
115175
115298
|
if (typeof o.port !== "number" || typeof o.token !== "string" || typeof o.protocolVersion !== "number") {
|
|
115176
115299
|
throw new Error("The remote answered with an incomplete connection line");
|
|
115177
115300
|
}
|
|
115301
|
+
const install = parseInstall(o.install);
|
|
115178
115302
|
return {
|
|
115179
115303
|
v: 1,
|
|
115180
115304
|
port: o.port,
|
|
@@ -115182,12 +115306,38 @@ function parseConnectionLine(stdout) {
|
|
|
115182
115306
|
version: typeof o.version === "string" ? o.version : "unknown",
|
|
115183
115307
|
protocolVersion: o.protocolVersion,
|
|
115184
115308
|
dataDir: typeof o.dataDir === "string" ? o.dataDir : "",
|
|
115185
|
-
hostRunning: o.hostRunning === true
|
|
115309
|
+
hostRunning: o.hostRunning === true,
|
|
115310
|
+
...install ? { install } : {}
|
|
115186
115311
|
};
|
|
115187
115312
|
}
|
|
115188
115313
|
}
|
|
115189
115314
|
throw new Error("The remote did not print a connection line; its Centralu may predate `centralu serve` (update it there: npm i -g centralu)");
|
|
115190
115315
|
}
|
|
115316
|
+
function parseInstall(raw) {
|
|
115317
|
+
if (!raw || typeof raw !== "object") return null;
|
|
115318
|
+
const o = raw;
|
|
115319
|
+
const pointer = (v) => {
|
|
115320
|
+
const p = v;
|
|
115321
|
+
return p && typeof p.version === "string" && typeof p.node === "string" ? { version: p.version, node: p.node } : null;
|
|
115322
|
+
};
|
|
115323
|
+
return { managed: o.managed === true, current: pointer(o.current), previous: pointer(o.previous), node: typeof o.node === "string" ? o.node : null };
|
|
115324
|
+
}
|
|
115325
|
+
function parseDetachLine(stdout) {
|
|
115326
|
+
const lines = stdout.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
115327
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
115328
|
+
let raw;
|
|
115329
|
+
try {
|
|
115330
|
+
raw = JSON.parse(lines[i]);
|
|
115331
|
+
} catch {
|
|
115332
|
+
continue;
|
|
115333
|
+
}
|
|
115334
|
+
const d2 = raw?.detach;
|
|
115335
|
+
if (!d2 || typeof d2 !== "object") continue;
|
|
115336
|
+
if (d2.ok === true) return { ok: true, line: parseConnectionLine(lines[i]), how: typeof d2.how === "string" ? d2.how : null };
|
|
115337
|
+
return { ok: false, reason: typeof d2.reason === "string" ? d2.reason : "failed", message: typeof d2.message === "string" ? d2.message : "centralu serve --detach failed" };
|
|
115338
|
+
}
|
|
115339
|
+
throw new Error("The remote did not say whether it started");
|
|
115340
|
+
}
|
|
115191
115341
|
var WSL_DISTRO_RE = /^[A-Za-z0-9._-]{1,64}$/;
|
|
115192
115342
|
function powershell(script) {
|
|
115193
115343
|
const full = `[Console]::OutputEncoding=[Text.Encoding]::UTF8
|
|
@@ -115202,18 +115352,27 @@ function wslKeepAlive(spec) {
|
|
|
115202
115352
|
}
|
|
115203
115353
|
var NOT_FOUND2 = "CENTRALU-NOT-FOUND";
|
|
115204
115354
|
function connectionCommand(spec) {
|
|
115355
|
+
return remoteCommand(spec, "serve --connection");
|
|
115356
|
+
}
|
|
115357
|
+
function detachCommand(spec) {
|
|
115358
|
+
return remoteCommand(spec, "serve --detach");
|
|
115359
|
+
}
|
|
115360
|
+
function serveCommand(spec) {
|
|
115361
|
+
return remoteCommand(spec, "serve");
|
|
115362
|
+
}
|
|
115363
|
+
function remoteCommand(spec, args) {
|
|
115205
115364
|
const custom2 = spec.command?.trim() || null;
|
|
115206
115365
|
if (spec.shell === "powershell") {
|
|
115207
|
-
if (custom2) return powershell(`${custom2}
|
|
115366
|
+
if (custom2) return powershell(`${custom2} ${args}`);
|
|
115208
115367
|
return powershell(
|
|
115209
|
-
`if (Get-Command centralu -ErrorAction SilentlyContinue) { & centralu
|
|
115368
|
+
`if (Get-Command centralu -ErrorAction SilentlyContinue) { & centralu ${args} } else { $l = Join-Path $env:USERPROFILE '.centralu\\bin\\centralu.cmd'; if (Test-Path $l) { & $l ${args} } else { '${NOT_FOUND2}' } }`
|
|
115210
115369
|
);
|
|
115211
115370
|
}
|
|
115212
|
-
const lookup = `if command -v centralu >/dev/null 2>&1; then exec centralu
|
|
115213
|
-
if (spec.shell === "posix") return custom2 ? `${custom2}
|
|
115371
|
+
const lookup = `if command -v centralu >/dev/null 2>&1; then exec centralu ${args}; elif [ -x "$HOME/.centralu/bin/centralu" ]; then exec "$HOME/.centralu/bin/centralu" ${args}; else echo ${NOT_FOUND2}; exit 127; fi`;
|
|
115372
|
+
if (spec.shell === "posix") return custom2 ? `${custom2} ${args}` : `sh -c '${lookup}'`;
|
|
115214
115373
|
const distro = spec.wslDistro ?? "";
|
|
115215
115374
|
if (!WSL_DISTRO_RE.test(distro)) throw new Error(`Not a WSL distro name: ${distro || "(none)"}`);
|
|
115216
|
-
const script = custom2 ? `${custom2}
|
|
115375
|
+
const script = custom2 ? `${custom2} ${args}` : `PATH=$(printf %s "$PATH" | tr : '\\n' | grep -v '^/mnt/' | paste -sd: -); ${lookup}`;
|
|
115217
115376
|
const b64 = Buffer.from(script, "utf8").toString("base64");
|
|
115218
115377
|
return powershell(`wsl.exe -d '${distro}' -- bash -lc 'echo ${b64} | base64 -d | bash -l'`);
|
|
115219
115378
|
}
|
|
@@ -115228,6 +115387,8 @@ var SshTunnel = class {
|
|
|
115228
115387
|
asking = /* @__PURE__ */ new Set();
|
|
115229
115388
|
downListeners = [];
|
|
115230
115389
|
closing = false;
|
|
115390
|
+
/** The remote cannot start a host outside a session (WMI blocked): the forward runs it (`startHost`) */
|
|
115391
|
+
linkBound = false;
|
|
115231
115392
|
async open() {
|
|
115232
115393
|
await this.stopChild();
|
|
115233
115394
|
this.closing = false;
|
|
@@ -115236,23 +115397,75 @@ var SshTunnel = class {
|
|
|
115236
115397
|
throw err;
|
|
115237
115398
|
});
|
|
115238
115399
|
this.ifClosed();
|
|
115239
|
-
if (!line.hostRunning) {
|
|
115400
|
+
if (!line.hostRunning && !this.linkBound) {
|
|
115240
115401
|
return { url: "", token: line.token, line, localPort: 0 };
|
|
115241
115402
|
}
|
|
115403
|
+
const hosting = !line.hostRunning;
|
|
115242
115404
|
let localPort = await portFree(line.port) ? line.port : await freePort();
|
|
115243
115405
|
try {
|
|
115244
|
-
await this.forward(localPort, line.port);
|
|
115406
|
+
await this.forward(localPort, line.port, hosting);
|
|
115245
115407
|
} catch (err) {
|
|
115246
115408
|
if (localPort !== line.port) throw err;
|
|
115247
115409
|
this.ifClosed();
|
|
115248
115410
|
localPort = await freePort();
|
|
115249
|
-
await this.forward(localPort, line.port);
|
|
115411
|
+
await this.forward(localPort, line.port, hosting);
|
|
115250
115412
|
}
|
|
115251
115413
|
if (this.closing) {
|
|
115252
115414
|
await this.stopChild();
|
|
115253
115415
|
this.ifClosed();
|
|
115254
115416
|
}
|
|
115255
|
-
|
|
115417
|
+
const served = hosting ? await this.untilServing() : line;
|
|
115418
|
+
return { url: `ws://127.0.0.1:${localPort}`, token: served.token, line: served, localPort };
|
|
115419
|
+
}
|
|
115420
|
+
/**
|
|
115421
|
+
* Starts the remote's host with `centralu serve --detach` (plan §10.4, owner decision 7): it then
|
|
115422
|
+
* runs outside any ssh session and outlives this link. When the remote says WMI process creation
|
|
115423
|
+
* is blocked there (decision 3), every `open` from then on runs `centralu serve` as the forward's
|
|
115424
|
+
* own remote command instead, so the host lives as long as the link.
|
|
115425
|
+
*/
|
|
115426
|
+
async startHost() {
|
|
115427
|
+
this.ifClosed();
|
|
115428
|
+
const remote = this.opts.remote ?? { shell: "posix" };
|
|
115429
|
+
const r = await this.run([...this.config(), "-T", "-o", "BatchMode=yes", "-o", "ConnectTimeout=15", "--", this.opts.target, detachCommand(remote)]);
|
|
115430
|
+
this.ifClosed();
|
|
115431
|
+
if (r.code === 255) throw new Error(`ssh could not reach ${this.opts.target}: ${lastLine(r.stderr) || "no answer"}`);
|
|
115432
|
+
if (r.stdout.includes(NOT_FOUND2) || r.code === 127 && !remote.command) {
|
|
115433
|
+
throw new Error(`Centralu is not installed on ${this.opts.target} (run \`npm i -g centralu\` there, then \`centralu serve\` once)`);
|
|
115434
|
+
}
|
|
115435
|
+
let answer;
|
|
115436
|
+
try {
|
|
115437
|
+
answer = parseDetachLine(r.stdout);
|
|
115438
|
+
} catch (err) {
|
|
115439
|
+
if (/unknown option for serve: --detach/.test(r.stderr)) {
|
|
115440
|
+
throw new Error(`Centralu on ${this.opts.target} is too old to be started from here: run \`centralu serve\` there, or update it (npm i -g centralu)`);
|
|
115441
|
+
}
|
|
115442
|
+
const why = lastLine(r.stderr);
|
|
115443
|
+
throw new Error(`${err.message}${why ? `: ${why}` : r.code ? ` (exit ${r.code})` : ""}`);
|
|
115444
|
+
}
|
|
115445
|
+
if (answer.ok) {
|
|
115446
|
+
this.linkBound = false;
|
|
115447
|
+
return { how: "detached", note: null };
|
|
115448
|
+
}
|
|
115449
|
+
if (answer.reason === "wmi_blocked") {
|
|
115450
|
+
this.linkBound = true;
|
|
115451
|
+
return { how: "link_bound", note: answer.message };
|
|
115452
|
+
}
|
|
115453
|
+
throw new Error(answer.message);
|
|
115454
|
+
}
|
|
115455
|
+
/** The connection line again until the link-bound host answers, or the forward that runs it ends */
|
|
115456
|
+
async untilServing() {
|
|
115457
|
+
const deadline = Date.now() + (this.opts.startTimeoutMs ?? 3e4);
|
|
115458
|
+
for (; ; ) {
|
|
115459
|
+
this.ifClosed();
|
|
115460
|
+
if (!this.child) throw new Error(`The ssh session that runs Centralu on ${this.opts.target} ended before it served`);
|
|
115461
|
+
const line = await this.connectionLine();
|
|
115462
|
+
if (line.hostRunning) return line;
|
|
115463
|
+
if (Date.now() > deadline) {
|
|
115464
|
+
await this.stopChild();
|
|
115465
|
+
throw new Error(`Centralu on ${this.opts.target} did not start serving in time`);
|
|
115466
|
+
}
|
|
115467
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
115468
|
+
}
|
|
115256
115469
|
}
|
|
115257
115470
|
onDown(listener) {
|
|
115258
115471
|
this.downListeners.push(listener);
|
|
@@ -115296,12 +115509,11 @@ var SshTunnel = class {
|
|
|
115296
115509
|
throw new Error(`${err.message}${why ? `: ${why}` : r.code ? ` (exit ${r.code})` : ""}`);
|
|
115297
115510
|
}
|
|
115298
115511
|
}
|
|
115299
|
-
forward(localPort, remotePort) {
|
|
115512
|
+
forward(localPort, remotePort, hosting = false) {
|
|
115300
115513
|
this.ifClosed();
|
|
115301
|
-
const keepAlive = wslKeepAlive(this.opts.remote);
|
|
115514
|
+
const keepAlive = hosting ? serveCommand(this.opts.remote ?? { shell: "posix" }) : wslKeepAlive(this.opts.remote);
|
|
115302
115515
|
const args = [
|
|
115303
115516
|
...this.config(),
|
|
115304
|
-
// A forward alone runs nothing; for WSL it runs the keep-alive instead (`wslKeepAlive`)
|
|
115305
115517
|
...keepAlive ? ["-T"] : ["-N"],
|
|
115306
115518
|
"-o",
|
|
115307
115519
|
"BatchMode=yes",
|
|
@@ -115427,13 +115639,15 @@ function storeMirror(store2) {
|
|
|
115427
115639
|
}
|
|
115428
115640
|
|
|
115429
115641
|
// packages/agent-host/src/main.ts
|
|
115430
|
-
var BUILD = true ? "
|
|
115642
|
+
var BUILD = true ? "b974cd2" : "dev";
|
|
115431
115643
|
var underKeeper = process.env.CC_KEEPER === "1";
|
|
115432
115644
|
var keeperSource = process.env.CC_HOST_SOURCE;
|
|
115433
115645
|
var frontDoor = process.env.CC_FRONT_DOOR;
|
|
115646
|
+
var startedByServe = process.env.CC_SERVE === "1";
|
|
115434
115647
|
delete process.env.CC_KEEPER;
|
|
115435
115648
|
delete process.env.CC_HOST_SOURCE;
|
|
115436
115649
|
delete process.env.CC_FRONT_DOOR;
|
|
115650
|
+
delete process.env.CC_SERVE;
|
|
115437
115651
|
var { values } = parseArgs({
|
|
115438
115652
|
options: {
|
|
115439
115653
|
port: { type: "string", default: "5175" },
|
|
@@ -115455,12 +115669,12 @@ var httpSecret = keeperToken ? deriveHttpSecret(keeperToken) : randomBytes6(32).
|
|
|
115455
115669
|
var dbPath = values.memory ? ":memory:" : values.db ?? defaultDbPath();
|
|
115456
115670
|
function defaultDbPath() {
|
|
115457
115671
|
const isDev = process.env.CC_DEV === "1";
|
|
115458
|
-
const dir =
|
|
115459
|
-
const legacy =
|
|
115672
|
+
const dir = join30(homedir11(), isDev ? DATA_DIR_DEV : DATA_DIR);
|
|
115673
|
+
const legacy = join30(homedir11(), isDev ? DATA_DIR_LEGACY.dev : DATA_DIR_LEGACY.prod);
|
|
115460
115674
|
movedNote = migrateLegacyDataDir(legacy, dir) ? `[agent-host] data folder moved: ${legacy} -> ${dir}` : null;
|
|
115461
115675
|
mkdirSync10(dir, { recursive: true });
|
|
115462
115676
|
process.env.CC_DATA_DIR = dir;
|
|
115463
|
-
return
|
|
115677
|
+
return join30(dir, "store.db");
|
|
115464
115678
|
}
|
|
115465
115679
|
var stopLog = dbPath === ":memory:" ? () => {
|
|
115466
115680
|
} : teeStderrToFile(hostLogPath(dirname11(dbPath)));
|
|
@@ -115541,7 +115755,7 @@ var mgr = new SessionManager(
|
|
|
115541
115755
|
},
|
|
115542
115756
|
() => bridgeAddress(frontDoor, port, token),
|
|
115543
115757
|
// Worktrees are created next to the data folder — dev and the packaged app never touch each other's worktrees
|
|
115544
|
-
|
|
115758
|
+
join30(dirname11(dbPath), "worktrees"),
|
|
115545
115759
|
held2 ? { processes: held2.processes, keptSessions: held2.kept.sessionIds } : {}
|
|
115546
115760
|
);
|
|
115547
115761
|
var appChanges = broadcastAppChanges((e) => server.broadcast(e));
|
|
@@ -115578,7 +115792,7 @@ if (held2) {
|
|
|
115578
115792
|
}
|
|
115579
115793
|
var AUTO_UPDATE_CHECK_KEY = "updates.auto";
|
|
115580
115794
|
var AUTO_APPLY_UPDATES_KEY = "updates.autoApply";
|
|
115581
|
-
var themes = new ThemeFiles(
|
|
115795
|
+
var themes = new ThemeFiles(join30(dataRoot(), "themes"), () => {
|
|
115582
115796
|
try {
|
|
115583
115797
|
server.broadcast({ type: "themes_changed" });
|
|
115584
115798
|
} catch {
|
|
@@ -115651,6 +115865,13 @@ if (underKeeper) {
|
|
|
115651
115865
|
console.error(`[agent-host] could not restore the app views handed over: ${err.stack ?? err}`);
|
|
115652
115866
|
}
|
|
115653
115867
|
}
|
|
115868
|
+
var stopNow = null;
|
|
115869
|
+
var stopAsked = false;
|
|
115870
|
+
var stopFromRequest = () => {
|
|
115871
|
+
console.error("[agent-host] asked to stop (centralu serve --stop)");
|
|
115872
|
+
if (stopNow) stopNow();
|
|
115873
|
+
else stopAsked = true;
|
|
115874
|
+
};
|
|
115654
115875
|
var links = new Links({
|
|
115655
115876
|
hub: { version: APP_VERSION, protocolVersion: PROTOCOL_VERSION, dev: BUILD === "dev" },
|
|
115656
115877
|
broadcast: (e) => server.broadcast(e),
|
|
@@ -115672,7 +115893,8 @@ var router = new Router({
|
|
|
115672
115893
|
views,
|
|
115673
115894
|
inlineViews,
|
|
115674
115895
|
heldPids: held2?.heldPids,
|
|
115675
|
-
machines: links
|
|
115896
|
+
machines: links,
|
|
115897
|
+
stopHost: startedByServe && !underKeeper ? () => stopFromRequest() : void 0
|
|
115676
115898
|
}),
|
|
115677
115899
|
machines: () => links.all()
|
|
115678
115900
|
});
|
|
@@ -115726,7 +115948,7 @@ var stopActivity = underKeeper ? startActivityReport(
|
|
|
115726
115948
|
`)
|
|
115727
115949
|
) : () => {
|
|
115728
115950
|
};
|
|
115729
|
-
var crashLog =
|
|
115951
|
+
var crashLog = join30(dirname11(dbPath), "host-errors.log");
|
|
115730
115952
|
function record2(kind, err) {
|
|
115731
115953
|
const e = err;
|
|
115732
115954
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${kind}: ${e?.stack ?? String(err)}
|
|
@@ -115795,6 +116017,8 @@ process.on("SIGTERM", () => void shutdown(onSignalMode, onSignalMode === "detach
|
|
|
115795
116017
|
process.off("SIGINT", holdSignal);
|
|
115796
116018
|
process.off("SIGTERM", holdSignal);
|
|
115797
116019
|
if (pendingSignal) void shutdown(onSignalMode, onSignalMode === "detach");
|
|
116020
|
+
stopNow = () => void shutdown("stop", false);
|
|
116021
|
+
if (stopAsked) stopNow();
|
|
115798
116022
|
held2?.children.on("stop", () => void shutdown("stop", false));
|
|
115799
116023
|
if (control) {
|
|
115800
116024
|
onDrain(control, {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"node": {
|
|
3
|
+
"version": "24.21.0",
|
|
4
|
+
"signedBy": "5BE8A3F6C8A5C01D106C0AD820B1A390B168D356",
|
|
5
|
+
"releaseKeys": "481637f813e912c4aa3622d7964ab426c97b8e8d",
|
|
6
|
+
"archives": {
|
|
7
|
+
"linux-arm64": {
|
|
8
|
+
"file": "node-v24.21.0-linux-arm64.tar.gz",
|
|
9
|
+
"sha256": "724282c3b43aec998aa9527380465b45d229e021b58035f5f4f63095eabfe5d5"
|
|
10
|
+
},
|
|
11
|
+
"linux-x64": {
|
|
12
|
+
"file": "node-v24.21.0-linux-x64.tar.gz",
|
|
13
|
+
"sha256": "6e1db87ef58b8819e5d5402eff1536491b18edd8eb7bee5ef7897876e88dc5ff"
|
|
14
|
+
},
|
|
15
|
+
"win32-x64": {
|
|
16
|
+
"file": "node-v24.21.0-win-x64.zip",
|
|
17
|
+
"sha256": "158f7685b44de51f6c0df1d153526cbcd3e1bc739a8dfc607721cef75de9e541"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|