@securecoms/agent-bridge 0.20.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;
@@ -7567,7 +7728,93 @@ function readLocalReport(home, identityId) {
7567
7728
  const persona = raw && typeof raw === "object" && !Array.isArray(raw) ? Object.fromEntries(
7568
7729
  Object.entries(raw).filter(([, v]) => typeof v === "string")
7569
7730
  ) : {};
7570
- return { persona, has_search_key: hasSearchKey, has_llm_key: hasLlmKey };
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 };
7571
7818
  }
7572
7819
  function readManagedMemory(home, identityId) {
7573
7820
  const p = join8(runtimeConfigDir(home, identityId), MEMORY_FILE);
@@ -7596,6 +7843,51 @@ function pruneManagedMemory(home, identityId, hashes, deps = REAL_FS) {
7596
7843
 
7597
7844
  // src/bridge-agent.ts
7598
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
7599
7891
  var VOICE_MODE_GUIDANCE = "Reply immediately and conversationally, one short sentence per message; never bundle multiple points into one message.";
7600
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.";
7601
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.";
@@ -7865,6 +8157,12 @@ var BridgeAgent = class _BridgeAgent {
7865
8157
  channelOwner = /* @__PURE__ */ new Map();
7866
8158
  buffer = [];
7867
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();
7868
8166
  /** Cumulative count of messages shed by the buffer cap (surfaced on health()/metrics()). */
7869
8167
  droppedMessages = 0;
7870
8168
  /**
@@ -8090,6 +8388,7 @@ var BridgeAgent = class _BridgeAgent {
8090
8388
  waiters = [];
8091
8389
  pingHandle;
8092
8390
  safetyTimer;
8391
+ runtimeConfigTimer;
8093
8392
  listeners = /* @__PURE__ */ new Set();
8094
8393
  startedAt = 0;
8095
8394
  store;
@@ -8199,25 +8498,65 @@ var BridgeAgent = class _BridgeAgent {
8199
8498
  });
8200
8499
  if (!res.ok) throw new Error(`runtime-config ${res.status}`);
8201
8500
  const payload = await res.json();
8202
- 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") {
8203
8506
  process.stderr.write(
8204
- `[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)
8205
8508
  `
8206
8509
  );
8207
8510
  return;
8208
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
+ }
8209
8520
  const skillsJson = payload.skills_json ?? {};
8210
- writeManagedFiles(this.home, this.cfg.identityId, {
8211
- ...payload.files ?? {},
8212
- "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)}
8213
8525
  `
8214
- });
8526
+ });
8527
+ }
8215
8528
  if (payload.memory_prunes?.length) {
8216
8529
  pruneManagedMemory(this.home, this.cfg.identityId, payload.memory_prunes);
8217
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
+ }
8218
8557
  const local = readLocalReport(this.home, this.cfg.identityId);
8219
8558
  const report = {
8220
- applied_version: payload.version,
8559
+ applied_version: version,
8221
8560
  paths: checkConfiguredPaths(skillsJson),
8222
8561
  // Read AFTER the prunes above, so the owner's next view of memory is
8223
8562
  // the file as it now stands, not the one they just pruned from.
@@ -8232,7 +8571,15 @@ var BridgeAgent = class _BridgeAgent {
8232
8571
  has_search_key: local?.has_search_key ?? false,
8233
8572
  // Same idea, for the BYOK LLM key file: only the runtime can see
8234
8573
  // whether one is configured in its own environment.
8235
- has_llm_key: local?.has_llm_key ?? false
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 ?? []
8236
8583
  };
8237
8584
  const posted = await this.fetchImpl(`${base}/report`, {
8238
8585
  method: "POST",
@@ -8244,13 +8591,16 @@ var BridgeAgent = class _BridgeAgent {
8244
8591
  });
8245
8592
  if (!posted.ok) {
8246
8593
  process.stderr.write(
8247
- `[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
8248
8596
  `
8249
8597
  );
8250
8598
  return;
8251
8599
  }
8252
- process.stderr.write(`[bridge] runtime config v${payload.version} applied
8253
- `);
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
+ );
8254
8604
  } catch (e) {
8255
8605
  process.stderr.write(
8256
8606
  `[bridge] runtime config refetch failed: ${e?.message ?? e}
@@ -8286,6 +8636,10 @@ var BridgeAgent = class _BridgeAgent {
8286
8636
  await this.provider.publishKeyPackage();
8287
8637
  await this.refetchVoiceSettings();
8288
8638
  await this.refetchRuntimeConfig();
8639
+ this.runtimeConfigTimer = setInterval(() => {
8640
+ void this.refetchRuntimeConfig();
8641
+ }, 6 * 60 * 60 * 1e3);
8642
+ this.runtimeConfigTimer.unref?.();
8289
8643
  this.startReceivePump();
8290
8644
  if (this.selfHealSweepEnabled) {
8291
8645
  this.bootSweepTimer = setTimeout(
@@ -9088,7 +9442,47 @@ var BridgeAgent = class _BridgeAgent {
9088
9442
  async recordMemory(input) {
9089
9443
  return this.memory.record(input);
9090
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
+ }
9091
9482
  async memoryContext(conversationId, limit) {
9483
+ if (!await this.mayReadOpenClawMemory(conversationId)) {
9484
+ return { recent: [], facts: [] };
9485
+ }
9092
9486
  return this.memory.context(conversationId, limit);
9093
9487
  }
9094
9488
  /**
@@ -9132,10 +9526,14 @@ var BridgeAgent = class _BridgeAgent {
9132
9526
  }
9133
9527
  try {
9134
9528
  let memory;
9135
- try {
9136
- memory = await this.memory.context(m.channelId, 30);
9137
- } catch {
9529
+ if (!this.mayReadOpenClawMemoryForTurn(m.authorization)) {
9138
9530
  memory = { recent: [], facts: [] };
9531
+ } else {
9532
+ try {
9533
+ memory = await this.memory.context(m.channelId, 30);
9534
+ } catch {
9535
+ memory = { recent: [], facts: [] };
9536
+ }
9139
9537
  }
9140
9538
  const messages = buildFastMessages({
9141
9539
  persona: this.cfg.voice.persona,
@@ -9388,13 +9786,42 @@ var BridgeAgent = class _BridgeAgent {
9388
9786
  const rows = await res.json();
9389
9787
  const members = /* @__PURE__ */ new Map();
9390
9788
  for (const r of rows) {
9391
- 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
+ });
9392
9795
  }
9393
9796
  this.channelRoster.set(channelId, { members, at: Date.now() });
9394
9797
  await this.resolveAndPinOwner(channelId, rows);
9395
9798
  } catch {
9396
9799
  }
9397
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
+ }
9398
9825
  /**
9399
9826
  * Task 8 (owner-attested Reset Secure Session): STRUCTURAL owner selection —
9400
9827
  * NEVER read the server's `role` field to pick the owner. In a DM the owner
@@ -9908,6 +10335,7 @@ var BridgeAgent = class _BridgeAgent {
9908
10335
  sender_id: m.envelope.sender_id,
9909
10336
  text: typeof parsed.text === "string" ? parsed.text : "",
9910
10337
  ts: m.receivedAt,
10338
+ authorization: this.authorizationFor(m.channelId, m.envelope.sender_id),
9911
10339
  sender_name: senderName,
9912
10340
  sender_is_agent: senderIsAgent,
9913
10341
  attachments: [attachment],
@@ -9946,6 +10374,11 @@ var BridgeAgent = class _BridgeAgent {
9946
10374
  const senderName = this.freshRoster(m.channelId)?.get(
9947
10375
  m.envelope.sender_id
9948
10376
  )?.name;
10377
+ const turnAuthorization = this.authorizationFor(
10378
+ m.channelId,
10379
+ m.envelope.sender_id
10380
+ );
10381
+ const runtimeMemory = this.memoryForTurn(m.channelId, turnAuthorization);
9949
10382
  const speaker = parsed.relay === true && typeof parsed.speaker_identity_id === "string" && typeof parsed.speaker_display_name === "string" ? {
9950
10383
  identityId: parsed.speaker_identity_id,
9951
10384
  displayName: parsed.speaker_display_name
@@ -9962,12 +10395,14 @@ var BridgeAgent = class _BridgeAgent {
9962
10395
  const turnText = parsed.text;
9963
10396
  const isCallStart = turnText.trim() === VOICE_CALL_START_MARKER;
9964
10397
  const fastGreeting = isCallStart && fastPathFiring;
9965
- const runtimeEntry = (ackSent) => ({
10398
+ const runtimeEntry = (ackSent, memory) => ({
9966
10399
  channel_id: m.channelId,
9967
10400
  message_id: m.messageId,
9968
10401
  sender_id: m.envelope.sender_id,
9969
10402
  text: turnText,
9970
10403
  ts: m.receivedAt,
10404
+ authorization: turnAuthorization,
10405
+ memory,
9971
10406
  // A relay-attributed turn is the SPEAKER's message to the runtime:
9972
10407
  // name it after them and never flag it as agent-authored.
9973
10408
  sender_name: speaker?.displayName ?? senderName,
@@ -9981,18 +10416,23 @@ var BridgeAgent = class _BridgeAgent {
9981
10416
  if (fastPathFiring) {
9982
10417
  const fastSpeaker = speaker ?? (senderName ? { identityId: m.envelope.sender_id, displayName: senderName } : void 0);
9983
10418
  void this.runFastPath(
9984
- { 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
+ },
9985
10425
  fastGreeting ? greetingTurnFor(this.voiceSettings ?? DEFAULT_VOICE_SETTINGS, fastSpeaker?.displayName) : parsed.text,
9986
10426
  voiceGuidance,
9987
10427
  fastSpeaker
9988
- ).then((outcome) => {
10428
+ ).then(async (outcome) => {
9989
10429
  if (outcome === "answered") return;
9990
- this.buffer.push(runtimeEntry(outcome === "deferred"));
10430
+ this.buffer.push(runtimeEntry(outcome === "deferred", await runtimeMemory));
9991
10431
  this.capBuffer();
9992
10432
  this.wakeListeners();
9993
10433
  });
9994
10434
  } else {
9995
- this.buffer.push(runtimeEntry(false));
10435
+ this.buffer.push(runtimeEntry(false, await runtimeMemory));
9996
10436
  }
9997
10437
  this.capBuffer();
9998
10438
  void recordInboundMemory(
@@ -10058,6 +10498,12 @@ var BridgeAgent = class _BridgeAgent {
10058
10498
  this.buffer.push(...keep);
10059
10499
  return out;
10060
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
+ }
10061
10507
  /**
10062
10508
  * Put messages back at the front (SSE/disconnect write-failure recovery).
10063
10509
  *
@@ -10209,6 +10655,7 @@ var BridgeAgent = class _BridgeAgent {
10209
10655
  this.closingDown = true;
10210
10656
  this.pingHandle?.close();
10211
10657
  if (this.safetyTimer) clearInterval(this.safetyTimer);
10658
+ if (this.runtimeConfigTimer) clearInterval(this.runtimeConfigTimer);
10212
10659
  if (this.bootSweepTimer) clearTimeout(this.bootSweepTimer);
10213
10660
  if (this.selfHealTimer) clearTimeout(this.selfHealTimer);
10214
10661
  await this.provider.drainInFlight(_BridgeAgent.GRACEFUL_DRAIN_MS);
@@ -10334,6 +10781,7 @@ var sendSchema = z3.object({
10334
10781
  channel_id: z3.string().min(1),
10335
10782
  text: z3.string().optional(),
10336
10783
  idempotency_key: z3.string().optional(),
10784
+ in_reply_to: z3.string().min(1).max(200).optional(),
10337
10785
  a2a: a2aSchema.optional()
10338
10786
  }).refine((d) => typeof d.text === "string" || d.a2a !== void 0, {
10339
10787
  message: "send requires either text or a2a"
@@ -10450,10 +10898,19 @@ function createHttpServer(agent, opts) {
10450
10898
  if (method === "GET" && path === "/v1/metrics") {
10451
10899
  return json(res, 200, agent.metrics());
10452
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
+ }
10453
10907
  if (method === "POST" && path === "/v1/send") {
10454
10908
  const parsed = sendSchema.safeParse(JSON.parse(await readBody(req, SEND_CAP)));
10455
10909
  if (!parsed.success) return jsonError(res, "invalid_request", parsed.error.issues);
10456
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
+ }
10457
10914
  return json(res, 200, r);
10458
10915
  }
10459
10916
  if (method === "POST" && path === "/v1/send-attachment") {
@@ -10709,6 +11166,10 @@ async function handleInbound(sock, agent, data, inflight) {
10709
11166
  sendJson(sock, wsErrorFrame("invalid_request"));
10710
11167
  return;
10711
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
+ }
10712
11173
  if (m.a2a && typeof m.a2a === "object") {
10713
11174
  const a = m.a2a;
10714
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) {
@@ -10717,6 +11178,9 @@ async function handleInbound(sock, agent, data, inflight) {
10717
11178
  }
10718
11179
  try {
10719
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
+ }
10720
11184
  sendJson(sock, { type: "sent", message_id: r.message_id });
10721
11185
  } catch (e) {
10722
11186
  if (e instanceof ChannelPendingError) {
@@ -10735,6 +11199,9 @@ async function handleInbound(sock, agent, data, inflight) {
10735
11199
  }
10736
11200
  try {
10737
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
+ }
10738
11205
  sendJson(sock, { type: "sent", message_id: r.message_id });
10739
11206
  } catch (e) {
10740
11207
  if (e instanceof ChannelPendingError) {
@@ -10846,7 +11313,7 @@ async function handleInbound(sock, agent, data, inflight) {
10846
11313
 
10847
11314
  // src/bin.ts
10848
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";
10849
- 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";
10850
11317
  import { fileURLToPath as fileURLToPath2 } from "node:url";
10851
11318
  import { homedir as homedir3, userInfo } from "node:os";
10852
11319
  import { spawnSync } from "node:child_process";
@@ -10855,7 +11322,7 @@ import { spawnSync } from "node:child_process";
10855
11322
  import { dirname as dirname4 } from "node:path";
10856
11323
 
10857
11324
  // src/service/config.ts
10858
- import { basename as basename3, extname, isAbsolute, resolve } from "node:path";
11325
+ import { basename as basename3, extname, isAbsolute as isAbsolute2, resolve } from "node:path";
10859
11326
 
10860
11327
  // src/service/resolve-node.ts
10861
11328
  var REQUIRED_NODE_MAJOR = 24;
@@ -10935,9 +11402,9 @@ function deriveServiceConfig(input) {
10935
11402
  }
10936
11403
  const stem = input.label ?? basename3(input.credentialPath, extname(input.credentialPath));
10937
11404
  const slug = slugify(stem);
10938
- const cred = isAbsolute(input.credentialPath) ? input.credentialPath : resolve(input.credentialPath);
11405
+ const cred = isAbsolute2(input.credentialPath) ? input.credentialPath : resolve(input.credentialPath);
10939
11406
  const args2 = ["--credential", cred];
10940
- 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));
10941
11408
  if (input.port != null) args2.push("--port", String(input.port));
10942
11409
  return {
10943
11410
  slug,
@@ -12048,7 +12515,7 @@ async function runServiceVerb(verb) {
12048
12515
  }
12049
12516
  try {
12050
12517
  if (verb === "install") {
12051
- const credAbs = isAbsolute2(credentialPath) ? credentialPath : resolve2(credentialPath);
12518
+ const credAbs = isAbsolute3(credentialPath) ? credentialPath : resolve2(credentialPath);
12052
12519
  const installExecPath = chooseInstallExecPath(
12053
12520
  process.execPath,
12054
12521
  argOrEnv("--exec-path", "SECURECOMS_EXEC_PATH"),