From 25a5c2eeb43f0fe4e38bbc3c572fc7566ecded15 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sun, 14 Dec 2025 10:56:50 +0900 Subject: [PATCH] feat(hooks): add tool-output-truncator for dynamic context-aware truncation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor grep-output-truncator into a general-purpose tool-output-truncator that applies dynamic truncation to multiple tools based on context window usage. Truncated tools: - Grep, safe_grep (existing) - Glob, safe_glob (new) - lsp_find_references (new) - lsp_document_symbols (new) - lsp_workspace_symbols (new) - lsp_diagnostics (new) - ast_grep_search (new) Uses the new dynamic-truncator utility from shared/ for context-aware output size limits based on remaining context window tokens. 🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode) --- src/config/schema.ts | 3 ++- src/hooks/index.ts | 4 +++- src/hooks/tool-output-truncator.ts | 38 ++++++++++++++++++++++++++++++ src/index.ts | 18 +++++++------- 4 files changed, 52 insertions(+), 11 deletions(-) create mode 100644 src/hooks/tool-output-truncator.ts diff --git a/src/config/schema.ts b/src/config/schema.ts index 0eff0bd..2f882cd 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -44,6 +44,7 @@ export const HookNameSchema = z.enum([ "session-notification", "comment-checker", "grep-output-truncator", + "tool-output-truncator", "directory-agents-injector", "directory-readme-injector", "empty-task-response-detector", @@ -52,7 +53,7 @@ export const HookNameSchema = z.enum([ "rules-injector", "background-notification", "auto-update-checker", - "ultrawork-mode", + "keyword-detector", "agent-usage-reminder", ]) diff --git a/src/hooks/index.ts b/src/hooks/index.ts index ea1f607..251fe2d 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -4,6 +4,7 @@ export { createSessionNotification } from "./session-notification"; export { createSessionRecoveryHook, type SessionRecoveryHook } from "./session-recovery"; export { createCommentCheckerHooks } from "./comment-checker"; export { createGrepOutputTruncatorHook } from "./grep-output-truncator"; +export { createToolOutputTruncatorHook } from "./tool-output-truncator"; export { createDirectoryAgentsInjectorHook } from "./directory-agents-injector"; export { createDirectoryReadmeInjectorHook } from "./directory-readme-injector"; export { createEmptyTaskResponseDetectorHook } from "./empty-task-response-detector"; @@ -13,5 +14,6 @@ export { createClaudeCodeHooksHook } from "./claude-code-hooks"; export { createRulesInjectorHook } from "./rules-injector"; export { createBackgroundNotificationHook } from "./background-notification" export { createAutoUpdateCheckerHook } from "./auto-update-checker"; -export { createUltraworkModeHook } from "./ultrawork-mode"; + export { createAgentUsageReminderHook } from "./agent-usage-reminder"; +export { createKeywordDetectorHook } from "./keyword-detector"; diff --git a/src/hooks/tool-output-truncator.ts b/src/hooks/tool-output-truncator.ts new file mode 100644 index 0000000..0182d5b --- /dev/null +++ b/src/hooks/tool-output-truncator.ts @@ -0,0 +1,38 @@ +import type { PluginInput } from "@opencode-ai/plugin" +import { createDynamicTruncator } from "../shared/dynamic-truncator" + +const TRUNCATABLE_TOOLS = [ + "Grep", + "safe_grep", + "Glob", + "safe_glob", + "lsp_find_references", + "lsp_document_symbols", + "lsp_workspace_symbols", + "lsp_diagnostics", + "ast_grep_search", +] + +export function createToolOutputTruncatorHook(ctx: PluginInput) { + const truncator = createDynamicTruncator(ctx) + + const toolExecuteAfter = async ( + input: { tool: string; sessionID: string; callID: string }, + output: { title: string; output: string; metadata: unknown } + ) => { + if (!TRUNCATABLE_TOOLS.includes(input.tool)) return + + try { + const { result, truncated } = await truncator.truncate(input.sessionID, output.output) + if (truncated) { + output.output = result + } + } catch { + // Graceful degradation - don't break tool execution + } + } + + return { + "tool.execute.after": toolExecuteAfter, + } +} diff --git a/src/index.ts b/src/index.ts index 49e306d..360043f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,7 @@ import { createSessionRecoveryHook, createSessionNotification, createCommentCheckerHooks, - createGrepOutputTruncatorHook, + createToolOutputTruncatorHook, createDirectoryAgentsInjectorHook, createDirectoryReadmeInjectorHook, createEmptyTaskResponseDetectorHook, @@ -16,7 +16,7 @@ import { createRulesInjectorHook, createBackgroundNotificationHook, createAutoUpdateCheckerHook, - createUltraworkModeHook, + createKeywordDetectorHook, createAgentUsageReminderHook, } from "./hooks"; import { createGoogleAntigravityAuthPlugin } from "./auth/antigravity"; @@ -178,8 +178,8 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => { const commentChecker = isHookEnabled("comment-checker") ? createCommentCheckerHooks() : null; - const grepOutputTruncator = isHookEnabled("grep-output-truncator") - ? createGrepOutputTruncatorHook(ctx) + const toolOutputTruncator = isHookEnabled("tool-output-truncator") + ? createToolOutputTruncatorHook(ctx) : null; const directoryAgentsInjector = isHookEnabled("directory-agents-injector") ? createDirectoryAgentsInjectorHook(ctx) @@ -205,8 +205,8 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => { const autoUpdateChecker = isHookEnabled("auto-update-checker") ? createAutoUpdateCheckerHook(ctx) : null; - const ultraworkMode = isHookEnabled("ultrawork-mode") - ? createUltraworkModeHook() + const keywordDetector = isHookEnabled("keyword-detector") + ? createKeywordDetectorHook() : null; const agentUsageReminder = isHookEnabled("agent-usage-reminder") ? createAgentUsageReminderHook(ctx) @@ -240,7 +240,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => { "chat.message": async (input, output) => { await claudeCodeHooks["chat.message"]?.(input, output); - await ultraworkMode?.["chat.message"]?.(input, output); + await keywordDetector?.["chat.message"]?.(input, output); }, config: async (config) => { @@ -337,7 +337,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => { await rulesInjector?.event(input); await thinkMode?.event(input); await anthropicAutoCompact?.event(input); - await ultraworkMode?.event(input); + await keywordDetector?.event(input); await agentUsageReminder?.event(input); const { event } = input; @@ -451,7 +451,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => { "tool.execute.after": async (input, output) => { await claudeCodeHooks["tool.execute.after"](input, output); - await grepOutputTruncator?.["tool.execute.after"](input, output); + await toolOutputTruncator?.["tool.execute.after"](input, output); await contextWindowMonitor?.["tool.execute.after"](input, output); await commentChecker?.["tool.execute.after"](input, output); await directoryAgentsInjector?.["tool.execute.after"](input, output);