@agentvault/agentvault 0.17.5 → 0.19.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.
Files changed (91) hide show
  1. package/README.md +10 -11
  2. package/dist/__tests__/crypto-helpers.test.d.ts +2 -0
  3. package/dist/__tests__/crypto-helpers.test.d.ts.map +1 -0
  4. package/dist/__tests__/functional.test.d.ts +21 -0
  5. package/dist/__tests__/functional.test.d.ts.map +1 -0
  6. package/dist/__tests__/multi-session.test.d.ts +2 -0
  7. package/dist/__tests__/multi-session.test.d.ts.map +1 -0
  8. package/dist/__tests__/state.test.d.ts +2 -0
  9. package/dist/__tests__/state.test.d.ts.map +1 -0
  10. package/dist/__tests__/transport.test.d.ts +2 -0
  11. package/dist/__tests__/transport.test.d.ts.map +1 -0
  12. package/dist/_cp.d.ts +10 -0
  13. package/dist/_cp.d.ts.map +1 -0
  14. package/dist/account-config.d.ts +20 -0
  15. package/dist/account-config.d.ts.map +1 -0
  16. package/dist/channel.d.ts +393 -0
  17. package/dist/channel.d.ts.map +1 -0
  18. package/dist/channel.js +2257 -0
  19. package/dist/channel.js.map +1 -0
  20. package/dist/cli.d.ts +2 -0
  21. package/dist/cli.d.ts.map +1 -0
  22. package/dist/cli.js +344 -2
  23. package/dist/cli.js.map +4 -4
  24. package/dist/create-agent.d.ts +28 -0
  25. package/dist/create-agent.d.ts.map +1 -0
  26. package/dist/credential-store.d.ts +62 -0
  27. package/dist/credential-store.d.ts.map +1 -0
  28. package/dist/crypto-helpers.d.ts +2 -0
  29. package/dist/crypto-helpers.d.ts.map +1 -0
  30. package/dist/crypto-helpers.js +4 -0
  31. package/dist/crypto-helpers.js.map +1 -0
  32. package/dist/doctor.d.ts +41 -0
  33. package/dist/doctor.d.ts.map +1 -0
  34. package/dist/fetch-interceptor.d.ts +32 -0
  35. package/dist/fetch-interceptor.d.ts.map +1 -0
  36. package/dist/gateway-send.d.ts +98 -0
  37. package/dist/gateway-send.d.ts.map +1 -0
  38. package/dist/http-handlers.d.ts +53 -0
  39. package/dist/http-handlers.d.ts.map +1 -0
  40. package/dist/index.d.ts +27 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +359 -11
  43. package/dist/index.js.map +4 -4
  44. package/dist/mcp-handlers.d.ts +26 -0
  45. package/dist/mcp-handlers.d.ts.map +1 -0
  46. package/dist/mcp-proxy-helpers.d.ts +9 -0
  47. package/dist/mcp-proxy-helpers.d.ts.map +1 -0
  48. package/dist/mcp-server.d.ts +90 -0
  49. package/dist/mcp-server.d.ts.map +1 -0
  50. package/dist/openclaw-compat.d.ts +33 -0
  51. package/dist/openclaw-compat.d.ts.map +1 -0
  52. package/dist/openclaw-entry.d.ts +27 -0
  53. package/dist/openclaw-entry.d.ts.map +1 -0
  54. package/dist/openclaw-entry.js +45184 -195
  55. package/dist/openclaw-entry.js.map +4 -4
  56. package/dist/openclaw-plugin.d.ts +102 -0
  57. package/dist/openclaw-plugin.d.ts.map +1 -0
  58. package/dist/openclaw-plugin.js +222 -0
  59. package/dist/openclaw-plugin.js.map +1 -0
  60. package/dist/openclaw-types.d.ts +155 -0
  61. package/dist/openclaw-types.d.ts.map +1 -0
  62. package/dist/policy-enforcer.d.ts +78 -0
  63. package/dist/policy-enforcer.d.ts.map +1 -0
  64. package/dist/setup.d.ts +27 -0
  65. package/dist/setup.d.ts.map +1 -0
  66. package/dist/setup.js +329 -0
  67. package/dist/setup.js.map +1 -0
  68. package/dist/skill-invoker.d.ts +30 -0
  69. package/dist/skill-invoker.d.ts.map +1 -0
  70. package/dist/skill-manifest.d.ts +30 -0
  71. package/dist/skill-manifest.d.ts.map +1 -0
  72. package/dist/skill-telemetry.d.ts +36 -0
  73. package/dist/skill-telemetry.d.ts.map +1 -0
  74. package/dist/skills-publish.d.ts +8 -0
  75. package/dist/skills-publish.d.ts.map +1 -0
  76. package/dist/state.d.ts +32 -0
  77. package/dist/state.d.ts.map +1 -0
  78. package/dist/state.js +61 -0
  79. package/dist/state.js.map +1 -0
  80. package/dist/transport.d.ts +24 -0
  81. package/dist/transport.d.ts.map +1 -0
  82. package/dist/transport.js +43 -0
  83. package/dist/transport.js.map +1 -0
  84. package/dist/types.d.ts +417 -0
  85. package/dist/types.d.ts.map +1 -0
  86. package/dist/types.js +2 -0
  87. package/dist/types.js.map +1 -0
  88. package/dist/workspace-handlers.d.ts +62 -0
  89. package/dist/workspace-handlers.d.ts.map +1 -0
  90. package/openclaw.plugin.json +1 -1
  91. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -46265,6 +46265,123 @@ var init_dist = __esm({
46265
46265
  }
46266
46266
  });
