@agentvault/agentvault 0.17.5 → 0.18.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/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"() {
@@ -46934,12 +47051,13 @@ function migratePersistedState(raw) {
46934
47051
  messageHistory: []
46935
47052
  };
46936
47053
  }
46937
- var ROOM_AGENT_TYPES, POLL_INTERVAL_MS, RECONNECT_BASE_MS, RECONNECT_MAX_MS, PENDING_POLL_INTERVAL_MS, SecureChannel;
47054
+ var ROOM_AGENT_TYPES, CREDENTIAL_MESSAGE_TYPES, POLL_INTERVAL_MS, RECONNECT_BASE_MS, RECONNECT_MAX_MS, PENDING_POLL_INTERVAL_MS, SecureChannel;
46938
47055
  var init_channel = __esm({
46939
47056
  async "src/channel.ts"() {
46940
47057
  "use strict";
46941
47058
  await init_libsodium_wrappers();
46942
47059
  await init_dist();
47060
+ init_credential_store();
46943
47061
  await init_crypto_helpers();
46944
47062
  await init_state();
46945
47063
  init_transport2();
@@ -46950,6 +47068,11 @@ var init_channel = __esm({
46950
47068
  "decision_response",
46951
47069
  "artifact_share"
46952
47070
  ]);
47071
+ CREDENTIAL_MESSAGE_TYPES = /* @__PURE__ */ new Set([
47072
+ "credential_grant",
47073
+ "credential_revoke",
47074
+ "credential_request"
47075
+ ]);
46953
47076
  POLL_INTERVAL_MS = 6e3;
46954
47077
  RECONNECT_BASE_MS = 1e3;
46955
47078
  RECONNECT_MAX_MS = 3e4;
@@ -46992,6 +47115,8 @@ var init_channel = __esm({
46992
47115
  _senderKeyChains = /* @__PURE__ */ new Map();
46993
47116
  /** Sender Key peer state — peer chains per room for decryption */
46994
47117
  _senderKeyStates = /* @__PURE__ */ new Map();
47118
+ /** In-memory credential store for renter-provided credentials (never persisted). */
47119
+ _credentialStore = new CredentialStore();
46995
47120
  /** Queued A2A messages for responder channels not yet activated (no first initiator message received). */
46996
47121
  _a2aPendingQueue = {};
46997
47122
  /** Dedup buffer for A2A message IDs (prevents double-delivery via direct + Redis) */
@@ -49778,6 +49903,14 @@ ${messageText}`;
49778
49903
  messageType = "message";
49779
49904
  messageText = plaintext;
49780
49905
  }
49906
+ if (CREDENTIAL_MESSAGE_TYPES.has(messageType)) {
49907
+ this._handleCredentialMessage(msgData.room_id, messageType, messageText, msgData.message_id);
49908
+ if (msgData.created_at && this._persisted) {
49909
+ this._persisted.lastMessageTimestamp = msgData.created_at;
49910
+ }
49911
+ await this._persistState();
49912
+ return;
49913
+ }
49781
49914
  if (!ROOM_AGENT_TYPES.has(messageType)) {
49782
49915
  return;
49783
49916
  }
@@ -49814,6 +49947,94 @@ ${messageText}`;
49814
49947
  console.error("[SecureChannel] onMessage callback error:", err);
49815
49948
  });
49816
49949
  }
49950
+ /**
49951
+ * Handle credential protocol messages (grant, revoke, request).
49952
+ * These are intercepted before reaching the agent's onMessage callback.
49953
+ */
49954
+ _handleCredentialMessage(roomId, messageType, plaintext, messageId) {
49955
+ try {
49956
+ const payload = JSON.parse(plaintext);
49957
+ if (messageType === "credential_grant") {
49958
+ const grant = payload;
49959
+ if (grant.nonce && !this._credentialStore.checkNonce(roomId, grant.nonce)) {
49960
+ console.warn(`[SecureChannel] Credential grant replay detected for room ${roomId.slice(0, 8)}..., ignoring`);
49961
+ return;
49962
+ }
49963
+ const keys = [];
49964
+ for (const cred of grant.credentials || []) {
49965
+ this._credentialStore.grant(roomId, {
49966
+ key: cred.key,
49967
+ value: cred.value,
49968
+ type: cred.type,
49969
+ scope: cred.scope,
49970
+ grantedAt: grant.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
49971
+ agreementId: grant.agreement_id,
49972
+ roomId
49973
+ });
49974
+ keys.push(cred.key);
49975
+ }
49976
+ console.log(
49977
+ `[SecureChannel] Credentials granted for room ${roomId.slice(0, 8)}...: ${keys.join(", ")} (${keys.length} keys)`
49978
+ );
49979
+ this._sendCredentialAck(roomId, grant.agreement_id, keys, "stored");
49980
+ this.emit("credentials_granted", { roomId, keys, agreementId: grant.agreement_id });
49981
+ } else if (messageType === "credential_revoke") {
49982
+ const revoke = payload;
49983
+ const revoked = [];
49984
+ for (const key of revoke.credential_keys || []) {
49985
+ if (this._credentialStore.revoke(roomId, key)) {
49986
+ revoked.push(key);
49987
+ }
49988
+ }
49989
+ console.log(
49990
+ `[SecureChannel] Credentials revoked for room ${roomId.slice(0, 8)}...: ${revoked.join(", ")} (reason: ${revoke.reason || "none"})`
49991
+ );
49992
+ this._sendCredentialAck(roomId, revoke.agreement_id, revoked, "revoked");
49993
+ this.emit("credentials_revoked", { roomId, keys: revoked, agreementId: revoke.agreement_id });
49994
+ }
49995
+ } catch (err) {
49996
+ console.error("[SecureChannel] Error handling credential message:", err);
49997
+ }
49998
+ if (messageId) {
49999
+ this._sendAck(messageId);
50000
+ }
50001
+ }
50002
+ /**
50003
+ * Send a credential_ack back to a room.
50004
+ */
50005
+ _sendCredentialAck(roomId, agreementId, keys, ackStatus) {
50006
+ const ack = JSON.stringify({
50007
+ type: "credential_ack",
50008
+ agreement_id: agreementId,
50009
+ acknowledged_keys: keys,
50010
+ status: ackStatus,
50011
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
50012
+ });
50013
+ this.sendToRoom(roomId, ack, { messageType: "credential_ack" }).catch((err) => {
50014
+ console.warn("[SecureChannel] Failed to send credential ACK:", err);
50015
+ });
50016
+ }
50017
+ // --- Public credential accessors ---
50018
+ /** Get a specific renter credential for a room. */
50019
+ getCredential(roomId, key) {
50020
+ return this._credentialStore.get(roomId, key);
50021
+ }
50022
+ /** Get all renter credentials for a room (includes values — for agent context). */
50023
+ getCredentials(roomId) {
50024
+ return this._credentialStore.getAll(roomId);
50025
+ }
50026
+ /** Get credential key→value map for a room (for context injection). */
50027
+ getCredentialMap(roomId) {
50028
+ return this._credentialStore.getCredentialMap(roomId);
50029
+ }
50030
+ /** Check if a specific credential exists for a room. */
50031
+ hasCredential(roomId, key) {
50032
+ return this._credentialStore.has(roomId, key);
50033
+ }
50034
+ /** Purge all credentials for a room (call on rental end). */
50035
+ purgeRoomCredentials(roomId) {
50036
+ this._credentialStore.purgeForRoom(roomId);
50037
+ }
49817
50038
  /**
49818
50039
  * Find the pairwise conversation ID for a given sender in a room.
49819
50040
  */
@@ -49976,6 +50197,14 @@ ${messageText}`;
49976
50197
  messageType = "message";
49977
50198
  messageText = plaintext;
49978
50199
  }
50200
+ if (CREDENTIAL_MESSAGE_TYPES.has(messageType)) {
50201
+ this._handleCredentialMessage(msgData.room_id, messageType, messageText, msgData.message_id);
50202
+ if (msgData.created_at && this._persisted) {
50203
+ this._persisted.lastMessageTimestamp = msgData.created_at;
50204
+ }
50205
+ await this._persistState();
50206
+ return;
50207
+ }
49979
50208
  if (!ROOM_AGENT_TYPES.has(messageType)) {
49980
50209
  return;
49981
50210
  }
@@ -76664,21 +76893,26 @@ function loadSkillsFromDirectory(dir) {
76664
76893
  const skills = [];
76665
76894
  const absDir = resolve2(dir);
76666
76895
  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);
76896
+ const seen = /* @__PURE__ */ new Set();
76897
+ function tryLoad(filePath) {
76898
+ if (seen.has(filePath)) return;
76899
+ seen.add(filePath);
76900
+ try {
76901
+ const content = readFileSync(filePath, "utf-8");
76902
+ const skill = parseSkillMd(content);
76903
+ if (skill) skills.push(skill);
76904
+ } catch {
76905
+ }
76672
76906
  }
76673
76907
  try {
76674
76908
  const entries = readdirSync(absDir, { withFileTypes: true });
76675
76909
  for (const entry of entries) {
76676
- if (entry.isDirectory()) {
76910
+ if (entry.isFile() && entry.name.endsWith("SKILL.md")) {
76911
+ tryLoad(join4(absDir, entry.name));
76912
+ } else if (entry.isDirectory()) {
76677
76913
  const subSkill = join4(absDir, entry.name, "SKILL.md");
76678
76914
  if (existsSync(subSkill)) {
76679
- const content = readFileSync(subSkill, "utf-8");
76680
- const skill = parseSkillMd(content);
76681
- if (skill) skills.push(skill);
76915
+ tryLoad(subSkill);
76682
76916
  }
76683
76917
  }
76684
76918
  }
@@ -77079,6 +77313,7 @@ var init_index = __esm({
77079
77313
  async "src/index.ts"() {
77080
77314
  await init_channel();
77081
77315
  init_types();
77316
+ init_credential_store();
77082
77317
  init_account_config();
77083
77318
  await init_openclaw_plugin();
77084
77319
  init_gateway_send();
@@ -77097,6 +77332,7 @@ var init_index = __esm({
77097
77332
  await init_index();
77098
77333
  export {
77099
77334
  AgentVaultMcpServer,
77335
+ CredentialStore,
77100
77336
  PolicyEnforcer,
77101
77337
  SecureChannel,
77102
77338
  VERSION,