@securecoms/agent-bridge 0.5.0 → 0.7.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/connectors/openclaw/gate.js +41 -0
- package/connectors/openclaw/gate.test.ts +54 -0
- package/connectors/openclaw/openclaw-entry.gate.test.ts +321 -0
- package/connectors/openclaw/openclaw-entry.js +602 -0
- package/connectors/openclaw/openclaw-entry.smoke.test.ts +30 -0
- package/connectors/openclaw/openclaw.plugin.json +25 -0
- package/connectors/openclaw/package.json +18 -0
- package/dist/bin.d.ts +4 -1
- package/dist/bin.js +1063 -70
- package/dist/bridge-agent.d.ts +178 -5
- package/dist/index.js +468 -39
- package/dist/preflight-upgrade.d.cts +2 -0
- package/dist/preflight.cjs +31 -8
- package/dist/service/connection-file.d.ts +33 -0
- package/dist/service/fleet.d.ts +27 -0
- package/dist/service/harness-wire.d.ts +21 -0
- package/dist/service/install-prep.d.ts +5 -7
- package/dist/service/resolve-node.d.ts +13 -0
- package/dist/service/status.d.ts +25 -0
- package/dist/service/wire.d.ts +76 -0
- package/dist/ws-server.d.ts +1 -1
- package/package.json +5 -4
|
@@ -24,6 +24,8 @@ export interface UpgradeDeps {
|
|
|
24
24
|
cleanup: (p: string) => void;
|
|
25
25
|
resolveNewNode: () => string | null;
|
|
26
26
|
reexec: (nodePath: string) => boolean;
|
|
27
|
+
activeVersion: string;
|
|
28
|
+
existingCompatibleNode: () => string | null;
|
|
27
29
|
}
|
|
28
30
|
export declare function guidedUpgrade(deps: UpgradeDeps): Promise<boolean>;
|
|
29
31
|
export declare function resolveNewNodeFrom(probe: (p: string) => string | null, candidates?: string[]): string | null;
|
package/dist/preflight.cjs
CHANGED
|
@@ -21,6 +21,15 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
21
21
|
};
|
|
22
22
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
23
23
|
|
|
24
|
+
// src/service/resolve-node.ts
|
|
25
|
+
var REQUIRED_NODE_MAJOR;
|
|
26
|
+
var init_resolve_node = __esm({
|
|
27
|
+
"src/service/resolve-node.ts"() {
|
|
28
|
+
"use strict";
|
|
29
|
+
REQUIRED_NODE_MAJOR = 24;
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
24
33
|
// src/preflight-upgrade.cts
|
|
25
34
|
var preflight_upgrade_exports = {};
|
|
26
35
|
__export(preflight_upgrade_exports, {
|
|
@@ -54,6 +63,13 @@ async function promptYesNo(question, deps) {
|
|
|
54
63
|
async function guidedUpgrade(deps) {
|
|
55
64
|
if (deps.platform !== "darwin") return false;
|
|
56
65
|
try {
|
|
66
|
+
const existing = deps.existingCompatibleNode();
|
|
67
|
+
if (existing) {
|
|
68
|
+
deps.stderr(
|
|
69
|
+
"[bridge] Node " + deps.activeVersion + " is running, but a compatible Node (>= " + REQUIRED_NODE_MAJOR + ") is already installed at " + existing + " \u2014 it is shadowed earlier on your PATH. Continuing under it (no install needed).\n"
|
|
70
|
+
);
|
|
71
|
+
return deps.reexec(existing);
|
|
72
|
+
}
|
|
57
73
|
const viaBrew = deps.hasBrew();
|
|
58
74
|
const source = viaBrew ? "Homebrew (brew install node \u2014 user-space, no sudo)" : "the official installer from " + DIST_BASE + " (needs your admin password)";
|
|
59
75
|
const ok = await promptYesNo(
|
|
@@ -94,7 +110,7 @@ function resolveNewNodeFrom(probe, candidates) {
|
|
|
94
110
|
const list = candidates || NODE_CANDIDATES;
|
|
95
111
|
for (let i = 0; i < list.length; i++) {
|
|
96
112
|
const v = probe(list[i]);
|
|
97
|
-
if (v && Number(String(v).replace(/^v/, "").split(".")[0]) >=
|
|
113
|
+
if (v && Number(String(v).replace(/^v/, "").split(".")[0]) >= REQUIRED_NODE_MAJOR) return list[i];
|
|
98
114
|
}
|
|
99
115
|
return null;
|
|
100
116
|
}
|
|
@@ -104,6 +120,13 @@ function realUpgradeDeps(originalArgv) {
|
|
|
104
120
|
const os = require("os");
|
|
105
121
|
const https = require("https");
|
|
106
122
|
const readline = require("readline");
|
|
123
|
+
const probeNodeVersion = (p) => {
|
|
124
|
+
try {
|
|
125
|
+
return String(execFileSync(p, ["--version"], { encoding: "utf8" })).trim();
|
|
126
|
+
} catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
107
130
|
function fetch(url, redirects) {
|
|
108
131
|
return new Promise((resolve, reject) => {
|
|
109
132
|
https.get(url, (res) => {
|
|
@@ -162,13 +185,12 @@ function realUpgradeDeps(originalArgv) {
|
|
|
162
185
|
} catch {
|
|
163
186
|
}
|
|
164
187
|
},
|
|
165
|
-
resolveNewNode: () => resolveNewNodeFrom(
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}),
|
|
188
|
+
resolveNewNode: () => resolveNewNodeFrom(probeNodeVersion),
|
|
189
|
+
activeVersion: process.versions.node,
|
|
190
|
+
// Same candidate probe as resolveNewNode: is a Node >= REQUIRED_NODE_MAJOR
|
|
191
|
+
// already installed at a stable location (shadowed on PATH)? If so, the
|
|
192
|
+
// pre-check re-execs under it and never prompts to install.
|
|
193
|
+
existingCompatibleNode: () => resolveNewNodeFrom(probeNodeVersion),
|
|
172
194
|
reexec: (nodePath) => {
|
|
173
195
|
const child = spawnSync(nodePath, originalArgv.slice(1), { stdio: "inherit" });
|
|
174
196
|
process.exit(child.status !== null && child.status !== void 0 ? child.status : child.signal ? 130 : 1);
|
|
@@ -179,6 +201,7 @@ var DIST_BASE, NODE_CANDIDATES;
|
|
|
179
201
|
var init_preflight_upgrade = __esm({
|
|
180
202
|
"src/preflight-upgrade.cts"() {
|
|
181
203
|
"use strict";
|
|
204
|
+
init_resolve_node();
|
|
182
205
|
DIST_BASE = "https://nodejs.org/dist/latest-v24.x";
|
|
183
206
|
NODE_CANDIDATES = [
|
|
184
207
|
"/usr/local/bin/node",
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type ConnectionFileV1 = {
|
|
2
|
+
schema: "securecoms.connection/1";
|
|
3
|
+
identity_id: string;
|
|
4
|
+
bridge_url: string;
|
|
5
|
+
token_file: string;
|
|
6
|
+
instance_id: string;
|
|
7
|
+
slug: string;
|
|
8
|
+
label: string;
|
|
9
|
+
written_at: string;
|
|
10
|
+
};
|
|
11
|
+
export interface FsDeps {
|
|
12
|
+
mkdir: (p: string, mode: number) => void;
|
|
13
|
+
chmod: (p: string, mode: number) => void;
|
|
14
|
+
writeFile: (p: string, data: string, mode: number) => void;
|
|
15
|
+
rename: (a: string, b: string) => void;
|
|
16
|
+
readFile: (p: string) => string;
|
|
17
|
+
unlink: (p: string) => void;
|
|
18
|
+
}
|
|
19
|
+
export declare function securecomsDir(home: string): string;
|
|
20
|
+
export declare function connectionsDir(home: string): string;
|
|
21
|
+
export declare function connectionFilePath(home: string, identityId: string): string;
|
|
22
|
+
export declare function newInstanceId(): string;
|
|
23
|
+
export declare function buildConnectionFile(args: {
|
|
24
|
+
identityId: string;
|
|
25
|
+
port: number;
|
|
26
|
+
tokenFile: string;
|
|
27
|
+
slug?: string;
|
|
28
|
+
label?: string;
|
|
29
|
+
instanceId: string;
|
|
30
|
+
now: string;
|
|
31
|
+
}): ConnectionFileV1;
|
|
32
|
+
export declare function writeConnectionFile(home: string, doc: ConnectionFileV1, deps?: FsDeps): void;
|
|
33
|
+
export declare function removeConnectionFileIfOwned(home: string, identityId: string, instanceId: string, deps?: FsDeps): void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare function parseArgFromDef(def: string, flag: string): string | null;
|
|
2
|
+
export declare function parseExecPathFromDef(def: string): string | null;
|
|
3
|
+
export interface FleetDeps {
|
|
4
|
+
home: string;
|
|
5
|
+
listDir: (p: string) => string[];
|
|
6
|
+
readFile: (p: string) => string;
|
|
7
|
+
}
|
|
8
|
+
export interface AgentFleetEntry {
|
|
9
|
+
identityId: string;
|
|
10
|
+
deviceId: string;
|
|
11
|
+
slug: string;
|
|
12
|
+
label: string;
|
|
13
|
+
environment: "prod" | "preprod" | "unknown";
|
|
14
|
+
interpreterStable: boolean;
|
|
15
|
+
port: number | null;
|
|
16
|
+
interpreterPath: string | null;
|
|
17
|
+
credentialDir: string | null;
|
|
18
|
+
}
|
|
19
|
+
export declare function surfaceableProjection(e: AgentFleetEntry): {
|
|
20
|
+
identityId: string;
|
|
21
|
+
deviceId: string;
|
|
22
|
+
slug: string;
|
|
23
|
+
label: string;
|
|
24
|
+
environment: "unknown" | "prod" | "preprod";
|
|
25
|
+
interpreterStable: boolean;
|
|
26
|
+
};
|
|
27
|
+
export declare function readFleet(deps: FleetDeps): AgentFleetEntry[];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface AutoWireInput {
|
|
2
|
+
harness: string | undefined;
|
|
3
|
+
identityId: string;
|
|
4
|
+
slug: string;
|
|
5
|
+
port: number | null;
|
|
6
|
+
home: string;
|
|
7
|
+
tokenFilePath: string;
|
|
8
|
+
connectorsSrcDir: string;
|
|
9
|
+
}
|
|
10
|
+
export interface AutoWireDeps {
|
|
11
|
+
statDir: (p: string) => boolean;
|
|
12
|
+
readBinding: (dstDir: string) => string | null;
|
|
13
|
+
copyConnector: (srcDir: string, dstDir: string) => void;
|
|
14
|
+
writeBinding: (dstDir: string, identityId: string) => void;
|
|
15
|
+
log: (s: string) => void;
|
|
16
|
+
}
|
|
17
|
+
export declare function autoWireHarness(input: AutoWireInput, deps: AutoWireDeps): {
|
|
18
|
+
echo: string;
|
|
19
|
+
wired: boolean;
|
|
20
|
+
error: boolean;
|
|
21
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ServiceInput } from "./config.js";
|
|
2
|
+
import { type AutoWireDeps } from "./harness-wire.js";
|
|
2
3
|
export interface PrepDeps {
|
|
3
4
|
readFile: (p: string) => string;
|
|
4
5
|
writeFileMode: (p: string, data: string, mode: number) => void;
|
|
@@ -37,12 +38,6 @@ export declare function verifyDaemonUp(args: {
|
|
|
37
38
|
attempts?: number;
|
|
38
39
|
delayMs?: number;
|
|
39
40
|
}, 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
41
|
export interface OrchestrateInstallCtx {
|
|
47
42
|
credentialAbsPath: string;
|
|
48
43
|
label?: string;
|
|
@@ -51,7 +46,9 @@ export interface OrchestrateInstallCtx {
|
|
|
51
46
|
home: string;
|
|
52
47
|
launchAgentsDir?: string;
|
|
53
48
|
systemdDir?: string;
|
|
49
|
+
connectorsSrcDir: string;
|
|
54
50
|
prepDeps: PrepDeps;
|
|
51
|
+
autoWireDeps: AutoWireDeps;
|
|
55
52
|
installService: (input: ServiceInput) => {
|
|
56
53
|
label: string;
|
|
57
54
|
filePath: string;
|
|
@@ -64,5 +61,6 @@ export interface OrchestrateInstallCtx {
|
|
|
64
61
|
}
|
|
65
62
|
export declare function orchestrateInstall(ctx: OrchestrateInstallCtx): Promise<{
|
|
66
63
|
echo: string;
|
|
67
|
-
|
|
64
|
+
harnessWired: boolean;
|
|
65
|
+
harnessError: boolean;
|
|
68
66
|
}>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const REQUIRED_NODE_MAJOR = 24;
|
|
2
|
+
export declare function isVersionEmbeddedNodePath(p: string): boolean;
|
|
3
|
+
export interface ResolveNodeDeps {
|
|
4
|
+
realpath: (p: string) => string;
|
|
5
|
+
fileExists: (p: string) => boolean;
|
|
6
|
+
nodeMajorOf: (p: string) => number | null;
|
|
7
|
+
arch: () => string;
|
|
8
|
+
archOf: (p: string) => string | null;
|
|
9
|
+
}
|
|
10
|
+
export declare function resolveStableNodePath(runningExecPath: string, deps: ResolveNodeDeps): {
|
|
11
|
+
path: string;
|
|
12
|
+
stable: boolean;
|
|
13
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { AgentFleetEntry } from "./fleet.js";
|
|
2
|
+
export interface StatusDeps {
|
|
3
|
+
readFleet: () => AgentFleetEntry[];
|
|
4
|
+
probeConnect: (port: number) => boolean;
|
|
5
|
+
getHealth: (port: number) => {
|
|
6
|
+
agent_sessions?: number;
|
|
7
|
+
evictions?: number;
|
|
8
|
+
} | null;
|
|
9
|
+
}
|
|
10
|
+
export interface AgentStatusRow {
|
|
11
|
+
slug: string;
|
|
12
|
+
environment: string;
|
|
13
|
+
port: number | null;
|
|
14
|
+
bridge: "up" | "down";
|
|
15
|
+
harness: "attached" | "no brain" | "-";
|
|
16
|
+
interpreter: "stable" | "REBOOT-RISK";
|
|
17
|
+
/** Task 3 (audit M3): cumulative newest-wins WS evictions for this identity,
|
|
18
|
+
* read straight off the daemon's health(). Nonzero/climbing while harness
|
|
19
|
+
* stays "attached" means two connectors are flapping the same identity
|
|
20
|
+
* (split-brain), not one stable harness — 0 when the bridge is down or
|
|
21
|
+
* the daemon predates this counter. */
|
|
22
|
+
evictions: number;
|
|
23
|
+
}
|
|
24
|
+
export declare function readFleetStatus(deps: StatusDeps): AgentStatusRow[];
|
|
25
|
+
export declare function renderStatusTable(rows: AgentStatusRow[]): string;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { AgentFleetEntry } from "./fleet.js";
|
|
2
|
+
export type Harness = "openclaw" | "hermes";
|
|
3
|
+
export interface WireDeps {
|
|
4
|
+
home: string;
|
|
5
|
+
argv: string[];
|
|
6
|
+
readFleet: () => AgentFleetEntry[];
|
|
7
|
+
copyConnector: (srcDir: string, dstDir: string) => void;
|
|
8
|
+
writeBinding: (dstDir: string, identityId: string) => void;
|
|
9
|
+
removeBinding: (dstDir: string) => void;
|
|
10
|
+
getHealth: (port: number) => {
|
|
11
|
+
agent_sessions?: number;
|
|
12
|
+
} | null;
|
|
13
|
+
log: (s: string) => void;
|
|
14
|
+
/**
|
|
15
|
+
* Precomputed absolute path to the reference connector's source dir
|
|
16
|
+
* (`<pkgRoot>/connectors/openclaw`). REQUIRED: the only real caller
|
|
17
|
+
* (bin.ts's realWireDeps) always computes this via
|
|
18
|
+
* `connectorsSrcDir(import.meta.url)` using bin.ts's OWN import.meta.url —
|
|
19
|
+
* see that function's doc comment for why it has to be bin.ts's URL, not
|
|
20
|
+
* wire.ts's. A fallback that read wire.ts's own import.meta.url used to
|
|
21
|
+
* live here, but wire.ts sits one directory deeper than bin.ts
|
|
22
|
+
* (`src/service/wire.ts` vs `src/bin.ts`), so that fallback resolved to
|
|
23
|
+
* the wrong path — removed rather than fixed, since every real caller
|
|
24
|
+
* already supplies this field. Unit tests must pass a value too (any
|
|
25
|
+
* string is fine — `copyConnector` is mocked in tests and never inspects
|
|
26
|
+
* it for the non-hermes paths that reach it).
|
|
27
|
+
*/
|
|
28
|
+
connectorsSrcDir: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Detects an installed harness by probing well-known home-relative dirs.
|
|
32
|
+
* Checked in order: openclaw, then hermes. Returns null when neither is
|
|
33
|
+
* present — the caller (bin.ts's realWireDeps) must then require an explicit
|
|
34
|
+
* --harness rather than guess.
|
|
35
|
+
*/
|
|
36
|
+
export declare function detectHarness(home: string, statDir: (p: string) => boolean): Harness | null;
|
|
37
|
+
/**
|
|
38
|
+
* Where the connector gets installed for a given harness. Openclaw is the
|
|
39
|
+
* REFERENCE CONNECTOR (Task 6, connectors/openclaw/) and is fully wired.
|
|
40
|
+
* Hermes gets a defined target path so `detectHarness`/`wire --harness hermes`
|
|
41
|
+
* don't crash, but the Hermes CONNECTOR ITSELF is a fast-follow — only
|
|
42
|
+
* detection + this target dir ship in this task. Do not claim a working
|
|
43
|
+
* Hermes connector exists.
|
|
44
|
+
*/
|
|
45
|
+
export declare function connectorTargetDir(home: string, harness: Harness): string;
|
|
46
|
+
/**
|
|
47
|
+
* Resolves the reference connector's SOURCE dir at runtime. `connectors/` is
|
|
48
|
+
* shipped SOURCE (not compiled — H2), so this must resolve from the running
|
|
49
|
+
* file's location back to the package root, never into dist/. Callers pass
|
|
50
|
+
* their OWN import.meta.url (bin.ts's), not wire.ts's: esbuild bundles
|
|
51
|
+
* wire.ts's code into dist/bin.js, so only the entry point's import.meta.url
|
|
52
|
+
* is guaranteed to sit exactly one directory below the package root in BOTH
|
|
53
|
+
* dev (src/bin.ts) and the built artifact (dist/bin.js) — mirrors
|
|
54
|
+
* resolve-bin.ts's loadPackageMeta (`dist/bin.js -> ../package.json`).
|
|
55
|
+
*/
|
|
56
|
+
export declare function connectorsSrcDir(importMetaUrl: string): string;
|
|
57
|
+
/**
|
|
58
|
+
* Installs the reference connector into the harness's plugin dir and drops
|
|
59
|
+
* an explicit securecoms.binding.json naming the resolved agent's identity.
|
|
60
|
+
* There is NO auto-discovery (audit C1): the binding is written for EVERY
|
|
61
|
+
* agent, singleton included, every time `wire` runs.
|
|
62
|
+
*
|
|
63
|
+
* Hermes has no real connector yet (this round only ships detection +
|
|
64
|
+
* `connectorTargetDir`, see that function's doc comment) — refuse rather
|
|
65
|
+
* than install the openclaw connector's files into a Hermes plugin dir,
|
|
66
|
+
* which would be a false "installed" success.
|
|
67
|
+
*/
|
|
68
|
+
export declare function runWire(deps: WireDeps): number;
|
|
69
|
+
/** Removes the explicit binding for the resolved agent. Leaves the copied
|
|
70
|
+
* connector files in place — unwire un-names the agent, it doesn't uninstall
|
|
71
|
+
* the plugin (that stays a harness-native operation).
|
|
72
|
+
*
|
|
73
|
+
* Hermes never has anything installed by `runWire` (see its hermes guard),
|
|
74
|
+
* so there's nothing to remove — no-op cleanly rather than call
|
|
75
|
+
* `removeBinding` against a dir that was never created. */
|
|
76
|
+
export declare function runUnwire(deps: WireDeps): number;
|
package/dist/ws-server.d.ts
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* Inbound delivery is at-least-once. A delivered `message` is held in-flight
|
|
28
28
|
* until the agent sends a matching `ack`; if the socket drops first, the frame
|
|
29
29
|
* is re-buffered and redelivered to the next consumer. The agent dedups by
|
|
30
|
-
* message_id (
|
|
30
|
+
* message_id (a fresh UUID minted per send; server-side dedup is on replay_nonce).
|
|
31
31
|
*
|
|
32
32
|
* Single consumer: the daemon serves one agent per process. A new `/v1/ws`
|
|
33
33
|
* connection replaces the prior one (newest wins, mirroring the SSE path); the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@securecoms/agent-bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"dist",
|
|
18
|
+
"connectors",
|
|
18
19
|
"README.md"
|
|
19
20
|
],
|
|
20
21
|
"publishConfig": {
|
|
@@ -28,20 +29,20 @@
|
|
|
28
29
|
},
|
|
29
30
|
"dependencies": {
|
|
30
31
|
"@agentvault/crypto": "^0.4.1",
|
|
32
|
+
"@noble/ciphers": "^1.3.0",
|
|
31
33
|
"@noble/curves": "^1.9.7",
|
|
32
34
|
"@noble/hashes": "^1.4.0",
|
|
33
|
-
"@noble/ciphers": "^1.3.0",
|
|
34
35
|
"ajv": "^8.18.0",
|
|
35
36
|
"ajv-formats": "^3.0.1",
|
|
36
37
|
"ws": "^8.21.0",
|
|
37
38
|
"zod": "^3.23.0"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
41
|
+
"@agentvault/connect-core": "*",
|
|
40
42
|
"@types/node": "^22.0.0",
|
|
41
43
|
"@types/ws": "^8.18.1",
|
|
42
44
|
"esbuild": "0.24.0",
|
|
43
45
|
"typescript": "^5.4.0",
|
|
44
|
-
"vitest": "^2.0.0"
|
|
45
|
-
"@agentvault/connect-core": "*"
|
|
46
|
+
"vitest": "^2.0.0"
|
|
46
47
|
}
|
|
47
48
|
}
|