@securecoms/agent-bridge 0.31.0 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -0
- package/dist/bin.js +780 -158
- package/dist/bridge-agent.d.ts +112 -1
- package/dist/index.js +763 -141
- package/dist/service/runtime-config-dir.d.ts +8 -2
- package/dist/version.d.ts +1 -1
- package/dist/voice/persona.d.ts +4 -0
- package/dist/voice-channel-binding.d.ts +201 -0
- package/dist/voice-channel-frame.d.ts +22 -0
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -7765,6 +7765,8 @@ var schema = z2.object({
|
|
|
7765
7765
|
model: z2.string().min(1),
|
|
7766
7766
|
api_key: z2.string().min(1).optional(),
|
|
7767
7767
|
api_key_env: z2.string().min(1).optional(),
|
|
7768
|
+
// E.2: only the exact string "direct" opts in; any other value is ignored (today's proxy behavior), never an error.
|
|
7769
|
+
route: z2.unknown().optional(),
|
|
7768
7770
|
timeout_ms: z2.number().int().min(500).max(1e4).default(2500),
|
|
7769
7771
|
max_tokens: z2.number().int().min(16).max(400).default(80)
|
|
7770
7772
|
})
|
|
@@ -7789,7 +7791,8 @@ function parseVoiceConfig(raw, env) {
|
|
|
7789
7791
|
model: v.fast_path.model,
|
|
7790
7792
|
apiKey,
|
|
7791
7793
|
timeoutMs: v.fast_path.timeout_ms,
|
|
7792
|
-
maxTokens: v.fast_path.max_tokens
|
|
7794
|
+
maxTokens: v.fast_path.max_tokens,
|
|
7795
|
+
...v.fast_path.route === "direct" ? { route: "direct" } : {}
|
|
7793
7796
|
}
|
|
7794
7797
|
};
|
|
7795
7798
|
}
|
|
@@ -15430,6 +15433,18 @@ async function sendWithSelfHeal(session, channelId, build, publish) {
|
|
|
15430
15433
|
// ../connect-sdk-ts/dist/groupkey/stub.js
|
|
15431
15434
|
import { sha256 as sha2563 } from "@noble/hashes/sha2";
|
|
15432
15435
|
|
|
15436
|
+
// ../connect-sdk-ts/dist/receive/inbox.js
|
|
15437
|
+
function parseOpenedAt(v) {
|
|
15438
|
+
if (typeof v !== "object" || v === null)
|
|
15439
|
+
return void 0;
|
|
15440
|
+
const o = v;
|
|
15441
|
+
if (typeof o.groupId !== "string" || o.groupId.length === 0)
|
|
15442
|
+
return void 0;
|
|
15443
|
+
if (typeof o.epoch !== "number" || !Number.isSafeInteger(o.epoch) || o.epoch < 0)
|
|
15444
|
+
return void 0;
|
|
15445
|
+
return { groupId: o.groupId, epoch: o.epoch };
|
|
15446
|
+
}
|
|
15447
|
+
|
|
15433
15448
|
// ../connect-sdk-ts/dist/spans/names.js
|
|
15434
15449
|
var SPAN = {
|
|
15435
15450
|
MESSAGE_SENT: "agentvault.connect.message.sent",
|
|
@@ -18158,7 +18173,9 @@ function decodeCustodyEntry(bytes) {
|
|
|
18158
18173
|
if (typeof plaintextHex !== "string" || typeof rest.queueId !== "string" || typeof rest.channelId !== "string" || typeof rest.custodiedAt !== "number") {
|
|
18159
18174
|
throw new Error("malformed custody entry");
|
|
18160
18175
|
}
|
|
18161
|
-
|
|
18176
|
+
const { openedAt, ...others } = rest;
|
|
18177
|
+
const position = parseOpenedAt(openedAt);
|
|
18178
|
+
return { ...others, ...position ? { openedAt: position } : {}, plaintext: hexToBytes22(plaintextHex) };
|
|
18162
18179
|
}
|
|
18163
18180
|
function custodyAad(identityId, queueId) {
|
|
18164
18181
|
return new TextEncoder().encode(`securecoms-custody-v1\0${identityId}\0${queueId}`);
|
|
@@ -22811,7 +22828,7 @@ var MlsGroupKeyProvider = class _MlsGroupKeyProvider {
|
|
|
22811
22828
|
* read #750 made in here is gone (ruling §8.2).
|
|
22812
22829
|
*/
|
|
22813
22830
|
async trySupersede(channelId, row, callerHeldGroupId, owner, mode, pullRows) {
|
|
22814
|
-
const
|
|
22831
|
+
const refuse3 = (msg) => {
|
|
22815
22832
|
this.recordDecryptError(row, new Error(msg));
|
|
22816
22833
|
const now = Date.now();
|
|
22817
22834
|
for (const [k, v] of this.supersedeRefusals) {
|
|
@@ -22844,34 +22861,34 @@ var MlsGroupKeyProvider = class _MlsGroupKeyProvider {
|
|
|
22844
22861
|
att = await this.o.backend.fetchResetAttestation(row.group_id);
|
|
22845
22862
|
}
|
|
22846
22863
|
if (!att) {
|
|
22847
|
-
return
|
|
22864
|
+
return refuse3(`supersede refused: no attestation for group ${row.group_id}`);
|
|
22848
22865
|
}
|
|
22849
22866
|
if (att.signerIdentityId !== owner.identityId) {
|
|
22850
|
-
return
|
|
22867
|
+
return refuse3("supersede refused: signer is not the pinned owner");
|
|
22851
22868
|
}
|
|
22852
22869
|
let content;
|
|
22853
22870
|
try {
|
|
22854
22871
|
content = JSON.parse(att.signedContent);
|
|
22855
22872
|
} catch {
|
|
22856
|
-
return
|
|
22873
|
+
return refuse3("supersede refused: unparseable signed_content");
|
|
22857
22874
|
}
|
|
22858
22875
|
if (content.channel_id !== row.connect_channel_id || content.new_group_id !== row.group_id) {
|
|
22859
|
-
return
|
|
22876
|
+
return refuse3("supersede refused: attestation channel/group mismatch");
|
|
22860
22877
|
}
|
|
22861
22878
|
if (content.purpose !== RESET_SUPERSEDE_PURPOSE) {
|
|
22862
|
-
return
|
|
22879
|
+
return refuse3("supersede refused: attestation purpose is not connect-reset-supersede");
|
|
22863
22880
|
}
|
|
22864
22881
|
const targetCreatedMs = signedCreatedAtMs(content);
|
|
22865
22882
|
if (targetCreatedMs === null) {
|
|
22866
|
-
return
|
|
22883
|
+
return refuse3("supersede refused: unparseable attestation created_at");
|
|
22867
22884
|
}
|
|
22868
22885
|
const targetPrev = signedPrevLink(content, row.group_id);
|
|
22869
22886
|
if (targetPrev === null) {
|
|
22870
|
-
return
|
|
22887
|
+
return refuse3("supersede refused: unparseable attestation prev_group_id");
|
|
22871
22888
|
}
|
|
22872
22889
|
const ok = await verifyApprovalArtifact({ content, signature: att.signatureHex, signerPublicKey: owner.publicKeyHex, createdAt: "", version: 1 }, owner.publicKeyHex);
|
|
22873
22890
|
if (!ok) {
|
|
22874
|
-
return
|
|
22891
|
+
return refuse3("supersede refused: signature does not verify against pinned owner key");
|
|
22875
22892
|
}
|
|
22876
22893
|
const targetLink = targetPrev.chained ? { createdAtMs: targetCreatedMs, chained: true, prevGroupId: targetPrev.prev } : { createdAtMs: targetCreatedMs, chained: false, prevGroupId: null };
|
|
22877
22894
|
if (callerHeldGroupId !== null && callerHeldGroupId !== row.group_id) {
|
|
@@ -22881,7 +22898,7 @@ var MlsGroupKeyProvider = class _MlsGroupKeyProvider {
|
|
|
22881
22898
|
if (v.kind === "refuse") {
|
|
22882
22899
|
if (v.notNewer)
|
|
22883
22900
|
this.supersedeNotNewer.add(`${channelId}:${row.group_id}`);
|
|
22884
|
-
return
|
|
22901
|
+
return refuse3(v.reason);
|
|
22885
22902
|
}
|
|
22886
22903
|
}
|
|
22887
22904
|
const bundle = await this.o.store.loadPendingBundle(this.o.deviceId);
|
|
@@ -22889,7 +22906,7 @@ var MlsGroupKeyProvider = class _MlsGroupKeyProvider {
|
|
|
22889
22906
|
if (!bundle && !supersedeSlot) {
|
|
22890
22907
|
if (mode === "adopt")
|
|
22891
22908
|
return { kind: "no_bundle" };
|
|
22892
|
-
return
|
|
22909
|
+
return refuse3("supersede refused: no pending KeyPackage bundle to open the new Welcome");
|
|
22893
22910
|
}
|
|
22894
22911
|
return await this.locks.run(channelId, async () => {
|
|
22895
22912
|
let inLockHeld;
|
|
@@ -22936,7 +22953,7 @@ var MlsGroupKeyProvider = class _MlsGroupKeyProvider {
|
|
|
22936
22953
|
return { kind: "unopenable", requested };
|
|
22937
22954
|
}
|
|
22938
22955
|
if (new TextDecoder().decode(mgr.groupId) !== row.group_id) {
|
|
22939
|
-
return
|
|
22956
|
+
return refuse3("supersede refused: post-join group id != attested new_group_id");
|
|
22940
22957
|
}
|
|
22941
22958
|
let dead = null;
|
|
22942
22959
|
try {
|
|
@@ -24061,15 +24078,20 @@ var MlsGroupKeyProvider = class _MlsGroupKeyProvider {
|
|
|
24061
24078
|
done();
|
|
24062
24079
|
};
|
|
24063
24080
|
};
|
|
24081
|
+
const fromManager = async (mgr) => {
|
|
24082
|
+
const openedAt = { groupId: new TextDecoder().decode(mgr.groupId), epoch: Number(mgr.epoch) };
|
|
24083
|
+
const key = await mgr.exportSecret(LABEL, buildExporterContext(envelope.enclave_id, envelope.channel_id, envelope.mls_group_epoch), 32);
|
|
24084
|
+
return { key, openedAt };
|
|
24085
|
+
};
|
|
24064
24086
|
const derive = keyFrom ? () => {
|
|
24065
24087
|
takeInFlight();
|
|
24066
|
-
return keyFrom
|
|
24067
|
-
} : () => {
|
|
24088
|
+
return fromManager(keyFrom);
|
|
24089
|
+
} : async () => {
|
|
24068
24090
|
takeInFlight();
|
|
24069
|
-
return this.
|
|
24091
|
+
return fromManager(await this.manager(envelope.channel_id));
|
|
24070
24092
|
};
|
|
24071
24093
|
try {
|
|
24072
|
-
const key = opts.lockHeld ? await derive() : await this.locks.run(envelope.channel_id, derive);
|
|
24094
|
+
const { key, openedAt } = opts.lockHeld ? await derive() : await this.locks.run(envelope.channel_id, derive);
|
|
24073
24095
|
const aad = await buildAssociatedData({
|
|
24074
24096
|
message_id: envelope.message_id,
|
|
24075
24097
|
channel_id: envelope.channel_id,
|
|
@@ -24093,7 +24115,8 @@ var MlsGroupKeyProvider = class _MlsGroupKeyProvider {
|
|
|
24093
24115
|
voiceMode: row.voice_mode ?? void 0,
|
|
24094
24116
|
voiceDefaultResponder: row.voice_default_responder ?? void 0,
|
|
24095
24117
|
...typeof row.identity_signature === "string" && row.identity_signature ? { identitySignature: row.identity_signature } : {},
|
|
24096
|
-
payloadHex: row.payload
|
|
24118
|
+
payloadHex: row.payload,
|
|
24119
|
+
openedAt
|
|
24097
24120
|
};
|
|
24098
24121
|
const custodiedAt = this.nowMs();
|
|
24099
24122
|
this.custody.set(row.queue_id, entry);
|
|
@@ -26827,6 +26850,7 @@ function openSchema(db) {
|
|
|
26827
26850
|
ensureToolGrantTables(db);
|
|
26828
26851
|
ensureToolGrantAppliedTable(db);
|
|
26829
26852
|
ensureRoomSearchCapTable(db);
|
|
26853
|
+
ensureVoiceChannelParentTable(db);
|
|
26830
26854
|
db.exec(`PRAGMA user_version = ${SCHEMA_VERSION}`);
|
|
26831
26855
|
}
|
|
26832
26856
|
function ensureMlsCustody(db) {
|
|
@@ -27012,6 +27036,16 @@ function ensureToolGrantAppliedTable(db) {
|
|
|
27012
27036
|
);
|
|
27013
27037
|
`);
|
|
27014
27038
|
}
|
|
27039
|
+
function ensureVoiceChannelParentTable(db) {
|
|
27040
|
+
db.exec(`
|
|
27041
|
+
CREATE TABLE IF NOT EXISTS voice_channel_parent (
|
|
27042
|
+
identity_id TEXT NOT NULL,
|
|
27043
|
+
voice_channel_id TEXT NOT NULL,
|
|
27044
|
+
parent_channel_id TEXT NOT NULL,
|
|
27045
|
+
PRIMARY KEY (identity_id, voice_channel_id)
|
|
27046
|
+
);
|
|
27047
|
+
`);
|
|
27048
|
+
}
|
|
27015
27049
|
function ensureRoomSearchCapTable(db) {
|
|
27016
27050
|
db.exec(`
|
|
27017
27051
|
CREATE TABLE IF NOT EXISTS room_search_cap (
|
|
@@ -27703,6 +27737,21 @@ var SqliteMlsStateStore = class _SqliteMlsStateStore {
|
|
|
27703
27737
|
VALUES (?,?,?,?)
|
|
27704
27738
|
ON CONFLICT(identity_id, channel_id) DO UPDATE SET is_dm=excluded.is_dm, updated_at=excluded.updated_at`).run(this.id, channelId, isDm ? 1 : 0, Date.now());
|
|
27705
27739
|
}
|
|
27740
|
+
/** ADR-0024 Task 20 (final review I-1): records V -> P once; a later, different parent is ignored (immutable link). */
|
|
27741
|
+
recordVoiceChannelParent(voiceChannelId, parentChannelId) {
|
|
27742
|
+
this.db.prepare(`INSERT INTO voice_channel_parent (identity_id, voice_channel_id, parent_channel_id) VALUES (?,?,?)
|
|
27743
|
+
ON CONFLICT(identity_id, voice_channel_id) DO NOTHING`).run(this.id, voiceChannelId, parentChannelId);
|
|
27744
|
+
}
|
|
27745
|
+
/** The recorded parent of a voice channel, if any. */
|
|
27746
|
+
voiceChannelParent(voiceChannelId) {
|
|
27747
|
+
const row = this.db.prepare("SELECT parent_channel_id FROM voice_channel_parent WHERE identity_id=? AND voice_channel_id=?").get(this.id, voiceChannelId);
|
|
27748
|
+
return row?.parent_channel_id;
|
|
27749
|
+
}
|
|
27750
|
+
/** Every recorded voice channel of a parent conversation. */
|
|
27751
|
+
voiceChannelsOf(parentChannelId) {
|
|
27752
|
+
const rows = this.db.prepare("SELECT voice_channel_id FROM voice_channel_parent WHERE identity_id=? AND parent_channel_id=?").all(this.id, parentChannelId);
|
|
27753
|
+
return rows.map((r) => r.voice_channel_id);
|
|
27754
|
+
}
|
|
27706
27755
|
getChannelMeta(channelId) {
|
|
27707
27756
|
const row = this.db.prepare("SELECT enclave_id, is_dm FROM channel_meta WHERE identity_id=? AND channel_id=?").get(this.id, channelId);
|
|
27708
27757
|
if (!row)
|
|
@@ -32407,7 +32456,7 @@ var EmbedderClient = class {
|
|
|
32407
32456
|
resolve4(ok);
|
|
32408
32457
|
}
|
|
32409
32458
|
}
|
|
32410
|
-
const
|
|
32459
|
+
const refuse3 = (reason) => {
|
|
32411
32460
|
s.destroy();
|
|
32412
32461
|
if (!settled) {
|
|
32413
32462
|
this.fail(reason);
|
|
@@ -32426,22 +32475,22 @@ var EmbedderClient = class {
|
|
|
32426
32475
|
return;
|
|
32427
32476
|
}
|
|
32428
32477
|
const f2 = this.chan.open(line);
|
|
32429
|
-
if (!f2) return
|
|
32478
|
+
if (!f2) return refuse3("embedder_unavailable");
|
|
32430
32479
|
this.onFrame(f2);
|
|
32431
32480
|
return;
|
|
32432
32481
|
}
|
|
32433
32482
|
const f = JSON.parse(line);
|
|
32434
|
-
if (!f || typeof f !== "object") return
|
|
32483
|
+
if (!f || typeof f !== "object") return refuse3("embedder_unavailable");
|
|
32435
32484
|
if (!ns) {
|
|
32436
|
-
if (f.t !== "challenge" || f.protocol !== EMBEDDER_PROTOCOL || typeof f.ns !== "string" || !/^[0-9a-f]{32}$/.test(f.ns)) return
|
|
32485
|
+
if (f.t !== "challenge" || f.protocol !== EMBEDDER_PROTOCOL || typeof f.ns !== "string" || !/^[0-9a-f]{32}$/.test(f.ns)) return refuse3(f.t === "challenge" ? "embedder_mismatch" : "embedder_unavailable");
|
|
32437
32486
|
ns = f.ns;
|
|
32438
32487
|
s.write(JSON.stringify({ t: "hello", nc, mac_c: macClient(token, ns, nc) }) + "\n");
|
|
32439
32488
|
return;
|
|
32440
32489
|
}
|
|
32441
|
-
if (f.t !== "hello" || typeof f.mac_s !== "string" || !macEqual(f.mac_s, macServer(token, ns, nc))) return
|
|
32490
|
+
if (f.t !== "hello" || typeof f.mac_s !== "string" || !macEqual(f.mac_s, macServer(token, ns, nc))) return refuse3("embedder_unavailable");
|
|
32442
32491
|
const model = this.m.files.find((x) => x.name === "model.onnx");
|
|
32443
32492
|
const ok = f.protocol === EMBEDDER_PROTOCOL && f.model_id === this.m.modelId && f.model_sha256 === model.sha256 && f.wasm_sha256 === this.m.wasmSha256 && f.dims === this.m.dims;
|
|
32444
|
-
if (!ok) return
|
|
32493
|
+
if (!ok) return refuse3("embedder_mismatch");
|
|
32445
32494
|
if (this.closed) {
|
|
32446
32495
|
s.destroy();
|
|
32447
32496
|
return done(true);
|
|
@@ -32459,9 +32508,9 @@ var EmbedderClient = class {
|
|
|
32459
32508
|
this.settleReady(true);
|
|
32460
32509
|
done(true);
|
|
32461
32510
|
} catch {
|
|
32462
|
-
|
|
32511
|
+
refuse3("embedder_unavailable");
|
|
32463
32512
|
}
|
|
32464
|
-
}, () =>
|
|
32513
|
+
}, () => refuse3("embedder_unavailable")));
|
|
32465
32514
|
});
|
|
32466
32515
|
}
|
|
32467
32516
|
onFrame(f) {
|
|
@@ -34662,6 +34711,7 @@ function lineHash(line) {
|
|
|
34662
34711
|
return createHash5("sha256").update(line.trim(), "utf8").digest("hex");
|
|
34663
34712
|
}
|
|
34664
34713
|
var LOCAL_REPORT_FILE = "local-config.json";
|
|
34714
|
+
var LOCAL_PERSONA_FILE_NAMES = ["IDENTITY.md", "SOUL.md", "AGENTS.md"];
|
|
34665
34715
|
var SECRET_PATH_COUNT_MAX = 1e4;
|
|
34666
34716
|
var isCount = (v) => typeof v === "number" && Number.isSafeInteger(v) && v >= 0 && v <= SECRET_PATH_COUNT_MAX;
|
|
34667
34717
|
function parseSecretPathsReport(raw) {
|
|
@@ -34743,18 +34793,23 @@ function readLocalReport(home, identityId) {
|
|
|
34743
34793
|
const hasSearchKey = doc.has_search_key === true;
|
|
34744
34794
|
const hasLlmKey = doc.has_llm_key === true;
|
|
34745
34795
|
const raw = doc.persona;
|
|
34746
|
-
const persona = raw && typeof raw === "object" && !Array.isArray(raw) ?
|
|
34747
|
-
|
|
34748
|
-
)
|
|
34796
|
+
const persona = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
34797
|
+
const personaFiles = [];
|
|
34798
|
+
for (const name of LOCAL_PERSONA_FILE_NAMES) {
|
|
34799
|
+
const text = persona[name];
|
|
34800
|
+
if (typeof text === "string" && text.trim() !== "") {
|
|
34801
|
+
personaFiles.push({ name, bytes: Buffer.byteLength(text, "utf8") });
|
|
34802
|
+
}
|
|
34803
|
+
}
|
|
34749
34804
|
const capRaw = doc.owner_dm_search_cap_effective;
|
|
34750
34805
|
const ownerDmCap = typeof capRaw === "number" && Number.isInteger(capRaw) && capRaw >= 0 && capRaw <= OWNER_DM_SEARCH_DAILY_CAP_MAX ? capRaw : void 0;
|
|
34751
34806
|
return {
|
|
34752
|
-
|
|
34807
|
+
local_persona_files: personaFiles,
|
|
34753
34808
|
has_search_key: hasSearchKey,
|
|
34754
34809
|
has_llm_key: hasLlmKey,
|
|
34755
34810
|
has_extract_provider: doc.has_extract_provider === true,
|
|
34756
34811
|
...ownerDmCap !== void 0 ? { owner_dm_search_cap_effective: ownerDmCap } : {},
|
|
34757
|
-
legacy_memory: localLegacySources(doc).map(({ path: _path, ...source }) => source),
|
|
34812
|
+
legacy_memory: localLegacySources(doc).map(({ path: _path, content: _content, ...source }) => source),
|
|
34758
34813
|
secret_paths: parseSecretPathsReport(doc.secret_paths)
|
|
34759
34814
|
};
|
|
34760
34815
|
}
|
|
@@ -34839,16 +34894,6 @@ async function applyLegacyMemoryFactDecisions(home, identityId, rawDecisions, re
|
|
|
34839
34894
|
}
|
|
34840
34895
|
return { sourcesDeleted, factsAssigned };
|
|
34841
34896
|
}
|
|
34842
|
-
function readManagedMemory(home, identityId) {
|
|
34843
|
-
const p = join18(runtimeConfigDir(home, identityId), MEMORY_FILE);
|
|
34844
|
-
if (!existsSync4(p)) return null;
|
|
34845
|
-
try {
|
|
34846
|
-
const body = readFileSync10(p, "utf8").trim();
|
|
34847
|
-
return body || null;
|
|
34848
|
-
} catch {
|
|
34849
|
-
return null;
|
|
34850
|
-
}
|
|
34851
|
-
}
|
|
34852
34897
|
function pruneManagedMemory(home, identityId, hashes, deps = REAL_FS) {
|
|
34853
34898
|
const dir = runtimeConfigDir(home, identityId);
|
|
34854
34899
|
const path = join18(dir, MEMORY_FILE);
|
|
@@ -35460,6 +35505,213 @@ function roomIsCrossOrg(rows) {
|
|
|
35460
35505
|
return unknown ? null : false;
|
|
35461
35506
|
}
|
|
35462
35507
|
|
|
35508
|
+
// src/voice-channel-binding.ts
|
|
35509
|
+
var BOUND = Object.freeze({ bound: true });
|
|
35510
|
+
var refuse = (reason) => ({ bound: false, reason });
|
|
35511
|
+
function evaluateVoiceBinding(v, p, isVRelay) {
|
|
35512
|
+
if (v.ownLeafIndex === null || v.leaves.length === 0) return refuse("v_not_held");
|
|
35513
|
+
if (p.ownLeafIndex === null || p.leaves.length === 0) return refuse("p_not_held");
|
|
35514
|
+
if (v.leaves.some((l) => !l.basic || l.identity === null)) return refuse("non_basic_leaf");
|
|
35515
|
+
const pIds = /* @__PURE__ */ new Set();
|
|
35516
|
+
for (const l of p.leaves) if (l.basic && l.identity !== null) pIds.add(l.identity);
|
|
35517
|
+
const nonParent = /* @__PURE__ */ new Set();
|
|
35518
|
+
for (const l of v.leaves) if (!pIds.has(l.identity)) nonParent.add(l.identity);
|
|
35519
|
+
if (nonParent.size > 1) return refuse("two_non_parent");
|
|
35520
|
+
if (isVRelay === void 0) return nonParent.size === 0 ? BOUND : refuse("relay_unknown");
|
|
35521
|
+
const relays = vRelayIdentities(v, isVRelay);
|
|
35522
|
+
if (relays.size > 1) return refuse("two_relays");
|
|
35523
|
+
const [relay] = relays;
|
|
35524
|
+
for (const id of nonParent) if (id !== relay) return refuse("foreign_leaf");
|
|
35525
|
+
return BOUND;
|
|
35526
|
+
}
|
|
35527
|
+
function vRelayIdentities(v, isVRelay) {
|
|
35528
|
+
const relays = /* @__PURE__ */ new Set();
|
|
35529
|
+
for (const l of v.leaves) if (l.basic && l.identity !== null && isVRelay(l.identity)) relays.add(l.identity);
|
|
35530
|
+
return relays;
|
|
35531
|
+
}
|
|
35532
|
+
function voiceRelayIdentity(v, isVRelay) {
|
|
35533
|
+
if (isVRelay === void 0) return void 0;
|
|
35534
|
+
const relays = vRelayIdentities(v, isVRelay);
|
|
35535
|
+
return relays.size === 1 ? [...relays][0] : void 0;
|
|
35536
|
+
}
|
|
35537
|
+
function parseVoiceResolution(channelId, status, body) {
|
|
35538
|
+
if (status === 404) return { kind: "absent" };
|
|
35539
|
+
if (status !== 200) return { kind: "error" };
|
|
35540
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) return { kind: "error" };
|
|
35541
|
+
const b = body;
|
|
35542
|
+
if (b.channel_id !== channelId || typeof b.channel_kind !== "string") return { kind: "error" };
|
|
35543
|
+
if (b.channel_kind !== "voice") return { kind: "not_voice" };
|
|
35544
|
+
const parent3 = b.parent_channel_id;
|
|
35545
|
+
if (typeof parent3 !== "string" || parent3.length === 0 || parent3 === channelId) return { kind: "error" };
|
|
35546
|
+
return { kind: "voice", parentChannelId: parent3 };
|
|
35547
|
+
}
|
|
35548
|
+
var VOICE_RESOLVE_RETRY_BASE_MS = 3e4;
|
|
35549
|
+
var VOICE_RESOLVE_RETRY_MAX_MS = 10 * 6e4;
|
|
35550
|
+
var VOICE_RESOLVE_ABSENT_HELD_MAX_MS = 6e4;
|
|
35551
|
+
var VOICE_RECEIPT_CAP = 1e4;
|
|
35552
|
+
var VOICE_RECEIPT_MAX_AGE_MS = 15 * 6e4;
|
|
35553
|
+
function voiceParentStamp(p) {
|
|
35554
|
+
return JSON.stringify([p.groupId, p.epoch]);
|
|
35555
|
+
}
|
|
35556
|
+
var VoiceChannelRegistry = class {
|
|
35557
|
+
parents = /* @__PURE__ */ new Map();
|
|
35558
|
+
notVoice = /* @__PURE__ */ new Set();
|
|
35559
|
+
failures = /* @__PURE__ */ new Map();
|
|
35560
|
+
bindings = /* @__PURE__ */ new Map();
|
|
35561
|
+
generations = /* @__PURE__ */ new Map();
|
|
35562
|
+
/** Fix I-1 / Task 20 R1: message id -> P's held position before the batch that first received it; null = not knowable. */
|
|
35563
|
+
receipts = /* @__PURE__ */ new Map();
|
|
35564
|
+
/** Final review M-d: arrivals not recorded because the map was full of young records. */
|
|
35565
|
+
capReached = 0;
|
|
35566
|
+
/** True when a lookup should run now: never once resolved; after a failure, only past its back-off. */
|
|
35567
|
+
resolveDue(channelId, now, opts = {}) {
|
|
35568
|
+
if (this.parents.has(channelId) || this.notVoice.has(channelId)) return false;
|
|
35569
|
+
const f = this.failures.get(channelId);
|
|
35570
|
+
if (f === void 0) return true;
|
|
35571
|
+
let wait = Math.min(VOICE_RESOLVE_RETRY_MAX_MS, VOICE_RESOLVE_RETRY_BASE_MS * 2 ** Math.min(f.attempts - 1, 20));
|
|
35572
|
+
if (f.kind === "absent" && opts.heldGroup === true) wait = Math.min(wait, VOICE_RESOLVE_ABSENT_HELD_MAX_MS);
|
|
35573
|
+
return now - f.at >= wait;
|
|
35574
|
+
}
|
|
35575
|
+
record(channelId, r, now) {
|
|
35576
|
+
if (r.kind === "voice") {
|
|
35577
|
+
this.parents.set(channelId, r.parentChannelId);
|
|
35578
|
+
this.failures.delete(channelId);
|
|
35579
|
+
} else if (r.kind === "not_voice") {
|
|
35580
|
+
this.notVoice.add(channelId);
|
|
35581
|
+
this.failures.delete(channelId);
|
|
35582
|
+
} else {
|
|
35583
|
+
const prev = this.failures.get(channelId);
|
|
35584
|
+
this.failures.set(channelId, { at: now, attempts: (prev?.attempts ?? 0) + 1, kind: r.kind });
|
|
35585
|
+
}
|
|
35586
|
+
}
|
|
35587
|
+
/** True once get_channel has answered for this channel (voice or not). */
|
|
35588
|
+
isResolved(channelId) {
|
|
35589
|
+
return this.parents.has(channelId) || this.notVoice.has(channelId);
|
|
35590
|
+
}
|
|
35591
|
+
/** Final review M-a: true once get_channel answered this channel's own row with a kind other than voice. */
|
|
35592
|
+
isNotVoice(channelId) {
|
|
35593
|
+
return this.notVoice.has(channelId);
|
|
35594
|
+
}
|
|
35595
|
+
/** Final review (R∈P): bound Vs whose admitted R is also a P leaf (the pre-stamp shape; a sunset signal). */
|
|
35596
|
+
relayInParentCount() {
|
|
35597
|
+
let n = 0;
|
|
35598
|
+
for (const [v, e] of this.bindings) {
|
|
35599
|
+
if (this.parents.has(v) && e.verdict.bound && e.relay !== void 0 && e.parentMembers?.has(e.relay) === true) n++;
|
|
35600
|
+
}
|
|
35601
|
+
return n;
|
|
35602
|
+
}
|
|
35603
|
+
/** True for a resolved V, bound or not. */
|
|
35604
|
+
isVoice(channelId) {
|
|
35605
|
+
return this.parents.has(channelId);
|
|
35606
|
+
}
|
|
35607
|
+
/** P of a resolved V, bound or not; undefined otherwise. Never use this to route: see {@link boundParentOf}. */
|
|
35608
|
+
parentOf(channelId) {
|
|
35609
|
+
return this.parents.get(channelId);
|
|
35610
|
+
}
|
|
35611
|
+
/** Every resolved V with its P. */
|
|
35612
|
+
voiceChannels() {
|
|
35613
|
+
return [...this.parents.entries()];
|
|
35614
|
+
}
|
|
35615
|
+
/** The gate for a channel the bridge's listing has never classified (`unlisted`), or for a resolved V. */
|
|
35616
|
+
gate(channelId, unlisted) {
|
|
35617
|
+
if (this.parents.has(channelId)) return "drop";
|
|
35618
|
+
if (!unlisted || this.notVoice.has(channelId)) return "normal";
|
|
35619
|
+
return this.failures.get(channelId)?.kind === "absent" ? "drop" : "hold";
|
|
35620
|
+
}
|
|
35621
|
+
bindingKey(channelId) {
|
|
35622
|
+
return this.bindings.get(channelId)?.key;
|
|
35623
|
+
}
|
|
35624
|
+
setBinding(channelId, key, verdict, positions = null, admitted = {}) {
|
|
35625
|
+
this.bindings.set(channelId, { key, positions, verdict, ...admitted });
|
|
35626
|
+
}
|
|
35627
|
+
/** Fix M-1: each evaluation of a V takes a generation; only the newest started may store its verdict. */
|
|
35628
|
+
beginEvaluation(channelId) {
|
|
35629
|
+
const g = (this.generations.get(channelId) ?? 0) + 1;
|
|
35630
|
+
this.generations.set(channelId, g);
|
|
35631
|
+
return g;
|
|
35632
|
+
}
|
|
35633
|
+
isLatestEvaluation(channelId, generation) {
|
|
35634
|
+
return this.generations.get(channelId) === generation;
|
|
35635
|
+
}
|
|
35636
|
+
/**
|
|
35637
|
+
* Fix I-1: records a frame's receipt stamp the FIRST time it is seen; a re-served frame keeps its first record.
|
|
35638
|
+
* Task 20 R2: at the cap nothing is evicted and the new arrival is not recorded (refused: no record, no parent).
|
|
35639
|
+
*/
|
|
35640
|
+
noteReceipt(messageId, pStamp, now = Date.now()) {
|
|
35641
|
+
if (this.receipts.has(messageId)) return;
|
|
35642
|
+
if (this.receipts.size >= VOICE_RECEIPT_CAP) {
|
|
35643
|
+
for (const [id, r] of this.receipts) if (now - r.at > VOICE_RECEIPT_MAX_AGE_MS) this.receipts.delete(id);
|
|
35644
|
+
if (this.receipts.size >= VOICE_RECEIPT_CAP) {
|
|
35645
|
+
this.capReached++;
|
|
35646
|
+
return;
|
|
35647
|
+
}
|
|
35648
|
+
}
|
|
35649
|
+
this.receipts.set(messageId, { stamp: pStamp, at: now });
|
|
35650
|
+
}
|
|
35651
|
+
/** Final review M-d: arrivals refused because the receipt map was full (a number only). */
|
|
35652
|
+
receiptCapReached() {
|
|
35653
|
+
return this.capReached;
|
|
35654
|
+
}
|
|
35655
|
+
/** Test visibility: receipt records held. */
|
|
35656
|
+
receiptCount() {
|
|
35657
|
+
return this.receipts.size;
|
|
35658
|
+
}
|
|
35659
|
+
forgetReceipt(messageId) {
|
|
35660
|
+
this.receipts.delete(messageId);
|
|
35661
|
+
}
|
|
35662
|
+
/**
|
|
35663
|
+
* Fix I-1: P for one frame on V only when V is bound now AND both groups' held positions are exactly those at the
|
|
35664
|
+
* frame's receipt. A frame received at other positions (or with none recorded) gets undefined: dropped, never held.
|
|
35665
|
+
*/
|
|
35666
|
+
boundParentForFrame(channelId, messageId, openedAt) {
|
|
35667
|
+
return this.frameContext(channelId, messageId, openedAt)?.parent;
|
|
35668
|
+
}
|
|
35669
|
+
/**
|
|
35670
|
+
* Task 20: the per-frame verdict with what the accept filter needs. Bound now AND the frame's pre-batch P stamp equals
|
|
35671
|
+
* the verdict's P position AND (R1) the frame's own opening position equals the verdict's V position. A frame with
|
|
35672
|
+
* no record, a null record, or no `openedAt` is refused.
|
|
35673
|
+
*/
|
|
35674
|
+
frameContext(channelId, messageId, openedAt) {
|
|
35675
|
+
const parent3 = this.boundParentOf(channelId);
|
|
35676
|
+
if (parent3 === void 0) return void 0;
|
|
35677
|
+
const entry = this.bindings.get(channelId);
|
|
35678
|
+
const at = this.receipts.get(messageId)?.stamp;
|
|
35679
|
+
if (entry?.positions == null || typeof at !== "string" || at !== entry.positions.p) return void 0;
|
|
35680
|
+
const v = entry.positions.v;
|
|
35681
|
+
if (v === null || openedAt == null || openedAt.groupId !== v.groupId || openedAt.epoch !== v.epoch) return void 0;
|
|
35682
|
+
return { parent: parent3, relay: entry.relay, parentMembers: entry.parentMembers ?? /* @__PURE__ */ new Set() };
|
|
35683
|
+
}
|
|
35684
|
+
/** The current verdict, or undefined when never evaluated. */
|
|
35685
|
+
binding(channelId) {
|
|
35686
|
+
return this.bindings.get(channelId)?.verdict;
|
|
35687
|
+
}
|
|
35688
|
+
/** P only while V is a resolved V whose last verdict is bound; undefined otherwise. */
|
|
35689
|
+
boundParentOf(channelId) {
|
|
35690
|
+
const parent3 = this.parents.get(channelId);
|
|
35691
|
+
return parent3 !== void 0 && this.bindings.get(channelId)?.verdict.bound === true ? parent3 : void 0;
|
|
35692
|
+
}
|
|
35693
|
+
};
|
|
35694
|
+
|
|
35695
|
+
// src/voice-channel-frame.ts
|
|
35696
|
+
function classifyVoiceFrame(f) {
|
|
35697
|
+
if (f.senderId === f.ownIdentityId) return "drop";
|
|
35698
|
+
const fromRelay = f.relay !== void 0 && f.senderId === f.relay;
|
|
35699
|
+
const fromMember = f.parentMembers.has(f.senderId);
|
|
35700
|
+
if (!fromRelay && !fromMember) return "drop";
|
|
35701
|
+
let body;
|
|
35702
|
+
try {
|
|
35703
|
+
body = JSON.parse(new TextDecoder().decode(f.plaintext));
|
|
35704
|
+
} catch {
|
|
35705
|
+
return "drop";
|
|
35706
|
+
}
|
|
35707
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) return "drop";
|
|
35708
|
+
const b = body;
|
|
35709
|
+
if (b.type === "call_transcript") return "call_transcript";
|
|
35710
|
+
if (b.type !== void 0 && b.type !== "text" || typeof b.text !== "string") return "drop";
|
|
35711
|
+
if (fromRelay) return "relay_turn";
|
|
35712
|
+
return b.text.trim() === VOICE_CALL_START_MARKER ? "greeting" : "drop";
|
|
35713
|
+
}
|
|
35714
|
+
|
|
35463
35715
|
// src/grants/owner-turn.ts
|
|
35464
35716
|
import { ed25519 as ed255196 } from "@noble/curves/ed25519";
|
|
35465
35717
|
var HEX = /^(?:[0-9a-fA-F]{2})+$/;
|
|
@@ -35555,6 +35807,7 @@ var OWNER_DM_DEFAULT_READ_GRANTS = Object.freeze([
|
|
|
35555
35807
|
var ROOM_DEFAULT_READ_GRANTS = Object.freeze([
|
|
35556
35808
|
Object.freeze({ tool: "current_time", mode: "read" })
|
|
35557
35809
|
]);
|
|
35810
|
+
var BRIDGE_CAPABILITIES = Object.freeze(["voice_channel_v1"]);
|
|
35558
35811
|
var RELAYED_VOICE_GRANTS = Object.freeze([
|
|
35559
35812
|
Object.freeze({ tool: "current_time", mode: "read" }),
|
|
35560
35813
|
Object.freeze({ tool: "web_search", mode: "read" })
|
|
@@ -35829,12 +36082,19 @@ var RoomClassChangedSendError = class extends SendForbiddenError {
|
|
|
35829
36082
|
this.message = `reply dropped for channel ${channelId}: the room changed after its tools were granted; no retry`;
|
|
35830
36083
|
}
|
|
35831
36084
|
};
|
|
36085
|
+
function voiceVerdictPositions(v, p) {
|
|
36086
|
+
return {
|
|
36087
|
+
v: v.groupId !== null && v.epoch !== null ? { groupId: v.groupId, epoch: v.epoch } : null,
|
|
36088
|
+
p: voiceParentStamp(p)
|
|
36089
|
+
};
|
|
36090
|
+
}
|
|
35832
36091
|
function rosterFreshAt(entry, heldEpoch, heldGroupId) {
|
|
35833
36092
|
return entry !== void 0 && typeof heldEpoch === "number" && entry.epoch === heldEpoch && typeof heldGroupId === "string" && entry.groupId === heldGroupId;
|
|
35834
36093
|
}
|
|
35835
36094
|
var RoomGrantEpochMovedError = class extends Error {
|
|
35836
36095
|
};
|
|
35837
36096
|
var ROOM_GRANT_TRACK_MAX = 4096;
|
|
36097
|
+
var VOICE_FRAME_PARENT_MAX = 4096;
|
|
35838
36098
|
var SessionSupersededError = class extends Error {
|
|
35839
36099
|
constructor() {
|
|
35840
36100
|
super("session_superseded: another copy of this agent took its session during start()");
|
|
@@ -36057,6 +36317,9 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
36057
36317
|
this.injectedFastLlm = !!deps.fastLlm;
|
|
36058
36318
|
this.fileVoiceConfig = cfg.voice;
|
|
36059
36319
|
this.fastLlm = deps.fastLlm ?? (cfg.voice?.fastPath.enabled ? new FastLlmClient(cfg.voice.fastPath, this.fetchImpl) : void 0);
|
|
36320
|
+
if (this.fileVoiceConfig?.fastPath.route === "direct" && !this.fileVoiceConfig.fastPath.apiKey) {
|
|
36321
|
+
writeServiceLog("warn", "voice.fast_path_direct_no_key");
|
|
36322
|
+
}
|
|
36060
36323
|
this.voiceSettingsSource = cfg.voice ? "file" : "none";
|
|
36061
36324
|
this.latency = deps.latency ?? new LatencyRecorder();
|
|
36062
36325
|
this.voiceCoalesceWindowMs = Math.max(0, deps.voiceCoalesceWindowMs ?? voiceCoalesceWindowFromEnv());
|
|
@@ -36263,6 +36526,13 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
36263
36526
|
supersedeRefundable = /* @__PURE__ */ new Set();
|
|
36264
36527
|
/** M-1: channels this process has used as a FileSafe vault channel. */
|
|
36265
36528
|
vaultChannelIds = /* @__PURE__ */ new Set();
|
|
36529
|
+
/**
|
|
36530
|
+
* ADR-0024 step 2a Task 19 (spec §9): hidden voice channels V resolved to their conversation P, lookup back-off, and
|
|
36531
|
+
* each V's binding verdict. In memory only: a restart re-resolves (one get_channel per V) and re-evaluates.
|
|
36532
|
+
*/
|
|
36533
|
+
voiceChannels = new VoiceChannelRegistry();
|
|
36534
|
+
/** Fix M-3: channels a listing row marked channel_kind "voice"; never recorded as listed. */
|
|
36535
|
+
listedAsVoice = /* @__PURE__ */ new Set();
|
|
36266
36536
|
/** Phase 3 F3: consecutive agent turns per channel. */
|
|
36267
36537
|
agentHops = new AgentHopTracker();
|
|
36268
36538
|
maxAgentHops;
|
|
@@ -36293,6 +36563,12 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
36293
36563
|
* counter is incremented. Surfaced on health()/metrics().
|
|
36294
36564
|
*/
|
|
36295
36565
|
failedAttachments = 0;
|
|
36566
|
+
/** Task 20 R4: V frames dropped by the per-frame verdict (not bound, or not at the verdict's positions). A number only. */
|
|
36567
|
+
voiceFramesRefused = 0;
|
|
36568
|
+
/** Task 20: frames on a bound V the accept filter dropped (not R's voice frame, a greeting marker or a transcript). */
|
|
36569
|
+
voiceFramesFiltered = 0;
|
|
36570
|
+
/** Task 20: message id of an admitted V frame -> its P (B.8 keys). Bounded; an entry lives one ingest cycle in practice. */
|
|
36571
|
+
voiceFrameParent = /* @__PURE__ */ new Map();
|
|
36296
36572
|
/** Files link: vault_ref bodies refused by R1 (strict parse) or R2 (enclave binding). */
|
|
36297
36573
|
refusedVaultRefs = 0;
|
|
36298
36574
|
/**
|
|
@@ -36706,7 +36982,9 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
36706
36982
|
// Fix round MEDIUM 2: a channel this process used as a FileSafe vault
|
|
36707
36983
|
// channel is never swept (the sweeper also refuses any channel no listing
|
|
36708
36984
|
// has shown, which covers a vault channel after a restart).
|
|
36709
|
-
|
|
36985
|
+
// Task 20 final review I-1: a voice channel V is never listed, so it is swept as its parent P: V is present exactly
|
|
36986
|
+
// when P is, and purgeConversation(P) purges every V of P. P is swept even when only its V holds memory.
|
|
36987
|
+
conversations: () => [...new Set(this.store.memoryConversationIds().filter((c) => !this.vaultChannelIds.has(c)).map((c) => this.store.voiceChannelParent(c) ?? c))],
|
|
36710
36988
|
enclaveOf: (c) => this.store.getChannelMeta(c)?.enclaveId,
|
|
36711
36989
|
// Audit r1 M6: the persisted flag, never the channelOwner cache. Set only
|
|
36712
36990
|
// from a listing, so undefined means no listing has ever shown the channel.
|
|
@@ -36856,6 +37134,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
36856
37134
|
const s = parseVoiceSettings(await res.json());
|
|
36857
37135
|
if (s.identityDisplayName) this.lastOwnDisplayName = s.identityDisplayName;
|
|
36858
37136
|
const proxy = settingsToVoiceConfig(s, { baseUrl: this.cfg.apiBase, apiKey: this.cfg.apiKey });
|
|
37137
|
+
const direct = this.fileVoiceConfig?.fastPath.route === "direct" ? this.fileVoiceConfig.fastPath : void 0;
|
|
36859
37138
|
if (s.version === 0 && this.fileVoiceConfig) {
|
|
36860
37139
|
const merged = {
|
|
36861
37140
|
...s,
|
|
@@ -36864,16 +37143,16 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
36864
37143
|
quirks: this.fileVoiceConfig.persona.quirks
|
|
36865
37144
|
};
|
|
36866
37145
|
this.voiceSettings = merged;
|
|
36867
|
-
this.cfg.voice = { persona: this.fileVoiceConfig.persona, fastPath: proxy.fastPath, settings: merged };
|
|
37146
|
+
this.cfg.voice = { persona: this.fileVoiceConfig.persona, fastPath: direct ?? proxy.fastPath, settings: merged };
|
|
36868
37147
|
this.voiceSettingsVersion = 0;
|
|
36869
37148
|
this.voiceSettingsSource = "file";
|
|
36870
37149
|
} else {
|
|
36871
37150
|
this.voiceSettings = s;
|
|
36872
|
-
this.cfg.voice = proxy;
|
|
37151
|
+
this.cfg.voice = direct ? { ...proxy, fastPath: direct } : proxy;
|
|
36873
37152
|
this.voiceSettingsVersion = s.version;
|
|
36874
37153
|
this.voiceSettingsSource = "backend";
|
|
36875
37154
|
}
|
|
36876
|
-
if (!this.injectedFastLlm) {
|
|
37155
|
+
if (!this.injectedFastLlm && !direct) {
|
|
36877
37156
|
this.fastLlm = new FastLlmClient(this.cfg.voice.fastPath, this.fetchImpl);
|
|
36878
37157
|
}
|
|
36879
37158
|
writeServiceLog("info", "voice.settings_loaded", {
|
|
@@ -37102,12 +37381,10 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
37102
37381
|
const report = {
|
|
37103
37382
|
applied_version: version,
|
|
37104
37383
|
paths: checkConfiguredPaths(skillsJson),
|
|
37105
|
-
//
|
|
37106
|
-
//
|
|
37107
|
-
|
|
37108
|
-
|
|
37109
|
-
// interpret any of it.
|
|
37110
|
-
local_persona: local?.persona ?? null,
|
|
37384
|
+
// F-0 (G-5): the report carries no content. No MEMORY.md text, no persona text, no legacy
|
|
37385
|
+
// text. E13 needs only to know which of the runtime's own persona files hold something, so
|
|
37386
|
+
// this is [{name, bytes}] for each non-blank file and nothing else.
|
|
37387
|
+
local_persona_files: local?.local_persona_files ?? [],
|
|
37111
37388
|
// Only the runtime knows whether a search key file is configured — it
|
|
37112
37389
|
// is an environment value on that process, invisible to the daemon and
|
|
37113
37390
|
// to the backend. The backend needs it to generate a truthful tool
|
|
@@ -37127,6 +37404,8 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
37127
37404
|
// the actual protected-memory adapter. It is an enum, never a path or
|
|
37128
37405
|
// provider credential.
|
|
37129
37406
|
memory_provider: this.cfg.memoryProvider ?? "securecoms",
|
|
37407
|
+
// Spec §2 (Task 18): the per-device capability declaration; a fixed enum list, one body for every report path.
|
|
37408
|
+
capabilities: [...BRIDGE_CAPABILITIES],
|
|
37130
37409
|
// Spec §11.1-11.2 (OQ-E): flat keys; the string `memory` above is
|
|
37131
37410
|
// unchanged. memory_notes is true only when notes are actually on (the
|
|
37132
37411
|
// provider is securecoms and the parsed payload.memory keeps them on);
|
|
@@ -37140,9 +37419,9 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
37140
37419
|
...this.reportedClearGeneration(),
|
|
37141
37420
|
// 7b spec §9.2 (Task 19): capability versus state. Read-only: the report never starts the embedder.
|
|
37142
37421
|
...this.reportedRecall(),
|
|
37143
|
-
// Inert legacy files only. `readLocalReport` revalidates the current
|
|
37144
|
-
// regular-file bytes and strips every local path before this
|
|
37145
|
-
// machine.
|
|
37422
|
+
// Inert legacy files only, metadata only. `readLocalReport` revalidates the current
|
|
37423
|
+
// regular-file bytes and strips every local path and every `content` before this
|
|
37424
|
+
// leaves the machine.
|
|
37146
37425
|
legacy_memory: local?.legacy_memory ?? [],
|
|
37147
37426
|
// §B.4: counts of server-named secret paths inside / allowlisted /
|
|
37148
37427
|
// outside the secrets directory, and the runtime's mode. Numbers and
|
|
@@ -38374,6 +38653,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
38374
38653
|
* across a supersede), and the group must not move around the reads.
|
|
38375
38654
|
*/
|
|
38376
38655
|
async reclassifyForReply(channelId, grant) {
|
|
38656
|
+
if (this.voiceChannels.isVoice(channelId)) return false;
|
|
38377
38657
|
const startedAt = Date.now();
|
|
38378
38658
|
await this.refreshChannelRoster(channelId);
|
|
38379
38659
|
const held = await this.heldPosition(channelId);
|
|
@@ -38390,8 +38670,9 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
38390
38670
|
this.enforceRateFloor("a2a", channelId);
|
|
38391
38671
|
return;
|
|
38392
38672
|
}
|
|
38393
|
-
const
|
|
38394
|
-
|
|
38673
|
+
const capChannel = this.capChannelFor(channelId);
|
|
38674
|
+
const meta = this.freshMeta(capChannel);
|
|
38675
|
+
if (meta && !meta.is_dm) this.enforceRateFloor("room", capChannel);
|
|
38395
38676
|
}
|
|
38396
38677
|
/** Bookkeeping after a CONFIRMED publish of `body` under `id`. */
|
|
38397
38678
|
afterPublish(channelId, body, id, opts, purgeEpoch, replyCapture) {
|
|
@@ -38873,8 +39154,289 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
38873
39154
|
return probeResultFor("threw");
|
|
38874
39155
|
}
|
|
38875
39156
|
}
|
|
39157
|
+
// ── ADR-0024 step 2a Task 19: the hidden voice channel V (spec §9, B.8) ──────
|
|
39158
|
+
//
|
|
39159
|
+
// The rule and the cache live in voice-channel-binding.ts; this is the wiring: the get_channel lookup, the bridge's
|
|
39160
|
+
// own held ratchet trees and positions, V's member list (is_relay only), and the receive-cycle gate.
|
|
39161
|
+
/**
|
|
39162
|
+
* Task 20's accessor: P's channel id ONLY while V is bound under the spec §9 rule at both groups' held positions as
|
|
39163
|
+
* of the last evaluation (every receive cycle, before any frame is processed; or {@link voiceParentNow}); undefined
|
|
39164
|
+
* for an unbound or refused V, and for any channel that is not a resolved V.
|
|
39165
|
+
*/
|
|
39166
|
+
voiceParentOf(channelId) {
|
|
39167
|
+
return this.voiceChannels.boundParentOf(channelId);
|
|
39168
|
+
}
|
|
39169
|
+
/** True for a channel resolved as a V, bound or not. Such a channel is never processed as a normal channel. */
|
|
39170
|
+
isKnownVoiceChannel(channelId) {
|
|
39171
|
+
return this.voiceChannels.isVoice(channelId);
|
|
39172
|
+
}
|
|
39173
|
+
/** Re-evaluates one V at the held positions now, then answers as {@link voiceParentOf}. */
|
|
39174
|
+
async voiceParentNow(channelId) {
|
|
39175
|
+
const parent3 = this.voiceChannels.parentOf(channelId);
|
|
39176
|
+
if (parent3 === void 0) return void 0;
|
|
39177
|
+
await this.reevaluateVoiceBinding(channelId, parent3);
|
|
39178
|
+
return this.voiceChannels.boundParentOf(channelId);
|
|
39179
|
+
}
|
|
39180
|
+
/** A channel no listing has ever classified (in memory or persisted). V is hidden from the listing, so it is one. */
|
|
39181
|
+
isUnlistedChannel(channelId) {
|
|
39182
|
+
return this.channelMeta.get(channelId) === void 0 && this.store.getChannelMeta(channelId)?.isDm === void 0;
|
|
39183
|
+
}
|
|
39184
|
+
voiceChannelGate(channelId) {
|
|
39185
|
+
if (this.vaultChannelIds.has(channelId)) return "normal";
|
|
39186
|
+
return this.voiceChannels.gate(channelId, this.isUnlistedChannel(channelId));
|
|
39187
|
+
}
|
|
39188
|
+
/**
|
|
39189
|
+
* Looks up one channel with get_channel (the voice arm, OQ-5 (a)). Only the classification is kept. Never throws; a
|
|
39190
|
+
* failure is recorded and retried (30 s doubling to 10 min). Called only from {@link resolveUnlistedChannels}, which
|
|
39191
|
+
* selects unlisted channels whose back-off allows.
|
|
39192
|
+
*/
|
|
39193
|
+
async resolveVoiceChannel(channelId) {
|
|
39194
|
+
let status = "threw";
|
|
39195
|
+
let body;
|
|
39196
|
+
try {
|
|
39197
|
+
const url = `${this.cfg.apiBase}/api/v1/channels/${encodeURIComponent(channelId)}?caller_identity_id=${encodeURIComponent(this.cfg.identityId)}`;
|
|
39198
|
+
const res = await this.fetchBounded("get_channel", "read", url, { headers: { authorization: `Bearer ${this.cfg.apiKey}` } });
|
|
39199
|
+
if (res.ok) {
|
|
39200
|
+
body = await res.json();
|
|
39201
|
+
} else {
|
|
39202
|
+
try {
|
|
39203
|
+
await res.body?.cancel();
|
|
39204
|
+
} catch {
|
|
39205
|
+
}
|
|
39206
|
+
}
|
|
39207
|
+
status = res.status;
|
|
39208
|
+
} catch {
|
|
39209
|
+
status = "threw";
|
|
39210
|
+
}
|
|
39211
|
+
const r = parseVoiceResolution(channelId, status, body);
|
|
39212
|
+
this.voiceChannels.record(channelId, r, Date.now());
|
|
39213
|
+
if (r.kind === "voice") {
|
|
39214
|
+
try {
|
|
39215
|
+
this.store.recordVoiceChannelParent(channelId, r.parentChannelId);
|
|
39216
|
+
} catch {
|
|
39217
|
+
}
|
|
39218
|
+
}
|
|
39219
|
+
if (r.kind === "voice") writeServiceLog("info", "voice.channel_lookup", { result: ServiceLogEnum.of("voice", ["voice", "absent", "error"]) });
|
|
39220
|
+
else if (r.kind !== "not_voice") writeServiceLog("warn", "voice.channel_lookup", { result: ServiceLogEnum.of(r.kind, ["voice", "absent", "error"]) });
|
|
39221
|
+
}
|
|
39222
|
+
/**
|
|
39223
|
+
* Looks up every unlisted channel among `channelIds` whose back-off allows. A channel is "unlisted" only after this
|
|
39224
|
+
* cycle's listing: if any candidate remains and the cycle has not listed yet, one listing runs first, so a channel
|
|
39225
|
+
* that is merely new (listed, not yet cached) is never looked up. Returns true when it ran that listing.
|
|
39226
|
+
*/
|
|
39227
|
+
async resolveUnlistedChannels(channelIds, listed) {
|
|
39228
|
+
const ids = [...new Set(channelIds)].filter((c) => !this.vaultChannelIds.has(c));
|
|
39229
|
+
const held = /* @__PURE__ */ new Set();
|
|
39230
|
+
for (const c of ids) {
|
|
39231
|
+
if (!this.isUnlistedChannel(c) || this.voiceChannels.isResolved(c)) continue;
|
|
39232
|
+
try {
|
|
39233
|
+
if ((await this.heldPosition(c)).groupId !== null) held.add(c);
|
|
39234
|
+
} catch {
|
|
39235
|
+
}
|
|
39236
|
+
}
|
|
39237
|
+
const due = () => ids.filter((c) => this.isUnlistedChannel(c) && this.voiceChannels.resolveDue(c, Date.now(), { heldGroup: held.has(c) }));
|
|
39238
|
+
let candidates = due();
|
|
39239
|
+
if (candidates.length === 0) return false;
|
|
39240
|
+
let ran = false;
|
|
39241
|
+
if (!listed) {
|
|
39242
|
+
await this.refreshChannelMeta();
|
|
39243
|
+
ran = true;
|
|
39244
|
+
candidates = due();
|
|
39245
|
+
}
|
|
39246
|
+
for (const c of candidates) await this.resolveVoiceChannel(c);
|
|
39247
|
+
return ran;
|
|
39248
|
+
}
|
|
39249
|
+
/** Final review M-c: V -> consecutive failed member-list reads for the binding, and when the last one ran. */
|
|
39250
|
+
voiceRosterRetry = /* @__PURE__ */ new Map();
|
|
39251
|
+
/** The first retry after a failure is immediate (next cycle); then 30 s, doubling, capped at 10 min. */
|
|
39252
|
+
voiceRosterRetryDue(v) {
|
|
39253
|
+
const f = this.voiceRosterRetry.get(v);
|
|
39254
|
+
if (f === void 0 || f.attempts <= 1) return true;
|
|
39255
|
+
const wait = Math.min(VOICE_RESOLVE_RETRY_MAX_MS, VOICE_RESOLVE_RETRY_BASE_MS * 2 ** Math.min(f.attempts - 2, 20));
|
|
39256
|
+
return Date.now() - f.at >= wait;
|
|
39257
|
+
}
|
|
39258
|
+
noteVoiceRosterRead(v, ok) {
|
|
39259
|
+
if (ok) {
|
|
39260
|
+
this.voiceRosterRetry.delete(v);
|
|
39261
|
+
return;
|
|
39262
|
+
}
|
|
39263
|
+
this.voiceRosterRetry.set(v, { at: Date.now(), attempts: (this.voiceRosterRetry.get(v)?.attempts ?? 0) + 1 });
|
|
39264
|
+
}
|
|
39265
|
+
/** Every resolved V, re-evaluated where a position or a roster moved. */
|
|
39266
|
+
async reevaluateVoiceBindings() {
|
|
39267
|
+
for (const [v, p] of this.voiceChannels.voiceChannels()) await this.reevaluateVoiceBinding(v, p);
|
|
39268
|
+
}
|
|
39269
|
+
/**
|
|
39270
|
+
* The spec §9 binding rule over the bridge's OWN ratchet trees (never a server list of leaves), keyed by both
|
|
39271
|
+
* groups' held (group, epoch) and both rosters' fetch times: a change in any of them re-evaluates. V's member list
|
|
39272
|
+
* is read only for `is_relay`, and only when it is fresh at V's held position. Fail closed: a tree that moved while
|
|
39273
|
+
* it was read, or any exception, leaves V unbound and is re-evaluated next time.
|
|
39274
|
+
*/
|
|
39275
|
+
async reevaluateVoiceBinding(v, p) {
|
|
39276
|
+
const generation = this.voiceChannels.beginEvaluation(v);
|
|
39277
|
+
try {
|
|
39278
|
+
const vPos = await this.heldPosition(v);
|
|
39279
|
+
const pPos = await this.heldPosition(p);
|
|
39280
|
+
const positions = voiceVerdictPositions(vPos, pPos);
|
|
39281
|
+
const parentNotConversation = this.voiceChannels.isVoice(p) || this.isUnlistedChannel(p);
|
|
39282
|
+
const keyOf = () => JSON.stringify([positions, this.freshRosterEntry(v)?.at ?? null, this.freshRosterEntry(p)?.at ?? null, parentNotConversation]);
|
|
39283
|
+
if (this.voiceChannels.bindingKey(v) === keyOf()) return;
|
|
39284
|
+
if (parentNotConversation) {
|
|
39285
|
+
if (this.voiceChannels.isLatestEvaluation(v, generation)) this.noteVoiceBinding(v, keyOf(), { bound: false, reason: "parent_not_conversation" }, positions);
|
|
39286
|
+
return;
|
|
39287
|
+
}
|
|
39288
|
+
const vTree = await this.provider.leafTreeSummary(v);
|
|
39289
|
+
const pTree = await this.provider.leafTreeSummary(p);
|
|
39290
|
+
const relayCheck = () => {
|
|
39291
|
+
const entry = this.freshRosterEntry(v);
|
|
39292
|
+
return entry !== void 0 && rosterFreshAt(entry, vPos.epoch, vPos.groupId) ? (id) => entry.members.get(id)?.is_relay === true : void 0;
|
|
39293
|
+
};
|
|
39294
|
+
if (relayCheck() === void 0 && this.voiceRosterRetryDue(v)) {
|
|
39295
|
+
await this.refreshChannelRoster(v);
|
|
39296
|
+
this.noteVoiceRosterRead(v, relayCheck() !== void 0);
|
|
39297
|
+
}
|
|
39298
|
+
const isVRelay = relayCheck();
|
|
39299
|
+
let verdict = evaluateVoiceBinding(vTree, pTree, isVRelay);
|
|
39300
|
+
const vAfter = await this.heldPosition(v);
|
|
39301
|
+
const pAfter = await this.heldPosition(p);
|
|
39302
|
+
const moved = vAfter.groupId !== vPos.groupId || vAfter.epoch !== vPos.epoch || pAfter.groupId !== pPos.groupId || pAfter.epoch !== pPos.epoch;
|
|
39303
|
+
if (moved) verdict = { bound: false, reason: "moved" };
|
|
39304
|
+
const retry = !verdict.bound && (verdict.reason === "relay_unknown" || verdict.reason === "moved") || verdict.bound && isVRelay === void 0;
|
|
39305
|
+
if (!this.voiceChannels.isLatestEvaluation(v, generation)) return;
|
|
39306
|
+
const parentMembers = /* @__PURE__ */ new Set();
|
|
39307
|
+
for (const l of pTree.leaves) if (l.basic && l.identity !== null) parentMembers.add(l.identity);
|
|
39308
|
+
this.noteVoiceBinding(v, retry ? "" : keyOf(), verdict, positions, void 0, {
|
|
39309
|
+
relay: verdict.bound ? voiceRelayIdentity(vTree, isVRelay) : void 0,
|
|
39310
|
+
parentMembers
|
|
39311
|
+
});
|
|
39312
|
+
} catch (e) {
|
|
39313
|
+
if (this.voiceChannels.isLatestEvaluation(v, generation)) this.noteVoiceBinding(v, "", { bound: false, reason: "moved" }, null, e);
|
|
39314
|
+
}
|
|
39315
|
+
}
|
|
39316
|
+
/**
|
|
39317
|
+
* Fix I-1 / Task 20 R1: P's held position for every resolved V, read before a receive so frames can be stamped. V's
|
|
39318
|
+
* half is not stamped here: each frame carries the epoch that opened it (`openedAt`).
|
|
39319
|
+
*/
|
|
39320
|
+
async voicePositionSnapshot() {
|
|
39321
|
+
const out = /* @__PURE__ */ new Map();
|
|
39322
|
+
for (const [v, p] of this.voiceChannels.voiceChannels()) {
|
|
39323
|
+
try {
|
|
39324
|
+
out.set(v, voiceParentStamp(await this.heldPosition(p)));
|
|
39325
|
+
} catch {
|
|
39326
|
+
}
|
|
39327
|
+
}
|
|
39328
|
+
return out;
|
|
39329
|
+
}
|
|
39330
|
+
/**
|
|
39331
|
+
* Fix I-1, for Task 20: P for ONE frame on V, only when V is bound now AND neither P's nor V's held (group, epoch)
|
|
39332
|
+
* moved between that frame's first receipt and the verdict. Otherwise undefined: the frame is dropped (acked, never
|
|
39333
|
+
* held, never processed). A frame first received before V was resolved has no receipt positions and is refused.
|
|
39334
|
+
*/
|
|
39335
|
+
voiceParentForFrame(channelId, messageId, openedAt) {
|
|
39336
|
+
return this.voiceChannels.boundParentForFrame(channelId, messageId, openedAt);
|
|
39337
|
+
}
|
|
39338
|
+
/** Stores a verdict; a change of verdict logs one content-free line. */
|
|
39339
|
+
noteVoiceBinding(v, key, verdict, positions, error, admitted = {}) {
|
|
39340
|
+
const prev = this.voiceChannels.binding(v);
|
|
39341
|
+
this.voiceChannels.setBinding(v, key, verdict, positions, admitted);
|
|
39342
|
+
const state = verdict.bound ? "bound" : verdict.reason;
|
|
39343
|
+
const prevState = prev === void 0 ? void 0 : prev.bound ? "bound" : prev.reason;
|
|
39344
|
+
if (state === prevState && error === void 0) return;
|
|
39345
|
+
writeServiceLog(verdict.bound ? "info" : "warn", "voice.channel_binding", {
|
|
39346
|
+
state: ServiceLogEnum.of(state, ["bound", "v_not_held", "p_not_held", "non_basic_leaf", "foreign_leaf", "two_non_parent", "two_relays", "relay_unknown", "moved", "parent_not_conversation"])
|
|
39347
|
+
}, error);
|
|
39348
|
+
}
|
|
39349
|
+
/**
|
|
39350
|
+
* The receive-cycle gate, before any per-frame work. A resolved V is never a normal channel: a frame passes only when
|
|
39351
|
+
* the per-frame verdict names P (bound now, its P stamp and its own opening epoch at the verdict's positions) AND the
|
|
39352
|
+
* accept filter takes it (Task 20: R's voice frame or a P member's greeting marker; a call_transcript is acked, not a
|
|
39353
|
+
* turn). Every other V frame is acked unprocessed and never recorded. A channel get_channel answered 404 is acked
|
|
39354
|
+
* unprocessed (fail closed). An unlisted channel whose lookup errored or has not run is held: neither processed nor
|
|
39355
|
+
* acked, so it is re-served once the lookup answers. Everything else passes unchanged.
|
|
39356
|
+
*/
|
|
39357
|
+
async gateVoiceEntries(entries) {
|
|
39358
|
+
const keep = [];
|
|
39359
|
+
const drop = [];
|
|
39360
|
+
for (const m of entries) {
|
|
39361
|
+
const gate = this.voiceChannelGate(m.channelId);
|
|
39362
|
+
if (gate === "hold") continue;
|
|
39363
|
+
if (gate === "normal") {
|
|
39364
|
+
this.voiceChannels.forgetReceipt(m.messageId);
|
|
39365
|
+
keep.push(m);
|
|
39366
|
+
continue;
|
|
39367
|
+
}
|
|
39368
|
+
if (this.voiceChannels.isVoice(m.channelId)) {
|
|
39369
|
+
const admitted = this.admitVoiceFrame(m);
|
|
39370
|
+
this.voiceChannels.forgetReceipt(m.messageId);
|
|
39371
|
+
if (admitted) {
|
|
39372
|
+
keep.push(m);
|
|
39373
|
+
continue;
|
|
39374
|
+
}
|
|
39375
|
+
} else {
|
|
39376
|
+
this.voiceChannels.forgetReceipt(m.messageId);
|
|
39377
|
+
}
|
|
39378
|
+
drop.push(m.queueId);
|
|
39379
|
+
}
|
|
39380
|
+
if (drop.length) {
|
|
39381
|
+
try {
|
|
39382
|
+
await this.provider.ackDelivery(this.cfg.deviceId, drop);
|
|
39383
|
+
} catch {
|
|
39384
|
+
}
|
|
39385
|
+
}
|
|
39386
|
+
return keep;
|
|
39387
|
+
}
|
|
39388
|
+
/**
|
|
39389
|
+
* Task 20: the per-frame verdict and the accept filter for one frame on a resolved V. True only for a model turn
|
|
39390
|
+
* (R's voice frame, a P member's greeting marker), which is then keyed per B.8 through {@link voiceFrameParent}. A
|
|
39391
|
+
* call_transcript is accepted but is not a turn (acked, nothing more, as step 1 on P). Counters are numbers only.
|
|
39392
|
+
*/
|
|
39393
|
+
admitVoiceFrame(m) {
|
|
39394
|
+
const parent3 = this.voiceParentForFrame(m.channelId, m.messageId, m.openedAt ?? null);
|
|
39395
|
+
const ctx = parent3 === void 0 ? void 0 : this.voiceChannels.frameContext(m.channelId, m.messageId, m.openedAt ?? null);
|
|
39396
|
+
if (ctx === void 0) {
|
|
39397
|
+
this.voiceFramesRefused++;
|
|
39398
|
+
return false;
|
|
39399
|
+
}
|
|
39400
|
+
const kind2 = classifyVoiceFrame({
|
|
39401
|
+
senderId: m.envelope.sender_id,
|
|
39402
|
+
ownIdentityId: this.cfg.identityId,
|
|
39403
|
+
relay: ctx.relay,
|
|
39404
|
+
parentMembers: ctx.parentMembers,
|
|
39405
|
+
plaintext: m.plaintext
|
|
39406
|
+
});
|
|
39407
|
+
if (kind2 === "call_transcript") return false;
|
|
39408
|
+
if (kind2 === "drop") {
|
|
39409
|
+
this.voiceFramesFiltered++;
|
|
39410
|
+
return false;
|
|
39411
|
+
}
|
|
39412
|
+
this.voiceFrameParent.set(m.messageId, ctx.parent);
|
|
39413
|
+
if (this.voiceFrameParent.size > VOICE_FRAME_PARENT_MAX) this.voiceFrameParent.delete(this.voiceFrameParent.keys().next().value);
|
|
39414
|
+
return true;
|
|
39415
|
+
}
|
|
39416
|
+
/**
|
|
39417
|
+
* Task 20 (B.8): the key channel for metadata, audience, owner, manifest and caps. For an admitted V frame, its P;
|
|
39418
|
+
* for any other frame, its own channel.
|
|
39419
|
+
*/
|
|
39420
|
+
keyChannelForFrame(m) {
|
|
39421
|
+
return this.voiceFrameParent.get(m.messageId) ?? m.channelId;
|
|
39422
|
+
}
|
|
39423
|
+
/**
|
|
39424
|
+
* Task 20 (B.8 "per-room daily caps → P", the room rate floor): the channel a cap or floor counts against. A known V
|
|
39425
|
+
* counts against its bound P; an unbound V against itself (no grant can be issued there, so nothing is widened).
|
|
39426
|
+
*/
|
|
39427
|
+
capChannelFor(channelId) {
|
|
39428
|
+
return this.voiceChannels.parentOf(channelId) ?? channelId;
|
|
39429
|
+
}
|
|
39430
|
+
/** Phase 3 F1 pause, for a voice channel V also read on its P (a paused agent runs no inference on the call either). */
|
|
39431
|
+
pausedFor(channelId) {
|
|
39432
|
+
return this.pausedChannels.has(channelId) || this.pausedChannels.has(this.capChannelFor(channelId));
|
|
39433
|
+
}
|
|
38876
39434
|
/** §5.4: one conversation's turns, facts, notes and recall, inside the memory mutex, with every in-memory copy. */
|
|
38877
39435
|
async purgeConversation(channelId) {
|
|
39436
|
+
await this.purgeOneConversation(channelId);
|
|
39437
|
+
for (const v of this.store.voiceChannelsOf(channelId)) await this.purgeOneConversation(v);
|
|
39438
|
+
}
|
|
39439
|
+
async purgeOneConversation(channelId) {
|
|
38878
39440
|
const scopes = this.unifiedOn ? this.store.ownerDmScopeIds().filter((s) => parseOwnerDmScopeId(s)?.channelId === channelId) : [];
|
|
38879
39441
|
await this.notes.purgeChannel(channelId);
|
|
38880
39442
|
for (const s of scopes) this.store.clearConversationAbsence(s);
|
|
@@ -40114,17 +40676,17 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
40114
40676
|
async applyMemoryNote(raw, ctx) {
|
|
40115
40677
|
if (this.unifiedOn) return this.applyUnifiedNote(raw, ctx);
|
|
40116
40678
|
const ids = echoIds(raw);
|
|
40117
|
-
const
|
|
40679
|
+
const refuse3 = (reason, echo = ids) => {
|
|
40118
40680
|
writeServiceLog("info", "memory.note_refused", {
|
|
40119
40681
|
reason_class: ServiceLogEnum.of(reason, ["mode", "invalid", "message", "audience", "provenance", "secret", "cap", "cleared", "store", "budget", "purged", "not_owner_words"])
|
|
40120
40682
|
});
|
|
40121
40683
|
return { type: "memory_note_result", ...echo, ok: false, reason };
|
|
40122
40684
|
};
|
|
40123
|
-
if (!this.notesProviderOn()) return
|
|
40685
|
+
if (!this.notesProviderOn()) return refuse3("mode");
|
|
40124
40686
|
const f = parseMemoryNoteFrame(raw);
|
|
40125
|
-
if (!f) return
|
|
40126
|
-
if (ctx.pendingRequests.has(f.request_id)) return
|
|
40127
|
-
if (ctx.pendingRequests.size >= MAX_PENDING_NOTE_REQUESTS) return
|
|
40687
|
+
if (!f) return refuse3("invalid");
|
|
40688
|
+
if (ctx.pendingRequests.has(f.request_id)) return refuse3("invalid", { message_id: f.message_id });
|
|
40689
|
+
if (ctx.pendingRequests.size >= MAX_PENDING_NOTE_REQUESTS) return refuse3("invalid", {});
|
|
40128
40690
|
ctx.pendingRequests.add(f.request_id);
|
|
40129
40691
|
try {
|
|
40130
40692
|
const w2 = () => {
|
|
@@ -40136,13 +40698,13 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
40136
40698
|
return cur.record;
|
|
40137
40699
|
};
|
|
40138
40700
|
const rec = w2();
|
|
40139
|
-
if (rec === void 0) return
|
|
40701
|
+
if (rec === void 0) return refuse3("message");
|
|
40140
40702
|
const channelId = rec.channel_id;
|
|
40141
|
-
if (rec.authorization?.memory_write !== true) return
|
|
40142
|
-
if (!this.noteTurnClean(rec.message_id)) return
|
|
40703
|
+
if (rec.authorization?.memory_write !== true) return refuse3("audience");
|
|
40704
|
+
if (!this.noteTurnClean(rec.message_id)) return refuse3("audience");
|
|
40143
40705
|
const held = await this.heldPosition(channelId);
|
|
40144
40706
|
const now = this.authorizationFor(channelId, rec.sender_id, void 0, held.epoch, held.groupId, { log: false });
|
|
40145
|
-
if (now.memory_write !== true) return
|
|
40707
|
+
if (now.memory_write !== true) return refuse3("audience");
|
|
40146
40708
|
const spend = async () => {
|
|
40147
40709
|
const rw = await this.store.prepareRuntimeInboxRewrite(rec.message_id, bumpW9);
|
|
40148
40710
|
if (rw === "declined") return "budget";
|
|
@@ -40158,44 +40720,44 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
40158
40720
|
};
|
|
40159
40721
|
if (f.op === "forget" && typeof f.id === "object") {
|
|
40160
40722
|
const shown = ctx.shownRecall(f.message_id, ctx.token);
|
|
40161
|
-
if (!shown || !shown.has(f.id.recall)) return
|
|
40723
|
+
if (!shown || !shown.has(f.id.recall)) return refuse3("invalid");
|
|
40162
40724
|
const rw = await spend();
|
|
40163
|
-
if (typeof rw === "string") return
|
|
40164
|
-
if (w2() !== rec) return
|
|
40165
|
-
if (!this.notesProviderOn()) return
|
|
40725
|
+
if (typeof rw === "string") return refuse3(rw);
|
|
40726
|
+
if (w2() !== rec) return refuse3("message");
|
|
40727
|
+
if (!this.notesProviderOn()) return refuse3("mode");
|
|
40166
40728
|
const r2 = await this.recall.forget(channelId, f.id.recall, rw, precommit(rw));
|
|
40167
|
-
if (r2 === "unknown_id") return
|
|
40168
|
-
if (r2 !== "ok") return
|
|
40729
|
+
if (r2 === "unknown_id") return refuse3("invalid");
|
|
40730
|
+
if (r2 !== "ok") return refuse3(r2);
|
|
40169
40731
|
writeServiceLog("info", "memory.recall_forgotten");
|
|
40170
40732
|
return { type: "memory_note_result", request_id: f.request_id, message_id: f.message_id, ok: true };
|
|
40171
40733
|
}
|
|
40172
40734
|
let mutation;
|
|
40173
40735
|
let rewrite;
|
|
40174
40736
|
if (f.op === "forget") {
|
|
40175
|
-
if (typeof f.id !== "number") return
|
|
40737
|
+
if (typeof f.id !== "number") return refuse3("invalid");
|
|
40176
40738
|
const rw = await spend();
|
|
40177
|
-
if (typeof rw === "string") return
|
|
40739
|
+
if (typeof rw === "string") return refuse3(rw);
|
|
40178
40740
|
rewrite = rw;
|
|
40179
40741
|
mutation = { op: "forget", id: f.id, rewrite: rw };
|
|
40180
40742
|
} else {
|
|
40181
40743
|
const p = prepareStoredNote(f.text, { text: rec.text, ...rec.transcribed === true ? { transcribed: true } : {} });
|
|
40182
|
-
if (!p.ok) return
|
|
40744
|
+
if (!p.ok) return refuse3(p.reason);
|
|
40183
40745
|
if (f.op === "replace") {
|
|
40184
|
-
if (typeof f.id !== "number") return
|
|
40746
|
+
if (typeof f.id !== "number") return refuse3("invalid");
|
|
40185
40747
|
const rw = await spend();
|
|
40186
|
-
if (typeof rw === "string") return
|
|
40748
|
+
if (typeof rw === "string") return refuse3(rw);
|
|
40187
40749
|
rewrite = rw;
|
|
40188
40750
|
mutation = { op: "replace", id: f.id, text: p.text, rewrite: rw };
|
|
40189
40751
|
} else {
|
|
40190
40752
|
mutation = { op: "add", text: p.text };
|
|
40191
40753
|
}
|
|
40192
40754
|
}
|
|
40193
|
-
if (w2() !== rec) return
|
|
40194
|
-
if (!this.notesProviderOn()) return
|
|
40755
|
+
if (w2() !== rec) return refuse3("message");
|
|
40756
|
+
if (!this.notesProviderOn()) return refuse3("mode");
|
|
40195
40757
|
const stamp = this.ingestGeneration.get(rec.message_id);
|
|
40196
|
-
if (stamp === void 0) return
|
|
40758
|
+
if (stamp === void 0) return refuse3("cleared");
|
|
40197
40759
|
const r = await this.notes.write(channelId, mutation, stamp, precommit(rewrite));
|
|
40198
|
-
if (!r.ok) return
|
|
40760
|
+
if (!r.ok) return refuse3(r.reason);
|
|
40199
40761
|
writeServiceLog("info", "memory.note_written", {
|
|
40200
40762
|
op: ServiceLogEnum.of(f.op, ["add", "replace", "forget"]),
|
|
40201
40763
|
notes_count: r.notesCount,
|
|
@@ -40204,7 +40766,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
40204
40766
|
return { type: "memory_note_result", request_id: f.request_id, message_id: f.message_id, ok: true, id: r.id };
|
|
40205
40767
|
} catch (e) {
|
|
40206
40768
|
writeServiceLog("warn", "memory.note_write_failed", {}, e);
|
|
40207
|
-
return
|
|
40769
|
+
return refuse3("store");
|
|
40208
40770
|
} finally {
|
|
40209
40771
|
ctx.pendingRequests.delete(f.request_id);
|
|
40210
40772
|
}
|
|
@@ -40218,17 +40780,17 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
40218
40780
|
*/
|
|
40219
40781
|
async applyUnifiedNote(raw, ctx) {
|
|
40220
40782
|
const ids = echoIds(raw);
|
|
40221
|
-
const
|
|
40783
|
+
const refuse3 = (reason, echo = ids) => {
|
|
40222
40784
|
writeServiceLog("info", "memory.note_refused", {
|
|
40223
40785
|
reason_class: ServiceLogEnum.of(reason, ["mode", "invalid", "message", "audience", "provenance", "secret", "cap", "cleared", "store", "budget", "purged", "not_owner_words"])
|
|
40224
40786
|
});
|
|
40225
40787
|
return { type: "memory_note_result", ...echo, ok: false, reason };
|
|
40226
40788
|
};
|
|
40227
|
-
if (!this.unifiedProviderOn()) return
|
|
40789
|
+
if (!this.unifiedProviderOn()) return refuse3("mode");
|
|
40228
40790
|
const f = parseMemoryNoteFrame(raw);
|
|
40229
|
-
if (!f) return
|
|
40230
|
-
if (ctx.pendingRequests.has(f.request_id)) return
|
|
40231
|
-
if (ctx.pendingRequests.size >= MAX_PENDING_NOTE_REQUESTS) return
|
|
40791
|
+
if (!f) return refuse3("invalid");
|
|
40792
|
+
if (ctx.pendingRequests.has(f.request_id)) return refuse3("invalid", { message_id: f.message_id });
|
|
40793
|
+
if (ctx.pendingRequests.size >= MAX_PENDING_NOTE_REQUESTS) return refuse3("invalid", {});
|
|
40232
40794
|
ctx.pendingRequests.add(f.request_id);
|
|
40233
40795
|
try {
|
|
40234
40796
|
const w2 = () => {
|
|
@@ -40240,25 +40802,25 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
40240
40802
|
return cur.record;
|
|
40241
40803
|
};
|
|
40242
40804
|
const rec = w2();
|
|
40243
|
-
if (rec === void 0) return
|
|
40805
|
+
if (rec === void 0) return refuse3("message");
|
|
40244
40806
|
const channelId = rec.channel_id;
|
|
40245
|
-
if (rec.authorization?.memory_write !== true) return
|
|
40246
|
-
if (!this.noteTurnClean(rec.message_id)) return
|
|
40807
|
+
if (rec.authorization?.memory_write !== true) return refuse3("audience");
|
|
40808
|
+
if (!this.noteTurnClean(rec.message_id)) return refuse3("audience");
|
|
40247
40809
|
const held = await this.heldPosition(channelId);
|
|
40248
|
-
if (this.authorizationFor(channelId, rec.sender_id, void 0, held.epoch, held.groupId, { log: false }).memory_write !== true) return
|
|
40810
|
+
if (this.authorizationFor(channelId, rec.sender_id, void 0, held.epoch, held.groupId, { log: false }).memory_write !== true) return refuse3("audience");
|
|
40249
40811
|
const scopeId = this.ownerScopeNow(channelId);
|
|
40250
|
-
if (scopeId === null) return
|
|
40812
|
+
if (scopeId === null) return refuse3("audience");
|
|
40251
40813
|
const precommit = () => !this.unifiedProviderOn() ? "mode" : w2() !== rec ? "message" : null;
|
|
40252
|
-
if (f.op === "replace") return
|
|
40253
|
-
if (f.op === "forget") return await this.forgetUnified(f, rec, channelId, ctx,
|
|
40814
|
+
if (f.op === "replace") return refuse3("invalid");
|
|
40815
|
+
if (f.op === "forget") return await this.forgetUnified(f, rec, channelId, ctx, refuse3, precommit);
|
|
40254
40816
|
const stripped = allowlistPin(f.text);
|
|
40255
|
-
if (stripped.trim().length === 0) return
|
|
40817
|
+
if (stripped.trim().length === 0) return refuse3("invalid");
|
|
40256
40818
|
const shown = ctx.shownUnified(f.message_id, ctx.token) ?? /* @__PURE__ */ new Set();
|
|
40257
|
-
if (!pinCoverage(stripped, rec.text, this.unifiedReads.coverageTexts(f.message_id, shown)).ok) return
|
|
40258
|
-
if (scanNote(stripped) !== null) return
|
|
40259
|
-
if (stripped.length > PIN_MAX_CHARS) return
|
|
40819
|
+
if (!pinCoverage(stripped, rec.text, this.unifiedReads.coverageTexts(f.message_id, shown)).ok) return refuse3("not_owner_words");
|
|
40820
|
+
if (scanNote(stripped) !== null) return refuse3("secret");
|
|
40821
|
+
if (stripped.length > PIN_MAX_CHARS) return refuse3("cap");
|
|
40260
40822
|
const stamp = this.ingestGeneration.get(rec.message_id);
|
|
40261
|
-
if (stamp === void 0) return
|
|
40823
|
+
if (stamp === void 0) return refuse3("cleared");
|
|
40262
40824
|
const capture = this.store.itemCapture(channelId);
|
|
40263
40825
|
const r = await this.items.commit({
|
|
40264
40826
|
scopeKind: "owner_dm",
|
|
@@ -40276,7 +40838,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
40276
40838
|
});
|
|
40277
40839
|
if (!r.ok) {
|
|
40278
40840
|
const map = { pin_cap: "cap", secret: "secret", cleared: "cleared", room_cleared: "cleared", disabled: "mode", mode: "mode", purged: "purged", message: "message", duplicate: "invalid", invalid: "invalid", empty: "invalid" };
|
|
40279
|
-
return
|
|
40841
|
+
return refuse3(map[r.reason] ?? "store");
|
|
40280
40842
|
}
|
|
40281
40843
|
await this.notes.exclusive(async () => this.unifiedReads.bumpGeneration());
|
|
40282
40844
|
void this.send(channelId, pinnedLine(stripped), void 0, { agentTurn: false }).catch((e) => writeServiceLog("warn", "memory.pinned_line_failed", {}, e));
|
|
@@ -40284,7 +40846,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
40284
40846
|
return { type: "memory_note_result", request_id: f.request_id, message_id: f.message_id, ok: true, id: r.itemIds[0] };
|
|
40285
40847
|
} catch (e) {
|
|
40286
40848
|
writeServiceLog("warn", "memory.note_write_failed", {}, e);
|
|
40287
|
-
return
|
|
40849
|
+
return refuse3("store");
|
|
40288
40850
|
} finally {
|
|
40289
40851
|
ctx.pendingRequests.delete(f.request_id);
|
|
40290
40852
|
}
|
|
@@ -40298,16 +40860,16 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
40298
40860
|
* is invalidated in the same mutex hold, the item leaves the ranking hot set, then the bridge publishes `Forgot:`
|
|
40299
40861
|
* (OQ-P2: afterPublish keeps the line out of history and the items). Every reason is a key of UNIFIED_REFUSAL.
|
|
40300
40862
|
*/
|
|
40301
|
-
async forgetUnified(f, rec, channelId, ctx,
|
|
40302
|
-
if (typeof f.id !== "object") return
|
|
40863
|
+
async forgetUnified(f, rec, channelId, ctx, refuse3, precommit) {
|
|
40864
|
+
if (typeof f.id !== "object") return refuse3("invalid");
|
|
40303
40865
|
const itemId = f.id.recall;
|
|
40304
40866
|
const shown = ctx.shownUnified(f.message_id, ctx.token);
|
|
40305
|
-
if (!shown || !shown.has(itemId)) return
|
|
40867
|
+
if (!shown || !shown.has(itemId)) return refuse3("invalid");
|
|
40306
40868
|
const text = this.unifiedReads.shownText(f.message_id, itemId);
|
|
40307
|
-
if (text === void 0) return
|
|
40869
|
+
if (text === void 0) return refuse3("invalid");
|
|
40308
40870
|
const rw = await this.store.prepareRuntimeInboxRewrite(rec.message_id, bumpW9);
|
|
40309
|
-
if (rw === "declined") return
|
|
40310
|
-
if (rw === "no_row") return
|
|
40871
|
+
if (rw === "declined") return refuse3("budget");
|
|
40872
|
+
if (rw === "no_row") return refuse3("message");
|
|
40311
40873
|
const r = await this.notes.exclusive(async () => {
|
|
40312
40874
|
const veto = precommit();
|
|
40313
40875
|
if (veto !== null) return { ok: false, reason: veto };
|
|
@@ -40320,7 +40882,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
40320
40882
|
}
|
|
40321
40883
|
return out;
|
|
40322
40884
|
});
|
|
40323
|
-
if (!r.ok) return
|
|
40885
|
+
if (!r.ok) return refuse3(r.reason === "unknown_id" ? "invalid" : r.reason);
|
|
40324
40886
|
this.rankingNote(() => this.ranking.removed(r.itemIds));
|
|
40325
40887
|
void this.send(channelId, forgotLine(text), void 0, { agentTurn: false }).catch((e) => writeServiceLog("warn", "memory.forgot_line_failed", {}, e));
|
|
40326
40888
|
writeServiceLog("info", "memory.item_forgotten");
|
|
@@ -40394,8 +40956,9 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
40394
40956
|
this.fastReplyChains.set(channelId, next);
|
|
40395
40957
|
}
|
|
40396
40958
|
/** Count a fast reply against FAST_REPLY_MAX_JOBS and reserve its room send-floor token; returns the release. */
|
|
40397
|
-
reserveFastReply(
|
|
40959
|
+
reserveFastReply(replyChannel) {
|
|
40398
40960
|
this.fastReplyJobs++;
|
|
40961
|
+
const channelId = this.capChannelFor(replyChannel);
|
|
40399
40962
|
this.fastReplyReserved.set(channelId, (this.fastReplyReserved.get(channelId) ?? 0) + 1);
|
|
40400
40963
|
return () => {
|
|
40401
40964
|
this.fastReplyJobs--;
|
|
@@ -40619,6 +41182,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
40619
41182
|
});
|
|
40620
41183
|
if (!res.ok) throw new Error(`list_channels failed: ${res.status}`);
|
|
40621
41184
|
const rows = await res.json();
|
|
41185
|
+
for (const r of rows) if (r.channel_kind === "voice") this.listedAsVoice.add(r.channel_id);
|
|
40622
41186
|
for (const r of rows) this.store.recordChannelEnclave(r.channel_id, r.enclave_id);
|
|
40623
41187
|
return rows.map((r) => ({
|
|
40624
41188
|
channel_id: r.channel_id,
|
|
@@ -40702,6 +41266,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
40702
41266
|
*/
|
|
40703
41267
|
recordListedChannelMeta(channels, at) {
|
|
40704
41268
|
for (const c of channels) {
|
|
41269
|
+
if (this.listedAsVoice.has(c.channel_id)) continue;
|
|
40705
41270
|
this.channelMeta.set(c.channel_id, { is_dm: c.is_dm, name: c.name, at });
|
|
40706
41271
|
this.store.recordChannelIsDm(c.channel_id, c.is_dm);
|
|
40707
41272
|
}
|
|
@@ -40885,7 +41450,8 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
40885
41450
|
}
|
|
40886
41451
|
const self = members.get(this.cfg.identityId);
|
|
40887
41452
|
if (self?.name) this.lastOwnDisplayName = self.name;
|
|
40888
|
-
|
|
41453
|
+
const unclassified = this.isUnlistedChannel(channelId) && !this.voiceChannels.isNotVoice(channelId);
|
|
41454
|
+
if (!this.voiceChannels.isVoice(channelId) && !unclassified) await this.resolveAndPinOwner(channelId, liveRows);
|
|
40889
41455
|
} catch {
|
|
40890
41456
|
}
|
|
40891
41457
|
}
|
|
@@ -40921,7 +41487,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
40921
41487
|
* held group (never the backend's row label, M-2). NEVER throws (M-4): any
|
|
40922
41488
|
* exception is `failed verify_error`, and the row is acked like any other turn.
|
|
40923
41489
|
*/
|
|
40924
|
-
async verifyOwnerTurn(m) {
|
|
41490
|
+
async verifyOwnerTurn(m, keyChannel = m.channelId) {
|
|
40925
41491
|
const pin = this.cfg.ownerGrantPin?.pin ?? null;
|
|
40926
41492
|
if (!pin || m.envelope.sender_id !== pin.ownerIdentityId) return OWNER_TURN_UNCHECKED;
|
|
40927
41493
|
try {
|
|
@@ -40933,9 +41499,9 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
40933
41499
|
}
|
|
40934
41500
|
if (v.sender_id !== pin.ownerIdentityId) return this.ownerTurnFailed(m, "malformed");
|
|
40935
41501
|
let leafClass = "ok";
|
|
40936
|
-
if (this.freshMeta(
|
|
41502
|
+
if (this.freshMeta(keyChannel)?.is_dm === true) {
|
|
40937
41503
|
const signedDm = this.toolGrantManifest?.owner_dm_channel_id;
|
|
40938
|
-
if (signedDm !== void 0 && signedDm !==
|
|
41504
|
+
if (signedDm !== void 0 && signedDm !== keyChannel) return this.ownerTurnFailed(m, "wrong_dm_channel");
|
|
40939
41505
|
leafClass = classifyOwnerDmLeaves((await this.provider.leafTreeSummary(m.channelId)).leaves, {
|
|
40940
41506
|
ownerIdentityId: pin.ownerIdentityId,
|
|
40941
41507
|
agentIdentityId: this.cfg.identityId,
|
|
@@ -41099,9 +41665,14 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
41099
41665
|
});
|
|
41100
41666
|
}
|
|
41101
41667
|
/** Classify one inbound turn at the agent's CURRENT epoch for its channel. */
|
|
41102
|
-
async authorizeTurn(channelId, messageId, senderId, ownerVerdict, relaySpeakerId, voiceScoped) {
|
|
41668
|
+
async authorizeTurn(channelId, messageId, senderId, ownerVerdict, relaySpeakerId, voiceScoped, voiceParent) {
|
|
41103
41669
|
const held = await this.heldPosition(channelId);
|
|
41104
|
-
const
|
|
41670
|
+
const keyHeld = voiceParent === void 0 ? held : await this.heldPosition(voiceParent);
|
|
41671
|
+
const auth = this.authorizationFor(voiceParent ?? channelId, senderId, relaySpeakerId, keyHeld.epoch, keyHeld.groupId, {
|
|
41672
|
+
ownerVerdict,
|
|
41673
|
+
voiceScoped,
|
|
41674
|
+
...voiceParent === void 0 ? {} : { voice: { channelId, heldEpoch: held.epoch, heldGroupId: held.groupId } }
|
|
41675
|
+
});
|
|
41105
41676
|
if (!(auth.audience === "team_room" && auth.tool_grants.length > 0)) return auth;
|
|
41106
41677
|
if (this.roomGrantEpochs.size >= ROOM_GRANT_TRACK_MAX || held.epoch === null || held.groupId === null) {
|
|
41107
41678
|
writeServiceLog("warn", "room_grant.untracked");
|
|
@@ -41154,7 +41725,10 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
41154
41725
|
const meta = this.freshMeta(channelId);
|
|
41155
41726
|
const entry = this.freshRosterEntry(channelId);
|
|
41156
41727
|
const roster = entry?.members;
|
|
41157
|
-
const
|
|
41728
|
+
const vEntry = opts.voice === void 0 ? void 0 : this.freshRosterEntry(opts.voice.channelId);
|
|
41729
|
+
const vSender = vEntry?.members.get(senderId);
|
|
41730
|
+
const sender = opts.voice === void 0 ? roster?.get(senderId) : vSender === void 0 ? void 0 : roster?.get(senderId) ?? vSender;
|
|
41731
|
+
const senderIsRelay = opts.voice === void 0 ? sender?.is_relay === true : vSender?.is_relay === true;
|
|
41158
41732
|
let audience = "unknown";
|
|
41159
41733
|
if (meta && sender) {
|
|
41160
41734
|
if (sender.is_agent) audience = "agent_to_agent";
|
|
@@ -41166,15 +41740,15 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
41166
41740
|
let tool_grants = NO_TOOL_GRANTS;
|
|
41167
41741
|
let relaySpeaker;
|
|
41168
41742
|
let memoryWrite = false;
|
|
41169
|
-
const epochFresh = rosterFreshAt(entry, heldEpoch, heldGroupId);
|
|
41170
|
-
const ownerDmFresh = epochFresh || this.ownerDmCycleFresh.has(channelId);
|
|
41743
|
+
const epochFresh = rosterFreshAt(entry, heldEpoch, heldGroupId) && (opts.voice === void 0 || rosterFreshAt(vEntry, opts.voice.heldEpoch, opts.voice.heldGroupId));
|
|
41744
|
+
const ownerDmFresh = epochFresh || opts.voice === void 0 && this.ownerDmCycleFresh.has(channelId);
|
|
41171
41745
|
if (ownerDmFresh && this.ownerDmDefaultGrants && meta?.is_dm === true && sender) {
|
|
41172
41746
|
const pinned = this.channelOwner.get(channelId);
|
|
41173
41747
|
const ownerId = pinned && pinned !== "anomalous" ? pinned.identityId : void 0;
|
|
41174
41748
|
if (ownerId !== void 0) {
|
|
41175
41749
|
const ownerDirect = audience === "owner_dm" && senderId === ownerId;
|
|
41176
41750
|
const owner = roster?.get(ownerId);
|
|
41177
|
-
const ownerRelayed = relaySpeakerId !== void 0 &&
|
|
41751
|
+
const ownerRelayed = relaySpeakerId !== void 0 && senderIsRelay && relaySpeakerId === ownerId && owner !== void 0 && !owner.is_agent && owner.is_agent_owner;
|
|
41178
41752
|
if (ownerDirect) tool_grants = opts.voiceScoped === true ? RELAYED_VOICE_GRANTS : OWNER_DM_DEFAULT_READ_GRANTS;
|
|
41179
41753
|
if (ownerRelayed) tool_grants = RELAYED_VOICE_GRANTS;
|
|
41180
41754
|
if (ownerRelayed) relaySpeaker = "owner";
|
|
@@ -41853,6 +42427,9 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
41853
42427
|
async ingestCycleBody(receiptIds) {
|
|
41854
42428
|
if (this.closingDown) return;
|
|
41855
42429
|
const cycleStart = performance.now();
|
|
42430
|
+
let voiceListed = false;
|
|
42431
|
+
const cycleStartedAt = Date.now();
|
|
42432
|
+
const joinedInPhase0 = /* @__PURE__ */ new Set();
|
|
41856
42433
|
try {
|
|
41857
42434
|
const rows = await this.latency.time("recv.pull", () => this.backend.pullDelivery(this.cfg.deviceId));
|
|
41858
42435
|
this.recordRecvLag(rows);
|
|
@@ -41862,9 +42439,11 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
41862
42439
|
}
|
|
41863
42440
|
for (const cid of channelIds) {
|
|
41864
42441
|
if (this.joinState.get(cid) !== "JOINED") {
|
|
42442
|
+
joinedInPhase0.add(cid);
|
|
41865
42443
|
await this.reconcileJoin(cid);
|
|
41866
42444
|
}
|
|
41867
42445
|
}
|
|
42446
|
+
if (await this.resolveUnlistedChannels(channelIds, false)) voiceListed = true;
|
|
41868
42447
|
for (const cid of channelIds) {
|
|
41869
42448
|
if (this.freshRoster(cid) === void 0) {
|
|
41870
42449
|
await this.refreshChannelRoster(cid);
|
|
@@ -41889,6 +42468,13 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
41889
42468
|
}
|
|
41890
42469
|
} catch {
|
|
41891
42470
|
}
|
|
42471
|
+
const voiceStamp = await this.voicePositionSnapshot();
|
|
42472
|
+
const receiveStartedAt = Date.now();
|
|
42473
|
+
const stampsFor = (m) => {
|
|
42474
|
+
if (m.receivedAt >= receiveStartedAt) return true;
|
|
42475
|
+
const p = this.voiceChannels.parentOf(m.channelId);
|
|
42476
|
+
return m.receivedAt >= cycleStartedAt && p !== void 0 && !joinedInPhase0.has(p);
|
|
42477
|
+
};
|
|
41892
42478
|
let entries;
|
|
41893
42479
|
try {
|
|
41894
42480
|
entries = await this.latency.time("recv.decrypt", () => this.provider.receiveOnce(this.cfg.deviceId));
|
|
@@ -41898,7 +42484,10 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
41898
42484
|
for (const cid of entries.supersededChannelIds ?? []) {
|
|
41899
42485
|
this.joinState.set(cid, "JOINED");
|
|
41900
42486
|
}
|
|
41901
|
-
if (!entries.length)
|
|
42487
|
+
if (!entries.length) {
|
|
42488
|
+
await this.reevaluateVoiceBindings();
|
|
42489
|
+
return;
|
|
42490
|
+
}
|
|
41902
42491
|
await this.maybePruneOwnerReplay();
|
|
41903
42492
|
const receivedAt = Date.now();
|
|
41904
42493
|
const purgeEpochAtReceipt = /* @__PURE__ */ new Map();
|
|
@@ -41911,16 +42500,31 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
41911
42500
|
if (this.unifiedOn && !itemCaptureAtReceipt.has(m.channelId)) itemCaptureAtReceipt.set(m.channelId, this.store.itemCapture(m.channelId));
|
|
41912
42501
|
this.recallReceipt.set(m.messageId, { purgeEpoch: purgeEpochAtReceipt.get(m.channelId), modeEpoch: recallModeEpochAtReceipt });
|
|
41913
42502
|
receiptIds.push(m.messageId);
|
|
42503
|
+
const stamp = voiceStamp.get(m.channelId);
|
|
42504
|
+
if (stamp !== void 0) this.voiceChannels.noteReceipt(m.messageId, stampsFor(m) ? stamp : null);
|
|
42505
|
+
else if (this.voiceChannels.isVoice(m.channelId) || this.isUnlistedChannel(m.channelId) && !this.voiceChannels.isResolved(m.channelId)) {
|
|
42506
|
+
this.voiceChannels.noteReceipt(m.messageId, null);
|
|
42507
|
+
}
|
|
41914
42508
|
}
|
|
41915
|
-
|
|
41916
|
-
|
|
42509
|
+
if (await this.resolveUnlistedChannels(entries.map((m) => m.channelId), voiceListed)) voiceListed = true;
|
|
42510
|
+
await this.reevaluateVoiceBindings();
|
|
42511
|
+
entries = await this.gateVoiceEntries(entries);
|
|
42512
|
+
let metaRefreshed = voiceListed;
|
|
42513
|
+
if (!voiceListed && entries.some((m) => this.freshMeta(this.keyChannelForFrame(m)) === void 0)) {
|
|
41917
42514
|
await this.refreshChannelMeta();
|
|
41918
42515
|
metaRefreshed = true;
|
|
41919
42516
|
}
|
|
41920
|
-
await this.refreshRostersForCycle(entries.
|
|
42517
|
+
await this.refreshRostersForCycle(entries.flatMap((m) => {
|
|
42518
|
+
const key = this.keyChannelForFrame(m);
|
|
42519
|
+
return key === m.channelId ? [m.channelId] : [m.channelId, key];
|
|
42520
|
+
}));
|
|
41921
42521
|
const relayMissRefreshed = /* @__PURE__ */ new Set();
|
|
41922
42522
|
const acked = [];
|
|
41923
42523
|
for (const m of entries) {
|
|
42524
|
+
if (this.voiceChannels.isVoice(m.channelId) && !this.voiceFrameParent.has(m.messageId)) {
|
|
42525
|
+
acked.push(m.queueId);
|
|
42526
|
+
continue;
|
|
42527
|
+
}
|
|
41924
42528
|
try {
|
|
41925
42529
|
if (m.voiceMode === false) this.callBuffer.reset(m.channelId);
|
|
41926
42530
|
const encId = m.envelope.enclave_id;
|
|
@@ -42081,7 +42685,9 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
42081
42685
|
}
|
|
42082
42686
|
if (typeof parsed.text === "string") {
|
|
42083
42687
|
if (parsed.text.trim() === VOICE_CALL_START_MARKER) this.callBuffer.reset(m.channelId);
|
|
42084
|
-
const
|
|
42688
|
+
const keyChannel = this.keyChannelForFrame(m);
|
|
42689
|
+
const voiceParent = keyChannel !== m.channelId ? keyChannel : void 0;
|
|
42690
|
+
const meta = this.freshMeta(keyChannel);
|
|
42085
42691
|
const senderType = m.envelope.sender_type;
|
|
42086
42692
|
const senderIsAgent = senderType === "agent" ? true : senderType === "human" ? false : void 0;
|
|
42087
42693
|
const claimsRelaySpeaker = parsed.relay === true && typeof parsed.speaker_identity_id === "string" && typeof parsed.speaker_display_name === "string";
|
|
@@ -42128,14 +42734,15 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
42128
42734
|
senderIsRelay: this.freshRoster(m.channelId)?.get(m.envelope.sender_id)?.is_relay === true,
|
|
42129
42735
|
text: parsed.text
|
|
42130
42736
|
});
|
|
42131
|
-
const ownerVerdict = await this.verifyOwnerTurn(m);
|
|
42737
|
+
const ownerVerdict = await this.verifyOwnerTurn(m, keyChannel);
|
|
42132
42738
|
const turnAuthorization = await this.authorizeTurn(
|
|
42133
42739
|
m.channelId,
|
|
42134
42740
|
m.messageId,
|
|
42135
42741
|
m.envelope.sender_id,
|
|
42136
42742
|
ownerVerdict,
|
|
42137
42743
|
speaker?.identityId,
|
|
42138
|
-
voiceScoped
|
|
42744
|
+
voiceScoped,
|
|
42745
|
+
voiceParent
|
|
42139
42746
|
);
|
|
42140
42747
|
await this.prepareNotesForTurn(m.channelId, turnAuthorization);
|
|
42141
42748
|
const voiceGuidance = m.voiceMode ? selectVoiceGuidance(meta?.is_dm, m.voiceDefaultResponder) : void 0;
|
|
@@ -42162,11 +42769,12 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
42162
42769
|
}
|
|
42163
42770
|
let fastPathRoomBlocked = false;
|
|
42164
42771
|
if (!coalescible && fastPathEligible && meta && meta.is_dm === false) {
|
|
42165
|
-
const
|
|
42166
|
-
|
|
42772
|
+
const floorKey = this.capChannelFor(m.channelId);
|
|
42773
|
+
const reserved = this.fastReplyReserved.get(floorKey) ?? 0;
|
|
42774
|
+
if (this.rateFloor.available(`room:${floorKey}`) - reserved < 2) fastPathRoomBlocked = true;
|
|
42167
42775
|
}
|
|
42168
42776
|
if (fastPathRoomBlocked) this.fastPathSkipped++;
|
|
42169
|
-
const fastPathFiring = fastPathEligible && !fastPathRoomBlocked && !this.
|
|
42777
|
+
const fastPathFiring = fastPathEligible && !fastPathRoomBlocked && !this.pausedFor(m.channelId);
|
|
42170
42778
|
const fastGreeting = isCallStart && fastPathFiring;
|
|
42171
42779
|
const runtimeVoice = (ackSent) => m.voiceMode ? {
|
|
42172
42780
|
voice: true,
|
|
@@ -42272,11 +42880,12 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
42272
42880
|
void recordInboundMemory(this.memory, channelId, senderId, text, firstId, "voice", win.purgeEpoch);
|
|
42273
42881
|
let roomBlocked = false;
|
|
42274
42882
|
if (fastPathEligible && isRoom) {
|
|
42275
|
-
const
|
|
42276
|
-
|
|
42883
|
+
const floorKey = this.capChannelFor(channelId);
|
|
42884
|
+
const reserved = this.fastReplyReserved.get(floorKey) ?? 0;
|
|
42885
|
+
if (this.rateFloor.available(`room:${floorKey}`) - reserved < 2) roomBlocked = true;
|
|
42277
42886
|
}
|
|
42278
42887
|
if (roomBlocked) this.fastPathSkipped++;
|
|
42279
|
-
const plan = fastPathEligible && !roomBlocked && !this.
|
|
42888
|
+
const plan = fastPathEligible && !roomBlocked && !this.pausedFor(channelId) ? this.planFastPath(firstId, text, false) : void 0;
|
|
42280
42889
|
if (!plan || plan.kind === "dup" || plan.kind === "skip") {
|
|
42281
42890
|
if (plan?.kind === "skip" && plan.full) this.fastPathSkipped++;
|
|
42282
42891
|
await this.settleHeldRuntimeMessage(merged, true);
|
|
@@ -42380,6 +42989,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
42380
42989
|
}
|
|
42381
42990
|
}
|
|
42382
42991
|
for (const m of entries) this.recallReceipt.delete(m.messageId);
|
|
42992
|
+
for (const m of entries) this.voiceFrameParent.delete(m.messageId);
|
|
42383
42993
|
if (acked.length) {
|
|
42384
42994
|
try {
|
|
42385
42995
|
await this.latency.time("recv.ack", () => this.provider.ackDelivery(this.cfg.deviceId, acked));
|
|
@@ -42660,8 +43270,9 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
42660
43270
|
}
|
|
42661
43271
|
}
|
|
42662
43272
|
/** Spec §7.5 (T-14c, M-4): the runtime reports an executed-call cap for a room. Never throws. */
|
|
42663
|
-
noteRoomCapReached(
|
|
42664
|
-
if (typeof
|
|
43273
|
+
noteRoomCapReached(reported) {
|
|
43274
|
+
if (typeof reported !== "string" || this.closingDown) return;
|
|
43275
|
+
const channelId = this.capChannelFor(reported);
|
|
42665
43276
|
if (this.roomSearchDailyCap === 0) return;
|
|
42666
43277
|
if (this.freshMeta(channelId)?.is_dm !== false || this.channelRoster.get(channelId)?.crossOrg !== false) return;
|
|
42667
43278
|
const day = utcDayOf(Date.now());
|
|
@@ -42718,8 +43329,9 @@ ${channelId}`;
|
|
|
42718
43329
|
const a = entry.authorization;
|
|
42719
43330
|
if (!a || a.audience !== "team_room" || !a.tool_grants.some((g) => g.tool === "web_search")) return entry;
|
|
42720
43331
|
const day = utcDayOf(Date.now());
|
|
43332
|
+
const room = this.capChannelFor(entry.channel_id);
|
|
42721
43333
|
if (this.roomSearchDailyCap === 0) {
|
|
42722
|
-
this.logRoomCap(day,
|
|
43334
|
+
this.logRoomCap(day, room, "operator");
|
|
42723
43335
|
return this.withoutWebSearch(entry);
|
|
42724
43336
|
}
|
|
42725
43337
|
if (this.agentRoomSearchDailyCap === 0) {
|
|
@@ -42727,9 +43339,9 @@ ${channelId}`;
|
|
|
42727
43339
|
return this.withoutWebSearch(entry);
|
|
42728
43340
|
}
|
|
42729
43341
|
try {
|
|
42730
|
-
const n = this.store.countRoomSearchGrant(
|
|
42731
|
-
if (n === this.roomSearchDailyCap && this.store.markRoomSearchCapReached(
|
|
42732
|
-
this.logRoomCap(day,
|
|
43342
|
+
const n = this.store.countRoomSearchGrant(room, day);
|
|
43343
|
+
if (n === this.roomSearchDailyCap && this.store.markRoomSearchCapReached(room, day, "bridge")) {
|
|
43344
|
+
this.logRoomCap(day, room, "bridge");
|
|
42733
43345
|
}
|
|
42734
43346
|
if (n > this.roomSearchDailyCap) return this.withoutWebSearch(entry);
|
|
42735
43347
|
const total = this.store.countRoomSearchGrant(AGENT_TOTAL_KEY, day);
|
|
@@ -42826,9 +43438,15 @@ ${channelId}`;
|
|
|
42826
43438
|
* PAUSE_RECHECK_MS window, so release does not depend on new traffic.
|
|
42827
43439
|
*/
|
|
42828
43440
|
heldForPause(channelId) {
|
|
42829
|
-
|
|
43441
|
+
let key = channelId;
|
|
43442
|
+
let at = this.pausedChannels.get(channelId);
|
|
43443
|
+
const parent3 = at === void 0 ? this.voiceChannels.parentOf(channelId) : void 0;
|
|
43444
|
+
if (parent3 !== void 0) {
|
|
43445
|
+
key = parent3;
|
|
43446
|
+
at = this.pausedChannels.get(parent3);
|
|
43447
|
+
}
|
|
42830
43448
|
if (at === void 0) return false;
|
|
42831
|
-
this.schedulePauseRecheck(
|
|
43449
|
+
this.schedulePauseRecheck(key, at);
|
|
42832
43450
|
return true;
|
|
42833
43451
|
}
|
|
42834
43452
|
schedulePauseRecheck(channelId, confirmedAt) {
|
|
@@ -42857,7 +43475,7 @@ ${channelId}`;
|
|
|
42857
43475
|
if (await this.probeOwnPause(channelId) === true) {
|
|
42858
43476
|
const now = Date.now();
|
|
42859
43477
|
this.pausedChannels.set(channelId, now);
|
|
42860
|
-
if (this.buffer.some((m) => m.channel_id === channelId)) this.schedulePauseRecheck(channelId, now);
|
|
43478
|
+
if (this.buffer.some((m) => m.channel_id === channelId || this.voiceChannels.parentOf(m.channel_id) === channelId)) this.schedulePauseRecheck(channelId, now);
|
|
42861
43479
|
return;
|
|
42862
43480
|
}
|
|
42863
43481
|
this.clearPause(channelId);
|
|
@@ -43336,6 +43954,10 @@ ${channelId}`;
|
|
|
43336
43954
|
custody_store_unavailable: this.provider.custodyHealth().storeUnavailable,
|
|
43337
43955
|
failed_attachments: this.failedAttachments,
|
|
43338
43956
|
refused_vault_refs: this.refusedVaultRefs,
|
|
43957
|
+
voice_frames_refused: this.voiceFramesRefused,
|
|
43958
|
+
voice_frames_filtered: this.voiceFramesFiltered,
|
|
43959
|
+
voice_receipt_cap_reached: this.voiceChannels.receiptCapReached(),
|
|
43960
|
+
voice_relay_in_parent: this.voiceChannels.relayInParentCount(),
|
|
43339
43961
|
fast_path_sent: this.fastPathSent,
|
|
43340
43962
|
fast_path_failed: this.fastPathFailed,
|
|
43341
43963
|
fast_path_skipped: this.fastPathSkipped,
|
|
@@ -45999,7 +46621,7 @@ var PIN_COPY = Object.freeze({
|
|
|
45999
46621
|
usage: "usage: agent-bridge pin --credential <path> --file <owner-signing-key.json> --agent <agent_identity_id>"
|
|
46000
46622
|
// [COPY, approved by Eric 2026-10-12] (P-4)
|
|
46001
46623
|
});
|
|
46002
|
-
var
|
|
46624
|
+
var refuse2 = (reason) => ({ ok: false, reason });
|
|
46003
46625
|
function isSymlink(path) {
|
|
46004
46626
|
try {
|
|
46005
46627
|
return lstatSync8(path).isSymbolicLink();
|
|
@@ -46029,33 +46651,33 @@ function runPinCommand(opts) {
|
|
|
46029
46651
|
const credDir = dirname12(resolve2(opts.credentialPath));
|
|
46030
46652
|
const target = join23(credDir, PIN_FILE_NAME);
|
|
46031
46653
|
try {
|
|
46032
|
-
if (realpathSync2(dirname12(resolve2(opts.pinSourcePath))) !== realpathSync2(credDir)) return
|
|
46654
|
+
if (realpathSync2(dirname12(resolve2(opts.pinSourcePath))) !== realpathSync2(credDir)) return refuse2("directory_mismatch");
|
|
46033
46655
|
} catch {
|
|
46034
|
-
return
|
|
46656
|
+
return refuse2("io_error");
|
|
46035
46657
|
}
|
|
46036
46658
|
try {
|
|
46037
|
-
if (isSymlink(opts.credentialPath) || isSymlink(opts.pinSourcePath) || isSymlink(target)) return
|
|
46659
|
+
if (isSymlink(opts.credentialPath) || isSymlink(opts.pinSourcePath) || isSymlink(target)) return refuse2("symlink_refused");
|
|
46038
46660
|
} catch {
|
|
46039
|
-
return
|
|
46661
|
+
return refuse2("io_error");
|
|
46040
46662
|
}
|
|
46041
46663
|
let source;
|
|
46042
46664
|
try {
|
|
46043
46665
|
source = readJson(opts.pinSourcePath);
|
|
46044
46666
|
} catch {
|
|
46045
|
-
return
|
|
46667
|
+
return refuse2("pin_invalid");
|
|
46046
46668
|
}
|
|
46047
46669
|
const parsed = parsePinObject(source);
|
|
46048
|
-
if (!parsed.ok) return
|
|
46670
|
+
if (!parsed.ok) return refuse2("pin_invalid");
|
|
46049
46671
|
let cred;
|
|
46050
46672
|
try {
|
|
46051
46673
|
const c = readJson(opts.credentialPath);
|
|
46052
|
-
if (typeof c !== "object" || c === null || Array.isArray(c)) return
|
|
46674
|
+
if (typeof c !== "object" || c === null || Array.isArray(c)) return refuse2("credential_invalid");
|
|
46053
46675
|
cred = c;
|
|
46054
46676
|
} catch {
|
|
46055
|
-
return
|
|
46677
|
+
return refuse2("credential_invalid");
|
|
46056
46678
|
}
|
|
46057
|
-
for (const k of REQUIRED) if (typeof cred[k] !== "string" || !cred[k]) return
|
|
46058
|
-
if (cred.identity_id !== opts.agentIdentityId) return
|
|
46679
|
+
for (const k of REQUIRED) if (typeof cred[k] !== "string" || !cred[k]) return refuse2("credential_invalid");
|
|
46680
|
+
if (cred.identity_id !== opts.agentIdentityId) return refuse2("agent_mismatch");
|
|
46059
46681
|
opts.announce(cred.identity_id);
|
|
46060
46682
|
const newKey = parsed.pin.publicKeyB64;
|
|
46061
46683
|
let priorFileKey;
|
|
@@ -46080,7 +46702,7 @@ function runPinCommand(opts) {
|
|
|
46080
46702
|
} catch {
|
|
46081
46703
|
}
|
|
46082
46704
|
}
|
|
46083
|
-
return
|
|
46705
|
+
return refuse2("io_error");
|
|
46084
46706
|
};
|
|
46085
46707
|
const step = (s) => opts.afterStep?.(s);
|
|
46086
46708
|
try {
|
|
@@ -46588,7 +47210,7 @@ function realRunModeDeps(credentialPath, storePath) {
|
|
|
46588
47210
|
}
|
|
46589
47211
|
async function runMode(deps) {
|
|
46590
47212
|
const { credentialPath, storePath, log } = deps;
|
|
46591
|
-
const
|
|
47213
|
+
const refuse3 = (reason, extra, e) => {
|
|
46592
47214
|
const fields = {
|
|
46593
47215
|
reason: ServiceLogEnum.of(reason, [
|
|
46594
47216
|
"credential_not_found",
|
|
@@ -46620,18 +47242,18 @@ async function runMode(deps) {
|
|
|
46620
47242
|
cfg = loadConfig({ credentialPath, storePath });
|
|
46621
47243
|
daemon = parseDaemonArgs(deps.argv, deps.env);
|
|
46622
47244
|
} catch (e) {
|
|
46623
|
-
if (e instanceof ConfigError)
|
|
46624
|
-
else
|
|
47245
|
+
if (e instanceof ConfigError) refuse3(e.reason, { field: e.field, source: e.source }, e);
|
|
47246
|
+
else refuse3("unknown", {}, e);
|
|
46625
47247
|
return deps.exit(1);
|
|
46626
47248
|
}
|
|
46627
47249
|
try {
|
|
46628
47250
|
validateBind(daemon.bind, daemon.tls);
|
|
46629
47251
|
} catch (e) {
|
|
46630
|
-
|
|
47252
|
+
refuse3("bind_refused", {}, e);
|
|
46631
47253
|
return deps.exit(1);
|
|
46632
47254
|
}
|
|
46633
47255
|
if (!CANONICAL_UUID2.test(cfg.identityId)) {
|
|
46634
|
-
|
|
47256
|
+
refuse3("identity_id_invalid", { field: "identity_id" });
|
|
46635
47257
|
return deps.exit(1);
|
|
46636
47258
|
}
|
|
46637
47259
|
const lockDir = join25(deps.home, ".securecoms", "locks");
|