diff --git a/README.ja.md b/README.ja.md index f540b01b2..6194d7cce 100644 --- a/README.ja.md +++ b/README.ja.md @@ -1047,7 +1047,6 @@ OpenCode でサポートされるすべての LSP 構成およびカスタム設 ```json { "experimental": { - "preemptive_compaction_threshold": 0.85, "truncate_all_tool_outputs": true, "aggressive_truncation": true, "auto_resume": true @@ -1055,13 +1054,11 @@ OpenCode でサポートされるすべての LSP 構成およびカスタム設 } ``` -| オプション | デフォルト | 説明 | -| --------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `preemptive_compaction_threshold` | `0.85` | プリエンプティブコンパクションをトリガーする閾値(0.5-0.95)。`preemptive-compaction` フックはデフォルトで有効です。このオプションで閾値をカスタマイズできます。 | -| `truncate_all_tool_outputs` | `false` | ホワイトリストのツール(Grep、Glob、LSP、AST-grep)だけでなく、すべてのツール出力を切り詰めます。Tool output truncator はデフォルトで有効です - `disabled_hooks`で無効化できます。 | -| `aggressive_truncation` | `false` | トークン制限を超えた場合、ツール出力を積極的に切り詰めて制限内に収めます。デフォルトの切り詰めより積極的です。不十分な場合は要約/復元にフォールバックします。 | -| `auto_resume` | `false` | thinking block エラーや thinking disabled violation からの回復成功後、自動的にセッションを再開します。最後のユーザーメッセージを抽出して続行します。 | -| `dcp_for_compaction` | `false` | コンパクション用DCP(動的コンテキスト整理)を有効化 - トークン制限超過時に最初に実行されます。コンパクション前に重複したツール呼び出しと古いツール出力を整理します。 | +| オプション | デフォルト | 説明 | +| --------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `truncate_all_tool_outputs` | `false` | ホワイトリストのツール(Grep、Glob、LSP、AST-grep)だけでなく、すべてのツール出力を切り詰めます。Tool output truncator はデフォルトで有効です - `disabled_hooks`で無効化できます。 | +| `aggressive_truncation` | `false` | トークン制限を超えた場合、ツール出力を積極的に切り詰めて制限内に収めます。デフォルトの切り詰めより積極的です。不十分な場合は要約/復元にフォールバックします。 | +| `auto_resume` | `false` | thinking block エラーや thinking disabled violation からの回復成功後、自動的にセッションを再開します。最後のユーザーメッセージを抽出して続行します。 | **警告**:これらの機能は実験的であり、予期しない動作を引き起こす可能性があります。影響を理解した場合にのみ有効にしてください。 diff --git a/README.md b/README.md index 8b7becb3d..c57b737d7 100644 --- a/README.md +++ b/README.md @@ -1165,7 +1165,6 @@ Opt-in experimental features that may change or be removed in future versions. U ```json { "experimental": { - "preemptive_compaction_threshold": 0.85, "truncate_all_tool_outputs": true, "aggressive_truncation": true, "auto_resume": true @@ -1173,13 +1172,11 @@ Opt-in experimental features that may change or be removed in future versions. U } ``` -| Option | Default | Description | -| --------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `preemptive_compaction_threshold` | `0.85` | Threshold percentage (0.5-0.95) to trigger preemptive compaction. The `preemptive-compaction` hook is enabled by default; this option customizes the threshold. | -| `truncate_all_tool_outputs` | `false` | Truncates ALL tool outputs instead of just whitelisted tools (Grep, Glob, LSP, AST-grep). Tool output truncator is enabled by default - disable via `disabled_hooks`. | -| `aggressive_truncation` | `false` | When token limit is exceeded, aggressively truncates tool outputs to fit within limits. More aggressive than the default truncation behavior. Falls back to summarize/revert if insufficient. | -| `auto_resume` | `false` | Automatically resumes session after successful recovery from thinking block errors or thinking disabled violations. Extracts the last user message and continues. | -| `dcp_for_compaction` | `false` | Enable DCP (Dynamic Context Pruning) for compaction - runs first when token limit exceeded. Prunes duplicate tool calls and old tool outputs before running compaction. | +| Option | Default | Description | +| --------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `truncate_all_tool_outputs` | `false` | Truncates ALL tool outputs instead of just whitelisted tools (Grep, Glob, LSP, AST-grep). Tool output truncator is enabled by default - disable via `disabled_hooks`. | +| `aggressive_truncation` | `false` | When token limit is exceeded, aggressively truncates tool outputs to fit within limits. More aggressive than the default truncation behavior. Falls back to summarize/revert if insufficient. | +| `auto_resume` | `false` | Automatically resumes session after successful recovery from thinking block errors or thinking disabled violations. Extracts the last user message and continues. | **Warning**: These features are experimental and may cause unexpected behavior. Enable only if you understand the implications. diff --git a/README.zh-cn.md b/README.zh-cn.md index d91dbc042..6cb7b24f9 100644 --- a/README.zh-cn.md +++ b/README.zh-cn.md @@ -1174,7 +1174,6 @@ Oh My OpenCode 添加了重构工具(重命名、代码操作)。 ```json { "experimental": { - "preemptive_compaction_threshold": 0.85, "truncate_all_tool_outputs": true, "aggressive_truncation": true, "auto_resume": true @@ -1182,13 +1181,11 @@ Oh My OpenCode 添加了重构工具(重命名、代码操作)。 } ``` -| 选项 | 默认 | 描述 | -| --------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `preemptive_compaction_threshold` | `0.85` | 触发预防性压缩的阈值百分比(0.5-0.95)。`preemptive-compaction` 钩子默认启用;此选项自定义阈值。 | -| `truncate_all_tool_outputs` | `false` | 截断所有工具输出而不仅仅是白名单工具(Grep、Glob、LSP、AST-grep)。工具输出截断器默认启用——通过 `disabled_hooks` 禁用。 | -| `aggressive_truncation` | `false` | 当超过 token 限制时,积极截断工具输出以适应限制。比默认截断行为更激进。如果不足以满足,则回退到总结/恢复。 | -| `auto_resume` | `false` | 从思考块错误或禁用思考违规成功恢复后自动恢复会话。提取最后一条用户消息并继续。 | -| `dcp_for_compaction` | `false` | 为压缩启用 DCP(动态上下文修剪)——当超过 token 限制时首先运行。在运行压缩之前修剪重复的工具调用和旧的工具输出。 | +| 选项 | 默认 | 描述 | +| --------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `truncate_all_tool_outputs` | `false` | 截断所有工具输出而不仅仅是白名单工具(Grep、Glob、LSP、AST-grep)。工具输出截断器默认启用——通过 `disabled_hooks` 禁用。 | +| `aggressive_truncation` | `false` | 当超过 token 限制时,积极截断工具输出以适应限制。比默认截断行为更激进。如果不足以满足,则回退到总结/恢复。 | +| `auto_resume` | `false` | 从思考块错误或禁用思考违规成功恢复后自动恢复会话。提取最后一条用户消息并继续。 | **警告**:这些功能是实验性的,可能导致意外行为。只有在理解其影响后才启用。 diff --git a/src/config/schema.ts b/src/config/schema.ts index d5fad7e01..edd40bb2a 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -79,7 +79,7 @@ export const HookNameSchema = z.enum([ "empty-message-sanitizer", "thinking-block-validator", "ralph-loop", - "preemptive-compaction", + "compaction-context-injector", "claude-code-hooks", "auto-slash-command", @@ -225,16 +225,10 @@ export const DynamicContextPruningConfigSchema = z.object({ export const ExperimentalConfigSchema = z.object({ aggressive_truncation: z.boolean().optional(), auto_resume: z.boolean().optional(), - /** Enable preemptive compaction at threshold (default: true since v2.9.0) */ - preemptive_compaction: z.boolean().optional(), - /** Threshold percentage to trigger preemptive compaction (default: 0.80) */ - preemptive_compaction_threshold: z.number().min(0.5).max(0.95).optional(), /** Truncate all tool outputs, not just whitelisted tools (default: false). Tool output truncator is enabled by default - disable via disabled_hooks. */ truncate_all_tool_outputs: z.boolean().optional(), /** Dynamic context pruning configuration */ dynamic_context_pruning: DynamicContextPruningConfigSchema.optional(), - /** Enable DCP (Dynamic Context Pruning) for compaction - runs first when token limit exceeded (default: false) */ - dcp_for_compaction: z.boolean().optional(), }) export const SkillSourceSchema = z.union([ diff --git a/src/hooks/anthropic-context-window-limit-recovery/executor.test.ts b/src/hooks/anthropic-context-window-limit-recovery/executor.test.ts index f773bc49b..35b7ccb01 100644 --- a/src/hooks/anthropic-context-window-limit-recovery/executor.test.ts +++ b/src/hooks/anthropic-context-window-limit-recovery/executor.test.ts @@ -17,7 +17,6 @@ describe("executeCompact lock management", () => { errorDataBySession: new Map(), retryStateBySession: new Map(), truncateStateBySession: new Map(), - dcpStateBySession: new Map(), emptyContentAttemptBySession: new Map(), compactionInProgress: new Set(), } @@ -119,7 +118,6 @@ describe("executeCompact lock management", () => { truncate_all_tool_outputs: false, aggressive_truncation: true, } - const dcpForCompaction = true // #when: Execute compaction with experimental flag await executeCompact( @@ -129,7 +127,6 @@ describe("executeCompact lock management", () => { mockClient, directory, experimental, - dcpForCompaction, ) // #then: Lock should be cleared even on early return diff --git a/src/hooks/anthropic-context-window-limit-recovery/executor.ts b/src/hooks/anthropic-context-window-limit-recovery/executor.ts index dbfaad19f..1e9f0ea5f 100644 --- a/src/hooks/anthropic-context-window-limit-recovery/executor.ts +++ b/src/hooks/anthropic-context-window-limit-recovery/executor.ts @@ -1,12 +1,11 @@ import type { AutoCompactState, - DcpState, RetryState, TruncateState, } from "./types"; import type { ExperimentalConfig } from "../../config"; import { RETRY_CONFIG, TRUNCATE_CONFIG } from "./types"; -import { executeDynamicContextPruning } from "./pruning-executor"; + import { findLargestToolResult, truncateToolResult, @@ -82,17 +81,7 @@ function getOrCreateTruncateState( return state; } -function getOrCreateDcpState( - autoCompactState: AutoCompactState, - sessionID: string, -): DcpState { - let state = autoCompactState.dcpStateBySession.get(sessionID); - if (!state) { - state = { attempted: false, itemsPruned: 0 }; - autoCompactState.dcpStateBySession.set(sessionID, state); - } - return state; -} + function sanitizeEmptyMessagesBeforeSummarize(sessionID: string): number { const emptyMessageIds = findEmptyMessages(sessionID); @@ -168,7 +157,6 @@ function clearSessionState( autoCompactState.errorDataBySession.delete(sessionID); autoCompactState.retryStateBySession.delete(sessionID); autoCompactState.truncateStateBySession.delete(sessionID); - autoCompactState.dcpStateBySession.delete(sessionID); autoCompactState.emptyContentAttemptBySession.delete(sessionID); autoCompactState.compactionInProgress.delete(sessionID); } @@ -275,7 +263,6 @@ export async function executeCompact( client: any, directory: string, experimental?: ExperimentalConfig, - dcpForCompaction?: boolean, ): Promise { if (autoCompactState.compactionInProgress.has(sessionID)) { await (client as Client).tui @@ -302,61 +289,7 @@ export async function executeCompact( errorData?.maxTokens && errorData.currentTokens > errorData.maxTokens; - // PHASE 1: DCP (Dynamic Context Pruning) - prune duplicate tool calls first - const dcpState = getOrCreateDcpState(autoCompactState, sessionID); - if (dcpForCompaction !== false && !dcpState.attempted && isOverLimit) { - dcpState.attempted = true; - log("[auto-compact] PHASE 1: DCP triggered on token limit error", { - sessionID, - currentTokens: errorData.currentTokens, - maxTokens: errorData.maxTokens, - }); - - const dcpConfig = experimental?.dynamic_context_pruning ?? { - enabled: true, - notification: "detailed" as const, - protected_tools: [ - "task", - "todowrite", - "todoread", - "lsp_rename", - ], - }; - - try { - const pruningResult = await executeDynamicContextPruning( - sessionID, - dcpConfig, - client, - ); - - if (pruningResult.itemsPruned > 0) { - dcpState.itemsPruned = pruningResult.itemsPruned; - log("[auto-compact] DCP successful, proceeding to truncation", { - itemsPruned: pruningResult.itemsPruned, - tokensSaved: pruningResult.totalTokensSaved, - }); - - await (client as Client).tui - .showToast({ - body: { - title: "Dynamic Context Pruning", - message: `Pruned ${pruningResult.itemsPruned} items (~${Math.round(pruningResult.totalTokensSaved / 1000)}k tokens). Proceeding to truncation...`, - variant: "success", - duration: 3000, - }, - }) - .catch(() => {}); - // Continue to PHASE 2 (truncation) instead of summarizing immediately - } else { - log("[auto-compact] DCP did not prune any items", { sessionID }); - } - } catch (error) { - log("[auto-compact] DCP failed", { error: String(error) }); - } - } - - // PHASE 2: Aggressive Truncation - always try when over limit (not experimental-only) + // Aggressive Truncation - always try when over limit if ( isOverLimit && truncateState.truncateAttempt < TRUNCATE_CONFIG.maxTruncateAttempts @@ -448,7 +381,6 @@ export async function executeCompact( client, directory, experimental, - dcpForCompaction, ); }, 500); return; @@ -517,7 +449,6 @@ export async function executeCompact( client, directory, experimental, - dcpForCompaction, ); }, cappedDelay); return; diff --git a/src/hooks/anthropic-context-window-limit-recovery/index.ts b/src/hooks/anthropic-context-window-limit-recovery/index.ts index 418b4e0de..cd8d1246a 100644 --- a/src/hooks/anthropic-context-window-limit-recovery/index.ts +++ b/src/hooks/anthropic-context-window-limit-recovery/index.ts @@ -7,7 +7,6 @@ import { log } from "../../shared/logger" export interface AnthropicContextWindowLimitRecoveryOptions { experimental?: ExperimentalConfig - dcpForCompaction?: boolean } function createRecoveryState(): AutoCompactState { @@ -16,7 +15,6 @@ function createRecoveryState(): AutoCompactState { errorDataBySession: new Map(), retryStateBySession: new Map(), truncateStateBySession: new Map(), - dcpStateBySession: new Map(), emptyContentAttemptBySession: new Map(), compactionInProgress: new Set(), } @@ -25,7 +23,6 @@ function createRecoveryState(): AutoCompactState { export function createAnthropicContextWindowLimitRecoveryHook(ctx: PluginInput, options?: AnthropicContextWindowLimitRecoveryOptions) { const autoCompactState = createRecoveryState() const experimental = options?.experimental - const dcpForCompaction = options?.dcpForCompaction const eventHandler = async ({ event }: { event: { type: string; properties?: unknown } }) => { const props = event.properties as Record | undefined @@ -37,7 +34,6 @@ export function createAnthropicContextWindowLimitRecoveryHook(ctx: PluginInput, autoCompactState.errorDataBySession.delete(sessionInfo.id) autoCompactState.retryStateBySession.delete(sessionInfo.id) autoCompactState.truncateStateBySession.delete(sessionInfo.id) - autoCompactState.dcpStateBySession.delete(sessionInfo.id) autoCompactState.emptyContentAttemptBySession.delete(sessionInfo.id) autoCompactState.compactionInProgress.delete(sessionInfo.id) } @@ -81,8 +77,7 @@ export function createAnthropicContextWindowLimitRecoveryHook(ctx: PluginInput, autoCompactState, ctx.client, ctx.directory, - experimental, - dcpForCompaction + experimental ) }, 300) } @@ -141,8 +136,7 @@ export function createAnthropicContextWindowLimitRecoveryHook(ctx: PluginInput, autoCompactState, ctx.client, ctx.directory, - experimental, - dcpForCompaction + experimental ) } } @@ -152,6 +146,6 @@ export function createAnthropicContextWindowLimitRecoveryHook(ctx: PluginInput, } } -export type { AutoCompactState, DcpState, ParsedTokenLimitError, TruncateState } from "./types" +export type { AutoCompactState, ParsedTokenLimitError, TruncateState } from "./types" export { parseAnthropicTokenLimitError } from "./parser" export { executeCompact, getLastAssistant } from "./executor" diff --git a/src/hooks/anthropic-context-window-limit-recovery/types.ts b/src/hooks/anthropic-context-window-limit-recovery/types.ts index 024fd544b..40b31d064 100644 --- a/src/hooks/anthropic-context-window-limit-recovery/types.ts +++ b/src/hooks/anthropic-context-window-limit-recovery/types.ts @@ -18,17 +18,11 @@ export interface TruncateState { lastTruncatedPartId?: string } -export interface DcpState { - attempted: boolean - itemsPruned: number -} - export interface AutoCompactState { pendingCompact: Set errorDataBySession: Map retryStateBySession: Map truncateStateBySession: Map - dcpStateBySession: Map emptyContentAttemptBySession: Map compactionInProgress: Set } diff --git a/src/hooks/compaction-context-injector/index.ts b/src/hooks/compaction-context-injector/index.ts index 62e14f237..1df79c4ab 100644 --- a/src/hooks/compaction-context-injector/index.ts +++ b/src/hooks/compaction-context-injector/index.ts @@ -1,7 +1,14 @@ -import type { SummarizeContext } from "../preemptive-compaction" import { injectHookMessage } from "../../features/hook-message-injector" import { log } from "../../shared/logger" +export interface SummarizeContext { + sessionID: string + providerID: string + modelID: string + usageRatio: number + directory: string +} + const SUMMARIZE_CONTEXT_PROMPT = `[COMPACTION CONTEXT INJECTION] When summarizing this session, you MUST include the following sections in your summary: diff --git a/src/hooks/index.ts b/src/hooks/index.ts index a8a1c85ed..f5fadfa89 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -8,7 +8,7 @@ export { createDirectoryAgentsInjectorHook } from "./directory-agents-injector"; export { createDirectoryReadmeInjectorHook } from "./directory-readme-injector"; export { createEmptyTaskResponseDetectorHook } from "./empty-task-response-detector"; export { createAnthropicContextWindowLimitRecoveryHook, type AnthropicContextWindowLimitRecoveryOptions } from "./anthropic-context-window-limit-recovery"; -export { createPreemptiveCompactionHook, type PreemptiveCompactionOptions, type SummarizeContext, type BeforeSummarizeCallback } from "./preemptive-compaction"; + export { createCompactionContextInjector } from "./compaction-context-injector"; export { createThinkModeHook } from "./think-mode"; export { createClaudeCodeHooksHook } from "./claude-code-hooks"; diff --git a/src/hooks/preemptive-compaction/constants.ts b/src/hooks/preemptive-compaction/constants.ts deleted file mode 100644 index 6e9543415..000000000 --- a/src/hooks/preemptive-compaction/constants.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const DEFAULT_THRESHOLD = 0.85 -export const MIN_TOKENS_FOR_COMPACTION = 50_000 -export const COMPACTION_COOLDOWN_MS = 60_000 diff --git a/src/hooks/preemptive-compaction/index.ts b/src/hooks/preemptive-compaction/index.ts deleted file mode 100644 index 58b5a8223..000000000 --- a/src/hooks/preemptive-compaction/index.ts +++ /dev/null @@ -1,265 +0,0 @@ -import { existsSync, readdirSync } from "node:fs" -import { join } from "node:path" -import type { PluginInput } from "@opencode-ai/plugin" -import type { ExperimentalConfig } from "../../config" -import type { PreemptiveCompactionState, TokenInfo } from "./types" -import { - DEFAULT_THRESHOLD, - MIN_TOKENS_FOR_COMPACTION, - COMPACTION_COOLDOWN_MS, -} from "./constants" -import { - findNearestMessageWithFields, - MESSAGE_STORAGE, -} from "../../features/hook-message-injector" -import { log } from "../../shared/logger" - -export interface SummarizeContext { - sessionID: string - providerID: string - modelID: string - usageRatio: number - directory: string -} - -export type BeforeSummarizeCallback = (ctx: SummarizeContext) => Promise | void - -export type GetModelLimitCallback = (providerID: string, modelID: string) => number | undefined - -export interface PreemptiveCompactionOptions { - experimental?: ExperimentalConfig - onBeforeSummarize?: BeforeSummarizeCallback - getModelLimit?: GetModelLimitCallback -} - -interface MessageInfo { - id: string - role: string - sessionID: string - providerID?: string - modelID?: string - tokens?: TokenInfo - summary?: boolean - finish?: boolean -} - -interface MessageWrapper { - info: MessageInfo -} - -const CLAUDE_MODEL_PATTERN = /claude-(opus|sonnet|haiku)/i -const CLAUDE_DEFAULT_CONTEXT_LIMIT = - process.env.ANTHROPIC_1M_CONTEXT === "true" || - process.env.VERTEX_ANTHROPIC_1M_CONTEXT === "true" - ? 1_000_000 - : 200_000 - -function isSupportedModel(modelID: string): boolean { - return CLAUDE_MODEL_PATTERN.test(modelID) -} - -function getMessageDir(sessionID: string): string | null { - if (!existsSync(MESSAGE_STORAGE)) return null - - const directPath = join(MESSAGE_STORAGE, sessionID) - if (existsSync(directPath)) return directPath - - for (const dir of readdirSync(MESSAGE_STORAGE)) { - const sessionPath = join(MESSAGE_STORAGE, dir, sessionID) - if (existsSync(sessionPath)) return sessionPath - } - - return null -} - -function createState(): PreemptiveCompactionState { - return { - lastCompactionTime: new Map(), - compactionInProgress: new Set(), - } -} - -export function createPreemptiveCompactionHook( - ctx: PluginInput, - options?: PreemptiveCompactionOptions -) { - const experimental = options?.experimental - const onBeforeSummarize = options?.onBeforeSummarize - const getModelLimit = options?.getModelLimit - // Preemptive compaction is now enabled by default. - // Backward compatibility: explicit false in experimental config disables the hook. - const explicitlyDisabled = experimental?.preemptive_compaction === false - const threshold = experimental?.preemptive_compaction_threshold ?? DEFAULT_THRESHOLD - - if (explicitlyDisabled) { - return { event: async () => {} } - } - - const state = createState() - - const checkAndTriggerCompaction = async ( - sessionID: string, - lastAssistant: MessageInfo - ): Promise => { - if (state.compactionInProgress.has(sessionID)) return - - const lastCompaction = state.lastCompactionTime.get(sessionID) ?? 0 - if (Date.now() - lastCompaction < COMPACTION_COOLDOWN_MS) return - - if (lastAssistant.summary === true) return - - const tokens = lastAssistant.tokens - if (!tokens) return - - const modelID = lastAssistant.modelID ?? "" - const providerID = lastAssistant.providerID ?? "" - - if (!isSupportedModel(modelID)) { - log("[preemptive-compaction] skipping unsupported model", { modelID }) - return - } - - const configLimit = getModelLimit?.(providerID, modelID) - const contextLimit = configLimit ?? CLAUDE_DEFAULT_CONTEXT_LIMIT - const totalUsed = tokens.input + tokens.cache.read + tokens.output - - if (totalUsed < MIN_TOKENS_FOR_COMPACTION) return - - const usageRatio = totalUsed / contextLimit - - log("[preemptive-compaction] checking", { - sessionID, - totalUsed, - contextLimit, - usageRatio: usageRatio.toFixed(2), - threshold, - }) - - if (usageRatio < threshold) return - - state.compactionInProgress.add(sessionID) - state.lastCompactionTime.set(sessionID, Date.now()) - - if (!providerID || !modelID) { - state.compactionInProgress.delete(sessionID) - return - } - - await ctx.client.tui - .showToast({ - body: { - title: "Preemptive Compaction", - message: `Context at ${(usageRatio * 100).toFixed(0)}% - compacting to prevent overflow...`, - variant: "warning", - duration: 3000, - }, - }) - .catch(() => {}) - - log("[preemptive-compaction] triggering compaction", { sessionID, usageRatio }) - - try { - if (onBeforeSummarize) { - await onBeforeSummarize({ - sessionID, - providerID, - modelID, - usageRatio, - directory: ctx.directory, - }) - } - - const summarizeBody = { providerID, modelID, auto: true } - await ctx.client.session.summarize({ - path: { id: sessionID }, - body: summarizeBody as never, - query: { directory: ctx.directory }, - }) - - await ctx.client.tui - .showToast({ - body: { - title: "Compaction Complete", - message: "Session compacted successfully. Resuming...", - variant: "success", - duration: 2000, - }, - }) - .catch(() => {}) - - state.compactionInProgress.delete(sessionID) - return - } catch (err) { - log("[preemptive-compaction] compaction failed", { sessionID, error: err }) - } finally { - state.compactionInProgress.delete(sessionID) - } - } - - const eventHandler = async ({ event }: { event: { type: string; properties?: unknown } }) => { - const props = event.properties as Record | undefined - - if (event.type === "session.deleted") { - const sessionInfo = props?.info as { id?: string } | undefined - if (sessionInfo?.id) { - state.lastCompactionTime.delete(sessionInfo.id) - state.compactionInProgress.delete(sessionInfo.id) - } - return - } - - if (event.type === "message.updated") { - const info = props?.info as MessageInfo | undefined - if (!info) return - - if (info.role !== "assistant" || !info.finish) return - - const sessionID = info.sessionID - if (!sessionID) return - - await checkAndTriggerCompaction(sessionID, info) - return - } - - if (event.type === "session.idle") { - const sessionID = props?.sessionID as string | undefined - if (!sessionID) return - - try { - const resp = await ctx.client.session.messages({ - path: { id: sessionID }, - query: { directory: ctx.directory }, - }) - - const messages = (resp.data ?? resp) as MessageWrapper[] - const assistants = messages - .filter((m) => m.info.role === "assistant") - .map((m) => m.info) - - if (assistants.length === 0) return - - const lastAssistant = assistants[assistants.length - 1] - - if (!lastAssistant.providerID || !lastAssistant.modelID) { - const messageDir = getMessageDir(sessionID) - const storedMessage = messageDir ? findNearestMessageWithFields(messageDir) : null - if (storedMessage?.model?.providerID && storedMessage?.model?.modelID) { - lastAssistant.providerID = storedMessage.model.providerID - lastAssistant.modelID = storedMessage.model.modelID - log("[preemptive-compaction] using stored message model info", { - sessionID, - providerID: lastAssistant.providerID, - modelID: lastAssistant.modelID, - }) - } - } - - await checkAndTriggerCompaction(sessionID, lastAssistant) - } catch {} - } - } - - return { - event: eventHandler, - } -} diff --git a/src/hooks/preemptive-compaction/types.ts b/src/hooks/preemptive-compaction/types.ts deleted file mode 100644 index 45a09364e..000000000 --- a/src/hooks/preemptive-compaction/types.ts +++ /dev/null @@ -1,16 +0,0 @@ -export interface PreemptiveCompactionState { - lastCompactionTime: Map - compactionInProgress: Set -} - -export interface TokenInfo { - input: number - output: number - reasoning: number - cache: { read: number; write: number } -} - -export interface ModelLimits { - context: number - output: number -} diff --git a/src/index.ts b/src/index.ts index fd152f0a1..78ca94758 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,7 +12,7 @@ import { createThinkModeHook, createClaudeCodeHooksHook, createAnthropicContextWindowLimitRecoveryHook, - createPreemptiveCompactionHook, + createCompactionContextInjector, createRulesInjectorHook, createBackgroundNotificationHook, @@ -145,20 +145,11 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => { ) ? createAnthropicContextWindowLimitRecoveryHook(ctx, { experimental: pluginConfig.experimental, - dcpForCompaction: pluginConfig.experimental?.dcp_for_compaction, }) : null; const compactionContextInjector = isHookEnabled("compaction-context-injector") ? createCompactionContextInjector() : undefined; - const preemptiveCompaction = isHookEnabled("preemptive-compaction") - ? createPreemptiveCompactionHook(ctx, { - experimental: pluginConfig.experimental, - onBeforeSummarize: compactionContextInjector, - getModelLimit: (providerID, modelID) => - getModelLimit(modelCacheState, providerID, modelID), - }) - : null; const rulesInjector = isHookEnabled("rules-injector") ? createRulesInjectorHook(ctx) : null; @@ -420,7 +411,6 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => { await rulesInjector?.event(input); await thinkMode?.event(input); await anthropicContextWindowLimitRecovery?.event(input); - await preemptiveCompaction?.event(input); await agentUsageReminder?.event(input); await interactiveBashSession?.event(input); await ralphLoop?.event(input);