@badgerclaw/connect 1.2.2 → 1.2.4

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/SETUP.md CHANGED
@@ -97,8 +97,9 @@ Use these commands in any BadgerClaw room or DM where your bot is present.
97
97
  | Command | Description |
98
98
  |---------|-------------|
99
99
  | `/bot help` | Show all available commands |
100
- | `/bot talk on` | **Enable auto-reply** — bot responds to every message without needing an @mention. Great for 1-on-1 rooms. |
101
- | `/bot talk off` | **Disable auto-reply** bot only responds when @mentioned. Use in busy group rooms. |
100
+ | `/bot talk on` | **Enable auto-reply** — bot responds to every message without needing an @mention. If multiple bots are in the room, you'll be asked to specify which one. |
101
+ | `/bot talk on @botname` | **Enable auto-reply for a specific bot** use when multiple bots are in the room. |
102
+ | `/bot talk off` | **Disable auto-reply for ALL bots** — kill switch. All bots in the room go quiet and only respond when @mentioned. |
102
103
  | `/bot add <name>` | Invite a bot to the current room (e.g. `/bot add jarvis`) |
103
104
  | `/bot list` | List all your bots and their connection status |
104
105
 
@@ -106,6 +107,7 @@ Use these commands in any BadgerClaw room or DM where your bot is present.
106
107
 
107
108
  - **New installs:** Auto-reply is **ON by default** in all rooms
108
109
  - **Per-room toggle:** `/bot talk on` and `/bot talk off` override the default for that specific room
110
+ - **Multi-bot rooms:** If multiple bots are present, `/bot talk on` asks which bot to enable. `/bot talk off` silences all bots at once.
109
111
  - **@mentions always work:** Even with auto-reply off, the bot responds when @mentioned
110
112
 
111
113
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@badgerclaw/connect",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
4
  "description": "BadgerClaw channel plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "dependencies": {
