@agentvault/claude-bridge 0.7.6 → 0.7.7

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
@@ -453,12 +453,12 @@ import { join as join11 } from "node:path";
453
453
  // ../plugin/dist/index.js
454
454
  import * as nc from "node:crypto";
455
455
  import { chmod, mkdir, writeFile } from "node:fs/promises";
456
- import { readFile, rm } from "node:fs/promises";
456
+ import { readdir, readFile, rename, rm } from "node:fs/promises";
457
457
  import { join } from "node:path";
458
- import { readFile as readFile2, rename, rm as rm2 } from "node:fs/promises";
458
+ import { readFile as readFile2, rename as rename2, rm as rm2 } from "node:fs/promises";
459
459
  import { join as join2 } from "node:path";
460
460
  import { Readable } from "node:stream";
461
- import { readdir, readFile as readFile3, writeFile as writeFile2, rename as rename2, stat, unlink, mkdir as mkdir2 } from "node:fs/promises";
461
+ import { readdir as readdir2, readFile as readFile3, writeFile as writeFile2, rename as rename3, stat, unlink, mkdir as mkdir2 } from "node:fs/promises";
462
462
  import { join as join3 } from "node:path";
463
463
  import { randomUUID } from "node:crypto";
464
464
  import { EventEmitter } from "node:events";
@@ -63991,7 +63991,7 @@ async function backupState(dataDir) {
63991
63991
  try {
63992
63992
  const data = await readFile2(src, "utf-8");
63993
63993
  await writeSecureFile(tmp, data);
63994
- await rename(tmp, bak);
63994
+ await rename2(tmp, bak);
63995
63995
  } catch {
63996
63996
  }
63997
63997
  }
@@ -64126,19 +64126,30 @@ var init_transport2 = __esm2({
64126
64126
  });
64127
64127
  var openclaw_compat_exports = {};
64128
64128
  __export2(openclaw_compat_exports, {
64129
+ AGENT_EVENT_CANDIDATES: () => AGENT_EVENT_CANDIDATES,
64130
+ HEARTBEAT_CANDIDATES: () => HEARTBEAT_CANDIDATES,
64131
+ TRANSCRIPT_CANDIDATES: () => TRANSCRIPT_CANDIDATES,
64132
+ _resetCompatCacheForTest: () => _resetCompatCacheForTest,
64129
64133
  onAgentEvent: () => onAgentEvent,
64130
64134
  onSessionTranscriptUpdate: () => onSessionTranscriptUpdate,
64131
- requestHeartbeatNow: () => requestHeartbeatNow
64135
+ requestHeartbeatNow: () => requestHeartbeatNow,
64136
+ resolveSdkFn: () => resolveSdkFn
64132
64137
  });
