From d114b4e0331123d81d667430dcaec755f4a25a0d Mon Sep 17 00:00:00 2001 From: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> Date: Fri, 3 Apr 2026 10:54:32 -0500 Subject: [PATCH] fix: honor signal setup dm policy accounts --- extensions/signal/src/core.test.ts | 58 ++++++++++++++++++++++++++++- extensions/signal/src/setup-core.ts | 36 ++++++++++++++++-- 2 files changed, 89 insertions(+), 5 deletions(-) diff --git a/extensions/signal/src/core.test.ts b/extensions/signal/src/core.test.ts index f7f0644f1a8..3c34e886ab9 100644 --- a/extensions/signal/src/core.test.ts +++ b/extensions/signal/src/core.test.ts @@ -10,7 +10,11 @@ import { } from "./identity.js"; import { probeSignal } from "./probe.js"; import { clearSignalRuntime } from "./runtime.js"; -import { normalizeSignalAccountInput, parseSignalAllowFromEntries } from "./setup-core.js"; +import { + normalizeSignalAccountInput, + parseSignalAllowFromEntries, + signalDmPolicy, +} from "./setup-core.js"; describe("looksLikeUuid", () => { it("accepts hyphenated UUIDs", () => { @@ -218,4 +222,56 @@ describe("signal setup parsing", () => { error: "Invalid entry: invalid", }); }); + + it("reads the named-account DM policy instead of the channel root", () => { + expect( + signalDmPolicy.getCurrent( + { + channels: { + signal: { + dmPolicy: "disabled", + accounts: { + work: { + account: "+15555550123", + dmPolicy: "allowlist", + }, + }, + }, + }, + }, + "work", + ), + ).toBe("allowlist"); + }); + + it("reports account-scoped config keys for named accounts", () => { + expect(signalDmPolicy.resolveConfigKeys?.({ channels: { signal: {} } }, "work")).toEqual({ + policyKey: "channels.signal.accounts.work.dmPolicy", + allowFromKey: "channels.signal.accounts.work.allowFrom", + }); + }); + + it('writes open policy state to the named account and preserves inherited allowFrom with "*"', () => { + const next = signalDmPolicy.setPolicy( + { + channels: { + signal: { + allowFrom: ["+15555550123"], + accounts: { + work: { + account: "+15555550999", + }, + }, + }, + }, + }, + "open", + "work", + ); + + expect(next.channels?.signal?.dmPolicy).toBeUndefined(); + expect(next.channels?.signal?.allowFrom).toEqual(["+15555550123"]); + expect(next.channels?.signal?.accounts?.work?.dmPolicy).toBe("open"); + expect(next.channels?.signal?.accounts?.work?.allowFrom).toEqual(["+15555550123", "*"]); + }); }); diff --git a/extensions/signal/src/setup-core.ts b/extensions/signal/src/setup-core.ts index 63defa05f5a..cb9f73aaae4 100644 --- a/extensions/signal/src/setup-core.ts +++ b/extensions/signal/src/setup-core.ts @@ -4,7 +4,8 @@ import { createDelegatedTextInputShouldPrompt, createPatchedAccountSetupAdapter, createSetupInputPresenceValidator, - createTopLevelChannelDmPolicy, + mergeAllowFromEntries, + patchChannelConfigForAccount, parseSetupEntriesAllowingWildcard, promptParsedAllowFromForAccount, setAccountAllowFromForChannel, @@ -120,14 +121,41 @@ export async function promptSignalAllowFrom(params: { }); } -export const signalDmPolicy = createTopLevelChannelDmPolicy({ +export const signalDmPolicy = { label: "Signal", channel, policyKey: "channels.signal.dmPolicy", allowFromKey: "channels.signal.allowFrom", - getCurrent: (cfg: OpenClawConfig) => cfg.channels?.signal?.dmPolicy ?? "pairing", + resolveConfigKeys: (_cfg: OpenClawConfig, accountId?: string) => + accountId && accountId !== resolveDefaultSignalAccountId(_cfg) + ? { + policyKey: `channels.signal.accounts.${accountId}.dmPolicy`, + allowFromKey: `channels.signal.accounts.${accountId}.allowFrom`, + } + : { + policyKey: "channels.signal.dmPolicy", + allowFromKey: "channels.signal.allowFrom", + }, + getCurrent: (cfg: OpenClawConfig, accountId?: string) => + resolveSignalAccount({ cfg, accountId }).config.dmPolicy ?? "pairing", + setPolicy: (cfg: OpenClawConfig, policy: "pairing" | "allowlist" | "open" | "disabled", accountId?: string) => + patchChannelConfigForAccount({ + cfg, + channel, + accountId: accountId ?? resolveDefaultSignalAccountId(cfg), + patch: + policy === "open" + ? { + dmPolicy: "open", + allowFrom: mergeAllowFromEntries( + resolveSignalAccount({ cfg, accountId }).config.allowFrom, + ["*"], + ), + } + : { dmPolicy: policy }, + }), promptAllowFrom: promptSignalAllowFrom, -}); +}; function resolveSignalCliPath(params: { cfg: OpenClawConfig;