mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-24 07:01:49 +00:00
refactor(setup): share env-aware patched adapters
This commit is contained in:
@@ -1,10 +1,7 @@
|
||||
import type { DiscordGuildEntry } from "openclaw/plugin-sdk/config-runtime";
|
||||
import {
|
||||
applyAccountNameToChannelSection,
|
||||
createPatchedAccountSetupAdapter,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
migrateBaseNameToDefaultAccount,
|
||||
normalizeAccountId,
|
||||
createEnvPatchedAccountSetupAdapter,
|
||||
noteChannelLookupFailure,
|
||||
noteChannelLookupSummary,
|
||||
parseMentionOrPrefixedId,
|
||||
@@ -74,71 +71,13 @@ export function parseDiscordAllowFromId(value: string): string | null {
|
||||
});
|
||||
}
|
||||
|
||||
export const discordSetupAdapter: ChannelSetupAdapter = {
|
||||
resolveAccountId: ({ accountId }) => normalizeAccountId(accountId),
|
||||
applyAccountName: ({ cfg, accountId, name }) =>
|
||||
applyAccountNameToChannelSection({
|
||||
cfg,
|
||||
channelKey: channel,
|
||||
accountId,
|
||||
name,
|
||||
}),
|
||||
validateInput: ({ accountId, input }) => {
|
||||
if (input.useEnv && accountId !== DEFAULT_ACCOUNT_ID) {
|
||||
return "DISCORD_BOT_TOKEN can only be used for the default account.";
|
||||
}
|
||||
if (!input.useEnv && !input.token) {
|
||||
return "Discord requires token (or --use-env).";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
applyAccountConfig: ({ cfg, accountId, input }) => {
|
||||
const namedConfig = applyAccountNameToChannelSection({
|
||||
cfg,
|
||||
channelKey: channel,
|
||||
accountId,
|
||||
name: input.name,
|
||||
});
|
||||
const next =
|
||||
accountId !== DEFAULT_ACCOUNT_ID
|
||||
? migrateBaseNameToDefaultAccount({
|
||||
cfg: namedConfig,
|
||||
channelKey: channel,
|
||||
})
|
||||
: namedConfig;
|
||||
if (accountId === DEFAULT_ACCOUNT_ID) {
|
||||
return {
|
||||
...next,
|
||||
channels: {
|
||||
...next.channels,
|
||||
discord: {
|
||||
...next.channels?.discord,
|
||||
enabled: true,
|
||||
...(input.useEnv ? {} : input.token ? { token: input.token } : {}),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
...next,
|
||||
channels: {
|
||||
...next.channels,
|
||||
discord: {
|
||||
...next.channels?.discord,
|
||||
enabled: true,
|
||||
accounts: {
|
||||
...next.channels?.discord?.accounts,
|
||||
[accountId]: {
|
||||
...next.channels?.discord?.accounts?.[accountId],
|
||||
enabled: true,
|
||||
...(input.token ? { token: input.token } : {}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
export const discordSetupAdapter: ChannelSetupAdapter = createEnvPatchedAccountSetupAdapter({
|
||||
channelKey: channel,
|
||||
defaultAccountOnlyEnvError: "DISCORD_BOT_TOKEN can only be used for the default account.",
|
||||
missingCredentialError: "Discord requires token (or --use-env).",
|
||||
hasCredentials: (input) => Boolean(input.token),
|
||||
buildPatch: (input) => (input.token ? { token: input.token } : {}),
|
||||
});
|
||||
|
||||
export function createDiscordSetupWizardBase(handlers: {
|
||||
promptAllowFrom: NonNullable<ChannelSetupDmPolicy["promptAllowFrom"]>;
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import {
|
||||
applyAccountNameToChannelSection,
|
||||
createAllowlistSetupWizardProxy,
|
||||
createPatchedAccountSetupAdapter,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
createEnvPatchedAccountSetupAdapter,
|
||||
hasConfiguredSecretInput,
|
||||
migrateBaseNameToDefaultAccount,
|
||||
normalizeAccountId,
|
||||
type OpenClawConfig,
|
||||
noteChannelLookupFailure,
|
||||
noteChannelLookupSummary,
|
||||
@@ -95,77 +92,16 @@ function createSlackTokenCredential(params: {
|
||||
};
|
||||
}
|
||||
|
||||
export const slackSetupAdapter: ChannelSetupAdapter = {
|
||||
resolveAccountId: ({ accountId }) => normalizeAccountId(accountId),
|
||||
applyAccountName: ({ cfg, accountId, name }) =>
|
||||
applyAccountNameToChannelSection({
|
||||
cfg,
|
||||
channelKey: channel,
|
||||
accountId,
|
||||
name,
|
||||
}),
|
||||
validateInput: ({ accountId, input }) => {
|
||||
if (input.useEnv && accountId !== DEFAULT_ACCOUNT_ID) {
|
||||
return "Slack env tokens can only be used for the default account.";
|
||||
}
|
||||
if (!input.useEnv && (!input.botToken || !input.appToken)) {
|
||||
return "Slack requires --bot-token and --app-token (or --use-env).";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
applyAccountConfig: ({ cfg, accountId, input }) => {
|
||||
const namedConfig = applyAccountNameToChannelSection({
|
||||
cfg,
|
||||
channelKey: channel,
|
||||
accountId,
|
||||
name: input.name,
|
||||
});
|
||||
const next =
|
||||
accountId !== DEFAULT_ACCOUNT_ID
|
||||
? migrateBaseNameToDefaultAccount({
|
||||
cfg: namedConfig,
|
||||
channelKey: channel,
|
||||
})
|
||||
: namedConfig;
|
||||
if (accountId === DEFAULT_ACCOUNT_ID) {
|
||||
return {
|
||||
...next,
|
||||
channels: {
|
||||
...next.channels,
|
||||
slack: {
|
||||
...next.channels?.slack,
|
||||
enabled: true,
|
||||
...(input.useEnv
|
||||
? {}
|
||||
: {
|
||||
...(input.botToken ? { botToken: input.botToken } : {}),
|
||||
...(input.appToken ? { appToken: input.appToken } : {}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
...next,
|
||||
channels: {
|
||||
...next.channels,
|
||||
slack: {
|
||||
...next.channels?.slack,
|
||||
enabled: true,
|
||||
accounts: {
|
||||
...next.channels?.slack?.accounts,
|
||||
[accountId]: {
|
||||
...next.channels?.slack?.accounts?.[accountId],
|
||||
enabled: true,
|
||||
...(input.botToken ? { botToken: input.botToken } : {}),
|
||||
...(input.appToken ? { appToken: input.appToken } : {}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
export const slackSetupAdapter: ChannelSetupAdapter = createEnvPatchedAccountSetupAdapter({
|
||||
channelKey: channel,
|
||||
defaultAccountOnlyEnvError: "Slack env tokens can only be used for the default account.",
|
||||
missingCredentialError: "Slack requires --bot-token and --app-token (or --use-env).",
|
||||
hasCredentials: (input) => Boolean(input.botToken && input.appToken),
|
||||
buildPatch: (input) => ({
|
||||
...(input.botToken ? { botToken: input.botToken } : {}),
|
||||
...(input.appToken ? { appToken: input.appToken } : {}),
|
||||
}),
|
||||
});
|
||||
|
||||
export function createSlackSetupWizardBase(handlers: {
|
||||
promptAllowFrom: NonNullable<ChannelSetupDmPolicy["promptAllowFrom"]>;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
createPatchedAccountSetupAdapter,
|
||||
createEnvPatchedAccountSetupAdapter,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
patchChannelConfigForAccount,
|
||||
promptResolvedAllowFrom,
|
||||
@@ -107,23 +107,11 @@ export async function promptTelegramAllowFromForAccount(params: {
|
||||
});
|
||||
}
|
||||
|
||||
export const telegramSetupAdapter: ChannelSetupAdapter = createPatchedAccountSetupAdapter({
|
||||
export const telegramSetupAdapter: ChannelSetupAdapter = createEnvPatchedAccountSetupAdapter({
|
||||
channelKey: channel,
|
||||
validateInput: ({ accountId, input }) => {
|
||||
if (input.useEnv && accountId !== DEFAULT_ACCOUNT_ID) {
|
||||
return "TELEGRAM_BOT_TOKEN can only be used for the default account.";
|
||||
}
|
||||
if (!input.useEnv && !input.token && !input.tokenFile) {
|
||||
return "Telegram requires token or --token-file (or --use-env).";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
defaultAccountOnlyEnvError: "TELEGRAM_BOT_TOKEN can only be used for the default account.",
|
||||
missingCredentialError: "Telegram requires token or --token-file (or --use-env).",
|
||||
hasCredentials: (input) => Boolean(input.token || input.tokenFile),
|
||||
buildPatch: (input) =>
|
||||
input.useEnv
|
||||
? {}
|
||||
: input.tokenFile
|
||||
? { tokenFile: input.tokenFile }
|
||||
: input.token
|
||||
? { botToken: input.token }
|
||||
: {},
|
||||
input.tokenFile ? { tokenFile: input.tokenFile } : input.token ? { botToken: input.token } : {},
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { DEFAULT_ACCOUNT_ID } from "../../routing/session-key.js";
|
||||
import {
|
||||
applySetupAccountConfigPatch,
|
||||
createEnvPatchedAccountSetupAdapter,
|
||||
createPatchedAccountSetupAdapter,
|
||||
prepareScopedSetupConfig,
|
||||
} from "./setup-helpers.js";
|
||||
@@ -162,6 +163,39 @@ describe("createPatchedAccountSetupAdapter", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("createEnvPatchedAccountSetupAdapter", () => {
|
||||
it("rejects env mode for named accounts and requires credentials otherwise", () => {
|
||||
const adapter = createEnvPatchedAccountSetupAdapter({
|
||||
channelKey: "telegram",
|
||||
defaultAccountOnlyEnvError: "env only on default",
|
||||
missingCredentialError: "token required",
|
||||
hasCredentials: (input) => Boolean(input.token || input.tokenFile),
|
||||
buildPatch: (input) => ({ token: input.token }),
|
||||
});
|
||||
|
||||
expect(
|
||||
adapter.validateInput?.({
|
||||
accountId: "work",
|
||||
input: { useEnv: true },
|
||||
}),
|
||||
).toBe("env only on default");
|
||||
|
||||
expect(
|
||||
adapter.validateInput?.({
|
||||
accountId: DEFAULT_ACCOUNT_ID,
|
||||
input: {},
|
||||
}),
|
||||
).toBe("token required");
|
||||
|
||||
expect(
|
||||
adapter.validateInput?.({
|
||||
accountId: DEFAULT_ACCOUNT_ID,
|
||||
input: { token: "tok" },
|
||||
}),
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("prepareScopedSetupConfig", () => {
|
||||
it("stores the name and migrates it for named accounts when requested", () => {
|
||||
const next = prepareScopedSetupConfig({
|
||||
|
||||
@@ -204,6 +204,35 @@ export function createPatchedAccountSetupAdapter(params: {
|
||||
};
|
||||
}
|
||||
|
||||
export function createEnvPatchedAccountSetupAdapter(params: {
|
||||
channelKey: string;
|
||||
alwaysUseAccounts?: boolean;
|
||||
ensureChannelEnabled?: boolean;
|
||||
ensureAccountEnabled?: boolean;
|
||||
defaultAccountOnlyEnvError: string;
|
||||
missingCredentialError: string;
|
||||
hasCredentials: (input: ChannelSetupInput) => boolean;
|
||||
validateInput?: ChannelSetupAdapter["validateInput"];
|
||||
buildPatch: (input: ChannelSetupInput) => Record<string, unknown>;
|
||||
}): ChannelSetupAdapter {
|
||||
return createPatchedAccountSetupAdapter({
|
||||
channelKey: params.channelKey,
|
||||
alwaysUseAccounts: params.alwaysUseAccounts,
|
||||
ensureChannelEnabled: params.ensureChannelEnabled,
|
||||
ensureAccountEnabled: params.ensureAccountEnabled,
|
||||
validateInput: (inputParams) => {
|
||||
if (inputParams.input.useEnv && inputParams.accountId !== DEFAULT_ACCOUNT_ID) {
|
||||
return params.defaultAccountOnlyEnvError;
|
||||
}
|
||||
if (!inputParams.input.useEnv && !params.hasCredentials(inputParams.input)) {
|
||||
return params.missingCredentialError;
|
||||
}
|
||||
return params.validateInput?.(inputParams) ?? null;
|
||||
},
|
||||
buildPatch: params.buildPatch,
|
||||
});
|
||||
}
|
||||
|
||||
export function patchScopedAccountConfig(params: {
|
||||
cfg: OpenClawConfig;
|
||||
channelKey: string;
|
||||
|
||||
@@ -24,6 +24,7 @@ export { normalizeE164, pathExists } from "../utils.js";
|
||||
export {
|
||||
applyAccountNameToChannelSection,
|
||||
applySetupAccountConfigPatch,
|
||||
createEnvPatchedAccountSetupAdapter,
|
||||
createPatchedAccountSetupAdapter,
|
||||
migrateBaseNameToDefaultAccount,
|
||||
patchScopedAccountConfig,
|
||||
|
||||
Reference in New Issue
Block a user