Skip to content

Commit 6142923

Browse files
unisonealtaywtf
andauthored
fix(signal): add groups config to Signal channel schema (#27199)
Merged via squash. Prepared head SHA: 4ba4a39 Co-authored-by: unisone <[email protected]> Co-authored-by: altaywtf <[email protected]> Reviewed-by: @altaywtf
1 parent 4e68684 commit 6142923

File tree

5 files changed

+90
-0
lines changed

5 files changed

+90
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Docs: https://docs.openclaw.ai
2222
- Config/validation: accept documented `agents.list[].params` per-agent overrides in strict config validation so `openclaw config validate` no longer rejects runtime-supported `cacheRetention`, `temperature`, and `maxTokens` settings. (#41171) Thanks @atian8179.
2323
- Android/onboarding QR scan: switch setup QR scanning to Google Code Scanner so onboarding uses a more reliable scanner instead of the legacy embedded ZXing flow. (#45021) Thanks @obviyus.
2424
- Config/web fetch: restore runtime validation for documented `tools.web.fetch.readability` and `tools.web.fetch.firecrawl` settings so valid web fetch configs no longer fail with unrecognized-key errors. (#42583) Thanks @stim64045-spec.
25+
- Signal/config validation: add `channels.signal.groups` schema support so per-group `requireMention`, `tools`, and `toolsBySender` overrides no longer get rejected during config validation. (#27199) Thanks @unisone.
2526

2627
## 2026.3.12
2728

docs/channels/signal.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,8 @@ Groups:
195195

196196
- `channels.signal.groupPolicy = open | allowlist | disabled`.
197197
- `channels.signal.groupAllowFrom` controls who can trigger in groups when `allowlist` is set.
198+
- `channels.signal.groups["<group-id>" | "*"]` can override group behavior with `requireMention`, `tools`, and `toolsBySender`.
199+
- Use `channels.signal.accounts.<id>.groups` for per-account overrides in multi-account setups.
198200
- Runtime note: if `channels.signal` is completely missing, runtime falls back to `groupPolicy="allowlist"` for group checks (even if `channels.defaults.groupPolicy` is set).
199201

200202
## How it works (behavior)
@@ -312,6 +314,8 @@ Provider options:
312314
- `channels.signal.allowFrom`: DM allowlist (E.164 or `uuid:<id>`). `open` requires `"*"`. Signal has no usernames; use phone/UUID ids.
313315
- `channels.signal.groupPolicy`: `open | allowlist | disabled` (default: allowlist).
314316
- `channels.signal.groupAllowFrom`: group sender allowlist.
317+
- `channels.signal.groups`: per-group overrides keyed by Signal group id (or `"*"`). Supported fields: `requireMention`, `tools`, `toolsBySender`.
318+
- `channels.signal.accounts.<id>.groups`: per-account version of `channels.signal.groups` for multi-account setups.
315319
- `channels.signal.historyLimit`: max group messages to include as context (0 disables).
316320
- `channels.signal.dmHistoryLimit`: DM history limit in user turns. Per-user overrides: `channels.signal.dms["<phone_or_uuid>"].historyLimit`.
317321
- `channels.signal.textChunkLimit`: outbound chunk size (chars).

src/config/types.signal.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
import type { CommonChannelMessagingConfig } from "./types.channel-messaging-common.js";
2+
import type { GroupToolPolicyBySenderConfig, GroupToolPolicyConfig } from "./types.tools.js";
23

34
export type SignalReactionNotificationMode = "off" | "own" | "all" | "allowlist";
45
export type SignalReactionLevel = "off" | "ack" | "minimal" | "extensive";
56

7+
export type SignalGroupConfig = {
8+
requireMention?: boolean;
9+
tools?: GroupToolPolicyConfig;
10+
toolsBySender?: GroupToolPolicyBySenderConfig;
11+
};
12+
613
export type SignalAccountConfig = CommonChannelMessagingConfig & {
714
/** Optional explicit E.164 account for signal-cli. */
815
account?: string;
@@ -24,6 +31,8 @@ export type SignalAccountConfig = CommonChannelMessagingConfig & {
2431
ignoreAttachments?: boolean;
2532
ignoreStories?: boolean;
2633
sendReadReceipts?: boolean;
34+
/** Per-group overrides keyed by Signal group id (or "*"). */
35+
groups?: Record<string, SignalGroupConfig>;
2736
/** Outbound text chunk size (chars). Default: 4000. */
2837
textChunkLimit?: number;
2938
/** Reaction notification mode (off|own|all|allowlist). Default: own. */

src/config/zod-schema.providers-core.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -971,6 +971,16 @@ export const SlackConfigSchema = SlackAccountSchema.safeExtend({
971971
validateSlackSigningSecretRequirements(value, ctx);
972972
});
973973

974+
const SignalGroupEntrySchema = z
975+
.object({
976+
requireMention: z.boolean().optional(),
977+
tools: ToolPolicySchema,
978+
toolsBySender: ToolPolicyBySenderSchema,
979+
})
980+
.strict();
981+
982+
const SignalGroupsSchema = z.record(z.string(), SignalGroupEntrySchema.optional()).optional();
983+
974984
export const SignalAccountSchemaBase = z
975985
.object({
976986
name: z.string().optional(),
@@ -995,6 +1005,7 @@ export const SignalAccountSchemaBase = z
9951005
defaultTo: z.string().optional(),
9961006
groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
9971007
groupPolicy: GroupPolicySchema.optional().default("allowlist"),
1008+
groups: SignalGroupsSchema,
9981009
historyLimit: z.number().int().min(0).optional(),
9991010
dmHistoryLimit: z.number().int().min(0).optional(),
10001011
dms: z.record(z.string(), DmConfigSchema.optional()).optional(),
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { describe, expect, it } from "vitest";
2+
import { validateConfigObject } from "./config.js";
3+
4+
describe("signal groups schema", () => {
5+
it("accepts top-level Signal groups overrides", () => {
6+
const res = validateConfigObject({
7+
channels: {
8+
signal: {
9+
groups: {
10+
"*": {
11+
requireMention: false,
12+
},
13+
"+1234567890": {
14+
requireMention: true,
15+
},
16+
},
17+
},
18+
},
19+
});
20+
21+
expect(res.ok).toBe(true);
22+
});
23+
24+
it("accepts per-account Signal groups overrides", () => {
25+
const res = validateConfigObject({
26+
channels: {
27+
signal: {
28+
accounts: {
29+
primary: {
30+
groups: {
31+
"*": {
32+
requireMention: false,
33+
},
34+
},
35+
},
36+
},
37+
},
38+
},
39+
});
40+
41+
expect(res.ok).toBe(true);
42+
});
43+
44+
it("rejects unknown keys in Signal groups entries", () => {
45+
const res = validateConfigObject({
46+
channels: {
47+
signal: {
48+
groups: {
49+
"*": {
50+
requireMention: false,
51+
nope: true,
52+
},
53+
},
54+
},
55+
},
56+
});
57+
58+
expect(res.ok).toBe(false);
59+
if (!res.ok) {
60+
expect(res.issues.some((issue) => issue.path.startsWith("channels.signal.groups"))).toBe(
61+
true,
62+
);
63+
}
64+
});
65+
});

0 commit comments

Comments
 (0)