Browse Source

refactor: wire HookRegistry, delete manual hook dispatching

Michael Henke 1 month ago
parent
commit
d2cfc64f84
2 changed files with 73 additions and 132 deletions
  1. 1 0
      src/hooks/index.ts
  2. 72 132
      src/index.ts

+ 1 - 0
src/hooks/index.ts

@@ -9,6 +9,7 @@ export {
   ForegroundFallbackManager,
   isRateLimitError,
 } from './foreground-fallback';
+export { HookRegistry } from './hook-registry';
 export { processImageAttachments } from './image-hook';
 export { createJsonErrorRecoveryHook } from './json-error-recovery/hook';
 export { createLoopCommandHook } from './loop-command';

+ 72 - 132
src/index.ts

@@ -31,6 +31,7 @@ import {
   createReflectCommandHook,
   createTaskSessionManagerHook,
   ForegroundFallbackManager,
+  HookRegistry,
   SessionLifecycle,
 } from './hooks';
 import { processImageAttachments } from './hooks/image-hook';
@@ -138,16 +139,10 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
   let depthTracker: SubagentDepthTracker;
   let multiplexerSessionManager: MultiplexerSessionManager;
   let autoUpdateChecker: ReturnType<typeof createAutoUpdateCheckerHook>;
-  let phaseReminderHook: ReturnType<typeof createPhaseReminderHook>;
-  let filterAvailableSkillsHook: ReturnType<
-    typeof createFilterAvailableSkillsHook
-  >;
   let sessionAgentMap: Map<string, string>;
-  let postFileToolNudgeHook: ReturnType<typeof createPostFileToolNudgeHook>;
+  let sessionLifecycle: SessionLifecycle;
+  let hookRegistry: HookRegistry;
   let chatHeadersHook: ReturnType<typeof createChatHeadersHook>;
-  let delegateTaskRetryHook: ReturnType<typeof createDelegateTaskRetryHook>;
-  let applyPatchHook: ReturnType<typeof createApplyPatchHook>;
-  let jsonErrorRecoveryHook: ReturnType<typeof createJsonErrorRecoveryHook>;
   let foregroundFallback: ForegroundFallbackManager;
   let deepworkCommandHook: ReturnType<typeof createDeepworkCommandHook>;
   let reflectCommandHook: ReturnType<typeof createReflectCommandHook>;
@@ -269,7 +264,7 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
       void multiplexerSessionManager.retryDeferredIdleClose(taskID);
     });
 
-    const sessionLifecycle = new SessionLifecycle(log);
+    sessionLifecycle = new SessionLifecycle(log);
 
     // Initialize auto-update checker hook
     autoUpdateChecker = createAutoUpdateCheckerHook(ctx, {
@@ -277,31 +272,11 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
       companion: config.companion,
     });
 
-    // Initialize phase reminder hook for workflow compliance
-    phaseReminderHook = createPhaseReminderHook(sessionLifecycle);
-
-    // Initialize available skills filter hook
-    filterAvailableSkillsHook = createFilterAvailableSkillsHook(ctx, config);
-
     // Track session → agent mapping for serve-mode system prompt injection
     sessionAgentMap = new Map<string, string>();
 
-    // Initialize post-file-tool nudge hook
-    postFileToolNudgeHook = createPostFileToolNudgeHook({
-      shouldInject: (sessionID) =>
-        sessionAgentMap.get(sessionID) === 'orchestrator',
-      coordinator: sessionLifecycle,
-    });
-
     chatHeadersHook = createChatHeadersHook(ctx);
 
-    // Initialize delegate-task retry guidance hook
-    delegateTaskRetryHook = createDelegateTaskRetryHook(ctx);
-
-    applyPatchHook = createApplyPatchHook(ctx);
-    // Initialize JSON parse error recovery hook
-    jsonErrorRecoveryHook = createJsonErrorRecoveryHook(ctx);
-
     // Initialize foreground fallback manager for runtime model switching.
     // Enabled by default even without fallback chains — the manager can still
     // abort rate-limited sessions after maxRetries to prevent infinite freezes.
@@ -327,6 +302,68 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
         foregroundFallback.isFallbackInProgress(sessionID),
       coordinator: sessionLifecycle,
     });
