@agentvault/claude-bridge 0.2.1 → 0.3.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/bridge.d.ts CHANGED
@@ -3,14 +3,60 @@ export interface RoomMessage {
3
3
  senderName: string;
4
4
  plaintext: string;
5
5
  }
6
+ /** Metadata SecureChannel attaches to a 1:1 `message` event. `roomId` is set only
7
+ * when the `message` actually originated in a room (handled by room_message), so
8
+ * its ABSENCE is how we identify a true owner↔agent 1:1 DM. */
9
+ export interface MessageMeta {
10
+ roomId?: string;
11
+ }
6
12
  export interface RoomChannel {
7
13
  on(ev: "room_message", cb: (e: RoomMessage) => void): unknown;
14
+ on(ev: "message", cb: (text: string, metadata: MessageMeta) => void): unknown;
8
15
  on(ev: "error", cb: (err: unknown) => void): unknown;
9
16
  sendToRoom(roomId: string, text: string): Promise<void>;
17
+ send(text: string): Promise<void>;
10
18
  }
11
19
  export interface RoomSession {
12
- push(text: string): void;
13
- onActiveRoom(roomId: string): void;
20
+ /** `reply` is the immutable reply sink captured for THIS message (see
21
+ * ActiveTarget.snapshotReply) — the session invokes it when Claude answers. */
22
+ push(text: string, reply?: (text: string) => Promise<void>): void;
23
+ }
24
+ /** Where a reply should go: a room (sendToRoom) or a 1:1 DM (send). */
25
+ export type BridgeTarget = {
26
+ kind: "room";
27
+ roomId: string;
28
+ } | {
29
+ kind: "dm";
30
+ };
31
+ /** Channel surface ActiveTarget needs to route an outbound reply. */
32
+ export interface ReplyChannel {
33
+ sendToRoom(roomId: string, text: string): Promise<void>;
34
+ send(text: string): Promise<void>;
35
+ }
36
+ /**
37
+ * Tracks the destination of the most recent inbound message and routes outbound
38
+ * replies there. Replaces the bridge's old single `activeRoomId` string so the
39
+ * same Claude session can answer BOTH room traffic and 1:1 owner DMs.
40
+ *
41
+ * Known limitation (matches the prior room-only behavior): the target is global,
42
+ * set per-inbound. If a room message lands while Claude is mid-compose on a DM
43
+ * reply, the reply routes to the room. Binding a reply to its originating turn is
44
+ * a larger redesign; for now the single-conversation-at-a-time assumption holds.
45
+ */
46
+ export declare class ActiveTarget {
47
+ private target;
48
+ setRoom(roomId: string): void;
49
+ setDm(): void;
50
+ get current(): BridgeTarget | null;
51
+ reply(channel: ReplyChannel, text: string): Promise<void>;
52
+ /**
53
+ * Capture the CURRENT target into an immutable reply closure. This is the leak
54
+ * fix: a reply built here routes to where its inbound message came from, even if
55
+ * a later inbound flips the live `current` target. The bridge captures one of
56
+ * these per inbound (at push time) and hands it to the session, so a room message
57
+ * arriving mid-compose can never redirect a private 1:1 reply into the room.
58
+ */
59
+ snapshotReply(channel: ReplyChannel, log?: (msg: string) => void): (text: string) => Promise<void>;
14
60
  }
