refactor(channels): share slack matching and allowlist prompt flow
This commit is contained in:
@@ -129,6 +129,41 @@ function resolveDiscordChannelEntry<TEntry>(
|
||||
);
|
||||
}
|
||||
|
||||
type SlackChannelPolicyEntry = {
|
||||
requireMention?: boolean;
|
||||
tools?: GroupToolPolicyConfig;
|
||||
toolsBySender?: GroupToolPolicyBySenderConfig;
|
||||
};
|
||||
|
||||
function resolveSlackChannelPolicyEntry(
|
||||
params: GroupMentionParams,
|
||||
): SlackChannelPolicyEntry | undefined {
|
||||
const account = resolveSlackAccount({
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
});
|
||||
const channels = (account.channels ?? {}) as Record<string, SlackChannelPolicyEntry>;
|
||||
if (Object.keys(channels).length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const channelId = params.groupId?.trim();
|
||||
const groupChannel = params.groupChannel;
|
||||
const channelName = groupChannel?.replace(/^#/, "");
|
||||
const normalizedName = normalizeHyphenSlug(channelName);
|
||||
const candidates = [
|
||||
channelId ?? "",
|
||||
channelName ? `#${channelName}` : "",
|
||||
channelName ?? "",
|
||||
normalizedName,
|
||||
].filter(Boolean);
|
||||
for (const candidate of candidates) {
|
||||
if (candidate && channels[candidate]) {
|
||||
return channels[candidate];
|
||||
}
|
||||
}
|
||||
return channels["*"];
|
||||
}
|
||||
|
||||
export function resolveTelegramGroupRequireMention(
|
||||
params: GroupMentionParams,
|
||||
): boolean | undefined {
|
||||
@@ -210,34 +245,7 @@ export function resolveGoogleChatGroupToolPolicy(
|
||||
}
|
||||
|
||||
export function resolveSlackGroupRequireMention(params: GroupMentionParams): boolean {
|
||||
const account = resolveSlackAccount({
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
});
|
||||
const channels = account.channels ?? {};
|
||||
const keys = Object.keys(channels);
|
||||
if (keys.length === 0) {
|
||||
return true;
|
||||
}
|
||||
const channelId = params.groupId?.trim();
|
||||
const groupChannel = params.groupChannel;
|
||||
const channelName = groupChannel?.replace(/^#/, "");
|
||||
const normalizedName = normalizeHyphenSlug(channelName);
|
||||
const candidates = [
|
||||
channelId ?? "",
|
||||
channelName ? `#${channelName}` : "",
|
||||
channelName ?? "",
|
||||
normalizedName,
|
||||
].filter(Boolean);
|
||||
let matched: { requireMention?: boolean } | undefined;
|
||||
for (const candidate of candidates) {
|
||||
if (candidate && channels[candidate]) {
|
||||
matched = channels[candidate];
|
||||
break;
|
||||
}
|
||||
}
|
||||
const fallback = channels["*"];
|
||||
const resolved = matched ?? fallback;
|
||||
const resolved = resolveSlackChannelPolicyEntry(params);
|
||||
if (typeof resolved?.requireMention === "boolean") {
|
||||
return resolved.requireMention;
|
||||
}
|
||||
@@ -342,35 +350,10 @@ export function resolveDiscordGroupToolPolicy(
|
||||
export function resolveSlackGroupToolPolicy(
|
||||
params: GroupMentionParams,
|
||||
): GroupToolPolicyConfig | undefined {
|
||||
const account = resolveSlackAccount({
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
});
|
||||
const channels = account.channels ?? {};
|
||||
const keys = Object.keys(channels);
|
||||
if (keys.length === 0) {
|
||||
const resolved = resolveSlackChannelPolicyEntry(params);
|
||||
if (!resolved) {
|
||||
return undefined;
|
||||
}
|
||||
const channelId = params.groupId?.trim();
|
||||
const groupChannel = params.groupChannel;
|
||||
const channelName = groupChannel?.replace(/^#/, "");
|
||||
const normalizedName = normalizeHyphenSlug(channelName);
|
||||
const candidates = [
|
||||
channelId ?? "",
|
||||
channelName ? `#${channelName}` : "",
|
||||
channelName ?? "",
|
||||
normalizedName,
|
||||
].filter(Boolean);
|
||||
let matched:
|
||||
| { tools?: GroupToolPolicyConfig; toolsBySender?: GroupToolPolicyBySenderConfig }
|
||||
| undefined;
|
||||
for (const candidate of candidates) {
|
||||
if (candidate && channels[candidate]) {
|
||||
matched = channels[candidate];
|
||||
break;
|
||||
}
|
||||
}
|
||||
const resolved = matched ?? channels["*"];
|
||||
const senderPolicy = resolveToolsBySender({
|
||||
toolsBySender: resolved?.toolsBySender,
|
||||
senderId: params.senderId,
|
||||
|
||||
@@ -17,7 +17,7 @@ import { formatDocsLink } from "../../../terminal/links.js";
|
||||
import type { WizardPrompter } from "../../../wizard/prompts.js";
|
||||
import type { ChannelOnboardingAdapter, ChannelOnboardingDmPolicy } from "../onboarding-types.js";
|
||||
import { promptChannelAccessConfig } from "./channel-access.js";
|
||||
import { addWildcardAllowFrom, mergeAllowFromEntries, promptAccountId } from "./helpers.js";
|
||||
import { addWildcardAllowFrom, promptAccountId, promptResolvedAllowFrom } from "./helpers.js";
|
||||
|
||||
const channel = "discord" as const;
|
||||
|
||||
@@ -195,47 +195,23 @@ async function promptDiscordAllowFrom(params: {
|
||||
return null;
|
||||
};
|
||||
|
||||
while (true) {
|
||||
const entry = await params.prompter.text({
|
||||
message: "Discord allowFrom (usernames or ids)",
|
||||
placeholder: "@alice, 123456789012345678",
|
||||
initialValue: existing[0] ? String(existing[0]) : undefined,
|
||||
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
|
||||
});
|
||||
const parts = parseInputs(String(entry));
|
||||
if (!token) {
|
||||
const ids = parts.map(parseId).filter(Boolean) as string[];
|
||||
if (ids.length !== parts.length) {
|
||||
await params.prompter.note(
|
||||
"Bot token missing; use numeric user ids (or mention form) only.",
|
||||
"Discord allowlist",
|
||||
);
|
||||
continue;
|
||||
}
|
||||
const unique = mergeAllowFromEntries(existing, ids);
|
||||
return setDiscordAllowFrom(params.cfg, unique);
|
||||
}
|
||||
|
||||
const results = await resolveDiscordUserAllowlist({
|
||||
token,
|
||||
entries: parts,
|
||||
}).catch(() => null);
|
||||
if (!results) {
|
||||
await params.prompter.note("Failed to resolve usernames. Try again.", "Discord allowlist");
|
||||
continue;
|
||||
}
|
||||
const unresolved = results.filter((res) => !res.resolved || !res.id);
|
||||
if (unresolved.length > 0) {
|
||||
await params.prompter.note(
|
||||
`Could not resolve: ${unresolved.map((res) => res.input).join(", ")}`,
|
||||
"Discord allowlist",
|
||||
);
|
||||
continue;
|
||||
}
|
||||
const ids = results.map((res) => res.id as string);
|
||||
const unique = mergeAllowFromEntries(existing, ids);
|
||||
return setDiscordAllowFrom(params.cfg, unique);
|
||||
}
|
||||
const unique = await promptResolvedAllowFrom({
|
||||
prompter: params.prompter,
|
||||
existing,
|
||||
token,
|
||||
message: "Discord allowFrom (usernames or ids)",
|
||||
placeholder: "@alice, 123456789012345678",
|
||||
label: "Discord allowlist",
|
||||
parseInputs,
|
||||
parseId,
|
||||
invalidWithoutTokenNote: "Bot token missing; use numeric user ids (or mention form) only.",
|
||||
resolveEntries: ({ token, entries }) =>
|
||||
resolveDiscordUserAllowlist({
|
||||
token,
|
||||
entries,
|
||||
}),
|
||||
});
|
||||
return setDiscordAllowFrom(params.cfg, unique);
|
||||
}
|
||||
|
||||
const dmPolicy: ChannelOnboardingDmPolicy = {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { promptAccountId as promptAccountIdSdk } from "../../../plugin-sdk/onboarding.js";
|
||||
import type { WizardPrompter } from "../../../wizard/prompts.js";
|
||||
import type { PromptAccountId, PromptAccountIdParams } from "../onboarding-types.js";
|
||||
|
||||
export const promptAccountId: PromptAccountId = async (params: PromptAccountIdParams) => {
|
||||
@@ -20,3 +21,61 @@ export function mergeAllowFromEntries(
|
||||
const merged = [...(current ?? []), ...additions].map((v) => String(v).trim()).filter(Boolean);
|
||||
return [...new Set(merged)];
|
||||
}
|
||||
|
||||
type AllowFromResolution = {
|
||||
input: string;
|
||||
resolved: boolean;
|
||||
id?: string | null;
|
||||
};
|
||||
|
||||
export async function promptResolvedAllowFrom(params: {
|
||||
prompter: WizardPrompter;
|
||||
existing: Array<string | number>;
|
||||
token?: string | null;
|
||||
message: string;
|
||||
placeholder: string;
|
||||
label: string;
|
||||
parseInputs: (value: string) => string[];
|
||||
parseId: (value: string) => string | null;
|
||||
invalidWithoutTokenNote: string;
|
||||
resolveEntries: (params: { token: string; entries: string[] }) => Promise<AllowFromResolution[]>;
|
||||
}): Promise<string[]> {
|
||||
while (true) {
|
||||
const entry = await params.prompter.text({
|
||||
message: params.message,
|
||||
placeholder: params.placeholder,
|
||||
initialValue: params.existing[0] ? String(params.existing[0]) : undefined,
|
||||
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
|
||||
});
|
||||
const parts = params.parseInputs(String(entry));
|
||||
if (!params.token) {
|
||||
const ids = parts.map(params.parseId).filter(Boolean) as string[];
|
||||
if (ids.length !== parts.length) {
|
||||
await params.prompter.note(params.invalidWithoutTokenNote, params.label);
|
||||
continue;
|
||||
}
|
||||
return mergeAllowFromEntries(params.existing, ids);
|
||||
}
|
||||
|
||||
const results = await params
|
||||
.resolveEntries({
|
||||
token: params.token,
|
||||
entries: parts,
|
||||
})
|
||||
.catch(() => null);
|
||||
if (!results) {
|
||||
await params.prompter.note("Failed to resolve usernames. Try again.", params.label);
|
||||
continue;
|
||||
}
|
||||
const unresolved = results.filter((res) => !res.resolved || !res.id);
|
||||
if (unresolved.length > 0) {
|
||||
await params.prompter.note(
|
||||
`Could not resolve: ${unresolved.map((res) => res.input).join(", ")}`,
|
||||
params.label,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
const ids = results.map((res) => res.id as string);
|
||||
return mergeAllowFromEntries(params.existing, ids);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { formatDocsLink } from "../../../terminal/links.js";
|
||||
import type { WizardPrompter } from "../../../wizard/prompts.js";
|
||||
import type { ChannelOnboardingAdapter, ChannelOnboardingDmPolicy } from "../onboarding-types.js";
|
||||
import { promptChannelAccessConfig } from "./channel-access.js";
|
||||
import { addWildcardAllowFrom, mergeAllowFromEntries, promptAccountId } from "./helpers.js";
|
||||
import { addWildcardAllowFrom, promptAccountId, promptResolvedAllowFrom } from "./helpers.js";
|
||||
|
||||
const channel = "slack" as const;
|
||||
|
||||
@@ -263,47 +263,23 @@ async function promptSlackAllowFrom(params: {
|
||||
return null;
|
||||
};
|
||||
|
||||
while (true) {
|
||||
const entry = await params.prompter.text({
|
||||
message: "Slack allowFrom (usernames or ids)",
|
||||
placeholder: "@alice, U12345678",
|
||||
initialValue: existing[0] ? String(existing[0]) : undefined,
|
||||
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
|
||||
});
|
||||
const parts = parseInputs(String(entry));
|
||||
if (!token) {
|
||||
const ids = parts.map(parseId).filter(Boolean) as string[];
|
||||
if (ids.length !== parts.length) {
|
||||
await params.prompter.note(
|
||||
"Slack token missing; use user ids (or mention form) only.",
|
||||
"Slack allowlist",
|
||||
);
|
||||
continue;
|
||||
}
|
||||
const unique = mergeAllowFromEntries(existing, ids);
|
||||
return setSlackAllowFrom(params.cfg, unique);
|
||||
}
|
||||
|
||||
const results = await resolveSlackUserAllowlist({
|
||||
token,
|
||||
entries: parts,
|
||||
}).catch(() => null);
|
||||
if (!results) {
|
||||
await params.prompter.note("Failed to resolve usernames. Try again.", "Slack allowlist");
|
||||
continue;
|
||||
}
|
||||
const unresolved = results.filter((res) => !res.resolved || !res.id);
|
||||
if (unresolved.length > 0) {
|
||||
await params.prompter.note(
|
||||
`Could not resolve: ${unresolved.map((res) => res.input).join(", ")}`,
|
||||
"Slack allowlist",
|
||||
);
|
||||
continue;
|
||||
}
|
||||
const ids = results.map((res) => res.id as string);
|
||||
const unique = mergeAllowFromEntries(existing, ids);
|
||||
return setSlackAllowFrom(params.cfg, unique);
|
||||
}
|
||||
const unique = await promptResolvedAllowFrom({
|
||||
prompter: params.prompter,
|
||||
existing,
|
||||
token,
|
||||
message: "Slack allowFrom (usernames or ids)",
|
||||
placeholder: "@alice, U12345678",
|
||||
label: "Slack allowlist",
|
||||
parseInputs,
|
||||
parseId,
|
||||
invalidWithoutTokenNote: "Slack token missing; use user ids (or mention form) only.",
|
||||
resolveEntries: ({ token, entries }) =>
|
||||
resolveSlackUserAllowlist({
|
||||
token,
|
||||
entries,
|
||||
}),
|
||||
});
|
||||
return setSlackAllowFrom(params.cfg, unique);
|
||||
}
|
||||
|
||||
const dmPolicy: ChannelOnboardingDmPolicy = {
|
||||
|
||||
Reference in New Issue
Block a user