mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-17 04:50:51 +00:00
refactor: move setup fallback into setup registry
This commit is contained in:
5
extensions/line/setup-entry.ts
Normal file
5
extensions/line/setup-entry.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { lineSetupPlugin } from "./src/channel.setup.js";
|
||||
|
||||
export default {
|
||||
plugin: lineSetupPlugin,
|
||||
};
|
||||
69
extensions/line/src/channel.setup.ts
Normal file
69
extensions/line/src/channel.setup.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import {
|
||||
buildChannelConfigSchema,
|
||||
LineConfigSchema,
|
||||
type ChannelPlugin,
|
||||
type OpenClawConfig,
|
||||
type ResolvedLineAccount,
|
||||
} from "openclaw/plugin-sdk/line";
|
||||
import {
|
||||
listLineAccountIds,
|
||||
resolveDefaultLineAccountId,
|
||||
resolveLineAccount,
|
||||
} from "../../../src/line/accounts.js";
|
||||
import { lineSetupAdapter } from "./setup-core.js";
|
||||
import { lineSetupWizard } from "./setup-surface.js";
|
||||
|
||||
const meta = {
|
||||
id: "line",
|
||||
label: "LINE",
|
||||
selectionLabel: "LINE (Messaging API)",
|
||||
detailLabel: "LINE Bot",
|
||||
docsPath: "/channels/line",
|
||||
docsLabel: "line",
|
||||
blurb: "LINE Messaging API bot for Japan/Taiwan/Thailand markets.",
|
||||
systemImage: "message.fill",
|
||||
} as const;
|
||||
|
||||
const normalizeLineAllowFrom = (entry: string) => entry.replace(/^line:(?:user:)?/i, "");
|
||||
|
||||
export const lineSetupPlugin: ChannelPlugin<ResolvedLineAccount> = {
|
||||
id: "line",
|
||||
meta: {
|
||||
...meta,
|
||||
quickstartAllowFrom: true,
|
||||
},
|
||||
capabilities: {
|
||||
chatTypes: ["direct", "group"],
|
||||
reactions: false,
|
||||
threads: false,
|
||||
media: true,
|
||||
nativeCommands: false,
|
||||
blockStreaming: true,
|
||||
},
|
||||
reload: { configPrefixes: ["channels.line"] },
|
||||
configSchema: buildChannelConfigSchema(LineConfigSchema),
|
||||
config: {
|
||||
listAccountIds: (cfg: OpenClawConfig) => listLineAccountIds(cfg),
|
||||
resolveAccount: (cfg: OpenClawConfig, accountId?: string | null) =>
|
||||
resolveLineAccount({ cfg, accountId: accountId ?? undefined }),
|
||||
defaultAccountId: (cfg: OpenClawConfig) => resolveDefaultLineAccountId(cfg),
|
||||
isConfigured: (account) =>
|
||||
Boolean(account.channelAccessToken?.trim() && account.channelSecret?.trim()),
|
||||
describeAccount: (account) => ({
|
||||
accountId: account.accountId,
|
||||
name: account.name,
|
||||
enabled: account.enabled,
|
||||
configured: Boolean(account.channelAccessToken?.trim() && account.channelSecret?.trim()),
|
||||
tokenSource: account.tokenSource ?? undefined,
|
||||
}),
|
||||
resolveAllowFrom: ({ cfg, accountId }) =>
|
||||
resolveLineAccount({ cfg, accountId: accountId ?? undefined }).config.allowFrom,
|
||||
formatAllowFrom: ({ allowFrom }) =>
|
||||
allowFrom
|
||||
.map((entry) => String(entry).trim())
|
||||
.filter(Boolean)
|
||||
.map((entry) => normalizeLineAllowFrom(entry)),
|
||||
},
|
||||
setupWizard: lineSetupWizard,
|
||||
setup: lineSetupAdapter,
|
||||
};
|
||||
@@ -1,3 +1,12 @@
|
||||
import { discordSetupPlugin } from "../../../extensions/discord/src/channel.setup.js";
|
||||
import { googlechatPlugin } from "../../../extensions/googlechat/src/channel.js";
|
||||
import { imessageSetupPlugin } from "../../../extensions/imessage/src/channel.setup.js";
|
||||
import { ircPlugin } from "../../../extensions/irc/src/channel.js";
|
||||
import { lineSetupPlugin } from "../../../extensions/line/src/channel.setup.js";
|
||||
import { signalSetupPlugin } from "../../../extensions/signal/src/channel.setup.js";
|
||||
import { slackSetupPlugin } from "../../../extensions/slack/src/channel.setup.js";
|
||||
import { telegramSetupPlugin } from "../../../extensions/telegram/src/channel.setup.js";
|
||||
import { whatsappSetupPlugin } from "../../../extensions/whatsapp/src/channel.setup.js";
|
||||
import {
|
||||
getActivePluginRegistryVersion,
|
||||
requireActivePluginRegistry,
|
||||
@@ -19,6 +28,18 @@ const EMPTY_CHANNEL_SETUP_CACHE: CachedChannelSetupPlugins = {
|
||||
|
||||
let cachedChannelSetupPlugins = EMPTY_CHANNEL_SETUP_CACHE;
|
||||
|
||||
const BUNDLED_CHANNEL_SETUP_PLUGINS = [
|
||||
telegramSetupPlugin,
|
||||
whatsappSetupPlugin,
|
||||
discordSetupPlugin,
|
||||
ircPlugin,
|
||||
googlechatPlugin,
|
||||
slackSetupPlugin,
|
||||
signalSetupPlugin,
|
||||
imessageSetupPlugin,
|
||||
lineSetupPlugin,
|
||||
] as ChannelPlugin[];
|
||||
|
||||
function dedupeSetupPlugins(plugins: ChannelPlugin[]): ChannelPlugin[] {
|
||||
const seen = new Set<string>();
|
||||
const resolved: ChannelPlugin[] = [];
|
||||
@@ -33,17 +54,8 @@ function dedupeSetupPlugins(plugins: ChannelPlugin[]): ChannelPlugin[] {
|
||||
return resolved;
|
||||
}
|
||||
|
||||
function resolveCachedChannelSetupPlugins(): CachedChannelSetupPlugins {
|
||||
const registry = requireActivePluginRegistry();
|
||||
const registryVersion = getActivePluginRegistryVersion();
|
||||
const cached = cachedChannelSetupPlugins;
|
||||
if (cached.registryVersion === registryVersion) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const sorted = dedupeSetupPlugins(
|
||||
(registry.channelSetups ?? []).map((entry) => entry.plugin),
|
||||
).toSorted((a, b) => {
|
||||
function sortChannelSetupPlugins(plugins: ChannelPlugin[]): ChannelPlugin[] {
|
||||
return dedupeSetupPlugins(plugins).toSorted((a, b) => {
|
||||
const indexA = CHAT_CHANNEL_ORDER.indexOf(a.id as ChatChannelId);
|
||||
const indexB = CHAT_CHANNEL_ORDER.indexOf(b.id as ChatChannelId);
|
||||
const orderA = a.meta.order ?? (indexA === -1 ? 999 : indexA);
|
||||
@@ -53,6 +65,20 @@ function resolveCachedChannelSetupPlugins(): CachedChannelSetupPlugins {
|
||||
}
|
||||
return a.id.localeCompare(b.id);
|
||||
});
|
||||
}
|
||||
|
||||
function resolveCachedChannelSetupPlugins(): CachedChannelSetupPlugins {
|
||||
const registry = requireActivePluginRegistry();
|
||||
const registryVersion = getActivePluginRegistryVersion();
|
||||
const cached = cachedChannelSetupPlugins;
|
||||
if (cached.registryVersion === registryVersion) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const registryPlugins = (registry.channelSetups ?? []).map((entry) => entry.plugin);
|
||||
const sorted = sortChannelSetupPlugins(
|
||||
registryPlugins.length > 0 ? registryPlugins : BUNDLED_CHANNEL_SETUP_PLUGINS,
|
||||
);
|
||||
const byId = new Map<string, ChannelPlugin>();
|
||||
for (const plugin of sorted) {
|
||||
byId.set(plugin.id, plugin);
|
||||
|
||||
@@ -1,46 +1,20 @@
|
||||
import { discordPlugin } from "../../../extensions/discord/src/channel.js";
|
||||
import { googlechatPlugin } from "../../../extensions/googlechat/src/channel.js";
|
||||
import { imessagePlugin } from "../../../extensions/imessage/src/channel.js";
|
||||
import { ircPlugin } from "../../../extensions/irc/src/channel.js";
|
||||
import { linePlugin } from "../../../extensions/line/src/channel.js";
|
||||
import { signalPlugin } from "../../../extensions/signal/src/channel.js";
|
||||
import { slackPlugin } from "../../../extensions/slack/src/channel.js";
|
||||
import { telegramPlugin } from "../../../extensions/telegram/src/channel.js";
|
||||
import { whatsappPlugin } from "../../../extensions/whatsapp/src/channel.js";
|
||||
import { listChannelSetupPlugins } from "../../channels/plugins/setup-registry.js";
|
||||
import { buildChannelOnboardingAdapterFromSetupWizard } from "../../channels/plugins/setup-wizard.js";
|
||||
import { buildChannelSetupFlowAdapterFromSetupWizard } from "../../channels/plugins/setup-wizard.js";
|
||||
import type { ChannelPlugin } from "../../channels/plugins/types.js";
|
||||
import type { ChannelChoice } from "../onboard-types.js";
|
||||
import type { ChannelOnboardingAdapter } from "../onboarding/types.js";
|
||||
import type { ChannelSetupFlowAdapter } from "./types.js";
|
||||
|
||||
const EMPTY_REGISTRY_FALLBACK_PLUGINS = [
|
||||
telegramPlugin,
|
||||
whatsappPlugin,
|
||||
discordPlugin,
|
||||
ircPlugin,
|
||||
googlechatPlugin,
|
||||
slackPlugin,
|
||||
signalPlugin,
|
||||
imessagePlugin,
|
||||
linePlugin,
|
||||
];
|
||||
const setupWizardAdapters = new WeakMap<object, ChannelSetupFlowAdapter>();
|
||||
|
||||
export type ChannelOnboardingSetupPlugin = Pick<
|
||||
ChannelPlugin,
|
||||
"id" | "meta" | "capabilities" | "config" | "setup" | "setupWizard"
|
||||
>;
|
||||
|
||||
const setupWizardAdapters = new WeakMap<object, ChannelOnboardingAdapter>();
|
||||
|
||||
export function resolveChannelOnboardingAdapterForPlugin(
|
||||
plugin?: ChannelOnboardingSetupPlugin,
|
||||
): ChannelOnboardingAdapter | undefined {
|
||||
export function resolveChannelSetupFlowAdapterForPlugin(
|
||||
plugin?: ChannelPlugin,
|
||||
): ChannelSetupFlowAdapter | undefined {
|
||||
if (plugin?.setupWizard) {
|
||||
const cached = setupWizardAdapters.get(plugin);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
const adapter = buildChannelOnboardingAdapterFromSetupWizard({
|
||||
const adapter = buildChannelSetupFlowAdapterFromSetupWizard({
|
||||
plugin,
|
||||
wizard: plugin.setupWizard,
|
||||
});
|
||||
@@ -50,15 +24,10 @@ export function resolveChannelOnboardingAdapterForPlugin(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const CHANNEL_ONBOARDING_ADAPTERS = () => {
|
||||
const adapters = new Map<ChannelChoice, ChannelOnboardingAdapter>();
|
||||
const setupPlugins = listChannelSetupPlugins();
|
||||
const plugins =
|
||||
setupPlugins.length > 0
|
||||
? setupPlugins
|
||||
: (EMPTY_REGISTRY_FALLBACK_PLUGINS as unknown as ReturnType<typeof listChannelSetupPlugins>);
|
||||
for (const plugin of plugins) {
|
||||
const adapter = resolveChannelOnboardingAdapterForPlugin(plugin);
|
||||
const CHANNEL_SETUP_FLOW_ADAPTERS = () => {
|
||||
const adapters = new Map<ChannelChoice, ChannelSetupFlowAdapter>();
|
||||
for (const plugin of listChannelSetupPlugins()) {
|
||||
const adapter = resolveChannelSetupFlowAdapterForPlugin(plugin);
|
||||
if (!adapter) {
|
||||
continue;
|
||||
}
|
||||
@@ -67,43 +36,12 @@ const CHANNEL_ONBOARDING_ADAPTERS = () => {
|
||||
return adapters;
|
||||
};
|
||||
|
||||
export function getChannelOnboardingAdapter(
|
||||
export function getChannelSetupFlowAdapter(
|
||||
channel: ChannelChoice,
|
||||
): ChannelOnboardingAdapter | undefined {
|
||||
return CHANNEL_ONBOARDING_ADAPTERS().get(channel);
|
||||
): ChannelSetupFlowAdapter | undefined {
|
||||
return CHANNEL_SETUP_FLOW_ADAPTERS().get(channel);
|
||||
}
|
||||
|
||||
export function listChannelOnboardingAdapters(): ChannelOnboardingAdapter[] {
|
||||
return Array.from(CHANNEL_ONBOARDING_ADAPTERS().values());
|
||||
export function listChannelSetupFlowAdapters(): ChannelSetupFlowAdapter[] {
|
||||
return Array.from(CHANNEL_SETUP_FLOW_ADAPTERS().values());
|
||||
}
|
||||
|
||||
export async function loadBundledChannelOnboardingPlugin(
|
||||
channel: ChannelChoice,
|
||||
): Promise<ChannelOnboardingSetupPlugin | undefined> {
|
||||
switch (channel) {
|
||||
case "discord":
|
||||
return discordPlugin as ChannelPlugin;
|
||||
case "googlechat":
|
||||
return googlechatPlugin as ChannelPlugin;
|
||||
case "imessage":
|
||||
return imessagePlugin as ChannelPlugin;
|
||||
case "irc":
|
||||
return ircPlugin as ChannelPlugin;
|
||||
case "line":
|
||||
return linePlugin as ChannelPlugin;
|
||||
case "signal":
|
||||
return signalPlugin as ChannelPlugin;
|
||||
case "slack":
|
||||
return slackPlugin as ChannelPlugin;
|
||||
case "telegram":
|
||||
return telegramPlugin as ChannelPlugin;
|
||||
case "whatsapp":
|
||||
return whatsappPlugin as ChannelPlugin;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy aliases (pre-rename).
|
||||
export const getProviderOnboardingAdapter = getChannelOnboardingAdapter;
|
||||
export const listProviderOnboardingAdapters = listChannelOnboardingAdapters;
|
||||
|
||||
@@ -6,22 +6,22 @@ import { telegramPlugin } from "../../extensions/telegram/src/channel.js";
|
||||
import { whatsappPlugin } from "../../extensions/whatsapp/src/channel.js";
|
||||
import { setActivePluginRegistry } from "../plugins/runtime.js";
|
||||
import { createTestRegistry } from "../test-utils/channel-plugins.js";
|
||||
import { getChannelOnboardingAdapter } from "./channel-setup/registry.js";
|
||||
import { getChannelSetupFlowAdapter } from "./channel-setup/registry.js";
|
||||
import type { ChannelSetupFlowAdapter } from "./channel-setup/types.js";
|
||||
import type { ChannelChoice } from "./onboard-types.js";
|
||||
import type { ChannelOnboardingAdapter } from "./onboarding/types.js";
|
||||
|
||||
type ChannelOnboardingAdapterPatch = Partial<
|
||||
type ChannelSetupFlowAdapterPatch = Partial<
|
||||
Pick<
|
||||
ChannelOnboardingAdapter,
|
||||
ChannelSetupFlowAdapter,
|
||||
"configure" | "configureInteractive" | "configureWhenConfigured" | "getStatus"
|
||||
>
|
||||
>;
|
||||
|
||||
type PatchedOnboardingAdapterFields = {
|
||||
configure?: ChannelOnboardingAdapter["configure"];
|
||||
configureInteractive?: ChannelOnboardingAdapter["configureInteractive"];
|
||||
configureWhenConfigured?: ChannelOnboardingAdapter["configureWhenConfigured"];
|
||||
getStatus?: ChannelOnboardingAdapter["getStatus"];
|
||||
type PatchedSetupAdapterFields = {
|
||||
configure?: ChannelSetupFlowAdapter["configure"];
|
||||
configureInteractive?: ChannelSetupFlowAdapter["configureInteractive"];
|
||||
configureWhenConfigured?: ChannelSetupFlowAdapter["configureWhenConfigured"];
|
||||
getStatus?: ChannelSetupFlowAdapter["getStatus"];
|
||||
};
|
||||
|
||||
export function setDefaultChannelPluginRegistryForTests(): void {
|
||||
@@ -36,16 +36,16 @@ export function setDefaultChannelPluginRegistryForTests(): void {
|
||||
setActivePluginRegistry(createTestRegistry(channels));
|
||||
}
|
||||
|
||||
export function patchChannelOnboardingAdapter(
|
||||
export function patchChannelSetupFlowAdapter(
|
||||
channel: ChannelChoice,
|
||||
patch: ChannelOnboardingAdapterPatch,
|
||||
patch: ChannelSetupFlowAdapterPatch,
|
||||
): () => void {
|
||||
const adapter = getChannelOnboardingAdapter(channel);
|
||||
const adapter = getChannelSetupFlowAdapter(channel);
|
||||
if (!adapter) {
|
||||
throw new Error(`missing onboarding adapter for ${channel}`);
|
||||
throw new Error(`missing setup adapter for ${channel}`);
|
||||
}
|
||||
|
||||
const previous: PatchedOnboardingAdapterFields = {};
|
||||
const previous: PatchedSetupAdapterFields = {};
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(patch, "getStatus")) {
|
||||
previous.getStatus = adapter.getStatus;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../../agents/ag
|
||||
import { listChannelPluginCatalogEntries } from "../../channels/plugins/catalog.js";
|
||||
import { parseOptionalDelimitedEntries } from "../../channels/plugins/helpers.js";
|
||||
import { getChannelPlugin, normalizeChannelId } from "../../channels/plugins/index.js";
|
||||
import type { ChannelOnboardingSetupPlugin } from "../../channels/plugins/onboarding-types.js";
|
||||
import type { ChannelOnboardingSetupPlugin } from "../../channels/plugins/setup-flow-types.js";
|
||||
import { moveSingleAccountChannelSectionToDefaultAccount } from "../../channels/plugins/setup-helpers.js";
|
||||
import type { ChannelId, ChannelPlugin, ChannelSetupInput } from "../../channels/plugins/types.js";
|
||||
import { writeConfigFile, type OpenClawConfig } from "../../config/config.js";
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createEmptyPluginRegistry } from "../plugins/registry.js";
|
||||
import { setActivePluginRegistry } from "../plugins/runtime.js";
|
||||
import type { WizardPrompter } from "../wizard/prompts.js";
|
||||
import {
|
||||
patchChannelOnboardingAdapter,
|
||||
patchChannelSetupFlowAdapter,
|
||||
setDefaultChannelPluginRegistryForTests,
|
||||
} from "./channel-test-helpers.js";
|
||||
import { setupChannels } from "./onboard-channels.js";
|
||||
@@ -96,8 +96,8 @@ function createTelegramCfg(botToken: string, enabled?: boolean): OpenClawConfig
|
||||
} as OpenClawConfig;
|
||||
}
|
||||
|
||||
function patchTelegramAdapter(overrides: Parameters<typeof patchChannelOnboardingAdapter>[1]) {
|
||||
return patchChannelOnboardingAdapter("telegram", {
|
||||
function patchTelegramAdapter(overrides: Parameters<typeof patchChannelSetupFlowAdapter>[1]) {
|
||||
return patchChannelSetupFlowAdapter("telegram", {
|
||||
...overrides,
|
||||
getStatus:
|
||||
overrides.getStatus ??
|
||||
@@ -277,7 +277,7 @@ describe("setupChannels", () => {
|
||||
expect(multiselect).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("continues Telegram onboarding even when plugin registry is empty (avoids 'plugin not available' block)", async () => {
|
||||
it("continues Telegram setup when the plugin registry is empty", async () => {
|
||||
// Simulate missing registry entries (the scenario reported in #25545).
|
||||
setActivePluginRegistry(createEmptyPluginRegistry());
|
||||
// Avoid accidental env-token configuration changing the prompt path.
|
||||
@@ -311,11 +311,7 @@ describe("setupChannels", () => {
|
||||
);
|
||||
});
|
||||
expect(sawHardStop).toBe(false);
|
||||
expect(loadOnboardingPluginRegistrySnapshotForChannel).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
channel: "telegram",
|
||||
}),
|
||||
);
|
||||
expect(loadOnboardingPluginRegistrySnapshotForChannel).not.toHaveBeenCalled();
|
||||
expect(reloadOnboardingPluginRegistry).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||
import { listChannelPluginCatalogEntries } from "../channels/plugins/catalog.js";
|
||||
import { resolveChannelDefaultAccountId } from "../channels/plugins/helpers.js";
|
||||
import type { ChannelOnboardingSetupPlugin } from "../channels/plugins/onboarding-types.js";
|
||||
import type { ChannelOnboardingSetupPlugin } from "../channels/plugins/setup-flow-types.js";
|
||||
import {
|
||||
getChannelSetupPlugin,
|
||||
listChannelSetupPlugins,
|
||||
@@ -21,23 +21,20 @@ import type { RuntimeEnv } from "../runtime.js";
|
||||
import { formatDocsLink } from "../terminal/links.js";
|
||||
import type { WizardPrompter, WizardSelectOption } from "../wizard/prompts.js";
|
||||
import { resolveChannelSetupEntries } from "./channel-setup/discovery.js";
|
||||
import {
|
||||
loadBundledChannelOnboardingPlugin,
|
||||
resolveChannelOnboardingAdapterForPlugin,
|
||||
} from "./channel-setup/registry.js";
|
||||
import { resolveChannelSetupFlowAdapterForPlugin } from "./channel-setup/registry.js";
|
||||
import type {
|
||||
ChannelSetupFlowAdapter,
|
||||
ChannelSetupConfiguredResult,
|
||||
ChannelSetupDmPolicy,
|
||||
ChannelSetupResult,
|
||||
ChannelSetupStatus,
|
||||
SetupChannelsOptions,
|
||||
} from "./channel-setup/types.js";
|
||||
import type { ChannelChoice } from "./onboard-types.js";
|
||||
import {
|
||||
ensureOnboardingPluginInstalled,
|
||||
loadOnboardingPluginRegistrySnapshotForChannel,
|
||||
} from "./onboarding/plugin-install.js";
|
||||
import type {
|
||||
ChannelOnboardingAdapter,
|
||||
ChannelOnboardingConfiguredResult,
|
||||
ChannelOnboardingDmPolicy,
|
||||
ChannelOnboardingResult,
|
||||
ChannelOnboardingStatus,
|
||||
SetupChannelsOptions,
|
||||
} from "./onboarding/types.js";
|
||||
|
||||
type ConfiguredChannelAction = "update" | "disable" | "delete" | "skip";
|
||||
|
||||
@@ -45,7 +42,7 @@ type ChannelStatusSummary = {
|
||||
installedPlugins: ReturnType<typeof listChannelSetupPlugins>;
|
||||
catalogEntries: ReturnType<typeof listChannelPluginCatalogEntries>;
|
||||
installedCatalogEntries: ReturnType<typeof listChannelPluginCatalogEntries>;
|
||||
statusByChannel: Map<ChannelChoice, ChannelOnboardingStatus>;
|
||||
statusByChannel: Map<ChannelChoice, ChannelSetupStatus>;
|
||||
statusLines: string[];
|
||||
};
|
||||
|
||||
@@ -122,7 +119,7 @@ async function collectChannelStatus(params: {
|
||||
options?: SetupChannelsOptions;
|
||||
accountOverrides: Partial<Record<ChannelChoice, string>>;
|
||||
installedPlugins?: ChannelOnboardingSetupPlugin[];
|
||||
resolveAdapter?: (channel: ChannelChoice) => ChannelOnboardingAdapter | undefined;
|
||||
resolveAdapter?: (channel: ChannelChoice) => ChannelSetupFlowAdapter | undefined;
|
||||
}): Promise<ChannelStatusSummary> {
|
||||
const installedPlugins = params.installedPlugins ?? listChannelSetupPlugins();
|
||||
const workspaceDir = resolveAgentWorkspaceDir(params.cfg, resolveDefaultAgentId(params.cfg));
|
||||
@@ -134,7 +131,7 @@ async function collectChannelStatus(params: {
|
||||
const resolveAdapter =
|
||||
params.resolveAdapter ??
|
||||
((channel: ChannelChoice) =>
|
||||
resolveChannelOnboardingAdapterForPlugin(
|
||||
resolveChannelSetupFlowAdapterForPlugin(
|
||||
installedPlugins.find((plugin) => plugin.id === channel),
|
||||
));
|
||||
const statusEntries = await Promise.all(
|
||||
@@ -274,13 +271,13 @@ async function maybeConfigureDmPolicies(params: {
|
||||
selection: ChannelChoice[];
|
||||
prompter: WizardPrompter;
|
||||
accountIdsByChannel?: Map<ChannelChoice, string>;
|
||||
resolveAdapter?: (channel: ChannelChoice) => ChannelOnboardingAdapter | undefined;
|
||||
resolveAdapter?: (channel: ChannelChoice) => ChannelSetupFlowAdapter | undefined;
|
||||
}): Promise<OpenClawConfig> {
|
||||
const { selection, prompter, accountIdsByChannel } = params;
|
||||
const resolve = params.resolveAdapter ?? (() => undefined);
|
||||
const dmPolicies = selection
|
||||
.map((channel) => resolve?.(channel)?.dmPolicy)
|
||||
.filter(Boolean) as ChannelOnboardingDmPolicy[];
|
||||
.map((channel) => resolve(channel)?.dmPolicy)
|
||||
.filter(Boolean) as ChannelSetupDmPolicy[];
|
||||
if (dmPolicies.length === 0) {
|
||||
return params.cfg;
|
||||
}
|
||||
@@ -294,7 +291,7 @@ async function maybeConfigureDmPolicies(params: {
|
||||
}
|
||||
|
||||
let cfg = params.cfg;
|
||||
const selectPolicy = async (policy: ChannelOnboardingDmPolicy) => {
|
||||
const selectPolicy = async (policy: ChannelSetupDmPolicy) => {
|
||||
await prompter.note(
|
||||
[
|
||||
"Default: pairing (unknown DMs get a pairing code).",
|
||||
@@ -337,7 +334,7 @@ async function maybeConfigureDmPolicies(params: {
|
||||
return cfg;
|
||||
}
|
||||
|
||||
// Channel-specific prompts moved into onboarding adapters.
|
||||
// Channel-specific prompts moved into setup flow adapters.
|
||||
|
||||
export async function setupChannels(
|
||||
cfg: OpenClawConfig,
|
||||
@@ -393,21 +390,17 @@ export async function setupChannels(
|
||||
rememberScopedPlugin(plugin);
|
||||
return plugin;
|
||||
}
|
||||
const bundledPlugin = await loadBundledChannelOnboardingPlugin(channel);
|
||||
if (bundledPlugin) {
|
||||
rememberScopedPlugin(bundledPlugin);
|
||||
}
|
||||
return bundledPlugin;
|
||||
return undefined;
|
||||
};
|
||||
const getVisibleOnboardingAdapter = (channel: ChannelChoice) => {
|
||||
const getVisibleSetupFlowAdapter = (channel: ChannelChoice) => {
|
||||
const scopedPlugin = scopedPluginsById.get(channel);
|
||||
if (scopedPlugin) {
|
||||
return resolveChannelOnboardingAdapterForPlugin(scopedPlugin);
|
||||
return resolveChannelSetupFlowAdapterForPlugin(scopedPlugin);
|
||||
}
|
||||
return resolveChannelOnboardingAdapterForPlugin(getChannelSetupPlugin(channel));
|
||||
return resolveChannelSetupFlowAdapterForPlugin(getChannelSetupPlugin(channel));
|
||||
};
|
||||
const preloadConfiguredExternalPlugins = () => {
|
||||
// Keep onboarding memory bounded by snapshot-loading only configured external plugins.
|
||||
// Keep setup memory bounded by snapshot-loading only configured external plugins.
|
||||
const workspaceDir = resolveWorkspaceDir();
|
||||
for (const entry of listChannelPluginCatalogEntries({ workspaceDir })) {
|
||||
const channel = entry.id as ChannelChoice;
|
||||
@@ -438,7 +431,7 @@ export async function setupChannels(
|
||||
options,
|
||||
accountOverrides,
|
||||
installedPlugins: listVisibleInstalledPlugins(),
|
||||
resolveAdapter: getVisibleOnboardingAdapter,
|
||||
resolveAdapter: getVisibleSetupFlowAdapter,
|
||||
});
|
||||
if (!options?.skipStatusNote && statusLines.length > 0) {
|
||||
await prompter.note(statusLines.join("\n"), "Channel status");
|
||||
@@ -493,7 +486,7 @@ export async function setupChannels(
|
||||
const accountIdsByChannel = new Map<ChannelChoice, string>();
|
||||
const recordAccount = (channel: ChannelChoice, accountId: string) => {
|
||||
options?.onAccountId?.(channel, accountId);
|
||||
const adapter = getVisibleOnboardingAdapter(channel);
|
||||
const adapter = getVisibleSetupFlowAdapter(channel);
|
||||
adapter?.onAccountRecorded?.(accountId, options);
|
||||
accountIdsByChannel.set(channel, accountId);
|
||||
};
|
||||
@@ -566,7 +559,7 @@ export async function setupChannels(
|
||||
};
|
||||
|
||||
const refreshStatus = async (channel: ChannelChoice) => {
|
||||
const adapter = getVisibleOnboardingAdapter(channel);
|
||||
const adapter = getVisibleSetupFlowAdapter(channel);
|
||||
if (!adapter) {
|
||||
return;
|
||||
}
|
||||
@@ -589,11 +582,11 @@ export async function setupChannels(
|
||||
return false;
|
||||
}
|
||||
const plugin = await loadScopedChannelPlugin(channel);
|
||||
const adapter = getVisibleOnboardingAdapter(channel);
|
||||
const adapter = getVisibleSetupFlowAdapter(channel);
|
||||
if (!plugin) {
|
||||
if (adapter) {
|
||||
await prompter.note(
|
||||
`${channel} plugin not available (continuing with onboarding). If the channel still doesn't work after setup, run \`${formatCliCommand(
|
||||
`${channel} plugin not available (continuing with setup). If the channel still doesn't work after setup, run \`${formatCliCommand(
|
||||
"openclaw plugins list",
|
||||
)}\` and \`${formatCliCommand("openclaw plugins enable " + channel)}\`, then restart the gateway.`,
|
||||
"Channel setup",
|
||||
@@ -608,7 +601,7 @@ export async function setupChannels(
|
||||
return true;
|
||||
};
|
||||
|
||||
const applyOnboardingResult = async (channel: ChannelChoice, result: ChannelOnboardingResult) => {
|
||||
const applySetupResult = async (channel: ChannelChoice, result: ChannelSetupResult) => {
|
||||
next = result.cfg;
|
||||
if (result.accountId) {
|
||||
recordAccount(channel, result.accountId);
|
||||
@@ -617,21 +610,21 @@ export async function setupChannels(
|
||||
await refreshStatus(channel);
|
||||
};
|
||||
|
||||
const applyCustomOnboardingResult = async (
|
||||
const applyCustomSetupResult = async (
|
||||
channel: ChannelChoice,
|
||||
result: ChannelOnboardingConfiguredResult,
|
||||
result: ChannelSetupConfiguredResult,
|
||||
) => {
|
||||
if (result === "skip") {
|
||||
return false;
|
||||
}
|
||||
await applyOnboardingResult(channel, result);
|
||||
await applySetupResult(channel, result);
|
||||
return true;
|
||||
};
|
||||
|
||||
const configureChannel = async (channel: ChannelChoice) => {
|
||||
const adapter = getVisibleOnboardingAdapter(channel);
|
||||
const adapter = getVisibleSetupFlowAdapter(channel);
|
||||
if (!adapter) {
|
||||
await prompter.note(`${channel} does not support onboarding yet.`, "Channel setup");
|
||||
await prompter.note(`${channel} does not support guided setup yet.`, "Channel setup");
|
||||
return;
|
||||
}
|
||||
const result = await adapter.configure({
|
||||
@@ -643,12 +636,12 @@ export async function setupChannels(
|
||||
shouldPromptAccountIds,
|
||||
forceAllowFrom: forceAllowFromChannels.has(channel),
|
||||
});
|
||||
await applyOnboardingResult(channel, result);
|
||||
await applySetupResult(channel, result);
|
||||
};
|
||||
|
||||
const handleConfiguredChannel = async (channel: ChannelChoice, label: string) => {
|
||||
const plugin = getVisibleChannelPlugin(channel);
|
||||
const adapter = getVisibleOnboardingAdapter(channel);
|
||||
const adapter = getVisibleSetupFlowAdapter(channel);
|
||||
if (adapter?.configureWhenConfigured) {
|
||||
const custom = await adapter.configureWhenConfigured({
|
||||
cfg: next,
|
||||
@@ -661,7 +654,7 @@ export async function setupChannels(
|
||||
configured: true,
|
||||
label,
|
||||
});
|
||||
if (!(await applyCustomOnboardingResult(channel, custom))) {
|
||||
if (!(await applyCustomSetupResult(channel, custom))) {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
@@ -772,7 +765,7 @@ export async function setupChannels(
|
||||
}
|
||||
|
||||
const plugin = getVisibleChannelPlugin(channel);
|
||||
const adapter = getVisibleOnboardingAdapter(channel);
|
||||
const adapter = getVisibleSetupFlowAdapter(channel);
|
||||
const label = plugin?.meta.label ?? catalogEntry?.meta.label ?? channel;
|
||||
const status = statusByChannel.get(channel);
|
||||
const configured = status?.configured ?? false;
|
||||
@@ -788,7 +781,7 @@ export async function setupChannels(
|
||||
configured,
|
||||
label,
|
||||
});
|
||||
if (!(await applyCustomOnboardingResult(channel, custom))) {
|
||||
if (!(await applyCustomSetupResult(channel, custom))) {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
@@ -861,7 +854,7 @@ export async function setupChannels(
|
||||
selection,
|
||||
prompter,
|
||||
accountIdsByChannel,
|
||||
resolveAdapter: getVisibleOnboardingAdapter,
|
||||
resolveAdapter: getVisibleSetupFlowAdapter,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user