@securecoms/agent-bridge 0.19.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -5460,8 +5460,11 @@ var RestMlsBackend = class {
5460
5460
  queue_ids: queueIds
5461
5461
  });
5462
5462
  }
5463
- async nackDelivery(_deviceId, queueIds) {
5464
- await this.post("/api/v1/mls/delivery/nack", { queue_ids: queueIds });
5463
+ async nackDelivery(deviceId, queueIds) {
5464
+ await this.post("/api/v1/mls/delivery/nack", {
5465
+ device_id: deviceId,
5466
+ queue_ids: queueIds
5467
+ });
5465
5468
  }
5466
5469
  async listGroupMessages(deviceId, groupId, opts) {
5467
5470
  const params = new URLSearchParams({ device_id: deviceId, limit: String(opts?.limit ?? 100) });
@@ -5969,7 +5972,7 @@ import { DatabaseSync } from "node:sqlite";
5969
5972
  import { MLSGroupManager as MLSGroupManager3, bytesToHex as bytesToHex4, hexToBytes as hexToBytes3 } from "@agentvault/crypto";
5970
5973
 
5971
5974
  // ../connect-mls-store/dist/schema.js
5972
- var SCHEMA_VERSION = 6;
5975
+ var SCHEMA_VERSION = 7;
5973
5976
  function openSchema(db) {
5974
5977
  db.exec("PRAGMA journal_mode=WAL");
5975
5978
  const row = db.prepare("PRAGMA user_version").get();
@@ -6061,6 +6064,17 @@ function openSchema(db) {
6061
6064
  );
6062
6065
  `);
6063
6066
  }
6067
+ if (row.user_version < 7) {
6068
+ db.exec(`
6069
+ CREATE TABLE IF NOT EXISTS agent_memory_fact_assignment (
6070
+ identity_id TEXT NOT NULL,
6071
+ assignment_key TEXT NOT NULL,
6072
+ conversation_id TEXT NOT NULL,
6073
+ applied_at INTEGER NOT NULL,
6074
+ PRIMARY KEY (identity_id, assignment_key)
6075
+ );
6076
+ `);
6077
+ }
6064
6078
  db.exec(`PRAGMA user_version = ${SCHEMA_VERSION}`);
6065
6079
  }
6066
6080
 
@@ -6100,6 +6114,10 @@ function decodeBundle(bytes) {
6100
6114
  }
6101
6115
  };
6102
6116
  }
6117
+ var DAY_MS = 24 * 60 * 60 * 1e3;
6118
+ var DEFAULT_MEMORY_RETENTION_MS = 30 * DAY_MS;
6119
+ var DEFAULT_MEMORY_FACT_RETENTION_K = 200;
6120
+ var DEFAULT_MEMORY_FACT_RETENTION_MS = 90 * DAY_MS;
6103
6121
  var SqliteMlsStateStore = class {
6104
6122
  db;
6105
6123
  id;
@@ -6113,6 +6131,10 @@ var SqliteMlsStateStore = class {
6113
6131
  bundles = /* @__PURE__ */ new Map();
6114
6132
  lastKpIds = /* @__PURE__ */ new Map();
6115
6133
  memoryRetentionK;
6134
+ memoryRetentionMs;
6135
+ memoryFactRetentionK;
6136
+ memoryFactRetentionMs;
6137
+ memoryNow;
6116
6138
  // M0(b)/F4-1 + C1 + H2: in-memory ONLY for now — the daemon side of FileSafe
6117
6139
  // (this store) isn't wired into the multi-member vault yet (Task 5's capture
6118
6140
  // sites are all connect-web/browser owner paths). Kept here purely so
@@ -6127,6 +6149,10 @@ var SqliteMlsStateStore = class {
6127
6149
  this.id = opts.identityId;
6128
6150
  this.cipher = opts.cipherFor(opts.identityId);
6129
6151
  this.memoryRetentionK = opts.memoryRetentionK ?? 500;
6152
+ this.memoryRetentionMs = opts.memoryRetentionMs ?? DEFAULT_MEMORY_RETENTION_MS;
6153
+ this.memoryFactRetentionK = opts.memoryFactRetentionK ?? DEFAULT_MEMORY_FACT_RETENTION_K;
6154
+ this.memoryFactRetentionMs = opts.memoryFactRetentionMs ?? DEFAULT_MEMORY_FACT_RETENTION_MS;
6155
+ this.memoryNow = opts.memoryNow ?? Date.now;
6130
6156
  try {
6131
6157
  this.db = new DatabaseSync(opts.dbPath);
6132
6158
  } catch (e) {
@@ -6344,23 +6370,24 @@ var SqliteMlsStateStore = class {
6344
6370
  const dedupKey = input.messageId ?? input.idempotencyKey ?? null;
6345
6371
  if (!dedupKey)
6346
6372
  throw new Error("recordMemory requires message_id or idempotency_key");
6373
+ const now = this.memoryNow();
6374
+ this.pruneMemory(input.conversationId, now);
6347
6375
  const sealed = await this.cipher.seal(new TextEncoder().encode(input.text));
6348
6376
  const info = this.db.prepare(`INSERT OR IGNORE INTO agent_memory
6349
6377
  (identity_id, conversation_id, seq, role, peer_id, sealed_text, modality, message_id, idempotency_key, dedup_key, recorded_at)
6350
6378
  VALUES (?, ?,
6351
6379
  (SELECT COALESCE(MAX(seq),0)+1 FROM agent_memory WHERE identity_id=? AND conversation_id=?),
6352
- ?, ?, ?, ?, ?, ?, ?, ?)`).run(this.id, input.conversationId, this.id, input.conversationId, input.role, input.peerId ?? null, sealed, input.modality, input.messageId ?? null, input.idempotencyKey ?? null, dedupKey, Date.now());
6380
+ ?, ?, ?, ?, ?, ?, ?, ?)`).run(this.id, input.conversationId, this.id, input.conversationId, input.role, input.peerId ?? null, sealed, input.modality, input.messageId ?? null, input.idempotencyKey ?? null, dedupKey, now);
6353
6381
  if (info.changes === 0) {
6354
6382
  const row2 = this.db.prepare("SELECT seq FROM agent_memory WHERE identity_id=? AND conversation_id=? AND dedup_key=?").get(this.id, input.conversationId, dedupKey);
6355
6383
  return { seq: row2.seq, deduped: true };
6356
6384
  }
6357
6385
  const row = this.db.prepare("SELECT MAX(seq) AS seq FROM agent_memory WHERE identity_id=? AND conversation_id=?").get(this.id, input.conversationId);
6358
- if (this.memoryRetentionK > 0) {
6359
- this.db.prepare("DELETE FROM agent_memory WHERE identity_id=? AND conversation_id=? AND seq <= ?").run(this.id, input.conversationId, row.seq - this.memoryRetentionK);
6360
- }
6386
+ this.pruneMemoryTurns(input.conversationId, now);
6361
6387
  return { seq: row.seq, deduped: false };
6362
6388
  }
6363
6389
  async memoryContext(conversationId, limit = 50) {
6390
+ this.pruneMemory(conversationId, this.memoryNow());
6364
6391
  const rows = this.db.prepare(`SELECT seq, role, peer_id, sealed_text, recorded_at FROM agent_memory
6365
6392
  WHERE identity_id=? AND conversation_id=? ORDER BY seq DESC LIMIT ?`).all(this.id, conversationId, limit);
6366
6393
  const recent = [];
@@ -6375,9 +6402,94 @@ var SqliteMlsStateStore = class {
6375
6402
  return { recent, facts };
6376
6403
  }
6377
6404
  async recordMemoryFact(conversationId, fact) {
6405
+ const now = this.memoryNow();
6378
6406
  const sealed = await this.cipher.seal(new TextEncoder().encode(fact));
6379
6407
  this.db.prepare(`INSERT INTO agent_memory_fact (identity_id, conversation_id, fact_id, sealed_fact, added_at)
6380
- VALUES (?, ?, (SELECT COALESCE(MAX(fact_id),0)+1 FROM agent_memory_fact WHERE identity_id=? AND conversation_id=?), ?, ?)`).run(this.id, conversationId, this.id, conversationId, sealed, Date.now());
6408
+ VALUES (?, ?, (SELECT COALESCE(MAX(fact_id),0)+1 FROM agent_memory_fact WHERE identity_id=? AND conversation_id=?), ?, ?)`).run(this.id, conversationId, this.id, conversationId, sealed, now);
6409
+ this.pruneMemoryFacts(conversationId, now);
6410
+ }
6411
+ /**
6412
+ * Import one owner-approved legacy fact exactly once. `assignmentKey` is
6413
+ * content-free (`source-id:line-hash`) and intentionally excludes the target
6414
+ * channel: the same legacy fact can therefore never be replayed into a
6415
+ * second audience. The ledger and sealed fact commit atomically.
6416
+ */
6417
+ async recordImportedMemoryFact(conversationId, fact, assignmentKey) {
6418
+ if (!/^[0-9a-f]{64}:[0-9a-f]{64}$/.test(assignmentKey)) {
6419
+ throw new Error("invalid legacy memory assignment key");
6420
+ }
6421
+ const existing = this.db.prepare(`SELECT conversation_id FROM agent_memory_fact_assignment
6422
+ WHERE identity_id=? AND assignment_key=?`).get(this.id, assignmentKey);
6423
+ if (existing) {
6424
+ if (existing.conversation_id !== conversationId) {
6425
+ throw new Error("legacy memory fact is already assigned to another channel");
6426
+ }
6427
+ return { factId: 0, deduped: true };
6428
+ }
6429
+ const now = this.memoryNow();
6430
+ const sealed = await this.cipher.seal(new TextEncoder().encode(fact));
6431
+ this.db.exec("BEGIN IMMEDIATE");
6432
+ try {
6433
+ const raced = this.db.prepare(`SELECT conversation_id FROM agent_memory_fact_assignment
6434
+ WHERE identity_id=? AND assignment_key=?`).get(this.id, assignmentKey);
6435
+ if (raced) {
6436
+ if (raced.conversation_id !== conversationId) {
6437
+ throw new Error("legacy memory fact is already assigned to another channel");
6438
+ }
6439
+ this.db.exec("COMMIT");
6440
+ return { factId: 0, deduped: true };
6441
+ }
6442
+ const row = this.db.prepare("SELECT COALESCE(MAX(fact_id),0)+1 AS fact_id FROM agent_memory_fact WHERE identity_id=? AND conversation_id=?").get(this.id, conversationId);
6443
+ this.db.prepare(`INSERT INTO agent_memory_fact (identity_id, conversation_id, fact_id, sealed_fact, added_at)
6444
+ VALUES (?, ?, ?, ?, ?)`).run(this.id, conversationId, row.fact_id, sealed, now);
6445
+ this.db.prepare(`INSERT INTO agent_memory_fact_assignment (identity_id, assignment_key, conversation_id, applied_at)
6446
+ VALUES (?, ?, ?, ?)`).run(this.id, assignmentKey, conversationId, now);
6447
+ this.pruneMemoryFacts(conversationId, now);
6448
+ this.db.exec("COMMIT");
6449
+ return { factId: row.fact_id, deduped: false };
6450
+ } catch (error) {
6451
+ this.db.exec("ROLLBACK");
6452
+ throw error;
6453
+ }
6454
+ }
6455
+ /** Remove every durable turn and fact in one channel-scoped namespace. */
6456
+ purgeMemory(conversationId) {
6457
+ this.db.exec("BEGIN IMMEDIATE");
6458
+ try {
6459
+ this.db.prepare("DELETE FROM agent_memory WHERE identity_id=? AND conversation_id=?").run(this.id, conversationId);
6460
+ this.db.prepare("DELETE FROM agent_memory_fact WHERE identity_id=? AND conversation_id=?").run(this.id, conversationId);
6461
+ this.db.prepare("DELETE FROM agent_memory_fact_assignment WHERE identity_id=? AND conversation_id=?").run(this.id, conversationId);
6462
+ this.db.exec("COMMIT");
6463
+ } catch (error) {
6464
+ this.db.exec("ROLLBACK");
6465
+ throw error;
6466
+ }
6467
+ }
6468
+ pruneMemory(conversationId, now) {
6469
+ this.pruneMemoryTurns(conversationId, now);
6470
+ this.pruneMemoryFacts(conversationId, now);
6471
+ }
6472
+ pruneMemoryTurns(conversationId, now) {
6473
+ if (this.memoryRetentionMs > 0) {
6474
+ this.db.prepare("DELETE FROM agent_memory WHERE identity_id=? AND conversation_id=? AND recorded_at < ?").run(this.id, conversationId, now - this.memoryRetentionMs);
6475
+ }
6476
+ if (this.memoryRetentionK > 0) {
6477
+ const row = this.db.prepare("SELECT MAX(seq) AS seq FROM agent_memory WHERE identity_id=? AND conversation_id=?").get(this.id, conversationId);
6478
+ if (row.seq != null) {
6479
+ this.db.prepare("DELETE FROM agent_memory WHERE identity_id=? AND conversation_id=? AND seq <= ?").run(this.id, conversationId, row.seq - this.memoryRetentionK);
6480
+ }
6481
+ }
6482
+ }
6483
+ pruneMemoryFacts(conversationId, now) {
6484
+ if (this.memoryFactRetentionMs > 0) {
6485
+ this.db.prepare("DELETE FROM agent_memory_fact WHERE identity_id=? AND conversation_id=? AND added_at < ?").run(this.id, conversationId, now - this.memoryFactRetentionMs);
6486
+ }
6487
+ if (this.memoryFactRetentionK > 0) {
6488
+ const row = this.db.prepare("SELECT MAX(fact_id) AS fact_id FROM agent_memory_fact WHERE identity_id=? AND conversation_id=?").get(this.id, conversationId);
6489
+ if (row.fact_id != null) {
6490
+ this.db.prepare("DELETE FROM agent_memory_fact WHERE identity_id=? AND conversation_id=? AND fact_id <= ?").run(this.id, conversationId, row.fact_id - this.memoryFactRetentionK);
6491
+ }
6492
+ }
6381
6493
  }
6382
6494
  /** @internal test-only */
6383
6495
  _rawMainForTest(identityId, channelId) {
@@ -7469,6 +7581,7 @@ import { createHash, randomUUID } from "node:crypto";
7469
7581
  import {
7470
7582
  chmodSync,
7471
7583
  existsSync,
7584
+ lstatSync,
7472
7585
  mkdirSync,
7473
7586
  readFileSync as readFileSync6,
7474
7587
  renameSync,
@@ -7476,7 +7589,7 @@ import {
7476
7589
  unlinkSync,
7477
7590
  writeFileSync
7478
7591
  } from "node:fs";
7479
- import { basename as basename2, join as join8 } from "node:path";
7592
+ import { basename as basename2, isAbsolute, join as join8 } from "node:path";
7480
7593
  var MEMORY_FILE = "MEMORY.md";
7481
7594
  var REAL_FS = {
7482
7595
  mkdir: (p, mode) => mkdirSync(p, { recursive: true, mode }),
@@ -7551,6 +7664,54 @@ function lineHash(line) {
7551
7664
  return createHash("sha256").update(line.trim(), "utf8").digest("hex");
7552
7665
  }
7553
7666
  var LOCAL_REPORT_FILE = "local-config.json";
7667
+ var LEGACY_MEMORY_SOURCE_MAX_BYTES = 4 * 1024 * 1024;
7668
+ var LEGACY_MEMORY_REPORT_MAX_BYTES = 4 * 1024 * 1024;
7669
+ var LEGACY_MEMORY_REPORT_MAX_SOURCES = 100;
7670
+ var LEGACY_MEMORY_FACT_MAX_BYTES = 1024;
7671
+ var LEGACY_MEMORY_FACT_DECISION_MAX = 500;
7672
+ function legacySourceId(source, bytes) {
7673
+ return createHash("sha256").update(source.kind).update("\0").update(source.channel_id ?? "").update("\0").update(source.path).update("\0").update(bytes).digest("hex");
7674
+ }
7675
+ function localLegacySources(doc) {
7676
+ if (!Array.isArray(doc.legacy_memory)) return [];
7677
+ const out = [];
7678
+ let contentBudget = LEGACY_MEMORY_REPORT_MAX_BYTES;
7679
+ for (const raw of doc.legacy_memory) {
7680
+ if (out.length >= LEGACY_MEMORY_REPORT_MAX_SOURCES) break;
7681
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
7682
+ const source = raw;
7683
+ const kind = source.kind;
7684
+ const path = source.path;
7685
+ const channelId = source.channelId;
7686
+ if (kind !== "global-memory" && kind !== "conversation-history" && kind !== "captured-memory" || typeof path !== "string" || !isAbsolute(path) || channelId !== void 0 && typeof channelId !== "string") continue;
7687
+ try {
7688
+ const st = lstatSync(path);
7689
+ if (!st.isFile() || st.isSymbolicLink()) continue;
7690
+ if (st.size <= LEGACY_MEMORY_SOURCE_MAX_BYTES && st.size > contentBudget) continue;
7691
+ const oversize = st.size > LEGACY_MEMORY_SOURCE_MAX_BYTES;
7692
+ const bytes = oversize ? Buffer.from(`oversize:${st.size}:${st.mtimeMs}:${st.ctimeMs}`, "utf8") : Buffer.from(readFileSync6(path));
7693
+ if (!oversize && (bytes.byteLength > LEGACY_MEMORY_SOURCE_MAX_BYTES || bytes.byteLength > contentBudget)) {
7694
+ continue;
7695
+ }
7696
+ const exportable = !oversize;
7697
+ const candidate = {
7698
+ id: "",
7699
+ kind,
7700
+ ...typeof channelId === "string" && channelId ? { channel_id: channelId } : {},
7701
+ path,
7702
+ content: exportable ? bytes.toString("utf8") : null,
7703
+ bytes: exportable ? bytes.byteLength : st.size,
7704
+ exportable
7705
+ };
7706
+ candidate.id = legacySourceId(candidate, bytes);
7707
+ if (source.id !== candidate.id) continue;
7708
+ out.push(candidate);
7709
+ if (exportable) contentBudget -= bytes.byteLength;
7710
+ } catch {
7711
+ }
7712
+ }
7713
+ return out;
7714
+ }
7554
7715
  function readLocalReport(home, identityId) {
7555
7716
  const p = join8(runtimeConfigDir(home, identityId), LOCAL_REPORT_FILE);
7556
7717
  if (!existsSync(p)) return null;
@@ -7562,11 +7723,98 @@ function readLocalReport(home, identityId) {
7562
7723
  }
7563
7724
  if (!doc || typeof doc !== "object" || Array.isArray(doc)) return null;
7564
7725
  const hasSearchKey = doc.has_search_key === true;
7726
+ const hasLlmKey = doc.has_llm_key === true;
7565
7727
  const raw = doc.persona;
7566
7728
  const persona = raw && typeof raw === "object" && !Array.isArray(raw) ? Object.fromEntries(
7567
7729
  Object.entries(raw).filter(([, v]) => typeof v === "string")
7568
7730
  ) : {};
7569
- return { persona, has_search_key: hasSearchKey };
7731
+ return {
7732
+ persona,
7733
+ has_search_key: hasSearchKey,
7734
+ has_llm_key: hasLlmKey,
7735
+ legacy_memory: localLegacySources(doc).map(({ path: _path, ...source }) => source)
7736
+ };
7737
+ }
7738
+ function deleteLegacyMemorySources(home, identityId, ids) {
7739
+ if (ids.length === 0) return 0;
7740
+ const p = join8(runtimeConfigDir(home, identityId), LOCAL_REPORT_FILE);
7741
+ if (!existsSync(p)) return 0;
7742
+ let doc;
7743
+ try {
7744
+ doc = JSON.parse(readFileSync6(p, "utf8"));
7745
+ } catch {
7746
+ return 0;
7747
+ }
7748
+ const wanted = new Set(ids);
7749
+ let deleted = 0;
7750
+ for (const source of localLegacySources(doc)) {
7751
+ if (!wanted.has(source.id)) continue;
7752
+ try {
7753
+ unlinkSync(source.path);
7754
+ deleted += 1;
7755
+ } catch {
7756
+ }
7757
+ }
7758
+ return deleted;
7759
+ }
7760
+ async function applyLegacyMemoryFactDecisions(home, identityId, rawDecisions, recordFact) {
7761
+ if (!Array.isArray(rawDecisions) || rawDecisions.length > LEGACY_MEMORY_FACT_DECISION_MAX) {
7762
+ return { sourcesDeleted: 0, factsAssigned: 0 };
7763
+ }
7764
+ const bySource = /* @__PURE__ */ new Map();
7765
+ for (const raw of rawDecisions) {
7766
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
7767
+ return { sourcesDeleted: 0, factsAssigned: 0 };
7768
+ }
7769
+ const d = raw;
7770
+ if (typeof d.source_id !== "string" || !/^[0-9a-f]{64}$/.test(d.source_id) || typeof d.fact_hash !== "string" || !/^[0-9a-f]{64}$/.test(d.fact_hash) || d.action !== "assign" && d.action !== "delete" || d.action === "assign" && (typeof d.channel_id !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(d.channel_id)) || d.action === "delete" && d.channel_id !== void 0) return { sourcesDeleted: 0, factsAssigned: 0 };
7771
+ const decision = d;
7772
+ const current = bySource.get(decision.source_id);
7773
+ if (current === null) continue;
7774
+ const group = current ?? /* @__PURE__ */ new Map();
7775
+ const prior = group.get(decision.fact_hash);
7776
+ if (prior && (prior.action !== decision.action || prior.channel_id !== decision.channel_id)) {
7777
+ bySource.set(decision.source_id, null);
7778
+ continue;
7779
+ }
7780
+ group.set(decision.fact_hash, decision);
7781
+ bySource.set(decision.source_id, group);
7782
+ }
7783
+ const reportPath = join8(runtimeConfigDir(home, identityId), LOCAL_REPORT_FILE);
7784
+ if (!existsSync(reportPath)) return { sourcesDeleted: 0, factsAssigned: 0 };
7785
+ let doc;
7786
+ try {
7787
+ doc = JSON.parse(readFileSync6(reportPath, "utf8"));
7788
+ } catch {
7789
+ return { sourcesDeleted: 0, factsAssigned: 0 };
7790
+ }
7791
+ let sourcesDeleted = 0;
7792
+ let factsAssigned = 0;
7793
+ for (const source of localLegacySources(doc)) {
7794
+ const decisions = bySource.get(source.id);
7795
+ if (!decisions || source.kind !== "global-memory" || source.content === null) continue;
7796
+ const facts = source.content.split("\n").map((line) => line.trim()).filter(Boolean);
7797
+ const currentHashes = new Set(facts.map(lineHash));
7798
+ if (currentHashes.size === 0 || decisions.size !== currentHashes.size || [...currentHashes].some((hash) => !decisions.has(hash)) || [...decisions].some(([hash]) => !currentHashes.has(hash))) continue;
7799
+ const uniqueFacts = /* @__PURE__ */ new Map();
7800
+ for (const fact of facts) uniqueFacts.set(lineHash(fact), fact);
7801
+ const assignments = [...decisions].filter(([, decision]) => decision.action === "assign");
7802
+ if (assignments.some(([hash]) => Buffer.byteLength(uniqueFacts.get(hash) ?? "", "utf8") > LEGACY_MEMORY_FACT_MAX_BYTES)) {
7803
+ continue;
7804
+ }
7805
+ try {
7806
+ for (const [hash, decision] of assignments) {
7807
+ await recordFact(decision.channel_id, uniqueFacts.get(hash), `${source.id}:${hash}`);
7808
+ factsAssigned += 1;
7809
+ }
7810
+ const stillCurrent = localLegacySources(doc).some((candidate) => candidate.id === source.id);
7811
+ if (!stillCurrent) continue;
7812
+ unlinkSync(source.path);
7813
+ sourcesDeleted += 1;
7814
+ } catch {
7815
+ }
7816
+ }
7817
+ return { sourcesDeleted, factsAssigned };
7570
7818
  }
7571
7819
  function readManagedMemory(home, identityId) {
7572
7820
  const p = join8(runtimeConfigDir(home, identityId), MEMORY_FILE);
@@ -7595,6 +7843,51 @@ function pruneManagedMemory(home, identityId, hashes, deps = REAL_FS) {
7595
7843
 
7596
7844
  // src/bridge-agent.ts
7597
7845
  import { homedir as homedir2 } from "node:os";
7846
+
7847
+ // src/reply-receipts.ts
7848
+ var DEFAULT_TTL_MS = 15 * 60 * 1e3;
7849
+ var DEFAULT_CAPACITY2 = 256;
7850
+ var RuntimeReplyReceiptLedger = class {
7851
+ receipts = /* @__PURE__ */ new Map();
7852
+ ttlMs;
7853
+ capacity;
7854
+ now;
7855
+ constructor(opts = {}) {
7856
+ this.ttlMs = opts.ttlMs ?? DEFAULT_TTL_MS;
7857
+ this.capacity = opts.capacity ?? DEFAULT_CAPACITY2;
7858
+ this.now = opts.now ?? Date.now;
7859
+ if (!Number.isFinite(this.ttlMs) || this.ttlMs <= 0) throw new Error("reply receipt ttlMs must be positive");
7860
+ if (!Number.isInteger(this.capacity) || this.capacity <= 0) throw new Error("reply receipt capacity must be positive");
7861
+ }
7862
+ record(inReplyTo, channelId, replyMessageId) {
7863
+ const receipt = Object.freeze({
7864
+ in_reply_to: inReplyTo,
7865
+ reply_message_id: replyMessageId,
7866
+ channel_id: channelId,
7867
+ replied_at: this.now()
7868
+ });
7869
+ this.prune(receipt.replied_at);
7870
+ this.receipts.delete(inReplyTo);
7871
+ this.receipts.set(inReplyTo, receipt);
7872
+ while (this.receipts.size > this.capacity) {
7873
+ const oldest = this.receipts.keys().next().value;
7874
+ if (oldest === void 0) break;
7875
+ this.receipts.delete(oldest);
7876
+ }
7877
+ return receipt;
7878
+ }
7879
+ get(inReplyTo) {
7880
+ this.prune(this.now());
7881
+ return this.receipts.get(inReplyTo) ?? null;
7882
+ }
7883
+ prune(now) {
7884
+ for (const [key, receipt] of this.receipts) {
7885
+ if (now - receipt.replied_at >= this.ttlMs) this.receipts.delete(key);
7886
+ }
7887
+ }
7888
+ };
7889
+
7890
+ // src/bridge-agent.ts
7598
7891
  var VOICE_MODE_GUIDANCE = "Reply immediately and conversationally, one short sentence per message; never bundle multiple points into one message.";
7599
7892
  var FAST_PATH_SPOKEN_GUIDANCE = "A one-sentence spoken acknowledgement has already been sent for this turn. Reply only with information it did not contain, in one or two short sentences; if there is nothing to add, send nothing.";
7600
7893
  var ROOM_VOICE_GUIDANCE_MENTION_ONLY = "You are in a group voice room. Reply ONLY when a message @mentions your display name. Keep replies to one short sentence. When you should not reply, send nothing.";
@@ -7864,6 +8157,12 @@ var BridgeAgent = class _BridgeAgent {
7864
8157
  channelOwner = /* @__PURE__ */ new Map();
7865
8158
  buffer = [];
7866
8159
  maxBuffer;
8160
+ /**
8161
+ * Short-lived, content-free runtime reply correlations. The attached runtime
8162
+ * supplies the inbound message id only when an outbound send is its reply;
8163
+ * ws-server/http-server record the receipt after send() publishes.
8164
+ */
8165
+ runtimeReplyReceipts = new RuntimeReplyReceiptLedger();
7867
8166
  /** Cumulative count of messages shed by the buffer cap (surfaced on health()/metrics()). */
7868
8167
  droppedMessages = 0;
7869
8168
  /**
@@ -8089,6 +8388,7 @@ var BridgeAgent = class _BridgeAgent {
8089
8388
  waiters = [];
8090
8389
  pingHandle;
8091
8390
  safetyTimer;
8391
+ runtimeConfigTimer;
8092
8392
  listeners = /* @__PURE__ */ new Set();
8093
8393
  startedAt = 0;
8094
8394
  store;
@@ -8198,25 +8498,65 @@ var BridgeAgent = class _BridgeAgent {
8198
8498
  });
8199
8499
  if (!res.ok) throw new Error(`runtime-config ${res.status}`);
8200
8500
  const payload = await res.json();
8201
- if (!payload || !payload.version) {
8501
+ if (!payload) {
8502
+ process.stderr.write("[bridge] runtime config not applied (empty response)\n");
8503
+ return;
8504
+ }
8505
+ if (typeof payload.version !== "number") {
8202
8506
  process.stderr.write(
8203
- `[bridge] runtime config not applied (version ${JSON.stringify(payload?.version ?? null)} \u2014 no saved config)
8507
+ `[bridge] runtime config not applied (version ${JSON.stringify(payload.version ?? null)} \u2014 invalid response)
8204
8508
  `
8205
8509
  );
8206
8510
  return;
8207
8511
  }
8512
+ const version = payload.version;
8513
+ if (!version) {
8514
+ writeManagedFiles(this.home, this.cfg.identityId, {});
8515
+ process.stderr.write(
8516
+ `[bridge] runtime config not applied (version ${JSON.stringify(payload?.version ?? null)} \u2014 no saved config)
8517
+ `
8518
+ );
8519
+ }
8208
8520
  const skillsJson = payload.skills_json ?? {};
8209
- writeManagedFiles(this.home, this.cfg.identityId, {
8210
- ...payload.files ?? {},
8211
- "skills.json": `${JSON.stringify(skillsJson, null, 2)}
8521
+ if (version) {
8522
+ writeManagedFiles(this.home, this.cfg.identityId, {
8523
+ ...payload.files ?? {},
8524
+ "skills.json": `${JSON.stringify(skillsJson, null, 2)}
8212
8525
  `
8213
- });
8526
+ });
8527
+ }
8214
8528
  if (payload.memory_prunes?.length) {
8215
8529
  pruneManagedMemory(this.home, this.cfg.identityId, payload.memory_prunes);
8216
8530
  }
8531
+ if (payload.legacy_memory_deletes?.length) {
8532
+ deleteLegacyMemorySources(this.home, this.cfg.identityId, payload.legacy_memory_deletes);
8533
+ }
8534
+ if (payload.legacy_memory_fact_decisions?.length) {
8535
+ const provider = this.cfg.memoryProvider ?? "securecoms";
8536
+ const result = await applyLegacyMemoryFactDecisions(
8537
+ this.home,
8538
+ this.cfg.identityId,
8539
+ payload.legacy_memory_fact_decisions,
8540
+ async (channelId, fact, assignmentKey) => {
8541
+ if (provider === "none" || provider === "hermes") {
8542
+ throw new Error("configured memory provider has no protected channel-fact import path");
8543
+ }
8544
+ if (provider === "openclaw" && !await this.mayReadOpenClawMemory(channelId)) {
8545
+ throw new Error("OpenClaw legacy fact target is not the active owner DM");
8546
+ }
8547
+ await this.store.recordImportedMemoryFact(channelId, fact, assignmentKey);
8548
+ }
8549
+ );
8550
+ if (result.sourcesDeleted > 0) {
8551
+ process.stderr.write(
8552
+ `[bridge] legacy-memory disposition applied (sources=${result.sourcesDeleted}, assigned=${result.factsAssigned})
8553
+ `
8554
+ );
8555
+ }
8556
+ }
8217
8557
  const local = readLocalReport(this.home, this.cfg.identityId);
8218
8558
  const report = {
8219
- applied_version: payload.version,
8559
+ applied_version: version,
8220
8560
  paths: checkConfiguredPaths(skillsJson),
8221
8561
  // Read AFTER the prunes above, so the owner's next view of memory is
8222
8562
  // the file as it now stands, not the one they just pruned from.
@@ -8228,7 +8568,18 @@ var BridgeAgent = class _BridgeAgent {
8228
8568
  // is an environment value on that process, invisible to the daemon and
8229
8569
  // to the backend. The backend needs it to generate a truthful tool
8230
8570
  // paragraph, so it rides along here.
8231
- has_search_key: local?.has_search_key ?? false
8571
+ has_search_key: local?.has_search_key ?? false,
8572
+ // Same idea, for the BYOK LLM key file: only the runtime can see
8573
+ // whether one is configured in its own environment.
8574
+ has_llm_key: local?.has_llm_key ?? false,
8575
+ // The backend uses this only to expose target channels compatible with
8576
+ // the actual protected-memory adapter. It is an enum, never a path or
8577
+ // provider credential.
8578
+ memory_provider: this.cfg.memoryProvider ?? "securecoms",
8579
+ // Inert legacy files only. `readLocalReport` revalidates the current
8580
+ // regular-file bytes and strips every local path before this leaves the
8581
+ // machine.
8582
+ legacy_memory: local?.legacy_memory ?? []
8232
8583
  };
8233
8584
  const posted = await this.fetchImpl(`${base}/report`, {
8234
8585
  method: "POST",
@@ -8240,13 +8591,16 @@ var BridgeAgent = class _BridgeAgent {
8240
8591
  });
8241
8592
  if (!posted.ok) {
8242
8593
  process.stderr.write(
8243
- `[bridge] runtime config v${payload.version} written, but reporting it failed (${posted.status}) \u2014 the console will keep showing it as not applied until the next cycle
8594
+ version ? `[bridge] runtime config v${version} written, but reporting it failed (${posted.status}) \u2014 the console will keep showing it as not applied until the next cycle
8595
+ ` : `[bridge] legacy-memory inventory report failed (${posted.status}); no saved runtime config was applied
8244
8596
  `
8245
8597
  );
8246
8598
  return;
8247
8599
  }
8248
- process.stderr.write(`[bridge] runtime config v${payload.version} applied
8249
- `);
8600
+ process.stderr.write(
8601
+ version ? `[bridge] runtime config v${version} applied
8602
+ ` : "[bridge] legacy-memory inventory reported; no saved runtime config was applied\n"
8603
+ );
8250
8604
  } catch (e) {
8251
8605
  process.stderr.write(
8252
8606
  `[bridge] runtime config refetch failed: ${e?.message ?? e}
@@ -8282,6 +8636,10 @@ var BridgeAgent = class _BridgeAgent {
8282
8636
  await this.provider.publishKeyPackage();
8283
8637
  await this.refetchVoiceSettings();
8284
8638
  await this.refetchRuntimeConfig();
8639
+ this.runtimeConfigTimer = setInterval(() => {
8640
+ void this.refetchRuntimeConfig();
8641
+ }, 6 * 60 * 60 * 1e3);
8642
+ this.runtimeConfigTimer.unref?.();
8285
8643
  this.startReceivePump();
8286
8644
  if (this.selfHealSweepEnabled) {
8287
8645
  this.bootSweepTimer = setTimeout(
@@ -9084,7 +9442,47 @@ var BridgeAgent = class _BridgeAgent {
9084
9442
  async recordMemory(input) {
9085
9443
  return this.memory.record(input);
9086
9444
  }
9445
+ /**
9446
+ * OpenClaw collapses text history and workspace facts into the owner's
9447
+ * session. Resolve the channel from fresh, authenticated server metadata
9448
+ * before exposing that provider through the local bridge API. A cold or
9449
+ * stale cache is refreshed once; any failure remains a fail-closed denial.
9450
+ */
9451
+ async mayReadOpenClawMemory(channelId) {
9452
+ if (this.cfg.memoryProvider !== "openclaw") return true;
9453
+ if (!this.freshMeta(channelId)) await this.refreshChannelMeta();
9454
+ if (!this.freshRoster(channelId)) await this.refreshChannelRoster(channelId);
9455
+ const meta = this.freshMeta(channelId);
9456
+ const roster = this.freshRoster(channelId);
9457
+ if (meta?.is_dm !== true || !roster) return false;
9458
+ const humanCounterparts = [...roster.entries()].filter(
9459
+ ([identityId, member]) => identityId !== this.cfg.identityId && !member.is_agent
9460
+ );
9461
+ return humanCounterparts.length === 1 && humanCounterparts[0][1].is_agent_owner;
9462
+ }
9463
+ /** An inbound turn may receive OpenClaw memory only from an active owner DM. */
9464
+ mayReadOpenClawMemoryForTurn(authorization) {
9465
+ return this.cfg.memoryProvider !== "openclaw" || authorization.sender_membership === "active" && authorization.audience === "owner_dm";
9466
+ }
9467
+ /**
9468
+ * Resolve the prior-turn snapshot carried to the native runtime. OpenClaw
9469
+ * stays owner-DM-only; every provider failure degrades to empty context and
9470
+ * never falls back to the runtime's retired plaintext JSON files.
9471
+ */
9472
+ async memoryForTurn(channelId, authorization, limit = 50) {
9473
+ if (!this.mayReadOpenClawMemoryForTurn(authorization)) {
9474
+ return { recent: [], facts: [] };
9475
+ }
9476
+ try {
9477
+ return await this.memory.context(channelId, limit);
9478
+ } catch {
9479
+ return { recent: [], facts: [] };
9480
+ }
9481
+ }
9087
9482
  async memoryContext(conversationId, limit) {
9483
+ if (!await this.mayReadOpenClawMemory(conversationId)) {
9484
+ return { recent: [], facts: [] };
9485
+ }
9088
9486
  return this.memory.context(conversationId, limit);
9089
9487
  }
9090
9488
  /**
@@ -9128,10 +9526,14 @@ var BridgeAgent = class _BridgeAgent {
9128
9526
  }
9129
9527
  try {
9130
9528
  let memory;
9131
- try {
9132
- memory = await this.memory.context(m.channelId, 30);
9133
- } catch {
9529
+ if (!this.mayReadOpenClawMemoryForTurn(m.authorization)) {
9134
9530
  memory = { recent: [], facts: [] };
9531
+ } else {
9532
+ try {
9533
+ memory = await this.memory.context(m.channelId, 30);
9534
+ } catch {
9535
+ memory = { recent: [], facts: [] };
9536
+ }
9135
9537
  }
9136
9538
  const messages = buildFastMessages({
9137
9539
  persona: this.cfg.voice.persona,
@@ -9384,13 +9786,42 @@ var BridgeAgent = class _BridgeAgent {
9384
9786
  const rows = await res.json();
9385
9787
  const members = /* @__PURE__ */ new Map();
9386
9788
  for (const r of rows) {
9387
- members.set(r.identity_id, { name: r.display_name, is_agent: r.is_agent });
9789
+ members.set(r.identity_id, {
9790
+ name: r.display_name,
9791
+ is_agent: r.is_agent,
9792
+ is_agent_owner: r.is_agent_owner === true,
9793
+ is_partner: r.is_partner === true
9794
+ });
9388
9795
  }
9389
9796
  this.channelRoster.set(channelId, { members, at: Date.now() });
9390
9797
  await this.resolveAndPinOwner(channelId, rows);
9391
9798
  } catch {
9392
9799
  }
9393
9800
  }
9801
+ /**
9802
+ * Classify the sender only from server-authenticated channel/roster data.
9803
+ * Missing or stale metadata is UNKNOWN with zero grants, never a permissive
9804
+ * guess. The returned object and grant array are frozen before they cross the
9805
+ * daemon boundary so one consumer cannot widen a later execution check.
9806
+ */
9807
+ authorizationFor(channelId, senderId) {
9808
+ const meta = this.freshMeta(channelId);
9809
+ const sender = this.freshRoster(channelId)?.get(senderId);
9810
+ let audience = "unknown";
9811
+ if (meta && sender) {
9812
+ if (sender.is_agent) audience = "agent_to_agent";
9813
+ else if (meta.is_dm && sender.is_agent_owner) audience = "owner_dm";
9814
+ else if (meta.is_dm) audience = "other_human_dm";
9815
+ else if (sender.is_partner) audience = "partner_room";
9816
+ else audience = "team_room";
9817
+ }
9818
+ return Object.freeze({
9819
+ version: 1,
9820
+ audience,
9821
+ sender_membership: sender ? "active" : "unknown",
9822
+ tool_grants: Object.freeze([])
9823
+ });
9824
+ }
9394
9825
  /**
9395
9826
  * Task 8 (owner-attested Reset Secure Session): STRUCTURAL owner selection —
9396
9827
  * NEVER read the server's `role` field to pick the owner. In a DM the owner
@@ -9904,6 +10335,7 @@ var BridgeAgent = class _BridgeAgent {
9904
10335
  sender_id: m.envelope.sender_id,
9905
10336
  text: typeof parsed.text === "string" ? parsed.text : "",
9906
10337
  ts: m.receivedAt,
10338
+ authorization: this.authorizationFor(m.channelId, m.envelope.sender_id),
9907
10339
  sender_name: senderName,
9908
10340
  sender_is_agent: senderIsAgent,
9909
10341
  attachments: [attachment],
@@ -9942,6 +10374,11 @@ var BridgeAgent = class _BridgeAgent {
9942
10374
  const senderName = this.freshRoster(m.channelId)?.get(
9943
10375
  m.envelope.sender_id
9944
10376
  )?.name;
10377
+ const turnAuthorization = this.authorizationFor(
10378
+ m.channelId,
10379
+ m.envelope.sender_id
10380
+ );
10381
+ const runtimeMemory = this.memoryForTurn(m.channelId, turnAuthorization);
9945
10382
  const speaker = parsed.relay === true && typeof parsed.speaker_identity_id === "string" && typeof parsed.speaker_display_name === "string" ? {
9946
10383
  identityId: parsed.speaker_identity_id,
9947
10384
  displayName: parsed.speaker_display_name
@@ -9958,12 +10395,14 @@ var BridgeAgent = class _BridgeAgent {
9958
10395
  const turnText = parsed.text;
9959
10396
  const isCallStart = turnText.trim() === VOICE_CALL_START_MARKER;
9960
10397
  const fastGreeting = isCallStart && fastPathFiring;
9961
- const runtimeEntry = (ackSent) => ({
10398
+ const runtimeEntry = (ackSent, memory) => ({
9962
10399
  channel_id: m.channelId,
9963
10400
  message_id: m.messageId,
9964
10401
  sender_id: m.envelope.sender_id,
9965
10402
  text: turnText,
9966
10403
  ts: m.receivedAt,
10404
+ authorization: turnAuthorization,
10405
+ memory,
9967
10406
  // A relay-attributed turn is the SPEAKER's message to the runtime:
9968
10407
  // name it after them and never flag it as agent-authored.
9969
10408
  sender_name: speaker?.displayName ?? senderName,
@@ -9977,18 +10416,23 @@ var BridgeAgent = class _BridgeAgent {
9977
10416
  if (fastPathFiring) {
9978
10417
  const fastSpeaker = speaker ?? (senderName ? { identityId: m.envelope.sender_id, displayName: senderName } : void 0);
9979
10418
  void this.runFastPath(
9980
- { channelId: m.channelId, messageId: m.messageId, senderId: m.envelope.sender_id },
10419
+ {
10420
+ channelId: m.channelId,
10421
+ messageId: m.messageId,
10422
+ senderId: m.envelope.sender_id,
10423
+ authorization: turnAuthorization
10424
+ },
9981
10425
  fastGreeting ? greetingTurnFor(this.voiceSettings ?? DEFAULT_VOICE_SETTINGS, fastSpeaker?.displayName) : parsed.text,
9982
10426
  voiceGuidance,
9983
10427
  fastSpeaker
9984
- ).then((outcome) => {
10428
+ ).then(async (outcome) => {
9985
10429
  if (outcome === "answered") return;
9986
- this.buffer.push(runtimeEntry(outcome === "deferred"));
10430
+ this.buffer.push(runtimeEntry(outcome === "deferred", await runtimeMemory));
9987
10431
  this.capBuffer();
9988
10432
  this.wakeListeners();
9989
10433
  });
9990
10434
  } else {
9991
- this.buffer.push(runtimeEntry(false));
10435
+ this.buffer.push(runtimeEntry(false, await runtimeMemory));
9992
10436
  }
9993
10437
  this.capBuffer();
9994
10438
  void recordInboundMemory(
@@ -10054,6 +10498,12 @@ var BridgeAgent = class _BridgeAgent {
10054
10498
  this.buffer.push(...keep);
10055
10499
  return out;
10056
10500
  }
10501
+ recordRuntimeReply(inReplyTo, channelId, replyMessageId) {
10502
+ return this.runtimeReplyReceipts.record(inReplyTo, channelId, replyMessageId);
10503
+ }
10504
+ runtimeReplyReceipt(inReplyTo) {
10505
+ return this.runtimeReplyReceipts.get(inReplyTo);
10506
+ }
10057
10507
  /**
10058
10508
  * Put messages back at the front (SSE/disconnect write-failure recovery).
10059
10509
  *
@@ -10205,6 +10655,7 @@ var BridgeAgent = class _BridgeAgent {
10205
10655
  this.closingDown = true;
10206
10656
  this.pingHandle?.close();
10207
10657
  if (this.safetyTimer) clearInterval(this.safetyTimer);
10658
+ if (this.runtimeConfigTimer) clearInterval(this.runtimeConfigTimer);
10208
10659
  if (this.bootSweepTimer) clearTimeout(this.bootSweepTimer);
10209
10660
  if (this.selfHealTimer) clearTimeout(this.selfHealTimer);
10210
10661
  await this.provider.drainInFlight(_BridgeAgent.GRACEFUL_DRAIN_MS);
@@ -10330,6 +10781,7 @@ var sendSchema = z3.object({
10330
10781
  channel_id: z3.string().min(1),
10331
10782
  text: z3.string().optional(),
10332
10783
  idempotency_key: z3.string().optional(),
10784
+ in_reply_to: z3.string().min(1).max(200).optional(),
10333
10785
  a2a: a2aSchema.optional()
10334
10786
  }).refine((d) => typeof d.text === "string" || d.a2a !== void 0, {
10335
10787
  message: "send requires either text or a2a"
@@ -10446,10 +10898,19 @@ function createHttpServer(agent, opts) {
10446
10898
  if (method === "GET" && path === "/v1/metrics") {
10447
10899
  return json(res, 200, agent.metrics());
10448
10900
  }
10901
+ if (method === "GET" && path === "/v1/reply-receipt") {
10902
+ const inReplyTo = url.searchParams.get("in_reply_to");
10903
+ if (!inReplyTo || inReplyTo.length > 200) return jsonError(res, "invalid_request");
10904
+ const receipt = agent.runtimeReplyReceipt(inReplyTo);
10905
+ return json(res, 200, receipt ? { found: true, receipt } : { found: false });
10906
+ }
10449
10907
  if (method === "POST" && path === "/v1/send") {
10450
10908
  const parsed = sendSchema.safeParse(JSON.parse(await readBody(req, SEND_CAP)));
10451
10909
  if (!parsed.success) return jsonError(res, "invalid_request", parsed.error.issues);
10452
10910
  const r = parsed.data.a2a ? await agent.sendA2A(parsed.data.channel_id, parsed.data.a2a) : await agent.send(parsed.data.channel_id, parsed.data.text);
10911
+ if (parsed.data.in_reply_to) {
10912
+ agent.recordRuntimeReply(parsed.data.in_reply_to, parsed.data.channel_id, r.message_id);
10913
+ }
10453
10914
  return json(res, 200, r);
10454
10915
  }
10455
10916
  if (method === "POST" && path === "/v1/send-attachment") {
@@ -10705,6 +11166,10 @@ async function handleInbound(sock, agent, data, inflight) {
10705
11166
  sendJson(sock, wsErrorFrame("invalid_request"));
10706
11167
  return;
10707
11168
  }
11169
+ if (m.in_reply_to !== void 0 && (typeof m.in_reply_to !== "string" || m.in_reply_to.length === 0 || m.in_reply_to.length > 200)) {
11170
+ sendJson(sock, wsErrorFrame("invalid_request"));
11171
+ return;
11172
+ }
10708
11173
  if (m.a2a && typeof m.a2a === "object") {
10709
11174
  const a = m.a2a;
10710
11175
  if (typeof a.a2a_version !== "string" || !a.a2a_version || a.kind !== "message" && a.kind !== "task" && a.kind !== "artifact" || typeof a.object !== "object" || a.object === null) {
@@ -10713,6 +11178,9 @@ async function handleInbound(sock, agent, data, inflight) {
10713
11178
  }
10714
11179
  try {
10715
11180
  const r = await agent.sendA2A(m.channel_id, { a2a_version: a.a2a_version, kind: a.kind, object: a.object });
11181
+ if (typeof m.in_reply_to === "string") {
11182
+ agent.recordRuntimeReply(m.in_reply_to, m.channel_id, r.message_id);
11183
+ }
10716
11184
  sendJson(sock, { type: "sent", message_id: r.message_id });
10717
11185
  } catch (e) {
10718
11186
  if (e instanceof ChannelPendingError) {
@@ -10731,6 +11199,9 @@ async function handleInbound(sock, agent, data, inflight) {
10731
11199
  }
10732
11200
  try {
10733
11201
  const r = await agent.send(m.channel_id, m.text);
11202
+ if (typeof m.in_reply_to === "string") {
11203
+ agent.recordRuntimeReply(m.in_reply_to, m.channel_id, r.message_id);
11204
+ }
10734
11205
  sendJson(sock, { type: "sent", message_id: r.message_id });
10735
11206
  } catch (e) {
10736
11207
  if (e instanceof ChannelPendingError) {
@@ -10842,7 +11313,7 @@ async function handleInbound(sock, agent, data, inflight) {
10842
11313
 
10843
11314
  // src/bin.ts
10844
11315
  import { readFileSync as readFileSync9, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, rmSync, existsSync as existsSync3, realpathSync, readdirSync as readdirSync2, statSync as statSync4 } from "node:fs";
10845
- import { join as join13, dirname as dirname7, isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
11316
+ import { join as join13, dirname as dirname7, isAbsolute as isAbsolute3, resolve as resolve2 } from "node:path";
10846
11317
  import { fileURLToPath as fileURLToPath2 } from "node:url";
10847
11318
  import { homedir as homedir3, userInfo } from "node:os";
10848
11319
  import { spawnSync } from "node:child_process";
@@ -10851,7 +11322,7 @@ import { spawnSync } from "node:child_process";
10851
11322
  import { dirname as dirname4 } from "node:path";
10852
11323
 
10853
11324
  // src/service/config.ts
10854
- import { basename as basename3, extname, isAbsolute, resolve } from "node:path";
11325
+ import { basename as basename3, extname, isAbsolute as isAbsolute2, resolve } from "node:path";
10855
11326
 
10856
11327
  // src/service/resolve-node.ts
10857
11328
  var REQUIRED_NODE_MAJOR = 24;
@@ -10931,9 +11402,9 @@ function deriveServiceConfig(input) {
10931
11402
  }
10932
11403
  const stem = input.label ?? basename3(input.credentialPath, extname(input.credentialPath));
10933
11404
  const slug = slugify(stem);
10934
- const cred = isAbsolute(input.credentialPath) ? input.credentialPath : resolve(input.credentialPath);
11405
+ const cred = isAbsolute2(input.credentialPath) ? input.credentialPath : resolve(input.credentialPath);
10935
11406
  const args2 = ["--credential", cred];
10936
- if (input.storePath) args2.push("--store", isAbsolute(input.storePath) ? input.storePath : resolve(input.storePath));
11407
+ if (input.storePath) args2.push("--store", isAbsolute2(input.storePath) ? input.storePath : resolve(input.storePath));
10937
11408
  if (input.port != null) args2.push("--port", String(input.port));
10938
11409
  return {
10939
11410
  slug,
@@ -12044,7 +12515,7 @@ async function runServiceVerb(verb) {
12044
12515
  }
12045
12516
  try {
12046
12517
  if (verb === "install") {
12047
- const credAbs = isAbsolute2(credentialPath) ? credentialPath : resolve2(credentialPath);
12518
+ const credAbs = isAbsolute3(credentialPath) ? credentialPath : resolve2(credentialPath);
12048
12519
  const installExecPath = chooseInstallExecPath(
12049
12520
  process.execPath,
12050
12521
  argOrEnv("--exec-path", "SECURECOMS_EXEC_PATH"),