Skip to content

Commit 44b94b8

Browse files
authored
chore: drop superseded test routing change
1 parent 3918e48 commit 44b94b8

16 files changed

Lines changed: 513 additions & 76 deletions

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
Docs: https://docs.openclaw.ai
44

5+
## Unreleased
6+
7+
### Changes
8+
9+
### Fixes
10+
11+
- **iMessage group warnings:** suppress the false drop-all startup warning when an effective group sender allowlist can admit groups, and point true empty-allowlist configurations at the correct remedy. (#100046)
12+
513
## 2026.7.1
614

715
### Highlights

Dockerfile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,14 @@ RUN --mount=type=cache,id=openclaw-pnpm-store,target=/root/.local/share/pnpm/sto
153153
OPENCLAW_EXTENSIONS="$OPENCLAW_EXTENSIONS" OPENCLAW_BUNDLED_PLUGIN_DIR="$OPENCLAW_BUNDLED_PLUGIN_DIR" node scripts/prune-docker-plugin-dist.mjs && \
154154
node scripts/postinstall-bundled-plugins.mjs && \
155155
find dist -type f \( -name '*.d.ts' -o -name '*.d.mts' -o -name '*.d.cts' -o -name '*.map' \) -delete && \
156+
if [ -L /app/node_modules/@openclaw/ai ]; then \
157+
ai_runtime_target="$(readlink -f /app/node_modules/@openclaw/ai)" && \
158+
ai_runtime_tmp="$(mktemp -d)" && \
159+
cp -a "$ai_runtime_target" "$ai_runtime_tmp/ai" && \
160+
rm /app/node_modules/@openclaw/ai && \
161+
mv "$ai_runtime_tmp/ai" /app/node_modules/@openclaw/ai && \
162+
rmdir "$ai_runtime_tmp"; \
163+
fi && \
156164
rm -rf \
157165
/app/node_modules/openclaw \
158166
/app/node_modules/.bin/openclaw \

docs/channels/imessage-from-bluebubbles.md

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ iMessage and BlueBubbles share most channel-level behavior keys. What changes is
9999
| `channels.bluebubbles.dmPolicy` | `channels.imessage.dmPolicy` | Same values (`pairing` / `allowlist` / `open` / `disabled`); default `pairing`. |
100100
| `channels.bluebubbles.allowFrom` | `channels.imessage.allowFrom` | Same handle formats (`+15555550123`, `[email protected]`). Pairing-store approvals do not transfer — see below. |
101101
| `channels.bluebubbles.groupPolicy` | `channels.imessage.groupPolicy` | Same values (`allowlist` / `open` / `disabled`); default `allowlist`. |
102-
| `channels.bluebubbles.groupAllowFrom` | `channels.imessage.groupAllowFrom` | Same. Does not fall back to `allowFrom`an empty `groupAllowFrom` under `groupPolicy: "allowlist"` drops all groups regardless of `allowFrom`. |
102+
| `channels.bluebubbles.groupAllowFrom` | `channels.imessage.groupAllowFrom` | Same. When unset, iMessage falls back to `allowFrom`; an explicitly empty `groupAllowFrom: []` blocks all groups under `groupPolicy: "allowlist"`. |
103103
| `channels.bluebubbles.groups` | `channels.imessage.groups` | Copy the `"*"` wildcard entry verbatim; re-key per-group entries by numeric iMessage `chat_id` — see "Group registry footgun". `requireMention`, `tools`, `toolsBySender`, `systemPrompt` carry over. |
104104
| `channels.bluebubbles.sendReadReceipts` | `channels.imessage.sendReadReceipts` | Default `true`. With the bundled plugin this only fires when the private API probe is up. |
105105
| `channels.bluebubbles.includeAttachments` | `channels.imessage.includeAttachments` | Same shape, same off-by-default. If attachments flowed on BlueBubbles, set this explicitly — inbound photos/media are silently dropped (no `Inbound message` log line) until you do. |
@@ -117,38 +117,35 @@ Multi-account configs (`channels.bluebubbles.accounts.*`) translate one-to-one t
117117

118118
The bundled iMessage plugin runs two group gates back to back. A group message must pass both to reach the agent:
119119

120-
1. **Sender / chat-target allowlist** (`channels.imessage.groupAllowFrom`) — matches the sender handle or the chat target (`chat_id:`, `chat_guid:`, `chat_identifier:` entries). Does not fall back to `allowFrom`: with `groupPolicy: "allowlist"` and an empty `groupAllowFrom`, every group message drops here regardless of `allowFrom`.
120+
1. **Sender / chat-target allowlist** (`channels.imessage.groupAllowFrom`) — matches the sender handle or the chat target (`chat_id:`, `chat_guid:`, `chat_identifier:` entries). When `groupAllowFrom` is unset, this gate falls back to `allowFrom`; an explicit `groupAllowFrom: []` disables that fallback and drops every group message under `groupPolicy: "allowlist"`.
121121
2. **Group registry** (`channels.imessage.groups`) — keyed by numeric iMessage `chat_id`:
122-
- No `groups` block (or an empty one): groups pass this gate as long as gate 1 has a non-empty `groupAllowFrom`; sender filtering governs access. The gateway still logs a startup warning nudging you to add a `groups` block.
122+
- No `groups` block (or an empty one): groups pass this gate as long as gate 1 has a non-empty effective sender allowlist; sender filtering governs access and no drop-all startup warning fires.
123123
- `groups` with entries but no `"*"`: only the listed `chat_id` keys pass. Listing any group turns the registry into an allowlist even under `groupPolicy: "open"`.
124124
- `groups: { "*": { ... } }`: every group passes this gate.
125125

126126
The migration trap: BlueBubbles keyed `groups` entries by chat GUID / chat identifier, while the iMessage registry keys by numeric `chat_id`. Per-group entries copied verbatim create a non-empty registry whose keys never match, so every group message drops at gate 2. Copy the `"*"` wildcard verbatim; re-key specific group entries with `chat_id` values from `imsg chats`.
127127

128128
Both drop paths are visible at the default log level via `warn` lines:
129129

130-
- Once per account at startup, when `groupPolicy: "allowlist"` is set and `channels.imessage.groups` is empty: `imessage: groupPolicy="allowlist" but channels.imessage.groups is empty for account "<id>"`.
130+
- Once per account at startup, when `groupPolicy: "allowlist"` is set and the effective group sender allowlist is empty: `imessage: groupPolicy="allowlist" for account "<id>" but no group sender allowlist is configured ...`. Set `groupAllowFrom` (or `allowFrom`) to admit senders; adding `groups` alone does not satisfy the sender gate.
131131
- Once per `chat_id` at runtime, when the registry drops a group: `imessage: dropping group message from chat_id=<id> ... not in channels.imessage.groups allowlist`, naming the exact key to add.
132132

133133
DMs keep working either way — they take a different code path, so DM success does not prove group routing.
134134

135-
The safe minimum with `groupPolicy: "allowlist"`:
135+
The minimum sender-scoped config with `groupPolicy: "allowlist"`:
136136

137137
```json5
138138
{
139139
channels: {
140140
imessage: {
141141
groupPolicy: "allowlist",
142142
groupAllowFrom: ["+15555550123", "chat_guid:any;-;..."],
143-
groups: {
144-
"*": { requireMention: true },
145-
},
146143
},
147144
},
148145
}
149146
```
150147

151-
`requireMention: true` under `"*"` is harmless when no mention patterns are configured: the runtime cannot detect mentions without patterns and skips the mention drop. With `agents.list[].groupChat.mentionPatterns` (fallback `messages.groupChat.mentionPatterns`) configured, mention gating applies as expected.
148+
This admits the configured senders in any group. Add `groups` entries to scope allowed chats or set per-chat options such as `requireMention`; copy the BlueBubbles `"*"` entry verbatim, but re-key specific entries with numeric iMessage `chat_id` values.
152149

153150
## Step-by-step
154151

@@ -182,7 +179,7 @@ The safe minimum with `groupPolicy: "allowlist"`:
182179

183180
3. **Verify DMs.** Send the agent a direct message; confirm the reply lands.
184181

185-
4. **Verify groups separately.** DMs and groups take different code paths — DM success does not prove groups are routing. Send a message in an allowed group chat and confirm the reply lands. If the group goes silent (no agent reply, no error), check the gateway log for the two `warn` lines from "Group registry footgun" above; either one means the `groups` block is missing, empty, or keyed wrong.
182+
4. **Verify groups separately.** DMs and groups take different code paths — DM success does not prove groups are routing. Send a message in an allowed group chat and confirm the reply lands. If the group goes silent (no agent reply, no error), check the gateway log for the two `warn` lines from "Group registry footgun" above. The startup warning means the effective sender allowlist is empty; a per-`chat_id` warning means a populated `groups` registry does not contain that chat.
186183

187184
5. **Verify the action surface.** From a paired DM, ask the agent to react, edit, unsend, reply, send a photo, and (in a group) rename the group or add/remove a participant. Each action should land natively in Messages.app. If any action throws `iMessage <action> requires the imsg private API bridge`, run `imsg launch` again and refresh with `openclaw channels status --probe`.
188185

docs/channels/imessage.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -282,13 +282,13 @@ If disabling SIP is not acceptable for your threat model:
282282
<Warning>
283283
Group routing under `groupPolicy: "allowlist"` runs **two** gates back-to-back:
284284

285-
1. **Sender allowlist** (`channels.imessage.groupAllowFrom`) — handle, `accessGroup:<name>`, `chat_guid`, `chat_identifier`, or `chat_id`. Empty (and no `allowFrom` fallback) means every group message is dropped (`groupPolicy allowlist (empty groupAllowFrom)` in verbose logs).
286-
2. **Group registry** (`channels.imessage.groups`) — enforced once the map has entries: the chat must match an explicit per-`chat_id` entry or a `groups: { "*": { ... } }` wildcard (`allowAll`). A group chat that matches neither is dropped. When `groups` is empty or missing, the sender allowlist alone decides admission.
285+
1. **Sender allowlist** (`channels.imessage.groupAllowFrom`) — handle, `accessGroup:<name>`, `chat_guid`, `chat_identifier`, or `chat_id`. An empty effective list (no `groupAllowFrom` and no `allowFrom` fallback) blocks every group sender.
286+
2. **Group registry** (`channels.imessage.groups`) — enforced once the map has entries: the chat must match an explicit per-`chat_id` entry or a `groups: { "*": { ... } }` wildcard. When `groups` is empty or missing, the sender allowlist alone decides admission.
287287

288-
The plugin emits `warn`-level signals at the default log level:
288+
If no effective group sender allowlist is configured, every group message is dropped before the registry gate. Each gate has its own `warn`-level signal at the default log level, and each names a different fix:
289289

290-
- one-time per account at startup when `groups` is empty: `imessage: groupPolicy="allowlist" but channels.imessage.groups is empty for account "<id>"`
291-
- one-time per `chat_id` when the registry gate drops a chat: `imessage: dropping group message from chat_id=<id> ...`
290+
- one-time per account at startup, when the effective group sender allowlist is empty: `imessage: groupPolicy="allowlist" for account "<id>" but no group sender allowlist is configured ...` — fix by setting `channels.imessage.groupAllowFrom` (or `allowFrom`); adding `groups` entries alone leaves gate 1 blocking every sender.
291+
- one-time per `chat_id` at runtime, when a sender passed gate 1 but the chat is missing from a populated `groups` registry: `imessage: dropping group message from chat_id=<id> ...` — fix by adding that `chat_id` (or `"*"`) under `channels.imessage.groups`.
292292

293293
DMs are unaffected — they take a different code path.
294294

@@ -306,7 +306,7 @@ If disabling SIP is not acceptable for your threat model:
306306
}
307307
```
308308

309-
If the per-`chat_id` drop lines appear in the gateway log, the registry gate is dropping that chat — add it to `groups` (or add the `"*"` wildcard).
309+
`groupAllowFrom` alone admits those senders in any group; add the `groups` block to scope which chats are allowed (and to set per-chat options like `requireMention`).
310310
</Warning>
311311

312312
Mention gating for groups:

extensions/imessage/src/monitor/group-allowlist-warnings.test.ts

Lines changed: 26 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -11,66 +11,52 @@ beforeEach(() => {
1111
});
1212

1313
describe("warnGroupAllowlistMisconfigOnce", () => {
14-
it("fires when groupPolicy=allowlist and groups is undefined", () => {
14+
it("fires when groupPolicy=allowlist has no effective groupAllowFrom", () => {
1515
const messages: string[] = [];
1616
const fired = warnGroupAllowlistMisconfigOnce({
1717
groupPolicy: "allowlist",
18-
groups: undefined,
18+
hasGroupAllowFrom: false,
1919
accountId: "default",
2020
log: (m) => messages.push(m),
2121
});
2222
expect(fired).toBe(true);
2323
expect(messages).toHaveLength(1);
2424
expect(messages[0]).toContain('groupPolicy="allowlist"');
25-
expect(messages[0]).toContain("channels.imessage.groups is empty");
25+
expect(messages[0]).toContain("channels.imessage.groupAllowFrom");
2626
expect(messages[0]).toContain("default");
2727
});
2828

29-
it("fires when groupPolicy=allowlist and groups is empty object", () => {
30-
const messages: string[] = [];
31-
const fired = warnGroupAllowlistMisconfigOnce({
32-
groupPolicy: "allowlist",
33-
groups: {},
34-
accountId: "default",
35-
log: (m) => messages.push(m),
36-
});
37-
expect(fired).toBe(true);
38-
expect(messages).toHaveLength(1);
39-
});
40-
4129
it("does not fire when groupPolicy is not allowlist", () => {
4230
const messages: string[] = [];
4331
const fired = warnGroupAllowlistMisconfigOnce({
4432
groupPolicy: "open",
45-
groups: undefined,
33+
hasGroupAllowFrom: false,
4634
accountId: "default",
4735
log: (m) => messages.push(m),
4836
});
4937
expect(fired).toBe(false);
5038
expect(messages).toHaveLength(0);
5139
});
5240

53-
it("does not fire when groups has a wildcard entry", () => {
41+
it("does not fire when groupAllowFrom admits groups despite empty groups (senderFilterBypass)", () => {
5442
const messages: string[] = [];
55-
const fired = warnGroupAllowlistMisconfigOnce({
56-
groupPolicy: "allowlist",
57-
groups: { "*": { requireMention: true } },
58-
accountId: "default",
59-
log: (m) => messages.push(m),
60-
});
61-
expect(fired).toBe(false);
62-
expect(messages).toHaveLength(0);
63-
});
64-
65-
it("does not fire when groups has explicit chat_id entries", () => {
66-
const messages: string[] = [];
67-
const fired = warnGroupAllowlistMisconfigOnce({
68-
groupPolicy: "allowlist",
69-
groups: { "12345": {} },
70-
accountId: "default",
71-
log: (m) => messages.push(m),
72-
});
73-
expect(fired).toBe(false);
43+
const log = (m: string) => messages.push(m);
44+
expect(
45+
warnGroupAllowlistMisconfigOnce({
46+
groupPolicy: "allowlist",
47+
hasGroupAllowFrom: true,
48+
accountId: "default",
49+
log,
50+
}),
51+
).toBe(false);
52+
expect(
53+
warnGroupAllowlistMisconfigOnce({
54+
groupPolicy: "allowlist",
55+
hasGroupAllowFrom: true,
56+
accountId: "default",
57+
log,
58+
}),
59+
).toBe(false);
7460
expect(messages).toHaveLength(0);
7561
});
7662

@@ -80,15 +66,15 @@ describe("warnGroupAllowlistMisconfigOnce", () => {
8066
expect(
8167
warnGroupAllowlistMisconfigOnce({
8268
groupPolicy: "allowlist",
83-
groups: undefined,
69+
hasGroupAllowFrom: false,
8470
accountId: "default",
8571
log,
8672
}),
8773
).toBe(true);
8874
expect(
8975
warnGroupAllowlistMisconfigOnce({
9076
groupPolicy: "allowlist",
91-
groups: undefined,
77+
hasGroupAllowFrom: false,
9278
accountId: "default",
9379
log,
9480
}),
@@ -101,13 +87,13 @@ describe("warnGroupAllowlistMisconfigOnce", () => {
10187
const log = (m: string) => messages.push(m);
10288
warnGroupAllowlistMisconfigOnce({
10389
groupPolicy: "allowlist",
104-
groups: undefined,
90+
hasGroupAllowFrom: false,
10591
accountId: "primary",
10692
log,
10793
});
10894
warnGroupAllowlistMisconfigOnce({
10995
groupPolicy: "allowlist",
110-
groups: undefined,
96+
hasGroupAllowFrom: false,
11197
accountId: "secondary",
11298
log,
11399
});

extensions/imessage/src/monitor/group-allowlist-warnings.ts

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,35 @@
1-
// Group-allowlist visibility helpers. The runtime gate at line ~336 of
2-
// inbound-processing.ts drops every group message when groupPolicy="allowlist"
3-
// and channels.imessage.groups is missing. Without these warnings the drop is
4-
// invisible at default log level during iMessage config migration. See
1+
// Group-allowlist visibility helpers. With groupPolicy="allowlist" the inbound
2+
// sender gate drops every group message when no effective group sender
3+
// allowlist exists (groupAllowFrom, or its allowFrom fallback), regardless of
4+
// channels.imessage.groups. A non-empty groupAllowFrom can admit groups despite
5+
// an empty groups map (senderFilterBypass in src/config/group-policy.ts).
6+
// Without these warnings the drop-all case is invisible at default log level
7+
// during iMessage config migration. See
58
// https://github.com/openclaw/openclaw/issues/78749.
69

7-
type GroupsConfig = Record<
8-
string,
9-
{ requireMention?: boolean; tools?: unknown; toolsBySender?: unknown }
10-
>;
11-
1210
const startupWarned = new Set<string>();
1311
const perChatWarned = new Set<string>();
1412

1513
/**
1614
* Fires once per `accountId` at monitor startup when `groupPolicy === "allowlist"`
17-
* but `channels.imessage.groups` is empty (no `"*"` wildcard, no explicit
18-
* `chat_id` entries). Without one of those, every group message is dropped at
19-
* the second gate even when the sender passes `groupAllowFrom`.
15+
* and the effective group sender allowlist is empty. The sender gate runs
16+
* before the group registry, so `channels.imessage.groups` entries cannot admit
17+
* traffic without a sender allowlist. A non-empty `groupAllowFrom` makes the
18+
* configuration potentially valid and suppresses this drop-all warning.
2019
*/
2120
export function warnGroupAllowlistMisconfigOnce(params: {
2221
groupPolicy: string;
23-
groups: GroupsConfig | undefined;
22+
hasGroupAllowFrom: boolean;
2423
accountId: string;
2524
log: (message: string) => void;
2625
}): boolean {
2726
if (params.groupPolicy !== "allowlist") {
2827
return false;
2928
}
30-
const entries = params.groups ? Object.keys(params.groups) : [];
31-
if (entries.length > 0) {
29+
// A non-empty effective groupAllowFrom can admit groups without a groups map
30+
// (senderFilterBypass in src/config/group-policy.ts), so this is not a
31+
// provable drop-all configuration.
32+
if (params.hasGroupAllowFrom) {
3233
return false;
3334
}
3435
const key = `imessage:${params.accountId}`;
@@ -37,10 +38,11 @@ export function warnGroupAllowlistMisconfigOnce(params: {
3738
}
3839
startupWarned.add(key);
3940
params.log(
40-
`imessage: groupPolicy="allowlist" but channels.imessage.groups is empty for account "${params.accountId}". ` +
41+
`imessage: groupPolicy="allowlist" for account "${params.accountId}" but no group sender allowlist is configured ` +
42+
`(channels.imessage.groupAllowFrom, or its allowFrom fallback). ` +
4143
`Every inbound group message will be dropped. ` +
42-
`Add channels.imessage.groups["*"] = { requireMention: true } to allow all groups, ` +
43-
`or explicit per-chat_id entries to allow specific groups.`,
44+
`Set channels.imessage.groupAllowFrom (sender handles, chat targets like chat_id:<id>, or "*") to admit group senders, ` +
45+
`and optionally add channels.imessage.groups entries to scope which chats are allowed.`,
4446
);
4547
return true;
4648
}

0 commit comments

Comments
 (0)