mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 18:54:48 +00:00
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>
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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<str
|
||||
}
|
||||
}
|
||||
|
||||
function pushConfiguredAgentModelRuntimeIds(config: OpenClawConfig, runtimes: Set<string>): void {
|
||||
function pushConfiguredAgentModelRuntimeIds(
|
||||
config: OpenClawConfig,
|
||||
runtimes: Set<string>,
|
||||
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<string>
|
||||
|
||||
export type ConfiguredAgentHarnessRuntimeOptions = {
|
||||
includeEnvRuntime?: boolean;
|
||||
includeImplicitRuntimePreferences?: boolean;
|
||||
includeLegacyAgentRuntimes?: boolean;
|
||||
};
|
||||
|
||||
@@ -179,6 +193,7 @@ export function collectConfiguredAgentHarnessRuntimes(
|
||||
): string[] {
|
||||
const runtimes = new Set<string>();
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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<void> {
|
||||
assertConfigWriteAllowedInCurrentMode();
|
||||
|
||||
@@ -240,10 +337,16 @@ export async function runPluginsDoctorCommand(): Promise<void> {
|
||||
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<void> {
|
||||
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("");
|
||||
}
|
||||
|
||||
@@ -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<string, unknown> | undefined {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: 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));
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user