+
+    // Initialize hooks and register them with the registry
+    hookRegistry = new HookRegistry();
+
+    const phaseReminder = createPhaseReminderHook(sessionLifecycle);
+    hookRegistry.register('experimental.chat.messages.transform', (i, o) =>
+      phaseReminder['experimental.chat.messages.transform'](
+        i as never,
+        o as never,
+      ),
+    );
+
+    const filterAvailableSkills = createFilterAvailableSkillsHook(ctx, config);
+    hookRegistry.register('experimental.chat.messages.transform', (i, o) =>
+      filterAvailableSkills['experimental.chat.messages.transform'](
+        i as never,
+        o as never,
+      ),
+    );
+
+    const postFileToolNudge = createPostFileToolNudgeHook({
+      shouldInject: (sessionID) =>
+        sessionAgentMap.get(sessionID) === 'orchestrator',
+      coordinator: sessionLifecycle,
+    });
+    hookRegistry.register('experimental.chat.system.transform', (i, o) =>
+      postFileToolNudge['experimental.chat.system.transform'](
+        i as never,
+        o as never,
+      ),
+    );
+    hookRegistry.register('tool.execute.after', (i, o) =>
+      postFileToolNudge['tool.execute.after'](i as never, o as never),
+    );
+
+    const delegateTaskRetry = createDelegateTaskRetryHook(ctx);
+    hookRegistry.register('tool.execute.after', (i, o) =>
+      delegateTaskRetry['tool.execute.after'](i as never, o as never),
+    );
+
+    const applyPatch = createApplyPatchHook(ctx);
+    hookRegistry.register('tool.execute.before', (i, o) =>
+      applyPatch['tool.execute.before'](i as never, o as never),
+    );
+
+    const jsonErrorRecovery = createJsonErrorRecoveryHook(ctx);
+    hookRegistry.register('tool.execute.after', (i, o) =>
+      jsonErrorRecovery['tool.execute.after'](i as never, o as never),
+    );
+
+    hookRegistry.register('tool.execute.before', (i, o) =>
+      taskSessionManagerHook['tool.execute.before'](i as never, o as never),
+    );
+    hookRegistry.register('experimental.chat.messages.transform', (i, o) =>
+      taskSessionManagerHook['experimental.chat.messages.transform'](
+        i as never,
+        o as never,
+      ),
+    );
+    hookRegistry.register('tool.execute.after', (i, o) =>
+      taskSessionManagerHook['tool.execute.after'](i as never, o as never),
+    );
     interviewManager = createInterviewManager(ctx, config);
     presetManager = createPresetManager(ctx, config);
     companionManager = new CompanionManager(
@@ -899,29 +936,8 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
       }
     },
 
-    // Best-effort rescue only for stale apply_patch input before native
-    // execution
     'tool.execute.before': async (input, output) => {
-      await applyPatchHook['tool.execute.before'](
-        input as {
-          tool: string;
-          directory?: string;
-        },
-        output as {
-          args?: { patchText?: unknown; [key: string]: unknown };
-        },
-      );
-
-      await taskSessionManagerHook['tool.execute.before'](
-        input as {
-          tool: string;
-          sessionID?: string;
-          callID?: string;
-        },
-        output as { args?: unknown },
-      );
-
-      // No-op for divoom
+      await hookRegistry.dispatch('tool.execute.before', input, output);
     },
 
     'command.execute.before': async (input, output) => {
@@ -1046,7 +1062,8 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
       }
 
       // Inject ephemeral post-file-tool-nudge reminder
-      await postFileToolNudgeHook['experimental.chat.system.transform'](
+      await hookRegistry.dispatch(
+        'experimental.chat.system.transform',
         input,
         output,
       );
@@ -1091,92 +1108,15 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
         log,
       });
 
-      await taskSessionManagerHook['experimental.chat.messages.transform'](
-        input,
-        typedOutput,
-      );
-      await phaseReminderHook['experimental.chat.messages.transform'](
-        input,
-        typedOutput,
-      );
-      await filterAvailableSkillsHook['experimental.chat.messages.transform'](
+      await hookRegistry.dispatch(
+        'experimental.chat.messages.transform',
         input,
         typedOutput,
       );
     },
 
-    // Post-tool hooks: retry guidance for delegation errors + file-tool
-    // nudge
     'tool.execute.after': async (input, output) => {
-      const meta = input as {
-        tool?: string;
-        sessionID?: string;
-        callID?: string;
-      };
-      const runPostToolHook = async (
-        name: string,
-        fn: () => Promise<void>,
-      ): Promise<void> => {
-        try {
-          await fn();
-        } catch (error) {
-          log('[plugin] post-tool hook failed open', {
-            hook: name,
-            tool: meta.tool,
-            sessionID: meta.sessionID,
-            callID: meta.callID,
-            error: error instanceof Error ? error.message : String(error),
-          });
-        }
-      };
-
-      await runPostToolHook('delegate-task-retry', () =>
-        delegateTaskRetryHook['tool.execute.after'](
-          input as { tool: string },
-          output as { output: unknown },
-        ),
-      );
-
-      await runPostToolHook('json-error-recovery', () =>
-        jsonErrorRecoveryHook['tool.execute.after'](
-          input as {
-            tool: string;
-            sessionID: string;
-            callID: string;
-          },
-          output as {
-            title: string;
-            output: unknown;
-            metadata: unknown;
-          },
-        ),
-      );
-
-      await runPostToolHook('post-file-tool-nudge', () =>
-        postFileToolNudgeHook['tool.execute.after'](
-          input as {
-            tool: string;
-            sessionID?: string;
-            callID?: string;
-          },
-          output as {
-            title: string;
-            output: string;
-            metadata: Record<string, unknown>;
-          },
-        ),
-      );
-
-      await runPostToolHook('task-session-manager', () =>
-        taskSessionManagerHook['tool.execute.after'](
-          input as {
-            tool: string;
-            sessionID?: string;
-            callID?: string;
-          },
-          output as { output: unknown },
-        ),
-      );
+      await hookRegistry.dispatch('tool.execute.after', input, output);
     },
   };
 };