fix(index): wrap hook creation with safeCreateHook + add defensive optional chaining (#1559)
This commit is contained in:
102
src/index.ts
102
src/index.ts
@@ -109,6 +109,7 @@ import {
|
||||
injectServerAuthIntoClient,
|
||||
} from "./shared";
|
||||
import { filterDisabledTools } from "./shared/disabled-tools";
|
||||
import { safeCreateHook } from "./shared/safe-create-hook";
|
||||
import { loadPluginConfig } from "./plugin-config";
|
||||
import { createModelCacheState } from "./plugin-state";
|
||||
import { createConfigHandler } from "./plugin-handlers";
|
||||
@@ -135,21 +136,22 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
||||
agent_pane_min_width: pluginConfig.tmux?.agent_pane_min_width ?? 40,
|
||||
} as const;
|
||||
const isHookEnabled = (hookName: HookName) => !disabledHooks.has(hookName);
|
||||
const safeHookEnabled = pluginConfig.experimental?.safe_hook_creation ?? true;
|
||||
|
||||
const modelCacheState = createModelCacheState();
|
||||
|
||||
const contextWindowMonitor = isHookEnabled("context-window-monitor")
|
||||
? createContextWindowMonitorHook(ctx)
|
||||
? safeCreateHook("context-window-monitor", () => createContextWindowMonitorHook(ctx), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
const preemptiveCompaction =
|
||||
isHookEnabled("preemptive-compaction") &&
|
||||
pluginConfig.experimental?.preemptive_compaction
|
||||
? createPreemptiveCompactionHook(ctx)
|
||||
? safeCreateHook("preemptive-compaction", () => createPreemptiveCompactionHook(ctx), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
const sessionRecovery = isHookEnabled("session-recovery")
|
||||
? createSessionRecoveryHook(ctx, {
|
||||
? safeCreateHook("session-recovery", () => createSessionRecoveryHook(ctx, {
|
||||
experimental: pluginConfig.experimental,
|
||||
})
|
||||
}), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
|
||||
// Check for conflicting notification plugins before creating session-notification
|
||||
@@ -166,17 +168,17 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
||||
allPlugins: externalNotifier.allPlugins,
|
||||
});
|
||||
} else {
|
||||
sessionNotification = createSessionNotification(ctx);
|
||||
sessionNotification = safeCreateHook("session-notification", () => createSessionNotification(ctx), { enabled: safeHookEnabled });
|
||||
}
|
||||
}
|
||||
|
||||
const commentChecker = isHookEnabled("comment-checker")
|
||||
? createCommentCheckerHooks(pluginConfig.comment_checker)
|
||||
? safeCreateHook("comment-checker", () => createCommentCheckerHooks(pluginConfig.comment_checker), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
const toolOutputTruncator = isHookEnabled("tool-output-truncator")
|
||||
? createToolOutputTruncatorHook(ctx, {
|
||||
? safeCreateHook("tool-output-truncator", () => createToolOutputTruncatorHook(ctx, {
|
||||
experimental: pluginConfig.experimental,
|
||||
})
|
||||
}), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
// Check for native OpenCode AGENTS.md injection support before creating hook
|
||||
let directoryAgentsInjector = null;
|
||||
@@ -195,18 +197,18 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
||||
},
|
||||
);
|
||||
} else {
|
||||
directoryAgentsInjector = createDirectoryAgentsInjectorHook(ctx);
|
||||
directoryAgentsInjector = safeCreateHook("directory-agents-injector", () => createDirectoryAgentsInjectorHook(ctx), { enabled: safeHookEnabled });
|
||||
}
|
||||
}
|
||||
const directoryReadmeInjector = isHookEnabled("directory-readme-injector")
|
||||
? createDirectoryReadmeInjectorHook(ctx)
|
||||
? safeCreateHook("directory-readme-injector", () => createDirectoryReadmeInjectorHook(ctx), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
const emptyTaskResponseDetector = isHookEnabled(
|
||||
"empty-task-response-detector",
|
||||
)
|
||||
? createEmptyTaskResponseDetectorHook(ctx)
|
||||
? safeCreateHook("empty-task-response-detector", () => createEmptyTaskResponseDetectorHook(ctx), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
const thinkMode = isHookEnabled("think-mode") ? createThinkModeHook() : null;
|
||||
const thinkMode = isHookEnabled("think-mode") ? safeCreateHook("think-mode", () => createThinkModeHook(), { enabled: safeHookEnabled }) : null;
|
||||
const claudeCodeHooks = createClaudeCodeHooksHook(
|
||||
ctx,
|
||||
{
|
||||
@@ -219,84 +221,84 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
||||
const anthropicContextWindowLimitRecovery = isHookEnabled(
|
||||
"anthropic-context-window-limit-recovery",
|
||||
)
|
||||
? createAnthropicContextWindowLimitRecoveryHook(ctx, {
|
||||
? safeCreateHook("anthropic-context-window-limit-recovery", () => createAnthropicContextWindowLimitRecoveryHook(ctx, {
|
||||
experimental: pluginConfig.experimental,
|
||||
})
|
||||
}), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
const rulesInjector = isHookEnabled("rules-injector")
|
||||
? createRulesInjectorHook(ctx)
|
||||
? safeCreateHook("rules-injector", () => createRulesInjectorHook(ctx), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
const autoUpdateChecker = isHookEnabled("auto-update-checker")
|
||||
? createAutoUpdateCheckerHook(ctx, {
|
||||
? safeCreateHook("auto-update-checker", () => createAutoUpdateCheckerHook(ctx, {
|
||||
showStartupToast: isHookEnabled("startup-toast"),
|
||||
isSisyphusEnabled: pluginConfig.sisyphus_agent?.disabled !== true,
|
||||
autoUpdate: pluginConfig.auto_update ?? true,
|
||||
})
|
||||
}), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
const keywordDetector = isHookEnabled("keyword-detector")
|
||||
? createKeywordDetectorHook(ctx, contextCollector)
|
||||
? safeCreateHook("keyword-detector", () => createKeywordDetectorHook(ctx, contextCollector), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
const contextInjectorMessagesTransform =
|
||||
createContextInjectorMessagesTransformHook(contextCollector);
|
||||
const agentUsageReminder = isHookEnabled("agent-usage-reminder")
|
||||
? createAgentUsageReminderHook(ctx)
|
||||
? safeCreateHook("agent-usage-reminder", () => createAgentUsageReminderHook(ctx), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
const nonInteractiveEnv = isHookEnabled("non-interactive-env")
|
||||
? createNonInteractiveEnvHook(ctx)
|
||||
? safeCreateHook("non-interactive-env", () => createNonInteractiveEnvHook(ctx), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
const interactiveBashSession = isHookEnabled("interactive-bash-session")
|
||||
? createInteractiveBashSessionHook(ctx)
|
||||
? safeCreateHook("interactive-bash-session", () => createInteractiveBashSessionHook(ctx), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
|
||||
const thinkingBlockValidator = isHookEnabled("thinking-block-validator")
|
||||
? createThinkingBlockValidatorHook()
|
||||
? safeCreateHook("thinking-block-validator", () => createThinkingBlockValidatorHook(), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
|
||||
let categorySkillReminder: ReturnType<typeof createCategorySkillReminderHook> | null = null;
|
||||
|
||||
const ralphLoop = isHookEnabled("ralph-loop")
|
||||
? createRalphLoopHook(ctx, {
|
||||
? safeCreateHook("ralph-loop", () => createRalphLoopHook(ctx, {
|
||||
config: pluginConfig.ralph_loop,
|
||||
checkSessionExists: async (sessionId) => sessionExists(sessionId),
|
||||
})
|
||||
}), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
|
||||
const editErrorRecovery = isHookEnabled("edit-error-recovery")
|
||||
? createEditErrorRecoveryHook(ctx)
|
||||
? safeCreateHook("edit-error-recovery", () => createEditErrorRecoveryHook(ctx), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
|
||||
const delegateTaskRetry = isHookEnabled("delegate-task-retry")
|
||||
? createDelegateTaskRetryHook(ctx)
|
||||
? safeCreateHook("delegate-task-retry", () => createDelegateTaskRetryHook(ctx), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
|
||||
const startWork = isHookEnabled("start-work")
|
||||
? createStartWorkHook(ctx)
|
||||
? safeCreateHook("start-work", () => createStartWorkHook(ctx), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
|
||||
const prometheusMdOnly = isHookEnabled("prometheus-md-only")
|
||||
? createPrometheusMdOnlyHook(ctx)
|
||||
? safeCreateHook("prometheus-md-only", () => createPrometheusMdOnlyHook(ctx), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
|
||||
const sisyphusJuniorNotepad = isHookEnabled("sisyphus-junior-notepad")
|
||||
? createSisyphusJuniorNotepadHook(ctx)
|
||||
? safeCreateHook("sisyphus-junior-notepad", () => createSisyphusJuniorNotepadHook(ctx), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
|
||||
const tasksTodowriteDisabler = isHookEnabled("tasks-todowrite-disabler")
|
||||
? createTasksTodowriteDisablerHook({
|
||||
? safeCreateHook("tasks-todowrite-disabler", () => createTasksTodowriteDisablerHook({
|
||||
experimental: pluginConfig.experimental,
|
||||
})
|
||||
}), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
|
||||
const questionLabelTruncator = createQuestionLabelTruncatorHook();
|
||||
const subagentQuestionBlocker = createSubagentQuestionBlockerHook();
|
||||
const writeExistingFileGuard = isHookEnabled("write-existing-file-guard")
|
||||
? createWriteExistingFileGuardHook(ctx)
|
||||
? safeCreateHook("write-existing-file-guard", () => createWriteExistingFileGuardHook(ctx), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
|
||||
const taskResumeInfo = createTaskResumeInfoHook();
|
||||
|
||||
const anthropicEffort = isHookEnabled("anthropic-effort")
|
||||
? createAnthropicEffortHook()
|
||||
? safeCreateHook("anthropic-effort", () => createAnthropicEffortHook(), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
|
||||
const tmuxSessionManager = new TmuxSessionManager(ctx, tmuxConfig);
|
||||
@@ -333,28 +335,28 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
||||
);
|
||||
|
||||
const atlasHook = isHookEnabled("atlas")
|
||||
? createAtlasHook(ctx, { directory: ctx.directory, backgroundManager })
|
||||
? safeCreateHook("atlas", () => createAtlasHook(ctx, { directory: ctx.directory, backgroundManager }), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
|
||||
initTaskToastManager(ctx.client);
|
||||
|
||||
const stopContinuationGuard = isHookEnabled("stop-continuation-guard")
|
||||
? createStopContinuationGuardHook(ctx)
|
||||
? safeCreateHook("stop-continuation-guard", () => createStopContinuationGuardHook(ctx), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
|
||||
const compactionContextInjector = isHookEnabled("compaction-context-injector")
|
||||
? createCompactionContextInjector()
|
||||
? safeCreateHook("compaction-context-injector", () => createCompactionContextInjector(), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
|
||||
const todoContinuationEnforcer = isHookEnabled("todo-continuation-enforcer")
|
||||
? createTodoContinuationEnforcer(ctx, {
|
||||
? safeCreateHook("todo-continuation-enforcer", () => createTodoContinuationEnforcer(ctx, {
|
||||
backgroundManager,
|
||||
isContinuationStopped: stopContinuationGuard?.isStopped,
|
||||
})
|
||||
}), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
|
||||
const unstableAgentBabysitter = isHookEnabled("unstable-agent-babysitter")
|
||||
? createUnstableAgentBabysitterHook(
|
||||
? safeCreateHook("unstable-agent-babysitter", () => createUnstableAgentBabysitterHook(
|
||||
{
|
||||
directory: ctx.directory,
|
||||
client: {
|
||||
@@ -382,7 +384,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
||||
backgroundManager,
|
||||
config: pluginConfig.babysitting,
|
||||
},
|
||||
)
|
||||
), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
|
||||
if (sessionRecovery && todoContinuationEnforcer) {
|
||||
@@ -393,7 +395,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
||||
}
|
||||
|
||||
const backgroundNotificationHook = isHookEnabled("background-notification")
|
||||
? createBackgroundNotificationHook(backgroundManager)
|
||||
? safeCreateHook("background-notification", () => createBackgroundNotificationHook(backgroundManager), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
const backgroundTools = createBackgroundTools(backgroundManager, ctx.client);
|
||||
|
||||
@@ -490,7 +492,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
||||
});
|
||||
|
||||
categorySkillReminder = isHookEnabled("category-skill-reminder")
|
||||
? createCategorySkillReminderHook(ctx, availableSkills)
|
||||
? safeCreateHook("category-skill-reminder", () => createCategorySkillReminderHook(ctx, availableSkills), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
|
||||
const skillMcpManager = new SkillMcpManager();
|
||||
@@ -515,7 +517,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
||||
});
|
||||
|
||||
const autoSlashCommand = isHookEnabled("auto-slash-command")
|
||||
? createAutoSlashCommandHook({ skills: mergedSkills })
|
||||
? safeCreateHook("auto-slash-command", () => createAutoSlashCommandHook({ skills: mergedSkills }), { enabled: safeHookEnabled })
|
||||
: null;
|
||||
|
||||
const configHandler = createConfigHandler({
|
||||
@@ -610,7 +612,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
||||
|
||||
await stopContinuationGuard?.["chat.message"]?.(input);
|
||||
await keywordDetector?.["chat.message"]?.(input, output);
|
||||
await claudeCodeHooks["chat.message"]?.(input, output);
|
||||
await claudeCodeHooks?.["chat.message"]?.(input, output);
|
||||
await autoSlashCommand?.["chat.message"]?.(input, output);
|
||||
await startWork?.["chat.message"]?.(input, output);
|
||||
|
||||
@@ -700,7 +702,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
||||
|
||||
event: async (input) => {
|
||||
await autoUpdateChecker?.event(input);
|
||||
await claudeCodeHooks.event(input);
|
||||
await claudeCodeHooks?.event?.(input);
|
||||
await backgroundNotificationHook?.event(input);
|
||||
await sessionNotification?.(input);
|
||||
await todoContinuationEnforcer?.handler(input);
|
||||
@@ -800,10 +802,10 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
||||
},
|
||||
|
||||
"tool.execute.before": async (input, output) => {
|
||||
await subagentQuestionBlocker["tool.execute.before"]?.(input, output);
|
||||
await subagentQuestionBlocker?.["tool.execute.before"]?.(input, output);
|
||||
await writeExistingFileGuard?.["tool.execute.before"]?.(input, output);
|
||||
await questionLabelTruncator["tool.execute.before"]?.(input, output);
|
||||
await claudeCodeHooks["tool.execute.before"](input, output);
|
||||
await questionLabelTruncator?.["tool.execute.before"]?.(input, output);
|
||||
await claudeCodeHooks?.["tool.execute.before"]?.(input, output);
|
||||
await nonInteractiveEnv?.["tool.execute.before"](input, output);
|
||||
await commentChecker?.["tool.execute.before"]?.(input, output);
|
||||
await directoryAgentsInjector?.["tool.execute.before"]?.(input, output);
|
||||
@@ -909,7 +911,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
||||
}
|
||||
}
|
||||
|
||||
await claudeCodeHooks["tool.execute.after"](input, output);
|
||||
await claudeCodeHooks?.["tool.execute.after"]?.(input, output);
|
||||
await toolOutputTruncator?.["tool.execute.after"](input, output);
|
||||
await preemptiveCompaction?.["tool.execute.after"](input, output);
|
||||
await contextWindowMonitor?.["tool.execute.after"](input, output);
|
||||
@@ -924,7 +926,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
||||
await editErrorRecovery?.["tool.execute.after"](input, output);
|
||||
await delegateTaskRetry?.["tool.execute.after"](input, output);
|
||||
await atlasHook?.["tool.execute.after"]?.(input, output);
|
||||
await taskResumeInfo["tool.execute.after"](input, output);
|
||||
await taskResumeInfo?.["tool.execute.after"]?.(input, output);
|
||||
},
|
||||
|
||||
"experimental.session.compacting": async (
|
||||
|
||||
Reference in New Issue
Block a user