fix(release): repair packaged plugin startup metadata

This commit is contained in:
Peter Steinberger
2026-05-01 19:44:04 +01:00
parent 068b33de87
commit f6fea7770d
9 changed files with 135 additions and 24 deletions

View File

@@ -1757,6 +1757,7 @@
"@mariozechner/pi-coding-agent",
"@modelcontextprotocol/sdk",
"ajv",
"chalk",
"chokidar",
"commander",
"croner",

View File

@@ -289,6 +289,13 @@ describe("prepareGatewayPluginBootstrap runtime-deps staging", () => {
exactPluginIds: ["telegram"],
}),
);
expect(resolveOpenClawPackageRootSync).toHaveBeenCalledWith(
expect.objectContaining({
moduleUrl: expect.stringContaining("server-startup-plugins"),
argv1: process.argv[1],
cwd: process.cwd(),
}),
);
expect(prepareBundledPluginRuntimeLoadRoot).toHaveBeenCalledWith(
expect.objectContaining({
pluginId: "telegram",

View File

@@ -75,7 +75,11 @@ async function prestageGatewayBundledRuntimeDepsImpl(params: {
return {};
}
let repairError: unknown;
const packageRoot = resolveOpenClawPackageRootSync({ moduleUrl: import.meta.url });
const packageRoot = resolveOpenClawPackageRootSync({
moduleUrl: import.meta.url,
argv1: process.argv[1],
cwd: process.cwd(),
});
if (packageRoot) {
try {
pruneUnknownBundledRuntimeDepsRoots({

View File

@@ -29,4 +29,42 @@ describe("plugin loader records", () => {
expect(record.providerIds).toEqual(["kitchen-sink-provider"]);
});
it("preserves manifest-declared capability provider ids before runtime registration", () => {
const record = createPluginRecord({
id: "kitchen-sink",
name: "Kitchen Sink",
source: "/tmp/kitchen-sink/index.js",
origin: "global",
enabled: true,
contracts: {
speechProviders: ["kitchen-sink-speech-provider"],
realtimeTranscriptionProviders: ["kitchen-sink-transcription-provider"],
realtimeVoiceProviders: ["kitchen-sink-voice-provider"],
mediaUnderstandingProviders: ["kitchen-sink-media-provider"],
imageGenerationProviders: ["kitchen-sink-image-provider"],
videoGenerationProviders: ["kitchen-sink-video-provider"],
musicGenerationProviders: ["kitchen-sink-music-provider"],
webFetchProviders: ["kitchen-sink-web-fetch-provider"],
webSearchProviders: ["kitchen-sink-web-search-provider"],
migrationProviders: ["kitchen-sink-migration-provider"],
memoryEmbeddingProviders: ["kitchen-sink-memory-provider"],
},
configSchema: false,
});
expect(record.speechProviderIds).toEqual(["kitchen-sink-speech-provider"]);
expect(record.realtimeTranscriptionProviderIds).toEqual([
"kitchen-sink-transcription-provider",
]);
expect(record.realtimeVoiceProviderIds).toEqual(["kitchen-sink-voice-provider"]);
expect(record.mediaUnderstandingProviderIds).toEqual(["kitchen-sink-media-provider"]);
expect(record.imageGenerationProviderIds).toEqual(["kitchen-sink-image-provider"]);
expect(record.videoGenerationProviderIds).toEqual(["kitchen-sink-video-provider"]);
expect(record.musicGenerationProviderIds).toEqual(["kitchen-sink-music-provider"]);
expect(record.webFetchProviderIds).toEqual(["kitchen-sink-web-fetch-provider"]);
expect(record.webSearchProviderIds).toEqual(["kitchen-sink-web-search-provider"]);
expect(record.migrationProviderIds).toEqual(["kitchen-sink-migration-provider"]);
expect(record.memoryEmbeddingProviderIds).toEqual(["kitchen-sink-memory-provider"]);
});
});

View File

@@ -52,18 +52,18 @@ export function createPluginRecord(params: {
channelIds: [...(params.channelIds ?? [])],
cliBackendIds: [],
providerIds: [...(params.providerIds ?? [])],
speechProviderIds: [],
realtimeTranscriptionProviderIds: [],
realtimeVoiceProviderIds: [],
mediaUnderstandingProviderIds: [],
imageGenerationProviderIds: [],
videoGenerationProviderIds: [],
musicGenerationProviderIds: [],
webFetchProviderIds: [],
webSearchProviderIds: [],
migrationProviderIds: [],
speechProviderIds: [...(params.contracts?.speechProviders ?? [])],
realtimeTranscriptionProviderIds: [...(params.contracts?.realtimeTranscriptionProviders ?? [])],
realtimeVoiceProviderIds: [...(params.contracts?.realtimeVoiceProviders ?? [])],
mediaUnderstandingProviderIds: [...(params.contracts?.mediaUnderstandingProviders ?? [])],
imageGenerationProviderIds: [...(params.contracts?.imageGenerationProviders ?? [])],
videoGenerationProviderIds: [...(params.contracts?.videoGenerationProviders ?? [])],
musicGenerationProviderIds: [...(params.contracts?.musicGenerationProviders ?? [])],
webFetchProviderIds: [...(params.contracts?.webFetchProviders ?? [])],
webSearchProviderIds: [...(params.contracts?.webSearchProviders ?? [])],
migrationProviderIds: [...(params.contracts?.migrationProviders ?? [])],
contextEngineIds: [],
memoryEmbeddingProviderIds: [],
memoryEmbeddingProviderIds: [...(params.contracts?.memoryEmbeddingProviders ?? [])],
agentHarnessIds: [],
gatewayMethods: [],
cliCommands: [],

View File

@@ -0,0 +1,49 @@
import { describe, expect, it } from "vitest";
import { createPluginRecord } from "./loader-records.js";
import { createPluginRegistry } from "./registry.js";
import type { PluginRuntime } from "./runtime/types.js";
function createTestRegistry() {
return createPluginRegistry({
logger: {
info() {},
warn() {},
error() {},
debug() {},
},
runtime: {} as PluginRuntime,
activateGlobalSideEffects: false,
});
}
describe("plugin registry provider-like registrations", () => {
it("does not duplicate manifest-declared capability provider ids during runtime registration", () => {
const pluginRegistry = createTestRegistry();
const record = createPluginRecord({
id: "kitchen-sink",
name: "Kitchen Sink",
source: "/tmp/kitchen-sink/index.js",
origin: "global",
enabled: true,
contracts: {
speechProviders: ["kitchen-sink-speech-provider"],
},
configSchema: false,
});
pluginRegistry.registerSpeechProvider(record, {
id: "kitchen-sink-speech-provider",
label: "Kitchen Sink Speech",
isConfigured: () => true,
synthesize: async () => ({
audioBuffer: Buffer.alloc(0),
fileExtension: "mp3",
outputFormat: "audio/mpeg",
voiceCompatible: true,
}),
});
expect(record.speechProviderIds).toEqual(["kitchen-sink-speech-provider"]);
expect(pluginRegistry.registry.speechProviders).toHaveLength(1);
});
});

View File

@@ -970,7 +970,9 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
});
return;
}
params.ownedIds.push(id);
if (!params.ownedIds.includes(id)) {
params.ownedIds.push(id);
}
params.registrations.push({
pluginId: record.id,
pluginName: record.name,

View File

@@ -33,6 +33,11 @@ describe("buildPluginRegistrySnapshotReport", () => {
description: "Manifest-backed list metadata",
version: "1.2.3",
providers: ["indexed-provider"],
contracts: {
speechProviders: ["indexed-speech-provider"],
realtimeTranscriptionProviders: ["indexed-transcription-provider"],
realtimeVoiceProviders: ["indexed-voice-provider"],
},
commandAliases: [{ name: "indexed-demo" }],
configSchema: {
type: "object",
@@ -58,6 +63,9 @@ describe("buildPluginRegistrySnapshotReport", () => {
version: "9.8.7",
format: "openclaw",
providerIds: ["indexed-provider"],
speechProviderIds: ["indexed-speech-provider"],
realtimeTranscriptionProviderIds: ["indexed-transcription-provider"],
realtimeVoiceProviderIds: ["indexed-voice-provider"],
commands: ["indexed-demo"],
source: fs.realpathSync(fixture.runtimeSource),
status: "loaded",

View File

@@ -200,17 +200,19 @@ function buildPluginRecordFromInstalledIndex(
channelIds: [...(manifest?.channels ?? [])],
cliBackendIds: [...(manifest?.cliBackends ?? []), ...(manifest?.setup?.cliBackends ?? [])],
providerIds: [...(manifest?.providers ?? [])],
speechProviderIds: [],
realtimeTranscriptionProviderIds: [],
realtimeVoiceProviderIds: [],
mediaUnderstandingProviderIds: [],
imageGenerationProviderIds: [],
videoGenerationProviderIds: [],
musicGenerationProviderIds: [],
webFetchProviderIds: [],
webSearchProviderIds: [],
migrationProviderIds: [],
memoryEmbeddingProviderIds: [],
speechProviderIds: [...(manifest?.contracts?.speechProviders ?? [])],
realtimeTranscriptionProviderIds: [
...(manifest?.contracts?.realtimeTranscriptionProviders ?? []),
],
realtimeVoiceProviderIds: [...(manifest?.contracts?.realtimeVoiceProviders ?? [])],
mediaUnderstandingProviderIds: [...(manifest?.contracts?.mediaUnderstandingProviders ?? [])],
imageGenerationProviderIds: [...(manifest?.contracts?.imageGenerationProviders ?? [])],
videoGenerationProviderIds: [...(manifest?.contracts?.videoGenerationProviders ?? [])],
musicGenerationProviderIds: [...(manifest?.contracts?.musicGenerationProviders ?? [])],
webFetchProviderIds: [...(manifest?.contracts?.webFetchProviders ?? [])],
webSearchProviderIds: [...(manifest?.contracts?.webSearchProviders ?? [])],
migrationProviderIds: [...(manifest?.contracts?.migrationProviders ?? [])],
memoryEmbeddingProviderIds: [...(manifest?.contracts?.memoryEmbeddingProviders ?? [])],
agentHarnessIds: [],
gatewayMethods: [],
cliCommands: [],