mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-15 22:00:42 +00:00
[codex] add Crestodian plugin management (#75869)
Summary: - The branch adds ClawHub plugin search and Crestodian plugin list/search/install/uninstall flows, with docs, changelog, tests, runtime injection, and regenerated config baseline hashes. - Reproducibility: not applicable. as a bug reproduction request. The high-confidence verification path is cur ... surface search plus exact-head diff/source inspection against the PR's targeted tests and queued CI checks. ClawSweeper fixups: - Included follow-up commit: Repair Crestodian plugin management config schema drift Validation: - ClawSweeper review passed for headc29cda6005. - Required merge gates passed before the squash merge. Prepared head SHA:c29cda6005Review: https://github.com/openclaw/openclaw/pull/75869#issuecomment-4362360704 Co-authored-by: Peter Steinberger <steipete@gmail.com> Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
47f76c563f
commit
eee3aeae00
@@ -22,7 +22,7 @@ import {
|
||||
} from "../plugins/marketplace.js";
|
||||
import { tracePluginLifecyclePhaseAsync } from "../plugins/plugin-lifecycle-trace.js";
|
||||
import { validateJsonSchemaValue } from "../plugins/schema-validator.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
|
||||
import { theme } from "../terminal/theme.js";
|
||||
import { shortenHomePath } from "../utils.js";
|
||||
import { looksLikeLocalInstallSpec } from "./install-spec.js";
|
||||
@@ -110,6 +110,7 @@ async function installBundledPluginSource(params: {
|
||||
rawSpec: string;
|
||||
bundledSource: BundledPluginSource;
|
||||
warning: string;
|
||||
runtime?: RuntimeEnv;
|
||||
}) {
|
||||
const existingEntry = params.snapshot.config.plugins?.entries?.[params.bundledSource.pluginId];
|
||||
const shouldEnable = hasValidBundledPluginConfig({
|
||||
@@ -136,6 +137,7 @@ async function installBundledPluginSource(params: {
|
||||
},
|
||||
enable: shouldEnable,
|
||||
warningMessage: [params.warning, configWarning].filter(Boolean).join("\n"),
|
||||
runtime: params.runtime,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -145,6 +147,7 @@ async function tryInstallHookPackFromLocalPath(params: {
|
||||
installMode: "install" | "update";
|
||||
safetyOverrides?: InstallSafetyOverrides;
|
||||
link?: boolean;
|
||||
runtime?: RuntimeEnv;
|
||||
}): Promise<{ ok: true } | { ok: false; error: string }> {
|
||||
if (params.link) {
|
||||
const stat = fs.statSync(params.resolvedPath);
|
||||
@@ -193,6 +196,7 @@ async function tryInstallHookPackFromLocalPath(params: {
|
||||
version: probe.version,
|
||||
},
|
||||
successMessage: `Linked hook pack path: ${shortenHomePath(params.resolvedPath)}`,
|
||||
runtime: params.runtime,
|
||||
});
|
||||
return { ok: true };
|
||||
}
|
||||
@@ -201,7 +205,7 @@ async function tryInstallHookPackFromLocalPath(params: {
|
||||
...resolveInstallSafetyOverrides(params.safetyOverrides ?? {}),
|
||||
path: params.resolvedPath,
|
||||
mode: params.installMode,
|
||||
logger: createHookPackInstallLogger(),
|
||||
logger: createHookPackInstallLogger(params.runtime),
|
||||
});
|
||||
if (!result.ok) {
|
||||
return result;
|
||||
@@ -218,6 +222,7 @@ async function tryInstallHookPackFromLocalPath(params: {
|
||||
installPath: result.targetDir,
|
||||
version: result.version,
|
||||
},
|
||||
runtime: params.runtime,
|
||||
});
|
||||
return { ok: true };
|
||||
}
|
||||
@@ -227,11 +232,12 @@ async function tryInstallHookPackFromNpmSpec(params: {
|
||||
installMode: "install" | "update";
|
||||
spec: string;
|
||||
pin?: boolean;
|
||||
runtime?: RuntimeEnv;
|
||||
}): Promise<{ ok: true } | { ok: false; error: string }> {
|
||||
const result = await installHooksFromNpmSpec({
|
||||
spec: params.spec,
|
||||
mode: params.installMode,
|
||||
logger: createHookPackInstallLogger(),
|
||||
logger: createHookPackInstallLogger(params.runtime),
|
||||
});
|
||||
if (!result.ok) {
|
||||
return result;
|
||||
@@ -243,7 +249,7 @@ async function tryInstallHookPackFromNpmSpec(params: {
|
||||
result.targetDir,
|
||||
result.version,
|
||||
result.npmResolution,
|
||||
defaultRuntime.log,
|
||||
params.runtime?.log ?? defaultRuntime.log,
|
||||
theme.warn,
|
||||
);
|
||||
await persistHookPackInstall({
|
||||
@@ -251,6 +257,7 @@ async function tryInstallHookPackFromNpmSpec(params: {
|
||||
hookPackId: result.hookPackId,
|
||||
hooks: result.hooks,
|
||||
install: installRecord,
|
||||
runtime: params.runtime,
|
||||
});
|
||||
return { ok: true };
|
||||
}
|
||||
@@ -263,17 +270,18 @@ async function tryInstallPluginOrHookPackFromNpmSpec(params: {
|
||||
safetyOverrides: InstallSafetyOverrides;
|
||||
allowBundledFallback: boolean;
|
||||
extensionsDir: string;
|
||||
runtime?: RuntimeEnv;
|
||||
}): Promise<{ ok: true } | { ok: false }> {
|
||||
const result = await installPluginFromNpmSpec({
|
||||
...params.safetyOverrides,
|
||||
mode: params.installMode,
|
||||
spec: params.spec,
|
||||
extensionsDir: params.extensionsDir,
|
||||
logger: createPluginInstallLogger(),
|
||||
logger: createPluginInstallLogger(params.runtime),
|
||||
});
|
||||
if (!result.ok) {
|
||||
if (isTerminalPluginInstallSecurityFailure(result.code)) {
|
||||
defaultRuntime.error(result.error);
|
||||
(params.runtime ?? defaultRuntime).error(result.error);
|
||||
return { ok: false };
|
||||
}
|
||||
if (params.allowBundledFallback) {
|
||||
@@ -288,6 +296,7 @@ async function tryInstallPluginOrHookPackFromNpmSpec(params: {
|
||||
rawSpec: params.spec,
|
||||
bundledSource: bundledFallbackPlan.bundledSource,
|
||||
warning: bundledFallbackPlan.warning,
|
||||
runtime: params.runtime,
|
||||
});
|
||||
return { ok: true };
|
||||
}
|
||||
@@ -297,11 +306,12 @@ async function tryInstallPluginOrHookPackFromNpmSpec(params: {
|
||||
installMode: params.installMode,
|
||||
spec: params.spec,
|
||||
pin: params.pin,
|
||||
runtime: params.runtime,
|
||||
});
|
||||
if (hookFallback.ok) {
|
||||
return { ok: true };
|
||||
}
|
||||
defaultRuntime.error(
|
||||
(params.runtime ?? defaultRuntime).error(
|
||||
formatPluginInstallWithHookFallbackError(result.error, hookFallback.error),
|
||||
);
|
||||
return { ok: false };
|
||||
@@ -313,13 +323,14 @@ async function tryInstallPluginOrHookPackFromNpmSpec(params: {
|
||||
result.targetDir,
|
||||
result.version,
|
||||
result.npmResolution,
|
||||
defaultRuntime.log,
|
||||
params.runtime?.log ?? defaultRuntime.log,
|
||||
theme.warn,
|
||||
);
|
||||
await persistPluginInstall({
|
||||
snapshot: params.snapshot,
|
||||
pluginId: result.pluginId,
|
||||
install: installRecord,
|
||||
runtime: params.runtime,
|
||||
});
|
||||
return { ok: true };
|
||||
}
|
||||
@@ -330,16 +341,17 @@ async function tryInstallPluginFromGitSpec(params: {
|
||||
spec: string;
|
||||
safetyOverrides: InstallSafetyOverrides;
|
||||
extensionsDir: string;
|
||||
runtime?: RuntimeEnv;
|
||||
}): Promise<{ ok: true } | { ok: false }> {
|
||||
const result = await installPluginFromGitSpec({
|
||||
...params.safetyOverrides,
|
||||
mode: params.installMode,
|
||||
spec: params.spec,
|
||||
extensionsDir: params.extensionsDir,
|
||||
logger: createPluginInstallLogger(),
|
||||
logger: createPluginInstallLogger(params.runtime),
|
||||
});
|
||||
if (!result.ok) {
|
||||
defaultRuntime.error(result.error);
|
||||
(params.runtime ?? defaultRuntime).error(result.error);
|
||||
return { ok: false };
|
||||
}
|
||||
|
||||
@@ -356,6 +368,7 @@ async function tryInstallPluginFromGitSpec(params: {
|
||||
gitRef: result.git.ref,
|
||||
gitCommit: result.git.commit,
|
||||
},
|
||||
runtime: params.runtime,
|
||||
});
|
||||
return { ok: true };
|
||||
}
|
||||
@@ -452,7 +465,9 @@ export async function runPluginInstallCommand(params: {
|
||||
pin?: boolean;
|
||||
marketplace?: string;
|
||||
};
|
||||
runtime?: RuntimeEnv;
|
||||
}) {
|
||||
const runtime = params.runtime ?? defaultRuntime;
|
||||
const shorthand = !params.opts.marketplace
|
||||
? await tracePluginLifecyclePhaseAsync(
|
||||
"marketplace shortcut resolution",
|
||||
@@ -461,8 +476,8 @@ export async function runPluginInstallCommand(params: {
|
||||
)
|
||||
: null;
|
||||
if (shorthand?.ok === false) {
|
||||
defaultRuntime.error(shorthand.error);
|
||||
return defaultRuntime.exit(1);
|
||||
runtime.error(shorthand.error);
|
||||
return runtime.exit(1);
|
||||
}
|
||||
|
||||
const raw = shorthand?.ok ? shorthand.plugin : params.raw;
|
||||
@@ -473,47 +488,47 @@ export async function runPluginInstallCommand(params: {
|
||||
};
|
||||
if (opts.marketplace) {
|
||||
if (opts.link) {
|
||||
defaultRuntime.error("`--link` is not supported with `--marketplace`.");
|
||||
return defaultRuntime.exit(1);
|
||||
runtime.error("`--link` is not supported with `--marketplace`.");
|
||||
return runtime.exit(1);
|
||||
}
|
||||
if (opts.pin) {
|
||||
defaultRuntime.error("`--pin` is not supported with `--marketplace`.");
|
||||
return defaultRuntime.exit(1);
|
||||
runtime.error("`--pin` is not supported with `--marketplace`.");
|
||||
return runtime.exit(1);
|
||||
}
|
||||
}
|
||||
const gitPrefix = raw.trim().toLowerCase().startsWith("git:");
|
||||
const gitSpec = parseGitPluginSpec(raw);
|
||||
if (gitPrefix && !gitSpec) {
|
||||
defaultRuntime.error(`unsupported git: plugin spec: ${raw}`);
|
||||
return defaultRuntime.exit(1);
|
||||
runtime.error(`unsupported git: plugin spec: ${raw}`);
|
||||
return runtime.exit(1);
|
||||
}
|
||||
if (gitSpec && opts.link) {
|
||||
defaultRuntime.error("`--link` is not supported with `git:` installs.");
|
||||
return defaultRuntime.exit(1);
|
||||
runtime.error("`--link` is not supported with `git:` installs.");
|
||||
return runtime.exit(1);
|
||||
}
|
||||
if (gitSpec && opts.pin) {
|
||||
defaultRuntime.error("`--pin` is not supported with `git:` installs; use `git:<repo>@<ref>`.");
|
||||
return defaultRuntime.exit(1);
|
||||
runtime.error("`--pin` is not supported with `git:` installs; use `git:<repo>@<ref>`.");
|
||||
return runtime.exit(1);
|
||||
}
|
||||
if (opts.link && opts.force) {
|
||||
defaultRuntime.error("`--force` is not supported with `--link`.");
|
||||
return defaultRuntime.exit(1);
|
||||
runtime.error("`--force` is not supported with `--link`.");
|
||||
return runtime.exit(1);
|
||||
}
|
||||
const requestResolution = resolvePluginInstallRequestContext({
|
||||
rawSpec: raw,
|
||||
marketplace: opts.marketplace,
|
||||
});
|
||||
if (!requestResolution.ok) {
|
||||
defaultRuntime.error(requestResolution.error);
|
||||
return defaultRuntime.exit(1);
|
||||
runtime.error(requestResolution.error);
|
||||
return runtime.exit(1);
|
||||
}
|
||||
const request = requestResolution.request;
|
||||
const snapshot = await loadConfigForInstall(request).catch((error: unknown) => {
|
||||
defaultRuntime.error(formatErrorMessage(error));
|
||||
runtime.error(formatErrorMessage(error));
|
||||
return null;
|
||||
});
|
||||
if (!snapshot) {
|
||||
return defaultRuntime.exit(1);
|
||||
return runtime.exit(1);
|
||||
}
|
||||
const cfg = snapshot.config;
|
||||
const installMode = resolveInstallMode(opts.force);
|
||||
@@ -527,11 +542,11 @@ export async function runPluginInstallCommand(params: {
|
||||
mode: installMode,
|
||||
plugin: raw,
|
||||
extensionsDir,
|
||||
logger: createPluginInstallLogger(),
|
||||
logger: createPluginInstallLogger(runtime),
|
||||
});
|
||||
if (!result.ok) {
|
||||
defaultRuntime.error(result.error);
|
||||
return defaultRuntime.exit(1);
|
||||
runtime.error(result.error);
|
||||
return runtime.exit(1);
|
||||
}
|
||||
|
||||
await persistPluginInstall({
|
||||
@@ -545,6 +560,7 @@ export async function runPluginInstallCommand(params: {
|
||||
marketplaceSource: result.marketplaceSource,
|
||||
marketplacePlugin: result.marketplacePlugin,
|
||||
},
|
||||
runtime,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -560,12 +576,12 @@ export async function runPluginInstallCommand(params: {
|
||||
path: resolved,
|
||||
dryRun: true,
|
||||
extensionsDir,
|
||||
logger: createPluginInstallLogger(),
|
||||
logger: createPluginInstallLogger(runtime),
|
||||
});
|
||||
if (!probe.ok) {
|
||||
if (isTerminalPluginInstallSecurityFailure(probe.code)) {
|
||||
defaultRuntime.error(probe.error);
|
||||
return defaultRuntime.exit(1);
|
||||
runtime.error(probe.error);
|
||||
return runtime.exit(1);
|
||||
}
|
||||
const hookFallback = await tryInstallHookPackFromLocalPath({
|
||||
snapshot,
|
||||
@@ -573,14 +589,13 @@ export async function runPluginInstallCommand(params: {
|
||||
resolvedPath: resolved,
|
||||
safetyOverrides,
|
||||
link: true,
|
||||
runtime,
|
||||
});
|
||||
if (hookFallback.ok) {
|
||||
return;
|
||||
}
|
||||
defaultRuntime.error(
|
||||
formatPluginInstallWithHookFallbackError(probe.error, hookFallback.error),
|
||||
);
|
||||
return defaultRuntime.exit(1);
|
||||
runtime.error(formatPluginInstallWithHookFallbackError(probe.error, hookFallback.error));
|
||||
return runtime.exit(1);
|
||||
}
|
||||
|
||||
await persistPluginInstall({
|
||||
@@ -605,6 +620,7 @@ export async function runPluginInstallCommand(params: {
|
||||
version: probe.version,
|
||||
},
|
||||
successMessage: `Linked plugin path: ${shortenHomePath(resolved)}`,
|
||||
runtime,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -614,26 +630,25 @@ export async function runPluginInstallCommand(params: {
|
||||
mode: installMode,
|
||||
path: resolved,
|
||||
extensionsDir,
|
||||
logger: createPluginInstallLogger(),
|
||||
logger: createPluginInstallLogger(runtime),
|
||||
});
|
||||
if (!result.ok) {
|
||||
if (isTerminalPluginInstallSecurityFailure(result.code)) {
|
||||
defaultRuntime.error(result.error);
|
||||
return defaultRuntime.exit(1);
|
||||
runtime.error(result.error);
|
||||
return runtime.exit(1);
|
||||
}
|
||||
const hookFallback = await tryInstallHookPackFromLocalPath({
|
||||
snapshot,
|
||||
installMode,
|
||||
resolvedPath: resolved,
|
||||
safetyOverrides,
|
||||
runtime,
|
||||
});
|
||||
if (hookFallback.ok) {
|
||||
return;
|
||||
}
|
||||
defaultRuntime.error(
|
||||
formatPluginInstallWithHookFallbackError(result.error, hookFallback.error),
|
||||
);
|
||||
return defaultRuntime.exit(1);
|
||||
runtime.error(formatPluginInstallWithHookFallbackError(result.error, hookFallback.error));
|
||||
return runtime.exit(1);
|
||||
}
|
||||
|
||||
const source: "archive" | "path" = resolveArchiveKind(resolved) ? "archive" : "path";
|
||||
@@ -646,20 +661,21 @@ export async function runPluginInstallCommand(params: {
|
||||
installPath: result.targetDir,
|
||||
version: result.version,
|
||||
},
|
||||
runtime,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (opts.link) {
|
||||
defaultRuntime.error("`--link` requires a local path.");
|
||||
return defaultRuntime.exit(1);
|
||||
runtime.error("`--link` requires a local path.");
|
||||
return runtime.exit(1);
|
||||
}
|
||||
|
||||
const npmPrefixSpec = parseNpmPrefixSpec(raw);
|
||||
if (npmPrefixSpec !== null) {
|
||||
if (!npmPrefixSpec) {
|
||||
defaultRuntime.error("unsupported npm: spec: missing package");
|
||||
return defaultRuntime.exit(1);
|
||||
runtime.error("unsupported npm: spec: missing package");
|
||||
return runtime.exit(1);
|
||||
}
|
||||
const npmPrefixResult = await tryInstallPluginOrHookPackFromNpmSpec({
|
||||
snapshot,
|
||||
@@ -669,9 +685,10 @@ export async function runPluginInstallCommand(params: {
|
||||
safetyOverrides,
|
||||
allowBundledFallback: false,
|
||||
extensionsDir,
|
||||
runtime,
|
||||
});
|
||||
if (!npmPrefixResult.ok) {
|
||||
return defaultRuntime.exit(1);
|
||||
return runtime.exit(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -683,9 +700,10 @@ export async function runPluginInstallCommand(params: {
|
||||
spec: raw,
|
||||
safetyOverrides,
|
||||
extensionsDir,
|
||||
runtime,
|
||||
});
|
||||
if (!gitResult.ok) {
|
||||
return defaultRuntime.exit(1);
|
||||
return runtime.exit(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -702,8 +720,8 @@ export async function runPluginInstallCommand(params: {
|
||||
".zip",
|
||||
])
|
||||
) {
|
||||
defaultRuntime.error(`Path not found: ${resolved}`);
|
||||
return defaultRuntime.exit(1);
|
||||
runtime.error(`Path not found: ${resolved}`);
|
||||
return runtime.exit(1);
|
||||
}
|
||||
|
||||
const bundledPreNpmPlan = resolveBundledInstallPlanBeforeNpm({
|
||||
@@ -719,6 +737,7 @@ export async function runPluginInstallCommand(params: {
|
||||
rawSpec: raw,
|
||||
bundledSource: bundledPreNpmPlan.bundledSource,
|
||||
warning: bundledPreNpmPlan.warning,
|
||||
runtime,
|
||||
}),
|
||||
{
|
||||
command: "install",
|
||||
@@ -736,11 +755,11 @@ export async function runPluginInstallCommand(params: {
|
||||
mode: installMode,
|
||||
spec: raw,
|
||||
extensionsDir,
|
||||
logger: createPluginInstallLogger(),
|
||||
logger: createPluginInstallLogger(runtime),
|
||||
});
|
||||
if (!result.ok) {
|
||||
defaultRuntime.error(result.error);
|
||||
return defaultRuntime.exit(1);
|
||||
runtime.error(result.error);
|
||||
return runtime.exit(1);
|
||||
}
|
||||
|
||||
await persistPluginInstall({
|
||||
@@ -762,6 +781,7 @@ export async function runPluginInstallCommand(params: {
|
||||
clawpackManifestSha256: result.clawhub.clawpackManifestSha256,
|
||||
clawpackSize: result.clawhub.clawpackSize,
|
||||
},
|
||||
runtime,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -773,7 +793,7 @@ export async function runPluginInstallCommand(params: {
|
||||
mode: installMode,
|
||||
spec: preferredClawHubSpec,
|
||||
extensionsDir,
|
||||
logger: createPluginInstallLogger(),
|
||||
logger: createPluginInstallLogger(runtime),
|
||||
});
|
||||
if (clawhubResult.ok) {
|
||||
await persistPluginInstall({
|
||||
@@ -795,12 +815,13 @@ export async function runPluginInstallCommand(params: {
|
||||
clawpackManifestSha256: clawhubResult.clawhub.clawpackManifestSha256,
|
||||
clawpackSize: clawhubResult.clawhub.clawpackSize,
|
||||
},
|
||||
runtime,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (decidePreferredClawHubFallback(clawhubResult) !== "fallback_to_npm") {
|
||||
defaultRuntime.error(clawhubResult.error);
|
||||
return defaultRuntime.exit(1);
|
||||
runtime.error(clawhubResult.error);
|
||||
return runtime.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -812,8 +833,9 @@ export async function runPluginInstallCommand(params: {
|
||||
safetyOverrides,
|
||||
allowBundledFallback: true,
|
||||
extensionsDir,
|
||||
runtime,
|
||||
});
|
||||
if (!npmResult.ok) {
|
||||
return defaultRuntime.exit(1);
|
||||
return runtime.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user