@securecoms/agent-bridge 0.34.0 → 0.36.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 +48 -3
- package/dist/attachments.d.ts +13 -0
- package/dist/bin.js +1974 -389
- package/dist/bridge-agent.d.ts +238 -9
- package/dist/consumer-grants.d.ts +28 -0
- package/dist/errors.d.ts +1 -1
- package/dist/grants/binding.d.ts +2 -1
- package/dist/grants/effective.d.ts +8 -0
- package/dist/grants/manifest.d.ts +14 -0
- package/dist/grants/mcp-catalog.d.ts +23 -0
- package/dist/grants/mcp-issuance.d.ts +34 -0
- package/dist/grants/mcp-policy.d.ts +75 -0
- package/dist/grants/mcp-section.d.ts +94 -0
- package/dist/grants/owner-turn.d.ts +40 -0
- package/dist/grants/tool-status.d.ts +79 -0
- package/dist/http-server.d.ts +1 -0
- package/dist/index.js +1910 -327
- package/dist/mcp-catalog-message.d.ts +79 -0
- package/dist/mcp-catalog-rate.d.ts +35 -0
- package/dist/outbox.d.ts +42 -0
- package/dist/reply-intent.d.ts +1 -1
- package/dist/service/runtime-config-dir.d.ts +55 -0
- package/dist/tool-status-sender.d.ts +71 -0
- package/dist/version.d.ts +1 -1
- package/dist/ws-server.d.ts +1 -1
- package/package.json +2 -1
package/dist/bin.js
CHANGED
|
@@ -3084,7 +3084,7 @@ function eddsa(Point, cHash, eddsaOpts = {}) {
|
|
|
3084
3084
|
if (outputLen !== expectedLen)
|
|
3085
3085
|
throw new Error(`hash.outputLen must be ${expectedLen}, got ${outputLen}`);
|
|
3086
3086
|
}
|
|
3087
|
-
const
|
|
3087
|
+
const randomBytes14 = opts.randomBytes === void 0 ? randomBytes4 : opts.randomBytes;
|
|
3088
3088
|
const adjustScalarBytes3 = opts.adjustScalarBytes === void 0 ? (bytes) => bytes : opts.adjustScalarBytes;
|
|
3089
3089
|
const domain = opts.domain === void 0 ? (data, ctx, phflag) => {
|
|
3090
3090
|
abool2(phflag, "phflag");
|
|
@@ -3170,7 +3170,7 @@ function eddsa(Point, cHash, eddsaOpts = {}) {
|
|
|
3170
3170
|
seed: _size
|
|
3171
3171
|
};
|
|
3172
3172
|
function randomSecretKey(seed) {
|
|
3173
|
-
seed = seed === void 0 ?
|
|
3173
|
+
seed = seed === void 0 ? randomBytes14(lengths.seed) : seed;
|
|
3174
3174
|
return abytes3(seed, lengths.seed, "seed");
|
|
3175
3175
|
}
|
|
3176
3176
|
function isValidSecretKey(key) {
|
|
@@ -6419,7 +6419,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
|
|
|
6419
6419
|
bits2int_modN: "function"
|
|
6420
6420
|
});
|
|
6421
6421
|
ecdsaOpts = Object.assign({}, ecdsaOpts);
|
|
6422
|
-
const
|
|
6422
|
+
const randomBytes14 = ecdsaOpts.randomBytes === void 0 ? randomBytes4 : ecdsaOpts.randomBytes;
|
|
6423
6423
|
const hmac5 = ecdsaOpts.hmac === void 0 ? (key, msg) => hmac(hash_, key, msg) : ecdsaOpts.hmac;
|
|
6424
6424
|
const { Fp: Fp3, Fn: Fn3 } = Point;
|
|
6425
6425
|
const { ORDER: CURVE_ORDER, BITS: fnBits } = Fn3;
|
|
@@ -6565,7 +6565,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
|
|
|
6565
6565
|
throw new Error("invalid private key");
|
|
6566
6566
|
const seedArgs = [int2octets(d), int2octets(h1int)];
|
|
6567
6567
|
if (extraEntropy != null && extraEntropy !== false) {
|
|
6568
|
-
const e = extraEntropy === true ?
|
|
6568
|
+
const e = extraEntropy === true ? randomBytes14(lengths.secretKey) : extraEntropy;
|
|
6569
6569
|
seedArgs.push(abytes3(e, void 0, "extraEntropy"));
|
|
6570
6570
|
}
|
|
6571
6571
|
const seed = concatBytes4(...seedArgs);
|
|
@@ -7416,37 +7416,37 @@ __export(layout_exports, {
|
|
|
7416
7416
|
presentWithSize: () => presentWithSize,
|
|
7417
7417
|
wrongTypeAt: () => wrongTypeAt
|
|
7418
7418
|
});
|
|
7419
|
-
import { chmodSync, lstatSync as
|
|
7419
|
+
import { chmodSync as chmodSync2, lstatSync as lstatSync3, mkdirSync as mkdirSync2, renameSync, rmSync } from "node:fs";
|
|
7420
7420
|
import { homedir as homedir2 } from "node:os";
|
|
7421
|
-
import { basename as
|
|
7421
|
+
import { basename as basename3, dirname as dirname5, join as join10 } from "node:path";
|
|
7422
7422
|
function kind(p) {
|
|
7423
7423
|
try {
|
|
7424
|
-
return
|
|
7424
|
+
return lstatSync3(p).isDirectory() ? "dir" : "other";
|
|
7425
7425
|
} catch (e) {
|
|
7426
7426
|
if (e.code === "ENOENT") return "absent";
|
|
7427
7427
|
throw e;
|
|
7428
7428
|
}
|
|
7429
7429
|
}
|
|
7430
7430
|
function plainDirChain(root3, parts, opts) {
|
|
7431
|
-
const modelsDir =
|
|
7431
|
+
const modelsDir = dirname5(root3);
|
|
7432
7432
|
for (const part of parts) if (!part || part === "." || part === ".." || part.includes("/") || part.includes("\\")) throw new LayoutRefused();
|
|
7433
7433
|
const k = kind(modelsDir);
|
|
7434
7434
|
if (k === "other") throw new LayoutRefused();
|
|
7435
7435
|
if (k === "absent") {
|
|
7436
7436
|
if (!opts.create) return null;
|
|
7437
|
-
|
|
7437
|
+
mkdirSync2(modelsDir, { recursive: true, mode: 448 });
|
|
7438
7438
|
if (kind(modelsDir) !== "dir") throw new LayoutRefused();
|
|
7439
7439
|
}
|
|
7440
|
-
const chain = opts.fromModelsDir ? parts : [
|
|
7440
|
+
const chain = opts.fromModelsDir ? parts : [basename3(root3), ...parts];
|
|
7441
7441
|
let p = modelsDir;
|
|
7442
7442
|
for (let i = 0; i < chain.length; i++) {
|
|
7443
|
-
p =
|
|
7443
|
+
p = join10(p, chain[i]);
|
|
7444
7444
|
const c = kind(p);
|
|
7445
7445
|
if (c === "other") throw new LayoutRefused();
|
|
7446
7446
|
if (c === "absent") {
|
|
7447
7447
|
if (!opts.create) return null;
|
|
7448
7448
|
try {
|
|
7449
|
-
|
|
7449
|
+
mkdirSync2(p, { mode: i === chain.length - 1 && opts.mode !== void 0 ? opts.mode : 493 });
|
|
7450
7450
|
} catch (e) {
|
|
7451
7451
|
if (e.code !== "EEXIST") throw e;
|
|
7452
7452
|
}
|
|
@@ -7457,7 +7457,7 @@ function plainDirChain(root3, parts, opts) {
|
|
|
7457
7457
|
}
|
|
7458
7458
|
function presentWithSize(p, size) {
|
|
7459
7459
|
try {
|
|
7460
|
-
const s =
|
|
7460
|
+
const s = lstatSync3(p);
|
|
7461
7461
|
return s.isFile() && s.size === size;
|
|
7462
7462
|
} catch {
|
|
7463
7463
|
return false;
|
|
@@ -7465,7 +7465,7 @@ function presentWithSize(p, size) {
|
|
|
7465
7465
|
}
|
|
7466
7466
|
function wrongTypeAt(p) {
|
|
7467
7467
|
try {
|
|
7468
|
-
const s =
|
|
7468
|
+
const s = lstatSync3(p);
|
|
7469
7469
|
return !s.isFile() && !s.isSymbolicLink();
|
|
7470
7470
|
} catch {
|
|
7471
7471
|
return false;
|
|
@@ -7478,16 +7478,16 @@ function installVerified(root3, modelId, f, partial, opts) {
|
|
|
7478
7478
|
dir = plainDirChain(root3, [modelId, f.sha256], { create: true });
|
|
7479
7479
|
if (!dir) throw new LayoutRefused();
|
|
7480
7480
|
if (!opts.replace && presentWithSize(dest, f.size)) return;
|
|
7481
|
-
|
|
7481
|
+
chmodSync2(dir, 493);
|
|
7482
7482
|
try {
|
|
7483
7483
|
renameSync(partial, dest);
|
|
7484
7484
|
} catch (e) {
|
|
7485
7485
|
if (!presentWithSize(dest, f.size)) throw e;
|
|
7486
7486
|
return;
|
|
7487
7487
|
}
|
|
7488
|
-
|
|
7488
|
+
chmodSync2(dest, 292);
|
|
7489
7489
|
} finally {
|
|
7490
|
-
if (dir)
|
|
7490
|
+
if (dir) chmodSync2(dir, 365);
|
|
7491
7491
|
rmSync(partial, { force: true });
|
|
7492
7492
|
}
|
|
7493
7493
|
}
|
|
@@ -7495,9 +7495,9 @@ var defaultModelsRoot, partialDir, filePath, LayoutRefused;
|
|
|
7495
7495
|
var init_layout = __esm({
|
|
7496
7496
|
"src/memory/embedder/layout.ts"() {
|
|
7497
7497
|
"use strict";
|
|
7498
|
-
defaultModelsRoot = (home = homedir2()) =>
|
|
7499
|
-
partialDir = (root3) =>
|
|
7500
|
-
filePath = (root3, modelId, f) =>
|
|
7498
|
+
defaultModelsRoot = (home = homedir2()) => join10(home, ".securecoms", "models", "embeddings");
|
|
7499
|
+
partialDir = (root3) => join10(dirname5(root3), ".partial");
|
|
7500
|
+
filePath = (root3, modelId, f) => join10(root3, modelId, f.sha256, f.name);
|
|
7501
7501
|
LayoutRefused = class extends Error {
|
|
7502
7502
|
constructor() {
|
|
7503
7503
|
super("the model directory is not a plain directory");
|
|
@@ -7555,13 +7555,13 @@ __export(import_bundle_exports, {
|
|
|
7555
7555
|
ModelImportError: () => ModelImportError,
|
|
7556
7556
|
importModelBundle: () => importModelBundle
|
|
7557
7557
|
});
|
|
7558
|
-
import { createHash as createHash6, randomBytes as
|
|
7558
|
+
import { createHash as createHash6, randomBytes as randomBytes13 } from "node:crypto";
|
|
7559
7559
|
import { readFileSync as readFileSync16, statSync as statSync7, writeFileSync as writeFileSync4 } from "node:fs";
|
|
7560
|
-
import { join as
|
|
7560
|
+
import { join as join25 } from "node:path";
|
|
7561
7561
|
async function importModelBundle(srcDir, root3, m) {
|
|
7562
7562
|
const verified = [];
|
|
7563
7563
|
for (const f of m.files) {
|
|
7564
|
-
const src =
|
|
7564
|
+
const src = join25(srcDir, f.name);
|
|
7565
7565
|
let size;
|
|
7566
7566
|
try {
|
|
7567
7567
|
const s = statSync7(src);
|
|
@@ -7585,7 +7585,7 @@ async function importModelBundle(srcDir, root3, m) {
|
|
|
7585
7585
|
for (const { f, bytes } of verified) {
|
|
7586
7586
|
try {
|
|
7587
7587
|
const pdir = plainDirChain(root3, [".partial"], { create: true, fromModelsDir: true, mode: 448 });
|
|
7588
|
-
const partial =
|
|
7588
|
+
const partial = join25(pdir, randomBytes13(12).toString("hex"));
|
|
7589
7589
|
writeFileSync4(partial, bytes, { mode: 384, flag: "wx" });
|
|
7590
7590
|
installVerified(root3, m.modelId, f, partial, { replace: true });
|
|
7591
7591
|
} catch (e) {
|
|
@@ -19161,8 +19161,8 @@ function sameRef(a, b) {
|
|
|
19161
19161
|
// ../connect-sdk-ts/dist/groupkey/own-removal.js
|
|
19162
19162
|
async function probeOwnRemoval(mgr, commitBytes) {
|
|
19163
19163
|
try {
|
|
19164
|
-
const
|
|
19165
|
-
if (typeof
|
|
19164
|
+
const own2 = mgr.state?.privatePath?.leafIndex;
|
|
19165
|
+
if (typeof own2 !== "number")
|
|
19166
19166
|
return null;
|
|
19167
19167
|
if (!hoistedTsMlsMatchesCrypto(mgr))
|
|
19168
19168
|
return null;
|
|
@@ -19187,7 +19187,7 @@ async function probeOwnRemoval(mgr, commitBytes) {
|
|
|
19187
19187
|
committer = typeof incoming.senderLeafIndex === "number" ? Number(incoming.senderLeafIndex) : null;
|
|
19188
19188
|
for (const p of incoming.proposals) {
|
|
19189
19189
|
const prop = p.proposal;
|
|
19190
|
-
if (prop.proposalType === "remove" && Number(prop.remove?.removed) ===
|
|
19190
|
+
if (prop.proposalType === "remove" && Number(prop.remove?.removed) === own2)
|
|
19191
19191
|
removesOwn = true;
|
|
19192
19192
|
}
|
|
19193
19193
|
}
|
|
@@ -19195,7 +19195,7 @@ async function probeOwnRemoval(mgr, commitBytes) {
|
|
|
19195
19195
|
}, cs);
|
|
19196
19196
|
if (!sawCommit || !removesOwn)
|
|
19197
19197
|
return null;
|
|
19198
|
-
return { ownLeafIndex:
|
|
19198
|
+
return { ownLeafIndex: own2, committerLeafIndex: committer };
|
|
19199
19199
|
} catch {
|
|
19200
19200
|
return null;
|
|
19201
19201
|
}
|
|
@@ -20057,6 +20057,33 @@ var MlsGroupKeyProvider = class _MlsGroupKeyProvider {
|
|
|
20057
20057
|
}
|
|
20058
20058
|
return { ownLeafIndex: this.ownLeafIndex(mgr), leaves };
|
|
20059
20059
|
}
|
|
20060
|
+
/**
|
|
20061
|
+
* MCP client (bridge Task 16; audit H-2, round 2 H-D): a SYNCHRONOUS, read-only snapshot of the WARM in-memory
|
|
20062
|
+
* manager for `channelId`: its group id, its epoch and EVERY non-blank leaf, reported exactly as
|
|
20063
|
+
* {@link leafTreeSummary} reports them (non-basic credentials included, identity null, #771 HIGH-B). All three are
|
|
20064
|
+
* read from ONE `state` object (MLSGroupManager replaces `state` whole on every commit), with no await, so they belong
|
|
20065
|
+
* to the same epoch and no commit can land between them.
|
|
20066
|
+
*
|
|
20067
|
+
* `null` when no manager is warm: it never loads from the store, never takes the channel lock, never touches the
|
|
20068
|
+
* network and never mutates. A caller treating `null` as "cannot tell" fails closed.
|
|
20069
|
+
*/
|
|
20070
|
+
leafSnapshotCached(channelId) {
|
|
20071
|
+
const state = this.managers.get(channelId)?.state;
|
|
20072
|
+
const tree = state?.ratchetTree;
|
|
20073
|
+
if (!state || !tree)
|
|
20074
|
+
return null;
|
|
20075
|
+
const leaves = [];
|
|
20076
|
+
const numLeaves = Math.floor((tree.length + 1) / 2);
|
|
20077
|
+
for (let i = 0; i < numLeaves; i++) {
|
|
20078
|
+
const node = tree[i * 2];
|
|
20079
|
+
if (!node || node.nodeType !== "leaf")
|
|
20080
|
+
continue;
|
|
20081
|
+
const cred = getCredentialFromLeafIndex(tree, toLeafIndex2(i));
|
|
20082
|
+
const basic = cred.credentialType === "basic";
|
|
20083
|
+
leaves.push({ leafIndex: i, basic, identity: basic ? new TextDecoder().decode(cred.identity) : null });
|
|
20084
|
+
}
|
|
20085
|
+
return { groupId: new TextDecoder().decode(state.groupContext.groupId), epoch: Number(state.groupContext.epoch), leaves };
|
|
20086
|
+
}
|
|
20060
20087
|
async fetchDeviceLeafCredential(deviceId) {
|
|
20061
20088
|
const kps = await this.o.backend.fetchKeyPackages([deviceId]);
|
|
20062
20089
|
const hex = kps[deviceId];
|
|
@@ -25474,8 +25501,8 @@ function openDeliveryPings(o) {
|
|
|
25474
25501
|
return Promise.resolve({ value: void 0, done: true });
|
|
25475
25502
|
if (queue.length)
|
|
25476
25503
|
return Promise.resolve({ value: queue.shift(), done: false });
|
|
25477
|
-
return new Promise((
|
|
25478
|
-
waiter =
|
|
25504
|
+
return new Promise((resolve5) => {
|
|
25505
|
+
waiter = resolve5;
|
|
25479
25506
|
});
|
|
25480
25507
|
}
|
|
25481
25508
|
};
|
|
@@ -25795,8 +25822,8 @@ var PingSubscriber = class {
|
|
|
25795
25822
|
return Promise.resolve({ value: void 0, done: true });
|
|
25796
25823
|
if (this.queue.length)
|
|
25797
25824
|
return Promise.resolve({ value: this.queue.shift(), done: false });
|
|
25798
|
-
return new Promise((
|
|
25799
|
-
this.waiter =
|
|
25825
|
+
return new Promise((resolve5) => {
|
|
25826
|
+
this.waiter = resolve5;
|
|
25800
25827
|
});
|
|
25801
25828
|
}
|
|
25802
25829
|
};
|
|
@@ -27390,10 +27417,12 @@ function openSchema(db) {
|
|
|
27390
27417
|
ensureRuntimeReplyIntent(db);
|
|
27391
27418
|
ensureRuntimeReplyIntentEnvelopeGroup(db);
|
|
27392
27419
|
ensureRuntimeReplyIntentCredit(db);
|
|
27420
|
+
ensureRuntimeReplyIntentMcpTurn(db);
|
|
27393
27421
|
ensureGroupLink(db);
|
|
27394
27422
|
ensureMlsCustody(db);
|
|
27395
27423
|
ensureToolGrantTables(db);
|
|
27396
27424
|
ensureToolGrantAppliedTable(db);
|
|
27425
|
+
ensureToolGrantMcpSeenTable(db);
|
|
27397
27426
|
ensureRoomSearchCapTable(db);
|
|
27398
27427
|
ensureVoiceChannelParentTable(db);
|
|
27399
27428
|
db.exec(`PRAGMA user_version = ${SCHEMA_VERSION}`);
|
|
@@ -27483,6 +27512,19 @@ function ensureRuntimeReplyIntentCredit(db) {
|
|
|
27483
27512
|
throw e;
|
|
27484
27513
|
}
|
|
27485
27514
|
}
|
|
27515
|
+
function ensureRuntimeReplyIntentMcpTurn(db) {
|
|
27516
|
+
const hasColumn = () => db.prepare("PRAGMA table_info(runtime_reply_intent)").all().some((c) => c.name === "mcp_turn");
|
|
27517
|
+
const cols = db.prepare("PRAGMA table_info(runtime_reply_intent)").all();
|
|
27518
|
+
if (cols.length === 0 || cols.some((c) => c.name === "mcp_turn"))
|
|
27519
|
+
return;
|
|
27520
|
+
try {
|
|
27521
|
+
db.exec("ALTER TABLE runtime_reply_intent ADD COLUMN mcp_turn INTEGER NOT NULL DEFAULT 0");
|
|
27522
|
+
} catch (e) {
|
|
27523
|
+
if (hasColumn())
|
|
27524
|
+
return;
|
|
27525
|
+
throw e;
|
|
27526
|
+
}
|
|
27527
|
+
}
|
|
27486
27528
|
var RECALL_STATE_COLUMNS = [
|
|
27487
27529
|
["recall_enabled", "INTEGER NOT NULL DEFAULT 0"],
|
|
27488
27530
|
["recall_mode_epoch", "INTEGER NOT NULL DEFAULT 0"],
|
|
@@ -27594,6 +27636,15 @@ function ensureToolGrantAppliedTable(db) {
|
|
|
27594
27636
|
);
|
|
27595
27637
|
`);
|
|
27596
27638
|
}
|
|
27639
|
+
function ensureToolGrantMcpSeenTable(db) {
|
|
27640
|
+
db.exec(`
|
|
27641
|
+
CREATE TABLE IF NOT EXISTS tool_grant_mcp_seen (
|
|
27642
|
+
identity_id TEXT PRIMARY KEY,
|
|
27643
|
+
sealed_state BLOB NOT NULL,
|
|
27644
|
+
updated_at INTEGER NOT NULL
|
|
27645
|
+
);
|
|
27646
|
+
`);
|
|
27647
|
+
}
|
|
27597
27648
|
function ensureVoiceChannelParentTable(db) {
|
|
27598
27649
|
db.exec(`
|
|
27599
27650
|
CREATE TABLE IF NOT EXISTS voice_channel_parent (
|
|
@@ -28534,8 +28585,8 @@ var SqliteMlsStateStore = class _SqliteMlsStateStore {
|
|
|
28534
28585
|
this.db.prepare("DELETE FROM runtime_reply_intent WHERE identity_id=? AND updated_at < ?").run(this.id, now - RUNTIME_REPLY_INTENT_RETENTION_MS);
|
|
28535
28586
|
const info = this.db.prepare(`INSERT OR IGNORE INTO runtime_reply_intent
|
|
28536
28587
|
(identity_id, scope, part, in_reply_to, channel_id, reply_message_id, body_mac,
|
|
28537
|
-
sealed_body, sealed_envelope, state, created_at, updated_at, reply_credit)
|
|
28538
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL, 'pending', ?, ?, ?)`).run(this.id, input.scope, input.part, input.inReplyTo, input.channelId, input.replyMessageId, input.bodyMac, sealed, now, now, input.replyCredit ?? null);
|
|
28588
|
+
sealed_body, sealed_envelope, state, created_at, updated_at, reply_credit, mcp_turn)
|
|
28589
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL, 'pending', ?, ?, ?, ?)`).run(this.id, input.scope, input.part, input.inReplyTo, input.channelId, input.replyMessageId, input.bodyMac, sealed, now, now, input.replyCredit ?? null, input.mcpTurn === true ? 1 : 0);
|
|
28539
28590
|
const count = this.db.prepare("SELECT COUNT(*) AS n FROM runtime_reply_intent WHERE identity_id=?").get(this.id);
|
|
28540
28591
|
if (count.n > RUNTIME_REPLY_INTENT_MAX) {
|
|
28541
28592
|
this.db.prepare(`DELETE FROM runtime_reply_intent
|
|
@@ -28545,7 +28596,7 @@ var SqliteMlsStateStore = class _SqliteMlsStateStore {
|
|
|
28545
28596
|
)`).run(this.id, this.id, RUNTIME_REPLY_INTENT_MAX);
|
|
28546
28597
|
}
|
|
28547
28598
|
const row = this.db.prepare(`SELECT scope, part, in_reply_to, channel_id, reply_message_id, body_mac,
|
|
28548
|
-
sealed_body, sealed_envelope, sealed_envelope_group, state, created_at, reply_credit
|
|
28599
|
+
sealed_body, sealed_envelope, sealed_envelope_group, state, created_at, reply_credit, mcp_turn
|
|
28549
28600
|
FROM runtime_reply_intent WHERE identity_id=? AND scope=? AND part=?`).get(this.id, input.scope, input.part);
|
|
28550
28601
|
if (!row)
|
|
28551
28602
|
throw new Error("reply intent vanished after claim");
|
|
@@ -28554,7 +28605,7 @@ var SqliteMlsStateStore = class _SqliteMlsStateStore {
|
|
|
28554
28605
|
/** SEC-06 #18: every intent answering `inReplyTo`, in claim order. */
|
|
28555
28606
|
async listReplyIntents(inReplyTo) {
|
|
28556
28607
|
const rows = this.db.prepare(`SELECT scope, part, in_reply_to, channel_id, reply_message_id, body_mac,
|
|
28557
|
-
sealed_body, sealed_envelope, sealed_envelope_group, state, created_at, reply_credit
|
|
28608
|
+
sealed_body, sealed_envelope, sealed_envelope_group, state, created_at, reply_credit, mcp_turn
|
|
28558
28609
|
FROM runtime_reply_intent WHERE identity_id=? AND in_reply_to=?
|
|
28559
28610
|
ORDER BY created_at ASC, rowid ASC`).all(this.id, inReplyTo);
|
|
28560
28611
|
const out = [];
|
|
@@ -28565,7 +28616,7 @@ var SqliteMlsStateStore = class _SqliteMlsStateStore {
|
|
|
28565
28616
|
/** SEC-06 #18: re-read one intent by its key (null when absent). */
|
|
28566
28617
|
async getReplyIntent(scope, part) {
|
|
28567
28618
|
const row = this.db.prepare(`SELECT scope, part, in_reply_to, channel_id, reply_message_id, body_mac,
|
|
28568
|
-
sealed_body, sealed_envelope, sealed_envelope_group, state, created_at, reply_credit
|
|
28619
|
+
sealed_body, sealed_envelope, sealed_envelope_group, state, created_at, reply_credit, mcp_turn
|
|
28569
28620
|
FROM runtime_reply_intent WHERE identity_id=? AND scope=? AND part=?`).get(this.id, scope, part);
|
|
28570
28621
|
return row ? this.openReplyIntent(row) : null;
|
|
28571
28622
|
}
|
|
@@ -28640,7 +28691,9 @@ var SqliteMlsStateStore = class _SqliteMlsStateStore {
|
|
|
28640
28691
|
envelopeGroupId: envelopeGroupFor(envelope, await openOrNull(row.sealed_envelope_group)),
|
|
28641
28692
|
state: row.state === "sent" ? "sent" : row.state === "skipped" ? "skipped" : "pending",
|
|
28642
28693
|
createdAt: Number(row.created_at),
|
|
28643
|
-
replyCredit: typeof row.reply_credit === "string" ? row.reply_credit : null
|
|
28694
|
+
replyCredit: typeof row.reply_credit === "string" ? row.reply_credit : null,
|
|
28695
|
+
// Any value other than exactly 1 (0, NULL, a missing column) is false; the bridge's MAC decides what a 1 means.
|
|
28696
|
+
mcpTurn: Number(row.mcp_turn ?? 0) === 1
|
|
28644
28697
|
};
|
|
28645
28698
|
}
|
|
28646
28699
|
async recordMemory(input) {
|
|
@@ -29212,10 +29265,10 @@ var SqliteMlsStateStore = class _SqliteMlsStateStore {
|
|
|
29212
29265
|
const r = this.db.prepare("SELECT owner_size_bytes AS b, owner_item_count AS n FROM agent_memory_state WHERE identity_id=?").get(this.id);
|
|
29213
29266
|
if (r && Number(r.b) >= 0 && Number(r.n) >= 0)
|
|
29214
29267
|
return { items: Number(r.n), bytes: Number(r.b) };
|
|
29215
|
-
const
|
|
29268
|
+
const own2 = this.scopeLoad("i.scope_kind='owner_dm'");
|
|
29216
29269
|
this.ensureStateRow();
|
|
29217
|
-
this.db.prepare("UPDATE agent_memory_state SET owner_size_bytes=?, owner_item_count=? WHERE identity_id=?").run(
|
|
29218
|
-
return
|
|
29270
|
+
this.db.prepare("UPDATE agent_memory_state SET owner_size_bytes=?, owner_item_count=? WHERE identity_id=?").run(own2.bytes, own2.items, this.id);
|
|
29271
|
+
return own2;
|
|
29219
29272
|
}
|
|
29220
29273
|
/** Inside a transaction: delete one origin group (the row, or the parent and its chunks) and its token rows; returns the ids and the accounts it held. */
|
|
29221
29274
|
deleteItemGroup(firstItemId) {
|
|
@@ -29389,8 +29442,8 @@ var SqliteMlsStateStore = class _SqliteMlsStateStore {
|
|
|
29389
29442
|
const o2 = this.overCap();
|
|
29390
29443
|
if (!o2.overItems && !o2.overBytes)
|
|
29391
29444
|
break;
|
|
29392
|
-
const
|
|
29393
|
-
const protectOwner = (!o2.overItems ||
|
|
29445
|
+
const own2 = this.ownerLoad();
|
|
29446
|
+
const protectOwner = (!o2.overItems || own2.items < this.itemCap * OWNER_DM_FLOOR_SHARE) && (!o2.overBytes || own2.bytes < this.byteCap * OWNER_DM_FLOOR_SHARE);
|
|
29394
29447
|
const v = (protectOwner ? this.oldestAutoGroup({ notOwnerDm: true }) : void 0) ?? this.oldestAutoGroup({});
|
|
29395
29448
|
if (v === void 0 || !drop(v))
|
|
29396
29449
|
break;
|
|
@@ -29417,8 +29470,8 @@ var SqliteMlsStateStore = class _SqliteMlsStateStore {
|
|
|
29417
29470
|
FROM agent_memory_item i WHERE identity_id=?`).get(this.id);
|
|
29418
29471
|
const tk = this.db.prepare("SELECT COUNT(*) AS n FROM agent_memory_token WHERE identity_id=?").get(this.id);
|
|
29419
29472
|
const size = Number(it.b) + Number(it.rows) * ITEM_ROW_OVERHEAD_BYTES + Number(tk.n) * TOKEN_ROW_OVERHEAD_BYTES;
|
|
29420
|
-
const
|
|
29421
|
-
this.db.prepare("UPDATE agent_memory_state SET size_bytes=?, item_count=?, owner_size_bytes=?, owner_item_count=? WHERE identity_id=?").run(size, Number(it.n),
|
|
29473
|
+
const own2 = this.scopeLoad("i.scope_kind='owner_dm'");
|
|
29474
|
+
this.db.prepare("UPDATE agent_memory_state SET size_bytes=?, item_count=?, owner_size_bytes=?, owner_item_count=? WHERE identity_id=?").run(size, Number(it.n), own2.bytes, own2.items, this.id);
|
|
29422
29475
|
}
|
|
29423
29476
|
/** Inside a transaction: delete every unified row of the identity and move each channel's content epoch. */
|
|
29424
29477
|
deleteAllItems() {
|
|
@@ -30234,9 +30287,40 @@ var SqliteMlsStateStore = class _SqliteMlsStateStore {
|
|
|
30234
30287
|
dropToolGrantAppliedExcept(pinKeyB64) {
|
|
30235
30288
|
return Number(this.db.prepare("DELETE FROM tool_grant_applied WHERE identity_id=? AND pin_key_b64<>?").run(this.id, pinKeyB64).changes);
|
|
30236
30289
|
}
|
|
30290
|
+
// MCP client Phase 1, Task 14 (OQ-3 ruling item 3): the sticky R-3 flag, per agent identity.
|
|
30291
|
+
/**
|
|
30292
|
+
* False when never written. A row that does not open, or opens to anything but this identity's own record, THROWS:
|
|
30293
|
+
* the bridge then holds R-3 (fail closed). Never returns false for an unreadable row.
|
|
30294
|
+
*/
|
|
30295
|
+
async loadMcpSeen() {
|
|
30296
|
+
const row = this.db.prepare("SELECT sealed_state FROM tool_grant_mcp_seen WHERE identity_id=?").get(this.id);
|
|
30297
|
+
if (!row)
|
|
30298
|
+
return false;
|
|
30299
|
+
const v = await this.unsealJson(row.sealed_state);
|
|
30300
|
+
if (v === null || typeof v !== "object" || v.identity_id !== this.id || typeof v.seen !== "boolean") {
|
|
30301
|
+
throw new MlsStoreSealError("tool_grant_mcp_seen: unexpected record");
|
|
30302
|
+
}
|
|
30303
|
+
return v.seen;
|
|
30304
|
+
}
|
|
30305
|
+
/** The sealed value names its identity, so a blob copied to another agent's row does not read as that agent's. */
|
|
30306
|
+
async saveMcpSeen(seen) {
|
|
30307
|
+
const sealed = await this.sealJson({ identity_id: this.id, seen });
|
|
30308
|
+
this.db.prepare(`INSERT INTO tool_grant_mcp_seen (identity_id, sealed_state, updated_at) VALUES (?, ?, ?)
|
|
30309
|
+
ON CONFLICT(identity_id) DO UPDATE SET sealed_state = excluded.sealed_state, updated_at = excluded.updated_at`).run(this.id, sealed, Date.now());
|
|
30310
|
+
}
|
|
30237
30311
|
hasOwnerMessage(channelId, messageId) {
|
|
30238
30312
|
return this.db.prepare("SELECT 1 FROM owner_replay WHERE identity_id=? AND channel_id=? AND message_id=?").get(this.id, channelId, messageId) !== void 0;
|
|
30239
30313
|
}
|
|
30314
|
+
/**
|
|
30315
|
+
* MCP client Phase 1, Task 18 (audit M-3): the owner replay row for a verified owner message that is NEVER staged for
|
|
30316
|
+
* the runtime (an `mcp_catalog_request`), so a redelivery of it is classed `replay`, after a restart too. The same row
|
|
30317
|
+
* stageRuntimeInbox writes in its staging transaction; insert-if-absent; pruned by pruneOwnerReplay like any other.
|
|
30318
|
+
*/
|
|
30319
|
+
recordOwnerMessage(channelId, messageId, groupId, epoch, processedAt = Date.now()) {
|
|
30320
|
+
this.db.prepare(`INSERT OR IGNORE INTO owner_replay
|
|
30321
|
+
(identity_id, channel_id, message_id, group_id, epoch, processed_at)
|
|
30322
|
+
VALUES (?, ?, ?, ?, ?, ?)`).run(this.id, channelId, messageId, groupId, epoch, processedAt);
|
|
30323
|
+
}
|
|
30240
30324
|
isOwnerEpochClosed(channelId, groupId, epoch) {
|
|
30241
30325
|
return this.db.prepare("SELECT 1 FROM owner_replay_closed_epoch WHERE identity_id=? AND channel_id=? AND group_id=? AND epoch=?").get(this.id, channelId, groupId, epoch) !== void 0;
|
|
30242
30326
|
}
|
|
@@ -30410,8 +30494,9 @@ function decodeReplyBody(bytes) {
|
|
|
30410
30494
|
if (b?.kind === "a2a" && b.a2a && typeof b.a2a === "object") return b;
|
|
30411
30495
|
throw new Error("reply intent body is malformed");
|
|
30412
30496
|
}
|
|
30413
|
-
function replyBodyMac(key, replyId, channelId, body, credit) {
|
|
30414
|
-
|
|
30497
|
+
function replyBodyMac(key, replyId, channelId, body, credit, mcpTurn) {
|
|
30498
|
+
const label = mcpTurn === true ? ["reply-body", replyId, channelId, credit ?? null, "mcp_turn"] : credit === void 0 ? ["reply-body", replyId, channelId] : ["reply-body", replyId, channelId, credit];
|
|
30499
|
+
return createHmac("sha256", key).update(JSON.stringify(label)).update("\0").update(body).digest("hex");
|
|
30415
30500
|
}
|
|
30416
30501
|
function partOrdinal(part) {
|
|
30417
30502
|
const m = /^p:(\d+)$/.exec(part);
|
|
@@ -30493,8 +30578,96 @@ function loadPackageMeta(importMetaUrl) {
|
|
|
30493
30578
|
}
|
|
30494
30579
|
|
|
30495
30580
|
// src/bridge-agent.ts
|
|
30496
|
-
import { existsSync as existsSync5, promises as
|
|
30497
|
-
|
|
30581
|
+
import { existsSync as existsSync5, promises as fsp2, readFileSync as readFileSync11, statSync as statSync5 } from "node:fs";
|
|
30582
|
+
|
|
30583
|
+
// src/outbox.ts
|
|
30584
|
+
import { constants, mkdirSync, lstatSync as lstatSync2, chmodSync, realpathSync, promises as fsp } from "node:fs";
|
|
30585
|
+
import { basename, dirname as dirname4, join as join4, resolve } from "node:path";
|
|
30586
|
+
var VAULT_OUTBOX_DIRNAME = "vault-outbox";
|
|
30587
|
+
var PathOutsideOutboxError = class extends Error {
|
|
30588
|
+
constructor() {
|
|
30589
|
+
super("path_outside_outbox");
|
|
30590
|
+
this.name = "PathOutsideOutboxError";
|
|
30591
|
+
}
|
|
30592
|
+
};
|
|
30593
|
+
var POSIX = process.platform !== "win32";
|
|
30594
|
+
function ownedPrivateDir(st) {
|
|
30595
|
+
if (st.isSymbolicLink() || !st.isDirectory()) return false;
|
|
30596
|
+
if (!POSIX) return true;
|
|
30597
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
30598
|
+
if (uid !== void 0 && st.uid !== uid) return false;
|
|
30599
|
+
return (st.mode & 511) === 448;
|
|
30600
|
+
}
|
|
30601
|
+
function ensureOutbox(dir) {
|
|
30602
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
30603
|
+
let st = lstatSync2(dir);
|
|
30604
|
+
if (st.isSymbolicLink() || !st.isDirectory()) throw new PathOutsideOutboxError();
|
|
30605
|
+
const uid = POSIX && typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
30606
|
+
if (uid !== void 0 && st.uid !== uid) throw new PathOutsideOutboxError();
|
|
30607
|
+
if (POSIX && (st.mode & 511) !== 448) {
|
|
30608
|
+
chmodSync(dir, 448);
|
|
30609
|
+
st = lstatSync2(dir);
|
|
30610
|
+
}
|
|
30611
|
+
if (!ownedPrivateDir(st)) throw new PathOutsideOutboxError();
|
|
30612
|
+
return { dir, real: realpathSync(dir), dev: st.dev, ino: st.ino };
|
|
30613
|
+
}
|
|
30614
|
+
var NOFOLLOW = constants.O_NOFOLLOW ?? 0;
|
|
30615
|
+
var NONBLOCK = constants.O_NONBLOCK ?? 0;
|
|
30616
|
+
async function readOutboxFile(pin, callerPath) {
|
|
30617
|
+
if (!pin) throw new PathOutsideOutboxError();
|
|
30618
|
+
try {
|
|
30619
|
+
const st = await fsp.lstat(pin.dir);
|
|
30620
|
+
if (!ownedPrivateDir(st) || st.dev !== pin.dev || st.ino !== pin.ino) throw new PathOutsideOutboxError();
|
|
30621
|
+
} catch {
|
|
30622
|
+
throw new PathOutsideOutboxError();
|
|
30623
|
+
}
|
|
30624
|
+
const requested = resolve(pin.dir, callerPath);
|
|
30625
|
+
const name = basename(requested);
|
|
30626
|
+
let realParent;
|
|
30627
|
+
try {
|
|
30628
|
+
realParent = await fsp.realpath(dirname4(requested));
|
|
30629
|
+
} catch {
|
|
30630
|
+
throw new PathOutsideOutboxError();
|
|
30631
|
+
}
|
|
30632
|
+
if (realParent !== pin.real) throw new PathOutsideOutboxError();
|
|
30633
|
+
const target = join4(pin.real, name);
|
|
30634
|
+
const pre = await fsp.lstat(target);
|
|
30635
|
+
if (pre.isSymbolicLink() || !pre.isFile()) throw new PathOutsideOutboxError();
|
|
30636
|
+
let handle;
|
|
30637
|
+
try {
|
|
30638
|
+
handle = await fsp.open(target, constants.O_RDONLY | NOFOLLOW | NONBLOCK);
|
|
30639
|
+
} catch (e) {
|
|
30640
|
+
if (e.code === "ENOENT") throw e;
|
|
30641
|
+
throw new PathOutsideOutboxError();
|
|
30642
|
+
}
|
|
30643
|
+
try {
|
|
30644
|
+
const st = await handle.stat();
|
|
30645
|
+
if (!st.isFile() || st.dev !== pre.dev || st.ino !== pre.ino || st.nlink !== 1) {
|
|
30646
|
+
throw new PathOutsideOutboxError();
|
|
30647
|
+
}
|
|
30648
|
+
return { bytes: new Uint8Array(await handle.readFile()), path: target, dev: st.dev, ino: st.ino };
|
|
30649
|
+
} finally {
|
|
30650
|
+
await handle.close().catch(() => {
|
|
30651
|
+
});
|
|
30652
|
+
}
|
|
30653
|
+
}
|
|
30654
|
+
async function consumeOutboxFile(pin, read) {
|
|
30655
|
+
try {
|
|
30656
|
+
const dir = await fsp.lstat(pin.dir);
|
|
30657
|
+
if (!ownedPrivateDir(dir) || dir.dev !== pin.dev || dir.ino !== pin.ino) return false;
|
|
30658
|
+
if (await fsp.realpath(pin.dir) !== pin.real) return false;
|
|
30659
|
+
const target = join4(pin.real, basename(read.path));
|
|
30660
|
+
const st = await fsp.lstat(target);
|
|
30661
|
+
if (st.isSymbolicLink() || st.dev !== read.dev || st.ino !== read.ino) return false;
|
|
30662
|
+
await fsp.unlink(target);
|
|
30663
|
+
return true;
|
|
30664
|
+
} catch {
|
|
30665
|
+
return false;
|
|
30666
|
+
}
|
|
30667
|
+
}
|
|
30668
|
+
|
|
30669
|
+
// src/bridge-agent.ts
|
|
30670
|
+
import { join as join20, dirname as dirname8 } from "node:path";
|
|
30498
30671
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
30499
30672
|
|
|
30500
30673
|
// src/latency.ts
|
|
@@ -30942,11 +31115,21 @@ function pinCoverage(pinStripped, ownerMessage, coverTexts) {
|
|
|
30942
31115
|
}
|
|
30943
31116
|
|
|
30944
31117
|
// src/attachments.ts
|
|
30945
|
-
import { basename } from "node:path";
|
|
31118
|
+
import { basename as basename2 } from "node:path";
|
|
30946
31119
|
var VAULT_REF_MAX_SIZE_HINT = 100 * 1024 * 1024 - 16;
|
|
30947
31120
|
var VAULT_REF_MAX_NAME = 255;
|
|
30948
31121
|
var VAULT_REF_MAX_MIME = 127;
|
|
30949
31122
|
var CANONICAL_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
31123
|
+
var InvalidBlobIdError = class extends Error {
|
|
31124
|
+
constructor() {
|
|
31125
|
+
super("invalid_blob_id");
|
|
31126
|
+
this.name = "InvalidBlobIdError";
|
|
31127
|
+
}
|
|
31128
|
+
};
|
|
31129
|
+
function assertBlobId(blobId) {
|
|
31130
|
+
if (typeof blobId !== "string" || !CANONICAL_UUID.test(blobId)) throw new InvalidBlobIdError();
|
|
31131
|
+
return blobId;
|
|
31132
|
+
}
|
|
30950
31133
|
var VAULT_PIN = /^blake2b:[0-9a-f]{64}$/;
|
|
30951
31134
|
var HINT_FORBIDDEN = /[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/;
|
|
30952
31135
|
var MIME_TYPE = /^[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*\/[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*$/;
|
|
@@ -30985,7 +31168,7 @@ function toVaultRefEntry(body) {
|
|
|
30985
31168
|
return entry;
|
|
30986
31169
|
}
|
|
30987
31170
|
function sanitizeFilename(name, fallback) {
|
|
30988
|
-
const base =
|
|
31171
|
+
const base = basename2(name ?? "");
|
|
30989
31172
|
const cleaned = base.replace(/[/\\]/g, "").replace(/^\.+$/, "");
|
|
30990
31173
|
return cleaned.length > 0 ? cleaned : fallback;
|
|
30991
31174
|
}
|
|
@@ -31015,7 +31198,7 @@ function mimeForName(name) {
|
|
|
31015
31198
|
async function prepareAttachment(filePath2, caption, channelId, deps) {
|
|
31016
31199
|
await deps.ready();
|
|
31017
31200
|
const bytes = await deps.readFile(filePath2);
|
|
31018
|
-
const name =
|
|
31201
|
+
const name = basename2(filePath2);
|
|
31019
31202
|
const { encryptedData, fileKey, fileNonce, digest } = await deps.encryptFile(bytes);
|
|
31020
31203
|
const blobId = await deps.uploadBlob(encryptedData, channelId);
|
|
31021
31204
|
const body = {
|
|
@@ -31033,8 +31216,9 @@ async function prepareAttachment(filePath2, caption, channelId, deps) {
|
|
|
31033
31216
|
return body;
|
|
31034
31217
|
}
|
|
31035
31218
|
async function materializeAttachment(body, ctx, deps) {
|
|
31219
|
+
const blobId = assertBlobId(body.blobId);
|
|
31036
31220
|
await deps.ready();
|
|
31037
|
-
const ciphertext = await deps.downloadBlob(
|
|
31221
|
+
const ciphertext = await deps.downloadBlob(blobId);
|
|
31038
31222
|
const sha25612 = await deps.computeFileDigest(ciphertext);
|
|
31039
31223
|
if (sha25612 !== body.digest) {
|
|
31040
31224
|
throw new Error(`attachment digest mismatch for blob ${body.blobId}`);
|
|
@@ -31060,13 +31244,13 @@ function ensureSodiumReady2() {
|
|
|
31060
31244
|
// src/memory/hermes-session-map.ts
|
|
31061
31245
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
31062
31246
|
import { homedir } from "node:os";
|
|
31063
|
-
import { join as
|
|
31247
|
+
import { join as join5 } from "node:path";
|
|
31064
31248
|
function expandHome(p) {
|
|
31065
|
-
return p === "~" || p.startsWith("~/") ?
|
|
31249
|
+
return p === "~" || p.startsWith("~/") ? join5(homedir(), p.slice(1)) : p;
|
|
31066
31250
|
}
|
|
31067
31251
|
function resolveHermesSession(baseDir, conversationId) {
|
|
31068
31252
|
try {
|
|
31069
|
-
const path =
|
|
31253
|
+
const path = join5(expandHome(baseDir), "sessions", "sessions.json");
|
|
31070
31254
|
const map = JSON.parse(readFileSync3(path, "utf8"));
|
|
31071
31255
|
const entry = map[`agent:main:securecoms:dm:${conversationId}`];
|
|
31072
31256
|
const sid = entry?.session_id;
|
|
@@ -31078,11 +31262,11 @@ function resolveHermesSession(baseDir, conversationId) {
|
|
|
31078
31262
|
|
|
31079
31263
|
// src/memory/hermes-store.ts
|
|
31080
31264
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
31081
|
-
import { join as
|
|
31265
|
+
import { join as join6 } from "node:path";
|
|
31082
31266
|
var HermesStore = class {
|
|
31083
31267
|
dbPath;
|
|
31084
31268
|
constructor(baseDir) {
|
|
31085
|
-
this.dbPath =
|
|
31269
|
+
this.dbPath = join6(expandHome(baseDir), "state.db");
|
|
31086
31270
|
}
|
|
31087
31271
|
/** Recent-K conversational turns for a session, chronological. Fail-safe → []. */
|
|
31088
31272
|
readRecent(sessionId, limit) {
|
|
@@ -31224,10 +31408,10 @@ var HermesMemoryProvider = class {
|
|
|
31224
31408
|
|
|
31225
31409
|
// src/memory/openclaw-session-map.ts
|
|
31226
31410
|
import { readFileSync as readFileSync4 } from "node:fs";
|
|
31227
|
-
import { join as
|
|
31411
|
+
import { join as join7 } from "node:path";
|
|
31228
31412
|
function resolveOwnerDmSession(baseDir, agent) {
|
|
31229
31413
|
try {
|
|
31230
|
-
const path =
|
|
31414
|
+
const path = join7(expandHome(baseDir), "agents", agent, "sessions", "sessions.json");
|
|
31231
31415
|
const map = JSON.parse(readFileSync4(path, "utf8"));
|
|
31232
31416
|
let best = null;
|
|
31233
31417
|
for (const [key, entry] of Object.entries(map)) {
|
|
@@ -31245,11 +31429,11 @@ function resolveOwnerDmSession(baseDir, agent) {
|
|
|
31245
31429
|
|
|
31246
31430
|
// src/memory/openclaw-session-log.ts
|
|
31247
31431
|
import { readFileSync as readFileSync5 } from "node:fs";
|
|
31248
|
-
import { join as
|
|
31432
|
+
import { join as join8 } from "node:path";
|
|
31249
31433
|
function readRecentTurns(baseDir, agent, sessionId, k) {
|
|
31250
31434
|
let raw;
|
|
31251
31435
|
try {
|
|
31252
|
-
raw = readFileSync5(
|
|
31436
|
+
raw = readFileSync5(join8(expandHome(baseDir), "agents", agent, "sessions", `${sessionId}.jsonl`), "utf8");
|
|
31253
31437
|
} catch {
|
|
31254
31438
|
return [];
|
|
31255
31439
|
}
|
|
@@ -31280,7 +31464,7 @@ function readRecentTurns(baseDir, agent, sessionId, k) {
|
|
|
31280
31464
|
|
|
31281
31465
|
// src/memory/openclaw-workspace-facts.ts
|
|
31282
31466
|
import { readdirSync, readFileSync as readFileSync6, statSync as statSync2 } from "node:fs";
|
|
31283
|
-
import { join as
|
|
31467
|
+
import { join as join9 } from "node:path";
|
|
31284
31468
|
var DEFAULT_FACTS_BUDGET = 8e3;
|
|
31285
31469
|
var MEMORY_MD_CAP = 6e3;
|
|
31286
31470
|
var MIN_FRAGMENT = 200;
|
|
@@ -31291,7 +31475,7 @@ function resolveWorkspaceDir(baseDir, agent, override) {
|
|
|
31291
31475
|
const base = expandHome(baseDir);
|
|
31292
31476
|
const isMain2 = agent === "" || agent === "main";
|
|
31293
31477
|
try {
|
|
31294
|
-
const cfg = JSON.parse(readFileSync6(
|
|
31478
|
+
const cfg = JSON.parse(readFileSync6(join9(base, "openclaw.json"), "utf8"));
|
|
31295
31479
|
const agents = cfg?.agents;
|
|
31296
31480
|
if (agents && typeof agents === "object") {
|
|
31297
31481
|
const list = Array.isArray(agents.list) ? agents.list : [];
|
|
@@ -31304,7 +31488,7 @@ function resolveWorkspaceDir(baseDir, agent, override) {
|
|
|
31304
31488
|
}
|
|
31305
31489
|
} catch {
|
|
31306
31490
|
}
|
|
31307
|
-
return isMain2 ?
|
|
31491
|
+
return isMain2 ? join9(base, "workspace") : join9(base, `workspace-${agent}`);
|
|
31308
31492
|
}
|
|
31309
31493
|
var TRUNC = "\u2026[truncated]";
|
|
31310
31494
|
function headSlice(s, n) {
|
|
@@ -31334,7 +31518,7 @@ function readWorkspaceFacts(baseDir, agent, opts) {
|
|
|
31334
31518
|
let truncated = false;
|
|
31335
31519
|
let remaining = budget;
|
|
31336
31520
|
try {
|
|
31337
|
-
const r = readBounded(
|
|
31521
|
+
const r = readBounded(join9(dir, "MEMORY.md"));
|
|
31338
31522
|
if (r.oversize) truncated = true;
|
|
31339
31523
|
else if (r.text && r.text.trim()) {
|
|
31340
31524
|
const { text, clipped } = headSlice(r.text, Math.min(MEMORY_MD_CAP, budget));
|
|
@@ -31347,7 +31531,7 @@ ${TRUNC}` : ""}`);
|
|
|
31347
31531
|
} catch {
|
|
31348
31532
|
}
|
|
31349
31533
|
try {
|
|
31350
|
-
const dailies = readdirSync(
|
|
31534
|
+
const dailies = readdirSync(join9(dir, "memory")).filter((f) => DAILY_RE.test(f)).sort().reverse().slice(0, 2);
|
|
31351
31535
|
for (const f of dailies) {
|
|
31352
31536
|
if (remaining < MIN_FRAGMENT) {
|
|
31353
31537
|
truncated = true;
|
|
@@ -31355,7 +31539,7 @@ ${TRUNC}` : ""}`);
|
|
|
31355
31539
|
}
|
|
31356
31540
|
let r;
|
|
31357
31541
|
try {
|
|
31358
|
-
r = readBounded(
|
|
31542
|
+
r = readBounded(join9(dir, "memory", f));
|
|
31359
31543
|
} catch {
|
|
31360
31544
|
continue;
|
|
31361
31545
|
}
|
|
@@ -32018,8 +32202,8 @@ var MembershipPurgeSweeper = class {
|
|
|
32018
32202
|
const dms = /* @__PURE__ */ new Set();
|
|
32019
32203
|
for (const c of this.d.conversations()) {
|
|
32020
32204
|
if (stopping()) return { purged: [], held: 0, absent: 0 };
|
|
32021
|
-
const
|
|
32022
|
-
const channels =
|
|
32205
|
+
const own2 = this.d.enclaveOf(c);
|
|
32206
|
+
const channels = own2 === void 0 ? void 0 : listed.get(own2);
|
|
32023
32207
|
if (channels === void 0) continue;
|
|
32024
32208
|
if (channels.has(c)) {
|
|
32025
32209
|
this.d.clearAbsence(c);
|
|
@@ -32094,14 +32278,14 @@ function parsePayloadMemory(raw) {
|
|
|
32094
32278
|
|
|
32095
32279
|
// src/memory/embedder/client.ts
|
|
32096
32280
|
import { connect } from "node:net";
|
|
32097
|
-
import { existsSync as existsSync3, mkdirSync as
|
|
32281
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync9, rmSync as rmSync6, statSync as statSync3 } from "node:fs";
|
|
32098
32282
|
import { fileURLToPath } from "node:url";
|
|
32099
32283
|
|
|
32100
32284
|
// src/memory/embedder/download.ts
|
|
32101
32285
|
import { createHash as createHash2, randomBytes as randomBytes6 } from "node:crypto";
|
|
32102
|
-
import { chmodSync as
|
|
32286
|
+
import { chmodSync as chmodSync3, rmSync as rmSync2 } from "node:fs";
|
|
32103
32287
|
import { open } from "node:fs/promises";
|
|
32104
|
-
import { join as
|
|
32288
|
+
import { join as join11 } from "node:path";
|
|
32105
32289
|
init_layout();
|
|
32106
32290
|
var WRONG_TYPE_STRIKES = 3;
|
|
32107
32291
|
var wrongTypeStrikes = /* @__PURE__ */ new Map();
|
|
@@ -32117,8 +32301,8 @@ function removeModelFiles(root3, m) {
|
|
|
32117
32301
|
for (const f of m.files) {
|
|
32118
32302
|
const dir = plainDirChain(root3, [m.modelId, f.sha256], { create: false });
|
|
32119
32303
|
if (!dir) continue;
|
|
32120
|
-
|
|
32121
|
-
rmSync2(
|
|
32304
|
+
chmodSync3(dir, 493);
|
|
32305
|
+
rmSync2(join11(dir, f.name), { force: true });
|
|
32122
32306
|
}
|
|
32123
32307
|
}
|
|
32124
32308
|
var VerifyFailed = class extends Error {
|
|
@@ -32165,7 +32349,7 @@ async function fetchSameOrigin(url, origin, fetchImpl, signal, stallMs, abort) {
|
|
|
32165
32349
|
async function downloadOne(f, o) {
|
|
32166
32350
|
if (wrongTypeAt(filePath(o.root, o.modelId, f))) throw new WrongType("wrong type at model path");
|
|
32167
32351
|
const pdir = plainDirChain(o.root, [".partial"], { create: true, fromModelsDir: true, mode: 448 });
|
|
32168
|
-
const partial =
|
|
32352
|
+
const partial = join11(pdir, randomBytes6(12).toString("hex"));
|
|
32169
32353
|
const fh = await open(partial, "wx", 384);
|
|
32170
32354
|
const ac = new AbortController();
|
|
32171
32355
|
const abort = () => ac.abort();
|
|
@@ -32242,17 +32426,17 @@ async function ensureModelFiles(o) {
|
|
|
32242
32426
|
|
|
32243
32427
|
// src/memory/embedder/gc.ts
|
|
32244
32428
|
init_layout();
|
|
32245
|
-
import { chmodSync as
|
|
32246
|
-
import { join as
|
|
32429
|
+
import { chmodSync as chmodSync4, closeSync, constants as constants2, fstatSync, futimesSync, lstatSync as lstatSync4, openSync, readdirSync as readdirSync2, rmSync as rmSync3 } from "node:fs";
|
|
32430
|
+
import { join as join12 } from "node:path";
|
|
32247
32431
|
var THIRTY_DAYS = 30 * 864e5;
|
|
32248
32432
|
var SAFE_ID = /^[A-Za-z0-9_-]{1,128}$/;
|
|
32249
32433
|
function touchUserMarker(root3, modelId, identityId) {
|
|
32250
32434
|
if (!SAFE_ID.test(identityId)) throw new Error("invalid identity id for the model marker");
|
|
32251
32435
|
const dir = plainDirChain(root3, [modelId, ".users"], { create: true });
|
|
32252
|
-
const p =
|
|
32436
|
+
const p = join12(dir, identityId);
|
|
32253
32437
|
let fd;
|
|
32254
32438
|
try {
|
|
32255
|
-
fd = openSync(p,
|
|
32439
|
+
fd = openSync(p, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_NOFOLLOW | constants2.O_NONBLOCK, 420);
|
|
32256
32440
|
} catch {
|
|
32257
32441
|
throw new Error("the model marker is not a regular file");
|
|
32258
32442
|
}
|
|
@@ -32266,22 +32450,22 @@ function touchUserMarker(root3, modelId, identityId) {
|
|
|
32266
32450
|
}
|
|
32267
32451
|
function isRealDir(p) {
|
|
32268
32452
|
try {
|
|
32269
|
-
return
|
|
32453
|
+
return lstatSync4(p).isDirectory();
|
|
32270
32454
|
} catch {
|
|
32271
32455
|
return false;
|
|
32272
32456
|
}
|
|
32273
32457
|
}
|
|
32274
32458
|
function makeWritable(p) {
|
|
32275
32459
|
if (!isRealDir(p)) return;
|
|
32276
|
-
|
|
32277
|
-
for (const e of readdirSync2(p)) makeWritable(
|
|
32460
|
+
chmodSync4(p, 493);
|
|
32461
|
+
for (const e of readdirSync2(p)) makeWritable(join12(p, e));
|
|
32278
32462
|
}
|
|
32279
32463
|
function newestMarkerMs(users) {
|
|
32280
32464
|
if (!isRealDir(users)) return void 0;
|
|
32281
32465
|
let newest;
|
|
32282
32466
|
for (const u of readdirSync2(users)) {
|
|
32283
32467
|
try {
|
|
32284
|
-
const s =
|
|
32468
|
+
const s = lstatSync4(join12(users, u));
|
|
32285
32469
|
if (!s.isFile()) continue;
|
|
32286
32470
|
newest = newest === void 0 ? s.mtimeMs : Math.max(newest, s.mtimeMs);
|
|
32287
32471
|
} catch {
|
|
@@ -32294,9 +32478,9 @@ function gcModels(root3, keepModelIds, now, maxAgeMs = THIRTY_DAYS) {
|
|
|
32294
32478
|
const removed = [];
|
|
32295
32479
|
for (const modelId of readdirSync2(root3).sort()) {
|
|
32296
32480
|
if (modelId.startsWith(".") || keepModelIds.includes(modelId)) continue;
|
|
32297
|
-
const tree =
|
|
32481
|
+
const tree = join12(root3, modelId);
|
|
32298
32482
|
if (!isRealDir(tree)) continue;
|
|
32299
|
-
const lastUse = newestMarkerMs(
|
|
32483
|
+
const lastUse = newestMarkerMs(join12(tree, ".users")) ?? lstatSync4(tree).mtimeMs;
|
|
32300
32484
|
if (now - lastUse <= maxAgeMs) continue;
|
|
32301
32485
|
makeWritable(tree);
|
|
32302
32486
|
rmSync3(tree, { recursive: true, force: true });
|
|
@@ -32402,44 +32586,44 @@ var SealedChannel = class {
|
|
|
32402
32586
|
// src/memory/embedder/embedder-paths.ts
|
|
32403
32587
|
import { createHash as createHash3 } from "node:crypto";
|
|
32404
32588
|
import { homedir as homedir3 } from "node:os";
|
|
32405
|
-
import { join as
|
|
32589
|
+
import { join as join13 } from "node:path";
|
|
32406
32590
|
function embedderKey(m) {
|
|
32407
32591
|
const model = m.files.find((f) => f.name === "model.onnx");
|
|
32408
32592
|
return createHash3("sha256").update(`${EMBEDDER_PROTOCOL}\0${m.modelId}\0${model.sha256}\0${m.wasmSha256}`).digest("hex").slice(0, 16);
|
|
32409
32593
|
}
|
|
32410
|
-
var embedderRunDir = (home = homedir3()) =>
|
|
32594
|
+
var embedderRunDir = (home = homedir3()) => join13(home, ".securecoms", "run", "embedder");
|
|
32411
32595
|
var embedderPaths = (dir, key) => ({
|
|
32412
|
-
sock:
|
|
32413
|
-
token:
|
|
32414
|
-
lock:
|
|
32415
|
-
status:
|
|
32416
|
-
pid:
|
|
32596
|
+
sock: join13(dir, `${key}.sock`),
|
|
32597
|
+
token: join13(dir, `${key}.token`),
|
|
32598
|
+
lock: join13(dir, `${key}.spawn.lock`),
|
|
32599
|
+
status: join13(dir, `${key}.status`),
|
|
32600
|
+
pid: join13(dir, `${key}.pid`)
|
|
32417
32601
|
// E1 delta H1: the instance's lifetime lock
|
|
32418
32602
|
});
|
|
32419
32603
|
var SOCKET_PATH_MAX = 103;
|
|
32420
32604
|
var windowsRunDir = (env = process.env) => {
|
|
32421
32605
|
if (!env.LOCALAPPDATA) throw new Error("LOCALAPPDATA unset");
|
|
32422
|
-
return
|
|
32606
|
+
return join13(env.LOCALAPPDATA, "SecureComs", "run", "embedder");
|
|
32423
32607
|
};
|
|
32424
32608
|
|
|
32425
32609
|
// src/memory/embedder/spawn.ts
|
|
32426
32610
|
import { spawn } from "node:child_process";
|
|
32427
|
-
import { existsSync as existsSync2, mkdirSync as
|
|
32611
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync4, realpathSync as realpathSync2 } from "node:fs";
|
|
32428
32612
|
|
|
32429
32613
|
// src/memory/embedder/run-dir.ts
|
|
32430
|
-
import { existsSync, lstatSync as
|
|
32431
|
-
import { dirname as
|
|
32614
|
+
import { existsSync, lstatSync as lstatSync5, mkdirSync as mkdirSync3 } from "node:fs";
|
|
32615
|
+
import { dirname as dirname6 } from "node:path";
|
|
32432
32616
|
var RunDirRefused = class extends Error {
|
|
32433
32617
|
};
|
|
32434
|
-
function ensureRunDir(dir, uid = process.getuid?.() ?? -1, lstat =
|
|
32435
|
-
const parent3 =
|
|
32436
|
-
if (!existsSync(parent3))
|
|
32618
|
+
function ensureRunDir(dir, uid = process.getuid?.() ?? -1, lstat = lstatSync5) {
|
|
32619
|
+
const parent3 = dirname6(dir);
|
|
32620
|
+
if (!existsSync(parent3)) mkdirSync3(parent3, { recursive: true, mode: 448 });
|
|
32437
32621
|
const ps = lstat(parent3);
|
|
32438
32622
|
if (ps.isSymbolicLink() || !ps.isDirectory()) throw new RunDirRefused("run directory parent is not a real directory");
|
|
32439
32623
|
if (ps.uid !== uid) throw new RunDirRefused("run directory parent is not owned by this user");
|
|
32440
32624
|
if ((ps.mode & 18) !== 0) throw new RunDirRefused("run directory parent is group or world writable");
|
|
32441
32625
|
try {
|
|
32442
|
-
|
|
32626
|
+
mkdirSync3(dir, { mode: 448 });
|
|
32443
32627
|
} catch (e) {
|
|
32444
32628
|
if (e.code !== "EEXIST") throw e;
|
|
32445
32629
|
}
|
|
@@ -32450,11 +32634,11 @@ function ensureRunDir(dir, uid = process.getuid?.() ?? -1, lstat = lstatSync4) {
|
|
|
32450
32634
|
}
|
|
32451
32635
|
|
|
32452
32636
|
// src/memory/embedder/spawn.ts
|
|
32453
|
-
import { basename as
|
|
32637
|
+
import { basename as basename4, dirname as dirname7, join as join14 } from "node:path";
|
|
32454
32638
|
init_layout();
|
|
32455
32639
|
|
|
32456
32640
|
// src/memory/embedder/locks.ts
|
|
32457
|
-
import { closeSync as closeSync2, constants as
|
|
32641
|
+
import { closeSync as closeSync2, constants as constants3, fstatSync as fstatSync2, linkSync, openSync as openSync2, readFileSync as readFileSync7, renameSync as renameSync2, rmSync as rmSync4, writeSync } from "node:fs";
|
|
32458
32642
|
import { randomBytes as randomBytes9 } from "node:crypto";
|
|
32459
32643
|
import { uptime } from "node:os";
|
|
32460
32644
|
var LOCK_STALE_MS = 1e4;
|
|
@@ -32476,7 +32660,7 @@ var PROCESS_LOCK_ENV = {
|
|
|
32476
32660
|
function inspect(path) {
|
|
32477
32661
|
let fd;
|
|
32478
32662
|
try {
|
|
32479
|
-
fd = openSync2(path,
|
|
32663
|
+
fd = openSync2(path, constants3.O_RDONLY | constants3.O_NOFOLLOW);
|
|
32480
32664
|
} catch {
|
|
32481
32665
|
return null;
|
|
32482
32666
|
}
|
|
@@ -32500,7 +32684,7 @@ function takeLock(path, now = Date.now, env = PROCESS_LOCK_ENV) {
|
|
|
32500
32684
|
const id = `${env.pid} ${env.bootTime()} ${env.startTime()} ${randomBytes9(8).toString("hex")}`;
|
|
32501
32685
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
32502
32686
|
try {
|
|
32503
|
-
const fd = openSync2(path,
|
|
32687
|
+
const fd = openSync2(path, constants3.O_CREAT | constants3.O_EXCL | constants3.O_WRONLY, 384);
|
|
32504
32688
|
try {
|
|
32505
32689
|
writeSync(fd, id);
|
|
32506
32690
|
} finally {
|
|
@@ -32561,7 +32745,7 @@ function embedderEnv(env = process.env, platform = process.platform) {
|
|
|
32561
32745
|
}
|
|
32562
32746
|
var withReal = (p) => {
|
|
32563
32747
|
try {
|
|
32564
|
-
const r =
|
|
32748
|
+
const r = realpathSync2(p);
|
|
32565
32749
|
return r === p ? [p] : [p, r];
|
|
32566
32750
|
} catch {
|
|
32567
32751
|
return [p];
|
|
@@ -32569,10 +32753,10 @@ var withReal = (p) => {
|
|
|
32569
32753
|
};
|
|
32570
32754
|
var realish = (p) => {
|
|
32571
32755
|
try {
|
|
32572
|
-
return
|
|
32756
|
+
return realpathSync2(p);
|
|
32573
32757
|
} catch {
|
|
32574
|
-
const up =
|
|
32575
|
-
return up === p ? p :
|
|
32758
|
+
const up = dirname7(p);
|
|
32759
|
+
return up === p ? p : join14(realish(up), basename4(p));
|
|
32576
32760
|
}
|
|
32577
32761
|
};
|
|
32578
32762
|
var both = (p) => {
|
|
@@ -32584,7 +32768,7 @@ function spawnEmbedder(o) {
|
|
|
32584
32768
|
const run = o.runDir ?? embedderRunDir(o.home);
|
|
32585
32769
|
const win = (o.platform ?? process.platform) === "win32";
|
|
32586
32770
|
if (o.createRunDir !== false) {
|
|
32587
|
-
if (win)
|
|
32771
|
+
if (win) mkdirSync4(run, { recursive: true, mode: 448 });
|
|
32588
32772
|
else ensureRunDir(run);
|
|
32589
32773
|
}
|
|
32590
32774
|
const perm = permissionFlags();
|
|
@@ -32593,13 +32777,13 @@ function spawnEmbedder(o) {
|
|
|
32593
32777
|
o.onNoPermissionModel?.();
|
|
32594
32778
|
}
|
|
32595
32779
|
const reads = perm.length === 0 ? [] : [
|
|
32596
|
-
...withReal(
|
|
32780
|
+
...withReal(dirname7(o.distDir)),
|
|
32597
32781
|
...existsSync2(defaultModelsRoot(o.home)) ? withReal(defaultModelsRoot(o.home)) : [defaultModelsRoot(o.home)],
|
|
32598
|
-
...both(
|
|
32599
|
-
...win ? both(
|
|
32782
|
+
...both(dirname7(run)),
|
|
32783
|
+
...win ? both(join14(o.home, ".securecoms", "run")) : []
|
|
32600
32784
|
// the voice-call heartbeats (the run dir lives elsewhere)
|
|
32601
32785
|
];
|
|
32602
|
-
const script = withReal(
|
|
32786
|
+
const script = withReal(join14(o.distDir, "embedder.js")).at(-1);
|
|
32603
32787
|
const net = (o.nodeFlags ?? process.allowedNodeEnvironmentFlags).has("--allow-net") ? ["--allow-net"] : [];
|
|
32604
32788
|
const flags = perm.length === 0 ? [] : [...perm, ...reads.map((r) => `--allow-fs-read=${r}`), ...both(run).map((w) => `--allow-fs-write=${w}`), "--allow-worker", ...net];
|
|
32605
32789
|
const child = (o.spawnFn ?? spawn)(
|
|
@@ -32608,7 +32792,7 @@ function spawnEmbedder(o) {
|
|
|
32608
32792
|
// 11c review LOW 3, fixed by the Task 12 Mini gate: cwd is the run directory's PARENT, which the bridge created (0700)
|
|
32609
32793
|
// before spawning; the run directory itself is created by the embedder, so on a first start it does not exist yet and
|
|
32610
32794
|
// a cwd there fails the spawn with ENOENT. Never the bridge's working directory.
|
|
32611
|
-
{ detached: true, stdio: "ignore", windowsHide: true, cwd:
|
|
32795
|
+
{ detached: true, stdio: "ignore", windowsHide: true, cwd: dirname7(run), env: win ? { ...embedderEnv(process.env, "win32"), USERPROFILE: o.home, LOCALAPPDATA: dirname7(dirname7(dirname7(run))) } : { ...embedderEnv(), HOME: o.home } }
|
|
32612
32796
|
);
|
|
32613
32797
|
child.unref();
|
|
32614
32798
|
child.on("error", () => {
|
|
@@ -32620,8 +32804,8 @@ function spawnEmbedder(o) {
|
|
|
32620
32804
|
import { execFile } from "node:child_process";
|
|
32621
32805
|
import { promisify } from "node:util";
|
|
32622
32806
|
import { randomBytes as randomBytes10 } from "node:crypto";
|
|
32623
|
-
import { lstatSync as
|
|
32624
|
-
import { join as
|
|
32807
|
+
import { lstatSync as lstatSync6, readFileSync as readFileSync8, rmSync as rmSync5 } from "node:fs";
|
|
32808
|
+
import { join as join15, win32 } from "node:path";
|
|
32625
32809
|
var WINDOWS_SEMANTIC_ENABLED = false;
|
|
32626
32810
|
var windowsSemanticOn = (platform, enabled = WINDOWS_SEMANTIC_ENABLED) => platform !== "win32" || enabled;
|
|
32627
32811
|
function parseSaveSddl(saveFile) {
|
|
@@ -32649,7 +32833,7 @@ function aclAllows(sddl, ownerSid) {
|
|
|
32649
32833
|
if (aces.length === 0 || rest !== "" && !rest.startsWith("S:")) return false;
|
|
32650
32834
|
return aces.every((a) => a[0] === "A" && allowed.has(a[5] ?? "") || a[0] === "D");
|
|
32651
32835
|
}
|
|
32652
|
-
function hasReparsePoint(path, lstat =
|
|
32836
|
+
function hasReparsePoint(path, lstat = lstatSync6) {
|
|
32653
32837
|
const root3 = win32.parse(path).root;
|
|
32654
32838
|
let cur = root3;
|
|
32655
32839
|
for (const part of path.slice(root3.length).split(/[\\/]/).filter(Boolean)) {
|
|
@@ -32668,7 +32852,7 @@ async function currentUserSid(run = defaultRunner) {
|
|
|
32668
32852
|
}
|
|
32669
32853
|
async function verifyRunDirAcl(path, deps = {}, sid) {
|
|
32670
32854
|
const run = deps.run ?? defaultRunner;
|
|
32671
|
-
const out =
|
|
32855
|
+
const out = join15(deps.tmp ?? process.env.TEMP ?? process.env.TMP ?? ".", `sc-acl-${randomBytes10(8).toString("hex")}.txt`);
|
|
32672
32856
|
try {
|
|
32673
32857
|
if (hasReparsePoint(path, deps.lstat)) return false;
|
|
32674
32858
|
const user = await (sid ? sid() : currentUserSid(run));
|
|
@@ -32749,12 +32933,12 @@ var EmbedderClient = class {
|
|
|
32749
32933
|
ready(ms) {
|
|
32750
32934
|
if (this.st.state === "ready") return Promise.resolve(true);
|
|
32751
32935
|
if (this.st.state !== "preparing" || !(ms > 0)) return Promise.resolve(false);
|
|
32752
|
-
return new Promise((
|
|
32936
|
+
return new Promise((resolve5) => {
|
|
32753
32937
|
let t;
|
|
32754
32938
|
const w = (ok) => {
|
|
32755
32939
|
clearTimeout(t);
|
|
32756
32940
|
this.readyWaiters.delete(w);
|
|
32757
|
-
|
|
32941
|
+
resolve5(ok);
|
|
32758
32942
|
};
|
|
32759
32943
|
this.readyWaiters.add(w);
|
|
32760
32944
|
t = setTimeout(() => w(false), ms);
|
|
@@ -32863,7 +33047,7 @@ var EmbedderClient = class {
|
|
|
32863
33047
|
const p = this.paths();
|
|
32864
33048
|
if (!this.tcp() && Buffer.byteLength(`${p.sock}.00000000`) > SOCKET_PATH_MAX) return this.fail("run_dir_refused");
|
|
32865
33049
|
if (this.tcp()) {
|
|
32866
|
-
|
|
33050
|
+
mkdirSync5(this.runDir(), { recursive: true, mode: 448 });
|
|
32867
33051
|
if (!await this.aclOk(true)) return this.fail("acl_unverified");
|
|
32868
33052
|
}
|
|
32869
33053
|
if (!this.tcp()) {
|
|
@@ -32999,7 +33183,7 @@ var EmbedderClient = class {
|
|
|
32999
33183
|
}
|
|
33000
33184
|
if (this.closed) return false;
|
|
33001
33185
|
}
|
|
33002
|
-
return new Promise((
|
|
33186
|
+
return new Promise((resolve5) => {
|
|
33003
33187
|
const s = this.tcp() ? connect({ host: "127.0.0.1", port: pub.port }) : connect(p.sock);
|
|
33004
33188
|
const nc = nonce();
|
|
33005
33189
|
let ns = "";
|
|
@@ -33012,7 +33196,7 @@ var EmbedderClient = class {
|
|
|
33012
33196
|
if (!settled) {
|
|
33013
33197
|
settled = true;
|
|
33014
33198
|
clearTimeout(t);
|
|
33015
|
-
|
|
33199
|
+
resolve5(ok);
|
|
33016
33200
|
}
|
|
33017
33201
|
}
|
|
33018
33202
|
const refuse3 = (reason) => {
|
|
@@ -33581,12 +33765,12 @@ var ReembedQueue = class {
|
|
|
33581
33765
|
};
|
|
33582
33766
|
|
|
33583
33767
|
// src/memory/recall/call-heartbeat.ts
|
|
33584
|
-
import { closeSync as closeSync3, constants as
|
|
33585
|
-
import { join as
|
|
33768
|
+
import { closeSync as closeSync3, constants as constants4, fstatSync as fstatSync3, futimesSync as futimesSync2, openSync as openSync3, rmSync as rmSync7 } from "node:fs";
|
|
33769
|
+
import { join as join17 } from "node:path";
|
|
33586
33770
|
|
|
33587
33771
|
// src/memory/embedder/heartbeats.ts
|
|
33588
|
-
import { lstatSync as
|
|
33589
|
-
import { join as
|
|
33772
|
+
import { lstatSync as lstatSync7, readdirSync as readdirSync3 } from "node:fs";
|
|
33773
|
+
import { join as join16 } from "node:path";
|
|
33590
33774
|
var HEARTBEAT_FUTURE_SKEW_MS = 6e4;
|
|
33591
33775
|
var HEARTBEAT_STALE_MS = 3e4;
|
|
33592
33776
|
function heartbeatAgeFresh(ageMs, staleMs = HEARTBEAT_STALE_MS) {
|
|
@@ -33601,7 +33785,7 @@ function anyHeartbeatFresh(dir, now, staleMs = HEARTBEAT_STALE_MS) {
|
|
|
33601
33785
|
}
|
|
33602
33786
|
return names.some((n) => {
|
|
33603
33787
|
try {
|
|
33604
|
-
const st =
|
|
33788
|
+
const st = lstatSync7(join16(dir, n));
|
|
33605
33789
|
return st.isFile() && heartbeatAgeFresh(now - st.mtimeMs, staleMs);
|
|
33606
33790
|
} catch {
|
|
33607
33791
|
return false;
|
|
@@ -33667,7 +33851,7 @@ var CallHeartbeat = class {
|
|
|
33667
33851
|
if (dir === null) return;
|
|
33668
33852
|
let fd;
|
|
33669
33853
|
try {
|
|
33670
|
-
fd = openSync3(
|
|
33854
|
+
fd = openSync3(join17(dir, this.identityId), constants4.O_WRONLY | constants4.O_CREAT | constants4.O_NOFOLLOW | constants4.O_NONBLOCK, 384);
|
|
33671
33855
|
if (!fstatSync3(fd).isFile()) return;
|
|
33672
33856
|
const t = new Date(this.now());
|
|
33673
33857
|
futimesSync2(fd, t, t);
|
|
@@ -33682,7 +33866,7 @@ var CallHeartbeat = class {
|
|
|
33682
33866
|
const dir = SAFE_ID2.test(this.identityId) ? this.dirOk() : null;
|
|
33683
33867
|
if (dir === null) return;
|
|
33684
33868
|
try {
|
|
33685
|
-
rmSync7(
|
|
33869
|
+
rmSync7(join17(dir, this.identityId), { force: true });
|
|
33686
33870
|
this.wrote = false;
|
|
33687
33871
|
} catch (e) {
|
|
33688
33872
|
this.log("warn", "memory.call_heartbeat_failed", {}, e);
|
|
@@ -33745,9 +33929,9 @@ function queryTokenMacs(key, text, max = QUERY_MACS_MAX) {
|
|
|
33745
33929
|
|
|
33746
33930
|
// src/memory/unified/ranking-client.ts
|
|
33747
33931
|
import { Worker } from "node:worker_threads";
|
|
33748
|
-
import { join as
|
|
33932
|
+
import { join as join18 } from "node:path";
|
|
33749
33933
|
function workerTransport(init, distDir) {
|
|
33750
|
-
const w = new Worker(
|
|
33934
|
+
const w = new Worker(join18(distDir, "memory-worker.js"), { workerData: init });
|
|
33751
33935
|
return {
|
|
33752
33936
|
post: (m) => w.postMessage(m),
|
|
33753
33937
|
onMessage: (cb) => {
|
|
@@ -33807,15 +33991,15 @@ var RankingClient = class {
|
|
|
33807
33991
|
ask(build, deadlineAt) {
|
|
33808
33992
|
const id = ++this.seq;
|
|
33809
33993
|
if (this.closed) return Promise.resolve("failed");
|
|
33810
|
-
return new Promise((
|
|
33994
|
+
return new Promise((resolve5) => {
|
|
33811
33995
|
const timer = setTimeout(() => {
|
|
33812
33996
|
this.waiting.delete(id);
|
|
33813
33997
|
this.misses++;
|
|
33814
|
-
|
|
33998
|
+
resolve5("late");
|
|
33815
33999
|
}, Math.max(0, deadlineAt - Date.now()));
|
|
33816
34000
|
this.waiting.set(id, { resolve: (m) => {
|
|
33817
34001
|
clearTimeout(timer);
|
|
33818
|
-
|
|
34002
|
+
resolve5(m);
|
|
33819
34003
|
} });
|
|
33820
34004
|
try {
|
|
33821
34005
|
this.transport().post(build(id));
|
|
@@ -33824,7 +34008,7 @@ var RankingClient = class {
|
|
|
33824
34008
|
this.waiting.delete(id);
|
|
33825
34009
|
this.misses++;
|
|
33826
34010
|
(this.d.log ?? writeServiceLog)("warn", "memory.ranking_worker_spawn_failed", void 0, e);
|
|
33827
|
-
|
|
34011
|
+
resolve5("failed");
|
|
33828
34012
|
}
|
|
33829
34013
|
});
|
|
33830
34014
|
}
|
|
@@ -35152,17 +35336,17 @@ async function runSummarize(opts) {
|
|
|
35152
35336
|
// src/service/runtime-config-dir.ts
|
|
35153
35337
|
import { createHash as createHash5, randomUUID } from "node:crypto";
|
|
35154
35338
|
import {
|
|
35155
|
-
chmodSync as
|
|
35339
|
+
chmodSync as chmodSync5,
|
|
35156
35340
|
existsSync as existsSync4,
|
|
35157
|
-
lstatSync as
|
|
35158
|
-
mkdirSync as
|
|
35341
|
+
lstatSync as lstatSync8,
|
|
35342
|
+
mkdirSync as mkdirSync6,
|
|
35159
35343
|
readFileSync as readFileSync10,
|
|
35160
35344
|
renameSync as renameSync3,
|
|
35161
35345
|
statSync as statSync4,
|
|
35162
35346
|
unlinkSync,
|
|
35163
35347
|
writeFileSync
|
|
35164
35348
|
} from "node:fs";
|
|
35165
|
-
import { basename as
|
|
35349
|
+
import { basename as basename5, isAbsolute, join as join19 } from "node:path";
|
|
35166
35350
|
|
|
35167
35351
|
// src/grants/search-providers.ts
|
|
35168
35352
|
var SEARCH_PROVIDERS = /* @__PURE__ */ new Set(["brave", "tavily", "parallel", "exa", "firecrawl"]);
|
|
@@ -35174,18 +35358,18 @@ function isSearchProvider(value) {
|
|
|
35174
35358
|
// src/service/runtime-config-dir.ts
|
|
35175
35359
|
var MEMORY_FILE = "MEMORY.md";
|
|
35176
35360
|
var REAL_FS = {
|
|
35177
|
-
mkdir: (p, mode) =>
|
|
35178
|
-
chmod: (p, mode) =>
|
|
35361
|
+
mkdir: (p, mode) => mkdirSync6(p, { recursive: true, mode }),
|
|
35362
|
+
chmod: (p, mode) => chmodSync5(p, mode),
|
|
35179
35363
|
writeFile: (p, data, mode) => writeFileSync(p, data, { mode }),
|
|
35180
35364
|
rename: (a, b) => renameSync3(a, b),
|
|
35181
35365
|
readFile: (p) => readFileSync10(p, "utf8"),
|
|
35182
35366
|
unlink: (p) => unlinkSync(p)
|
|
35183
35367
|
};
|
|
35184
35368
|
function runtimeConfigDir(home, identityId) {
|
|
35185
|
-
return
|
|
35369
|
+
return join19(home, ".securecoms", "runtime-config", identityId);
|
|
35186
35370
|
}
|
|
35187
35371
|
function assertManagedName(name) {
|
|
35188
|
-
if (!name || name.includes("/") || name.includes("\\") || name === "." || name === ".." || name !==
|
|
35372
|
+
if (!name || name.includes("/") || name.includes("\\") || name === "." || name === ".." || name !== basename5(name)) {
|
|
35189
35373
|
throw new Error(`refusing managed config file name ${JSON.stringify(name)}: not a plain filename`);
|
|
35190
35374
|
}
|
|
35191
35375
|
if (isLocalOnlyName(name)) {
|
|
@@ -35194,24 +35378,46 @@ function assertManagedName(name) {
|
|
|
35194
35378
|
}
|
|
35195
35379
|
function isLocalOnlyName(name) {
|
|
35196
35380
|
const n = name.toLowerCase().replace(/[. ]+$/, "");
|
|
35197
|
-
return n === "llm.key" || n.startsWith("llm.key.") || n === "local-config.json" || n === "secrets" || n === MANIFEST_POLICY_FILE;
|
|
35381
|
+
return n === "llm.key" || n.startsWith("llm.key.") || n === "local-config.json" || n === "secrets" || n === MANIFEST_POLICY_FILE || n === MCP_POLICY_FILE;
|
|
35198
35382
|
}
|
|
35199
35383
|
var MANIFEST_POLICY_FILE = "manifest-policy.json";
|
|
35200
35384
|
function removeManagedLlmSource(home, identityId, deps = REAL_FS) {
|
|
35201
35385
|
try {
|
|
35202
|
-
deps.unlink(
|
|
35386
|
+
deps.unlink(join19(runtimeConfigDir(home, identityId), "llm-source.json"));
|
|
35203
35387
|
} catch (e) {
|
|
35204
35388
|
if (e?.code !== "ENOENT") throw e;
|
|
35205
35389
|
}
|
|
35206
35390
|
}
|
|
35207
35391
|
function writeManifestPolicy(home, identityId, policy, deps = REAL_FS) {
|
|
35208
|
-
const parent3 =
|
|
35392
|
+
const parent3 = join19(home, ".securecoms", "runtime-config");
|
|
35209
35393
|
const dir = runtimeConfigDir(home, identityId);
|
|
35210
|
-
for (const d of [
|
|
35394
|
+
for (const d of [join19(home, ".securecoms"), parent3, dir]) {
|
|
35211
35395
|
deps.mkdir(d, 448);
|
|
35212
35396
|
deps.chmod(d, 448);
|
|
35213
35397
|
}
|
|
35214
|
-
atomicWrite(
|
|
35398
|
+
atomicWrite(join19(dir, MANIFEST_POLICY_FILE), JSON.stringify(policy) + "\n", deps);
|
|
35399
|
+
}
|
|
35400
|
+
var MCP_POLICY_FILE = "mcp-policy.json";
|
|
35401
|
+
var MCP_POLICY_TOP_STATES = Object.freeze([
|
|
35402
|
+
"ok",
|
|
35403
|
+
"not_entitled",
|
|
35404
|
+
"kill_switch",
|
|
35405
|
+
"suspended",
|
|
35406
|
+
"no_config",
|
|
35407
|
+
"switch_off",
|
|
35408
|
+
"no_manifest",
|
|
35409
|
+
"malformed",
|
|
35410
|
+
"no_capability",
|
|
35411
|
+
"section_absent"
|
|
35412
|
+
]);
|
|
35413
|
+
function writeMcpPolicy(home, identityId, doc, deps = REAL_FS) {
|
|
35414
|
+
const parent3 = join19(home, ".securecoms", "runtime-config");
|
|
35415
|
+
const dir = runtimeConfigDir(home, identityId);
|
|
35416
|
+
for (const d of [join19(home, ".securecoms"), parent3, dir]) {
|
|
35417
|
+
deps.mkdir(d, 448);
|
|
35418
|
+
deps.chmod(d, 448);
|
|
35419
|
+
}
|
|
35420
|
+
atomicWrite(join19(dir, MCP_POLICY_FILE), JSON.stringify(doc) + "\n", deps);
|
|
35215
35421
|
}
|
|
35216
35422
|
function atomicWrite(path, body, deps) {
|
|
35217
35423
|
const tmp = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
@@ -35230,13 +35436,13 @@ function atomicWrite(path, body, deps) {
|
|
|
35230
35436
|
function writeManagedFiles(home, identityId, files, deps = REAL_FS) {
|
|
35231
35437
|
const entries = Object.entries(files);
|
|
35232
35438
|
for (const [name] of entries) assertManagedName(name);
|
|
35233
|
-
const parent3 =
|
|
35439
|
+
const parent3 = join19(home, ".securecoms", "runtime-config");
|
|
35234
35440
|
const dir = runtimeConfigDir(home, identityId);
|
|
35235
|
-
for (const d of [
|
|
35441
|
+
for (const d of [join19(home, ".securecoms"), parent3, dir]) {
|
|
35236
35442
|
deps.mkdir(d, 448);
|
|
35237
35443
|
deps.chmod(d, 448);
|
|
35238
35444
|
}
|
|
35239
|
-
for (const [name, body] of entries) atomicWrite(
|
|
35445
|
+
for (const [name, body] of entries) atomicWrite(join19(dir, name), body, deps);
|
|
35240
35446
|
}
|
|
35241
35447
|
function checkConfiguredPaths(skillsJson, statFn = (p) => {
|
|
35242
35448
|
const s = statSync4(p);
|
|
@@ -35315,7 +35521,7 @@ function localLegacySources(doc) {
|
|
|
35315
35521
|
const channelId = source.channelId;
|
|
35316
35522
|
if (kind2 !== "global-memory" && kind2 !== "conversation-history" && kind2 !== "captured-memory" || typeof path !== "string" || !isAbsolute(path) || channelId !== void 0 && typeof channelId !== "string") continue;
|
|
35317
35523
|
try {
|
|
35318
|
-
const st =
|
|
35524
|
+
const st = lstatSync8(path);
|
|
35319
35525
|
if (!st.isFile() || st.isSymbolicLink()) continue;
|
|
35320
35526
|
if (st.size <= LEGACY_MEMORY_SOURCE_MAX_BYTES && st.size > contentBudget) continue;
|
|
35321
35527
|
const oversize = st.size > LEGACY_MEMORY_SOURCE_MAX_BYTES;
|
|
@@ -35343,7 +35549,7 @@ function localLegacySources(doc) {
|
|
|
35343
35549
|
return out;
|
|
35344
35550
|
}
|
|
35345
35551
|
function readLocalReport(home, identityId) {
|
|
35346
|
-
const p =
|
|
35552
|
+
const p = join19(runtimeConfigDir(home, identityId), LOCAL_REPORT_FILE);
|
|
35347
35553
|
if (!existsSync4(p)) return null;
|
|
35348
35554
|
let doc;
|
|
35349
35555
|
try {
|
|
@@ -35378,7 +35584,7 @@ function readLocalReport(home, identityId) {
|
|
|
35378
35584
|
}
|
|
35379
35585
|
function deleteLegacyMemorySources(home, identityId, ids) {
|
|
35380
35586
|
if (ids.length === 0) return 0;
|
|
35381
|
-
const p =
|
|
35587
|
+
const p = join19(runtimeConfigDir(home, identityId), LOCAL_REPORT_FILE);
|
|
35382
35588
|
if (!existsSync4(p)) return 0;
|
|
35383
35589
|
let doc;
|
|
35384
35590
|
try {
|
|
@@ -35421,7 +35627,7 @@ async function applyLegacyMemoryFactDecisions(home, identityId, rawDecisions, re
|
|
|
35421
35627
|
group.set(decision.fact_hash, decision);
|
|
35422
35628
|
bySource.set(decision.source_id, group);
|
|
35423
35629
|
}
|
|
35424
|
-
const reportPath =
|
|
35630
|
+
const reportPath = join19(runtimeConfigDir(home, identityId), LOCAL_REPORT_FILE);
|
|
35425
35631
|
if (!existsSync4(reportPath)) return { sourcesDeleted: 0, factsAssigned: 0 };
|
|
35426
35632
|
let doc;
|
|
35427
35633
|
try {
|
|
@@ -35459,7 +35665,7 @@ async function applyLegacyMemoryFactDecisions(home, identityId, rawDecisions, re
|
|
|
35459
35665
|
}
|
|
35460
35666
|
function pruneManagedMemory(home, identityId, hashes, deps = REAL_FS) {
|
|
35461
35667
|
const dir = runtimeConfigDir(home, identityId);
|
|
35462
|
-
const path =
|
|
35668
|
+
const path = join19(dir, MEMORY_FILE);
|
|
35463
35669
|
if (!existsSync4(path)) return 0;
|
|
35464
35670
|
const drop = new Set(hashes);
|
|
35465
35671
|
const before = deps.readFile(path).split("\n");
|
|
@@ -35490,6 +35696,195 @@ import { sha256 as sha25611 } from "@noble/hashes/sha256";
|
|
|
35490
35696
|
|
|
35491
35697
|
// src/grants/manifest.ts
|
|
35492
35698
|
import { ed25519 as ed255195 } from "@noble/curves/ed25519";
|
|
35699
|
+
|
|
35700
|
+
// src/grants/mcp-section.ts
|
|
35701
|
+
var MCP_MAX_SERVERS = 8;
|
|
35702
|
+
var MCP_MAX_TOOLS_PER_SERVER = 64;
|
|
35703
|
+
var MCP_MAX_SIGNED_TOOLS = 160;
|
|
35704
|
+
var MCP_MAX_REMOVED = 8;
|
|
35705
|
+
var SECURECOMS_CIMD_CLIENT_ID = "https://app.securecoms.ai/oauth/securecoms-agent-client.json";
|
|
35706
|
+
var MCP_SERVER_ID = /^[a-z0-9-]{1,24}$/;
|
|
35707
|
+
var MCP_TOOL_NAME = /^[A-Za-z0-9_.-]{1,64}$/;
|
|
35708
|
+
var VENDOR_KEY = /^[a-z0-9_]{1,32}$/;
|
|
35709
|
+
var MCP_RESERVED_NAMES = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
35710
|
+
var HEX64 = /^[0-9a-f]{64}$/;
|
|
35711
|
+
var PRINTABLE_ASCII = /^[\x21-\x7e]{1,256}$/;
|
|
35712
|
+
var MCP_GRANT = /^mcp:([a-z0-9-]{1,24})\/([A-Za-z0-9_.-]{1,64})$/;
|
|
35713
|
+
var isBag = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
35714
|
+
var keysExactly = (o, keys) => {
|
|
35715
|
+
const have = Object.keys(o);
|
|
35716
|
+
return have.length === keys.length && keys.every((k) => Object.prototype.hasOwnProperty.call(o, k));
|
|
35717
|
+
};
|
|
35718
|
+
function canonicalHttpsUrl(v) {
|
|
35719
|
+
if (typeof v !== "string" || !PRINTABLE_ASCII.test(v) || !URL.canParse(v)) return false;
|
|
35720
|
+
const u = new URL(v);
|
|
35721
|
+
return u.protocol === "https:" && u.username === "" && u.password === "" && u.href === v;
|
|
35722
|
+
}
|
|
35723
|
+
function httpsIssuer(v) {
|
|
35724
|
+
if (typeof v !== "string" || !PRINTABLE_ASCII.test(v) || !URL.canParse(v)) return false;
|
|
35725
|
+
const u = new URL(v);
|
|
35726
|
+
return u.protocol === "https:" && u.username === "" && u.password === "" && u.search === "" && u.hash === "" && (u.href === v || u.href === `${v}/`);
|
|
35727
|
+
}
|
|
35728
|
+
var MCP_AUTH_KEYS = Object.freeze({
|
|
35729
|
+
oauth_securecoms_cimd: Object.freeze(["mode", "issuer", "client"]),
|
|
35730
|
+
oauth_customer: Object.freeze(["mode", "issuer", "client", "client_id"]),
|
|
35731
|
+
key: Object.freeze(["mode"])
|
|
35732
|
+
});
|
|
35733
|
+
function parseAuth(v) {
|
|
35734
|
+
if (!isBag(v)) return null;
|
|
35735
|
+
if (v.mode === "key") return keysExactly(v, MCP_AUTH_KEYS.key) ? Object.freeze({ mode: "key" }) : null;
|
|
35736
|
+
if (v.mode !== "oauth" || !httpsIssuer(v.issuer)) return null;
|
|
35737
|
+
if (v.client === "securecoms_cimd") {
|
|
35738
|
+
if (!keysExactly(v, MCP_AUTH_KEYS.oauth_securecoms_cimd)) return null;
|
|
35739
|
+
return Object.freeze({ mode: "oauth", issuer: v.issuer, client: "securecoms_cimd" });
|
|
35740
|
+
}
|
|
35741
|
+
if (v.client === "customer") {
|
|
35742
|
+
if (!keysExactly(v, MCP_AUTH_KEYS.oauth_customer)) return null;
|
|
35743
|
+
if (typeof v.client_id !== "string" || !PRINTABLE_ASCII.test(v.client_id)) return null;
|
|
35744
|
+
return Object.freeze({ mode: "oauth", issuer: v.issuer, client: "customer", client_id: v.client_id });
|
|
35745
|
+
}
|
|
35746
|
+
return null;
|
|
35747
|
+
}
|
|
35748
|
+
function parseTools(v) {
|
|
35749
|
+
if (!isBag(v)) return null;
|
|
35750
|
+
const entries = Object.entries(v);
|
|
35751
|
+
if (entries.length > MCP_MAX_TOOLS_PER_SERVER) return null;
|
|
35752
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
35753
|
+
for (const [name, t] of entries) {
|
|
35754
|
+
if (!MCP_TOOL_NAME.test(name) || MCP_RESERVED_NAMES.has(name) || !isBag(t) || !keysExactly(t, ["sha256", "class"])) return null;
|
|
35755
|
+
if (typeof t.sha256 !== "string" || !HEX64.test(t.sha256) || t.class !== "read") return null;
|
|
35756
|
+
out[name] = Object.freeze({ sha256: t.sha256, class: "read" });
|
|
35757
|
+
}
|
|
35758
|
+
return out;
|
|
35759
|
+
}
|
|
35760
|
+
function parseServer(v) {
|
|
35761
|
+
if (!isBag(v) || !keysExactly(v, ["vendor", "url", "auth", "tools"])) return null;
|
|
35762
|
+
if (typeof v.vendor !== "string" || !VENDOR_KEY.test(v.vendor) || !canonicalHttpsUrl(v.url)) return null;
|
|
35763
|
+
const auth = parseAuth(v.auth);
|
|
35764
|
+
const tools = parseTools(v.tools);
|
|
35765
|
+
if (auth === null || tools === null) return null;
|
|
35766
|
+
return Object.freeze({ vendor: v.vendor, url: v.url, auth, tools: Object.freeze(tools) });
|
|
35767
|
+
}
|
|
35768
|
+
function parseRemoved(v, servers) {
|
|
35769
|
+
if (v === "*") return Object.keys(servers).length === 0 ? "*" : null;
|
|
35770
|
+
if (!Array.isArray(v) || v.length < 1 || v.length > MCP_MAX_REMOVED) return null;
|
|
35771
|
+
const seen = /* @__PURE__ */ new Set();
|
|
35772
|
+
for (const id of v) {
|
|
35773
|
+
if (typeof id !== "string" || !MCP_SERVER_ID.test(id) || MCP_RESERVED_NAMES.has(id)) return null;
|
|
35774
|
+
if (seen.has(id) || Object.prototype.hasOwnProperty.call(servers, id)) return null;
|
|
35775
|
+
seen.add(id);
|
|
35776
|
+
}
|
|
35777
|
+
return Object.freeze([...seen]);
|
|
35778
|
+
}
|
|
35779
|
+
var NOT_REMOVED = Object.freeze([]);
|
|
35780
|
+
function parseMcp(v) {
|
|
35781
|
+
if (!isBag(v) || !(keysExactly(v, ["servers"]) || keysExactly(v, ["servers", "removed"])) || !isBag(v.servers)) return null;
|
|
35782
|
+
const entries = Object.entries(v.servers);
|
|
35783
|
+
if (entries.length > MCP_MAX_SERVERS) return null;
|
|
35784
|
+
const servers = /* @__PURE__ */ Object.create(null);
|
|
35785
|
+
let signedTools = 0;
|
|
35786
|
+
for (const [id, raw] of entries) {
|
|
35787
|
+
if (!MCP_SERVER_ID.test(id) || MCP_RESERVED_NAMES.has(id)) return null;
|
|
35788
|
+
const s = parseServer(raw);
|
|
35789
|
+
if (s === null) return null;
|
|
35790
|
+
signedTools += Object.keys(s.tools).length;
|
|
35791
|
+
if (signedTools > MCP_MAX_SIGNED_TOOLS) return null;
|
|
35792
|
+
servers[id] = s;
|
|
35793
|
+
}
|
|
35794
|
+
let removed = NOT_REMOVED;
|
|
35795
|
+
if (Object.prototype.hasOwnProperty.call(v, "removed")) {
|
|
35796
|
+
const r = parseRemoved(v.removed, servers);
|
|
35797
|
+
if (r === null) return null;
|
|
35798
|
+
removed = r;
|
|
35799
|
+
}
|
|
35800
|
+
return Object.freeze({ servers: Object.freeze(servers), removed });
|
|
35801
|
+
}
|
|
35802
|
+
function mcpServerCatalogRow(s, catalog) {
|
|
35803
|
+
if (!Object.prototype.hasOwnProperty.call(catalog, s.vendor)) return null;
|
|
35804
|
+
const row = catalog[s.vendor];
|
|
35805
|
+
if (s.url !== row.readOnlyUrl) return null;
|
|
35806
|
+
if (s.auth.mode === "oauth" && s.auth.issuer !== row.issuer) return null;
|
|
35807
|
+
return row;
|
|
35808
|
+
}
|
|
35809
|
+
function parseMcpGrant(name) {
|
|
35810
|
+
const g = MCP_GRANT.exec(name);
|
|
35811
|
+
return g === null ? null : { serverId: g[1], tool: g[2] };
|
|
35812
|
+
}
|
|
35813
|
+
function mcpGrantWithinCeiling(name, mcp, catalog) {
|
|
35814
|
+
const g = parseMcpGrant(name);
|
|
35815
|
+
if (g === null || mcp === null || !Object.prototype.hasOwnProperty.call(mcp.servers, g.serverId)) return false;
|
|
35816
|
+
const s = mcp.servers[g.serverId];
|
|
35817
|
+
if (!Object.prototype.hasOwnProperty.call(s.tools, g.tool)) return false;
|
|
35818
|
+
const row = mcpServerCatalogRow(s, catalog);
|
|
35819
|
+
return row !== null && row.readTools.includes(g.tool);
|
|
35820
|
+
}
|
|
35821
|
+
|
|
35822
|
+
// src/grants/mcp-catalog.ts
|
|
35823
|
+
var LINEAR = Object.freeze({
|
|
35824
|
+
displayName: "Linear",
|
|
35825
|
+
host: "mcp.linear.app",
|
|
35826
|
+
readOnlyUrl: "https://mcp.linear.app/mcp/readonly",
|
|
35827
|
+
readTools: Object.freeze([
|
|
35828
|
+
"get_attachment",
|
|
35829
|
+
"list_agent_skills",
|
|
35830
|
+
"get_agent_skill",
|
|
35831
|
+
"list_comments",
|
|
35832
|
+
"list_cycles",
|
|
35833
|
+
"list_custom_views",
|
|
35834
|
+
"get_document",
|
|
35835
|
+
"list_documents",
|
|
35836
|
+
"extract_images",
|
|
35837
|
+
"get_issue",
|
|
35838
|
+
"list_issue_statuses",
|
|
35839
|
+
"get_issue_status",
|
|
35840
|
+
"list_issue_labels",
|
|
35841
|
+
"list_projects",
|
|
35842
|
+
"get_project",
|
|
35843
|
+
"list_project_labels",
|
|
35844
|
+
"list_release_pipelines",
|
|
35845
|
+
"list_releases",
|
|
35846
|
+
"get_release",
|
|
35847
|
+
"list_release_notes",
|
|
35848
|
+
"get_release_note",
|
|
35849
|
+
"list_milestones",
|
|
35850
|
+
"get_milestone",
|
|
35851
|
+
"get_notifications",
|
|
35852
|
+
"list_teams",
|
|
35853
|
+
"get_team",
|
|
35854
|
+
"get_triage_responsibility",
|
|
35855
|
+
"list_templates",
|
|
35856
|
+
"get_template",
|
|
35857
|
+
"list_users",
|
|
35858
|
+
"get_user",
|
|
35859
|
+
"get_workspace",
|
|
35860
|
+
"search_documentation",
|
|
35861
|
+
"get_diff",
|
|
35862
|
+
"list_diffs",
|
|
35863
|
+
"get_diff_threads",
|
|
35864
|
+
"list_initiatives",
|
|
35865
|
+
"get_initiative",
|
|
35866
|
+
"list_initiative_labels",
|
|
35867
|
+
"get_status_updates"
|
|
35868
|
+
]),
|
|
35869
|
+
scopes: Object.freeze(["read"]),
|
|
35870
|
+
issuer: "https://mcp.linear.app",
|
|
35871
|
+
authorizationEndpoint: "https://mcp.linear.app/authorize",
|
|
35872
|
+
tokenEndpoint: "https://mcp.linear.app/token",
|
|
35873
|
+
revocationEndpoint: "https://mcp.linear.app/token",
|
|
35874
|
+
docs: Object.freeze({
|
|
35875
|
+
host: "https://linear.app/docs/mcp",
|
|
35876
|
+
readOnlyUrl: "https://linear.app/docs/mcp",
|
|
35877
|
+
readTools: "https://linear.app/docs/mcp",
|
|
35878
|
+
scopes: "https://linear.app/docs/mcp",
|
|
35879
|
+
issuer: "https://mcp.linear.app/.well-known/oauth-authorization-server",
|
|
35880
|
+
authorizationEndpoint: "https://mcp.linear.app/.well-known/oauth-authorization-server",
|
|
35881
|
+
tokenEndpoint: "https://mcp.linear.app/.well-known/oauth-authorization-server",
|
|
35882
|
+
revocationEndpoint: "https://mcp.linear.app/.well-known/oauth-authorization-server"
|
|
35883
|
+
})
|
|
35884
|
+
});
|
|
35885
|
+
var MCP_CATALOG = Object.freeze({ linear: LINEAR });
|
|
35886
|
+
|
|
35887
|
+
// src/grants/manifest.ts
|
|
35493
35888
|
var TOOL_GRANTS_DOMAIN = "SECURECOMS-TOOL-GRANTS-V1:";
|
|
35494
35889
|
var MANIFEST_KNOWN_TOOLS = Object.freeze({
|
|
35495
35890
|
owner_dm: Object.freeze(["web_search", "web_extract", "fetch_url", "http_request", "sql_query", "weather_forecast", "currency_convert"]),
|
|
@@ -35514,9 +35909,9 @@ function wellTyped(v) {
|
|
|
35514
35909
|
return false;
|
|
35515
35910
|
}
|
|
35516
35911
|
var WRAPPER_KEYS = ["algo", "manifest", "signature"];
|
|
35517
|
-
var
|
|
35518
|
-
var
|
|
35519
|
-
var absolutePath = (v) => typeof v === "string" && v.startsWith("/") &&
|
|
35912
|
+
var HEX642 = /^[0-9a-f]{64}$/;
|
|
35913
|
+
var PRINTABLE_ASCII2 = /^[\x21-\x7e]+$/;
|
|
35914
|
+
var absolutePath = (v) => typeof v === "string" && v.startsWith("/") && PRINTABLE_ASCII2.test(v);
|
|
35520
35915
|
var exactKeys = (o, required, optional = []) => required.every((k) => k in o) && Object.keys(o).every((k) => required.includes(k) || optional.includes(k));
|
|
35521
35916
|
var isStringArray = (v) => Array.isArray(v) && v.every((x) => typeof x === "string");
|
|
35522
35917
|
function parseEndpoints(v) {
|
|
@@ -35528,7 +35923,7 @@ function parseEndpoints(v) {
|
|
|
35528
35923
|
const e = { ...raw };
|
|
35529
35924
|
if (e.headersFile === "") delete e.headersFile;
|
|
35530
35925
|
if (!exactKeys(e, ["baseUrl"], ["headersFile"])) return null;
|
|
35531
|
-
if (typeof e.baseUrl !== "string" || e.baseUrl === "" || !
|
|
35926
|
+
if (typeof e.baseUrl !== "string" || e.baseUrl === "" || !PRINTABLE_ASCII2.test(e.baseUrl)) return null;
|
|
35532
35927
|
let url;
|
|
35533
35928
|
try {
|
|
35534
35929
|
url = new URL(e.baseUrl);
|
|
@@ -35559,8 +35954,8 @@ function parseDatabases(v) {
|
|
|
35559
35954
|
function parseConfig(v) {
|
|
35560
35955
|
if (!isPlainObject(v)) return null;
|
|
35561
35956
|
if (!exactKeys(v, ["persona_sha256", "flags", "memory", "legacy_assign_sha256"], ["llm", "search", "tools_allow"])) return null;
|
|
35562
|
-
if (typeof v.persona_sha256 !== "string" || !
|
|
35563
|
-
if (typeof v.legacy_assign_sha256 !== "string" || !
|
|
35957
|
+
if (typeof v.persona_sha256 !== "string" || !HEX642.test(v.persona_sha256)) return null;
|
|
35958
|
+
if (typeof v.legacy_assign_sha256 !== "string" || !HEX642.test(v.legacy_assign_sha256)) return null;
|
|
35564
35959
|
if (!isStringArray(v.flags)) return null;
|
|
35565
35960
|
if ("tools_allow" in v && !isStringArray(v.tools_allow)) return null;
|
|
35566
35961
|
if ("llm" in v) {
|
|
@@ -35604,7 +35999,7 @@ function verifyToolGrantManifest(raw, ctx) {
|
|
|
35604
35999
|
const m = raw.manifest;
|
|
35605
36000
|
if (!isPlainObject(m)) return { state: "malformed" };
|
|
35606
36001
|
if (m.type !== "securecoms.tool-grants" || m.v !== 1) return { state: "malformed" };
|
|
35607
|
-
if (!wellTyped(m)) return { state: "malformed" };
|
|
36002
|
+
if (!wellTyped(Object.fromEntries(Object.entries(m).filter(([k]) => k !== "mcp")))) return { state: "malformed" };
|
|
35608
36003
|
if (typeof m.agent_identity_id !== "string" || typeof m.owner_identity_id !== "string") return { state: "malformed" };
|
|
35609
36004
|
for (const k of ["owner_dm_channel_id", "owner_dm_relay_identity_id"]) {
|
|
35610
36005
|
if (k in m && typeof m[k] !== "string") return { state: "malformed" };
|
|
@@ -35634,6 +36029,11 @@ function verifyToolGrantManifest(raw, ctx) {
|
|
|
35634
36029
|
if (counter === ctx.storedCounter) return { state: "unchanged" };
|
|
35635
36030
|
if (counter < ctx.storedCounter) return { state: "rollback" };
|
|
35636
36031
|
}
|
|
36032
|
+
const rawOwnerDm = m.grants.owner_dm;
|
|
36033
|
+
const signedMcpPresent = Array.isArray(rawOwnerDm) && rawOwnerDm.some((t) => t.startsWith("mcp:"));
|
|
36034
|
+
const mcpKey = "mcp" in m ? "present" : "absent";
|
|
36035
|
+
const mcp = mcpKey === "present" ? parseMcp(m.mcp) : null;
|
|
36036
|
+
const catalog = ctx.mcpCatalog ?? MCP_CATALOG;
|
|
35637
36037
|
let droppedUnknown = false;
|
|
35638
36038
|
const grants = {};
|
|
35639
36039
|
for (const [audience, list] of Object.entries(m.grants)) {
|
|
@@ -35642,7 +36042,7 @@ function verifyToolGrantManifest(raw, ctx) {
|
|
|
35642
36042
|
continue;
|
|
35643
36043
|
}
|
|
35644
36044
|
const known = MANIFEST_KNOWN_TOOLS[audience];
|
|
35645
|
-
const kept = list.filter((t) => known.includes(t));
|
|
36045
|
+
const kept = list.filter((t) => known.includes(t) || audience === "owner_dm" && mcpGrantWithinCeiling(t, mcp, catalog));
|
|
35646
36046
|
if (kept.length !== list.length) droppedUnknown = true;
|
|
35647
36047
|
grants[audience] = Object.freeze(kept);
|
|
35648
36048
|
}
|
|
@@ -35657,7 +36057,10 @@ function verifyToolGrantManifest(raw, ctx) {
|
|
|
35657
36057
|
grants: Object.freeze(grants),
|
|
35658
36058
|
endpoints: Object.freeze(endpoints),
|
|
35659
36059
|
databases: Object.freeze(databases),
|
|
35660
|
-
config
|
|
36060
|
+
config,
|
|
36061
|
+
mcp,
|
|
36062
|
+
mcpKey,
|
|
36063
|
+
signedMcpPresent
|
|
35661
36064
|
});
|
|
35662
36065
|
return { state: "ok", manifest, droppedUnknown, wrapper: raw };
|
|
35663
36066
|
}
|
|
@@ -35978,21 +36381,32 @@ var AGENT_TOTAL_KEY = "agent-total";
|
|
|
35978
36381
|
// src/grants/effective.ts
|
|
35979
36382
|
var TOOL_GRANT_CODE_CEILING = MANIFEST_KNOWN_TOOLS;
|
|
35980
36383
|
var CURRENT_TIME = Object.freeze({ tool: "current_time", mode: "read" });
|
|
36384
|
+
var NO_WITHHELD = Object.freeze([]);
|
|
35981
36385
|
function effectiveToolGrants(i) {
|
|
35982
|
-
if (i.base.length === 0) return { grants: i.base, source: "none", withheldBySearchCap: false };
|
|
35983
|
-
if (i.manifest === null) return { grants: i.base, source: "constant", withheldBySearchCap: false };
|
|
36386
|
+
if (i.base.length === 0) return { grants: i.base, source: "none", withheldBySearchCap: false, withheld: NO_WITHHELD };
|
|
36387
|
+
if (i.manifest === null) return { grants: i.base, source: "constant", withheldBySearchCap: false, withheld: NO_WITHHELD };
|
|
35984
36388
|
const ceiling = TOOL_GRANT_CODE_CEILING[i.audience];
|
|
35985
36389
|
const listed = new Set(i.manifest.grants[i.audience] ?? []);
|
|
35986
36390
|
const signed = ceiling.filter((t) => listed.has(t));
|
|
35987
36391
|
if (!i.eligible || !i.manifestSwitchOn) {
|
|
35988
36392
|
const fallback = i.base.filter((g) => g.tool !== "current_time" && signed.includes(g.tool));
|
|
35989
|
-
return { grants: Object.freeze([CURRENT_TIME, ...fallback]), source: "fallback", withheldBySearchCap: false };
|
|
36393
|
+
return { grants: Object.freeze([CURRENT_TIME, ...fallback]), source: "fallback", withheldBySearchCap: false, withheld: NO_WITHHELD };
|
|
35990
36394
|
}
|
|
35991
36395
|
let withheld = false;
|
|
36396
|
+
const dropped = [];
|
|
35992
36397
|
const tools = signed.filter((t) => {
|
|
35993
|
-
if (i.disabled.has(t))
|
|
35994
|
-
|
|
35995
|
-
|
|
36398
|
+
if (i.disabled.has(t)) {
|
|
36399
|
+
dropped.push(Object.freeze({ tool: t, why: "disabled_by_owner" }));
|
|
36400
|
+
return false;
|
|
36401
|
+
}
|
|
36402
|
+
if (t === "http_request" && i.signedTargets.endpoints === 0) {
|
|
36403
|
+
dropped.push(Object.freeze({ tool: t, why: "no_endpoint" }));
|
|
36404
|
+
return false;
|
|
36405
|
+
}
|
|
36406
|
+
if (t === "sql_query" && i.signedTargets.databases === 0) {
|
|
36407
|
+
dropped.push(Object.freeze({ tool: t, why: "no_database" }));
|
|
36408
|
+
return false;
|
|
36409
|
+
}
|
|
35996
36410
|
if (i.audience === "team_room" && t === "web_search" && i.roomSearchCapped) {
|
|
35997
36411
|
withheld = true;
|
|
35998
36412
|
return false;
|
|
@@ -36002,7 +36416,8 @@ function effectiveToolGrants(i) {
|
|
|
36002
36416
|
return {
|
|
36003
36417
|
grants: Object.freeze([CURRENT_TIME, ...tools.map((tool) => Object.freeze({ tool, mode: "read", source: "manifest" }))]),
|
|
36004
36418
|
source: "manifest",
|
|
36005
|
-
withheldBySearchCap: withheld
|
|
36419
|
+
withheldBySearchCap: withheld,
|
|
36420
|
+
withheld: Object.freeze(dropped)
|
|
36006
36421
|
};
|
|
36007
36422
|
}
|
|
36008
36423
|
|
|
@@ -36307,6 +36722,200 @@ function classifyVoiceFrame(f) {
|
|
|
36307
36722
|
return b.text.trim() === VOICE_CALL_START_MARKER ? "greeting" : "drop";
|
|
36308
36723
|
}
|
|
36309
36724
|
|
|
36725
|
+
// src/grants/tool-status.ts
|
|
36726
|
+
var TOOL_STATUS_ALLOWLIST = [
|
|
36727
|
+
"current_time",
|
|
36728
|
+
"web_search",
|
|
36729
|
+
"web_extract",
|
|
36730
|
+
"fetch_url",
|
|
36731
|
+
"http_request",
|
|
36732
|
+
"sql_query",
|
|
36733
|
+
"weather_forecast",
|
|
36734
|
+
"currency_convert"
|
|
36735
|
+
];
|
|
36736
|
+
var ALLOW2 = new Set(TOOL_STATUS_ALLOWLIST);
|
|
36737
|
+
var TOOL_STATUS_BUCKET_MS = 9e5;
|
|
36738
|
+
function bucketStartAt(ms) {
|
|
36739
|
+
return new Date(Math.floor(ms / TOOL_STATUS_BUCKET_MS) * TOOL_STATUS_BUCKET_MS).toISOString().slice(0, 16) + "Z";
|
|
36740
|
+
}
|
|
36741
|
+
function bucketEndMs(at) {
|
|
36742
|
+
return Date.parse(at) + TOOL_STATUS_BUCKET_MS;
|
|
36743
|
+
}
|
|
36744
|
+
var SIGNATURE_FAILURES = /* @__PURE__ */ new Set(["bad_signature", "malformed", "unknown_algo", "no_signature"]);
|
|
36745
|
+
function ownerDmCause(i) {
|
|
36746
|
+
try {
|
|
36747
|
+
const v = i.verdict;
|
|
36748
|
+
if (v.kind === "failed" && v.reason === "replay") return null;
|
|
36749
|
+
if (v.kind !== "verified" && v.kind !== "constants_only" && v.kind !== "failed") return null;
|
|
36750
|
+
const failedReason = v.kind === "failed" ? v.reason : void 0;
|
|
36751
|
+
const blocked = i.killSwitchOff || i.pinMissingOrAnomalous || i.senderNotOwner || i.stale || v.kind === "failed";
|
|
36752
|
+
const eligible = v.kind === "verified" && i.signedDmChannel === i.channelId && i.boundNow && i.consumerAttached;
|
|
36753
|
+
const source = blocked ? "none" : !i.manifestPresent ? "constant" : !eligible || i.manifestSwitchOff ? "fallback" : "manifest";
|
|
36754
|
+
const at = (cause, sub) => sub === void 0 ? { source, cause } : { source, cause, sub };
|
|
36755
|
+
if (i.killSwitchOff) return at("owner_dm_unavailable", "kill_switch");
|
|
36756
|
+
if (i.manifestSwitchOff) return at("manifest_switch_off");
|
|
36757
|
+
if (i.pinMissingOrAnomalous) return at("owner_dm_unavailable", "pin");
|
|
36758
|
+
if (i.senderNotOwner) return at("owner_dm_unavailable", "sender_not_owner");
|
|
36759
|
+
if (i.stale) return at("owner_dm_unavailable", "stale");
|
|
36760
|
+
if (failedReason === "not_owner_dm_leaves") return at("unexpected_member_in_dm");
|
|
36761
|
+
if (failedReason !== void 0 && SIGNATURE_FAILURES.has(failedReason)) return at("owner_turn_failed");
|
|
36762
|
+
if (failedReason === "verify_error") return at("agent_state_error");
|
|
36763
|
+
if (failedReason === "wrong_dm_channel" || v.kind === "verified" && i.manifestPresent && i.signedDmChannel !== void 0 && i.signedDmChannel !== i.channelId) {
|
|
36764
|
+
return at("dm_channel_mismatch");
|
|
36765
|
+
}
|
|
36766
|
+
if (failedReason !== void 0) return null;
|
|
36767
|
+
if (i.manifestPresent && i.signedDmChannel === void 0) return at("dm_not_linked");
|
|
36768
|
+
if (v.kind === "constants_only") return at("unsigned_relay_in_dm");
|
|
36769
|
+
if (!i.manifestPresent || !i.boundNow) return at("manifest_not_bound");
|
|
36770
|
+
if (!i.consumerAttached) return at("no_runtime_consumer");
|
|
36771
|
+
return source === "manifest" ? { source } : null;
|
|
36772
|
+
} catch {
|
|
36773
|
+
return null;
|
|
36774
|
+
}
|
|
36775
|
+
}
|
|
36776
|
+
function sameTuple(a, b) {
|
|
36777
|
+
if (!a || !b) return false;
|
|
36778
|
+
return a.source === b.source && a.cause === b.cause && a.sub === b.sub && a.relay === b.relay && a.mcp_issued === b.mcp_issued && a.counter === b.counter && a.issued.length === b.issued.length && a.issued.every((t, k) => t === b.issued[k]) && a.withheld.length === b.withheld.length && a.withheld.every((w, k) => w.tool === b.withheld[k].tool && w.why === b.withheld[k].why);
|
|
36779
|
+
}
|
|
36780
|
+
function sanitizeIssued(names) {
|
|
36781
|
+
return [...new Set(names.filter((n) => ALLOW2.has(n)))].sort();
|
|
36782
|
+
}
|
|
36783
|
+
|
|
36784
|
+
// src/tool-status-sender.ts
|
|
36785
|
+
var TOOL_STATUS_JITTER_MS = 6e4;
|
|
36786
|
+
var RETRY_AFTER_FLOOR_MS = 6e4;
|
|
36787
|
+
function defaultToolStatusSetTimer(fn, ms) {
|
|
36788
|
+
const t = setTimeout(fn, ms);
|
|
36789
|
+
t.unref?.();
|
|
36790
|
+
return t;
|
|
36791
|
+
}
|
|
36792
|
+
function defaultToolStatusClearTimer(h) {
|
|
36793
|
+
clearTimeout(h);
|
|
36794
|
+
}
|
|
36795
|
+
function retryAfterMs(raw) {
|
|
36796
|
+
if (typeof raw !== "string" || !/^\d{1,9}$/.test(raw.trim())) return RETRY_AFTER_FLOOR_MS;
|
|
36797
|
+
return Math.max(RETRY_AFTER_FLOOR_MS, Number(raw.trim()) * 1e3);
|
|
36798
|
+
}
|
|
36799
|
+
var ToolStatusSender = class {
|
|
36800
|
+
postFn;
|
|
36801
|
+
now;
|
|
36802
|
+
setTimer;
|
|
36803
|
+
clearTimer;
|
|
36804
|
+
random;
|
|
36805
|
+
onFailure;
|
|
36806
|
+
/** The latest noted record while its bucket is still open. */
|
|
36807
|
+
open = null;
|
|
36808
|
+
/** The latest noted record whose bucket has closed: the only record an attempt or carry() may send. */
|
|
36809
|
+
closed = null;
|
|
36810
|
+
lastPosted = null;
|
|
36811
|
+
terminalFor = null;
|
|
36812
|
+
notBefore = Number.NEGATIVE_INFINITY;
|
|
36813
|
+
timer = null;
|
|
36814
|
+
inFlight = false;
|
|
36815
|
+
shut = false;
|
|
36816
|
+
constructor(o) {
|
|
36817
|
+
this.postFn = o.post;
|
|
36818
|
+
this.now = o.now ?? (() => Date.now());
|
|
36819
|
+
this.setTimer = o.setTimer ?? defaultToolStatusSetTimer;
|
|
36820
|
+
this.clearTimer = o.clearTimer ?? defaultToolStatusClearTimer;
|
|
36821
|
+
this.random = o.random ?? Math.random;
|
|
36822
|
+
this.onFailure = o.onFailure ?? (() => {
|
|
36823
|
+
});
|
|
36824
|
+
}
|
|
36825
|
+
note(rec) {
|
|
36826
|
+
if (this.shut) return;
|
|
36827
|
+
try {
|
|
36828
|
+
this.promote(this.now());
|
|
36829
|
+
this.open = rec;
|
|
36830
|
+
this.schedule();
|
|
36831
|
+
} catch {
|
|
36832
|
+
}
|
|
36833
|
+
}
|
|
36834
|
+
/** Ruling M-2: what the apply-time report carries. The closed record only; an open bucket is never revealed early. */
|
|
36835
|
+
carry() {
|
|
36836
|
+
try {
|
|
36837
|
+
this.promote(this.now());
|
|
36838
|
+
} catch {
|
|
36839
|
+
}
|
|
36840
|
+
return this.closed;
|
|
36841
|
+
}
|
|
36842
|
+
/** R2-M1: the record belongs to an owner who is no longer the manifest's; forget it and what was posted. */
|
|
36843
|
+
drop() {
|
|
36844
|
+
this.open = null;
|
|
36845
|
+
this.closed = null;
|
|
36846
|
+
this.lastPosted = null;
|
|
36847
|
+
this.terminalFor = null;
|
|
36848
|
+
this.cancelTimer();
|
|
36849
|
+
}
|
|
36850
|
+
close() {
|
|
36851
|
+
this.shut = true;
|
|
36852
|
+
this.cancelTimer();
|
|
36853
|
+
}
|
|
36854
|
+
cancelTimer() {
|
|
36855
|
+
if (this.timer !== null) this.clearTimer(this.timer);
|
|
36856
|
+
this.timer = null;
|
|
36857
|
+
}
|
|
36858
|
+
promote(t) {
|
|
36859
|
+
if (this.open !== null && bucketEndMs(this.open.at) <= t) {
|
|
36860
|
+
this.closed = this.open;
|
|
36861
|
+
this.open = null;
|
|
36862
|
+
}
|
|
36863
|
+
}
|
|
36864
|
+
pending() {
|
|
36865
|
+
return this.closed !== null && !sameTuple(this.closed, this.lastPosted) && !sameTuple(this.closed, this.terminalFor);
|
|
36866
|
+
}
|
|
36867
|
+
schedule() {
|
|
36868
|
+
if (this.shut || this.inFlight || this.timer !== null) return;
|
|
36869
|
+
if (this.open === null && !this.pending()) return;
|
|
36870
|
+
const t = this.now();
|
|
36871
|
+
let b = (Math.floor(t / TOOL_STATUS_BUCKET_MS) + 1) * TOOL_STATUS_BUCKET_MS;
|
|
36872
|
+
while (b < this.notBefore) b += TOOL_STATUS_BUCKET_MS;
|
|
36873
|
+
const jitter = Math.floor(this.random() * TOOL_STATUS_JITTER_MS);
|
|
36874
|
+
this.timer = this.setTimer(() => {
|
|
36875
|
+
this.timer = null;
|
|
36876
|
+
this.fire();
|
|
36877
|
+
}, b - t + jitter);
|
|
36878
|
+
}
|
|
36879
|
+
fire() {
|
|
36880
|
+
try {
|
|
36881
|
+
if (this.shut) return;
|
|
36882
|
+
this.promote(this.now());
|
|
36883
|
+
if (this.pending()) {
|
|
36884
|
+
void this.attempt(this.closed);
|
|
36885
|
+
return;
|
|
36886
|
+
}
|
|
36887
|
+
this.schedule();
|
|
36888
|
+
} catch {
|
|
36889
|
+
}
|
|
36890
|
+
}
|
|
36891
|
+
async attempt(rec) {
|
|
36892
|
+
this.inFlight = true;
|
|
36893
|
+
const attemptAt = this.now();
|
|
36894
|
+
let result;
|
|
36895
|
+
try {
|
|
36896
|
+
result = await this.postFn(rec);
|
|
36897
|
+
} catch {
|
|
36898
|
+
result = "network";
|
|
36899
|
+
}
|
|
36900
|
+
this.inFlight = false;
|
|
36901
|
+
try {
|
|
36902
|
+
if (typeof result === "object" && result.status >= 200 && result.status < 300) {
|
|
36903
|
+
this.lastPosted = rec;
|
|
36904
|
+
} else if (typeof result === "object" && result.status === 429) {
|
|
36905
|
+
this.notBefore = attemptAt + retryAfterMs(result.retryAfter);
|
|
36906
|
+
this.onFailure("http_error");
|
|
36907
|
+
} else if (typeof result === "object" && result.status >= 400 && result.status < 500) {
|
|
36908
|
+
this.terminalFor = rec;
|
|
36909
|
+
this.onFailure("http_error");
|
|
36910
|
+
} else {
|
|
36911
|
+
this.onFailure(typeof result === "object" ? "http_error" : result);
|
|
36912
|
+
}
|
|
36913
|
+
this.schedule();
|
|
36914
|
+
} catch {
|
|
36915
|
+
}
|
|
36916
|
+
}
|
|
36917
|
+
};
|
|
36918
|
+
|
|
36310
36919
|
// src/grants/owner-turn.ts
|
|
36311
36920
|
import { ed25519 as ed255196 } from "@noble/curves/ed25519";
|
|
36312
36921
|
var HEX = /^(?:[0-9a-fA-F]{2})+$/;
|
|
@@ -36350,6 +36959,310 @@ function classifyOwnerDmLeaves(leaves, allowed) {
|
|
|
36350
36959
|
}
|
|
36351
36960
|
return "foreign";
|
|
36352
36961
|
}
|
|
36962
|
+
function ownerDmLeavesRelayFree(leaves, ids) {
|
|
36963
|
+
let owner = false;
|
|
36964
|
+
let agent = false;
|
|
36965
|
+
for (const l of leaves) {
|
|
36966
|
+
if (!l.basic || l.identity === null) return false;
|
|
36967
|
+
if (l.identity === ids.ownerIdentityId) owner = true;
|
|
36968
|
+
else if (l.identity === ids.agentIdentityId) agent = true;
|
|
36969
|
+
else return false;
|
|
36970
|
+
}
|
|
36971
|
+
return owner && agent;
|
|
36972
|
+
}
|
|
36973
|
+
function relayLeafFromPhase0Relay(relay) {
|
|
36974
|
+
return relay !== "none";
|
|
36975
|
+
}
|
|
36976
|
+
function ownerDmRelayOf(leafClass, leaves, ids) {
|
|
36977
|
+
if (leafClass === "unsigned_relay") return "unsigned";
|
|
36978
|
+
if (ownerDmLeavesRelayFree(leaves, ids)) return "none";
|
|
36979
|
+
if (ids.relayIdentityId !== void 0 && leaves.some((l) => l.identity === ids.relayIdentityId)) return "signed";
|
|
36980
|
+
return void 0;
|
|
36981
|
+
}
|
|
36982
|
+
|
|
36983
|
+
// src/grants/mcp-policy.ts
|
|
36984
|
+
var MCP_POLICY_WRITER_FEATURE = "mcp_policy_writer_v1";
|
|
36985
|
+
function mcpEntitlementReading(mcpEntitled, mcpCause) {
|
|
36986
|
+
if (mcpEntitled === true && mcpCause === "entitled") return "entitled";
|
|
36987
|
+
if (mcpEntitled === false && mcpCause === "not_entitled") return "not_entitled";
|
|
36988
|
+
return "suspended";
|
|
36989
|
+
}
|
|
36990
|
+
function mcpGrantsHostSwitch(env, log = writeServiceLog) {
|
|
36991
|
+
const v = env.SECURECOMS_MCP_GRANTS;
|
|
36992
|
+
if (v === void 0 || v === "on") return "on";
|
|
36993
|
+
if (v === "off") return "off";
|
|
36994
|
+
log("warn", "config.mcp_grants_switch_suspended", { state: ServiceLogEnum.of("suspended", ["suspended"]) });
|
|
36995
|
+
return "suspended";
|
|
36996
|
+
}
|
|
36997
|
+
var empty = (state, r3Hold) => Object.freeze({ v: 1, state, r3_hold: r3Hold, servers: Object.freeze({}), remove: NO_REMOVE });
|
|
36998
|
+
var NO_REMOVE = Object.freeze([]);
|
|
36999
|
+
function policyAuth(s) {
|
|
37000
|
+
if (s.auth.mode === "key") return Object.freeze({ mode: "key" });
|
|
37001
|
+
const clientId = s.auth.client === "customer" ? s.auth.client_id : SECURECOMS_CIMD_CLIENT_ID;
|
|
37002
|
+
return Object.freeze({ mode: "oauth", issuer: s.auth.issuer, client: s.auth.client, client_id: clientId });
|
|
37003
|
+
}
|
|
37004
|
+
function mcpPolicyFor(i) {
|
|
37005
|
+
const m = i.manifest;
|
|
37006
|
+
const r3Hold = m?.signedMcpPresent === true || i.mcpSeen;
|
|
37007
|
+
if (i.hostSwitch === "off") return empty("kill_switch", r3Hold);
|
|
37008
|
+
if (i.hostSwitch !== "on") return empty("suspended", r3Hold);
|
|
37009
|
+
if (!i.config.fetched) return empty("no_config", r3Hold);
|
|
37010
|
+
const ent = mcpEntitlementReading(i.config.mcp_entitled, i.config.mcp_cause);
|
|
37011
|
+
if (ent !== "entitled") return empty(ent, r3Hold);
|
|
37012
|
+
if (!i.ownerDmManifestSwitchOn) return empty("switch_off", r3Hold);
|
|
37013
|
+
if (m === null) return empty("no_manifest", r3Hold);
|
|
37014
|
+
if (m.mcpKey === "absent") return empty("section_absent", r3Hold);
|
|
37015
|
+
if (m.mcp === null) return empty("malformed", r3Hold);
|
|
37016
|
+
if (!i.capability) return empty("no_capability", r3Hold);
|
|
37017
|
+
const catalog = i.catalog ?? MCP_CATALOG;
|
|
37018
|
+
const granted = /* @__PURE__ */ new Map();
|
|
37019
|
+
for (const name of m.grants.owner_dm ?? []) {
|
|
37020
|
+
const g = parseMcpGrant(name);
|
|
37021
|
+
if (g === null) continue;
|
|
37022
|
+
if (!granted.has(g.serverId)) granted.set(g.serverId, /* @__PURE__ */ new Set());
|
|
37023
|
+
granted.get(g.serverId).add(g.tool);
|
|
37024
|
+
}
|
|
37025
|
+
const servers = {};
|
|
37026
|
+
for (const id of Object.keys(m.mcp.servers).sort()) {
|
|
37027
|
+
const s = m.mcp.servers[id];
|
|
37028
|
+
const row = mcpServerCatalogRow(s, catalog);
|
|
37029
|
+
if (row === null) {
|
|
37030
|
+
servers[id] = Object.freeze({ state: "refused" });
|
|
37031
|
+
continue;
|
|
37032
|
+
}
|
|
37033
|
+
const tools = {};
|
|
37034
|
+
for (const t of Object.keys(s.tools).sort()) {
|
|
37035
|
+
if (granted.get(id)?.has(t) === true && row.readTools.includes(t)) tools[t] = Object.freeze({ sha256: s.tools[t].sha256, class: "read" });
|
|
37036
|
+
}
|
|
37037
|
+
servers[id] = Object.freeze({ state: "ok", vendor: s.vendor, url: row.readOnlyUrl, auth: policyAuth(s), tools: Object.freeze(tools) });
|
|
37038
|
+
}
|
|
37039
|
+
const remove = m.mcp.removed === "*" ? "*" : Object.freeze([...m.mcp.removed]);
|
|
37040
|
+
return Object.freeze({ v: 1, state: "ok", r3_hold: r3Hold, servers: Object.freeze(servers), remove });
|
|
37041
|
+
}
|
|
37042
|
+
|
|
37043
|
+
// src/grants/mcp-issuance.ts
|
|
37044
|
+
var NONE = Object.freeze([]);
|
|
37045
|
+
var own = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
|
|
37046
|
+
function mcpPolicyAllows(policy, serverId, tool) {
|
|
37047
|
+
if (policy === null || policy.state !== "ok" || !own(policy.servers, serverId)) return false;
|
|
37048
|
+
const s = policy.servers[serverId];
|
|
37049
|
+
return s.state === "ok" && own(s.tools, tool);
|
|
37050
|
+
}
|
|
37051
|
+
function mcpIssuance(i) {
|
|
37052
|
+
if (i.source !== "manifest") return NONE;
|
|
37053
|
+
if (i.verdict?.kind !== "verified" || i.verdict.relayLeaf !== false) return NONE;
|
|
37054
|
+
if (i.entitlement !== "entitled" || i.hostSwitch !== "on" || !i.ownerDmManifestSwitchOn) return NONE;
|
|
37055
|
+
if (i.manifest === null || i.policy === null || i.policy.state !== "ok") return NONE;
|
|
37056
|
+
const out = [];
|
|
37057
|
+
for (const name of i.manifest.grants.owner_dm ?? []) {
|
|
37058
|
+
const g = parseMcpGrant(name);
|
|
37059
|
+
if (g === null || i.disabled.has(name) || !mcpPolicyAllows(i.policy, g.serverId, g.tool)) continue;
|
|
37060
|
+
out.push(Object.freeze({ tool: name, mode: "read", source: "manifest" }));
|
|
37061
|
+
}
|
|
37062
|
+
return out.length === 0 ? NONE : Object.freeze(out);
|
|
37063
|
+
}
|
|
37064
|
+
|
|
37065
|
+
// src/consumer-grants.ts
|
|
37066
|
+
var NO_GRANTS = Object.freeze([]);
|
|
37067
|
+
var PER_TOOL_GRANTS_FEATURE = "per_tool_grants";
|
|
37068
|
+
function withConsumerGrants(m, perToolConsumer) {
|
|
37069
|
+
if (perToolConsumer) return m;
|
|
37070
|
+
const a = m.authorization;
|
|
37071
|
+
if (!a) return m;
|
|
37072
|
+
const stripGrants = !(a.tool_grants.length === 0 || a.audience === "owner_dm" || a.relay_speaker === "owner");
|
|
37073
|
+
const stripMemory = "memory_write" in a;
|
|
37074
|
+
if (!stripGrants && !stripMemory) return m;
|
|
37075
|
+
const { memory_write: _dropped, ...rest } = a;
|
|
37076
|
+
return { ...m, authorization: Object.freeze({ ...rest, ...stripGrants ? { tool_grants: NO_GRANTS } : {} }) };
|
|
37077
|
+
}
|
|
37078
|
+
var MANIFEST_GRANTS_FEATURE = "manifest_grants_v1";
|
|
37079
|
+
var SEARCH_PROVIDERS_FEATURE = "search_providers_v2";
|
|
37080
|
+
var UTILITY_TOOLS_FEATURE = "utility_tools_v1";
|
|
37081
|
+
var MCP_CLIENT_FEATURE = "mcp_client_v1";
|
|
37082
|
+
var CONSTANT_TOOL_NAMES = Object.freeze({
|
|
37083
|
+
owner_dm: Object.freeze(["current_time", "web_search"]),
|
|
37084
|
+
team_room: Object.freeze(["current_time"])
|
|
37085
|
+
});
|
|
37086
|
+
var isManifestGrant = (g) => g.mode === "read" && g.source === "manifest";
|
|
37087
|
+
function withManifestCapability(m, manifestConsumer) {
|
|
37088
|
+
if (manifestConsumer) return m;
|
|
37089
|
+
const a = m.authorization;
|
|
37090
|
+
if (!a || !a.tool_grants.some(isManifestGrant)) return m;
|
|
37091
|
+
const constants5 = new Set(a.audience === "owner_dm" || a.audience === "team_room" ? CONSTANT_TOOL_NAMES[a.audience] : []);
|
|
37092
|
+
const narrowed = a.tool_grants.flatMap((g) => {
|
|
37093
|
+
if (!isManifestGrant(g)) return [g];
|
|
37094
|
+
return constants5.has(g.tool) ? [Object.freeze({ tool: g.tool, mode: "read" })] : [];
|
|
37095
|
+
});
|
|
37096
|
+
return { ...m, authorization: Object.freeze({ ...a, tool_grants: Object.freeze(narrowed) }) };
|
|
37097
|
+
}
|
|
37098
|
+
function withHarnessGrants(m, withheld) {
|
|
37099
|
+
if (!withheld) return m;
|
|
37100
|
+
const a = m.authorization;
|
|
37101
|
+
if (!a || a.tool_grants.length === 0) return m;
|
|
37102
|
+
return { ...m, authorization: Object.freeze({ ...a, tool_grants: NO_GRANTS }) };
|
|
37103
|
+
}
|
|
37104
|
+
function withSearchCapability(m, consumerHasFeature, signedProvider) {
|
|
37105
|
+
if (consumerHasFeature) return m;
|
|
37106
|
+
const a = m.authorization;
|
|
37107
|
+
if (!a) return m;
|
|
37108
|
+
const stripSearch = signedProvider !== void 0 && signedProvider !== "brave";
|
|
37109
|
+
const kept = a.tool_grants.filter((g) => g.tool !== "web_extract" && !(stripSearch && g.tool === "web_search"));
|
|
37110
|
+
if (kept.length === a.tool_grants.length) return m;
|
|
37111
|
+
return { ...m, authorization: Object.freeze({ ...a, tool_grants: kept.length === 0 ? NO_GRANTS : Object.freeze(kept) }) };
|
|
37112
|
+
}
|
|
37113
|
+
var UTILITY_TOOL_NAMES = /* @__PURE__ */ new Set(["weather_forecast", "currency_convert"]);
|
|
37114
|
+
function withUtilityCapability(m, consumerHasFeature) {
|
|
37115
|
+
if (consumerHasFeature) return m;
|
|
37116
|
+
const a = m.authorization;
|
|
37117
|
+
if (!a) return m;
|
|
37118
|
+
const kept = a.tool_grants.filter((g) => !UTILITY_TOOL_NAMES.has(g.tool));
|
|
37119
|
+
if (kept.length === a.tool_grants.length) return m;
|
|
37120
|
+
return { ...m, authorization: Object.freeze({ ...a, tool_grants: kept.length === 0 ? NO_GRANTS : Object.freeze(kept) }) };
|
|
37121
|
+
}
|
|
37122
|
+
var MCP_GRANT_PREFIX = "mcp:";
|
|
37123
|
+
var isMcpGrant = (g) => g.tool.startsWith(MCP_GRANT_PREFIX);
|
|
37124
|
+
function withMcpCapability(m, consumerHasFeature) {
|
|
37125
|
+
const a = m.authorization;
|
|
37126
|
+
if (!a) return m;
|
|
37127
|
+
const { relay_free: _relayFree, ...rest } = a;
|
|
37128
|
+
if (!a.tool_grants.some(isMcpGrant)) return "relay_free" in a ? { ...m, authorization: Object.freeze(rest) } : m;
|
|
37129
|
+
if (consumerHasFeature && a.audience === "owner_dm" && a.relay_speaker === void 0) return m;
|
|
37130
|
+
const kept = a.tool_grants.filter((g) => !isMcpGrant(g));
|
|
37131
|
+
return { ...m, authorization: Object.freeze({ ...rest, tool_grants: kept.length === 0 ? NO_GRANTS : Object.freeze(kept) }) };
|
|
37132
|
+
}
|
|
37133
|
+
var R3_KEEP_TOOLS = /* @__PURE__ */ new Set(["current_time", "web_search", "weather_forecast", "currency_convert"]);
|
|
37134
|
+
function withR3Holds(m, hold) {
|
|
37135
|
+
if (!hold) return m;
|
|
37136
|
+
const a = m.authorization;
|
|
37137
|
+
if (!a || !(a.audience === "owner_dm" || a.relay_speaker === "owner")) return m;
|
|
37138
|
+
const kept = a.tool_grants.filter((g) => R3_KEEP_TOOLS.has(g.tool) || isMcpGrant(g));
|
|
37139
|
+
if (kept.length === a.tool_grants.length) return m;
|
|
37140
|
+
return { ...m, authorization: Object.freeze({ ...a, tool_grants: kept.length === 0 ? NO_GRANTS : Object.freeze(kept) }) };
|
|
37141
|
+
}
|
|
37142
|
+
|
|
37143
|
+
// src/mcp-catalog-message.ts
|
|
37144
|
+
var MCP_CATALOG_MAX_BYTES = 384 * 1024;
|
|
37145
|
+
var MCP_CATALOG_MAX_TOOLS = 64;
|
|
37146
|
+
var MCP_CATALOG_LIST_TIMEOUT_MS = 5e4;
|
|
37147
|
+
var REQUEST_ID = /^[0-9a-f]{32}$/;
|
|
37148
|
+
var SERVER_ID = /^[a-z0-9-]{1,24}$/;
|
|
37149
|
+
var TOOL_NAME = /^[A-Za-z0-9_.-]{1,64}$/;
|
|
37150
|
+
var RESERVED = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
37151
|
+
var ISO_TIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/;
|
|
37152
|
+
var MCP_CATALOG_REASONS = /* @__PURE__ */ new Set(["catalog", "write_name", "hint", "schema", "header_param", "too_large"]);
|
|
37153
|
+
var isBag2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
37154
|
+
var keysExactly2 = (o, keys) => {
|
|
37155
|
+
const k = Object.keys(o);
|
|
37156
|
+
return k.length === keys.length && keys.every((x) => Object.prototype.hasOwnProperty.call(o, x));
|
|
37157
|
+
};
|
|
37158
|
+
function parseMcpCatalogRequest(body) {
|
|
37159
|
+
if (!isBag2(body) || !keysExactly2(body, ["v", "type", "server_id", "request_id"])) return null;
|
|
37160
|
+
if (body.v !== 1 || body.type !== "mcp_catalog_request") return null;
|
|
37161
|
+
const { server_id: serverId, request_id: requestId } = body;
|
|
37162
|
+
if (typeof serverId !== "string" || !SERVER_ID.test(serverId) || RESERVED.has(serverId)) return null;
|
|
37163
|
+
if (typeof requestId !== "string" || !REQUEST_ID.test(requestId)) return null;
|
|
37164
|
+
return Object.freeze({ serverId, requestId });
|
|
37165
|
+
}
|
|
37166
|
+
var PROJECTION_KEYS = /* @__PURE__ */ new Set(["name", "title", "description", "inputSchema", "annotations"]);
|
|
37167
|
+
function validProjection(p, toolName) {
|
|
37168
|
+
if (!Object.keys(p).every((k) => PROJECTION_KEYS.has(k))) return false;
|
|
37169
|
+
if (p.name !== toolName || typeof p.description !== "string" || !isBag2(p.inputSchema)) return false;
|
|
37170
|
+
if (Object.prototype.hasOwnProperty.call(p, "title") && typeof p.title !== "string") return false;
|
|
37171
|
+
if (Object.prototype.hasOwnProperty.call(p, "annotations") && !isBag2(p.annotations)) return false;
|
|
37172
|
+
return true;
|
|
37173
|
+
}
|
|
37174
|
+
function validTool(t) {
|
|
37175
|
+
if (!isBag2(t) || typeof t.name !== "string" || typeof t.grantable !== "boolean") return null;
|
|
37176
|
+
if (t.grantable) {
|
|
37177
|
+
if (!TOOL_NAME.test(t.name) || t.reason !== null || !isBag2(t.projection) || !validProjection(t.projection, t.name)) return null;
|
|
37178
|
+
return { name: t.name, projection: t.projection, grantable: true, reason: null };
|
|
37179
|
+
}
|
|
37180
|
+
if (t.projection !== void 0 || typeof t.reason !== "string" || !MCP_CATALOG_REASONS.has(t.reason)) return null;
|
|
37181
|
+
if (t.name.length < 1 || t.name.length > 64 || !/^[\x20-\x7e]+$/.test(t.name)) return null;
|
|
37182
|
+
return { name: t.name, grantable: false, reason: t.reason };
|
|
37183
|
+
}
|
|
37184
|
+
function validateCatalogListReply(frame, serverId, vendor) {
|
|
37185
|
+
if (!isBag2(frame) || frame.ok !== true || frame.server_id !== serverId || frame.vendor !== vendor) return null;
|
|
37186
|
+
if (typeof frame.listed_at !== "string" || !ISO_TIME.test(frame.listed_at) || typeof frame.truncated !== "boolean") return null;
|
|
37187
|
+
if (!Array.isArray(frame.tools) || frame.tools.length > MCP_CATALOG_MAX_TOOLS) return null;
|
|
37188
|
+
const tools = [];
|
|
37189
|
+
const names = /* @__PURE__ */ new Set();
|
|
37190
|
+
for (const t of frame.tools) {
|
|
37191
|
+
const v = validTool(t);
|
|
37192
|
+
if (v === null) return null;
|
|
37193
|
+
if (names.has(v.name)) return null;
|
|
37194
|
+
names.add(v.name);
|
|
37195
|
+
tools.push(v);
|
|
37196
|
+
}
|
|
37197
|
+
return { listedAt: frame.listed_at, truncated: frame.truncated, tools };
|
|
37198
|
+
}
|
|
37199
|
+
var utf8 = (v) => Buffer.byteLength(JSON.stringify(v), "utf8");
|
|
37200
|
+
var mcpCatalogBodyBytes = (b) => utf8(b);
|
|
37201
|
+
function buildMcpCatalogBody(i) {
|
|
37202
|
+
const head = { v: 1, type: "mcp_catalog", server_id: i.serverId, request_id: i.requestId, vendor: i.vendor, issuer: i.issuer, listed_at: i.reply.listedAt };
|
|
37203
|
+
const full = { ...head, tools: [...i.reply.tools], truncated: i.reply.truncated };
|
|
37204
|
+
if (mcpCatalogBodyBytes(full) <= MCP_CATALOG_MAX_BYTES) return full;
|
|
37205
|
+
const stub = (t) => ({ name: t.name, grantable: false, reason: "too_large" });
|
|
37206
|
+
const tools = i.reply.tools.map(stub);
|
|
37207
|
+
const body = { ...head, tools, truncated: true };
|
|
37208
|
+
let size = mcpCatalogBodyBytes(body);
|
|
37209
|
+
for (let k = 0; k < tools.length; k++) {
|
|
37210
|
+
const want = i.reply.tools[k];
|
|
37211
|
+
const delta = utf8(want) - utf8(tools[k]);
|
|
37212
|
+
if (size + delta <= MCP_CATALOG_MAX_BYTES) {
|
|
37213
|
+
tools[k] = want;
|
|
37214
|
+
size += delta;
|
|
37215
|
+
}
|
|
37216
|
+
}
|
|
37217
|
+
return body;
|
|
37218
|
+
}
|
|
37219
|
+
|
|
37220
|
+
// src/mcp-catalog-rate.ts
|
|
37221
|
+
var MINUTE_MS = 6e4;
|
|
37222
|
+
var HOUR_MS = 60 * MINUTE_MS;
|
|
37223
|
+
var MCP_CATALOG_PER_SERVER_MINUTE = 3;
|
|
37224
|
+
var MCP_CATALOG_PER_SERVER_HOUR = 20;
|
|
37225
|
+
var MCP_CATALOG_PER_AGENT_HOUR = 40;
|
|
37226
|
+
var McpCatalogRateLimiter = class {
|
|
37227
|
+
now;
|
|
37228
|
+
onRateLimited;
|
|
37229
|
+
inFlight = /* @__PURE__ */ new Set();
|
|
37230
|
+
perServer = /* @__PURE__ */ new Map();
|
|
37231
|
+
agent = [];
|
|
37232
|
+
/** Per scope key: no further log until this time (the end of the window that was full when it last logged). */
|
|
37233
|
+
quietUntil = /* @__PURE__ */ new Map();
|
|
37234
|
+
constructor(deps = {}) {
|
|
37235
|
+
this.now = deps.now ?? Date.now;
|
|
37236
|
+
this.onRateLimited = deps.onRateLimited ?? (() => {
|
|
37237
|
+
});
|
|
37238
|
+
}
|
|
37239
|
+
/** "ok" admits (and counts) the request and marks the server in flight; the caller MUST call release(serverId). */
|
|
37240
|
+
admit(serverId) {
|
|
37241
|
+
if (this.inFlight.has(serverId)) return "in_flight";
|
|
37242
|
+
const t = this.now();
|
|
37243
|
+
const server = (this.perServer.get(serverId) ?? []).filter((x) => t - x < HOUR_MS);
|
|
37244
|
+
this.perServer.set(serverId, server);
|
|
37245
|
+
this.agent = this.agent.filter((x) => t - x < HOUR_MS);
|
|
37246
|
+
const lastMinute = server.filter((x) => t - x < MINUTE_MS);
|
|
37247
|
+
if (lastMinute.length >= MCP_CATALOG_PER_SERVER_MINUTE) return this.limited(`m:${serverId}`, "server_minute", lastMinute[0] + MINUTE_MS, t);
|
|
37248
|
+
if (server.length >= MCP_CATALOG_PER_SERVER_HOUR) return this.limited(`h:${serverId}`, "server_hour", server[0] + HOUR_MS, t);
|
|
37249
|
+
if (this.agent.length >= MCP_CATALOG_PER_AGENT_HOUR) return this.limited("a", "agent_hour", this.agent[0] + HOUR_MS, t);
|
|
37250
|
+
server.push(t);
|
|
37251
|
+
this.agent.push(t);
|
|
37252
|
+
this.inFlight.add(serverId);
|
|
37253
|
+
return "ok";
|
|
37254
|
+
}
|
|
37255
|
+
release(serverId) {
|
|
37256
|
+
this.inFlight.delete(serverId);
|
|
37257
|
+
}
|
|
37258
|
+
limited(key, scope, windowEnd, t) {
|
|
37259
|
+
if (t >= (this.quietUntil.get(key) ?? -Infinity)) {
|
|
37260
|
+
this.quietUntil.set(key, windowEnd);
|
|
37261
|
+
this.onRateLimited(scope);
|
|
37262
|
+
}
|
|
37263
|
+
return "rate_limited";
|
|
37264
|
+
}
|
|
37265
|
+
};
|
|
36353
37266
|
|
|
36354
37267
|
// src/bridge-agent.ts
|
|
36355
37268
|
function logSendSupersededAdoption(kind2) {
|
|
@@ -36489,6 +37402,7 @@ function selectVoiceGuidance(isDm, voiceDefaultResponder) {
|
|
|
36489
37402
|
}
|
|
36490
37403
|
return VOICE_MODE_GUIDANCE;
|
|
36491
37404
|
}
|
|
37405
|
+
var MCP_INERT_STATES = /* @__PURE__ */ new Set(["suspended", "no_config", "switch_off", "no_manifest", "section_absent", "malformed", "no_capability"]);
|
|
36492
37406
|
var VOICE_COALESCE_WINDOW_MS = 0;
|
|
36493
37407
|
var VOICE_COALESCE_WINDOW_MAX_MS = 5e3;
|
|
36494
37408
|
function voiceCoalesceWindowFromEnv(env = process.env) {
|
|
@@ -36501,8 +37415,8 @@ function voiceWindowEligible(t) {
|
|
|
36501
37415
|
}
|
|
36502
37416
|
function waitMs(ms) {
|
|
36503
37417
|
if (ms <= 0) return Promise.resolve();
|
|
36504
|
-
return new Promise((
|
|
36505
|
-
const t = setTimeout(
|
|
37418
|
+
return new Promise((resolve5) => {
|
|
37419
|
+
const t = setTimeout(resolve5, ms);
|
|
36506
37420
|
t.unref?.();
|
|
36507
37421
|
});
|
|
36508
37422
|
}
|
|
@@ -36539,10 +37453,10 @@ var ChannelPendingError = class extends Error {
|
|
|
36539
37453
|
}
|
|
36540
37454
|
};
|
|
36541
37455
|
var RateLimitedError = class extends Error {
|
|
36542
|
-
constructor(channelId,
|
|
36543
|
-
super(`channel ${channelId} send rate limit exceeded (retry in ${
|
|
37456
|
+
constructor(channelId, retryAfterMs2) {
|
|
37457
|
+
super(`channel ${channelId} send rate limit exceeded (retry in ${retryAfterMs2}ms)`);
|
|
36544
37458
|
this.channelId = channelId;
|
|
36545
|
-
this.retryAfterMs =
|
|
37459
|
+
this.retryAfterMs = retryAfterMs2;
|
|
36546
37460
|
this.name = "RateLimitedError";
|
|
36547
37461
|
}
|
|
36548
37462
|
};
|
|
@@ -36639,7 +37553,7 @@ var FileOwnerAuthorityStore = class {
|
|
|
36639
37553
|
}
|
|
36640
37554
|
async readAll() {
|
|
36641
37555
|
try {
|
|
36642
|
-
return JSON.parse(await
|
|
37556
|
+
return JSON.parse(await fsp2.readFile(this.path, "utf8"));
|
|
36643
37557
|
} catch (e) {
|
|
36644
37558
|
if (e.code === "ENOENT") return {};
|
|
36645
37559
|
throw e;
|
|
@@ -36652,8 +37566,8 @@ var FileOwnerAuthorityStore = class {
|
|
|
36652
37566
|
async save(enclaveId, pin) {
|
|
36653
37567
|
const all = await this.readAll();
|
|
36654
37568
|
all[enclaveId] = pin;
|
|
36655
|
-
await
|
|
36656
|
-
await
|
|
37569
|
+
await fsp2.mkdir(dirname8(this.path), { recursive: true });
|
|
37570
|
+
await fsp2.writeFile(this.path, JSON.stringify(all), "utf8");
|
|
36657
37571
|
}
|
|
36658
37572
|
};
|
|
36659
37573
|
var AgentPausedSendError = class extends Error {
|
|
@@ -36670,6 +37584,31 @@ var SendForbiddenError = class extends Error {
|
|
|
36670
37584
|
this.name = "SendForbiddenError";
|
|
36671
37585
|
}
|
|
36672
37586
|
};
|
|
37587
|
+
var MCP_POLICY_RETRY_DELAYS_MS = Object.freeze([1e3, 5e3, 3e4, 12e4, 3e5]);
|
|
37588
|
+
function logMcpCatalogRequest(state) {
|
|
37589
|
+
writeServiceLog(state === "sent" ? "info" : "warn", "mcp.catalog_request", { state: ServiceLogEnum.of(state, [
|
|
37590
|
+
"malformed",
|
|
37591
|
+
"not_owner_dm",
|
|
37592
|
+
"not_verified",
|
|
37593
|
+
"replay",
|
|
37594
|
+
"relay_leaf",
|
|
37595
|
+
"not_ready",
|
|
37596
|
+
"in_flight",
|
|
37597
|
+
"runtime_timeout",
|
|
37598
|
+
"runtime_refused",
|
|
37599
|
+
"withheld",
|
|
37600
|
+
"send_failed",
|
|
37601
|
+
"failed",
|
|
37602
|
+
"sent"
|
|
37603
|
+
]) });
|
|
37604
|
+
}
|
|
37605
|
+
var McpReplyWithheldError = class extends SendForbiddenError {
|
|
37606
|
+
constructor(channelId) {
|
|
37607
|
+
super(channelId);
|
|
37608
|
+
this.name = "McpReplyWithheldError";
|
|
37609
|
+
this.message = "reply withheld: the chat is no longer private to the owner and the agent; no retry";
|
|
37610
|
+
}
|
|
37611
|
+
};
|
|
36673
37612
|
var RoomClassChangedSendError = class extends SendForbiddenError {
|
|
36674
37613
|
constructor(channelId) {
|
|
36675
37614
|
super(channelId);
|
|
@@ -36689,6 +37628,7 @@ function rosterFreshAt(entry, heldEpoch, heldGroupId) {
|
|
|
36689
37628
|
var RoomGrantEpochMovedError = class extends Error {
|
|
36690
37629
|
};
|
|
36691
37630
|
var ROOM_GRANT_TRACK_MAX = 4096;
|
|
37631
|
+
var MCP_DELIVERED_TRACK_MAX = 4096;
|
|
36692
37632
|
var VOICE_FRAME_PARENT_MAX = 4096;
|
|
36693
37633
|
var SessionSupersededError = class extends Error {
|
|
36694
37634
|
constructor() {
|
|
@@ -36840,8 +37780,12 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
36840
37780
|
onSupersedeUnopenable: (channelId, groupId) => this.healSupersedeUnopenable(channelId, groupId, this.drainExemptionAtCallIn())
|
|
36841
37781
|
});
|
|
36842
37782
|
this.home = deps.home ?? homedir4();
|
|
37783
|
+
this.mcpCatalog = deps.mcpCatalog;
|
|
37784
|
+
this.mcpDeliveredTrackMax = deps.mcpDeliveredTrackMax ?? MCP_DELIVERED_TRACK_MAX;
|
|
37785
|
+
this.mcpPolicyRetryDelaysMs = deps.mcpPolicyRetryDelaysMs?.length ? deps.mcpPolicyRetryDelaysMs : MCP_POLICY_RETRY_DELAYS_MS;
|
|
36843
37786
|
this.grantWriters = {
|
|
36844
37787
|
writeManifestPolicy: deps.grantWriters?.writeManifestPolicy ?? writeManifestPolicy,
|
|
37788
|
+
writeMcpPolicy: deps.grantWriters?.writeMcpPolicy ?? writeMcpPolicy,
|
|
36845
37789
|
writeManagedFiles: deps.grantWriters?.writeManagedFiles ?? writeManagedFiles,
|
|
36846
37790
|
bindRuntimeConfig: deps.grantWriters?.bindRuntimeConfig ?? bindRuntimeConfig,
|
|
36847
37791
|
removeManagedLlmSource: deps.grantWriters?.removeManagedLlmSource ?? removeManagedLlmSource
|
|
@@ -36860,7 +37804,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
36860
37804
|
threshold: EMBEDDING_MANIFEST.similarityThreshold,
|
|
36861
37805
|
warm: windowsSemanticOn(this.platform)
|
|
36862
37806
|
};
|
|
36863
|
-
return deps.rankingTransport ? deps.rankingTransport(init) : workerTransport(init,
|
|
37807
|
+
return deps.rankingTransport ? deps.rankingTransport(init) : workerTransport(init, dirname8(fileURLToPath2(import.meta.url)));
|
|
36864
37808
|
}
|
|
36865
37809
|
});
|
|
36866
37810
|
this.embedder = deps.embedder ?? (deps.hostMachine === true && deps.home === void 0 ? new EmbedderClient({ identityId: cfg.identityId, home: homedir4(), source: cfg.modelSource, fetchImpl: this.fetchImpl, platform: this.platform }) : INERT_EMBEDDER);
|
|
@@ -36871,7 +37815,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
36871
37815
|
modelId: EMBEDDING_MANIFEST.modelId,
|
|
36872
37816
|
threshold: EMBEDDING_MANIFEST.similarityThreshold
|
|
36873
37817
|
});
|
|
36874
|
-
this.heartbeat = new CallHeartbeat(this.machinePaths ?
|
|
37818
|
+
this.heartbeat = new CallHeartbeat(this.machinePaths ? join20(this.home, ".securecoms", "run", "voice-call") : null, cfg.identityId, VOICE_LIVE_WINDOW_MS);
|
|
36875
37819
|
this.reembed = new ReembedQueue({
|
|
36876
37820
|
store: this.store,
|
|
36877
37821
|
embedder: this.embedder,
|
|
@@ -36930,15 +37874,15 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
36930
37874
|
this.bootDrainWaitMs = deps.bootDrainWaitMs ?? BOOT_DRAIN_WAIT_MS_DEFAULT;
|
|
36931
37875
|
this.searchCapabilityRefetchDebounceMs = deps.searchCapabilityRefetchDebounceMs ?? SEARCH_CAPABILITY_REFETCH_DEBOUNCE_MS;
|
|
36932
37876
|
this.searchCapabilityRefetchMaxWaitMs = deps.searchCapabilityRefetchMaxWaitMs ?? SEARCH_CAPABILITY_REFETCH_MAX_WAIT_MS;
|
|
36933
|
-
this.replySleep = deps.replySleep ?? ((ms) => new Promise((
|
|
36934
|
-
const t = setTimeout(
|
|
37877
|
+
this.replySleep = deps.replySleep ?? ((ms) => new Promise((resolve5) => {
|
|
37878
|
+
const t = setTimeout(resolve5, ms);
|
|
36935
37879
|
t.unref?.();
|
|
36936
37880
|
}));
|
|
36937
37881
|
this.actionRateFloor = new TokenBucketFloor({ capacity: 1, refillPerMinute: 30, now: this.actionClock });
|
|
36938
37882
|
this.globalActionRateFloor = new TokenBucketFloor({ capacity: 1, refillPerMinute: 30, now: this.actionClock });
|
|
36939
37883
|
const self = this;
|
|
36940
37884
|
this.downloadBlobImpl = deps.downloadBlob ?? async function(blobId) {
|
|
36941
|
-
const url = `${cfg.apiBase}/api/v1/attachments/${encodeURIComponent(blobId)}?caller_identity_id=${encodeURIComponent(cfg.identityId)}`;
|
|
37885
|
+
const url = `${cfg.apiBase}/api/v1/attachments/${encodeURIComponent(assertBlobId(blobId))}?caller_identity_id=${encodeURIComponent(cfg.identityId)}`;
|
|
36942
37886
|
const res = await fetchWithDeadline(self.fetchImpl, url, { headers: { authorization: `Bearer ${cfg.apiKey}` } }, {
|
|
36943
37887
|
op: "attachment_download",
|
|
36944
37888
|
timeoutMs: FETCH_DEADLINE_MS.blobBase,
|
|
@@ -36958,8 +37902,9 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
36958
37902
|
const j = await res.json();
|
|
36959
37903
|
return String(j.attachment_id);
|
|
36960
37904
|
};
|
|
36961
|
-
this.attachmentsDirImpl = deps.attachmentsDir ??
|
|
36962
|
-
this.
|
|
37905
|
+
this.attachmentsDirImpl = deps.attachmentsDir ?? join20(dirname8(cfg.storePath), "attachments");
|
|
37906
|
+
this.vaultOutboxDirImpl = deps.vaultOutboxDir !== void 0 ? deps.vaultOutboxDir : cfg.storePath === ":memory:" ? null : join20(dirname8(cfg.storePath), VAULT_OUTBOX_DIRNAME);
|
|
37907
|
+
this.vaultOwnerStoreImpl = deps.vaultOwnerStore ?? new FileOwnerAuthorityStore(join20(dirname8(cfg.storePath), "vault-owner-pins.json"));
|
|
36963
37908
|
this.action = {
|
|
36964
37909
|
begin: (channelId) => this.actionBegin(channelId),
|
|
36965
37910
|
start: (opts) => this.actionStart(opts),
|
|
@@ -37326,6 +38271,39 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
37326
38271
|
roomDefaultGrants = roomDefaultGrantsEnabled();
|
|
37327
38272
|
/** Owner grants (spec §7.4): the two manifest switches, read once at construction. */
|
|
37328
38273
|
ownerDmManifestGrants = ownerDmManifestGrantsEnabled();
|
|
38274
|
+
/**
|
|
38275
|
+
* MCP client Task 16 (OQ-16, coordinator ruling D-7): SECURECOMS_MCP_GRANTS, read ONCE at construction. "off" writes
|
|
38276
|
+
* kill_switch (deleting); any value other than unset/"on"/"off" is suspended (non-deleting, one content-free warn).
|
|
38277
|
+
*/
|
|
38278
|
+
mcpHostSwitch = mcpGrantsHostSwitch(process.env);
|
|
38279
|
+
/** MCP Task 16 test seam (BridgeAgentDeps.mcpCatalog); undefined in production, so MCP_CATALOG decides. */
|
|
38280
|
+
mcpCatalog;
|
|
38281
|
+
/**
|
|
38282
|
+
* MCP Task 16 (OQ-1; PR-B1 audit M-2): the two raw /files fields from the LAST fetch. Any failed or rejected fetch
|
|
38283
|
+
* sets `fetched: false` (no_config, non-deleting): the last values are never kept to keep an `ok` document alive.
|
|
38284
|
+
*/
|
|
38285
|
+
mcpConfig = Object.freeze({ fetched: false });
|
|
38286
|
+
/**
|
|
38287
|
+
* MCP Task 16: the mcp-policy.json document this process last WROTE (the file the runtime reads), or null before the
|
|
38288
|
+
* first write and after a failed one (fail closed: issuance reads only a document known to be on disk).
|
|
38289
|
+
*/
|
|
38290
|
+
mcpPolicyWritten = null;
|
|
38291
|
+
mcpPolicyWrittenJson = null;
|
|
38292
|
+
/** PR-B2 follow-up: a failed write is retried on this backoff (the last value repeats) until one lands or close(). */
|
|
38293
|
+
mcpPolicyRetryDelaysMs;
|
|
38294
|
+
mcpPolicyRetryTimer = null;
|
|
38295
|
+
mcpPolicyRetryAttempt = 0;
|
|
38296
|
+
/** MCP Task 18: the loopback catalog listers of the attached runtime sockets that declared mcp_client_v1 (ws-server.ts). */
|
|
38297
|
+
mcpCatalogListers = /* @__PURE__ */ new Map();
|
|
38298
|
+
/** MCP Task 18 (OQ-15): per server 1 in flight, 3 / 60 s, 20 / hour; per agent 40 / hour. Memory only. */
|
|
38299
|
+
mcpCatalogRate = new McpCatalogRateLimiter({
|
|
38300
|
+
onRateLimited: (scope) => writeServiceLog("warn", "mcp.catalog_request", {
|
|
38301
|
+
state: ServiceLogEnum.of("catalog_rate_limited", ["catalog_rate_limited"]),
|
|
38302
|
+
scope: ServiceLogEnum.of(scope, ["server_minute", "server_hour", "agent_hour"])
|
|
38303
|
+
})
|
|
38304
|
+
});
|
|
38305
|
+
/** MCP Task 18: answers in progress (outside the ingest cycle). Observable by tests; close() does not wait on them. */
|
|
38306
|
+
mcpCatalogWork = /* @__PURE__ */ new Set();
|
|
37329
38307
|
roomManifestGrants = roomManifestGrantsEnabled();
|
|
37330
38308
|
/**
|
|
37331
38309
|
* Audit M-B4: DM channels whose roster was epoch-fresh at THIS ingest
|
|
@@ -37336,6 +38314,39 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
37336
38314
|
// RECORDED here and in the sealed store; no grant code reads it in step 1.
|
|
37337
38315
|
toolGrantManifest = null;
|
|
37338
38316
|
toolGrantCounter = null;
|
|
38317
|
+
/**
|
|
38318
|
+
* Tool status Phase 0 (spec §3.1, §3.2; plan Task 8): the latest owner-DM last-turn record, in memory only (no store
|
|
38319
|
+
* table: after a restart there is none until the next typed owner-DM turn). ADVISORY: nothing reads it to decide a
|
|
38320
|
+
* grant. Content-free: enums, booleans, one 15-minute bucket `at` (ruling M-2), one counter, built-in names from the
|
|
38321
|
+
* allowlist. This is the latest record, open bucket included; what leaves the host is the sender's closed record.
|
|
38322
|
+
*/
|
|
38323
|
+
ownerDmLastTurn = null;
|
|
38324
|
+
/**
|
|
38325
|
+
* Test seams (Task 8; ruling M-2): the cause function, the bridge clock the record and the sender read, and the
|
|
38326
|
+
* sender's timer and jitter source.
|
|
38327
|
+
*/
|
|
38328
|
+
toolStatusCause = ownerDmCause;
|
|
38329
|
+
toolStatusNow = () => Date.now();
|
|
38330
|
+
toolStatusSetTimer = defaultToolStatusSetTimer;
|
|
38331
|
+
toolStatusClearTimer = defaultToolStatusClearTimer;
|
|
38332
|
+
toolStatusRandom = Math.random;
|
|
38333
|
+
/**
|
|
38334
|
+
* Phase 0 Task 9 (spec §3.5), ruling M-2: the post of the record to the narrow route, only just after a 15-minute
|
|
38335
|
+
* UTC bucket boundary, and only a record whose bucket has closed.
|
|
38336
|
+
*/
|
|
38337
|
+
toolStatusSender = new ToolStatusSender({
|
|
38338
|
+
post: (rec) => this.postToolStatus(rec),
|
|
38339
|
+
now: () => this.toolStatusNow(),
|
|
38340
|
+
setTimer: (fn, ms) => this.toolStatusSetTimer(fn, ms),
|
|
38341
|
+
clearTimer: (h) => this.toolStatusClearTimer(h),
|
|
38342
|
+
random: () => this.toolStatusRandom(),
|
|
38343
|
+
onFailure: (kind2) => writeServiceLog("warn", "tool_status.post_failed", { status: ServiceLogEnum.of(kind2, ["http_error", "timeout", "network"]) })
|
|
38344
|
+
});
|
|
38345
|
+
/**
|
|
38346
|
+
* MCP Phase 1 Task 14 (OQ-3 ruling item 3): the sticky R-3 flag, mirrored from the sealed store. Starts false and is
|
|
38347
|
+
* set true at start() by the store (or by a failed read, fail closed). Only applyToolGrantManifest changes it after.
|
|
38348
|
+
*/
|
|
38349
|
+
mcpSeen = false;
|
|
37339
38350
|
manifestState = "no_manifest";
|
|
37340
38351
|
/** Failed owner-turn checks since the last successful runtime-config report (§7.7). */
|
|
37341
38352
|
ownerVerifyFailures = 0;
|
|
@@ -37690,6 +38701,9 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
37690
38701
|
uploadBlobImpl;
|
|
37691
38702
|
/** Track 5: directory where received attachments are saved. */
|
|
37692
38703
|
attachmentsDirImpl;
|
|
38704
|
+
vaultOutboxDirImpl;
|
|
38705
|
+
/** Audit 2026-10-30 HIGH 2: the outbox pinned at start(); null = every upload refused. */
|
|
38706
|
+
outboxPin = null;
|
|
37693
38707
|
/** B7: persistence for the vault kekset's owner-pin TOFU state. */
|
|
37694
38708
|
vaultOwnerStoreImpl;
|
|
37695
38709
|
/** B7: last-seen kekset generation per enclave, enforced monotonic across
|
|
@@ -37798,10 +38812,12 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
37798
38812
|
const payload = await res.json();
|
|
37799
38813
|
if (!payload) {
|
|
37800
38814
|
writeServiceLog("warn", "runtime_config.empty_response");
|
|
38815
|
+
this.noteMcpConfigUnfetched();
|
|
37801
38816
|
return;
|
|
37802
38817
|
}
|
|
37803
38818
|
if (typeof payload.version !== "number") {
|
|
37804
38819
|
writeServiceLog("warn", "runtime_config.invalid_version");
|
|
38820
|
+
this.noteMcpConfigUnfetched();
|
|
37805
38821
|
return;
|
|
37806
38822
|
}
|
|
37807
38823
|
const version = payload.version;
|
|
@@ -37815,6 +38831,9 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
37815
38831
|
manifestStepThrew = true;
|
|
37816
38832
|
writeServiceLog("warn", "authz.config_binding", { state: ServiceLogEnum.of("bind_failed", ["bind_failed"]) }, e);
|
|
37817
38833
|
}
|
|
38834
|
+
const raw = payload;
|
|
38835
|
+
this.mcpConfig = Object.freeze({ fetched: true, mcp_entitled: raw.mcp_entitled, mcp_cause: raw.mcp_cause });
|
|
38836
|
+
this.recomputeMcpPolicy();
|
|
37818
38837
|
const pin = this.cfg.ownerGrantPin?.pin ?? null;
|
|
37819
38838
|
const inForce = pin !== null ? this.toolGrantManifest : null;
|
|
37820
38839
|
if (inForce !== null && pin !== null) {
|
|
@@ -37881,7 +38900,80 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
37881
38900
|
await this.postRuntimeConfigReport(base, version, skillsJson);
|
|
37882
38901
|
} catch (e) {
|
|
37883
38902
|
writeServiceLog("warn", "runtime_config.refetch_failed", {}, e);
|
|
38903
|
+
this.noteMcpConfigUnfetched();
|
|
38904
|
+
}
|
|
38905
|
+
}
|
|
38906
|
+
/** MCP Task 16 (OQ-1): the bridge's in-force entitlement reading; not fetched (or failed) is suspended. */
|
|
38907
|
+
mcpEntitlementInForce() {
|
|
38908
|
+
return this.mcpConfig.fetched ? mcpEntitlementReading(this.mcpConfig.mcp_entitled, this.mcpConfig.mcp_cause) : "suspended";
|
|
38909
|
+
}
|
|
38910
|
+
/** PR-B1 audit M-2: forget the last /files fields and rewrite mcp-policy.json (no_config: no grants, no deletion). */
|
|
38911
|
+
noteMcpConfigUnfetched() {
|
|
38912
|
+
this.mcpConfig = Object.freeze({ fetched: false });
|
|
38913
|
+
this.recomputeMcpPolicy();
|
|
38914
|
+
}
|
|
38915
|
+
/** MCP Task 16: the live inputs of mcp-policy.json (the pinned manifest in force only). */
|
|
38916
|
+
mcpPolicyInput() {
|
|
38917
|
+
const pin = this.cfg.ownerGrantPin?.pin ?? null;
|
|
38918
|
+
return {
|
|
38919
|
+
hostSwitch: this.mcpHostSwitch,
|
|
38920
|
+
config: this.mcpConfig,
|
|
38921
|
+
ownerDmManifestSwitchOn: this.ownerDmManifestGrants,
|
|
38922
|
+
manifest: pin !== null ? this.toolGrantManifest : null,
|
|
38923
|
+
capability: this.mcpClientCapable(),
|
|
38924
|
+
mcpSeen: this.mcpSeen,
|
|
38925
|
+
...this.mcpCatalog ? { catalog: this.mcpCatalog } : {}
|
|
38926
|
+
};
|
|
38927
|
+
}
|
|
38928
|
+
/**
|
|
38929
|
+
* MCP Task 16 (spec §12.1): recompute mcp-policy.json and write it when it changed. The written document is what MCP
|
|
38930
|
+
* issuance and release-time re-narrowing read, so it is cleared BEFORE the write and set only after it lands: a failed
|
|
38931
|
+
* write leaves no document in force here (no MCP grant), and the next trigger tries again. Never throws.
|
|
38932
|
+
*/
|
|
38933
|
+
recomputeMcpPolicy() {
|
|
38934
|
+
const doc = mcpPolicyFor(this.mcpPolicyInput());
|
|
38935
|
+
const json2 = JSON.stringify(doc);
|
|
38936
|
+
if (json2 === this.mcpPolicyWrittenJson) return;
|
|
38937
|
+
if (MCP_INERT_STATES.has(doc.state) && !doc.r3_hold && Object.keys(doc.servers).length === 0 && !existsSync5(join20(runtimeConfigDir(this.home, this.cfg.identityId), MCP_POLICY_FILE))) {
|
|
38938
|
+
this.mcpPolicyWritten = doc;
|
|
38939
|
+
this.mcpPolicyWrittenJson = json2;
|
|
38940
|
+
this.clearMcpPolicyRetry();
|
|
38941
|
+
return;
|
|
37884
38942
|
}
|
|
38943
|
+
this.mcpPolicyWritten = null;
|
|
38944
|
+
this.mcpPolicyWrittenJson = null;
|
|
38945
|
+
try {
|
|
38946
|
+
this.grantWriters.writeMcpPolicy(this.home, this.cfg.identityId, doc);
|
|
38947
|
+
} catch (e) {
|
|
38948
|
+
writeServiceLog("warn", "authz.mcp_policy", { state: ServiceLogEnum.of("write_failed", ["write_failed"]) }, e);
|
|
38949
|
+
this.scheduleMcpPolicyRetry();
|
|
38950
|
+
return;
|
|
38951
|
+
}
|
|
38952
|
+
this.mcpPolicyWritten = doc;
|
|
38953
|
+
this.mcpPolicyWrittenJson = json2;
|
|
38954
|
+
this.clearMcpPolicyRetry();
|
|
38955
|
+
}
|
|
38956
|
+
/** A document is in force: no pending retry, and the next failure starts the backoff from the first delay. */
|
|
38957
|
+
clearMcpPolicyRetry() {
|
|
38958
|
+
this.mcpPolicyRetryAttempt = 0;
|
|
38959
|
+
if (this.mcpPolicyRetryTimer !== null) clearTimeout(this.mcpPolicyRetryTimer);
|
|
38960
|
+
this.mcpPolicyRetryTimer = null;
|
|
38961
|
+
}
|
|
38962
|
+
/**
|
|
38963
|
+
* PR-B2 follow-up: one pending retry at a time. The retry recomputes from the state THEN (never re-writes a stale
|
|
38964
|
+
* document); any other trigger that writes successfully meanwhile cancels it. Nothing is in force on the bridge side
|
|
38965
|
+
* until a write lands (mcpPolicyWritten stays null).
|
|
38966
|
+
*/
|
|
38967
|
+
scheduleMcpPolicyRetry() {
|
|
38968
|
+
if (this.closingDown || this.mcpPolicyRetryTimer !== null) return;
|
|
38969
|
+
const d = this.mcpPolicyRetryDelaysMs;
|
|
38970
|
+
const delay = d[Math.min(this.mcpPolicyRetryAttempt, d.length - 1)];
|
|
38971
|
+
this.mcpPolicyRetryAttempt++;
|
|
38972
|
+
this.mcpPolicyRetryTimer = setTimeout(() => {
|
|
38973
|
+
this.mcpPolicyRetryTimer = null;
|
|
38974
|
+
if (!this.closingDown) this.recomputeMcpPolicy();
|
|
38975
|
+
}, delay);
|
|
38976
|
+
this.mcpPolicyRetryTimer.unref?.();
|
|
37885
38977
|
}
|
|
37886
38978
|
/**
|
|
37887
38979
|
* Plan 4 Task 11: the refetch under a manifest in force. Returns false when the binding failed (H-4),
|
|
@@ -37966,7 +39058,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
37966
39058
|
}
|
|
37967
39059
|
/** No manifest in force: a manifest-policy.json left by an earlier one is replaced by an empty tool list. */
|
|
37968
39060
|
overwriteStalePolicy() {
|
|
37969
|
-
if (!existsSync5(
|
|
39061
|
+
if (!existsSync5(join20(runtimeConfigDir(this.home, this.cfg.identityId), "manifest-policy.json"))) return;
|
|
37970
39062
|
this.grantWriters.writeManifestPolicy(this.home, this.cfg.identityId, { v: 1, manifest_tools: [], secret_path_mode: "enforce" });
|
|
37971
39063
|
}
|
|
37972
39064
|
/** The report half of refetchRuntimeConfigOnce (split out for the plan 4 Task 11 bind_failed path). */
|
|
@@ -37974,6 +39066,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
37974
39066
|
const local = readLocalReport(this.home, this.cfg.identityId);
|
|
37975
39067
|
const reportedSearchProviders = this.searchProvidersCapable();
|
|
37976
39068
|
const reportedUtilityTools = this.utilityToolsCapable();
|
|
39069
|
+
const carriedOwnerDmTurn = this.toolStatusSender.carry();
|
|
37977
39070
|
const report = {
|
|
37978
39071
|
applied_version: version,
|
|
37979
39072
|
paths: checkConfiguredPaths(skillsJson),
|
|
@@ -38043,8 +39136,17 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
38043
39136
|
// D19 item 4: content-free; the signed provider is one the attached runtime(s) cannot honour.
|
|
38044
39137
|
search_provider_unsupported: this.searchProviderUnsupported(reportedSearchProviders),
|
|
38045
39138
|
// Weather/currency §8 item 5: a separate key, false with no runtime attached.
|
|
38046
|
-
utility_tools_v1: reportedUtilityTools
|
|
38047
|
-
|
|
39139
|
+
utility_tools_v1: reportedUtilityTools,
|
|
39140
|
+
// MCP Task 19 (spec §12.3): separate from `capability`; content-free enums and pattern tokens only.
|
|
39141
|
+
mcp_capability: this.reportedMcpCapability(),
|
|
39142
|
+
mcp_servers: this.reportedMcpServers(),
|
|
39143
|
+
// Tool status Phase 0 (spec §3.5, round 2 H-5): an updated bridge, with or without a record yet.
|
|
39144
|
+
tool_status_v1: true
|
|
39145
|
+
},
|
|
39146
|
+
// Tool status Phase 0 (spec §3.5): the record rides every apply-time report, so record_report's wholesale replace
|
|
39147
|
+
// never erases it. Ruling M-2: the sender's CLOSED record, never the latest one, so a report sent inside a bucket
|
|
39148
|
+
// never reveals that bucket's change early. Advisory and content-free.
|
|
39149
|
+
...carriedOwnerDmTurn ? { owner_dm_last_turn: carriedOwnerDmTurn } : {}
|
|
38048
39150
|
};
|
|
38049
39151
|
const reportedVerifyFailures = this.ownerVerifyFailures;
|
|
38050
39152
|
const posted = await this.fetchBounded("runtime_config_report", "control", `${base}/report`, {
|
|
@@ -38133,8 +39235,16 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
38133
39235
|
}
|
|
38134
39236
|
async startBody() {
|
|
38135
39237
|
this.startedAt = Date.now();
|
|
39238
|
+
if (this.vaultOutboxDirImpl) {
|
|
39239
|
+
try {
|
|
39240
|
+
this.outboxPin = ensureOutbox(this.vaultOutboxDirImpl);
|
|
39241
|
+
} catch (e) {
|
|
39242
|
+
writeServiceLog("warn", "vault_outbox.unavailable", {}, e);
|
|
39243
|
+
}
|
|
39244
|
+
}
|
|
38136
39245
|
await this.restoreRuntimeInbox();
|
|
38137
39246
|
await this.loadToolGrantState();
|
|
39247
|
+
this.recomputeMcpPolicy();
|
|
38138
39248
|
this.recomputeManifestPolicy(NO_FEATURES);
|
|
38139
39249
|
try {
|
|
38140
39250
|
this.store.pruneRoomSearchCap(utcDayOf(Date.now()));
|
|
@@ -38158,8 +39268,8 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
38158
39268
|
const drain = this.ingestOnce().then(() => true, () => true);
|
|
38159
39269
|
const drainedInTime = await Promise.race([
|
|
38160
39270
|
drain,
|
|
38161
|
-
new Promise((
|
|
38162
|
-
const t = setTimeout(() =>
|
|
39271
|
+
new Promise((resolve5) => {
|
|
39272
|
+
const t = setTimeout(() => resolve5(false), this.bootDrainWaitMs);
|
|
38163
39273
|
t.unref?.();
|
|
38164
39274
|
void drain.then(() => clearTimeout(t));
|
|
38165
39275
|
})
|
|
@@ -38169,7 +39279,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
38169
39279
|
await this.bootReconcileKeyPackage();
|
|
38170
39280
|
} else {
|
|
38171
39281
|
writeServiceLog("warn", "boot.drain_slow", { wait_ms: this.bootDrainWaitMs });
|
|
38172
|
-
const settled = new Promise((
|
|
39282
|
+
const settled = new Promise((resolve5) => this.ingestSettledWaiters.push(resolve5));
|
|
38173
39283
|
this.deferredBootPublish = settled.then(async () => {
|
|
38174
39284
|
this.bootDrainPending = false;
|
|
38175
39285
|
if (this.closingDown) return;
|
|
@@ -39152,7 +40262,9 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
39152
40262
|
* for every envelope built, BEFORE it is published (SEC-06 #18 persists it
|
|
39153
40263
|
* there so a retry can resend the exact bytes).
|
|
39154
40264
|
*/
|
|
39155
|
-
async publishBody(channelId, body, id, onEnvelope, sealCheck) {
|
|
40265
|
+
async publishBody(channelId, body, id, onEnvelope, sealCheck, inReplyTo, mcpTurn) {
|
|
40266
|
+
const mcpGuard = mcpTurn === true || this.mcpReplyGuarded(channelId, inReplyTo ?? void 0);
|
|
40267
|
+
let attempt = 0;
|
|
39156
40268
|
this.enforceBodyRateFloor(channelId, body);
|
|
39157
40269
|
this.statusGate.replyStarted(channelId);
|
|
39158
40270
|
const joined = await this.reconcileJoin(channelId);
|
|
@@ -39192,6 +40304,8 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
39192
40304
|
);
|
|
39193
40305
|
},
|
|
39194
40306
|
async (envelope) => {
|
|
40307
|
+
attempt++;
|
|
40308
|
+
if (mcpGuard) this.assertMcpReplyRelayFree(channelId, envelope, attempt);
|
|
39195
40309
|
if (onEnvelope) await onEnvelope(envelope);
|
|
39196
40310
|
return this.publishEnvelope(
|
|
39197
40311
|
channelId,
|
|
@@ -39201,6 +40315,77 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
39201
40315
|
);
|
|
39202
40316
|
this.latency.record("send.total", performance.now() - sendStart);
|
|
39203
40317
|
}
|
|
40318
|
+
/**
|
|
40319
|
+
* MCP Task 16 (round 2 H-D): message ids whose DELIVERED authorization (after release-time re-narrowing) carried an
|
|
40320
|
+
* `mcp:` grant, with their channel and whether the runtime acked them. Recorded at the buffer exit, the one place
|
|
40321
|
+
* every consumer's copy leaves from (over-recording a turn a non-capable consumer then received without the grant only
|
|
40322
|
+
* adds a check). Kept after the ack, so a late frame citing the turn is still checked. Bounded: the oldest ACKED entry
|
|
40323
|
+
* goes first.
|
|
40324
|
+
*/
|
|
40325
|
+
mcpDeliveredTurns = /* @__PURE__ */ new Map();
|
|
40326
|
+
mcpDeliveredTrackMax;
|
|
40327
|
+
/**
|
|
40328
|
+
* Record `m` when its authorization carries an `mcp:` grant. Returns false ONLY when `m` is a new turn, the record is
|
|
40329
|
+
* at its bound and no entry is acked: the caller (the buffer exit) must then issue no mcp: grant (audit M-2, fail
|
|
40330
|
+
* closed). `rebuild` (PR-B2 audit C-1 (b): a restored runtime-inbox row, or a replayed turn) records unconditionally:
|
|
40331
|
+
* those grants were issued before, so the record may exceed its bound by at most the runtime inbox's size.
|
|
40332
|
+
*/
|
|
40333
|
+
noteMcpDelivered(m, rebuild = false) {
|
|
40334
|
+
if (!m.authorization?.tool_grants.some((g) => g.tool.startsWith(MCP_GRANT_PREFIX))) return true;
|
|
40335
|
+
if (!this.mcpDeliveredTurns.has(m.message_id) && this.mcpDeliveredTurns.size >= this.mcpDeliveredTrackMax) {
|
|
40336
|
+
let victim;
|
|
40337
|
+
for (const [id, r] of this.mcpDeliveredTurns) if (r.acked) {
|
|
40338
|
+
victim = id;
|
|
40339
|
+
break;
|
|
40340
|
+
}
|
|
40341
|
+
if (victim !== void 0) this.mcpDeliveredTurns.delete(victim);
|
|
40342
|
+
else if (!rebuild) return false;
|
|
40343
|
+
}
|
|
40344
|
+
this.mcpDeliveredTurns.set(m.message_id, { channelId: m.channel_id, acked: false });
|
|
40345
|
+
return true;
|
|
40346
|
+
}
|
|
40347
|
+
/**
|
|
40348
|
+
* H-D: whether a frame to `channelId` must pass the relay-free check: it answers a recorded MCP turn (any channel:
|
|
40349
|
+
* a frame of that turn sent elsewhere is checked against ITS channel), or it cites no turn while an MCP turn is
|
|
40350
|
+
* unacked in the same channel.
|
|
40351
|
+
*/
|
|
40352
|
+
mcpReplyGuarded(channelId, inReplyTo) {
|
|
40353
|
+
if (inReplyTo !== void 0 && this.mcpDeliveredTurns.has(inReplyTo)) return true;
|
|
40354
|
+
if (inReplyTo !== void 0) return false;
|
|
40355
|
+
for (const r of this.mcpDeliveredTurns.values()) if (!r.acked && r.channelId === channelId) return true;
|
|
40356
|
+
return false;
|
|
40357
|
+
}
|
|
40358
|
+
/**
|
|
40359
|
+
* H-D, synchronous: read the channel's local leaves from the warm manager in one step and tie the read to the
|
|
40360
|
+
* envelope's SEALED group and epoch, so the leaf set is exactly the membership that can open these bytes. Owner and
|
|
40361
|
+
* agent only: pass. Anyone else: withhold. The tree moved past the seal on the first attempt: StaleEpochError, which
|
|
40362
|
+
* sendWithSelfHeal answers with its one converge-and-re-seal (the existing semantics); still unprovable on the retry,
|
|
40363
|
+
* or no warm manager, no pin, or any throw: withhold (fail closed). Read-only; never touches the group.
|
|
40364
|
+
*
|
|
40365
|
+
* Scope (PR-B2 audit item 5): this guards the `send` path (publishBody) only. `status` detail, the `action_*` run
|
|
40366
|
+
* frames, `/v1/send-attachment` and summary frames are NOT checked; they are safe for MCP turns only because the
|
|
40367
|
+
* runtime emits none of them (pinned by connect-agent-runtime/src/outbound-frames.arch.test.ts). A runtime that starts
|
|
40368
|
+
* emitting any of them needs this guard extended to that frame first.
|
|
40369
|
+
*/
|
|
40370
|
+
assertMcpReplyRelayFree(channelId, envelope, attempt) {
|
|
40371
|
+
let verdict = "unknown";
|
|
40372
|
+
try {
|
|
40373
|
+
const pin = this.cfg.ownerGrantPin?.pin ?? null;
|
|
40374
|
+
const snap = this.provider.leafSnapshotCached(channelId);
|
|
40375
|
+
const sealedGroup = sealedGroupIdOf(envelope);
|
|
40376
|
+
if (pin !== null && snap !== null && sealedGroup !== void 0 && snap.groupId === sealedGroup) {
|
|
40377
|
+
if (snap.epoch === envelope.mls_group_epoch) {
|
|
40378
|
+
verdict = ownerDmLeavesRelayFree(snap.leaves, { ownerIdentityId: pin.ownerIdentityId, agentIdentityId: this.cfg.identityId }) ? "free" : "relay";
|
|
40379
|
+
} else if (snap.epoch > envelope.mls_group_epoch) verdict = "moved";
|
|
40380
|
+
}
|
|
40381
|
+
} catch {
|
|
40382
|
+
verdict = "unknown";
|
|
40383
|
+
}
|
|
40384
|
+
if (verdict === "free") return;
|
|
40385
|
+
if (verdict === "moved" && attempt === 1) throw new StaleEpochError(envelope.mls_group_epoch, envelope.mls_group_epoch + 1);
|
|
40386
|
+
writeServiceLog("warn", "mcp.reply_withheld", { state: ServiceLogEnum.of(verdict, ["relay", "moved", "unknown"]) });
|
|
40387
|
+
throw new McpReplyWithheldError(channelId);
|
|
40388
|
+
}
|
|
39204
40389
|
/**
|
|
39205
40390
|
* Audit M-B1 (controller ruling): a reply to a room-granted turn is sealed
|
|
39206
40391
|
* only at the epoch the room was classified at. On a move, the room is
|
|
@@ -39212,9 +40397,9 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
39212
40397
|
* the redelivery replay (replayReplyIntents, which has no in_reply_to in its
|
|
39213
40398
|
* options) is checked the same as a live send.
|
|
39214
40399
|
*/
|
|
39215
|
-
async publishBodyForReply(channelId, body, id, inReplyTo, onEnvelope) {
|
|
40400
|
+
async publishBodyForReply(channelId, body, id, inReplyTo, onEnvelope, mcpTurn) {
|
|
39216
40401
|
const grant = inReplyTo !== null ? this.roomGrantEpochs.get(inReplyTo) : void 0;
|
|
39217
|
-
if (grant === void 0) return this.publishBody(channelId, body, id, onEnvelope);
|
|
40402
|
+
if (grant === void 0) return this.publishBody(channelId, body, id, onEnvelope, void 0, inReplyTo, mcpTurn);
|
|
39218
40403
|
if (channelId !== grant.channelId) {
|
|
39219
40404
|
writeServiceLog("warn", "reply.room_class_changed");
|
|
39220
40405
|
throw new RoomClassChangedSendError(channelId);
|
|
@@ -39236,7 +40421,9 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
39236
40421
|
},
|
|
39237
40422
|
(epoch) => {
|
|
39238
40423
|
if (epoch !== grant.epoch) throw new RoomGrantEpochMovedError();
|
|
39239
|
-
}
|
|
40424
|
+
},
|
|
40425
|
+
inReplyTo,
|
|
40426
|
+
mcpTurn
|
|
39240
40427
|
);
|
|
39241
40428
|
} catch (e) {
|
|
39242
40429
|
if (!(e instanceof RoomGrantEpochMovedError)) throw e;
|
|
@@ -39371,14 +40558,15 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
39371
40558
|
const k = replyIntentKey({ inReplyTo: opts.inReplyTo, idempotencyKey: opts.idempotencyKey, ordinal: () => 0 });
|
|
39372
40559
|
if (!k) throw new Error("idempotent send without a key");
|
|
39373
40560
|
const id = replyMessageId(this.replyIdKey, this.cfg.identityId, channelId, k);
|
|
39374
|
-
const
|
|
39375
|
-
const
|
|
39376
|
-
|
|
40561
|
+
const mcpTurn = this.mcpReplyGuarded(channelId, opts.inReplyTo);
|
|
40562
|
+
const mac2 = replyBodyMac(this.replyIdKey, id, channelId, bytes, opts.credit, mcpTurn);
|
|
40563
|
+
const { intent } = await this.store.claimReplyIntent({ ...k, channelId, replyMessageId: id, bodyMac: mac2, body: bytes, replyCredit: opts.credit ?? null, mcpTurn });
|
|
40564
|
+
if (intent.bodyMac !== replyBodyMac(this.replyIdKey, id, channelId, bytes, opts.credit, intent.mcpTurn) || intent.channelId !== channelId || intent.state === "skipped") {
|
|
39377
40565
|
throw new IdempotencyConflictError();
|
|
39378
40566
|
}
|
|
39379
40567
|
return k;
|
|
39380
40568
|
}
|
|
39381
|
-
return this.claimUnkeyedPart(opts.inReplyTo, channelId, bytes, true, opts.credit ?? null);
|
|
40569
|
+
return this.claimUnkeyedPart(opts.inReplyTo, channelId, bytes, true, opts.credit ?? null, false);
|
|
39382
40570
|
});
|
|
39383
40571
|
return this.publishIntent(key, opts);
|
|
39384
40572
|
}
|
|
@@ -39387,25 +40575,26 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
39387
40575
|
* the reply to `inReplyTo`. Caller holds the scope lock. `matchRetry` false
|
|
39388
40576
|
* after a slot collision: the body must go to a NEW part.
|
|
39389
40577
|
*/
|
|
39390
|
-
async claimUnkeyedPart(inReplyTo, channelId, bytes, matchRetry, replyCredit) {
|
|
40578
|
+
async claimUnkeyedPart(inReplyTo, channelId, bytes, matchRetry, replyCredit, inheritedMcpTurn) {
|
|
39391
40579
|
const credit = replyCredit ?? void 0;
|
|
40580
|
+
const mcpTurn = inheritedMcpTurn || this.mcpReplyGuarded(channelId, inReplyTo);
|
|
39392
40581
|
const scope = `reply:${inReplyTo}`;
|
|
39393
40582
|
let latest = null;
|
|
39394
40583
|
for (const intent of await this.store.listReplyIntents(inReplyTo)) {
|
|
39395
40584
|
const n = intent.scope === scope ? partOrdinal(intent.part) : null;
|
|
39396
40585
|
if (n !== null && (latest === null || n > latest.ordinal)) latest = { ordinal: n, intent };
|
|
39397
40586
|
}
|
|
39398
|
-
if (matchRetry && latest && latest.intent.state !== "skipped" && latest.intent.channelId === channelId && latest.intent.bodyMac === replyBodyMac(this.replyIdKey, latest.intent.replyMessageId, channelId, bytes, credit)) {
|
|
40587
|
+
if (matchRetry && latest && latest.intent.state !== "skipped" && latest.intent.channelId === channelId && latest.intent.bodyMac === replyBodyMac(this.replyIdKey, latest.intent.replyMessageId, channelId, bytes, credit, latest.intent.mcpTurn)) {
|
|
39399
40588
|
return { scope, part: latest.intent.part, inReplyTo };
|
|
39400
40589
|
}
|
|
39401
40590
|
let ordinal = latest ? latest.ordinal + 1 : 0;
|
|
39402
40591
|
for (let i = 0; i < REPLY_MAX_PART_ADVANCES; i++, ordinal++) {
|
|
39403
40592
|
const k = { scope, part: `p:${ordinal}`, inReplyTo };
|
|
39404
40593
|
const id = replyMessageId(this.replyIdKey, this.cfg.identityId, channelId, k);
|
|
39405
|
-
const mac2 = replyBodyMac(this.replyIdKey, id, channelId, bytes, credit);
|
|
39406
|
-
const { created, intent } = await this.store.claimReplyIntent({ ...k, channelId, replyMessageId: id, bodyMac: mac2, body: bytes, replyCredit });
|
|
40594
|
+
const mac2 = replyBodyMac(this.replyIdKey, id, channelId, bytes, credit, mcpTurn);
|
|
40595
|
+
const { created, intent } = await this.store.claimReplyIntent({ ...k, channelId, replyMessageId: id, bodyMac: mac2, body: bytes, replyCredit, mcpTurn });
|
|
39407
40596
|
if (created) return k;
|
|
39408
|
-
if (matchRetry && intent.state !== "skipped" && intent.
|
|
40597
|
+
if (matchRetry && intent.state !== "skipped" && intent.channelId === channelId && intent.bodyMac === replyBodyMac(this.replyIdKey, id, channelId, bytes, credit, intent.mcpTurn)) return k;
|
|
39409
40598
|
writeServiceLog("info", "reply.part_slot_taken", { attempt: i + 1 });
|
|
39410
40599
|
}
|
|
39411
40600
|
throw new Error("could not claim a reply part");
|
|
@@ -39457,7 +40646,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
39457
40646
|
const purgeEpochBeforePublish = this.store.conversationPurgeEpoch(intent.channelId);
|
|
39458
40647
|
const replyCapture = this.unifiedReplyCapture(intent.channelId, intent.inReplyTo ?? void 0);
|
|
39459
40648
|
try {
|
|
39460
|
-
await this.publishIntentWithRetry(intent.channelId, body, intent.replyMessageId, key, intent.inReplyTo);
|
|
40649
|
+
await this.publishIntentWithRetry(intent.channelId, body, intent.replyMessageId, key, intent.inReplyTo, intent.mcpTurn);
|
|
39461
40650
|
} catch (e) {
|
|
39462
40651
|
if (e instanceof AgentPausedSendError || e instanceof SendForbiddenError) {
|
|
39463
40652
|
if (intent.inReplyTo !== null) this.store.deletePendingReplyIntents(intent.inReplyTo);
|
|
@@ -39484,7 +40673,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
39484
40673
|
JSON.stringify(["scope", key.scope]),
|
|
39485
40674
|
() => (
|
|
39486
40675
|
// r2 BLOCKER 1: the colliding row's OWN credit (verified above), never `opts`.
|
|
39487
|
-
this.claimUnkeyedPart(inReplyTo, intent.channelId, bytes, false, isReplyCredit(intent.replyCredit) ? intent.replyCredit : null)
|
|
40676
|
+
this.claimUnkeyedPart(inReplyTo, intent.channelId, bytes, false, isReplyCredit(intent.replyCredit) ? intent.replyCredit : null, intent.mcpTurn)
|
|
39488
40677
|
)
|
|
39489
40678
|
);
|
|
39490
40679
|
return this.publishIntent(next, opts, advances + 1);
|
|
@@ -39506,7 +40695,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
39506
40695
|
if (credit !== null && !isReplyCredit(credit)) return false;
|
|
39507
40696
|
const id = replyMessageId(this.replyIdKey, this.cfg.identityId, intent.channelId, intent);
|
|
39508
40697
|
if (id !== intent.replyMessageId) return false;
|
|
39509
|
-
return replyBodyMac(this.replyIdKey, intent.replyMessageId, intent.channelId, intent.body, credit ?? void 0) === intent.bodyMac;
|
|
40698
|
+
return replyBodyMac(this.replyIdKey, intent.replyMessageId, intent.channelId, intent.body, credit ?? void 0, intent.mcpTurn === true) === intent.bodyMac;
|
|
39510
40699
|
}
|
|
39511
40700
|
/**
|
|
39512
40701
|
* Weather/currency Task 17: what a verified row's reply is RECORDED as. Null credit: the decoded text unchanged (null
|
|
@@ -39527,12 +40716,12 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
39527
40716
|
* failure. Either way the intent stays pending, so the redelivery
|
|
39528
40717
|
* republishes the same reply.
|
|
39529
40718
|
*/
|
|
39530
|
-
async publishIntentWithRetry(channelId, body, id, key, inReplyTo) {
|
|
40719
|
+
async publishIntentWithRetry(channelId, body, id, key, inReplyTo, mcpTurn) {
|
|
39531
40720
|
let rateLimited = 0;
|
|
39532
40721
|
let inFlight = 0;
|
|
39533
40722
|
for (; ; ) {
|
|
39534
40723
|
try {
|
|
39535
|
-
await this.publishIntentOnce(channelId, body, id, key, inReplyTo);
|
|
40724
|
+
await this.publishIntentOnce(channelId, body, id, key, inReplyTo, mcpTurn);
|
|
39536
40725
|
return;
|
|
39537
40726
|
} catch (e) {
|
|
39538
40727
|
if (e instanceof ReplyInFlightError) {
|
|
@@ -39603,8 +40792,9 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
39603
40792
|
* original ack; a conflict can only mean the slot belongs to another
|
|
39604
40793
|
* message (for example an old reply whose intent was pruned).
|
|
39605
40794
|
*/
|
|
39606
|
-
async publishIntentOnce(channelId, body, id, key, inReplyTo) {
|
|
40795
|
+
async publishIntentOnce(channelId, body, id, key, inReplyTo, mcpTurn) {
|
|
39607
40796
|
const current = await this.store.getReplyIntent(key.scope, key.part);
|
|
40797
|
+
const guardMcp = mcpTurn || current?.mcpTurn === true;
|
|
39608
40798
|
try {
|
|
39609
40799
|
if (current?.envelope) {
|
|
39610
40800
|
const cached = JSON.parse(new TextDecoder().decode(current.envelope));
|
|
@@ -39654,7 +40844,8 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
39654
40844
|
key.part,
|
|
39655
40845
|
new TextEncoder().encode(JSON.stringify(envelope)),
|
|
39656
40846
|
sealedGroupIdOf(envelope)
|
|
39657
|
-
)
|
|
40847
|
+
),
|
|
40848
|
+
guardMcp
|
|
39658
40849
|
);
|
|
39659
40850
|
} catch (e) {
|
|
39660
40851
|
if (publishConflictCode(e) === "message_id_conflict") throw new ReplyIdCollisionError();
|
|
@@ -39676,7 +40867,8 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
39676
40867
|
uploadBlob: (ciphertext, channelId) => this.uploadBlobImpl(ciphertext, channelId),
|
|
39677
40868
|
encryptFile,
|
|
39678
40869
|
bytesToBase64: bytesToBase642,
|
|
39679
|
-
|
|
40870
|
+
// Security fix 2026-10-30: attachments upload only from the outbox.
|
|
40871
|
+
readFile: (p) => readOutboxFile(this.outboxPin, p).then((r) => r.bytes)
|
|
39680
40872
|
};
|
|
39681
40873
|
}
|
|
39682
40874
|
receiveDeps() {
|
|
@@ -39686,8 +40878,8 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
39686
40878
|
decryptFile,
|
|
39687
40879
|
computeFileDigest,
|
|
39688
40880
|
base64ToBytes: base64ToBytes2,
|
|
39689
|
-
mkdirp: (d) =>
|
|
39690
|
-
writeFile: (p, data) =>
|
|
40881
|
+
mkdirp: (d) => fsp2.mkdir(d, { recursive: true }).then(() => void 0),
|
|
40882
|
+
writeFile: (p, data) => fsp2.writeFile(p, data)
|
|
39691
40883
|
};
|
|
39692
40884
|
}
|
|
39693
40885
|
async sendAttachment(channelId, filePath2, caption) {
|
|
@@ -39695,8 +40887,11 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
39695
40887
|
const joined = await this.reconcileJoin(channelId);
|
|
39696
40888
|
if (!joined) throw new ChannelPendingError(channelId);
|
|
39697
40889
|
let body;
|
|
40890
|
+
let read;
|
|
39698
40891
|
try {
|
|
39699
|
-
|
|
40892
|
+
read = await readOutboxFile(this.outboxPin, filePath2);
|
|
40893
|
+
const bytes = read.bytes;
|
|
40894
|
+
body = await prepareAttachment(filePath2, caption, channelId, { ...this.sendDeps(), readFile: async () => bytes });
|
|
39700
40895
|
} catch (e) {
|
|
39701
40896
|
if (e.code === "ENOENT" || e.code === "EACCES" || e.code === "ENOTDIR" || e.code === "EISDIR") {
|
|
39702
40897
|
throw new InvalidPathError(filePath2, e);
|
|
@@ -39737,6 +40932,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
39737
40932
|
);
|
|
39738
40933
|
this.latency.record("send.total", performance.now() - sendStart);
|
|
39739
40934
|
this.agentHops.noteOwnSend(channelId);
|
|
40935
|
+
await this.consumeUploaded(read);
|
|
39740
40936
|
return { message_id: id, status: "sent" };
|
|
39741
40937
|
}
|
|
39742
40938
|
// ── B7: FileSafe vault commands (loopback boundary) ──────────────────────
|
|
@@ -40262,25 +41458,26 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
40262
41458
|
}
|
|
40263
41459
|
};
|
|
40264
41460
|
}
|
|
40265
|
-
/** Read `path`
|
|
41461
|
+
/** Read `path` from the vault outbox (security fix 2026-10-30; see
|
|
41462
|
+
* ./outbox.ts), encrypt + wrap its DEK under the vault's
|
|
40266
41463
|
* current KEK generation, and create+upload+commit it as a new vault
|
|
40267
41464
|
* file node named `name` (which may differ from `path`'s basename). */
|
|
40268
41465
|
async vaultWrite(enclaveId, vaultChannelId, path, name) {
|
|
40269
|
-
|
|
40270
|
-
let bytes;
|
|
41466
|
+
let read;
|
|
40271
41467
|
try {
|
|
40272
|
-
|
|
41468
|
+
read = await readOutboxFile(this.outboxPin, path);
|
|
40273
41469
|
} catch (e) {
|
|
40274
41470
|
const code = e.code;
|
|
40275
41471
|
if (code === "ENOENT" || code === "EACCES" || code === "ENOTDIR" || code === "EISDIR") throw new InvalidPathError(path, e);
|
|
40276
41472
|
throw e;
|
|
40277
41473
|
}
|
|
41474
|
+
const session = await this.getVaultSession(enclaveId, await this.resolveVaultChannelId(enclaveId, vaultChannelId));
|
|
40278
41475
|
const nodeId = crypto.randomUUID();
|
|
40279
|
-
|
|
41476
|
+
const written = await vaultWriteImpl({
|
|
40280
41477
|
enclaveId,
|
|
40281
41478
|
nodeId,
|
|
40282
41479
|
name,
|
|
40283
|
-
bytes,
|
|
41480
|
+
bytes: read.bytes,
|
|
40284
41481
|
deps: {
|
|
40285
41482
|
exporterKekForGeneration: session.kekForGeneration,
|
|
40286
41483
|
currentGeneration: session.currentGeneration,
|
|
@@ -40289,6 +41486,16 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
40289
41486
|
commitNode: (committedNodeId) => this.vaultCommitNode(enclaveId, committedNodeId)
|
|
40290
41487
|
}
|
|
40291
41488
|
});
|
|
41489
|
+
await this.consumeUploaded(read);
|
|
41490
|
+
return written;
|
|
41491
|
+
}
|
|
41492
|
+
/** Audit 2026-10-30 MED 5: an uploaded file leaves the outbox (no plaintext lingers). Content-free log. */
|
|
41493
|
+
async consumeUploaded(read) {
|
|
41494
|
+
if (!this.outboxPin || !await consumeOutboxFile(this.outboxPin, read)) writeServiceLog("warn", "vault_outbox.consume_failed");
|
|
41495
|
+
}
|
|
41496
|
+
/** Audit 2026-10-30 MED 3: the pinned outbox path, served only on the authenticated GET /v1/vault-outbox. */
|
|
41497
|
+
vaultOutboxPath() {
|
|
41498
|
+
return this.outboxPin?.dir ?? null;
|
|
40292
41499
|
}
|
|
40293
41500
|
/** Download + verify + decrypt a vault file node. Returns the plaintext
|
|
40294
41501
|
* base64-encoded (the loopback boundary is JSON-only, mirroring how
|
|
@@ -42149,16 +43356,20 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
42149
43356
|
}
|
|
42150
43357
|
if (v.sender_id !== pin.ownerIdentityId) return this.ownerTurnFailed(m, "malformed");
|
|
42151
43358
|
let leafClass = "ok";
|
|
43359
|
+
let relay = void 0;
|
|
42152
43360
|
if (this.freshMeta(keyChannel)?.is_dm === true) {
|
|
42153
43361
|
const signedDm = this.toolGrantManifest?.owner_dm_channel_id;
|
|
42154
43362
|
if (signedDm !== void 0 && signedDm !== keyChannel) return this.ownerTurnFailed(m, "wrong_dm_channel");
|
|
42155
|
-
|
|
43363
|
+
const leaves = (await this.provider.leafTreeSummary(m.channelId)).leaves;
|
|
43364
|
+
const relayIdentityId = this.toolGrantManifest?.owner_dm_relay_identity_id;
|
|
43365
|
+
leafClass = classifyOwnerDmLeaves(leaves, {
|
|
42156
43366
|
ownerIdentityId: pin.ownerIdentityId,
|
|
42157
43367
|
agentIdentityId: this.cfg.identityId,
|
|
42158
|
-
relayIdentityId
|
|
43368
|
+
relayIdentityId,
|
|
42159
43369
|
isServerRelay: (id) => this.freshRoster(m.channelId)?.get(id)?.is_relay === true
|
|
42160
43370
|
});
|
|
42161
43371
|
if (leafClass === "foreign") return this.ownerTurnFailed(m, "not_owner_dm_leaves");
|
|
43372
|
+
relay = ownerDmRelayOf(leafClass, leaves, { ownerIdentityId: pin.ownerIdentityId, agentIdentityId: this.cfg.identityId, relayIdentityId });
|
|
42162
43373
|
}
|
|
42163
43374
|
const held = await this.heldPosition(m.channelId);
|
|
42164
43375
|
if (held.groupId === null) return this.ownerTurnFailed(m, "verify_error");
|
|
@@ -42167,7 +43378,9 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
42167
43378
|
}
|
|
42168
43379
|
const replay = Object.freeze({ channelId: m.channelId, groupId: held.groupId, epoch: v.mls_group_epoch });
|
|
42169
43380
|
this.ownerReplayPending.set(m.messageId, replay);
|
|
42170
|
-
|
|
43381
|
+
const relayField = relay !== void 0 ? { relay } : {};
|
|
43382
|
+
if (leafClass === "unsigned_relay") return Object.freeze({ kind: "constants_only", replay, ...relayField });
|
|
43383
|
+
return Object.freeze({ kind: "verified", replay, ...relayField, relayLeaf: relayLeafFromPhase0Relay(relay) });
|
|
42171
43384
|
} catch {
|
|
42172
43385
|
return this.ownerTurnFailed(m, "verify_error");
|
|
42173
43386
|
}
|
|
@@ -42243,6 +43456,12 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
42243
43456
|
}
|
|
42244
43457
|
/** start(): drop other keys' manifest state, re-verify the stored one for the pinned key, and prune. */
|
|
42245
43458
|
async loadToolGrantState() {
|
|
43459
|
+
try {
|
|
43460
|
+
this.mcpSeen = await this.store.loadMcpSeen();
|
|
43461
|
+
} catch (e) {
|
|
43462
|
+
this.mcpSeen = true;
|
|
43463
|
+
writeServiceLog("warn", "authz.mcp_seen_load_failed", {}, e);
|
|
43464
|
+
}
|
|
42246
43465
|
const pin = this.cfg.ownerGrantPin?.pin;
|
|
42247
43466
|
if (!pin) return;
|
|
42248
43467
|
try {
|
|
@@ -42250,9 +43469,10 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
42250
43469
|
this.store.dropToolGrantAppliedExcept(pin.publicKeyB64);
|
|
42251
43470
|
const saved = await this.store.loadToolGrantManifest(pin.publicKeyB64);
|
|
42252
43471
|
if (saved) {
|
|
42253
|
-
const v = verifyToolGrantManifest(saved.wrapper, { pin, agentIdentityId: this.cfg.identityId, storedCounter: null });
|
|
43472
|
+
const v = verifyToolGrantManifest(saved.wrapper, { pin, agentIdentityId: this.cfg.identityId, storedCounter: null, ...this.mcpCatalog ? { mcpCatalog: this.mcpCatalog } : {} });
|
|
42254
43473
|
if (v.state === "ok") {
|
|
42255
43474
|
this.toolGrantManifest = v.manifest;
|
|
43475
|
+
if (v.manifest.signedMcpPresent && !this.mcpSeen) await this.setMcpSeen(true);
|
|
42256
43476
|
this.toolGrantCounter = v.manifest.counter;
|
|
42257
43477
|
} else {
|
|
42258
43478
|
this.logManifest("malformed");
|
|
@@ -42277,8 +43497,9 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
42277
43497
|
this.logManifest("no_manifest");
|
|
42278
43498
|
return;
|
|
42279
43499
|
}
|
|
42280
|
-
const v = verifyToolGrantManifest(raw, { pin, agentIdentityId: this.cfg.identityId, storedCounter: this.toolGrantCounter });
|
|
43500
|
+
const v = verifyToolGrantManifest(raw, { pin, agentIdentityId: this.cfg.identityId, storedCounter: this.toolGrantCounter, ...this.mcpCatalog ? { mcpCatalog: this.mcpCatalog } : {} });
|
|
42281
43501
|
if (v.state === "ok") {
|
|
43502
|
+
if (v.manifest.signedMcpPresent) await this.setMcpSeen(true);
|
|
42282
43503
|
try {
|
|
42283
43504
|
await this.store.saveToolGrantManifest(pin.publicKeyB64, v.manifest.counter, v.wrapper);
|
|
42284
43505
|
} catch {
|
|
@@ -42287,13 +43508,34 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
42287
43508
|
}
|
|
42288
43509
|
this.toolGrantManifest = v.manifest;
|
|
42289
43510
|
this.toolGrantCounter = v.manifest.counter;
|
|
43511
|
+
this.recomputeMcpPolicy();
|
|
43512
|
+
if (v.manifest.mcpKey === "present" && !v.manifest.signedMcpPresent && this.mcpSeen) await this.setMcpSeen(false);
|
|
42290
43513
|
this.logManifest("ok");
|
|
42291
43514
|
if (v.droppedUnknown) this.logManifest("unknown_entry");
|
|
42292
43515
|
return;
|
|
42293
43516
|
}
|
|
42294
43517
|
this.logManifest(v.state);
|
|
43518
|
+
if (v.state === "wrong_owner") this.dropOwnerDmLastTurn();
|
|
42295
43519
|
if (v.state !== "unchanged" && this.toolGrantManifest) this.logManifest("kept_last_good");
|
|
42296
43520
|
}
|
|
43521
|
+
/**
|
|
43522
|
+
* MCP Task 14: the R-3 hold Task 17 filters on and Task 15 writes as `r3_hold`. True while the manifest in force signs
|
|
43523
|
+
* an owner_dm `mcp:` string, or while the sticky flag is set (an old client's re-sign cannot lift it, audit C-1).
|
|
43524
|
+
*/
|
|
43525
|
+
r3Hold() {
|
|
43526
|
+
return this.toolGrantManifest?.signedMcpPresent === true || this.mcpSeen;
|
|
43527
|
+
}
|
|
43528
|
+
/** Persist then mirror. Setting mirrors even on a failed write; clearing mirrors only after the write (fail closed). */
|
|
43529
|
+
async setMcpSeen(seen) {
|
|
43530
|
+
if (seen) this.mcpSeen = true;
|
|
43531
|
+
try {
|
|
43532
|
+
await this.store.saveMcpSeen(seen);
|
|
43533
|
+
} catch (e) {
|
|
43534
|
+
writeServiceLog("warn", "authz.mcp_seen_save_failed", {}, e);
|
|
43535
|
+
return;
|
|
43536
|
+
}
|
|
43537
|
+
this.mcpSeen = seen;
|
|
43538
|
+
}
|
|
42297
43539
|
logManifest(state) {
|
|
42298
43540
|
this.manifestState = state;
|
|
42299
43541
|
const quiet = state === "ok" || state === "unchanged" || state === "no_pin" || state === "no_manifest";
|
|
@@ -42390,6 +43632,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
42390
43632
|
let tool_grants = NO_TOOL_GRANTS;
|
|
42391
43633
|
let relaySpeaker;
|
|
42392
43634
|
let memoryWrite = false;
|
|
43635
|
+
let relayFree = false;
|
|
42393
43636
|
const epochFresh = rosterFreshAt(entry, heldEpoch, heldGroupId) && (opts.voice === void 0 || rosterFreshAt(vEntry, opts.voice.heldEpoch, opts.voice.heldGroupId));
|
|
42394
43637
|
const ownerDmFresh = epochFresh || opts.voice === void 0 && this.ownerDmCycleFresh.has(channelId);
|
|
42395
43638
|
if (ownerDmFresh && this.ownerDmDefaultGrants && meta?.is_dm === true && sender) {
|
|
@@ -42416,6 +43659,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
42416
43659
|
tool_grants = ROOM_DEFAULT_READ_GRANTS;
|
|
42417
43660
|
}
|
|
42418
43661
|
let grantSource = tool_grants.length ? "constant" : "none";
|
|
43662
|
+
let ownerDmEffective = null;
|
|
42419
43663
|
const manifest = this.cfg.ownerGrantPin?.pin ? this.toolGrantManifest : null;
|
|
42420
43664
|
const binding = this.manifestBinding;
|
|
42421
43665
|
const boundNow = binding !== null && binding.state === "bound" && binding.counter === this.toolGrantCounter;
|
|
@@ -42434,6 +43678,21 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
42434
43678
|
});
|
|
42435
43679
|
tool_grants = r.grants;
|
|
42436
43680
|
grantSource = r.source;
|
|
43681
|
+
ownerDmEffective = r;
|
|
43682
|
+
const mcp = mcpIssuance({
|
|
43683
|
+
source: r.source,
|
|
43684
|
+
verdict: opts.ownerVerdict,
|
|
43685
|
+
entitlement: this.mcpEntitlementInForce(),
|
|
43686
|
+
hostSwitch: this.mcpHostSwitch,
|
|
43687
|
+
ownerDmManifestSwitchOn: this.ownerDmManifestGrants,
|
|
43688
|
+
disabled: this.payloadToolDeny,
|
|
43689
|
+
manifest,
|
|
43690
|
+
policy: this.mcpPolicyWritten
|
|
43691
|
+
});
|
|
43692
|
+
if (mcp.length > 0) {
|
|
43693
|
+
tool_grants = Object.freeze([...tool_grants, ...mcp]);
|
|
43694
|
+
relayFree = true;
|
|
43695
|
+
}
|
|
42437
43696
|
} else if (tool_grants === ROOM_DEFAULT_READ_GRANTS) {
|
|
42438
43697
|
const r = effectiveToolGrants({
|
|
42439
43698
|
audience: "team_room",
|
|
@@ -42451,12 +43710,39 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
42451
43710
|
if (opts.ownerVerdict?.kind === "failed") {
|
|
42452
43711
|
tool_grants = NO_TOOL_GRANTS;
|
|
42453
43712
|
memoryWrite = false;
|
|
43713
|
+
relayFree = false;
|
|
42454
43714
|
relaySpeaker = void 0;
|
|
42455
43715
|
grantSource = "none";
|
|
42456
43716
|
}
|
|
42457
43717
|
const roomClass = meta?.is_dm !== false ? "na" : entry?.crossOrg === false ? "internal" : entry?.crossOrg === true ? "cross_org" : "unknown";
|
|
42458
43718
|
if (opts.log !== false) {
|
|
42459
43719
|
this.logAuthzDecision(channelId, senderId, relaySpeakerId, audience, tool_grants.length, roomClass, epochFresh, memoryWrite, grantSource);
|
|
43720
|
+
const pinOwner = this.cfg.ownerGrantPin?.pin?.ownerIdentityId;
|
|
43721
|
+
const verdict = opts.ownerVerdict;
|
|
43722
|
+
if (pinOwner !== void 0 && senderId === pinOwner && meta?.is_dm === true && relaySpeakerId === void 0 && opts.voiceScoped !== true && opts.voice === void 0 && verdict !== void 0 && verdict.kind !== "unchecked" && !(verdict.kind === "failed" && verdict.reason === "replay")) {
|
|
43723
|
+
const pinned = this.channelOwner.get(channelId);
|
|
43724
|
+
const ownerId = pinned && pinned !== "anomalous" ? pinned.identityId : void 0;
|
|
43725
|
+
const ownerDirect = ownerId !== void 0 && audience === "owner_dm" && senderId === ownerId;
|
|
43726
|
+
this.noteOwnerDmTurn({
|
|
43727
|
+
cause: {
|
|
43728
|
+
killSwitchOff: !this.ownerDmDefaultGrants,
|
|
43729
|
+
manifestSwitchOff: !this.ownerDmManifestGrants,
|
|
43730
|
+
pinMissingOrAnomalous: ownerId === void 0,
|
|
43731
|
+
// OQ-5: only from a PRESENT roster entry; a missing (TTL-expired) entry is `stale` below.
|
|
43732
|
+
senderNotOwner: entry !== void 0 && (sender === void 0 || !ownerDirect),
|
|
43733
|
+
stale: !ownerDmFresh || entry === void 0,
|
|
43734
|
+
verdict,
|
|
43735
|
+
manifestPresent: manifest !== null,
|
|
43736
|
+
signedDmChannel: manifest?.owner_dm_channel_id,
|
|
43737
|
+
channelId,
|
|
43738
|
+
boundNow,
|
|
43739
|
+
consumerAttached: this.manifestConsumer !== null
|
|
43740
|
+
},
|
|
43741
|
+
grantSource,
|
|
43742
|
+
toolGrants: tool_grants,
|
|
43743
|
+
withheld: grantSource === "manifest" && ownerDmEffective !== null ? ownerDmEffective.withheld : []
|
|
43744
|
+
});
|
|
43745
|
+
}
|
|
42460
43746
|
}
|
|
42461
43747
|
return Object.freeze({
|
|
42462
43748
|
version: 1,
|
|
@@ -42464,9 +43750,78 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
42464
43750
|
sender_membership: sender ? "active" : "unknown",
|
|
42465
43751
|
tool_grants,
|
|
42466
43752
|
...relaySpeaker ? { relay_speaker: relaySpeaker } : {},
|
|
42467
|
-
...memoryWrite ? { memory_write: true } : {}
|
|
43753
|
+
...memoryWrite ? { memory_write: true } : {},
|
|
43754
|
+
...relayFree ? { relay_free: true } : {}
|
|
42468
43755
|
});
|
|
42469
43756
|
}
|
|
43757
|
+
/**
|
|
43758
|
+
* Tool status Phase 0 (spec §3.2, §3.5; plan Task 8; round 1 H-2, M-4; round 2 H-4): build the candidate record and
|
|
43759
|
+
* keep the previous one (and its `at`) when the change tuple is the same, so `at` means "last changed". Never throws
|
|
43760
|
+
* into the authorization path. Never logs a channel id, sender id or tool name.
|
|
43761
|
+
*/
|
|
43762
|
+
noteOwnerDmTurn(t) {
|
|
43763
|
+
try {
|
|
43764
|
+
const v = t.cause.verdict;
|
|
43765
|
+
const relay = (v.kind === "verified" || v.kind === "constants_only") && v.relay !== void 0 ? v.relay : "unknown";
|
|
43766
|
+
let reason = {};
|
|
43767
|
+
if (t.grantSource !== "manifest") {
|
|
43768
|
+
const c = this.toolStatusCause(t.cause);
|
|
43769
|
+
if (c !== null && c.source === t.grantSource && c.cause !== void 0) {
|
|
43770
|
+
reason = c.sub !== void 0 ? { cause: c.cause, sub: c.sub } : { cause: c.cause };
|
|
43771
|
+
} else {
|
|
43772
|
+
writeServiceLog("warn", "tool_status.source_mismatch");
|
|
43773
|
+
}
|
|
43774
|
+
} else {
|
|
43775
|
+
const c = this.toolStatusCause(t.cause);
|
|
43776
|
+
if (c === null || c.source !== "manifest") writeServiceLog("warn", "tool_status.source_mismatch");
|
|
43777
|
+
}
|
|
43778
|
+
const names = t.toolGrants.map((g) => g.tool);
|
|
43779
|
+
const candidate = Object.freeze({
|
|
43780
|
+
at: bucketStartAt(this.toolStatusNow()),
|
|
43781
|
+
// ruling M-2: the 15-minute UTC bucket, never the minute
|
|
43782
|
+
counter: this.toolGrantCounter,
|
|
43783
|
+
source: t.grantSource,
|
|
43784
|
+
...reason,
|
|
43785
|
+
relay,
|
|
43786
|
+
issued: Object.freeze(sanitizeIssued(names)),
|
|
43787
|
+
withheld: Object.freeze(t.withheld.map((w) => Object.freeze({ tool: w.tool, why: w.why }))),
|
|
43788
|
+
// A count only, never a name (content-free). 0 unless the MCP issuance above added mcp: grants.
|
|
43789
|
+
mcp_issued: Math.min(160, names.filter((n) => n.startsWith("mcp:")).length)
|
|
43790
|
+
});
|
|
43791
|
+
if (sameTuple(this.ownerDmLastTurn, candidate)) return;
|
|
43792
|
+
this.ownerDmLastTurn = candidate;
|
|
43793
|
+
this.toolStatusSender.note(candidate);
|
|
43794
|
+
} catch (e) {
|
|
43795
|
+
writeServiceLog("warn", "tool_status.record_failed", {}, e);
|
|
43796
|
+
}
|
|
43797
|
+
}
|
|
43798
|
+
/** R2-M1: the served manifest names another owner than the pin; the held record belongs to the old owner. */
|
|
43799
|
+
dropOwnerDmLastTurn() {
|
|
43800
|
+
this.ownerDmLastTurn = null;
|
|
43801
|
+
this.toolStatusSender.drop();
|
|
43802
|
+
}
|
|
43803
|
+
/**
|
|
43804
|
+
* Phase 0 Task 9 (spec §3.5): POST the record, body = the record only, to the narrow agent-key route. The backend
|
|
43805
|
+
* resolves the identity from the device credential; no identity, channel or sender id is in the URL or the body.
|
|
43806
|
+
* Returns the status class only; never throws.
|
|
43807
|
+
*/
|
|
43808
|
+
async postToolStatus(rec) {
|
|
43809
|
+
try {
|
|
43810
|
+
const res = await this.fetchBounded("runtime_config_tool_status", "control", `${this.cfg.apiBase}/api/v1/agents/me/runtime-config/tool-status`, {
|
|
43811
|
+
method: "POST",
|
|
43812
|
+
headers: { authorization: `Bearer ${this.cfg.apiKey}`, "content-type": "application/json" },
|
|
43813
|
+
body: JSON.stringify(rec)
|
|
43814
|
+
});
|
|
43815
|
+
const retryAfter = res.headers.get("retry-after");
|
|
43816
|
+
try {
|
|
43817
|
+
await res.body?.cancel();
|
|
43818
|
+
} catch {
|
|
43819
|
+
}
|
|
43820
|
+
return { status: res.status, retryAfter };
|
|
43821
|
+
} catch (e) {
|
|
43822
|
+
return e?.name === "FetchDeadlineError" ? "timeout" : "network";
|
|
43823
|
+
}
|
|
43824
|
+
}
|
|
42470
43825
|
/**
|
|
42471
43826
|
* One content-free INFO line per authorization decision (every call site of
|
|
42472
43827
|
* authorizationFor stages exactly one runtime turn). It says WHY a turn got
|
|
@@ -42690,6 +44045,111 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
42690
44045
|
* client's own wait already renders "Summary unavailable" — so there is
|
|
42691
44046
|
* nothing to catch here besides the send itself.
|
|
42692
44047
|
*/
|
|
44048
|
+
/**
|
|
44049
|
+
* MCP client Task 18 (spec §8 item 2; audit M-3, H-3; OQ-2, OQ-15). Accept an `mcp_catalog_request` only when: its
|
|
44050
|
+
* body is exactly the §8 shape; it is a direct frame (never a voice channel's) in a channel the server marks as a DM
|
|
44051
|
+
* that IS the signed owner DM; verifyOwnerTurn says `verified` (the pinned owner's signature, the signed DM, the leaf
|
|
44052
|
+
* check, not a replay) with `relayLeaf === false`; mcp-policy.json as written is `ok` with this server `ok`; a runtime
|
|
44053
|
+
* socket that declared mcp_client_v1 is attached; and the rate limits admit it. The replay row is written HERE
|
|
44054
|
+
* (M-3: the request never reaches stageRuntimeInbox, where it is otherwise written), for every verified request, before
|
|
44055
|
+
* anything is answered; the ownerReplayPending entry is removed in `finally`. A failure to record answers nothing.
|
|
44056
|
+
* Never throws. The answer runs outside the ingest cycle, so the row is acked now.
|
|
44057
|
+
*/
|
|
44058
|
+
async acceptMcpCatalogRequest(m, body) {
|
|
44059
|
+
try {
|
|
44060
|
+
const req = parseMcpCatalogRequest(body);
|
|
44061
|
+
if (req === null) return logMcpCatalogRequest("malformed");
|
|
44062
|
+
const signedDm = this.toolGrantManifest?.owner_dm_channel_id;
|
|
44063
|
+
if (this.keyChannelForFrame(m) !== m.channelId || this.freshMeta(m.channelId)?.is_dm !== true || signedDm === void 0 || signedDm !== m.channelId) {
|
|
44064
|
+
return logMcpCatalogRequest("not_owner_dm");
|
|
44065
|
+
}
|
|
44066
|
+
const verdict = await this.verifyOwnerTurn(m);
|
|
44067
|
+
if (verdict.kind !== "verified") return logMcpCatalogRequest(verdict.kind === "failed" && verdict.reason === "replay" ? "replay" : "not_verified");
|
|
44068
|
+
this.store.recordOwnerMessage(verdict.replay.channelId, m.messageId, verdict.replay.groupId, verdict.replay.epoch, Date.now());
|
|
44069
|
+
if (verdict.relayLeaf !== false) return logMcpCatalogRequest("relay_leaf");
|
|
44070
|
+
const target = this.mcpCatalogTarget(req.serverId);
|
|
44071
|
+
const lister = this.mcpClientCapable() ? this.mcpCatalogListers.values().next().value : void 0;
|
|
44072
|
+
if (target === null || lister === void 0) return logMcpCatalogRequest("not_ready");
|
|
44073
|
+
const admitted = this.mcpCatalogRate.admit(req.serverId);
|
|
44074
|
+
if (admitted !== "ok") return admitted === "in_flight" ? logMcpCatalogRequest("in_flight") : void 0;
|
|
44075
|
+
const work = this.outsideIngestCycle(() => this.answerMcpCatalogRequest(m.channelId, req, lister)).finally(() => {
|
|
44076
|
+
this.mcpCatalogRate.release(req.serverId);
|
|
44077
|
+
this.mcpCatalogWork.delete(work);
|
|
44078
|
+
});
|
|
44079
|
+
this.mcpCatalogWork.add(work);
|
|
44080
|
+
} catch {
|
|
44081
|
+
logMcpCatalogRequest("failed");
|
|
44082
|
+
} finally {
|
|
44083
|
+
this.ownerReplayPending.delete(m.messageId);
|
|
44084
|
+
}
|
|
44085
|
+
}
|
|
44086
|
+
/** The signed server's vendor and the bridge catalog row's issuer, when mcp-policy.json as WRITTEN is `ok` with it `ok`. */
|
|
44087
|
+
mcpCatalogTarget(serverId) {
|
|
44088
|
+
const doc = this.mcpPolicyWritten;
|
|
44089
|
+
if (doc === null || doc.state !== "ok" || !Object.prototype.hasOwnProperty.call(doc.servers, serverId)) return null;
|
|
44090
|
+
const s = doc.servers[serverId];
|
|
44091
|
+
if (s.state !== "ok" || s.vendor === void 0) return null;
|
|
44092
|
+
const catalog = this.mcpCatalog ?? MCP_CATALOG;
|
|
44093
|
+
if (!Object.prototype.hasOwnProperty.call(catalog, s.vendor)) return null;
|
|
44094
|
+
return { vendor: s.vendor, issuer: catalog[s.vendor].issuer };
|
|
44095
|
+
}
|
|
44096
|
+
/** Ask the runtime, re-check, build (384 KB cap) and send. Never throws. */
|
|
44097
|
+
async answerMcpCatalogRequest(channelId, req, lister) {
|
|
44098
|
+
try {
|
|
44099
|
+
const target = this.mcpCatalogTarget(req.serverId);
|
|
44100
|
+
if (target === null) return logMcpCatalogRequest("not_ready");
|
|
44101
|
+
const frame = await lister(req.serverId, MCP_CATALOG_LIST_TIMEOUT_MS);
|
|
44102
|
+
if (frame === null || frame === void 0) return logMcpCatalogRequest("runtime_timeout");
|
|
44103
|
+
const listed = validateCatalogListReply(frame, req.serverId, target.vendor);
|
|
44104
|
+
if (listed === null) return logMcpCatalogRequest("runtime_refused");
|
|
44105
|
+
const still = this.mcpCatalogTarget(req.serverId);
|
|
44106
|
+
if (still === null || still.vendor !== target.vendor) return logMcpCatalogRequest("not_ready");
|
|
44107
|
+
await this.sendMcpCatalog(channelId, buildMcpCatalogBody({ serverId: req.serverId, requestId: req.requestId, vendor: still.vendor, issuer: still.issuer, reply: listed }));
|
|
44108
|
+
logMcpCatalogRequest("sent");
|
|
44109
|
+
} catch (e) {
|
|
44110
|
+
logMcpCatalogRequest(e instanceof McpReplyWithheldError ? "withheld" : "send_failed");
|
|
44111
|
+
}
|
|
44112
|
+
}
|
|
44113
|
+
/**
|
|
44114
|
+
* The `mcp_catalog` reply: a system_event envelope with a body-level type (the sendSummary shape), on the shared
|
|
44115
|
+
* self-heal seam and the one publish chokepoint. H-D applies to EVERY attempt (it carries vendor-supplied tool
|
|
44116
|
+
* descriptions): after the seal, before the POST, the leaves at the SEALED group and epoch must be exactly the owner
|
|
44117
|
+
* and the agent, or the frame is withheld. One message id across the retry.
|
|
44118
|
+
*/
|
|
44119
|
+
async sendMcpCatalog(channelId, body) {
|
|
44120
|
+
const joined = await this.reconcileJoin(channelId);
|
|
44121
|
+
if (!joined) throw new ChannelPendingError(channelId);
|
|
44122
|
+
const messageId = crypto.randomUUID();
|
|
44123
|
+
let attempt = 0;
|
|
44124
|
+
await this.sendWithSelfHealTerminal(
|
|
44125
|
+
this.provider,
|
|
44126
|
+
channelId,
|
|
44127
|
+
(epoch) => buildOutboundEnvelope({
|
|
44128
|
+
custody: this.custody,
|
|
44129
|
+
groupKey: this.provider,
|
|
44130
|
+
messageId,
|
|
44131
|
+
channelId,
|
|
44132
|
+
enclaveId: this.resolveEnclave(channelId),
|
|
44133
|
+
senderId: this.cfg.identityId,
|
|
44134
|
+
senderType: "agent",
|
|
44135
|
+
type: "system_event",
|
|
44136
|
+
bodyPlaintext: JSON.stringify(body),
|
|
44137
|
+
bindingId: "",
|
|
44138
|
+
bindingVersion: "0.0.0",
|
|
44139
|
+
agentSkillId: null,
|
|
44140
|
+
epoch,
|
|
44141
|
+
traceparent: ZERO_TRACEPARENT,
|
|
44142
|
+
references: [],
|
|
44143
|
+
stage5Outcome: "allow",
|
|
44144
|
+
stage5Evaluators: []
|
|
44145
|
+
}),
|
|
44146
|
+
async (envelope) => {
|
|
44147
|
+
attempt++;
|
|
44148
|
+
this.assertMcpReplyRelayFree(channelId, envelope, attempt);
|
|
44149
|
+
return this.publishEnvelope(channelId, () => this.transport.publish(envelope));
|
|
44150
|
+
}
|
|
44151
|
+
);
|
|
44152
|
+
}
|
|
42693
44153
|
async handleSummarize(channelId, request) {
|
|
42694
44154
|
const body = await runSummarize({
|
|
42695
44155
|
request,
|
|
@@ -42721,13 +44181,13 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
42721
44181
|
throw new Error("summarize_no_runtime");
|
|
42722
44182
|
}
|
|
42723
44183
|
const messages = [{ role: "user", content: prompt }];
|
|
42724
|
-
return new Promise((
|
|
44184
|
+
return new Promise((resolve5, reject) => {
|
|
42725
44185
|
const onAbort = () => reject(new Error("aborted"));
|
|
42726
44186
|
signal.addEventListener("abort", onAbort);
|
|
42727
44187
|
this.fastLlm.complete(messages).then(
|
|
42728
44188
|
(text) => {
|
|
42729
44189
|
signal.removeEventListener("abort", onAbort);
|
|
42730
|
-
|
|
44190
|
+
resolve5(text);
|
|
42731
44191
|
},
|
|
42732
44192
|
(err) => {
|
|
42733
44193
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -43200,6 +44660,11 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
43200
44660
|
new TextDecoder().decode(m.plaintext)
|
|
43201
44661
|
);
|
|
43202
44662
|
if (parsed.type && parsed.type !== "text") {
|
|
44663
|
+
if (parsed.type === "mcp_catalog_request" || parsed.type === "mcp_catalog") {
|
|
44664
|
+
if (parsed.type === "mcp_catalog_request") await this.acceptMcpCatalogRequest(m, parsed);
|
|
44665
|
+
acked.push(m.queueId);
|
|
44666
|
+
continue;
|
|
44667
|
+
}
|
|
43203
44668
|
if (parsed.type === "summarize") {
|
|
43204
44669
|
const request = parseSummarizeBody(parsed, this.cfg.identityId, writeServiceLog);
|
|
43205
44670
|
if (request !== null) {
|
|
@@ -43675,7 +45140,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
43675
45140
|
return got;
|
|
43676
45141
|
}
|
|
43677
45142
|
const cap = Math.min(waitSeconds, 50) * 1e3;
|
|
43678
|
-
await new Promise((
|
|
45143
|
+
await new Promise((resolve5) => {
|
|
43679
45144
|
let fired = false;
|
|
43680
45145
|
let timer;
|
|
43681
45146
|
const wake = () => {
|
|
@@ -43684,7 +45149,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
43684
45149
|
clearTimeout(timer);
|
|
43685
45150
|
const i = this.waiters.indexOf(wake);
|
|
43686
45151
|
if (i >= 0) this.waiters.splice(i, 1);
|
|
43687
|
-
|
|
45152
|
+
resolve5();
|
|
43688
45153
|
};
|
|
43689
45154
|
timer = setTimeout(wake, cap);
|
|
43690
45155
|
this.waiters.push(wake);
|
|
@@ -43708,7 +45173,11 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
43708
45173
|
}
|
|
43709
45174
|
/** Every buffer exit: re-narrow manifest grants (review H-1), then note the frame's attribution class (review H-3). */
|
|
43710
45175
|
delivering(m) {
|
|
43711
|
-
|
|
45176
|
+
let out = this.renarrowManifestGrants(m);
|
|
45177
|
+
if (!this.noteMcpDelivered(out)) {
|
|
45178
|
+
out = withMcpCapability(out, false);
|
|
45179
|
+
writeServiceLog("warn", "mcp.delivered_record_full");
|
|
45180
|
+
}
|
|
43712
45181
|
this.noteDeliveredFrame(out);
|
|
43713
45182
|
return out;
|
|
43714
45183
|
}
|
|
@@ -43719,6 +45188,50 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
43719
45188
|
* audience that is still signed, else dropped. Only narrows; an unchanged message is returned by reference.
|
|
43720
45189
|
*/
|
|
43721
45190
|
renarrowManifestGrants(m) {
|
|
45191
|
+
const narrowed = this.renarrowManifestGrantsBase(m);
|
|
45192
|
+
return this.renarrowMcpGrants(narrowed);
|
|
45193
|
+
}
|
|
45194
|
+
/**
|
|
45195
|
+
* MCP Task 16 (audit H-2; round 2 LOW), after the base re-narrowing: a buffered `mcp:` grant survives release only
|
|
45196
|
+
* when, NOW, the turn is still a plain owner-DM turn in the signed DM channel, the host switch reads "on", the
|
|
45197
|
+
* entitlement reading is in force, the name is not in skills.disabled, the mcp-policy.json document last written
|
|
45198
|
+
* lists the server ok with the tool, and a FRESH synchronous read of the DM's local leaves holds exactly the pinned
|
|
45199
|
+
* owner and the agent (MlsGroupKeyProvider.leafSnapshotCached: one `state` object, no await; null fails closed). Any
|
|
45200
|
+
* other mcp: grant is dropped, never kept as a fallback. relay_free is dropped whenever no mcp: grant survives.
|
|
45201
|
+
* Read-only: never touches the MLS group. Only narrows; returns `m` by reference when nothing changes.
|
|
45202
|
+
*/
|
|
45203
|
+
renarrowMcpGrants(m) {
|
|
45204
|
+
const a = m.authorization;
|
|
45205
|
+
if (!a) return m;
|
|
45206
|
+
const isMcp = (g) => g.tool.startsWith(MCP_GRANT_PREFIX);
|
|
45207
|
+
if (!a.tool_grants.some(isMcp) && a.relay_free === void 0) return m;
|
|
45208
|
+
const pin = this.cfg.ownerGrantPin?.pin ?? null;
|
|
45209
|
+
const manifest = pin !== null ? this.toolGrantManifest : null;
|
|
45210
|
+
const turnOk = pin !== null && manifest !== null && a.audience === "owner_dm" && a.relay_speaker === void 0 && manifest.owner_dm_channel_id === m.channel_id && this.mcpHostSwitch === "on" && this.ownerDmManifestGrants && this.mcpEntitlementInForce() === "entitled" && this.ownerDmRelayFreeNow(m.channel_id, pin.ownerIdentityId);
|
|
45211
|
+
const kept = a.tool_grants.filter((g) => {
|
|
45212
|
+
if (!isMcp(g)) return true;
|
|
45213
|
+
const parsed = parseMcpGrant(g.tool);
|
|
45214
|
+
return turnOk && g.mode === "read" && g.source === "manifest" && parsed !== null && !this.payloadToolDeny.has(g.tool) && mcpPolicyAllows(this.mcpPolicyWritten, parsed.serverId, parsed.tool);
|
|
45215
|
+
});
|
|
45216
|
+
const keepRelayFree = a.relay_free === true && kept.some(isMcp);
|
|
45217
|
+
if (kept.length === a.tool_grants.length && keepRelayFree === (a.relay_free === true)) return m;
|
|
45218
|
+
const { relay_free: _relayFree, ...rest } = a;
|
|
45219
|
+
return { ...m, authorization: Object.freeze({ ...rest, tool_grants: Object.freeze(kept), ...keepRelayFree ? { relay_free: true } : {} }) };
|
|
45220
|
+
}
|
|
45221
|
+
/**
|
|
45222
|
+
* MCP Task 16 (OQ-2; audit H-2, round 2 H-D): a FRESH read of the channel's local leaf set holds exactly the pinned
|
|
45223
|
+
* owner and this agent. Synchronous (one manager `state`, so the leaves belong to one epoch); a cold manager or any
|
|
45224
|
+
* throw reads as NOT relay-free.
|
|
45225
|
+
*/
|
|
45226
|
+
ownerDmRelayFreeNow(channelId, ownerIdentityId) {
|
|
45227
|
+
try {
|
|
45228
|
+
const snap = this.provider.leafSnapshotCached(channelId);
|
|
45229
|
+
return snap !== null && ownerDmLeavesRelayFree(snap.leaves, { ownerIdentityId, agentIdentityId: this.cfg.identityId });
|
|
45230
|
+
} catch {
|
|
45231
|
+
return false;
|
|
45232
|
+
}
|
|
45233
|
+
}
|
|
45234
|
+
renarrowManifestGrantsBase(m) {
|
|
43722
45235
|
const a = m.authorization;
|
|
43723
45236
|
if (!a || !a.tool_grants.some((g) => g.mode === "read" && g.source === "manifest")) return m;
|
|
43724
45237
|
const audience = a.audience === "owner_dm" || a.audience === "team_room" ? a.audience : null;
|
|
@@ -43727,13 +45240,13 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
43727
45240
|
const bound = b !== null && b.state === "bound" && b.counter === this.toolGrantCounter;
|
|
43728
45241
|
const switchOn = audience === "owner_dm" ? this.ownerDmManifestGrants : audience === "team_room" ? this.roomManifestGrants : false;
|
|
43729
45242
|
const signedNow = new Set(audience && manifest ? manifest.grants[audience] ?? [] : []);
|
|
43730
|
-
const
|
|
45243
|
+
const constants5 = new Set(audience === "owner_dm" ? OWNER_DM_DEFAULT_READ_GRANTS.map((g) => g.tool) : audience === "team_room" ? ROOM_DEFAULT_READ_GRANTS.map((g) => g.tool) : []);
|
|
43731
45244
|
let changed = false;
|
|
43732
45245
|
const out = a.tool_grants.flatMap((g) => {
|
|
43733
45246
|
if (!(g.mode === "read" && g.source === "manifest")) return [g];
|
|
43734
45247
|
if (switchOn && bound && signedNow.has(g.tool)) return [g];
|
|
43735
45248
|
changed = true;
|
|
43736
|
-
return
|
|
45249
|
+
return constants5.has(g.tool) && signedNow.has(g.tool) ? [Object.freeze({ tool: g.tool, mode: "read" })] : [];
|
|
43737
45250
|
});
|
|
43738
45251
|
if (!changed) return m;
|
|
43739
45252
|
return { ...m, authorization: Object.freeze({ ...a, tool_grants: Object.freeze(out) }) };
|
|
@@ -43814,6 +45327,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
43814
45327
|
return;
|
|
43815
45328
|
}
|
|
43816
45329
|
if (!this.admitRuntimeDelivery(m)) return;
|
|
45330
|
+
this.noteMcpDelivered(m, true);
|
|
43817
45331
|
const ch = m.channel_id;
|
|
43818
45332
|
this.replayingChannels.add(ch);
|
|
43819
45333
|
this.replyIntentReplays++;
|
|
@@ -44164,11 +45678,11 @@ ${channelId}`;
|
|
|
44164
45678
|
if (!res.ok) return void 0;
|
|
44165
45679
|
const rows = await res.json();
|
|
44166
45680
|
if (!Array.isArray(rows)) return void 0;
|
|
44167
|
-
const
|
|
45681
|
+
const own2 = rows.find(
|
|
44168
45682
|
(r) => r?.identity_id === this.cfg.identityId
|
|
44169
45683
|
);
|
|
44170
|
-
if (!
|
|
44171
|
-
return typeof
|
|
45684
|
+
if (!own2) return void 0;
|
|
45685
|
+
return typeof own2.paused_at === "string" && own2.paused_at.length > 0;
|
|
44172
45686
|
} catch (e) {
|
|
44173
45687
|
writeServiceLog("warn", "pause.probe_failed", {}, e);
|
|
44174
45688
|
return void 0;
|
|
@@ -44201,6 +45715,10 @@ ${channelId}`;
|
|
|
44201
45715
|
/** Complete the durable handoff only after the runtime explicitly acks. */
|
|
44202
45716
|
ackRuntimeMessages(messageIds) {
|
|
44203
45717
|
for (const id of messageIds) this.roomGrantEpochs.delete(id);
|
|
45718
|
+
for (const id of messageIds) {
|
|
45719
|
+
const r = this.mcpDeliveredTurns.get(id);
|
|
45720
|
+
if (r) r.acked = true;
|
|
45721
|
+
}
|
|
44204
45722
|
for (const messageId of messageIds) {
|
|
44205
45723
|
this.store.ackRuntimeInbox(messageId);
|
|
44206
45724
|
this.runtimePendingIds.delete(messageId);
|
|
@@ -44282,6 +45800,7 @@ ${channelId}`;
|
|
|
44282
45800
|
const message = rest;
|
|
44283
45801
|
this.applyStagedMarks(message.message_id, marks);
|
|
44284
45802
|
this.runtimePendingIds.add(message.message_id);
|
|
45803
|
+
this.noteMcpDelivered(message, true);
|
|
44285
45804
|
if (this.cfg.ownerGrantPin?.pin && this.isOwnerVerifyFailed(message.message_id)) {
|
|
44286
45805
|
this.ownerVerifyFailedIds.set(message.message_id, message.channel_id);
|
|
44287
45806
|
}
|
|
@@ -44462,6 +45981,67 @@ ${channelId}`;
|
|
|
44462
45981
|
clearSearchProvidersSocket(token) {
|
|
44463
45982
|
if (this.searchProvidersSockets.delete(token)) this.onCapabilityMaybeChanged();
|
|
44464
45983
|
}
|
|
45984
|
+
/** MCP Task 16 (spec §15): the same AND-over-sockets state for mcp_client_v1, kept per socket. */
|
|
45985
|
+
mcpClientSockets = /* @__PURE__ */ new Map();
|
|
45986
|
+
noteMcpClientSocket(token, declared) {
|
|
45987
|
+
this.mcpClientSockets.set(token, declared);
|
|
45988
|
+
this.onCapabilityMaybeChanged();
|
|
45989
|
+
}
|
|
45990
|
+
clearMcpClientSocket(token) {
|
|
45991
|
+
if (this.mcpClientSockets.delete(token)) this.onCapabilityMaybeChanged();
|
|
45992
|
+
}
|
|
45993
|
+
/** MCP Task 18: ws-server registers (or, with null, removes) a socket's loopback catalog lister. */
|
|
45994
|
+
noteMcpCatalogLister(token, lister) {
|
|
45995
|
+
if (lister === null) this.mcpCatalogListers.delete(token);
|
|
45996
|
+
else this.mcpCatalogListers.set(token, lister);
|
|
45997
|
+
}
|
|
45998
|
+
/**
|
|
45999
|
+
* MCP Task 19 (spec §12.3): "ok" when every attached runtime socket declared mcp_client_v1 (the AND, which harness
|
|
46000
|
+
* sockets hold false: a harness socket counts in the AND and never declares it); else "harness_consumer" when the
|
|
46001
|
+
* latest hello was a harness; else "missing".
|
|
46002
|
+
*/
|
|
46003
|
+
reportedMcpCapability() {
|
|
46004
|
+
if (this.mcpClientCapable()) return "ok";
|
|
46005
|
+
return this.lastConsumerCapability === "harness_consumer" ? "harness_consumer" : "missing";
|
|
46006
|
+
}
|
|
46007
|
+
/**
|
|
46008
|
+
* MCP Task 19 (spec §12.3): at most 8 `{server_id, vendor, state}` for the signed servers of the manifest in force,
|
|
46009
|
+
* from mcp-policy.json as WRITTEN. Only the states the bridge itself knows: top `kill_switch` and `not_entitled` give
|
|
46010
|
+
* that state per server; top `ok` gives `ok` for a server the bridge lists `ok` and `malformed` for one it refused.
|
|
46011
|
+
* Every other top state (no capability, suspended, no config, switch off, section absent, malformed section) gives no
|
|
46012
|
+
* row. The runtime-only states (needs_login, login_expired, definition_changed, server_version_unsupported,
|
|
46013
|
+
* server_unreachable) have no runtime-to-bridge report path in Phase 1. `vendor` is reported only when it is a key of
|
|
46014
|
+
* the bridge's compiled catalog; a signed vendor outside it gives no row. Never a tool name, URL or issuer.
|
|
46015
|
+
*/
|
|
46016
|
+
reportedMcpServers() {
|
|
46017
|
+
const doc = this.mcpPolicyWritten;
|
|
46018
|
+
const signed = (this.cfg.ownerGrantPin?.pin ?? null) !== null ? this.toolGrantManifest?.mcp?.servers : void 0;
|
|
46019
|
+
if (doc === null || signed === void 0) return [];
|
|
46020
|
+
const catalog = this.mcpCatalog ?? MCP_CATALOG;
|
|
46021
|
+
const out = [];
|
|
46022
|
+
for (const id of Object.keys(signed).sort()) {
|
|
46023
|
+
if (out.length >= 8) break;
|
|
46024
|
+
const vendor = signed[id].vendor;
|
|
46025
|
+
if (!Object.prototype.hasOwnProperty.call(catalog, vendor)) continue;
|
|
46026
|
+
let state;
|
|
46027
|
+
if (doc.state === "kill_switch" || doc.state === "not_entitled") state = doc.state;
|
|
46028
|
+
else if (doc.state === "ok") state = Object.prototype.hasOwnProperty.call(doc.servers, id) && doc.servers[id].state === "ok" ? "ok" : "malformed";
|
|
46029
|
+
else continue;
|
|
46030
|
+
out.push({ server_id: id, vendor, state });
|
|
46031
|
+
}
|
|
46032
|
+
return out;
|
|
46033
|
+
}
|
|
46034
|
+
/**
|
|
46035
|
+
* MCP Task 16: every attached runtime socket declared mcp_client_v1; no socket attached means absent (false).
|
|
46036
|
+
* PR-B2 follow-up (documented): a HARNESS socket counts in this AND. ws-server notes every socket that said hello,
|
|
46037
|
+
* and a harness hello is always noted `false` (harnessClaimed never resets), so one attached harness connector holds
|
|
46038
|
+
* the capability false for files, the report and mcp-policy.json (top `no_capability`) while it is attached.
|
|
46039
|
+
*/
|
|
46040
|
+
mcpClientCapable() {
|
|
46041
|
+
if (this.mcpClientSockets.size === 0) return false;
|
|
46042
|
+
for (const v of this.mcpClientSockets.values()) if (!v) return false;
|
|
46043
|
+
return true;
|
|
46044
|
+
}
|
|
44465
46045
|
noteUtilityToolsSocket(token, declared) {
|
|
44466
46046
|
this.utilityToolsSockets.set(token, declared);
|
|
44467
46047
|
this.onCapabilityMaybeChanged();
|
|
@@ -44480,6 +46060,7 @@ ${channelId}`;
|
|
|
44480
46060
|
const out = /* @__PURE__ */ new Set();
|
|
44481
46061
|
if (this.searchProvidersCapable()) out.add("search_providers_v2");
|
|
44482
46062
|
if (this.utilityToolsCapable()) out.add("utility_tools_v1");
|
|
46063
|
+
if (this.mcpClientCapable()) out.add("mcp_client_v1");
|
|
44483
46064
|
return out;
|
|
44484
46065
|
}
|
|
44485
46066
|
/** D28: every attached runtime socket declared search_providers_v2; no socket attached means absent (false). */
|
|
@@ -44508,11 +46089,12 @@ ${channelId}`;
|
|
|
44508
46089
|
* switches and the new capability, then a debounced refetch (one per burst) only when needed.
|
|
44509
46090
|
*/
|
|
44510
46091
|
onCapabilityMaybeChanged() {
|
|
44511
|
-
const next = { search: this.searchProvidersCapable(), utility: this.utilityToolsCapable() };
|
|
46092
|
+
const next = { search: this.searchProvidersCapable(), utility: this.utilityToolsCapable(), mcp: this.mcpClientCapable() };
|
|
44512
46093
|
const prev = this.capabilityState;
|
|
44513
|
-
if (prev !== null && prev.search === next.search && prev.utility === next.utility) return;
|
|
46094
|
+
if (prev !== null && prev.search === next.search && prev.utility === next.utility && prev.mcp === next.mcp) return;
|
|
44514
46095
|
this.capabilityState = next;
|
|
44515
46096
|
this.recomputeManifestPolicy(this.capabilityFeatures());
|
|
46097
|
+
this.recomputeMcpPolicy();
|
|
44516
46098
|
if (this.closingDown) return;
|
|
44517
46099
|
if (prev === null && !next.search && !next.utility) return;
|
|
44518
46100
|
const now = Date.now();
|
|
@@ -44550,7 +46132,7 @@ ${channelId}`;
|
|
|
44550
46132
|
}
|
|
44551
46133
|
const withheld = new Set(Object.keys(TOOL_FEATURE).filter((t) => !toolAllowedByFeatures(t, features)));
|
|
44552
46134
|
if (withheld.size === 0) return;
|
|
44553
|
-
const path =
|
|
46135
|
+
const path = join20(runtimeConfigDir(this.home, this.cfg.identityId), MANIFEST_POLICY_FILE);
|
|
44554
46136
|
if (!existsSync5(path)) return;
|
|
44555
46137
|
let doc;
|
|
44556
46138
|
try {
|
|
@@ -44758,6 +46340,7 @@ ${channelId}`;
|
|
|
44758
46340
|
async closeOnce() {
|
|
44759
46341
|
this.closingDown = true;
|
|
44760
46342
|
this.statusGate.close();
|
|
46343
|
+
this.toolStatusSender.close();
|
|
44761
46344
|
for (const t of this.pauseRecheckTimers.values()) clearTimeout(t);
|
|
44762
46345
|
this.pauseRecheckTimers.clear();
|
|
44763
46346
|
for (const t of this.supersedeRecheckTimers.values()) clearTimeout(t);
|
|
@@ -44766,6 +46349,8 @@ ${channelId}`;
|
|
|
44766
46349
|
this.redeliveryTimer = null;
|
|
44767
46350
|
if (this.searchCapabilityRefetchTimer !== null) clearTimeout(this.searchCapabilityRefetchTimer);
|
|
44768
46351
|
this.searchCapabilityRefetchTimer = null;
|
|
46352
|
+
if (this.mcpPolicyRetryTimer !== null) clearTimeout(this.mcpPolicyRetryTimer);
|
|
46353
|
+
this.mcpPolicyRetryTimer = null;
|
|
44769
46354
|
this.pingHandle?.close();
|
|
44770
46355
|
if (this.safetyTimer) clearInterval(this.safetyTimer);
|
|
44771
46356
|
if (this.runtimeConfigTimer) clearInterval(this.runtimeConfigTimer);
|
|
@@ -44775,8 +46360,8 @@ ${channelId}`;
|
|
|
44775
46360
|
let timer;
|
|
44776
46361
|
await Promise.race([
|
|
44777
46362
|
ingest.released,
|
|
44778
|
-
new Promise((
|
|
44779
|
-
timer = setTimeout(
|
|
46363
|
+
new Promise((resolve5) => {
|
|
46364
|
+
timer = setTimeout(resolve5, _BridgeAgent.GRACEFUL_DRAIN_MS);
|
|
44780
46365
|
})
|
|
44781
46366
|
]);
|
|
44782
46367
|
if (timer) clearTimeout(timer);
|
|
@@ -44786,8 +46371,8 @@ ${channelId}`;
|
|
|
44786
46371
|
let timer;
|
|
44787
46372
|
await Promise.race([
|
|
44788
46373
|
starting,
|
|
44789
|
-
new Promise((
|
|
44790
|
-
timer = setTimeout(
|
|
46374
|
+
new Promise((resolve5) => {
|
|
46375
|
+
timer = setTimeout(resolve5, _BridgeAgent.GRACEFUL_DRAIN_MS);
|
|
44791
46376
|
})
|
|
44792
46377
|
]);
|
|
44793
46378
|
if (timer) clearTimeout(timer);
|
|
@@ -44796,8 +46381,8 @@ ${channelId}`;
|
|
|
44796
46381
|
let timer;
|
|
44797
46382
|
await Promise.race([
|
|
44798
46383
|
this.membershipSweepWork,
|
|
44799
|
-
new Promise((
|
|
44800
|
-
timer = setTimeout(
|
|
46384
|
+
new Promise((resolve5) => {
|
|
46385
|
+
timer = setTimeout(resolve5, _BridgeAgent.GRACEFUL_DRAIN_MS);
|
|
44801
46386
|
})
|
|
44802
46387
|
]);
|
|
44803
46388
|
if (timer) clearTimeout(timer);
|
|
@@ -44807,8 +46392,8 @@ ${channelId}`;
|
|
|
44807
46392
|
let timer;
|
|
44808
46393
|
await Promise.race([
|
|
44809
46394
|
this.recallPruneWork,
|
|
44810
|
-
new Promise((
|
|
44811
|
-
timer = setTimeout(
|
|
46395
|
+
new Promise((resolve5) => {
|
|
46396
|
+
timer = setTimeout(resolve5, _BridgeAgent.GRACEFUL_DRAIN_MS);
|
|
44812
46397
|
})
|
|
44813
46398
|
]);
|
|
44814
46399
|
if (timer) clearTimeout(timer);
|
|
@@ -44818,8 +46403,8 @@ ${channelId}`;
|
|
|
44818
46403
|
let timer;
|
|
44819
46404
|
await Promise.race([
|
|
44820
46405
|
this.recallMaintenanceWork,
|
|
44821
|
-
new Promise((
|
|
44822
|
-
timer = setTimeout(
|
|
46406
|
+
new Promise((resolve5) => {
|
|
46407
|
+
timer = setTimeout(resolve5, _BridgeAgent.GRACEFUL_DRAIN_MS);
|
|
44823
46408
|
})
|
|
44824
46409
|
]);
|
|
44825
46410
|
if (timer) clearTimeout(timer);
|
|
@@ -44831,8 +46416,8 @@ ${channelId}`;
|
|
|
44831
46416
|
let timer;
|
|
44832
46417
|
await Promise.race([
|
|
44833
46418
|
Promise.allSettled([...this.recallTapWork]),
|
|
44834
|
-
new Promise((
|
|
44835
|
-
timer = setTimeout(
|
|
46419
|
+
new Promise((resolve5) => {
|
|
46420
|
+
timer = setTimeout(resolve5, _BridgeAgent.GRACEFUL_DRAIN_MS);
|
|
44836
46421
|
})
|
|
44837
46422
|
]);
|
|
44838
46423
|
if (timer) clearTimeout(timer);
|
|
@@ -44841,8 +46426,8 @@ ${channelId}`;
|
|
|
44841
46426
|
let timer;
|
|
44842
46427
|
await Promise.race([
|
|
44843
46428
|
Promise.allSettled([...this.unifiedWork]),
|
|
44844
|
-
new Promise((
|
|
44845
|
-
timer = setTimeout(
|
|
46429
|
+
new Promise((resolve5) => {
|
|
46430
|
+
timer = setTimeout(resolve5, _BridgeAgent.GRACEFUL_DRAIN_MS);
|
|
44846
46431
|
})
|
|
44847
46432
|
]);
|
|
44848
46433
|
if (timer) clearTimeout(timer);
|
|
@@ -44851,8 +46436,8 @@ ${channelId}`;
|
|
|
44851
46436
|
let timer;
|
|
44852
46437
|
await Promise.race([
|
|
44853
46438
|
this.recallPrewarmWork,
|
|
44854
|
-
new Promise((
|
|
44855
|
-
timer = setTimeout(
|
|
46439
|
+
new Promise((resolve5) => {
|
|
46440
|
+
timer = setTimeout(resolve5, _BridgeAgent.GRACEFUL_DRAIN_MS);
|
|
44856
46441
|
})
|
|
44857
46442
|
]);
|
|
44858
46443
|
if (timer) clearTimeout(timer);
|
|
@@ -44862,8 +46447,8 @@ ${channelId}`;
|
|
|
44862
46447
|
let timer;
|
|
44863
46448
|
await Promise.race([
|
|
44864
46449
|
Promise.allSettled([...this.replyWork]),
|
|
44865
|
-
new Promise((
|
|
44866
|
-
timer = setTimeout(
|
|
46450
|
+
new Promise((resolve5) => {
|
|
46451
|
+
timer = setTimeout(resolve5, _BridgeAgent.GRACEFUL_DRAIN_MS);
|
|
44867
46452
|
})
|
|
44868
46453
|
]);
|
|
44869
46454
|
if (timer) clearTimeout(timer);
|
|
@@ -44956,6 +46541,10 @@ var BRIDGE_ERRORS = {
|
|
|
44956
46541
|
// Weather/currency Task 17 (OQ-P1): a send frame's credit is not the closed value, or rides an a2a frame. TERMINAL:
|
|
44957
46542
|
// the inbound is retired, so the model (and the weather call) never runs again for it.
|
|
44958
46543
|
reply_credit_unknown: { http: 400, message: "reply credit not recognised; not sent" },
|
|
46544
|
+
// Security fix 2026-10-30: an upload route was given a path that is not a
|
|
46545
|
+
// regular file directly inside the bridge's vault outbox (`vault_outbox` on
|
|
46546
|
+
// /healthz). Fixed and content-free: the path is never echoed or logged.
|
|
46547
|
+
path_outside_outbox: { http: 400, message: "uploads must come from the bridge vault outbox; copy the file there first" },
|
|
44959
46548
|
internal: { http: 500, message: "internal error" }
|
|
44960
46549
|
};
|
|
44961
46550
|
function errorBody(code, detail) {
|
|
@@ -44970,63 +46559,6 @@ function wsErrorFrame(code, detail) {
|
|
|
44970
46559
|
return { type: "error", ...errorBody(code, detail) };
|
|
44971
46560
|
}
|
|
44972
46561
|
|
|
44973
|
-
// src/consumer-grants.ts
|
|
44974
|
-
var NO_GRANTS = Object.freeze([]);
|
|
44975
|
-
var PER_TOOL_GRANTS_FEATURE = "per_tool_grants";
|
|
44976
|
-
function withConsumerGrants(m, perToolConsumer) {
|
|
44977
|
-
if (perToolConsumer) return m;
|
|
44978
|
-
const a = m.authorization;
|
|
44979
|
-
if (!a) return m;
|
|
44980
|
-
const stripGrants = !(a.tool_grants.length === 0 || a.audience === "owner_dm" || a.relay_speaker === "owner");
|
|
44981
|
-
const stripMemory = "memory_write" in a;
|
|
44982
|
-
if (!stripGrants && !stripMemory) return m;
|
|
44983
|
-
const { memory_write: _dropped, ...rest } = a;
|
|
44984
|
-
return { ...m, authorization: Object.freeze({ ...rest, ...stripGrants ? { tool_grants: NO_GRANTS } : {} }) };
|
|
44985
|
-
}
|
|
44986
|
-
var MANIFEST_GRANTS_FEATURE = "manifest_grants_v1";
|
|
44987
|
-
var SEARCH_PROVIDERS_FEATURE = "search_providers_v2";
|
|
44988
|
-
var UTILITY_TOOLS_FEATURE = "utility_tools_v1";
|
|
44989
|
-
var CONSTANT_TOOL_NAMES = Object.freeze({
|
|
44990
|
-
owner_dm: Object.freeze(["current_time", "web_search"]),
|
|
44991
|
-
team_room: Object.freeze(["current_time"])
|
|
44992
|
-
});
|
|
44993
|
-
var isManifestGrant = (g) => g.mode === "read" && g.source === "manifest";
|
|
44994
|
-
function withManifestCapability(m, manifestConsumer) {
|
|
44995
|
-
if (manifestConsumer) return m;
|
|
44996
|
-
const a = m.authorization;
|
|
44997
|
-
if (!a || !a.tool_grants.some(isManifestGrant)) return m;
|
|
44998
|
-
const constants4 = new Set(a.audience === "owner_dm" || a.audience === "team_room" ? CONSTANT_TOOL_NAMES[a.audience] : []);
|
|
44999
|
-
const narrowed = a.tool_grants.flatMap((g) => {
|
|
45000
|
-
if (!isManifestGrant(g)) return [g];
|
|
45001
|
-
return constants4.has(g.tool) ? [Object.freeze({ tool: g.tool, mode: "read" })] : [];
|
|
45002
|
-
});
|
|
45003
|
-
return { ...m, authorization: Object.freeze({ ...a, tool_grants: Object.freeze(narrowed) }) };
|
|
45004
|
-
}
|
|
45005
|
-
function withHarnessGrants(m, withheld) {
|
|
45006
|
-
if (!withheld) return m;
|
|
45007
|
-
const a = m.authorization;
|
|
45008
|
-
if (!a || a.tool_grants.length === 0) return m;
|
|
45009
|
-
return { ...m, authorization: Object.freeze({ ...a, tool_grants: NO_GRANTS }) };
|
|
45010
|
-
}
|
|
45011
|
-
function withSearchCapability(m, consumerHasFeature, signedProvider) {
|
|
45012
|
-
if (consumerHasFeature) return m;
|
|
45013
|
-
const a = m.authorization;
|
|
45014
|
-
if (!a) return m;
|
|
45015
|
-
const stripSearch = signedProvider !== void 0 && signedProvider !== "brave";
|
|
45016
|
-
const kept = a.tool_grants.filter((g) => g.tool !== "web_extract" && !(stripSearch && g.tool === "web_search"));
|
|
45017
|
-
if (kept.length === a.tool_grants.length) return m;
|
|
45018
|
-
return { ...m, authorization: Object.freeze({ ...a, tool_grants: kept.length === 0 ? NO_GRANTS : Object.freeze(kept) }) };
|
|
45019
|
-
}
|
|
45020
|
-
var UTILITY_TOOL_NAMES = /* @__PURE__ */ new Set(["weather_forecast", "currency_convert"]);
|
|
45021
|
-
function withUtilityCapability(m, consumerHasFeature) {
|
|
45022
|
-
if (consumerHasFeature) return m;
|
|
45023
|
-
const a = m.authorization;
|
|
45024
|
-
if (!a) return m;
|
|
45025
|
-
const kept = a.tool_grants.filter((g) => !UTILITY_TOOL_NAMES.has(g.tool));
|
|
45026
|
-
if (kept.length === a.tool_grants.length) return m;
|
|
45027
|
-
return { ...m, authorization: Object.freeze({ ...a, tool_grants: kept.length === 0 ? NO_GRANTS : Object.freeze(kept) }) };
|
|
45028
|
-
}
|
|
45029
|
-
|
|
45030
46562
|
// src/http-server.ts
|
|
45031
46563
|
function writeSse(res, msgs, onWriteError) {
|
|
45032
46564
|
for (let i = 0; i < msgs.length; i++) {
|
|
@@ -45049,7 +46581,8 @@ data: ${JSON.stringify(msgs[i])}
|
|
|
45049
46581
|
function deliverSseBatch(res, batch, agent, onWriteError) {
|
|
45050
46582
|
const withheld = agent.harnessGrantsWithheld();
|
|
45051
46583
|
const provider = agent.signedSearchProvider();
|
|
45052
|
-
const
|
|
46584
|
+
const hold = agent.r3Hold();
|
|
46585
|
+
const left3 = writeSse(res, batch.map((m) => withR3Holds(withMcpCapability(withUtilityCapability(withSearchCapability(withManifestCapability(withHarnessGrants(withConsumerGrants(m, false), withheld), false), false, provider), false), false), hold)), onWriteError);
|
|
45053
46586
|
const leftIds = new Set(left3.map((message) => message.message_id));
|
|
45054
46587
|
agent.ackRuntimeMessages(batch.filter((message) => !leftIds.has(message.message_id)).map((message) => message.message_id));
|
|
45055
46588
|
if (left3.length) agent.requeueFront(batch.filter((message) => leftIds.has(message.message_id)));
|
|
@@ -45156,7 +46689,7 @@ function jsonError(res, code, detail) {
|
|
|
45156
46689
|
json(res, httpStatus(code), errorBody(code, detail));
|
|
45157
46690
|
}
|
|
45158
46691
|
function readBody(req, cap) {
|
|
45159
|
-
return new Promise((
|
|
46692
|
+
return new Promise((resolve5, reject) => {
|
|
45160
46693
|
let size = 0;
|
|
45161
46694
|
let done = false;
|
|
45162
46695
|
const chunks = [];
|
|
@@ -45176,7 +46709,7 @@ function readBody(req, cap) {
|
|
|
45176
46709
|
req.on("end", () => {
|
|
45177
46710
|
if (!done) {
|
|
45178
46711
|
done = true;
|
|
45179
|
-
|
|
46712
|
+
resolve5(Buffer.concat(chunks).toString("utf8"));
|
|
45180
46713
|
}
|
|
45181
46714
|
});
|
|
45182
46715
|
req.on("error", (e) => {
|
|
@@ -45254,6 +46787,9 @@ function createHttpServer(agent, opts) {
|
|
|
45254
46787
|
}
|
|
45255
46788
|
return json(res, 200, r);
|
|
45256
46789
|
}
|
|
46790
|
+
if (method === "GET" && path === "/v1/vault-outbox") {
|
|
46791
|
+
return json(res, 200, { path: agent.vaultOutboxPath() });
|
|
46792
|
+
}
|
|
45257
46793
|
if (method === "POST" && path === "/v1/send-attachment") {
|
|
45258
46794
|
const parsed = sendAttachmentSchema.safeParse(JSON.parse(await readBody(req, OTHER_CAP)));
|
|
45259
46795
|
if (!parsed.success) return jsonError(res, "invalid_request", parsed.error.issues);
|
|
@@ -45323,7 +46859,8 @@ function createHttpServer(agent, opts) {
|
|
|
45323
46859
|
const drained = await agent.drain(channel, wait);
|
|
45324
46860
|
const withheld = agent.harnessGrantsWithheld();
|
|
45325
46861
|
const provider = agent.signedSearchProvider();
|
|
45326
|
-
|
|
46862
|
+
const hold = agent.r3Hold();
|
|
46863
|
+
return json(res, 200, drained.map((m) => withR3Holds(withMcpCapability(withUtilityCapability(withSearchCapability(withManifestCapability(withHarnessGrants(withConsumerGrants(m, false), withheld), false), false, provider), false), false), hold)));
|
|
45327
46864
|
}
|
|
45328
46865
|
if (method === "GET" && path === "/v1/events") {
|
|
45329
46866
|
const channel = url.searchParams.get("channel_id") ?? void 0;
|
|
@@ -45365,6 +46902,10 @@ function createHttpServer(agent, opts) {
|
|
|
45365
46902
|
if (e instanceof SendForbiddenError) return jsonError(res, "send_forbidden");
|
|
45366
46903
|
if (e instanceof GroupSupersededSendError) return jsonError(res, "group_superseded");
|
|
45367
46904
|
if (e instanceof IdempotencyConflictError) return jsonError(res, "invalid_request", e.message);
|
|
46905
|
+
if (e instanceof PathOutsideOutboxError) {
|
|
46906
|
+
writeServiceLog("warn", "vault_outbox.refused");
|
|
46907
|
+
return jsonError(res, "path_outside_outbox");
|
|
46908
|
+
}
|
|
45368
46909
|
if (e instanceof InvalidPathError) return jsonError(res, "invalid_request", e.message);
|
|
45369
46910
|
if (e instanceof VaultPinMismatchError) return jsonError(res, "vault_ref_changed", { pin: e.pin });
|
|
45370
46911
|
if (e instanceof VaultIntegrityError) return jsonError(res, "vault_integrity_failed", e.message);
|
|
@@ -45389,6 +46930,7 @@ function parseHarnessReport(raw) {
|
|
|
45389
46930
|
}
|
|
45390
46931
|
|
|
45391
46932
|
// src/ws-server.ts
|
|
46933
|
+
import { randomBytes as randomBytes12 } from "node:crypto";
|
|
45392
46934
|
import { WebSocketServer, WebSocket } from "ws";
|
|
45393
46935
|
|
|
45394
46936
|
// src/memory/consumer-notes.ts
|
|
@@ -45499,6 +47041,8 @@ function attachAgentWebSocket(server, agent, opts) {
|
|
|
45499
47041
|
manifestGrants: false,
|
|
45500
47042
|
searchProviders: false,
|
|
45501
47043
|
utilityTools: false,
|
|
47044
|
+
mcpClient: false,
|
|
47045
|
+
pendingCatalog: /* @__PURE__ */ new Map(),
|
|
45502
47046
|
// helloWaitMs 0 keeps today's synchronous initial flush exactly (the opt-out the existing tests use).
|
|
45503
47047
|
deliveryOpen: helloWaitMs <= 0,
|
|
45504
47048
|
deliveryToken: /* @__PURE__ */ new Map(),
|
|
@@ -45552,14 +47096,14 @@ function attachAgentWebSocket(server, agent, opts) {
|
|
|
45552
47096
|
continue;
|
|
45553
47097
|
}
|
|
45554
47098
|
const out = withConsumerNotes(
|
|
45555
|
-
withUtilityCapability(withSearchCapability(
|
|
47099
|
+
withR3Holds(withMcpCapability(withUtilityCapability(withSearchCapability(
|
|
45556
47100
|
withManifestCapability(
|
|
45557
47101
|
withHarnessGrants(withConsumerGrants(batch[i], session.perToolGrants), !session.perToolGrants && agent.harnessGrantsWithheld()),
|
|
45558
47102
|
session.manifestGrants
|
|
45559
47103
|
),
|
|
45560
47104
|
session.searchProviders,
|
|
45561
47105
|
agent.signedSearchProvider()
|
|
45562
|
-
), session.utilityTools),
|
|
47106
|
+
), session.utilityTools), session.mcpClient), agent.r3Hold()),
|
|
45563
47107
|
batch[i],
|
|
45564
47108
|
session,
|
|
45565
47109
|
agent
|
|
@@ -45617,6 +47161,10 @@ function attachAgentWebSocket(server, agent, opts) {
|
|
|
45617
47161
|
if (session.manifestGrants) agent.clearManifestConsumer(session);
|
|
45618
47162
|
agent.clearSearchProvidersSocket(session);
|
|
45619
47163
|
agent.clearUtilityToolsSocket(session);
|
|
47164
|
+
agent.clearMcpClientSocket(session);
|
|
47165
|
+
agent.noteMcpCatalogLister(session, null);
|
|
47166
|
+
for (const settle of session.pendingCatalog.values()) settle(null);
|
|
47167
|
+
session.pendingCatalog.clear();
|
|
45620
47168
|
};
|
|
45621
47169
|
current = { sock, cleanup, sweepStale };
|
|
45622
47170
|
sock.on("close", cleanup);
|
|
@@ -45650,6 +47198,32 @@ function attachAgentWebSocket(server, agent, opts) {
|
|
|
45650
47198
|
}
|
|
45651
47199
|
};
|
|
45652
47200
|
}
|
|
47201
|
+
var MCP_CATALOG_PENDING_MAX = 8;
|
|
47202
|
+
function mcpCatalogLister(sock, session) {
|
|
47203
|
+
return (serverId, timeoutMs) => new Promise((resolve5) => {
|
|
47204
|
+
if (session.closed || sock.readyState !== WebSocket.OPEN || session.pendingCatalog.size >= MCP_CATALOG_PENDING_MAX) {
|
|
47205
|
+
resolve5(null);
|
|
47206
|
+
return;
|
|
47207
|
+
}
|
|
47208
|
+
const requestId = randomBytes12(16).toString("hex");
|
|
47209
|
+
const timer = setTimeout(() => {
|
|
47210
|
+
session.pendingCatalog.delete(requestId);
|
|
47211
|
+
resolve5(null);
|
|
47212
|
+
}, timeoutMs);
|
|
47213
|
+
timer.unref?.();
|
|
47214
|
+
session.pendingCatalog.set(requestId, (frame) => {
|
|
47215
|
+
clearTimeout(timer);
|
|
47216
|
+
resolve5(frame);
|
|
47217
|
+
});
|
|
47218
|
+
try {
|
|
47219
|
+
sock.send(JSON.stringify({ type: "mcp_catalog_list", request_id: requestId, server_id: serverId }));
|
|
47220
|
+
} catch {
|
|
47221
|
+
session.pendingCatalog.delete(requestId);
|
|
47222
|
+
clearTimeout(timer);
|
|
47223
|
+
resolve5(null);
|
|
47224
|
+
}
|
|
47225
|
+
});
|
|
47226
|
+
}
|
|
45653
47227
|
function sendJson(sock, body) {
|
|
45654
47228
|
if (sock.readyState !== WebSocket.OPEN) return;
|
|
45655
47229
|
try {
|
|
@@ -45723,6 +47297,9 @@ async function handleInbound(sock, agent, data, inflight, flush, session) {
|
|
|
45723
47297
|
agent.noteSearchProvidersSocket(session, session.searchProviders);
|
|
45724
47298
|
session.utilityTools = session.consumer === "runtime" && session.harnessClaimed !== true && perTool && Array.isArray(m.features) && m.features.includes(UTILITY_TOOLS_FEATURE);
|
|
45725
47299
|
agent.noteUtilityToolsSocket(session, session.utilityTools);
|
|
47300
|
+
session.mcpClient = session.consumer === "runtime" && session.harnessClaimed !== true && perTool && Array.isArray(m.features) && m.features.includes(MANIFEST_GRANTS_FEATURE) && m.features.includes(MCP_CLIENT_FEATURE);
|
|
47301
|
+
agent.noteMcpClientSocket(session, session.mcpClient);
|
|
47302
|
+
agent.noteMcpCatalogLister(session, session.mcpClient ? mcpCatalogLister(sock, session) : null);
|
|
45726
47303
|
if (session.consumer === "runtime" && session.harnessClaimed !== true && Array.isArray(m.features) && m.features.includes(MEMORY_NOTES_FEATURE)) {
|
|
45727
47304
|
agent.noteNotesConsumer();
|
|
45728
47305
|
}
|
|
@@ -45730,13 +47307,21 @@ async function handleInbound(sock, agent, data, inflight, flush, session) {
|
|
|
45730
47307
|
if (session.recallConsumer) agent.noteRecallConsumer();
|
|
45731
47308
|
session.unifiedConsumer = agent.unifiedFeatureOn() && session.consumer === "runtime" && session.harnessClaimed !== true && Array.isArray(m.features) && m.features.includes(MEMORY_UNIFIED_FEATURE);
|
|
45732
47309
|
if (session.unifiedConsumer) agent.noteUnifiedConsumer();
|
|
45733
|
-
sendJson(sock, { type: "hello", features: ["nack", MEMORY_NOTES_FEATURE, MEMORY_RECALL_FEATURE, ...agent.unifiedFeatureOn() ? [MEMORY_UNIFIED_FEATURE] : [], MANIFEST_GRANTS_FEATURE, SEARCH_PROVIDERS_FEATURE, REPLY_CREDIT_FEATURE] });
|
|
47310
|
+
sendJson(sock, { type: "hello", features: ["nack", MEMORY_NOTES_FEATURE, MEMORY_RECALL_FEATURE, ...agent.unifiedFeatureOn() ? [MEMORY_UNIFIED_FEATURE] : [], MANIFEST_GRANTS_FEATURE, SEARCH_PROVIDERS_FEATURE, REPLY_CREDIT_FEATURE, MCP_POLICY_WRITER_FEATURE] });
|
|
45734
47311
|
if (!session.deliveryOpen) {
|
|
45735
47312
|
session.deliveryOpen = true;
|
|
45736
47313
|
flush();
|
|
45737
47314
|
}
|
|
45738
47315
|
return;
|
|
45739
47316
|
}
|
|
47317
|
+
if (m.type === "mcp_catalog_list_result") {
|
|
47318
|
+
const id = m.request_id;
|
|
47319
|
+
const settle = typeof id === "string" ? session.pendingCatalog.get(id) : void 0;
|
|
47320
|
+
if (settle === void 0) return;
|
|
47321
|
+
session.pendingCatalog.delete(id);
|
|
47322
|
+
settle(m);
|
|
47323
|
+
return;
|
|
47324
|
+
}
|
|
45740
47325
|
if (m.type === "nack") {
|
|
45741
47326
|
if (typeof m.message_id !== "string" || !m.message_id) {
|
|
45742
47327
|
sendJson(sock, wsErrorFrame("invalid_request"));
|
|
@@ -45966,18 +47551,18 @@ async function handleInbound(sock, agent, data, inflight, flush, session) {
|
|
|
45966
47551
|
var LOWER_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
45967
47552
|
|
|
45968
47553
|
// src/bin.ts
|
|
45969
|
-
import { readFileSync as readFileSync17, mkdirSync as
|
|
45970
|
-
import { join as
|
|
47554
|
+
import { readFileSync as readFileSync17, mkdirSync as mkdirSync9, writeFileSync as writeFileSync5, rmSync as rmSync8, existsSync as existsSync7, realpathSync as realpathSync4, readdirSync as readdirSync4, statSync as statSync8, chmodSync as chmodSync7 } from "node:fs";
|
|
47555
|
+
import { join as join26, dirname as dirname14, isAbsolute as isAbsolute3, resolve as resolve4 } from "node:path";
|
|
45971
47556
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
45972
47557
|
import { homedir as homedir5, userInfo } from "node:os";
|
|
45973
47558
|
import { spawnSync } from "node:child_process";
|
|
45974
47559
|
import { createServer as netCreateServer } from "node:net";
|
|
45975
47560
|
|
|
45976
47561
|
// src/service/install.ts
|
|
45977
|
-
import { dirname as
|
|
47562
|
+
import { dirname as dirname9 } from "node:path";
|
|
45978
47563
|
|
|
45979
47564
|
// src/service/config.ts
|
|
45980
|
-
import { basename as
|
|
47565
|
+
import { basename as basename6, extname, isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
|
|
45981
47566
|
|
|
45982
47567
|
// src/service/resolve-node.ts
|
|
45983
47568
|
var REQUIRED_NODE_MAJOR = 24;
|
|
@@ -46055,11 +47640,11 @@ function deriveServiceConfig(input) {
|
|
|
46055
47640
|
`The Node at ${input.execPath} moves when it is upgraded, so the background service can't use it. Install Node ${REQUIRED_NODE_MAJOR} from https://nodejs.org, then run the install command again.`
|
|
46056
47641
|
);
|
|
46057
47642
|
}
|
|
46058
|
-
const stem = input.label ??
|
|
47643
|
+
const stem = input.label ?? basename6(input.credentialPath, extname(input.credentialPath));
|
|
46059
47644
|
const slug = slugify(stem);
|
|
46060
|
-
const cred = isAbsolute2(input.credentialPath) ? input.credentialPath :
|
|
47645
|
+
const cred = isAbsolute2(input.credentialPath) ? input.credentialPath : resolve2(input.credentialPath);
|
|
46061
47646
|
const args2 = ["--credential", cred];
|
|
46062
|
-
if (input.storePath) args2.push("--store", isAbsolute2(input.storePath) ? input.storePath :
|
|
47647
|
+
if (input.storePath) args2.push("--store", isAbsolute2(input.storePath) ? input.storePath : resolve2(input.storePath));
|
|
46063
47648
|
if (input.port != null) args2.push("--port", String(input.port));
|
|
46064
47649
|
return {
|
|
46065
47650
|
slug,
|
|
@@ -46154,7 +47739,7 @@ function waitForPreviousExit(deps, pid) {
|
|
|
46154
47739
|
}
|
|
46155
47740
|
}
|
|
46156
47741
|
function tokenHint(input) {
|
|
46157
|
-
return `${
|
|
47742
|
+
return `${dirname9(input.credentialPath)}/bridge-token`;
|
|
46158
47743
|
}
|
|
46159
47744
|
function requireOk(r, what) {
|
|
46160
47745
|
if (r.status !== 0) throw new Error(`${what} failed (exit ${r.status})${r.stderr ? `: ${r.stderr.trim()}` : ""}`);
|
|
@@ -46162,8 +47747,8 @@ function requireOk(r, what) {
|
|
|
46162
47747
|
function installService(input, deps) {
|
|
46163
47748
|
const cfg = deriveServiceConfig(input);
|
|
46164
47749
|
if (deps.platform === "darwin") {
|
|
46165
|
-
deps.mkdirp(
|
|
46166
|
-
deps.mkdirp(
|
|
47750
|
+
deps.mkdirp(dirname9(cfg.plistPath));
|
|
47751
|
+
deps.mkdirp(dirname9(cfg.logPath));
|
|
46167
47752
|
deps.writeFile(cfg.plistPath, renderLaunchdPlist(cfg));
|
|
46168
47753
|
const target = `gui/${cfg.uid}/${cfg.label}`;
|
|
46169
47754
|
const previousPid = launchdPid(deps, target);
|
|
@@ -46175,7 +47760,7 @@ function installService(input, deps) {
|
|
|
46175
47760
|
return { label: cfg.label, filePath: cfg.plistPath, logHint: cfg.logPath, tokenHint: tokenHint(input) };
|
|
46176
47761
|
}
|
|
46177
47762
|
if (deps.platform === "linux") {
|
|
46178
|
-
deps.mkdirp(
|
|
47763
|
+
deps.mkdirp(dirname9(cfg.unitPath));
|
|
46179
47764
|
deps.writeFile(cfg.unitPath, renderSystemdUnit(cfg));
|
|
46180
47765
|
deps.run("systemctl", ["--user", "daemon-reload"]);
|
|
46181
47766
|
deps.run("loginctl", ["enable-linger", deps.user]);
|
|
@@ -46204,10 +47789,10 @@ function uninstallService(input, deps) {
|
|
|
46204
47789
|
}
|
|
46205
47790
|
|
|
46206
47791
|
// src/service/install-prep.ts
|
|
46207
|
-
import { join as
|
|
47792
|
+
import { join as join22 } from "node:path";
|
|
46208
47793
|
|
|
46209
47794
|
// src/service/wire.ts
|
|
46210
|
-
import { join as
|
|
47795
|
+
import { join as join21, dirname as dirname10 } from "node:path";
|
|
46211
47796
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
46212
47797
|
|
|
46213
47798
|
// src/service/hermes-config.ts
|
|
@@ -46382,8 +47967,8 @@ function checkBindingConflict(prev, next, allowRebind) {
|
|
|
46382
47967
|
};
|
|
46383
47968
|
}
|
|
46384
47969
|
function connectorsSrcDir(importMetaUrl, harness) {
|
|
46385
|
-
const pkgRoot =
|
|
46386
|
-
return
|
|
47970
|
+
const pkgRoot = dirname10(dirname10(fileURLToPath3(importMetaUrl)));
|
|
47971
|
+
return join21(pkgRoot, "connectors", harness);
|
|
46387
47972
|
}
|
|
46388
47973
|
function hermesConfigPath(home) {
|
|
46389
47974
|
return `${home}/.hermes/config.yaml`;
|
|
@@ -46645,10 +48230,10 @@ function assertReadableCredential(absPath, deps) {
|
|
|
46645
48230
|
return { deviceId: json2.device_id, identityId: json2.identity_id, issuedAt };
|
|
46646
48231
|
}
|
|
46647
48232
|
function durableDir(home, slug) {
|
|
46648
|
-
return
|
|
48233
|
+
return join22(home, `securecoms-agent-${slug}`);
|
|
46649
48234
|
}
|
|
46650
48235
|
function durableCredentialPath(home, slug) {
|
|
46651
|
-
return
|
|
48236
|
+
return join22(durableDir(home, slug), `${slug}.json`);
|
|
46652
48237
|
}
|
|
46653
48238
|
var DURABLE_PREFIX = "securecoms-agent-";
|
|
46654
48239
|
var InstallRefusedError = class extends Error {
|
|
@@ -46669,8 +48254,8 @@ function refuseExistingIdentity(args2, deps) {
|
|
|
46669
48254
|
const others = names.filter((n) => n.startsWith(DURABLE_PREFIX) && n !== destName).sort();
|
|
46670
48255
|
for (const name of [destName, ...others]) {
|
|
46671
48256
|
const slug = name.slice(DURABLE_PREFIX.length);
|
|
46672
|
-
const dir =
|
|
46673
|
-
const credPath =
|
|
48257
|
+
const dir = join22(args2.home, name);
|
|
48258
|
+
const credPath = join22(dir, `${slug}.json`);
|
|
46674
48259
|
if (!deps.fileExists(credPath)) continue;
|
|
46675
48260
|
let existing = null;
|
|
46676
48261
|
try {
|
|
@@ -46679,7 +48264,7 @@ function refuseExistingIdentity(args2, deps) {
|
|
|
46679
48264
|
} catch {
|
|
46680
48265
|
}
|
|
46681
48266
|
if (!existing || typeof existing.identity_id !== "string" || !existing.identity_id) {
|
|
46682
|
-
if (deps.fileExists(
|
|
48267
|
+
if (deps.fileExists(join22(dir, "bridge-state.db"))) throw existingIdentityError(dir, slug);
|
|
46683
48268
|
continue;
|
|
46684
48269
|
}
|
|
46685
48270
|
if (existing.identity_id !== args2.parsed.identityId) continue;
|
|
@@ -46734,7 +48319,7 @@ function readSiblingDefs(dir, prefix, deps) {
|
|
|
46734
48319
|
for (const n of names) {
|
|
46735
48320
|
if (!n.startsWith(prefix)) continue;
|
|
46736
48321
|
try {
|
|
46737
|
-
out.push(deps.readFile(
|
|
48322
|
+
out.push(deps.readFile(join22(dir, n)));
|
|
46738
48323
|
} catch {
|
|
46739
48324
|
}
|
|
46740
48325
|
}
|
|
@@ -46815,7 +48400,7 @@ async function orchestrateInstall(ctx) {
|
|
|
46815
48400
|
}
|
|
46816
48401
|
|
|
46817
48402
|
// src/service/fleet.ts
|
|
46818
|
-
import { dirname as
|
|
48403
|
+
import { dirname as dirname11 } from "node:path";
|
|
46819
48404
|
function parseArgFromDef(def, flag) {
|
|
46820
48405
|
const esc = flag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
46821
48406
|
const re = new RegExp(`${esc}(?:<\\/string>\\s*<string>|\\s+)([^<\\s]+)`);
|
|
@@ -46859,7 +48444,7 @@ function readFleet(deps) {
|
|
|
46859
48444
|
let identityId = `unknown:${slug}`, deviceId = "", label = slug, env = "unknown";
|
|
46860
48445
|
let credDir = null;
|
|
46861
48446
|
if (credPath) {
|
|
46862
|
-
credDir =
|
|
48447
|
+
credDir = dirname11(credPath);
|
|
46863
48448
|
try {
|
|
46864
48449
|
const c = JSON.parse(deps.readFile(credPath));
|
|
46865
48450
|
if (c.identity_id) identityId = c.identity_id;
|
|
@@ -46927,31 +48512,31 @@ function renderStatusTable(rows) {
|
|
|
46927
48512
|
|
|
46928
48513
|
// src/service/connection-file.ts
|
|
46929
48514
|
import {
|
|
46930
|
-
chmodSync as
|
|
46931
|
-
mkdirSync as
|
|
48515
|
+
chmodSync as chmodSync6,
|
|
48516
|
+
mkdirSync as mkdirSync7,
|
|
46932
48517
|
readFileSync as readFileSync13,
|
|
46933
48518
|
renameSync as renameSync4,
|
|
46934
48519
|
unlinkSync as unlinkSync2,
|
|
46935
48520
|
writeFileSync as writeFileSync3
|
|
46936
48521
|
} from "node:fs";
|
|
46937
48522
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
46938
|
-
import { join as
|
|
48523
|
+
import { join as join23 } from "node:path";
|
|
46939
48524
|
var REAL_FS2 = {
|
|
46940
|
-
mkdir: (p, mode) =>
|
|
46941
|
-
chmod: (p, mode) =>
|
|
48525
|
+
mkdir: (p, mode) => mkdirSync7(p, { recursive: true, mode }),
|
|
48526
|
+
chmod: (p, mode) => chmodSync6(p, mode),
|
|
46942
48527
|
writeFile: (p, data, mode) => writeFileSync3(p, data, { mode }),
|
|
46943
48528
|
rename: (a, b) => renameSync4(a, b),
|
|
46944
48529
|
readFile: (p) => readFileSync13(p, "utf8"),
|
|
46945
48530
|
unlink: (p) => unlinkSync2(p)
|
|
46946
48531
|
};
|
|
46947
48532
|
function securecomsDir(home) {
|
|
46948
|
-
return
|
|
48533
|
+
return join23(home, ".securecoms");
|
|
46949
48534
|
}
|
|
46950
48535
|
function connectionsDir(home) {
|
|
46951
|
-
return
|
|
48536
|
+
return join23(securecomsDir(home), "connections");
|
|
46952
48537
|
}
|
|
46953
48538
|
function connectionFilePath(home, identityId) {
|
|
46954
|
-
return
|
|
48539
|
+
return join23(connectionsDir(home), `${identityId}.json`);
|
|
46955
48540
|
}
|
|
46956
48541
|
function newInstanceId() {
|
|
46957
48542
|
return randomUUID2();
|
|
@@ -46990,9 +48575,9 @@ function removeConnectionFileIfOwned(home, identityId, instanceId, deps = REAL_F
|
|
|
46990
48575
|
|
|
46991
48576
|
// src/service/instance-lock.ts
|
|
46992
48577
|
import { execFileSync } from "node:child_process";
|
|
46993
|
-
import { closeSync as closeSync4, fsyncSync, mkdirSync as
|
|
48578
|
+
import { closeSync as closeSync4, fsyncSync, mkdirSync as mkdirSync8, openSync as openSync4, readFileSync as readFileSync14, statSync as statSync6, unlinkSync as unlinkSync3, utimesSync, writeSync as writeSync2 } from "node:fs";
|
|
46994
48579
|
import { hostname, uptime as uptime2 } from "node:os";
|
|
46995
|
-
import { dirname as
|
|
48580
|
+
import { dirname as dirname12 } from "node:path";
|
|
46996
48581
|
var LOCK_PARTIAL_WRITE_GRACE_MS = 1e4;
|
|
46997
48582
|
var LOCK_TAKEOVER_GUARD_STALE_MS = 3e4;
|
|
46998
48583
|
var BOOT_TOLERANCE_SEC = 120;
|
|
@@ -47221,7 +48806,7 @@ var InstanceLock = class {
|
|
|
47221
48806
|
create() {
|
|
47222
48807
|
let fd;
|
|
47223
48808
|
try {
|
|
47224
|
-
|
|
48809
|
+
mkdirSync8(dirname12(this.path), { recursive: true, mode: 448 });
|
|
47225
48810
|
fd = openSync4(this.path, "wx", 384);
|
|
47226
48811
|
} catch (error) {
|
|
47227
48812
|
if (errno(error) === "EEXIST") return "exists";
|
|
@@ -47312,15 +48897,15 @@ import {
|
|
|
47312
48897
|
closeSync as closeSync5,
|
|
47313
48898
|
fchmodSync,
|
|
47314
48899
|
fsyncSync as fsyncSync2,
|
|
47315
|
-
lstatSync as
|
|
48900
|
+
lstatSync as lstatSync9,
|
|
47316
48901
|
openSync as openSync5,
|
|
47317
48902
|
readFileSync as readFileSync15,
|
|
47318
|
-
realpathSync as
|
|
48903
|
+
realpathSync as realpathSync3,
|
|
47319
48904
|
renameSync as renameSync5,
|
|
47320
48905
|
unlinkSync as unlinkSync4,
|
|
47321
48906
|
writeSync as writeSync3
|
|
47322
48907
|
} from "node:fs";
|
|
47323
|
-
import { dirname as
|
|
48908
|
+
import { dirname as dirname13, join as join24, resolve as resolve3 } from "node:path";
|
|
47324
48909
|
var PIN_COPY = Object.freeze({
|
|
47325
48910
|
saved: "pin saved. Restart the Connector to use it.",
|
|
47326
48911
|
// [COPY, approved by Eric 2026-10-12]
|
|
@@ -47344,7 +48929,7 @@ var PIN_COPY = Object.freeze({
|
|
|
47344
48929
|
var refuse2 = (reason) => ({ ok: false, reason });
|
|
47345
48930
|
function isSymlink(path) {
|
|
47346
48931
|
try {
|
|
47347
|
-
return
|
|
48932
|
+
return lstatSync9(path).isSymbolicLink();
|
|
47348
48933
|
} catch (e) {
|
|
47349
48934
|
if (e?.code === "ENOENT") return null;
|
|
47350
48935
|
throw e;
|
|
@@ -47368,10 +48953,10 @@ function writeTemp(path, bytes) {
|
|
|
47368
48953
|
}
|
|
47369
48954
|
}
|
|
47370
48955
|
function runPinCommand(opts) {
|
|
47371
|
-
const credDir =
|
|
47372
|
-
const target =
|
|
48956
|
+
const credDir = dirname13(resolve3(opts.credentialPath));
|
|
48957
|
+
const target = join24(credDir, PIN_FILE_NAME);
|
|
47373
48958
|
try {
|
|
47374
|
-
if (
|
|
48959
|
+
if (realpathSync3(dirname13(resolve3(opts.pinSourcePath))) !== realpathSync3(credDir)) return refuse2("directory_mismatch");
|
|
47375
48960
|
} catch {
|
|
47376
48961
|
return refuse2("io_error");
|
|
47377
48962
|
}
|
|
@@ -47413,7 +48998,7 @@ function runPinCommand(opts) {
|
|
|
47413
48998
|
const pinBytes = JSON.stringify(pinObject, null, 2) + "\n";
|
|
47414
48999
|
const credBytes = JSON.stringify({ ...cred, owner_signing_key: pinObject }, null, 2) + "\n";
|
|
47415
49000
|
const pinTmp = `${target}.tmp-${process.pid}`;
|
|
47416
|
-
const credTmp = `${
|
|
49001
|
+
const credTmp = `${resolve3(opts.credentialPath)}.tmp-${process.pid}`;
|
|
47417
49002
|
const created = [];
|
|
47418
49003
|
const fail = () => {
|
|
47419
49004
|
for (const p of created) {
|
|
@@ -47447,7 +49032,7 @@ function runPinCommand(opts) {
|
|
|
47447
49032
|
created.splice(created.indexOf(pinTmp), 1);
|
|
47448
49033
|
step("pin_renamed");
|
|
47449
49034
|
try {
|
|
47450
|
-
renameSync5(credTmp,
|
|
49035
|
+
renameSync5(credTmp, resolve3(opts.credentialPath));
|
|
47451
49036
|
} catch {
|
|
47452
49037
|
return fail();
|
|
47453
49038
|
}
|
|
@@ -47528,7 +49113,7 @@ function parseVerb(argv) {
|
|
|
47528
49113
|
return v === "install" || v === "uninstall" || v === "status" || v === "wire" || v === "unwire" || v === "models" || v === "pin" ? v : null;
|
|
47529
49114
|
}
|
|
47530
49115
|
function slugFromCredentialPath(credentialPath) {
|
|
47531
|
-
const dir =
|
|
49116
|
+
const dir = dirname14(credentialPath);
|
|
47532
49117
|
const base = dir.split(/[\\/]/).pop() ?? "";
|
|
47533
49118
|
const m = base.match(/^securecoms-agent-(.+)$/);
|
|
47534
49119
|
if (m) return m[1];
|
|
@@ -47544,7 +49129,7 @@ function realServiceDeps() {
|
|
|
47544
49129
|
},
|
|
47545
49130
|
writeFile: (p, data) => writeFileSync5(p, data, { mode: 420 }),
|
|
47546
49131
|
removeFile: (p) => rmSync8(p, { force: true }),
|
|
47547
|
-
mkdirp: (p) =>
|
|
49132
|
+
mkdirp: (p) => mkdirSync9(p, { recursive: true }),
|
|
47548
49133
|
fileExists: (p) => existsSync7(p),
|
|
47549
49134
|
user: userInfo().username,
|
|
47550
49135
|
pidAlive: pidAliveByKill,
|
|
@@ -47574,7 +49159,7 @@ function realResolveNodeDeps() {
|
|
|
47574
49159
|
return m ? Number(m[1]) : null;
|
|
47575
49160
|
};
|
|
47576
49161
|
return {
|
|
47577
|
-
realpath: (p) =>
|
|
49162
|
+
realpath: (p) => realpathSync4(p),
|
|
47578
49163
|
fileExists: (p) => existsSync7(p),
|
|
47579
49164
|
nodeMajorOf: majorOf,
|
|
47580
49165
|
arch: () => process.arch,
|
|
@@ -47598,10 +49183,10 @@ function realPrepDeps() {
|
|
|
47598
49183
|
readFile: (p) => readFileSync17(p, "utf8"),
|
|
47599
49184
|
writeFileMode: (p, data, mode) => writeFileSync5(p, data, { mode }),
|
|
47600
49185
|
mkdirp: (p, mode) => {
|
|
47601
|
-
|
|
49186
|
+
mkdirSync9(p, { recursive: true, mode: mode ?? 493 });
|
|
47602
49187
|
},
|
|
47603
49188
|
fileExists: (p) => existsSync7(p),
|
|
47604
|
-
realpath: (p) =>
|
|
49189
|
+
realpath: (p) => realpathSync4(p),
|
|
47605
49190
|
listDir: (p) => {
|
|
47606
49191
|
try {
|
|
47607
49192
|
return readdirSync4(p);
|
|
@@ -47663,33 +49248,33 @@ function runStatus() {
|
|
|
47663
49248
|
}
|
|
47664
49249
|
function realCopyConnector(srcDir, dstDir, harness) {
|
|
47665
49250
|
const allowedExt = CONNECTOR_FILE_EXTENSIONS[harness];
|
|
47666
|
-
|
|
49251
|
+
mkdirSync9(dstDir, { recursive: true });
|
|
47667
49252
|
for (const name of readdirSync4(srcDir)) {
|
|
47668
|
-
const srcPath =
|
|
49253
|
+
const srcPath = join26(srcDir, name);
|
|
47669
49254
|
if (statSync8(srcPath).isDirectory()) {
|
|
47670
49255
|
if (CONNECTOR_SKIP_DIRS.has(name)) continue;
|
|
47671
|
-
realCopyConnector(srcPath,
|
|
49256
|
+
realCopyConnector(srcPath, join26(dstDir, name), harness);
|
|
47672
49257
|
continue;
|
|
47673
49258
|
}
|
|
47674
49259
|
if (name.includes(".test.")) continue;
|
|
47675
49260
|
if (!allowedExt.some((ext) => name.endsWith(ext))) continue;
|
|
47676
|
-
writeFileSync5(
|
|
49261
|
+
writeFileSync5(join26(dstDir, name), readFileSync17(srcPath));
|
|
47677
49262
|
}
|
|
47678
49263
|
}
|
|
47679
49264
|
function realWriteBinding(dstDir, identityId) {
|
|
47680
|
-
|
|
47681
|
-
writeFileSync5(
|
|
49265
|
+
mkdirSync9(dstDir, { recursive: true });
|
|
49266
|
+
writeFileSync5(join26(dstDir, "securecoms.binding.json"), JSON.stringify({ identity_id: identityId }) + "\n", { mode: 384 });
|
|
47682
49267
|
}
|
|
47683
49268
|
function realReadBinding(dstDir) {
|
|
47684
49269
|
try {
|
|
47685
|
-
const parsed = JSON.parse(readFileSync17(
|
|
49270
|
+
const parsed = JSON.parse(readFileSync17(join26(dstDir, "securecoms.binding.json"), "utf8"));
|
|
47686
49271
|
return parsed.identity_id ?? null;
|
|
47687
49272
|
} catch {
|
|
47688
49273
|
return null;
|
|
47689
49274
|
}
|
|
47690
49275
|
}
|
|
47691
49276
|
function realRemoveBinding(dstDir) {
|
|
47692
|
-
rmSync8(
|
|
49277
|
+
rmSync8(join26(dstDir, "securecoms.binding.json"), { force: true });
|
|
47693
49278
|
}
|
|
47694
49279
|
function realStatDir(p) {
|
|
47695
49280
|
try {
|
|
@@ -47765,7 +49350,7 @@ async function runServiceVerb(verb) {
|
|
|
47765
49350
|
}
|
|
47766
49351
|
try {
|
|
47767
49352
|
if (verb === "install") {
|
|
47768
|
-
const credAbs = isAbsolute3(credentialPath) ? credentialPath :
|
|
49353
|
+
const credAbs = isAbsolute3(credentialPath) ? credentialPath : resolve4(credentialPath);
|
|
47769
49354
|
const installExecPath = chooseInstallExecPath(
|
|
47770
49355
|
process.execPath,
|
|
47771
49356
|
argOrEnv("--exec-path", "SECURECOMS_EXEC_PATH"),
|
|
@@ -47976,11 +49561,11 @@ async function runMode(deps) {
|
|
|
47976
49561
|
refuse3("identity_id_invalid", { field: "identity_id" });
|
|
47977
49562
|
return deps.exit(1);
|
|
47978
49563
|
}
|
|
47979
|
-
const lockDir =
|
|
47980
|
-
const lockPath =
|
|
49564
|
+
const lockDir = join26(deps.home, ".securecoms", "locks");
|
|
49565
|
+
const lockPath = join26(lockDir, `${cfg.identityId.toLowerCase()}.lock`);
|
|
47981
49566
|
try {
|
|
47982
|
-
|
|
47983
|
-
|
|
49567
|
+
mkdirSync9(lockDir, { recursive: true, mode: 448 });
|
|
49568
|
+
chmodSync7(lockDir, 448);
|
|
47984
49569
|
} catch (e) {
|
|
47985
49570
|
log("error", "startup.instance_lock_failed", {}, e);
|
|
47986
49571
|
return deps.exit(1);
|
|
@@ -48027,7 +49612,7 @@ async function runMode(deps) {
|
|
|
48027
49612
|
return deps.exit(1);
|
|
48028
49613
|
}
|
|
48029
49614
|
deps.onExit(() => lock.release());
|
|
48030
|
-
const tokenFile = daemon.tokenFile ??
|
|
49615
|
+
const tokenFile = daemon.tokenFile ?? join26(dirname14(credentialPath), "bridge-token");
|
|
48031
49616
|
const token = resolveToken({ tokenFile, token: daemon.token });
|
|
48032
49617
|
const preBound = deps.net.createServer((sock) => sock.destroy());
|
|
48033
49618
|
const bindError = await new Promise((resolveBind) => {
|
|
@@ -48176,7 +49761,7 @@ async function main() {
|
|
|
48176
49761
|
await runMode(realRunModeDeps(credentialPath, storePath));
|
|
48177
49762
|
}
|
|
48178
49763
|
var here = fileURLToPath4(import.meta.url);
|
|
48179
|
-
var isMain = !!process.argv[1] && refersToSameFile(here, process.argv[1],
|
|
49764
|
+
var isMain = !!process.argv[1] && refersToSameFile(here, process.argv[1], realpathSync4);
|
|
48180
49765
|
if (isMain) {
|
|
48181
49766
|
main().catch((e) => {
|
|
48182
49767
|
writeServiceLog("error", "startup.fatal", {}, e);
|