openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
317 lines (316 loc) • 13.3 kB
JavaScript
import { r as theme } from "./theme-vjDs9tao.js";
import { n as defaultRuntime } from "./runtime-B4lgFmsS.js";
import { s as parseRegistryNpmSpec } from "./npm-registry-spec-DjR-Lha9.js";
import { t as loadInstalledPluginIndexInstallRecords } from "./installed-plugin-index-record-reader-CPHayOZI.js";
import { n as promptYesNo } from "./prompt-DN3iQ5gF.js";
import { i as getRuntimeConfig, u as readConfigFileSnapshot } from "./io-Gi7-pyU-.js";
import { t as buildNpmResolutionFields } from "./install-source-utils-Zb798u54.js";
import { a as withPluginInstallRecords, o as withoutPluginInstallRecords } from "./installed-plugin-index-records-DXbAzXDd.js";
import { n as assertConfigWriteAllowedInCurrentMode } from "./nix-mode-write-guard-DMwPAosm.js";
import { i as replaceConfigFile } from "./config-C9RxTsn1.js";
import { i as readInstalledPackageVersion, t as expectedIntegrityForUpdate } from "./package-update-utils-CMbraz6-.js";
import { t as recordHookInstall } from "./installs-DC4E2xLJ.js";
import { r as commitPluginInstallRecordsWithConfig } from "./plugins-install-record-commit--Zbbdxlm.js";
import { t as refreshPluginRegistryAfterConfigMutation } from "./plugins-registry-refresh-D83-QnpV.js";
import { r as resolveHookInstallDir, t as installHooksFromNpmSpec } from "./install-BYbeO-DA.js";
import { n as updateNpmInstalledPlugins } from "./update-XxqM_NQd.js";
//#region src/hooks/update.ts
function createHookPackUpdateIntegrityDriftHandler(params) {
return async (drift) => {
const payload = {
hookId: params.hookId,
spec: drift.spec,
expectedIntegrity: drift.expectedIntegrity,
actualIntegrity: drift.actualIntegrity,
resolution: drift.resolution,
resolvedSpec: drift.resolution.resolvedSpec,
resolvedVersion: drift.resolution.version,
dryRun: params.dryRun
};
if (params.onIntegrityDrift) return await params.onIntegrityDrift(payload);
params.logger.warn?.(`Integrity drift for hook pack "${params.hookId}" (${payload.resolvedSpec ?? payload.spec}): expected ${payload.expectedIntegrity}, got ${payload.actualIntegrity}`);
return false;
};
}
/** Update npm-installed hook packs and return config changes plus per-pack outcomes. */
async function updateNpmInstalledHookPacks(params) {
const logger = params.logger ?? {};
const installs = params.config.hooks?.internal?.installs ?? {};
const targets = params.hookIds?.length ? params.hookIds : Object.keys(installs);
const outcomes = [];
let next = params.config;
let changed = false;
for (const hookId of targets) {
const record = installs[hookId];
if (!record) {
outcomes.push({
hookId,
status: "skipped",
message: `No install record for hook pack "${hookId}".`
});
continue;
}
if (record.source !== "npm") {
outcomes.push({
hookId,
status: "skipped",
message: `Skipping hook pack "${hookId}" (source: ${record.source}).`
});
continue;
}
const effectiveSpec = params.specOverrides?.[hookId] ?? record.spec;
const expectedIntegrity = effectiveSpec === record.spec ? expectedIntegrityForUpdate(record.spec, record.integrity) : void 0;
if (!effectiveSpec) {
outcomes.push({
hookId,
status: "skipped",
message: `Skipping hook pack "${hookId}" (missing npm spec).`
});
continue;
}
let installPath;
try {
installPath = record.installPath ?? resolveHookInstallDir(hookId);
} catch (err) {
outcomes.push({
hookId,
status: "error",
message: `Invalid install path for hook pack "${hookId}": ${String(err)}`
});
continue;
}
const currentVersion = await readInstalledPackageVersion(installPath);
const result = await installHooksFromNpmSpec({
spec: effectiveSpec,
mode: "update",
dryRun: params.dryRun,
expectedHookPackId: hookId,
expectedIntegrity,
onIntegrityDrift: createHookPackUpdateIntegrityDriftHandler({
hookId,
dryRun: Boolean(params.dryRun),
logger,
onIntegrityDrift: params.onIntegrityDrift
}),
logger
});
if (!result.ok) {
outcomes.push({
hookId,
status: "error",
message: `Failed to ${params.dryRun ? "check" : "update"} hook pack "${hookId}": ${result.error}`
});
continue;
}
const nextVersion = result.version ?? await readInstalledPackageVersion(result.targetDir);
const currentLabel = currentVersion ?? "unknown";
const nextLabel = nextVersion ?? "unknown";
const status = currentVersion && nextVersion && currentVersion === nextVersion ? "unchanged" : "updated";
if (params.dryRun) {
outcomes.push({
hookId,
status,
currentVersion: currentVersion ?? void 0,
nextVersion: nextVersion ?? void 0,
message: status === "unchanged" ? `Hook pack "${hookId}" is up to date (${currentLabel}).` : `Would update hook pack "${hookId}": ${currentLabel} -> ${nextLabel}.`
});
continue;
}
next = recordHookInstall(next, {
hookId,
source: "npm",
spec: effectiveSpec,
installPath: result.targetDir,
version: nextVersion,
...buildNpmResolutionFields(result.npmResolution),
hooks: result.hooks
});
changed = true;
outcomes.push({
hookId,
status,
currentVersion: currentVersion ?? void 0,
nextVersion: nextVersion ?? void 0,
message: status === "unchanged" ? `Hook pack "${hookId}" already at ${currentLabel}.` : `Updated hook pack "${hookId}": ${currentLabel} -> ${nextLabel}.`
});
}
return {
config: next,
changed,
outcomes
};
}
//#endregion
//#region src/cli/plugins-update-outcomes.ts
/** Log update outcomes with severity styling and report whether any errors occurred. */
function logPluginUpdateOutcomes(params) {
let hasErrors = false;
for (const outcome of params.outcomes) {
if (outcome.status === "error") {
hasErrors = true;
params.log(theme.error(outcome.message));
if (outcome.channelFallback) params.log(theme.warn(outcome.channelFallback.message));
continue;
}
if (outcome.status === "skipped") {
params.log(theme.warn(outcome.message));
if (outcome.channelFallback) params.log(theme.warn(outcome.channelFallback.message));
continue;
}
params.log(outcome.message);
if (outcome.channelFallback) params.log(theme.warn(outcome.channelFallback.message));
}
return { hasErrors };
}
//#endregion
//#region src/cli/plugins-install-records.ts
/** Return the installed npm package name for a plugin install record when available. */
function extractInstalledNpmPackageName(install) {
if (install.source !== "npm") return;
const resolvedName = install.resolvedName?.trim();
if (resolvedName) return resolvedName;
return (install.spec ? parseRegistryNpmSpec(install.spec)?.name : void 0) ?? (install.resolvedSpec ? parseRegistryNpmSpec(install.resolvedSpec)?.name : void 0);
}
/** Return the installed npm package name for a hook-pack install record when available. */
function extractInstalledNpmHookPackageName(install) {
const resolvedName = install.resolvedName?.trim();
if (resolvedName) return resolvedName;
return (install.spec ? parseRegistryNpmSpec(install.spec)?.name : void 0) ?? (install.resolvedSpec ? parseRegistryNpmSpec(install.resolvedSpec)?.name : void 0);
}
//#endregion
//#region src/cli/plugins-update-selection.ts
/** Resolve a plugin update target and optional npm spec override from CLI input. */
function resolvePluginUpdateSelection(params) {
if (params.all) return { pluginIds: Object.keys(params.installs) };
if (!params.rawId) return { pluginIds: [] };
if (params.rawId in params.installs) return { pluginIds: [params.rawId] };
const parsedSpec = parseRegistryNpmSpec(params.rawId);
if (!parsedSpec) return { pluginIds: [params.rawId] };
const matches = Object.entries(params.installs).filter(([, install]) => {
return extractInstalledNpmPackageName(install) === parsedSpec.name;
});
if (matches.length !== 1) return { pluginIds: [params.rawId] };
const [pluginId] = matches[0];
if (!pluginId) return { pluginIds: [params.rawId] };
if (parsedSpec.selectorKind === "none") return {
pluginIds: [pluginId],
specOverrides: { [pluginId]: parsedSpec.raw }
};
return {
pluginIds: [pluginId],
specOverrides: { [pluginId]: parsedSpec.raw }
};
}
/** Resolve a hook-pack update target and optional npm spec override from CLI input. */
function resolveHookPackUpdateSelection(params) {
if (params.all) return { hookIds: Object.keys(params.installs) };
if (!params.rawId) return { hookIds: [] };
if (params.rawId in params.installs) return { hookIds: [params.rawId] };
const parsedSpec = parseRegistryNpmSpec(params.rawId);
if (!parsedSpec || parsedSpec.selectorKind === "none") return { hookIds: [] };
const matches = Object.entries(params.installs).filter(([, install]) => {
return extractInstalledNpmHookPackageName(install) === parsedSpec.name;
});
if (matches.length !== 1) return { hookIds: [] };
const [hookId] = matches[0];
if (!hookId) return { hookIds: [] };
return {
hookIds: [hookId],
specOverrides: { [hookId]: parsedSpec.raw }
};
}
//#endregion
//#region src/cli/plugins-update-command.ts
const DEPRECATED_DANGEROUS_FORCE_UNSAFE_UPDATE_WARNING = "--dangerously-force-unsafe-install is deprecated and no longer affects plugin updates because built-in install-time dangerous-code scanning has been removed. Configure security.installPolicy for operator-owned install decisions.";
/** Run plugin/hook-pack updates, persist changed install records, and refresh runtime registry. */
async function runPluginUpdateCommand(params) {
assertConfigWriteAllowedInCurrentMode();
const sourceSnapshotPromise = readConfigFileSnapshot().catch(() => null);
const cfg = getRuntimeConfig();
const pluginInstallRecords = await loadInstalledPluginIndexInstallRecords();
const cfgWithPluginInstallRecords = withPluginInstallRecords(cfg, pluginInstallRecords);
const logger = {
info: (msg) => defaultRuntime.log(msg),
warn: (msg) => defaultRuntime.log(theme.warn(msg))
};
if (params.opts.dangerouslyForceUnsafeInstall) defaultRuntime.log(theme.warn(DEPRECATED_DANGEROUS_FORCE_UNSAFE_UPDATE_WARNING));
const pluginSelection = resolvePluginUpdateSelection({
installs: pluginInstallRecords,
rawId: params.id,
all: params.opts.all
});
const hookSelection = resolveHookPackUpdateSelection({
installs: cfg.hooks?.internal?.installs ?? {},
rawId: params.id,
all: params.opts.all
});
if (pluginSelection.pluginIds.length === 0 && hookSelection.hookIds.length === 0) {
if (params.opts.all) {
defaultRuntime.log("No tracked plugins or hook packs to update.");
return;
}
defaultRuntime.error("Provide a plugin or hook-pack id, or use --all.");
return defaultRuntime.exit(1);
}
const pluginResult = await updateNpmInstalledPlugins({
config: cfgWithPluginInstallRecords,
pluginIds: pluginSelection.pluginIds,
specOverrides: pluginSelection.specOverrides,
dryRun: params.opts.dryRun,
dangerouslyForceUnsafeInstall: params.opts.dangerouslyForceUnsafeInstall,
logger,
onIntegrityDrift: async (drift) => {
const specLabel = drift.resolvedSpec ?? drift.spec;
defaultRuntime.log(theme.warn(`Integrity drift detected for "${drift.pluginId}" (${specLabel})\nExpected: ${drift.expectedIntegrity}\nActual: ${drift.actualIntegrity}`));
if (drift.dryRun) return true;
return await promptYesNo(`Continue updating "${drift.pluginId}" with this artifact?`);
}
});
const hookResult = await updateNpmInstalledHookPacks({
config: pluginResult.config,
hookIds: hookSelection.hookIds,
specOverrides: hookSelection.specOverrides,
dryRun: params.opts.dryRun,
logger,
onIntegrityDrift: async (drift) => {
const specLabel = drift.resolvedSpec ?? drift.spec;
defaultRuntime.log(theme.warn(`Integrity drift detected for hook pack "${drift.hookId}" (${specLabel})\nExpected: ${drift.expectedIntegrity}\nActual: ${drift.actualIntegrity}`));
if (drift.dryRun) return true;
return await promptYesNo(`Continue updating hook pack "${drift.hookId}" with this artifact?`);
}
});
const outcomeSummary = logPluginUpdateOutcomes({
outcomes: [...pluginResult.outcomes, ...hookResult.outcomes],
log: (message) => defaultRuntime.log(message)
});
if (!params.opts.dryRun && (pluginResult.changed || hookResult.changed)) {
const nextPluginInstallRecords = pluginResult.config.plugins?.installs ?? {};
const shouldPersistPluginInstallIndex = pluginResult.changed || Object.keys(pluginInstallRecords).length > 0;
const nextConfig = shouldPersistPluginInstallIndex ? withoutPluginInstallRecords(hookResult.config) : hookResult.config;
if (shouldPersistPluginInstallIndex) await commitPluginInstallRecordsWithConfig({
previousInstallRecords: pluginInstallRecords,
nextInstallRecords: nextPluginInstallRecords,
nextConfig,
baseHash: (await sourceSnapshotPromise)?.hash,
writeOptions: { afterWrite: {
mode: "restart",
reason: "plugin source changed"
} }
});
else await replaceConfigFile({
nextConfig,
baseHash: (await sourceSnapshotPromise)?.hash
});
if (pluginResult.changed) await refreshPluginRegistryAfterConfigMutation({
config: nextConfig,
reason: "source-changed",
installRecords: nextPluginInstallRecords,
invalidateRuntimeCache: false,
logger
});
defaultRuntime.log("Restart the gateway to load plugins and hooks.");
}
if (outcomeSummary.hasErrors) defaultRuntime.exit(1);
}
//#endregion
export { runPluginUpdateCommand as t };