@agentvault/agentvault 0.23.13 → 0.23.15

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
@@ -63965,6 +63965,24 @@ var init_channel = __esm({
63965
63965
  _pendingKpBundles = [];
63966
63966
  /** Pool target: keep this many unconsumed KeyPackages published; backend caps to match. */
63967
63967
  static _KP_POOL_TARGET = 10;
63968
+ /**
63969
+ * Local retention bound for `_pendingKpBundles` (#363). Deliberately ABOVE
63970
+ * `_KP_POOL_TARGET`, because the two counts are in DIFFERENT UNITS: the backend
63971
+ * keeps up to `_KP_POOL_TARGET` **unconsumed** KeyPackages, while this array
63972
+ * holds every bundle published since the last join — consumed ones are removed
63973
+ * only by `_removeKpFromPool` on a successful join.
63974
+ *
63975
+ * Capping local retention AT the backend's target therefore evicts bundles the
63976
+ * backend can still serve, and their private material cannot be rebuilt (#341:
63977
+ * `mls_rs_uniffi` exposes no KeyPackage repository), so any Welcome minted
63978
+ * against an evicted bundle is PERMANENTLY unjoinable.
63979
+ *
63980
+ * Same defect and same remedy as #511, where the web store capped 6 against a
63981
+ * backend pool of 10 and was raised to 12. Retaining extra bundles is cheap and
63982
+ * purely protective — `_handleMlsWelcome` trial-decrypts against the whole pool,
63983
+ * so a surplus only ever adds chances to match.
63984
+ */
63985
+ static _KP_POOL_RETENTION = 20;
63968
63986
  /** Reentrancy guard so concurrent pool top-ups don't double-publish past target. */
63969
63987
  _kpPoolFilling = false;
63970
63988
  /** Buffer for MLS commits received before Welcome (keyed by groupId, sorted by epoch). */
@@ -64628,7 +64646,9 @@ var init_channel = __esm({
64628
64646
  const pendingWsSends = [];
64629
64647
  const sentSharedGroupIds = /* @__PURE__ */ new Set();
64630
64648
  if (this._persisted?.mlsGroups && this._state === "ready" && this._ws) {
64649
+ const targetSharedGid = targetConvId ? this._sessionGroupIds?.get(targetConvId) : void 0;
64631
64650
  for (const [gid, entry] of Object.entries(this._persisted.mlsGroups)) {
64651
+ if (targetConvId && gid !== targetSharedGid) continue;
64632
64652
  if (!entry.mlsGroupId) continue;
64633
64653
  const mlsGroup = this._mlsGroups.get(`1to1-group:${gid}`);
64634
64654
  if (!mlsGroup?.isInitialized || Number(mlsGroup.epoch) <= 0) continue;
@@ -64829,7 +64849,7 @@ var init_channel = __esm({
64829
64849
  */
64830
64850
  sendActivitySpan(spanData) {
64831
64851
  if (!this._ws || this._ws.readyState !== WebSocket.OPEN) return;
64832
- const pluginVersion = true ? "0.23.13" : "0.0.0-dev";
64852
+ const pluginVersion = true ? "0.23.15" : "0.0.0-dev";
64833
64853
  const agentName = this.config.agentName ?? "Agent";
64834
64854
  const resource = {
64835
64855
  "service.name": "agentvault-agent",
@@ -65174,13 +65194,18 @@ var init_channel = __esm({
65174
65194
  * entry, then persist.
65175
65195
  */
65176
65196
  async _handleServerError(payload) {
65177
- if (payload?.detail !== "conversation deleted") return;
65197
+ const detail = payload?.detail;
65198
+ if (detail !== "conversation deleted" && detail !== "unknown group") return;
65178
65199
  const mlsGroupId = payload.group_id;
65179
65200
  if (!mlsGroupId) return;
65180
65201
  const groups = this._persisted?.mlsGroups;
65181
65202
  if (!groups) return;
65182
65203
  const gid = Object.keys(groups).find((k2) => groups[k2]?.mlsGroupId === mlsGroupId);
65183
65204
  if (!gid) return;
65205
+ if (detail === "unknown group") {
65206
+ await this._reconcileConversationGroups(gid, mlsGroupId);
65207
+ return;
65208
+ }
65184
65209
  try {
65185
65210
  await deleteMlsState(this.config.dataDir, mlsGroupId);
65186
65211
  } catch {
@@ -65193,6 +65218,70 @@ var init_channel = __esm({
65193
65218
  );
65194
65219
  this.emit("dm_group_pruned", { conversationGroupId: gid, mlsGroupId });
65195
65220
  }
65221
+ /**
65222
+ * Re-resolve the conversation→group map from the server (#719).
65223
+ *
65224
+ * `_persisted.conversationGroupIds` and `_persisted.groupId` are built ONCE,
65225
+ * in `_activate()`, from the activation response. `_activate` runs at
65226
+ * enrollment and never again, so when the owner deletes a conversation and a
65227
+ * new one is created the agent keeps addressing the OLD group forever.
65228
+ * Nothing reconciled it — not restart (the stale map is reloaded from disk),
65229
+ * not reconnect, not the heartbeat.
65230
+ *
65231
+ * wren, 2026-07-31: `groupId` 53798dd2 had no `mls_groups` row while three
65232
+ * ACTIVE conversations sat on 99faf707. Inbound worked, every reply was
65233
+ * refused `unknown group` and dropped. Silent since 2026-07-02 — the #460
65234
+ * fan-out was spraying all 12 known groups, so a live one always got hit.
65235
+ *
65236
+ * Best-effort and conservative, mirroring `_refreshRoomRoster`: on any
65237
+ * failure the existing map is kept. The prune is NARROW — only the group the
65238
+ * server named, and only once the server has positively reported an active
65239
+ * conversation on some other group. Absence of data is never treated as
65240
+ * evidence of deletion.
65241
+ */
65242
+ async _reconcileConversationGroups(staleGid, staleMlsGroupId) {
65243
+ const jwt2 = this._deviceJwt ?? this._persisted?.deviceJwt;
65244
+ if (!jwt2 || !this._persisted) return;
65245
+ let rows;
65246
+ try {
65247
+ const res = await fetch(`${this.config.apiUrl}/api/v1/conversations`, {
65248
+ headers: { Authorization: `Bearer ${jwt2}` }
65249
+ });
65250
+ if (!res.ok) return;
65251
+ rows = await res.json();
65252
+ } catch (err) {
65253
+ console.warn("[SecureChannel] Conversation re-resolve failed:", err);
65254
+ return;
65255
+ }
65256
+ if (!Array.isArray(rows)) return;
65257
+ const mine = rows.filter(
65258
+ (r2) => r2?.agent_device_id === this._deviceId && r2?.status === "active" && typeof r2?.group_id === "string" && typeof r2?.id === "string"
65259
+ );
65260
+ if (mine.length === 0) return;
65261
+ const before = this._persisted.groupId;
65262
+ this._sessionGroupIds = new Map(mine.map((r2) => [r2.id, r2.group_id]));
65263
+ this._persisted.conversationGroupIds = Object.fromEntries(this._sessionGroupIds);
65264
+ const primary = mine.find((r2) => r2.id === this._persisted.primaryConversationId) ?? mine[0];
65265
+ this._persisted.groupId = primary.group_id;
65266
+ this._persisted.primaryConversationId = primary.id;
65267
+ const liveGroupIds = new Set(mine.map((r2) => r2.group_id));
65268
+ if (!liveGroupIds.has(staleGid)) {
65269
+ try {
65270
+ await deleteMlsState(this.config.dataDir, staleMlsGroupId);
65271
+ } catch {
65272
+ }
65273
+ this._mlsGroups.delete(`1to1-group:${staleGid}`);
65274
+ delete this._persisted.mlsGroups?.[staleGid];
65275
+ this.emit("dm_group_pruned", {
65276
+ conversationGroupId: staleGid,
65277
+ mlsGroupId: staleMlsGroupId
65278
+ });
65279
+ }
65280
+ await this._persistState();
65281
+ console.log(
65282
+ `[SecureChannel] Re-resolved conversation groups after 'unknown group' ${staleMlsGroupId.slice(0, 8)}: primary group ${String(before).slice(0, 8)} \u2192 ${this._persisted.groupId.slice(0, 8)} (${mine.length} active conversation(s))`
65283
+ );
65284
+ }
65196
65285
  /**
65197
65286
  * Return info for all joined rooms.
65198
65287
  */
@@ -66459,13 +66548,12 @@ var init_channel = __esm({
66459
66548
  sessions,
66460
66549
  messageHistory: this._persisted.messageHistory ?? []
66461
66550
  };
66462
- if (conversations.length > 0) {
66463
- const firstConv = conversations[0];
66464
- if (firstConv.group_id) {
66465
- this._persisted.groupId = firstConv.group_id;
66551
+ if (primary) {
66552
+ if (primary.group_id) {
66553
+ this._persisted.groupId = primary.group_id;
66466
66554
  }
66467
- if (firstConv.default_topic_id) {
66468
- this._persisted.defaultTopicId = firstConv.default_topic_id;
66555
+ if (primary.default_topic_id) {
66556
+ this._persisted.defaultTopicId = primary.default_topic_id;
66469
66557
  }
66470
66558
  }
66471
66559
  this._sessionGroupIds = /* @__PURE__ */ new Map();
@@ -66568,7 +66656,7 @@ var init_channel = __esm({
66568
66656
  agentVersion: this.config.agentVersion ?? "0.0.0",
66569
66657
  // __AV_VERSION__ is injected by esbuild from package.json at build time.
66570
66658
  // Falls back to "0.0.0-dev" in non-bundled contexts (tests).
66571
- pluginVersion: true ? "0.23.13" : "0.0.0-dev"
66659
+ pluginVersion: true ? "0.23.15" : "0.0.0-dev"
66572
66660
  });
66573
66661
  this._telemetryReporter.startAutoFlush(3e4);
66574
66662
  }
@@ -66886,7 +66974,7 @@ var init_channel = __esm({
66886
66974
  agentVersion: this.config.agentVersion ?? "0.0.0",
66887
66975
  // __AV_VERSION__ is injected by esbuild from package.json at build time.
66888
66976
  // Falls back to "0.0.0-dev" in non-bundled contexts (tests).
66889
- pluginVersion: true ? "0.23.13" : "0.0.0-dev"
66977
+ pluginVersion: true ? "0.23.15" : "0.0.0-dev"
66890
66978
  });
66891
66979
  this._telemetryReporter.startAutoFlush(3e4);
66892
66980
  }
@@ -68549,7 +68637,7 @@ ${messageText}`;
68549
68637
  if (!this._pendingKpBundles.some((b2) => idOf(b2) === id)) {
68550
68638
  this._pendingKpBundles.push(kp);
68551
68639
  }
68552
- while (this._pendingKpBundles.length > _SecureChannel._KP_POOL_TARGET) {
68640
+ while (this._pendingKpBundles.length > _SecureChannel._KP_POOL_RETENTION) {
68553
68641
  this._pendingKpBundles.shift();
68554
68642
  }
68555
68643
  }
@@ -97171,7 +97259,7 @@ var init_index = __esm({
97171
97259
  init_skill_invoker();
97172
97260
  await init_skill_telemetry();
97173
97261
  await init_policy_enforcer();
97174
- VERSION = true ? "0.23.13" : "0.0.0-dev";
97262
+ VERSION = true ? "0.23.15" : "0.0.0-dev";
97175
97263
  }
97176
97264
  });
97177
97265
  await init_index();