@securecoms/agent-bridge 0.4.0 → 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 +10 -0
- package/dist/bin.js +248 -31
- package/dist/config.d.ts +1 -0
- package/dist/index.js +10 -2
- package/dist/service/config.d.ts +1 -0
- package/dist/service/install-prep.d.ts +68 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -348,6 +348,16 @@ pm2 start "npx @agentvault/connect-agent-bridge --credential ./agent.json" \
|
|
|
348
348
|
--name securecoms-bridge
|
|
349
349
|
```
|
|
350
350
|
|
|
351
|
+
### Install behavior
|
|
352
|
+
|
|
353
|
+
`agent-bridge install --credential <path>` (launchd on macOS, systemd user unit on Linux) prepares and verifies the service rather than pointing it at your download:
|
|
354
|
+
|
|
355
|
+
- The credential file is validated (readable, valid JSON, all required fields) and **copied into a durable per-agent directory** `~/securecoms-agent-<slug>/` (dir `0700`, file `0600`). The service runs from that copy, so moving or deleting the original download can't break the daemon. A directory already owned by a *different* agent identity is refused, never overwritten.
|
|
356
|
+
- A **free port is auto-selected** from `7077-7276` (lowest free wins; sibling service definitions and live binds both count as taken). Pass `--port N` to override; the explicit port is honored as-is.
|
|
357
|
+
- Install **verifies the daemon actually came up** and fails loudly with the log path if it didn't — no silent crash-loops.
|
|
358
|
+
- Install echoes the credential's `device_id`, `issued_at`, and a redacted api-key fingerprint so you can confirm you installed the newest download. **Re-running install is the only way to update a live (e.g. rotated) credential** — replacing the downloaded file alone does nothing.
|
|
359
|
+
- Optional `--harness openclaw|hermes|custom` prints a paste-ready config snippet with the chosen port and the token-file path (the token itself never appears in output).
|
|
360
|
+
|
|
351
361
|
---
|
|
352
362
|
|
|
353
363
|
## Security
|
package/dist/bin.js
CHANGED
|
@@ -2725,6 +2725,15 @@ function parseServerFrame(frame) {
|
|
|
2725
2725
|
}
|
|
2726
2726
|
|
|
2727
2727
|
// ../connect-sdk-ts/dist/transport/ws-pings.js
|
|
2728
|
+
function base64UrlNoPad(s) {
|
|
2729
|
+
const bytes = new TextEncoder().encode(s);
|
|
2730
|
+
let bin = "";
|
|
2731
|
+
for (const b of bytes)
|
|
2732
|
+
bin += String.fromCharCode(b);
|
|
2733
|
+
const b64 = typeof btoa === "function" ? btoa(bin) : Buffer.from(bytes).toString("base64");
|
|
2734
|
+
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
2735
|
+
}
|
|
2736
|
+
var BEARER_SUBPROTOCOL = "securecoms.bearer.v1";
|
|
2728
2737
|
function openDeliveryPings(o) {
|
|
2729
2738
|
const base = o.backoffBaseMs ?? 500;
|
|
2730
2739
|
const cap = o.backoffCapMs ?? 8e3;
|
|
@@ -2873,8 +2882,7 @@ function openDeliveryPings(o) {
|
|
|
2873
2882
|
}
|
|
2874
2883
|
if (closed || gen !== connectGen)
|
|
2875
2884
|
return;
|
|
2876
|
-
const
|
|
2877
|
-
const ws = new o.wsImpl(`${o.url}${sep}token=${encodeURIComponent(token)}`);
|
|
2885
|
+
const ws = new o.wsImpl(o.url, [BEARER_SUBPROTOCOL, base64UrlNoPad(token)]);
|
|
2878
2886
|
sock = ws;
|
|
2879
2887
|
ws.onopen = () => {
|
|
2880
2888
|
push({ reconnected: true });
|
|
@@ -2974,8 +2982,8 @@ function openDeliveryPings(o) {
|
|
|
2974
2982
|
return Promise.resolve({ value: void 0, done: true });
|
|
2975
2983
|
if (queue.length)
|
|
2976
2984
|
return Promise.resolve({ value: queue.shift(), done: false });
|
|
2977
|
-
return new Promise((
|
|
2978
|
-
waiter =
|
|
2985
|
+
return new Promise((resolve3) => {
|
|
2986
|
+
waiter = resolve3;
|
|
2979
2987
|
});
|
|
2980
2988
|
}
|
|
2981
2989
|
};
|
|
@@ -5693,7 +5701,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
5693
5701
|
let got = this.takeBuffered(channelId);
|
|
5694
5702
|
if (got.length || waitSeconds <= 0) return got;
|
|
5695
5703
|
const cap = Math.min(waitSeconds, 50) * 1e3;
|
|
5696
|
-
await new Promise((
|
|
5704
|
+
await new Promise((resolve3) => {
|
|
5697
5705
|
let fired = false;
|
|
5698
5706
|
let timer;
|
|
5699
5707
|
const wake = () => {
|
|
@@ -5702,7 +5710,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
5702
5710
|
clearTimeout(timer);
|
|
5703
5711
|
const i = this.waiters.indexOf(wake);
|
|
5704
5712
|
if (i >= 0) this.waiters.splice(i, 1);
|
|
5705
|
-
|
|
5713
|
+
resolve3();
|
|
5706
5714
|
};
|
|
5707
5715
|
timer = setTimeout(wake, cap);
|
|
5708
5716
|
this.waiters.push(wake);
|
|
@@ -5961,7 +5969,7 @@ function jsonError(res, code, detail) {
|
|
|
5961
5969
|
json(res, httpStatus(code), errorBody(code, detail));
|
|
5962
5970
|
}
|
|
5963
5971
|
function readBody(req, cap) {
|
|
5964
|
-
return new Promise((
|
|
5972
|
+
return new Promise((resolve3, reject) => {
|
|
5965
5973
|
let size = 0;
|
|
5966
5974
|
let done = false;
|
|
5967
5975
|
const chunks = [];
|
|
@@ -5981,7 +5989,7 @@ function readBody(req, cap) {
|
|
|
5981
5989
|
req.on("end", () => {
|
|
5982
5990
|
if (!done) {
|
|
5983
5991
|
done = true;
|
|
5984
|
-
|
|
5992
|
+
resolve3(Buffer.concat(chunks).toString("utf8"));
|
|
5985
5993
|
}
|
|
5986
5994
|
});
|
|
5987
5995
|
req.on("error", (e) => {
|
|
@@ -6395,8 +6403,8 @@ async function handleInbound(sock, agent, data, inflight) {
|
|
|
6395
6403
|
}
|
|
6396
6404
|
|
|
6397
6405
|
// src/bin.ts
|
|
6398
|
-
import { readFileSync as readFileSync6, mkdirSync, writeFileSync as writeFileSync2, rmSync, existsSync as existsSync2, realpathSync } from "node:fs";
|
|
6399
|
-
import { join as
|
|
6406
|
+
import { readFileSync as readFileSync6, mkdirSync, writeFileSync as writeFileSync2, rmSync, existsSync as existsSync2, realpathSync, readdirSync } from "node:fs";
|
|
6407
|
+
import { join as join9, dirname as dirname5, isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
|
|
6400
6408
|
import { fileURLToPath } from "node:url";
|
|
6401
6409
|
import { homedir as homedir2, userInfo } from "node:os";
|
|
6402
6410
|
import { spawnSync } from "node:child_process";
|
|
@@ -6531,6 +6539,161 @@ function uninstallService(input, deps) {
|
|
|
6531
6539
|
throw new ServiceUnsupportedError(`Service uninstall isn't supported on ${deps.platform} yet.`);
|
|
6532
6540
|
}
|
|
6533
6541
|
|
|
6542
|
+
// src/service/install-prep.ts
|
|
6543
|
+
import { join as join8 } from "node:path";
|
|
6544
|
+
function assertReadableCredential(absPath, deps) {
|
|
6545
|
+
let raw;
|
|
6546
|
+
try {
|
|
6547
|
+
raw = deps.readFile(absPath);
|
|
6548
|
+
} catch {
|
|
6549
|
+
throw new Error(`install: credential file not found or unreadable: ${absPath}`);
|
|
6550
|
+
}
|
|
6551
|
+
let json2;
|
|
6552
|
+
try {
|
|
6553
|
+
json2 = JSON.parse(raw);
|
|
6554
|
+
} catch {
|
|
6555
|
+
throw new Error(`install: credential file is not valid JSON: ${absPath}`);
|
|
6556
|
+
}
|
|
6557
|
+
for (const k of REQUIRED) {
|
|
6558
|
+
if (typeof json2[k] !== "string" || !json2[k]) {
|
|
6559
|
+
throw new Error(`install: credential file missing required field '${k}': ${absPath}`);
|
|
6560
|
+
}
|
|
6561
|
+
}
|
|
6562
|
+
const issuedAt = typeof json2.issued_at === "string" && json2.issued_at ? json2.issued_at : null;
|
|
6563
|
+
return { deviceId: json2.device_id, identityId: json2.identity_id, issuedAt };
|
|
6564
|
+
}
|
|
6565
|
+
function durableDir(home, slug) {
|
|
6566
|
+
return join8(home, `securecoms-agent-${slug}`);
|
|
6567
|
+
}
|
|
6568
|
+
function durableCredentialPath(home, slug) {
|
|
6569
|
+
return join8(durableDir(home, slug), `${slug}.json`);
|
|
6570
|
+
}
|
|
6571
|
+
function takeCustody(args2, deps) {
|
|
6572
|
+
const dir = durableDir(args2.home, args2.slug);
|
|
6573
|
+
const dest = durableCredentialPath(args2.home, args2.slug);
|
|
6574
|
+
try {
|
|
6575
|
+
if (deps.fileExists(dest) && deps.realpath(args2.sourceAbsPath) === deps.realpath(dest)) return dest;
|
|
6576
|
+
} catch {
|
|
6577
|
+
}
|
|
6578
|
+
if (deps.fileExists(dest)) {
|
|
6579
|
+
try {
|
|
6580
|
+
const existing = JSON.parse(deps.readFile(dest));
|
|
6581
|
+
if (existing.device_id && existing.device_id !== args2.parsed.deviceId) {
|
|
6582
|
+
throw new Error(
|
|
6583
|
+
`install: ${dir} already belongs to a different agent (device ${existing.device_id}). Pass a distinct --label to install this agent in its own directory.`
|
|
6584
|
+
);
|
|
6585
|
+
}
|
|
6586
|
+
} catch (e) {
|
|
6587
|
+
if (e instanceof Error && e.message.startsWith("install:")) throw e;
|
|
6588
|
+
}
|
|
6589
|
+
}
|
|
6590
|
+
deps.mkdirp(dir, 448);
|
|
6591
|
+
deps.writeFileMode(dest, deps.readFile(args2.sourceAbsPath), 384);
|
|
6592
|
+
return dest;
|
|
6593
|
+
}
|
|
6594
|
+
var PORT_MIN = 7077;
|
|
6595
|
+
var PORT_MAX = 7276;
|
|
6596
|
+
function parseClaimedPorts(fileContents) {
|
|
6597
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
6598
|
+
const re = /--port(?:<\/string>\s*<string>|\s+)(\d+)/g;
|
|
6599
|
+
for (const text of fileContents) {
|
|
6600
|
+
let m;
|
|
6601
|
+
while ((m = re.exec(text)) !== null) claimed.add(Number(m[1]));
|
|
6602
|
+
}
|
|
6603
|
+
return claimed;
|
|
6604
|
+
}
|
|
6605
|
+
function readSiblingDefs(dir, prefix, deps) {
|
|
6606
|
+
let names;
|
|
6607
|
+
try {
|
|
6608
|
+
names = deps.listDir(dir);
|
|
6609
|
+
} catch {
|
|
6610
|
+
return [];
|
|
6611
|
+
}
|
|
6612
|
+
const out = [];
|
|
6613
|
+
for (const n of names) {
|
|
6614
|
+
if (!n.startsWith(prefix)) continue;
|
|
6615
|
+
try {
|
|
6616
|
+
out.push(deps.readFile(join8(dir, n)));
|
|
6617
|
+
} catch {
|
|
6618
|
+
}
|
|
6619
|
+
}
|
|
6620
|
+
return out;
|
|
6621
|
+
}
|
|
6622
|
+
function selectPort(args2, deps) {
|
|
6623
|
+
const defs = [
|
|
6624
|
+
...readSiblingDefs(args2.launchAgentsDir, "com.securecoms.bridge.", deps),
|
|
6625
|
+
...readSiblingDefs(args2.systemdDir, "securecoms-bridge-", deps)
|
|
6626
|
+
];
|
|
6627
|
+
const claimed = parseClaimedPorts(defs);
|
|
6628
|
+
for (let port = PORT_MIN; port <= PORT_MAX; port++) {
|
|
6629
|
+
if (claimed.has(port)) continue;
|
|
6630
|
+
if (!deps.probeBindFree(port)) continue;
|
|
6631
|
+
return port;
|
|
6632
|
+
}
|
|
6633
|
+
throw new Error(`install: no free port in ${PORT_MIN}-${PORT_MAX} \u2014 stop an unused bridge or pass --port`);
|
|
6634
|
+
}
|
|
6635
|
+
async function verifyDaemonUp(args2, deps) {
|
|
6636
|
+
const attempts = args2.attempts ?? 20;
|
|
6637
|
+
const delayMs = args2.delayMs ?? 250;
|
|
6638
|
+
for (let i = 0; i < attempts; i++) {
|
|
6639
|
+
if (deps.probeConnect(args2.port)) return true;
|
|
6640
|
+
try {
|
|
6641
|
+
if (deps.readFile(args2.logPath).includes("[bridge] listening on")) return true;
|
|
6642
|
+
} catch {
|
|
6643
|
+
}
|
|
6644
|
+
await deps.sleep(delayMs);
|
|
6645
|
+
}
|
|
6646
|
+
return false;
|
|
6647
|
+
}
|
|
6648
|
+
function harnessSnippet(args2) {
|
|
6649
|
+
const label = args2.harness === "hermes" ? "Hermes" : args2.harness === "openclaw" ? "OpenClaw" : "your harness";
|
|
6650
|
+
return [
|
|
6651
|
+
`# ${label}: point the agent at this bridge`,
|
|
6652
|
+
`SECURECOMS_BRIDGE_URL=ws://127.0.0.1:${args2.port}/v1/ws`,
|
|
6653
|
+
`SECURECOMS_BRIDGE_TOKEN="$(cat ${args2.tokenFilePath})"`
|
|
6654
|
+
].join("\n");
|
|
6655
|
+
}
|
|
6656
|
+
async function orchestrateInstall(ctx) {
|
|
6657
|
+
const parsed = assertReadableCredential(ctx.credentialAbsPath, ctx.prepDeps);
|
|
6658
|
+
const stemForSlug = ctx.label ?? ctx.credentialAbsPath.replace(/^.*[\\/]/, "").replace(/\.[^.]*$/, "");
|
|
6659
|
+
const slug = slugify(stemForSlug);
|
|
6660
|
+
const durablePath = takeCustody(
|
|
6661
|
+
{ home: ctx.home, slug, sourceAbsPath: ctx.credentialAbsPath, parsed },
|
|
6662
|
+
ctx.prepDeps
|
|
6663
|
+
);
|
|
6664
|
+
const port = ctx.explicitPort ?? selectPort(
|
|
6665
|
+
{ launchAgentsDir: ctx.launchAgentsDir ?? `${ctx.home}/Library/LaunchAgents`, systemdDir: ctx.systemdDir ?? `${ctx.home}/.config/systemd/user` },
|
|
6666
|
+
ctx.prepDeps
|
|
6667
|
+
);
|
|
6668
|
+
const input = ctx.buildServiceInput(durablePath, port, slug);
|
|
6669
|
+
const result = ctx.installService(input);
|
|
6670
|
+
const up = await verifyDaemonUp(
|
|
6671
|
+
{ port, logPath: result.logHint, attempts: ctx.verifyAttempts, delayMs: ctx.verifyDelayMs },
|
|
6672
|
+
ctx.prepDeps
|
|
6673
|
+
);
|
|
6674
|
+
if (!up) {
|
|
6675
|
+
throw new Error(
|
|
6676
|
+
`install: the daemon did not come up \u2014 check its log: ${result.logHint}`
|
|
6677
|
+
);
|
|
6678
|
+
}
|
|
6679
|
+
const apiKey = JSON.parse(ctx.prepDeps.readFile(durablePath)).api_key;
|
|
6680
|
+
const lines = [
|
|
6681
|
+
`Installed service ${result.label}`,
|
|
6682
|
+
` device: ${parsed.deviceId}`,
|
|
6683
|
+
parsed.issuedAt ? ` issued: ${parsed.issuedAt}` : ` issued: (unknown \u2014 older credential)`,
|
|
6684
|
+
` api key: ${tokenFingerprint(apiKey)}`,
|
|
6685
|
+
` port: ${port}`,
|
|
6686
|
+
` token: ${result.tokenHint}`,
|
|
6687
|
+
` (Re-running install is the only way to update a live credential \u2014 replacing the download alone does nothing.)`
|
|
6688
|
+
];
|
|
6689
|
+
let harnessSnippetShown = false;
|
|
6690
|
+
if (ctx.harness && ctx.harness !== "none") {
|
|
6691
|
+
lines.push("", harnessSnippet({ slug, port, tokenFilePath: result.tokenHint, harness: ctx.harness }));
|
|
6692
|
+
harnessSnippetShown = true;
|
|
6693
|
+
}
|
|
6694
|
+
return { echo: lines.join("\n") + "\n", harnessSnippetShown };
|
|
6695
|
+
}
|
|
6696
|
+
|
|
6534
6697
|
// src/bin.ts
|
|
6535
6698
|
function nodeMajorOk(version) {
|
|
6536
6699
|
return Number(version.split(".")[0]) >= 24;
|
|
@@ -6571,11 +6734,47 @@ function realResolveDeps() {
|
|
|
6571
6734
|
fileExists: (p) => existsSync2(p)
|
|
6572
6735
|
};
|
|
6573
6736
|
}
|
|
6574
|
-
function
|
|
6737
|
+
function realPrepDeps() {
|
|
6738
|
+
return {
|
|
6739
|
+
readFile: (p) => readFileSync6(p, "utf8"),
|
|
6740
|
+
writeFileMode: (p, data, mode) => writeFileSync2(p, data, { mode }),
|
|
6741
|
+
mkdirp: (p, mode) => {
|
|
6742
|
+
mkdirSync(p, { recursive: true, mode: mode ?? 493 });
|
|
6743
|
+
},
|
|
6744
|
+
fileExists: (p) => existsSync2(p),
|
|
6745
|
+
realpath: (p) => realpathSync(p),
|
|
6746
|
+
listDir: (p) => {
|
|
6747
|
+
try {
|
|
6748
|
+
return readdirSync(p);
|
|
6749
|
+
} catch {
|
|
6750
|
+
return [];
|
|
6751
|
+
}
|
|
6752
|
+
},
|
|
6753
|
+
// Synchronous spawn-free-loop probes: node's net API is async-only, so each
|
|
6754
|
+
// probe is a tiny `node -e` child that binds/connects and exits 0/1.
|
|
6755
|
+
probeBindFree: (port) => {
|
|
6756
|
+
const r = spawnSync(process.execPath, [
|
|
6757
|
+
"-e",
|
|
6758
|
+
`const s=require('net').createServer();s.once('error',()=>process.exit(1));s.listen(${port},'127.0.0.1',()=>{s.close(()=>process.exit(0))});`
|
|
6759
|
+
]);
|
|
6760
|
+
return r.status === 0;
|
|
6761
|
+
},
|
|
6762
|
+
probeConnect: (port) => {
|
|
6763
|
+
const r = spawnSync(process.execPath, [
|
|
6764
|
+
"-e",
|
|
6765
|
+
`const s=require('net').connect(${port},'127.0.0.1');s.once('connect',()=>{s.destroy();process.exit(0)});s.once('error',()=>process.exit(1));setTimeout(()=>process.exit(1),500);`
|
|
6766
|
+
]);
|
|
6767
|
+
return r.status === 0;
|
|
6768
|
+
},
|
|
6769
|
+
// No unref: an unref'd timer would let the process exit mid-verification.
|
|
6770
|
+
sleep: (ms) => new Promise((res) => setTimeout(res, ms))
|
|
6771
|
+
};
|
|
6772
|
+
}
|
|
6773
|
+
async function runServiceVerb(verb) {
|
|
6575
6774
|
const credentialPath = argOrEnv("--credential", "SECURECOMS_CREDENTIAL");
|
|
6576
6775
|
const label = argOrEnv("--label", "SECURECOMS_LABEL");
|
|
6577
|
-
if (!credentialPath && !label) {
|
|
6578
|
-
process.stderr.write(`usage: agent-bridge ${verb} --credential <path> [--label <name>] [--store <path>] [--port N]
|
|
6776
|
+
if (verb === "install" ? !credentialPath : !credentialPath && !label) {
|
|
6777
|
+
process.stderr.write(`usage: agent-bridge ${verb} --credential <path> [--label <name>] [--store <path>] [--port N] [--harness openclaw|hermes|custom]
|
|
6579
6778
|
`);
|
|
6580
6779
|
process.exit(2);
|
|
6581
6780
|
}
|
|
@@ -6585,25 +6784,43 @@ function runServiceVerb(verb) {
|
|
|
6585
6784
|
const meta = loadPackageMeta(import.meta.url);
|
|
6586
6785
|
binScript = resolveStableBinScript({ binScript, packageName: meta.name }, realResolveDeps());
|
|
6587
6786
|
}
|
|
6588
|
-
const input = {
|
|
6589
|
-
credentialPath: credentialPath ?? "",
|
|
6590
|
-
storePath: argOrEnv("--store", "SECURECOMS_STORE"),
|
|
6591
|
-
port: portStr ? Number(portStr) : void 0,
|
|
6592
|
-
label,
|
|
6593
|
-
execPath: process.execPath,
|
|
6594
|
-
binScript,
|
|
6595
|
-
home: homedir2(),
|
|
6596
|
-
uid: userInfo().uid
|
|
6597
|
-
};
|
|
6598
6787
|
try {
|
|
6599
6788
|
if (verb === "install") {
|
|
6600
|
-
const
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
|
|
6605
|
-
|
|
6789
|
+
const credAbs = isAbsolute2(credentialPath) ? credentialPath : resolve2(credentialPath);
|
|
6790
|
+
const r = await orchestrateInstall({
|
|
6791
|
+
credentialAbsPath: credAbs,
|
|
6792
|
+
label,
|
|
6793
|
+
explicitPort: portStr ? Number(portStr) : void 0,
|
|
6794
|
+
harness: argOrEnv("--harness", "SECURECOMS_HARNESS"),
|
|
6795
|
+
home: homedir2(),
|
|
6796
|
+
prepDeps: realPrepDeps(),
|
|
6797
|
+
// O-1: the real installService is 2-arg; partially apply the deps so
|
|
6798
|
+
// orchestrateInstall only ever sees the 1-arg seam.
|
|
6799
|
+
installService: (input) => installService(input, realServiceDeps()),
|
|
6800
|
+
buildServiceInput: (durableCredentialPath2, port, _slug) => ({
|
|
6801
|
+
credentialPath: durableCredentialPath2,
|
|
6802
|
+
storePath: argOrEnv("--store", "SECURECOMS_STORE"),
|
|
6803
|
+
// O-2: not dropped
|
|
6804
|
+
port,
|
|
6805
|
+
label,
|
|
6806
|
+
execPath: process.execPath,
|
|
6807
|
+
binScript,
|
|
6808
|
+
home: homedir2(),
|
|
6809
|
+
uid: userInfo().uid
|
|
6810
|
+
})
|
|
6811
|
+
});
|
|
6812
|
+
process.stdout.write(r.echo);
|
|
6606
6813
|
} else {
|
|
6814
|
+
const input = {
|
|
6815
|
+
credentialPath: credentialPath ?? "",
|
|
6816
|
+
storePath: argOrEnv("--store", "SECURECOMS_STORE"),
|
|
6817
|
+
port: portStr ? Number(portStr) : void 0,
|
|
6818
|
+
label,
|
|
6819
|
+
execPath: process.execPath,
|
|
6820
|
+
binScript,
|
|
6821
|
+
home: homedir2(),
|
|
6822
|
+
uid: userInfo().uid
|
|
6823
|
+
};
|
|
6607
6824
|
const r = uninstallService(input, realServiceDeps());
|
|
6608
6825
|
process.stdout.write(r.removed ? `Removed service (${r.filePath})
|
|
6609
6826
|
` : `No service installed for this agent
|
|
@@ -6625,7 +6842,7 @@ async function main() {
|
|
|
6625
6842
|
}
|
|
6626
6843
|
const verb = parseVerb(process.argv);
|
|
6627
6844
|
if (verb) {
|
|
6628
|
-
runServiceVerb(verb);
|
|
6845
|
+
await runServiceVerb(verb);
|
|
6629
6846
|
return;
|
|
6630
6847
|
}
|
|
6631
6848
|
const credentialPath = argOrEnv("--credential", "SECURECOMS_CREDENTIAL");
|
|
@@ -6644,7 +6861,7 @@ async function main() {
|
|
|
6644
6861
|
`);
|
|
6645
6862
|
process.exit(1);
|
|
6646
6863
|
}
|
|
6647
|
-
const tokenFile = daemon.tokenFile ??
|
|
6864
|
+
const tokenFile = daemon.tokenFile ?? join9(dirname5(credentialPath), "bridge-token");
|
|
6648
6865
|
const token = resolveToken({ tokenFile, token: daemon.token });
|
|
6649
6866
|
const agent = new BridgeAgent(cfg);
|
|
6650
6867
|
await agent.start();
|
package/dist/config.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export interface BridgeConfig {
|
|
|
24
24
|
/** For memoryProvider="openclaw": the OpenClaw agent dir slug, e.g. "cortina" (→ <base_dir>/agents/<agent>/). */
|
|
25
25
|
memoryAgent?: string;
|
|
26
26
|
}
|
|
27
|
+
export declare const REQUIRED: readonly ["api_key", "identity_id", "device_id", "signing_private_key", "enclave_id", "api_base"];
|
|
27
28
|
export declare function loadConfig(opts: {
|
|
28
29
|
credentialPath: string;
|
|
29
30
|
storePath?: string;
|
package/dist/index.js
CHANGED
|
@@ -2630,6 +2630,15 @@ function parseServerFrame(frame) {
|
|
|
2630
2630
|
}
|
|
2631
2631
|
|
|
2632
2632
|
// ../connect-sdk-ts/dist/transport/ws-pings.js
|
|
2633
|
+
function base64UrlNoPad(s) {
|
|
2634
|
+
const bytes = new TextEncoder().encode(s);
|
|
2635
|
+
let bin = "";
|
|
2636
|
+
for (const b of bytes)
|
|
2637
|
+
bin += String.fromCharCode(b);
|
|
2638
|
+
const b64 = typeof btoa === "function" ? btoa(bin) : Buffer.from(bytes).toString("base64");
|
|
2639
|
+
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
2640
|
+
}
|
|
2641
|
+
var BEARER_SUBPROTOCOL = "securecoms.bearer.v1";
|
|
2633
2642
|
function openDeliveryPings(o) {
|
|
2634
2643
|
const base = o.backoffBaseMs ?? 500;
|
|
2635
2644
|
const cap = o.backoffCapMs ?? 8e3;
|
|
@@ -2778,8 +2787,7 @@ function openDeliveryPings(o) {
|
|
|
2778
2787
|
}
|
|
2779
2788
|
if (closed || gen !== connectGen)
|
|
2780
2789
|
return;
|
|
2781
|
-
const
|
|
2782
|
-
const ws = new o.wsImpl(`${o.url}${sep}token=${encodeURIComponent(token)}`);
|
|
2790
|
+
const ws = new o.wsImpl(o.url, [BEARER_SUBPROTOCOL, base64UrlNoPad(token)]);
|
|
2783
2791
|
sock = ws;
|
|
2784
2792
|
ws.onopen = () => {
|
|
2785
2793
|
push({ reconnected: true });
|
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
|
+
}>;
|