@badgerclaw/connect 1.1.3 → 1.2.1
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 +162 -40
- package/package.json +1 -1
- package/src/group-mentions.ts +42 -0
- package/src/matrix/monitor/auto-join.ts +30 -0
- package/src/matrix/monitor/bot-commands.ts +200 -0
- package/src/matrix/monitor/handler.ts +43 -5
- package/src/onboarding.ts +11 -0
package/SETUP.md
CHANGED
|
@@ -6,7 +6,6 @@ Connect your OpenClaw AI agent to BadgerClaw encrypted chat rooms.
|
|
|
6
6
|
|
|
7
7
|
- **BadgerClaw iOS app** installed and logged in
|
|
8
8
|
- **OpenClaw** installed on your machine (`npm install -g openclaw`)
|
|
9
|
-
- Both on the same network (not required, just for initial setup)
|
|
10
9
|
|
|
11
10
|
---
|
|
12
11
|
|
|
@@ -14,28 +13,24 @@ Connect your OpenClaw AI agent to BadgerClaw encrypted chat rooms.
|
|
|
14
13
|
|
|
15
14
|
Open the BadgerClaw app on your phone.
|
|
16
15
|
|
|
17
|
-
1. Open any room or DM
|
|
18
|
-
2. Type: `/bot
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
1. Open any room or DM with BotBadger
|
|
17
|
+
2. Type: `/bot new`
|
|
18
|
+
3. Follow the guided flow to set a bot name and username
|
|
19
|
+
4. BotBadger responds with a pairing code:
|
|
21
20
|
|
|
22
21
|
```
|
|
23
22
|
🦡 Bot created!
|
|
24
23
|
|
|
25
24
|
Name: jarvis
|
|
26
|
-
ID: @abc123_jarvis_bot:badger.signout.io
|
|
27
|
-
|
|
28
25
|
Connect to OpenClaw:
|
|
29
|
-
openclaw plugins install @badgerclaw/connect
|
|
30
26
|
openclaw badgerclaw connect BCK-A8F3-X9K2
|
|
31
27
|
|
|
32
28
|
Code expires in 24 hours.
|
|
33
|
-
Run /bot pair jarvis to generate a new code.
|
|
34
29
|
```
|
|
35
30
|
|
|
36
|
-
|
|
31
|
+
5. **Copy the pairing code** (BCK-XXXX-XXXX)
|
|
37
32
|
|
|
38
|
-
> **Need a new code?** Type `/bot pair
|
|
33
|
+
> **Need a new code?** Type `/bot pair <name>` to regenerate.
|
|
39
34
|
|
|
40
35
|
---
|
|
41
36
|
|
|
@@ -44,38 +39,42 @@ Run /bot pair jarvis to generate a new code.
|
|
|
44
39
|
On your machine (Mac, PC, Linux, Pi), run:
|
|
45
40
|
|
|
46
41
|
```bash
|
|
47
|
-
|
|
42
|
+
openclaw plugins install @badgerclaw/connect
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Or install from source:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
48
|
git clone https://github.com/darkstaar4/badgerclaw-plugin.git
|
|
49
49
|
openclaw plugins install ./badgerclaw-plugin
|
|
50
|
-
|
|
51
|
-
# From npm (coming soon)
|
|
52
|
-
# openclaw plugins install @badgerclaw/connect
|
|
53
50
|
```
|
|
54
51
|
|
|
55
52
|
---
|
|
56
53
|
|
|
57
54
|
## Step 3: Connect with Pairing Code
|
|
58
55
|
|
|
59
|
-
|
|
56
|
+
```bash
|
|
57
|
+
openclaw badgerclaw connect BCK-XXXX-XXXX
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Or run the full configure wizard:
|
|
60
61
|
|
|
61
62
|
```bash
|
|
62
63
|
openclaw configure
|
|
63
64
|
```
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
- Configures auto-join for room invites
|
|
72
|
-
4. Done! Your agent is connected.
|
|
66
|
+
The plugin automatically:
|
|
67
|
+
- Validates the code with BadgerClaw servers
|
|
68
|
+
- Retrieves your bot's credentials
|
|
69
|
+
- Enables end-to-end encryption
|
|
70
|
+
- Configures auto-join for room invites
|
|
71
|
+
- Sets up auto-reply in group chats
|
|
73
72
|
|
|
74
73
|
---
|
|
75
74
|
|
|
76
75
|
## Step 4: Start Chatting
|
|
77
76
|
|
|
78
|
-
Restart the OpenClaw gateway
|
|
77
|
+
Restart the OpenClaw gateway:
|
|
79
78
|
|
|
80
79
|
```bash
|
|
81
80
|
openclaw gateway restart
|
|
@@ -83,30 +82,153 @@ openclaw gateway restart
|
|
|
83
82
|
|
|
84
83
|
Now in the BadgerClaw app:
|
|
85
84
|
|
|
86
|
-
1. **DM your bot** — find it in your contacts
|
|
87
|
-
2. **Add to a room** — type `/bot add
|
|
85
|
+
1. **DM your bot** — find it in your contacts and send a message
|
|
86
|
+
2. **Add to a room** — type `/bot add <name>` in any room
|
|
88
87
|
3. **Add to a Space** — add the bot to any room inside a Space
|
|
89
88
|
|
|
90
|
-
Your OpenClaw agent will receive messages and respond
|
|
89
|
+
Your OpenClaw agent will receive messages and respond — fully end-to-end encrypted.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Bot Commands
|
|
94
|
+
|
|
95
|
+
Use these commands in any BadgerClaw room or DM where your bot is present.
|
|
96
|
+
|
|
97
|
+
| Command | Description |
|
|
98
|
+
|---------|-------------|
|
|
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. |
|
|
102
|
+
| `/bot add <name>` | Invite a bot to the current room (e.g. `/bot add jarvis`) |
|
|
103
|
+
| `/bot list` | List all your bots and their connection status |
|
|
104
|
+
|
|
105
|
+
### How Auto-Reply Works
|
|
106
|
+
|
|
107
|
+
- **New installs:** Auto-reply is **ON by default** in all rooms
|
|
108
|
+
- **Per-room toggle:** `/bot talk on` and `/bot talk off` override the default for that specific room
|
|
109
|
+
- **@mentions always work:** Even with auto-reply off, the bot responds when @mentioned
|
|
91
110
|
|
|
92
111
|
---
|
|
93
112
|
|
|
94
|
-
##
|
|
113
|
+
## Group Chat Configuration
|
|
114
|
+
|
|
115
|
+
### Default Behavior
|
|
116
|
+
|
|
117
|
+
When you install BadgerClaw with a fresh pairing code, the plugin automatically configures:
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
{
|
|
121
|
+
"groupPolicy": "open",
|
|
122
|
+
"groups": {
|
|
123
|
+
"*": {
|
|
124
|
+
"autoReply": true,
|
|
125
|
+
"enabled": true,
|
|
126
|
+
"systemPrompt": "You are a helpful AI assistant..."
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
This means:
|
|
133
|
+
- Bot joins any room it's invited to
|
|
134
|
+
- Bot responds to all messages (no @mention required)
|
|
135
|
+
- Bot uses a helpful assistant personality in groups
|
|
136
|
+
|
|
137
|
+
### Custom Group Personality
|
|
138
|
+
|
|
139
|
+
By default, the bot uses a generic helpful assistant prompt in groups — **not** your OpenClaw workspace personality (SOUL.md, IDENTITY.md). This prevents workspace personas from causing the bot to stay silent in groups.
|
|
140
|
+
|
|
141
|
+
To customize the group personality, edit your OpenClaw config:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
openclaw configure
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Or manually edit `~/.openclaw/openclaw.json`:
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"channels": {
|
|
152
|
+
"badgerclaw": {
|
|
153
|
+
"groups": {
|
|
154
|
+
"*": {
|
|
155
|
+
"autoReply": true,
|
|
156
|
+
"systemPrompt": "You are Captain Bot, a pirate AI. Answer in pirate speak."
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Per-Room Configuration
|
|
165
|
+
|
|
166
|
+
You can configure specific rooms differently:
|
|
167
|
+
|
|
168
|
+
```json
|
|
169
|
+
{
|
|
170
|
+
"channels": {
|
|
171
|
+
"badgerclaw": {
|
|
172
|
+
"groups": {
|
|
173
|
+
"*": { "autoReply": true },
|
|
174
|
+
"!specificRoomId:server": {
|
|
175
|
+
"autoReply": false,
|
|
176
|
+
"systemPrompt": "Custom prompt for this room only"
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## FAQ
|
|
187
|
+
|
|
188
|
+
### Bot doesn't respond in group chats
|
|
189
|
+
|
|
190
|
+
1. Send `/bot talk on` in the room to enable auto-reply
|
|
191
|
+
2. Make sure the bot is a member of the room (`/bot add <name>`)
|
|
192
|
+
3. Check OpenClaw is running: `openclaw status`
|
|
193
|
+
|
|
194
|
+
### Bot shows typing indicator but never replies
|
|
195
|
+
|
|
196
|
+
This usually means the bot's system prompt is telling it to stay silent. Check:
|
|
197
|
+
|
|
198
|
+
1. Your `groups` config has `autoReply: true`
|
|
199
|
+
2. The `systemPrompt` in groups config tells the agent to respond (not stay silent)
|
|
200
|
+
3. If you have a SOUL.md with "stay silent in groups" — the groups `systemPrompt` overrides this, but only if it's set
|
|
201
|
+
|
|
202
|
+
Fix: Run `openclaw configure` and reconfigure BadgerClaw, or manually add the groups config shown above.
|
|
203
|
+
|
|
204
|
+
### `/bot talk on` doesn't work
|
|
205
|
+
|
|
206
|
+
After changing bot commands, you may need to restart the gateway:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
openclaw gateway restart
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Bot responds with wrong personality
|
|
213
|
+
|
|
214
|
+
The bot uses your **OpenClaw workspace personality** for DMs (SOUL.md, IDENTITY.md) but uses the **groups systemPrompt** for group chats. To change the group personality, edit `channels.badgerclaw.groups.*.systemPrompt` in your config.
|
|
215
|
+
|
|
216
|
+
### Bot keeps saying "NO_REPLY" or stays silent
|
|
217
|
+
|
|
218
|
+
Your workspace files (SOUL.md) may instruct the agent to stay silent in groups. The groups `systemPrompt` should override this. If it doesn't:
|
|
219
|
+
|
|
220
|
+
1. Make sure `groups.*.systemPrompt` is set in your config
|
|
221
|
+
2. The prompt should explicitly say to respond to messages
|
|
222
|
+
3. Restart the gateway after config changes
|
|
95
223
|
|
|
96
224
|
### "Invalid pairing code"
|
|
97
|
-
- Code may have expired (24h limit). Run `/bot pair <name>` in the app to get a new one.
|
|
98
|
-
- Make sure you're entering the code exactly as shown (BCK-XXXX-XXXX).
|
|
99
225
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
2. Check gateway logs: `openclaw logs --follow`
|
|
103
|
-
3. Verify the BadgerClaw channel is configured: `openclaw channels status`
|
|
226
|
+
- Code expires after 24 hours. Run `/bot pair <name>` in the app for a new one.
|
|
227
|
+
- Enter the code exactly as shown (BCK-XXXX-XXXX).
|
|
104
228
|
|
|
105
229
|
### "Pairing code already redeemed"
|
|
106
|
-
Each code is one-time use. Run `/bot pair <name>` to generate a fresh one.
|
|
107
230
|
|
|
108
|
-
|
|
109
|
-
Run `openclaw configure` again and enter a new pairing code.
|
|
231
|
+
Each code is one-time use. Run `/bot pair <name>` to generate a fresh one.
|
|
110
232
|
|
|
111
233
|
---
|
|
112
234
|
|
|
@@ -115,9 +237,9 @@ Run `openclaw configure` again and enter a new pairing code.
|
|
|
115
237
|
```
|
|
116
238
|
Your Phone (BadgerClaw App)
|
|
117
239
|
│
|
|
118
|
-
│ E2EE Messages
|
|
240
|
+
│ E2EE Messages (Megolm/Olm)
|
|
119
241
|
▼
|
|
120
|
-
BadgerClaw Server (
|
|
242
|
+
BadgerClaw Server (Matrix homeserver)
|
|
121
243
|
│
|
|
122
244
|
│ E2EE Messages
|
|
123
245
|
▼
|
package/package.json
CHANGED
package/src/group-mentions.ts
CHANGED
|
@@ -29,6 +29,48 @@ function resolveMatrixRoomConfigForGroup(params: ChannelGroupContext) {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export function resolveMatrixGroupRequireMention(params: ChannelGroupContext): boolean {
|
|
32
|
+
// Check per-room config first (set by /bot talk on|off)
|
|
33
|
+
try {
|
|
34
|
+
const fs = require("fs");
|
|
35
|
+
const path = require("path");
|
|
36
|
+
const configPath = path.join(
|
|
37
|
+
process.env.HOME || "/tmp",
|
|
38
|
+
".openclaw/extensions/badgerclaw/room-config.json"
|
|
39
|
+
);
|
|
40
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
41
|
+
const roomConfig = JSON.parse(raw) as Record<string, { autoReply?: boolean }>;
|
|
42
|
+
const rawGroupId = params.groupId?.trim() ?? "";
|
|
43
|
+
// Try multiple formats: raw, stripped prefixes, just the room ID part
|
|
44
|
+
const variants = [
|
|
45
|
+
rawGroupId,
|
|
46
|
+
rawGroupId.replace(/^badgerclaw:/, ""),
|
|
47
|
+
rawGroupId.replace(/^channel:/, ""),
|
|
48
|
+
rawGroupId.replace(/^room:/, ""),
|
|
49
|
+
];
|
|
50
|
+
// Case-insensitive lookup: OpenClaw may lowercase the groupId while
|
|
51
|
+
// room-config.json stores original-cased Matrix room IDs
|
|
52
|
+
const configKeys = Object.keys(roomConfig);
|
|
53
|
+
for (const variant of variants) {
|
|
54
|
+
if (!variant) continue;
|
|
55
|
+
const matchedKey = configKeys.find(
|
|
56
|
+
(k) => k.toLowerCase() === variant.toLowerCase()
|
|
57
|
+
);
|
|
58
|
+
if (matchedKey) {
|
|
59
|
+
if (roomConfig[matchedKey]?.autoReply === true) {
|
|
60
|
+
console.log(`[badgerclaw] room-config autoReply=true for ${matchedKey} (lookup=${variant})`);
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
if (roomConfig[matchedKey]?.autoReply === false) {
|
|
64
|
+
console.log(`[badgerclaw] room-config autoReply=false for ${matchedKey} (lookup=${variant})`);
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
console.log(`[badgerclaw] room-config: no match for groupId="${rawGroupId}", keys=${configKeys.join(",")}`);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
console.log(`[badgerclaw] room-config error: ${err}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
32
74
|
const resolved = resolveMatrixRoomConfigForGroup(params);
|
|
33
75
|
if (resolved) {
|
|
34
76
|
if (resolved.autoReply === true) {
|
|
@@ -34,11 +34,40 @@ export function registerMatrixAutoJoin(params: {
|
|
|
34
34
|
}
|
|
35
35
|
autoJoinRegistered.add(client);
|
|
36
36
|
|
|
37
|
+
// After joining a room, send a notice in encrypted rooms to force Megolm session rotation.
|
|
38
|
+
// When the bot joins, other clients may use a cached outbound Megolm session that doesn't
|
|
39
|
+
// include the bot. Sending a message causes compliant clients to detect the new member and
|
|
40
|
+
// rotate their session so subsequent messages are decryptable by the bot.
|
|
41
|
+
async function postJoinEncryptionHandshake(roomId: string) {
|
|
42
|
+
try {
|
|
43
|
+
const encryptionState = await client
|
|
44
|
+
.getRoomStateEvent(roomId, "m.room.encryption", "")
|
|
45
|
+
.catch(() => null);
|
|
46
|
+
|
|
47
|
+
if (!encryptionState) {
|
|
48
|
+
logVerbose(`badgerclaw: room ${roomId} is not encrypted, skipping handshake`);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
logVerbose(`badgerclaw: room ${roomId} is encrypted, sending notice to trigger key rotation`);
|
|
53
|
+
await client.sendNotice(roomId, "🔐 Encryption active — session keys exchanged.");
|
|
54
|
+
logVerbose(`badgerclaw: sent encryption handshake notice in room ${roomId}`);
|
|
55
|
+
} catch (err) {
|
|
56
|
+
runtime.log?.(`badgerclaw: encryption handshake failed for room ${roomId}: ${String(err)}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
37
60
|
if (autoJoin === "always") {
|
|
38
61
|
// Use the built-in autojoin mixin for "always" mode
|
|
39
62
|
const { AutojoinRoomsMixin } = loadMatrixSdk();
|
|
40
63
|
AutojoinRoomsMixin.setupOnClient(client);
|
|
41
64
|
logVerbose("badgerclaw: auto-join enabled for all invites");
|
|
65
|
+
|
|
66
|
+
// AutojoinRoomsMixin handles the join, so listen for room.join to run post-join logic
|
|
67
|
+
client.on("room.join", async (roomId: string, _joinEvent: unknown) => {
|
|
68
|
+
logVerbose(`badgerclaw: bot joined room ${roomId} (always mode), running post-join handshake`);
|
|
69
|
+
await postJoinEncryptionHandshake(roomId);
|
|
70
|
+
});
|
|
42
71
|
return;
|
|
43
72
|
}
|
|
44
73
|
|
|
@@ -75,6 +104,7 @@ export function registerMatrixAutoJoin(params: {
|
|
|
75
104
|
try {
|
|
76
105
|
await client.joinRoom(roomId);
|
|
77
106
|
logVerbose(`badgerclaw: joined room ${roomId}`);
|
|
107
|
+
await postJoinEncryptionHandshake(roomId);
|
|
78
108
|
} catch (err) {
|
|
79
109
|
runtime.error?.(`badgerclaw: failed to join room ${roomId}: ${String(err)}`);
|
|
80
110
|
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import type { MatrixClient } from "@vector-im/matrix-bot-sdk";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
|
|
5
|
+
const ROOM_CONFIG_PATH = path.join(
|
|
6
|
+
process.env.HOME || "/tmp",
|
|
7
|
+
".openclaw/extensions/badgerclaw/room-config.json"
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
// Session store path for core groupActivation
|
|
11
|
+
const SESSION_STORE_PATH = path.join(
|
|
12
|
+
process.env.HOME || "/tmp",
|
|
13
|
+
".openclaw/agents/main/sessions/sessions.json"
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
function loadRoomConfig(): Record<string, { autoReply?: boolean }> {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(fs.readFileSync(ROOM_CONFIG_PATH, "utf-8"));
|
|
19
|
+
} catch {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function saveRoomConfig(config: Record<string, { autoReply?: boolean }>): void {
|
|
25
|
+
fs.writeFileSync(ROOM_CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function setGroupActivation(roomId: string, activation: "always" | "mention"): void {
|
|
29
|
+
try {
|
|
30
|
+
const store = JSON.parse(fs.readFileSync(SESSION_STORE_PATH, "utf-8"));
|
|
31
|
+
// Session key format: agent:main:badgerclaw:channel:!roomid (lowercase)
|
|
32
|
+
const sessionKey = `agent:main:badgerclaw:channel:${roomId.toLowerCase()}`;
|
|
33
|
+
if (!store[sessionKey]) {
|
|
34
|
+
store[sessionKey] = {};
|
|
35
|
+
}
|
|
36
|
+
store[sessionKey].groupActivation = activation;
|
|
37
|
+
fs.writeFileSync(SESSION_STORE_PATH, JSON.stringify(store, null, 2));
|
|
38
|
+
console.log(`[badgerclaw] set groupActivation=${activation} for ${sessionKey}`);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
console.error(`[badgerclaw] failed to set groupActivation: ${err}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function getRoomAutoReply(roomId: string): boolean | undefined {
|
|
45
|
+
const config = loadRoomConfig();
|
|
46
|
+
return config[roomId]?.autoReply;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function handleBotCommand(params: {
|
|
50
|
+
client: MatrixClient;
|
|
51
|
+
roomId: string;
|
|
52
|
+
senderId: string;
|
|
53
|
+
body: string;
|
|
54
|
+
selfUserId: string;
|
|
55
|
+
}): Promise<boolean> {
|
|
56
|
+
const { client, roomId, senderId, body, selfUserId } = params;
|
|
57
|
+
const trimmed = body.trim();
|
|
58
|
+
|
|
59
|
+
if (!trimmed.startsWith("/bot")) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const parts = trimmed.split(/\s+/);
|
|
64
|
+
const command = parts[1]?.toLowerCase();
|
|
65
|
+
const arg = parts[2]?.toLowerCase();
|
|
66
|
+
const arg2 = parts[3];
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
switch (command) {
|
|
70
|
+
case "help": {
|
|
71
|
+
await client.sendMessage(roomId, {
|
|
72
|
+
msgtype: "m.text",
|
|
73
|
+
body: [
|
|
74
|
+
"🦡 BadgerClaw Bot Commands",
|
|
75
|
+
"",
|
|
76
|
+
"━━━ Available Everywhere ━━━",
|
|
77
|
+
"",
|
|
78
|
+
"/bot help",
|
|
79
|
+
" Show this help message with all available commands.",
|
|
80
|
+
" Works in: Any room or DM",
|
|
81
|
+
"",
|
|
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.",
|
|
86
|
+
" Works in: Any room or DM",
|
|
87
|
+
"",
|
|
88
|
+
"/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.",
|
|
92
|
+
" Works in: Any room or DM",
|
|
93
|
+
"",
|
|
94
|
+
"/bot add <botname>",
|
|
95
|
+
" Invite a bot to the current room. The bot username",
|
|
96
|
+
" will be @<botname>_bot on this server.",
|
|
97
|
+
" Example: /bot add jarvis → invites @jarvis_bot",
|
|
98
|
+
" Works in: Any room",
|
|
99
|
+
"",
|
|
100
|
+
"━━━ BotBadger DM Only ━━━",
|
|
101
|
+
"",
|
|
102
|
+
"/bot new",
|
|
103
|
+
" Create a new bot. Starts a guided flow to set up",
|
|
104
|
+
" a bot name, username, and generate a pairing code",
|
|
105
|
+
" for connecting to an OpenClaw instance.",
|
|
106
|
+
" Works in: Direct message with BotBadger only",
|
|
107
|
+
"",
|
|
108
|
+
"/bot pair <name>",
|
|
109
|
+
" Generate a new pairing code for an existing bot.",
|
|
110
|
+
" Use this to connect or reconnect a bot to OpenClaw.",
|
|
111
|
+
" Works in: Direct message with BotBadger only",
|
|
112
|
+
"",
|
|
113
|
+
"/bot delete <name>",
|
|
114
|
+
" Permanently delete a bot and remove it from all rooms.",
|
|
115
|
+
" This action cannot be undone.",
|
|
116
|
+
" Works in: Direct message with BotBadger only",
|
|
117
|
+
"",
|
|
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",
|
|
122
|
+
" Works in: Any room or DM",
|
|
123
|
+
].join("\n"),
|
|
124
|
+
});
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
case "talk": {
|
|
129
|
+
if (arg === "on") {
|
|
130
|
+
const config = loadRoomConfig();
|
|
131
|
+
// Store with lowercase key to match OpenClaw's lowercased groupId
|
|
132
|
+
const normalizedRoomId = roomId.toLowerCase();
|
|
133
|
+
config[normalizedRoomId] = { ...config[normalizedRoomId], autoReply: true };
|
|
134
|
+
saveRoomConfig(config);
|
|
135
|
+
setGroupActivation(roomId, "always");
|
|
136
|
+
await client.sendMessage(roomId, {
|
|
137
|
+
msgtype: "m.text",
|
|
138
|
+
body: "✅ Auto-reply enabled — I'll respond to every message in this room.",
|
|
139
|
+
});
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
if (arg === "off") {
|
|
143
|
+
const config = loadRoomConfig();
|
|
144
|
+
const normalizedRoomId = roomId.toLowerCase();
|
|
145
|
+
config[normalizedRoomId] = { ...config[normalizedRoomId], autoReply: false };
|
|
146
|
+
saveRoomConfig(config);
|
|
147
|
+
setGroupActivation(roomId, "mention");
|
|
148
|
+
await client.sendMessage(roomId, {
|
|
149
|
+
msgtype: "m.text",
|
|
150
|
+
body: "✅ Auto-reply disabled — I'll only respond when @mentioned.",
|
|
151
|
+
});
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
await client.sendMessage(roomId, {
|
|
155
|
+
msgtype: "m.text",
|
|
156
|
+
body: "Usage: /bot talk on or /bot talk off",
|
|
157
|
+
});
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
case "add": {
|
|
162
|
+
const botName = arg;
|
|
163
|
+
if (!botName) {
|
|
164
|
+
await client.sendMessage(roomId, {
|
|
165
|
+
msgtype: "m.text",
|
|
166
|
+
body: "Usage: /bot add <botname>",
|
|
167
|
+
});
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
const botUserId = `@${botName}_bot:badger.signout.io`;
|
|
171
|
+
try {
|
|
172
|
+
await client.inviteUser(botUserId, roomId);
|
|
173
|
+
await client.sendMessage(roomId, {
|
|
174
|
+
msgtype: "m.text",
|
|
175
|
+
body: `✅ Invited ${botUserId} to this room.`,
|
|
176
|
+
});
|
|
177
|
+
} catch (err) {
|
|
178
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
179
|
+
await client.sendMessage(roomId, {
|
|
180
|
+
msgtype: "m.text",
|
|
181
|
+
body: `❌ Failed to invite ${botUserId}: ${msg}`,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
default: {
|
|
188
|
+
// Unknown /bot command — show help hint
|
|
189
|
+
await client.sendMessage(roomId, {
|
|
190
|
+
msgtype: "m.text",
|
|
191
|
+
body: "Unknown command. Type /bot help for available commands.",
|
|
192
|
+
});
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
} catch (err) {
|
|
197
|
+
console.error("badgerclaw: bot command error:", err);
|
|
198
|
+
return true; // Still consumed the command
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -410,6 +410,19 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
|
|
410
410
|
return;
|
|
411
411
|
}
|
|
412
412
|
|
|
413
|
+
// Handle /bot commands before anything else
|
|
414
|
+
if (bodyText.trim().startsWith("/bot")) {
|
|
415
|
+
const { handleBotCommand } = await import("./bot-commands.js");
|
|
416
|
+
const handled = await handleBotCommand({
|
|
417
|
+
client,
|
|
418
|
+
roomId,
|
|
419
|
+
senderId,
|
|
420
|
+
body: bodyText,
|
|
421
|
+
selfUserId,
|
|
422
|
+
});
|
|
423
|
+
if (handled) return;
|
|
424
|
+
}
|
|
425
|
+
|
|
413
426
|
const { wasMentioned, hasExplicitMention } = resolveMentions({
|
|
414
427
|
content,
|
|
415
428
|
userId: selfUserId,
|
|
@@ -459,14 +472,39 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
|
|
459
472
|
});
|
|
460
473
|
return;
|
|
461
474
|
}
|
|
475
|
+
// Check room-config.json first (set by /bot talk on|off) — it takes priority
|
|
476
|
+
// over the OpenClaw config wildcard entry.
|
|
477
|
+
const roomConfigJsonAutoReply = (() => {
|
|
478
|
+
try {
|
|
479
|
+
const fs = require("fs");
|
|
480
|
+
const path = require("path");
|
|
481
|
+
const configPath = path.join(
|
|
482
|
+
process.env.HOME || "/tmp",
|
|
483
|
+
".openclaw/extensions/badgerclaw/room-config.json"
|
|
484
|
+
);
|
|
485
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
486
|
+
const roomCfg = JSON.parse(raw) as Record<string, { autoReply?: boolean }>;
|
|
487
|
+
const normalizedRoomId = roomId.toLowerCase();
|
|
488
|
+
const keys = Object.keys(roomCfg);
|
|
489
|
+
const matchedKey = keys.find((k) => k.toLowerCase() === normalizedRoomId);
|
|
490
|
+
if (matchedKey && typeof roomCfg[matchedKey]?.autoReply === "boolean") {
|
|
491
|
+
return roomCfg[matchedKey].autoReply;
|
|
492
|
+
}
|
|
493
|
+
} catch { /* ignore */ }
|
|
494
|
+
return undefined;
|
|
495
|
+
})();
|
|
462
496
|
const shouldRequireMention = isRoom
|
|
463
|
-
?
|
|
497
|
+
? roomConfigJsonAutoReply === true
|
|
464
498
|
? false
|
|
465
|
-
:
|
|
499
|
+
: roomConfigJsonAutoReply === false
|
|
466
500
|
? true
|
|
467
|
-
:
|
|
468
|
-
?
|
|
469
|
-
:
|
|
501
|
+
: roomConfig?.autoReply === true
|
|
502
|
+
? false
|
|
503
|
+
: roomConfig?.autoReply === false
|
|
504
|
+
? true
|
|
505
|
+
: typeof roomConfig?.requireMention === "boolean"
|
|
506
|
+
? roomConfig?.requireMention
|
|
507
|
+
: true
|
|
470
508
|
: false;
|
|
471
509
|
const shouldBypassMention =
|
|
472
510
|
allowTextCommands &&
|
package/src/onboarding.ts
CHANGED
|
@@ -245,6 +245,17 @@ export const badgerclawOnboardingAdapter: ChannelOnboardingAdapter = {
|
|
|
245
245
|
autoJoin: "always",
|
|
246
246
|
// Open policy: bot responds in any room it's invited to
|
|
247
247
|
groupPolicy: "open",
|
|
248
|
+
// Default group config: auto-reply in all rooms with a helpful assistant prompt.
|
|
249
|
+
// This prevents the bot from inheriting workspace personas (SOUL.md) that may
|
|
250
|
+
// tell it to stay silent in groups or reply with NO_REPLY.
|
|
251
|
+
groups: {
|
|
252
|
+
"*": {
|
|
253
|
+
autoReply: true,
|
|
254
|
+
enabled: true,
|
|
255
|
+
systemPrompt:
|
|
256
|
+
"You are a helpful AI assistant in a BadgerClaw encrypted chat room. Respond to messages directly and helpfully. Be concise and friendly.",
|
|
257
|
+
},
|
|
258
|
+
},
|
|
248
259
|
dm: {
|
|
249
260
|
...next.channels?.badgerclaw?.dm,
|
|
250
261
|
policy: "open",
|