@securecoms/agent-bridge 0.3.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -1
- package/dist/bin.js +1073 -90
- package/dist/bridge-agent.d.ts +68 -7
- package/dist/config.d.ts +7 -0
- package/dist/http-server.d.ts +42 -0
- package/dist/index.js +837 -63
- package/dist/memory/hermes-provider.d.ts +21 -0
- package/dist/memory/hermes-session-map.d.ts +8 -0
- package/dist/memory/hermes-store.d.ts +32 -0
- package/dist/memory/hermes-test-fixture.d.ts +5 -0
- package/dist/memory/openclaw-provider.d.ts +24 -0
- package/dist/memory/openclaw-session-log.d.ts +15 -0
- package/dist/memory/openclaw-session-map.d.ts +7 -0
- package/dist/memory/provider.d.ts +29 -0
- package/dist/memory-taps.d.ts +13 -0
- package/dist/preflight-modern.cjs +35 -0
- package/dist/preflight-modern.d.cts +1 -0
- package/dist/preflight-upgrade.d.cts +30 -0
- package/dist/preflight.cjs +277 -0
- package/dist/preflight.d.cts +14 -0
- package/dist/service/config.d.ts +1 -0
- package/dist/service/install-prep.d.ts +68 -0
- package/package.json +3 -5
- package/dist/attachments.js +0 -49
- package/dist/auth.js +0 -56
- package/dist/bin.js.pre-c1-f803007 +0 -5795
- package/dist/bin.js.pre-c3-096a9a3 +0 -5832
- package/dist/bin.js.pre-ffrebuild-b162d4a +0 -5639
- package/dist/bin.js.pre-kpheal-20260708-150924 +0 -5531
- package/dist/bin.js.pre-kppersist-20260708-152341 +0 -5543
- package/dist/bin.js.pre-p0-20260708-102415 +0 -5196
- package/dist/bin.js.pre-p1b-02f47e1 +0 -5639
- package/dist/bin.js.pre-warmfix-20260708-153705 +0 -5613
- package/dist/bin.js.pretrace-113205 +0 -5395
- package/dist/bridge-agent.js +0 -831
- package/dist/config.js +0 -74
- package/dist/coordination.js +0 -63
- package/dist/errors.js +0 -25
- package/dist/http-server.js +0 -220
- package/dist/index.js.pre-p0-20260708-102415 +0 -4879
- package/dist/latency.js +0 -87
- package/dist/rate-limit.js +0 -56
- package/dist/service/config.js +0 -26
- package/dist/service/install.js +0 -60
- package/dist/service/launchd.js +0 -30
- package/dist/service/systemd.js +0 -25
- package/dist/ws-server.js +0 -254
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { MemoryProvider } from "./provider.js";
|
|
2
|
+
import type { MemoryRecordInput, MemoryContextResult } from "@agentvault/connect-mls-store";
|
|
3
|
+
/**
|
|
4
|
+
* Backs the MemoryProvider interface with Hermes's own ~/.hermes brain:
|
|
5
|
+
* context() reads state.db recent turns; record() writes voice turns back.
|
|
6
|
+
* Text records are no-ops (Hermes persists text natively). Fully fail-safe.
|
|
7
|
+
*/
|
|
8
|
+
export declare class HermesMemoryProvider implements MemoryProvider {
|
|
9
|
+
private readonly baseDir;
|
|
10
|
+
private readonly store;
|
|
11
|
+
private noSession;
|
|
12
|
+
private skipped;
|
|
13
|
+
private writeFailed;
|
|
14
|
+
constructor(baseDir: string);
|
|
15
|
+
record(input: MemoryRecordInput): Promise<{
|
|
16
|
+
seq: number;
|
|
17
|
+
deduped: boolean;
|
|
18
|
+
}>;
|
|
19
|
+
context(conversationId: string, limit?: number): Promise<MemoryContextResult>;
|
|
20
|
+
stats(): Record<string, number>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Expand a leading ~ to the user's home dir. Absolute/relative paths pass through. */
|
|
2
|
+
export declare function expandHome(p: string): string;
|
|
3
|
+
/**
|
|
4
|
+
* Resolve a SecureComs DM channel to its Hermes state.db session id via
|
|
5
|
+
* sessions.json, or null. DM keys only (group keys are out of scope this slice).
|
|
6
|
+
* Fail-safe: any read/parse error returns null.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveHermesSession(baseDir: string, conversationId: string): string | null;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface HermesTurn {
|
|
2
|
+
id: number;
|
|
3
|
+
role: "user" | "assistant";
|
|
4
|
+
content: string;
|
|
5
|
+
timestamp: number;
|
|
6
|
+
}
|
|
7
|
+
export declare class HermesStore {
|
|
8
|
+
private readonly dbPath;
|
|
9
|
+
constructor(baseDir: string);
|
|
10
|
+
/** Recent-K conversational turns for a session, chronological. Fail-safe → []. */
|
|
11
|
+
readRecent(sessionId: string, limit: number): HermesTurn[];
|
|
12
|
+
private open;
|
|
13
|
+
/**
|
|
14
|
+
* Append a voice turn to a session in one synchronous BEGIN IMMEDIATE block
|
|
15
|
+
* (no await between statements → atomic against Hermes and our own retries).
|
|
16
|
+
* Guards: skip when the session is absent/archived/ended; in-transaction dedup
|
|
17
|
+
* on (session_id, platform_message_id). Writes only `messages`. Fail-safe.
|
|
18
|
+
*/
|
|
19
|
+
writeVoiceTurn(input: {
|
|
20
|
+
sessionId: string;
|
|
21
|
+
role: "user" | "assistant";
|
|
22
|
+
content: string;
|
|
23
|
+
dedupKey: string | null;
|
|
24
|
+
timestamp: number;
|
|
25
|
+
}): "written" | "deduped" | "skipped" | "failed";
|
|
26
|
+
/**
|
|
27
|
+
* Startup write-capability probe: insert a sentinel against any existing
|
|
28
|
+
* session inside a rolled-back transaction. Throws if the write path is broken
|
|
29
|
+
* (e.g. a schema regression). No-op when the db has no sessions.
|
|
30
|
+
*/
|
|
31
|
+
probeWrite(): void;
|
|
32
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { MemoryProvider } from "./provider.js";
|
|
2
|
+
import type { SqliteMlsStateStore, MemoryRecordInput, MemoryContextResult } from "@agentvault/connect-mls-store";
|
|
3
|
+
/**
|
|
4
|
+
* MemoryProvider for an OpenClaw-hosted agent (Cortina). context() merges
|
|
5
|
+
* OpenClaw's own text history (read-only, plain session .jsonl) with the
|
|
6
|
+
* SecureComs daemon-local voice turns, ordered on one wall-clock. record()
|
|
7
|
+
* stores voice turns in the SC store ([voice]-prefixed); text is a no-op
|
|
8
|
+
* (OpenClaw owns text). OpenClaw files are NEVER written. Fully fail-safe:
|
|
9
|
+
* any text-side failure degrades to voice-only.
|
|
10
|
+
*/
|
|
11
|
+
export declare class OpenClawMemoryProvider implements MemoryProvider {
|
|
12
|
+
private readonly store;
|
|
13
|
+
private readonly baseDir;
|
|
14
|
+
private readonly agent;
|
|
15
|
+
private noSession;
|
|
16
|
+
private readFailed;
|
|
17
|
+
constructor(store: SqliteMlsStateStore, baseDir: string, agent: string);
|
|
18
|
+
record(input: MemoryRecordInput): Promise<{
|
|
19
|
+
seq: number;
|
|
20
|
+
deduped: boolean;
|
|
21
|
+
}>;
|
|
22
|
+
context(conversationId: string, limit?: number): Promise<MemoryContextResult>;
|
|
23
|
+
stats(): Record<string, number>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface OpenClawTurn {
|
|
2
|
+
role: "peer" | "agent";
|
|
3
|
+
text: string;
|
|
4
|
+
ts: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Read the last K conversational turns from the agent's owner-DM session log,
|
|
8
|
+
* the CLEAN plain <baseDir>/agents/<agent>/sessions/<sessionId>.jsonl (NEVER the
|
|
9
|
+
* .trajectory.jsonl, a .reset.* snapshot, or the `main` log). Ordering uses the
|
|
10
|
+
* top-level ISO `timestamp`; the inner message.timestamp is unreliable and unused.
|
|
11
|
+
* Normalization is an allowlist gated on role: keep user (string) and assistant
|
|
12
|
+
* (joined `text` blocks only); skip every other role and empty (tool-only)
|
|
13
|
+
* assistant turns. Fail-safe: any read/parse error returns [].
|
|
14
|
+
*/
|
|
15
|
+
export declare function readRecentTurns(baseDir: string, agent: string, sessionId: string, k: number): OpenClawTurn[];
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the agent's single OpenClaw owner-DM session id from sessions.json.
|
|
3
|
+
* OpenClaw collapses every owner DM into one `…:direct:…:owner` session and
|
|
4
|
+
* discards the SecureComs channel id, so the SC conversationId is NOT used here.
|
|
5
|
+
* Fail-safe: any read/parse error, or no owner-direct key, returns null.
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveOwnerDmSession(baseDir: string, agent: string): string | null;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { SqliteMlsStateStore, MemoryRecordInput, MemoryContextResult } from "@agentvault/connect-mls-store";
|
|
2
|
+
export interface MemoryProvider {
|
|
3
|
+
record(input: MemoryRecordInput): Promise<{
|
|
4
|
+
seq: number;
|
|
5
|
+
deduped: boolean;
|
|
6
|
+
}>;
|
|
7
|
+
context(conversationId: string, limit?: number): Promise<MemoryContextResult>;
|
|
8
|
+
/** Optional adapter counters surfaced on the daemon healthcheck. */
|
|
9
|
+
stats?(): Record<string, number>;
|
|
10
|
+
}
|
|
11
|
+
/** Default: the SecureComs-owned, daemon-local store (encrypted at rest). */
|
|
12
|
+
export declare class SecureComsLocalMemory implements MemoryProvider {
|
|
13
|
+
private readonly store;
|
|
14
|
+
constructor(store: SqliteMlsStateStore);
|
|
15
|
+
record(input: MemoryRecordInput): Promise<{
|
|
16
|
+
seq: number;
|
|
17
|
+
deduped: boolean;
|
|
18
|
+
}>;
|
|
19
|
+
context(conversationId: string, limit?: number): Promise<MemoryContextResult>;
|
|
20
|
+
}
|
|
21
|
+
/** BYO / disabled: the harness owns memory; SecureComs keeps none. */
|
|
22
|
+
export declare class NullMemory implements MemoryProvider {
|
|
23
|
+
record(): Promise<{
|
|
24
|
+
seq: number;
|
|
25
|
+
deduped: boolean;
|
|
26
|
+
}>;
|
|
27
|
+
context(): Promise<MemoryContextResult>;
|
|
28
|
+
}
|
|
29
|
+
export declare function createMemoryProvider(provider: "securecoms" | "none" | "hermes" | "openclaw", store: SqliteMlsStateStore, baseDir?: string, agent?: string): MemoryProvider;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { MemoryRecordInput } from "@agentvault/connect-mls-store";
|
|
2
|
+
interface MemorySink {
|
|
3
|
+
record(input: MemoryRecordInput): Promise<{
|
|
4
|
+
seq: number;
|
|
5
|
+
deduped: boolean;
|
|
6
|
+
}>;
|
|
7
|
+
}
|
|
8
|
+
/** Record an inbound (peer) text turn the agent actually consumed. Best-effort:
|
|
9
|
+
* never throws, so a memory failure can't break message delivery. */
|
|
10
|
+
export declare function recordInboundMemory(sink: MemorySink, channelId: string, senderId: string, text: string, messageId: string): Promise<void>;
|
|
11
|
+
/** Record the agent's own outbound text reply. Best-effort. */
|
|
12
|
+
export declare function recordOutboundMemory(sink: MemorySink, channelId: string, text: string, messageId: string): Promise<void>;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/preflight-modern.cts
|
|
21
|
+
var preflight_modern_exports = {};
|
|
22
|
+
__export(preflight_modern_exports, {
|
|
23
|
+
passThrough: () => passThrough
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(preflight_modern_exports);
|
|
26
|
+
var path = require("node:path");
|
|
27
|
+
function passThrough() {
|
|
28
|
+
const binPath = path.join(__dirname, "bin.js");
|
|
29
|
+
process.argv[1] = binPath;
|
|
30
|
+
return import(binPath);
|
|
31
|
+
}
|
|
32
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
33
|
+
0 && (module.exports = {
|
|
34
|
+
passThrough
|
|
35
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function passThrough(): Promise<unknown>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export declare function parseShasums(text: string, arch: string): {
|
|
2
|
+
filename: string;
|
|
3
|
+
sha256: string;
|
|
4
|
+
} | null;
|
|
5
|
+
export declare function sha256Hex(buf: Buffer): string;
|
|
6
|
+
export declare function promptYesNo(question: string, deps: {
|
|
7
|
+
isTTY: boolean;
|
|
8
|
+
ask: (q: string) => Promise<string>;
|
|
9
|
+
stderr: (s: string) => void;
|
|
10
|
+
}): Promise<boolean>;
|
|
11
|
+
export interface UpgradeDeps {
|
|
12
|
+
platform: string;
|
|
13
|
+
arch: string;
|
|
14
|
+
isTTY: boolean;
|
|
15
|
+
stderr: (s: string) => void;
|
|
16
|
+
ask: (q: string) => Promise<string>;
|
|
17
|
+
hasBrew: () => boolean;
|
|
18
|
+
runBrew: () => number;
|
|
19
|
+
fetchText: (url: string) => Promise<string>;
|
|
20
|
+
fetchBinary: (url: string, destPath: string) => Promise<void>;
|
|
21
|
+
fileSha256: (p: string) => string;
|
|
22
|
+
runSudoInstaller: (pkgPath: string) => number;
|
|
23
|
+
tmpdir: () => string;
|
|
24
|
+
cleanup: (p: string) => void;
|
|
25
|
+
resolveNewNode: () => string | null;
|
|
26
|
+
reexec: (nodePath: string) => boolean;
|
|
27
|
+
}
|
|
28
|
+
export declare function guidedUpgrade(deps: UpgradeDeps): Promise<boolean>;
|
|
29
|
+
export declare function resolveNewNodeFrom(probe: (p: string) => string | null, candidates?: string[]): string | null;
|
|
30
|
+
export declare function realUpgradeDeps(originalArgv: string[]): UpgradeDeps;
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __esm = (fn, res) => function __init() {
|
|
8
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
9
|
+
};
|
|
10
|
+
var __export = (target, all) => {
|
|
11
|
+
for (var name in all)
|
|
12
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
13
|
+
};
|
|
14
|
+
var __copyProps = (to, from, except, desc) => {
|
|
15
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
16
|
+
for (let key of __getOwnPropNames(from))
|
|
17
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
18
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
23
|
+
|
|
24
|
+
// src/preflight-upgrade.cts
|
|
25
|
+
var preflight_upgrade_exports = {};
|
|
26
|
+
__export(preflight_upgrade_exports, {
|
|
27
|
+
guidedUpgrade: () => guidedUpgrade,
|
|
28
|
+
parseShasums: () => parseShasums,
|
|
29
|
+
promptYesNo: () => promptYesNo,
|
|
30
|
+
realUpgradeDeps: () => realUpgradeDeps,
|
|
31
|
+
resolveNewNodeFrom: () => resolveNewNodeFrom,
|
|
32
|
+
sha256Hex: () => sha256Hex
|
|
33
|
+
});
|
|
34
|
+
function parseShasums(text, arch) {
|
|
35
|
+
const want = "-darwin-" + arch + ".pkg";
|
|
36
|
+
const lines = String(text).split("\n");
|
|
37
|
+
for (let i = 0; i < lines.length; i++) {
|
|
38
|
+
const m = lines[i].trim().match(/^([0-9a-f]{6,64})\s+(\S+)$/);
|
|
39
|
+
if (m && m[2].indexOf(want) !== -1 && m[2].indexOf("node-v24.") === 0) {
|
|
40
|
+
return { filename: m[2], sha256: m[1] };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
function sha256Hex(buf) {
|
|
46
|
+
const crypto = require("crypto");
|
|
47
|
+
return crypto.createHash("sha256").update(buf).digest("hex");
|
|
48
|
+
}
|
|
49
|
+
async function promptYesNo(question, deps) {
|
|
50
|
+
if (!deps.isTTY) return false;
|
|
51
|
+
const a = (await deps.ask(question)).trim().toLowerCase();
|
|
52
|
+
return a === "y" || a === "yes";
|
|
53
|
+
}
|
|
54
|
+
async function guidedUpgrade(deps) {
|
|
55
|
+
if (deps.platform !== "darwin") return false;
|
|
56
|
+
try {
|
|
57
|
+
const viaBrew = deps.hasBrew();
|
|
58
|
+
const source = viaBrew ? "Homebrew (brew install node \u2014 user-space, no sudo)" : "the official installer from " + DIST_BASE + " (needs your admin password)";
|
|
59
|
+
const ok = await promptYesNo(
|
|
60
|
+
"[bridge] Node 24 LTS is required. Install it now via " + source + "? [y/N] ",
|
|
61
|
+
deps
|
|
62
|
+
);
|
|
63
|
+
if (!ok) return false;
|
|
64
|
+
if (viaBrew) {
|
|
65
|
+
deps.stderr("[bridge] running: brew install node\n");
|
|
66
|
+
if (deps.runBrew() !== 0) return false;
|
|
67
|
+
} else {
|
|
68
|
+
const shasums = await deps.fetchText(DIST_BASE + "/SHASUMS256.txt");
|
|
69
|
+
const entry = parseShasums(shasums, deps.arch);
|
|
70
|
+
if (!entry) return false;
|
|
71
|
+
const dest = deps.tmpdir() + "/" + entry.filename;
|
|
72
|
+
deps.stderr("[bridge] downloading " + DIST_BASE + "/" + entry.filename + "\n");
|
|
73
|
+
try {
|
|
74
|
+
await deps.fetchBinary(DIST_BASE + "/" + entry.filename, dest);
|
|
75
|
+
if (deps.fileSha256(dest) !== entry.sha256) {
|
|
76
|
+
deps.stderr("[bridge] checksum mismatch \u2014 aborting install (file discarded)\n");
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
deps.stderr("[bridge] running: sudo installer -pkg " + entry.filename + " -target /\n");
|
|
80
|
+
if (deps.runSudoInstaller(dest) !== 0) return false;
|
|
81
|
+
} finally {
|
|
82
|
+
deps.cleanup(dest);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const fresh = deps.resolveNewNode();
|
|
86
|
+
if (!fresh) return false;
|
|
87
|
+
deps.stderr("[bridge] Node 24 installed \u2014 continuing under " + fresh + "\n");
|
|
88
|
+
return deps.reexec(fresh);
|
|
89
|
+
} catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function resolveNewNodeFrom(probe, candidates) {
|
|
94
|
+
const list = candidates || NODE_CANDIDATES;
|
|
95
|
+
for (let i = 0; i < list.length; i++) {
|
|
96
|
+
const v = probe(list[i]);
|
|
97
|
+
if (v && Number(String(v).replace(/^v/, "").split(".")[0]) >= 24) return list[i];
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
function realUpgradeDeps(originalArgv) {
|
|
102
|
+
const { spawnSync, execFileSync } = require("child_process");
|
|
103
|
+
const { createWriteStream, readFileSync, rmSync } = require("fs");
|
|
104
|
+
const os = require("os");
|
|
105
|
+
const https = require("https");
|
|
106
|
+
const readline = require("readline");
|
|
107
|
+
function fetch(url, redirects) {
|
|
108
|
+
return new Promise((resolve, reject) => {
|
|
109
|
+
https.get(url, (res) => {
|
|
110
|
+
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location && redirects > 0) {
|
|
111
|
+
res.resume();
|
|
112
|
+
fetch(res.headers.location, redirects - 1).then(resolve, reject);
|
|
113
|
+
} else if (res.statusCode !== 200) {
|
|
114
|
+
res.resume();
|
|
115
|
+
reject(new Error("HTTP " + res.statusCode + " for " + url));
|
|
116
|
+
} else resolve(res);
|
|
117
|
+
}).on("error", reject);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
platform: process.platform,
|
|
122
|
+
arch: process.arch,
|
|
123
|
+
isTTY: Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
124
|
+
stderr: (s) => process.stderr.write(s),
|
|
125
|
+
ask: (q) => new Promise((resolve) => {
|
|
126
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
127
|
+
rl.question(q, (a) => {
|
|
128
|
+
rl.close();
|
|
129
|
+
resolve(a);
|
|
130
|
+
});
|
|
131
|
+
}),
|
|
132
|
+
hasBrew: () => spawnSync("brew", ["--version"], { stdio: "ignore" }).status === 0,
|
|
133
|
+
runBrew: () => {
|
|
134
|
+
var _a;
|
|
135
|
+
return (_a = spawnSync("brew", ["install", "node"], { stdio: "inherit" }).status) != null ? _a : 1;
|
|
136
|
+
},
|
|
137
|
+
fetchText: async (url) => {
|
|
138
|
+
const res = await fetch(url, 3);
|
|
139
|
+
let out = "";
|
|
140
|
+
for await (const chunk of res) out += chunk;
|
|
141
|
+
return out;
|
|
142
|
+
},
|
|
143
|
+
fetchBinary: async (url, destPath) => {
|
|
144
|
+
const res = await fetch(url, 3);
|
|
145
|
+
await new Promise((resolve, reject) => {
|
|
146
|
+
const ws = createWriteStream(destPath);
|
|
147
|
+
res.pipe(ws);
|
|
148
|
+
ws.on("finish", resolve);
|
|
149
|
+
ws.on("error", reject);
|
|
150
|
+
res.on("error", reject);
|
|
151
|
+
});
|
|
152
|
+
},
|
|
153
|
+
fileSha256: (p) => sha256Hex(readFileSync(p)),
|
|
154
|
+
runSudoInstaller: (pkgPath) => {
|
|
155
|
+
var _a;
|
|
156
|
+
return (_a = spawnSync("sudo", ["installer", "-pkg", pkgPath, "-target", "/"], { stdio: "inherit" }).status) != null ? _a : 1;
|
|
157
|
+
},
|
|
158
|
+
tmpdir: () => os.tmpdir(),
|
|
159
|
+
cleanup: (p) => {
|
|
160
|
+
try {
|
|
161
|
+
rmSync(p, { force: true });
|
|
162
|
+
} catch {
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
resolveNewNode: () => resolveNewNodeFrom((p) => {
|
|
166
|
+
try {
|
|
167
|
+
return String(execFileSync(p, ["--version"], { encoding: "utf8" })).trim();
|
|
168
|
+
} catch {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}),
|
|
172
|
+
reexec: (nodePath) => {
|
|
173
|
+
const child = spawnSync(nodePath, originalArgv.slice(1), { stdio: "inherit" });
|
|
174
|
+
process.exit(child.status !== null && child.status !== void 0 ? child.status : child.signal ? 130 : 1);
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
var DIST_BASE, NODE_CANDIDATES;
|
|
179
|
+
var init_preflight_upgrade = __esm({
|
|
180
|
+
"src/preflight-upgrade.cts"() {
|
|
181
|
+
"use strict";
|
|
182
|
+
DIST_BASE = "https://nodejs.org/dist/latest-v24.x";
|
|
183
|
+
NODE_CANDIDATES = [
|
|
184
|
+
"/usr/local/bin/node",
|
|
185
|
+
// nodejs.org pkg (stable across upgrades)
|
|
186
|
+
"/opt/homebrew/bin/node",
|
|
187
|
+
// brew symlink (Apple Silicon)
|
|
188
|
+
"/opt/homebrew/opt/node/bin/node"
|
|
189
|
+
];
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// src/preflight.cts
|
|
194
|
+
var preflight_exports = {};
|
|
195
|
+
__export(preflight_exports, {
|
|
196
|
+
effectiveVersion: () => effectiveVersion,
|
|
197
|
+
instructionsBlock: () => instructionsBlock,
|
|
198
|
+
meetsFloor: () => meetsFloor,
|
|
199
|
+
parseNodeMajor: () => parseNodeMajor,
|
|
200
|
+
runPreflight: () => runPreflight
|
|
201
|
+
});
|
|
202
|
+
module.exports = __toCommonJS(preflight_exports);
|
|
203
|
+
var NODE_FLOOR = 24;
|
|
204
|
+
var FLOOR_REASON = "requires >= 24 (node:sqlite stable API)";
|
|
205
|
+
function parseNodeMajor(version) {
|
|
206
|
+
const n = Number(String(version).split(".")[0]);
|
|
207
|
+
return Number.isFinite(n) && n > 0 ? n : 0;
|
|
208
|
+
}
|
|
209
|
+
function meetsFloor(version) {
|
|
210
|
+
return parseNodeMajor(version) >= NODE_FLOOR;
|
|
211
|
+
}
|
|
212
|
+
function effectiveVersion(real, faked) {
|
|
213
|
+
if (!faked) return real;
|
|
214
|
+
const f = parseNodeMajor(faked);
|
|
215
|
+
if (f >= 1 && f < parseNodeMajor(real)) return faked;
|
|
216
|
+
return real;
|
|
217
|
+
}
|
|
218
|
+
function instructionsBlock(platform, current) {
|
|
219
|
+
const lines = [
|
|
220
|
+
"[bridge] Node " + current + " is too old \u2014 SecureComs " + FLOOR_REASON + ".",
|
|
221
|
+
"",
|
|
222
|
+
"How to get Node 24 LTS:",
|
|
223
|
+
" 1. Official installer (recommended): https://nodejs.org \u2014 download the",
|
|
224
|
+
" Node 24 LTS installer for your platform and run it."
|
|
225
|
+
];
|
|
226
|
+
if (platform === "darwin") {
|
|
227
|
+
lines.push(" 2. Homebrew: brew install node");
|
|
228
|
+
}
|
|
229
|
+
lines.push(" " + (platform === "darwin" ? "3" : "2") + ". nvm: nvm install 24 && nvm use 24");
|
|
230
|
+
lines.push(
|
|
231
|
+
"",
|
|
232
|
+
"Then re-run the install command. (Always re-run the install command after",
|
|
233
|
+
"upgrading Node \u2014 the background service pins the Node path it was",
|
|
234
|
+
"installed with.)"
|
|
235
|
+
);
|
|
236
|
+
return lines.join("\n") + "\n";
|
|
237
|
+
}
|
|
238
|
+
async function runPreflight(deps) {
|
|
239
|
+
if (meetsFloor(deps.version)) {
|
|
240
|
+
await deps.delegate();
|
|
241
|
+
return 0;
|
|
242
|
+
}
|
|
243
|
+
if (deps.upgrade) {
|
|
244
|
+
if (await deps.upgrade()) return 0;
|
|
245
|
+
}
|
|
246
|
+
deps.stderr(instructionsBlock(deps.platform, deps.version));
|
|
247
|
+
return 1;
|
|
248
|
+
}
|
|
249
|
+
if (require.main === module) {
|
|
250
|
+
const version = effectiveVersion(
|
|
251
|
+
process.versions.node,
|
|
252
|
+
process.env.SECURECOMS_PREFLIGHT_FAKE_NODE_VERSION
|
|
253
|
+
);
|
|
254
|
+
runPreflight({
|
|
255
|
+
version,
|
|
256
|
+
platform: process.platform,
|
|
257
|
+
isTTY: Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
258
|
+
stderr: (s) => process.stderr.write(s),
|
|
259
|
+
// The modern half is only ever require()d here, AFTER the gate passed —
|
|
260
|
+
// old Node never parses its dynamic-import syntax.
|
|
261
|
+
delegate: () => require("./preflight-modern.cjs").passThrough(),
|
|
262
|
+
upgrade: () => {
|
|
263
|
+
const up = (init_preflight_upgrade(), __toCommonJS(preflight_upgrade_exports));
|
|
264
|
+
return up.guidedUpgrade(up.realUpgradeDeps(process.argv));
|
|
265
|
+
}
|
|
266
|
+
}).then((code) => {
|
|
267
|
+
if (code !== 0) process.exit(code);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
271
|
+
0 && (module.exports = {
|
|
272
|
+
effectiveVersion,
|
|
273
|
+
instructionsBlock,
|
|
274
|
+
meetsFloor,
|
|
275
|
+
parseNodeMajor,
|
|
276
|
+
runPreflight
|
|
277
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export declare function parseNodeMajor(version: string): number;
|
|
3
|
+
export declare function meetsFloor(version: string): boolean;
|
|
4
|
+
export declare function effectiveVersion(real: string, faked: string | undefined): string;
|
|
5
|
+
export declare function instructionsBlock(platform: string, current: string): string;
|
|
6
|
+
export interface PreflightDeps {
|
|
7
|
+
version: string;
|
|
8
|
+
platform: string;
|
|
9
|
+
isTTY: boolean;
|
|
10
|
+
stderr: (s: string) => void;
|
|
11
|
+
delegate: () => Promise<unknown>;
|
|
12
|
+
upgrade: (() => Promise<boolean>) | null;
|
|
13
|
+
}
|
|
14
|
+
export declare function runPreflight(deps: PreflightDeps): Promise<number>;
|
package/dist/service/config.d.ts
CHANGED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { type ServiceInput } from "./config.js";
|
|
2
|
+
export interface PrepDeps {
|
|
3
|
+
readFile: (p: string) => string;
|
|
4
|
+
writeFileMode: (p: string, data: string, mode: number) => void;
|
|
5
|
+
mkdirp: (p: string, mode?: number) => void;
|
|
6
|
+
fileExists: (p: string) => boolean;
|
|
7
|
+
realpath: (p: string) => string;
|
|
8
|
+
listDir: (p: string) => string[];
|
|
9
|
+
probeBindFree: (port: number) => boolean;
|
|
10
|
+
probeConnect: (port: number) => boolean;
|
|
11
|
+
sleep: (ms: number) => Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export interface ParsedCredential {
|
|
14
|
+
deviceId: string;
|
|
15
|
+
identityId: string;
|
|
16
|
+
issuedAt: string | null;
|
|
17
|
+
}
|
|
18
|
+
export declare function assertReadableCredential(absPath: string, deps: PrepDeps): ParsedCredential;
|
|
19
|
+
export declare function durableDir(home: string, slug: string): string;
|
|
20
|
+
export declare function durableCredentialPath(home: string, slug: string): string;
|
|
21
|
+
export declare function takeCustody(args: {
|
|
22
|
+
home: string;
|
|
23
|
+
slug: string;
|
|
24
|
+
sourceAbsPath: string;
|
|
25
|
+
parsed: ParsedCredential;
|
|
26
|
+
}, deps: PrepDeps): string;
|
|
27
|
+
export declare const PORT_MIN = 7077;
|
|
28
|
+
export declare const PORT_MAX = 7276;
|
|
29
|
+
export declare function parseClaimedPorts(fileContents: string[]): Set<number>;
|
|
30
|
+
export declare function selectPort(args: {
|
|
31
|
+
launchAgentsDir: string;
|
|
32
|
+
systemdDir: string;
|
|
33
|
+
}, deps: PrepDeps): number;
|
|
34
|
+
export declare function verifyDaemonUp(args: {
|
|
35
|
+
port: number;
|
|
36
|
+
logPath: string;
|
|
37
|
+
attempts?: number;
|
|
38
|
+
delayMs?: number;
|
|
39
|
+
}, deps: PrepDeps): Promise<boolean>;
|
|
40
|
+
export declare function harnessSnippet(args: {
|
|
41
|
+
slug: string;
|
|
42
|
+
port: number;
|
|
43
|
+
tokenFilePath: string;
|
|
44
|
+
harness: string;
|
|
45
|
+
}): string;
|
|
46
|
+
export interface OrchestrateInstallCtx {
|
|
47
|
+
credentialAbsPath: string;
|
|
48
|
+
label?: string;
|
|
49
|
+
explicitPort?: number;
|
|
50
|
+
harness?: string;
|
|
51
|
+
home: string;
|
|
52
|
+
launchAgentsDir?: string;
|
|
53
|
+
systemdDir?: string;
|
|
54
|
+
prepDeps: PrepDeps;
|
|
55
|
+
installService: (input: ServiceInput) => {
|
|
56
|
+
label: string;
|
|
57
|
+
filePath: string;
|
|
58
|
+
logHint: string;
|
|
59
|
+
tokenHint: string;
|
|
60
|
+
};
|
|
61
|
+
buildServiceInput: (durableCredentialPath: string, port: number, slug: string) => ServiceInput;
|
|
62
|
+
verifyAttempts?: number;
|
|
63
|
+
verifyDelayMs?: number;
|
|
64
|
+
}
|
|
65
|
+
export declare function orchestrateInstall(ctx: OrchestrateInstallCtx): Promise<{
|
|
66
|
+
echo: string;
|
|
67
|
+
harnessSnippetShown: boolean;
|
|
68
|
+
}>;
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@securecoms/agent-bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"bin": {
|
|
8
|
-
"agent-bridge": "dist/
|
|
8
|
+
"agent-bridge": "dist/preflight.cjs"
|
|
9
9
|
},
|
|
10
10
|
"exports": {
|
|
11
11
|
".": {
|
|
@@ -17,13 +17,11 @@
|
|
|
17
17
|
"dist",
|
|
18
18
|
"README.md"
|
|
19
19
|
],
|
|
20
|
-
"engines": {
|
|
21
|
-
"node": ">=24"
|
|
22
|
-
},
|
|
23
20
|
"publishConfig": {
|
|
24
21
|
"access": "public"
|
|
25
22
|
},
|
|
26
23
|
"scripts": {
|
|
24
|
+
"prepack": "npm run build",
|
|
27
25
|
"build": "tsc && node esbuild.config.mjs",
|
|
28
26
|
"test": "vitest run",
|
|
29
27
|
"test:watch": "vitest"
|