refactor: move group access into setup wizard

This commit is contained in:
Peter Steinberger
2026-03-15 19:52:17 -07:00
parent d8e138c743
commit 84c0326f4d
15 changed files with 305 additions and 217 deletions

View File

@@ -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({

View File

@@ -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<TResolved>(params: {
cfg: OpenClawConfig;
@@ -10,9 +10,10 @@ export async function configureChannelAccessWithAllowlist<TResolved>(params: {
currentEntries: string[];
placeholder: string;
updatePrompt: boolean;
skipAllowlistEntries?: boolean;
setPolicy: (cfg: OpenClawConfig, policy: ChannelAccessPolicy) => OpenClawConfig;
resolveAllowlist: (params: { cfg: OpenClawConfig; entries: string[] }) => Promise<TResolved>;
applyAllowlist: (params: { cfg: OpenClawConfig; resolved: TResolved }) => OpenClawConfig;
resolveAllowlist?: (params: { cfg: OpenClawConfig; entries: string[] }) => Promise<TResolved>;
applyAllowlist?: (params: { cfg: OpenClawConfig; resolved: TResolved }) => OpenClawConfig;
}): Promise<OpenClawConfig> {
let next = params.cfg;
const accessConfig = await promptChannelAccessConfig({
@@ -22,6 +23,7 @@ export async function configureChannelAccessWithAllowlist<TResolved>(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<TResolved>(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,

View File

@@ -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<boolean>;
@@ -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({

View File

@@ -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,

View File

@@ -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<WizardPrompter, "note">;
}) => Promise<unknown>;
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,
});
}

View File

@@ -27,7 +27,6 @@ export { resolveChannelMediaMaxBytes } from "../channels/plugins/media-limits.js
export {
addWildcardAllowFrom,
mergeAllowFromEntries,
promptAccountId,
splitOnboardingEntries,
setTopLevelChannelDmPolicyWithAllowFrom,
} from "../channels/plugins/onboarding/helpers.js";

View File

@@ -15,7 +15,6 @@ export {
} from "../channels/plugins/helpers.js";
export {
addWildcardAllowFrom,
promptAccountId,
setTopLevelChannelAllowFrom,
setTopLevelChannelDmPolicyWithAllowFrom,
} from "../channels/plugins/onboarding/helpers.js";

View File

@@ -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,

View File

@@ -15,7 +15,6 @@ export {
buildSingleChannelSecretPromptState,
addWildcardAllowFrom,
mergeAllowFromEntries,
promptAccountId,
promptSingleChannelSecretInput,
runSingleChannelSecretStep,
setTopLevelChannelDmPolicyWithAllowFrom,

View File

@@ -14,7 +14,6 @@ export { formatPairingApproveHint } from "../channels/plugins/helpers.js";
export {
addWildcardAllowFrom,
mergeAllowFromEntries,
promptAccountId,
setTopLevelChannelDmPolicyWithAllowFrom,
} from "../channels/plugins/onboarding/helpers.js";
export {