fix(signal): add groups config to Signal channel schema (#27199)

Merged via squash.

Prepared head SHA: 4ba4a39ddf
Co-authored-by: unisone <32521398+unisone@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
This commit is contained in:
Alex Zaytsev 2026-03-13 08:14:30 -04:00 committed by GitHub
parent 4e68684bd2
commit 61429230b2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 90 additions and 0 deletions

View File

@ -22,6 +22,7 @@ Docs: https://docs.openclaw.ai
- 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.
- 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.
- 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.
- 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.
## 2026.3.12

View File

@ -195,6 +195,8 @@ Groups:
- `channels.signal.groupPolicy = open | allowlist | disabled`.
- `channels.signal.groupAllowFrom` controls who can trigger in groups when `allowlist` is set.
- `channels.signal.groups["<group-id>" | "*"]` can override group behavior with `requireMention`, `tools`, and `toolsBySender`.
- Use `channels.signal.accounts.<id>.groups` for per-account overrides in multi-account setups.
- Runtime note: if `channels.signal` is completely missing, runtime falls back to `groupPolicy="allowlist"` for group checks (even if `channels.defaults.groupPolicy` is set).
## How it works (behavior)
@ -312,6 +314,8 @@ Provider options:
- `channels.signal.allowFrom`: DM allowlist (E.164 or `uuid:<id>`). `open` requires `"*"`. Signal has no usernames; use phone/UUID ids.
- `channels.signal.groupPolicy`: `open | allowlist | disabled` (default: allowlist).
- `channels.signal.groupAllowFrom`: group sender allowlist.
- `channels.signal.groups`: per-group overrides keyed by Signal group id (or `"*"`). Supported fields: `requireMention`, `tools`, `toolsBySender`.
- `channels.signal.accounts.<id>.groups`: per-account version of `channels.signal.groups` for multi-account setups.
- `channels.signal.historyLimit`: max group messages to include as context (0 disables).
- `channels.signal.dmHistoryLimit`: DM history limit in user turns. Per-user overrides: `channels.signal.dms["<phone_or_uuid>"].historyLimit`.
- `channels.signal.textChunkLimit`: outbound chunk size (chars).

View File

@ -1,8 +1,15 @@
import type { CommonChannelMessagingConfig } from "./types.channel-messaging-common.js";
import type { GroupToolPolicyBySenderConfig, GroupToolPolicyConfig } from "./types.tools.js";
export type SignalReactionNotificationMode = "off" | "own" | "all" | "allowlist";
export type SignalReactionLevel = "off" | "ack" | "minimal" | "extensive";
export type SignalGroupConfig = {
requireMention?: boolean;
tools?: GroupToolPolicyConfig;
toolsBySender?: GroupToolPolicyBySenderConfig;
};
export type SignalAccountConfig = CommonChannelMessagingConfig & {
/** Optional explicit E.164 account for signal-cli. */
account?: string;
@ -24,6 +31,8 @@ export type SignalAccountConfig = CommonChannelMessagingConfig & {
ignoreAttachments?: boolean;
ignoreStories?: boolean;
sendReadReceipts?: boolean;
/** Per-group overrides keyed by Signal group id (or "*"). */
groups?: Record<string, SignalGroupConfig>;
/** Outbound text chunk size (chars). Default: 4000. */
textChunkLimit?: number;
/** Reaction notification mode (off|own|all|allowlist). Default: own. */

View File

@ -971,6 +971,16 @@ export const SlackConfigSchema = SlackAccountSchema.safeExtend({
validateSlackSigningSecretRequirements(value, ctx);
});
const SignalGroupEntrySchema = z
.object({
requireMention: z.boolean().optional(),
tools: ToolPolicySchema,
toolsBySender: ToolPolicyBySenderSchema,
})
.strict();
const SignalGroupsSchema = z.record(z.string(), SignalGroupEntrySchema.optional()).optional();
export const SignalAccountSchemaBase = z
.object({
name: z.string().optional(),
@ -995,6 +1005,7 @@ export const SignalAccountSchemaBase = z
defaultTo: z.string().optional(),
groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
groupPolicy: GroupPolicySchema.optional().default("allowlist"),
groups: SignalGroupsSchema,
historyLimit: z.number().int().min(0).optional(),
dmHistoryLimit: z.number().int().min(0).optional(),
dms: z.record(z.string(), DmConfigSchema.optional()).optional(),

View File

@ -0,0 +1,65 @@
import { describe, expect, it } from "vitest";
import { validateConfigObject } from "./config.js";
describe("signal groups schema", () => {
it("accepts top-level Signal groups overrides", () => {
const res = validateConfigObject({
channels: {
signal: {
groups: {
"*": {
requireMention: false,
},
"+1234567890": {
requireMention: true,
},
},
},
},
});
expect(res.ok).toBe(true);
});
it("accepts per-account Signal groups overrides", () => {
const res = validateConfigObject({
channels: {
signal: {
accounts: {
primary: {
groups: {
"*": {
requireMention: false,
},
},
},
},
},
},
});
expect(res.ok).toBe(true);
});
it("rejects unknown keys in Signal groups entries", () => {
const res = validateConfigObject({
channels: {
signal: {
groups: {
"*": {
requireMention: false,
nope: true,
},
},
},
},
});
expect(res.ok).toBe(false);
if (!res.ok) {
expect(res.issues.some((issue) => issue.path.startsWith("channels.signal.groups"))).toBe(
true,
);
}
});
});