mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:30:42 +00:00
fix(plugins): sync official plugin installs during update (#78065)
* fix(plugins): sync official npm installs during update * fix(plugins): sync official clawhub installs during update * test(update): mock official plugin sync helpers --------- Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
This commit is contained in:
@@ -100,6 +100,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Control UI/sessions: show each session's agent runtime in the Sessions table and allow filtering by runtime labels, matching the Agents panel runtime wording. Thanks @vincentkoc.
|
||||
- Discord/streaming: show live reasoning text in progress drafts instead of a bare `Reasoning` status line.
|
||||
- Gateway/status: avoid marking fast repeated health/status samples as event-loop degraded from CPU/utilization alone until the Gateway has accumulated a sustained sampling window. Thanks @shakkernerd.
|
||||
- Plugins/update: keep installed official npm and ClawHub plugins such as Codex, Discord, WhatsApp, and diagnostics plugins synced during host updates even when disabled or previously exact-pinned, while preserving third-party plugin pins. Thanks @vincentkoc.
|
||||
- Doctor/status: warn when `OPENCLAW_GATEWAY_TOKEN` would shadow a different active `gateway.auth.token` source for local CLI commands, while avoiding false positives when config points at the same env token. Fixes #74271. Thanks @yelog.
|
||||
- Gateway/HTTP: avoid loading managed outgoing-image media handlers for unrelated requests, so disabled OpenAI-compatible routes return 404 without waiting on lazy media sidecars. Thanks @vincentkoc.
|
||||
- Gateway/OpenAI-compatible: send the assistant role SSE chunk as soon as streaming chat-completion headers are accepted, so cold agent setup cannot leave `/v1/chat/completions` clients with a bodyless 200 response until their idle timeout fires.
|
||||
|
||||
@@ -168,6 +168,8 @@ vi.mock("../utils.js", async (importOriginal) => {
|
||||
});
|
||||
|
||||
vi.mock("../plugins/update.js", () => ({
|
||||
resolveTrustedSourceLinkedOfficialClawHubSpec: vi.fn(() => undefined),
|
||||
resolveTrustedSourceLinkedOfficialNpmSpec: vi.fn(() => undefined),
|
||||
syncPluginsForUpdateChannel: (...args: unknown[]) => syncPluginsForUpdateChannel(...args),
|
||||
updateNpmInstalledPlugins: (...args: unknown[]) => updateNpmInstalledPlugins(...args),
|
||||
}));
|
||||
@@ -2439,13 +2441,14 @@ describe("update-cli", () => {
|
||||
| OpenClawConfig
|
||||
| undefined;
|
||||
const updateCall = vi.mocked(updateNpmInstalledPlugins).mock.calls[0]?.[0] as
|
||||
| { skipDisabledPlugins?: boolean }
|
||||
| { skipDisabledPlugins?: boolean; syncOfficialPluginInstalls?: boolean }
|
||||
| undefined;
|
||||
expect(syncConfig?.plugins?.installs).toEqual(pluginInstallRecords);
|
||||
expect(syncConfig?.update?.channel).toBe("beta");
|
||||
expect(syncConfig?.gateway?.auth).toBeUndefined();
|
||||
expect(syncConfig?.plugins?.entries).toBeUndefined();
|
||||
expect(updateCall?.skipDisabledPlugins).toBe(true);
|
||||
expect(updateCall?.syncOfficialPluginInstalls).toBe(true);
|
||||
});
|
||||
|
||||
it("persists channel and runs post-update work after switching from package to git", async () => {
|
||||
|
||||
@@ -253,6 +253,84 @@ describe("collectMissingPluginInstallPayloads", () => {
|
||||
await fs.rm(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps disabled trusted official npm records eligible for payload repair when requested", async () => {
|
||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-update-plugin-payload-"));
|
||||
const missingDir = path.join(tmpDir, "state", "npm", "node_modules", "@openclaw", "codex");
|
||||
try {
|
||||
await expect(
|
||||
collectMissingPluginInstallPayloads({
|
||||
env: { HOME: tmpDir } as NodeJS.ProcessEnv,
|
||||
skipDisabledPlugins: true,
|
||||
syncOfficialPluginInstalls: true,
|
||||
config: {
|
||||
plugins: {
|
||||
entries: {
|
||||
codex: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
records: {
|
||||
codex: {
|
||||
source: "npm",
|
||||
spec: "@openclaw/codex@2026.5.3",
|
||||
resolvedName: "@openclaw/codex",
|
||||
resolvedSpec: "@openclaw/codex@2026.5.3",
|
||||
installPath: missingDir,
|
||||
},
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual([
|
||||
{
|
||||
pluginId: "codex",
|
||||
installPath: missingDir,
|
||||
reason: "missing-package-dir",
|
||||
},
|
||||
]);
|
||||
} finally {
|
||||
await fs.rm(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps disabled trusted official ClawHub records eligible for payload repair when requested", async () => {
|
||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-update-plugin-payload-"));
|
||||
const missingDir = path.join(tmpDir, "state", "clawhub", "diagnostics-otel");
|
||||
try {
|
||||
await expect(
|
||||
collectMissingPluginInstallPayloads({
|
||||
env: { HOME: tmpDir } as NodeJS.ProcessEnv,
|
||||
skipDisabledPlugins: true,
|
||||
syncOfficialPluginInstalls: true,
|
||||
config: {
|
||||
plugins: {
|
||||
entries: {
|
||||
"diagnostics-otel": {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
records: {
|
||||
"diagnostics-otel": {
|
||||
source: "clawhub",
|
||||
spec: "clawhub:@openclaw/diagnostics-otel@2026.5.3",
|
||||
installPath: missingDir,
|
||||
},
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual([
|
||||
{
|
||||
pluginId: "diagnostics-otel",
|
||||
installPath: missingDir,
|
||||
reason: "missing-package-dir",
|
||||
},
|
||||
]);
|
||||
} finally {
|
||||
await fs.rm(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("shouldUseLegacyProcessRestartAfterUpdate", () => {
|
||||
|
||||
@@ -58,6 +58,8 @@ import {
|
||||
withPluginInstallRecords,
|
||||
} from "../../plugins/installed-plugin-index-records.js";
|
||||
import {
|
||||
resolveTrustedSourceLinkedOfficialClawHubSpec,
|
||||
resolveTrustedSourceLinkedOfficialNpmSpec,
|
||||
syncPluginsForUpdateChannel,
|
||||
updateNpmInstalledPlugins,
|
||||
type PluginUpdateIntegrityDriftParams,
|
||||
@@ -190,6 +192,7 @@ export async function collectMissingPluginInstallPayloads(params: {
|
||||
records: Record<string, PluginInstallRecord>;
|
||||
config?: OpenClawConfig;
|
||||
skipDisabledPlugins?: boolean;
|
||||
syncOfficialPluginInstalls?: boolean;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): Promise<MissingPluginInstallPayload[]> {
|
||||
const env = params.env ?? process.env;
|
||||
@@ -204,6 +207,12 @@ export async function collectMissingPluginInstallPayloads(params: {
|
||||
if (!isTrackedPackageInstallRecord(record)) {
|
||||
continue;
|
||||
}
|
||||
const officialNpmSpec = params.syncOfficialPluginInstalls
|
||||
? resolveTrustedSourceLinkedOfficialNpmSpec({ pluginId, record })
|
||||
: undefined;
|
||||
const officialClawHubSpec = params.syncOfficialPluginInstalls
|
||||
? resolveTrustedSourceLinkedOfficialClawHubSpec({ pluginId, record })
|
||||
: undefined;
|
||||
if (normalizedPluginConfig && params.config) {
|
||||
const enableState = resolveEffectiveEnableState({
|
||||
id: pluginId,
|
||||
@@ -211,7 +220,7 @@ export async function collectMissingPluginInstallPayloads(params: {
|
||||
config: normalizedPluginConfig,
|
||||
rootConfig: params.config,
|
||||
});
|
||||
if (!enableState.enabled) {
|
||||
if (!enableState.enabled && !officialNpmSpec && !officialClawHubSpec) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -1168,6 +1177,7 @@ async function updatePluginsAfterCoreUpdate(params: {
|
||||
records,
|
||||
config: pluginConfig,
|
||||
skipDisabledPlugins: true,
|
||||
syncOfficialPluginInstalls: true,
|
||||
});
|
||||
if (missing.length === 0) {
|
||||
return [];
|
||||
@@ -1188,6 +1198,21 @@ async function updatePluginsAfterCoreUpdate(params: {
|
||||
defaultRuntime.log(theme.warn(warning.message));
|
||||
}
|
||||
}
|
||||
const repairResult = await updateNpmInstalledPlugins({
|
||||
config: pluginConfig,
|
||||
pluginIds: missingIds,
|
||||
timeoutMs: params.timeoutMs,
|
||||
updateChannel: params.channel,
|
||||
skipDisabledPlugins: true,
|
||||
syncOfficialPluginInstalls: true,
|
||||
disableOnFailure: true,
|
||||
logger: pluginLogger,
|
||||
onIntegrityDrift: onPluginIntegrityDrift,
|
||||
});
|
||||
pluginConfig = repairResult.config;
|
||||
pluginsChanged ||= repairResult.changed;
|
||||
npmPluginsChanged ||= repairResult.changed;
|
||||
pluginUpdateOutcomes.push(...repairResult.outcomes);
|
||||
return missingIds;
|
||||
};
|
||||
|
||||
@@ -1199,6 +1224,8 @@ async function updatePluginsAfterCoreUpdate(params: {
|
||||
updateChannel: params.channel,
|
||||
skipIds: new Set([...syncResult.summary.switchedToNpm, ...missingPayloadIds]),
|
||||
skipDisabledPlugins: true,
|
||||
syncOfficialPluginInstalls: true,
|
||||
disableOnFailure: true,
|
||||
logger: pluginLogger,
|
||||
onIntegrityDrift: onPluginIntegrityDrift,
|
||||
});
|
||||
@@ -1217,6 +1244,7 @@ async function updatePluginsAfterCoreUpdate(params: {
|
||||
records: pluginConfig.plugins?.installs ?? {},
|
||||
config: pluginConfig,
|
||||
skipDisabledPlugins: true,
|
||||
syncOfficialPluginInstalls: true,
|
||||
});
|
||||
pluginUpdateOutcomes.push(
|
||||
...remainingMissingPayloads
|
||||
|
||||
@@ -1111,6 +1111,207 @@ describe("updateNpmInstalledPlugins", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("updates disabled trusted official npm installs from the channel spec when requested", async () => {
|
||||
const installPath = createInstalledPackageDir({
|
||||
name: "@openclaw/codex",
|
||||
version: "2026.5.3",
|
||||
});
|
||||
mockNpmViewMetadata({
|
||||
name: "@openclaw/codex",
|
||||
version: "2026.5.4",
|
||||
integrity: "sha512-next",
|
||||
shasum: "next",
|
||||
});
|
||||
installPluginFromNpmSpecMock.mockResolvedValue(
|
||||
createSuccessfulNpmUpdateResult({
|
||||
pluginId: "codex",
|
||||
targetDir: installPath,
|
||||
version: "2026.5.4",
|
||||
npmResolution: {
|
||||
name: "@openclaw/codex",
|
||||
version: "2026.5.4",
|
||||
resolvedSpec: "@openclaw/codex@2026.5.4",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const result = await updateNpmInstalledPlugins({
|
||||
config: {
|
||||
plugins: {
|
||||
entries: {
|
||||
codex: {
|
||||
enabled: false,
|
||||
config: { preserved: true },
|
||||
},
|
||||
},
|
||||
installs: {
|
||||
codex: {
|
||||
source: "npm",
|
||||
spec: "@openclaw/codex@2026.5.3",
|
||||
installPath,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
skipDisabledPlugins: true,
|
||||
syncOfficialPluginInstalls: true,
|
||||
});
|
||||
|
||||
expect(installPluginFromNpmSpecMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
spec: "@openclaw/codex",
|
||||
expectedPluginId: "codex",
|
||||
trustedSourceLinkedOfficialInstall: true,
|
||||
}),
|
||||
);
|
||||
expect(result.changed).toBe(true);
|
||||
expect(result.config.plugins?.entries?.codex).toEqual({
|
||||
enabled: false,
|
||||
config: { preserved: true },
|
||||
});
|
||||
expect(result.config.plugins?.installs?.codex).toMatchObject({
|
||||
source: "npm",
|
||||
spec: "@openclaw/codex",
|
||||
version: "2026.5.4",
|
||||
resolvedName: "@openclaw/codex",
|
||||
resolvedVersion: "2026.5.4",
|
||||
resolvedSpec: "@openclaw/codex@2026.5.4",
|
||||
});
|
||||
expect(result.outcomes[0]).toMatchObject({
|
||||
pluginId: "codex",
|
||||
status: "updated",
|
||||
currentVersion: "2026.5.3",
|
||||
nextVersion: "2026.5.4",
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps third-party exact pinned npm specs pinned during official install sync", async () => {
|
||||
const installPath = createInstalledPackageDir({
|
||||
name: "@acme/demo",
|
||||
version: "1.2.3",
|
||||
});
|
||||
installPluginFromNpmSpecMock.mockResolvedValue(
|
||||
createSuccessfulNpmUpdateResult({
|
||||
pluginId: "demo",
|
||||
targetDir: installPath,
|
||||
version: "1.2.3",
|
||||
}),
|
||||
);
|
||||
|
||||
await updateNpmInstalledPlugins({
|
||||
config: createNpmInstallConfig({
|
||||
pluginId: "demo",
|
||||
spec: "@acme/demo@1.2.3",
|
||||
installPath,
|
||||
}),
|
||||
pluginIds: ["demo"],
|
||||
dryRun: true,
|
||||
syncOfficialPluginInstalls: true,
|
||||
});
|
||||
|
||||
expect(installPluginFromNpmSpecMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
spec: "@acme/demo@1.2.3",
|
||||
expectedPluginId: "demo",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("updates disabled trusted official ClawHub installs through the catalog spec", async () => {
|
||||
installPluginFromClawHubMock.mockResolvedValue(
|
||||
createSuccessfulClawHubUpdateResult({
|
||||
pluginId: "diagnostics-otel",
|
||||
targetDir: "/tmp/diagnostics-otel",
|
||||
version: "2026.5.4",
|
||||
clawhubPackage: "@openclaw/diagnostics-otel",
|
||||
}),
|
||||
);
|
||||
|
||||
const config = createClawHubInstallConfig({
|
||||
pluginId: "diagnostics-otel",
|
||||
installPath: "/tmp/diagnostics-otel",
|
||||
clawhubUrl: "https://clawhub.ai",
|
||||
clawhubPackage: "@openclaw/diagnostics-otel",
|
||||
clawhubFamily: "code-plugin",
|
||||
clawhubChannel: "official",
|
||||
spec: "clawhub:@openclaw/diagnostics-otel@2026.5.3",
|
||||
});
|
||||
const result = await updateNpmInstalledPlugins({
|
||||
config: {
|
||||
...config,
|
||||
plugins: {
|
||||
...config.plugins,
|
||||
entries: {
|
||||
"diagnostics-otel": {
|
||||
enabled: false,
|
||||
config: { preserved: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
skipDisabledPlugins: true,
|
||||
syncOfficialPluginInstalls: true,
|
||||
});
|
||||
|
||||
expect(installPluginFromClawHubMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
spec: "clawhub:@openclaw/diagnostics-otel",
|
||||
expectedPluginId: "diagnostics-otel",
|
||||
}),
|
||||
);
|
||||
expect(result.config.plugins?.installs?.["diagnostics-otel"]).toMatchObject({
|
||||
source: "clawhub",
|
||||
spec: "clawhub:@openclaw/diagnostics-otel",
|
||||
version: "2026.5.4",
|
||||
clawhubPackage: "@openclaw/diagnostics-otel",
|
||||
clawhubChannel: "official",
|
||||
});
|
||||
expect(result.config.plugins?.entries?.["diagnostics-otel"]).toEqual({
|
||||
enabled: false,
|
||||
config: { preserved: true },
|
||||
});
|
||||
});
|
||||
|
||||
it("updates bare trusted official ClawHub installs through the catalog spec", async () => {
|
||||
installPluginFromClawHubMock.mockResolvedValue(
|
||||
createSuccessfulClawHubUpdateResult({
|
||||
pluginId: "diagnostics-prometheus",
|
||||
targetDir: "/tmp/diagnostics-prometheus",
|
||||
version: "2026.5.4",
|
||||
clawhubPackage: "@openclaw/diagnostics-prometheus",
|
||||
}),
|
||||
);
|
||||
|
||||
const result = await updateNpmInstalledPlugins({
|
||||
config: {
|
||||
plugins: {
|
||||
installs: {
|
||||
"diagnostics-prometheus": {
|
||||
source: "clawhub",
|
||||
spec: "clawhub:@openclaw/diagnostics-prometheus@2026.5.3",
|
||||
installPath: "/tmp/diagnostics-prometheus",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
syncOfficialPluginInstalls: true,
|
||||
});
|
||||
|
||||
expect(installPluginFromClawHubMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
spec: "clawhub:@openclaw/diagnostics-prometheus",
|
||||
expectedPluginId: "diagnostics-prometheus",
|
||||
}),
|
||||
);
|
||||
expect(result.config.plugins?.installs?.["diagnostics-prometheus"]).toMatchObject({
|
||||
source: "clawhub",
|
||||
spec: "clawhub:@openclaw/diagnostics-prometheus",
|
||||
version: "2026.5.4",
|
||||
clawhubPackage: "@openclaw/diagnostics-prometheus",
|
||||
clawhubChannel: "official",
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps enabled tracked plugin update failures fatal when disabled skipping is enabled", async () => {
|
||||
installPluginFromNpmSpecMock.mockResolvedValue({
|
||||
ok: false,
|
||||
|
||||
@@ -467,31 +467,66 @@ function resolveNpmSpecPackageName(spec: string | undefined): string | undefined
|
||||
return spec ? parseRegistryNpmSpec(spec)?.name : undefined;
|
||||
}
|
||||
|
||||
function isTrustedSourceLinkedOfficialNpmUpdate(params: {
|
||||
function resolveClawHubSpecPackageName(spec: string | undefined): string | undefined {
|
||||
return spec ? parseClawHubPluginSpec(spec)?.name : undefined;
|
||||
}
|
||||
|
||||
export function resolveTrustedSourceLinkedOfficialNpmSpec(params: {
|
||||
pluginId: string;
|
||||
spec: string | undefined;
|
||||
record: PluginInstallRecord;
|
||||
}): boolean {
|
||||
}): string | undefined {
|
||||
if (params.record.source !== "npm") {
|
||||
return false;
|
||||
return undefined;
|
||||
}
|
||||
const entry = getOfficialExternalPluginCatalogEntry(params.pluginId);
|
||||
if (!entry) {
|
||||
return false;
|
||||
return undefined;
|
||||
}
|
||||
const officialPackageName = resolveNpmSpecPackageName(
|
||||
resolveOfficialExternalPluginInstall(entry)?.npmSpec,
|
||||
);
|
||||
const requestedPackageName = resolveNpmSpecPackageName(params.spec);
|
||||
if (!officialPackageName || requestedPackageName !== officialPackageName) {
|
||||
return false;
|
||||
const officialSpec = resolveOfficialExternalPluginInstall(entry)?.npmSpec;
|
||||
const officialPackageName = resolveNpmSpecPackageName(officialSpec);
|
||||
if (!officialSpec || !officialPackageName) {
|
||||
return undefined;
|
||||
}
|
||||
const recordedPackageNames = [
|
||||
params.record.resolvedName,
|
||||
resolveNpmSpecPackageName(params.record.spec),
|
||||
resolveNpmSpecPackageName(params.record.resolvedSpec),
|
||||
].filter((value): value is string => Boolean(value));
|
||||
return recordedPackageNames.includes(officialPackageName);
|
||||
return recordedPackageNames.includes(officialPackageName) ? officialSpec : undefined;
|
||||
}
|
||||
|
||||
export function resolveTrustedSourceLinkedOfficialClawHubSpec(params: {
|
||||
pluginId: string;
|
||||
record: PluginInstallRecord;
|
||||
}): string | undefined {
|
||||
if (params.record.source !== "clawhub") {
|
||||
return undefined;
|
||||
}
|
||||
const entry = getOfficialExternalPluginCatalogEntry(params.pluginId);
|
||||
if (!entry) {
|
||||
return undefined;
|
||||
}
|
||||
const officialSpec = resolveOfficialExternalPluginInstall(entry)?.clawhubSpec;
|
||||
const officialPackageName = resolveClawHubSpecPackageName(officialSpec);
|
||||
if (!officialSpec || !officialPackageName) {
|
||||
return undefined;
|
||||
}
|
||||
const recordedPackageNames = [
|
||||
params.record.clawhubPackage,
|
||||
resolveClawHubSpecPackageName(params.record.spec),
|
||||
].filter((value): value is string => Boolean(value));
|
||||
return recordedPackageNames.includes(officialPackageName) ? officialSpec : undefined;
|
||||
}
|
||||
|
||||
function isTrustedSourceLinkedOfficialNpmUpdate(params: {
|
||||
pluginId: string;
|
||||
spec: string | undefined;
|
||||
record: PluginInstallRecord;
|
||||
}): boolean {
|
||||
const officialSpec = resolveTrustedSourceLinkedOfficialNpmSpec(params);
|
||||
const officialPackageName = resolveNpmSpecPackageName(officialSpec);
|
||||
const requestedPackageName = resolveNpmSpecPackageName(params.spec);
|
||||
return Boolean(officialPackageName && requestedPackageName === officialPackageName);
|
||||
}
|
||||
|
||||
function isTrustedSourceLinkedOfficialBridgeNpmInstall(params: {
|
||||
@@ -542,6 +577,7 @@ function isBridgeClawHubInstall(params: {
|
||||
function resolveNpmUpdateSpecs(params: {
|
||||
record: PluginInstallRecord;
|
||||
specOverride?: string;
|
||||
officialSpecOverride?: string;
|
||||
updateChannel?: UpdateChannel;
|
||||
}): {
|
||||
installSpec?: string;
|
||||
@@ -549,7 +585,7 @@ function resolveNpmUpdateSpecs(params: {
|
||||
fallbackSpec?: string;
|
||||
fallbackLabel?: string;
|
||||
} {
|
||||
const recordSpec = params.specOverride ?? params.record.spec;
|
||||
const recordSpec = params.specOverride ?? params.officialSpecOverride ?? params.record.spec;
|
||||
if (!recordSpec) {
|
||||
return {};
|
||||
}
|
||||
@@ -567,6 +603,7 @@ function resolveNpmUpdateSpecs(params: {
|
||||
|
||||
function resolveClawHubUpdateSpecs(params: {
|
||||
record: PluginInstallRecord;
|
||||
officialSpecOverride?: string;
|
||||
updateChannel?: UpdateChannel;
|
||||
}): {
|
||||
installSpec?: string;
|
||||
@@ -574,10 +611,11 @@ function resolveClawHubUpdateSpecs(params: {
|
||||
fallbackSpec?: string;
|
||||
fallbackLabel?: string;
|
||||
} {
|
||||
if (!params.record.clawhubPackage) {
|
||||
if (!params.officialSpecOverride && !params.record.clawhubPackage) {
|
||||
return {};
|
||||
}
|
||||
const recordSpec = params.record.spec ?? `clawhub:${params.record.clawhubPackage}`;
|
||||
const recordSpec =
|
||||
params.officialSpecOverride ?? params.record.spec ?? `clawhub:${params.record.clawhubPackage}`;
|
||||
return resolveClawHubInstallSpecsForUpdateChannel({
|
||||
spec: recordSpec,
|
||||
updateChannel: params.updateChannel,
|
||||
@@ -726,6 +764,7 @@ export async function updateNpmInstalledPlugins(params: {
|
||||
pluginIds?: string[];
|
||||
skipIds?: Set<string>;
|
||||
skipDisabledPlugins?: boolean;
|
||||
syncOfficialPluginInstalls?: boolean;
|
||||
disableOnFailure?: boolean;
|
||||
timeoutMs?: number;
|
||||
dryRun?: boolean;
|
||||
@@ -787,6 +826,13 @@ export async function updateNpmInstalledPlugins(params: {
|
||||
continue;
|
||||
}
|
||||
|
||||
const officialNpmSpec = params.syncOfficialPluginInstalls
|
||||
? resolveTrustedSourceLinkedOfficialNpmSpec({ pluginId, record })
|
||||
: undefined;
|
||||
const officialClawHubSpec = params.syncOfficialPluginInstalls
|
||||
? resolveTrustedSourceLinkedOfficialClawHubSpec({ pluginId, record })
|
||||
: undefined;
|
||||
|
||||
if (normalizedPluginConfig) {
|
||||
const enableState = resolveEffectiveEnableState({
|
||||
id: pluginId,
|
||||
@@ -794,7 +840,7 @@ export async function updateNpmInstalledPlugins(params: {
|
||||
config: normalizedPluginConfig,
|
||||
rootConfig: params.config,
|
||||
});
|
||||
if (!enableState.enabled) {
|
||||
if (!enableState.enabled && !officialNpmSpec && !officialClawHubSpec) {
|
||||
outcomes.push({
|
||||
pluginId,
|
||||
status: "skipped",
|
||||
@@ -823,6 +869,7 @@ export async function updateNpmInstalledPlugins(params: {
|
||||
? resolveNpmUpdateSpecs({
|
||||
record,
|
||||
specOverride: params.specOverrides?.[pluginId],
|
||||
officialSpecOverride: officialNpmSpec,
|
||||
updateChannel: params.updateChannel,
|
||||
})
|
||||
: undefined;
|
||||
@@ -830,6 +877,7 @@ export async function updateNpmInstalledPlugins(params: {
|
||||
record.source === "clawhub"
|
||||
? resolveClawHubUpdateSpecs({
|
||||
record,
|
||||
officialSpecOverride: officialClawHubSpec,
|
||||
updateChannel: params.updateChannel,
|
||||
})
|
||||
: undefined;
|
||||
@@ -877,7 +925,7 @@ export async function updateNpmInstalledPlugins(params: {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (record.source === "clawhub" && !record.clawhubPackage) {
|
||||
if (record.source === "clawhub" && !record.clawhubPackage && !officialClawHubSpec) {
|
||||
outcomes.push({
|
||||
pluginId,
|
||||
status: "skipped",
|
||||
|
||||
Reference in New Issue
Block a user