@securecoms/agent-bridge 0.30.0 → 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 +77 -6
- package/dist/bin.d.ts +101 -0
- package/dist/bin.js +1102 -161
- package/dist/bridge-agent.d.ts +60 -0
- package/dist/config.d.ts +13 -0
- package/dist/errors.d.ts +1 -1
- package/dist/index.js +335 -27
- 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-prep.d.ts +21 -1
- package/dist/service/install.d.ts +7 -0
- package/dist/service/instance-lock.d.ts +88 -0
- package/dist/service/launchd.d.ts +1 -1
- package/dist/service/resolve-node.d.ts +1 -0
- package/dist/service/status.d.ts +3 -5
- package/dist/service/systemd.d.ts +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/ws-server.d.ts +4 -0
- package/package.json +2 -2
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;
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import { type ServiceInput } from "./config.js";
|
|
2
2
|
import { type AutoWireDeps } from "./harness-wire.js";
|
|
3
|
+
/** The parsed `/healthz` body fields the install and status liveness checks read. */
|
|
4
|
+
export interface DaemonHealth {
|
|
5
|
+
agent_sessions?: number;
|
|
6
|
+
evictions?: number;
|
|
7
|
+
state?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Liveness is a routed `/healthz` 200 whose state is not `session_superseded`
|
|
11
|
+
* (P-2). A TCP accept proves nothing: the daemon pre-binds its port before
|
|
12
|
+
* `agent.start()`, so a connect succeeds before the bridge routes anything.
|
|
13
|
+
*/
|
|
14
|
+
export declare function isDaemonRouted(h: DaemonHealth | null): h is DaemonHealth;
|
|
15
|
+
/** Default install wait: the boot drain may take up to BOOT_DRAIN_WAIT_MS_DEFAULT, plus 15 s. */
|
|
16
|
+
export declare const VERIFY_BUDGET_MS: number;
|
|
3
17
|
export interface PrepDeps {
|
|
4
18
|
readFile: (p: string) => string;
|
|
5
19
|
writeFileMode: (p: string, data: string, mode: number) => void;
|
|
@@ -8,8 +22,11 @@ export interface PrepDeps {
|
|
|
8
22
|
realpath: (p: string) => string;
|
|
9
23
|
listDir: (p: string) => string[];
|
|
10
24
|
probeBindFree: (port: number) => boolean;
|
|
11
|
-
|
|
25
|
+
/** Routed `/healthz` probe (P-2): null unless the daemon answered a 200 with parseable JSON. */
|
|
26
|
+
getHealth: (port: number) => DaemonHealth | null;
|
|
12
27
|
sleep: (ms: number) => Promise<void>;
|
|
28
|
+
/** Wall clock in ms (Date.now). verifyDaemonUp's deadline reads it, so probe time counts, not only sleep. */
|
|
29
|
+
now: () => number;
|
|
13
30
|
}
|
|
14
31
|
export interface ParsedCredential {
|
|
15
32
|
deviceId: string;
|
|
@@ -19,6 +36,9 @@ export interface ParsedCredential {
|
|
|
19
36
|
export declare function assertReadableCredential(absPath: string, deps: PrepDeps): ParsedCredential;
|
|
20
37
|
export declare function durableDir(home: string, slug: string): string;
|
|
21
38
|
export declare function durableCredentialPath(home: string, slug: string): string;
|
|
39
|
+
/** M5 (OQ-3): the approved refusal text for an identity that is already installed. */
|
|
40
|
+
export declare class InstallRefusedError extends Error {
|
|
41
|
+
}
|
|
22
42
|
export declare function takeCustody(args: {
|
|
23
43
|
home: string;
|
|
24
44
|
slug: 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;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { ServiceLog } from "../service-log.js";
|
|
2
|
+
/** An unparseable lock this young may be a holder mid-write. */
|
|
3
|
+
export declare const LOCK_PARTIAL_WRITE_GRACE_MS = 10000;
|
|
4
|
+
/** A takeover guard this old was left by a crashed takeover. */
|
|
5
|
+
export declare const LOCK_TAKEOVER_GUARD_STALE_MS = 30000;
|
|
6
|
+
/** Two boot-epoch reads further apart than this are two different boots. */
|
|
7
|
+
export declare const BOOT_TOLERANCE_SEC = 120;
|
|
8
|
+
/** A holder refreshes its lock's mtime this often (#808 N1). */
|
|
9
|
+
export declare const LOCK_HEARTBEAT_MS = 60000;
|
|
10
|
+
/** A same-kernel, other-hostname lock untouched for this long is abandoned. */
|
|
11
|
+
export declare const LOCK_FOREIGN_STALE_MS: number;
|
|
12
|
+
export type LockRefusal = "held" | "foreign_host" | "unreadable" | "busy" | "failed";
|
|
13
|
+
export type LockResult = "acquired" | LockRefusal;
|
|
14
|
+
/** Everything the lock reads about the machine. Injected in tests. */
|
|
15
|
+
export interface LockEnv {
|
|
16
|
+
pid: number;
|
|
17
|
+
hostname(): string;
|
|
18
|
+
/** Linux boot_id, or undefined where the platform has none. */
|
|
19
|
+
bootId(): string | undefined;
|
|
20
|
+
bootEpochSec(): number;
|
|
21
|
+
/** Linux /proc/<pid>/stat start time, or undefined (not Linux / no such pid). */
|
|
22
|
+
procStart(pid: number): string | undefined;
|
|
23
|
+
/** kill(pid, 0): true when running (EPERM counts as running). */
|
|
24
|
+
pidAlive(pid: number): boolean;
|
|
25
|
+
/** Test seam: runs after the takeover guard is taken, before the re-read. */
|
|
26
|
+
beforeTakeoverReread?(): void;
|
|
27
|
+
/** Test seam: runs after the stale lock is unlinked, before our create. */
|
|
28
|
+
beforeTakeoverCreate?(): void;
|
|
29
|
+
/** Test seam: runs after an O_EXCL create found the lock, before it is read. */
|
|
30
|
+
beforeLockRead?(): void;
|
|
31
|
+
}
|
|
32
|
+
/** Field 22 of /proc/<pid>/stat. The comm field (2) may contain spaces or
|
|
33
|
+
* parentheses, so fields are counted from the LAST ')'. */
|
|
34
|
+
export declare function procStartFromStat(stat: string): string | undefined;
|
|
35
|
+
export declare function pidAliveByKill(pid: number): boolean;
|
|
36
|
+
export declare const realLockEnv: LockEnv;
|
|
37
|
+
export declare class InstanceLock {
|
|
38
|
+
readonly path: string;
|
|
39
|
+
private readonly env;
|
|
40
|
+
private readonly log;
|
|
41
|
+
private readonly heartbeatMs;
|
|
42
|
+
held: boolean;
|
|
43
|
+
/** The exact body this process wrote (#808 N1): release and the ownership
|
|
44
|
+
* check compare the file against THIS, never against live machine facts
|
|
45
|
+
* (a hostname can change under a running process). */
|
|
46
|
+
private written;
|
|
47
|
+
private heartbeat;
|
|
48
|
+
/** The error behind the last `unreadable` / `failed`, for the caller's one
|
|
49
|
+
* log line (the lock itself never logs a refusal: it is retried per call). */
|
|
50
|
+
lastError: unknown;
|
|
51
|
+
/** PID read from the lock body of the live holder behind the last `held`. */
|
|
52
|
+
private holderPid;
|
|
53
|
+
constructor(path: string, env?: LockEnv, log?: ServiceLog, heartbeatMs?: number);
|
|
54
|
+
private body;
|
|
55
|
+
/** Try to hold the lock. Never throws (#808 L-e: machine reads included). */
|
|
56
|
+
acquire(): {
|
|
57
|
+
result: LockResult;
|
|
58
|
+
holderPid?: number;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* True while the file is still exactly the body this process wrote. Read
|
|
62
|
+
* before every reserve (one small read), so a holder whose lock was taken
|
|
63
|
+
* while it was frozen stops before it spends (#808 N1). A lost lock is
|
|
64
|
+
* dropped: the caller then refuses, and later calls try to re-acquire.
|
|
65
|
+
*/
|
|
66
|
+
stillHeld(): boolean;
|
|
67
|
+
private acquireUnguarded;
|
|
68
|
+
/** Remove the lock only while it is still exactly the body this process
|
|
69
|
+
* wrote (#808 N1: not the live hostname, which can change under us). */
|
|
70
|
+
release(): void;
|
|
71
|
+
private drop;
|
|
72
|
+
/** We hold it: remember what we wrote (the file as it is now, ours) and
|
|
73
|
+
* start the heartbeat. */
|
|
74
|
+
private took;
|
|
75
|
+
/** Refresh the mtime while the file is still ours; stop if it is not. */
|
|
76
|
+
private beat;
|
|
77
|
+
private sameBoot;
|
|
78
|
+
/** O_EXCL create with our body. A write failure removes the half-written
|
|
79
|
+
* file (it is ours: we created it) and reports `failed`, never throws. */
|
|
80
|
+
private create;
|
|
81
|
+
/**
|
|
82
|
+
* Replace a stale lock. Only the holder of the O_EXCL takeover guard may
|
|
83
|
+
* remove a lock, and only after re-reading it UNCHANGED, so two runtimes
|
|
84
|
+
* that both saw the same stale lock cannot each delete the other's new one.
|
|
85
|
+
* A fresh guard means a takeover is in progress: busy this time.
|
|
86
|
+
*/
|
|
87
|
+
private takeOver;
|
|
88
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { ServiceConfig } from "./config.js";
|
|
2
2
|
/** A deterministic launchd LaunchAgent plist. ProgramArguments re-invoke the
|
|
3
|
-
* running bin in run mode; KeepAlive restarts on
|
|
3
|
+
* running bin in run mode; KeepAlive{SuccessfulExit:false} restarts only on a non-zero exit (a clean exit stays down), RunAtLoad starts at login. */
|
|
4
4
|
export declare function renderLaunchdPlist(cfg: ServiceConfig): 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/service/status.d.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import type { AgentFleetEntry } from "./fleet.js";
|
|
2
|
+
import { type DaemonHealth } from "./install-prep.js";
|
|
2
3
|
export interface StatusDeps {
|
|
3
4
|
readFleet: () => AgentFleetEntry[];
|
|
4
|
-
|
|
5
|
-
getHealth: (port: number) =>
|
|
6
|
-
agent_sessions?: number;
|
|
7
|
-
evictions?: number;
|
|
8
|
-
} | null;
|
|
5
|
+
/** Routed `/healthz` probe (P-2): null unless the daemon answered a 200. */
|
|
6
|
+
getHealth: (port: number) => DaemonHealth | null;
|
|
9
7
|
}
|
|
10
8
|
export interface AgentStatusRow {
|
|
11
9
|
slug: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { ServiceConfig } from "./config.js";
|
|
2
2
|
/** A systemd --user unit. ExecStart re-invokes the running bin in run mode;
|
|
3
|
-
* Restart=
|
|
3
|
+
* Restart=on-failure supervises crashes only (a clean exit stays down). WantedBy=default.target enables at login. */
|
|
4
4
|
export declare function renderSystemdUnit(cfg: ServiceConfig): string;
|
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";
|
package/dist/ws-server.d.ts
CHANGED
|
@@ -34,6 +34,9 @@ export declare const INFLIGHT_STALE_AFTER_MS: number;
|
|
|
34
34
|
* consumer, so it never carried its notes.
|
|
35
35
|
*/
|
|
36
36
|
export declare const HELLO_WAIT_MS = 2000;
|
|
37
|
+
/** C9: the close code a superseded bridge sends its local runtime sockets;
|
|
38
|
+
* the same code the server sends the bridge (spec §4.1.5, OQ-9). */
|
|
39
|
+
export declare const WS_CLOSE_SESSION_SUPERSEDED = 4409;
|
|
37
40
|
/** Spec §8.5: the hello-reply feature that tells the runtime it may offer memory_note on this socket. */
|
|
38
41
|
export declare const MEMORY_NOTES_FEATURE = "memory_notes";
|
|
39
42
|
/** Semantic memory 7b (spec §7.2 Q4): the hello feature a runtime declares to receive `memory.recall`; the bridge echoes it. */
|
|
@@ -52,4 +55,5 @@ export declare const WS_AGENT_MEMBERS: readonly ["ackRuntimeMessages", "action",
|
|
|
52
55
|
export type WsAgent = Pick<BridgeAgent, (typeof WS_AGENT_MEMBERS)[number]>;
|
|
53
56
|
export declare function attachAgentWebSocket(server: HttpServer, agent: WsAgent, opts: AgentWebSocketOptions): {
|
|
54
57
|
close(): void;
|
|
58
|
+
enterSessionSuperseded(): void;
|
|
55
59
|
};
|