diff --git a/extensions/googlechat/src/setup-surface.ts b/extensions/googlechat/src/setup-surface.ts index 5d552eed2d7..43562ff65a3 100644 --- a/extensions/googlechat/src/setup-surface.ts +++ b/extensions/googlechat/src/setup-surface.ts @@ -1,7 +1,7 @@ import { + addWildcardAllowFrom, applySetupAccountConfigPatch, createNestedChannelParsedAllowFromPrompt, - createNestedChannelDmPolicy, createStandardChannelSetupStatus, DEFAULT_ACCOUNT_ID, formatDocsLink, @@ -37,16 +37,49 @@ const promptAllowFrom = createNestedChannelParsedAllowFromPrompt({ }), }); -const googlechatDmPolicy: ChannelSetupDmPolicy = createNestedChannelDmPolicy({ +const googlechatDmPolicy: ChannelSetupDmPolicy = { label: "Google Chat", channel, - section: "dm", policyKey: "channels.googlechat.dm.policy", allowFromKey: "channels.googlechat.dm.allowFrom", - getCurrent: (cfg) => cfg.channels?.googlechat?.dm?.policy ?? "pairing", + resolveConfigKeys: (_cfg, accountId) => + accountId && accountId !== DEFAULT_ACCOUNT_ID + ? { + policyKey: `channels.googlechat.accounts.${accountId}.dm.policy`, + allowFromKey: `channels.googlechat.accounts.${accountId}.dm.allowFrom`, + } + : { + policyKey: "channels.googlechat.dm.policy", + allowFromKey: "channels.googlechat.dm.allowFrom", + }, + getCurrent: (cfg, accountId) => + resolveGoogleChatAccount({ + cfg, + accountId: accountId ?? DEFAULT_ACCOUNT_ID, + }).config.dm?.policy ?? "pairing", + setPolicy: (cfg, policy, accountId) => { + const resolvedAccountId = accountId ?? DEFAULT_ACCOUNT_ID; + const currentDm = resolveGoogleChatAccount({ + cfg, + accountId: resolvedAccountId, + }).config.dm; + return applySetupAccountConfigPatch({ + cfg, + channelKey: channel, + accountId: resolvedAccountId, + patch: { + dm: { + ...currentDm, + policy, + ...(policy === "open" + ? { allowFrom: addWildcardAllowFrom(currentDm?.allowFrom) } + : {}), + }, + }, + }); + }, promptAllowFrom, - enabled: true, -}); +}; export { googlechatSetupAdapter } from "./setup-core.js"; diff --git a/extensions/googlechat/src/setup.test.ts b/extensions/googlechat/src/setup.test.ts index 3fb330fa34e..daa82917dcb 100644 --- a/extensions/googlechat/src/setup.test.ts +++ b/extensions/googlechat/src/setup.test.ts @@ -161,6 +161,66 @@ describe("googlechat setup", () => { expect(result.cfg.channels?.googlechat?.audience).toBe("https://example.com/googlechat"); }); + it("reads the named-account DM policy instead of the channel root", () => { + expect( + googlechatPlugin.setupWizard?.dmPolicy?.getCurrent( + { + channels: { + googlechat: { + dm: { + policy: "disabled", + }, + accounts: { + alerts: { + serviceAccount: { client_email: "bot@example.com" }, + dm: { + policy: "allowlist", + }, + }, + }, + }, + }, + } as OpenClawConfig, + "alerts", + ), + ).toBe("allowlist"); + }); + + it("reports account-scoped config keys for named accounts", () => { + expect(googlechatPlugin.setupWizard?.dmPolicy?.resolveConfigKeys?.({}, "alerts")).toEqual({ + policyKey: "channels.googlechat.accounts.alerts.dm.policy", + allowFromKey: "channels.googlechat.accounts.alerts.dm.allowFrom", + }); + }); + + it('writes open DM policy to the named account and preserves inherited allowFrom with "*"', () => { + const next = googlechatPlugin.setupWizard?.dmPolicy?.setPolicy( + { + channels: { + googlechat: { + dm: { + allowFrom: ["users/123"], + }, + accounts: { + alerts: { + serviceAccount: { client_email: "bot@example.com" }, + }, + }, + }, + }, + } as OpenClawConfig, + "open", + "alerts", + ); + + expect(next?.channels?.googlechat?.dm?.policy).toBeUndefined(); + expect(next?.channels?.googlechat?.accounts?.alerts?.dm?.policy).toBe("open"); + expect(next?.channels?.googlechat?.accounts?.alerts?.dm?.allowFrom).toEqual([ + "users/123", + "*", + ]); + }); + it("keeps startAccount pending until abort, then unregisters", async () => { const unregister = vi.fn(); hoisted.startGoogleChatMonitor.mockResolvedValue(unregister); diff --git a/extensions/telegram/src/setup-surface.test.ts b/extensions/telegram/src/setup-surface.test.ts index 17eeb2c68e9..e14ce7db3da 100644 --- a/extensions/telegram/src/setup-surface.test.ts +++ b/extensions/telegram/src/setup-surface.test.ts @@ -162,6 +162,59 @@ describe("telegramSetupWizard.finalize", () => { }); }); +describe("telegramSetupWizard.dmPolicy", () => { + it("reads the named-account DM policy instead of the channel root", () => { + expect( + telegramSetupWizard.dmPolicy?.getCurrent( + { + channels: { + telegram: { + dmPolicy: "disabled", + accounts: { + alerts: { + dmPolicy: "allowlist", + botToken: "tok", + }, + }, + }, + }, + }, + "alerts", + ), + ).toBe("allowlist"); + }); + + it("reports account-scoped config keys for named accounts", () => { + expect(telegramSetupWizard.dmPolicy?.resolveConfigKeys?.({}, "alerts")).toEqual({ + policyKey: "channels.telegram.accounts.alerts.dmPolicy", + allowFromKey: "channels.telegram.accounts.alerts.allowFrom", + }); + }); + + it('writes open policy state to the named account and preserves inherited allowFrom with "*"', () => { + const next = telegramSetupWizard.dmPolicy?.setPolicy( + { + channels: { + telegram: { + allowFrom: ["123"], + accounts: { + alerts: { + botToken: "tok", + }, + }, + }, + }, + }, + "open", + "alerts", + ); + + expect(next?.channels?.telegram?.dmPolicy).toBeUndefined(); + expect(next?.channels?.telegram?.accounts?.alerts?.dmPolicy).toBe("open"); + expect(next?.channels?.telegram?.accounts?.alerts?.allowFrom).toEqual(["123", "*"]); + }); +}); + describe("resolveTelegramAllowFromEntries", () => { it("passes apiRoot through username lookups", async () => { const globalFetch = vi.fn(async () => { diff --git a/extensions/telegram/src/setup-surface.ts b/extensions/telegram/src/setup-surface.ts index d49fbf1b6eb..a91c4d4a801 100644 --- a/extensions/telegram/src/setup-surface.ts +++ b/extensions/telegram/src/setup-surface.ts @@ -1,7 +1,8 @@ import { + addWildcardAllowFrom, createAllowFromSection, - createTopLevelChannelDmPolicy, createStandardChannelSetupStatus, + type ChannelSetupDmPolicy, DEFAULT_ACCOUNT_ID, hasConfiguredSecretInput, type OpenClawConfig, @@ -76,14 +77,40 @@ function buildTelegramDmAccessWarningLines(accountId: string): string[] { ]; } -const dmPolicy = createTopLevelChannelDmPolicy({ +const dmPolicy: ChannelSetupDmPolicy = { label: "Telegram", channel, policyKey: "channels.telegram.dmPolicy", allowFromKey: "channels.telegram.allowFrom", - getCurrent: (cfg) => cfg.channels?.telegram?.dmPolicy ?? "pairing", + resolveConfigKeys: (_cfg, accountId) => + accountId && accountId !== DEFAULT_ACCOUNT_ID + ? { + policyKey: `channels.telegram.accounts.${accountId}.dmPolicy`, + allowFromKey: `channels.telegram.accounts.${accountId}.allowFrom`, + } + : { + policyKey: "channels.telegram.dmPolicy", + allowFromKey: "channels.telegram.allowFrom", + }, + getCurrent: (cfg, accountId) => + mergeTelegramAccountConfig(cfg, accountId ?? DEFAULT_ACCOUNT_ID).dmPolicy ?? "pairing", + setPolicy: (cfg, policy, accountId) => { + const resolvedAccountId = accountId ?? DEFAULT_ACCOUNT_ID; + const merged = mergeTelegramAccountConfig(cfg, resolvedAccountId); + return patchChannelConfigForAccount({ + cfg, + channel, + accountId: resolvedAccountId, + patch: { + dmPolicy: policy, + ...(policy === "open" + ? { allowFrom: addWildcardAllowFrom(merged.allowFrom) } + : {}), + }, + }); + }, promptAllowFrom: promptTelegramAllowFromForAccount, -}); +}; export const telegramSetupWizard: ChannelSetupWizard = { channel,