2026-03-12 15:31:31 +00:00
import { beforeEach , describe , expect , it , vi } from "vitest" ;
const loadConfigMock = vi . fn ( ) ;
const loadOpenClawPluginsMock = vi . fn ( ) ;
2026-03-17 07:47:17 +00:00
let buildPluginStatusReport : typeof import ( "./status.js" ) . buildPluginStatusReport ;
2026-03-17 10:15:22 -07:00
let buildPluginInspectReport : typeof import ( "./status.js" ) . buildPluginInspectReport ;
2026-03-17 10:33:35 -07:00
let buildAllPluginInspectReports : typeof import ( "./status.js" ) . buildAllPluginInspectReports ;
2026-03-17 19:58:40 -07:00
let buildPluginCompatibilityNotices : typeof import ( "./status.js" ) . buildPluginCompatibilityNotices ;
let buildPluginCompatibilityWarnings : typeof import ( "./status.js" ) . buildPluginCompatibilityWarnings ;
2026-03-17 20:31:59 -07:00
let formatPluginCompatibilityNotice : typeof import ( "./status.js" ) . formatPluginCompatibilityNotice ;
let summarizePluginCompatibility : typeof import ( "./status.js" ) . summarizePluginCompatibility ;
2026-03-12 15:31:31 +00:00
vi . mock ( "../config/config.js" , ( ) = > ( {
loadConfig : ( ) = > loadConfigMock ( ) ,
} ) ) ;
vi . mock ( "./loader.js" , ( ) = > ( {
loadOpenClawPlugins : ( . . . args : unknown [ ] ) = > loadOpenClawPluginsMock ( . . . args ) ,
} ) ) ;
vi . mock ( "../agents/agent-scope.js" , ( ) = > ( {
resolveAgentWorkspaceDir : ( ) = > undefined ,
resolveDefaultAgentId : ( ) = > "default" ,
} ) ) ;
vi . mock ( "../agents/workspace.js" , ( ) = > ( {
resolveDefaultAgentWorkspaceDir : ( ) = > "/default-workspace" ,
} ) ) ;
describe ( "buildPluginStatusReport" , ( ) = > {
2026-03-17 07:47:17 +00:00
beforeEach ( async ( ) = > {
vi . resetModules ( ) ;
2026-03-12 15:31:31 +00:00
loadConfigMock . mockReset ( ) ;
loadOpenClawPluginsMock . mockReset ( ) ;
loadConfigMock . mockReturnValue ( { } ) ;
loadOpenClawPluginsMock . mockReturnValue ( {
plugins : [ ] ,
diagnostics : [ ] ,
channels : [ ] ,
providers : [ ] ,
2026-03-17 10:15:22 -07:00
speechProviders : [ ] ,
mediaUnderstandingProviders : [ ] ,
imageGenerationProviders : [ ] ,
webSearchProviders : [ ] ,
2026-03-12 15:31:31 +00:00
tools : [ ] ,
hooks : [ ] ,
2026-03-17 10:15:22 -07:00
typedHooks : [ ] ,
channelSetups : [ ] ,
httpRoutes : [ ] ,
2026-03-12 15:31:31 +00:00
gatewayHandlers : { } ,
cliRegistrars : [ ] ,
services : [ ] ,
commands : [ ] ,
} ) ;
2026-03-17 19:58:40 -07:00
( {
buildAllPluginInspectReports ,
buildPluginCompatibilityNotices ,
buildPluginCompatibilityWarnings ,
buildPluginInspectReport ,
buildPluginStatusReport ,
2026-03-17 20:31:59 -07:00
formatPluginCompatibilityNotice ,
summarizePluginCompatibility ,
2026-03-17 19:58:40 -07:00
} = await import ( "./status.js" ) ) ;
2026-03-12 15:31:31 +00:00
} ) ;
it ( "forwards an explicit env to plugin loading" , ( ) = > {
const env = { HOME : "/tmp/openclaw-home" } as NodeJS . ProcessEnv ;
buildPluginStatusReport ( {
config : { } ,
workspaceDir : "/workspace" ,
env ,
} ) ;
expect ( loadOpenClawPluginsMock ) . toHaveBeenCalledWith (
expect . objectContaining ( {
config : { } ,
workspaceDir : "/workspace" ,
env ,
} ) ,
) ;
} ) ;
2026-03-17 10:15:22 -07:00
it ( "builds an inspect report with capability shape and policy" , ( ) = > {
loadConfigMock . mockReturnValue ( {
plugins : {
entries : {
google : {
hooks : { allowPromptInjection : false } ,
subagent : {
allowModelOverride : true ,
allowedModels : [ "openai/gpt-5.4" ] ,
} ,
} ,
} ,
} ,
} ) ;
loadOpenClawPluginsMock . mockReturnValue ( {
plugins : [
{
id : "google" ,
name : "Google" ,
description : "Google provider plugin" ,
source : "/tmp/google/index.ts" ,
origin : "bundled" ,
enabled : true ,
status : "loaded" ,
toolNames : [ ] ,
hookNames : [ ] ,
channelIds : [ ] ,
providerIds : [ "google" ] ,
speechProviderIds : [ ] ,
mediaUnderstandingProviderIds : [ "google" ] ,
imageGenerationProviderIds : [ "google" ] ,
webSearchProviderIds : [ "google" ] ,
gatewayMethods : [ ] ,
cliCommands : [ ] ,
services : [ ] ,
commands : [ ] ,
httpRoutes : 0 ,
hookCount : 0 ,
configSchema : false ,
} ,
] ,
diagnostics : [ { level : "warn" , pluginId : "google" , message : "watch this seam" } ] ,
channels : [ ] ,
channelSetups : [ ] ,
providers : [ ] ,
speechProviders : [ ] ,
mediaUnderstandingProviders : [ ] ,
imageGenerationProviders : [ ] ,
webSearchProviders : [ ] ,
tools : [ ] ,
hooks : [ ] ,
typedHooks : [
{
pluginId : "google" ,
hookName : "before_agent_start" ,
handler : ( ) = > undefined ,
source : "/tmp/google/index.ts" ,
} ,
] ,
httpRoutes : [ ] ,
gatewayHandlers : { } ,
cliRegistrars : [ ] ,
services : [ ] ,
commands : [ ] ,
} ) ;
const inspect = buildPluginInspectReport ( { id : "google" } ) ;
expect ( inspect ) . not . toBeNull ( ) ;
expect ( inspect ? . shape ) . toBe ( "hybrid-capability" ) ;
expect ( inspect ? . capabilityMode ) . toBe ( "hybrid" ) ;
expect ( inspect ? . capabilities . map ( ( entry ) = > entry . kind ) ) . toEqual ( [
"text-inference" ,
"media-understanding" ,
"image-generation" ,
"web-search" ,
] ) ;
expect ( inspect ? . usesLegacyBeforeAgentStart ) . toBe ( true ) ;
2026-03-17 19:58:40 -07:00
expect ( inspect ? . compatibility ) . toEqual ( [
{
pluginId : "google" ,
code : "legacy-before-agent-start" ,
severity : "warn" ,
message :
"still relies on legacy before_agent_start; keep upgrade coverage on this plugin and prefer before_model_resolve/before_prompt_build for new work." ,
} ,
] ) ;
2026-03-17 10:15:22 -07:00
expect ( inspect ? . policy ) . toEqual ( {
allowPromptInjection : false ,
allowModelOverride : true ,
allowedModels : [ "openai/gpt-5.4" ] ,
hasAllowedModelsConfig : true ,
} ) ;
expect ( inspect ? . diagnostics ) . toEqual ( [
{ level : "warn" , pluginId : "google" , message : "watch this seam" } ,
] ) ;
} ) ;
2026-03-17 10:33:35 -07:00
it ( "builds inspect reports for every loaded plugin" , ( ) = > {
loadOpenClawPluginsMock . mockReturnValue ( {
plugins : [
{
id : "lca" ,
name : "LCA" ,
description : "Legacy hook plugin" ,
source : "/tmp/lca/index.ts" ,
origin : "workspace" ,
enabled : true ,
status : "loaded" ,
toolNames : [ ] ,
hookNames : [ ] ,
channelIds : [ ] ,
providerIds : [ ] ,
speechProviderIds : [ ] ,
mediaUnderstandingProviderIds : [ ] ,
imageGenerationProviderIds : [ ] ,
webSearchProviderIds : [ ] ,
gatewayMethods : [ ] ,
cliCommands : [ ] ,
services : [ ] ,
commands : [ ] ,
httpRoutes : 0 ,
hookCount : 1 ,
configSchema : false ,
} ,
{
id : "microsoft" ,
name : "Microsoft" ,
description : "Hybrid capability plugin" ,
source : "/tmp/microsoft/index.ts" ,
origin : "bundled" ,
enabled : true ,
status : "loaded" ,
toolNames : [ ] ,
hookNames : [ ] ,
channelIds : [ ] ,
providerIds : [ "microsoft" ] ,
speechProviderIds : [ ] ,
mediaUnderstandingProviderIds : [ ] ,
imageGenerationProviderIds : [ ] ,
webSearchProviderIds : [ "microsoft" ] ,
gatewayMethods : [ ] ,
cliCommands : [ ] ,
services : [ ] ,
commands : [ ] ,
httpRoutes : 0 ,
hookCount : 0 ,
configSchema : false ,
} ,
] ,
diagnostics : [ ] ,
channels : [ ] ,
channelSetups : [ ] ,
providers : [ ] ,
speechProviders : [ ] ,
mediaUnderstandingProviders : [ ] ,
imageGenerationProviders : [ ] ,
webSearchProviders : [ ] ,
tools : [ ] ,
hooks : [
{
pluginId : "lca" ,
events : [ "message" ] ,
entry : {
hook : {
name : "legacy" ,
handler : ( ) = > undefined ,
} ,
} ,
} ,
] ,
typedHooks : [
{
pluginId : "lca" ,
hookName : "before_agent_start" ,
handler : ( ) = > undefined ,
source : "/tmp/lca/index.ts" ,
} ,
] ,
httpRoutes : [ ] ,
gatewayHandlers : { } ,
cliRegistrars : [ ] ,
services : [ ] ,
commands : [ ] ,
} ) ;
const inspect = buildAllPluginInspectReports ( ) ;
expect ( inspect . map ( ( entry ) = > entry . plugin . id ) ) . toEqual ( [ "lca" , "microsoft" ] ) ;
expect ( inspect . map ( ( entry ) = > entry . shape ) ) . toEqual ( [ "hook-only" , "hybrid-capability" ] ) ;
expect ( inspect [ 0 ] ? . usesLegacyBeforeAgentStart ) . toBe ( true ) ;
expect ( inspect [ 1 ] ? . capabilities . map ( ( entry ) = > entry . kind ) ) . toEqual ( [
"text-inference" ,
"web-search" ,
] ) ;
} ) ;
2026-03-17 19:58:40 -07:00
it ( "builds compatibility warnings for legacy compatibility paths" , ( ) = > {
loadOpenClawPluginsMock . mockReturnValue ( {
plugins : [
{
id : "lca" ,
name : "LCA" ,
description : "Legacy hook plugin" ,
source : "/tmp/lca/index.ts" ,
origin : "workspace" ,
enabled : true ,
status : "loaded" ,
toolNames : [ ] ,
hookNames : [ ] ,
channelIds : [ ] ,
providerIds : [ ] ,
speechProviderIds : [ ] ,
mediaUnderstandingProviderIds : [ ] ,
imageGenerationProviderIds : [ ] ,
webSearchProviderIds : [ ] ,
gatewayMethods : [ ] ,
cliCommands : [ ] ,
services : [ ] ,
commands : [ ] ,
httpRoutes : 0 ,
hookCount : 1 ,
configSchema : false ,
} ,
] ,
diagnostics : [ ] ,
channels : [ ] ,
channelSetups : [ ] ,
providers : [ ] ,
speechProviders : [ ] ,
mediaUnderstandingProviders : [ ] ,
imageGenerationProviders : [ ] ,
webSearchProviders : [ ] ,
tools : [ ] ,
hooks : [ ] ,
typedHooks : [
{
pluginId : "lca" ,
hookName : "before_agent_start" ,
handler : ( ) = > undefined ,
source : "/tmp/lca/index.ts" ,
} ,
] ,
httpRoutes : [ ] ,
gatewayHandlers : { } ,
cliRegistrars : [ ] ,
services : [ ] ,
commands : [ ] ,
} ) ;
expect ( buildPluginCompatibilityWarnings ( ) ) . toEqual ( [
"lca still relies on legacy before_agent_start; keep upgrade coverage on this plugin and prefer before_model_resolve/before_prompt_build for new work." ,
"lca is hook-only; this remains supported for compatibility, but it has not migrated to explicit capability registration." ,
] ) ;
} ) ;
it ( "builds structured compatibility notices with deterministic ordering" , ( ) = > {
loadOpenClawPluginsMock . mockReturnValue ( {
plugins : [
{
id : "hook-only" ,
name : "Hook Only" ,
description : "" ,
source : "/tmp/hook-only/index.ts" ,
origin : "workspace" ,
enabled : true ,
status : "loaded" ,
toolNames : [ ] ,
hookNames : [ ] ,
channelIds : [ ] ,
providerIds : [ ] ,
speechProviderIds : [ ] ,
mediaUnderstandingProviderIds : [ ] ,
imageGenerationProviderIds : [ ] ,
webSearchProviderIds : [ ] ,
gatewayMethods : [ ] ,
cliCommands : [ ] ,
services : [ ] ,
commands : [ ] ,
httpRoutes : 0 ,
hookCount : 1 ,
configSchema : false ,
} ,
{
id : "legacy-only" ,
name : "Legacy Only" ,
description : "" ,
source : "/tmp/legacy-only/index.ts" ,
origin : "workspace" ,
enabled : true ,
status : "loaded" ,
toolNames : [ ] ,
hookNames : [ ] ,
channelIds : [ ] ,
providerIds : [ "legacy-only" ] ,
speechProviderIds : [ ] ,
mediaUnderstandingProviderIds : [ ] ,
imageGenerationProviderIds : [ ] ,
webSearchProviderIds : [ ] ,
gatewayMethods : [ ] ,
cliCommands : [ ] ,
services : [ ] ,
commands : [ ] ,
httpRoutes : 0 ,
hookCount : 1 ,
configSchema : false ,
} ,
] ,
diagnostics : [ ] ,
channels : [ ] ,
channelSetups : [ ] ,
providers : [ ] ,
speechProviders : [ ] ,
mediaUnderstandingProviders : [ ] ,
imageGenerationProviders : [ ] ,
webSearchProviders : [ ] ,
tools : [ ] ,
hooks : [
{
pluginId : "hook-only" ,
events : [ "message" ] ,
entry : {
hook : {
name : "legacy" ,
handler : ( ) = > undefined ,
} ,
} ,
} ,
] ,
typedHooks : [
{
pluginId : "legacy-only" ,
hookName : "before_agent_start" ,
handler : ( ) = > undefined ,
source : "/tmp/legacy-only/index.ts" ,
} ,
] ,
httpRoutes : [ ] ,
gatewayHandlers : { } ,
cliRegistrars : [ ] ,
services : [ ] ,
commands : [ ] ,
} ) ;
expect ( buildPluginCompatibilityNotices ( ) ) . toEqual ( [
{
pluginId : "hook-only" ,
code : "hook-only" ,
severity : "info" ,
message :
"is hook-only; this remains supported for compatibility, but it has not migrated to explicit capability registration." ,
} ,
{
pluginId : "legacy-only" ,
code : "legacy-before-agent-start" ,
severity : "warn" ,
message :
"still relies on legacy before_agent_start; keep upgrade coverage on this plugin and prefer before_model_resolve/before_prompt_build for new work." ,
} ,
] ) ;
} ) ;
it ( "returns no compatibility warnings for modern capability plugins" , ( ) = > {
loadOpenClawPluginsMock . mockReturnValue ( {
plugins : [
{
id : "modern" ,
name : "Modern" ,
description : "" ,
source : "/tmp/modern/index.ts" ,
origin : "workspace" ,
enabled : true ,
status : "loaded" ,
toolNames : [ ] ,
hookNames : [ ] ,
channelIds : [ ] ,
providerIds : [ "modern" ] ,
speechProviderIds : [ ] ,
mediaUnderstandingProviderIds : [ ] ,
imageGenerationProviderIds : [ ] ,
webSearchProviderIds : [ ] ,
gatewayMethods : [ ] ,
cliCommands : [ ] ,
services : [ ] ,
commands : [ ] ,
httpRoutes : 0 ,
hookCount : 0 ,
configSchema : false ,
} ,
] ,
diagnostics : [ ] ,
channels : [ ] ,
channelSetups : [ ] ,
providers : [ ] ,
speechProviders : [ ] ,
mediaUnderstandingProviders : [ ] ,
imageGenerationProviders : [ ] ,
webSearchProviders : [ ] ,
tools : [ ] ,
hooks : [ ] ,
typedHooks : [ ] ,
httpRoutes : [ ] ,
gatewayHandlers : { } ,
cliRegistrars : [ ] ,
services : [ ] ,
commands : [ ] ,
} ) ;
expect ( buildPluginCompatibilityNotices ( ) ) . toEqual ( [ ] ) ;
expect ( buildPluginCompatibilityWarnings ( ) ) . toEqual ( [ ] ) ;
} ) ;
2026-03-17 20:31:59 -07:00
it ( "formats and summarizes compatibility notices" , ( ) = > {
const notice = {
pluginId : "legacy-plugin" ,
code : "legacy-before-agent-start" as const ,
severity : "warn" as const ,
message :
"still relies on legacy before_agent_start; keep upgrade coverage on this plugin and prefer before_model_resolve/before_prompt_build for new work." ,
} ;
expect ( formatPluginCompatibilityNotice ( notice ) ) . toBe (
"legacy-plugin still relies on legacy before_agent_start; keep upgrade coverage on this plugin and prefer before_model_resolve/before_prompt_build for new work." ,
) ;
expect (
summarizePluginCompatibility ( [
notice ,
{
pluginId : "legacy-plugin" ,
code : "hook-only" ,
severity : "info" ,
message :
"is hook-only; this remains supported for compatibility, but it has not migrated to explicit capability registration." ,
} ,
] ) ,
) . toEqual ( {
noticeCount : 2 ,
pluginCount : 1 ,
} ) ;
} ) ;
2026-03-12 15:31:31 +00:00
} ) ;