46267
46267
 
46268
+ // src/credential-store.ts
46269
+ var CredentialStore;
46270
+ var init_credential_store = __esm({
46271
+ "src/credential-store.ts"() {
46272
+ "use strict";
46273
+ CredentialStore = class _CredentialStore {
46274
+ /** Map<roomId, Map<credentialKey, RenterCredential>> */
46275
+ _store = /* @__PURE__ */ new Map();
46276
+ /** Seen nonces for replay prevention — bounded per room */
46277
+ _seenNonces = /* @__PURE__ */ new Map();
46278
+ static MAX_NONCES_PER_ROOM = 1e3;
46279
+ constructor() {
46280
+ const purge = () => this.purgeAll();
46281
+ process.on("exit", purge);
46282
+ process.on("SIGINT", () => {
46283
+ purge();
46284
+ process.exit(0);
46285
+ });
46286
+ process.on("SIGTERM", () => {
46287
+ purge();
46288
+ process.exit(0);
46289
+ });
46290
+ }
46291
+ /**
46292
+ * Check if a nonce has been seen (replay prevention).
46293
+ * Returns true if the nonce is new (not a replay), false if seen before.
46294
+ */
46295
+ checkNonce(roomId, nonce) {
46296
+ if (!nonce) return true;
46297
+ let nonces = this._seenNonces.get(roomId);
46298
+ if (!nonces) {
46299
+ nonces = /* @__PURE__ */ new Set();
46300
+ this._seenNonces.set(roomId, nonces);
46301
+ }
46302
+ if (nonces.has(nonce)) return false;
46303
+ nonces.add(nonce);
46304
+ if (nonces.size > _CredentialStore.MAX_NONCES_PER_ROOM) {
46305
+ const iter = nonces.values();
46306
+ for (let i2 = 0; i2 < 100; i2++) {
46307
+ const val = iter.next().value;
46308
+ if (val !== void 0) nonces.delete(val);
46309
+ }
46310
+ }
46311
+ return true;
46312
+ }
46313
+ /** Store a credential for a room. */
46314
+ grant(roomId, credential) {
46315
+ let room = this._store.get(roomId);
46316
+ if (!room) {
46317
+ room = /* @__PURE__ */ new Map();
46318
+ this._store.set(roomId, room);
46319
+ }
46320
+ room.set(credential.key, credential);
46321
+ }
46322
+ /** Revoke a specific credential. */
46323
+ revoke(roomId, key) {
46324
+ const room = this._store.get(roomId);
46325
+ if (!room) return false;
46326
+ return room.delete(key);
46327
+ }
46328
+ /** Revoke all credentials for a room. */
46329
+ revokeAll(roomId) {
46330
+ this._store.delete(roomId);
46331
+ }
46332
+ /** Get a credential value. */
46333
+ get(roomId, key) {
46334
+ return this._store.get(roomId)?.get(key);
46335
+ }
46336
+ /** Get all credentials for a room (values included — only for agent context injection). */
46337
+ getAll(roomId) {
46338
+ const room = this._store.get(roomId);
46339
+ if (!room) return [];
46340
+ return Array.from(room.values());
46341
+ }
46342
+ /** Get credential info without values (safe for logging). */
46343
+ getInfo(roomId) {
46344
+ const room = this._store.get(roomId);
46345
+ if (!room) return [];
46346
+ return Array.from(room.values()).map((c2) => ({
46347
+ key: c2.key,
46348
+ type: c2.type,
46349
+ scope: c2.scope,
46350
+ grantedAt: c2.grantedAt
46351
+ }));
46352
+ }
46353
+ /** Check if a specific credential exists. */
46354
+ has(roomId, key) {
46355
+ return this._store.get(roomId)?.has(key) ?? false;
46356
+ }
46357
+ /** Get credential count for a room. */
46358
+ count(roomId) {
46359
+ return this._store.get(roomId)?.size ?? 0;
46360
+ }
46361
+ /** Purge all credentials for a room (rental end). */
46362
+ purgeForRoom(roomId) {
46363
+ this._store.delete(roomId);
46364
+ this._seenNonces.delete(roomId);
46365
+ }
46366
+ /** Purge everything (process exit). */
46367
+ purgeAll() {
46368
+ this._store.clear();
46369
+ this._seenNonces.clear();
46370
+ }
46371
+ /** Get a map of credential key → value for context injection. */
46372
+ getCredentialMap(roomId) {
46373
+ const room = this._store.get(roomId);
46374
+ if (!room) return {};
46375
+ const result = {};
46376
+ for (const [key, cred] of room) {
46377
+ result[key] = cred.value;
46378
+ }
46379
+ return result;
46380
+ }
46381
+ };
46382
+ }
46383
+ });
46384
+
46268
46385
  // src/crypto-helpers.ts
