@@ -1789,6 +1789,13 @@ describe("loadOpenClawPlugins", () => {
17891789 api.registerAgentToolResultMiddleware(() => undefined, {
17901790 runtimes: ["openclaw"],
17911791 });
1792+ api.registerHook(
1793+ "gateway:startup",
1794+ (event) => {
1795+ event.messages.push("rollback-hook-fired");
1796+ },
1797+ { name: "reload-rollback-hook" },
1798+ );
17921799 api.on("gateway_stop", async () => {});
17931800 },
17941801 };` ,
@@ -1811,7 +1818,7 @@ describe("loadOpenClawPlugins", () => {
18111818 } ;
18121819
18131820 const activeRegistry = loadOpenClawPlugins ( loadOptions ) ;
1814- const expectRegistrationsIntact = ( ) => {
1821+ const expectRegistrationsIntact = async ( ) => {
18151822 expect ( getActivePluginRegistry ( ) ) . toBe ( activeRegistry ) ;
18161823 expect ( getRegisteredAgentHarness ( "codex" ) ) . toBeDefined ( ) ;
18171824 expect ( getPluginCommandSpecs ( ) . map ( ( entry ) => entry . name ) ) . toEqual ( [ "pair" ] ) ;
@@ -1820,8 +1827,11 @@ describe("loadOpenClawPlugins", () => {
18201827 ] ) ;
18211828 expect ( activeRegistry . agentToolResultMiddlewares ) . toHaveLength ( 1 ) ;
18221829 expect ( activeRegistry . typedHooks . map ( ( entry ) => entry . hookName ) ) . toEqual ( [ "gateway_stop" ] ) ;
1830+ const event = createInternalHookEvent ( "gateway" , "startup" , "gateway:startup" ) ;
1831+ await triggerInternalHook ( event ) ;
1832+ expect ( event . messages ) . toEqual ( [ "rollback-hook-fired" ] ) ;
18231833 } ;
1824- expectRegistrationsIntact ( ) ;
1834+ await expectRegistrationsIntact ( ) ;
18251835
18261836 const manifestRegistry = await import ( "./manifest-registry.js" ) ;
18271837 const manifestSpy = vi
@@ -1832,7 +1842,7 @@ describe("loadOpenClawPlugins", () => {
18321842
18331843 try {
18341844 expect ( ( ) => loadOpenClawPlugins ( loadOptions ) ) . toThrow ( "corrupt plugin manifest" ) ;
1835- expectRegistrationsIntact ( ) ;
1845+ await expectRegistrationsIntact ( ) ;
18361846 } finally {
18371847 manifestSpy . mockRestore ( ) ;
18381848 }
@@ -1858,7 +1868,7 @@ describe("loadOpenClawPlugins", () => {
18581868 onlyPluginIds : [ "reload-rollback" , "reload-rollback-failure" ] ,
18591869 } ) ,
18601870 ) . toThrow ( "plugin load failed: reload-rollback-failure: Error: register failed" ) ;
1861- expectRegistrationsIntact ( ) ;
1871+ await expectRegistrationsIntact ( ) ;
18621872 } ) ;
18631873
18641874 it ( "rejects malformed plugin agent harness registrations" , ( ) => {
@@ -1980,6 +1990,72 @@ describe("loadOpenClawPlugins", () => {
19801990 clearInternalHooks ( ) ;
19811991 } ) ;
19821992
1993+ it . each ( [ "disabled" , "removed" ] as const ) (
1994+ "clears legacy internal hooks when their plugin is %s" ,
1995+ async ( nextState ) => {
1996+ useNoBundledPlugins ( ) ;
1997+ const plugin = writePlugin ( {
1998+ id : "internal-hook-lifecycle" ,
1999+ filename : "internal-hook-lifecycle.cjs" ,
2000+ body : `module.exports = {
2001+ id: "internal-hook-lifecycle",
2002+ register(api) {
2003+ api.registerHook(
2004+ "gateway:startup",
2005+ (event) => {
2006+ event.messages.push("legacy-hook-fired");
2007+ },
2008+ { name: "legacy-lifecycle-hook" },
2009+ );
2010+ },
2011+ };` ,
2012+ } ) ;
2013+
2014+ clearInternalHooks ( ) ;
2015+ loadOpenClawPlugins ( {
2016+ cache : false ,
2017+ workspaceDir : plugin . dir ,
2018+ config : {
2019+ plugins : {
2020+ load : { paths : [ plugin . file ] } ,
2021+ allow : [ "internal-hook-lifecycle" ] ,
2022+ } ,
2023+ } ,
2024+ } ) ;
2025+
2026+ const activeEvent = createInternalHookEvent ( "gateway" , "startup" , "gateway:startup" ) ;
2027+ await triggerInternalHook ( activeEvent ) ;
2028+ expect ( activeEvent . messages ) . toEqual ( [ "legacy-hook-fired" ] ) ;
2029+
2030+ loadOpenClawPlugins ( {
2031+ cache : false ,
2032+ workspaceDir : plugin . dir ,
2033+ config : {
2034+ plugins :
2035+ nextState === "disabled"
2036+ ? {
2037+ load : { paths : [ plugin . file ] } ,
2038+ allow : [ "internal-hook-lifecycle" ] ,
2039+ entries : {
2040+ "internal-hook-lifecycle" : {
2041+ enabled : false ,
2042+ } ,
2043+ } ,
2044+ }
2045+ : {
2046+ allow : [ ] ,
2047+ } ,
2048+ } ,
2049+ } ) ;
2050+
2051+ const retiredEvent = createInternalHookEvent ( "gateway" , "startup" , "gateway:startup" ) ;
2052+ await triggerInternalHook ( retiredEvent ) ;
2053+ expect ( retiredEvent . messages ) . toStrictEqual ( [ ] ) ;
2054+
2055+ clearInternalHooks ( ) ;
2056+ } ,
2057+ ) ;
2058+
19832059 it ( "injects plugin config into internal hook event context" , async ( ) => {
19842060 useNoBundledPlugins ( ) ;
19852061 const plugin = writePlugin ( {
0 commit comments