mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-04 01:04:07 +00:00
* refactor: extract agent core package Introduce packages/agent-core as the OpenClaw-owned home for reusable agent loop, harness, session, prompt, and runtime dependency contracts. * refactor: extract shared llm runtime Move provider model registries, stream wrappers, OAuth helpers, and LLM utilities into src/llm with plugin-sdk barrels instead of depending on the old embedded runtime layout. * refactor: remove pi runtime internals Rename remaining Pi-shaped agent surfaces to OpenClaw agent runtime names, delete obsolete Pi docs and package graph checks, and add the third-party notice for incorporated code. * refactor: tighten agent session runtime Make agent-core/runtime dependencies explicit, consolidate compaction and session transcript helpers, and move model/session helpers behind OpenClaw-owned contracts. * refactor: remove static model and pi auth paths Drop static model catalogs and Pi auth bridges, move model/provider facts to manifest-owned runtime contracts, and harden internal embedded-agent utilities. * refactor: remove legacy provider compat paths * docs: remove agent parity notes * fix: skip provider wildcard metadata parsing * refactor: share session extension sdk loading * refactor: inline acpx proxy error formatter * refactor: fold edit recovery into edit tool * fix: accept extension batch separator * test: align startup provider plugin expectations * fix: restore provider-scoped release discovery * test: align static asset packaging expectations * fix: run static provider catalogs during scoped discovery * fix: add provider entry catalogs for scoped live discovery * fix: load lightweight provider catalog entries * fix: refresh provider-scoped plugin metadata * fix: keep provider catalog entries on release live path * fix: keep static manifest models in release live checks * fix: harden release model discovery * fix: reduce OpenAI live cache probe reasoning * fix: disable OpenAI cache probe reasoning * ci: extend OpenAI gateway live timeout * fix: extend live gateway model budget * fix: stabilize release validation regressions * fix: honor provider aliases in model rows * fix: stabilize release validation lanes * fix: stabilize release memory qa * ci: stabilize release validation lanes * ci: prefer ipv4 for live docker node calls * fix: restore shared tool-call stream wrapper * ci: remove legacy pi test shard alias * fix: clean up embedded agent test drift * fix: stabilize runtime alias status * fix: clean up embedded agent ci drift * fix: restore release ci invariants * fix: clean up post-rebase runtime drift * fix: restore release ci checks * fix: restore release ci after rebase * fix: remove stale pi runtime path * test: align compaction runtime expectations * test: update plugin prerelease expectations * fix: handle claude live tool approvals * fix: stabilize release validation gates * fix: finish agent runtime import * test: finish post-rebase agent runtime mocks * fix: keep codex compaction native * fix: stabilize codex app-server hook tests * test: isolate codex diagnostic active run * test: remove codex diagnostic completion race # Conflicts: # extensions/codex/src/app-server/run-attempt.test.ts * ci: fix full release manifest performance run id * refactor: narrow llm plugin sdk boundary * chore: drop generated google boundary stamps * fix: repair rebase fallout * fix: clean up rebased runtime references * fix: decode codex jwt payloads as base64url * fix: preserve shipped pi runtime alias * fix: add scoped sdk virtual modules * fix: decode llm codex oauth jwt as base64url * fix: avoid stale vertex adc negative cache * fix: harden tool arg decoding and codeql path * fix: keep vertex adc negative checks live * refactor: consolidate codex jwt and edit helpers * fix: await codex oauth node runtime imports * fix: preserve sdk tool and notice contracts * fix: preserve shipped compat config boundaries * fix: align codex oauth callback host * fix: terminate agent-core loop streams on failure * fix: keep codex oauth callback alive during fallback * ci: include session tools in critical codeql scans * fix: keep Cloudflare Anthropic provider auth header * docs: redirect legacy pi runtime pages * fix: honor bundled web provider compat discovery * fix: protect session output spill files * fix: keep legacy agent dir env blocked * fix: contain auto-discovered skill symlinks * fix: harden agent core sdk proxy surfaces * fix: restore approval reaction sdk compat * fix: keep live docker runs bounded * fix: keep codex oauth redirect host aligned * fix: resolve post-rebase agent runtime drift * fix: redact anthropic oauth parse failures * fix: preserve responses strict tool shaping * fix: repair agent runtime rebase cleanup * docs: redirect retired parity pages * fix: bound auto-discovered resources to roots * fix: repair post-rebase agent test drift * fix: preserve bundled provider allowlist migration * fix: preserve manifest-owned provider aliases * fix: declare photon image dependency * fix: keep provider headers out of proxy body * fix: preserve shipped env aliases * fix: refresh control ui i18n generated state * fix: quote read fallback paths * fix: preview edits through configured backend * test: satisfy core test typecheck * fix: preserve ZAI usage auth fallback * test: repair codex diagnostic test * fix: repair agent runtime rebase drift * test: finish embedded runner import rename * fix: repair agent runtime rebase integrations * test: align compaction oauth fallback expectations * fix: allow sdk-auth session models * fix: update doctor tool schema import * fix: preserve bedrock plugin region * fix: stream harmony-like prose immediately * ci: include session runtime in codeql shards * fix: repair latest rebase integrations * fix: honor explicit codex websocket transport * fix: keep openai-compatible credentials provider-scoped * fix: refresh sdk api baseline after rebase * fix: route cli runtime aliases through openclaw harness * test: rename stale harness mock expectation * test: rename embedded agent overflow calls * test: clean embedded auth test wording * test: use openclaw stream types in deepinfra cache test * fix: refresh sdk api baseline on latest main * fix: honor bundled discovery compat allowlists * fix: refresh sdk api baseline after latest rebase * fix: remove stale rebase imports * test: rename stale model catalog mock * test: mock renamed doctor runtime modules * fix: map canonical kimi env auth * fix: use internal model registry in bench script * fix: migrate deepinfra provider catalog entry * fix: enforce builtin tool suppression * fix: route compaction auth and proxy payloads safely * refactor: prune unused llm registry leftovers * test: update codex hooks session import * test: fix model picker ci coverage * test: align model picker auth mock types
465 lines
17 KiB
TypeScript
465 lines
17 KiB
TypeScript
import {
|
|
collectConfiguredRuntimePluginIds,
|
|
resolveConfiguredRuntimePluginInstallCandidate,
|
|
} from "../commands/doctor/shared/configured-runtime-plugin-installs.js";
|
|
import {
|
|
assertConfigWriteAllowedInCurrentMode,
|
|
getRuntimeConfig,
|
|
readConfigFileSnapshot,
|
|
replaceConfigFile,
|
|
} from "../config/config.js";
|
|
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
|
import { tracePluginLifecyclePhaseAsync } from "../plugins/plugin-lifecycle-trace.js";
|
|
import { defaultRuntime } from "../runtime.js";
|
|
import { formatDocsLink } from "../terminal/links.js";
|
|
import { theme } from "../terminal/theme.js";
|
|
import { shortenHomeInString } from "../utils.js";
|
|
import { formatMissingPluginMessage } from "./error-format.js";
|
|
import type { PluginMarketplaceListOptions, PluginRegistryOptions } from "./plugins-cli.js";
|
|
|
|
type PluginInstallActionOptions = {
|
|
dangerouslyForceUnsafeInstall?: boolean;
|
|
force?: boolean;
|
|
link?: boolean;
|
|
pin?: boolean;
|
|
marketplace?: string;
|
|
};
|
|
|
|
function countEnabledPlugins(plugins: readonly { enabled: boolean }[]): number {
|
|
return plugins.filter((plugin) => plugin.enabled).length;
|
|
}
|
|
|
|
function formatRegistryState(state: "missing" | "fresh" | "stale"): string {
|
|
if (state === "fresh") {
|
|
return theme.success(state);
|
|
}
|
|
if (state === "stale") {
|
|
return theme.warn(state);
|
|
}
|
|
return theme.warn(state);
|
|
}
|
|
|
|
function reportMissingPlugin(id: string) {
|
|
defaultRuntime.error(formatMissingPluginMessage({ id, includeSearch: true }));
|
|
return defaultRuntime.exit(1);
|
|
}
|
|
|
|
function matchesPluginId(plugin: { id: string }, id: string) {
|
|
return plugin.id === id;
|
|
}
|
|
|
|
function isConfigSelectedShadowDiagnostic(entry: { level?: string; message?: string }): boolean {
|
|
return (
|
|
entry.level === "warn" &&
|
|
typeof entry.message === "string" &&
|
|
entry.message.includes("duplicate plugin id resolved by explicit config-selected plugin")
|
|
);
|
|
}
|
|
|
|
function isErroredConfigSelectedShadowDiagnostic(params: {
|
|
entry: { level?: string; message?: string; pluginId?: string };
|
|
plugins: readonly { id: string; origin: string; status: string }[];
|
|
}): boolean {
|
|
if (!params.entry.pluginId || !isConfigSelectedShadowDiagnostic(params.entry)) {
|
|
return false;
|
|
}
|
|
return params.plugins.some(
|
|
(plugin) =>
|
|
plugin.id === params.entry.pluginId &&
|
|
plugin.origin === "config" &&
|
|
plugin.status === "error",
|
|
);
|
|
}
|
|
|
|
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 "openclaw"';
|
|
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 "openclaw"';
|
|
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, {
|
|
includeImplicitRuntimePreferences: 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();
|
|
|
|
const { enablePluginInConfig } = await import("../plugins/enable.js");
|
|
const { normalizePluginId } = await import("../plugins/config-state.js");
|
|
const { buildPluginRegistrySnapshotReport } = await import("../plugins/status.js");
|
|
const { applySlotSelectionForPlugin, logSlotWarnings } =
|
|
await import("./plugins-command-helpers.js");
|
|
const { refreshPluginRegistryAfterConfigMutation } =
|
|
await import("./plugins-registry-refresh.js");
|
|
const snapshot = await readConfigFileSnapshot();
|
|
const cfg = (snapshot.sourceConfig ?? snapshot.config) as OpenClawConfig;
|
|
const report = buildPluginRegistrySnapshotReport({ config: cfg });
|
|
id = normalizePluginId(id);
|
|
if (!report.plugins.some((plugin) => matchesPluginId(plugin, id))) {
|
|
return reportMissingPlugin(id);
|
|
}
|
|
const enableResult = enablePluginInConfig(cfg, id, {
|
|
updateChannelConfig: false,
|
|
});
|
|
let next: OpenClawConfig = enableResult.config;
|
|
const slotResult = applySlotSelectionForPlugin(next, id);
|
|
next = slotResult.config;
|
|
await replaceConfigFile({
|
|
nextConfig: next,
|
|
...(snapshot.hash !== undefined ? { baseHash: snapshot.hash } : {}),
|
|
});
|
|
await refreshPluginRegistryAfterConfigMutation({
|
|
config: next,
|
|
reason: "policy-changed",
|
|
policyPluginIds: [enableResult.pluginId],
|
|
logger: {
|
|
warn: (message) => defaultRuntime.log(theme.warn(message)),
|
|
},
|
|
});
|
|
logSlotWarnings(slotResult.warnings);
|
|
if (enableResult.enabled) {
|
|
defaultRuntime.log(`Enabled plugin "${id}". Restart the gateway to apply.`);
|
|
return;
|
|
}
|
|
defaultRuntime.log(
|
|
theme.warn(`Plugin "${id}" could not be enabled (${enableResult.reason ?? "unknown reason"}).`),
|
|
);
|
|
}
|
|
|
|
export async function runPluginsDisableCommand(id: string): Promise<void> {
|
|
assertConfigWriteAllowedInCurrentMode();
|
|
|
|
const { normalizePluginId } = await import("../plugins/config-state.js");
|
|
const { buildPluginRegistrySnapshotReport } = await import("../plugins/status.js");
|
|
const { setPluginEnabledInConfig } = await import("./plugins-config.js");
|
|
const { refreshPluginRegistryAfterConfigMutation } =
|
|
await import("./plugins-registry-refresh.js");
|
|
const snapshot = await readConfigFileSnapshot();
|
|
const cfg = (snapshot.sourceConfig ?? snapshot.config) as OpenClawConfig;
|
|
const report = buildPluginRegistrySnapshotReport({ config: cfg });
|
|
id = normalizePluginId(id);
|
|
if (!report.plugins.some((plugin) => matchesPluginId(plugin, id))) {
|
|
return reportMissingPlugin(id);
|
|
}
|
|
const next = setPluginEnabledInConfig(cfg, id, false, {
|
|
updateChannelConfig: false,
|
|
});
|
|
await replaceConfigFile({
|
|
nextConfig: next,
|
|
...(snapshot.hash !== undefined ? { baseHash: snapshot.hash } : {}),
|
|
});
|
|
await refreshPluginRegistryAfterConfigMutation({
|
|
config: next,
|
|
reason: "policy-changed",
|
|
policyPluginIds: [id],
|
|
logger: {
|
|
warn: (message) => defaultRuntime.log(theme.warn(message)),
|
|
},
|
|
});
|
|
defaultRuntime.log(`Disabled plugin "${id}". Restart the gateway to apply.`);
|
|
}
|
|
|
|
export async function runPluginsInstallAction(
|
|
raw: string,
|
|
opts: PluginInstallActionOptions,
|
|
): Promise<void> {
|
|
await tracePluginLifecyclePhaseAsync(
|
|
"install command",
|
|
async () => {
|
|
const { runPluginInstallCommand } = await import("./plugins-install-command.js");
|
|
await runPluginInstallCommand({ raw, opts });
|
|
},
|
|
{ command: "install" },
|
|
);
|
|
}
|
|
|
|
export async function runPluginsRegistryCommand(opts: PluginRegistryOptions): Promise<void> {
|
|
const { inspectPluginRegistry, refreshPluginRegistry } =
|
|
await import("../plugins/plugin-registry.js");
|
|
const cfg = getRuntimeConfig();
|
|
|
|
if (opts.refresh) {
|
|
const index = await refreshPluginRegistry({
|
|
config: cfg,
|
|
reason: "manual",
|
|
});
|
|
if (opts.json) {
|
|
defaultRuntime.writeJson({
|
|
refreshed: true,
|
|
registry: index,
|
|
});
|
|
return;
|
|
}
|
|
const total = index.plugins.length;
|
|
const enabled = countEnabledPlugins(index.plugins);
|
|
defaultRuntime.log(`Plugin registry refreshed: ${enabled}/${total} enabled plugins indexed.`);
|
|
return;
|
|
}
|
|
|
|
const inspection = await inspectPluginRegistry({ config: cfg });
|
|
if (opts.json) {
|
|
defaultRuntime.writeJson({
|
|
state: inspection.state,
|
|
refreshReasons: inspection.refreshReasons,
|
|
persisted: inspection.persisted,
|
|
current: inspection.current,
|
|
});
|
|
return;
|
|
}
|
|
|
|
const currentTotal = inspection.current.plugins.length;
|
|
const currentEnabled = countEnabledPlugins(inspection.current.plugins);
|
|
const persistedTotal = inspection.persisted?.plugins.length ?? 0;
|
|
const persistedEnabled = inspection.persisted
|
|
? countEnabledPlugins(inspection.persisted.plugins)
|
|
: 0;
|
|
const lines = [
|
|
`${theme.muted("State:")} ${formatRegistryState(inspection.state)}`,
|
|
`${theme.muted("Current:")} ${currentEnabled}/${currentTotal} enabled plugins`,
|
|
`${theme.muted("Persisted:")} ${persistedEnabled}/${persistedTotal} enabled plugins`,
|
|
];
|
|
if (inspection.refreshReasons.length > 0) {
|
|
lines.push(`${theme.muted("Refresh reasons:")} ${inspection.refreshReasons.join(", ")}`);
|
|
lines.push(`${theme.muted("Repair:")} ${theme.command("openclaw plugins registry --refresh")}`);
|
|
}
|
|
defaultRuntime.log(lines.join("\n"));
|
|
}
|
|
|
|
export async function runPluginsDoctorCommand(): Promise<void> {
|
|
const {
|
|
buildPluginCompatibilityNotices,
|
|
buildPluginDiagnosticsReport,
|
|
formatPluginCompatibilityNotice,
|
|
} = await import("../plugins/status.js");
|
|
const {
|
|
collectStalePluginConfigWarnings,
|
|
isStalePluginAutoRepairBlocked,
|
|
scanStalePluginConfig,
|
|
} = await import("../commands/doctor/shared/stale-plugin-config.js");
|
|
const cfg = getRuntimeConfig();
|
|
const configSnapshot = await readConfigFileSnapshot().catch(() => null);
|
|
const sourceCfg = (configSnapshot?.sourceConfig ?? configSnapshot?.config ?? cfg) as
|
|
| OpenClawConfig
|
|
| undefined;
|
|
const report = buildPluginDiagnosticsReport({ config: cfg, effectiveOnly: true });
|
|
const errors = report.plugins.filter((p) => p.status === "error");
|
|
const diags = report.diagnostics.filter((d) => d.level === "error");
|
|
const shadowed = report.diagnostics.filter((entry) =>
|
|
isErroredConfigSelectedShadowDiagnostic({ entry, plugins: report.plugins }),
|
|
);
|
|
const compatibility = buildPluginCompatibilityNotices({ report });
|
|
const stalePluginConfigHits = scanStalePluginConfig(sourceCfg ?? cfg, process.env);
|
|
const stalePluginConfigWarnings = collectStalePluginConfigWarnings({
|
|
hits: stalePluginConfigHits,
|
|
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 && pluginConfigWarnings.length === 0) {
|
|
defaultRuntime.log("No plugin issues detected.");
|
|
return;
|
|
}
|
|
|
|
const lines: string[] = [];
|
|
if (errors.length > 0) {
|
|
lines.push(theme.error("Plugin errors:"));
|
|
for (const entry of errors) {
|
|
const phase = entry.failurePhase ? ` [${entry.failurePhase}]` : "";
|
|
lines.push(`- ${entry.id}${phase}: ${entry.error ?? "failed to load"} (${entry.source})`);
|
|
}
|
|
}
|
|
if (diags.length > 0) {
|
|
if (lines.length > 0) {
|
|
lines.push("");
|
|
}
|
|
lines.push(theme.warn("Diagnostics:"));
|
|
for (const diag of diags) {
|
|
const target = diag.pluginId ? `${diag.pluginId}: ` : "";
|
|
lines.push(`- ${target}${diag.message}`);
|
|
}
|
|
}
|
|
if (shadowed.length > 0) {
|
|
if (lines.length > 0) {
|
|
lines.push("");
|
|
}
|
|
lines.push(theme.warn("Plugin source shadowing:"));
|
|
for (const diag of shadowed) {
|
|
const active = report.plugins.find((plugin) => plugin.id === diag.pluginId);
|
|
const target = diag.pluginId ? `${diag.pluginId}: ` : "";
|
|
lines.push(`- ${target}${diag.message}`);
|
|
if (active) {
|
|
lines.push(` active: ${shortenHomeInString(active.source)} (${active.origin})`);
|
|
if (active.status === "error") {
|
|
lines.push(` active status: error${active.error ? `: ${active.error}` : ""}`);
|
|
}
|
|
}
|
|
if (diag.source) {
|
|
lines.push(` shadowed: ${shortenHomeInString(diag.source)}`);
|
|
}
|
|
lines.push(" repair:");
|
|
lines.push(" openclaw plugins inspect " + (diag.pluginId ?? "<plugin-id>"));
|
|
lines.push(" edit or remove the config-selected plugin source");
|
|
lines.push(" openclaw plugins registry --refresh");
|
|
lines.push(" openclaw gateway restart --force");
|
|
}
|
|
}
|
|
if (compatibility.length > 0) {
|
|
if (lines.length > 0) {
|
|
lines.push("");
|
|
}
|
|
lines.push(theme.warn("Compatibility:"));
|
|
for (const notice of compatibility) {
|
|
const marker = notice.severity === "warn" ? theme.warn("warn") : theme.muted("info");
|
|
lines.push(`- ${formatPluginCompatibilityNotice(notice)} [${marker}]`);
|
|
}
|
|
}
|
|
if (pluginConfigWarnings.length > 0) {
|
|
if (lines.length > 0) {
|
|
lines.push("");
|
|
}
|
|
lines.push(theme.warn("Plugin configuration:"));
|
|
lines.push(...pluginConfigWarnings);
|
|
}
|
|
if (!hasInstallTreeIssues && pluginConfigWarnings.length > 0) {
|
|
if (lines.length > 0) {
|
|
lines.push("");
|
|
}
|
|
lines.push("No plugin install-tree issues detected; configuration warnings remain.");
|
|
}
|
|
const docs = formatDocsLink("/plugin", "docs.openclaw.ai/plugin");
|
|
lines.push("");
|
|
lines.push(`${theme.muted("Docs:")} ${docs}`);
|
|
defaultRuntime.log(lines.join("\n"));
|
|
}
|
|
|
|
export async function runPluginMarketplaceListCommand(
|
|
source: string,
|
|
opts: PluginMarketplaceListOptions,
|
|
): Promise<void> {
|
|
const { listMarketplacePlugins } = await import("../plugins/marketplace.js");
|
|
const { createPluginInstallLogger } = await import("./plugins-command-helpers.js");
|
|
const result = await listMarketplacePlugins({
|
|
marketplace: source,
|
|
logger: createPluginInstallLogger(),
|
|
});
|
|
if (!result.ok) {
|
|
defaultRuntime.error(result.error);
|
|
return defaultRuntime.exit(1);
|
|
}
|
|
|
|
if (opts.json) {
|
|
defaultRuntime.writeJson({
|
|
source: result.sourceLabel,
|
|
name: result.manifest.name,
|
|
version: result.manifest.version,
|
|
plugins: result.manifest.plugins,
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (result.manifest.plugins.length === 0) {
|
|
defaultRuntime.log(`No plugins found in marketplace ${result.sourceLabel}.`);
|
|
return;
|
|
}
|
|
|
|
defaultRuntime.log(
|
|
`${theme.heading("Marketplace")} ${theme.muted(result.manifest.name ?? result.sourceLabel)}`,
|
|
);
|
|
for (const plugin of result.manifest.plugins) {
|
|
const suffix = plugin.version ? theme.muted(` v${plugin.version}`) : "";
|
|
const desc = plugin.description ? ` - ${theme.muted(plugin.description)}` : "";
|
|
defaultRuntime.log(`${theme.command(plugin.name)}${suffix}${desc}`);
|
|
}
|
|
}
|