46269
46386
  var init_crypto_helpers = __esm({
46270
46387
  async "src/crypto-helpers.ts"() {
@@ -46541,7 +46658,8 @@ __export(http_handlers_exports, {
46541
46658
  handleMcpConfigRequest: () => handleMcpConfigRequest,
46542
46659
  handleSendRequest: () => handleSendRequest,
46543
46660
  handleStatusRequest: () => handleStatusRequest,
46544
- handleTargetsRequest: () => handleTargetsRequest
46661
+ handleTargetsRequest: () => handleTargetsRequest,
46662
+ handleTrustRequest: () => handleTrustRequest
46545
46663
  });
46546
46664
  async function handleSendRequest(parsed, channel) {
46547
46665
  const text = parsed.text;
@@ -46680,6 +46798,24 @@ function handleTargetsRequest(channel) {
46680
46798
  }
46681
46799
  };
46682
46800
  }
46801
+ function handleTrustRequest(channel) {
46802
+ const token = channel.trustToken;
46803
+ if (!token) {
46804
+ return {
46805
+ status: 503,
46806
+ body: { ok: false, error: "token_unavailable" }
46807
+ };
46808
+ }
46809
+ return {
46810
+ status: 200,
46811
+ body: {
46812
+ ok: true,
46813
+ tier: channel.trustTier,
46814
+ composite: null,
46815
+ token_expires_at: channel.trustTokenExpiresAt
46816
+ }
46817
+ };
46818
+ }
46683
46819
  function handleMcpConfigRequest(agentName, port, mcpSkillCount) {
46684
46820
  return {
46685
46821
  status: 200,
@@ -46934,12 +47070,13 @@ function migratePersistedState(raw) {
46934
47070
  messageHistory: []
46935
47071
  };
46936
47072
  }
46937
- var ROOM_AGENT_TYPES, POLL_INTERVAL_MS, RECONNECT_BASE_MS, RECONNECT_MAX_MS, PENDING_POLL_INTERVAL_MS, SecureChannel;
47073
+ var ROOM_AGENT_TYPES, CREDENTIAL_MESSAGE_TYPES, POLL_INTERVAL_MS, RECONNECT_BASE_MS, RECONNECT_MAX_MS, PENDING_POLL_INTERVAL_MS, SecureChannel;
46938
47074
  var init_channel = __esm({
46939
47075
  async "src/channel.ts"() {
46940
47076
  "use strict";
46941
47077
  await init_libsodium_wrappers();
46942
47078
  await init_dist();
47079
+ init_credential_store();
46943
47080
  await init_crypto_helpers();
46944
47081
  await init_state();
46945
47082
  init_transport2();
@@ -46950,6 +47087,11 @@ var init_channel = __esm({
46950
47087
  "decision_response",
46951
47088
  "artifact_share"
46952
47089
  ]);
47090
+ CREDENTIAL_MESSAGE_TYPES = /* @__PURE__ */ new Set([
47091
+ "credential_grant",
47092
+ "credential_revoke",
47093
+ "credential_request"
47094
+ ]);
46953
47095
  POLL_INTERVAL_MS = 6e3;
46954
47096
  RECONNECT_BASE_MS = 1e3;
46955
47097
  RECONNECT_MAX_MS = 3e4;
@@ -46986,17 +47128,26 @@ var init_channel = __esm({
46986
47128
  _heartbeatIntervalSeconds = 0;
46987
47129
  _wakeDetectorTimer = null;
46988
47130
  _lastWakeTick = Date.now();
47131
+ _trustToken = null;
47132
+ _trustTier = null;
47133
+ _trustTokenExpiresAt = null;
47134
+ _trustTokenInterval = null;
46989
47135
  _pendingPollTimer = null;
46990
47136
  _syncMessageIds = null;
46991
47137
  /** Sender Key chains — own chain per room for O(1) encryption */
46992
47138
  _senderKeyChains = /* @__PURE__ */ new Map();
46993
47139
  /** Sender Key peer state — peer chains per room for decryption */
46994
47140
  _senderKeyStates = /* @__PURE__ */ new Map();
47141
+ /** In-memory credential store for renter-provided credentials (never persisted). */
47142
+ _credentialStore = new CredentialStore();
46995
47143
  /** Queued A2A messages for responder channels not yet activated (no first initiator message received). */
46996
47144
  _a2aPendingQueue = {};
46997
47145
  /** Dedup buffer for A2A message IDs (prevents double-delivery via direct + Redis) */
46998
47146
  _a2aSeenMessageIds = /* @__PURE__ */ new Set();
46999
47147
  static A2A_SEEN_MAX = 500;
47148
+ /** Dedup buffer for regular message IDs (prevents double-decrypt via direct WS + Redis pub/sub) */
47149
+ _seenMessageIds = /* @__PURE__ */ new Set();
47150
+ static SEEN_MSG_MAX = 500;
47000
47151
  _scanEngine = null;
47001
47152
  _scanRuleSetVersion = 0;
47002
47153
  _telemetryReporter = null;
@@ -47926,9 +48077,56 @@ var init_channel = __esm({
47926
48077
  this.emit("error", new Error(`Heartbeat send failed: ${err}`));
47927
48078
  });
47928
48079
  }
48080
+ // --- Trust Gate token management ---
48081
+ getTrustHeaders() {
48082
+ if (!this._trustToken) return {};
48083
+ return { "AgentVault-Trust": this._trustToken };
48084
+ }
48085
+ get trustToken() {
48086
+ return this._trustToken;
48087
+ }
48088
+ get trustTier() {
48089
+ return this._trustTier;
48090
+ }
48091
+ get trustTokenExpiresAt() {
48092
+ return this._trustTokenExpiresAt;
48093
+ }
48094
+ async refreshTrustToken() {
48095
+ try {
48096
+ const res = await fetch(`${this.config.apiUrl}/api/v1/gate/token`, {
48097
+ method: "POST",
48098
+ headers: {
48099
+ Authorization: `Bearer ${this._deviceJwt}`,
48100
+ "Content-Type": "application/json"
48101
+ }
48102
+ });
48103
+ if (res.ok) {
48104
+ const data = await res.json();
48105
+ this._trustToken = data.token;
48106
+ this._trustTier = data.tier;
48107
+ this._trustTokenExpiresAt = data.expires_at;
48108
+ }
48109
+ } catch (err) {
48110
+ console.warn("[AgentVault] Trust token refresh failed:", err);
48111
+ }
48112
+ }
48113
+ startTrustTokenRefresh() {
48114
+ this.refreshTrustToken();
48115
+ this._trustTokenInterval = setInterval(
48116
+ () => this.refreshTrustToken(),
48117
+ 12 * 60 * 1e3
48118
+ );
48119
+ }
48120
+ stopTrustTokenRefresh() {
48121
+ if (this._trustTokenInterval) {
48122
+ clearInterval(this._trustTokenInterval);
48123
+ this._trustTokenInterval = null;
48124
+ }
48125
+ }
47929
48126
  async stop() {
47930
48127
  this._stopped = true;
47931
48128
  await this.stopHeartbeat();
48129
+ this.stopTrustTokenRefresh();
47932
48130
  this._flushAcks();
47933
48131
  this._stopPing();
47934
48132
  this._stopWakeDetector();
@@ -48044,6 +48242,10 @@ var init_channel = __esm({
48044
48242
  res.end(JSON.stringify({ ok: false, error: "Internal MCP error" }));
48045
48243
  }
48046
48244
  });
48245
+ } else if (req.method === "GET" && req.url === "/trust") {
48246
+ const result = handlers.handleTrustRequest(this);
48247
+ res.writeHead(result.status, { "Content-Type": "application/json" });
48248
+ res.end(JSON.stringify(result.body));
48047
48249
  } else if (req.method === "GET" && req.url === "/mcp-config") {
48048
48250
  const agentName = this.config.agentName ?? "agent";
48049
48251
  const mcpSkillCount = this._mcpServer?.skillCount ?? 0;
@@ -48550,6 +48752,7 @@ var init_channel = __esm({
48550
48752
  });
48551
48753
  this._telemetryReporter.startAutoFlush(3e4);
48552
48754
  }
48755
+ this.startTrustTokenRefresh();
48553
48756
  this.emit("ready");
48554
48757
  } catch (openErr) {
48555
48758
  console.error("[SecureChannel] Error in WS open handler:", openErr);
@@ -48582,6 +48785,17 @@ var init_channel = __esm({
48582
48785
  return;
48583
48786
  }
48584
48787
  if (data.event === "message") {
48788
+ const inMsgId = data.data?.message_id;
48789
+ if (inMsgId && this._seenMessageIds.has(inMsgId)) {
48790
+ return;
48791
+ }
48792
+ if (inMsgId) {
48793
+ this._seenMessageIds.add(inMsgId);
48794
+ if (this._seenMessageIds.size > _SecureChannel.SEEN_MSG_MAX) {
48795
+ const first = this._seenMessageIds.values().next().value;
48796
+ if (first) this._seenMessageIds.delete(first);
48797
+ }
48798
+ }
48585
48799
  try {
48586
48800
  await this._handleIncomingMessage(data.data);
48587
48801
  } catch (msgErr) {
@@ -48612,6 +48826,17 @@ var init_channel = __esm({
48612
48826
  }).catch((err) => this.emit("error", err));
48613
48827
  }
48614
48828
  if (data.event === "room_message") {
48829
+ const rmMsgId = data.data?.message_id;
48830
+ if (rmMsgId && this._seenMessageIds.has(rmMsgId)) {
48831
+ return;
48832
+ }
48833
+ if (rmMsgId) {
48834
+ this._seenMessageIds.add(rmMsgId);
48835
+ if (this._seenMessageIds.size > _SecureChannel.SEEN_MSG_MAX) {
48836
+ const first = this._seenMessageIds.values().next().value;
48837
+ if (first) this._seenMessageIds.delete(first);
48838
+ }
48839
+ }
48615
48840
  try {
48616
48841
  await this._handleRoomMessage(data.data);
48617
48842
  } catch (rmErr) {
@@ -49146,6 +49371,10 @@ var init_channel = __esm({
49146
49371
  if (!session.activated) {
49147
49372
  session.activated = true;
49148
49373
  console.log(`[SecureChannel] Session ${convId.slice(0, 8)}... activated by first owner message`);
49374
+ if (this._persisted?.sessions[convId]) {
49375
+ this._persisted.sessions[convId].activated = true;
49376
+ }
49377
+ await this._persistState();
49149
49378
  }
49150
49379
  let messageText;
49151
49380
  let messageType;
@@ -49760,6 +49989,14 @@ ${messageText}`;
49760
49989
  `[SecureChannel] Room ratchet re-initialized for conv ${convId.slice(0, 8)}...`
49761
49990
  );
49762
49991
  plaintext = session.ratchet.decrypt(encrypted);
49992
+ session.activated = true;
49993
+ if (this._persisted.sessions[convId]) {
49994
+ this._persisted.sessions[convId].activated = true;
49995
+ }
49996
+ await this._persistState();
49997
+ console.log(
49998
+ `[SecureChannel] Room session ${convId.slice(0, 8)}... re-activated after ratchet re-init`
49999
+ );
49763
50000
  } catch (reinitErr) {
49764
50001
  console.error(
49765
50002
  `[SecureChannel] Room ratchet re-init failed for conv ${convId.slice(0, 8)}...:`,
@@ -49778,6 +50015,14 @@ ${messageText}`;
49778
50015
  messageType = "message";
49779
50016
  messageText = plaintext;
49780
50017
  }
50018
+ if (CREDENTIAL_MESSAGE_TYPES.has(messageType)) {
50019
+ this._handleCredentialMessage(msgData.room_id, messageType, messageText, msgData.message_id);
50020
+ if (msgData.created_at && this._persisted) {
50021
+ this._persisted.lastMessageTimestamp = msgData.created_at;
50022
+ }
50023
+ await this._persistState();
50024
+ return;
50025
+ }
49781
50026
  if (!ROOM_AGENT_TYPES.has(messageType)) {
49782
50027
  return;
49783
50028
  }
@@ -49814,6 +50059,94 @@ ${messageText}`;
49814
50059
  console.error("[SecureChannel] onMessage callback error:", err);
49815
50060
  });
49816
50061
  }
50062
+ /**
50063
+ * Handle credential protocol messages (grant, revoke, request).
50064
+ * These are intercepted before reaching the agent's onMessage callback.
50065
+ */
50066
+ _handleCredentialMessage(roomId, messageType, plaintext, messageId) {
50067
+ try {
50068
+ const payload = JSON.parse(plaintext);
50069
+ if (messageType === "credential_grant") {
50070
+ const grant = payload;
50071
+ if (grant.nonce && !this._credentialStore.checkNonce(roomId, grant.nonce)) {
50072
+ console.warn(`[SecureChannel] Credential grant replay detected for room ${roomId.slice(0, 8)}..., ignoring`);
50073
+ return;
50074
+ }
50075
+ const keys = [];
50076
+ for (const cred of grant.credentials || []) {
50077
+ this._credentialStore.grant(roomId, {
50078
+ key: cred.key,
50079
+ value: cred.value,
50080
+ type: cred.type,
50081
+ scope: cred.scope,
50082
+ grantedAt: grant.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
50083
+ agreementId: grant.agreement_id,
50084
+ roomId
50085
+ });
50086
+ keys.push(cred.key);
50087
+ }
50088
+ console.log(
50089
+ `[SecureChannel] Credentials granted for room ${roomId.slice(0, 8)}...: ${keys.join(", ")} (${keys.length} keys)`
50090
+ );
50091
+ this._sendCredentialAck(roomId, grant.agreement_id, keys, "stored");
50092
+ this.emit("credentials_granted", { roomId, keys, agreementId: grant.agreement_id });
50093
+ } else if (messageType === "credential_revoke") {
50094
+ const revoke = payload;
50095
+ const revoked = [];
50096
+ for (const key of revoke.credential_keys || []) {
50097
+ if (this._credentialStore.revoke(roomId, key)) {
50098
+ revoked.push(key);
50099
+ }
50100
+ }
50101
+ console.log(
50102
+ `[SecureChannel] Credentials revoked for room ${roomId.slice(0, 8)}...: ${revoked.join(", ")} (reason: ${revoke.reason || "none"})`
50103
+ );
50104
+ this._sendCredentialAck(roomId, revoke.agreement_id, revoked, "revoked");
50105
+ this.emit("credentials_revoked", { roomId, keys: revoked, agreementId: revoke.agreement_id });
50106
+ }
50107
+ } catch (err) {
50108
+ console.error("[SecureChannel] Error handling credential message:", err);
50109
+ }
50110
+ if (messageId) {
50111
+ this._sendAck(messageId);
50112
+ }
50113
+ }
50114
+ /**
50115
+ * Send a credential_ack back to a room.
50116
+ */
50117
+ _sendCredentialAck(roomId, agreementId, keys, ackStatus) {
50118
+ const ack = JSON.stringify({
50119
+ type: "credential_ack",
50120
+ agreement_id: agreementId,
50121
+ acknowledged_keys: keys,
50122
+ status: ackStatus,
50123
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
50124
+ });
50125
+ this.sendToRoom(roomId, ack, { messageType: "credential_ack" }).catch((err) => {
50126
+ console.warn("[SecureChannel] Failed to send credential ACK:", err);
50127
+ });
50128
+ }
50129
+ // --- Public credential accessors ---
50130
+ /** Get a specific renter credential for a room. */
50131
+ getCredential(roomId, key) {
50132
+ return this._credentialStore.get(roomId, key);
50133
+ }
50134
+ /** Get all renter credentials for a room (includes values — for agent context). */
50135
+ getCredentials(roomId) {
50136
+ return this._credentialStore.getAll(roomId);
50137
+ }
50138
+ /** Get credential key→value map for a room (for context injection). */
50139
+ getCredentialMap(roomId) {
50140
+ return this._credentialStore.getCredentialMap(roomId);
50141
+ }
50142
+ /** Check if a specific credential exists for a room. */
50143
+ hasCredential(roomId, key) {
50144
+ return this._credentialStore.has(roomId, key);
50145
+ }
50146
+ /** Purge all credentials for a room (call on rental end). */
50147
+ purgeRoomCredentials(roomId) {
50148
+ this._credentialStore.purgeForRoom(roomId);
50149
+ }
49817
50150
  /**
49818
50151
  * Find the pairwise conversation ID for a given sender in a room.
49819
50152
  */
@@ -49976,6 +50309,14 @@ ${messageText}`;
49976
50309
  messageType = "message";
49977
50310
  messageText = plaintext;
49978
50311
  }
50312
+ if (CREDENTIAL_MESSAGE_TYPES.has(messageType)) {
50313
+ this._handleCredentialMessage(msgData.room_id, messageType, messageText, msgData.message_id);
50314
+ if (msgData.created_at && this._persisted) {
50315
+ this._persisted.lastMessageTimestamp = msgData.created_at;
50316
+ }
50317
+ await this._persistState();
50318
+ return;
50319
+ }
49979
50320
  if (!ROOM_AGENT_TYPES.has(messageType)) {
49980
50321
  return;
49981
50322
  }
@@ -76664,21 +77005,26 @@ function loadSkillsFromDirectory(dir) {
76664
77005
  const skills = [];
76665
77006
  const absDir = resolve2(dir);
76666
77007
  if (!existsSync(absDir)) return skills;
76667
- const rootSkill = join4(absDir, "SKILL.md");
76668
- if (existsSync(rootSkill)) {
76669
- const content = readFileSync(rootSkill, "utf-8");
76670
- const skill = parseSkillMd(content);
76671
- if (skill) skills.push(skill);
77008
+ const seen = /* @__PURE__ */ new Set();
77009
+ function tryLoad(filePath) {
77010
+ if (seen.has(filePath)) return;
77011
+ seen.add(filePath);
77012
+ try {
77013
+ const content = readFileSync(filePath, "utf-8");
77014
+ const skill = parseSkillMd(content);
77015
+ if (skill) skills.push(skill);
77016
+ } catch {
77017
+ }
76672
77018
  }
76673
77019
  try {
76674
77020
  const entries = readdirSync(absDir, { withFileTypes: true });
76675
77021
  for (const entry of entries) {
76676
- if (entry.isDirectory()) {
77022
+ if (entry.isFile() && entry.name.endsWith("SKILL.md")) {
77023
+ tryLoad(join4(absDir, entry.name));
77024
+ } else if (entry.isDirectory()) {
76677
77025
  const subSkill = join4(absDir, entry.name, "SKILL.md");
76678
77026
  if (existsSync(subSkill)) {
76679
- const content = readFileSync(subSkill, "utf-8");
76680
- const skill = parseSkillMd(content);
76681
- if (skill) skills.push(skill);
77027
+ tryLoad(subSkill);
76682
77028
  }
76683
77029
  }
76684
77030
  }
@@ -77079,6 +77425,7 @@ var init_index = __esm({
77079
77425
  async "src/index.ts"() {
77080
77426
  await init_channel();
77081
77427
  init_types();
77428
+ init_credential_store();
77082
77429
  init_account_config();
77083
77430
  await init_openclaw_plugin();
77084
77431
  init_gateway_send();
@@ -77097,6 +77444,7 @@ var init_index = __esm({
77097
77444
  await init_index();
77098
77445
  export {
77099
77446
  AgentVaultMcpServer,
77447
+ CredentialStore,
77100
77448
  PolicyEnforcer,
77101
77449
  SecureChannel,
77102
77450
  VERSION,