mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 06:12:45 +00:00
feat: add onboarding plugin install flow
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||
import { listChannelPluginCatalogEntries } from "../channels/plugins/catalog.js";
|
||||
import { listChannelPlugins, getChannelPlugin } from "../channels/plugins/index.js";
|
||||
import { formatChannelPrimerLine, formatChannelSelectionLine } from "../channels/registry.js";
|
||||
import type { ClawdbotConfig } from "../config/config.js";
|
||||
@@ -10,10 +12,25 @@ import {
|
||||
getChannelOnboardingAdapter,
|
||||
listChannelOnboardingAdapters,
|
||||
} from "./onboarding/registry.js";
|
||||
import {
|
||||
ensureOnboardingPluginInstalled,
|
||||
reloadOnboardingPluginRegistry,
|
||||
} from "./onboarding/plugin-install.js";
|
||||
import type { ChannelOnboardingDmPolicy, SetupChannelsOptions } from "./onboarding/types.js";
|
||||
|
||||
async function noteChannelPrimer(prompter: WizardPrompter): Promise<void> {
|
||||
const channelLines = listChannelPlugins().map((plugin) => formatChannelPrimerLine(plugin.meta));
|
||||
async function noteChannelPrimer(
|
||||
prompter: WizardPrompter,
|
||||
channels: Array<{ id: ChannelChoice; blurb: string; label: string }>,
|
||||
): Promise<void> {
|
||||
const channelLines = channels.map((channel) =>
|
||||
formatChannelPrimerLine({
|
||||
id: channel.id,
|
||||
label: channel.label,
|
||||
selectionLabel: channel.label,
|
||||
docsPath: "/",
|
||||
blurb: channel.blurb,
|
||||
}),
|
||||
);
|
||||
await prompter.note(
|
||||
[
|
||||
"DM security: default is pairing; unknown DMs get a pairing code.",
|
||||
@@ -97,6 +114,7 @@ export async function setupChannels(
|
||||
prompter: WizardPrompter,
|
||||
options?: SetupChannelsOptions,
|
||||
): Promise<ClawdbotConfig> {
|
||||
let next = cfg;
|
||||
const forceAllowFromChannels = new Set(options?.forceAllowFromChannels ?? []);
|
||||
const accountOverrides: Partial<Record<ChannelChoice, string>> = {
|
||||
...options?.accountIds,
|
||||
@@ -105,13 +123,27 @@ export async function setupChannels(
|
||||
accountOverrides.whatsapp = options.whatsappAccountId.trim();
|
||||
}
|
||||
|
||||
const installedPlugins = listChannelPlugins();
|
||||
const catalogEntries = listChannelPluginCatalogEntries().filter(
|
||||
(entry) => !installedPlugins.some((plugin) => plugin.id === entry.id),
|
||||
);
|
||||
const statusEntries = await Promise.all(
|
||||
listChannelOnboardingAdapters().map((adapter) =>
|
||||
adapter.getStatus({ cfg, options, accountOverrides }),
|
||||
),
|
||||
);
|
||||
const statusByChannel = new Map(statusEntries.map((entry) => [entry.channel, entry]));
|
||||
const statusLines = statusEntries.flatMap((entry) => entry.statusLines);
|
||||
const catalogStatuses = catalogEntries.map((entry) => ({
|
||||
channel: entry.id,
|
||||
configured: false,
|
||||
statusLines: [`${entry.meta.label}: install plugin to enable`],
|
||||
selectionHint: "plugin · install",
|
||||
quickstartScore: 0,
|
||||
}));
|
||||
const combinedStatuses = [...statusEntries, ...catalogStatuses];
|
||||
const statusByChannel = new Map(
|
||||
combinedStatuses.map((entry) => [entry.channel, entry]),
|
||||
);
|
||||
const statusLines = combinedStatuses.flatMap((entry) => entry.statusLines);
|
||||
if (statusLines.length > 0) {
|
||||
await prompter.note(statusLines.join("\n"), "Channel status");
|
||||
}
|
||||
@@ -124,11 +156,32 @@ export async function setupChannels(
|
||||
});
|
||||
if (!shouldConfigure) return cfg;
|
||||
|
||||
await noteChannelPrimer(prompter);
|
||||
const primerChannels = [
|
||||
...installedPlugins.map((plugin) => ({
|
||||
id: plugin.id as ChannelChoice,
|
||||
label: plugin.meta.label,
|
||||
blurb: plugin.meta.blurb,
|
||||
})),
|
||||
...catalogEntries.map((entry) => ({
|
||||
id: entry.id as ChannelChoice,
|
||||
label: entry.meta.label,
|
||||
blurb: entry.meta.blurb,
|
||||
})),
|
||||
];
|
||||
await noteChannelPrimer(prompter, primerChannels);
|
||||
|
||||
const selectionOptions = listChannelPlugins().map((plugin) => {
|
||||
const meta = plugin.meta;
|
||||
const status = statusByChannel.get(meta.id as ChannelChoice);
|
||||
const selectionOptions = [
|
||||
...installedPlugins.map((plugin) => ({
|
||||
id: plugin.id as ChannelChoice,
|
||||
meta: plugin.meta,
|
||||
})),
|
||||
...catalogEntries.map((entry) => ({
|
||||
id: entry.id as ChannelChoice,
|
||||
meta: entry.meta,
|
||||
})),
|
||||
].map((entry) => {
|
||||
const meta = entry.meta;
|
||||
const status = statusByChannel.get(entry.id);
|
||||
return {
|
||||
value: meta.id,
|
||||
label: meta.selectionLabel ?? meta.label,
|
||||
@@ -163,12 +216,43 @@ export async function setupChannels(
|
||||
})) as ChannelChoice[];
|
||||
}
|
||||
|
||||
const catalogById = new Map(
|
||||
catalogEntries.map((entry) => [entry.id as ChannelChoice, entry]),
|
||||
);
|
||||
if (selection.some((channel) => catalogById.has(channel))) {
|
||||
const workspaceDir = resolveAgentWorkspaceDir(next, resolveDefaultAgentId(next));
|
||||
for (const channel of selection) {
|
||||
const entry = catalogById.get(channel);
|
||||
if (!entry) continue;
|
||||
const result = await ensureOnboardingPluginInstalled({
|
||||
cfg: next,
|
||||
entry,
|
||||
prompter,
|
||||
runtime,
|
||||
workspaceDir,
|
||||
});
|
||||
next = result.cfg;
|
||||
if (!result.installed) {
|
||||
selection = selection.filter((id) => id !== channel);
|
||||
continue;
|
||||
}
|
||||
reloadOnboardingPluginRegistry({
|
||||
cfg: next,
|
||||
runtime,
|
||||
workspaceDir,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
options?.onSelection?.(selection);
|
||||
|
||||
const selectionNotes = new Map(
|
||||
listChannelPlugins().map((plugin) => [
|
||||
plugin.id,
|
||||
formatChannelSelectionLine(plugin.meta, formatDocsLink),
|
||||
[
|
||||
...installedPlugins.map((plugin) => [plugin.id, plugin.meta]),
|
||||
...catalogEntries.map((entry) => [entry.id, entry.meta]),
|
||||
].map(([id, meta]) => [
|
||||
id,
|
||||
formatChannelSelectionLine(meta, formatDocsLink),
|
||||
]),
|
||||
);
|
||||
const selectedLines = selection
|
||||
@@ -185,7 +269,6 @@ export async function setupChannels(
|
||||
adapter?.onAccountRecorded?.(accountId, options);
|
||||
};
|
||||
|
||||
let next = cfg;
|
||||
for (const channel of selection) {
|
||||
const adapter = getChannelOnboardingAdapter(channel);
|
||||
if (!adapter) continue;
|
||||
|
||||
Reference in New Issue
Block a user