refactor: install optional channel capabilities on demand
This commit is contained in:
parent
19126033dd
commit
25015161fe
@ -7,6 +7,10 @@ import { channelsCapabilitiesCommand } from "./capabilities.js";
|
||||
|
||||
const logs: string[] = [];
|
||||
const errors: string[] = [];
|
||||
const mocks = vi.hoisted(() => ({
|
||||
writeConfigFile: vi.fn(),
|
||||
resolveInstallableChannelPlugin: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./shared.js", () => ({
|
||||
requireValidConfig: vi.fn(async () => ({ channels: {} })),
|
||||
@ -20,6 +24,18 @@ vi.mock("../../channels/plugins/index.js", () => ({
|
||||
getChannelPlugin: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../../config/config.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../../config/config.js")>();
|
||||
return {
|
||||
...actual,
|
||||
writeConfigFile: mocks.writeConfigFile,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../channel-setup/channel-plugin-resolution.js", () => ({
|
||||
resolveInstallableChannelPlugin: mocks.resolveInstallableChannelPlugin,
|
||||
}));
|
||||
|
||||
const runtime = {
|
||||
log: (...args: unknown[]) => {
|
||||
logs.push(args.map(String).join(" "));
|
||||
@ -77,6 +93,11 @@ describe("channelsCapabilitiesCommand", () => {
|
||||
beforeEach(() => {
|
||||
resetOutput();
|
||||
vi.clearAllMocks();
|
||||
mocks.writeConfigFile.mockResolvedValue(undefined);
|
||||
mocks.resolveInstallableChannelPlugin.mockResolvedValue({
|
||||
cfg: { channels: {} },
|
||||
configChanged: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("prints Slack bot + user scopes when user token is configured", async () => {
|
||||
@ -106,6 +127,12 @@ describe("channelsCapabilitiesCommand", () => {
|
||||
};
|
||||
vi.mocked(listChannelPlugins).mockReturnValue([plugin]);
|
||||
vi.mocked(getChannelPlugin).mockReturnValue(plugin);
|
||||
mocks.resolveInstallableChannelPlugin.mockResolvedValue({
|
||||
cfg: { channels: {} },
|
||||
channelId: "slack",
|
||||
plugin,
|
||||
configChanged: false,
|
||||
});
|
||||
|
||||
await channelsCapabilitiesCommand({ channel: "slack" }, runtime);
|
||||
|
||||
@ -139,6 +166,12 @@ describe("channelsCapabilitiesCommand", () => {
|
||||
};
|
||||
vi.mocked(listChannelPlugins).mockReturnValue([plugin]);
|
||||
vi.mocked(getChannelPlugin).mockReturnValue(plugin);
|
||||
mocks.resolveInstallableChannelPlugin.mockResolvedValue({
|
||||
cfg: { channels: {} },
|
||||
channelId: "msteams",
|
||||
plugin,
|
||||
configChanged: false,
|
||||
});
|
||||
|
||||
await channelsCapabilitiesCommand({ channel: "msteams" }, runtime);
|
||||
|
||||
@ -146,4 +179,41 @@ describe("channelsCapabilitiesCommand", () => {
|
||||
expect(output).toContain("ChannelMessage.Read.All (channel history)");
|
||||
expect(output).toContain("Files.Read.All (files (OneDrive))");
|
||||
});
|
||||
|
||||
it("installs an explicit optional channel before rendering capabilities", async () => {
|
||||
const plugin = buildPlugin({
|
||||
id: "whatsapp",
|
||||
probe: { ok: true },
|
||||
});
|
||||
plugin.status = {
|
||||
...plugin.status,
|
||||
formatCapabilitiesProbe: () => [{ text: "Probe: linked" }],
|
||||
};
|
||||
mocks.resolveInstallableChannelPlugin.mockResolvedValue({
|
||||
cfg: {
|
||||
channels: {},
|
||||
plugins: { entries: { whatsapp: { enabled: true } } },
|
||||
},
|
||||
channelId: "whatsapp",
|
||||
plugin,
|
||||
configChanged: true,
|
||||
});
|
||||
vi.mocked(listChannelPlugins).mockReturnValue([]);
|
||||
vi.mocked(getChannelPlugin).mockReturnValue(undefined);
|
||||
|
||||
await channelsCapabilitiesCommand({ channel: "whatsapp" }, runtime);
|
||||
|
||||
expect(mocks.resolveInstallableChannelPlugin).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
rawChannel: "whatsapp",
|
||||
allowInstall: true,
|
||||
}),
|
||||
);
|
||||
expect(mocks.writeConfigFile).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
plugins: { entries: { whatsapp: { enabled: true } } },
|
||||
}),
|
||||
);
|
||||
expect(logs.join("\n")).toContain("Probe: linked");
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { resolveChannelDefaultAccountId } from "../../channels/plugins/helpers.js";
|
||||
import { getChannelPlugin, listChannelPlugins } from "../../channels/plugins/index.js";
|
||||
import { listChannelPlugins } from "../../channels/plugins/index.js";
|
||||
import {
|
||||
createMessageActionDiscoveryContext,
|
||||
resolveMessageActionDiscoveryForPlugin,
|
||||
@ -10,10 +10,11 @@ import type {
|
||||
ChannelCapabilitiesDisplayLine,
|
||||
ChannelPlugin,
|
||||
} from "../../channels/plugins/types.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { writeConfigFile, type OpenClawConfig } from "../../config/config.js";
|
||||
import { danger } from "../../globals.js";
|
||||
import { defaultRuntime, type RuntimeEnv } from "../../runtime.js";
|
||||
import { theme } from "../../terminal/theme.js";
|
||||
import { resolveInstallableChannelPlugin } from "../channel-setup/channel-plugin-resolution.js";
|
||||
import { formatChannelAccountLabel, requireValidConfig } from "./shared.js";
|
||||
|
||||
export type ChannelsCapabilitiesOptions = {
|
||||
@ -25,6 +26,7 @@ export type ChannelsCapabilitiesOptions = {
|
||||
};
|
||||
|
||||
type ChannelCapabilitiesReport = {
|
||||
plugin: ChannelPlugin;
|
||||
channel: string;
|
||||
accountId: string;
|
||||
accountName?: string;
|
||||
@ -183,6 +185,7 @@ async function resolveChannelReports(params: {
|
||||
);
|
||||
|
||||
reports.push({
|
||||
plugin,
|
||||
channel: plugin.id,
|
||||
accountId,
|
||||
accountName:
|
||||
@ -204,10 +207,11 @@ export async function channelsCapabilitiesCommand(
|
||||
opts: ChannelsCapabilitiesOptions,
|
||||
runtime: RuntimeEnv = defaultRuntime,
|
||||
) {
|
||||
const cfg = await requireValidConfig(runtime);
|
||||
if (!cfg) {
|
||||
const loadedCfg = await requireValidConfig(runtime);
|
||||
if (!loadedCfg) {
|
||||
return;
|
||||
}
|
||||
let cfg = loadedCfg;
|
||||
const timeoutMs = normalizeTimeout(opts.timeout, 10_000);
|
||||
const rawChannel = typeof opts.channel === "string" ? opts.channel.trim().toLowerCase() : "";
|
||||
const rawTarget = typeof opts.target === "string" ? opts.target.trim() : "";
|
||||
@ -227,12 +231,18 @@ export async function channelsCapabilitiesCommand(
|
||||
const selected =
|
||||
!rawChannel || rawChannel === "all"
|
||||
? plugins
|
||||
: (() => {
|
||||
const plugin = getChannelPlugin(rawChannel);
|
||||
if (!plugin) {
|
||||
return null;
|
||||
: await (async () => {
|
||||
const resolved = await resolveInstallableChannelPlugin({
|
||||
cfg,
|
||||
runtime,
|
||||
rawChannel,
|
||||
allowInstall: true,
|
||||
});
|
||||
if (resolved.configChanged) {
|
||||
cfg = resolved.cfg;
|
||||
await writeConfigFile(cfg);
|
||||
}
|
||||
return [plugin];
|
||||
return resolved.plugin ? [resolved.plugin] : null;
|
||||
})();
|
||||
|
||||
if (!selected || selected.length === 0) {
|
||||
@ -280,7 +290,7 @@ export async function channelsCapabilitiesCommand(
|
||||
lines.push(`Status: ${configuredLabel}, ${enabledLabel}`);
|
||||
}
|
||||
const probeLines =
|
||||
getChannelPlugin(report.channel)?.status?.formatCapabilitiesProbe?.({
|
||||
report.plugin.status?.formatCapabilitiesProbe?.({
|
||||
probe: report.probe,
|
||||
}) ?? formatGenericProbeLines(report.probe);
|
||||
if (probeLines.length > 0) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user