@agentvault/claude-bridge 0.6.1 → 0.6.3

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
@@ -65555,7 +65555,7 @@ var init_channel = __esm2({
65555
65555
  */
65556
65556
  sendActivitySpan(spanData) {
65557
65557
  if (!this._ws || this._ws.readyState !== WebSocket2.OPEN) return;
65558
- const pluginVersion = true ? "0.23.8" : "0.0.0-dev";
65558
+ const pluginVersion = true ? "0.23.9" : "0.0.0-dev";
65559
65559
  const agentName = this.config.agentName ?? "Agent";
65560
65560
  const resource = {
65561
65561
  "service.name": "agentvault-agent",
@@ -65796,10 +65796,76 @@ var init_channel = __esm2({
65796
65796
  this._sessions.delete(convId);
65797
65797
  delete this._persisted.sessions[convId];
65798
65798
  }
65799
+ if (room.mlsGroupId) {
65800
+ try {
65801
+ await deleteMlsState(this.config.dataDir, room.mlsGroupId);
65802
+ } catch {
65803
+ }
65804
+ }
65805
+ this._mlsGroups.delete(roomId);
65799
65806
  delete this._persisted.rooms[roomId];
65800
65807
  await this._persistState();
65801
65808
  this.emit("room_left", { roomId });
65802
65809
  }
65810
+ /**
65811
+ * #629 recurrence fix — reconcile local room state against the server on
65812
+ * connect.
65813
+ *
65814
+ * On disband the server marks each member ``"left"`` and sets
65815
+ * ``room.status="disbanded"`` but sends NO WS event to the agent's bridge, so a
65816
+ * disbanded/left room lingers in local state and replays dead MLS commit
65817
+ * history on every reconnect ("Desired gen in the past" / "invalid ghash tag").
65818
+ * ``GET /rooms`` returns ONLY active rooms the caller is a member of, so any
65819
+ * locally-persisted room ABSENT from it is terminal — prune its MLS state
65820
+ * (file + in-memory group, keyed by roomId), its sessions, and the room entry.
65821
+ *
65822
+ * Fail-safe: a failed fetch / non-ok / non-array response prunes NOTHING, so a
65823
+ * transient error can never over-prune a live room.
65824
+ */
65825
+ async _reconcileRoomsWithServer() {
65826
+ const local = this._persisted?.rooms;
65827
+ if (!local || Object.keys(local).length === 0) return;
65828
+ if (!this._deviceJwt) return;
65829
+ let activeIds;
65830
+ try {
65831
+ const res = await fetch(`${this.config.apiUrl}/api/v1/rooms`, {
65832
+ headers: { Authorization: `Bearer ${this._deviceJwt}` }
65833
+ });
65834
+ if (!res.ok) return;
65835
+ const list = await res.json();
65836
+ if (!Array.isArray(list)) return;
65837
+ activeIds = new Set(
65838
+ list.map((r22) => r22?.id).filter((id) => !!id)
65839
+ );
65840
+ } catch (err) {
65841
+ console.warn(
65842
+ `[SecureChannel] room reconcile skipped (fetch failed, pruning nothing): ${err instanceof Error ? err.message : String(err)}`
65843
+ );
65844
+ return;
65845
+ }
65846
+ const stale = Object.keys(local).filter((rid) => !activeIds.has(rid));
65847
+ if (stale.length === 0) return;
65848
+ for (const rid of stale) {
65849
+ const room = local[rid];
65850
+ if (room?.mlsGroupId) {
65851
+ try {
65852
+ await deleteMlsState(this.config.dataDir, room.mlsGroupId);
65853
+ } catch {
65854
+ }
65855
+ }
65856
+ this._mlsGroups.delete(rid);
65857
+ for (const convId of room?.conversationIds ?? []) {
65858
+ this._sessions.delete(convId);
65859
+ if (this._persisted?.sessions) delete this._persisted.sessions[convId];
65860
+ }
65861
+ delete local[rid];
65862
+ }
65863
+ await this._persistState();
65864
+ console.log(
65865
+ `[SecureChannel] Room reconcile: pruned ${stale.length} disbanded/left room(s) from local state (${stale.map((r22) => r22.slice(0, 8)).join(", ")})`
65866
+ );
65867
+ this.emit("rooms_reconciled", { pruned: stale });
65868
+ }
65803
65869
  /**
65804
65870
  * Return info for all joined rooms.
65805
65871
  */
@@ -67150,6 +67216,9 @@ var init_channel = __esm2({
67150
67216
  await this._pullDrDeliveryQueue();
67151
67217
  await this._flushOutboundQueue();
67152
67218
  this._setState("ready");
67219
+ void this._reconcileRoomsWithServer().catch(
67220
+ (err) => console.warn(`[SecureChannel] room reconcile failed (ignored): ${err instanceof Error ? err.message : String(err)}`)
67221
+ );
67153
67222
  if (this.config.enableScanning) {
67154
67223
  this._scanEngine = new ScanEngine();
67155
67224
  await this._fetchScanRules();
@@ -67172,7 +67241,7 @@ var init_channel = __esm2({
67172
67241
  agentVersion: this.config.agentVersion ?? "0.0.0",
67173
67242
  // __AV_VERSION__ is injected by esbuild from package.json at build time.
67174
67243
  // Falls back to "0.0.0-dev" in non-bundled contexts (tests).
67175
- pluginVersion: true ? "0.23.8" : "0.0.0-dev"
67244
+ pluginVersion: true ? "0.23.9" : "0.0.0-dev"
67176
67245
  });
67177
67246
  this._telemetryReporter.startAutoFlush(3e4);
67178
67247
  }
@@ -67490,7 +67559,7 @@ var init_channel = __esm2({
67490
67559
  agentVersion: this.config.agentVersion ?? "0.0.0",
67491
67560
  // __AV_VERSION__ is injected by esbuild from package.json at build time.
67492
67561
  // Falls back to "0.0.0-dev" in non-bundled contexts (tests).
67493
- pluginVersion: true ? "0.23.8" : "0.0.0-dev"
67562
+ pluginVersion: true ? "0.23.9" : "0.0.0-dev"
67494
67563
  });
67495
67564
  this._telemetryReporter.startAutoFlush(3e4);
67496
67565
  }
@@ -97268,7 +97337,7 @@ var init_index = __esm2({
97268
97337
  init_skill_invoker();
97269
97338
  await init_skill_telemetry();
97270
97339
  await init_policy_enforcer();
97271
- VERSION = true ? "0.23.8" : "0.0.0-dev";
97340
+ VERSION = true ? "0.23.9" : "0.0.0-dev";
97272
97341
  }
97273
97342
  });
97274
97343
  await init_index();
@@ -132545,6 +132614,12 @@ var PersistentClaudeSession = class {
132545
132614
  currentReplyExpected = false;
132546
132615
  saidThisTurn = false;
132547
132616
  turnText = "";
132617
+ /** #630 drop-probe: did a `result` event arrive for the CURRENT turn? Reset per
132618
+ * turn in input(), set in the result block. Distinguishes a turn that never
132619
+ * reached a result boundary (the silent-drop prime suspect) from a healthy
132620
+ * #416 fallback delivery (which delivers via `void reply(...)` WITHOUT setting
132621
+ * saidThisTurn, so !saidThisTurn alone would false-flag it). */
132622
+ sawResultThisTurn = false;
132548
132623
  /** In-process room MCP server — hoisted so buildSdkOptions can reference it. */
132549
132624
  roomServer;
132550
132625
  /** AbortController for the in-flight query(); abort() triggers it (queue timeout). */
@@ -132613,6 +132688,7 @@ var PersistentClaudeSession = class {
132613
132688
  this.currentArmedGetter = item.armed ?? (() => false);
132614
132689
  this.saidThisTurn = false;
132615
132690
  this.turnText = "";
132691
+ this.sawResultThisTurn = false;
132616
132692
  yield item.msg;
132617
132693
  }
132618
132694
  }
@@ -132732,20 +132808,35 @@ var PersistentClaudeSession = class {
132732
132808
  prompt: this.input(),
132733
132809
  options: sdkOptions
132734
132810
  });
132735
- for await (const m6 of q10) {
132736
- if (m6.type === "assistant") {
132737
- const blocks = m6.message?.content ?? [];
132738
- const text = blocks.filter((b5) => b5.type === "text").map((b5) => b5.text ?? "").join("");
132739
- if (text.trim()) {
132740
- this.turnText += text;
132741
- this.opts.onObserve?.(text);
132742
- }
132743
- } else if (m6.type === "result") {
132744
- const reply = this.activeReply;
132745
- if (this.currentReplyExpected && !this.saidThisTurn && this.turnText.trim() && reply) {
132746
- void reply(this.turnText);
132811
+ try {
132812
+ for await (const m6 of q10) {
132813
+ if (m6.type === "assistant") {
132814
+ const blocks = m6.message?.content ?? [];
132815
+ const text = blocks.filter((b5) => b5.type === "text").map((b5) => b5.text ?? "").join("");
132816
+ if (text.trim()) {
132817
+ this.turnText += text;
132818
+ this.opts.onObserve?.(text);
132819
+ }
132820
+ } else if (m6.type === "result") {
132821
+ this.sawResultThisTurn = true;
132822
+ const reply = this.activeReply;
132823
+ if (this.currentReplyExpected && !this.saidThisTurn && this.turnText.trim()) {
132824
+ if (reply) {
132825
+ void reply(this.turnText);
132826
+ } else {
132827
+ console.error(
132828
+ `[drop-probe] BLOCKED at result: composed ${this.turnText.length} chars, reply expected but activeReply unset \u2014 reply DROPPED`
132829
+ );
132830
+ }
132831
+ }
132747
132832
  }
132748
132833
  }
132834
+ } finally {
132835
+ if (this.currentReplyExpected && !this.saidThisTurn && !this.sawResultThisTurn && this.turnText.trim()) {
132836
+ console.error(
132837
+ `[drop-probe] STREAM ENDED before result: composed ${this.turnText.length} chars, reply expected, said=false \u2014 reply DROPPED (no result event)`
132838
+ );
132839
+ }
132749
132840
  }
132750
132841
  }
132751
132842
  };
@@ -133281,7 +133372,7 @@ async function main() {
133281
133372
  "[bridge] warning: passing the invite token on the command line is visible to other local users via 'ps'. Prefer: AV_INVITE_TOKEN=\u2026 npx @agentvault/claude-bridge"
133282
133373
  );
133283
133374
  }
133284
- console.error(`[bridge] version: ${true ? "0.6.1" : "dev"}`);
133375
+ console.error(`[bridge] version: ${true ? "0.6.3" : "dev"}`);
133285
133376
  console.error(`[bridge] data dir: ${cfg.dataDir} (${cfg.dataDirSource})`);
133286
133377
  console.error(`[bridge] workspace: ${cfg.workspaceDir} \xB7 permissions: ${cfg.permissionMode} \xB7 enclave dir fenced`);
133287
133378
  if (cfg.armRoom) {
package/dist/session.d.ts CHANGED
@@ -137,6 +137,12 @@ export declare class PersistentClaudeSession {
137
137
  private currentReplyExpected;
138
138
  private saidThisTurn;
139
139
  private turnText;
140
+ /** #630 drop-probe: did a `result` event arrive for the CURRENT turn? Reset per
141
+ * turn in input(), set in the result block. Distinguishes a turn that never
142
+ * reached a result boundary (the silent-drop prime suspect) from a healthy
143
+ * #416 fallback delivery (which delivers via `void reply(...)` WITHOUT setting
144
+ * saidThisTurn, so !saidThisTurn alone would false-flag it). */
145
+ private sawResultThisTurn;
140
146
  /** In-process room MCP server — hoisted so buildSdkOptions can reference it. */
141
147
  private roomServer;
142
148
  /** AbortController for the in-flight query(); abort() triggers it (queue timeout). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentvault/claude-bridge",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "type": "module",
5
5
  "description": "AgentVault Claude Bridge — daemon for bridging a Claude agent into secure E2E-encrypted AgentVault 1:1 direct messages and rooms.",
6
6
  "main": "dist/index.js",