@securecoms/agent-bridge 0.30.1 → 0.30.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -6
- package/dist/bin.d.ts +20 -1
- package/dist/bin.js +165 -16
- package/dist/config.d.ts +13 -0
- package/dist/index.js +40 -6
- package/dist/preflight-upgrade.d.cts +0 -2
- package/dist/preflight.cjs +51 -49
- package/dist/preflight.d.cts +14 -0
- package/dist/service/credential-arg.d.ts +23 -0
- package/dist/service/install.d.ts +7 -0
- package/dist/service/resolve-node.d.ts +1 -0
- package/dist/version.d.ts +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -35,13 +35,9 @@ The server still sees ciphertext only. The trust boundary is the machine running
|
|
|
35
35
|
- **Node.js 24 LTS or newer.** The daemon requires >= 24 (node:sqlite stable API).
|
|
36
36
|
- A valid SecureComs agent credential (`agent.json`, `chmod 600`)
|
|
37
37
|
|
|
38
|
-
You don't have to check your Node version yourself. The installer
|
|
38
|
+
You don't have to check your Node version yourself. The installer checks it before anything else runs. On an older Node it says so plainly, and on macOS it offers to install Node 24 from nodejs.org after asking you. That step needs your computer's administrator password. It checks the download's published SHA-256 checksum before running it.
|
|
39
39
|
|
|
40
|
-
To install Node 24
|
|
41
|
-
|
|
42
|
-
1. Official installer (recommended): download the Node 24 LTS installer from [nodejs.org](https://nodejs.org) and run it.
|
|
43
|
-
2. Homebrew (macOS): `brew install node`
|
|
44
|
-
3. nvm: `nvm install 24 && nvm use 24`
|
|
40
|
+
To install Node 24 yourself, download the Node 24 LTS installer from [nodejs.org](https://nodejs.org) and run it. Then run the install command again.
|
|
45
41
|
|
|
46
42
|
Always re-run the install command after upgrading Node — the background service pins the Node path it was installed with.
|
|
47
43
|
|
|
@@ -935,6 +931,44 @@ copied after the owner's next typed message.
|
|
|
935
931
|
|
|
936
932
|
---
|
|
937
933
|
|
|
934
|
+
## Release notes: 0.30.2
|
|
935
|
+
|
|
936
|
+
<!-- [COPY, approved by Eric 2026-10-15] -->
|
|
937
|
+
|
|
938
|
+
0.30.2 fixes the guided Node upgrade, makes reinstall safe while the agent is
|
|
939
|
+
running, and prints plain messages when the agent cannot start.
|
|
940
|
+
|
|
941
|
+
- **The Node upgrade works again on macOS.** When this computer has an older
|
|
942
|
+
Node, install offers to install Node 24 from nodejs.org. Before 0.30.2 this
|
|
943
|
+
step could not find the installer and stopped without saying why. It now
|
|
944
|
+
finds it, checks its published SHA-256 checksum, and asks first:
|
|
945
|
+
`SecureComs needs Node 24. Install it now from nodejs.org? This needs your
|
|
946
|
+
computer's administrator password. [y/N]`. If the step fails after you say
|
|
947
|
+
yes, it prints: `Couldn't install Node 24 from nodejs.org.`
|
|
948
|
+
- **Homebrew is no longer used.** The guided upgrade always uses the
|
|
949
|
+
nodejs.org installer.
|
|
950
|
+
- **Reinstall while the agent runs.** Install now waits up to 10 seconds for
|
|
951
|
+
the running Connector to stop before it starts the new one. Before, the new
|
|
952
|
+
copy could start too early, find the old copy still running, and stay
|
|
953
|
+
stopped. If the old copy does not stop in time, install starts nothing and
|
|
954
|
+
tells you to run install again.
|
|
955
|
+
- **Plain messages when the agent cannot start.** A missing, unreadable or
|
|
956
|
+
invalid key file, or a bad setting in it, now each print one short line
|
|
957
|
+
that says what to do. File paths are printed only in a terminal, not in the
|
|
958
|
+
service log.
|
|
959
|
+
- **No key file found.** If the copied install command finds no key file, it
|
|
960
|
+
prints: `No key file was found in your Downloads folder. Download the key
|
|
961
|
+
file again from SecureComs, or add --credential <path>.` An empty
|
|
962
|
+
`--credential` no longer falls back to the `SECURECOMS_CREDENTIAL`
|
|
963
|
+
environment variable.
|
|
964
|
+
- **Node 24 or newer.** The package now declares `node >= 24`, which the
|
|
965
|
+
Connector already required to run.
|
|
966
|
+
|
|
967
|
+
You do not need to change anything after the upgrade. Run the install command
|
|
968
|
+
again only if you want the new install behavior now.
|
|
969
|
+
|
|
970
|
+
---
|
|
971
|
+
|
|
938
972
|
## Release notes: 0.30.1
|
|
939
973
|
|
|
940
974
|
<!-- [COPY, approved by Eric 2026-10-14] -->
|
package/dist/bin.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* token, its fingerprint, or its path. This prevents the former
|
|
8
8
|
* `BRIDGE_TOKEN=<value>` leak into journald/launchd logs.
|
|
9
9
|
*/
|
|
10
|
-
import { type BridgeConfig } from "./config.js";
|
|
10
|
+
import { type BridgeConfig, type ConfigErrorReason } from "./config.js";
|
|
11
11
|
import { BridgeAgent } from "./bridge-agent.js";
|
|
12
12
|
import { createHttpServer } from "./http-server.js";
|
|
13
13
|
import { attachAgentWebSocket } from "./ws-server.js";
|
|
@@ -98,8 +98,27 @@ export interface RunModeDeps {
|
|
|
98
98
|
exit(code: number): void;
|
|
99
99
|
/** One line of approved user-facing copy (spec §8). */
|
|
100
100
|
print(line: string): void;
|
|
101
|
+
/**
|
|
102
|
+
* Whether stderr is a terminal. Under launchd and systemd stderr IS the
|
|
103
|
+
* service log, so a printed line may carry a path only when this is true
|
|
104
|
+
* (spec 2026-10-15 §5.3 path rule).
|
|
105
|
+
*/
|
|
106
|
+
isTTY: boolean;
|
|
101
107
|
log: ServiceLog;
|
|
102
108
|
}
|
|
109
|
+
export type StartupConfigReason = ConfigErrorReason | "identity_id_invalid" | "bind_refused" | "unknown";
|
|
110
|
+
/**
|
|
111
|
+
* The one human line for a run-mode config refusal (spec 2026-10-15 §5.3,
|
|
112
|
+
* E1-E7 approved; E5 with source=env is the BT-5 draft). The path is shown
|
|
113
|
+
* only on a TTY; `field` is one of the six fixed REQUIRED names. `unknown`
|
|
114
|
+
* has no line. Never built from an error message.
|
|
115
|
+
*/
|
|
116
|
+
export declare function configErrorLine(reason: StartupConfigReason, o: {
|
|
117
|
+
path: string;
|
|
118
|
+
isTTY: boolean;
|
|
119
|
+
field?: string;
|
|
120
|
+
source?: "file" | "env";
|
|
121
|
+
}): string | undefined;
|
|
103
122
|
/**
|
|
104
123
|
* The daemon's run mode (spec §4.1.1). Order, each step gated on the last:
|
|
105
124
|
* 1. loadConfig (+ daemon args, bind check);
|
package/dist/bin.js
CHANGED
|
@@ -8001,8 +8001,30 @@ function ackOnlyGreeting(agentName, callerName) {
|
|
|
8001
8001
|
|
|
8002
8002
|
// src/config.ts
|
|
8003
8003
|
var REQUIRED = ["api_key", "identity_id", "device_id", "signing_private_key", "enclave_id", "api_base"];
|
|
8004
|
+
var CONFIG_ERROR_MESSAGE = {
|
|
8005
|
+
credential_not_found: "credential file not found",
|
|
8006
|
+
credential_unreadable: "credential file could not be read",
|
|
8007
|
+
credential_not_json: "credential file is not valid JSON",
|
|
8008
|
+
credential_missing_field: "credential file missing required field",
|
|
8009
|
+
credential_voice_invalid: "credential file voice config invalid"
|
|
8010
|
+
};
|
|
8011
|
+
var ConfigError = class extends Error {
|
|
8012
|
+
constructor(reason, field, source) {
|
|
8013
|
+
super(CONFIG_ERROR_MESSAGE[reason] + (field ? `: ${field}` : ""));
|
|
8014
|
+
this.reason = reason;
|
|
8015
|
+
this.field = field;
|
|
8016
|
+
this.source = source;
|
|
8017
|
+
this.name = "ConfigError";
|
|
8018
|
+
}
|
|
8019
|
+
};
|
|
8004
8020
|
function loadConfig(opts) {
|
|
8005
|
-
|
|
8021
|
+
let raw;
|
|
8022
|
+
try {
|
|
8023
|
+
raw = readFileSync2(opts.credentialPath, "utf8");
|
|
8024
|
+
} catch (e) {
|
|
8025
|
+
const code = e?.code;
|
|
8026
|
+
throw new ConfigError(code === "ENOENT" ? "credential_not_found" : "credential_unreadable");
|
|
8027
|
+
}
|
|
8006
8028
|
try {
|
|
8007
8029
|
const mode = statSync(opts.credentialPath).mode & 511;
|
|
8008
8030
|
if (mode & 63) writeServiceLog("warn", "config.credential_permissions_unsafe");
|
|
@@ -8012,9 +8034,9 @@ function loadConfig(opts) {
|
|
|
8012
8034
|
try {
|
|
8013
8035
|
json2 = JSON.parse(raw);
|
|
8014
8036
|
} catch {
|
|
8015
|
-
throw new
|
|
8037
|
+
throw new ConfigError("credential_not_json");
|
|
8016
8038
|
}
|
|
8017
|
-
for (const k of REQUIRED) if (typeof json2[k] !== "string" || !json2[k]) throw new
|
|
8039
|
+
for (const k of REQUIRED) if (typeof json2[k] !== "string" || !json2[k]) throw new ConfigError("credential_missing_field", k);
|
|
8018
8040
|
const storePath = opts.storePath ?? join2(dirname2(opts.credentialPath), "bridge-state.db");
|
|
8019
8041
|
const ownerGrantPin = loadOwnerGrantPin({ credentialPath: opts.credentialPath, credentialJson: json2 });
|
|
8020
8042
|
writeServiceLog(ownerGrantPin.state === "ok" || ownerGrantPin.state === "no_pin" ? "info" : "warn", "authz.pin", {
|
|
@@ -8052,8 +8074,14 @@ function loadConfig(opts) {
|
|
|
8052
8074
|
let voice;
|
|
8053
8075
|
try {
|
|
8054
8076
|
voice = parseVoiceConfig(json2.voice, process.env);
|
|
8055
|
-
} catch
|
|
8056
|
-
|
|
8077
|
+
} catch {
|
|
8078
|
+
let source = "env";
|
|
8079
|
+
try {
|
|
8080
|
+
parseVoiceConfig(json2.voice, {});
|
|
8081
|
+
} catch {
|
|
8082
|
+
source = "file";
|
|
8083
|
+
}
|
|
8084
|
+
throw new ConfigError("credential_voice_invalid", void 0, source);
|
|
8057
8085
|
}
|
|
8058
8086
|
const modelSource = json2.memory?.model_source === "import_only" ? "import_only" : "download";
|
|
8059
8087
|
const enabledRaw = json2.memory?.enabled;
|
|
@@ -18169,7 +18197,13 @@ var CODES = [
|
|
|
18169
18197
|
// lookup missed and before any write, so a retry can never land: the
|
|
18170
18198
|
// pending commit is dropped instead of being resent forever. The same pair
|
|
18171
18199
|
// answers an application send into a deleted channel (#325 F5).
|
|
18172
|
-
{ status: 409, code: "channel_unavailable" }
|
|
18200
|
+
{ status: 409, code: "channel_unavailable" },
|
|
18201
|
+
// ADR-0023 B.1: an Add or Welcome into a humans-only enclave's group naming
|
|
18202
|
+
// a device that is not provably a person's. Refused after the replay lookup
|
|
18203
|
+
// missed and before any write, so nothing landed. Definite: the committer's
|
|
18204
|
+
// work cannot name an agent in a humans-only enclave (admission refuses
|
|
18205
|
+
// one), so dropping the commit starts no resend loop.
|
|
18206
|
+
{ status: 403, code: "humans_only_enclave" }
|
|
18173
18207
|
];
|
|
18174
18208
|
var DISTRIBUTE_DEFINITE_CODES = Object.freeze(CODES.map((c) => Object.freeze({ ...c })));
|
|
18175
18209
|
var DEFINITE_KEYS = new Set(DISTRIBUTE_DEFINITE_CODES.map((c) => `${c.status} ${c.code}`));
|
|
@@ -44197,7 +44231,7 @@ function slugify(s) {
|
|
|
44197
44231
|
function deriveServiceConfig(input) {
|
|
44198
44232
|
if (isVersionEmbeddedNodePath(input.execPath)) {
|
|
44199
44233
|
throw new Error(
|
|
44200
|
-
`
|
|
44234
|
+
`The Node at ${input.execPath} moves when it is upgraded, so the background service can't use it. Install Node ${REQUIRED_NODE_MAJOR} from https://nodejs.org, then run the install command again.`
|
|
44201
44235
|
);
|
|
44202
44236
|
}
|
|
44203
44237
|
const stem = input.label ?? basename5(input.credentialPath, extname(input.credentialPath));
|
|
@@ -44278,6 +44312,26 @@ WantedBy=default.target
|
|
|
44278
44312
|
// src/service/install.ts
|
|
44279
44313
|
var ServiceUnsupportedError = class extends Error {
|
|
44280
44314
|
};
|
|
44315
|
+
var PREVIOUS_EXIT_WAIT_MS = 1e4;
|
|
44316
|
+
var PREVIOUS_EXIT_POLL_MS = 100;
|
|
44317
|
+
function launchdPid(deps, target) {
|
|
44318
|
+
const r = deps.run("launchctl", ["print", target]);
|
|
44319
|
+
if (r.status !== 0) return void 0;
|
|
44320
|
+
const m = /^\s*pid = (\d+)\s*$/m.exec(r.stdout);
|
|
44321
|
+
const pid = m ? Number(m[1]) : NaN;
|
|
44322
|
+
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
44323
|
+
}
|
|
44324
|
+
function waitForPreviousExit(deps, pid) {
|
|
44325
|
+
const deadline = deps.now() + PREVIOUS_EXIT_WAIT_MS;
|
|
44326
|
+
while (deps.pidAlive(pid)) {
|
|
44327
|
+
if (deps.now() >= deadline) {
|
|
44328
|
+
throw new Error(
|
|
44329
|
+
`the previous bridge (pid ${pid}) did not exit within ${PREVIOUS_EXIT_WAIT_MS / 1e3} s of launchctl bootout, so a new copy would find the instance lock held and stay down. Wait for that process to exit (check with: ps -p ${pid}), then run install again.`
|
|
44330
|
+
);
|
|
44331
|
+
}
|
|
44332
|
+
deps.sleepMs(PREVIOUS_EXIT_POLL_MS);
|
|
44333
|
+
}
|
|
44334
|
+
}
|
|
44281
44335
|
function tokenHint(input) {
|
|
44282
44336
|
return `${dirname8(input.credentialPath)}/bridge-token`;
|
|
44283
44337
|
}
|
|
@@ -44290,7 +44344,10 @@ function installService(input, deps) {
|
|
|
44290
44344
|
deps.mkdirp(dirname8(cfg.plistPath));
|
|
44291
44345
|
deps.mkdirp(dirname8(cfg.logPath));
|
|
44292
44346
|
deps.writeFile(cfg.plistPath, renderLaunchdPlist(cfg));
|
|
44293
|
-
|
|
44347
|
+
const target = `gui/${cfg.uid}/${cfg.label}`;
|
|
44348
|
+
const previousPid = launchdPid(deps, target);
|
|
44349
|
+
deps.run("launchctl", ["bootout", target]);
|
|
44350
|
+
if (previousPid !== void 0) waitForPreviousExit(deps, previousPid);
|
|
44294
44351
|
requireOk(deps.run("launchctl", ["bootstrap", `gui/${cfg.uid}`, cfg.plistPath]), "launchctl bootstrap");
|
|
44295
44352
|
deps.run("launchctl", ["enable", `gui/${cfg.uid}/${cfg.label}`]);
|
|
44296
44353
|
requireOk(deps.run("launchctl", ["kickstart", "-k", `gui/${cfg.uid}/${cfg.label}`]), "launchctl kickstart");
|
|
@@ -45617,6 +45674,20 @@ function runPinCli(argv, io) {
|
|
|
45617
45674
|
return 1;
|
|
45618
45675
|
}
|
|
45619
45676
|
|
|
45677
|
+
// src/service/credential-arg.ts
|
|
45678
|
+
function readCredentialArg(argv, env) {
|
|
45679
|
+
const i = argv.indexOf("--credential");
|
|
45680
|
+
if (i >= 0) {
|
|
45681
|
+
const v = argv[i + 1];
|
|
45682
|
+
return v ? { kind: "path", path: v } : { kind: "empty_flag" };
|
|
45683
|
+
}
|
|
45684
|
+
const fromEnv = env.SECURECOMS_CREDENTIAL;
|
|
45685
|
+
return fromEnv ? { kind: "path", path: fromEnv } : { kind: "absent" };
|
|
45686
|
+
}
|
|
45687
|
+
function noCredentialLine(verb) {
|
|
45688
|
+
return verb === "run" ? "No key file was given. If you used the copied run command, no key file for this agent was found in your Downloads folder." : "No key file was found in your Downloads folder. Download the key file again from SecureComs, or add --credential <path>.";
|
|
45689
|
+
}
|
|
45690
|
+
|
|
45620
45691
|
// src/bin.ts
|
|
45621
45692
|
function nodeMajorOk(version) {
|
|
45622
45693
|
return Number(version.split(".")[0]) >= 24;
|
|
@@ -45653,15 +45724,22 @@ function realServiceDeps() {
|
|
|
45653
45724
|
removeFile: (p) => rmSync8(p, { force: true }),
|
|
45654
45725
|
mkdirp: (p) => mkdirSync8(p, { recursive: true }),
|
|
45655
45726
|
fileExists: (p) => existsSync7(p),
|
|
45656
|
-
user: userInfo().username
|
|
45727
|
+
user: userInfo().username,
|
|
45728
|
+
pidAlive: pidAliveByKill,
|
|
45729
|
+
// installService is synchronous; Atomics.wait is a real blocking sleep.
|
|
45730
|
+
sleepMs: (ms) => {
|
|
45731
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
45732
|
+
},
|
|
45733
|
+
now: () => Date.now()
|
|
45657
45734
|
};
|
|
45658
45735
|
}
|
|
45659
45736
|
function chooseInstallExecPath(runningExecPath, override, deps) {
|
|
45660
45737
|
if (override) return override;
|
|
45661
45738
|
const r = resolveStableNodePath(runningExecPath, deps);
|
|
45662
45739
|
if (!r.stable) {
|
|
45740
|
+
const list = STABLE_LOCATIONS.slice(0, -1).join(", ") + " or " + STABLE_LOCATIONS[STABLE_LOCATIONS.length - 1];
|
|
45663
45741
|
throw new Error(
|
|
45664
|
-
"
|
|
45742
|
+
"The agent's background service needs Node " + REQUIRED_NODE_MAJOR + " or newer at a fixed location (" + list + "), and none was found. Node from nvm, fnm and similar tools moves when it is upgraded, so it is not used. Install Node " + REQUIRED_NODE_MAJOR + " from https://nodejs.org, then run the install command again."
|
|
45665
45743
|
);
|
|
45666
45744
|
}
|
|
45667
45745
|
return r.path;
|
|
@@ -45841,9 +45919,17 @@ function runWireVerb(verb) {
|
|
|
45841
45919
|
process.exit(verb === "wire" ? runWire(realWireDeps()) : runUnwire(realWireDeps()));
|
|
45842
45920
|
}
|
|
45843
45921
|
async function runServiceVerb(verb) {
|
|
45844
|
-
|
|
45922
|
+
let credentialPath;
|
|
45923
|
+
if (verb === "install") {
|
|
45924
|
+
const cred = readCredentialArg(process.argv, process.env);
|
|
45925
|
+
credentialPath = cred.kind === "path" ? cred.path : void 0;
|
|
45926
|
+
} else {
|
|
45927
|
+
credentialPath = argOrEnv("--credential", "SECURECOMS_CREDENTIAL");
|
|
45928
|
+
}
|
|
45845
45929
|
const label = argOrEnv("--label", "SECURECOMS_LABEL");
|
|
45846
45930
|
if (verb === "install" ? !credentialPath : !credentialPath && !label) {
|
|
45931
|
+
if (verb === "install") process.stderr.write(`${noCredentialLine("install")}
|
|
45932
|
+
`);
|
|
45847
45933
|
const execPathUsage = verb === "install" ? " [--exec-path <path>]" : "";
|
|
45848
45934
|
process.stderr.write(`usage: agent-bridge ${verb} --credential <path> [--label <name>] [--store <path>] [--port N] [--harness openclaw|hermes|universal]${execPathUsage}
|
|
45849
45935
|
`);
|
|
@@ -45966,6 +46052,26 @@ var HELD_POLL_TOTAL_MS = 1e4;
|
|
|
45966
46052
|
var HELD_POLL_INTERVAL_MS = 500;
|
|
45967
46053
|
var HELD_NO_PID_REACQUIRES = 3;
|
|
45968
46054
|
var SHUTDOWN_HARD_EXIT_MS = 8e3;
|
|
46055
|
+
function configErrorLine(reason, o) {
|
|
46056
|
+
const at = o.isTTY ? ` at ${o.path}` : "";
|
|
46057
|
+
switch (reason) {
|
|
46058
|
+
case "credential_not_found":
|
|
46059
|
+
return `Can't find the key file${at}. Check the path after --credential.`;
|
|
46060
|
+
case "credential_unreadable":
|
|
46061
|
+
return `Can't open the key file${at}. Check that it exists and that your user can read it.`;
|
|
46062
|
+
case "credential_not_json":
|
|
46063
|
+
case "identity_id_invalid":
|
|
46064
|
+
return `The key file${at} is damaged. Get a new key file from the agent's page in SecureComs.`;
|
|
46065
|
+
case "credential_missing_field":
|
|
46066
|
+
return `The key file${at} is incomplete (no ${o.field}). Get a new key file from the agent's page in SecureComs.`;
|
|
46067
|
+
case "credential_voice_invalid":
|
|
46068
|
+
return o.source === "env" ? "The voice settings in this computer's environment are not valid." : `The voice settings in the key file${at} are not valid.`;
|
|
46069
|
+
case "bind_refused":
|
|
46070
|
+
return "The Connector only accepts connections from this computer unless TLS is set. Remove --bind, or add --tls-cert and --tls-key.";
|
|
46071
|
+
case "unknown":
|
|
46072
|
+
return void 0;
|
|
46073
|
+
}
|
|
46074
|
+
}
|
|
45969
46075
|
function realRunModeDeps(credentialPath, storePath) {
|
|
45970
46076
|
return {
|
|
45971
46077
|
credentialPath,
|
|
@@ -45996,22 +46102,56 @@ function realRunModeDeps(credentialPath, storePath) {
|
|
|
45996
46102
|
process.stderr.write(`${line}
|
|
45997
46103
|
`);
|
|
45998
46104
|
},
|
|
46105
|
+
isTTY: process.stderr.isTTY === true,
|
|
45999
46106
|
log: writeServiceLog
|
|
46000
46107
|
};
|
|
46001
46108
|
}
|
|
46002
46109
|
async function runMode(deps) {
|
|
46003
46110
|
const { credentialPath, storePath, log } = deps;
|
|
46111
|
+
const refuse2 = (reason, extra, e) => {
|
|
46112
|
+
const fields = {
|
|
46113
|
+
reason: ServiceLogEnum.of(reason, [
|
|
46114
|
+
"credential_not_found",
|
|
46115
|
+
"credential_unreadable",
|
|
46116
|
+
"credential_not_json",
|
|
46117
|
+
"credential_missing_field",
|
|
46118
|
+
"credential_voice_invalid",
|
|
46119
|
+
"identity_id_invalid",
|
|
46120
|
+
"bind_refused",
|
|
46121
|
+
"unknown"
|
|
46122
|
+
])
|
|
46123
|
+
};
|
|
46124
|
+
if (extra.field) {
|
|
46125
|
+
fields.field = ServiceLogEnum.of(extra.field, [
|
|
46126
|
+
"api_key",
|
|
46127
|
+
"identity_id",
|
|
46128
|
+
"device_id",
|
|
46129
|
+
"signing_private_key",
|
|
46130
|
+
"enclave_id",
|
|
46131
|
+
"api_base"
|
|
46132
|
+
]);
|
|
46133
|
+
}
|
|
46134
|
+
log("error", "startup.config_invalid", fields, e);
|
|
46135
|
+
const line = configErrorLine(reason, { path: credentialPath, isTTY: deps.isTTY, ...extra });
|
|
46136
|
+
if (line !== void 0) deps.print(line);
|
|
46137
|
+
};
|
|
46004
46138
|
let cfg, daemon;
|
|
46005
46139
|
try {
|
|
46006
46140
|
cfg = loadConfig({ credentialPath, storePath });
|
|
46007
46141
|
daemon = parseDaemonArgs(deps.argv, deps.env);
|
|
46142
|
+
} catch (e) {
|
|
46143
|
+
if (e instanceof ConfigError) refuse2(e.reason, { field: e.field, source: e.source }, e);
|
|
46144
|
+
else refuse2("unknown", {}, e);
|
|
46145
|
+
return deps.exit(1);
|
|
46146
|
+
}
|
|
46147
|
+
try {
|
|
46008
46148
|
validateBind(daemon.bind, daemon.tls);
|
|
46009
46149
|
} catch (e) {
|
|
46010
|
-
|
|
46150
|
+
refuse2("bind_refused", {}, e);
|
|
46011
46151
|
return deps.exit(1);
|
|
46012
46152
|
}
|
|
46013
46153
|
if (!CANONICAL_UUID2.test(cfg.identityId)) {
|
|
46014
|
-
|
|
46154
|
+
refuse2("identity_id_invalid", { field: "identity_id" });
|
|
46015
46155
|
return deps.exit(1);
|
|
46016
46156
|
}
|
|
46017
46157
|
const lockDir = join25(deps.home, ".securecoms", "locks");
|
|
@@ -46050,7 +46190,9 @@ async function runMode(deps) {
|
|
|
46050
46190
|
lock_path: ServiceLogEnum.of("home_securecoms_locks_identity", ["home_securecoms_locks_identity"]),
|
|
46051
46191
|
remedy: ServiceLogEnum.of("delete_lock_if_no_other_host", ["delete_lock_if_no_other_host"])
|
|
46052
46192
|
});
|
|
46053
|
-
deps.print(
|
|
46193
|
+
deps.print(
|
|
46194
|
+
deps.isTTY ? `This agent's lock file was written under a different computer name: ${lockPath}. If no other computer runs this agent, delete that file and start the agent again.` : "This agent's lock file was written under a different computer name. If no other computer runs this agent, delete that file and start the agent again."
|
|
46195
|
+
);
|
|
46054
46196
|
return deps.exit(1);
|
|
46055
46197
|
case "unreadable":
|
|
46056
46198
|
log("error", "startup.instance_lock_unreadable", {}, lock.lastError);
|
|
@@ -46171,6 +46313,10 @@ async function runMode(deps) {
|
|
|
46171
46313
|
}
|
|
46172
46314
|
});
|
|
46173
46315
|
}
|
|
46316
|
+
function printNoCredentialRunLine() {
|
|
46317
|
+
process.stderr.write(`${noCredentialLine("run")}
|
|
46318
|
+
`);
|
|
46319
|
+
}
|
|
46174
46320
|
async function main() {
|
|
46175
46321
|
if (!nodeMajorOk(process.versions.node)) {
|
|
46176
46322
|
writeServiceLog("error", "startup.node_unsupported");
|
|
@@ -46197,11 +46343,13 @@ async function main() {
|
|
|
46197
46343
|
await runServiceVerb(verb);
|
|
46198
46344
|
return;
|
|
46199
46345
|
}
|
|
46200
|
-
const
|
|
46201
|
-
if (
|
|
46346
|
+
const cred = readCredentialArg(process.argv, process.env);
|
|
46347
|
+
if (cred.kind !== "path") {
|
|
46348
|
+
printNoCredentialRunLine();
|
|
46202
46349
|
writeServiceLog("error", "startup.credential_required.use_--credential");
|
|
46203
46350
|
process.exit(2);
|
|
46204
46351
|
}
|
|
46352
|
+
const credentialPath = cred.path;
|
|
46205
46353
|
const storePath = argOrEnv("--store", "SECURECOMS_STORE");
|
|
46206
46354
|
await runMode(realRunModeDeps(credentialPath, storePath));
|
|
46207
46355
|
}
|
|
@@ -46219,6 +46367,7 @@ export {
|
|
|
46219
46367
|
HELD_POLL_TOTAL_MS,
|
|
46220
46368
|
SHUTDOWN_HARD_EXIT_MS,
|
|
46221
46369
|
chooseInstallExecPath,
|
|
46370
|
+
configErrorLine,
|
|
46222
46371
|
formatServiceError,
|
|
46223
46372
|
nodeMajorOk,
|
|
46224
46373
|
parseVerb,
|
package/dist/config.d.ts
CHANGED
|
@@ -56,6 +56,19 @@ export interface BridgeConfig {
|
|
|
56
56
|
ownerGrantPin?: OwnerGrantPinResult;
|
|
57
57
|
}
|
|
58
58
|
export declare const REQUIRED: readonly ["api_key", "identity_id", "device_id", "signing_private_key", "enclave_id", "api_base"];
|
|
59
|
+
export type ConfigErrorReason = "credential_not_found" | "credential_unreadable" | "credential_not_json" | "credential_missing_field" | "credential_voice_invalid";
|
|
60
|
+
/**
|
|
61
|
+
* A key-file load failure with a fixed reason code, set by the step that
|
|
62
|
+
* failed (spec 2026-10-15 §5.2). Callers map the code to a log field and a
|
|
63
|
+
* fixed human line; nothing reads `message`, which carries no path, no
|
|
64
|
+
* identifier and no upstream error text.
|
|
65
|
+
*/
|
|
66
|
+
export declare class ConfigError extends Error {
|
|
67
|
+
readonly reason: ConfigErrorReason;
|
|
68
|
+
readonly field?: (typeof REQUIRED)[number] | undefined;
|
|
69
|
+
readonly source?: "file" | "env" | undefined;
|
|
70
|
+
constructor(reason: ConfigErrorReason, field?: (typeof REQUIRED)[number] | undefined, source?: "file" | "env" | undefined);
|
|
71
|
+
}
|
|
59
72
|
export declare function loadConfig(opts: {
|
|
60
73
|
credentialPath: string;
|
|
61
74
|
storePath?: string;
|
package/dist/index.js
CHANGED
|
@@ -17457,7 +17457,13 @@ var CODES = [
|
|
|
17457
17457
|
// lookup missed and before any write, so a retry can never land: the
|
|
17458
17458
|
// pending commit is dropped instead of being resent forever. The same pair
|
|
17459
17459
|
// answers an application send into a deleted channel (#325 F5).
|
|
17460
|
-
{ status: 409, code: "channel_unavailable" }
|
|
17460
|
+
{ status: 409, code: "channel_unavailable" },
|
|
17461
|
+
// ADR-0023 B.1: an Add or Welcome into a humans-only enclave's group naming
|
|
17462
|
+
// a device that is not provably a person's. Refused after the replay lookup
|
|
17463
|
+
// missed and before any write, so nothing landed. Definite: the committer's
|
|
17464
|
+
// work cannot name an agent in a humans-only enclave (admission refuses
|
|
17465
|
+
// one), so dropping the commit starts no resend loop.
|
|
17466
|
+
{ status: 403, code: "humans_only_enclave" }
|
|
17461
17467
|
];
|
|
17462
17468
|
var DISTRIBUTE_DEFINITE_CODES = Object.freeze(CODES.map((c) => Object.freeze({ ...c })));
|
|
17463
17469
|
var DEFINITE_KEYS = new Set(DISTRIBUTE_DEFINITE_CODES.map((c) => `${c.status} ${c.code}`));
|
|
@@ -42821,8 +42827,30 @@ ${channelId}`;
|
|
|
42821
42827
|
import { readFileSync as readFileSync10, statSync as statSync5 } from "node:fs";
|
|
42822
42828
|
import { dirname as dirname6, join as join18 } from "node:path";
|
|
42823
42829
|
var REQUIRED = ["api_key", "identity_id", "device_id", "signing_private_key", "enclave_id", "api_base"];
|
|
42830
|
+
var CONFIG_ERROR_MESSAGE = {
|
|
42831
|
+
credential_not_found: "credential file not found",
|
|
42832
|
+
credential_unreadable: "credential file could not be read",
|
|
42833
|
+
credential_not_json: "credential file is not valid JSON",
|
|
42834
|
+
credential_missing_field: "credential file missing required field",
|
|
42835
|
+
credential_voice_invalid: "credential file voice config invalid"
|
|
42836
|
+
};
|
|
42837
|
+
var ConfigError = class extends Error {
|
|
42838
|
+
constructor(reason, field, source) {
|
|
42839
|
+
super(CONFIG_ERROR_MESSAGE[reason] + (field ? `: ${field}` : ""));
|
|
42840
|
+
this.reason = reason;
|
|
42841
|
+
this.field = field;
|
|
42842
|
+
this.source = source;
|
|
42843
|
+
this.name = "ConfigError";
|
|
42844
|
+
}
|
|
42845
|
+
};
|
|
42824
42846
|
function loadConfig(opts) {
|
|
42825
|
-
|
|
42847
|
+
let raw;
|
|
42848
|
+
try {
|
|
42849
|
+
raw = readFileSync10(opts.credentialPath, "utf8");
|
|
42850
|
+
} catch (e) {
|
|
42851
|
+
const code = e?.code;
|
|
42852
|
+
throw new ConfigError(code === "ENOENT" ? "credential_not_found" : "credential_unreadable");
|
|
42853
|
+
}
|
|
42826
42854
|
try {
|
|
42827
42855
|
const mode = statSync5(opts.credentialPath).mode & 511;
|
|
42828
42856
|
if (mode & 63) writeServiceLog2("warn", "config.credential_permissions_unsafe");
|
|
@@ -42832,9 +42860,9 @@ function loadConfig(opts) {
|
|
|
42832
42860
|
try {
|
|
42833
42861
|
json2 = JSON.parse(raw);
|
|
42834
42862
|
} catch {
|
|
42835
|
-
throw new
|
|
42863
|
+
throw new ConfigError("credential_not_json");
|
|
42836
42864
|
}
|
|
42837
|
-
for (const k of REQUIRED) if (typeof json2[k] !== "string" || !json2[k]) throw new
|
|
42865
|
+
for (const k of REQUIRED) if (typeof json2[k] !== "string" || !json2[k]) throw new ConfigError("credential_missing_field", k);
|
|
42838
42866
|
const storePath = opts.storePath ?? join18(dirname6(opts.credentialPath), "bridge-state.db");
|
|
42839
42867
|
const ownerGrantPin = loadOwnerGrantPin({ credentialPath: opts.credentialPath, credentialJson: json2 });
|
|
42840
42868
|
writeServiceLog2(ownerGrantPin.state === "ok" || ownerGrantPin.state === "no_pin" ? "info" : "warn", "authz.pin", {
|
|
@@ -42872,8 +42900,14 @@ function loadConfig(opts) {
|
|
|
42872
42900
|
let voice;
|
|
42873
42901
|
try {
|
|
42874
42902
|
voice = parseVoiceConfig(json2.voice, process.env);
|
|
42875
|
-
} catch
|
|
42876
|
-
|
|
42903
|
+
} catch {
|
|
42904
|
+
let source = "env";
|
|
42905
|
+
try {
|
|
42906
|
+
parseVoiceConfig(json2.voice, {});
|
|
42907
|
+
} catch {
|
|
42908
|
+
source = "file";
|
|
42909
|
+
}
|
|
42910
|
+
throw new ConfigError("credential_voice_invalid", void 0, source);
|
|
42877
42911
|
}
|
|
42878
42912
|
const modelSource = json2.memory?.model_source === "import_only" ? "import_only" : "download";
|
|
42879
42913
|
const enabledRaw = json2.memory?.enabled;
|
|
@@ -14,8 +14,6 @@ export interface UpgradeDeps {
|
|
|
14
14
|
isTTY: boolean;
|
|
15
15
|
stderr: (s: string) => void;
|
|
16
16
|
ask: (q: string) => Promise<string>;
|
|
17
|
-
hasBrew: () => boolean;
|
|
18
|
-
runBrew: () => number;
|
|
19
17
|
fetchText: (url: string) => Promise<string>;
|
|
20
18
|
fetchBinary: (url: string, destPath: string) => Promise<void>;
|
|
21
19
|
fileSha256: (p: string) => string;
|
package/dist/preflight.cjs
CHANGED
|
@@ -41,11 +41,10 @@ __export(preflight_upgrade_exports, {
|
|
|
41
41
|
sha256Hex: () => sha256Hex
|
|
42
42
|
});
|
|
43
43
|
function parseShasums(text, arch) {
|
|
44
|
-
const want = "-darwin-" + arch + ".pkg";
|
|
45
44
|
const lines = String(text).split("\n");
|
|
46
45
|
for (let i = 0; i < lines.length; i++) {
|
|
47
46
|
const m = lines[i].trim().match(/^([0-9a-f]{6,64})\s+(\S+)$/);
|
|
48
|
-
if (m &&
|
|
47
|
+
if (m && PKG_NAME.test(m[2])) {
|
|
49
48
|
return { filename: m[2], sha256: m[1] };
|
|
50
49
|
}
|
|
51
50
|
}
|
|
@@ -62,48 +61,47 @@ async function promptYesNo(question, deps) {
|
|
|
62
61
|
}
|
|
63
62
|
async function guidedUpgrade(deps) {
|
|
64
63
|
if (deps.platform !== "darwin") return false;
|
|
64
|
+
let consented = false;
|
|
65
|
+
const failed = () => {
|
|
66
|
+
if (consented) deps.stderr("[bridge] Couldn't install Node 24 from nodejs.org.\n");
|
|
67
|
+
return false;
|
|
68
|
+
};
|
|
65
69
|
try {
|
|
66
70
|
const existing = deps.existingCompatibleNode();
|
|
67
71
|
if (existing) {
|
|
68
72
|
deps.stderr(
|
|
69
|
-
"[bridge]
|
|
73
|
+
"[bridge] Using Node " + REQUIRED_NODE_MAJOR + " or newer found at " + existing + ". (The node command on this computer is an older version.)\n"
|
|
70
74
|
);
|
|
71
75
|
return deps.reexec(existing);
|
|
72
76
|
}
|
|
73
|
-
const viaBrew = deps.hasBrew();
|
|
74
|
-
const source = viaBrew ? "Homebrew (brew install node \u2014 user-space, no sudo)" : "the official installer from " + DIST_BASE + " (needs your admin password)";
|
|
75
77
|
const ok = await promptYesNo(
|
|
76
|
-
"[bridge] Node 24
|
|
78
|
+
"[bridge] SecureComs needs Node 24. Install it now from nodejs.org? This needs your computer's administrator password. [y/N] ",
|
|
77
79
|
deps
|
|
78
80
|
);
|
|
79
81
|
if (!ok) return false;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
deps.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (deps.fileSha256(dest) !== entry.sha256) {
|
|
92
|
-
deps.stderr("[bridge] checksum mismatch \u2014 aborting install (file discarded)\n");
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
deps.stderr("[bridge] running: sudo installer -pkg " + entry.filename + " -target /\n");
|
|
96
|
-
if (deps.runSudoInstaller(dest) !== 0) return false;
|
|
97
|
-
} finally {
|
|
98
|
-
deps.cleanup(dest);
|
|
82
|
+
consented = true;
|
|
83
|
+
const shasums = await deps.fetchText(DIST_BASE + "/SHASUMS256.txt");
|
|
84
|
+
const entry = parseShasums(shasums, deps.arch);
|
|
85
|
+
if (!entry) return failed();
|
|
86
|
+
const dest = deps.tmpdir() + "/" + entry.filename;
|
|
87
|
+
deps.stderr("[bridge] downloading " + DIST_BASE + "/" + entry.filename + "\n");
|
|
88
|
+
try {
|
|
89
|
+
await deps.fetchBinary(DIST_BASE + "/" + entry.filename, dest);
|
|
90
|
+
if (deps.fileSha256(dest) !== entry.sha256) {
|
|
91
|
+
deps.stderr("[bridge] checksum mismatch \u2014 aborting install (file discarded)\n");
|
|
92
|
+
return failed();
|
|
99
93
|
}
|
|
94
|
+
deps.stderr("[bridge] running: sudo installer -pkg " + entry.filename + " -target /\n");
|
|
95
|
+
if (deps.runSudoInstaller(dest) !== 0) return failed();
|
|
96
|
+
} finally {
|
|
97
|
+
deps.cleanup(dest);
|
|
100
98
|
}
|
|
101
99
|
const fresh = deps.resolveNewNode();
|
|
102
|
-
if (!fresh) return
|
|
100
|
+
if (!fresh) return failed();
|
|
103
101
|
deps.stderr("[bridge] Node 24 installed \u2014 continuing under " + fresh + "\n");
|
|
104
102
|
return deps.reexec(fresh);
|
|
105
103
|
} catch {
|
|
106
|
-
return
|
|
104
|
+
return failed();
|
|
107
105
|
}
|
|
108
106
|
}
|
|
109
107
|
function resolveNewNodeFrom(probe, candidates) {
|
|
@@ -152,11 +150,6 @@ function realUpgradeDeps(originalArgv) {
|
|
|
152
150
|
resolve(a);
|
|
153
151
|
});
|
|
154
152
|
}),
|
|
155
|
-
hasBrew: () => spawnSync("brew", ["--version"], { stdio: "ignore" }).status === 0,
|
|
156
|
-
runBrew: () => {
|
|
157
|
-
var _a;
|
|
158
|
-
return (_a = spawnSync("brew", ["install", "node"], { stdio: "inherit" }).status) != null ? _a : 1;
|
|
159
|
-
},
|
|
160
153
|
fetchText: async (url) => {
|
|
161
154
|
const res = await fetch(url, 3);
|
|
162
155
|
let out = "";
|
|
@@ -197,12 +190,13 @@ function realUpgradeDeps(originalArgv) {
|
|
|
197
190
|
}
|
|
198
191
|
};
|
|
199
192
|
}
|
|
200
|
-
var DIST_BASE, NODE_CANDIDATES;
|
|
193
|
+
var DIST_BASE, PKG_NAME, NODE_CANDIDATES;
|
|
201
194
|
var init_preflight_upgrade = __esm({
|
|
202
195
|
"src/preflight-upgrade.cts"() {
|
|
203
196
|
"use strict";
|
|
204
197
|
init_resolve_node();
|
|
205
198
|
DIST_BASE = "https://nodejs.org/dist/latest-v24.x";
|
|
199
|
+
PKG_NAME = /^node-v24\.\d+\.\d+\.pkg$/;
|
|
206
200
|
NODE_CANDIDATES = [
|
|
207
201
|
"/usr/local/bin/node",
|
|
208
202
|
// nodejs.org pkg (stable across upgrades)
|
|
@@ -216,6 +210,7 @@ var init_preflight_upgrade = __esm({
|
|
|
216
210
|
// src/preflight.cts
|
|
217
211
|
var preflight_exports = {};
|
|
218
212
|
__export(preflight_exports, {
|
|
213
|
+
NODE_FLOOR: () => NODE_FLOOR,
|
|
219
214
|
effectiveVersion: () => effectiveVersion,
|
|
220
215
|
instructionsBlock: () => instructionsBlock,
|
|
221
216
|
meetsFloor: () => meetsFloor,
|
|
@@ -224,7 +219,6 @@ __export(preflight_exports, {
|
|
|
224
219
|
});
|
|
225
220
|
module.exports = __toCommonJS(preflight_exports);
|
|
226
221
|
var NODE_FLOOR = 24;
|
|
227
|
-
var FLOOR_REASON = "requires >= 24 (node:sqlite stable API)";
|
|
228
222
|
function parseNodeMajor(version) {
|
|
229
223
|
const n = Number(String(version).split(".")[0]);
|
|
230
224
|
return Number.isFinite(n) && n > 0 ? n : 0;
|
|
@@ -239,23 +233,30 @@ function effectiveVersion(real, faked) {
|
|
|
239
233
|
return real;
|
|
240
234
|
}
|
|
241
235
|
function instructionsBlock(platform, current) {
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
"How to get Node 24 LTS:",
|
|
246
|
-
" 1. Official installer (recommended): https://nodejs.org \u2014 download the",
|
|
247
|
-
" Node 24 LTS installer for your platform and run it."
|
|
248
|
-
];
|
|
236
|
+
const n = String(NODE_FLOOR);
|
|
237
|
+
const head = "[bridge] This computer has Node " + current + ". SecureComs needs Node " + n + " or newer.";
|
|
238
|
+
let lines;
|
|
249
239
|
if (platform === "darwin") {
|
|
250
|
-
lines
|
|
240
|
+
lines = [
|
|
241
|
+
head,
|
|
242
|
+
"",
|
|
243
|
+
"To get Node " + n + ":",
|
|
244
|
+
" 1. Download the Node " + n + " LTS installer for macOS from https://nodejs.org and run it.",
|
|
245
|
+
" 2. Run the SecureComs install command again."
|
|
246
|
+
];
|
|
247
|
+
} else if (platform === "win32") {
|
|
248
|
+
lines = [
|
|
249
|
+
head,
|
|
250
|
+
"",
|
|
251
|
+
"Install Node " + n + " from https://nodejs.org, then run the SecureComs install command again."
|
|
252
|
+
];
|
|
253
|
+
} else {
|
|
254
|
+
lines = [
|
|
255
|
+
head,
|
|
256
|
+
"",
|
|
257
|
+
"Install Node " + n + " so that it is at /usr/local/bin/node or /usr/bin/node, then run the SecureComs install command again. See https://nodejs.org for options."
|
|
258
|
+
];
|
|
251
259
|
}
|
|
252
|
-
lines.push(" " + (platform === "darwin" ? "3" : "2") + ". nvm: nvm install 24 && nvm use 24");
|
|
253
|
-
lines.push(
|
|
254
|
-
"",
|
|
255
|
-
"Then re-run the install command. (Always re-run the install command after",
|
|
256
|
-
"upgrading Node \u2014 the background service pins the Node path it was",
|
|
257
|
-
"installed with.)"
|
|
258
|
-
);
|
|
259
260
|
return lines.join("\n") + "\n";
|
|
260
261
|
}
|
|
261
262
|
async function runPreflight(deps) {
|
|
@@ -292,6 +293,7 @@ if (require.main === module) {
|
|
|
292
293
|
}
|
|
293
294
|
// Annotate the CommonJS export names for ESM import in node:
|
|
294
295
|
0 && (module.exports = {
|
|
296
|
+
NODE_FLOOR,
|
|
295
297
|
effectiveVersion,
|
|
296
298
|
instructionsBlock,
|
|
297
299
|
meetsFloor,
|
package/dist/preflight.d.cts
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CommonJS preflight shim — the package's bin entry. CJS on purpose: any
|
|
4
|
+
* Node back to v10 can parse it, so a machine with stale Node always gets
|
|
5
|
+
* the friendly diagnosis instead of an ESM/builtin crash (static imports in
|
|
6
|
+
* the ESM bundle hoist — node:sqlite would detonate before any guard ran).
|
|
7
|
+
* Built with esbuild format=cjs target=node10 to dist/preflight.cjs.
|
|
8
|
+
*
|
|
9
|
+
* AUDIT C1: this file must contain ZERO dynamic `import()` — esbuild's
|
|
10
|
+
* node10 downlevel would turn it into require() (cannot load ESM), and
|
|
11
|
+
* keeping the syntax breaks node10 parse. The real import lives in
|
|
12
|
+
* preflight-modern.cts, which is only require()d AFTER the gate passes.
|
|
13
|
+
* An artifact test greps the emitted file to pin this.
|
|
14
|
+
*/
|
|
15
|
+
export declare const NODE_FLOOR = 24;
|
|
2
16
|
export declare function parseNodeMajor(version: string): number;
|
|
3
17
|
export declare function meetsFloor(version: string): boolean;
|
|
4
18
|
export declare function effectiveVersion(real: string, faked: string | undefined): string;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How `--credential` is read for `install` and run mode (spec 2026-10-15
|
|
3
|
+
* §5.3, OQ-18 (b)). A flag that is present with an empty value means "no key
|
|
4
|
+
* file": it never falls back to SECURECOMS_CREDENTIAL, so a copied command
|
|
5
|
+
* whose Downloads lookup found nothing cannot silently pick up a different
|
|
6
|
+
* agent's key file from the environment. The variable is used only when the
|
|
7
|
+
* flag is absent. Every other flag keeps argOrEnv's behaviour.
|
|
8
|
+
*/
|
|
9
|
+
export type CredentialArg = {
|
|
10
|
+
kind: "path";
|
|
11
|
+
path: string;
|
|
12
|
+
} | {
|
|
13
|
+
kind: "empty_flag";
|
|
14
|
+
} | {
|
|
15
|
+
kind: "absent";
|
|
16
|
+
};
|
|
17
|
+
export declare function readCredentialArg(argv: string[], env: NodeJS.ProcessEnv): CredentialArg;
|
|
18
|
+
/**
|
|
19
|
+
* The one line printed when no key file was given: E8 for run mode
|
|
20
|
+
* (approved), E9 for install (DRAFT: the approved E9 names the agent, which
|
|
21
|
+
* the bridge cannot know when no key file was found).
|
|
22
|
+
*/
|
|
23
|
+
export declare function noCredentialLine(verb: "run" | "install"): string;
|
|
@@ -13,7 +13,14 @@ export interface ServiceDeps {
|
|
|
13
13
|
mkdirp: (path: string) => void;
|
|
14
14
|
fileExists: (path: string) => boolean;
|
|
15
15
|
user: string;
|
|
16
|
+
/** kill(pid, 0): true while the process runs (EPERM counts as running). */
|
|
17
|
+
pidAlive: (pid: number) => boolean;
|
|
18
|
+
/** Blocking sleep: installService is synchronous end to end. */
|
|
19
|
+
sleepMs: (ms: number) => void;
|
|
20
|
+
now: () => number;
|
|
16
21
|
}
|
|
22
|
+
/** How long install waits for the previous bridge to exit after bootout. */
|
|
23
|
+
export declare const PREVIOUS_EXIT_WAIT_MS = 10000;
|
|
17
24
|
export interface InstallResult {
|
|
18
25
|
label: string;
|
|
19
26
|
filePath: string;
|
|
@@ -7,6 +7,7 @@ export interface ResolveNodeDeps {
|
|
|
7
7
|
arch: () => string;
|
|
8
8
|
archOf: (p: string) => string | null;
|
|
9
9
|
}
|
|
10
|
+
export declare const STABLE_LOCATIONS: readonly string[];
|
|
10
11
|
export declare function resolveStableNodePath(runningExecPath: string, deps: ResolveNodeDeps): {
|
|
11
12
|
path: string;
|
|
12
13
|
stable: boolean;
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const BRIDGE_VERSION = "0.30.
|
|
1
|
+
export declare const BRIDGE_VERSION = "0.30.2";
|