@agentvault/claude-bridge 0.3.2 → 0.3.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/bridge.d.ts CHANGED
@@ -35,8 +35,12 @@ export declare class RoomHushState {
35
35
  }
36
36
  export interface RoomSession {
37
37
  /** `reply` is the immutable reply sink captured for THIS message (see
38
- * ActiveTarget.snapshotReply) — the session invokes it when Claude answers. */
39
- push(text: string, reply?: (text: string) => Promise<void>): void;
38
+ * ActiveTarget.snapshotReply) — the session invokes it when Claude answers.
39
+ * `opts.autoReplyOnText` (set for 1:1 DMs) makes the session fall back to
40
+ * sending plain assistant text when the model never calls the say tool (#416). */
41
+ push(text: string, reply?: (text: string) => Promise<void>, opts?: {
42
+ autoReplyOnText?: boolean;
43
+ }): void;
40
44
  }
41
45
  /** Where a reply should go: a room (sendToRoom) or a 1:1 DM (send). */
42
46
  export type BridgeTarget = {
package/dist/index.js CHANGED
@@ -131554,14 +131554,25 @@ var PersistentClaudeSession = class {
131554
131554
  /** Reply sink bound to the message currently being processed. Set as each
131555
131555
  * message is handed to the model so the say tool routes to the right place. */
131556
131556
  activeReply;
131557
- push(text, reply) {
131557
+ /** Per-turn state for the #416 DM auto-reply fallback. */
131558
+ currentAutoReply = false;
131559
+ saidThisTurn = false;
131560
+ turnText = "";
131561
+ /**
131562
+ * Queue an inbound message for the model.
131563
+ * @param opts.autoReplyOnText — for 1:1 DMs (where the owner always expects a
131564
+ * reply): if the turn produces assistant text but never calls the say tool,
131565
+ * send that text as the reply instead of silently dropping it (#416). Rooms
131566
+ * omit this so the agent can still choose silence.
131567
+ */
131568
+ push(text, reply, opts) {
131558
131569
  const msg = {
131559
131570
  type: "user",
131560
131571
  message: { role: "user", content: text },
131561
131572
  parent_tool_use_id: null,
131562
131573
  session_id: ""
131563
131574
  };
131564
- const item = { msg, reply };
131575
+ const item = { msg, reply, autoReplyOnText: opts?.autoReplyOnText };
131565
131576
  if (this.waiting) {
131566
131577
  const w2 = this.waiting;
131567
131578
  this.waiting = null;
@@ -131574,21 +131585,19 @@ var PersistentClaudeSession = class {
131574
131585
  * back to opts.onSay. This is what closes the DM→room leak: the destination is
131575
131586
  * the one captured for the message being answered, not a live global target. */
131576
131587
  async deliver(text) {
131588
+ this.saidThisTurn = true;
131577
131589
  if (this.activeReply) await this.activeReply(text);
131578
131590
  else if (this.opts.onSay) await this.opts.onSay(text);
131579
131591
  }
131580
131592
  async *input() {
131581
131593
  while (true) {
131582
- if (this.pending.length > 0) {
131583
- const item2 = this.pending.shift();
131584
- this.activeReply = item2.reply;
131585
- yield item2.msg;
131586
- continue;
131587
- }
131588
- const item = await new Promise((resolve2) => {
131594
+ const item = this.pending.length > 0 ? this.pending.shift() : await new Promise((resolve2) => {
131589
131595
  this.waiting = resolve2;
131590
131596
  });
131591
131597
  this.activeReply = item.reply;
131598
+ this.currentAutoReply = item.autoReplyOnText ?? false;
131599
+ this.saidThisTurn = false;
131600
+ this.turnText = "";
131592
131601
  yield item.msg;
131593
131602
  }
131594
131603
  }
@@ -131635,7 +131644,15 @@ var PersistentClaudeSession = class {
131635
131644
  if (m6.type === "assistant") {
131636
131645
  const blocks = m6.message?.content ?? [];
131637
131646
  const text = blocks.filter((b5) => b5.type === "text").map((b5) => b5.text ?? "").join("");
131638
- if (text.trim()) this.opts.onObserve?.(text);
131647
+ if (text.trim()) {
131648
+ this.turnText += text;
131649
+ this.opts.onObserve?.(text);
131650
+ }
131651
+ } else if (m6.type === "result") {
131652
+ const reply = this.activeReply;
131653
+ if (this.currentAutoReply && !this.saidThisTurn && this.turnText.trim() && reply) {
131654
+ void reply(this.turnText);
131655
+ }
131639
131656
  }
131640
131657
  }
131641
131658
  }
@@ -131741,13 +131758,15 @@ function wireBridge(channel, session, target, opts = {}) {
131741
131758
  }
131742
131759
  log(`inbound from ${e7.senderName} in ${e7.roomId.slice(0, 8)}`);
131743
131760
  target.setRoom(e7.roomId);
131744
- session.push(`[${e7.senderName}]: ${e7.plaintext}`, target.snapshotReply(channel, log));
131761
+ session.push(`[${e7.senderName}]: ${e7.plaintext}`, target.snapshotReply(channel, log), {
131762
+ autoReplyOnText: false
131763
+ });
131745
131764
  });
131746
131765
  channel.on("message", (text, metadata) => {
131747
131766
  if (metadata?.roomId) return;
131748
131767
  log("inbound 1:1 DM from owner");
131749
131768
  target.setDm();
131750
- session.push(text, target.snapshotReply(channel, log));
131769
+ session.push(text, target.snapshotReply(channel, log), { autoReplyOnText: true });
131751
131770
  });
131752
131771
  }
131753
131772
 
@@ -131783,7 +131802,7 @@ async function main() {
131783
131802
  });
131784
131803
  wireBridge(
131785
131804
  channel,
131786
- { push: (t7, reply) => session.push(t7, reply) },
131805
+ { push: (t7, reply, opts) => session.push(t7, reply, opts) },
131787
131806
  target,
131788
131807
  { roomFilter: cfg.roomFilter, log: (m6) => console.error("[bridge] " + m6) }
131789
131808
  );
package/dist/session.d.ts CHANGED
@@ -63,8 +63,21 @@ export declare class PersistentClaudeSession {
63
63
  /** Reply sink bound to the message currently being processed. Set as each
64
64
  * message is handed to the model so the say tool routes to the right place. */
65
65
  private activeReply?;
66
+ /** Per-turn state for the #416 DM auto-reply fallback. */
67
+ private currentAutoReply;
68
+ private saidThisTurn;
69
+ private turnText;
66
70
  constructor(opts: SessionOpts);
67
- push(text: string, reply?: ReplySink): void;
71
+ /**
72
+ * Queue an inbound message for the model.
73
+ * @param opts.autoReplyOnText — for 1:1 DMs (where the owner always expects a
74
+ * reply): if the turn produces assistant text but never calls the say tool,
75
+ * send that text as the reply instead of silently dropping it (#416). Rooms
76
+ * omit this so the agent can still choose silence.
77
+ */
78
+ push(text: string, reply?: ReplySink, opts?: {
79
+ autoReplyOnText?: boolean;
80
+ }): void;
68
81
  /** Route a say-tool message to the reply bound to the in-flight message, falling
69
82
  * back to opts.onSay. This is what closes the DM→room leak: the destination is
70
83
  * the one captured for the message being answered, not a live global target. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentvault/claude-bridge",
3
- "version": "0.3.2",
3
+ "version": "0.3.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",