@securecoms/agent-bridge 0.33.1 → 0.35.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 +61 -3
- package/dist/attachments.d.ts +13 -0
- package/dist/bin.js +687 -325
- package/dist/bridge-agent.d.ts +53 -7
- package/dist/consumer-grants.d.ts +9 -0
- package/dist/errors.d.ts +1 -1
- package/dist/grants/binding.d.ts +18 -5
- package/dist/http-server.d.ts +10 -36
- package/dist/index.js +626 -264
- package/dist/outbox.d.ts +42 -0
- package/dist/reply-credit.d.ts +22 -0
- package/dist/reply-intent.d.ts +6 -1
- package/dist/service/runtime-config-dir.d.ts +3 -0
- package/dist/version.d.ts +1 -1
- package/dist/ws-server.d.ts +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -18585,6 +18585,8 @@ function isEpochConflict(e) {
|
|
|
18585
18585
|
var LABEL = "agentvault-connect-v1";
|
|
18586
18586
|
var DEFAULT_STUCK_THRESHOLD = 2;
|
|
18587
18587
|
var DEFAULT_QUARANTINE_THRESHOLD = 8;
|
|
18588
|
+
var KEPT_WELCOME_COMMIT_DEFER_MS = 24 * 60 * 60 * 1e3;
|
|
18589
|
+
var KEPT_WELCOME_COMMIT_DEFER_MAX_ROWS = 32;
|
|
18588
18590
|
var DELIVERY_PULL_PAGE = 100;
|
|
18589
18591
|
var DELIVERY_MAX_RETRIES = 5;
|
|
18590
18592
|
var DrainPageFullError = class extends Error {
|
|
@@ -18789,6 +18791,15 @@ var MlsGroupKeyProvider = class _MlsGroupKeyProvider {
|
|
|
18789
18791
|
* {@link errorStats}; mutated only through {@link recordDecryptError} /
|
|
18790
18792
|
* {@link recordDecryptSuccess}.
|
|
18791
18793
|
*/
|
|
18794
|
+
/** {@link KEPT_WELCOME_COMMIT_DEFER_MS}: channel -> when its first commit was
|
|
18795
|
+
* deferred behind the kept Welcome `welcomeId`. Restarted for a different
|
|
18796
|
+
* Welcome; cleared when the channel decrypts, or when a pass that saw the
|
|
18797
|
+
* whole queue kept no Welcome for it (fix round 1, L2: the Welcome
|
|
18798
|
+
* resolved, so a later re-Welcome gets a fresh, bounded window). An entry
|
|
18799
|
+
* past its bound stays (expired) until then, so the bound cannot restart
|
|
18800
|
+
* itself pass after pass. In memory: a reload restarts it, and the
|
|
18801
|
+
* per-pass row cap is what bounds the page cost across reloads. */
|
|
18802
|
+
keptWelcomeDeferSince = /* @__PURE__ */ new Map();
|
|
18792
18803
|
errors = /* @__PURE__ */ new Map();
|
|
18793
18804
|
stuckThreshold;
|
|
18794
18805
|
/** Consecutive-error threshold at which a wedged channel's row is quarantined
|
|
@@ -19007,8 +19018,33 @@ var MlsGroupKeyProvider = class _MlsGroupKeyProvider {
|
|
|
19007
19018
|
* channel resumes normal processing and can quarantine again later if it
|
|
19008
19019
|
* re-wedges (the onWedged guard is reset so the hook can fire again).
|
|
19009
19020
|
*/
|
|
19021
|
+
/** See {@link MlsGroupKeyProviderOpts.deferCommitsWhileWelcomePending}. The
|
|
19022
|
+
* caller has already established that this pass KEPT a no-state Welcome
|
|
19023
|
+
* for `channelId`. True: defer the commit row. Never throws. */
|
|
19024
|
+
deferKeptWelcomeCommit(channelId, welcomeId) {
|
|
19025
|
+
const pred = this.o.deferCommitsWhileWelcomePending;
|
|
19026
|
+
if (!pred)
|
|
19027
|
+
return false;
|
|
19028
|
+
let boundMs = 0;
|
|
19029
|
+
try {
|
|
19030
|
+
const v = pred(channelId);
|
|
19031
|
+
boundMs = v === true ? KEPT_WELCOME_COMMIT_DEFER_MS : typeof v === "number" && Number.isFinite(v) && v > 0 ? Math.min(v, KEPT_WELCOME_COMMIT_DEFER_MS) : 0;
|
|
19032
|
+
} catch {
|
|
19033
|
+
boundMs = 0;
|
|
19034
|
+
}
|
|
19035
|
+
if (boundMs <= 0)
|
|
19036
|
+
return false;
|
|
19037
|
+
const now = this.nowMs();
|
|
19038
|
+
const entry = this.keptWelcomeDeferSince.get(channelId);
|
|
19039
|
+
if (entry === void 0 || entry.welcomeId !== welcomeId) {
|
|
19040
|
+
this.keptWelcomeDeferSince.set(channelId, { since: now, welcomeId });
|
|
19041
|
+
return true;
|
|
19042
|
+
}
|
|
19043
|
+
return now - entry.since < boundMs;
|
|
19044
|
+
}
|
|
19010
19045
|
recordDecryptSuccess(channelId) {
|
|
19011
19046
|
this.lastDecryptAt.set(channelId, Date.now());
|
|
19047
|
+
this.keptWelcomeDeferSince.delete(channelId);
|
|
19012
19048
|
const e = this.errors.get(channelId);
|
|
19013
19049
|
if (!e)
|
|
19014
19050
|
return;
|
|
@@ -19404,6 +19440,12 @@ var MlsGroupKeyProvider = class _MlsGroupKeyProvider {
|
|
|
19404
19440
|
* server's REJOIN_THRESHOLD, there was nothing to apply, or a commit would
|
|
19405
19441
|
* not apply (which means the state is genuinely FORKED, not merely behind,
|
|
19406
19442
|
* and replay cannot fix a fork).
|
|
19443
|
+
*
|
|
19444
|
+
* THROWS (retryable, never `false`, #1290): CustodyPersistError when the
|
|
19445
|
+
* channel is under the custody hold (nothing applied), and the C2 classes
|
|
19446
|
+
* (PendingCommitUnresolvedError from the guarded MAIN write) after evicting
|
|
19447
|
+
* the advanced cache, so the persisted state is unchanged. A /sync request
|
|
19448
|
+
* that fails also throws, before anything is applied.
|
|
19407
19449
|
*/
|
|
19408
19450
|
async resyncByReplay(channelId, groupId) {
|
|
19409
19451
|
if (!await this.hasPersistedChannelState(channelId))
|
|
@@ -19423,12 +19465,26 @@ var MlsGroupKeyProvider = class _MlsGroupKeyProvider {
|
|
|
19423
19465
|
let applied = 0;
|
|
19424
19466
|
try {
|
|
19425
19467
|
await this.locks.run(channelId, async () => {
|
|
19468
|
+
if (await this.custodyHeld(channelId))
|
|
19469
|
+
throw new CustodyPersistError(channelId);
|
|
19426
19470
|
const mgr = await this.manager(channelId);
|
|
19427
19471
|
try {
|
|
19428
19472
|
for (const c of commits) {
|
|
19429
19473
|
if (c.epoch <= Number(mgr.epoch))
|
|
19430
19474
|
continue;
|
|
19431
|
-
|
|
19475
|
+
const commitBytes = hexToBytes22(c.payload);
|
|
19476
|
+
try {
|
|
19477
|
+
await mgr.decrypt(commitBytes);
|
|
19478
|
+
} catch (e) {
|
|
19479
|
+
if (await probeOwnRemoval(mgr, commitBytes) !== null) {
|
|
19480
|
+
this.noteOwnLeafRemoved(channelId, {
|
|
19481
|
+
group_id: groupId,
|
|
19482
|
+
epoch: c.epoch,
|
|
19483
|
+
message_id: c.id
|
|
19484
|
+
});
|
|
19485
|
+
}
|
|
19486
|
+
throw e;
|
|
19487
|
+
}
|
|
19432
19488
|
applied++;
|
|
19433
19489
|
}
|
|
19434
19490
|
if (applied > 0)
|
|
@@ -19440,6 +19496,10 @@ var MlsGroupKeyProvider = class _MlsGroupKeyProvider {
|
|
|
19440
19496
|
}
|
|
19441
19497
|
});
|
|
19442
19498
|
} catch (e) {
|
|
19499
|
+
if (isSdkError(e, "CustodyPersistError") || e instanceof CustodyPersistError || e instanceof PendingCommitUnresolvedError || e instanceof GroupSupersededError || e instanceof AttestedOrphanRefusedError) {
|
|
19500
|
+
writeServiceLog("warn", "resync.replay_held", { applied }, e);
|
|
19501
|
+
throw e;
|
|
19502
|
+
}
|
|
19443
19503
|
writeServiceLog("warn", "resync.replay_commit_failed", { applied }, e);
|
|
19444
19504
|
return false;
|
|
19445
19505
|
}
|
|
@@ -22140,6 +22200,8 @@ var MlsGroupKeyProvider = class _MlsGroupKeyProvider {
|
|
|
22140
22200
|
out.push({
|
|
22141
22201
|
queueId: `replay-${m.id}`,
|
|
22142
22202
|
messageId: envelope.message_id,
|
|
22203
|
+
// #1246 C-1: the group-messages row id is the same mls_messages key.
|
|
22204
|
+
...typeof m.id === "string" && m.id ? { serverMessageId: m.id } : {},
|
|
22143
22205
|
channelId,
|
|
22144
22206
|
plaintext,
|
|
22145
22207
|
envelope,
|
|
@@ -23040,6 +23102,8 @@ var MlsGroupKeyProvider = class _MlsGroupKeyProvider {
|
|
|
23040
23102
|
const heldBundleRefs = () => heldBundleRefsMemo ??= this.readHeldBundleRefs();
|
|
23041
23103
|
const w2Acked = /* @__PURE__ */ new Map();
|
|
23042
23104
|
const w2Kept = /* @__PURE__ */ new Set();
|
|
23105
|
+
const keptWelcomeId = /* @__PURE__ */ new Map();
|
|
23106
|
+
const deferredThisPass = /* @__PURE__ */ new Map();
|
|
23043
23107
|
const unpinnedRefusals = /* @__PURE__ */ new Map();
|
|
23044
23108
|
const welcomes = rows.filter((r) => r.message_type === "welcome");
|
|
23045
23109
|
const nonWelcomes = rows.filter((r) => r.message_type !== "welcome");
|
|
@@ -23198,6 +23262,8 @@ var MlsGroupKeyProvider = class _MlsGroupKeyProvider {
|
|
|
23198
23262
|
writeServiceLog("warn", "welcome.unopenable");
|
|
23199
23263
|
} else {
|
|
23200
23264
|
w2Kept.add(wcid);
|
|
23265
|
+
if (!keptWelcomeId.has(wcid))
|
|
23266
|
+
keptWelcomeId.set(wcid, row.queue_id);
|
|
23201
23267
|
if (typeof process !== "undefined" && process.env?.SECURECOMS_TRACE_DELIVERY) {
|
|
23202
23268
|
writeServiceLog("debug", "delivery.welcome_no_local_state");
|
|
23203
23269
|
}
|
|
@@ -23208,6 +23274,10 @@ var MlsGroupKeyProvider = class _MlsGroupKeyProvider {
|
|
|
23208
23274
|
const cid = row.connect_channel_id;
|
|
23209
23275
|
if (!cid)
|
|
23210
23276
|
continue;
|
|
23277
|
+
if (w2Kept.has(cid) && (deferredThisPass.get(cid) ?? 0) < KEPT_WELCOME_COMMIT_DEFER_MAX_ROWS && this.deferKeptWelcomeCommit(cid, keptWelcomeId.get(cid) ?? "")) {
|
|
23278
|
+
deferredThisPass.set(cid, (deferredThisPass.get(cid) ?? 0) + 1);
|
|
23279
|
+
continue;
|
|
23280
|
+
}
|
|
23211
23281
|
let advancedEpoch;
|
|
23212
23282
|
let custodyHeld = false;
|
|
23213
23283
|
await this.locks.run(cid, async () => {
|
|
@@ -23364,6 +23434,12 @@ var MlsGroupKeyProvider = class _MlsGroupKeyProvider {
|
|
|
23364
23434
|
this.leftStreak.set(cid, { pulls: 1, since: pullNow });
|
|
23365
23435
|
this.scheduleAutoResolve(cid);
|
|
23366
23436
|
}
|
|
23437
|
+
if (rows.length < DELIVERY_PULL_PAGE) {
|
|
23438
|
+
for (const cid of [...this.keptWelcomeDeferSince.keys()]) {
|
|
23439
|
+
if (!w2Kept.has(cid))
|
|
23440
|
+
this.keptWelcomeDeferSince.delete(cid);
|
|
23441
|
+
}
|
|
23442
|
+
}
|
|
23367
23443
|
for (const [cid, gid] of w2Acked) {
|
|
23368
23444
|
if (w2Kept.has(cid))
|
|
23369
23445
|
continue;
|
|
@@ -23643,7 +23719,9 @@ var MlsGroupKeyProvider = class _MlsGroupKeyProvider {
|
|
|
23643
23719
|
voiceDefaultResponder: row.voice_default_responder ?? void 0,
|
|
23644
23720
|
...typeof row.identity_signature === "string" && row.identity_signature ? { identitySignature: row.identity_signature } : {},
|
|
23645
23721
|
payloadHex: row.payload,
|
|
23646
|
-
openedAt
|
|
23722
|
+
openedAt,
|
|
23723
|
+
// #1246 C-1: the server row id, beside the sender-chosen envelope id.
|
|
23724
|
+
...typeof row.message_id === "string" && row.message_id ? { serverMessageId: row.message_id } : {}
|
|
23647
23725
|
};
|
|
23648
23726
|
const custodiedAt = this.nowMs();
|
|
23649
23727
|
this.custody.set(row.queue_id, entry);
|
|
@@ -24650,8 +24728,8 @@ function openDeliveryPings(o) {
|
|
|
24650
24728
|
return Promise.resolve({ value: void 0, done: true });
|
|
24651
24729
|
if (queue.length)
|
|
24652
24730
|
return Promise.resolve({ value: queue.shift(), done: false });
|
|
24653
|
-
return new Promise((
|
|
24654
|
-
waiter =
|
|
24731
|
+
return new Promise((resolve2) => {
|
|
24732
|
+
waiter = resolve2;
|
|
24655
24733
|
});
|
|
24656
24734
|
}
|
|
24657
24735
|
};
|
|
@@ -24971,8 +25049,8 @@ var PingSubscriber = class {
|
|
|
24971
25049
|
return Promise.resolve({ value: void 0, done: true });
|
|
24972
25050
|
if (this.queue.length)
|
|
24973
25051
|
return Promise.resolve({ value: this.queue.shift(), done: false });
|
|
24974
|
-
return new Promise((
|
|
24975
|
-
this.waiter =
|
|
25052
|
+
return new Promise((resolve2) => {
|
|
25053
|
+
this.waiter = resolve2;
|
|
24976
25054
|
});
|
|
24977
25055
|
}
|
|
24978
25056
|
};
|
|
@@ -26165,6 +26243,11 @@ var FakeConnectBackend = class _FakeConnectBackend {
|
|
|
26165
26243
|
// Owner-signed tool grants (spec §7.0): like Plan 0's backend, each row carries
|
|
26166
26244
|
// the envelope's `identity_proof.signature` as `identity_signature` beside the
|
|
26167
26245
|
// unsigned canonical payload. `omitSignature` models a pre-Plan-0 row.
|
|
26246
|
+
/** `rowMessageId` (#1246 C-1): the server row id to deliver, when it is NOT
|
|
26247
|
+
* the envelope's id. The REST send path keys the row by the envelope id
|
|
26248
|
+
* (connect_messages.py, `MLSMessage(id=message_id)`); the WS `message_mls`
|
|
26249
|
+
* path stores through `store_mls_message`, whose row id is server-generated,
|
|
26250
|
+
* so there the two differ and the envelope id is the sender's free choice. */
|
|
26168
26251
|
async ingestApplication(envelope, opts) {
|
|
26169
26252
|
const channelId = envelope.channel_id;
|
|
26170
26253
|
const canonical = await canonicalEnvelopeBytes(envelope);
|
|
@@ -26176,7 +26259,7 @@ var FakeConnectBackend = class _FakeConnectBackend {
|
|
|
26176
26259
|
continue;
|
|
26177
26260
|
this.q(dev).push({
|
|
26178
26261
|
queue_id: "q-" + this._seq++,
|
|
26179
|
-
message_id: envelope.message_id,
|
|
26262
|
+
message_id: opts?.rowMessageId ?? envelope.message_id,
|
|
26180
26263
|
group_id: groupId,
|
|
26181
26264
|
message_type: "application",
|
|
26182
26265
|
sender_device_id: senderDeviceId ?? "",
|
|
@@ -26560,6 +26643,7 @@ function openSchema(db) {
|
|
|
26560
26643
|
ensureRuntimeInboxDeliveryAttempts(db);
|
|
26561
26644
|
ensureRuntimeReplyIntent(db);
|
|
26562
26645
|
ensureRuntimeReplyIntentEnvelopeGroup(db);
|
|
26646
|
+
ensureRuntimeReplyIntentCredit(db);
|
|
26563
26647
|
ensureGroupLink(db);
|
|
26564
26648
|
ensureMlsCustody(db);
|
|
26565
26649
|
ensureToolGrantTables(db);
|
|
@@ -26640,6 +26724,19 @@ function ensureRuntimeReplyIntentEnvelopeGroup(db) {
|
|
|
26640
26724
|
throw e;
|
|
26641
26725
|
}
|
|
26642
26726
|
}
|
|
26727
|
+
function ensureRuntimeReplyIntentCredit(db) {
|
|
26728
|
+
const hasColumn = () => db.prepare("PRAGMA table_info(runtime_reply_intent)").all().some((c) => c.name === "reply_credit");
|
|
26729
|
+
const cols = db.prepare("PRAGMA table_info(runtime_reply_intent)").all();
|
|
26730
|
+
if (cols.length === 0 || cols.some((c) => c.name === "reply_credit"))
|
|
26731
|
+
return;
|
|
26732
|
+
try {
|
|
26733
|
+
db.exec("ALTER TABLE runtime_reply_intent ADD COLUMN reply_credit TEXT");
|
|
26734
|
+
} catch (e) {
|
|
26735
|
+
if (hasColumn())
|
|
26736
|
+
return;
|
|
26737
|
+
throw e;
|
|
26738
|
+
}
|
|
26739
|
+
}
|
|
26643
26740
|
var RECALL_STATE_COLUMNS = [
|
|
26644
26741
|
["recall_enabled", "INTEGER NOT NULL DEFAULT 0"],
|
|
26645
26742
|
["recall_mode_epoch", "INTEGER NOT NULL DEFAULT 0"],
|
|
@@ -27691,8 +27788,8 @@ var SqliteMlsStateStore = class _SqliteMlsStateStore {
|
|
|
27691
27788
|
this.db.prepare("DELETE FROM runtime_reply_intent WHERE identity_id=? AND updated_at < ?").run(this.id, now - RUNTIME_REPLY_INTENT_RETENTION_MS);
|
|
27692
27789
|
const info = this.db.prepare(`INSERT OR IGNORE INTO runtime_reply_intent
|
|
27693
27790
|
(identity_id, scope, part, in_reply_to, channel_id, reply_message_id, body_mac,
|
|
27694
|
-
sealed_body, sealed_envelope, state, created_at, updated_at)
|
|
27695
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL, 'pending', ?, ?)`).run(this.id, input.scope, input.part, input.inReplyTo, input.channelId, input.replyMessageId, input.bodyMac, sealed, now, now);
|
|
27791
|
+
sealed_body, sealed_envelope, state, created_at, updated_at, reply_credit)
|
|
27792
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL, 'pending', ?, ?, ?)`).run(this.id, input.scope, input.part, input.inReplyTo, input.channelId, input.replyMessageId, input.bodyMac, sealed, now, now, input.replyCredit ?? null);
|
|
27696
27793
|
const count = this.db.prepare("SELECT COUNT(*) AS n FROM runtime_reply_intent WHERE identity_id=?").get(this.id);
|
|
27697
27794
|
if (count.n > RUNTIME_REPLY_INTENT_MAX) {
|
|
27698
27795
|
this.db.prepare(`DELETE FROM runtime_reply_intent
|
|
@@ -27702,7 +27799,7 @@ var SqliteMlsStateStore = class _SqliteMlsStateStore {
|
|
|
27702
27799
|
)`).run(this.id, this.id, RUNTIME_REPLY_INTENT_MAX);
|
|
27703
27800
|
}
|
|
27704
27801
|
const row = this.db.prepare(`SELECT scope, part, in_reply_to, channel_id, reply_message_id, body_mac,
|
|
27705
|
-
sealed_body, sealed_envelope, sealed_envelope_group, state, created_at
|
|
27802
|
+
sealed_body, sealed_envelope, sealed_envelope_group, state, created_at, reply_credit
|
|
27706
27803
|
FROM runtime_reply_intent WHERE identity_id=? AND scope=? AND part=?`).get(this.id, input.scope, input.part);
|
|
27707
27804
|
if (!row)
|
|
27708
27805
|
throw new Error("reply intent vanished after claim");
|
|
@@ -27711,7 +27808,7 @@ var SqliteMlsStateStore = class _SqliteMlsStateStore {
|
|
|
27711
27808
|
/** SEC-06 #18: every intent answering `inReplyTo`, in claim order. */
|
|
27712
27809
|
async listReplyIntents(inReplyTo) {
|
|
27713
27810
|
const rows = this.db.prepare(`SELECT scope, part, in_reply_to, channel_id, reply_message_id, body_mac,
|
|
27714
|
-
sealed_body, sealed_envelope, sealed_envelope_group, state, created_at
|
|
27811
|
+
sealed_body, sealed_envelope, sealed_envelope_group, state, created_at, reply_credit
|
|
27715
27812
|
FROM runtime_reply_intent WHERE identity_id=? AND in_reply_to=?
|
|
27716
27813
|
ORDER BY created_at ASC, rowid ASC`).all(this.id, inReplyTo);
|
|
27717
27814
|
const out = [];
|
|
@@ -27722,7 +27819,7 @@ var SqliteMlsStateStore = class _SqliteMlsStateStore {
|
|
|
27722
27819
|
/** SEC-06 #18: re-read one intent by its key (null when absent). */
|
|
27723
27820
|
async getReplyIntent(scope, part) {
|
|
27724
27821
|
const row = this.db.prepare(`SELECT scope, part, in_reply_to, channel_id, reply_message_id, body_mac,
|
|
27725
|
-
sealed_body, sealed_envelope, sealed_envelope_group, state, created_at
|
|
27822
|
+
sealed_body, sealed_envelope, sealed_envelope_group, state, created_at, reply_credit
|
|
27726
27823
|
FROM runtime_reply_intent WHERE identity_id=? AND scope=? AND part=?`).get(this.id, scope, part);
|
|
27727
27824
|
return row ? this.openReplyIntent(row) : null;
|
|
27728
27825
|
}
|
|
@@ -27796,7 +27893,8 @@ var SqliteMlsStateStore = class _SqliteMlsStateStore {
|
|
|
27796
27893
|
envelope,
|
|
27797
27894
|
envelopeGroupId: envelopeGroupFor(envelope, await openOrNull(row.sealed_envelope_group)),
|
|
27798
27895
|
state: row.state === "sent" ? "sent" : row.state === "skipped" ? "skipped" : "pending",
|
|
27799
|
-
createdAt: Number(row.created_at)
|
|
27896
|
+
createdAt: Number(row.created_at),
|
|
27897
|
+
replyCredit: typeof row.reply_credit === "string" ? row.reply_credit : null
|
|
27800
27898
|
};
|
|
27801
27899
|
}
|
|
27802
27900
|
async recordMemory(input) {
|
|
@@ -29566,8 +29664,8 @@ function decodeReplyBody(bytes) {
|
|
|
29566
29664
|
if (b?.kind === "a2a" && b.a2a && typeof b.a2a === "object") return b;
|
|
29567
29665
|
throw new Error("reply intent body is malformed");
|
|
29568
29666
|
}
|
|
29569
|
-
function replyBodyMac(key, replyId, channelId, body) {
|
|
29570
|
-
return createHmac("sha256", key).update(JSON.stringify(["reply-body", replyId, channelId])).update("\0").update(body).digest("hex");
|
|
29667
|
+
function replyBodyMac(key, replyId, channelId, body, credit) {
|
|
29668
|
+
return createHmac("sha256", key).update(JSON.stringify(credit === void 0 ? ["reply-body", replyId, channelId] : ["reply-body", replyId, channelId, credit])).update("\0").update(body).digest("hex");
|
|
29571
29669
|
}
|
|
29572
29670
|
function partOrdinal(part) {
|
|
29573
29671
|
const m = /^p:(\d+)$/.exec(part);
|
|
@@ -29608,8 +29706,96 @@ function loadPackageMeta(importMetaUrl) {
|
|
|
29608
29706
|
}
|
|
29609
29707
|
|
|
29610
29708
|
// src/bridge-agent.ts
|
|
29611
|
-
import { existsSync as existsSync5, promises as
|
|
29612
|
-
|
|
29709
|
+
import { existsSync as existsSync5, promises as fsp2, readFileSync as readFileSync10, statSync as statSync4 } from "node:fs";
|
|
29710
|
+
|
|
29711
|
+
// src/outbox.ts
|
|
29712
|
+
import { constants, mkdirSync, lstatSync, chmodSync, realpathSync, promises as fsp } from "node:fs";
|
|
29713
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
29714
|
+
var VAULT_OUTBOX_DIRNAME = "vault-outbox";
|
|
29715
|
+
var PathOutsideOutboxError = class extends Error {
|
|
29716
|
+
constructor() {
|
|
29717
|
+
super("path_outside_outbox");
|
|
29718
|
+
this.name = "PathOutsideOutboxError";
|
|
29719
|
+
}
|
|
29720
|
+
};
|
|
29721
|
+
var POSIX = process.platform !== "win32";
|
|
29722
|
+
function ownedPrivateDir(st) {
|
|
29723
|
+
if (st.isSymbolicLink() || !st.isDirectory()) return false;
|
|
29724
|
+
if (!POSIX) return true;
|
|
29725
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
29726
|
+
if (uid !== void 0 && st.uid !== uid) return false;
|
|
29727
|
+
return (st.mode & 511) === 448;
|
|
29728
|
+
}
|
|
29729
|
+
function ensureOutbox(dir) {
|
|
29730
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
29731
|
+
let st = lstatSync(dir);
|
|
29732
|
+
if (st.isSymbolicLink() || !st.isDirectory()) throw new PathOutsideOutboxError();
|
|
29733
|
+
const uid = POSIX && typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
29734
|
+
if (uid !== void 0 && st.uid !== uid) throw new PathOutsideOutboxError();
|
|
29735
|
+
if (POSIX && (st.mode & 511) !== 448) {
|
|
29736
|
+
chmodSync(dir, 448);
|
|
29737
|
+
st = lstatSync(dir);
|
|
29738
|
+
}
|
|
29739
|
+
if (!ownedPrivateDir(st)) throw new PathOutsideOutboxError();
|
|
29740
|
+
return { dir, real: realpathSync(dir), dev: st.dev, ino: st.ino };
|
|
29741
|
+
}
|
|
29742
|
+
var NOFOLLOW = constants.O_NOFOLLOW ?? 0;
|
|
29743
|
+
var NONBLOCK = constants.O_NONBLOCK ?? 0;
|
|
29744
|
+
async function readOutboxFile(pin, callerPath) {
|
|
29745
|
+
if (!pin) throw new PathOutsideOutboxError();
|
|
29746
|
+
try {
|
|
29747
|
+
const st = await fsp.lstat(pin.dir);
|
|
29748
|
+
if (!ownedPrivateDir(st) || st.dev !== pin.dev || st.ino !== pin.ino) throw new PathOutsideOutboxError();
|
|
29749
|
+
} catch {
|
|
29750
|
+
throw new PathOutsideOutboxError();
|
|
29751
|
+
}
|
|
29752
|
+
const requested = resolve(pin.dir, callerPath);
|
|
29753
|
+
const name = basename(requested);
|
|
29754
|
+
let realParent;
|
|
29755
|
+
try {
|
|
29756
|
+
realParent = await fsp.realpath(dirname(requested));
|
|
29757
|
+
} catch {
|
|
29758
|
+
throw new PathOutsideOutboxError();
|
|
29759
|
+
}
|
|
29760
|
+
if (realParent !== pin.real) throw new PathOutsideOutboxError();
|
|
29761
|
+
const target = join(pin.real, name);
|
|
29762
|
+
const pre = await fsp.lstat(target);
|
|
29763
|
+
if (pre.isSymbolicLink() || !pre.isFile()) throw new PathOutsideOutboxError();
|
|
29764
|
+
let handle;
|
|
29765
|
+
try {
|
|
29766
|
+
handle = await fsp.open(target, constants.O_RDONLY | NOFOLLOW | NONBLOCK);
|
|
29767
|
+
} catch (e) {
|
|
29768
|
+
if (e.code === "ENOENT") throw e;
|
|
29769
|
+
throw new PathOutsideOutboxError();
|
|
29770
|
+
}
|
|
29771
|
+
try {
|
|
29772
|
+
const st = await handle.stat();
|
|
29773
|
+
if (!st.isFile() || st.dev !== pre.dev || st.ino !== pre.ino || st.nlink !== 1) {
|
|
29774
|
+
throw new PathOutsideOutboxError();
|
|
29775
|
+
}
|
|
29776
|
+
return { bytes: new Uint8Array(await handle.readFile()), path: target, dev: st.dev, ino: st.ino };
|
|
29777
|
+
} finally {
|
|
29778
|
+
await handle.close().catch(() => {
|
|
29779
|
+
});
|
|
29780
|
+
}
|
|
29781
|
+
}
|
|
29782
|
+
async function consumeOutboxFile(pin, read) {
|
|
29783
|
+
try {
|
|
29784
|
+
const dir = await fsp.lstat(pin.dir);
|
|
29785
|
+
if (!ownedPrivateDir(dir) || dir.dev !== pin.dev || dir.ino !== pin.ino) return false;
|
|
29786
|
+
if (await fsp.realpath(pin.dir) !== pin.real) return false;
|
|
29787
|
+
const target = join(pin.real, basename(read.path));
|
|
29788
|
+
const st = await fsp.lstat(target);
|
|
29789
|
+
if (st.isSymbolicLink() || st.dev !== read.dev || st.ino !== read.ino) return false;
|
|
29790
|
+
await fsp.unlink(target);
|
|
29791
|
+
return true;
|
|
29792
|
+
} catch {
|
|
29793
|
+
return false;
|
|
29794
|
+
}
|
|
29795
|
+
}
|
|
29796
|
+
|
|
29797
|
+
// src/bridge-agent.ts
|
|
29798
|
+
import { join as join18, dirname as dirname6 } from "node:path";
|
|
29613
29799
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
29614
29800
|
|
|
29615
29801
|
// src/latency.ts
|
|
@@ -30107,11 +30293,21 @@ function pinCoverage(pinStripped, ownerMessage, coverTexts) {
|
|
|
30107
30293
|
}
|
|
30108
30294
|
|
|
30109
30295
|
// src/attachments.ts
|
|
30110
|
-
import { basename } from "node:path";
|
|
30296
|
+
import { basename as basename2 } from "node:path";
|
|
30111
30297
|
var VAULT_REF_MAX_SIZE_HINT = 100 * 1024 * 1024 - 16;
|
|
30112
30298
|
var VAULT_REF_MAX_NAME = 255;
|
|
30113
30299
|
var VAULT_REF_MAX_MIME = 127;
|
|
30114
30300
|
var CANONICAL_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
30301
|
+
var InvalidBlobIdError = class extends Error {
|
|
30302
|
+
constructor() {
|
|
30303
|
+
super("invalid_blob_id");
|
|
30304
|
+
this.name = "InvalidBlobIdError";
|
|
30305
|
+
}
|
|
30306
|
+
};
|
|
30307
|
+
function assertBlobId(blobId) {
|
|
30308
|
+
if (typeof blobId !== "string" || !CANONICAL_UUID.test(blobId)) throw new InvalidBlobIdError();
|
|
30309
|
+
return blobId;
|
|
30310
|
+
}
|
|
30115
30311
|
var VAULT_PIN = /^blake2b:[0-9a-f]{64}$/;
|
|
30116
30312
|
var HINT_FORBIDDEN = /[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/;
|
|
30117
30313
|
var MIME_TYPE = /^[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*\/[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*$/;
|
|
@@ -30150,7 +30346,7 @@ function toVaultRefEntry(body) {
|
|
|
30150
30346
|
return entry;
|
|
30151
30347
|
}
|
|
30152
30348
|
function sanitizeFilename(name, fallback) {
|
|
30153
|
-
const base =
|
|
30349
|
+
const base = basename2(name ?? "");
|
|
30154
30350
|
const cleaned = base.replace(/[/\\]/g, "").replace(/^\.+$/, "");
|
|
30155
30351
|
return cleaned.length > 0 ? cleaned : fallback;
|
|
30156
30352
|
}
|
|
@@ -30180,7 +30376,7 @@ function mimeForName(name) {
|
|
|
30180
30376
|
async function prepareAttachment(filePath2, caption, channelId, deps) {
|
|
30181
30377
|
await deps.ready();
|
|
30182
30378
|
const bytes = await deps.readFile(filePath2);
|
|
30183
|
-
const name =
|
|
30379
|
+
const name = basename2(filePath2);
|
|
30184
30380
|
const { encryptedData, fileKey, fileNonce, digest } = await deps.encryptFile(bytes);
|
|
30185
30381
|
const blobId = await deps.uploadBlob(encryptedData, channelId);
|
|
30186
30382
|
const body = {
|
|
@@ -30198,8 +30394,9 @@ async function prepareAttachment(filePath2, caption, channelId, deps) {
|
|
|
30198
30394
|
return body;
|
|
30199
30395
|
}
|
|
30200
30396
|
async function materializeAttachment(body, ctx, deps) {
|
|
30397
|
+
const blobId = assertBlobId(body.blobId);
|
|
30201
30398
|
await deps.ready();
|
|
30202
|
-
const ciphertext = await deps.downloadBlob(
|
|
30399
|
+
const ciphertext = await deps.downloadBlob(blobId);
|
|
30203
30400
|
const sha25612 = await deps.computeFileDigest(ciphertext);
|
|
30204
30401
|
if (sha25612 !== body.digest) {
|
|
30205
30402
|
throw new Error(`attachment digest mismatch for blob ${body.blobId}`);
|
|
@@ -30225,13 +30422,13 @@ function ensureSodiumReady2() {
|
|
|
30225
30422
|
// src/memory/hermes-session-map.ts
|
|
30226
30423
|
import { readFileSync } from "node:fs";
|
|
30227
30424
|
import { homedir } from "node:os";
|
|
30228
|
-
import { join } from "node:path";
|
|
30425
|
+
import { join as join2 } from "node:path";
|
|
30229
30426
|
function expandHome(p) {
|
|
30230
|
-
return p === "~" || p.startsWith("~/") ?
|
|
30427
|
+
return p === "~" || p.startsWith("~/") ? join2(homedir(), p.slice(1)) : p;
|
|
30231
30428
|
}
|
|
30232
30429
|
function resolveHermesSession(baseDir, conversationId) {
|
|
30233
30430
|
try {
|
|
30234
|
-
const path =
|
|
30431
|
+
const path = join2(expandHome(baseDir), "sessions", "sessions.json");
|
|
30235
30432
|
const map = JSON.parse(readFileSync(path, "utf8"));
|
|
30236
30433
|
const entry = map[`agent:main:securecoms:dm:${conversationId}`];
|
|
30237
30434
|
const sid = entry?.session_id;
|
|
@@ -30243,11 +30440,11 @@ function resolveHermesSession(baseDir, conversationId) {
|
|
|
30243
30440
|
|
|
30244
30441
|
// src/memory/hermes-store.ts
|
|
30245
30442
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
30246
|
-
import { join as
|
|
30443
|
+
import { join as join3 } from "node:path";
|
|
30247
30444
|
var HermesStore = class {
|
|
30248
30445
|
dbPath;
|
|
30249
30446
|
constructor(baseDir) {
|
|
30250
|
-
this.dbPath =
|
|
30447
|
+
this.dbPath = join3(expandHome(baseDir), "state.db");
|
|
30251
30448
|
}
|
|
30252
30449
|
/** Recent-K conversational turns for a session, chronological. Fail-safe → []. */
|
|
30253
30450
|
readRecent(sessionId, limit) {
|
|
@@ -30389,10 +30586,10 @@ var HermesMemoryProvider = class {
|
|
|
30389
30586
|
|
|
30390
30587
|
// src/memory/openclaw-session-map.ts
|
|
30391
30588
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
30392
|
-
import { join as
|
|
30589
|
+
import { join as join4 } from "node:path";
|
|
30393
30590
|
function resolveOwnerDmSession(baseDir, agent) {
|
|
30394
30591
|
try {
|
|
30395
|
-
const path =
|
|
30592
|
+
const path = join4(expandHome(baseDir), "agents", agent, "sessions", "sessions.json");
|
|
30396
30593
|
const map = JSON.parse(readFileSync2(path, "utf8"));
|
|
30397
30594
|
let best = null;
|
|
30398
30595
|
for (const [key, entry] of Object.entries(map)) {
|
|
@@ -30410,11 +30607,11 @@ function resolveOwnerDmSession(baseDir, agent) {
|
|
|
30410
30607
|
|
|
30411
30608
|
// src/memory/openclaw-session-log.ts
|
|
30412
30609
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
30413
|
-
import { join as
|
|
30610
|
+
import { join as join5 } from "node:path";
|
|
30414
30611
|
function readRecentTurns(baseDir, agent, sessionId, k) {
|
|
30415
30612
|
let raw;
|
|
30416
30613
|
try {
|
|
30417
|
-
raw = readFileSync3(
|
|
30614
|
+
raw = readFileSync3(join5(expandHome(baseDir), "agents", agent, "sessions", `${sessionId}.jsonl`), "utf8");
|
|
30418
30615
|
} catch {
|
|
30419
30616
|
return [];
|
|
30420
30617
|
}
|
|
@@ -30445,7 +30642,7 @@ function readRecentTurns(baseDir, agent, sessionId, k) {
|
|
|
30445
30642
|
|
|
30446
30643
|
// src/memory/openclaw-workspace-facts.ts
|
|
30447
30644
|
import { readdirSync, readFileSync as readFileSync4, statSync } from "node:fs";
|
|
30448
|
-
import { join as
|
|
30645
|
+
import { join as join6 } from "node:path";
|
|
30449
30646
|
var DEFAULT_FACTS_BUDGET = 8e3;
|
|
30450
30647
|
var MEMORY_MD_CAP = 6e3;
|
|
30451
30648
|
var MIN_FRAGMENT = 200;
|
|
@@ -30456,7 +30653,7 @@ function resolveWorkspaceDir(baseDir, agent, override) {
|
|
|
30456
30653
|
const base = expandHome(baseDir);
|
|
30457
30654
|
const isMain = agent === "" || agent === "main";
|
|
30458
30655
|
try {
|
|
30459
|
-
const cfg = JSON.parse(readFileSync4(
|
|
30656
|
+
const cfg = JSON.parse(readFileSync4(join6(base, "openclaw.json"), "utf8"));
|
|
30460
30657
|
const agents = cfg?.agents;
|
|
30461
30658
|
if (agents && typeof agents === "object") {
|
|
30462
30659
|
const list = Array.isArray(agents.list) ? agents.list : [];
|
|
@@ -30469,7 +30666,7 @@ function resolveWorkspaceDir(baseDir, agent, override) {
|
|
|
30469
30666
|
}
|
|
30470
30667
|
} catch {
|
|
30471
30668
|
}
|
|
30472
|
-
return isMain ?
|
|
30669
|
+
return isMain ? join6(base, "workspace") : join6(base, `workspace-${agent}`);
|
|
30473
30670
|
}
|
|
30474
30671
|
var TRUNC = "\u2026[truncated]";
|
|
30475
30672
|
function headSlice(s, n) {
|
|
@@ -30499,7 +30696,7 @@ function readWorkspaceFacts(baseDir, agent, opts) {
|
|
|
30499
30696
|
let truncated = false;
|
|
30500
30697
|
let remaining = budget;
|
|
30501
30698
|
try {
|
|
30502
|
-
const r = readBounded(
|
|
30699
|
+
const r = readBounded(join6(dir, "MEMORY.md"));
|
|
30503
30700
|
if (r.oversize) truncated = true;
|
|
30504
30701
|
else if (r.text && r.text.trim()) {
|
|
30505
30702
|
const { text, clipped } = headSlice(r.text, Math.min(MEMORY_MD_CAP, budget));
|
|
@@ -30512,7 +30709,7 @@ ${TRUNC}` : ""}`);
|
|
|
30512
30709
|
} catch {
|
|
30513
30710
|
}
|
|
30514
30711
|
try {
|
|
30515
|
-
const dailies = readdirSync(
|
|
30712
|
+
const dailies = readdirSync(join6(dir, "memory")).filter((f) => DAILY_RE.test(f)).sort().reverse().slice(0, 2);
|
|
30516
30713
|
for (const f of dailies) {
|
|
30517
30714
|
if (remaining < MIN_FRAGMENT) {
|
|
30518
30715
|
truncated = true;
|
|
@@ -30520,7 +30717,7 @@ ${TRUNC}` : ""}`);
|
|
|
30520
30717
|
}
|
|
30521
30718
|
let r;
|
|
30522
30719
|
try {
|
|
30523
|
-
r = readBounded(
|
|
30720
|
+
r = readBounded(join6(dir, "memory", f));
|
|
30524
30721
|
} catch {
|
|
30525
30722
|
continue;
|
|
30526
30723
|
}
|
|
@@ -31259,21 +31456,21 @@ function parsePayloadMemory(raw) {
|
|
|
31259
31456
|
|
|
31260
31457
|
// src/memory/embedder/client.ts
|
|
31261
31458
|
import { connect } from "node:net";
|
|
31262
|
-
import { existsSync as existsSync3, mkdirSync as
|
|
31459
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync7, rmSync as rmSync6, statSync as statSync2 } from "node:fs";
|
|
31263
31460
|
import { fileURLToPath } from "node:url";
|
|
31264
31461
|
|
|
31265
31462
|
// src/memory/embedder/download.ts
|
|
31266
31463
|
import { createHash as createHash2, randomBytes as randomBytes6 } from "node:crypto";
|
|
31267
|
-
import { chmodSync as
|
|
31464
|
+
import { chmodSync as chmodSync3, rmSync as rmSync2 } from "node:fs";
|
|
31268
31465
|
import { open } from "node:fs/promises";
|
|
31269
|
-
import { join as
|
|
31466
|
+
import { join as join8 } from "node:path";
|
|
31270
31467
|
|
|
31271
31468
|
// src/memory/embedder/layout.ts
|
|
31272
|
-
import { chmodSync, lstatSync, mkdirSync, renameSync, rmSync } from "node:fs";
|
|
31469
|
+
import { chmodSync as chmodSync2, lstatSync as lstatSync2, mkdirSync as mkdirSync2, renameSync, rmSync } from "node:fs";
|
|
31273
31470
|
import { homedir as homedir2 } from "node:os";
|
|
31274
|
-
import { basename as
|
|
31275
|
-
var defaultModelsRoot = (home = homedir2()) =>
|
|
31276
|
-
var filePath = (root3, modelId, f) =>
|
|
31471
|
+
import { basename as basename3, dirname as dirname2, join as join7 } from "node:path";
|
|
31472
|
+
var defaultModelsRoot = (home = homedir2()) => join7(home, ".securecoms", "models", "embeddings");
|
|
31473
|
+
var filePath = (root3, modelId, f) => join7(root3, modelId, f.sha256, f.name);
|
|
31277
31474
|
var LayoutRefused = class extends Error {
|
|
31278
31475
|
constructor() {
|
|
31279
31476
|
super("the model directory is not a plain directory");
|
|
@@ -31281,32 +31478,32 @@ var LayoutRefused = class extends Error {
|
|
|
31281
31478
|
};
|
|
31282
31479
|
function kind(p) {
|
|
31283
31480
|
try {
|
|
31284
|
-
return
|
|
31481
|
+
return lstatSync2(p).isDirectory() ? "dir" : "other";
|
|
31285
31482
|
} catch (e) {
|
|
31286
31483
|
if (e.code === "ENOENT") return "absent";
|
|
31287
31484
|
throw e;
|
|
31288
31485
|
}
|
|
31289
31486
|
}
|
|
31290
31487
|
function plainDirChain(root3, parts, opts) {
|
|
31291
|
-
const modelsDir =
|
|
31488
|
+
const modelsDir = dirname2(root3);
|
|
31292
31489
|
for (const part of parts) if (!part || part === "." || part === ".." || part.includes("/") || part.includes("\\")) throw new LayoutRefused();
|
|
31293
31490
|
const k = kind(modelsDir);
|
|
31294
31491
|
if (k === "other") throw new LayoutRefused();
|
|
31295
31492
|
if (k === "absent") {
|
|
31296
31493
|
if (!opts.create) return null;
|
|
31297
|
-
|
|
31494
|
+
mkdirSync2(modelsDir, { recursive: true, mode: 448 });
|
|
31298
31495
|
if (kind(modelsDir) !== "dir") throw new LayoutRefused();
|
|
31299
31496
|
}
|
|
31300
|
-
const chain = opts.fromModelsDir ? parts : [
|
|
31497
|
+
const chain = opts.fromModelsDir ? parts : [basename3(root3), ...parts];
|
|
31301
31498
|
let p = modelsDir;
|
|
31302
31499
|
for (let i = 0; i < chain.length; i++) {
|
|
31303
|
-
p =
|
|
31500
|
+
p = join7(p, chain[i]);
|
|
31304
31501
|
const c = kind(p);
|
|
31305
31502
|
if (c === "other") throw new LayoutRefused();
|
|
31306
31503
|
if (c === "absent") {
|
|
31307
31504
|
if (!opts.create) return null;
|
|
31308
31505
|
try {
|
|
31309
|
-
|
|
31506
|
+
mkdirSync2(p, { mode: i === chain.length - 1 && opts.mode !== void 0 ? opts.mode : 493 });
|
|
31310
31507
|
} catch (e) {
|
|
31311
31508
|
if (e.code !== "EEXIST") throw e;
|
|
31312
31509
|
}
|
|
@@ -31317,7 +31514,7 @@ function plainDirChain(root3, parts, opts) {
|
|
|
31317
31514
|
}
|
|
31318
31515
|
function presentWithSize(p, size) {
|
|
31319
31516
|
try {
|
|
31320
|
-
const s =
|
|
31517
|
+
const s = lstatSync2(p);
|
|
31321
31518
|
return s.isFile() && s.size === size;
|
|
31322
31519
|
} catch {
|
|
31323
31520
|
return false;
|
|
@@ -31325,7 +31522,7 @@ function presentWithSize(p, size) {
|
|
|
31325
31522
|
}
|
|
31326
31523
|
function wrongTypeAt(p) {
|
|
31327
31524
|
try {
|
|
31328
|
-
const s =
|
|
31525
|
+
const s = lstatSync2(p);
|
|
31329
31526
|
return !s.isFile() && !s.isSymbolicLink();
|
|
31330
31527
|
} catch {
|
|
31331
31528
|
return false;
|
|
@@ -31338,16 +31535,16 @@ function installVerified(root3, modelId, f, partial, opts) {
|
|
|
31338
31535
|
dir = plainDirChain(root3, [modelId, f.sha256], { create: true });
|
|
31339
31536
|
if (!dir) throw new LayoutRefused();
|
|
31340
31537
|
if (!opts.replace && presentWithSize(dest, f.size)) return;
|
|
31341
|
-
|
|
31538
|
+
chmodSync2(dir, 493);
|
|
31342
31539
|
try {
|
|
31343
31540
|
renameSync(partial, dest);
|
|
31344
31541
|
} catch (e) {
|
|
31345
31542
|
if (!presentWithSize(dest, f.size)) throw e;
|
|
31346
31543
|
return;
|
|
31347
31544
|
}
|
|
31348
|
-
|
|
31545
|
+
chmodSync2(dest, 292);
|
|
31349
31546
|
} finally {
|
|
31350
|
-
if (dir)
|
|
31547
|
+
if (dir) chmodSync2(dir, 365);
|
|
31351
31548
|
rmSync(partial, { force: true });
|
|
31352
31549
|
}
|
|
31353
31550
|
}
|
|
@@ -31367,8 +31564,8 @@ function removeModelFiles(root3, m) {
|
|
|
31367
31564
|
for (const f of m.files) {
|
|
31368
31565
|
const dir = plainDirChain(root3, [m.modelId, f.sha256], { create: false });
|
|
31369
31566
|
if (!dir) continue;
|
|
31370
|
-
|
|
31371
|
-
rmSync2(
|
|
31567
|
+
chmodSync3(dir, 493);
|
|
31568
|
+
rmSync2(join8(dir, f.name), { force: true });
|
|
31372
31569
|
}
|
|
31373
31570
|
}
|
|
31374
31571
|
var VerifyFailed = class extends Error {
|
|
@@ -31415,7 +31612,7 @@ async function fetchSameOrigin(url, origin, fetchImpl, signal, stallMs, abort) {
|
|
|
31415
31612
|
async function downloadOne(f, o) {
|
|
31416
31613
|
if (wrongTypeAt(filePath(o.root, o.modelId, f))) throw new WrongType("wrong type at model path");
|
|
31417
31614
|
const pdir = plainDirChain(o.root, [".partial"], { create: true, fromModelsDir: true, mode: 448 });
|
|
31418
|
-
const partial =
|
|
31615
|
+
const partial = join8(pdir, randomBytes6(12).toString("hex"));
|
|
31419
31616
|
const fh = await open(partial, "wx", 384);
|
|
31420
31617
|
const ac = new AbortController();
|
|
31421
31618
|
const abort = () => ac.abort();
|
|
@@ -31491,17 +31688,17 @@ async function ensureModelFiles(o) {
|
|
|
31491
31688
|
}
|
|
31492
31689
|
|
|
31493
31690
|
// src/memory/embedder/gc.ts
|
|
31494
|
-
import { chmodSync as
|
|
31495
|
-
import { join as
|
|
31691
|
+
import { chmodSync as chmodSync4, closeSync, constants as constants2, fstatSync, futimesSync, lstatSync as lstatSync3, openSync, readdirSync as readdirSync2, rmSync as rmSync3 } from "node:fs";
|
|
31692
|
+
import { join as join9 } from "node:path";
|
|
31496
31693
|
var THIRTY_DAYS = 30 * 864e5;
|
|
31497
31694
|
var SAFE_ID = /^[A-Za-z0-9_-]{1,128}$/;
|
|
31498
31695
|
function touchUserMarker(root3, modelId, identityId) {
|
|
31499
31696
|
if (!SAFE_ID.test(identityId)) throw new Error("invalid identity id for the model marker");
|
|
31500
31697
|
const dir = plainDirChain(root3, [modelId, ".users"], { create: true });
|
|
31501
|
-
const p =
|
|
31698
|
+
const p = join9(dir, identityId);
|
|
31502
31699
|
let fd;
|
|
31503
31700
|
try {
|
|
31504
|
-
fd = openSync(p,
|
|
31701
|
+
fd = openSync(p, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_NOFOLLOW | constants2.O_NONBLOCK, 420);
|
|
31505
31702
|
} catch {
|
|
31506
31703
|
throw new Error("the model marker is not a regular file");
|
|
31507
31704
|
}
|
|
@@ -31515,22 +31712,22 @@ function touchUserMarker(root3, modelId, identityId) {
|
|
|
31515
31712
|
}
|
|
31516
31713
|
function isRealDir(p) {
|
|
31517
31714
|
try {
|
|
31518
|
-
return
|
|
31715
|
+
return lstatSync3(p).isDirectory();
|
|
31519
31716
|
} catch {
|
|
31520
31717
|
return false;
|
|
31521
31718
|
}
|
|
31522
31719
|
}
|
|
31523
31720
|
function makeWritable(p) {
|
|
31524
31721
|
if (!isRealDir(p)) return;
|
|
31525
|
-
|
|
31526
|
-
for (const e of readdirSync2(p)) makeWritable(
|
|
31722
|
+
chmodSync4(p, 493);
|
|
31723
|
+
for (const e of readdirSync2(p)) makeWritable(join9(p, e));
|
|
31527
31724
|
}
|
|
31528
31725
|
function newestMarkerMs(users) {
|
|
31529
31726
|
if (!isRealDir(users)) return void 0;
|
|
31530
31727
|
let newest;
|
|
31531
31728
|
for (const u of readdirSync2(users)) {
|
|
31532
31729
|
try {
|
|
31533
|
-
const s =
|
|
31730
|
+
const s = lstatSync3(join9(users, u));
|
|
31534
31731
|
if (!s.isFile()) continue;
|
|
31535
31732
|
newest = newest === void 0 ? s.mtimeMs : Math.max(newest, s.mtimeMs);
|
|
31536
31733
|
} catch {
|
|
@@ -31543,9 +31740,9 @@ function gcModels(root3, keepModelIds, now, maxAgeMs = THIRTY_DAYS) {
|
|
|
31543
31740
|
const removed = [];
|
|
31544
31741
|
for (const modelId of readdirSync2(root3).sort()) {
|
|
31545
31742
|
if (modelId.startsWith(".") || keepModelIds.includes(modelId)) continue;
|
|
31546
|
-
const tree =
|
|
31743
|
+
const tree = join9(root3, modelId);
|
|
31547
31744
|
if (!isRealDir(tree)) continue;
|
|
31548
|
-
const lastUse = newestMarkerMs(
|
|
31745
|
+
const lastUse = newestMarkerMs(join9(tree, ".users")) ?? lstatSync3(tree).mtimeMs;
|
|
31549
31746
|
if (now - lastUse <= maxAgeMs) continue;
|
|
31550
31747
|
makeWritable(tree);
|
|
31551
31748
|
rmSync3(tree, { recursive: true, force: true });
|
|
@@ -31678,44 +31875,44 @@ var SealedChannel = class {
|
|
|
31678
31875
|
// src/memory/embedder/embedder-paths.ts
|
|
31679
31876
|
import { createHash as createHash3 } from "node:crypto";
|
|
31680
31877
|
import { homedir as homedir3 } from "node:os";
|
|
31681
|
-
import { join as
|
|
31878
|
+
import { join as join10 } from "node:path";
|
|
31682
31879
|
function embedderKey(m) {
|
|
31683
31880
|
const model = m.files.find((f) => f.name === "model.onnx");
|
|
31684
31881
|
return createHash3("sha256").update(`${EMBEDDER_PROTOCOL}\0${m.modelId}\0${model.sha256}\0${m.wasmSha256}`).digest("hex").slice(0, 16);
|
|
31685
31882
|
}
|
|
31686
|
-
var embedderRunDir = (home = homedir3()) =>
|
|
31883
|
+
var embedderRunDir = (home = homedir3()) => join10(home, ".securecoms", "run", "embedder");
|
|
31687
31884
|
var embedderPaths = (dir, key) => ({
|
|
31688
|
-
sock:
|
|
31689
|
-
token:
|
|
31690
|
-
lock:
|
|
31691
|
-
status:
|
|
31692
|
-
pid:
|
|
31885
|
+
sock: join10(dir, `${key}.sock`),
|
|
31886
|
+
token: join10(dir, `${key}.token`),
|
|
31887
|
+
lock: join10(dir, `${key}.spawn.lock`),
|
|
31888
|
+
status: join10(dir, `${key}.status`),
|
|
31889
|
+
pid: join10(dir, `${key}.pid`)
|
|
31693
31890
|
// E1 delta H1: the instance's lifetime lock
|
|
31694
31891
|
});
|
|
31695
31892
|
var SOCKET_PATH_MAX = 103;
|
|
31696
31893
|
var windowsRunDir = (env = process.env) => {
|
|
31697
31894
|
if (!env.LOCALAPPDATA) throw new Error("LOCALAPPDATA unset");
|
|
31698
|
-
return
|
|
31895
|
+
return join10(env.LOCALAPPDATA, "SecureComs", "run", "embedder");
|
|
31699
31896
|
};
|
|
31700
31897
|
|
|
31701
31898
|
// src/memory/embedder/spawn.ts
|
|
31702
31899
|
import { spawn } from "node:child_process";
|
|
31703
|
-
import { existsSync as existsSync2, mkdirSync as
|
|
31900
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync4, realpathSync as realpathSync2 } from "node:fs";
|
|
31704
31901
|
|
|
31705
31902
|
// src/memory/embedder/run-dir.ts
|
|
31706
|
-
import { existsSync, lstatSync as
|
|
31707
|
-
import { dirname as
|
|
31903
|
+
import { existsSync, lstatSync as lstatSync4, mkdirSync as mkdirSync3 } from "node:fs";
|
|
31904
|
+
import { dirname as dirname3 } from "node:path";
|
|
31708
31905
|
var RunDirRefused = class extends Error {
|
|
31709
31906
|
};
|
|
31710
|
-
function ensureRunDir(dir, uid = process.getuid?.() ?? -1, lstat =
|
|
31711
|
-
const parent3 =
|
|
31712
|
-
if (!existsSync(parent3))
|
|
31907
|
+
function ensureRunDir(dir, uid = process.getuid?.() ?? -1, lstat = lstatSync4) {
|
|
31908
|
+
const parent3 = dirname3(dir);
|
|
31909
|
+
if (!existsSync(parent3)) mkdirSync3(parent3, { recursive: true, mode: 448 });
|
|
31713
31910
|
const ps = lstat(parent3);
|
|
31714
31911
|
if (ps.isSymbolicLink() || !ps.isDirectory()) throw new RunDirRefused("run directory parent is not a real directory");
|
|
31715
31912
|
if (ps.uid !== uid) throw new RunDirRefused("run directory parent is not owned by this user");
|
|
31716
31913
|
if ((ps.mode & 18) !== 0) throw new RunDirRefused("run directory parent is group or world writable");
|
|
31717
31914
|
try {
|
|
31718
|
-
|
|
31915
|
+
mkdirSync3(dir, { mode: 448 });
|
|
31719
31916
|
} catch (e) {
|
|
31720
31917
|
if (e.code !== "EEXIST") throw e;
|
|
31721
31918
|
}
|
|
@@ -31726,10 +31923,10 @@ function ensureRunDir(dir, uid = process.getuid?.() ?? -1, lstat = lstatSync3) {
|
|
|
31726
31923
|
}
|
|
31727
31924
|
|
|
31728
31925
|
// src/memory/embedder/spawn.ts
|
|
31729
|
-
import { basename as
|
|
31926
|
+
import { basename as basename4, dirname as dirname4, join as join11 } from "node:path";
|
|
31730
31927
|
|
|
31731
31928
|
// src/memory/embedder/locks.ts
|
|
31732
|
-
import { closeSync as closeSync2, constants as
|
|
31929
|
+
import { closeSync as closeSync2, constants as constants3, fstatSync as fstatSync2, linkSync, openSync as openSync2, readFileSync as readFileSync5, renameSync as renameSync2, rmSync as rmSync4, writeSync } from "node:fs";
|
|
31733
31930
|
import { randomBytes as randomBytes9 } from "node:crypto";
|
|
31734
31931
|
import { uptime } from "node:os";
|
|
31735
31932
|
var LOCK_STALE_MS = 1e4;
|
|
@@ -31751,7 +31948,7 @@ var PROCESS_LOCK_ENV = {
|
|
|
31751
31948
|
function inspect(path) {
|
|
31752
31949
|
let fd;
|
|
31753
31950
|
try {
|
|
31754
|
-
fd = openSync2(path,
|
|
31951
|
+
fd = openSync2(path, constants3.O_RDONLY | constants3.O_NOFOLLOW);
|
|
31755
31952
|
} catch {
|
|
31756
31953
|
return null;
|
|
31757
31954
|
}
|
|
@@ -31775,7 +31972,7 @@ function takeLock(path, now = Date.now, env = PROCESS_LOCK_ENV) {
|
|
|
31775
31972
|
const id = `${env.pid} ${env.bootTime()} ${env.startTime()} ${randomBytes9(8).toString("hex")}`;
|
|
31776
31973
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
31777
31974
|
try {
|
|
31778
|
-
const fd = openSync2(path,
|
|
31975
|
+
const fd = openSync2(path, constants3.O_CREAT | constants3.O_EXCL | constants3.O_WRONLY, 384);
|
|
31779
31976
|
try {
|
|
31780
31977
|
writeSync(fd, id);
|
|
31781
31978
|
} finally {
|
|
@@ -31836,7 +32033,7 @@ function embedderEnv(env = process.env, platform = process.platform) {
|
|
|
31836
32033
|
}
|
|
31837
32034
|
var withReal = (p) => {
|
|
31838
32035
|
try {
|
|
31839
|
-
const r =
|
|
32036
|
+
const r = realpathSync2(p);
|
|
31840
32037
|
return r === p ? [p] : [p, r];
|
|
31841
32038
|
} catch {
|
|
31842
32039
|
return [p];
|
|
@@ -31844,10 +32041,10 @@ var withReal = (p) => {
|
|
|
31844
32041
|
};
|
|
31845
32042
|
var realish = (p) => {
|
|
31846
32043
|
try {
|
|
31847
|
-
return
|
|
32044
|
+
return realpathSync2(p);
|
|
31848
32045
|
} catch {
|
|
31849
|
-
const up =
|
|
31850
|
-
return up === p ? p :
|
|
32046
|
+
const up = dirname4(p);
|
|
32047
|
+
return up === p ? p : join11(realish(up), basename4(p));
|
|
31851
32048
|
}
|
|
31852
32049
|
};
|
|
31853
32050
|
var both = (p) => {
|
|
@@ -31859,7 +32056,7 @@ function spawnEmbedder(o) {
|
|
|
31859
32056
|
const run = o.runDir ?? embedderRunDir(o.home);
|
|
31860
32057
|
const win = (o.platform ?? process.platform) === "win32";
|
|
31861
32058
|
if (o.createRunDir !== false) {
|
|
31862
|
-
if (win)
|
|
32059
|
+
if (win) mkdirSync4(run, { recursive: true, mode: 448 });
|
|
31863
32060
|
else ensureRunDir(run);
|
|
31864
32061
|
}
|
|
31865
32062
|
const perm = permissionFlags();
|
|
@@ -31868,13 +32065,13 @@ function spawnEmbedder(o) {
|
|
|
31868
32065
|
o.onNoPermissionModel?.();
|
|
31869
32066
|
}
|
|
31870
32067
|
const reads = perm.length === 0 ? [] : [
|
|
31871
|
-
...withReal(
|
|
32068
|
+
...withReal(dirname4(o.distDir)),
|
|
31872
32069
|
...existsSync2(defaultModelsRoot(o.home)) ? withReal(defaultModelsRoot(o.home)) : [defaultModelsRoot(o.home)],
|
|
31873
|
-
...both(
|
|
31874
|
-
...win ? both(
|
|
32070
|
+
...both(dirname4(run)),
|
|
32071
|
+
...win ? both(join11(o.home, ".securecoms", "run")) : []
|
|
31875
32072
|
// the voice-call heartbeats (the run dir lives elsewhere)
|
|
31876
32073
|
];
|
|
31877
|
-
const script = withReal(
|
|
32074
|
+
const script = withReal(join11(o.distDir, "embedder.js")).at(-1);
|
|
31878
32075
|
const net = (o.nodeFlags ?? process.allowedNodeEnvironmentFlags).has("--allow-net") ? ["--allow-net"] : [];
|
|
31879
32076
|
const flags = perm.length === 0 ? [] : [...perm, ...reads.map((r) => `--allow-fs-read=${r}`), ...both(run).map((w) => `--allow-fs-write=${w}`), "--allow-worker", ...net];
|
|
31880
32077
|
const child = (o.spawnFn ?? spawn)(
|
|
@@ -31883,7 +32080,7 @@ function spawnEmbedder(o) {
|
|
|
31883
32080
|
// 11c review LOW 3, fixed by the Task 12 Mini gate: cwd is the run directory's PARENT, which the bridge created (0700)
|
|
31884
32081
|
// before spawning; the run directory itself is created by the embedder, so on a first start it does not exist yet and
|
|
31885
32082
|
// a cwd there fails the spawn with ENOENT. Never the bridge's working directory.
|
|
31886
|
-
{ detached: true, stdio: "ignore", windowsHide: true, cwd:
|
|
32083
|
+
{ detached: true, stdio: "ignore", windowsHide: true, cwd: dirname4(run), env: win ? { ...embedderEnv(process.env, "win32"), USERPROFILE: o.home, LOCALAPPDATA: dirname4(dirname4(dirname4(run))) } : { ...embedderEnv(), HOME: o.home } }
|
|
31887
32084
|
);
|
|
31888
32085
|
child.unref();
|
|
31889
32086
|
child.on("error", () => {
|
|
@@ -31895,8 +32092,8 @@ function spawnEmbedder(o) {
|
|
|
31895
32092
|
import { execFile } from "node:child_process";
|
|
31896
32093
|
import { promisify } from "node:util";
|
|
31897
32094
|
import { randomBytes as randomBytes10 } from "node:crypto";
|
|
31898
|
-
import { lstatSync as
|
|
31899
|
-
import { join as
|
|
32095
|
+
import { lstatSync as lstatSync5, readFileSync as readFileSync6, rmSync as rmSync5 } from "node:fs";
|
|
32096
|
+
import { join as join12, win32 } from "node:path";
|
|
31900
32097
|
var WINDOWS_SEMANTIC_ENABLED = false;
|
|
31901
32098
|
var windowsSemanticOn = (platform, enabled = WINDOWS_SEMANTIC_ENABLED) => platform !== "win32" || enabled;
|
|
31902
32099
|
function parseSaveSddl(saveFile) {
|
|
@@ -31924,7 +32121,7 @@ function aclAllows(sddl, ownerSid) {
|
|
|
31924
32121
|
if (aces.length === 0 || rest !== "" && !rest.startsWith("S:")) return false;
|
|
31925
32122
|
return aces.every((a) => a[0] === "A" && allowed.has(a[5] ?? "") || a[0] === "D");
|
|
31926
32123
|
}
|
|
31927
|
-
function hasReparsePoint(path, lstat =
|
|
32124
|
+
function hasReparsePoint(path, lstat = lstatSync5) {
|
|
31928
32125
|
const root3 = win32.parse(path).root;
|
|
31929
32126
|
let cur = root3;
|
|
31930
32127
|
for (const part of path.slice(root3.length).split(/[\\/]/).filter(Boolean)) {
|
|
@@ -31943,7 +32140,7 @@ async function currentUserSid(run = defaultRunner) {
|
|
|
31943
32140
|
}
|
|
31944
32141
|
async function verifyRunDirAcl(path, deps = {}, sid) {
|
|
31945
32142
|
const run = deps.run ?? defaultRunner;
|
|
31946
|
-
const out =
|
|
32143
|
+
const out = join12(deps.tmp ?? process.env.TEMP ?? process.env.TMP ?? ".", `sc-acl-${randomBytes10(8).toString("hex")}.txt`);
|
|
31947
32144
|
try {
|
|
31948
32145
|
if (hasReparsePoint(path, deps.lstat)) return false;
|
|
31949
32146
|
const user = await (sid ? sid() : currentUserSid(run));
|
|
@@ -32024,12 +32221,12 @@ var EmbedderClient = class {
|
|
|
32024
32221
|
ready(ms) {
|
|
32025
32222
|
if (this.st.state === "ready") return Promise.resolve(true);
|
|
32026
32223
|
if (this.st.state !== "preparing" || !(ms > 0)) return Promise.resolve(false);
|
|
32027
|
-
return new Promise((
|
|
32224
|
+
return new Promise((resolve2) => {
|
|
32028
32225
|
let t;
|
|
32029
32226
|
const w = (ok) => {
|
|
32030
32227
|
clearTimeout(t);
|
|
32031
32228
|
this.readyWaiters.delete(w);
|
|
32032
|
-
|
|
32229
|
+
resolve2(ok);
|
|
32033
32230
|
};
|
|
32034
32231
|
this.readyWaiters.add(w);
|
|
32035
32232
|
t = setTimeout(() => w(false), ms);
|
|
@@ -32138,7 +32335,7 @@ var EmbedderClient = class {
|
|
|
32138
32335
|
const p = this.paths();
|
|
32139
32336
|
if (!this.tcp() && Buffer.byteLength(`${p.sock}.00000000`) > SOCKET_PATH_MAX) return this.fail("run_dir_refused");
|
|
32140
32337
|
if (this.tcp()) {
|
|
32141
|
-
|
|
32338
|
+
mkdirSync5(this.runDir(), { recursive: true, mode: 448 });
|
|
32142
32339
|
if (!await this.aclOk(true)) return this.fail("acl_unverified");
|
|
32143
32340
|
}
|
|
32144
32341
|
if (!this.tcp()) {
|
|
@@ -32274,7 +32471,7 @@ var EmbedderClient = class {
|
|
|
32274
32471
|
}
|
|
32275
32472
|
if (this.closed) return false;
|
|
32276
32473
|
}
|
|
32277
|
-
return new Promise((
|
|
32474
|
+
return new Promise((resolve2) => {
|
|
32278
32475
|
const s = this.tcp() ? connect({ host: "127.0.0.1", port: pub.port }) : connect(p.sock);
|
|
32279
32476
|
const nc = nonce();
|
|
32280
32477
|
let ns = "";
|
|
@@ -32287,7 +32484,7 @@ var EmbedderClient = class {
|
|
|
32287
32484
|
if (!settled) {
|
|
32288
32485
|
settled = true;
|
|
32289
32486
|
clearTimeout(t);
|
|
32290
|
-
|
|
32487
|
+
resolve2(ok);
|
|
32291
32488
|
}
|
|
32292
32489
|
}
|
|
32293
32490
|
const refuse2 = (reason) => {
|
|
@@ -32852,12 +33049,12 @@ var ReembedQueue = class {
|
|
|
32852
33049
|
};
|
|
32853
33050
|
|
|
32854
33051
|
// src/memory/recall/call-heartbeat.ts
|
|
32855
|
-
import { closeSync as closeSync3, constants as
|
|
32856
|
-
import { join as
|
|
33052
|
+
import { closeSync as closeSync3, constants as constants4, fstatSync as fstatSync3, futimesSync as futimesSync2, openSync as openSync3, rmSync as rmSync7 } from "node:fs";
|
|
33053
|
+
import { join as join14 } from "node:path";
|
|
32857
33054
|
|
|
32858
33055
|
// src/memory/embedder/heartbeats.ts
|
|
32859
|
-
import { lstatSync as
|
|
32860
|
-
import { join as
|
|
33056
|
+
import { lstatSync as lstatSync6, readdirSync as readdirSync3 } from "node:fs";
|
|
33057
|
+
import { join as join13 } from "node:path";
|
|
32861
33058
|
var HEARTBEAT_FUTURE_SKEW_MS = 6e4;
|
|
32862
33059
|
var HEARTBEAT_STALE_MS = 3e4;
|
|
32863
33060
|
function heartbeatAgeFresh(ageMs, staleMs = HEARTBEAT_STALE_MS) {
|
|
@@ -32872,7 +33069,7 @@ function anyHeartbeatFresh(dir, now, staleMs = HEARTBEAT_STALE_MS) {
|
|
|
32872
33069
|
}
|
|
32873
33070
|
return names.some((n) => {
|
|
32874
33071
|
try {
|
|
32875
|
-
const st =
|
|
33072
|
+
const st = lstatSync6(join13(dir, n));
|
|
32876
33073
|
return st.isFile() && heartbeatAgeFresh(now - st.mtimeMs, staleMs);
|
|
32877
33074
|
} catch {
|
|
32878
33075
|
return false;
|
|
@@ -32938,7 +33135,7 @@ var CallHeartbeat = class {
|
|
|
32938
33135
|
if (dir === null) return;
|
|
32939
33136
|
let fd;
|
|
32940
33137
|
try {
|
|
32941
|
-
fd = openSync3(
|
|
33138
|
+
fd = openSync3(join14(dir, this.identityId), constants4.O_WRONLY | constants4.O_CREAT | constants4.O_NOFOLLOW | constants4.O_NONBLOCK, 384);
|
|
32942
33139
|
if (!fstatSync3(fd).isFile()) return;
|
|
32943
33140
|
const t = new Date(this.now());
|
|
32944
33141
|
futimesSync2(fd, t, t);
|
|
@@ -32953,7 +33150,7 @@ var CallHeartbeat = class {
|
|
|
32953
33150
|
const dir = SAFE_ID2.test(this.identityId) ? this.dirOk() : null;
|
|
32954
33151
|
if (dir === null) return;
|
|
32955
33152
|
try {
|
|
32956
|
-
rmSync7(
|
|
33153
|
+
rmSync7(join14(dir, this.identityId), { force: true });
|
|
32957
33154
|
this.wrote = false;
|
|
32958
33155
|
} catch (e) {
|
|
32959
33156
|
this.log("warn", "memory.call_heartbeat_failed", {}, e);
|
|
@@ -33016,9 +33213,9 @@ function queryTokenMacs(key, text, max = QUERY_MACS_MAX) {
|
|
|
33016
33213
|
|
|
33017
33214
|
// src/memory/unified/ranking-client.ts
|
|
33018
33215
|
import { Worker } from "node:worker_threads";
|
|
33019
|
-
import { join as
|
|
33216
|
+
import { join as join15 } from "node:path";
|
|
33020
33217
|
function workerTransport(init, distDir) {
|
|
33021
|
-
const w = new Worker(
|
|
33218
|
+
const w = new Worker(join15(distDir, "memory-worker.js"), { workerData: init });
|
|
33022
33219
|
return {
|
|
33023
33220
|
post: (m) => w.postMessage(m),
|
|
33024
33221
|
onMessage: (cb) => {
|
|
@@ -33078,15 +33275,15 @@ var RankingClient = class {
|
|
|
33078
33275
|
ask(build, deadlineAt) {
|
|
33079
33276
|
const id = ++this.seq;
|
|
33080
33277
|
if (this.closed) return Promise.resolve("failed");
|
|
33081
|
-
return new Promise((
|
|
33278
|
+
return new Promise((resolve2) => {
|
|
33082
33279
|
const timer = setTimeout(() => {
|
|
33083
33280
|
this.waiting.delete(id);
|
|
33084
33281
|
this.misses++;
|
|
33085
|
-
|
|
33282
|
+
resolve2("late");
|
|
33086
33283
|
}, Math.max(0, deadlineAt - Date.now()));
|
|
33087
33284
|
this.waiting.set(id, { resolve: (m) => {
|
|
33088
33285
|
clearTimeout(timer);
|
|
33089
|
-
|
|
33286
|
+
resolve2(m);
|
|
33090
33287
|
} });
|
|
33091
33288
|
try {
|
|
33092
33289
|
this.transport().post(build(id));
|
|
@@ -33095,7 +33292,7 @@ var RankingClient = class {
|
|
|
33095
33292
|
this.waiting.delete(id);
|
|
33096
33293
|
this.misses++;
|
|
33097
33294
|
(this.d.log ?? writeServiceLog2)("warn", "memory.ranking_worker_spawn_failed", void 0, e);
|
|
33098
|
-
|
|
33295
|
+
resolve2("failed");
|
|
33099
33296
|
}
|
|
33100
33297
|
});
|
|
33101
33298
|
}
|
|
@@ -34689,17 +34886,17 @@ async function runSummarize(opts) {
|
|
|
34689
34886
|
// src/service/runtime-config-dir.ts
|
|
34690
34887
|
import { createHash as createHash5, randomUUID } from "node:crypto";
|
|
34691
34888
|
import {
|
|
34692
|
-
chmodSync as
|
|
34889
|
+
chmodSync as chmodSync5,
|
|
34693
34890
|
existsSync as existsSync4,
|
|
34694
|
-
lstatSync as
|
|
34695
|
-
mkdirSync as
|
|
34891
|
+
lstatSync as lstatSync7,
|
|
34892
|
+
mkdirSync as mkdirSync6,
|
|
34696
34893
|
readFileSync as readFileSync8,
|
|
34697
34894
|
renameSync as renameSync3,
|
|
34698
34895
|
statSync as statSync3,
|
|
34699
34896
|
unlinkSync,
|
|
34700
34897
|
writeFileSync
|
|
34701
34898
|
} from "node:fs";
|
|
34702
|
-
import { basename as
|
|
34899
|
+
import { basename as basename5, isAbsolute, join as join16 } from "node:path";
|
|
34703
34900
|
|
|
34704
34901
|
// src/grants/search-providers.ts
|
|
34705
34902
|
var SEARCH_PROVIDERS = /* @__PURE__ */ new Set(["brave", "tavily", "parallel", "exa", "firecrawl"]);
|
|
@@ -34711,18 +34908,18 @@ function isSearchProvider(value) {
|
|
|
34711
34908
|
// src/service/runtime-config-dir.ts
|
|
34712
34909
|
var MEMORY_FILE = "MEMORY.md";
|
|
34713
34910
|
var REAL_FS = {
|
|
34714
|
-
mkdir: (p, mode) =>
|
|
34715
|
-
chmod: (p, mode) =>
|
|
34911
|
+
mkdir: (p, mode) => mkdirSync6(p, { recursive: true, mode }),
|
|
34912
|
+
chmod: (p, mode) => chmodSync5(p, mode),
|
|
34716
34913
|
writeFile: (p, data, mode) => writeFileSync(p, data, { mode }),
|
|
34717
34914
|
rename: (a, b) => renameSync3(a, b),
|
|
34718
34915
|
readFile: (p) => readFileSync8(p, "utf8"),
|
|
34719
34916
|
unlink: (p) => unlinkSync(p)
|
|
34720
34917
|
};
|
|
34721
34918
|
function runtimeConfigDir(home, identityId) {
|
|
34722
|
-
return
|
|
34919
|
+
return join16(home, ".securecoms", "runtime-config", identityId);
|
|
34723
34920
|
}
|
|
34724
34921
|
function assertManagedName(name) {
|
|
34725
|
-
if (!name || name.includes("/") || name.includes("\\") || name === "." || name === ".." || name !==
|
|
34922
|
+
if (!name || name.includes("/") || name.includes("\\") || name === "." || name === ".." || name !== basename5(name)) {
|
|
34726
34923
|
throw new Error(`refusing managed config file name ${JSON.stringify(name)}: not a plain filename`);
|
|
34727
34924
|
}
|
|
34728
34925
|
if (isLocalOnlyName(name)) {
|
|
@@ -34736,19 +34933,19 @@ function isLocalOnlyName(name) {
|
|
|
34736
34933
|
var MANIFEST_POLICY_FILE = "manifest-policy.json";
|
|
34737
34934
|
function removeManagedLlmSource(home, identityId, deps = REAL_FS) {
|
|
34738
34935
|
try {
|
|
34739
|
-
deps.unlink(
|
|
34936
|
+
deps.unlink(join16(runtimeConfigDir(home, identityId), "llm-source.json"));
|
|
34740
34937
|
} catch (e) {
|
|
34741
34938
|
if (e?.code !== "ENOENT") throw e;
|
|
34742
34939
|
}
|
|
34743
34940
|
}
|
|
34744
34941
|
function writeManifestPolicy(home, identityId, policy, deps = REAL_FS) {
|
|
34745
|
-
const parent3 =
|
|
34942
|
+
const parent3 = join16(home, ".securecoms", "runtime-config");
|
|
34746
34943
|
const dir = runtimeConfigDir(home, identityId);
|
|
34747
|
-
for (const d of [
|
|
34944
|
+
for (const d of [join16(home, ".securecoms"), parent3, dir]) {
|
|
34748
34945
|
deps.mkdir(d, 448);
|
|
34749
34946
|
deps.chmod(d, 448);
|
|
34750
34947
|
}
|
|
34751
|
-
atomicWrite(
|
|
34948
|
+
atomicWrite(join16(dir, MANIFEST_POLICY_FILE), JSON.stringify(policy) + "\n", deps);
|
|
34752
34949
|
}
|
|
34753
34950
|
function atomicWrite(path, body, deps) {
|
|
34754
34951
|
const tmp = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
@@ -34767,13 +34964,13 @@ function atomicWrite(path, body, deps) {
|
|
|
34767
34964
|
function writeManagedFiles(home, identityId, files, deps = REAL_FS) {
|
|
34768
34965
|
const entries = Object.entries(files);
|
|
34769
34966
|
for (const [name] of entries) assertManagedName(name);
|
|
34770
|
-
const parent3 =
|
|
34967
|
+
const parent3 = join16(home, ".securecoms", "runtime-config");
|
|
34771
34968
|
const dir = runtimeConfigDir(home, identityId);
|
|
34772
|
-
for (const d of [
|
|
34969
|
+
for (const d of [join16(home, ".securecoms"), parent3, dir]) {
|
|
34773
34970
|
deps.mkdir(d, 448);
|
|
34774
34971
|
deps.chmod(d, 448);
|
|
34775
34972
|
}
|
|
34776
|
-
for (const [name, body] of entries) atomicWrite(
|
|
34973
|
+
for (const [name, body] of entries) atomicWrite(join16(dir, name), body, deps);
|
|
34777
34974
|
}
|
|
34778
34975
|
function checkConfiguredPaths(skillsJson, statFn = (p) => {
|
|
34779
34976
|
const s = statSync3(p);
|
|
@@ -34789,6 +34986,8 @@ function checkConfiguredPaths(skillsJson, statFn = (p) => {
|
|
|
34789
34986
|
}
|
|
34790
34987
|
const search = skillsJson.search;
|
|
34791
34988
|
if (search?.keyFile) paths.push(search.keyFile);
|
|
34989
|
+
const weather = skillsJson.weather;
|
|
34990
|
+
if (weather?.keyFile) paths.push(weather.keyFile);
|
|
34792
34991
|
return paths.map((path) => {
|
|
34793
34992
|
let st;
|
|
34794
34993
|
try {
|
|
@@ -34814,7 +35013,7 @@ function parseSecretPathsReport(raw) {
|
|
|
34814
35013
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
34815
35014
|
const r = raw;
|
|
34816
35015
|
const f = r.outside_by_field;
|
|
34817
|
-
if (r.mode !== "report" && r.mode !== "enforce" || !isCount(r.inside) || !isCount(r.allowlisted) || !isCount(r.outside) || !f || typeof f !== "object" || Array.isArray(f) || !isCount(f.headers_file) || !isCount(f.postgres_url_file) || !isCount(f.search_key_file) || f.sqlite !== void 0 && !isCount(f.sqlite)) return void 0;
|
|
35016
|
+
if (r.mode !== "report" && r.mode !== "enforce" || !isCount(r.inside) || !isCount(r.allowlisted) || !isCount(r.outside) || !f || typeof f !== "object" || Array.isArray(f) || !isCount(f.headers_file) || !isCount(f.postgres_url_file) || !isCount(f.search_key_file) || f.sqlite !== void 0 && !isCount(f.sqlite) || f.weather_key_file !== void 0 && !isCount(f.weather_key_file)) return void 0;
|
|
34818
35017
|
return {
|
|
34819
35018
|
mode: r.mode,
|
|
34820
35019
|
inside: r.inside,
|
|
@@ -34824,7 +35023,8 @@ function parseSecretPathsReport(raw) {
|
|
|
34824
35023
|
headers_file: f.headers_file,
|
|
34825
35024
|
postgres_url_file: f.postgres_url_file,
|
|
34826
35025
|
search_key_file: f.search_key_file,
|
|
34827
|
-
...f.sqlite !== void 0 ? { sqlite: f.sqlite } : {}
|
|
35026
|
+
...f.sqlite !== void 0 ? { sqlite: f.sqlite } : {},
|
|
35027
|
+
...f.weather_key_file !== void 0 ? { weather_key_file: f.weather_key_file } : {}
|
|
34828
35028
|
}
|
|
34829
35029
|
};
|
|
34830
35030
|
}
|
|
@@ -34849,7 +35049,7 @@ function localLegacySources(doc) {
|
|
|
34849
35049
|
const channelId = source.channelId;
|
|
34850
35050
|
if (kind2 !== "global-memory" && kind2 !== "conversation-history" && kind2 !== "captured-memory" || typeof path !== "string" || !isAbsolute(path) || channelId !== void 0 && typeof channelId !== "string") continue;
|
|
34851
35051
|
try {
|
|
34852
|
-
const st =
|
|
35052
|
+
const st = lstatSync7(path);
|
|
34853
35053
|
if (!st.isFile() || st.isSymbolicLink()) continue;
|
|
34854
35054
|
if (st.size <= LEGACY_MEMORY_SOURCE_MAX_BYTES && st.size > contentBudget) continue;
|
|
34855
35055
|
const oversize = st.size > LEGACY_MEMORY_SOURCE_MAX_BYTES;
|
|
@@ -34877,7 +35077,7 @@ function localLegacySources(doc) {
|
|
|
34877
35077
|
return out;
|
|
34878
35078
|
}
|
|
34879
35079
|
function readLocalReport(home, identityId) {
|
|
34880
|
-
const p =
|
|
35080
|
+
const p = join16(runtimeConfigDir(home, identityId), LOCAL_REPORT_FILE);
|
|
34881
35081
|
if (!existsSync4(p)) return null;
|
|
34882
35082
|
let doc;
|
|
34883
35083
|
try {
|
|
@@ -34904,6 +35104,7 @@ function readLocalReport(home, identityId) {
|
|
|
34904
35104
|
has_search_key: hasSearchKey,
|
|
34905
35105
|
has_llm_key: hasLlmKey,
|
|
34906
35106
|
has_extract_provider: doc.has_extract_provider === true,
|
|
35107
|
+
has_weather_key: doc.has_weather_key === true,
|
|
34907
35108
|
...ownerDmCap !== void 0 ? { owner_dm_search_cap_effective: ownerDmCap } : {},
|
|
34908
35109
|
legacy_memory: localLegacySources(doc).map(({ path: _path, content: _content, ...source }) => source),
|
|
34909
35110
|
secret_paths: parseSecretPathsReport(doc.secret_paths)
|
|
@@ -34911,7 +35112,7 @@ function readLocalReport(home, identityId) {
|
|
|
34911
35112
|
}
|
|
34912
35113
|
function deleteLegacyMemorySources(home, identityId, ids) {
|
|
34913
35114
|
if (ids.length === 0) return 0;
|
|
34914
|
-
const p =
|
|
35115
|
+
const p = join16(runtimeConfigDir(home, identityId), LOCAL_REPORT_FILE);
|
|
34915
35116
|
if (!existsSync4(p)) return 0;
|
|
34916
35117
|
let doc;
|
|
34917
35118
|
try {
|
|
@@ -34954,7 +35155,7 @@ async function applyLegacyMemoryFactDecisions(home, identityId, rawDecisions, re
|
|
|
34954
35155
|
group.set(decision.fact_hash, decision);
|
|
34955
35156
|
bySource.set(decision.source_id, group);
|
|
34956
35157
|
}
|
|
34957
|
-
const reportPath =
|
|
35158
|
+
const reportPath = join16(runtimeConfigDir(home, identityId), LOCAL_REPORT_FILE);
|
|
34958
35159
|
if (!existsSync4(reportPath)) return { sourcesDeleted: 0, factsAssigned: 0 };
|
|
34959
35160
|
let doc;
|
|
34960
35161
|
try {
|
|
@@ -34992,7 +35193,7 @@ async function applyLegacyMemoryFactDecisions(home, identityId, rawDecisions, re
|
|
|
34992
35193
|
}
|
|
34993
35194
|
function pruneManagedMemory(home, identityId, hashes, deps = REAL_FS) {
|
|
34994
35195
|
const dir = runtimeConfigDir(home, identityId);
|
|
34995
|
-
const path =
|
|
35196
|
+
const path = join16(dir, MEMORY_FILE);
|
|
34996
35197
|
if (!existsSync4(path)) return 0;
|
|
34997
35198
|
const drop = new Set(hashes);
|
|
34998
35199
|
const before = deps.readFile(path).split("\n");
|
|
@@ -35005,6 +35206,19 @@ function pruneManagedMemory(home, identityId, hashes, deps = REAL_FS) {
|
|
|
35005
35206
|
return removed;
|
|
35006
35207
|
}
|
|
35007
35208
|
|
|
35209
|
+
// src/reply-credit.ts
|
|
35210
|
+
var REPLY_CREDIT_FEATURE = "reply_credit_v1";
|
|
35211
|
+
var WEATHER_CREDIT_LINE = "Weather data: WeatherAPI.com. For general information only.";
|
|
35212
|
+
var WEATHER_CREDIT_SUFFIX = `
|
|
35213
|
+
|
|
35214
|
+
${WEATHER_CREDIT_LINE}`;
|
|
35215
|
+
function isReplyCredit(v) {
|
|
35216
|
+
return v === "weatherapi";
|
|
35217
|
+
}
|
|
35218
|
+
function stripCreditSuffix(text) {
|
|
35219
|
+
return text.endsWith(WEATHER_CREDIT_SUFFIX) ? text.slice(0, text.length - WEATHER_CREDIT_SUFFIX.length) : text;
|
|
35220
|
+
}
|
|
35221
|
+
|
|
35008
35222
|
// src/grants/binding.ts
|
|
35009
35223
|
import { sha256 as sha25611 } from "@noble/hashes/sha256";
|
|
35010
35224
|
|
|
@@ -35012,9 +35226,9 @@ import { sha256 as sha25611 } from "@noble/hashes/sha256";
|
|
|
35012
35226
|
import { ed25519 as ed255195 } from "@noble/curves/ed25519";
|
|
35013
35227
|
|
|
35014
35228
|
// src/grants/pin.ts
|
|
35015
|
-
import { lstatSync as
|
|
35229
|
+
import { lstatSync as lstatSync8, readFileSync as readFileSync9 } from "node:fs";
|
|
35016
35230
|
import { ed25519 as ed255194 } from "@noble/curves/ed25519";
|
|
35017
|
-
import { dirname as
|
|
35231
|
+
import { dirname as dirname5, join as join17 } from "node:path";
|
|
35018
35232
|
var PIN_FILE_NAME = "owner-signing-key.json";
|
|
35019
35233
|
var UUID_LOWER = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
35020
35234
|
var PIN_KEYS = ["key_algo", "owner_identity_id", "public_key"];
|
|
@@ -35044,7 +35258,7 @@ function parsePinObject(v) {
|
|
|
35044
35258
|
pin: Object.freeze({ ownerIdentityId: o.owner_identity_id, keyAlgo: "ed25519", publicKey, publicKeyB64: o.public_key })
|
|
35045
35259
|
};
|
|
35046
35260
|
}
|
|
35047
|
-
var NODE_FS = { lstat: (p) =>
|
|
35261
|
+
var NODE_FS = { lstat: (p) => lstatSync8(p), readFile: (p) => readFileSync9(p, "utf8") };
|
|
35048
35262
|
function fromValue(v) {
|
|
35049
35263
|
const r = parsePinObject(v);
|
|
35050
35264
|
return r.ok ? { kind: "ok", pin: r.pin } : { kind: "bad", state: r.state };
|
|
@@ -35078,7 +35292,7 @@ function samePin(a, b) {
|
|
|
35078
35292
|
}
|
|
35079
35293
|
function loadOwnerGrantPin(opts) {
|
|
35080
35294
|
const fs = opts.fs ?? NODE_FS;
|
|
35081
|
-
const file2 = readPinFile(
|
|
35295
|
+
const file2 = readPinFile(join17(dirname5(opts.credentialPath), PIN_FILE_NAME), fs);
|
|
35082
35296
|
const field = "owner_signing_key" in opts.credentialJson && opts.credentialJson.owner_signing_key !== void 0 ? fromValue(opts.credentialJson.owner_signing_key) : { kind: "absent" };
|
|
35083
35297
|
if (file2.kind === "bad") return { state: file2.state, pin: null };
|
|
35084
35298
|
if (field.kind === "bad") return { state: field.state, pin: null };
|
|
@@ -35093,9 +35307,10 @@ function loadOwnerGrantPin(opts) {
|
|
|
35093
35307
|
// src/grants/manifest.ts
|
|
35094
35308
|
var TOOL_GRANTS_DOMAIN = "SECURECOMS-TOOL-GRANTS-V1:";
|
|
35095
35309
|
var MANIFEST_KNOWN_TOOLS = Object.freeze({
|
|
35096
|
-
owner_dm: Object.freeze(["web_search", "web_extract", "fetch_url", "http_request", "sql_query"]),
|
|
35310
|
+
owner_dm: Object.freeze(["web_search", "web_extract", "fetch_url", "http_request", "sql_query", "weather_forecast", "currency_convert"]),
|
|
35097
35311
|
// web_extract: owner DM only, never team_room (spec §5.4, D5)
|
|
35098
|
-
|
|
35312
|
+
// Weather/currency spec §6.3 (U5): both utility tools in both audiences, each held behind utility_tools_v1 (TOOL_FEATURE).
|
|
35313
|
+
team_room: Object.freeze(["web_search", "weather_forecast", "currency_convert"])
|
|
35099
35314
|
});
|
|
35100
35315
|
function manifestSignedBytes(manifest) {
|
|
35101
35316
|
const domain = new TextEncoder().encode(TOOL_GRANTS_DOMAIN);
|
|
@@ -35419,20 +35634,30 @@ function bindTargets(raw, signed, equals) {
|
|
|
35419
35634
|
}
|
|
35420
35635
|
return { written, mismatches };
|
|
35421
35636
|
}
|
|
35422
|
-
|
|
35423
|
-
|
|
35637
|
+
var NO_FEATURES = Object.freeze(/* @__PURE__ */ new Set());
|
|
35638
|
+
var TOOL_FEATURE = Object.freeze({
|
|
35639
|
+
web_extract: "search_providers_v2",
|
|
35640
|
+
weather_forecast: "utility_tools_v1",
|
|
35641
|
+
currency_convert: "utility_tools_v1"
|
|
35642
|
+
});
|
|
35643
|
+
function toolAllowedByFeatures(tool, features) {
|
|
35644
|
+
if (!Object.hasOwn(TOOL_FEATURE, tool)) return true;
|
|
35645
|
+
return features.has(TOOL_FEATURE[tool]);
|
|
35646
|
+
}
|
|
35647
|
+
function signedManifestTools(manifest, audience, features) {
|
|
35648
|
+
return MANIFEST_KNOWN_TOOLS[audience].filter((t) => (manifest.grants[audience] ?? []).includes(t) && toolAllowedByFeatures(t, features));
|
|
35424
35649
|
}
|
|
35425
|
-
function manifestPolicyFor(manifest, switches,
|
|
35650
|
+
function manifestPolicyFor(manifest, switches, features) {
|
|
35426
35651
|
return {
|
|
35427
35652
|
v: 1,
|
|
35428
35653
|
manifest_tools: [.../* @__PURE__ */ new Set([
|
|
35429
|
-
...switches.ownerDm ? signedManifestTools(manifest, "owner_dm",
|
|
35430
|
-
...switches.room ? signedManifestTools(manifest, "team_room",
|
|
35654
|
+
...switches.ownerDm ? signedManifestTools(manifest, "owner_dm", features) : [],
|
|
35655
|
+
...switches.room ? signedManifestTools(manifest, "team_room", features) : []
|
|
35431
35656
|
])].sort(),
|
|
35432
35657
|
secret_path_mode: "enforce"
|
|
35433
35658
|
};
|
|
35434
35659
|
}
|
|
35435
|
-
function bindRuntimeConfig(manifest, payload, prior, switches = { ownerDm: true, room: true },
|
|
35660
|
+
function bindRuntimeConfig(manifest, payload, prior, switches = { ownerDm: true, room: true }, features = NO_FEATURES) {
|
|
35436
35661
|
const mismatches = /* @__PURE__ */ new Set();
|
|
35437
35662
|
const cfg = manifest.config;
|
|
35438
35663
|
const out = {};
|
|
@@ -35461,7 +35686,8 @@ function bindRuntimeConfig(manifest, payload, prior, switches = { ownerDm: true,
|
|
|
35461
35686
|
for (const name of Object.keys(files)) {
|
|
35462
35687
|
if (name !== LLM_SOURCE && !PERSONA_FILES.includes(name)) mismatches.add("files");
|
|
35463
35688
|
}
|
|
35464
|
-
const ownerDmTools = signedManifestTools(manifest, "owner_dm",
|
|
35689
|
+
const ownerDmTools = signedManifestTools(manifest, "owner_dm", features);
|
|
35690
|
+
const ownerDmSearchTools = ownerDmTools.filter((t) => t === "web_search" || t === "web_extract");
|
|
35465
35691
|
const sj = isObj2(payload.skills_json) ? payload.skills_json : void 0;
|
|
35466
35692
|
const eps = bindTargets(sj?.endpoints, manifest.endpoints, endpointEquals);
|
|
35467
35693
|
const dbs = bindTargets(sj?.databases, manifest.databases, databaseEquals);
|
|
@@ -35482,17 +35708,20 @@ function bindRuntimeConfig(manifest, payload, prior, switches = { ownerDm: true,
|
|
|
35482
35708
|
const usablePrior = cfg.search !== void 0 && prior.search?.provider === cfg.search.provider ? prior.search : void 0;
|
|
35483
35709
|
searchBlock = cfg.search === void 0 ? void 0 : usablePrior ?? Object.freeze({ provider: cfg.search.provider });
|
|
35484
35710
|
}
|
|
35711
|
+
const pw = isObj2(sj?.weather) ? sj.weather : void 0;
|
|
35712
|
+
const weatherBlock = typeof pw?.keyFile === "string" && pw.keyFile !== "" ? { keyFile: pw.keyFile } : void 0;
|
|
35485
35713
|
const skillsDoc = {
|
|
35486
35714
|
endpoints: eps.written,
|
|
35487
35715
|
databases: dbs.written,
|
|
35488
35716
|
flags: [...cfg.flags],
|
|
35489
35717
|
...deny !== void 0 || cfg.tools_allow !== void 0 ? { tools: { ...deny !== void 0 ? { deny } : {}, ...cfg.tools_allow !== void 0 ? { allow: [...cfg.tools_allow] } : {} } } : {},
|
|
35490
35718
|
...searchBlock ? { search: searchBlock } : {},
|
|
35719
|
+
...weatherBlock ? { weather: weatherBlock } : {},
|
|
35491
35720
|
// D31: the marker the runtime keys D24 (iii) on; written under every manifest, true when config.search is signed.
|
|
35492
35721
|
search_signed: cfg.search !== void 0,
|
|
35493
35722
|
// D36, D39: the signed owner-DM cap, only when the owner-DM switch is on and the manifest grants an owner-DM tool;
|
|
35494
35723
|
// otherwise none, so the runtime clamps a signed raise to the default. Never read from the payload.
|
|
35495
|
-
...cfg.search?.owner_dm_daily_cap !== void 0 && switches.ownerDm &&
|
|
35724
|
+
...cfg.search?.owner_dm_daily_cap !== void 0 && switches.ownerDm && ownerDmSearchTools.length > 0 ? { owner_dm_search_cap: cfg.search.owner_dm_daily_cap } : {}
|
|
35496
35725
|
};
|
|
35497
35726
|
out["skills.json"] = JSON.stringify(skillsDoc, null, 2) + "\n";
|
|
35498
35727
|
const signedMemory = { mode: cfg.memory.mode, ...cfg.memory.recall_days !== void 0 ? { recall_days: cfg.memory.recall_days } : {} };
|
|
@@ -35523,7 +35752,7 @@ function bindRuntimeConfig(manifest, payload, prior, switches = { ownerDm: true,
|
|
|
35523
35752
|
mismatches.add("legacy_assign");
|
|
35524
35753
|
legacyDecisions = decisions.filter((d) => isObj2(d) && d.action === "delete");
|
|
35525
35754
|
}
|
|
35526
|
-
const policy = manifestPolicyFor(manifest, switches,
|
|
35755
|
+
const policy = manifestPolicyFor(manifest, switches, features);
|
|
35527
35756
|
return {
|
|
35528
35757
|
mismatches: CONFIG_BINDING_FIELDS.filter((f) => mismatches.has(f)).sort(),
|
|
35529
35758
|
targetMismatches: { endpoint: eps.mismatches, database: dbs.mismatches },
|
|
@@ -35666,7 +35895,11 @@ var TOOL_ROOM_CLASSES = Object.freeze({
|
|
|
35666
35895
|
// D5: spends the owner's key and sends URLs out
|
|
35667
35896
|
fetch_url: Object.freeze(["egress"]),
|
|
35668
35897
|
sql_query: Object.freeze(["owner_credential"]),
|
|
35669
|
-
http_request: Object.freeze(["owner_credential", "egress", "side_effect"])
|
|
35898
|
+
http_request: Object.freeze(["owner_credential", "egress", "side_effect"]),
|
|
35899
|
+
// Weather/currency (plan audit r1; supersedes spec §8's values): both send room words to an outside host, and weather
|
|
35900
|
+
// spends the owner's key.
|
|
35901
|
+
weather_forecast: Object.freeze(["owner_credential", "egress"]),
|
|
35902
|
+
currency_convert: Object.freeze(["egress"])
|
|
35670
35903
|
});
|
|
35671
35904
|
var LIVE_MEMBER_STATUSES = /* @__PURE__ */ new Set(["active", "add_pending"]);
|
|
35672
35905
|
function isLiveRow(r) {
|
|
@@ -36083,8 +36316,8 @@ function voiceWindowEligible(t) {
|
|
|
36083
36316
|
}
|
|
36084
36317
|
function waitMs(ms) {
|
|
36085
36318
|
if (ms <= 0) return Promise.resolve();
|
|
36086
|
-
return new Promise((
|
|
36087
|
-
const t = setTimeout(
|
|
36319
|
+
return new Promise((resolve2) => {
|
|
36320
|
+
const t = setTimeout(resolve2, ms);
|
|
36088
36321
|
t.unref?.();
|
|
36089
36322
|
});
|
|
36090
36323
|
}
|
|
@@ -36221,7 +36454,7 @@ var FileOwnerAuthorityStore = class {
|
|
|
36221
36454
|
}
|
|
36222
36455
|
async readAll() {
|
|
36223
36456
|
try {
|
|
36224
|
-
return JSON.parse(await
|
|
36457
|
+
return JSON.parse(await fsp2.readFile(this.path, "utf8"));
|
|
36225
36458
|
} catch (e) {
|
|
36226
36459
|
if (e.code === "ENOENT") return {};
|
|
36227
36460
|
throw e;
|
|
@@ -36234,8 +36467,8 @@ var FileOwnerAuthorityStore = class {
|
|
|
36234
36467
|
async save(enclaveId, pin) {
|
|
36235
36468
|
const all = await this.readAll();
|
|
36236
36469
|
all[enclaveId] = pin;
|
|
36237
|
-
await
|
|
36238
|
-
await
|
|
36470
|
+
await fsp2.mkdir(dirname6(this.path), { recursive: true });
|
|
36471
|
+
await fsp2.writeFile(this.path, JSON.stringify(all), "utf8");
|
|
36239
36472
|
}
|
|
36240
36473
|
};
|
|
36241
36474
|
var AgentPausedSendError = class extends Error {
|
|
@@ -36442,7 +36675,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
36442
36675
|
threshold: EMBEDDING_MANIFEST.similarityThreshold,
|
|
36443
36676
|
warm: windowsSemanticOn(this.platform)
|
|
36444
36677
|
};
|
|
36445
|
-
return deps.rankingTransport ? deps.rankingTransport(init) : workerTransport(init,
|
|
36678
|
+
return deps.rankingTransport ? deps.rankingTransport(init) : workerTransport(init, dirname6(fileURLToPath2(import.meta.url)));
|
|
36446
36679
|
}
|
|
36447
36680
|
});
|
|
36448
36681
|
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);
|
|
@@ -36453,7 +36686,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
36453
36686
|
modelId: EMBEDDING_MANIFEST.modelId,
|
|
36454
36687
|
threshold: EMBEDDING_MANIFEST.similarityThreshold
|
|
36455
36688
|
});
|
|
36456
|
-
this.heartbeat = new CallHeartbeat(this.machinePaths ?
|
|
36689
|
+
this.heartbeat = new CallHeartbeat(this.machinePaths ? join18(this.home, ".securecoms", "run", "voice-call") : null, cfg.identityId, VOICE_LIVE_WINDOW_MS);
|
|
36457
36690
|
this.reembed = new ReembedQueue({
|
|
36458
36691
|
store: this.store,
|
|
36459
36692
|
embedder: this.embedder,
|
|
@@ -36512,15 +36745,15 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
36512
36745
|
this.bootDrainWaitMs = deps.bootDrainWaitMs ?? BOOT_DRAIN_WAIT_MS_DEFAULT;
|
|
36513
36746
|
this.searchCapabilityRefetchDebounceMs = deps.searchCapabilityRefetchDebounceMs ?? SEARCH_CAPABILITY_REFETCH_DEBOUNCE_MS;
|
|
36514
36747
|
this.searchCapabilityRefetchMaxWaitMs = deps.searchCapabilityRefetchMaxWaitMs ?? SEARCH_CAPABILITY_REFETCH_MAX_WAIT_MS;
|
|
36515
|
-
this.replySleep = deps.replySleep ?? ((ms) => new Promise((
|
|
36516
|
-
const t = setTimeout(
|
|
36748
|
+
this.replySleep = deps.replySleep ?? ((ms) => new Promise((resolve2) => {
|
|
36749
|
+
const t = setTimeout(resolve2, ms);
|
|
36517
36750
|
t.unref?.();
|
|
36518
36751
|
}));
|
|
36519
36752
|
this.actionRateFloor = new TokenBucketFloor({ capacity: 1, refillPerMinute: 30, now: this.actionClock });
|
|
36520
36753
|
this.globalActionRateFloor = new TokenBucketFloor({ capacity: 1, refillPerMinute: 30, now: this.actionClock });
|
|
36521
36754
|
const self = this;
|
|
36522
36755
|
this.downloadBlobImpl = deps.downloadBlob ?? async function(blobId) {
|
|
36523
|
-
const url = `${cfg.apiBase}/api/v1/attachments/${encodeURIComponent(blobId)}?caller_identity_id=${encodeURIComponent(cfg.identityId)}`;
|
|
36756
|
+
const url = `${cfg.apiBase}/api/v1/attachments/${encodeURIComponent(assertBlobId(blobId))}?caller_identity_id=${encodeURIComponent(cfg.identityId)}`;
|
|
36524
36757
|
const res = await fetchWithDeadline(self.fetchImpl, url, { headers: { authorization: `Bearer ${cfg.apiKey}` } }, {
|
|
36525
36758
|
op: "attachment_download",
|
|
36526
36759
|
timeoutMs: FETCH_DEADLINE_MS.blobBase,
|
|
@@ -36540,8 +36773,9 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
36540
36773
|
const j = await res.json();
|
|
36541
36774
|
return String(j.attachment_id);
|
|
36542
36775
|
};
|
|
36543
|
-
this.attachmentsDirImpl = deps.attachmentsDir ??
|
|
36544
|
-
this.
|
|
36776
|
+
this.attachmentsDirImpl = deps.attachmentsDir ?? join18(dirname6(cfg.storePath), "attachments");
|
|
36777
|
+
this.vaultOutboxDirImpl = deps.vaultOutboxDir !== void 0 ? deps.vaultOutboxDir : cfg.storePath === ":memory:" ? null : join18(dirname6(cfg.storePath), VAULT_OUTBOX_DIRNAME);
|
|
36778
|
+
this.vaultOwnerStoreImpl = deps.vaultOwnerStore ?? new FileOwnerAuthorityStore(join18(dirname6(cfg.storePath), "vault-owner-pins.json"));
|
|
36545
36779
|
this.action = {
|
|
36546
36780
|
begin: (channelId) => this.actionBegin(channelId),
|
|
36547
36781
|
start: (opts) => this.actionStart(opts),
|
|
@@ -37272,6 +37506,9 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
37272
37506
|
uploadBlobImpl;
|
|
37273
37507
|
/** Track 5: directory where received attachments are saved. */
|
|
37274
37508
|
attachmentsDirImpl;
|
|
37509
|
+
vaultOutboxDirImpl;
|
|
37510
|
+
/** Audit 2026-10-30 HIGH 2: the outbox pinned at start(); null = every upload refused. */
|
|
37511
|
+
outboxPin = null;
|
|
37275
37512
|
/** B7: persistence for the vault kekset's owner-pin TOFU state. */
|
|
37276
37513
|
vaultOwnerStoreImpl;
|
|
37277
37514
|
/** B7: last-seen kekset generation per enclave, enforced monotonic across
|
|
@@ -37480,7 +37717,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
37480
37717
|
}
|
|
37481
37718
|
let bound;
|
|
37482
37719
|
try {
|
|
37483
|
-
bound = this.grantWriters.bindRuntimeConfig(manifest, payload, { search: prior }, { ownerDm: this.ownerDmManifestGrants, room: this.roomManifestGrants }, this.
|
|
37720
|
+
bound = this.grantWriters.bindRuntimeConfig(manifest, payload, { search: prior }, { ownerDm: this.ownerDmManifestGrants, room: this.roomManifestGrants }, this.capabilityFeatures());
|
|
37484
37721
|
} catch (e) {
|
|
37485
37722
|
bound = null;
|
|
37486
37723
|
return this.bindFailed(manifest, payload.memory, e);
|
|
@@ -37501,7 +37738,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
37501
37738
|
try {
|
|
37502
37739
|
this.grantWriters.writeManagedFiles(this.home, this.cfg.identityId, bound.files);
|
|
37503
37740
|
if (manifest.config.llm === void 0) this.grantWriters.removeManagedLlmSource(this.home, this.cfg.identityId);
|
|
37504
|
-
this.grantWriters.writeManifestPolicy(this.home, this.cfg.identityId, manifestPolicyFor(manifest, { ownerDm: this.ownerDmManifestGrants, room: this.roomManifestGrants }, this.
|
|
37741
|
+
this.grantWriters.writeManifestPolicy(this.home, this.cfg.identityId, manifestPolicyFor(manifest, { ownerDm: this.ownerDmManifestGrants, room: this.roomManifestGrants }, this.capabilityFeatures()));
|
|
37505
37742
|
} catch (e) {
|
|
37506
37743
|
this.manifestBinding = null;
|
|
37507
37744
|
throw e;
|
|
@@ -37548,13 +37785,14 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
37548
37785
|
}
|
|
37549
37786
|
/** No manifest in force: a manifest-policy.json left by an earlier one is replaced by an empty tool list. */
|
|
37550
37787
|
overwriteStalePolicy() {
|
|
37551
|
-
if (!existsSync5(
|
|
37788
|
+
if (!existsSync5(join18(runtimeConfigDir(this.home, this.cfg.identityId), "manifest-policy.json"))) return;
|
|
37552
37789
|
this.grantWriters.writeManifestPolicy(this.home, this.cfg.identityId, { v: 1, manifest_tools: [], secret_path_mode: "enforce" });
|
|
37553
37790
|
}
|
|
37554
37791
|
/** The report half of refetchRuntimeConfigOnce (split out for the plan 4 Task 11 bind_failed path). */
|
|
37555
37792
|
async postRuntimeConfigReport(base, version, skillsJson) {
|
|
37556
37793
|
const local = readLocalReport(this.home, this.cfg.identityId);
|
|
37557
37794
|
const reportedSearchProviders = this.searchProvidersCapable();
|
|
37795
|
+
const reportedUtilityTools = this.utilityToolsCapable();
|
|
37558
37796
|
const report = {
|
|
37559
37797
|
applied_version: version,
|
|
37560
37798
|
paths: checkConfiguredPaths(skillsJson),
|
|
@@ -37572,6 +37810,9 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
37572
37810
|
// Final review I-2 (controller ruling): AND the current search_providers_v2, so the web never shows extract while
|
|
37573
37811
|
// the bridge withholds web_extract from the runtime.
|
|
37574
37812
|
has_extract_provider: (local?.has_extract_provider ?? false) && reportedSearchProviders,
|
|
37813
|
+
// Weather/currency L-1, §8 item 5: AND the current utility_tools_v1, so the web never shows a usable weather key
|
|
37814
|
+
// while the bridge withholds weather_forecast from the runtime.
|
|
37815
|
+
has_weather_key: (local?.has_weather_key ?? false) && reportedUtilityTools,
|
|
37575
37816
|
// D39: the owner-DM daily cap the runtime enforces, passed through when it reported a valid one; never the signed value.
|
|
37576
37817
|
...local?.owner_dm_search_cap_effective !== void 0 ? { owner_dm_search_cap_effective: local.owner_dm_search_cap_effective } : {},
|
|
37577
37818
|
// Same idea, for the BYOK LLM key file: only the runtime can see
|
|
@@ -37619,7 +37860,9 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
37619
37860
|
// D28 (§11.1 step 3): a separate key, never a value in the capability enum. False with no runtime attached.
|
|
37620
37861
|
search_providers_v2: reportedSearchProviders,
|
|
37621
37862
|
// D19 item 4: content-free; the signed provider is one the attached runtime(s) cannot honour.
|
|
37622
|
-
search_provider_unsupported: this.searchProviderUnsupported(reportedSearchProviders)
|
|
37863
|
+
search_provider_unsupported: this.searchProviderUnsupported(reportedSearchProviders),
|
|
37864
|
+
// Weather/currency §8 item 5: a separate key, false with no runtime attached.
|
|
37865
|
+
utility_tools_v1: reportedUtilityTools
|
|
37623
37866
|
}
|
|
37624
37867
|
};
|
|
37625
37868
|
const reportedVerifyFailures = this.ownerVerifyFailures;
|
|
@@ -37641,6 +37884,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
37641
37884
|
}
|
|
37642
37885
|
this.ownerVerifyFailures = Math.max(0, this.ownerVerifyFailures - reportedVerifyFailures);
|
|
37643
37886
|
this.lastReportedSearchProviders = reportedSearchProviders;
|
|
37887
|
+
this.lastReportedUtilityTools = reportedUtilityTools;
|
|
37644
37888
|
if (version) writeServiceLog2("info", "runtime_config.applied", { version });
|
|
37645
37889
|
else writeServiceLog2("info", "runtime_config.legacy_memory_reported");
|
|
37646
37890
|
}
|
|
@@ -37708,9 +37952,16 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
37708
37952
|
}
|
|
37709
37953
|
async startBody() {
|
|
37710
37954
|
this.startedAt = Date.now();
|
|
37955
|
+
if (this.vaultOutboxDirImpl) {
|
|
37956
|
+
try {
|
|
37957
|
+
this.outboxPin = ensureOutbox(this.vaultOutboxDirImpl);
|
|
37958
|
+
} catch (e) {
|
|
37959
|
+
writeServiceLog2("warn", "vault_outbox.unavailable", {}, e);
|
|
37960
|
+
}
|
|
37961
|
+
}
|
|
37711
37962
|
await this.restoreRuntimeInbox();
|
|
37712
37963
|
await this.loadToolGrantState();
|
|
37713
|
-
this.recomputeManifestPolicy(
|
|
37964
|
+
this.recomputeManifestPolicy(NO_FEATURES);
|
|
37714
37965
|
try {
|
|
37715
37966
|
this.store.pruneRoomSearchCap(utcDayOf(Date.now()));
|
|
37716
37967
|
} catch (e) {
|
|
@@ -37733,8 +37984,8 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
37733
37984
|
const drain = this.ingestOnce().then(() => true, () => true);
|
|
37734
37985
|
const drainedInTime = await Promise.race([
|
|
37735
37986
|
drain,
|
|
37736
|
-
new Promise((
|
|
37737
|
-
const t = setTimeout(() =>
|
|
37987
|
+
new Promise((resolve2) => {
|
|
37988
|
+
const t = setTimeout(() => resolve2(false), this.bootDrainWaitMs);
|
|
37738
37989
|
t.unref?.();
|
|
37739
37990
|
void drain.then(() => clearTimeout(t));
|
|
37740
37991
|
})
|
|
@@ -37744,7 +37995,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
37744
37995
|
await this.bootReconcileKeyPackage();
|
|
37745
37996
|
} else {
|
|
37746
37997
|
writeServiceLog2("warn", "boot.drain_slow", { wait_ms: this.bootDrainWaitMs });
|
|
37747
|
-
const settled = new Promise((
|
|
37998
|
+
const settled = new Promise((resolve2) => this.ingestSettledWaiters.push(resolve2));
|
|
37748
37999
|
this.deferredBootPublish = settled.then(async () => {
|
|
37749
38000
|
this.bootDrainPending = false;
|
|
37750
38001
|
if (this.closingDown) return;
|
|
@@ -38693,13 +38944,13 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
38693
38944
|
* absent, because the id is derived.
|
|
38694
38945
|
*/
|
|
38695
38946
|
async send(channelId, text, messageId, opts = {}) {
|
|
38696
|
-
const body = { kind: "text", text };
|
|
38947
|
+
const body = { kind: "text", text: opts.credit !== void 0 ? text + WEATHER_CREDIT_SUFFIX : text };
|
|
38697
38948
|
if (this.isIdempotentSend(messageId, opts)) return this.trackReplyWork(this.sendIdempotent(channelId, body, opts));
|
|
38698
38949
|
const id = resolveMessageId(messageId);
|
|
38699
38950
|
const purgeEpochBeforePublish = this.store.conversationPurgeEpoch(channelId);
|
|
38700
38951
|
const replyCapture = this.unifiedReplyCapture(channelId, opts.inReplyTo);
|
|
38701
38952
|
await this.publishBody(channelId, body, id);
|
|
38702
|
-
this.afterPublish(channelId, body, id, opts, purgeEpochBeforePublish, replyCapture);
|
|
38953
|
+
this.afterPublish(channelId, body, id, opts, purgeEpochBeforePublish, replyCapture, text);
|
|
38703
38954
|
return { message_id: id, status: "sent" };
|
|
38704
38955
|
}
|
|
38705
38956
|
/** PR #716 review MEDIUM-5: register reply work so close() can wait for it. */
|
|
@@ -38718,7 +38969,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
38718
38969
|
const purgeEpochBeforePublish = this.store.conversationPurgeEpoch(channelId);
|
|
38719
38970
|
const replyCapture = this.unifiedReplyCapture(channelId, opts.inReplyTo);
|
|
38720
38971
|
await this.publishBody(channelId, body, id);
|
|
38721
|
-
this.afterPublish(channelId, body, id, opts, purgeEpochBeforePublish, replyCapture);
|
|
38972
|
+
this.afterPublish(channelId, body, id, opts, purgeEpochBeforePublish, replyCapture, null);
|
|
38722
38973
|
return { message_id: id, status: "sent" };
|
|
38723
38974
|
}
|
|
38724
38975
|
/**
|
|
@@ -38851,18 +39102,24 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
38851
39102
|
const meta = this.freshMeta(capChannel);
|
|
38852
39103
|
if (meta && !meta.is_dm) this.enforceRateFloor("room", capChannel);
|
|
38853
39104
|
}
|
|
38854
|
-
/**
|
|
38855
|
-
|
|
39105
|
+
/**
|
|
39106
|
+
* Bookkeeping after a CONFIRMED publish of `body` under `id`. `recordText` (weather/currency Task 17) is what a text
|
|
39107
|
+
* reply is RECORDED as: the published text minus any credit line. Every record site below reads `recorded`, never
|
|
39108
|
+
* `body.text`. Every production caller passes it (pinned by reply-credit.test.ts: text, or null for an a2a body);
|
|
39109
|
+
* undefined is left only for direct test calls of an uncredited body, where the published and record texts are equal.
|
|
39110
|
+
*/
|
|
39111
|
+
afterPublish(channelId, body, id, opts, purgeEpoch, replyCapture, recordText) {
|
|
39112
|
+
const recorded = recordText ?? (recordText === void 0 && body.kind === "text" ? body.text : "");
|
|
38856
39113
|
if (opts.agentTurn !== false) this.agentHops.noteOwnSend(channelId);
|
|
38857
39114
|
const modality = this.spokenReplyIds.has(id) ? "voice" : "text";
|
|
38858
|
-
const bridgeLine = this.unifiedOn && body.kind === "text" && isBridgeMemoryLine(
|
|
39115
|
+
const bridgeLine = this.unifiedOn && body.kind === "text" && isBridgeMemoryLine(recorded);
|
|
38859
39116
|
if (opts.inReplyTo !== void 0 && this.isOwnerVerifyFailed(opts.inReplyTo)) return;
|
|
38860
|
-
if (body.kind === "text" && opts.callBufferReply === true && opts.inReplyTo !== void 0 && !this.spokenReplyIds.has(id) && !isBridgeMemoryLine(
|
|
38861
|
-
this.callBuffer.appendAgent(channelId,
|
|
39117
|
+
if (body.kind === "text" && opts.callBufferReply === true && opts.inReplyTo !== void 0 && !this.spokenReplyIds.has(id) && !isBridgeMemoryLine(recorded) && this.callBuffer.isVoiceScopedId(opts.inReplyTo)) {
|
|
39118
|
+
this.callBuffer.appendAgent(channelId, recorded, "reply");
|
|
38862
39119
|
}
|
|
38863
39120
|
const origin = this.spokenReplyIds.has(id) ? "fast_path" : !this.taintScope(channelId) ? "reply" : opts.inReplyTo !== void 0 && this.cleanTurns.has(opts.inReplyTo) && !this.taintedTurns.has(opts.inReplyTo) ? "reply" : "untrusted";
|
|
38864
|
-
if (body.kind === "text" && !bridgeLine) void recordOutboundMemory(this.memory, channelId,
|
|
38865
|
-
if (this.unifiedOn && body.kind === "text" && !bridgeLine) this.spawnUnifiedReply(channelId,
|
|
39121
|
+
if (body.kind === "text" && !bridgeLine) void recordOutboundMemory(this.memory, channelId, recorded, id, modality, purgeEpoch, origin);
|
|
39122
|
+
if (this.unifiedOn && body.kind === "text" && !bridgeLine) this.spawnUnifiedReply(channelId, recorded, id, replyCapture ?? null, origin);
|
|
38866
39123
|
}
|
|
38867
39124
|
/**
|
|
38868
39125
|
* Final review bridge I-1 (ledger T25): the agent reply's U11 capture, taken BEFORE the publish await. The clear generation
|
|
@@ -38940,14 +39197,14 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
38940
39197
|
const k = replyIntentKey({ inReplyTo: opts.inReplyTo, idempotencyKey: opts.idempotencyKey, ordinal: () => 0 });
|
|
38941
39198
|
if (!k) throw new Error("idempotent send without a key");
|
|
38942
39199
|
const id = replyMessageId(this.replyIdKey, this.cfg.identityId, channelId, k);
|
|
38943
|
-
const mac2 = replyBodyMac(this.replyIdKey, id, channelId, bytes);
|
|
38944
|
-
const { intent } = await this.store.claimReplyIntent({ ...k, channelId, replyMessageId: id, bodyMac: mac2, body: bytes });
|
|
39200
|
+
const mac2 = replyBodyMac(this.replyIdKey, id, channelId, bytes, opts.credit);
|
|
39201
|
+
const { intent } = await this.store.claimReplyIntent({ ...k, channelId, replyMessageId: id, bodyMac: mac2, body: bytes, replyCredit: opts.credit ?? null });
|
|
38945
39202
|
if (intent.bodyMac !== mac2 || intent.channelId !== channelId || intent.state === "skipped") {
|
|
38946
39203
|
throw new IdempotencyConflictError();
|
|
38947
39204
|
}
|
|
38948
39205
|
return k;
|
|
38949
39206
|
}
|
|
38950
|
-
return this.claimUnkeyedPart(opts.inReplyTo, channelId, bytes, true);
|
|
39207
|
+
return this.claimUnkeyedPart(opts.inReplyTo, channelId, bytes, true, opts.credit ?? null);
|
|
38951
39208
|
});
|
|
38952
39209
|
return this.publishIntent(key, opts);
|
|
38953
39210
|
}
|
|
@@ -38956,22 +39213,23 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
38956
39213
|
* the reply to `inReplyTo`. Caller holds the scope lock. `matchRetry` false
|
|
38957
39214
|
* after a slot collision: the body must go to a NEW part.
|
|
38958
39215
|
*/
|
|
38959
|
-
async claimUnkeyedPart(inReplyTo, channelId, bytes, matchRetry) {
|
|
39216
|
+
async claimUnkeyedPart(inReplyTo, channelId, bytes, matchRetry, replyCredit) {
|
|
39217
|
+
const credit = replyCredit ?? void 0;
|
|
38960
39218
|
const scope = `reply:${inReplyTo}`;
|
|
38961
39219
|
let latest = null;
|
|
38962
39220
|
for (const intent of await this.store.listReplyIntents(inReplyTo)) {
|
|
38963
39221
|
const n = intent.scope === scope ? partOrdinal(intent.part) : null;
|
|
38964
39222
|
if (n !== null && (latest === null || n > latest.ordinal)) latest = { ordinal: n, intent };
|
|
38965
39223
|
}
|
|
38966
|
-
if (matchRetry && latest && latest.intent.state !== "skipped" && latest.intent.channelId === channelId && latest.intent.bodyMac === replyBodyMac(this.replyIdKey, latest.intent.replyMessageId, channelId, bytes)) {
|
|
39224
|
+
if (matchRetry && latest && latest.intent.state !== "skipped" && latest.intent.channelId === channelId && latest.intent.bodyMac === replyBodyMac(this.replyIdKey, latest.intent.replyMessageId, channelId, bytes, credit)) {
|
|
38967
39225
|
return { scope, part: latest.intent.part, inReplyTo };
|
|
38968
39226
|
}
|
|
38969
39227
|
let ordinal = latest ? latest.ordinal + 1 : 0;
|
|
38970
39228
|
for (let i = 0; i < REPLY_MAX_PART_ADVANCES; i++, ordinal++) {
|
|
38971
39229
|
const k = { scope, part: `p:${ordinal}`, inReplyTo };
|
|
38972
39230
|
const id = replyMessageId(this.replyIdKey, this.cfg.identityId, channelId, k);
|
|
38973
|
-
const mac2 = replyBodyMac(this.replyIdKey, id, channelId, bytes);
|
|
38974
|
-
const { created, intent } = await this.store.claimReplyIntent({ ...k, channelId, replyMessageId: id, bodyMac: mac2, body: bytes });
|
|
39231
|
+
const mac2 = replyBodyMac(this.replyIdKey, id, channelId, bytes, credit);
|
|
39232
|
+
const { created, intent } = await this.store.claimReplyIntent({ ...k, channelId, replyMessageId: id, bodyMac: mac2, body: bytes, replyCredit });
|
|
38975
39233
|
if (created) return k;
|
|
38976
39234
|
if (matchRetry && intent.state !== "skipped" && intent.bodyMac === mac2 && intent.channelId === channelId) return k;
|
|
38977
39235
|
writeServiceLog2("info", "reply.part_slot_taken", { attempt: i + 1 });
|
|
@@ -39016,6 +39274,12 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
39016
39274
|
}
|
|
39017
39275
|
const bytes = intent.body;
|
|
39018
39276
|
const body = decodeReplyBody(bytes);
|
|
39277
|
+
const recordText = this.recordTextOf(intent.replyCredit, body);
|
|
39278
|
+
if (recordText === void 0) {
|
|
39279
|
+
writeServiceLog2("error", "reply.intent_integrity_refused");
|
|
39280
|
+
this.store.deletePendingReplyIntent(key.scope, key.part);
|
|
39281
|
+
throw new ReplyIntentIntegrityError();
|
|
39282
|
+
}
|
|
39019
39283
|
const purgeEpochBeforePublish = this.store.conversationPurgeEpoch(intent.channelId);
|
|
39020
39284
|
const replyCapture = this.unifiedReplyCapture(intent.channelId, intent.inReplyTo ?? void 0);
|
|
39021
39285
|
try {
|
|
@@ -39044,14 +39308,17 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
39044
39308
|
const inReplyTo = intent.inReplyTo;
|
|
39045
39309
|
const next = await this.withReplyLock(
|
|
39046
39310
|
JSON.stringify(["scope", key.scope]),
|
|
39047
|
-
() =>
|
|
39311
|
+
() => (
|
|
39312
|
+
// r2 BLOCKER 1: the colliding row's OWN credit (verified above), never `opts`.
|
|
39313
|
+
this.claimUnkeyedPart(inReplyTo, intent.channelId, bytes, false, isReplyCredit(intent.replyCredit) ? intent.replyCredit : null)
|
|
39314
|
+
)
|
|
39048
39315
|
);
|
|
39049
39316
|
return this.publishIntent(next, opts, advances + 1);
|
|
39050
39317
|
}
|
|
39051
39318
|
throw e;
|
|
39052
39319
|
}
|
|
39053
39320
|
this.store.markReplyIntentSent(key.scope, key.part);
|
|
39054
|
-
this.afterPublish(intent.channelId, body, intent.replyMessageId, opts, purgeEpochBeforePublish, replyCapture);
|
|
39321
|
+
this.afterPublish(intent.channelId, body, intent.replyMessageId, opts, purgeEpochBeforePublish, replyCapture, recordText);
|
|
39055
39322
|
return { message_id: intent.replyMessageId, status: "sent" };
|
|
39056
39323
|
});
|
|
39057
39324
|
}
|
|
@@ -39061,9 +39328,21 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
39061
39328
|
*/
|
|
39062
39329
|
intentVerifies(intent) {
|
|
39063
39330
|
if (!intent.body) return false;
|
|
39331
|
+
const credit = intent.replyCredit ?? null;
|
|
39332
|
+
if (credit !== null && !isReplyCredit(credit)) return false;
|
|
39064
39333
|
const id = replyMessageId(this.replyIdKey, this.cfg.identityId, intent.channelId, intent);
|
|
39065
39334
|
if (id !== intent.replyMessageId) return false;
|
|
39066
|
-
return replyBodyMac(this.replyIdKey, intent.replyMessageId, intent.channelId, intent.body) === intent.bodyMac;
|
|
39335
|
+
return replyBodyMac(this.replyIdKey, intent.replyMessageId, intent.channelId, intent.body, credit ?? void 0) === intent.bodyMac;
|
|
39336
|
+
}
|
|
39337
|
+
/**
|
|
39338
|
+
* Weather/currency Task 17: what a verified row's reply is RECORDED as. Null credit: the decoded text unchanged (null
|
|
39339
|
+
* for an a2a body). "weatherapi": the text minus the exact credit suffix. Undefined: the row is refused (an unknown
|
|
39340
|
+
* credit, a credited a2a body, or a credited text without the exact suffix).
|
|
39341
|
+
*/
|
|
39342
|
+
recordTextOf(credit, body) {
|
|
39343
|
+
if (credit === null) return body.kind === "text" ? body.text : null;
|
|
39344
|
+
if (!isReplyCredit(credit) || body.kind !== "text" || !body.text.endsWith(WEATHER_CREDIT_SUFFIX)) return void 0;
|
|
39345
|
+
return body.text.slice(0, body.text.length - WEATHER_CREDIT_SUFFIX.length);
|
|
39067
39346
|
}
|
|
39068
39347
|
/**
|
|
39069
39348
|
* SEC-06 #18 item 3: a rate-limited reply retries the PUBLISH in-process
|
|
@@ -39223,7 +39502,8 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
39223
39502
|
uploadBlob: (ciphertext, channelId) => this.uploadBlobImpl(ciphertext, channelId),
|
|
39224
39503
|
encryptFile,
|
|
39225
39504
|
bytesToBase64: bytesToBase642,
|
|
39226
|
-
|
|
39505
|
+
// Security fix 2026-10-30: attachments upload only from the outbox.
|
|
39506
|
+
readFile: (p) => readOutboxFile(this.outboxPin, p).then((r) => r.bytes)
|
|
39227
39507
|
};
|
|
39228
39508
|
}
|
|
39229
39509
|
receiveDeps() {
|
|
@@ -39233,8 +39513,8 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
39233
39513
|
decryptFile,
|
|
39234
39514
|
computeFileDigest,
|
|
39235
39515
|
base64ToBytes: base64ToBytes2,
|
|
39236
|
-
mkdirp: (d) =>
|
|
39237
|
-
writeFile: (p, data) =>
|
|
39516
|
+
mkdirp: (d) => fsp2.mkdir(d, { recursive: true }).then(() => void 0),
|
|
39517
|
+
writeFile: (p, data) => fsp2.writeFile(p, data)
|
|
39238
39518
|
};
|
|
39239
39519
|
}
|
|
39240
39520
|
async sendAttachment(channelId, filePath2, caption) {
|
|
@@ -39242,8 +39522,11 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
39242
39522
|
const joined = await this.reconcileJoin(channelId);
|
|
39243
39523
|
if (!joined) throw new ChannelPendingError(channelId);
|
|
39244
39524
|
let body;
|
|
39525
|
+
let read;
|
|
39245
39526
|
try {
|
|
39246
|
-
|
|
39527
|
+
read = await readOutboxFile(this.outboxPin, filePath2);
|
|
39528
|
+
const bytes = read.bytes;
|
|
39529
|
+
body = await prepareAttachment(filePath2, caption, channelId, { ...this.sendDeps(), readFile: async () => bytes });
|
|
39247
39530
|
} catch (e) {
|
|
39248
39531
|
if (e.code === "ENOENT" || e.code === "EACCES" || e.code === "ENOTDIR" || e.code === "EISDIR") {
|
|
39249
39532
|
throw new InvalidPathError(filePath2, e);
|
|
@@ -39284,6 +39567,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
39284
39567
|
);
|
|
39285
39568
|
this.latency.record("send.total", performance.now() - sendStart);
|
|
39286
39569
|
this.agentHops.noteOwnSend(channelId);
|
|
39570
|
+
await this.consumeUploaded(read);
|
|
39287
39571
|
return { message_id: id, status: "sent" };
|
|
39288
39572
|
}
|
|
39289
39573
|
// ── B7: FileSafe vault commands (loopback boundary) ──────────────────────
|
|
@@ -39809,25 +40093,26 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
39809
40093
|
}
|
|
39810
40094
|
};
|
|
39811
40095
|
}
|
|
39812
|
-
/** Read `path`
|
|
40096
|
+
/** Read `path` from the vault outbox (security fix 2026-10-30; see
|
|
40097
|
+
* ./outbox.ts), encrypt + wrap its DEK under the vault's
|
|
39813
40098
|
* current KEK generation, and create+upload+commit it as a new vault
|
|
39814
40099
|
* file node named `name` (which may differ from `path`'s basename). */
|
|
39815
40100
|
async vaultWrite(enclaveId, vaultChannelId, path, name) {
|
|
39816
|
-
|
|
39817
|
-
let bytes;
|
|
40101
|
+
let read;
|
|
39818
40102
|
try {
|
|
39819
|
-
|
|
40103
|
+
read = await readOutboxFile(this.outboxPin, path);
|
|
39820
40104
|
} catch (e) {
|
|
39821
40105
|
const code = e.code;
|
|
39822
40106
|
if (code === "ENOENT" || code === "EACCES" || code === "ENOTDIR" || code === "EISDIR") throw new InvalidPathError(path, e);
|
|
39823
40107
|
throw e;
|
|
39824
40108
|
}
|
|
40109
|
+
const session = await this.getVaultSession(enclaveId, await this.resolveVaultChannelId(enclaveId, vaultChannelId));
|
|
39825
40110
|
const nodeId = crypto.randomUUID();
|
|
39826
|
-
|
|
40111
|
+
const written = await vaultWriteImpl({
|
|
39827
40112
|
enclaveId,
|
|
39828
40113
|
nodeId,
|
|
39829
40114
|
name,
|
|
39830
|
-
bytes,
|
|
40115
|
+
bytes: read.bytes,
|
|
39831
40116
|
deps: {
|
|
39832
40117
|
exporterKekForGeneration: session.kekForGeneration,
|
|
39833
40118
|
currentGeneration: session.currentGeneration,
|
|
@@ -39836,6 +40121,16 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
39836
40121
|
commitNode: (committedNodeId) => this.vaultCommitNode(enclaveId, committedNodeId)
|
|
39837
40122
|
}
|
|
39838
40123
|
});
|
|
40124
|
+
await this.consumeUploaded(read);
|
|
40125
|
+
return written;
|
|
40126
|
+
}
|
|
40127
|
+
/** Audit 2026-10-30 MED 5: an uploaded file leaves the outbox (no plaintext lingers). Content-free log. */
|
|
40128
|
+
async consumeUploaded(read) {
|
|
40129
|
+
if (!this.outboxPin || !await consumeOutboxFile(this.outboxPin, read)) writeServiceLog2("warn", "vault_outbox.consume_failed");
|
|
40130
|
+
}
|
|
40131
|
+
/** Audit 2026-10-30 MED 3: the pinned outbox path, served only on the authenticated GET /v1/vault-outbox. */
|
|
40132
|
+
vaultOutboxPath() {
|
|
40133
|
+
return this.outboxPin?.dir ?? null;
|
|
39839
40134
|
}
|
|
39840
40135
|
/** Download + verify + decrypt a vault file node. Returns the plaintext
|
|
39841
40136
|
* base64-encoded (the loopback boundary is JSON-only, mirroring how
|
|
@@ -42268,13 +42563,13 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
42268
42563
|
throw new Error("summarize_no_runtime");
|
|
42269
42564
|
}
|
|
42270
42565
|
const messages = [{ role: "user", content: prompt }];
|
|
42271
|
-
return new Promise((
|
|
42566
|
+
return new Promise((resolve2, reject) => {
|
|
42272
42567
|
const onAbort = () => reject(new Error("aborted"));
|
|
42273
42568
|
signal.addEventListener("abort", onAbort);
|
|
42274
42569
|
this.fastLlm.complete(messages).then(
|
|
42275
42570
|
(text) => {
|
|
42276
42571
|
signal.removeEventListener("abort", onAbort);
|
|
42277
|
-
|
|
42572
|
+
resolve2(text);
|
|
42278
42573
|
},
|
|
42279
42574
|
(err) => {
|
|
42280
42575
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -43187,7 +43482,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
43187
43482
|
this.memory,
|
|
43188
43483
|
m.channelId,
|
|
43189
43484
|
m.envelope.sender_id,
|
|
43190
|
-
parsed.text,
|
|
43485
|
+
m.envelope.sender_id === this.cfg.identityId ? stripCreditSuffix(parsed.text) : parsed.text,
|
|
43191
43486
|
m.messageId,
|
|
43192
43487
|
m.voiceMode ? "voice" : "text",
|
|
43193
43488
|
purgeEpochAtTap
|
|
@@ -43222,7 +43517,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
43222
43517
|
return got;
|
|
43223
43518
|
}
|
|
43224
43519
|
const cap = Math.min(waitSeconds, 50) * 1e3;
|
|
43225
|
-
await new Promise((
|
|
43520
|
+
await new Promise((resolve2) => {
|
|
43226
43521
|
let fired = false;
|
|
43227
43522
|
let timer;
|
|
43228
43523
|
const wake = () => {
|
|
@@ -43231,7 +43526,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
43231
43526
|
clearTimeout(timer);
|
|
43232
43527
|
const i = this.waiters.indexOf(wake);
|
|
43233
43528
|
if (i >= 0) this.waiters.splice(i, 1);
|
|
43234
|
-
|
|
43529
|
+
resolve2();
|
|
43235
43530
|
};
|
|
43236
43531
|
timer = setTimeout(wake, cap);
|
|
43237
43532
|
this.waiters.push(wake);
|
|
@@ -43274,13 +43569,13 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
43274
43569
|
const bound = b !== null && b.state === "bound" && b.counter === this.toolGrantCounter;
|
|
43275
43570
|
const switchOn = audience === "owner_dm" ? this.ownerDmManifestGrants : audience === "team_room" ? this.roomManifestGrants : false;
|
|
43276
43571
|
const signedNow = new Set(audience && manifest ? manifest.grants[audience] ?? [] : []);
|
|
43277
|
-
const
|
|
43572
|
+
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) : []);
|
|
43278
43573
|
let changed = false;
|
|
43279
43574
|
const out = a.tool_grants.flatMap((g) => {
|
|
43280
43575
|
if (!(g.mode === "read" && g.source === "manifest")) return [g];
|
|
43281
43576
|
if (switchOn && bound && signedNow.has(g.tool)) return [g];
|
|
43282
43577
|
changed = true;
|
|
43283
|
-
return
|
|
43578
|
+
return constants5.has(g.tool) && signedNow.has(g.tool) ? [Object.freeze({ tool: g.tool, mode: "read" })] : [];
|
|
43284
43579
|
});
|
|
43285
43580
|
if (!changed) return m;
|
|
43286
43581
|
return { ...m, authorization: Object.freeze({ ...a, tool_grants: Object.freeze(out) }) };
|
|
@@ -43985,21 +44280,49 @@ ${channelId}`;
|
|
|
43985
44280
|
*/
|
|
43986
44281
|
searchProvidersSockets = /* @__PURE__ */ new Map();
|
|
43987
44282
|
/**
|
|
43988
|
-
*
|
|
43989
|
-
*
|
|
44283
|
+
* Weather/currency U6 (H-1): the same AND-over-sockets state for utility_tools_v1, kept separately per socket, so a
|
|
44284
|
+
* runtime that declares search_providers_v2 but not utility_tools_v1 (0.14.0, 0.14.1) keeps web_extract and never
|
|
44285
|
+
* sees a utility name.
|
|
43990
44286
|
*/
|
|
43991
|
-
|
|
44287
|
+
utilityToolsSockets = /* @__PURE__ */ new Map();
|
|
44288
|
+
/**
|
|
44289
|
+
* The (search, utility) tuple the files were last recomputed for; null until the first socket observation, so that
|
|
44290
|
+
* observation always recomputes (final review I-1), even when it is non-capable. One trigger keyed on BOTH values.
|
|
44291
|
+
*/
|
|
44292
|
+
capabilityState = null;
|
|
43992
44293
|
searchCapabilityRefetchTimer = null;
|
|
43993
44294
|
/** When the current burst's first change happened (valid while the timer is set). */
|
|
43994
44295
|
searchCapabilityBurstStart = 0;
|
|
43995
44296
|
/** The D28 value in the last report posted (null until one is); a change needs a refetch to reach the web. */
|
|
43996
44297
|
lastReportedSearchProviders = null;
|
|
44298
|
+
/** U6: the utility_tools_v1 value in the last report posted (null until one is). */
|
|
44299
|
+
lastReportedUtilityTools = null;
|
|
43997
44300
|
noteSearchProvidersSocket(token, declared) {
|
|
43998
44301
|
this.searchProvidersSockets.set(token, declared);
|
|
43999
|
-
this.
|
|
44302
|
+
this.onCapabilityMaybeChanged();
|
|
44000
44303
|
}
|
|
44001
44304
|
clearSearchProvidersSocket(token) {
|
|
44002
|
-
if (this.searchProvidersSockets.delete(token)) this.
|
|
44305
|
+
if (this.searchProvidersSockets.delete(token)) this.onCapabilityMaybeChanged();
|
|
44306
|
+
}
|
|
44307
|
+
noteUtilityToolsSocket(token, declared) {
|
|
44308
|
+
this.utilityToolsSockets.set(token, declared);
|
|
44309
|
+
this.onCapabilityMaybeChanged();
|
|
44310
|
+
}
|
|
44311
|
+
clearUtilityToolsSocket(token) {
|
|
44312
|
+
if (this.utilityToolsSockets.delete(token)) this.onCapabilityMaybeChanged();
|
|
44313
|
+
}
|
|
44314
|
+
/** U6: every attached runtime socket declared utility_tools_v1; no socket attached means absent (false). */
|
|
44315
|
+
utilityToolsCapable() {
|
|
44316
|
+
if (this.utilityToolsSockets.size === 0) return false;
|
|
44317
|
+
for (const v of this.utilityToolsSockets.values()) if (!v) return false;
|
|
44318
|
+
return true;
|
|
44319
|
+
}
|
|
44320
|
+
/** U6: the features every attached runtime socket declared, for the policy functions. */
|
|
44321
|
+
capabilityFeatures() {
|
|
44322
|
+
const out = /* @__PURE__ */ new Set();
|
|
44323
|
+
if (this.searchProvidersCapable()) out.add("search_providers_v2");
|
|
44324
|
+
if (this.utilityToolsCapable()) out.add("utility_tools_v1");
|
|
44325
|
+
return out;
|
|
44003
44326
|
}
|
|
44004
44327
|
/** D28: every attached runtime socket declared search_providers_v2; no socket attached means absent (false). */
|
|
44005
44328
|
searchProvidersCapable() {
|
|
@@ -44026,14 +44349,14 @@ ${channelId}`;
|
|
|
44026
44349
|
* D35 (P-4): on a capability change, rewrite manifest-policy.json at once from the stored manifest, the kill
|
|
44027
44350
|
* switches and the new capability, then a debounced refetch (one per burst) only when needed.
|
|
44028
44351
|
*/
|
|
44029
|
-
|
|
44030
|
-
const
|
|
44031
|
-
const prev = this.
|
|
44032
|
-
if (
|
|
44033
|
-
this.
|
|
44034
|
-
this.recomputeManifestPolicy(
|
|
44352
|
+
onCapabilityMaybeChanged() {
|
|
44353
|
+
const next = { search: this.searchProvidersCapable(), utility: this.utilityToolsCapable() };
|
|
44354
|
+
const prev = this.capabilityState;
|
|
44355
|
+
if (prev !== null && prev.search === next.search && prev.utility === next.utility) return;
|
|
44356
|
+
this.capabilityState = next;
|
|
44357
|
+
this.recomputeManifestPolicy(this.capabilityFeatures());
|
|
44035
44358
|
if (this.closingDown) return;
|
|
44036
|
-
if (prev === null && !
|
|
44359
|
+
if (prev === null && !next.search && !next.utility) return;
|
|
44037
44360
|
const now = Date.now();
|
|
44038
44361
|
if (this.searchCapabilityRefetchTimer !== null) clearTimeout(this.searchCapabilityRefetchTimer);
|
|
44039
44362
|
else this.searchCapabilityBurstStart = now;
|
|
@@ -44051,7 +44374,7 @@ ${channelId}`;
|
|
|
44051
44374
|
searchCapabilityRefetchNeeded() {
|
|
44052
44375
|
if (!this.bootConfigSettled || this.closingDown) return false;
|
|
44053
44376
|
const inForce = (this.cfg.ownerGrantPin?.pin ?? null) !== null && this.toolGrantManifest !== null;
|
|
44054
|
-
return inForce || this.lastReportedSearchProviders !== this.searchProvidersCapable();
|
|
44377
|
+
return inForce || this.lastReportedSearchProviders !== this.searchProvidersCapable() || this.lastReportedUtilityTools !== this.utilityToolsCapable();
|
|
44055
44378
|
}
|
|
44056
44379
|
/**
|
|
44057
44380
|
* D35: the local recompute. Widening (web_extract back in) only while the binding for the in-force manifest stands,
|
|
@@ -44059,16 +44382,17 @@ ${channelId}`;
|
|
|
44059
44382
|
* lists web_extract loses it at once, so an older runtime never reads it as unreadable (which drops every grant).
|
|
44060
44383
|
* Content-free; a write failure is logged and the debounced refetch tries again.
|
|
44061
44384
|
*/
|
|
44062
|
-
recomputeManifestPolicy(
|
|
44385
|
+
recomputeManifestPolicy(features) {
|
|
44063
44386
|
const pin = this.cfg.ownerGrantPin?.pin ?? null;
|
|
44064
44387
|
const manifest = pin !== null ? this.toolGrantManifest : null;
|
|
44065
44388
|
try {
|
|
44066
44389
|
if (manifest !== null && this.manifestBinding !== null && this.manifestBinding.counter === manifest.counter) {
|
|
44067
|
-
this.grantWriters.writeManifestPolicy(this.home, this.cfg.identityId, manifestPolicyFor(manifest, { ownerDm: this.ownerDmManifestGrants, room: this.roomManifestGrants },
|
|
44390
|
+
this.grantWriters.writeManifestPolicy(this.home, this.cfg.identityId, manifestPolicyFor(manifest, { ownerDm: this.ownerDmManifestGrants, room: this.roomManifestGrants }, features));
|
|
44068
44391
|
return;
|
|
44069
44392
|
}
|
|
44070
|
-
|
|
44071
|
-
|
|
44393
|
+
const withheld = new Set(Object.keys(TOOL_FEATURE).filter((t) => !toolAllowedByFeatures(t, features)));
|
|
44394
|
+
if (withheld.size === 0) return;
|
|
44395
|
+
const path = join18(runtimeConfigDir(this.home, this.cfg.identityId), MANIFEST_POLICY_FILE);
|
|
44072
44396
|
if (!existsSync5(path)) return;
|
|
44073
44397
|
let doc;
|
|
44074
44398
|
try {
|
|
@@ -44077,10 +44401,10 @@ ${channelId}`;
|
|
|
44077
44401
|
return;
|
|
44078
44402
|
}
|
|
44079
44403
|
const tools = doc?.manifest_tools;
|
|
44080
|
-
if (!Array.isArray(tools) || !tools.
|
|
44404
|
+
if (!Array.isArray(tools) || !tools.some((t) => typeof t === "string" && withheld.has(t))) return;
|
|
44081
44405
|
this.grantWriters.writeManifestPolicy(this.home, this.cfg.identityId, {
|
|
44082
44406
|
v: 1,
|
|
44083
|
-
manifest_tools: tools.filter((t) => typeof t === "string" && t
|
|
44407
|
+
manifest_tools: tools.filter((t) => typeof t === "string" && !withheld.has(t)),
|
|
44084
44408
|
secret_path_mode: "enforce"
|
|
44085
44409
|
});
|
|
44086
44410
|
} catch (e) {
|
|
@@ -44293,8 +44617,8 @@ ${channelId}`;
|
|
|
44293
44617
|
let timer;
|
|
44294
44618
|
await Promise.race([
|
|
44295
44619
|
ingest.released,
|
|
44296
|
-
new Promise((
|
|
44297
|
-
timer = setTimeout(
|
|
44620
|
+
new Promise((resolve2) => {
|
|
44621
|
+
timer = setTimeout(resolve2, _BridgeAgent.GRACEFUL_DRAIN_MS);
|
|
44298
44622
|
})
|
|
44299
44623
|
]);
|
|
44300
44624
|
if (timer) clearTimeout(timer);
|
|
@@ -44304,8 +44628,8 @@ ${channelId}`;
|
|
|
44304
44628
|
let timer;
|
|
44305
44629
|
await Promise.race([
|
|
44306
44630
|
starting,
|
|
44307
|
-
new Promise((
|
|
44308
|
-
timer = setTimeout(
|
|
44631
|
+
new Promise((resolve2) => {
|
|
44632
|
+
timer = setTimeout(resolve2, _BridgeAgent.GRACEFUL_DRAIN_MS);
|
|
44309
44633
|
})
|
|
44310
44634
|
]);
|
|
44311
44635
|
if (timer) clearTimeout(timer);
|
|
@@ -44314,8 +44638,8 @@ ${channelId}`;
|
|
|
44314
44638
|
let timer;
|
|
44315
44639
|
await Promise.race([
|
|
44316
44640
|
this.membershipSweepWork,
|
|
44317
|
-
new Promise((
|
|
44318
|
-
timer = setTimeout(
|
|
44641
|
+
new Promise((resolve2) => {
|
|
44642
|
+
timer = setTimeout(resolve2, _BridgeAgent.GRACEFUL_DRAIN_MS);
|
|
44319
44643
|
})
|
|
44320
44644
|
]);
|
|
44321
44645
|
if (timer) clearTimeout(timer);
|
|
@@ -44325,8 +44649,8 @@ ${channelId}`;
|
|
|
44325
44649
|
let timer;
|
|
44326
44650
|
await Promise.race([
|
|
44327
44651
|
this.recallPruneWork,
|
|
44328
|
-
new Promise((
|
|
44329
|
-
timer = setTimeout(
|
|
44652
|
+
new Promise((resolve2) => {
|
|
44653
|
+
timer = setTimeout(resolve2, _BridgeAgent.GRACEFUL_DRAIN_MS);
|
|
44330
44654
|
})
|
|
44331
44655
|
]);
|
|
44332
44656
|
if (timer) clearTimeout(timer);
|
|
@@ -44336,8 +44660,8 @@ ${channelId}`;
|
|
|
44336
44660
|
let timer;
|
|
44337
44661
|
await Promise.race([
|
|
44338
44662
|
this.recallMaintenanceWork,
|
|
44339
|
-
new Promise((
|
|
44340
|
-
timer = setTimeout(
|
|
44663
|
+
new Promise((resolve2) => {
|
|
44664
|
+
timer = setTimeout(resolve2, _BridgeAgent.GRACEFUL_DRAIN_MS);
|
|
44341
44665
|
})
|
|
44342
44666
|
]);
|
|
44343
44667
|
if (timer) clearTimeout(timer);
|
|
@@ -44349,8 +44673,8 @@ ${channelId}`;
|
|
|
44349
44673
|
let timer;
|
|
44350
44674
|
await Promise.race([
|
|
44351
44675
|
Promise.allSettled([...this.recallTapWork]),
|
|
44352
|
-
new Promise((
|
|
44353
|
-
timer = setTimeout(
|
|
44676
|
+
new Promise((resolve2) => {
|
|
44677
|
+
timer = setTimeout(resolve2, _BridgeAgent.GRACEFUL_DRAIN_MS);
|
|
44354
44678
|
})
|
|
44355
44679
|
]);
|
|
44356
44680
|
if (timer) clearTimeout(timer);
|
|
@@ -44359,8 +44683,8 @@ ${channelId}`;
|
|
|
44359
44683
|
let timer;
|
|
44360
44684
|
await Promise.race([
|
|
44361
44685
|
Promise.allSettled([...this.unifiedWork]),
|
|
44362
|
-
new Promise((
|
|
44363
|
-
timer = setTimeout(
|
|
44686
|
+
new Promise((resolve2) => {
|
|
44687
|
+
timer = setTimeout(resolve2, _BridgeAgent.GRACEFUL_DRAIN_MS);
|
|
44364
44688
|
})
|
|
44365
44689
|
]);
|
|
44366
44690
|
if (timer) clearTimeout(timer);
|
|
@@ -44369,8 +44693,8 @@ ${channelId}`;
|
|
|
44369
44693
|
let timer;
|
|
44370
44694
|
await Promise.race([
|
|
44371
44695
|
this.recallPrewarmWork,
|
|
44372
|
-
new Promise((
|
|
44373
|
-
timer = setTimeout(
|
|
44696
|
+
new Promise((resolve2) => {
|
|
44697
|
+
timer = setTimeout(resolve2, _BridgeAgent.GRACEFUL_DRAIN_MS);
|
|
44374
44698
|
})
|
|
44375
44699
|
]);
|
|
44376
44700
|
if (timer) clearTimeout(timer);
|
|
@@ -44380,8 +44704,8 @@ ${channelId}`;
|
|
|
44380
44704
|
let timer;
|
|
44381
44705
|
await Promise.race([
|
|
44382
44706
|
Promise.allSettled([...this.replyWork]),
|
|
44383
|
-
new Promise((
|
|
44384
|
-
timer = setTimeout(
|
|
44707
|
+
new Promise((resolve2) => {
|
|
44708
|
+
timer = setTimeout(resolve2, _BridgeAgent.GRACEFUL_DRAIN_MS);
|
|
44385
44709
|
})
|
|
44386
44710
|
]);
|
|
44387
44711
|
if (timer) clearTimeout(timer);
|
|
@@ -44394,7 +44718,7 @@ ${channelId}`;
|
|
|
44394
44718
|
|
|
44395
44719
|
// src/config.ts
|
|
44396
44720
|
import { readFileSync as readFileSync11, statSync as statSync5 } from "node:fs";
|
|
44397
|
-
import { dirname as
|
|
44721
|
+
import { dirname as dirname7, join as join19 } from "node:path";
|
|
44398
44722
|
var REQUIRED = ["api_key", "identity_id", "device_id", "signing_private_key", "enclave_id", "api_base"];
|
|
44399
44723
|
var CONFIG_ERROR_MESSAGE = {
|
|
44400
44724
|
credential_not_found: "credential file not found",
|
|
@@ -44432,7 +44756,7 @@ function loadConfig(opts) {
|
|
|
44432
44756
|
throw new ConfigError("credential_not_json");
|
|
44433
44757
|
}
|
|
44434
44758
|
for (const k of REQUIRED) if (typeof json2[k] !== "string" || !json2[k]) throw new ConfigError("credential_missing_field", k);
|
|
44435
|
-
const storePath = opts.storePath ??
|
|
44759
|
+
const storePath = opts.storePath ?? join19(dirname7(opts.credentialPath), "bridge-state.db");
|
|
44436
44760
|
const ownerGrantPin = loadOwnerGrantPin({ credentialPath: opts.credentialPath, credentialJson: json2 });
|
|
44437
44761
|
writeServiceLog2(ownerGrantPin.state === "ok" || ownerGrantPin.state === "no_pin" ? "info" : "warn", "authz.pin", {
|
|
44438
44762
|
state: ServiceLogEnum.of(ownerGrantPin.state, ["ok", "no_pin", "pin_conflict", "malformed", "unsupported_algo", "symlink_refused", "unreadable"])
|
|
@@ -44617,6 +44941,13 @@ var BRIDGE_ERRORS = {
|
|
|
44617
44941
|
// C9 (spec §4.1.6 step 1): another copy of this agent took its SecureComs
|
|
44618
44942
|
// session (WS 4409), so this copy stopped. Every local route but /healthz.
|
|
44619
44943
|
session_superseded: { http: 503, message: "another copy of this agent took its session; this copy stopped" },
|
|
44944
|
+
// Weather/currency Task 17 (OQ-P1): a send frame's credit is not the closed value, or rides an a2a frame. TERMINAL:
|
|
44945
|
+
// the inbound is retired, so the model (and the weather call) never runs again for it.
|
|
44946
|
+
reply_credit_unknown: { http: 400, message: "reply credit not recognised; not sent" },
|
|
44947
|
+
// Security fix 2026-10-30: an upload route was given a path that is not a
|
|
44948
|
+
// regular file directly inside the bridge's vault outbox (`vault_outbox` on
|
|
44949
|
+
// /healthz). Fixed and content-free: the path is never echoed or logged.
|
|
44950
|
+
path_outside_outbox: { http: 400, message: "uploads must come from the bridge vault outbox; copy the file there first" },
|
|
44620
44951
|
internal: { http: 500, message: "internal error" }
|
|
44621
44952
|
};
|
|
44622
44953
|
function errorBody(code, detail) {
|
|
@@ -44646,6 +44977,7 @@ function withConsumerGrants(m, perToolConsumer) {
|
|
|
44646
44977
|
}
|
|
44647
44978
|
var MANIFEST_GRANTS_FEATURE = "manifest_grants_v1";
|
|
44648
44979
|
var SEARCH_PROVIDERS_FEATURE = "search_providers_v2";
|
|
44980
|
+
var UTILITY_TOOLS_FEATURE = "utility_tools_v1";
|
|
44649
44981
|
var CONSTANT_TOOL_NAMES = Object.freeze({
|
|
44650
44982
|
owner_dm: Object.freeze(["current_time", "web_search"]),
|
|
44651
44983
|
team_room: Object.freeze(["current_time"])
|
|
@@ -44655,10 +44987,10 @@ function withManifestCapability(m, manifestConsumer) {
|
|
|
44655
44987
|
if (manifestConsumer) return m;
|
|
44656
44988
|
const a = m.authorization;
|
|
44657
44989
|
if (!a || !a.tool_grants.some(isManifestGrant)) return m;
|
|
44658
|
-
const
|
|
44990
|
+
const constants5 = new Set(a.audience === "owner_dm" || a.audience === "team_room" ? CONSTANT_TOOL_NAMES[a.audience] : []);
|
|
44659
44991
|
const narrowed = a.tool_grants.flatMap((g) => {
|
|
44660
44992
|
if (!isManifestGrant(g)) return [g];
|
|
44661
|
-
return
|
|
44993
|
+
return constants5.has(g.tool) ? [Object.freeze({ tool: g.tool, mode: "read" })] : [];
|
|
44662
44994
|
});
|
|
44663
44995
|
return { ...m, authorization: Object.freeze({ ...a, tool_grants: Object.freeze(narrowed) }) };
|
|
44664
44996
|
}
|
|
@@ -44677,6 +45009,15 @@ function withSearchCapability(m, consumerHasFeature, signedProvider) {
|
|
|
44677
45009
|
if (kept.length === a.tool_grants.length) return m;
|
|
44678
45010
|
return { ...m, authorization: Object.freeze({ ...a, tool_grants: kept.length === 0 ? NO_GRANTS : Object.freeze(kept) }) };
|
|
44679
45011
|
}
|
|
45012
|
+
var UTILITY_TOOL_NAMES = /* @__PURE__ */ new Set(["weather_forecast", "currency_convert"]);
|
|
45013
|
+
function withUtilityCapability(m, consumerHasFeature) {
|
|
45014
|
+
if (consumerHasFeature) return m;
|
|
45015
|
+
const a = m.authorization;
|
|
45016
|
+
if (!a) return m;
|
|
45017
|
+
const kept = a.tool_grants.filter((g) => !UTILITY_TOOL_NAMES.has(g.tool));
|
|
45018
|
+
if (kept.length === a.tool_grants.length) return m;
|
|
45019
|
+
return { ...m, authorization: Object.freeze({ ...a, tool_grants: kept.length === 0 ? NO_GRANTS : Object.freeze(kept) }) };
|
|
45020
|
+
}
|
|
44680
45021
|
|
|
44681
45022
|
// src/http-server.ts
|
|
44682
45023
|
function writeSse(res, msgs, onWriteError) {
|
|
@@ -44700,7 +45041,7 @@ data: ${JSON.stringify(msgs[i])}
|
|
|
44700
45041
|
function deliverSseBatch(res, batch, agent, onWriteError) {
|
|
44701
45042
|
const withheld = agent.harnessGrantsWithheld();
|
|
44702
45043
|
const provider = agent.signedSearchProvider();
|
|
44703
|
-
const left3 = writeSse(res, batch.map((m) => withSearchCapability(withManifestCapability(withHarnessGrants(withConsumerGrants(m, false), withheld), false), false, provider)), onWriteError);
|
|
45044
|
+
const left3 = writeSse(res, batch.map((m) => withUtilityCapability(withSearchCapability(withManifestCapability(withHarnessGrants(withConsumerGrants(m, false), withheld), false), false, provider), false)), onWriteError);
|
|
44704
45045
|
const leftIds = new Set(left3.map((message) => message.message_id));
|
|
44705
45046
|
agent.ackRuntimeMessages(batch.filter((message) => !leftIds.has(message.message_id)).map((message) => message.message_id));
|
|
44706
45047
|
if (left3.length) agent.requeueFront(batch.filter((message) => leftIds.has(message.message_id)));
|
|
@@ -44732,7 +45073,7 @@ var OTHER_CAP = 8 * 1024;
|
|
|
44732
45073
|
var a2aSchema = z3.object({
|
|
44733
45074
|
a2a_version: z3.string().min(1),
|
|
44734
45075
|
kind: z3.enum(["message", "task", "artifact"]),
|
|
44735
|
-
object: z3.record(z3.unknown())
|
|
45076
|
+
object: z3.record(z3.string(), z3.unknown())
|
|
44736
45077
|
});
|
|
44737
45078
|
var sendAttachmentSchema = z3.object({
|
|
44738
45079
|
channel_id: z3.string().min(1),
|
|
@@ -44807,7 +45148,7 @@ function jsonError(res, code, detail) {
|
|
|
44807
45148
|
json(res, httpStatus(code), errorBody(code, detail));
|
|
44808
45149
|
}
|
|
44809
45150
|
function readBody(req, cap) {
|
|
44810
|
-
return new Promise((
|
|
45151
|
+
return new Promise((resolve2, reject) => {
|
|
44811
45152
|
let size = 0;
|
|
44812
45153
|
let done = false;
|
|
44813
45154
|
const chunks = [];
|
|
@@ -44827,7 +45168,7 @@ function readBody(req, cap) {
|
|
|
44827
45168
|
req.on("end", () => {
|
|
44828
45169
|
if (!done) {
|
|
44829
45170
|
done = true;
|
|
44830
|
-
|
|
45171
|
+
resolve2(Buffer.concat(chunks).toString("utf8"));
|
|
44831
45172
|
}
|
|
44832
45173
|
});
|
|
44833
45174
|
req.on("error", (e) => {
|
|
@@ -44905,6 +45246,9 @@ function createHttpServer(agent, opts) {
|
|
|
44905
45246
|
}
|
|
44906
45247
|
return json(res, 200, r);
|
|
44907
45248
|
}
|
|
45249
|
+
if (method === "GET" && path === "/v1/vault-outbox") {
|
|
45250
|
+
return json(res, 200, { path: agent.vaultOutboxPath() });
|
|
45251
|
+
}
|
|
44908
45252
|
if (method === "POST" && path === "/v1/send-attachment") {
|
|
44909
45253
|
const parsed = sendAttachmentSchema.safeParse(JSON.parse(await readBody(req, OTHER_CAP)));
|
|
44910
45254
|
if (!parsed.success) return jsonError(res, "invalid_request", parsed.error.issues);
|
|
@@ -44974,7 +45318,7 @@ function createHttpServer(agent, opts) {
|
|
|
44974
45318
|
const drained = await agent.drain(channel, wait);
|
|
44975
45319
|
const withheld = agent.harnessGrantsWithheld();
|
|
44976
45320
|
const provider = agent.signedSearchProvider();
|
|
44977
|
-
return json(res, 200, drained.map((m) => withSearchCapability(withManifestCapability(withHarnessGrants(withConsumerGrants(m, false), withheld), false), false, provider)));
|
|
45321
|
+
return json(res, 200, drained.map((m) => withUtilityCapability(withSearchCapability(withManifestCapability(withHarnessGrants(withConsumerGrants(m, false), withheld), false), false, provider), false)));
|
|
44978
45322
|
}
|
|
44979
45323
|
if (method === "GET" && path === "/v1/events") {
|
|
44980
45324
|
const channel = url.searchParams.get("channel_id") ?? void 0;
|
|
@@ -45016,6 +45360,10 @@ function createHttpServer(agent, opts) {
|
|
|
45016
45360
|
if (e instanceof SendForbiddenError) return jsonError(res, "send_forbidden");
|
|
45017
45361
|
if (e instanceof GroupSupersededSendError) return jsonError(res, "group_superseded");
|
|
45018
45362
|
if (e instanceof IdempotencyConflictError) return jsonError(res, "invalid_request", e.message);
|
|
45363
|
+
if (e instanceof PathOutsideOutboxError) {
|
|
45364
|
+
writeServiceLog2("warn", "vault_outbox.refused");
|
|
45365
|
+
return jsonError(res, "path_outside_outbox");
|
|
45366
|
+
}
|
|
45019
45367
|
if (e instanceof InvalidPathError) return jsonError(res, "invalid_request", e.message);
|
|
45020
45368
|
if (e instanceof VaultPinMismatchError) return jsonError(res, "vault_ref_changed", { pin: e.pin });
|
|
45021
45369
|
if (e instanceof VaultIntegrityError) return jsonError(res, "vault_integrity_failed", e.message);
|
|
@@ -45149,6 +45497,7 @@ function attachAgentWebSocket(server, agent, opts) {
|
|
|
45149
45497
|
perToolGrants: false,
|
|
45150
45498
|
manifestGrants: false,
|
|
45151
45499
|
searchProviders: false,
|
|
45500
|
+
utilityTools: false,
|
|
45152
45501
|
// helloWaitMs 0 keeps today's synchronous initial flush exactly (the opt-out the existing tests use).
|
|
45153
45502
|
deliveryOpen: helloWaitMs <= 0,
|
|
45154
45503
|
deliveryToken: /* @__PURE__ */ new Map(),
|
|
@@ -45202,14 +45551,14 @@ function attachAgentWebSocket(server, agent, opts) {
|
|
|
45202
45551
|
continue;
|
|
45203
45552
|
}
|
|
45204
45553
|
const out = withConsumerNotes(
|
|
45205
|
-
withSearchCapability(
|
|
45554
|
+
withUtilityCapability(withSearchCapability(
|
|
45206
45555
|
withManifestCapability(
|
|
45207
45556
|
withHarnessGrants(withConsumerGrants(batch[i], session.perToolGrants), !session.perToolGrants && agent.harnessGrantsWithheld()),
|
|
45208
45557
|
session.manifestGrants
|
|
45209
45558
|
),
|
|
45210
45559
|
session.searchProviders,
|
|
45211
45560
|
agent.signedSearchProvider()
|
|
45212
|
-
),
|
|
45561
|
+
), session.utilityTools),
|
|
45213
45562
|
batch[i],
|
|
45214
45563
|
session,
|
|
45215
45564
|
agent
|
|
@@ -45266,6 +45615,7 @@ function attachAgentWebSocket(server, agent, opts) {
|
|
|
45266
45615
|
if (session.perToolGrants) agent.clearPerToolConsumer(session);
|
|
45267
45616
|
if (session.manifestGrants) agent.clearManifestConsumer(session);
|
|
45268
45617
|
agent.clearSearchProvidersSocket(session);
|
|
45618
|
+
agent.clearUtilityToolsSocket(session);
|
|
45269
45619
|
};
|
|
45270
45620
|
current = { sock, cleanup, sweepStale };
|
|
45271
45621
|
sock.on("close", cleanup);
|
|
@@ -45306,7 +45656,10 @@ function sendJson(sock, body) {
|
|
|
45306
45656
|
} catch {
|
|
45307
45657
|
}
|
|
45308
45658
|
}
|
|
45659
|
+
var ReplyCreditUnknownError = class extends Error {
|
|
45660
|
+
};
|
|
45309
45661
|
function sendErrorCode(e) {
|
|
45662
|
+
if (e instanceof ReplyCreditUnknownError) return "reply_credit_unknown";
|
|
45310
45663
|
if (e instanceof ChannelPendingError) return "channel_pending";
|
|
45311
45664
|
if (e instanceof RateLimitedError) return "rate_limited";
|
|
45312
45665
|
if (e instanceof AgentPausedSendError) return "agent_paused";
|
|
@@ -45367,6 +45720,8 @@ async function handleInbound(sock, agent, data, inflight, flush, session) {
|
|
|
45367
45720
|
agent.noteConsumerCapability(manifest ? "ok" : session.harnessClaimed ? "harness_consumer" : "missing");
|
|
45368
45721
|
session.searchProviders = session.consumer === "runtime" && session.harnessClaimed !== true && perTool && Array.isArray(m.features) && m.features.includes(SEARCH_PROVIDERS_FEATURE);
|
|
45369
45722
|
agent.noteSearchProvidersSocket(session, session.searchProviders);
|
|
45723
|
+
session.utilityTools = session.consumer === "runtime" && session.harnessClaimed !== true && perTool && Array.isArray(m.features) && m.features.includes(UTILITY_TOOLS_FEATURE);
|
|
45724
|
+
agent.noteUtilityToolsSocket(session, session.utilityTools);
|
|
45370
45725
|
if (session.consumer === "runtime" && session.harnessClaimed !== true && Array.isArray(m.features) && m.features.includes(MEMORY_NOTES_FEATURE)) {
|
|
45371
45726
|
agent.noteNotesConsumer();
|
|
45372
45727
|
}
|
|
@@ -45374,7 +45729,7 @@ async function handleInbound(sock, agent, data, inflight, flush, session) {
|
|
|
45374
45729
|
if (session.recallConsumer) agent.noteRecallConsumer();
|
|
45375
45730
|
session.unifiedConsumer = agent.unifiedFeatureOn() && session.consumer === "runtime" && session.harnessClaimed !== true && Array.isArray(m.features) && m.features.includes(MEMORY_UNIFIED_FEATURE);
|
|
45376
45731
|
if (session.unifiedConsumer) agent.noteUnifiedConsumer();
|
|
45377
|
-
sendJson(sock, { type: "hello", features: ["nack", MEMORY_NOTES_FEATURE, MEMORY_RECALL_FEATURE, ...agent.unifiedFeatureOn() ? [MEMORY_UNIFIED_FEATURE] : [], MANIFEST_GRANTS_FEATURE, SEARCH_PROVIDERS_FEATURE] });
|
|
45732
|
+
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] });
|
|
45378
45733
|
if (!session.deliveryOpen) {
|
|
45379
45734
|
session.deliveryOpen = true;
|
|
45380
45735
|
flush();
|
|
@@ -45403,7 +45758,8 @@ async function handleInbound(sock, agent, data, inflight, flush, session) {
|
|
|
45403
45758
|
}
|
|
45404
45759
|
const replyFailed = (e, inReplyTo) => {
|
|
45405
45760
|
const code = sendErrorCode(e);
|
|
45406
|
-
|
|
45761
|
+
const terminal = code === "agent_paused" || code === "send_forbidden" || code === "reply_credit_unknown";
|
|
45762
|
+
if (terminal && typeof inReplyTo === "string") {
|
|
45407
45763
|
inflight.delete(inReplyTo);
|
|
45408
45764
|
session.deliveryToken.delete(inReplyTo);
|
|
45409
45765
|
session.shownRecall.delete(inReplyTo);
|
|
@@ -45411,7 +45767,7 @@ async function handleInbound(sock, agent, data, inflight, flush, session) {
|
|
|
45411
45767
|
agent.ackRuntimeMessages([inReplyTo]);
|
|
45412
45768
|
}
|
|
45413
45769
|
sendJson(sock, { ...wsErrorFrame(code), ...typeof inReplyTo === "string" ? { in_reply_to: inReplyTo } : {} });
|
|
45414
|
-
if (
|
|
45770
|
+
if (terminal) flush();
|
|
45415
45771
|
};
|
|
45416
45772
|
if (m.type === "send") {
|
|
45417
45773
|
if (typeof m.channel_id !== "string" || !m.channel_id) {
|
|
@@ -45428,8 +45784,14 @@ async function handleInbound(sock, agent, data, inflight, flush, session) {
|
|
|
45428
45784
|
}
|
|
45429
45785
|
const record = typeof m.in_reply_to === "string" ? inflight.get(m.in_reply_to) : void 0;
|
|
45430
45786
|
const callBufferReply = session.consumer === "runtime" && session.perToolGrants === true && session.harnessClaimed !== true && typeof m.in_reply_to === "string" && record !== void 0 && session.deliveryToken.get(m.in_reply_to) !== void 0 && record.channel_id === m.channel_id && !agent.isRuntimeMessageRetired(m.in_reply_to);
|
|
45787
|
+
if (m.credit !== void 0 && (m.a2a !== void 0 || !isReplyCredit(m.credit))) {
|
|
45788
|
+
writeServiceLog2("error", "reply.credit_unknown");
|
|
45789
|
+
replyFailed(new ReplyCreditUnknownError(), m.in_reply_to);
|
|
45790
|
+
return;
|
|
45791
|
+
}
|
|
45431
45792
|
const sendOpts = {
|
|
45432
45793
|
agentTurn: isAgentTurn(agent, m),
|
|
45794
|
+
...isReplyCredit(m.credit) ? { credit: m.credit } : {},
|
|
45433
45795
|
...typeof m.in_reply_to === "string" ? { inReplyTo: m.in_reply_to } : {},
|
|
45434
45796
|
...typeof m.idempotency_key === "string" ? { idempotencyKey: m.idempotency_key } : {},
|
|
45435
45797
|
...callBufferReply ? { callBufferReply: true } : {}
|