diff --git a/CHANGELOG.md b/CHANGELOG.md index ed0bdea4c3b..4f6f7639d58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Docs: https://docs.openclaw.ai - Plugins/models: wire manifest `modelCatalog.aliases` and `modelCatalog.suppressions` into model-catalog planning and built-in model suppression, with stale Spark and Qwen Coding Plan suppressions now declared in plugin manifests instead of runtime fallback hooks. Thanks @shakkernerd. - Channels/Yuanbao: register the Tencent Yuanbao external channel plugin (`openclaw-plugin-yuanbao`) in the official channel catalog, contract suites, and community plugin docs, with a new `docs/channels/yuanbao.md` quick-start guide for WebSocket bot DMs and group chats. (#72756) Thanks @loongfay. - Channels/QQBot: add full group chat support (history tracking, @-mention gating, activation modes, per-group config, FIFO message queue with deliver debounce), C2C `stream_messages` streaming with a `StreamingController` lifecycle manager, unified `sendMedia` with chunked upload for large files, and refactor the engine into pipeline stages, focused outbound submodules, builtin slash-command modules, and explicit DI ports via `createEngineAdapters()`. (#70624) Thanks @cxyhhhhh. +- Plugins/startup: migrate bundled plugin manifests to explicit `activation.onStartup` declarations so Gateway startup imports only the bundled plugins that intentionally register startup-time runtime surfaces. Thanks @shakkernerd. - Plugins/startup: add explicit `activation.onStartup` metadata so plugins can declare Gateway startup import behavior while the deprecated implicit sidecar fallback remains for legacy plugins. Thanks @shakkernerd. - Gateway/startup: reuse lookup-table plugin manifests when loading startup plugins so Gateway boot avoids rebuilding plugin discovery and manifest metadata. Thanks @shakkernerd. - CLI/models: declare fixed Qianfan, Xiaomi, NVIDIA, Cerebras, and Mistral model catalogs in plugin manifests so provider-filtered model listing can use the manifest fast path without loading provider runtime catalog code. Thanks @shakkernerd. diff --git a/src/plugins/bundled-plugin-metadata.test.ts b/src/plugins/bundled-plugin-metadata.test.ts index 58e5b71832f..9b06783794d 100644 --- a/src/plugins/bundled-plugin-metadata.test.ts +++ b/src/plugins/bundled-plugin-metadata.test.ts @@ -24,6 +24,26 @@ import { collectBundledRuntimeSidecarPaths } from "./runtime-sidecar-paths-basel import { BUNDLED_RUNTIME_SIDECAR_PATHS } from "./runtime-sidecar-paths.js"; const BUNDLED_PLUGIN_METADATA_TEST_TIMEOUT_MS = 300_000; +const EXPECTED_BUNDLED_STARTUP_PLUGIN_IDS = [ + "acpx", + "active-memory", + "bonjour", + "browser", + "device-pair", + "diagnostics-otel", + "diagnostics-prometheus", + "diffs", + "google-meet", + "llm-task", + "lobster", + "memory-wiki", + "openshell", + "phone-control", + "talk-voice", + "thread-ownership", + "voice-call", + "webhooks", +] as const; installGeneratedPluginTempRootCleanup(); @@ -88,6 +108,22 @@ function listRepoBundledPluginMetadata(): readonly BundledPluginMetadata[] { }); } +function listRepoBundledPluginManifests() { + const bundledPluginsDir = path.join(repoRoot, "extensions"); + return fs + .readdirSync(bundledPluginsDir, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => ({ + dirName: entry.name, + manifest: loadPluginManifest(path.join(bundledPluginsDir, entry.name), false), + })) + .filter((entry) => entry.manifest.ok) + .map((entry) => ({ + dirName: entry.dirName, + manifest: entry.manifest.manifest, + })); +} + function readPackageManifest(pluginDir: string): PackageManifest | undefined { const packagePath = path.join(pluginDir, "package.json"); return fs.existsSync(packagePath) @@ -276,6 +312,21 @@ describe("bundled plugin metadata", () => { } }); + it("declares explicit startup activation on all bundled plugin manifests", () => { + const startupPluginIds: string[] = []; + + for (const entry of listRepoBundledPluginManifests()) { + expect(typeof entry.manifest.activation?.onStartup).toBe("boolean"); + if (entry.manifest.activation?.onStartup === true) { + startupPluginIds.push(entry.manifest.id); + } + } + + expect(startupPluginIds.toSorted((left, right) => left.localeCompare(right))).toEqual( + EXPECTED_BUNDLED_STARTUP_PLUGIN_IDS, + ); + }); + it("prefers built generated paths when present and falls back to source paths", () => { const tempRoot = createGeneratedPluginTempRoot("openclaw-bundled-plugin-metadata-"); const pluginRoot = path.join(tempRoot, "extensions", "plugin");