@securecoms/agent-bridge 0.17.0 → 0.19.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 +294 -74
- package/dist/bridge-agent.d.ts +24 -0
- package/dist/index.js +233 -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,137 @@ 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
|
+
const search = skillsJson.search;
|
|
7535
|
+
if (search?.keyFile) paths.push(search.keyFile);
|
|
7536
|
+
return paths.map((path) => {
|
|
7537
|
+
let st;
|
|
7538
|
+
try {
|
|
7539
|
+
st = statFn(path);
|
|
7540
|
+
} catch {
|
|
7541
|
+
return { path, ok: false, why: "not found" };
|
|
7542
|
+
}
|
|
7543
|
+
if ((st.mode & 63) !== 0) {
|
|
7544
|
+
return { path, ok: false, why: "readable by other users on this machine \u2014 tighten its permissions to owner-only" };
|
|
7545
|
+
}
|
|
7546
|
+
if (st.size === 0) return { path, ok: false, why: "the file is empty" };
|
|
7547
|
+
return { path, ok: true, why: "" };
|
|
7548
|
+
});
|
|
7549
|
+
}
|
|
7550
|
+
function lineHash(line) {
|
|
7551
|
+
return createHash("sha256").update(line.trim(), "utf8").digest("hex");
|
|
7552
|
+
}
|
|
7553
|
+
var LOCAL_REPORT_FILE = "local-config.json";
|
|
7554
|
+
function readLocalReport(home, identityId) {
|
|
7555
|
+
const p = join8(runtimeConfigDir(home, identityId), LOCAL_REPORT_FILE);
|
|
7556
|
+
if (!existsSync(p)) return null;
|
|
7557
|
+
let doc;
|
|
7558
|
+
try {
|
|
7559
|
+
doc = JSON.parse(readFileSync6(p, "utf8"));
|
|
7560
|
+
} catch {
|
|
7561
|
+
return null;
|
|
7562
|
+
}
|
|
7563
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc)) return null;
|
|
7564
|
+
const hasSearchKey = doc.has_search_key === true;
|
|
7565
|
+
const raw = doc.persona;
|
|
7566
|
+
const persona = raw && typeof raw === "object" && !Array.isArray(raw) ? Object.fromEntries(
|
|
7567
|
+
Object.entries(raw).filter(([, v]) => typeof v === "string")
|
|
7568
|
+
) : {};
|
|
7569
|
+
return { persona, has_search_key: hasSearchKey };
|
|
7570
|
+
}
|
|
7571
|
+
function readManagedMemory(home, identityId) {
|
|
7572
|
+
const p = join8(runtimeConfigDir(home, identityId), MEMORY_FILE);
|
|
7573
|
+
if (!existsSync(p)) return null;
|
|
7574
|
+
try {
|
|
7575
|
+
const body = readFileSync6(p, "utf8").trim();
|
|
7576
|
+
return body || null;
|
|
7577
|
+
} catch {
|
|
7578
|
+
return null;
|
|
7579
|
+
}
|
|
7580
|
+
}
|
|
7581
|
+
function pruneManagedMemory(home, identityId, hashes, deps = REAL_FS) {
|
|
7582
|
+
const dir = runtimeConfigDir(home, identityId);
|
|
7583
|
+
const path = join8(dir, MEMORY_FILE);
|
|
7584
|
+
if (!existsSync(path)) return 0;
|
|
7585
|
+
const drop = new Set(hashes);
|
|
7586
|
+
const before = deps.readFile(path).split("\n");
|
|
7587
|
+
const kept = before.filter((l) => l.trim() === "" || !drop.has(lineHash(l)));
|
|
7588
|
+
const removed = before.filter((l) => l.trim() !== "" && drop.has(lineHash(l))).length;
|
|
7589
|
+
if (removed === 0) return 0;
|
|
7590
|
+
const body = kept.filter((l, i) => l.trim() !== "" || i < kept.length - 1).join("\n").trimEnd();
|
|
7591
|
+
atomicWrite(path, body ? `${body}
|
|
7592
|
+
` : "", deps);
|
|
7593
|
+
return removed;
|
|
7594
|
+
}
|
|
7595
|
+
|
|
7464
7596
|
// src/bridge-agent.ts
|
|
7597
|
+
import { homedir as homedir2 } from "node:os";
|
|
7465
7598
|
var VOICE_MODE_GUIDANCE = "Reply immediately and conversationally, one short sentence per message; never bundle multiple points into one message.";
|
|
7466
7599
|
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
7600
|
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 +7649,7 @@ function resolveMessageId(messageId) {
|
|
|
7516
7649
|
return messageId;
|
|
7517
7650
|
}
|
|
7518
7651
|
function fastReplyId(messageId) {
|
|
7519
|
-
const h =
|
|
7652
|
+
const h = createHash2("sha256").update(`fast:${messageId}`).digest();
|
|
7520
7653
|
h[6] = h[6] & 15 | 64;
|
|
7521
7654
|
h[8] = h[8] & 63 | 128;
|
|
7522
7655
|
const x = h.subarray(0, 16).toString("hex");
|
|
@@ -7646,6 +7779,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
7646
7779
|
onSupersedeUnopenable: (channelId, groupId) => this.healSupersedeUnopenable(channelId, groupId)
|
|
7647
7780
|
});
|
|
7648
7781
|
this.fetchImpl = deps.fetchImpl ?? fetch;
|
|
7782
|
+
this.home = deps.home ?? homedir2();
|
|
7649
7783
|
this.injectedFastLlm = !!deps.fastLlm;
|
|
7650
7784
|
this.fileVoiceConfig = cfg.voice;
|
|
7651
7785
|
this.fastLlm = deps.fastLlm ?? (cfg.voice?.fastPath.enabled ? new FastLlmClient(cfg.voice.fastPath, this.fetchImpl) : void 0);
|
|
@@ -7678,8 +7812,8 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
7678
7812
|
const j = await res.json();
|
|
7679
7813
|
return String(j.attachment_id);
|
|
7680
7814
|
};
|
|
7681
|
-
this.attachmentsDirImpl = deps.attachmentsDir ??
|
|
7682
|
-
this.vaultOwnerStoreImpl = deps.vaultOwnerStore ?? new FileOwnerAuthorityStore(
|
|
7815
|
+
this.attachmentsDirImpl = deps.attachmentsDir ?? join9(dirname3(cfg.storePath), "attachments");
|
|
7816
|
+
this.vaultOwnerStoreImpl = deps.vaultOwnerStore ?? new FileOwnerAuthorityStore(join9(dirname3(cfg.storePath), "vault-owner-pins.json"));
|
|
7683
7817
|
this.action = {
|
|
7684
7818
|
begin: (channelId) => this.actionBegin(channelId),
|
|
7685
7819
|
start: (opts) => this.actionStart(opts),
|
|
@@ -7695,6 +7829,8 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
7695
7829
|
// stored for peek-then-join in ingestOnce
|
|
7696
7830
|
custody;
|
|
7697
7831
|
fetchImpl;
|
|
7832
|
+
/** Round 5: home for the managed runtime-config directory (test seam). */
|
|
7833
|
+
home;
|
|
7698
7834
|
joinState = /* @__PURE__ */ new Map();
|
|
7699
7835
|
/**
|
|
7700
7836
|
* Phase 2: lazy channel-metadata cache (channel_id → { is_dm, name, at }) used
|
|
@@ -8036,6 +8172,84 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
8036
8172
|
if (this.voiceSettingsSource === "backend") return;
|
|
8037
8173
|
process.stderr.write(
|
|
8038
8174
|
`[bridge] voice settings unavailable (${e.message}); using ${this.voiceSettingsSource}
|
|
8175
|
+
`
|
|
8176
|
+
);
|
|
8177
|
+
}
|
|
8178
|
+
}
|
|
8179
|
+
/**
|
|
8180
|
+
* Fetch this agent's runtime config and apply it to the managed directory the
|
|
8181
|
+
* runtime reads (spec §8). Safe to call any time — at start(), or from the
|
|
8182
|
+
* receive pump on a `runtime_config_changed` ping — and never throws: a save
|
|
8183
|
+
* the owner already made must not be undone by a network blip here, and no
|
|
8184
|
+
* shape the backend can send may take the daemon down.
|
|
8185
|
+
*
|
|
8186
|
+
* Order matters. Prunes are applied BEFORE the memory is read back, so the
|
|
8187
|
+
* report the owner sees next is the file as it now stands.
|
|
8188
|
+
*
|
|
8189
|
+
* Every cycle rewrites, even at an unchanged version: directory permissions
|
|
8190
|
+
* are repaired only on a write, so skipping would leave a loosened directory
|
|
8191
|
+
* loose until the next save.
|
|
8192
|
+
*/
|
|
8193
|
+
async refetchRuntimeConfig() {
|
|
8194
|
+
const base = `${this.cfg.apiBase}/api/v1/agents/${encodeURIComponent(this.cfg.identityId)}/runtime-config`;
|
|
8195
|
+
try {
|
|
8196
|
+
const res = await this.fetchImpl(`${base}/files`, {
|
|
8197
|
+
headers: { authorization: `Bearer ${this.cfg.apiKey}` }
|
|
8198
|
+
});
|
|
8199
|
+
if (!res.ok) throw new Error(`runtime-config ${res.status}`);
|
|
8200
|
+
const payload = await res.json();
|
|
8201
|
+
if (!payload || !payload.version) {
|
|
8202
|
+
process.stderr.write(
|
|
8203
|
+
`[bridge] runtime config not applied (version ${JSON.stringify(payload?.version ?? null)} \u2014 no saved config)
|
|
8204
|
+
`
|
|
8205
|
+
);
|
|
8206
|
+
return;
|
|
8207
|
+
}
|
|
8208
|
+
const skillsJson = payload.skills_json ?? {};
|
|
8209
|
+
writeManagedFiles(this.home, this.cfg.identityId, {
|
|
8210
|
+
...payload.files ?? {},
|
|
8211
|
+
"skills.json": `${JSON.stringify(skillsJson, null, 2)}
|
|
8212
|
+
`
|
|
8213
|
+
});
|
|
8214
|
+
if (payload.memory_prunes?.length) {
|
|
8215
|
+
pruneManagedMemory(this.home, this.cfg.identityId, payload.memory_prunes);
|
|
8216
|
+
}
|
|
8217
|
+
const local = readLocalReport(this.home, this.cfg.identityId);
|
|
8218
|
+
const report = {
|
|
8219
|
+
applied_version: payload.version,
|
|
8220
|
+
paths: checkConfiguredPaths(skillsJson),
|
|
8221
|
+
// Read AFTER the prunes above, so the owner's next view of memory is
|
|
8222
|
+
// the file as it now stands, not the one they just pruned from.
|
|
8223
|
+
memory: readManagedMemory(this.home, this.cfg.identityId),
|
|
8224
|
+
// E13: pass through what the runtime reported. The daemon does not
|
|
8225
|
+
// interpret any of it.
|
|
8226
|
+
local_persona: local?.persona ?? null,
|
|
8227
|
+
// Only the runtime knows whether a search key file is configured — it
|
|
8228
|
+
// is an environment value on that process, invisible to the daemon and
|
|
8229
|
+
// to the backend. The backend needs it to generate a truthful tool
|
|
8230
|
+
// paragraph, so it rides along here.
|
|
8231
|
+
has_search_key: local?.has_search_key ?? false
|
|
8232
|
+
};
|
|
8233
|
+
const posted = await this.fetchImpl(`${base}/report`, {
|
|
8234
|
+
method: "POST",
|
|
8235
|
+
headers: {
|
|
8236
|
+
authorization: `Bearer ${this.cfg.apiKey}`,
|
|
8237
|
+
"content-type": "application/json"
|
|
8238
|
+
},
|
|
8239
|
+
body: JSON.stringify(report)
|
|
8240
|
+
});
|
|
8241
|
+
if (!posted.ok) {
|
|
8242
|
+
process.stderr.write(
|
|
8243
|
+
`[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
|
|
8244
|
+
`
|
|
8245
|
+
);
|
|
8246
|
+
return;
|
|
8247
|
+
}
|
|
8248
|
+
process.stderr.write(`[bridge] runtime config v${payload.version} applied
|
|
8249
|
+
`);
|
|
8250
|
+
} catch (e) {
|
|
8251
|
+
process.stderr.write(
|
|
8252
|
+
`[bridge] runtime config refetch failed: ${e?.message ?? e}
|
|
8039
8253
|
`
|
|
8040
8254
|
);
|
|
8041
8255
|
}
|
|
@@ -8067,6 +8281,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
8067
8281
|
}
|
|
8068
8282
|
await this.provider.publishKeyPackage();
|
|
8069
8283
|
await this.refetchVoiceSettings();
|
|
8284
|
+
await this.refetchRuntimeConfig();
|
|
8070
8285
|
this.startReceivePump();
|
|
8071
8286
|
if (this.selfHealSweepEnabled) {
|
|
8072
8287
|
this.bootSweepTimer = setTimeout(
|
|
@@ -9551,11 +9766,16 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
9551
9766
|
this.pingsReceived++;
|
|
9552
9767
|
if (ping?.reconnected) {
|
|
9553
9768
|
process.stderr.write("[bridge] delivery-ping WS (re)connected \u2014 push inbound active\n");
|
|
9769
|
+
await this.refetchRuntimeConfig();
|
|
9554
9770
|
}
|
|
9555
9771
|
if (ping?.voiceSettingsChanged) {
|
|
9556
9772
|
await this.refetchVoiceSettings();
|
|
9557
9773
|
continue;
|
|
9558
9774
|
}
|
|
9775
|
+
if (ping?.runtimeConfigChanged) {
|
|
9776
|
+
await this.refetchRuntimeConfig();
|
|
9777
|
+
continue;
|
|
9778
|
+
}
|
|
9559
9779
|
await this.ingestOnce();
|
|
9560
9780
|
}
|
|
9561
9781
|
} catch {
|
|
@@ -9994,13 +10214,13 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
9994
10214
|
|
|
9995
10215
|
// src/auth.ts
|
|
9996
10216
|
import { randomBytes as randomBytes3, timingSafeEqual } from "node:crypto";
|
|
9997
|
-
import { existsSync, readFileSync as
|
|
10217
|
+
import { existsSync as existsSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "node:fs";
|
|
9998
10218
|
function resolveToken(opts) {
|
|
9999
10219
|
if (opts.token) return opts.token;
|
|
10000
10220
|
const f = opts.tokenFile;
|
|
10001
|
-
if (f &&
|
|
10221
|
+
if (f && existsSync2(f)) return readFileSync7(f, "utf8").trim();
|
|
10002
10222
|
const tok = randomBytes3(32).toString("base64url");
|
|
10003
|
-
if (f)
|
|
10223
|
+
if (f) writeFileSync2(f, tok, { mode: 384 });
|
|
10004
10224
|
return tok;
|
|
10005
10225
|
}
|
|
10006
10226
|
function tokenMatches(provided, expected) {
|
|
@@ -10621,17 +10841,17 @@ async function handleInbound(sock, agent, data, inflight) {
|
|
|
10621
10841
|
}
|
|
10622
10842
|
|
|
10623
10843
|
// src/bin.ts
|
|
10624
|
-
import { readFileSync as
|
|
10625
|
-
import { join as
|
|
10844
|
+
import { readFileSync as readFileSync9, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, rmSync, existsSync as existsSync3, realpathSync, readdirSync as readdirSync2, statSync as statSync4 } from "node:fs";
|
|
10845
|
+
import { join as join13, dirname as dirname7, isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
|
|
10626
10846
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
10627
|
-
import { homedir as
|
|
10847
|
+
import { homedir as homedir3, userInfo } from "node:os";
|
|
10628
10848
|
import { spawnSync } from "node:child_process";
|
|
10629
10849
|
|
|
10630
10850
|
// src/service/install.ts
|
|
10631
10851
|
import { dirname as dirname4 } from "node:path";
|
|
10632
10852
|
|
|
10633
10853
|
// src/service/config.ts
|
|
10634
|
-
import { basename as
|
|
10854
|
+
import { basename as basename3, extname, isAbsolute, resolve } from "node:path";
|
|
10635
10855
|
|
|
10636
10856
|
// src/service/resolve-node.ts
|
|
10637
10857
|
var REQUIRED_NODE_MAJOR = 24;
|
|
@@ -10709,7 +10929,7 @@ function deriveServiceConfig(input) {
|
|
|
10709
10929
|
`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
10930
|
);
|
|
10711
10931
|
}
|
|
10712
|
-
const stem = input.label ??
|
|
10932
|
+
const stem = input.label ?? basename3(input.credentialPath, extname(input.credentialPath));
|
|
10713
10933
|
const slug = slugify(stem);
|
|
10714
10934
|
const cred = isAbsolute(input.credentialPath) ? input.credentialPath : resolve(input.credentialPath);
|
|
10715
10935
|
const args2 = ["--credential", cred];
|
|
@@ -10831,10 +11051,10 @@ function uninstallService(input, deps) {
|
|
|
10831
11051
|
}
|
|
10832
11052
|
|
|
10833
11053
|
// src/service/install-prep.ts
|
|
10834
|
-
import { join as
|
|
11054
|
+
import { join as join11 } from "node:path";
|
|
10835
11055
|
|
|
10836
11056
|
// src/service/wire.ts
|
|
10837
|
-
import { join as
|
|
11057
|
+
import { join as join10, dirname as dirname5 } from "node:path";
|
|
10838
11058
|
import { fileURLToPath } from "node:url";
|
|
10839
11059
|
|
|
10840
11060
|
// src/service/hermes-config.ts
|
|
@@ -11010,7 +11230,7 @@ function checkBindingConflict(prev, next, allowRebind) {
|
|
|
11010
11230
|
}
|
|
11011
11231
|
function connectorsSrcDir(importMetaUrl, harness) {
|
|
11012
11232
|
const pkgRoot = dirname5(dirname5(fileURLToPath(importMetaUrl)));
|
|
11013
|
-
return
|
|
11233
|
+
return join10(pkgRoot, "connectors", harness);
|
|
11014
11234
|
}
|
|
11015
11235
|
function hermesConfigPath(home) {
|
|
11016
11236
|
return `${home}/.hermes/config.yaml`;
|
|
@@ -11268,10 +11488,10 @@ function assertReadableCredential(absPath, deps) {
|
|
|
11268
11488
|
return { deviceId: json2.device_id, identityId: json2.identity_id, issuedAt };
|
|
11269
11489
|
}
|
|
11270
11490
|
function durableDir(home, slug) {
|
|
11271
|
-
return
|
|
11491
|
+
return join11(home, `securecoms-agent-${slug}`);
|
|
11272
11492
|
}
|
|
11273
11493
|
function durableCredentialPath(home, slug) {
|
|
11274
|
-
return
|
|
11494
|
+
return join11(durableDir(home, slug), `${slug}.json`);
|
|
11275
11495
|
}
|
|
11276
11496
|
function takeCustody(args2, deps) {
|
|
11277
11497
|
const dir = durableDir(args2.home, args2.slug);
|
|
@@ -11318,7 +11538,7 @@ function readSiblingDefs(dir, prefix, deps) {
|
|
|
11318
11538
|
for (const n of names) {
|
|
11319
11539
|
if (!n.startsWith(prefix)) continue;
|
|
11320
11540
|
try {
|
|
11321
|
-
out.push(deps.readFile(
|
|
11541
|
+
out.push(deps.readFile(join11(dir, n)));
|
|
11322
11542
|
} catch {
|
|
11323
11543
|
}
|
|
11324
11544
|
}
|
|
@@ -11513,34 +11733,34 @@ function renderStatusTable(rows) {
|
|
|
11513
11733
|
|
|
11514
11734
|
// src/service/connection-file.ts
|
|
11515
11735
|
import {
|
|
11516
|
-
chmodSync,
|
|
11517
|
-
mkdirSync,
|
|
11518
|
-
readFileSync as
|
|
11519
|
-
renameSync,
|
|
11520
|
-
unlinkSync,
|
|
11521
|
-
writeFileSync as
|
|
11736
|
+
chmodSync as chmodSync2,
|
|
11737
|
+
mkdirSync as mkdirSync2,
|
|
11738
|
+
readFileSync as readFileSync8,
|
|
11739
|
+
renameSync as renameSync2,
|
|
11740
|
+
unlinkSync as unlinkSync2,
|
|
11741
|
+
writeFileSync as writeFileSync3
|
|
11522
11742
|
} 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) =>
|
|
11743
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
11744
|
+
import { join as join12 } from "node:path";
|
|
11745
|
+
var REAL_FS2 = {
|
|
11746
|
+
mkdir: (p, mode) => mkdirSync2(p, { recursive: true, mode }),
|
|
11747
|
+
chmod: (p, mode) => chmodSync2(p, mode),
|
|
11748
|
+
writeFile: (p, data, mode) => writeFileSync3(p, data, { mode }),
|
|
11749
|
+
rename: (a, b) => renameSync2(a, b),
|
|
11750
|
+
readFile: (p) => readFileSync8(p, "utf8"),
|
|
11751
|
+
unlink: (p) => unlinkSync2(p)
|
|
11532
11752
|
};
|
|
11533
11753
|
function securecomsDir(home) {
|
|
11534
|
-
return
|
|
11754
|
+
return join12(home, ".securecoms");
|
|
11535
11755
|
}
|
|
11536
11756
|
function connectionsDir(home) {
|
|
11537
|
-
return
|
|
11757
|
+
return join12(securecomsDir(home), "connections");
|
|
11538
11758
|
}
|
|
11539
11759
|
function connectionFilePath(home, identityId) {
|
|
11540
|
-
return
|
|
11760
|
+
return join12(connectionsDir(home), `${identityId}.json`);
|
|
11541
11761
|
}
|
|
11542
11762
|
function newInstanceId() {
|
|
11543
|
-
return
|
|
11763
|
+
return randomUUID2();
|
|
11544
11764
|
}
|
|
11545
11765
|
function buildConnectionFile(args2) {
|
|
11546
11766
|
return {
|
|
@@ -11554,18 +11774,18 @@ function buildConnectionFile(args2) {
|
|
|
11554
11774
|
written_at: args2.now
|
|
11555
11775
|
};
|
|
11556
11776
|
}
|
|
11557
|
-
function writeConnectionFile(home, doc, deps =
|
|
11777
|
+
function writeConnectionFile(home, doc, deps = REAL_FS2) {
|
|
11558
11778
|
deps.mkdir(securecomsDir(home), 448);
|
|
11559
11779
|
deps.chmod(securecomsDir(home), 448);
|
|
11560
11780
|
deps.mkdir(connectionsDir(home), 448);
|
|
11561
11781
|
deps.chmod(connectionsDir(home), 448);
|
|
11562
11782
|
const path = connectionFilePath(home, doc.identity_id);
|
|
11563
|
-
const tmp = `${path}.${process.pid}.${
|
|
11783
|
+
const tmp = `${path}.${process.pid}.${randomUUID2()}.tmp`;
|
|
11564
11784
|
deps.writeFile(tmp, JSON.stringify(doc, null, 2) + "\n", 384);
|
|
11565
11785
|
deps.chmod(tmp, 384);
|
|
11566
11786
|
deps.rename(tmp, path);
|
|
11567
11787
|
}
|
|
11568
|
-
function removeConnectionFileIfOwned(home, identityId, instanceId, deps =
|
|
11788
|
+
function removeConnectionFileIfOwned(home, identityId, instanceId, deps = REAL_FS2) {
|
|
11569
11789
|
const path = connectionFilePath(home, identityId);
|
|
11570
11790
|
try {
|
|
11571
11791
|
const cur = JSON.parse(deps.readFile(path));
|
|
@@ -11606,10 +11826,10 @@ function realServiceDeps() {
|
|
|
11606
11826
|
const r = spawnSync(cmd, args2, { encoding: "utf8" });
|
|
11607
11827
|
return { status: r.status ?? 1, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
|
|
11608
11828
|
},
|
|
11609
|
-
writeFile: (p, data) =>
|
|
11829
|
+
writeFile: (p, data) => writeFileSync4(p, data, { mode: 420 }),
|
|
11610
11830
|
removeFile: (p) => rmSync(p, { force: true }),
|
|
11611
|
-
mkdirp: (p) =>
|
|
11612
|
-
fileExists: (p) =>
|
|
11831
|
+
mkdirp: (p) => mkdirSync3(p, { recursive: true }),
|
|
11832
|
+
fileExists: (p) => existsSync3(p),
|
|
11613
11833
|
user: userInfo().username
|
|
11614
11834
|
};
|
|
11615
11835
|
}
|
|
@@ -11632,7 +11852,7 @@ function realResolveNodeDeps() {
|
|
|
11632
11852
|
};
|
|
11633
11853
|
return {
|
|
11634
11854
|
realpath: (p) => realpathSync(p),
|
|
11635
|
-
fileExists: (p) =>
|
|
11855
|
+
fileExists: (p) => existsSync3(p),
|
|
11636
11856
|
nodeMajorOf: majorOf,
|
|
11637
11857
|
arch: () => process.arch,
|
|
11638
11858
|
archOf: (p) => {
|
|
@@ -11647,17 +11867,17 @@ function realResolveDeps() {
|
|
|
11647
11867
|
const r = spawnSync(cmd, args2, { encoding: "utf8" });
|
|
11648
11868
|
return { status: r.status ?? 1, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
|
|
11649
11869
|
},
|
|
11650
|
-
fileExists: (p) =>
|
|
11870
|
+
fileExists: (p) => existsSync3(p)
|
|
11651
11871
|
};
|
|
11652
11872
|
}
|
|
11653
11873
|
function realPrepDeps() {
|
|
11654
11874
|
return {
|
|
11655
|
-
readFile: (p) =>
|
|
11656
|
-
writeFileMode: (p, data, mode) =>
|
|
11875
|
+
readFile: (p) => readFileSync9(p, "utf8"),
|
|
11876
|
+
writeFileMode: (p, data, mode) => writeFileSync4(p, data, { mode }),
|
|
11657
11877
|
mkdirp: (p, mode) => {
|
|
11658
|
-
|
|
11878
|
+
mkdirSync3(p, { recursive: true, mode: mode ?? 493 });
|
|
11659
11879
|
},
|
|
11660
|
-
fileExists: (p) =>
|
|
11880
|
+
fileExists: (p) => existsSync3(p),
|
|
11661
11881
|
realpath: (p) => realpathSync(p),
|
|
11662
11882
|
listDir: (p) => {
|
|
11663
11883
|
try {
|
|
@@ -11700,7 +11920,7 @@ function realGetHealth(port) {
|
|
|
11700
11920
|
}
|
|
11701
11921
|
function realReadFleet() {
|
|
11702
11922
|
return readFleet({
|
|
11703
|
-
home:
|
|
11923
|
+
home: homedir3(),
|
|
11704
11924
|
listDir: (p) => {
|
|
11705
11925
|
try {
|
|
11706
11926
|
return readdirSync2(p);
|
|
@@ -11708,7 +11928,7 @@ function realReadFleet() {
|
|
|
11708
11928
|
return [];
|
|
11709
11929
|
}
|
|
11710
11930
|
},
|
|
11711
|
-
readFile: (p) =>
|
|
11931
|
+
readFile: (p) => readFileSync9(p, "utf8")
|
|
11712
11932
|
});
|
|
11713
11933
|
}
|
|
11714
11934
|
function runStatus() {
|
|
@@ -11730,62 +11950,62 @@ function runStatus() {
|
|
|
11730
11950
|
}
|
|
11731
11951
|
function realCopyConnector(srcDir, dstDir, harness) {
|
|
11732
11952
|
const allowedExt = CONNECTOR_FILE_EXTENSIONS[harness];
|
|
11733
|
-
|
|
11953
|
+
mkdirSync3(dstDir, { recursive: true });
|
|
11734
11954
|
for (const name of readdirSync2(srcDir)) {
|
|
11735
|
-
const srcPath =
|
|
11736
|
-
if (
|
|
11955
|
+
const srcPath = join13(srcDir, name);
|
|
11956
|
+
if (statSync4(srcPath).isDirectory()) {
|
|
11737
11957
|
if (CONNECTOR_SKIP_DIRS.has(name)) continue;
|
|
11738
|
-
realCopyConnector(srcPath,
|
|
11958
|
+
realCopyConnector(srcPath, join13(dstDir, name), harness);
|
|
11739
11959
|
continue;
|
|
11740
11960
|
}
|
|
11741
11961
|
if (name.includes(".test.")) continue;
|
|
11742
11962
|
if (!allowedExt.some((ext) => name.endsWith(ext))) continue;
|
|
11743
|
-
|
|
11963
|
+
writeFileSync4(join13(dstDir, name), readFileSync9(srcPath));
|
|
11744
11964
|
}
|
|
11745
11965
|
}
|
|
11746
11966
|
function realWriteBinding(dstDir, identityId) {
|
|
11747
|
-
|
|
11748
|
-
|
|
11967
|
+
mkdirSync3(dstDir, { recursive: true });
|
|
11968
|
+
writeFileSync4(join13(dstDir, "securecoms.binding.json"), JSON.stringify({ identity_id: identityId }) + "\n", { mode: 384 });
|
|
11749
11969
|
}
|
|
11750
11970
|
function realReadBinding(dstDir) {
|
|
11751
11971
|
try {
|
|
11752
|
-
const parsed = JSON.parse(
|
|
11972
|
+
const parsed = JSON.parse(readFileSync9(join13(dstDir, "securecoms.binding.json"), "utf8"));
|
|
11753
11973
|
return parsed.identity_id ?? null;
|
|
11754
11974
|
} catch {
|
|
11755
11975
|
return null;
|
|
11756
11976
|
}
|
|
11757
11977
|
}
|
|
11758
11978
|
function realRemoveBinding(dstDir) {
|
|
11759
|
-
rmSync(
|
|
11979
|
+
rmSync(join13(dstDir, "securecoms.binding.json"), { force: true });
|
|
11760
11980
|
}
|
|
11761
11981
|
function realStatDir(p) {
|
|
11762
11982
|
try {
|
|
11763
|
-
return
|
|
11983
|
+
return statSync4(p).isDirectory();
|
|
11764
11984
|
} catch {
|
|
11765
11985
|
return false;
|
|
11766
11986
|
}
|
|
11767
11987
|
}
|
|
11768
11988
|
function realReadHermesConfig(p) {
|
|
11769
11989
|
try {
|
|
11770
|
-
return
|
|
11990
|
+
return readFileSync9(p, "utf8");
|
|
11771
11991
|
} catch {
|
|
11772
11992
|
return null;
|
|
11773
11993
|
}
|
|
11774
11994
|
}
|
|
11775
11995
|
function realBackupHermesConfig(p, originalText) {
|
|
11776
|
-
|
|
11996
|
+
writeFileSync4(hermesConfigBackupPath(p, Date.now(), process.pid), originalText, "utf8");
|
|
11777
11997
|
}
|
|
11778
11998
|
function realWriteHermesConfig(p, newText) {
|
|
11779
|
-
|
|
11999
|
+
writeFileSync4(p, newText, "utf8");
|
|
11780
12000
|
}
|
|
11781
12001
|
function realWireArgv() {
|
|
11782
12002
|
if (process.argv.includes("--harness")) return process.argv;
|
|
11783
|
-
const detected = detectHarness(
|
|
12003
|
+
const detected = detectHarness(homedir3(), realStatDir);
|
|
11784
12004
|
return detected ? [...process.argv, "--harness", detected] : process.argv;
|
|
11785
12005
|
}
|
|
11786
12006
|
function realWireDeps() {
|
|
11787
12007
|
return {
|
|
11788
|
-
home:
|
|
12008
|
+
home: homedir3(),
|
|
11789
12009
|
argv: realWireArgv(),
|
|
11790
12010
|
readFleet: realReadFleet,
|
|
11791
12011
|
copyConnector: realCopyConnector,
|
|
@@ -11837,7 +12057,7 @@ async function runServiceVerb(verb) {
|
|
|
11837
12057
|
explicitPort: portStr ? Number(portStr) : void 0,
|
|
11838
12058
|
harness: installHarness,
|
|
11839
12059
|
allowRebind: process.argv.includes("--rebind"),
|
|
11840
|
-
home:
|
|
12060
|
+
home: homedir3(),
|
|
11841
12061
|
connectorsSrcDir: connectorsSrcDir(import.meta.url, installHarness === "hermes" ? "hermes" : "openclaw"),
|
|
11842
12062
|
autoWireDeps: {
|
|
11843
12063
|
statDir: realStatDir,
|
|
@@ -11864,7 +12084,7 @@ async function runServiceVerb(verb) {
|
|
|
11864
12084
|
label,
|
|
11865
12085
|
execPath: installExecPath,
|
|
11866
12086
|
binScript,
|
|
11867
|
-
home:
|
|
12087
|
+
home: homedir3(),
|
|
11868
12088
|
uid: userInfo().uid
|
|
11869
12089
|
})
|
|
11870
12090
|
});
|
|
@@ -11878,7 +12098,7 @@ async function runServiceVerb(verb) {
|
|
|
11878
12098
|
label,
|
|
11879
12099
|
execPath: process.execPath,
|
|
11880
12100
|
binScript,
|
|
11881
|
-
home:
|
|
12101
|
+
home: homedir3(),
|
|
11882
12102
|
uid: userInfo().uid
|
|
11883
12103
|
};
|
|
11884
12104
|
const r = uninstallService(input, realServiceDeps());
|
|
@@ -11929,11 +12149,11 @@ async function main() {
|
|
|
11929
12149
|
`);
|
|
11930
12150
|
process.exit(1);
|
|
11931
12151
|
}
|
|
11932
|
-
const tokenFile = daemon.tokenFile ??
|
|
12152
|
+
const tokenFile = daemon.tokenFile ?? join13(dirname7(credentialPath), "bridge-token");
|
|
11933
12153
|
const token = resolveToken({ tokenFile, token: daemon.token });
|
|
11934
12154
|
const agent = new BridgeAgent(cfg);
|
|
11935
12155
|
await agent.start();
|
|
11936
|
-
const tls = daemon.tls ? { cert:
|
|
12156
|
+
const tls = daemon.tls ? { cert: readFileSync9(daemon.tlsCert, "utf8"), key: readFileSync9(daemon.tlsKey, "utf8") } : void 0;
|
|
11937
12157
|
const { server, closeAllSseStreams } = createHttpServer(agent, { token, allowOrigins: daemon.allowOrigins, tls, websocket: true });
|
|
11938
12158
|
const agentWs = attachAgentWebSocket(server, agent, { token, allowOrigins: daemon.allowOrigins });
|
|
11939
12159
|
let down = false;
|
|
@@ -11955,7 +12175,7 @@ async function main() {
|
|
|
11955
12175
|
const instanceId = newInstanceId();
|
|
11956
12176
|
const identityId = agent.health().identity_id;
|
|
11957
12177
|
const slug = slugFromCredentialPath(credentialPath);
|
|
11958
|
-
process.once("exit", () => removeConnectionFileIfOwned(
|
|
12178
|
+
process.once("exit", () => removeConnectionFileIfOwned(homedir3(), identityId, instanceId));
|
|
11959
12179
|
server.listen(daemon.port, daemon.bind, () => {
|
|
11960
12180
|
const scheme = tls ? "https" : "http";
|
|
11961
12181
|
const wsScheme = tls ? "wss" : "ws";
|
|
@@ -11966,7 +12186,7 @@ async function main() {
|
|
|
11966
12186
|
const addr = server.address();
|
|
11967
12187
|
const boundPort = typeof addr === "object" && addr ? addr.port : daemon.port;
|
|
11968
12188
|
try {
|
|
11969
|
-
writeConnectionFile(
|
|
12189
|
+
writeConnectionFile(homedir3(), buildConnectionFile({
|
|
11970
12190
|
identityId,
|
|
11971
12191
|
port: boundPort,
|
|
11972
12192
|
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,137 @@ 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
|
+
const search = skillsJson.search;
|
|
7382
|
+
if (search?.keyFile) paths.push(search.keyFile);
|
|
7383
|
+
return paths.map((path) => {
|
|
7384
|
+
let st;
|
|
7385
|
+
try {
|
|
7386
|
+
st = statFn(path);
|
|
7387
|
+
} catch {
|
|
7388
|
+
return { path, ok: false, why: "not found" };
|
|
7389
|
+
}
|
|
7390
|
+
if ((st.mode & 63) !== 0) {
|
|
7391
|
+
return { path, ok: false, why: "readable by other users on this machine \u2014 tighten its permissions to owner-only" };
|
|
7392
|
+
}
|
|
7393
|
+
if (st.size === 0) return { path, ok: false, why: "the file is empty" };
|
|
7394
|
+
return { path, ok: true, why: "" };
|
|
7395
|
+
});
|
|
7396
|
+
}
|
|
7397
|
+
function lineHash(line) {
|
|
7398
|
+
return createHash("sha256").update(line.trim(), "utf8").digest("hex");
|
|
7399
|
+
}
|
|
7400
|
+
var LOCAL_REPORT_FILE = "local-config.json";
|
|
7401
|
+
function readLocalReport(home, identityId) {
|
|
7402
|
+
const p = join6(runtimeConfigDir(home, identityId), LOCAL_REPORT_FILE);
|
|
7403
|
+
if (!existsSync(p)) return null;
|
|
7404
|
+
let doc;
|
|
7405
|
+
try {
|
|
7406
|
+
doc = JSON.parse(readFileSync5(p, "utf8"));
|
|
7407
|
+
} catch {
|
|
7408
|
+
return null;
|
|
7409
|
+
}
|
|
7410
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc)) return null;
|
|
7411
|
+
const hasSearchKey = doc.has_search_key === true;
|
|
7412
|
+
const raw = doc.persona;
|
|
7413
|
+
const persona = raw && typeof raw === "object" && !Array.isArray(raw) ? Object.fromEntries(
|
|
7414
|
+
Object.entries(raw).filter(([, v]) => typeof v === "string")
|
|
7415
|
+
) : {};
|
|
7416
|
+
return { persona, has_search_key: hasSearchKey };
|
|
7417
|
+
}
|
|
7418
|
+
function readManagedMemory(home, identityId) {
|
|
7419
|
+
const p = join6(runtimeConfigDir(home, identityId), MEMORY_FILE);
|
|
7420
|
+
if (!existsSync(p)) return null;
|
|
7421
|
+
try {
|
|
7422
|
+
const body = readFileSync5(p, "utf8").trim();
|
|
7423
|
+
return body || null;
|
|
7424
|
+
} catch {
|
|
7425
|
+
return null;
|
|
7426
|
+
}
|
|
7427
|
+
}
|
|
7428
|
+
function pruneManagedMemory(home, identityId, hashes, deps = REAL_FS) {
|
|
7429
|
+
const dir = runtimeConfigDir(home, identityId);
|
|
7430
|
+
const path = join6(dir, MEMORY_FILE);
|
|
7431
|
+
if (!existsSync(path)) return 0;
|
|
7432
|
+
const drop = new Set(hashes);
|
|
7433
|
+
const before = deps.readFile(path).split("\n");
|
|
7434
|
+
const kept = before.filter((l) => l.trim() === "" || !drop.has(lineHash(l)));
|
|
7435
|
+
const removed = before.filter((l) => l.trim() !== "" && drop.has(lineHash(l))).length;
|
|
7436
|
+
if (removed === 0) return 0;
|
|
7437
|
+
const body = kept.filter((l, i) => l.trim() !== "" || i < kept.length - 1).join("\n").trimEnd();
|
|
7438
|
+
atomicWrite(path, body ? `${body}
|
|
7439
|
+
` : "", deps);
|
|
7440
|
+
return removed;
|
|
7441
|
+
}
|
|
7442
|
+
|
|
7311
7443
|
// src/bridge-agent.ts
|
|
7444
|
+
import { homedir as homedir2 } from "node:os";
|
|
7312
7445
|
var VOICE_MODE_GUIDANCE = "Reply immediately and conversationally, one short sentence per message; never bundle multiple points into one message.";
|
|
7313
7446
|
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
7447
|
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 +7496,7 @@ function resolveMessageId(messageId) {
|
|
|
7363
7496
|
return messageId;
|
|
7364
7497
|
}
|
|
7365
7498
|
function fastReplyId(messageId) {
|
|
7366
|
-
const h =
|
|
7499
|
+
const h = createHash2("sha256").update(`fast:${messageId}`).digest();
|
|
7367
7500
|
h[6] = h[6] & 15 | 64;
|
|
7368
7501
|
h[8] = h[8] & 63 | 128;
|
|
7369
7502
|
const x = h.subarray(0, 16).toString("hex");
|
|
@@ -7493,6 +7626,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
7493
7626
|
onSupersedeUnopenable: (channelId, groupId) => this.healSupersedeUnopenable(channelId, groupId)
|
|
7494
7627
|
});
|
|
7495
7628
|
this.fetchImpl = deps.fetchImpl ?? fetch;
|
|
7629
|
+
this.home = deps.home ?? homedir2();
|
|
7496
7630
|
this.injectedFastLlm = !!deps.fastLlm;
|
|
7497
7631
|
this.fileVoiceConfig = cfg.voice;
|
|
7498
7632
|
this.fastLlm = deps.fastLlm ?? (cfg.voice?.fastPath.enabled ? new FastLlmClient(cfg.voice.fastPath, this.fetchImpl) : void 0);
|
|
@@ -7525,8 +7659,8 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
7525
7659
|
const j = await res.json();
|
|
7526
7660
|
return String(j.attachment_id);
|
|
7527
7661
|
};
|
|
7528
|
-
this.attachmentsDirImpl = deps.attachmentsDir ??
|
|
7529
|
-
this.vaultOwnerStoreImpl = deps.vaultOwnerStore ?? new FileOwnerAuthorityStore(
|
|
7662
|
+
this.attachmentsDirImpl = deps.attachmentsDir ?? join7(dirname(cfg.storePath), "attachments");
|
|
7663
|
+
this.vaultOwnerStoreImpl = deps.vaultOwnerStore ?? new FileOwnerAuthorityStore(join7(dirname(cfg.storePath), "vault-owner-pins.json"));
|
|
7530
7664
|
this.action = {
|
|
7531
7665
|
begin: (channelId) => this.actionBegin(channelId),
|
|
7532
7666
|
start: (opts) => this.actionStart(opts),
|
|
@@ -7542,6 +7676,8 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
7542
7676
|
// stored for peek-then-join in ingestOnce
|
|
7543
7677
|
custody;
|
|
7544
7678
|
fetchImpl;
|
|
7679
|
+
/** Round 5: home for the managed runtime-config directory (test seam). */
|
|
7680
|
+
home;
|
|
7545
7681
|
joinState = /* @__PURE__ */ new Map();
|
|
7546
7682
|
/**
|
|
7547
7683
|
* Phase 2: lazy channel-metadata cache (channel_id → { is_dm, name, at }) used
|
|
@@ -7883,6 +8019,84 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
7883
8019
|
if (this.voiceSettingsSource === "backend") return;
|
|
7884
8020
|
process.stderr.write(
|
|
7885
8021
|
`[bridge] voice settings unavailable (${e.message}); using ${this.voiceSettingsSource}
|
|
8022
|
+
`
|
|
8023
|
+
);
|
|
8024
|
+
}
|
|
8025
|
+
}
|
|
8026
|
+
/**
|
|
8027
|
+
* Fetch this agent's runtime config and apply it to the managed directory the
|
|
8028
|
+
* runtime reads (spec §8). Safe to call any time — at start(), or from the
|
|
8029
|
+
* receive pump on a `runtime_config_changed` ping — and never throws: a save
|
|
8030
|
+
* the owner already made must not be undone by a network blip here, and no
|
|
8031
|
+
* shape the backend can send may take the daemon down.
|
|
8032
|
+
*
|
|
8033
|
+
* Order matters. Prunes are applied BEFORE the memory is read back, so the
|
|
8034
|
+
* report the owner sees next is the file as it now stands.
|
|
8035
|
+
*
|
|
8036
|
+
* Every cycle rewrites, even at an unchanged version: directory permissions
|
|
8037
|
+
* are repaired only on a write, so skipping would leave a loosened directory
|
|
8038
|
+
* loose until the next save.
|
|
8039
|
+
*/
|
|
8040
|
+
async refetchRuntimeConfig() {
|
|
8041
|
+
const base = `${this.cfg.apiBase}/api/v1/agents/${encodeURIComponent(this.cfg.identityId)}/runtime-config`;
|
|
8042
|
+
try {
|
|
8043
|
+
const res = await this.fetchImpl(`${base}/files`, {
|
|
8044
|
+
headers: { authorization: `Bearer ${this.cfg.apiKey}` }
|
|
8045
|
+
});
|
|
8046
|
+
if (!res.ok) throw new Error(`runtime-config ${res.status}`);
|
|
8047
|
+
const payload = await res.json();
|
|
8048
|
+
if (!payload || !payload.version) {
|
|
8049
|
+
process.stderr.write(
|
|
8050
|
+
`[bridge] runtime config not applied (version ${JSON.stringify(payload?.version ?? null)} \u2014 no saved config)
|
|
8051
|
+
`
|
|
8052
|
+
);
|
|
8053
|
+
return;
|
|
8054
|
+
}
|
|
8055
|
+
const skillsJson = payload.skills_json ?? {};
|
|
8056
|
+
writeManagedFiles(this.home, this.cfg.identityId, {
|
|
8057
|
+
...payload.files ?? {},
|
|
8058
|
+
"skills.json": `${JSON.stringify(skillsJson, null, 2)}
|
|
8059
|
+
`
|
|
8060
|
+
});
|
|
8061
|
+
if (payload.memory_prunes?.length) {
|
|
8062
|
+
pruneManagedMemory(this.home, this.cfg.identityId, payload.memory_prunes);
|
|
8063
|
+
}
|
|
8064
|
+
const local = readLocalReport(this.home, this.cfg.identityId);
|
|
8065
|
+
const report = {
|
|
8066
|
+
applied_version: payload.version,
|
|
8067
|
+
paths: checkConfiguredPaths(skillsJson),
|
|
8068
|
+
// Read AFTER the prunes above, so the owner's next view of memory is
|
|
8069
|
+
// the file as it now stands, not the one they just pruned from.
|
|
8070
|
+
memory: readManagedMemory(this.home, this.cfg.identityId),
|
|
8071
|
+
// E13: pass through what the runtime reported. The daemon does not
|
|
8072
|
+
// interpret any of it.
|
|
8073
|
+
local_persona: local?.persona ?? null,
|
|
8074
|
+
// Only the runtime knows whether a search key file is configured — it
|
|
8075
|
+
// is an environment value on that process, invisible to the daemon and
|
|
8076
|
+
// to the backend. The backend needs it to generate a truthful tool
|
|
8077
|
+
// paragraph, so it rides along here.
|
|
8078
|
+
has_search_key: local?.has_search_key ?? false
|
|
8079
|
+
};
|
|
8080
|
+
const posted = await this.fetchImpl(`${base}/report`, {
|
|
8081
|
+
method: "POST",
|
|
8082
|
+
headers: {
|
|
8083
|
+
authorization: `Bearer ${this.cfg.apiKey}`,
|
|
8084
|
+
"content-type": "application/json"
|
|
8085
|
+
},
|
|
8086
|
+
body: JSON.stringify(report)
|
|
8087
|
+
});
|
|
8088
|
+
if (!posted.ok) {
|
|
8089
|
+
process.stderr.write(
|
|
8090
|
+
`[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
|
|
8091
|
+
`
|
|
8092
|
+
);
|
|
8093
|
+
return;
|
|
8094
|
+
}
|
|
8095
|
+
process.stderr.write(`[bridge] runtime config v${payload.version} applied
|
|
8096
|
+
`);
|
|
8097
|
+
} catch (e) {
|
|
8098
|
+
process.stderr.write(
|
|
8099
|
+
`[bridge] runtime config refetch failed: ${e?.message ?? e}
|
|
7886
8100
|
`
|
|
7887
8101
|
);
|
|
7888
8102
|
}
|
|
@@ -7914,6 +8128,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
7914
8128
|
}
|
|
7915
8129
|
await this.provider.publishKeyPackage();
|
|
7916
8130
|
await this.refetchVoiceSettings();
|
|
8131
|
+
await this.refetchRuntimeConfig();
|
|
7917
8132
|
this.startReceivePump();
|
|
7918
8133
|
if (this.selfHealSweepEnabled) {
|
|
7919
8134
|
this.bootSweepTimer = setTimeout(
|
|
@@ -9398,11 +9613,16 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
9398
9613
|
this.pingsReceived++;
|
|
9399
9614
|
if (ping?.reconnected) {
|
|
9400
9615
|
process.stderr.write("[bridge] delivery-ping WS (re)connected \u2014 push inbound active\n");
|
|
9616
|
+
await this.refetchRuntimeConfig();
|
|
9401
9617
|
}
|
|
9402
9618
|
if (ping?.voiceSettingsChanged) {
|
|
9403
9619
|
await this.refetchVoiceSettings();
|
|
9404
9620
|
continue;
|
|
9405
9621
|
}
|
|
9622
|
+
if (ping?.runtimeConfigChanged) {
|
|
9623
|
+
await this.refetchRuntimeConfig();
|
|
9624
|
+
continue;
|
|
9625
|
+
}
|
|
9406
9626
|
await this.ingestOnce();
|
|
9407
9627
|
}
|
|
9408
9628
|
} catch {
|
|
@@ -9840,13 +10060,13 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
9840
10060
|
};
|
|
9841
10061
|
|
|
9842
10062
|
// src/config.ts
|
|
9843
|
-
import { readFileSync as
|
|
9844
|
-
import { dirname as dirname2, join as
|
|
10063
|
+
import { readFileSync as readFileSync6, statSync as statSync3 } from "node:fs";
|
|
10064
|
+
import { dirname as dirname2, join as join8 } from "node:path";
|
|
9845
10065
|
var REQUIRED = ["api_key", "identity_id", "device_id", "signing_private_key", "enclave_id", "api_base"];
|
|
9846
10066
|
function loadConfig(opts) {
|
|
9847
|
-
const raw =
|
|
10067
|
+
const raw = readFileSync6(opts.credentialPath, "utf8");
|
|
9848
10068
|
try {
|
|
9849
|
-
const mode =
|
|
10069
|
+
const mode = statSync3(opts.credentialPath).mode & 511;
|
|
9850
10070
|
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
10071
|
`);
|
|
9852
10072
|
} catch {
|
|
@@ -9858,7 +10078,7 @@ function loadConfig(opts) {
|
|
|
9858
10078
|
throw new Error("credential file is not valid JSON");
|
|
9859
10079
|
}
|
|
9860
10080
|
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 ??
|
|
10081
|
+
const storePath = opts.storePath ?? join8(dirname2(opts.credentialPath), "bridge-state.db");
|
|
9862
10082
|
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
10083
|
const rateLimitCapacity = typeof json2.rate_limit_capacity === "number" ? json2.rate_limit_capacity : 20;
|
|
9864
10084
|
const rateLimitRefillPerMinute = typeof json2.rate_limit_refill_per_minute === "number" ? json2.rate_limit_refill_per_minute : 20;
|
|
@@ -9954,13 +10174,13 @@ import { z as z3 } from "zod";
|
|
|
9954
10174
|
|
|
9955
10175
|
// src/auth.ts
|
|
9956
10176
|
import { randomBytes as randomBytes3, timingSafeEqual } from "node:crypto";
|
|
9957
|
-
import { existsSync, readFileSync as
|
|
10177
|
+
import { existsSync as existsSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "node:fs";
|
|
9958
10178
|
function resolveToken(opts) {
|
|
9959
10179
|
if (opts.token) return opts.token;
|
|
9960
10180
|
const f = opts.tokenFile;
|
|
9961
|
-
if (f &&
|
|
10181
|
+
if (f && existsSync2(f)) return readFileSync7(f, "utf8").trim();
|
|
9962
10182
|
const tok = randomBytes3(32).toString("base64url");
|
|
9963
|
-
if (f)
|
|
10183
|
+
if (f) writeFileSync2(f, tok, { mode: 384 });
|
|
9964
10184
|
return tok;
|
|
9965
10185
|
}
|
|
9966
10186
|
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;
|