Files
openclaw/src/plugins/plugin-install-preflight.ts
Gio Della-Libera ba467fbd3e Install ClawHub packages for new Claw agents (#102228)
* Install ClawHub packages for new Claw agents

* test(claws): cover skill and plugin installation

* docs(claws): document package artifacts

* fix(claws): bind package trust preflight

* fix(claws): bind package capability consent

* fix(claws): satisfy package type guards

* test(claws): update package installer fixtures

* test(claws): complete package plan fixtures

* test(claws): model complete ClawHub results

* fix(claws): serialize plugin ownership changes

* fix(claws): cover legacy plugin lifecycle updates

* fix(claws): preserve legacy ClawHub update selectors

* test(claws): type lifecycle lease mock

* refactor(claws): isolate plugin update lifecycle

* fix(claws): declare managed installer coordination

* test(claws): preserve partial collision result

* test(claws): clean installer temp dirs

---------

Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
2026-07-21 18:55:10 -07:00

117 lines
3.8 KiB
TypeScript

import {
resolvePluginInstallRequestContext,
type PluginInstallRequestContext,
} from "../cli/plugin-install-config-policy.js";
import type { PluginInstallRecord } from "../config/types.plugins.js";
import { parseClawHubPluginSpec } from "../infra/clawhub-spec.js";
import { loadInstalledPluginIndexInstallRecords } from "./installed-plugin-index-records.js";
type PluginInstallPreflightResult =
| { ok: true; action: "install"; request: PluginInstallRequestContext }
| {
ok: true;
action: "reuse";
request: PluginInstallRequestContext;
installedId: string;
installedVersion: string;
installedIntegrity?: string;
installedAt?: string;
}
| {
ok: false;
code: "plugin_version_conflict";
request: PluginInstallRequestContext;
installedVersion: string;
expectedVersion: string;
}
| { ok: false; code: "invalid_plugin_spec"; error: string };
type InstalledClawHubPluginResolution =
| { status: "missing" }
| { status: "ambiguous"; pluginIds: string[] }
| {
status: "found";
pluginId: string;
record: PluginInstallRecord;
installedVersion?: string;
};
/** Resolves one installed plugin by its stable ClawHub package identity. */
export async function resolveInstalledClawHubPlugin(params: {
clawhubPackage: string;
loadInstallRecords?: typeof loadInstalledPluginIndexInstallRecords;
}): Promise<InstalledClawHubPluginResolution> {
const records = await (params.loadInstallRecords ?? loadInstalledPluginIndexInstallRecords)();
const matches = Object.entries(records).filter(
([, record]) =>
(record.clawhubPackage ??
parseClawHubPluginSpec(record.spec ?? "")?.name ??
parseClawHubPluginSpec(record.resolvedSpec ?? "")?.name) === params.clawhubPackage,
);
if (matches.length === 0) {
return { status: "missing" };
}
if (matches.length > 1) {
return { status: "ambiguous", pluginIds: matches.map(([pluginId]) => pluginId).toSorted() };
}
const match = matches[0];
if (!match) {
return { status: "missing" };
}
const [pluginId, record] = match;
return {
status: "found",
pluginId,
record,
installedVersion: record.resolvedVersion ?? record.version,
};
}
export async function preflightPluginInstall(params: {
clawhubPackage: string;
rawSpec: string;
expectedVersion: string;
marketplace?: string;
loadInstallRecords?: typeof loadInstalledPluginIndexInstallRecords;
}): Promise<PluginInstallPreflightResult> {
const resolved = resolvePluginInstallRequestContext({
rawSpec: params.rawSpec,
...(params.marketplace ? { marketplace: params.marketplace } : {}),
installKind: "plugin",
});
if (!resolved.ok) {
return { ok: false, code: "invalid_plugin_spec", error: resolved.error };
}
const records = await (params.loadInstallRecords ?? loadInstalledPluginIndexInstallRecords)();
const installedEntry = Object.entries(records).find(
([, record]) =>
(record.clawhubPackage ?? parseClawHubPluginSpec(record.spec ?? "")?.name) ===
params.clawhubPackage,
);
const installedId = installedEntry?.[0];
const installed = installedEntry?.[1];
const installedVersion = installed?.resolvedVersion ?? installed?.version;
if (!installedVersion || !installedId) {
return { ok: true, action: "install", request: resolved.request };
}
if (installedVersion === params.expectedVersion) {
return {
ok: true,
action: "reuse",
request: resolved.request,
installedId,
installedVersion,
...(installed?.integrity ? { installedIntegrity: installed.integrity } : {}),
...(installed?.installedAt ? { installedAt: installed.installedAt } : {}),
};
}
return {
ok: false,
code: "plugin_version_conflict",
request: resolved.request,
installedVersion,
expectedVersion: params.expectedVersion,
};
}