64133
- async function requestHeartbeatNow(opts) {
64134
- if (_heartbeatFn === null) {
64138
+ async function resolveSdkFn(candidates, importer = defaultImporter) {
64139
+ for (const { specifier, symbol: symbol22 } of candidates) {
64135
64140
  try {
64136
- const mod4 = await import("openclaw/dist/plugin-sdk/infra/heartbeat-wake.js");
64137
- _heartbeatFn = mod4.requestHeartbeatNow ?? mod4.default?.requestHeartbeatNow ?? false;
64141
+ const mod4 = await importer(specifier);
64142
+ const found = mod4?.[symbol22] ?? mod4?.default?.[symbol22];
64143
+ if (typeof found === "function") return found;
64138
64144
  } catch {
64139
- _heartbeatFn = false;
64140
64145
  }
64141
64146
  }
64147
+ return false;
64148
+ }
64149
+ async function requestHeartbeatNow(opts) {
64150
+ if (_heartbeatFn === null) {
64151
+ _heartbeatFn = await resolveSdkFn(HEARTBEAT_CANDIDATES);
64152
+ }
64142
64153
  if (typeof _heartbeatFn === "function") {
64143
64154
  try {
64144
64155
  await _heartbeatFn(opts);
@@ -64151,12 +64162,7 @@ async function requestHeartbeatNow(opts) {
64151
64162
  }
64152
64163
  async function onAgentEvent(callback) {
64153
64164
  if (_agentEventFn === null) {
64154
- try {
64155
- const mod4 = await import("openclaw/dist/plugin-sdk/infra/agent-events.js");
64156
- _agentEventFn = mod4.onAgentEvent ?? mod4.default?.onAgentEvent ?? false;
64157
- } catch {
64158
- _agentEventFn = false;
64159
- }
64165
+ _agentEventFn = await resolveSdkFn(AGENT_EVENT_CANDIDATES);
64160
64166
  }
64161
64167
  if (typeof _agentEventFn === "function") {
64162
64168
  try {
@@ -64171,12 +64177,7 @@ async function onAgentEvent(callback) {
64171
64177
  }
64172
64178
  async function onSessionTranscriptUpdate(callback) {
64173
64179
  if (_transcriptFn === null) {
64174
- try {
64175
- const mod4 = await import("openclaw/dist/plugin-sdk/sessions/transcript-events.js");
64176
- _transcriptFn = mod4.onSessionTranscriptUpdate ?? mod4.default?.onSessionTranscriptUpdate ?? false;
64177
- } catch {
64178
- _transcriptFn = false;
64179
- }
64180
+ _transcriptFn = await resolveSdkFn(TRANSCRIPT_CANDIDATES);
64180
64181
  }
64181
64182
  if (typeof _transcriptFn === "function") {
64182
64183
  try {
@@ -64189,12 +64190,45 @@ async function onSessionTranscriptUpdate(callback) {
64189
64190
  return () => {
64190
64191
  };
64191
64192
  }
64193
+ function _resetCompatCacheForTest() {
64194
+ _heartbeatFn = null;
64195
+ _agentEventFn = null;
64196
+ _transcriptFn = null;
64197
+ }
64198
+ var defaultImporter;
64199
+ var HEARTBEAT_CANDIDATES;
64200
+ var AGENT_EVENT_CANDIDATES;
64201
+ var TRANSCRIPT_CANDIDATES;
64192
64202
  var _heartbeatFn;
64193
64203
  var _agentEventFn;
64194
64204
  var _transcriptFn;
64195
64205
  var init_openclaw_compat = __esm2({
64196
64206
  "src/openclaw-compat.ts"() {
64197
64207
  "use strict";
64208
+ defaultImporter = (specifier) => import(
64209
+ /* @vite-ignore */
64210
+ specifier
64211
+ );
64212
+ HEARTBEAT_CANDIDATES = [
64213
+ // 2026.7.x — exported subpath, and the symbol lost its `Now` suffix.
64214
+ { specifier: "openclaw/plugin-sdk/heartbeat-runtime", symbol: "requestHeartbeat" },
64215
+ // <=2026.6.x — deep dist path, original name.
64216
+ { specifier: "openclaw/dist/plugin-sdk/infra/heartbeat-wake.js", symbol: "requestHeartbeatNow" }
64217
+ ];
64218
+ AGENT_EVENT_CANDIDATES = [
64219
+ { specifier: "openclaw/plugin-sdk/agent-harness", symbol: "onAgentEvent" },
64220
+ { specifier: "openclaw/dist/plugin-sdk/infra/agent-events.js", symbol: "onAgentEvent" }
64221
+ ];
64222
+ TRANSCRIPT_CANDIDATES = [
64223
+ {
64224
+ specifier: "openclaw/plugin-sdk/memory-core-host-engine-foundation",
64225
+ symbol: "onSessionTranscriptUpdate"
64226
+ },
64227
+ {
64228
+ specifier: "openclaw/dist/plugin-sdk/sessions/transcript-events.js",
64229
+ symbol: "onSessionTranscriptUpdate"
64230
+ }
64231
+ ];
64198
64232
  _heartbeatFn = null;
64199
64233
  _agentEventFn = null;
64200
64234
  _transcriptFn = null;
@@ -64509,7 +64543,7 @@ async function handleWorkspaceUpload(data, workspaceDir) {
64509
64543
  const tempPath = join3(workspaceDir, `.tmp_${randomUUID()}_${data.filename}`);
64510
64544
  try {
64511
64545
  await writeFile2(tempPath, data.content, "utf-8");
64512
- await rename2(tempPath, targetPath);
64546
+ await rename3(tempPath, targetPath);
64513
64547
  } catch (err) {
64514
64548
  try {
64515
64549
  await unlink(tempPath);
@@ -64526,7 +64560,7 @@ async function handleWorkspaceUpload(data, workspaceDir) {
64526
64560
  }
64527
64561
  async function handleWorkspaceList(workspaceDir) {
64528
64562
  try {
64529
- const entries = await readdir(workspaceDir);
64563
+ const entries = await readdir2(workspaceDir);
64530
64564
  const files = [];
64531
64565
  for (const entry of entries) {
64532
64566
  if (!entry.endsWith(".md")) continue;
@@ -65687,7 +65721,7 @@ var init_channel = __esm2({
65687
65721
  */
65688
65722
  sendActivitySpan(spanData) {
65689
65723
  if (!this._ws || this._ws.readyState !== WebSocket2.OPEN) return;
65690
- const pluginVersion = true ? "0.23.18" : "0.0.0-dev";
65724
+ const pluginVersion = true ? "0.23.19" : "0.0.0-dev";
65691
65725
  const agentName = this.config.agentName ?? "Agent";
65692
65726
  const resource = {
65693
65727
  "service.name": "agentvault-agent",
@@ -67552,7 +67586,7 @@ var init_channel = __esm2({
67552
67586
  agentVersion: this.config.agentVersion ?? "0.0.0",
67553
67587
  // __AV_VERSION__ is injected by esbuild from package.json at build time.
67554
67588
  // Falls back to "0.0.0-dev" in non-bundled contexts (tests).
67555
- pluginVersion: true ? "0.23.18" : "0.0.0-dev"
67589
+ pluginVersion: true ? "0.23.19" : "0.0.0-dev"
67556
67590
  });
67557
67591
  this._telemetryReporter.startAutoFlush(3e4);
67558
67592
  }
@@ -67875,7 +67909,7 @@ var init_channel = __esm2({
67875
67909
  agentVersion: this.config.agentVersion ?? "0.0.0",
67876
67910
  // __AV_VERSION__ is injected by esbuild from package.json at build time.
67877
67911
  // Falls back to "0.0.0-dev" in non-bundled contexts (tests).
67878
- pluginVersion: true ? "0.23.18" : "0.0.0-dev"
67912
+ pluginVersion: true ? "0.23.19" : "0.0.0-dev"
67879
67913
  });
67880
67914
  this._telemetryReporter.startAutoFlush(3e4);
67881
67915
  }
@@ -71697,6 +71731,11 @@ var init_tool_audit = __esm2({
71697
71731
  "use strict";
71698
71732
  }
71699
71733
  });
71734
+ var init_harness_compat = __esm2({
71735
+ "src/harness-compat.ts"() {
71736
+ "use strict";
71737
+ }
71738
+ });
71700
71739
  var isUsingManagedRoutes;
71701
71740
  var init_openclaw_entry = __esm2({
71702
71741
  "src/openclaw-entry.ts"() {
@@ -71707,6 +71746,7 @@ var init_openclaw_entry = __esm2({
71707
71746
  init_openclaw_compat();
71708
71747
  init_lifecycle();
71709
71748
  init_tool_audit();
71749
+ init_harness_compat();
71710
71750
  init_types();
71711
71751
  isUsingManagedRoutes = false;
71712
71752
  }
@@ -97758,7 +97798,7 @@ var init_index = __esm2({
97758
97798
  init_skill_invoker();
97759
97799
  await init_skill_telemetry();
97760
97800
  await init_policy_enforcer();
97761
- VERSION = true ? "0.23.18" : "0.0.0-dev";
97801
+ VERSION = true ? "0.23.19" : "0.0.0-dev";
97762
97802
  }
97763
97803
  });
97764
97804
  await init_index();
@@ -133398,6 +133438,16 @@ var WorkerQueue = class {
133398
133438
  this.running = false;
133399
133439
  }
133400
133440
  }
133441
+ /**
133442
+ * What the worker had produced, when the session can report it. Read on BOTH the
133443
+ * clean and the failing path: on a clean turn `said: false` with a non-zero
133444
+ * `composedChars` is the "composed but could never ship it" signature (agent-
133445
+ * authored room traffic, where `replyExpected` is false), which is otherwise
133446
+ * indistinguishable from a turn that did nothing.
133447
+ */
133448
+ snapshotOf(session) {
133449
+ return session?.snapshot?.();
133450
+ }
133401
133451
  /** Best-effort trap emit. A broken sink must never break the queue. */
133402
133452
  emitIncident(rec) {
133403
133453
  try {
@@ -133429,10 +133479,23 @@ var WorkerQueue = class {
133429
133479
  }, this.deps.timeoutMs);
133430
133480
  });
133431
133481
  await Promise.race([session.start(), timeout]);
133482
+ const okSnap = this.snapshotOf(session);
133483
+ this.emitIncident({
133484
+ at: new Date(startedAt).toISOString(),
133485
+ outcome: "ok",
133486
+ waitedMs,
133487
+ ranMs: Date.now() - startedAt,
133488
+ timeoutMs: this.deps.timeoutMs,
133489
+ queueDepthAtEnqueue: entry.queueDepthAtEnqueue,
133490
+ queueDepthAtStart,
133491
+ instructionChars: task.instruction.length,
133492
+ replyExpected: task.replyExpected,
133493
+ ...okSnap ? { session: okSnap } : {}
133494
+ });
133432
133495
  } catch (e7) {
133433
133496
  const err = e7;
133434
133497
  this.deps.log(`[worker-queue] task failed: ${err.message}`);
133435
- const snap = session?.snapshot?.();
133498
+ const snap = this.snapshotOf(session);
133436
133499
  this.emitIncident({
133437
133500
  at: new Date(startedAt).toISOString(),
133438
133501
  outcome: timedOut ? "timeout" : "error",
@@ -133443,6 +133506,7 @@ var WorkerQueue = class {
133443
133506
  queueDepthAtEnqueue: entry.queueDepthAtEnqueue,
133444
133507
  queueDepthAtStart,
133445
133508
  instructionChars: task.instruction.length,
133509
+ replyExpected: task.replyExpected,
133446
133510
  ...snap ? { session: snap } : {}
133447
133511
  });
133448
133512
  session?.abort();
@@ -133464,7 +133528,7 @@ function makeRouter(deps) {
133464
133528
  const replySink = reply ?? (() => {
133465
133529
  });
133466
133530
  const isOwnerDm = opts?.autoReplyOnText === true && opts?.armed === void 0 && deps.workAllowed();
133467
- const isArmedRoom = opts?.armed?.() === true && deps.workAllowed();
133531
+ const isArmedRoom = opts?.armed?.() === true && deps.workAllowed() && opts?.replyExpected !== false;
133468
133532
  if (isOwnerDm) {
133469
133533
  deps.queue.enqueue({
133470
133534
  instruction: text,
@@ -134027,7 +134091,7 @@ async function main() {
134027
134091
  "[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"
134028
134092
  );
134029
134093
  }
134030
- console.error(`[bridge] version: ${true ? "0.7.6" : "dev"}`);
134094
+ console.error(`[bridge] version: ${true ? "0.7.7" : "dev"}`);
134031
134095
  console.error(`[bridge] data dir: ${cfg.dataDir} (${cfg.dataDirSource})`);
134032
134096
  console.error(`[bridge] workspace: ${cfg.workspaceDir} \xB7 permissions: ${cfg.permissionMode} \xB7 enclave dir fenced`);
134033
134097
  if (cfg.armRoom) {
@@ -134110,7 +134174,7 @@ async function main() {
134110
134174
  // incident is visible in bridge.error.log at the moment it happens.
134111
134175
  onIncident: (rec) => {
134112
134176
  console.error(
134113
- `[worker-trap] ${rec.at} ${rec.outcome} after ${rec.ranMs}ms (waited ${rec.waitedMs}ms behind ${rec.queueDepthAtEnqueue}, ${rec.queueDepthAtStart} still queued)` + (rec.session ? ` composed=${rec.session.composedChars} result=${rec.session.sawResult} said=${rec.session.said}` : "")
134177
+ `[worker-trap] ${rec.at} ${rec.outcome} after ${rec.ranMs}ms (waited ${rec.waitedMs}ms behind ${rec.queueDepthAtEnqueue}, ${rec.queueDepthAtStart} still queued) replyExpected=${rec.replyExpected}` + (rec.session ? ` composed=${rec.session.composedChars} result=${rec.session.sawResult} said=${rec.session.said}` : "")
134114
134178
  );
134115
134179
  try {
134116
134180
  const dir = join11(cfg.dataDir, "logs");
@@ -17,19 +17,27 @@ export type WorkerTask = {
17
17
  armed?: () => boolean;
18
18
  };
19
19
  /**
20
- * Forensic record of a task that did NOT complete cleanly.
20
+ * Forensic record of EVERY task the queue runs clean or not.
21
21
  *
22
22
  * Exists because the 2026-07-24 "loopita went quiet for 5 minutes" incident was
23
23
  * diagnosable only by coincidence: the drop-trap happened to capture that the
24
24
  * reply landed 300.016s after the ack, and 300s is `WORKER_TIMEOUT_MS` exactly.
25
25
  * Nothing in the bridge recorded WHY the worker hung, and the logs carry no
26
26
  * timestamps at all, so the evidence was gone by the time we looked.
27
+ *
28
+ * 2026-08-06: widened from failures-only to every turn. Recording only failures
29
+ * made the SLOW case unmeasurable, and slow is the common case: loopita answered
30
+ * Ballroom2 in 21s when the room was quiet and 81s when a second agent was
31
+ * talking, and nothing said whether the extra 60s was `waitedMs` or `ranMs`.
32
+ * Those are different bugs with identical symptoms. A trap that only reports
33
+ * when it breaks cannot tell you it is the reason things are slow.
27
34
  */
28
35
  export type WorkerIncident = {
29
36
  /** ISO timestamp — the bridge's own logs are unstamped, so this is the anchor. */
30
37
  at: string;
31
- outcome: "timeout" | "error";
32
- error: string;
38
+ outcome: "ok" | "timeout" | "error";
39
+ /** Absent on a clean turn — its presence is what marks a real incident. */
40
+ error?: string;
33
41
  /**
34
42
  * enqueue -> start. THE load-bearing field: it separates "this task hung" from
35
43
  * "this task was stuck behind one that hung". Both look identical to the owner
@@ -44,6 +52,15 @@ export type WorkerIncident = {
44
52
  /** Depth still waiting when this task began — the blast radius of this hang. */
45
53
  queueDepthAtStart: number;
46
54
  instructionChars: number;
55
+ /**
56
+ * Whether a reply was expected of this turn — mirrors `WorkerTask.replyExpected`,
57
+ * i.e. `senderIsAgent === false` upstream. Without it a silent turn
58
+ * (composed=0, said=false) is unattributable: it could be agent-authored traffic
59
+ * that was never going to answer, or a turn that waited so long the previous one
60
+ * already answered. Ballroom2 on 2026-08-06 produced both readings and the record
61
+ * could not separate them.
62
+ */
63
+ replyExpected?: boolean;
47
64
  /** What the worker had produced when it died, when the session can report it. */
48
65
  session?: {
49
66
  composedChars: number;
@@ -91,6 +108,14 @@ export declare class WorkerQueue {
91
108
  /** Resolves when the queue has processed everything enqueued so far. */
92
109
  whenDrained(): Promise<void>;
93
110
  private drain;
111
+ /**
112
+ * What the worker had produced, when the session can report it. Read on BOTH the
113
+ * clean and the failing path: on a clean turn `said: false` with a non-zero
114
+ * `composedChars` is the "composed but could never ship it" signature (agent-
115
+ * authored room traffic, where `replyExpected` is false), which is otherwise
116
+ * indistinguishable from a turn that did nothing.
117
+ */
118
+ private snapshotOf;
94
119
  /** Best-effort trap emit. A broken sink must never break the queue. */
95
120
  private emitIncident;
96
121
  private runOne;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentvault/claude-bridge",
3
- "version": "0.7.6",
3
+ "version": "0.7.7",
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",