diff --git a/extensions/discord/src/setup-core.ts b/extensions/discord/src/setup-core.ts index cec63dd01ec..f75a0312416 100644 --- a/extensions/discord/src/setup-core.ts +++ b/extensions/discord/src/setup-core.ts @@ -251,7 +251,7 @@ export function createDiscordSetupWizardProxy( prompter: { note: (message: string, title?: string) => Promise }; }) => { const wizard = (await loadWizard()).discordSetupWizard; - if (!wizard.groupAccess) { + if (!wizard.groupAccess?.resolveAllowlist) { return entries.map((input) => ({ input, resolved: false })); } try { diff --git a/extensions/matrix/src/setup-surface.ts b/extensions/matrix/src/setup-surface.ts index e01e0d57750..b475b6bf742 100644 --- a/extensions/matrix/src/setup-surface.ts +++ b/extensions/matrix/src/setup-surface.ts @@ -1,5 +1,4 @@ import type { ChannelOnboardingDmPolicy } from "../../../src/channels/plugins/onboarding-types.js"; -import { promptChannelAccessConfig } from "../../../src/channels/plugins/onboarding/channel-access.js"; import { addWildcardAllowFrom, buildSingleChannelSecretPromptState, @@ -171,6 +170,78 @@ function setMatrixGroupRooms(cfg: CoreConfig, roomKeys: string[]) { }; } +async function resolveMatrixGroupRooms(params: { + cfg: CoreConfig; + entries: string[]; + prompter: Pick; +}): Promise { + if (params.entries.length === 0) { + return []; + } + try { + const resolvedIds: string[] = []; + const unresolved: string[] = []; + for (const entry of params.entries) { + const trimmed = entry.trim(); + if (!trimmed) { + continue; + } + const cleaned = trimmed.replace(/^(room|channel):/i, "").trim(); + if (cleaned.startsWith("!") && cleaned.includes(":")) { + resolvedIds.push(cleaned); + continue; + } + const matches = await listMatrixDirectoryGroupsLive({ + cfg: params.cfg, + query: trimmed, + limit: 10, + }); + const exact = matches.find( + (match) => (match.name ?? "").toLowerCase() === trimmed.toLowerCase(), + ); + const best = exact ?? matches[0]; + if (best?.id) { + resolvedIds.push(best.id); + } else { + unresolved.push(entry); + } + } + const roomKeys = [...resolvedIds, ...unresolved.map((entry) => entry.trim()).filter(Boolean)]; + const resolution = formatResolvedUnresolvedNote({ + resolved: resolvedIds, + unresolved, + }); + if (resolution) { + await params.prompter.note(resolution, "Matrix rooms"); + } + return roomKeys; + } catch (err) { + await params.prompter.note( + `Room lookup failed; keeping entries as typed. ${String(err)}`, + "Matrix rooms", + ); + return params.entries.map((entry) => entry.trim()).filter(Boolean); + } +} + +const matrixGroupAccess: NonNullable = { + label: "Matrix rooms", + placeholder: "!roomId:server, #alias:server, Project Room", + currentPolicy: ({ cfg }) => cfg.channels?.matrix?.groupPolicy ?? "allowlist", + currentEntries: ({ cfg }) => + Object.keys(cfg.channels?.matrix?.groups ?? cfg.channels?.matrix?.rooms ?? {}), + updatePrompt: ({ cfg }) => Boolean(cfg.channels?.matrix?.groups ?? cfg.channels?.matrix?.rooms), + setPolicy: ({ cfg, policy }) => setMatrixGroupPolicy(cfg as CoreConfig, policy), + resolveAllowlist: async ({ cfg, entries, prompter }) => + await resolveMatrixGroupRooms({ + cfg: cfg as CoreConfig, + entries, + prompter, + }), + applyAllowlist: ({ cfg, resolved }) => + setMatrixGroupRooms(cfg as CoreConfig, resolved as string[]), +}; + const matrixDmPolicy: ChannelOnboardingDmPolicy = { label: "Matrix", channel, @@ -386,72 +457,10 @@ export const matrixSetupWizard: ChannelSetupWizard = { next = await promptMatrixAllowFrom({ cfg: next, prompter }); } - const existingGroups = next.channels?.matrix?.groups ?? next.channels?.matrix?.rooms; - const accessConfig = await promptChannelAccessConfig({ - prompter, - label: "Matrix rooms", - currentPolicy: next.channels?.matrix?.groupPolicy ?? "allowlist", - currentEntries: Object.keys(existingGroups ?? {}), - placeholder: "!roomId:server, #alias:server, Project Room", - updatePrompt: Boolean(existingGroups), - }); - if (accessConfig) { - if (accessConfig.policy !== "allowlist") { - next = setMatrixGroupPolicy(next, accessConfig.policy); - } else { - let roomKeys = accessConfig.entries; - if (accessConfig.entries.length > 0) { - try { - const resolvedIds: string[] = []; - const unresolved: string[] = []; - for (const entry of accessConfig.entries) { - const trimmed = entry.trim(); - if (!trimmed) { - continue; - } - const cleaned = trimmed.replace(/^(room|channel):/i, "").trim(); - if (cleaned.startsWith("!") && cleaned.includes(":")) { - resolvedIds.push(cleaned); - continue; - } - const matches = await listMatrixDirectoryGroupsLive({ - cfg: next, - query: trimmed, - limit: 10, - }); - const exact = matches.find( - (match) => (match.name ?? "").toLowerCase() === trimmed.toLowerCase(), - ); - const best = exact ?? matches[0]; - if (best?.id) { - resolvedIds.push(best.id); - } else { - unresolved.push(entry); - } - } - roomKeys = [...resolvedIds, ...unresolved.map((entry) => entry.trim()).filter(Boolean)]; - const resolution = formatResolvedUnresolvedNote({ - resolved: resolvedIds, - unresolved, - }); - if (resolution) { - await prompter.note(resolution, "Matrix rooms"); - } - } catch (err) { - await prompter.note( - `Room lookup failed; keeping entries as typed. ${String(err)}`, - "Matrix rooms", - ); - } - } - next = setMatrixGroupPolicy(next, "allowlist"); - next = setMatrixGroupRooms(next, roomKeys); - } - } - return { cfg: next }; }, dmPolicy: matrixDmPolicy, + groupAccess: matrixGroupAccess, disable: (cfg) => ({ ...(cfg as CoreConfig), channels: { diff --git a/extensions/msteams/src/setup-surface.ts b/extensions/msteams/src/setup-surface.ts index f8db90e5079..9e39a24563e 100644 --- a/extensions/msteams/src/setup-surface.ts +++ b/extensions/msteams/src/setup-surface.ts @@ -1,5 +1,4 @@ import type { ChannelOnboardingDmPolicy } from "../../../src/channels/plugins/onboarding-types.js"; -import { promptChannelAccessConfig } from "../../../src/channels/plugins/onboarding/channel-access.js"; import { mergeAllowFromEntries, setTopLevelChannelAllowFrom, @@ -191,6 +190,96 @@ function setMSTeamsTeamsAllowlist( }; } +function listMSTeamsGroupEntries(cfg: OpenClawConfig): string[] { + return Object.entries(cfg.channels?.msteams?.teams ?? {}).flatMap(([teamKey, value]) => { + const channels = value?.channels ?? {}; + const channelKeys = Object.keys(channels); + if (channelKeys.length === 0) { + return [teamKey]; + } + return channelKeys.map((channelKey) => `${teamKey}/${channelKey}`); + }); +} + +async function resolveMSTeamsGroupAllowlist(params: { + cfg: OpenClawConfig; + entries: string[]; + prompter: Pick; +}): Promise> { + let resolvedEntries = params.entries + .map((entry) => parseMSTeamsTeamEntry(entry)) + .filter(Boolean) as Array<{ teamKey: string; channelKey?: string }>; + if (params.entries.length === 0 || !resolveMSTeamsCredentials(params.cfg.channels?.msteams)) { + return resolvedEntries; + } + try { + const lookups = await resolveMSTeamsChannelAllowlist({ + cfg: params.cfg, + entries: params.entries, + }); + const resolvedChannels = lookups.filter( + (entry) => entry.resolved && entry.teamId && entry.channelId, + ); + const resolvedTeams = lookups.filter( + (entry) => entry.resolved && entry.teamId && !entry.channelId, + ); + const unresolved = lookups.filter((entry) => !entry.resolved).map((entry) => entry.input); + resolvedEntries = [ + ...resolvedChannels.map((entry) => ({ + teamKey: entry.teamId as string, + channelKey: entry.channelId as string, + })), + ...resolvedTeams.map((entry) => ({ + teamKey: entry.teamId as string, + })), + ...unresolved.map((entry) => parseMSTeamsTeamEntry(entry)).filter(Boolean), + ] as Array<{ teamKey: string; channelKey?: string }>; + const summary: string[] = []; + if (resolvedChannels.length > 0) { + summary.push( + `Resolved channels: ${resolvedChannels + .map((entry) => entry.channelId) + .filter(Boolean) + .join(", ")}`, + ); + } + if (resolvedTeams.length > 0) { + summary.push( + `Resolved teams: ${resolvedTeams + .map((entry) => entry.teamId) + .filter(Boolean) + .join(", ")}`, + ); + } + if (unresolved.length > 0) { + summary.push(`Unresolved (kept as typed): ${unresolved.join(", ")}`); + } + if (summary.length > 0) { + await params.prompter.note(summary.join("\n"), "MS Teams channels"); + } + return resolvedEntries; + } catch (err) { + await params.prompter.note( + `Channel lookup failed; keeping entries as typed. ${String(err)}`, + "MS Teams channels", + ); + return resolvedEntries; + } +} + +const msteamsGroupAccess: NonNullable = { + label: "MS Teams channels", + placeholder: "Team Name/Channel Name, teamId/conversationId", + currentPolicy: ({ cfg }) => cfg.channels?.msteams?.groupPolicy ?? "allowlist", + currentEntries: ({ cfg }) => listMSTeamsGroupEntries(cfg), + updatePrompt: ({ cfg }) => Boolean(cfg.channels?.msteams?.teams), + setPolicy: ({ cfg, policy }) => setMSTeamsGroupPolicy(cfg, policy), + resolveAllowlist: async ({ cfg, entries, prompter }) => + await resolveMSTeamsGroupAllowlist({ cfg, entries, prompter }), + applyAllowlist: ({ cfg, resolved }) => + setMSTeamsTeamsAllowlist(cfg, resolved as Array<{ teamKey: string; channelKey?: string }>), +}; + const msteamsDmPolicy: ChannelOnboardingDmPolicy = { label: "MS Teams", channel, @@ -290,96 +379,10 @@ export const msteamsSetupWizard: ChannelSetupWizard = { }; } - const currentEntries = Object.entries(next.channels?.msteams?.teams ?? {}).flatMap( - ([teamKey, value]) => { - const channels = value?.channels ?? {}; - const channelKeys = Object.keys(channels); - if (channelKeys.length === 0) { - return [teamKey]; - } - return channelKeys.map((channelKey) => `${teamKey}/${channelKey}`); - }, - ); - const accessConfig = await promptChannelAccessConfig({ - prompter, - label: "MS Teams channels", - currentPolicy: next.channels?.msteams?.groupPolicy ?? "allowlist", - currentEntries, - placeholder: "Team Name/Channel Name, teamId/conversationId", - updatePrompt: Boolean(next.channels?.msteams?.teams), - }); - if (accessConfig) { - if (accessConfig.policy !== "allowlist") { - next = setMSTeamsGroupPolicy(next, accessConfig.policy); - } else { - let entries = accessConfig.entries - .map((entry) => parseMSTeamsTeamEntry(entry)) - .filter(Boolean) as Array<{ teamKey: string; channelKey?: string }>; - if (accessConfig.entries.length > 0 && resolveMSTeamsCredentials(next.channels?.msteams)) { - try { - const resolvedEntries = await resolveMSTeamsChannelAllowlist({ - cfg: next, - entries: accessConfig.entries, - }); - const resolvedChannels = resolvedEntries.filter( - (entry) => entry.resolved && entry.teamId && entry.channelId, - ); - const resolvedTeams = resolvedEntries.filter( - (entry) => entry.resolved && entry.teamId && !entry.channelId, - ); - const unresolved = resolvedEntries - .filter((entry) => !entry.resolved) - .map((entry) => entry.input); - - entries = [ - ...resolvedChannels.map((entry) => ({ - teamKey: entry.teamId as string, - channelKey: entry.channelId as string, - })), - ...resolvedTeams.map((entry) => ({ - teamKey: entry.teamId as string, - })), - ...unresolved.map((entry) => parseMSTeamsTeamEntry(entry)).filter(Boolean), - ] as Array<{ teamKey: string; channelKey?: string }>; - - if (resolvedChannels.length > 0 || resolvedTeams.length > 0 || unresolved.length > 0) { - const summary: string[] = []; - if (resolvedChannels.length > 0) { - summary.push( - `Resolved channels: ${resolvedChannels - .map((entry) => entry.channelId) - .filter(Boolean) - .join(", ")}`, - ); - } - if (resolvedTeams.length > 0) { - summary.push( - `Resolved teams: ${resolvedTeams - .map((entry) => entry.teamId) - .filter(Boolean) - .join(", ")}`, - ); - } - if (unresolved.length > 0) { - summary.push(`Unresolved (kept as typed): ${unresolved.join(", ")}`); - } - await prompter.note(summary.join("\n"), "MS Teams channels"); - } - } catch (err) { - await prompter.note( - `Channel lookup failed; keeping entries as typed. ${String(err)}`, - "MS Teams channels", - ); - } - } - next = setMSTeamsGroupPolicy(next, "allowlist"); - next = setMSTeamsTeamsAllowlist(next, entries); - } - } - return { cfg: next, accountId: DEFAULT_ACCOUNT_ID }; }, dmPolicy: msteamsDmPolicy, + groupAccess: msteamsGroupAccess, disable: (cfg) => ({ ...cfg, channels: { diff --git a/extensions/slack/src/setup-core.ts b/extensions/slack/src/setup-core.ts index 0cf7903e6d4..c30f0134009 100644 --- a/extensions/slack/src/setup-core.ts +++ b/extensions/slack/src/setup-core.ts @@ -455,7 +455,7 @@ export function createSlackSetupWizardProxy( }) => { try { const wizard = (await loadWizard()).slackSetupWizard; - if (!wizard.groupAccess) { + if (!wizard.groupAccess?.resolveAllowlist) { return entries; } return await wizard.groupAccess.resolveAllowlist({ diff --git a/extensions/twitch/src/setup-surface.ts b/extensions/twitch/src/setup-surface.ts index 776644a2d23..bff81f47fff 100644 --- a/extensions/twitch/src/setup-surface.ts +++ b/extensions/twitch/src/setup-surface.ts @@ -3,7 +3,6 @@ */ import type { ChannelOnboardingDmPolicy } from "../../../src/channels/plugins/onboarding-types.js"; -import { promptChannelAccessConfig } from "../../../src/channels/plugins/onboarding/channel-access.js"; import type { ChannelSetupWizard } from "../../../src/channels/plugins/setup-wizard.js"; import type { ChannelSetupAdapter } from "../../../src/channels/plugins/types.adapters.js"; import type { OpenClawConfig } from "../../../src/config/config.js"; @@ -228,6 +227,26 @@ function setTwitchAccessControl( }); } +function resolveTwitchGroupPolicy(cfg: OpenClawConfig): "open" | "allowlist" | "disabled" { + const account = getAccountConfig(cfg, DEFAULT_ACCOUNT_ID); + if (account?.allowedRoles?.includes("all")) { + return "open"; + } + if (account?.allowedRoles?.includes("moderator")) { + return "allowlist"; + } + return "disabled"; +} + +function setTwitchGroupPolicy( + cfg: OpenClawConfig, + policy: "open" | "allowlist" | "disabled", +): OpenClawConfig { + const allowedRoles: TwitchRole[] = + policy === "open" ? ["all"] : policy === "allowlist" ? ["moderator", "vip"] : []; + return setTwitchAccessControl(cfg, allowedRoles, true); +} + const twitchDmPolicy: ChannelOnboardingDmPolicy = { label: "Twitch", channel, @@ -270,6 +289,24 @@ const twitchDmPolicy: ChannelOnboardingDmPolicy = { }, }; +const twitchGroupAccess: NonNullable = { + label: "Twitch chat", + placeholder: "", + skipAllowlistEntries: true, + currentPolicy: ({ cfg }) => resolveTwitchGroupPolicy(cfg as OpenClawConfig), + currentEntries: ({ cfg }) => { + const account = getAccountConfig(cfg as OpenClawConfig, DEFAULT_ACCOUNT_ID); + return account?.allowFrom ?? []; + }, + updatePrompt: ({ cfg }) => { + const account = getAccountConfig(cfg as OpenClawConfig, DEFAULT_ACCOUNT_ID); + return Boolean(account?.allowedRoles?.length || account?.allowFrom?.length); + }, + setPolicy: ({ cfg, policy }) => setTwitchGroupPolicy(cfg as OpenClawConfig, policy), + resolveAllowlist: async () => [], + applyAllowlist: ({ cfg }) => cfg as OpenClawConfig, +}; + export const twitchSetupAdapter: ChannelSetupAdapter = { resolveAccountId: () => DEFAULT_ACCOUNT_ID, applyAccountConfig: ({ cfg }) => @@ -342,37 +379,10 @@ export const twitchSetupWizard: ChannelSetupWizard = { ? await twitchDmPolicy.promptAllowFrom({ cfg: cfgWithAccount, prompter }) : cfgWithAccount; - if (!account?.allowFrom || account.allowFrom.length === 0) { - const accessConfig = await promptChannelAccessConfig({ - prompter, - label: "Twitch chat", - currentPolicy: account?.allowedRoles?.includes("all") - ? "open" - : account?.allowedRoles?.includes("moderator") - ? "allowlist" - : "disabled", - currentEntries: [], - placeholder: "", - updatePrompt: false, - }); - - if (accessConfig) { - const allowedRoles: TwitchRole[] = - accessConfig.policy === "open" - ? ["all"] - : accessConfig.policy === "allowlist" - ? ["moderator", "vip"] - : []; - - return { - cfg: setTwitchAccessControl(cfgWithAllowFrom, allowedRoles, true), - }; - } - } - return { cfg: cfgWithAllowFrom }; }, dmPolicy: twitchDmPolicy, + groupAccess: twitchGroupAccess, disable: (cfg) => { const twitch = (cfg.channels as Record)?.twitch as | Record diff --git a/src/channels/plugins/onboarding/channel-access-configure.test.ts b/src/channels/plugins/setup-group-access-configure.test.ts similarity index 77% rename from src/channels/plugins/onboarding/channel-access-configure.test.ts rename to src/channels/plugins/setup-group-access-configure.test.ts index aba8f05ea95..bb3b0307501 100644 --- a/src/channels/plugins/onboarding/channel-access-configure.test.ts +++ b/src/channels/plugins/setup-group-access-configure.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it, vi } from "vitest"; -import type { OpenClawConfig } from "../../../config/config.js"; -import { configureChannelAccessWithAllowlist } from "./channel-access-configure.js"; -import type { ChannelAccessPolicy } from "./channel-access.js"; +import type { OpenClawConfig } from "../../config/config.js"; +import { configureChannelAccessWithAllowlist } from "./setup-group-access-configure.js"; +import type { ChannelAccessPolicy } from "./setup-group-access.js"; function createPrompter(params: { confirm: boolean; policy?: ChannelAccessPolicy; text?: string }) { return { @@ -89,6 +89,41 @@ describe("configureChannelAccessWithAllowlist", () => { expect(applyAllowlist).not.toHaveBeenCalled(); }); + it("supports allowlist policies without prompting for entries", async () => { + const cfg: OpenClawConfig = {}; + const prompter = createPrompter({ + confirm: true, + policy: "allowlist", + }); + const setPolicy = vi.fn( + (next: OpenClawConfig, policy: ChannelAccessPolicy): OpenClawConfig => ({ + ...next, + channels: { twitch: { groupPolicy: policy } }, + }), + ); + const resolveAllowlist = vi.fn(async () => ["ignored"]); + const applyAllowlist = vi.fn((params: { cfg: OpenClawConfig }) => params.cfg); + + const next = await configureChannelAccessWithAllowlist({ + cfg, + // oxlint-disable-next-line typescript/no-explicit-any + prompter: prompter as any, + label: "Twitch chat", + currentPolicy: "disabled", + currentEntries: [], + placeholder: "", + updatePrompt: false, + skipAllowlistEntries: true, + setPolicy, + resolveAllowlist, + applyAllowlist, + }); + + expect(next.channels).toEqual({ twitch: { groupPolicy: "allowlist" } }); + expect(resolveAllowlist).not.toHaveBeenCalled(); + expect(applyAllowlist).not.toHaveBeenCalled(); + }); + it("resolves allowlist entries and applies them after forcing allowlist policy", async () => { const cfg: OpenClawConfig = {}; const prompter = createPrompter({ diff --git a/src/channels/plugins/onboarding/channel-access-configure.ts b/src/channels/plugins/setup-group-access-configure.ts similarity index 65% rename from src/channels/plugins/onboarding/channel-access-configure.ts rename to src/channels/plugins/setup-group-access-configure.ts index 200efce5811..26b07f9cf99 100644 --- a/src/channels/plugins/onboarding/channel-access-configure.ts +++ b/src/channels/plugins/setup-group-access-configure.ts @@ -1,6 +1,6 @@ -import type { OpenClawConfig } from "../../../config/config.js"; -import type { WizardPrompter } from "../../../wizard/prompts.js"; -import { promptChannelAccessConfig, type ChannelAccessPolicy } from "./channel-access.js"; +import type { OpenClawConfig } from "../../config/config.js"; +import type { WizardPrompter } from "../../wizard/prompts.js"; +import { promptChannelAccessConfig, type ChannelAccessPolicy } from "./setup-group-access.js"; export async function configureChannelAccessWithAllowlist(params: { cfg: OpenClawConfig; @@ -10,9 +10,10 @@ export async function configureChannelAccessWithAllowlist(params: { currentEntries: string[]; placeholder: string; updatePrompt: boolean; + skipAllowlistEntries?: boolean; setPolicy: (cfg: OpenClawConfig, policy: ChannelAccessPolicy) => OpenClawConfig; - resolveAllowlist: (params: { cfg: OpenClawConfig; entries: string[] }) => Promise; - applyAllowlist: (params: { cfg: OpenClawConfig; resolved: TResolved }) => OpenClawConfig; + resolveAllowlist?: (params: { cfg: OpenClawConfig; entries: string[] }) => Promise; + applyAllowlist?: (params: { cfg: OpenClawConfig; resolved: TResolved }) => OpenClawConfig; }): Promise { let next = params.cfg; const accessConfig = await promptChannelAccessConfig({ @@ -22,6 +23,7 @@ export async function configureChannelAccessWithAllowlist(params: { currentEntries: params.currentEntries, placeholder: params.placeholder, updatePrompt: params.updatePrompt, + skipAllowlistEntries: params.skipAllowlistEntries, }); if (!accessConfig) { return next; @@ -29,6 +31,9 @@ export async function configureChannelAccessWithAllowlist(params: { if (accessConfig.policy !== "allowlist") { return params.setPolicy(next, accessConfig.policy); } + if (params.skipAllowlistEntries || !params.resolveAllowlist || !params.applyAllowlist) { + return params.setPolicy(next, "allowlist"); + } const resolved = await params.resolveAllowlist({ cfg: next, entries: accessConfig.entries, diff --git a/src/channels/plugins/onboarding/channel-access.test.ts b/src/channels/plugins/setup-group-access.test.ts similarity index 84% rename from src/channels/plugins/onboarding/channel-access.test.ts rename to src/channels/plugins/setup-group-access.test.ts index 0e5b2ba6651..a19ed348015 100644 --- a/src/channels/plugins/onboarding/channel-access.test.ts +++ b/src/channels/plugins/setup-group-access.test.ts @@ -5,7 +5,7 @@ import { promptChannelAccessConfig, promptChannelAllowlist, promptChannelAccessPolicy, -} from "./channel-access.js"; +} from "./setup-group-access.js"; function createPrompter(params?: { confirm?: (options: { message: string; initialValue: boolean }) => Promise; @@ -83,6 +83,27 @@ describe("promptChannelAccessPolicy", () => { }); }); +describe("promptChannelAccessConfig", () => { + it("skips the allowlist text prompt when entries are policy-only", async () => { + const prompter = createPrompter({ + confirm: async () => true, + select: async () => "allowlist", + text: async () => { + throw new Error("text prompt should not run"); + }, + }); + + const result = await promptChannelAccessConfig({ + // oxlint-disable-next-line typescript/no-explicit-any + prompter: prompter as any, + label: "Twitch chat", + skipAllowlistEntries: true, + }); + + expect(result).toEqual({ policy: "allowlist", entries: [] }); + }); +}); + describe("promptChannelAccessConfig", () => { it("returns null when user skips configuration", async () => { const prompter = createPrompter({ diff --git a/src/channels/plugins/onboarding/channel-access.ts b/src/channels/plugins/setup-group-access.ts similarity index 92% rename from src/channels/plugins/onboarding/channel-access.ts rename to src/channels/plugins/setup-group-access.ts index ef86b37f336..a757816e9ec 100644 --- a/src/channels/plugins/onboarding/channel-access.ts +++ b/src/channels/plugins/setup-group-access.ts @@ -1,5 +1,5 @@ -import type { WizardPrompter } from "../../../wizard/prompts.js"; -import { splitOnboardingEntries } from "./helpers.js"; +import type { WizardPrompter } from "../../wizard/prompts.js"; +import { splitOnboardingEntries } from "./onboarding/helpers.js"; export type ChannelAccessPolicy = "allowlist" | "open" | "disabled"; @@ -64,6 +64,7 @@ export async function promptChannelAccessConfig(params: { placeholder?: string; allowOpen?: boolean; allowDisabled?: boolean; + skipAllowlistEntries?: boolean; defaultPrompt?: boolean; updatePrompt?: boolean; }): Promise<{ policy: ChannelAccessPolicy; entries: string[] } | null> { @@ -88,6 +89,9 @@ export async function promptChannelAccessConfig(params: { if (policy !== "allowlist") { return { policy, entries: [] }; } + if (params.skipAllowlistEntries) { + return { policy, entries: [] }; + } const entries = await promptChannelAllowlist({ prompter: params.prompter, label: params.label, diff --git a/src/channels/plugins/setup-wizard.ts b/src/channels/plugins/setup-wizard.ts index 9f4f1fdb5cc..2d4896dd733 100644 --- a/src/channels/plugins/setup-wizard.ts +++ b/src/channels/plugins/setup-wizard.ts @@ -8,14 +8,14 @@ import type { ChannelOnboardingStatus, ChannelOnboardingStatusContext, } from "./onboarding-types.js"; -import { configureChannelAccessWithAllowlist } from "./onboarding/channel-access-configure.js"; -import type { ChannelAccessPolicy } from "./onboarding/channel-access.js"; import { promptResolvedAllowFrom, resolveAccountIdForConfigure, runSingleChannelSecretStep, splitOnboardingEntries, } from "./onboarding/helpers.js"; +import { configureChannelAccessWithAllowlist } from "./setup-group-access-configure.js"; +import type { ChannelAccessPolicy } from "./setup-group-access.js"; import type { ChannelSetupInput } from "./types.core.js"; import type { ChannelPlugin } from "./types.js"; @@ -184,6 +184,7 @@ export type ChannelSetupWizardGroupAccess = { placeholder: string; helpTitle?: string; helpLines?: string[]; + skipAllowlistEntries?: boolean; currentPolicy: (params: { cfg: OpenClawConfig; accountId: string }) => ChannelAccessPolicy; currentEntries: (params: { cfg: OpenClawConfig; accountId: string }) => string[]; updatePrompt: (params: { cfg: OpenClawConfig; accountId: string }) => boolean; @@ -192,14 +193,14 @@ export type ChannelSetupWizardGroupAccess = { accountId: string; policy: ChannelAccessPolicy; }) => OpenClawConfig; - resolveAllowlist: (params: { + resolveAllowlist?: (params: { cfg: OpenClawConfig; accountId: string; credentialValues: ChannelSetupWizardCredentialValues; entries: string[]; prompter: Pick; }) => Promise; - applyAllowlist: (params: { + applyAllowlist?: (params: { cfg: OpenClawConfig; accountId: string; resolved: unknown; @@ -757,26 +758,31 @@ export function buildChannelOnboardingAdapterFromSetupWizard(params: { currentEntries: access.currentEntries({ cfg: next, accountId }), placeholder: access.placeholder, updatePrompt: access.updatePrompt({ cfg: next, accountId }), + skipAllowlistEntries: access.skipAllowlistEntries, setPolicy: (currentCfg, policy) => access.setPolicy({ cfg: currentCfg, accountId, policy, }), - resolveAllowlist: async ({ cfg: currentCfg, entries }) => - await access.resolveAllowlist({ - cfg: currentCfg, - accountId, - credentialValues, - entries, - prompter, - }), - applyAllowlist: ({ cfg: currentCfg, resolved }) => - access.applyAllowlist({ - cfg: currentCfg, - accountId, - resolved, - }), + resolveAllowlist: access.resolveAllowlist + ? async ({ cfg: currentCfg, entries }) => + await access.resolveAllowlist!({ + cfg: currentCfg, + accountId, + credentialValues, + entries, + prompter, + }) + : undefined, + applyAllowlist: access.applyAllowlist + ? ({ cfg: currentCfg, resolved }) => + access.applyAllowlist!({ + cfg: currentCfg, + accountId, + resolved, + }) + : undefined, }); } diff --git a/src/plugin-sdk/googlechat.ts b/src/plugin-sdk/googlechat.ts index 464af58776b..42ad2eb032f 100644 --- a/src/plugin-sdk/googlechat.ts +++ b/src/plugin-sdk/googlechat.ts @@ -27,7 +27,6 @@ export { resolveChannelMediaMaxBytes } from "../channels/plugins/media-limits.js export { addWildcardAllowFrom, mergeAllowFromEntries, - promptAccountId, splitOnboardingEntries, setTopLevelChannelDmPolicyWithAllowFrom, } from "../channels/plugins/onboarding/helpers.js"; diff --git a/src/plugin-sdk/irc.ts b/src/plugin-sdk/irc.ts index 472c46ea2e5..c74aab071ca 100644 --- a/src/plugin-sdk/irc.ts +++ b/src/plugin-sdk/irc.ts @@ -15,7 +15,6 @@ export { } from "../channels/plugins/helpers.js"; export { addWildcardAllowFrom, - promptAccountId, setTopLevelChannelAllowFrom, setTopLevelChannelDmPolicyWithAllowFrom, } from "../channels/plugins/onboarding/helpers.js"; diff --git a/src/plugin-sdk/tlon.ts b/src/plugin-sdk/tlon.ts index f1415103398..291834b9648 100644 --- a/src/plugin-sdk/tlon.ts +++ b/src/plugin-sdk/tlon.ts @@ -3,7 +3,6 @@ export type { ReplyPayload } from "../auto-reply/types.js"; export { buildChannelConfigSchema } from "../channels/plugins/config-schema.js"; -export { promptAccountId } from "../channels/plugins/onboarding/helpers.js"; export { applyAccountNameToChannelSection, patchScopedAccountConfig, diff --git a/src/plugin-sdk/zalo.ts b/src/plugin-sdk/zalo.ts index 307ea5f16f5..9f680ce6b0e 100644 --- a/src/plugin-sdk/zalo.ts +++ b/src/plugin-sdk/zalo.ts @@ -15,7 +15,6 @@ export { buildSingleChannelSecretPromptState, addWildcardAllowFrom, mergeAllowFromEntries, - promptAccountId, promptSingleChannelSecretInput, runSingleChannelSecretStep, setTopLevelChannelDmPolicyWithAllowFrom, diff --git a/src/plugin-sdk/zalouser.ts b/src/plugin-sdk/zalouser.ts index 3ad3ca47549..5dba9c0aa77 100644 --- a/src/plugin-sdk/zalouser.ts +++ b/src/plugin-sdk/zalouser.ts @@ -14,7 +14,6 @@ export { formatPairingApproveHint } from "../channels/plugins/helpers.js"; export { addWildcardAllowFrom, mergeAllowFromEntries, - promptAccountId, setTopLevelChannelDmPolicyWithAllowFrom, } from "../channels/plugins/onboarding/helpers.js"; export {