diff --git a/extensions/zalouser/src/setup-surface.test.ts b/extensions/zalouser/src/setup-surface.test.ts index c707e64bb4e..26a8483d8d6 100644 --- a/extensions/zalouser/src/setup-surface.test.ts +++ b/extensions/zalouser/src/setup-surface.test.ts @@ -7,6 +7,7 @@ import { import type { OpenClawConfig } from "../runtime-api.js"; import "./zalo-js.test-mocks.js"; import { zalouserPlugin } from "./channel.js"; +import { zalouserSetupWizard } from "./setup-surface.js"; const zalouserConfigure = createPluginSetupWizardConfigure(zalouserPlugin); @@ -221,4 +222,89 @@ describe("zalouser setup wizard", () => { expect(result.cfg.plugins?.entries?.zalouser?.enabled).toBe(true); expect(result.cfg.plugins?.allow).toEqual(["telegram", "zalouser"]); }); + + it("reads the named-account DM policy instead of the channel root", () => { + expect( + zalouserSetupWizard.dmPolicy?.getCurrent( + { + channels: { + zalouser: { + dmPolicy: "disabled", + accounts: { + work: { + profile: "work", + dmPolicy: "allowlist", + }, + }, + }, + }, + } as OpenClawConfig, + "work", + ), + ).toBe("allowlist"); + }); + + it("reports account-scoped config keys for named accounts", () => { + expect(zalouserSetupWizard.dmPolicy?.resolveConfigKeys?.({} as OpenClawConfig, "work")).toEqual( + { + policyKey: "channels.zalouser.accounts.work.dmPolicy", + allowFromKey: "channels.zalouser.accounts.work.allowFrom", + }, + ); + }); + + it('writes open policy state to the named account and preserves inherited allowFrom with "*"', () => { + const next = zalouserSetupWizard.dmPolicy?.setPolicy( + { + channels: { + zalouser: { + allowFrom: ["123456789"], + accounts: { + work: { + profile: "work", + }, + }, + }, + }, + } as OpenClawConfig, + "open", + "work", + ); + + expect(next?.channels?.zalouser?.dmPolicy).toBeUndefined(); + expect(next?.channels?.zalouser?.accounts?.work?.dmPolicy).toBe("open"); + expect(next?.channels?.zalouser?.accounts?.work?.allowFrom).toEqual(["123456789", "*"]); + }); + + it("shows the account-scoped current DM policy in quickstart notes", async () => { + const note = vi.fn(async (_message: string, _title?: string) => {}); + const prompter = createQuickstartPrompter({ note, dmPolicy: "pairing" }); + + await runSetupWizardConfigure({ + configure: zalouserConfigure, + cfg: { + channels: { + zalouser: { + dmPolicy: "disabled", + accounts: { + work: { + profile: "work", + dmPolicy: "allowlist", + allowFrom: ["123456789"], + }, + }, + }, + }, + } as OpenClawConfig, + prompter, + options: { quickstartDefaults: true }, + accountOverrides: { zalouser: "work" }, + }); + + expect( + note.mock.calls.some(([message]) => + String(message).includes("Current: dmPolicy=allowlist, allowFrom=123456789"), + ), + ).toBe(true); + }); }); diff --git a/extensions/zalouser/src/setup-surface.ts b/extensions/zalouser/src/setup-surface.ts index b8eb5bc022b..0cd0548132f 100644 --- a/extensions/zalouser/src/setup-surface.ts +++ b/extensions/zalouser/src/setup-surface.ts @@ -1,6 +1,5 @@ import { - createTopLevelChannelDmPolicy, - createTopLevelChannelDmPolicySetter, + addWildcardAllowFrom, DEFAULT_ACCOUNT_ID, formatCliCommand, formatDocsLink, @@ -30,9 +29,6 @@ import { } from "./zalo-js.js"; const channel = "zalouser" as const; -const setZalouserDmPolicy = createTopLevelChannelDmPolicySetter({ - channel, -}); const ZALOUSER_ALLOW_FROM_PLACEHOLDER = "Alice, 123456789, or leave empty to configure later"; const ZALOUSER_GROUPS_PLACEHOLDER = "Family, Work, 123456789, or leave empty for now"; const ZALOUSER_DM_ACCESS_TITLE = "Zalo Personal DM access"; @@ -61,6 +57,31 @@ function setZalouserAccountScopedConfig( }) as OpenClawConfig; } +function setZalouserDmPolicy( + cfg: OpenClawConfig, + accountId: string, + policy: DmPolicy, +): OpenClawConfig { + const resolvedAccountId = normalizeAccountId(accountId) ?? DEFAULT_ACCOUNT_ID; + const resolved = resolveZalouserAccountSync({ cfg, accountId: resolvedAccountId }); + return setZalouserAccountScopedConfig( + cfg, + resolvedAccountId, + { + dmPolicy: policy, + ...(policy === "open" + ? { allowFrom: addWildcardAllowFrom(resolved.config.allowFrom) } + : {}), + }, + { + dmPolicy: policy, + ...(policy === "open" + ? { allowFrom: addWildcardAllowFrom(resolved.config.allowFrom) } + : {}), + }, + ); +} + function setZalouserGroupPolicy( cfg: OpenClawConfig, accountId: string, @@ -189,12 +210,28 @@ async function promptZalouserAllowFrom(params: { } } -const zalouserDmPolicy: ChannelSetupDmPolicy = createTopLevelChannelDmPolicy({ +const zalouserDmPolicy: ChannelSetupDmPolicy = { label: "Zalo Personal", channel, policyKey: "channels.zalouser.dmPolicy", allowFromKey: "channels.zalouser.allowFrom", - getCurrent: (cfg) => (cfg.channels?.zalouser?.dmPolicy ?? "pairing") as DmPolicy, + resolveConfigKeys: (_cfg, accountId) => + accountId && accountId !== DEFAULT_ACCOUNT_ID + ? { + policyKey: `channels.zalouser.accounts.${accountId}.dmPolicy`, + allowFromKey: `channels.zalouser.accounts.${accountId}.allowFrom`, + } + : { + policyKey: "channels.zalouser.dmPolicy", + allowFromKey: "channels.zalouser.allowFrom", + }, + getCurrent: (cfg, accountId) => + resolveZalouserAccountSync({ + cfg, + accountId: accountId ?? DEFAULT_ACCOUNT_ID, + }).config.dmPolicy ?? "pairing", + setPolicy: (cfg, policy, accountId) => + setZalouserDmPolicy(cfg as OpenClawConfig, accountId ?? DEFAULT_ACCOUNT_ID, policy), promptAllowFrom: async ({ cfg, prompter, accountId }) => { const id = accountId && normalizeAccountId(accountId) @@ -206,7 +243,7 @@ const zalouserDmPolicy: ChannelSetupDmPolicy = createTopLevelChannelDmPolicy({ accountId: id, }); }, -}); +}; async function promptZalouserQuickstartDmPolicy(params: { cfg: OpenClawConfig; @@ -215,7 +252,7 @@ async function promptZalouserQuickstartDmPolicy(params: { }): Promise { const { cfg, prompter, accountId } = params; const resolved = resolveZalouserAccountSync({ cfg, accountId }); - const existingPolicy = (cfg.channels?.zalouser?.dmPolicy ?? "pairing") as DmPolicy; + const existingPolicy = resolved.config.dmPolicy ?? "pairing"; const existingAllowFrom = resolved.config.allowFrom ?? []; const existingLabel = existingAllowFrom.length > 0 ? existingAllowFrom.join(", ") : "unset"; @@ -251,7 +288,7 @@ async function promptZalouserQuickstartDmPolicy(params: { accountId, }); } - return setZalouserDmPolicy(cfg, policy); + return setZalouserDmPolicy(cfg, accountId, policy); } export { zalouserSetupAdapter } from "./setup-core.js";