@securecoms/agent-bridge 0.17.0 → 0.18.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/dist/bin.js +292 -74
- package/dist/bridge-agent.d.ts +24 -0
- package/dist/index.js +231 -13
- package/dist/service/runtime-config-dir.d.ts +44 -0
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -4834,6 +4834,9 @@ var FRAME_PARSERS = {
|
|
|
4834
4834
|
}),
|
|
4835
4835
|
voice_settings_changed: (d) => ({
|
|
4836
4836
|
voiceSettingsChanged: { identityId: str(d?.identity_id), version: Number(d?.version ?? 0) }
|
|
4837
|
+
}),
|
|
4838
|
+
runtime_config_changed: (d) => ({
|
|
4839
|
+
runtimeConfigChanged: { identityId: str(d?.identity_id), version: Number(d?.version ?? 0) }
|
|
4837
4840
|
})
|
|
4838
4841
|
};
|
|
4839
4842
|
var S2C_FRAME_NAMES = new Set(Object.keys(FRAME_PARSERS));
|
|
@@ -6388,7 +6391,7 @@ var SqliteMlsStateStore = class {
|
|
|
6388
6391
|
|
|
6389
6392
|
// src/bridge-agent.ts
|
|
6390
6393
|
import { decryptFile as decryptFile2, encryptFile as encryptFile2, computeFileDigest as computeFileDigest2, base64ToBytes as base64ToBytes4, bytesToBase64 as bytesToBase644 } from "@agentvault/crypto";
|
|
6391
|
-
import { createHash } from "node:crypto";
|
|
6394
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
6392
6395
|
|
|
6393
6396
|
// src/service/resolve-bin.ts
|
|
6394
6397
|
import { join as join2, dirname as dirname2 } from "node:path";
|
|
@@ -6441,7 +6444,7 @@ function loadPackageMeta(importMetaUrl) {
|
|
|
6441
6444
|
|
|
6442
6445
|
// src/bridge-agent.ts
|
|
6443
6446
|
import { promises as fsp } from "node:fs";
|
|
6444
|
-
import { join as
|
|
6447
|
+
import { join as join9, dirname as dirname3 } from "node:path";
|
|
6445
6448
|
|
|
6446
6449
|
// src/latency.ts
|
|
6447
6450
|
var DEFAULT_CAPACITY = 1024;
|
|
@@ -7461,7 +7464,135 @@ async function runSummarize(opts) {
|
|
|
7461
7464
|
}
|
|
7462
7465
|
}
|
|
7463
7466
|
|
|
7467
|
+
// src/service/runtime-config-dir.ts
|
|
7468
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
7469
|
+
import {
|
|
7470
|
+
chmodSync,
|
|
7471
|
+
existsSync,
|
|
7472
|
+
mkdirSync,
|
|
7473
|
+
readFileSync as readFileSync6,
|
|
7474
|
+
renameSync,
|
|
7475
|
+
statSync as statSync3,
|
|
7476
|
+
unlinkSync,
|
|
7477
|
+
writeFileSync
|
|
7478
|
+
} from "node:fs";
|
|
7479
|
+
import { basename as basename2, join as join8 } from "node:path";
|
|
7480
|
+
var MEMORY_FILE = "MEMORY.md";
|
|
7481
|
+
var REAL_FS = {
|
|
7482
|
+
mkdir: (p, mode) => mkdirSync(p, { recursive: true, mode }),
|
|
7483
|
+
chmod: (p, mode) => chmodSync(p, mode),
|
|
7484
|
+
writeFile: (p, data, mode) => writeFileSync(p, data, { mode }),
|
|
7485
|
+
rename: (a, b) => renameSync(a, b),
|
|
7486
|
+
readFile: (p) => readFileSync6(p, "utf8"),
|
|
7487
|
+
unlink: (p) => unlinkSync(p)
|
|
7488
|
+
};
|
|
7489
|
+
function runtimeConfigDir(home, identityId) {
|
|
7490
|
+
return join8(home, ".securecoms", "runtime-config", identityId);
|
|
7491
|
+
}
|
|
7492
|
+
function assertManagedName(name) {
|
|
7493
|
+
if (!name || name.includes("/") || name.includes("\\") || name === "." || name === ".." || name !== basename2(name)) {
|
|
7494
|
+
throw new Error(`refusing managed config file name ${JSON.stringify(name)}: not a plain filename`);
|
|
7495
|
+
}
|
|
7496
|
+
}
|
|
7497
|
+
function atomicWrite(path, body, deps) {
|
|
7498
|
+
const tmp = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
7499
|
+
deps.writeFile(tmp, body, 384);
|
|
7500
|
+
deps.chmod(tmp, 384);
|
|
7501
|
+
try {
|
|
7502
|
+
deps.rename(tmp, path);
|
|
7503
|
+
} catch (err) {
|
|
7504
|
+
try {
|
|
7505
|
+
deps.unlink(tmp);
|
|
7506
|
+
} catch {
|
|
7507
|
+
}
|
|
7508
|
+
throw err;
|
|
7509
|
+
}
|
|
7510
|
+
}
|
|
7511
|
+
function writeManagedFiles(home, identityId, files, deps = REAL_FS) {
|
|
7512
|
+
const entries = Object.entries(files);
|
|
7513
|
+
for (const [name] of entries) assertManagedName(name);
|
|
7514
|
+
const parent2 = join8(home, ".securecoms", "runtime-config");
|
|
7515
|
+
const dir = runtimeConfigDir(home, identityId);
|
|
7516
|
+
for (const d of [join8(home, ".securecoms"), parent2, dir]) {
|
|
7517
|
+
deps.mkdir(d, 448);
|
|
7518
|
+
deps.chmod(d, 448);
|
|
7519
|
+
}
|
|
7520
|
+
for (const [name, body] of entries) atomicWrite(join8(dir, name), body, deps);
|
|
7521
|
+
}
|
|
7522
|
+
function checkConfiguredPaths(skillsJson, statFn = (p) => {
|
|
7523
|
+
const s = statSync3(p);
|
|
7524
|
+
return { mode: s.mode, size: s.size };
|
|
7525
|
+
}) {
|
|
7526
|
+
const paths = [];
|
|
7527
|
+
const endpoints = skillsJson.endpoints ?? {};
|
|
7528
|
+
for (const e of Object.values(endpoints)) if (e?.headersFile) paths.push(e.headersFile);
|
|
7529
|
+
const databases = skillsJson.databases ?? {};
|
|
7530
|
+
for (const d of Object.values(databases)) {
|
|
7531
|
+
if (d?.sqlite) paths.push(d.sqlite);
|
|
7532
|
+
if (d?.postgresUrlFile) paths.push(d.postgresUrlFile);
|
|
7533
|
+
}
|
|
7534
|
+
return paths.map((path) => {
|
|
7535
|
+
let st;
|
|
7536
|
+
try {
|
|
7537
|
+
st = statFn(path);
|
|
7538
|
+
} catch {
|
|
7539
|
+
return { path, ok: false, why: "not found" };
|
|
7540
|
+
}
|
|
7541
|
+
if ((st.mode & 63) !== 0) {
|
|
7542
|
+
return { path, ok: false, why: "readable by other users on this machine \u2014 tighten its permissions to owner-only" };
|
|
7543
|
+
}
|
|
7544
|
+
if (st.size === 0) return { path, ok: false, why: "the file is empty" };
|
|
7545
|
+
return { path, ok: true, why: "" };
|
|
7546
|
+
});
|
|
7547
|
+
}
|
|
7548
|
+
function lineHash(line) {
|
|
7549
|
+
return createHash("sha256").update(line.trim(), "utf8").digest("hex");
|
|
7550
|
+
}
|
|
7551
|
+
var LOCAL_REPORT_FILE = "local-config.json";
|
|
7552
|
+
function readLocalReport(home, identityId) {
|
|
7553
|
+
const p = join8(runtimeConfigDir(home, identityId), LOCAL_REPORT_FILE);
|
|
7554
|
+
if (!existsSync(p)) return null;
|
|
7555
|
+
let doc;
|
|
7556
|
+
try {
|
|
7557
|
+
doc = JSON.parse(readFileSync6(p, "utf8"));
|
|
7558
|
+
} catch {
|
|
7559
|
+
return null;
|
|
7560
|
+
}
|
|
7561
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc)) return null;
|
|
7562
|
+
const hasSearchKey = doc.has_search_key === true;
|
|
7563
|
+
const raw = doc.persona;
|
|
7564
|
+
const persona = raw && typeof raw === "object" && !Array.isArray(raw) ? Object.fromEntries(
|
|
7565
|
+
Object.entries(raw).filter(([, v]) => typeof v === "string")
|
|
7566
|
+
) : {};
|
|
7567
|
+
return { persona, has_search_key: hasSearchKey };
|
|
7568
|
+
}
|
|
7569
|
+
function readManagedMemory(home, identityId) {
|
|
7570
|
+
const p = join8(runtimeConfigDir(home, identityId), MEMORY_FILE);
|
|
7571
|
+
if (!existsSync(p)) return null;
|
|
7572
|
+
try {
|
|
7573
|
+
const body = readFileSync6(p, "utf8").trim();
|
|
7574
|
+
return body || null;
|
|
7575
|
+
} catch {
|
|
7576
|
+
return null;
|
|
7577
|
+
}
|
|
7578
|
+
}
|
|
7579
|
+
function pruneManagedMemory(home, identityId, hashes, deps = REAL_FS) {
|
|
7580
|
+
const dir = runtimeConfigDir(home, identityId);
|
|
7581
|
+
const path = join8(dir, MEMORY_FILE);
|
|
7582
|
+
if (!existsSync(path)) return 0;
|
|
7583
|
+
const drop = new Set(hashes);
|
|
7584
|
+
const before = deps.readFile(path).split("\n");
|
|
7585
|
+
const kept = before.filter((l) => l.trim() === "" || !drop.has(lineHash(l)));
|
|
7586
|
+
const removed = before.filter((l) => l.trim() !== "" && drop.has(lineHash(l))).length;
|
|
7587
|
+
if (removed === 0) return 0;
|
|
7588
|
+
const body = kept.filter((l, i) => l.trim() !== "" || i < kept.length - 1).join("\n").trimEnd();
|
|
7589
|
+
atomicWrite(path, body ? `${body}
|
|
7590
|
+
` : "", deps);
|
|
7591
|
+
return removed;
|
|
7592
|
+
}
|
|
7593
|
+
|
|
7464
7594
|
// src/bridge-agent.ts
|
|
7595
|
+
import { homedir as homedir2 } from "node:os";
|
|
7465
7596
|
var VOICE_MODE_GUIDANCE = "Reply immediately and conversationally, one short sentence per message; never bundle multiple points into one message.";
|
|
7466
7597
|
var FAST_PATH_SPOKEN_GUIDANCE = "A one-sentence spoken acknowledgement has already been sent for this turn. Reply only with information it did not contain, in one or two short sentences; if there is nothing to add, send nothing.";
|
|
7467
7598
|
var ROOM_VOICE_GUIDANCE_MENTION_ONLY = "You are in a group voice room. Reply ONLY when a message @mentions your display name. Keep replies to one short sentence. When you should not reply, send nothing.";
|
|
@@ -7516,7 +7647,7 @@ function resolveMessageId(messageId) {
|
|
|
7516
7647
|
return messageId;
|
|
7517
7648
|
}
|
|
7518
7649
|
function fastReplyId(messageId) {
|
|
7519
|
-
const h =
|
|
7650
|
+
const h = createHash2("sha256").update(`fast:${messageId}`).digest();
|
|
7520
7651
|
h[6] = h[6] & 15 | 64;
|
|
7521
7652
|
h[8] = h[8] & 63 | 128;
|
|
7522
7653
|
const x = h.subarray(0, 16).toString("hex");
|
|
@@ -7646,6 +7777,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
7646
7777
|
onSupersedeUnopenable: (channelId, groupId) => this.healSupersedeUnopenable(channelId, groupId)
|
|
7647
7778
|
});
|
|
7648
7779
|
this.fetchImpl = deps.fetchImpl ?? fetch;
|
|
7780
|
+
this.home = deps.home ?? homedir2();
|
|
7649
7781
|
this.injectedFastLlm = !!deps.fastLlm;
|
|
7650
7782
|
this.fileVoiceConfig = cfg.voice;
|
|
7651
7783
|
this.fastLlm = deps.fastLlm ?? (cfg.voice?.fastPath.enabled ? new FastLlmClient(cfg.voice.fastPath, this.fetchImpl) : void 0);
|
|
@@ -7678,8 +7810,8 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
7678
7810
|
const j = await res.json();
|
|
7679
7811
|
return String(j.attachment_id);
|
|
7680
7812
|
};
|
|
7681
|
-
this.attachmentsDirImpl = deps.attachmentsDir ??
|
|
7682
|
-
this.vaultOwnerStoreImpl = deps.vaultOwnerStore ?? new FileOwnerAuthorityStore(
|
|
7813
|
+
this.attachmentsDirImpl = deps.attachmentsDir ?? join9(dirname3(cfg.storePath), "attachments");
|
|
7814
|
+
this.vaultOwnerStoreImpl = deps.vaultOwnerStore ?? new FileOwnerAuthorityStore(join9(dirname3(cfg.storePath), "vault-owner-pins.json"));
|
|
7683
7815
|
this.action = {
|
|
7684
7816
|
begin: (channelId) => this.actionBegin(channelId),
|
|
7685
7817
|
start: (opts) => this.actionStart(opts),
|
|
@@ -7695,6 +7827,8 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
7695
7827
|
// stored for peek-then-join in ingestOnce
|
|
7696
7828
|
custody;
|
|
7697
7829
|
fetchImpl;
|
|
7830
|
+
/** Round 5: home for the managed runtime-config directory (test seam). */
|
|
7831
|
+
home;
|
|
7698
7832
|
joinState = /* @__PURE__ */ new Map();
|
|
7699
7833
|
/**
|
|
7700
7834
|
* Phase 2: lazy channel-metadata cache (channel_id → { is_dm, name, at }) used
|
|
@@ -8036,6 +8170,84 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
8036
8170
|
if (this.voiceSettingsSource === "backend") return;
|
|
8037
8171
|
process.stderr.write(
|
|
8038
8172
|
`[bridge] voice settings unavailable (${e.message}); using ${this.voiceSettingsSource}
|
|
8173
|
+
`
|
|
8174
|
+
);
|
|
8175
|
+
}
|
|
8176
|
+
}
|
|
8177
|
+
/**
|
|
8178
|
+
* Fetch this agent's runtime config and apply it to the managed directory the
|
|
8179
|
+
* runtime reads (spec §8). Safe to call any time — at start(), or from the
|
|
8180
|
+
* receive pump on a `runtime_config_changed` ping — and never throws: a save
|
|
8181
|
+
* the owner already made must not be undone by a network blip here, and no
|
|
8182
|
+
* shape the backend can send may take the daemon down.
|
|
8183
|
+
*
|
|
8184
|
+
* Order matters. Prunes are applied BEFORE the memory is read back, so the
|
|
8185
|
+
* report the owner sees next is the file as it now stands.
|
|
8186
|
+
*
|
|
8187
|
+
* Every cycle rewrites, even at an unchanged version: directory permissions
|
|
8188
|
+
* are repaired only on a write, so skipping would leave a loosened directory
|
|
8189
|
+
* loose until the next save.
|
|
8190
|
+
*/
|
|
8191
|
+
async refetchRuntimeConfig() {
|
|
8192
|
+
const base = `${this.cfg.apiBase}/api/v1/agents/${encodeURIComponent(this.cfg.identityId)}/runtime-config`;
|
|
8193
|
+
try {
|
|
8194
|
+
const res = await this.fetchImpl(`${base}/files`, {
|
|
8195
|
+
headers: { authorization: `Bearer ${this.cfg.apiKey}` }
|
|
8196
|
+
});
|
|
8197
|
+
if (!res.ok) throw new Error(`runtime-config ${res.status}`);
|
|
8198
|
+
const payload = await res.json();
|
|
8199
|
+
if (!payload || !payload.version) {
|
|
8200
|
+
process.stderr.write(
|
|
8201
|
+
`[bridge] runtime config not applied (version ${JSON.stringify(payload?.version ?? null)} \u2014 no saved config)
|
|
8202
|
+
`
|
|
8203
|
+
);
|
|
8204
|
+
return;
|
|
8205
|
+
}
|
|
8206
|
+
const skillsJson = payload.skills_json ?? {};
|
|
8207
|
+
writeManagedFiles(this.home, this.cfg.identityId, {
|
|
8208
|
+
...payload.files ?? {},
|
|
8209
|
+
"skills.json": `${JSON.stringify(skillsJson, null, 2)}
|
|
8210
|
+
`
|
|
8211
|
+
});
|
|
8212
|
+
if (payload.memory_prunes?.length) {
|
|
8213
|
+
pruneManagedMemory(this.home, this.cfg.identityId, payload.memory_prunes);
|
|
8214
|
+
}
|
|
8215
|
+
const local = readLocalReport(this.home, this.cfg.identityId);
|
|
8216
|
+
const report = {
|
|
8217
|
+
applied_version: payload.version,
|
|
8218
|
+
paths: checkConfiguredPaths(skillsJson),
|
|
8219
|
+
// Read AFTER the prunes above, so the owner's next view of memory is
|
|
8220
|
+
// the file as it now stands, not the one they just pruned from.
|
|
8221
|
+
memory: readManagedMemory(this.home, this.cfg.identityId),
|
|
8222
|
+
// E13: pass through what the runtime reported. The daemon does not
|
|
8223
|
+
// interpret any of it.
|
|
8224
|
+
local_persona: local?.persona ?? null,
|
|
8225
|
+
// Only the runtime knows whether a search key file is configured — it
|
|
8226
|
+
// is an environment value on that process, invisible to the daemon and
|
|
8227
|
+
// to the backend. The backend needs it to generate a truthful tool
|
|
8228
|
+
// paragraph, so it rides along here.
|
|
8229
|
+
has_search_key: local?.has_search_key ?? false
|
|
8230
|
+
};
|
|
8231
|
+
const posted = await this.fetchImpl(`${base}/report`, {
|
|
8232
|
+
method: "POST",
|
|
8233
|
+
headers: {
|
|
8234
|
+
authorization: `Bearer ${this.cfg.apiKey}`,
|
|
8235
|
+
"content-type": "application/json"
|
|
8236
|
+
},
|
|
8237
|
+
body: JSON.stringify(report)
|
|
8238
|
+
});
|
|
8239
|
+
if (!posted.ok) {
|
|
8240
|
+
process.stderr.write(
|
|
8241
|
+
`[bridge] runtime config v${payload.version} written, but reporting it failed (${posted.status}) \u2014 the console will keep showing it as not applied until the next cycle
|
|
8242
|
+
`
|
|
8243
|
+
);
|
|
8244
|
+
return;
|
|
8245
|
+
}
|
|
8246
|
+
process.stderr.write(`[bridge] runtime config v${payload.version} applied
|
|
8247
|
+
`);
|
|
8248
|
+
} catch (e) {
|
|
8249
|
+
process.stderr.write(
|
|
8250
|
+
`[bridge] runtime config refetch failed: ${e?.message ?? e}
|
|
8039
8251
|
`
|
|
8040
8252
|
);
|
|
8041
8253
|
}
|
|
@@ -8067,6 +8279,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
8067
8279
|
}
|
|
8068
8280
|
await this.provider.publishKeyPackage();
|
|
8069
8281
|
await this.refetchVoiceSettings();
|
|
8282
|
+
await this.refetchRuntimeConfig();
|
|
8070
8283
|
this.startReceivePump();
|
|
8071
8284
|
if (this.selfHealSweepEnabled) {
|
|
8072
8285
|
this.bootSweepTimer = setTimeout(
|
|
@@ -9551,11 +9764,16 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
9551
9764
|
this.pingsReceived++;
|
|
9552
9765
|
if (ping?.reconnected) {
|
|
9553
9766
|
process.stderr.write("[bridge] delivery-ping WS (re)connected \u2014 push inbound active\n");
|
|
9767
|
+
await this.refetchRuntimeConfig();
|
|
9554
9768
|
}
|
|
9555
9769
|
if (ping?.voiceSettingsChanged) {
|
|
9556
9770
|
await this.refetchVoiceSettings();
|
|
9557
9771
|
continue;
|
|
9558
9772
|
}
|
|
9773
|
+
if (ping?.runtimeConfigChanged) {
|
|
9774
|
+
await this.refetchRuntimeConfig();
|
|
9775
|
+
continue;
|
|
9776
|
+
}
|
|
9559
9777
|
await this.ingestOnce();
|
|
9560
9778
|
}
|
|
9561
9779
|
} catch {
|
|
@@ -9994,13 +10212,13 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
9994
10212
|
|
|
9995
10213
|
// src/auth.ts
|
|
9996
10214
|
import { randomBytes as randomBytes3, timingSafeEqual } from "node:crypto";
|
|
9997
|
-
import { existsSync, readFileSync as
|
|
10215
|
+
import { existsSync as existsSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "node:fs";
|
|
9998
10216
|
function resolveToken(opts) {
|
|
9999
10217
|
if (opts.token) return opts.token;
|
|
10000
10218
|
const f = opts.tokenFile;
|
|
10001
|
-
if (f &&
|
|
10219
|
+
if (f && existsSync2(f)) return readFileSync7(f, "utf8").trim();
|
|
10002
10220
|
const tok = randomBytes3(32).toString("base64url");
|
|
10003
|
-
if (f)
|
|
10221
|
+
if (f) writeFileSync2(f, tok, { mode: 384 });
|
|
10004
10222
|
return tok;
|
|
10005
10223
|
}
|
|
10006
10224
|
function tokenMatches(provided, expected) {
|
|
@@ -10621,17 +10839,17 @@ async function handleInbound(sock, agent, data, inflight) {
|
|
|
10621
10839
|
}
|
|
10622
10840
|
|
|
10623
10841
|
// src/bin.ts
|
|
10624
|
-
import { readFileSync as
|
|
10625
|
-
import { join as
|
|
10842
|
+
import { readFileSync as readFileSync9, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, rmSync, existsSync as existsSync3, realpathSync, readdirSync as readdirSync2, statSync as statSync4 } from "node:fs";
|
|
10843
|
+
import { join as join13, dirname as dirname7, isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
|
|
10626
10844
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
10627
|
-
import { homedir as
|
|
10845
|
+
import { homedir as homedir3, userInfo } from "node:os";
|
|
10628
10846
|
import { spawnSync } from "node:child_process";
|
|
10629
10847
|
|
|
10630
10848
|
// src/service/install.ts
|
|
10631
10849
|
import { dirname as dirname4 } from "node:path";
|
|
10632
10850
|
|
|
10633
10851
|
// src/service/config.ts
|
|
10634
|
-
import { basename as
|
|
10852
|
+
import { basename as basename3, extname, isAbsolute, resolve } from "node:path";
|
|
10635
10853
|
|
|
10636
10854
|
// src/service/resolve-node.ts
|
|
10637
10855
|
var REQUIRED_NODE_MAJOR = 24;
|
|
@@ -10709,7 +10927,7 @@ function deriveServiceConfig(input) {
|
|
|
10709
10927
|
`install: refusing to bake a version-embedded Node path into the service definition: ${input.execPath}. Re-run install under a system/Homebrew Node, or pass --exec-path <stable-path>.`
|
|
10710
10928
|
);
|
|
10711
10929
|
}
|
|
10712
|
-
const stem = input.label ??
|
|
10930
|
+
const stem = input.label ?? basename3(input.credentialPath, extname(input.credentialPath));
|
|
10713
10931
|
const slug = slugify(stem);
|
|
10714
10932
|
const cred = isAbsolute(input.credentialPath) ? input.credentialPath : resolve(input.credentialPath);
|
|
10715
10933
|
const args2 = ["--credential", cred];
|
|
@@ -10831,10 +11049,10 @@ function uninstallService(input, deps) {
|
|
|
10831
11049
|
}
|
|
10832
11050
|
|
|
10833
11051
|
// src/service/install-prep.ts
|
|
10834
|
-
import { join as
|
|
11052
|
+
import { join as join11 } from "node:path";
|
|
10835
11053
|
|
|
10836
11054
|
// src/service/wire.ts
|
|
10837
|
-
import { join as
|
|
11055
|
+
import { join as join10, dirname as dirname5 } from "node:path";
|
|
10838
11056
|
import { fileURLToPath } from "node:url";
|
|
10839
11057
|
|
|
10840
11058
|
// src/service/hermes-config.ts
|
|
@@ -11010,7 +11228,7 @@ function checkBindingConflict(prev, next, allowRebind) {
|
|
|
11010
11228
|
}
|
|
11011
11229
|
function connectorsSrcDir(importMetaUrl, harness) {
|
|
11012
11230
|
const pkgRoot = dirname5(dirname5(fileURLToPath(importMetaUrl)));
|
|
11013
|
-
return
|
|
11231
|
+
return join10(pkgRoot, "connectors", harness);
|
|
11014
11232
|
}
|
|
11015
11233
|
function hermesConfigPath(home) {
|
|
11016
11234
|
return `${home}/.hermes/config.yaml`;
|
|
@@ -11268,10 +11486,10 @@ function assertReadableCredential(absPath, deps) {
|
|
|
11268
11486
|
return { deviceId: json2.device_id, identityId: json2.identity_id, issuedAt };
|
|
11269
11487
|
}
|
|
11270
11488
|
function durableDir(home, slug) {
|
|
11271
|
-
return
|
|
11489
|
+
return join11(home, `securecoms-agent-${slug}`);
|
|
11272
11490
|
}
|
|
11273
11491
|
function durableCredentialPath(home, slug) {
|
|
11274
|
-
return
|
|
11492
|
+
return join11(durableDir(home, slug), `${slug}.json`);
|
|
11275
11493
|
}
|
|
11276
11494
|
function takeCustody(args2, deps) {
|
|
11277
11495
|
const dir = durableDir(args2.home, args2.slug);
|
|
@@ -11318,7 +11536,7 @@ function readSiblingDefs(dir, prefix, deps) {
|
|
|
11318
11536
|
for (const n of names) {
|
|
11319
11537
|
if (!n.startsWith(prefix)) continue;
|
|
11320
11538
|
try {
|
|
11321
|
-
out.push(deps.readFile(
|
|
11539
|
+
out.push(deps.readFile(join11(dir, n)));
|
|
11322
11540
|
} catch {
|
|
11323
11541
|
}
|
|
11324
11542
|
}
|
|
@@ -11513,34 +11731,34 @@ function renderStatusTable(rows) {
|
|
|
11513
11731
|
|
|
11514
11732
|
// src/service/connection-file.ts
|
|
11515
11733
|
import {
|
|
11516
|
-
chmodSync,
|
|
11517
|
-
mkdirSync,
|
|
11518
|
-
readFileSync as
|
|
11519
|
-
renameSync,
|
|
11520
|
-
unlinkSync,
|
|
11521
|
-
writeFileSync as
|
|
11734
|
+
chmodSync as chmodSync2,
|
|
11735
|
+
mkdirSync as mkdirSync2,
|
|
11736
|
+
readFileSync as readFileSync8,
|
|
11737
|
+
renameSync as renameSync2,
|
|
11738
|
+
unlinkSync as unlinkSync2,
|
|
11739
|
+
writeFileSync as writeFileSync3
|
|
11522
11740
|
} from "node:fs";
|
|
11523
|
-
import { randomUUID } from "node:crypto";
|
|
11524
|
-
import { join as
|
|
11525
|
-
var
|
|
11526
|
-
mkdir: (p, mode) =>
|
|
11527
|
-
chmod: (p, mode) =>
|
|
11528
|
-
writeFile: (p, data, mode) =>
|
|
11529
|
-
rename: (a, b) =>
|
|
11530
|
-
readFile: (p) =>
|
|
11531
|
-
unlink: (p) =>
|
|
11741
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
11742
|
+
import { join as join12 } from "node:path";
|
|
11743
|
+
var REAL_FS2 = {
|
|
11744
|
+
mkdir: (p, mode) => mkdirSync2(p, { recursive: true, mode }),
|
|
11745
|
+
chmod: (p, mode) => chmodSync2(p, mode),
|
|
11746
|
+
writeFile: (p, data, mode) => writeFileSync3(p, data, { mode }),
|
|
11747
|
+
rename: (a, b) => renameSync2(a, b),
|
|
11748
|
+
readFile: (p) => readFileSync8(p, "utf8"),
|
|
11749
|
+
unlink: (p) => unlinkSync2(p)
|
|
11532
11750
|
};
|
|
11533
11751
|
function securecomsDir(home) {
|
|
11534
|
-
return
|
|
11752
|
+
return join12(home, ".securecoms");
|
|
11535
11753
|
}
|
|
11536
11754
|
function connectionsDir(home) {
|
|
11537
|
-
return
|
|
11755
|
+
return join12(securecomsDir(home), "connections");
|
|
11538
11756
|
}
|
|
11539
11757
|
function connectionFilePath(home, identityId) {
|
|
11540
|
-
return
|
|
11758
|
+
return join12(connectionsDir(home), `${identityId}.json`);
|
|
11541
11759
|
}
|
|
11542
11760
|
function newInstanceId() {
|
|
11543
|
-
return
|
|
11761
|
+
return randomUUID2();
|
|
11544
11762
|
}
|
|
11545
11763
|
function buildConnectionFile(args2) {
|
|
11546
11764
|
return {
|
|
@@ -11554,18 +11772,18 @@ function buildConnectionFile(args2) {
|
|
|
11554
11772
|
written_at: args2.now
|
|
11555
11773
|
};
|
|
11556
11774
|
}
|
|
11557
|
-
function writeConnectionFile(home, doc, deps =
|
|
11775
|
+
function writeConnectionFile(home, doc, deps = REAL_FS2) {
|
|
11558
11776
|
deps.mkdir(securecomsDir(home), 448);
|
|
11559
11777
|
deps.chmod(securecomsDir(home), 448);
|
|
11560
11778
|
deps.mkdir(connectionsDir(home), 448);
|
|
11561
11779
|
deps.chmod(connectionsDir(home), 448);
|
|
11562
11780
|
const path = connectionFilePath(home, doc.identity_id);
|
|
11563
|
-
const tmp = `${path}.${process.pid}.${
|
|
11781
|
+
const tmp = `${path}.${process.pid}.${randomUUID2()}.tmp`;
|
|
11564
11782
|
deps.writeFile(tmp, JSON.stringify(doc, null, 2) + "\n", 384);
|
|
11565
11783
|
deps.chmod(tmp, 384);
|
|
11566
11784
|
deps.rename(tmp, path);
|
|
11567
11785
|
}
|
|
11568
|
-
function removeConnectionFileIfOwned(home, identityId, instanceId, deps =
|
|
11786
|
+
function removeConnectionFileIfOwned(home, identityId, instanceId, deps = REAL_FS2) {
|
|
11569
11787
|
const path = connectionFilePath(home, identityId);
|
|
11570
11788
|
try {
|
|
11571
11789
|
const cur = JSON.parse(deps.readFile(path));
|
|
@@ -11606,10 +11824,10 @@ function realServiceDeps() {
|
|
|
11606
11824
|
const r = spawnSync(cmd, args2, { encoding: "utf8" });
|
|
11607
11825
|
return { status: r.status ?? 1, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
|
|
11608
11826
|
},
|
|
11609
|
-
writeFile: (p, data) =>
|
|
11827
|
+
writeFile: (p, data) => writeFileSync4(p, data, { mode: 420 }),
|
|
11610
11828
|
removeFile: (p) => rmSync(p, { force: true }),
|
|
11611
|
-
mkdirp: (p) =>
|
|
11612
|
-
fileExists: (p) =>
|
|
11829
|
+
mkdirp: (p) => mkdirSync3(p, { recursive: true }),
|
|
11830
|
+
fileExists: (p) => existsSync3(p),
|
|
11613
11831
|
user: userInfo().username
|
|
11614
11832
|
};
|
|
11615
11833
|
}
|
|
@@ -11632,7 +11850,7 @@ function realResolveNodeDeps() {
|
|
|
11632
11850
|
};
|
|
11633
11851
|
return {
|
|
11634
11852
|
realpath: (p) => realpathSync(p),
|
|
11635
|
-
fileExists: (p) =>
|
|
11853
|
+
fileExists: (p) => existsSync3(p),
|
|
11636
11854
|
nodeMajorOf: majorOf,
|
|
11637
11855
|
arch: () => process.arch,
|
|
11638
11856
|
archOf: (p) => {
|
|
@@ -11647,17 +11865,17 @@ function realResolveDeps() {
|
|
|
11647
11865
|
const r = spawnSync(cmd, args2, { encoding: "utf8" });
|
|
11648
11866
|
return { status: r.status ?? 1, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
|
|
11649
11867
|
},
|
|
11650
|
-
fileExists: (p) =>
|
|
11868
|
+
fileExists: (p) => existsSync3(p)
|
|
11651
11869
|
};
|
|
11652
11870
|
}
|
|
11653
11871
|
function realPrepDeps() {
|
|
11654
11872
|
return {
|
|
11655
|
-
readFile: (p) =>
|
|
11656
|
-
writeFileMode: (p, data, mode) =>
|
|
11873
|
+
readFile: (p) => readFileSync9(p, "utf8"),
|
|
11874
|
+
writeFileMode: (p, data, mode) => writeFileSync4(p, data, { mode }),
|
|
11657
11875
|
mkdirp: (p, mode) => {
|
|
11658
|
-
|
|
11876
|
+
mkdirSync3(p, { recursive: true, mode: mode ?? 493 });
|
|
11659
11877
|
},
|
|
11660
|
-
fileExists: (p) =>
|
|
11878
|
+
fileExists: (p) => existsSync3(p),
|
|
11661
11879
|
realpath: (p) => realpathSync(p),
|
|
11662
11880
|
listDir: (p) => {
|
|
11663
11881
|
try {
|
|
@@ -11700,7 +11918,7 @@ function realGetHealth(port) {
|
|
|
11700
11918
|
}
|
|
11701
11919
|
function realReadFleet() {
|
|
11702
11920
|
return readFleet({
|
|
11703
|
-
home:
|
|
11921
|
+
home: homedir3(),
|
|
11704
11922
|
listDir: (p) => {
|
|
11705
11923
|
try {
|
|
11706
11924
|
return readdirSync2(p);
|
|
@@ -11708,7 +11926,7 @@ function realReadFleet() {
|
|
|
11708
11926
|
return [];
|
|
11709
11927
|
}
|
|
11710
11928
|
},
|
|
11711
|
-
readFile: (p) =>
|
|
11929
|
+
readFile: (p) => readFileSync9(p, "utf8")
|
|
11712
11930
|
});
|
|
11713
11931
|
}
|
|
11714
11932
|
function runStatus() {
|
|
@@ -11730,62 +11948,62 @@ function runStatus() {
|
|
|
11730
11948
|
}
|
|
11731
11949
|
function realCopyConnector(srcDir, dstDir, harness) {
|
|
11732
11950
|
const allowedExt = CONNECTOR_FILE_EXTENSIONS[harness];
|
|
11733
|
-
|
|
11951
|
+
mkdirSync3(dstDir, { recursive: true });
|
|
11734
11952
|
for (const name of readdirSync2(srcDir)) {
|
|
11735
|
-
const srcPath =
|
|
11736
|
-
if (
|
|
11953
|
+
const srcPath = join13(srcDir, name);
|
|
11954
|
+
if (statSync4(srcPath).isDirectory()) {
|
|
11737
11955
|
if (CONNECTOR_SKIP_DIRS.has(name)) continue;
|
|
11738
|
-
realCopyConnector(srcPath,
|
|
11956
|
+
realCopyConnector(srcPath, join13(dstDir, name), harness);
|
|
11739
11957
|
continue;
|
|
11740
11958
|
}
|
|
11741
11959
|
if (name.includes(".test.")) continue;
|
|
11742
11960
|
if (!allowedExt.some((ext) => name.endsWith(ext))) continue;
|
|
11743
|
-
|
|
11961
|
+
writeFileSync4(join13(dstDir, name), readFileSync9(srcPath));
|
|
11744
11962
|
}
|
|
11745
11963
|
}
|
|
11746
11964
|
function realWriteBinding(dstDir, identityId) {
|
|
11747
|
-
|
|
11748
|
-
|
|
11965
|
+
mkdirSync3(dstDir, { recursive: true });
|
|
11966
|
+
writeFileSync4(join13(dstDir, "securecoms.binding.json"), JSON.stringify({ identity_id: identityId }) + "\n", { mode: 384 });
|
|
11749
11967
|
}
|
|
11750
11968
|
function realReadBinding(dstDir) {
|
|
11751
11969
|
try {
|
|
11752
|
-
const parsed = JSON.parse(
|
|
11970
|
+
const parsed = JSON.parse(readFileSync9(join13(dstDir, "securecoms.binding.json"), "utf8"));
|
|
11753
11971
|
return parsed.identity_id ?? null;
|
|
11754
11972
|
} catch {
|
|
11755
11973
|
return null;
|
|
11756
11974
|
}
|
|
11757
11975
|
}
|
|
11758
11976
|
function realRemoveBinding(dstDir) {
|
|
11759
|
-
rmSync(
|
|
11977
|
+
rmSync(join13(dstDir, "securecoms.binding.json"), { force: true });
|
|
11760
11978
|
}
|
|
11761
11979
|
function realStatDir(p) {
|
|
11762
11980
|
try {
|
|
11763
|
-
return
|
|
11981
|
+
return statSync4(p).isDirectory();
|
|
11764
11982
|
} catch {
|
|
11765
11983
|
return false;
|
|
11766
11984
|
}
|
|
11767
11985
|
}
|
|
11768
11986
|
function realReadHermesConfig(p) {
|
|
11769
11987
|
try {
|
|
11770
|
-
return
|
|
11988
|
+
return readFileSync9(p, "utf8");
|
|
11771
11989
|
} catch {
|
|
11772
11990
|
return null;
|
|
11773
11991
|
}
|
|
11774
11992
|
}
|
|
11775
11993
|
function realBackupHermesConfig(p, originalText) {
|
|
11776
|
-
|
|
11994
|
+
writeFileSync4(hermesConfigBackupPath(p, Date.now(), process.pid), originalText, "utf8");
|
|
11777
11995
|
}
|
|
11778
11996
|
function realWriteHermesConfig(p, newText) {
|
|
11779
|
-
|
|
11997
|
+
writeFileSync4(p, newText, "utf8");
|
|
11780
11998
|
}
|
|
11781
11999
|
function realWireArgv() {
|
|
11782
12000
|
if (process.argv.includes("--harness")) return process.argv;
|
|
11783
|
-
const detected = detectHarness(
|
|
12001
|
+
const detected = detectHarness(homedir3(), realStatDir);
|
|
11784
12002
|
return detected ? [...process.argv, "--harness", detected] : process.argv;
|
|
11785
12003
|
}
|
|
11786
12004
|
function realWireDeps() {
|
|
11787
12005
|
return {
|
|
11788
|
-
home:
|
|
12006
|
+
home: homedir3(),
|
|
11789
12007
|
argv: realWireArgv(),
|
|
11790
12008
|
readFleet: realReadFleet,
|
|
11791
12009
|
copyConnector: realCopyConnector,
|
|
@@ -11837,7 +12055,7 @@ async function runServiceVerb(verb) {
|
|
|
11837
12055
|
explicitPort: portStr ? Number(portStr) : void 0,
|
|
11838
12056
|
harness: installHarness,
|
|
11839
12057
|
allowRebind: process.argv.includes("--rebind"),
|
|
11840
|
-
home:
|
|
12058
|
+
home: homedir3(),
|
|
11841
12059
|
connectorsSrcDir: connectorsSrcDir(import.meta.url, installHarness === "hermes" ? "hermes" : "openclaw"),
|
|
11842
12060
|
autoWireDeps: {
|
|
11843
12061
|
statDir: realStatDir,
|
|
@@ -11864,7 +12082,7 @@ async function runServiceVerb(verb) {
|
|
|
11864
12082
|
label,
|
|
11865
12083
|
execPath: installExecPath,
|
|
11866
12084
|
binScript,
|
|
11867
|
-
home:
|
|
12085
|
+
home: homedir3(),
|
|
11868
12086
|
uid: userInfo().uid
|
|
11869
12087
|
})
|
|
11870
12088
|
});
|
|
@@ -11878,7 +12096,7 @@ async function runServiceVerb(verb) {
|
|
|
11878
12096
|
label,
|
|
11879
12097
|
execPath: process.execPath,
|
|
11880
12098
|
binScript,
|
|
11881
|
-
home:
|
|
12099
|
+
home: homedir3(),
|
|
11882
12100
|
uid: userInfo().uid
|
|
11883
12101
|
};
|
|
11884
12102
|
const r = uninstallService(input, realServiceDeps());
|
|
@@ -11929,11 +12147,11 @@ async function main() {
|
|
|
11929
12147
|
`);
|
|
11930
12148
|
process.exit(1);
|
|
11931
12149
|
}
|
|
11932
|
-
const tokenFile = daemon.tokenFile ??
|
|
12150
|
+
const tokenFile = daemon.tokenFile ?? join13(dirname7(credentialPath), "bridge-token");
|
|
11933
12151
|
const token = resolveToken({ tokenFile, token: daemon.token });
|
|
11934
12152
|
const agent = new BridgeAgent(cfg);
|
|
11935
12153
|
await agent.start();
|
|
11936
|
-
const tls = daemon.tls ? { cert:
|
|
12154
|
+
const tls = daemon.tls ? { cert: readFileSync9(daemon.tlsCert, "utf8"), key: readFileSync9(daemon.tlsKey, "utf8") } : void 0;
|
|
11937
12155
|
const { server, closeAllSseStreams } = createHttpServer(agent, { token, allowOrigins: daemon.allowOrigins, tls, websocket: true });
|
|
11938
12156
|
const agentWs = attachAgentWebSocket(server, agent, { token, allowOrigins: daemon.allowOrigins });
|
|
11939
12157
|
let down = false;
|
|
@@ -11955,7 +12173,7 @@ async function main() {
|
|
|
11955
12173
|
const instanceId = newInstanceId();
|
|
11956
12174
|
const identityId = agent.health().identity_id;
|
|
11957
12175
|
const slug = slugFromCredentialPath(credentialPath);
|
|
11958
|
-
process.once("exit", () => removeConnectionFileIfOwned(
|
|
12176
|
+
process.once("exit", () => removeConnectionFileIfOwned(homedir3(), identityId, instanceId));
|
|
11959
12177
|
server.listen(daemon.port, daemon.bind, () => {
|
|
11960
12178
|
const scheme = tls ? "https" : "http";
|
|
11961
12179
|
const wsScheme = tls ? "wss" : "ws";
|
|
@@ -11966,7 +12184,7 @@ async function main() {
|
|
|
11966
12184
|
const addr = server.address();
|
|
11967
12185
|
const boundPort = typeof addr === "object" && addr ? addr.port : daemon.port;
|
|
11968
12186
|
try {
|
|
11969
|
-
writeConnectionFile(
|
|
12187
|
+
writeConnectionFile(homedir3(), buildConnectionFile({
|
|
11970
12188
|
identityId,
|
|
11971
12189
|
port: boundPort,
|
|
11972
12190
|
tokenFile,
|
package/dist/bridge-agent.d.ts
CHANGED
|
@@ -144,6 +144,13 @@ export interface BridgeAgentDeps {
|
|
|
144
144
|
transport?: any;
|
|
145
145
|
backend?: any;
|
|
146
146
|
fetchImpl?: typeof fetch;
|
|
147
|
+
/**
|
|
148
|
+
* Round 5 (runtime config delivery): the home directory under which the
|
|
149
|
+
* managed runtime-config directory lives. Injected only by tests, so they can
|
|
150
|
+
* write into a temp dir instead of the developer's real ~; production leaves
|
|
151
|
+
* it undefined and gets homedir().
|
|
152
|
+
*/
|
|
153
|
+
home?: string;
|
|
147
154
|
/** Injectable so tests can assert recorded stages; defaults to a fresh one. */
|
|
148
155
|
latency?: LatencyRecorder;
|
|
149
156
|
/**
|
|
@@ -341,6 +348,8 @@ export declare class BridgeAgent {
|
|
|
341
348
|
private readonly backend;
|
|
342
349
|
private readonly custody;
|
|
343
350
|
private readonly fetchImpl;
|
|
351
|
+
/** Round 5: home for the managed runtime-config directory (test seam). */
|
|
352
|
+
private readonly home;
|
|
344
353
|
private readonly joinState;
|
|
345
354
|
/**
|
|
346
355
|
* Phase 2: lazy channel-metadata cache (channel_id → { is_dm, name, at }) used
|
|
@@ -642,6 +651,21 @@ export declare class BridgeAgent {
|
|
|
642
651
|
* `voice_settings_changed` ping); never throws.
|
|
643
652
|
*/
|
|
644
653
|
refetchVoiceSettings(): Promise<void>;
|
|
654
|
+
/**
|
|
655
|
+
* Fetch this agent's runtime config and apply it to the managed directory the
|
|
656
|
+
* runtime reads (spec §8). Safe to call any time — at start(), or from the
|
|
657
|
+
* receive pump on a `runtime_config_changed` ping — and never throws: a save
|
|
658
|
+
* the owner already made must not be undone by a network blip here, and no
|
|
659
|
+
* shape the backend can send may take the daemon down.
|
|
660
|
+
*
|
|
661
|
+
* Order matters. Prunes are applied BEFORE the memory is read back, so the
|
|
662
|
+
* report the owner sees next is the file as it now stands.
|
|
663
|
+
*
|
|
664
|
+
* Every cycle rewrites, even at an unchanged version: directory permissions
|
|
665
|
+
* are repaired only on a write, so skipping would leave a loosened directory
|
|
666
|
+
* loose until the next save.
|
|
667
|
+
*/
|
|
668
|
+
refetchRuntimeConfig(): Promise<void>;
|
|
645
669
|
/**
|
|
646
670
|
* Enforce the inbound-buffer cap with a DROP-OLDEST policy.
|
|
647
671
|
*
|
package/dist/index.js
CHANGED
|
@@ -4546,6 +4546,9 @@ var FRAME_PARSERS = {
|
|
|
4546
4546
|
}),
|
|
4547
4547
|
voice_settings_changed: (d) => ({
|
|
4548
4548
|
voiceSettingsChanged: { identityId: str(d?.identity_id), version: Number(d?.version ?? 0) }
|
|
4549
|
+
}),
|
|
4550
|
+
runtime_config_changed: (d) => ({
|
|
4551
|
+
runtimeConfigChanged: { identityId: str(d?.identity_id), version: Number(d?.version ?? 0) }
|
|
4549
4552
|
})
|
|
4550
4553
|
};
|
|
4551
4554
|
var S2C_FRAME_NAMES = new Set(Object.keys(FRAME_PARSERS));
|
|
@@ -6100,7 +6103,7 @@ var SqliteMlsStateStore = class {
|
|
|
6100
6103
|
|
|
6101
6104
|
// src/bridge-agent.ts
|
|
6102
6105
|
import { decryptFile as decryptFile2, encryptFile as encryptFile2, computeFileDigest as computeFileDigest2, base64ToBytes as base64ToBytes4, bytesToBase64 as bytesToBase644 } from "@agentvault/crypto";
|
|
6103
|
-
import { createHash } from "node:crypto";
|
|
6106
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
6104
6107
|
|
|
6105
6108
|
// src/service/resolve-bin.ts
|
|
6106
6109
|
import { createRequire } from "node:module";
|
|
@@ -6112,7 +6115,7 @@ function loadPackageMeta(importMetaUrl) {
|
|
|
6112
6115
|
|
|
6113
6116
|
// src/bridge-agent.ts
|
|
6114
6117
|
import { promises as fsp } from "node:fs";
|
|
6115
|
-
import { join as
|
|
6118
|
+
import { join as join7, dirname } from "node:path";
|
|
6116
6119
|
|
|
6117
6120
|
// src/latency.ts
|
|
6118
6121
|
var DEFAULT_CAPACITY = 1024;
|
|
@@ -7308,7 +7311,135 @@ async function runSummarize(opts) {
|
|
|
7308
7311
|
}
|
|
7309
7312
|
}
|
|
7310
7313
|
|
|
7314
|
+
// src/service/runtime-config-dir.ts
|
|
7315
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
7316
|
+
import {
|
|
7317
|
+
chmodSync,
|
|
7318
|
+
existsSync,
|
|
7319
|
+
mkdirSync,
|
|
7320
|
+
readFileSync as readFileSync5,
|
|
7321
|
+
renameSync,
|
|
7322
|
+
statSync as statSync2,
|
|
7323
|
+
unlinkSync,
|
|
7324
|
+
writeFileSync
|
|
7325
|
+
} from "node:fs";
|
|
7326
|
+
import { basename as basename2, join as join6 } from "node:path";
|
|
7327
|
+
var MEMORY_FILE = "MEMORY.md";
|
|
7328
|
+
var REAL_FS = {
|
|
7329
|
+
mkdir: (p, mode) => mkdirSync(p, { recursive: true, mode }),
|
|
7330
|
+
chmod: (p, mode) => chmodSync(p, mode),
|
|
7331
|
+
writeFile: (p, data, mode) => writeFileSync(p, data, { mode }),
|
|
7332
|
+
rename: (a, b) => renameSync(a, b),
|
|
7333
|
+
readFile: (p) => readFileSync5(p, "utf8"),
|
|
7334
|
+
unlink: (p) => unlinkSync(p)
|
|
7335
|
+
};
|
|
7336
|
+
function runtimeConfigDir(home, identityId) {
|
|
7337
|
+
return join6(home, ".securecoms", "runtime-config", identityId);
|
|
7338
|
+
}
|
|
7339
|
+
function assertManagedName(name) {
|
|
7340
|
+
if (!name || name.includes("/") || name.includes("\\") || name === "." || name === ".." || name !== basename2(name)) {
|
|
7341
|
+
throw new Error(`refusing managed config file name ${JSON.stringify(name)}: not a plain filename`);
|
|
7342
|
+
}
|
|
7343
|
+
}
|
|
7344
|
+
function atomicWrite(path, body, deps) {
|
|
7345
|
+
const tmp = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
7346
|
+
deps.writeFile(tmp, body, 384);
|
|
7347
|
+
deps.chmod(tmp, 384);
|
|
7348
|
+
try {
|
|
7349
|
+
deps.rename(tmp, path);
|
|
7350
|
+
} catch (err) {
|
|
7351
|
+
try {
|
|
7352
|
+
deps.unlink(tmp);
|
|
7353
|
+
} catch {
|
|
7354
|
+
}
|
|
7355
|
+
throw err;
|
|
7356
|
+
}
|
|
7357
|
+
}
|
|
7358
|
+
function writeManagedFiles(home, identityId, files, deps = REAL_FS) {
|
|
7359
|
+
const entries = Object.entries(files);
|
|
7360
|
+
for (const [name] of entries) assertManagedName(name);
|
|
7361
|
+
const parent2 = join6(home, ".securecoms", "runtime-config");
|
|
7362
|
+
const dir = runtimeConfigDir(home, identityId);
|
|
7363
|
+
for (const d of [join6(home, ".securecoms"), parent2, dir]) {
|
|
7364
|
+
deps.mkdir(d, 448);
|
|
7365
|
+
deps.chmod(d, 448);
|
|
7366
|
+
}
|
|
7367
|
+
for (const [name, body] of entries) atomicWrite(join6(dir, name), body, deps);
|
|
7368
|
+
}
|
|
7369
|
+
function checkConfiguredPaths(skillsJson, statFn = (p) => {
|
|
7370
|
+
const s = statSync2(p);
|
|
7371
|
+
return { mode: s.mode, size: s.size };
|
|
7372
|
+
}) {
|
|
7373
|
+
const paths = [];
|
|
7374
|
+
const endpoints = skillsJson.endpoints ?? {};
|
|
7375
|
+
for (const e of Object.values(endpoints)) if (e?.headersFile) paths.push(e.headersFile);
|
|
7376
|
+
const databases = skillsJson.databases ?? {};
|
|
7377
|
+
for (const d of Object.values(databases)) {
|
|
7378
|
+
if (d?.sqlite) paths.push(d.sqlite);
|
|
7379
|
+
if (d?.postgresUrlFile) paths.push(d.postgresUrlFile);
|
|
7380
|
+
}
|
|
7381
|
+
return paths.map((path) => {
|
|
7382
|
+
let st;
|
|
7383
|
+
try {
|
|
7384
|
+
st = statFn(path);
|
|
7385
|
+
} catch {
|
|
7386
|
+
return { path, ok: false, why: "not found" };
|
|
7387
|
+
}
|
|
7388
|
+
if ((st.mode & 63) !== 0) {
|
|
7389
|
+
return { path, ok: false, why: "readable by other users on this machine \u2014 tighten its permissions to owner-only" };
|
|
7390
|
+
}
|
|
7391
|
+
if (st.size === 0) return { path, ok: false, why: "the file is empty" };
|
|
7392
|
+
return { path, ok: true, why: "" };
|
|
7393
|
+
});
|
|
7394
|
+
}
|
|
7395
|
+
function lineHash(line) {
|
|
7396
|
+
return createHash("sha256").update(line.trim(), "utf8").digest("hex");
|
|
7397
|
+
}
|
|
7398
|
+
var LOCAL_REPORT_FILE = "local-config.json";
|
|
7399
|
+
function readLocalReport(home, identityId) {
|
|
7400
|
+
const p = join6(runtimeConfigDir(home, identityId), LOCAL_REPORT_FILE);
|
|
7401
|
+
if (!existsSync(p)) return null;
|
|
7402
|
+
let doc;
|
|
7403
|
+
try {
|
|
7404
|
+
doc = JSON.parse(readFileSync5(p, "utf8"));
|
|
7405
|
+
} catch {
|
|
7406
|
+
return null;
|
|
7407
|
+
}
|
|
7408
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc)) return null;
|
|
7409
|
+
const hasSearchKey = doc.has_search_key === true;
|
|
7410
|
+
const raw = doc.persona;
|
|
7411
|
+
const persona = raw && typeof raw === "object" && !Array.isArray(raw) ? Object.fromEntries(
|
|
7412
|
+
Object.entries(raw).filter(([, v]) => typeof v === "string")
|
|
7413
|
+
) : {};
|
|
7414
|
+
return { persona, has_search_key: hasSearchKey };
|
|
7415
|
+
}
|
|
7416
|
+
function readManagedMemory(home, identityId) {
|
|
7417
|
+
const p = join6(runtimeConfigDir(home, identityId), MEMORY_FILE);
|
|
7418
|
+
if (!existsSync(p)) return null;
|
|
7419
|
+
try {
|
|
7420
|
+
const body = readFileSync5(p, "utf8").trim();
|
|
7421
|
+
return body || null;
|
|
7422
|
+
} catch {
|
|
7423
|
+
return null;
|
|
7424
|
+
}
|
|
7425
|
+
}
|
|
7426
|
+
function pruneManagedMemory(home, identityId, hashes, deps = REAL_FS) {
|
|
7427
|
+
const dir = runtimeConfigDir(home, identityId);
|
|
7428
|
+
const path = join6(dir, MEMORY_FILE);
|
|
7429
|
+
if (!existsSync(path)) return 0;
|
|
7430
|
+
const drop = new Set(hashes);
|
|
7431
|
+
const before = deps.readFile(path).split("\n");
|
|
7432
|
+
const kept = before.filter((l) => l.trim() === "" || !drop.has(lineHash(l)));
|
|
7433
|
+
const removed = before.filter((l) => l.trim() !== "" && drop.has(lineHash(l))).length;
|
|
7434
|
+
if (removed === 0) return 0;
|
|
7435
|
+
const body = kept.filter((l, i) => l.trim() !== "" || i < kept.length - 1).join("\n").trimEnd();
|
|
7436
|
+
atomicWrite(path, body ? `${body}
|
|
7437
|
+
` : "", deps);
|
|
7438
|
+
return removed;
|
|
7439
|
+
}
|
|
7440
|
+
|
|
7311
7441
|
// src/bridge-agent.ts
|
|
7442
|
+
import { homedir as homedir2 } from "node:os";
|
|
7312
7443
|
var VOICE_MODE_GUIDANCE = "Reply immediately and conversationally, one short sentence per message; never bundle multiple points into one message.";
|
|
7313
7444
|
var FAST_PATH_SPOKEN_GUIDANCE = "A one-sentence spoken acknowledgement has already been sent for this turn. Reply only with information it did not contain, in one or two short sentences; if there is nothing to add, send nothing.";
|
|
7314
7445
|
var ROOM_VOICE_GUIDANCE_MENTION_ONLY = "You are in a group voice room. Reply ONLY when a message @mentions your display name. Keep replies to one short sentence. When you should not reply, send nothing.";
|
|
@@ -7363,7 +7494,7 @@ function resolveMessageId(messageId) {
|
|
|
7363
7494
|
return messageId;
|
|
7364
7495
|
}
|
|
7365
7496
|
function fastReplyId(messageId) {
|
|
7366
|
-
const h =
|
|
7497
|
+
const h = createHash2("sha256").update(`fast:${messageId}`).digest();
|
|
7367
7498
|
h[6] = h[6] & 15 | 64;
|
|
7368
7499
|
h[8] = h[8] & 63 | 128;
|
|
7369
7500
|
const x = h.subarray(0, 16).toString("hex");
|
|
@@ -7493,6 +7624,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
7493
7624
|
onSupersedeUnopenable: (channelId, groupId) => this.healSupersedeUnopenable(channelId, groupId)
|
|
7494
7625
|
});
|
|
7495
7626
|
this.fetchImpl = deps.fetchImpl ?? fetch;
|
|
7627
|
+
this.home = deps.home ?? homedir2();
|
|
7496
7628
|
this.injectedFastLlm = !!deps.fastLlm;
|
|
7497
7629
|
this.fileVoiceConfig = cfg.voice;
|
|
7498
7630
|
this.fastLlm = deps.fastLlm ?? (cfg.voice?.fastPath.enabled ? new FastLlmClient(cfg.voice.fastPath, this.fetchImpl) : void 0);
|
|
@@ -7525,8 +7657,8 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
7525
7657
|
const j = await res.json();
|
|
7526
7658
|
return String(j.attachment_id);
|
|
7527
7659
|
};
|
|
7528
|
-
this.attachmentsDirImpl = deps.attachmentsDir ??
|
|
7529
|
-
this.vaultOwnerStoreImpl = deps.vaultOwnerStore ?? new FileOwnerAuthorityStore(
|
|
7660
|
+
this.attachmentsDirImpl = deps.attachmentsDir ?? join7(dirname(cfg.storePath), "attachments");
|
|
7661
|
+
this.vaultOwnerStoreImpl = deps.vaultOwnerStore ?? new FileOwnerAuthorityStore(join7(dirname(cfg.storePath), "vault-owner-pins.json"));
|
|
7530
7662
|
this.action = {
|
|
7531
7663
|
begin: (channelId) => this.actionBegin(channelId),
|
|
7532
7664
|
start: (opts) => this.actionStart(opts),
|
|
@@ -7542,6 +7674,8 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
7542
7674
|
// stored for peek-then-join in ingestOnce
|
|
7543
7675
|
custody;
|
|
7544
7676
|
fetchImpl;
|
|
7677
|
+
/** Round 5: home for the managed runtime-config directory (test seam). */
|
|
7678
|
+
home;
|
|
7545
7679
|
joinState = /* @__PURE__ */ new Map();
|
|
7546
7680
|
/**
|
|
7547
7681
|
* Phase 2: lazy channel-metadata cache (channel_id → { is_dm, name, at }) used
|
|
@@ -7883,6 +8017,84 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
7883
8017
|
if (this.voiceSettingsSource === "backend") return;
|
|
7884
8018
|
process.stderr.write(
|
|
7885
8019
|
`[bridge] voice settings unavailable (${e.message}); using ${this.voiceSettingsSource}
|
|
8020
|
+
`
|
|
8021
|
+
);
|
|
8022
|
+
}
|
|
8023
|
+
}
|
|
8024
|
+
/**
|
|
8025
|
+
* Fetch this agent's runtime config and apply it to the managed directory the
|
|
8026
|
+
* runtime reads (spec §8). Safe to call any time — at start(), or from the
|
|
8027
|
+
* receive pump on a `runtime_config_changed` ping — and never throws: a save
|
|
8028
|
+
* the owner already made must not be undone by a network blip here, and no
|
|
8029
|
+
* shape the backend can send may take the daemon down.
|
|
8030
|
+
*
|
|
8031
|
+
* Order matters. Prunes are applied BEFORE the memory is read back, so the
|
|
8032
|
+
* report the owner sees next is the file as it now stands.
|
|
8033
|
+
*
|
|
8034
|
+
* Every cycle rewrites, even at an unchanged version: directory permissions
|
|
8035
|
+
* are repaired only on a write, so skipping would leave a loosened directory
|
|
8036
|
+
* loose until the next save.
|
|
8037
|
+
*/
|
|
8038
|
+
async refetchRuntimeConfig() {
|
|
8039
|
+
const base = `${this.cfg.apiBase}/api/v1/agents/${encodeURIComponent(this.cfg.identityId)}/runtime-config`;
|
|
8040
|
+
try {
|
|
8041
|
+
const res = await this.fetchImpl(`${base}/files`, {
|
|
8042
|
+
headers: { authorization: `Bearer ${this.cfg.apiKey}` }
|
|
8043
|
+
});
|
|
8044
|
+
if (!res.ok) throw new Error(`runtime-config ${res.status}`);
|
|
8045
|
+
const payload = await res.json();
|
|
8046
|
+
if (!payload || !payload.version) {
|
|
8047
|
+
process.stderr.write(
|
|
8048
|
+
`[bridge] runtime config not applied (version ${JSON.stringify(payload?.version ?? null)} \u2014 no saved config)
|
|
8049
|
+
`
|
|
8050
|
+
);
|
|
8051
|
+
return;
|
|
8052
|
+
}
|
|
8053
|
+
const skillsJson = payload.skills_json ?? {};
|
|
8054
|
+
writeManagedFiles(this.home, this.cfg.identityId, {
|
|
8055
|
+
...payload.files ?? {},
|
|
8056
|
+
"skills.json": `${JSON.stringify(skillsJson, null, 2)}
|
|
8057
|
+
`
|
|
8058
|
+
});
|
|
8059
|
+
if (payload.memory_prunes?.length) {
|
|
8060
|
+
pruneManagedMemory(this.home, this.cfg.identityId, payload.memory_prunes);
|
|
8061
|
+
}
|
|
8062
|
+
const local = readLocalReport(this.home, this.cfg.identityId);
|
|
8063
|
+
const report = {
|
|
8064
|
+
applied_version: payload.version,
|
|
8065
|
+
paths: checkConfiguredPaths(skillsJson),
|
|
8066
|
+
// Read AFTER the prunes above, so the owner's next view of memory is
|
|
8067
|
+
// the file as it now stands, not the one they just pruned from.
|
|
8068
|
+
memory: readManagedMemory(this.home, this.cfg.identityId),
|
|
8069
|
+
// E13: pass through what the runtime reported. The daemon does not
|
|
8070
|
+
// interpret any of it.
|
|
8071
|
+
local_persona: local?.persona ?? null,
|
|
8072
|
+
// Only the runtime knows whether a search key file is configured — it
|
|
8073
|
+
// is an environment value on that process, invisible to the daemon and
|
|
8074
|
+
// to the backend. The backend needs it to generate a truthful tool
|
|
8075
|
+
// paragraph, so it rides along here.
|
|
8076
|
+
has_search_key: local?.has_search_key ?? false
|
|
8077
|
+
};
|
|
8078
|
+
const posted = await this.fetchImpl(`${base}/report`, {
|
|
8079
|
+
method: "POST",
|
|
8080
|
+
headers: {
|
|
8081
|
+
authorization: `Bearer ${this.cfg.apiKey}`,
|
|
8082
|
+
"content-type": "application/json"
|
|
8083
|
+
},
|
|
8084
|
+
body: JSON.stringify(report)
|
|
8085
|
+
});
|
|
8086
|
+
if (!posted.ok) {
|
|
8087
|
+
process.stderr.write(
|
|
8088
|
+
`[bridge] runtime config v${payload.version} written, but reporting it failed (${posted.status}) \u2014 the console will keep showing it as not applied until the next cycle
|
|
8089
|
+
`
|
|
8090
|
+
);
|
|
8091
|
+
return;
|
|
8092
|
+
}
|
|
8093
|
+
process.stderr.write(`[bridge] runtime config v${payload.version} applied
|
|
8094
|
+
`);
|
|
8095
|
+
} catch (e) {
|
|
8096
|
+
process.stderr.write(
|
|
8097
|
+
`[bridge] runtime config refetch failed: ${e?.message ?? e}
|
|
7886
8098
|
`
|
|
7887
8099
|
);
|
|
7888
8100
|
}
|
|
@@ -7914,6 +8126,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
7914
8126
|
}
|
|
7915
8127
|
await this.provider.publishKeyPackage();
|
|
7916
8128
|
await this.refetchVoiceSettings();
|
|
8129
|
+
await this.refetchRuntimeConfig();
|
|
7917
8130
|
this.startReceivePump();
|
|
7918
8131
|
if (this.selfHealSweepEnabled) {
|
|
7919
8132
|
this.bootSweepTimer = setTimeout(
|
|
@@ -9398,11 +9611,16 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
9398
9611
|
this.pingsReceived++;
|
|
9399
9612
|
if (ping?.reconnected) {
|
|
9400
9613
|
process.stderr.write("[bridge] delivery-ping WS (re)connected \u2014 push inbound active\n");
|
|
9614
|
+
await this.refetchRuntimeConfig();
|
|
9401
9615
|
}
|
|
9402
9616
|
if (ping?.voiceSettingsChanged) {
|
|
9403
9617
|
await this.refetchVoiceSettings();
|
|
9404
9618
|
continue;
|
|
9405
9619
|
}
|
|
9620
|
+
if (ping?.runtimeConfigChanged) {
|
|
9621
|
+
await this.refetchRuntimeConfig();
|
|
9622
|
+
continue;
|
|
9623
|
+
}
|
|
9406
9624
|
await this.ingestOnce();
|
|
9407
9625
|
}
|
|
9408
9626
|
} catch {
|
|
@@ -9840,13 +10058,13 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
9840
10058
|
};
|
|
9841
10059
|
|
|
9842
10060
|
// src/config.ts
|
|
9843
|
-
import { readFileSync as
|
|
9844
|
-
import { dirname as dirname2, join as
|
|
10061
|
+
import { readFileSync as readFileSync6, statSync as statSync3 } from "node:fs";
|
|
10062
|
+
import { dirname as dirname2, join as join8 } from "node:path";
|
|
9845
10063
|
var REQUIRED = ["api_key", "identity_id", "device_id", "signing_private_key", "enclave_id", "api_base"];
|
|
9846
10064
|
function loadConfig(opts) {
|
|
9847
|
-
const raw =
|
|
10065
|
+
const raw = readFileSync6(opts.credentialPath, "utf8");
|
|
9848
10066
|
try {
|
|
9849
|
-
const mode =
|
|
10067
|
+
const mode = statSync3(opts.credentialPath).mode & 511;
|
|
9850
10068
|
if (mode & 63) process.stderr.write(`[bridge] WARNING: credential file ${opts.credentialPath} permission ${mode.toString(8)} is not 0600 \u2014 tighten it (chmod 600).
|
|
9851
10069
|
`);
|
|
9852
10070
|
} catch {
|
|
@@ -9858,7 +10076,7 @@ function loadConfig(opts) {
|
|
|
9858
10076
|
throw new Error("credential file is not valid JSON");
|
|
9859
10077
|
}
|
|
9860
10078
|
for (const k of REQUIRED) if (typeof json2[k] !== "string" || !json2[k]) throw new Error(`credential file missing required field: ${k}`);
|
|
9861
|
-
const storePath = opts.storePath ??
|
|
10079
|
+
const storePath = opts.storePath ?? join8(dirname2(opts.credentialPath), "bridge-state.db");
|
|
9862
10080
|
const agentName = typeof json2.agent_name === "string" && json2.agent_name ? json2.agent_name : typeof json2.display_name === "string" && json2.display_name ? json2.display_name : void 0;
|
|
9863
10081
|
const rateLimitCapacity = typeof json2.rate_limit_capacity === "number" ? json2.rate_limit_capacity : 20;
|
|
9864
10082
|
const rateLimitRefillPerMinute = typeof json2.rate_limit_refill_per_minute === "number" ? json2.rate_limit_refill_per_minute : 20;
|
|
@@ -9954,13 +10172,13 @@ import { z as z3 } from "zod";
|
|
|
9954
10172
|
|
|
9955
10173
|
// src/auth.ts
|
|
9956
10174
|
import { randomBytes as randomBytes3, timingSafeEqual } from "node:crypto";
|
|
9957
|
-
import { existsSync, readFileSync as
|
|
10175
|
+
import { existsSync as existsSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "node:fs";
|
|
9958
10176
|
function resolveToken(opts) {
|
|
9959
10177
|
if (opts.token) return opts.token;
|
|
9960
10178
|
const f = opts.tokenFile;
|
|
9961
|
-
if (f &&
|
|
10179
|
+
if (f && existsSync2(f)) return readFileSync7(f, "utf8").trim();
|
|
9962
10180
|
const tok = randomBytes3(32).toString("base64url");
|
|
9963
|
-
if (f)
|
|
10181
|
+
if (f) writeFileSync2(f, tok, { mode: 384 });
|
|
9964
10182
|
return tok;
|
|
9965
10183
|
}
|
|
9966
10184
|
function tokenMatches(provided, expected) {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export declare const MEMORY_FILE = "MEMORY.md";
|
|
2
|
+
export interface PathCheck {
|
|
3
|
+
path: string;
|
|
4
|
+
ok: boolean;
|
|
5
|
+
why: string;
|
|
6
|
+
}
|
|
7
|
+
export interface FsDeps {
|
|
8
|
+
mkdir: (p: string, mode: number) => void;
|
|
9
|
+
chmod: (p: string, mode: number) => void;
|
|
10
|
+
writeFile: (p: string, data: string, mode: number) => void;
|
|
11
|
+
rename: (a: string, b: string) => void;
|
|
12
|
+
readFile: (p: string) => string;
|
|
13
|
+
unlink: (p: string) => void;
|
|
14
|
+
}
|
|
15
|
+
export declare function runtimeConfigDir(home: string, identityId: string): string;
|
|
16
|
+
export declare function writeManagedFiles(home: string, identityId: string, files: Record<string, string>, deps?: FsDeps): void;
|
|
17
|
+
/** Every path skills.json names, checked on disk. Contents are never read. */
|
|
18
|
+
export declare function checkConfiguredPaths(skillsJson: Record<string, unknown>, statFn?: (p: string) => {
|
|
19
|
+
mode: number;
|
|
20
|
+
size: number;
|
|
21
|
+
}): PathCheck[];
|
|
22
|
+
export declare function lineHash(line: string): string;
|
|
23
|
+
export declare const LOCAL_REPORT_FILE = "local-config.json";
|
|
24
|
+
/**
|
|
25
|
+
* What the runtime says its OWN persona files currently contain (spec E13).
|
|
26
|
+
*
|
|
27
|
+
* The runtime writes this; the daemon only reads it. It exists because the
|
|
28
|
+
* daemon cannot see the runtime's data dir, so without it an owner's
|
|
29
|
+
* hand-written persona would be invisible in the web app and the first save
|
|
30
|
+
* would replace it sight unseen. It is inert: nothing here decides behaviour,
|
|
31
|
+
* and it never affects which file the runtime actually reads.
|
|
32
|
+
*/
|
|
33
|
+
export interface LocalReport {
|
|
34
|
+
persona: Record<string, string>;
|
|
35
|
+
has_search_key: boolean;
|
|
36
|
+
}
|
|
37
|
+
export declare function readLocalReport(home: string, identityId: string): LocalReport | null;
|
|
38
|
+
export declare function readManagedMemory(home: string, identityId: string): string | null;
|
|
39
|
+
/**
|
|
40
|
+
* Remove exactly the named lines. Line-level, never a whole-file replace: the
|
|
41
|
+
* agent may append a fact between the owner reading and pruning, and that fact
|
|
42
|
+
* must survive (spec §7).
|
|
43
|
+
*/
|
|
44
|
+
export declare function pruneManagedMemory(home: string, identityId: string, hashes: string[], deps?: FsDeps): number;
|