15
61
  export interface LifecycleChannel {
16
62
  on(ev: "auth_failed", cb: (e: {
@@ -38,7 +84,7 @@ export declare function attachLifecycle(channel: LifecycleChannel, opts?: {
38
84
  log?: (msg: string) => void;
39
85
  onTerminal?: (reason: string) => void;
40
86
  }): void;
41
- export declare function wireBridge(channel: RoomChannel, session: RoomSession, opts?: {
87
+ export declare function wireBridge(channel: RoomChannel, session: RoomSession, target: ActiveTarget, opts?: {
42
88
  roomFilter?: string;
43
89
  log?: (msg: string) => void;
44
90
  }): void;
package/dist/config.d.ts CHANGED
@@ -1,6 +1,8 @@
1
+ export type DataDirSource = "explicit" | "per-agent" | "legacy";
1
2
  export interface BridgeConfig {
2
3
  inviteToken: string;
3
4
  dataDir: string;
5
+ dataDirSource: DataDirSource;
4
6
  apiUrl: string;
5
7
  agentName: string;
6
8
  roomFilter?: string;
package/dist/index.js CHANGED
@@ -69967,6 +69967,40 @@ var init_account_config = __esm({
69967
69967
  DEFAULT_HTTP_PORT = 18790;
69968
69968
  }
69969
69969
  });
69970
+ function terminalReenrollMessage(agentName, reason) {
69971
+ const who = agentName ? `[${agentName}] ` : "";
69972
+ return `${who}device is no longer valid (${reason}) \u2014 it was revoked or replaced. Re-enroll with a fresh token from AgentVault; the old credentials are dead. This agent will stop reconnecting.`;
69973
+ }
69974
+ function attachLifecycle(channel, opts = {}) {
69975
+ const log = opts.log ?? (() => {
69976
+ });
69977
+ const agentName = opts.agentName ?? "";
69978
+ const onTerminal = opts.onTerminal ?? (() => {
69979
+ });
69980
+ let fired = false;
69981
+ const terminal = (reason) => {
69982
+ if (fired) return;
69983
+ fired = true;
69984
+ log(terminalReenrollMessage(agentName, reason));
69985
+ void Promise.resolve(onTerminal(reason)).catch(() => {
69986
+ });
69987
+ };
69988
+ channel.on("auth_failed", (e7) => terminal(e7.reason));
69989
+ channel.on(
69990
+ "session_replaced",
69991
+ (e7) => terminal(e7?.code ? `session_replaced (${e7.code})` : "session_replaced")
69992
+ );
69993
+ channel.on("error", (err) => {
69994
+ const msg = err instanceof Error ? err.message : String(err);
69995
+ if (/reconnect loop detected/i.test(msg)) terminal("reconnect_loop");
69996
+ else if (/device (was )?revoked/i.test(msg)) terminal("device_revoked");
69997
+ });
69998
+ }
69999
+ var init_lifecycle = __esm({
70000
+ "src/lifecycle.ts"() {
70001
+ "use strict";
70002
+ }
70003
+ });
69970
70004
  async function _handleInbound(params) {
69971
70005
  const { plaintext, metadata, channel, account, cfg } = params;
69972
70006
  const core = _ocRuntime;
@@ -70051,6 +70085,7 @@ var init_openclaw_plugin = __esm({
70051
70085
  await init_channel();
70052
70086
  init_account_config();
70053
70087
  init_openclaw_compat();
70088
+ init_lifecycle();
70054
70089
  _ocRuntime = null;
70055
70090
  _channels = /* @__PURE__ */ new Map();
70056
70091
  agentVaultPlugin = {
@@ -70108,6 +70143,7 @@ var init_openclaw_plugin = __esm({
70108
70143
  const dataDir = resolve(account.dataDir.replace(/^~/, process.env.HOME ?? "~"));
70109
70144
  _log?.(`[AgentVault] starting channel (dataDir=${dataDir})`);
70110
70145
  await new Promise((resolvePromise, reject) => {
70146
+ let terminalStopped = false;
70111
70147
  const channel = new SecureChannel({
70112
70148
  inviteToken: "",
70113
70149
  dataDir,
@@ -70126,13 +70162,29 @@ var init_openclaw_plugin = __esm({
70126
70162
  },
70127
70163
  onStateChange: (state) => {
70128
70164
  _log?.(`[AgentVault] state \u2192 ${state}`);
70129
- if (state === "error") reject(new Error("AgentVault channel permanent error"));
70165
+ if (state === "error") {
70166
+ queueMicrotask(() => {
70167
+ if (!terminalStopped) reject(new Error("AgentVault channel permanent error"));
70168
+ });
70169
+ }
70130
70170
  }
70131
70171
  });
70132
70172
  _channels.set(account.accountId, channel);
70133
70173
  channel.on("error", (err) => {
70134
70174
  _log?.(`[AgentVault] channel error (non-fatal): ${String(err)}`);
70135
70175
  });
70176
+ attachLifecycle(channel, {
70177
+ agentName: account.agentName,
70178
+ log: (m22) => _log?.(`[AgentVault] ${m22}`),
70179
+ onTerminal: async () => {
70180
+ terminalStopped = true;
70181
+ try {
70182
+ await channel.stop();
70183
+ } catch {
70184
+ }
70185
+ _channels.delete(account.accountId);
70186
+ }
70187
+ });
70136
70188
  const httpPort = account.httpPort;
70137
70189
  channel.on("ready", () => {
70138
70190
  channel.startHttpServer(httpPort);
@@ -70143,7 +70195,9 @@ var init_openclaw_plugin = __esm({
70143
70195
  _channels.delete(account.accountId);
70144
70196
  resolvePromise();
70145
70197
  });
70146
- channel.start().catch(reject);
70198
+ channel.start().catch((e7) => {
70199
+ if (!terminalStopped) reject(e7);
70200
+ });
70147
70201
  });
70148
70202
  return { stop: async () => {
70149
70203
  } };
@@ -70262,6 +70316,7 @@ var init_openclaw_entry = __esm({
70262
70316
  init_fetch_interceptor();
70263
70317
  init_http_handlers();
70264
70318
  init_openclaw_compat();
70319
+ init_lifecycle();
70265
70320
  init_types();
70266
70321
  isUsingManagedRoutes = false;
70267
70322
  }
@@ -96325,8 +96380,19 @@ var CRED_FILES = ["agentvault.json", "secure-channel.json"];
96325
96380
  function hasPersistedCreds(dataDir) {
96326
96381
  return CRED_FILES.some((f7) => existsSync(join5(dataDir, f7)));
96327
96382
  }
96383
+ function slugify2(name) {
96384
+ return name.toLowerCase().replace(/[^a-z0-9-]/g, "-");
96385
+ }
96386
+ function resolveDataDir(env) {
96387
+ if (env.AV_DATA_DIR) return { dataDir: env.AV_DATA_DIR, source: "explicit" };
96388
+ const base = join5(env.HOME ?? "", ".agentvault", "claude-room-bridge");
96389
+ const perAgent = join5(base, slugify2(env.AV_AGENT_NAME ?? "claude"));
96390
+ if (hasPersistedCreds(perAgent)) return { dataDir: perAgent, source: "per-agent" };
96391
+ if (hasPersistedCreds(base)) return { dataDir: base, source: "legacy" };
96392
+ return { dataDir: perAgent, source: "per-agent" };
96393
+ }
96328
96394
  function loadConfig(env, argv = []) {
96329
- const dataDir = env.AV_DATA_DIR ?? `${env.HOME}/.agentvault/claude-room-bridge`;
96395
+ const { dataDir, source: dataDirSource } = resolveDataDir(env);
96330
96396
  const inviteToken = (argv[0] && !argv[0].startsWith("-") ? argv[0] : "") || env.AV_INVITE_TOKEN || "";
96331
96397
  if (!inviteToken && !hasPersistedCreds(dataDir)) {
96332
96398
  throw new Error(
@@ -96336,6 +96402,7 @@ function loadConfig(env, argv = []) {
96336
96402
  return {
96337
96403
  inviteToken,
96338
96404
  dataDir,
96405
+ dataDirSource,
96339
96406
  apiUrl: env.AV_API_URL ?? "https://api.agentvault.chat",
96340
96407
  // Identity shown to the agent's own session. Set this to the agent's
96341
96408
  // AgentVault name via AV_AGENT_NAME (the native connect command passes it).
@@ -118294,7 +118361,7 @@ __export(util_exports2, {
118294
118361
  required: () => required2,
118295
118362
  safeExtend: () => safeExtend2,
118296
118363
  shallowClone: () => shallowClone2,
118297
- slugify: () => slugify2,
118364
+ slugify: () => slugify3,
118298
118365
  stringifyPrimitive: () => stringifyPrimitive2,
118299
118366
  uint8ArrayToBase64: () => uint8ArrayToBase643,
118300
118367
  uint8ArrayToBase64url: () => uint8ArrayToBase64url2,
@@ -118435,7 +118502,7 @@ function randomString2(length = 10) {
118435
118502
  function esc2(str) {
118436
118503
  return JSON.stringify(str);
118437
118504
  }
118438
- function slugify2(input) {
118505
+ function slugify3(input) {
118439
118506
  return input.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
118440
118507
  }
118441
118508
  var captureStackTrace2 = "captureStackTrace" in Error ? Error.captureStackTrace : (..._args) => {
@@ -128169,7 +128236,7 @@ function _toUpperCase2() {
128169
128236
  }
128170
128237
  // @__NO_SIDE_EFFECTS__
128171
128238
  function _slugify2() {
128172
- return /* @__PURE__ */ _overwrite2((input) => slugify2(input));
128239
+ return /* @__PURE__ */ _overwrite2((input) => slugify3(input));
128173
128240
  }
128174
128241
  // @__NO_SIDE_EFFECTS__
128175
128242
  function _array2(Class3, element, params) {
@@ -131413,11 +131480,11 @@ config2(en_default3());
131413
131480
  function makeRoomSayTool(onSay) {
131414
131481
  return bs(
131415
131482
  "say",
131416
- "Send a message to the current AgentVault room so the other participants see it. Call this ONLY when you have something worth adding. If you have nothing to say, do not call it \u2014 stay silent.",
131417
- { message: external_exports.string().describe("The message to post to the room.") },
131483
+ "Send a message to the current AgentVault conversation \u2014 your owner in a 1:1 direct message, or the other participants in a room. In a room, call this ONLY when you have something worth adding; if you have nothing to say, stay silent.",
131484
+ { message: external_exports.string().describe("The message to send to the current conversation.") },
131418
131485
  async (args) => {
131419
131486
  await onSay(args.message);
131420
- return { content: [{ type: "text", text: "Message sent to the room." }] };
131487
+ return { content: [{ type: "text", text: "Message sent." }] };
131421
131488
  }
131422
131489
  );
131423
131490
  }
@@ -131429,37 +131496,52 @@ var PersistentClaudeSession = class {
131429
131496
  opts;
131430
131497
  waiting = null;
131431
131498
  pending = [];
131432
- push(text) {
131499
+ /** Reply sink bound to the message currently being processed. Set as each
131500
+ * message is handed to the model so the say tool routes to the right place. */
131501
+ activeReply;
131502
+ push(text, reply) {
131433
131503
  const msg = {
131434
131504
  type: "user",
131435
131505
  message: { role: "user", content: text },
131436
131506
  parent_tool_use_id: null,
131437
131507
  session_id: ""
131438
131508
  };
131509
+ const item = { msg, reply };
131439
131510
  if (this.waiting) {
131440
131511
  const w2 = this.waiting;
131441
131512
  this.waiting = null;
131442
- w2(msg);
131513
+ w2(item);
131443
131514
  } else {
131444
- this.pending.push(msg);
131515
+ this.pending.push(item);
131445
131516
  }
131446
131517
  }
131518
+ /** Route a say-tool message to the reply bound to the in-flight message, falling
131519
+ * back to opts.onSay. This is what closes the DM→room leak: the destination is
131520
+ * the one captured for the message being answered, not a live global target. */
131521
+ async deliver(text) {
131522
+ if (this.activeReply) await this.activeReply(text);
131523
+ else if (this.opts.onSay) await this.opts.onSay(text);
131524
+ }
131447
131525
  async *input() {
131448
131526
  while (true) {
131449
131527
  if (this.pending.length > 0) {
131450
- yield this.pending.shift();
131528
+ const item2 = this.pending.shift();
131529
+ this.activeReply = item2.reply;
131530
+ yield item2.msg;
131451
131531
  continue;
131452
131532
  }
131453
- yield await new Promise((resolve2) => {
131533
+ const item = await new Promise((resolve2) => {
131454
131534
  this.waiting = resolve2;
131455
131535
  });
131536
+ this.activeReply = item.reply;
131537
+ yield item.msg;
131456
131538
  }
131457
131539
  }
131458
131540
  async start() {
131459
131541
  const roomServer = _s({
131460
131542
  name: "room",
131461
131543
  version: "0.2.0",
131462
- tools: [makeRoomSayTool(this.opts.onSay)]
131544
+ tools: [makeRoomSayTool((text) => this.deliver(text))]
131463
131545
  });
131464
131546
  const sdkOptions = {
131465
131547
  model: this.opts.model,
@@ -131505,7 +131587,47 @@ var PersistentClaudeSession = class {
131505
131587
  };
131506
131588
 
131507
131589
  // src/bridge.ts
131508
- function attachLifecycle(channel, opts = {}) {
131590
+ var ActiveTarget = class {
131591
+ target = null;
131592
+ setRoom(roomId) {
131593
+ this.target = { kind: "room", roomId };
131594
+ }
131595
+ setDm() {
131596
+ this.target = { kind: "dm" };
131597
+ }
131598
+ get current() {
131599
+ return this.target;
131600
+ }
131601
+ async reply(channel, text) {
131602
+ const t7 = this.target;
131603
+ if (!t7) return;
131604
+ if (t7.kind === "room") await channel.sendToRoom(t7.roomId, text);
131605
+ else await channel.send(text);
131606
+ }
131607
+ /**
131608
+ * Capture the CURRENT target into an immutable reply closure. This is the leak
131609
+ * fix: a reply built here routes to where its inbound message came from, even if
131610
+ * a later inbound flips the live `current` target. The bridge captures one of
131611
+ * these per inbound (at push time) and hands it to the session, so a room message
131612
+ * arriving mid-compose can never redirect a private 1:1 reply into the room.
131613
+ */
131614
+ snapshotReply(channel, log = () => {
131615
+ }) {
131616
+ const t7 = this.target;
131617
+ const where = !t7 ? "nowhere" : t7.kind === "room" ? `room ${t7.roomId.slice(0, 8)}` : "1:1 owner";
131618
+ return async (text) => {
131619
+ if (!t7) return;
131620
+ try {
131621
+ if (t7.kind === "room") await channel.sendToRoom(t7.roomId, text);
131622
+ else await channel.send(text);
131623
+ log(`said to ${where}`);
131624
+ } catch (err) {
131625
+ log(`send failed to ${where}: ${err instanceof Error ? err.message : String(err)}`);
131626
+ }
131627
+ };
131628
+ }
131629
+ };
131630
+ function attachLifecycle2(channel, opts = {}) {
131509
131631
  const log = opts.log ?? (() => {
131510
131632
  });
131511
131633
  const onTerminal = opts.onTerminal ?? (() => process.exit(1));
@@ -131525,7 +131647,7 @@ function attachLifecycle(channel, opts = {}) {
131525
131647
  if (/reconnect loop detected/i.test(msg)) terminal("reconnect_loop");
131526
131648
  });
131527
131649
  }
131528
- function wireBridge(channel, session, opts = {}) {
131650
+ function wireBridge(channel, session, target, opts = {}) {
131529
131651
  const log = opts.log ?? (() => {
131530
131652
  });
131531
131653
  channel.on("error", (err) => {
@@ -131535,8 +131657,14 @@ function wireBridge(channel, session, opts = {}) {
131535
131657
  channel.on("room_message", (e7) => {
131536
131658
  if (opts.roomFilter && e7.roomId !== opts.roomFilter) return;
131537
131659
  log(`inbound from ${e7.senderName} in ${e7.roomId.slice(0, 8)}`);
131538
- session.onActiveRoom(e7.roomId);
131539
- session.push(`[${e7.senderName}]: ${e7.plaintext}`);
131660
+ target.setRoom(e7.roomId);
131661
+ session.push(`[${e7.senderName}]: ${e7.plaintext}`, target.snapshotReply(channel, log));
131662
+ });
131663
+ channel.on("message", (text, metadata) => {
131664
+ if (metadata?.roomId) return;
131665
+ log("inbound 1:1 DM from owner");
131666
+ target.setDm();
131667
+ session.push(text, target.snapshotReply(channel, log));
131540
131668
  });
131541
131669
  }
131542
131670
 
@@ -131549,7 +131677,9 @@ async function main() {
131549
131677
  "[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"
131550
131678
  );
131551
131679
  }
131552
- let activeRoomId = cfg.roomFilter ?? "";
131680
+ console.error(`[bridge] data dir: ${cfg.dataDir} (${cfg.dataDirSource})`);
131681
+ const target = new ActiveTarget();
131682
+ if (cfg.roomFilter) target.setRoom(cfg.roomFilter);
131553
131683
  const channel = new SecureChannel({
131554
131684
  inviteToken: cfg.inviteToken,
131555
131685
  dataDir: cfg.dataDir,
@@ -131559,28 +131689,22 @@ async function main() {
131559
131689
  });
131560
131690
  const session = new PersistentClaudeSession({
131561
131691
  model: cfg.model,
131562
- systemPrompt: cfg.systemPrompt ?? `You are ${cfg.agentName}, an AI agent collaborating with other agents in an AgentVault room. That is your identity in this room \u2014 introduce yourself by that name and do not claim to be any other agent. You see every room message. To speak, call the room_say tool \u2014 and only when you have something worth adding. If you have nothing to contribute, stay silent (do not call room_say). Keep messages concise.`,
131563
- // Claude speaks by calling room_sayposted to the active room.
131564
- onSay: async (text) => {
131565
- if (!activeRoomId) return;
131566
- try {
131567
- await channel.sendToRoom(activeRoomId, text);
131568
- console.error(`[bridge] said in ${activeRoomId.slice(0, 8)}`);
131569
- } catch (err) {
131570
- console.error(`[bridge] send failed to ${activeRoomId.slice(0, 8)}:`, err);
131571
- }
131572
- },
131692
+ systemPrompt: cfg.systemPrompt ?? `You are ${cfg.agentName}, an AI agent on AgentVault. You talk with your owner in 1:1 direct messages and collaborate with other agents in shared rooms. That is your identity \u2014 introduce yourself by that name and do not claim to be any other agent. To speak, call the say tool. In a 1:1 with your owner, reply to what they say. In a room you see every message \u2014 call say only when you have something worth adding, and otherwise stay silent. Keep messages concise.`,
131693
+ // Claude speaks by calling the say tool the session routes it to the reply
131694
+ // bound to the message being answered (wireBridge captures that per inbound via
131695
+ // ActiveTarget.snapshotReply, which also logs the "said to …" line). This
131696
+ // per-message binding is what prevents a private 1:1 reply from leaking into a
131697
+ // room when room traffic arrives mid-compose.
131573
131698
  // Assistant reasoning that wasn't sent — log a short trace only.
131574
131699
  onObserve: (text) => console.error(`[bridge] observed (${text.length} chars, not sent)`)
131575
131700
  });
131576
131701
  wireBridge(
131577
131702
  channel,
131578
- { push: (t7) => session.push(t7), onActiveRoom: (r7) => {
131579
- activeRoomId = r7;
131580
- } },
131703
+ { push: (t7, reply) => session.push(t7, reply) },
131704
+ target,
131581
131705
  { roomFilter: cfg.roomFilter, log: (m6) => console.error("[bridge] " + m6) }
131582
131706
  );
131583
- attachLifecycle(channel, {
131707
+ attachLifecycle2(channel, {
131584
131708
  log: (m6) => console.error("[bridge] " + m6)
131585
131709
  });
131586
131710
  channel.on("state", (s10) => console.error(`[bridge] channel state: ${JSON.stringify(s10)}`));
package/dist/session.d.ts CHANGED
@@ -45,9 +45,11 @@ export type QueryFn = (args: {
45
45
  type: string;
46
46
  [k: string]: unknown;
47
47
  }>;
48
+ type ReplySink = (text: string) => void | Promise<void>;
48
49
  export interface SessionOpts {
49
- /** Called when Claude chooses to speak (via the room_say tool). */
50
- onSay: (text: string) => void | Promise<void>;
50
+ /** Fallback reply sink when a message carries no per-message reply (e.g. a
51
+ * proactive say). Per-message replies passed to push() take precedence. */
52
+ onSay?: ReplySink;
51
53
  /** Called with assistant text that is NOT sent to the room (for logging). */
52
54
  onObserve?: (text: string) => void;
53
55
  model?: string;
@@ -58,9 +60,17 @@ export declare class PersistentClaudeSession {
58
60
  private opts;
59
61
  private waiting;
60
62
  private pending;
63
+ /** Reply sink bound to the message currently being processed. Set as each
64
+ * message is handed to the model so the say tool routes to the right place. */
65
+ private activeReply?;
61
66
  constructor(opts: SessionOpts);
62
- push(text: string): void;
67
+ push(text: string, reply?: ReplySink): void;
68
+ /** Route a say-tool message to the reply bound to the in-flight message, falling
69
+ * back to opts.onSay. This is what closes the DM→room leak: the destination is
70
+ * the one captured for the message being answered, not a live global target. */
71
+ deliver(text: string): Promise<void>;
63
72
  private input;
64
73
  start(): Promise<void>;
65
74
  }
75
+ export {};
66
76
  //# sourceMappingURL=session.d.ts.map
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@agentvault/claude-bridge",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
- "description": "AgentVault Claude Room Bridge — daemon for bridging AI agents into secure E2E-encrypted AgentVault rooms.",
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",
7
7
  "types": "dist/index.d.ts",
8
8
  "bin": {