@@ -9,12 +9,15 @@ import {
99 resolveBundledPluginGeneratedPath ,
1010 resolveBundledPluginRepoEntryPath ,
1111} from "./bundled-plugin-metadata.js" ;
12+ import { resolveGatewayStartupPluginIdsFromRegistry } from "./gateway-startup-plugin-ids.js" ;
1213import {
1314 createGeneratedPluginTempRoot ,
1415 installGeneratedPluginTempRootCleanup ,
1516 pluginTestRepoRoot as repoRoot ,
1617 writeJson ,
1718} from "./generated-plugin-test-helpers.js" ;
19+ import type { InstalledPluginIndex , InstalledPluginIndexRecord } from "./installed-plugin-index.js" ;
20+ import type { PluginManifestRecord , PluginManifestRegistry } from "./manifest-registry.js" ;
1821import {
1922 getPackageManifestMetadata ,
2023 loadPluginManifest ,
@@ -45,6 +48,15 @@ const EXPECTED_BUNDLED_STARTUP_PLUGIN_IDS = [
4548 "voice-call" ,
4649 "webhooks" ,
4750] as const ;
51+ const EXPECTED_EMPTY_CONFIG_GATEWAY_STARTUP_PLUGIN_IDS = [
52+ "acpx" ,
53+ "bonjour" ,
54+ "browser" ,
55+ "device-pair" ,
56+ "memory-core" ,
57+ "phone-control" ,
58+ "talk-voice" ,
59+ ] as const ;
4860
4961installGeneratedPluginTempRootCleanup ( ) ;
5062
@@ -140,6 +152,51 @@ function collectRepoBundledChannelConfigsForTest(dirName: string) {
140152 } ) ;
141153}
142154
155+ function hasPluginKind ( record : PluginManifestRecord , kind : string ) : boolean {
156+ return Array . isArray ( record . kind ) ? record . kind . includes ( kind as never ) : record . kind === kind ;
157+ }
158+
159+ function createInstalledPluginRecordForManifest (
160+ record : PluginManifestRecord ,
161+ ) : InstalledPluginIndexRecord {
162+ return {
163+ pluginId : record . id ,
164+ manifestPath : record . manifestPath ,
165+ manifestHash : `test-${ record . id } ` ,
166+ source : record . source ,
167+ rootDir : record . rootDir ,
168+ origin : record . origin ,
169+ enabled : record . enabledByDefault === true ,
170+ ...( record . enabledByDefault === true ? { enabledByDefault : true } : { } ) ,
171+ startup : {
172+ sidecar : record . activation ?. onStartup === true ,
173+ memory : hasPluginKind ( record , "memory" ) ,
174+ deferConfiguredChannelFullLoadUntilAfterListen :
175+ record . startupDeferConfiguredChannelFullLoadUntilAfterListen === true ,
176+ agentHarnesses : [
177+ ...new Set ( [ ...( record . activation ?. onAgentHarnesses ?? [ ] ) , ...record . cliBackends ] ) ,
178+ ] . toSorted ( ( left , right ) => left . localeCompare ( right ) ) ,
179+ } ,
180+ compat : [ ] ,
181+ } ;
182+ }
183+
184+ function createInstalledPluginIndexForManifests (
185+ manifestRegistry : PluginManifestRegistry ,
186+ ) : InstalledPluginIndex {
187+ return {
188+ version : 1 ,
189+ hostContractVersion : "test" ,
190+ compatRegistryVersion : "test" ,
191+ migrationVersion : 1 ,
192+ policyHash : "test" ,
193+ generatedAtMs : 0 ,
194+ installRecords : { } ,
195+ plugins : manifestRegistry . plugins . map ( createInstalledPluginRecordForManifest ) ,
196+ diagnostics : [ ] ,
197+ } ;
198+ }
199+
143200describe ( "bundled plugin metadata" , ( ) => {
144201 it (
145202 "matches the runtime metadata snapshot" ,
@@ -323,6 +380,44 @@ describe("bundled plugin metadata", () => {
323380 ) ;
324381 } ) ;
325382
383+ it ( "keeps empty-config Gateway startup narrower than declared startup sidecars" , ( ) => {
384+ const manifestRegistry = {
385+ plugins : listRepoBundledPluginManifests ( ) . map ( ( { manifest, dirName } ) => ( {
386+ id : manifest . id ,
387+ name : manifest . name ,
388+ description : manifest . description ,
389+ version : manifest . version ,
390+ enabledByDefault : manifest . enabledByDefault === true ? true : undefined ,
391+ kind : manifest . kind ,
392+ channels : manifest . channels ?? [ ] ,
393+ providers : manifest . providers ?? [ ] ,
394+ cliBackends : manifest . cliBackends ?? [ ] ,
395+ syntheticAuthRefs : manifest . syntheticAuthRefs ?? [ ] ,
396+ nonSecretAuthMarkers : manifest . nonSecretAuthMarkers ?? [ ] ,
397+ skills : manifest . skills ?? [ ] ,
398+ origin : "bundled" ,
399+ rootDir : path . join ( repoRoot , "extensions" , dirName ) ,
400+ source : path . join ( repoRoot , "extensions" , dirName , "index.ts" ) ,
401+ manifestPath : path . join ( repoRoot , "extensions" , dirName , "openclaw.plugin.json" ) ,
402+ activation : manifest . activation ,
403+ setup : manifest . setup ,
404+ hooks : [ ] ,
405+ contracts : manifest . contracts ,
406+ } ) ) ,
407+ diagnostics : [ ] ,
408+ } satisfies PluginManifestRegistry ;
409+ const index = createInstalledPluginIndexForManifests ( manifestRegistry ) ;
410+
411+ expect (
412+ resolveGatewayStartupPluginIdsFromRegistry ( {
413+ config : { } ,
414+ env : process . env ,
415+ index,
416+ manifestRegistry,
417+ } ) ,
418+ ) . toEqual ( EXPECTED_EMPTY_CONFIG_GATEWAY_STARTUP_PLUGIN_IDS ) ;
419+ } ) ;
420+
326421 it ( "prefers built generated paths when present and falls back to source paths" , ( ) => {
327422 const tempRoot = createGeneratedPluginTempRoot ( "openclaw-bundled-plugin-metadata-" ) ;
328423 const pluginRoot = path . join ( tempRoot , "extensions" , "plugin" ) ;
0 commit comments