From 9a11e764589c3c650afd89be7c6ed9d02ae7bd9a Mon Sep 17 00:00:00 2001 From: Zavian Wang <36817799+Zavianx@users.noreply.github.com> Date: Sun, 17 May 2026 14:13:55 +0800 Subject: [PATCH] fix(plugins): surface configured runtime plugin doctor warnings (#81674) Fixes #81326. Summary: - Warn from `openclaw plugins doctor` when configured runtime owner plugins are missing, disabled, or blocked. - Share configured-runtime plugin install mapping with `openclaw doctor --fix`, including ACP/acpx. - Keep implicit OpenAI Codex preferences quiet to avoid false-positive plugin doctor warnings. Verification: - `pnpm test src/agents/harness-runtimes.test.ts src/cli/plugins-cli.list.test.ts src/commands/doctor/shared/missing-configured-plugin-install.test.ts -- --reporter=verbose` - `pnpm exec oxfmt --check CHANGELOG.md src/agents/harness-runtimes.ts src/agents/harness-runtimes.test.ts src/cli/plugins-cli.runtime.ts src/cli/plugins-cli.list.test.ts src/commands/doctor/shared/configured-runtime-plugin-installs.ts src/commands/doctor/shared/missing-configured-plugin-install.ts` - `pnpm build:plugin-sdk:dts` - `codex-review --mode branch` - Testbox-through-Crabbox `pnpm check:changed`: provider `blacksmith-testbox`, id `tbx_01krt8vte22m7ht6wfss4jkeaa`, Actions run https://github.com/openclaw/openclaw/actions/runs/25983150787, exit 0 Co-authored-by: Zavian Wang <36817799+Zavianx@users.noreply.github.com> --- CHANGELOG.md | 1 + src/agents/harness-runtimes.test.ts | 27 ++ src/agents/harness-runtimes.ts | 21 +- src/cli/plugins-cli.list.test.ts | 257 ++++++++++++++++++ src/cli/plugins-cli.runtime.ts | 111 +++++++- .../configured-runtime-plugin-installs.ts | 67 +++++ .../missing-configured-plugin-install.ts | 35 +-- 7 files changed, 483 insertions(+), 36 deletions(-) create mode 100644 src/commands/doctor/shared/configured-runtime-plugin-installs.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ce665fb92c..745cd46e761 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- CLI/plugins: have `openclaw plugins doctor` warn when a configured runtime needs a missing owner plugin, sharing the same install mapping as `openclaw doctor --fix`. Fixes #81326. (#81674) Thanks @Zavianx. - Agents/skills: apply the full effective tool policy pipeline to inline `command-dispatch: tool` skill dispatch before owner-only filtering, preserving configured allow, deny, sandbox, sender, group, and subagent restrictions. (#78525) - Channel accounts: keep top-level default channel accounts visible when named accounts are added alongside default credential material, so mixed legacy/new account configs keep resolving `default` instead of silently dropping it. - Codex/Telegram: synthesize native Codex tool progress from final turn snapshots so Telegram `/verbose` stays visible when command events arrive only at completion. diff --git a/src/agents/harness-runtimes.test.ts b/src/agents/harness-runtimes.test.ts index c727f660a20..cd256a8a28d 100644 --- a/src/agents/harness-runtimes.test.ts +++ b/src/agents/harness-runtimes.test.ts @@ -20,6 +20,33 @@ describe("collectConfiguredAgentHarnessRuntimes", () => { ); }); + it("can ignore implicit OpenAI Codex runtime preferences", () => { + const config = { + agents: { + defaults: { + model: "openai/gpt-5.5", + models: { + "openai/gpt-5.4": {}, + "anthropic/claude-opus-4-7": { + agentRuntime: { id: "codex" }, + }, + }, + }, + }, + } as OpenClawConfig; + + expect( + collectConfiguredAgentHarnessRuntimes( + config, + {}, + { + includeEnvRuntime: false, + includeImplicitRuntimePreferences: false, + }, + ), + ).toEqual(["codex"]); + }); + it("requires Codex for selectable per-agent OpenAI models", () => { const config = { agents: { diff --git a/src/agents/harness-runtimes.ts b/src/agents/harness-runtimes.ts index d2501bd7b70..e69d1048d4f 100644 --- a/src/agents/harness-runtimes.ts +++ b/src/agents/harness-runtimes.ts @@ -62,6 +62,7 @@ function parseConfiguredModelRef( function resolveConfiguredModelHarnessRuntime(params: { config: OpenClawConfig; + includeImplicitRuntimePreferences: boolean; modelRef: string; agentId?: string; }): string | undefined { @@ -75,6 +76,9 @@ function resolveConfiguredModelHarnessRuntime(params: { modelId: parsed.modelId, agentId: params.agentId, }); + if (!params.includeImplicitRuntimePreferences && policy.runtimeSource === "implicit") { + return undefined; + } const runtime = normalizeRuntimeId(policy.runtime); return runtime && runtime !== "auto" && runtime !== "pi" ? runtime : undefined; } @@ -115,10 +119,19 @@ function pushConfiguredModelRuntimeIds(config: OpenClawConfig, runtimes: Set): void { +function pushConfiguredAgentModelRuntimeIds( + config: OpenClawConfig, + runtimes: Set, + includeImplicitRuntimePreferences: boolean, +): void { const pushModelRefs = (modelRefs: string[], agentId?: string) => { for (const modelRef of modelRefs) { - const runtime = resolveConfiguredModelHarnessRuntime({ config, modelRef, agentId }); + const runtime = resolveConfiguredModelHarnessRuntime({ + config, + includeImplicitRuntimePreferences, + modelRef, + agentId, + }); if (runtime) { runtimes.add(runtime); } @@ -169,6 +182,7 @@ function pushLegacyAgentRuntimeIds(config: OpenClawConfig, runtimes: Set export type ConfiguredAgentHarnessRuntimeOptions = { includeEnvRuntime?: boolean; + includeImplicitRuntimePreferences?: boolean; includeLegacyAgentRuntimes?: boolean; }; @@ -179,6 +193,7 @@ export function collectConfiguredAgentHarnessRuntimes( ): string[] { const runtimes = new Set(); const includeEnvRuntime = options.includeEnvRuntime ?? true; + const includeImplicitRuntimePreferences = options.includeImplicitRuntimePreferences ?? true; const includeLegacyAgentRuntimes = options.includeLegacyAgentRuntimes ?? true; if (includeEnvRuntime) { @@ -191,7 +206,7 @@ export function collectConfiguredAgentHarnessRuntimes( if (includeLegacyAgentRuntimes) { pushLegacyAgentRuntimeIds(config, runtimes); } - pushConfiguredAgentModelRuntimeIds(config, runtimes); + pushConfiguredAgentModelRuntimeIds(config, runtimes, includeImplicitRuntimePreferences); return [...runtimes].toSorted((left, right) => left.localeCompare(right)); } diff --git a/src/cli/plugins-cli.list.test.ts b/src/cli/plugins-cli.list.test.ts index 741716d8fb9..05e44cf55ad 100644 --- a/src/cli/plugins-cli.list.test.ts +++ b/src/cli/plugins-cli.list.test.ts @@ -138,6 +138,263 @@ describe("plugins cli list", () => { expect(output).not.toContain("No plugin issues detected."); }); + it("reports missing configured Codex runtime plugin in doctor output", async () => { + const sourceConfig = { + agents: { + defaults: { + models: { + "openai/gpt-5.5": { + agentRuntime: { id: "codex" }, + }, + }, + }, + }, + }; + loadConfig.mockReturnValue(sourceConfig); + readConfigFileSnapshot.mockResolvedValueOnce({ + path: "/tmp/openclaw-config.json5", + exists: true, + raw: "{}", + parsed: sourceConfig, + resolved: sourceConfig, + sourceConfig, + runtimeConfig: sourceConfig, + config: sourceConfig, + valid: true, + hash: "mock", + issues: [], + warnings: [], + legacyIssues: [], + }); + buildPluginDiagnosticsReport.mockReturnValue({ + plugins: [], + diagnostics: [], + }); + + await runPluginsCommand(["plugins", "doctor"]); + + const output = runtimeLogs.join("\n"); + expect(output).toContain("Plugin configuration:"); + expect(output).toContain('Configured runtime "codex" requires the Codex plugin'); + expect(output).toContain("openclaw doctor --fix"); + expect(output).toContain("openclaw plugins install @openclaw/codex"); + expect(output).toContain( + "No plugin install-tree issues detected; configuration warnings remain.", + ); + expect(output).not.toContain("No plugin issues detected."); + }); + + it("reports missing configured ACPX runtime plugin in doctor output", async () => { + const sourceConfig = { + acp: { + backend: "acpx", + }, + }; + loadConfig.mockReturnValue(sourceConfig); + buildPluginDiagnosticsReport.mockReturnValue({ + plugins: [], + diagnostics: [], + }); + + await runPluginsCommand(["plugins", "doctor"]); + + const output = runtimeLogs.join("\n"); + expect(output).toContain("Plugin configuration:"); + expect(output).toContain('Configured runtime "acpx" requires the ACPX Runtime plugin'); + expect(output).toContain("openclaw doctor --fix"); + expect(output).toContain("openclaw plugins install @openclaw/acpx"); + expect(output).not.toContain("No plugin issues detected."); + }); + + it("reports blocked configured ACPX runtime with ACP-specific guidance", async () => { + const sourceConfig = { + acp: { + backend: "acpx", + }, + plugins: { + entries: { + acpx: { enabled: false }, + }, + }, + }; + loadConfig.mockReturnValue(sourceConfig); + buildPluginDiagnosticsReport.mockReturnValue({ + plugins: [], + diagnostics: [], + }); + + await runPluginsCommand(["plugins", "doctor"]); + + const output = runtimeLogs.join("\n"); + expect(output).toContain('Configured runtime "acpx" requires the ACPX Runtime plugin'); + expect(output).toContain("Set plugins.entries.acpx.enabled=true"); + expect(output).toContain("disable ACP/acpx in acp config"); + expect(output).not.toContain('runtime policy to "pi"'); + expect(output).not.toContain("openclaw plugins install @openclaw/acpx"); + expect(output).not.toContain("No plugin issues detected."); + }); + + it("reports disabled configured ACPX runtime with ACP-specific guidance", async () => { + const sourceConfig = { + acp: { + backend: "acpx", + }, + }; + loadConfig.mockReturnValue(sourceConfig); + buildPluginDiagnosticsReport.mockReturnValue({ + plugins: [createPluginRecord({ id: "acpx", enabled: false, status: "disabled" })], + diagnostics: [], + }); + + await runPluginsCommand(["plugins", "doctor"]); + + const output = runtimeLogs.join("\n"); + expect(output).toContain('Configured runtime "acpx" requires the ACPX Runtime plugin'); + expect(output).toContain('Enable the "acpx" plugin'); + expect(output).toContain("disable ACP/acpx in acp config"); + expect(output).not.toContain('runtime policy to "pi"'); + expect(output).not.toContain("openclaw plugins install @openclaw/acpx"); + expect(output).not.toContain("No plugin issues detected."); + }); + + it("does not report implicit OpenAI Codex preference as configured runtime", async () => { + const sourceConfig = { + agents: { + defaults: { + model: "openai/gpt-5.5", + }, + }, + }; + loadConfig.mockReturnValue(sourceConfig); + buildPluginDiagnosticsReport.mockReturnValue({ + plugins: [], + diagnostics: [], + }); + + await runPluginsCommand(["plugins", "doctor"]); + + const output = runtimeLogs.join("\n"); + expect(output).not.toContain('Configured runtime "codex"'); + expect(output).toContain("No plugin issues detected."); + }); + + it("does not report configured Codex runtime when the plugin is enabled", async () => { + const sourceConfig = { + agents: { + defaults: { + models: { + "openai/gpt-5.5": { + agentRuntime: { id: "codex" }, + }, + }, + }, + }, + }; + loadConfig.mockReturnValue(sourceConfig); + buildPluginDiagnosticsReport.mockReturnValue({ + plugins: [createPluginRecord({ id: "codex" })], + diagnostics: [], + }); + + await runPluginsCommand(["plugins", "doctor"]); + + expect(runtimeLogs).toContain("No plugin issues detected."); + }); + + it("reports configured Codex runtime when the plugin record is disabled", async () => { + const sourceConfig = { + agents: { + defaults: { + models: { + "openai/gpt-5.5": { + agentRuntime: { id: "codex" }, + }, + }, + }, + }, + }; + loadConfig.mockReturnValue(sourceConfig); + buildPluginDiagnosticsReport.mockReturnValue({ + plugins: [createPluginRecord({ id: "codex", enabled: false, status: "disabled" })], + diagnostics: [], + }); + + await runPluginsCommand(["plugins", "doctor"]); + + const output = runtimeLogs.join("\n"); + expect(output).toContain('Configured runtime "codex" requires the Codex plugin'); + expect(output).toContain('but "codex" is disabled'); + expect(output).toContain('Enable the "codex" plugin'); + expect(output).not.toContain("openclaw plugins install @openclaw/codex"); + expect(output).not.toContain("No plugin issues detected."); + }); + + it("reports blocked configured Codex runtime without install advice", async () => { + const sourceConfig = { + plugins: { + deny: ["codex"], + }, + agents: { + defaults: { + models: { + "openai/gpt-5.5": { + agentRuntime: { id: "codex" }, + }, + }, + }, + }, + }; + loadConfig.mockReturnValue(sourceConfig); + buildPluginDiagnosticsReport.mockReturnValue({ + plugins: [], + diagnostics: [], + }); + + await runPluginsCommand(["plugins", "doctor"]); + + const output = runtimeLogs.join("\n"); + expect(output).toContain('Configured runtime "codex" requires the Codex plugin'); + expect(output).toContain('but "codex" is blocked by plugin configuration'); + expect(output).toContain('Remove "codex" from plugins.deny'); + expect(output).not.toContain('Run "openclaw doctor --fix" to install'); + expect(output).not.toContain("openclaw plugins install @openclaw/codex"); + expect(output).not.toContain("No plugin issues detected."); + }); + + it("reports disabled configured Codex runtime entry without install advice", async () => { + const sourceConfig = { + plugins: { + entries: { + codex: { enabled: false }, + }, + }, + agents: { + defaults: { + models: { + "openai/gpt-5.5": { + agentRuntime: { id: "codex" }, + }, + }, + }, + }, + }; + loadConfig.mockReturnValue(sourceConfig); + buildPluginDiagnosticsReport.mockReturnValue({ + plugins: [], + diagnostics: [], + }); + + await runPluginsCommand(["plugins", "doctor"]); + + const output = runtimeLogs.join("\n"); + expect(output).toContain('Configured runtime "codex" requires the Codex plugin'); + expect(output).toContain('but "codex" is blocked by plugin configuration'); + expect(output).toContain("Set plugins.entries.codex.enabled=true"); + expect(output).not.toContain('Run "openclaw doctor --fix" to install'); + expect(output).not.toContain("openclaw plugins install @openclaw/codex"); + expect(output).not.toContain("No plugin issues detected."); + }); + it("reports config-selected plugin source shadowing in doctor output", async () => { buildPluginDiagnosticsReport.mockReturnValue({ plugins: [ diff --git a/src/cli/plugins-cli.runtime.ts b/src/cli/plugins-cli.runtime.ts index 94b799d12b5..91be8d4ed54 100644 --- a/src/cli/plugins-cli.runtime.ts +++ b/src/cli/plugins-cli.runtime.ts @@ -1,3 +1,7 @@ +import { + collectConfiguredRuntimePluginIds, + resolveConfiguredRuntimePluginInstallCandidate, +} from "../commands/doctor/shared/configured-runtime-plugin-installs.js"; import { assertConfigWriteAllowedInCurrentMode, getRuntimeConfig, @@ -67,6 +71,99 @@ function isErroredConfigSelectedShadowDiagnostic(params: { ); } +function formatConfiguredRuntimePluginInstallSpec(params: { + clawhubSpec?: string; + defaultChoice?: string; + npmSpec?: string; + pluginId: string; +}): string { + const clawhubSpec = params.clawhubSpec?.trim(); + const npmSpec = params.npmSpec?.trim(); + if (clawhubSpec && params.defaultChoice !== "npm") { + return clawhubSpec; + } + return npmSpec ?? clawhubSpec ?? params.pluginId; +} + +function pluginIdListIncludes(list: readonly string[] | undefined, pluginId: string): boolean { + return Array.isArray(list) && list.some((entry) => entry.trim() === pluginId); +} + +function formatBlockedRuntimePluginGuidance(params: { + cfg: OpenClawConfig; + pluginId: string; +}): string | undefined { + const pluginId = params.pluginId; + const alternative = + pluginId === "acpx" ? "disable ACP/acpx in acp config" : 'change the runtime policy to "pi"'; + if (params.cfg.plugins?.enabled === false) { + return `Enable plugin loading and the "${pluginId}" plugin, or ${alternative}.`; + } + if (pluginIdListIncludes(params.cfg.plugins?.deny, pluginId)) { + return `Remove "${pluginId}" from plugins.deny and enable the "${pluginId}" plugin, or ${alternative}.`; + } + if (params.cfg.plugins?.entries?.[pluginId]?.enabled === false) { + return `Set plugins.entries.${pluginId}.enabled=true or remove that disabled entry, or ${alternative}.`; + } + return undefined; +} + +function formatDisabledRuntimePluginGuidance(params: { + cfg: OpenClawConfig; + pluginId: string; +}): string { + const allow = params.cfg.plugins?.allow; + const alternative = + params.pluginId === "acpx" + ? "disable ACP/acpx in acp config" + : 'change the runtime policy to "pi"'; + if (Array.isArray(allow) && allow.length > 0 && !allow.includes(params.pluginId)) { + return `Add "${params.pluginId}" to plugins.allow and enable the plugin, or ${alternative}.`; + } + return `Enable the "${params.pluginId}" plugin, or ${alternative}.`; +} + +function collectConfiguredRuntimePluginWarnings(params: { + cfg: OpenClawConfig; + env: NodeJS.ProcessEnv; + plugins: readonly { enabled?: boolean; id: string; status?: string }[]; +}): string[] { + const enabledPluginIds = new Set( + params.plugins + .filter((plugin) => plugin.enabled !== false && plugin.status !== "disabled") + .map((plugin) => plugin.id), + ); + return collectConfiguredRuntimePluginIds(params.cfg, params.env, { + includeEnvRuntime: false, + includeImplicitRuntimePreferences: false, + includeLegacyAgentRuntimes: false, + }).flatMap((runtimeId) => { + const candidate = resolveConfiguredRuntimePluginInstallCandidate(runtimeId); + if (!candidate || enabledPluginIds.has(runtimeId)) { + return []; + } + const disabledPluginRecord = params.plugins.find((plugin) => plugin.id === runtimeId); + const blockedGuidance = formatBlockedRuntimePluginGuidance({ + cfg: params.cfg, + pluginId: runtimeId, + }); + if (blockedGuidance) { + return [ + `- Configured runtime "${runtimeId}" requires the ${candidate.label} plugin, but "${runtimeId}" is blocked by plugin configuration. ${blockedGuidance}`, + ]; + } + if (disabledPluginRecord) { + return [ + `- Configured runtime "${runtimeId}" requires the ${candidate.label} plugin, but "${runtimeId}" is disabled. ${formatDisabledRuntimePluginGuidance({ cfg: params.cfg, pluginId: runtimeId })}`, + ]; + } + const installSpec = formatConfiguredRuntimePluginInstallSpec(candidate); + return [ + `- Configured runtime "${runtimeId}" requires the ${candidate.label} plugin, but no enabled "${runtimeId}" plugin was found. Run "openclaw doctor --fix" to install ${installSpec}, or install it manually with "openclaw plugins install ${installSpec}".`, + ]; + }); +} + export async function runPluginsEnableCommand(id: string): Promise { assertConfigWriteAllowedInCurrentMode(); @@ -240,10 +337,16 @@ export async function runPluginsDoctorCommand(): Promise { doctorFixCommand: "openclaw doctor --fix", autoRepairBlocked: isStalePluginAutoRepairBlocked(sourceCfg ?? cfg, process.env), }); + const configuredRuntimePluginWarnings = collectConfiguredRuntimePluginWarnings({ + cfg: sourceCfg ?? cfg, + env: process.env, + plugins: report.plugins, + }); const hasInstallTreeIssues = errors.length > 0 || diags.length > 0 || shadowed.length > 0 || compatibility.length > 0; + const pluginConfigWarnings = [...stalePluginConfigWarnings, ...configuredRuntimePluginWarnings]; - if (!hasInstallTreeIssues && stalePluginConfigWarnings.length === 0) { + if (!hasInstallTreeIssues && pluginConfigWarnings.length === 0) { defaultRuntime.log("No plugin issues detected."); return; } @@ -301,14 +404,14 @@ export async function runPluginsDoctorCommand(): Promise { lines.push(`- ${formatPluginCompatibilityNotice(notice)} [${marker}]`); } } - if (stalePluginConfigWarnings.length > 0) { + if (pluginConfigWarnings.length > 0) { if (lines.length > 0) { lines.push(""); } lines.push(theme.warn("Plugin configuration:")); - lines.push(...stalePluginConfigWarnings); + lines.push(...pluginConfigWarnings); } - if (!hasInstallTreeIssues && stalePluginConfigWarnings.length > 0) { + if (!hasInstallTreeIssues && pluginConfigWarnings.length > 0) { if (lines.length > 0) { lines.push(""); } diff --git a/src/commands/doctor/shared/configured-runtime-plugin-installs.ts b/src/commands/doctor/shared/configured-runtime-plugin-installs.ts new file mode 100644 index 00000000000..e17f4c3636c --- /dev/null +++ b/src/commands/doctor/shared/configured-runtime-plugin-installs.ts @@ -0,0 +1,67 @@ +import { + collectConfiguredAgentHarnessRuntimes, + type ConfiguredAgentHarnessRuntimeOptions, +} from "../../../agents/harness-runtimes.js"; +import type { OpenClawConfig } from "../../../config/types.openclaw.js"; +import type { PluginPackageInstall } from "../../../plugins/manifest.js"; + +export type ConfiguredRuntimePluginInstallCandidate = { + pluginId: string; + label: string; + npmSpec?: string; + clawhubSpec?: string; + trustedSourceLinkedOfficialInstall?: boolean; + defaultChoice?: PluginPackageInstall["defaultChoice"]; +}; + +export const CONFIGURED_RUNTIME_PLUGIN_INSTALL_CANDIDATES: readonly ConfiguredRuntimePluginInstallCandidate[] = + [ + { + pluginId: "acpx", + label: "ACPX Runtime", + npmSpec: "@openclaw/acpx", + trustedSourceLinkedOfficialInstall: true, + }, + // Runtime-only configs do not have a provider/channel integration catalog entry. + { + pluginId: "codex", + label: "Codex", + npmSpec: "@openclaw/codex", + trustedSourceLinkedOfficialInstall: true, + }, + ]; + +export function resolveConfiguredRuntimePluginInstallCandidate( + runtimeId: string, +): ConfiguredRuntimePluginInstallCandidate | undefined { + return CONFIGURED_RUNTIME_PLUGIN_INSTALL_CANDIDATES.find( + (candidate) => candidate.pluginId === runtimeId, + ); +} + +function asRecord(value: unknown): Record | undefined { + return value && typeof value === "object" && !Array.isArray(value) + ? (value as Record) + : undefined; +} + +function acpxRuntimeIsConfigured(cfg: OpenClawConfig): boolean { + const acp = asRecord(cfg.acp); + const backend = typeof acp?.backend === "string" ? acp.backend.trim().toLowerCase() : ""; + return ( + (backend === "acpx" || acp?.enabled === true || asRecord(acp?.dispatch)?.enabled === true) && + (!backend || backend === "acpx") + ); +} + +export function collectConfiguredRuntimePluginIds( + cfg: OpenClawConfig, + env: NodeJS.ProcessEnv, + options?: ConfiguredAgentHarnessRuntimeOptions, +): string[] { + const ids = new Set(collectConfiguredAgentHarnessRuntimes(cfg, env, options)); + if (acpxRuntimeIsConfigured(cfg)) { + ids.add("acpx"); + } + return [...ids].toSorted((left, right) => left.localeCompare(right)); +} diff --git a/src/commands/doctor/shared/missing-configured-plugin-install.ts b/src/commands/doctor/shared/missing-configured-plugin-install.ts index 39779c247b0..95acf032094 100644 --- a/src/commands/doctor/shared/missing-configured-plugin-install.ts +++ b/src/commands/doctor/shared/missing-configured-plugin-install.ts @@ -1,7 +1,6 @@ import { existsSync } from "node:fs"; import { rm } from "node:fs/promises"; import path from "node:path"; -import { collectConfiguredAgentHarnessRuntimes } from "../../../agents/harness-runtimes.js"; import { listExplicitlyDisabledChannelIdsForConfig, listPotentialConfiguredChannelIds, @@ -49,6 +48,10 @@ import { resolveWebSearchInstallCatalogEntry } from "../../../plugins/web-search import { normalizeOptionalLowercaseString } from "../../../shared/string-coerce.js"; import { resolveUserPath } from "../../../utils.js"; import { VERSION } from "../../../version.js"; +import { + collectConfiguredRuntimePluginIds, + CONFIGURED_RUNTIME_PLUGIN_INSTALL_CANDIDATES, +} from "./configured-runtime-plugin-installs.js"; import { asObjectRecord } from "./object.js"; import { isLegacyPackageUpdateDoctorPass, @@ -70,22 +73,6 @@ type BundledPluginPackageDescriptor = { packageName?: string; }; -const RUNTIME_PLUGIN_INSTALL_CANDIDATES: readonly DownloadableInstallCandidate[] = [ - { - pluginId: "acpx", - label: "ACPX Runtime", - npmSpec: "@openclaw/acpx", - trustedSourceLinkedOfficialInstall: true, - }, - // Runtime-only configs do not have a provider/channel integration catalog entry. - { - pluginId: "codex", - label: "Codex", - npmSpec: "@openclaw/codex", - trustedSourceLinkedOfficialInstall: true, - }, -]; - const MISSING_CHANNEL_CONFIG_DESCRIPTOR_DIAGNOSTIC = "without channelConfigs metadata"; const REPAIRABLE_PACKAGE_ENTRY_DIAGNOSTIC_MARKERS = [ "extension entry escapes package directory", @@ -123,7 +110,7 @@ function addConfiguredAgentRuntimePluginIds( cfg: OpenClawConfig, env?: NodeJS.ProcessEnv, ): void { - for (const runtime of collectConfiguredAgentHarnessRuntimes(cfg, env ?? process.env, { + for (const runtime of collectConfiguredRuntimePluginIds(cfg, env ?? process.env, { includeEnvRuntime: false, includeLegacyAgentRuntimes: false, })) { @@ -151,16 +138,6 @@ function collectConfiguredPluginIds(cfg: OpenClawConfig, env?: NodeJS.ProcessEnv ids.add(installEntry.pluginId); } } - const acp = asObjectRecord(cfg.acp); - const acpBackend = typeof acp?.backend === "string" ? acp.backend.trim().toLowerCase() : ""; - if ( - (acpBackend === "acpx" || - acp?.enabled === true || - asObjectRecord(acp?.dispatch)?.enabled === true) && - (!acpBackend || acpBackend === "acpx") - ) { - ids.add("acpx"); - } addConfiguredAgentRuntimePluginIds(ids, cfg, env); return ids; } @@ -364,7 +341,7 @@ function collectDownloadableInstallCandidates(params: { }); } - for (const entry of RUNTIME_PLUGIN_INSTALL_CANDIDATES) { + for (const entry of CONFIGURED_RUNTIME_PLUGIN_INSTALL_CANDIDATES) { if (!configuredPluginIds.has(entry.pluginId) && !params.missingPluginIds.has(entry.pluginId)) { continue; }