refactor: share parsed channel allowlist prompts

This commit is contained in:
Peter Steinberger
2026-03-23 01:45:58 +00:00
parent 7d032ed38c
commit 583bea001c
9 changed files with 297 additions and 147 deletions

View File

@@ -12,6 +12,8 @@ import {
createAccountScopedGroupAccessSection,
createAllowFromSection,
createLegacyCompatChannelDmPolicy,
createNestedChannelParsedAllowFromPrompt,
createPromptParsedAllowFromForAccount,
createNestedChannelAllowFromSetter,
createNestedChannelDmPolicy,
createNestedChannelDmPolicySetter,
@@ -19,6 +21,7 @@ import {
createTopLevelChannelDmPolicy,
createTopLevelChannelDmPolicySetter,
createTopLevelChannelGroupPolicySetter,
createTopLevelChannelParsedAllowFromPrompt,
normalizeAllowFromEntries,
noteChannelLookupFailure,
noteChannelLookupSummary,
@@ -659,6 +662,91 @@ describe("promptParsedAllowFromForAccount", () => {
});
});
describe("createPromptParsedAllowFromForAccount", () => {
it("supports computed default account ids and optional notes", async () => {
const promptAllowFrom = createPromptParsedAllowFromForAccount<OpenClawConfig>({
defaultAccountId: () => "work",
message: "msg",
placeholder: "placeholder",
parseEntries: (raw) => ({ entries: [raw.trim().toLowerCase()] }),
getExistingAllowFrom: ({ cfg, accountId }) =>
cfg.channels?.bluebubbles?.accounts?.[accountId]?.allowFrom ?? [],
applyAllowFrom: ({ cfg, accountId, allowFrom }) =>
patchChannelConfigForAccount({
cfg,
channel: "bluebubbles",
accountId,
patch: { allowFrom },
}),
});
const prompter = createPrompter(["Alice"]);
const next = await promptAllowFrom({
cfg: {
channels: {
bluebubbles: {
accounts: {
work: {
allowFrom: ["old"],
},
},
},
},
},
// oxlint-disable-next-line typescript/no-explicit-any
prompter: prompter as any,
});
expect(next.channels?.bluebubbles?.accounts?.work?.allowFrom).toEqual(["alice"]);
expect(prompter.note).not.toHaveBeenCalled();
});
});
describe("parsed allowFrom prompt builders", () => {
it("builds a top-level parsed allowFrom prompt", async () => {
const promptAllowFrom = createTopLevelChannelParsedAllowFromPrompt({
channel: "nostr",
defaultAccountId: DEFAULT_ACCOUNT_ID,
noteTitle: "Nostr allowlist",
noteLines: ["line"],
message: "msg",
placeholder: "placeholder",
parseEntries: (raw) => ({ entries: [raw.trim().toLowerCase()] }),
});
const prompter = createPrompter(["npub1"]);
const next = await promptAllowFrom({
cfg: {},
// oxlint-disable-next-line typescript/no-explicit-any
prompter: prompter as any,
});
expect(next.channels?.nostr?.allowFrom).toEqual(["npub1"]);
expect(prompter.note).toHaveBeenCalledWith("line", "Nostr allowlist");
});
it("builds a nested parsed allowFrom prompt", async () => {
const promptAllowFrom = createNestedChannelParsedAllowFromPrompt({
channel: "googlechat",
section: "dm",
defaultAccountId: DEFAULT_ACCOUNT_ID,
enabled: true,
message: "msg",
placeholder: "placeholder",
parseEntries: (raw) => ({ entries: [raw.trim()] }),
});
const next = await promptAllowFrom({
cfg: {},
// oxlint-disable-next-line typescript/no-explicit-any
prompter: createPrompter(["users/123"]) as any,
});
expect(next.channels?.googlechat?.enabled).toBe(true);
expect(next.channels?.googlechat?.dm?.allowFrom).toEqual(["users/123"]);
});
});
describe("channel lookup note helpers", () => {
it("emits summary lines for resolved and unresolved entries", async () => {
const prompter = { note: vi.fn(async () => undefined) };

View File

@@ -1070,8 +1070,8 @@ export async function promptParsedAllowFromForAccount<TConfig extends OpenClawCo
accountId?: string;
defaultAccountId: string;
prompter: Pick<WizardPrompter, "note" | "text">;
noteTitle: string;
noteLines: string[];
noteTitle?: string;
noteLines?: string[];
message: string;
placeholder: string;
parseEntries: (raw: string) => ParsedAllowFromResult;
@@ -1091,7 +1091,9 @@ export async function promptParsedAllowFromForAccount<TConfig extends OpenClawCo
cfg: params.cfg,
accountId,
});
await params.prompter.note(params.noteLines.join("\n"), params.noteTitle);
if (params.noteTitle && params.noteLines && params.noteLines.length > 0) {
await params.prompter.note(params.noteLines.join("\n"), params.noteTitle);
}
const entry = await params.prompter.text({
message: params.message,
placeholder: params.placeholder,
@@ -1117,6 +1119,41 @@ export async function promptParsedAllowFromForAccount<TConfig extends OpenClawCo
});
}
export function createPromptParsedAllowFromForAccount<TConfig extends OpenClawConfig>(params: {
defaultAccountId: string | ((cfg: TConfig) => string);
noteTitle?: string;
noteLines?: string[];
message: string;
placeholder: string;
parseEntries: (raw: string) => ParsedAllowFromResult;
getExistingAllowFrom: (params: { cfg: TConfig; accountId: string }) => Array<string | number>;
mergeEntries?: (params: { existing: Array<string | number>; parsed: string[] }) => string[];
applyAllowFrom: (params: {
cfg: TConfig;
accountId: string;
allowFrom: string[];
}) => TConfig | Promise<TConfig>;
}): NonNullable<ChannelSetupDmPolicy["promptAllowFrom"]> {
return async ({ cfg, prompter, accountId }) =>
await promptParsedAllowFromForAccount({
cfg: cfg as TConfig,
accountId,
defaultAccountId:
typeof params.defaultAccountId === "function"
? params.defaultAccountId(cfg as TConfig)
: params.defaultAccountId,
prompter,
...(params.noteTitle ? { noteTitle: params.noteTitle } : {}),
...(params.noteLines ? { noteLines: params.noteLines } : {}),
message: params.message,
placeholder: params.placeholder,
parseEntries: params.parseEntries,
getExistingAllowFrom: params.getExistingAllowFrom,
...(params.mergeEntries ? { mergeEntries: params.mergeEntries } : {}),
applyAllowFrom: params.applyAllowFrom,
});
}
export async function promptParsedAllowFromForScopedChannel(params: {
cfg: OpenClawConfig;
channel: "imessage" | "signal";
@@ -1154,6 +1191,75 @@ export async function promptParsedAllowFromForScopedChannel(params: {
});
}
export function createTopLevelChannelParsedAllowFromPrompt(params: {
channel: string;
defaultAccountId: string;
enabled?: boolean;
noteTitle?: string;
noteLines?: string[];
message: string;
placeholder: string;
parseEntries: (raw: string) => ParsedAllowFromResult;
getExistingAllowFrom?: (cfg: OpenClawConfig) => Array<string | number>;
mergeEntries?: (params: { existing: Array<string | number>; parsed: string[] }) => string[];
}): NonNullable<ChannelSetupDmPolicy["promptAllowFrom"]> {
const setAllowFrom = createTopLevelChannelAllowFromSetter({
channel: params.channel,
...(params.enabled ? { enabled: true } : {}),
});
return createPromptParsedAllowFromForAccount({
defaultAccountId: params.defaultAccountId,
...(params.noteTitle ? { noteTitle: params.noteTitle } : {}),
...(params.noteLines ? { noteLines: params.noteLines } : {}),
message: params.message,
placeholder: params.placeholder,
parseEntries: params.parseEntries,
getExistingAllowFrom: ({ cfg }) =>
params.getExistingAllowFrom?.(cfg) ??
(((cfg.channels?.[params.channel] as { allowFrom?: Array<string | number> } | undefined)
?.allowFrom ??
[]) as Array<string | number>),
...(params.mergeEntries ? { mergeEntries: params.mergeEntries } : {}),
applyAllowFrom: ({ cfg, allowFrom }) => setAllowFrom(cfg, allowFrom),
});
}
export function createNestedChannelParsedAllowFromPrompt(params: {
channel: string;
section: string;
defaultAccountId: string;
enabled?: boolean;
noteTitle?: string;
noteLines?: string[];
message: string;
placeholder: string;
parseEntries: (raw: string) => ParsedAllowFromResult;
getExistingAllowFrom?: (cfg: OpenClawConfig) => Array<string | number>;
mergeEntries?: (params: { existing: Array<string | number>; parsed: string[] }) => string[];
}): NonNullable<ChannelSetupDmPolicy["promptAllowFrom"]> {
const setAllowFrom = createNestedChannelAllowFromSetter({
channel: params.channel,
section: params.section,
...(params.enabled ? { enabled: true } : {}),
});
return createPromptParsedAllowFromForAccount({
defaultAccountId: params.defaultAccountId,
...(params.noteTitle ? { noteTitle: params.noteTitle } : {}),
...(params.noteLines ? { noteLines: params.noteLines } : {}),
message: params.message,
placeholder: params.placeholder,
parseEntries: params.parseEntries,
getExistingAllowFrom: ({ cfg }) =>
params.getExistingAllowFrom?.(cfg) ??
(((cfg.channels?.[params.channel] as Record<string, unknown> | undefined)?.[
params.section
] as { allowFrom?: Array<string | number> } | undefined)?.allowFrom ??
[]),
...(params.mergeEntries ? { mergeEntries: params.mergeEntries } : {}),
applyAllowFrom: ({ cfg, allowFrom }) => setAllowFrom(cfg, allowFrom),
});
}
export function resolveParsedAllowFromEntries(params: {
entries: string[];
parseId: (raw: string) => string | null;

View File

@@ -40,6 +40,8 @@ export {
createAccountScopedGroupAccessSection,
createAllowFromSection,
createLegacyCompatChannelDmPolicy,
createNestedChannelParsedAllowFromPrompt,
createPromptParsedAllowFromForAccount,
createNestedChannelAllowFromSetter,
createNestedChannelDmPolicy,
createNestedChannelDmPolicySetter,
@@ -47,6 +49,7 @@ export {
createTopLevelChannelDmPolicy,
createTopLevelChannelDmPolicySetter,
createTopLevelChannelGroupPolicySetter,
createTopLevelChannelParsedAllowFromPrompt,
mergeAllowFromEntries,
normalizeAllowFromEntries,
noteChannelLookupFailure,