@@ -80,21 +80,21 @@ export async function handleBotCommand(params: {
80
80
  " Works in: Any room or DM",
81
81
  "",
82
82
  "/bot talk on",
83
- " Enable auto-reply mode. The bot will respond to every",
84
- " message in this room without needing an @mention.",
85
- " Perfect for 1-on-1 rooms with your AI assistant.",
83
+ " Enable auto-reply for this bot. It will respond to",
84
+ " every message without needing an @mention.",
85
+ " If multiple bots are in the room, specify which one:",
86
+ " /bot talk on @botname",
86
87
  " Works in: Any room or DM",
87
88
  "",
88
89
  "/bot talk off",
89
- " Disable auto-reply mode. The bot will only respond",
90
- " when @mentioned. Use this in busy group rooms where",
91
- " you don't want the bot responding to everything.",
90
+ " Disable auto-reply for ALL bots in this room.",
91
+ " Bots will only respond when @mentioned.",
92
92
  " Works in: Any room or DM",
93
93
  "",
94
94
  "/bot add <botname>",
95
- " Invite a bot to the current room. The bot username",
96
- " will be @<botname>_bot on this server.",
95
+ " Invite a bot to the current room.",
97
96
  " Example: /bot add jarvis → invites @jarvis_bot",
97
+ " Also accepts: /bot add @jarvis_bot",
98
98
  " Works in: Any room",
99
99
  "",
100
100
  "━━━ BotBadger DM Only ━━━",
@@ -116,9 +116,8 @@ export async function handleBotCommand(params: {
116
116
  " Works in: Direct message with BotBadger only",
117
117
  "",
118
118
  "/bot list",
119
- " List all your bots and their connection status.",
120
- " 🟢 Connected bot is online and responding",
121
- " 🔴 Not connected — bot needs pairing",
119
+ " List all bots in this room.",
120
+ " 🟢 = this bot 🤖 = other bots",
122
121
  " Works in: Any room or DM",
123
122
  ].join("\n"),
124
123
  });
@@ -126,20 +125,70 @@ export async function handleBotCommand(params: {
126
125
  }
127
126
 
128
127
  case "talk": {
128
+ // Resolve bot members in the room
129
+ const botSuffix = "_bot:badger.signout.io";
130
+ let roomBots: string[] = [];
131
+ try {
132
+ const members = await client.getJoinedRoomMembers(roomId);
133
+ roomBots = members.filter((m: string) => m.includes(botSuffix) && m !== selfUserId);
134
+ } catch {
135
+ // If we can't list members, proceed as single-bot
136
+ }
137
+ const multipleBots = roomBots.length > 0; // other bots besides self
138
+
139
+ // Check if a specific bot was mentioned: /bot talk on @botname
140
+ const mentionArg = parts.slice(3).join(" ").trim();
141
+ const mentionedBot = mentionArg
142
+ ? mentionArg.startsWith("@") ? mentionArg : `@${mentionArg}`
143
+ : null;
144
+
145
+ // If a bot was mentioned and it's not us, ignore the command (let that bot handle it)
146
+ if (mentionedBot) {
147
+ const mentionLower = mentionedBot.toLowerCase();
148
+ const selfLower = selfUserId.toLowerCase();
149
+ // Match full userId or just the localpart
150
+ const selfLocalpart = selfLower.split(":")[0]; // @test_bot
151
+ if (!selfLower.startsWith(mentionLower) && !mentionLower.startsWith(selfLocalpart)) {
152
+ // Not addressed to us — ignore silently
153
+ return false;
154
+ }
155
+ }
156
+
129
157
  if (arg === "on") {
158
+ // Multiple bots + no specific mention → ask which bot
159
+ if (multipleBots && !mentionedBot) {
160
+ const allBots = [selfUserId, ...roomBots];
161
+ const botList = allBots
162
+ .map((b, i) => ` ${i + 1}. ${b.split(":")[0]}`)
163
+ .join("\n");
164
+ await client.sendMessage(roomId, {
165
+ msgtype: "m.text",
166
+ body: [
167
+ "🦡 Multiple bots in this room:",
168
+ "",
169
+ botList,
170
+ "",
171
+ "Specify which bot: /bot talk on @botname",
172
+ ].join("\n"),
173
+ });
174
+ return true;
175
+ }
176
+
130
177
  const config = loadRoomConfig();
131
- // Store with lowercase key to match OpenClaw's lowercased groupId
132
178
  const normalizedRoomId = roomId.toLowerCase();
133
179
  config[normalizedRoomId] = { ...config[normalizedRoomId], autoReply: true };
134
180
  saveRoomConfig(config);
135
181
  setGroupActivation(roomId, "always");
182
+ const selfName = selfUserId.split(":")[0];
136
183
  await client.sendMessage(roomId, {
137
184
  msgtype: "m.text",
138
- body: "✅ Auto-reply enabled — I'll respond to every message in this room.",
185
+ body: `✅ Auto-reply enabled for ${selfName} — I'll respond to every message in this room.`,
139
186
  });
140
187
  return true;
141
188
  }
189
+
142
190
  if (arg === "off") {
191
+ // Off is a kill switch — disable auto-reply regardless of how many bots
143
192
  const config = loadRoomConfig();
144
193
  const normalizedRoomId = roomId.toLowerCase();
145
194
  config[normalizedRoomId] = { ...config[normalizedRoomId], autoReply: false };
@@ -147,43 +196,88 @@ export async function handleBotCommand(params: {
147
196
  setGroupActivation(roomId, "mention");
148
197
  await client.sendMessage(roomId, {
149
198
  msgtype: "m.text",
150
- body: "✅ Auto-reply disabled — I'll only respond when @mentioned.",
199
+ body: "✅ Auto-reply disabled for all bots bots will only respond when @mentioned.",
151
200
  });
152
201
  return true;
153
202
  }
203
+
154
204
  await client.sendMessage(roomId, {
155
205
  msgtype: "m.text",
156
- body: "Usage: /bot talk on or /bot talk off",
206
+ body: "Usage: /bot talk on [@botname] or /bot talk off",
157
207
  });
158
208
  return true;
159
209
  }
160
210
 
161
211
  case "add": {
162
- const botName = arg;
163
- if (!botName) {
212
+ let rawName = parts.slice(2).join(" ").trim();
213
+ if (!rawName) {
164
214
  await client.sendMessage(roomId, {
165
215
  msgtype: "m.text",
166
- body: "Usage: /bot add <botname>",
216
+ body: "Usage: /bot add <botname>\nExample: /bot add jarvis or /bot add @jarvis_bot",
167
217
  });
168
218
  return true;
169
219
  }
170
- const botUserId = `@${botName}_bot:badger.signout.io`;
220
+ // Normalize: strip @, strip _bot suffix, strip :server
221
+ rawName = rawName.replace(/^@/, "").split(":")[0].replace(/_bot$/i, "").toLowerCase();
222
+ const addBotUserId = `@${rawName}_bot:badger.signout.io`;
223
+ const addBotDisplay = `@${rawName}_bot`;
171
224
  try {
172
- await client.inviteUser(botUserId, roomId);
225
+ await client.inviteUser(addBotUserId, roomId);
173
226
  await client.sendMessage(roomId, {
174
227
  msgtype: "m.text",
175
- body: `✅ Invited ${botUserId} to this room.`,
228
+ body: `✅ Invited ${addBotDisplay} to this room.`,
176
229
  });
177
230
  } catch (err) {
178
231
  const msg = err instanceof Error ? err.message : String(err);
232
+ // Strip homeserver from error messages too
233
+ const cleanMsg = msg.replace(/:badger\.signout\.io/g, "");
179
234
  await client.sendMessage(roomId, {
180
235
  msgtype: "m.text",
181
- body: `❌ Failed to invite ${botUserId}: ${msg}`,
236
+ body: `❌ Failed to invite ${addBotDisplay}: ${cleanMsg}`,
182
237
  });
183
238
  }
184
239
  return true;
185
240
  }
186
241
 
242
+ case "list": {
243
+ const botSuffix2 = "_bot:badger.signout.io";
244
+ let botsInRoom: string[] = [];
245
+ try {
246
+ const members = await client.getJoinedRoomMembers(roomId);
247
+ botsInRoom = members.filter((m: string) => m.includes(botSuffix2));
248
+ } catch {
249
+ botsInRoom = [selfUserId]; // At minimum, we know we're here
250
+ }
251
+
252
+ if (botsInRoom.length === 0) {
253
+ await client.sendMessage(roomId, {
254
+ msgtype: "m.text",
255
+ body: "No bots in this room.",
256
+ });
257
+ return true;
258
+ }
259
+
260
+ const botList = botsInRoom
261
+ .map((b) => {
262
+ const displayName = b.split(":")[0];
263
+ const isMe = b === selfUserId;
264
+ return ` ${isMe ? "🟢" : "🤖"} ${displayName}${isMe ? " (me)" : ""}`;
265
+ })
266
+ .join("\n");
267
+
268
+ await client.sendMessage(roomId, {
269
+ msgtype: "m.text",
270
+ body: [
271
+ "🦡 Bots in this room:",
272
+ "",
273
+ botList,
274
+ "",
275
+ `${botsInRoom.length} bot${botsInRoom.length === 1 ? "" : "s"} total`,
276
+ ].join("\n"),
277
+ });
278
+ return true;
279
+ }
280
+
187
281
  default: {
188
282
  // Unknown /bot command — show help hint
189
283
  await client.sendMessage(roomId, {