diff --git a/README.ja.md b/README.ja.md index 2bf44c2a..031fc617 100644 --- a/README.ja.md +++ b/README.ja.md @@ -846,7 +846,8 @@ OpenCode でサポートされるすべての LSP 構成およびカスタム設 "experimental": { "aggressive_truncation": true, "auto_resume": true, - "truncate_all_tool_outputs": false + "truncate_all_tool_outputs": false, + "dcp_on_compaction_failure": true } } ``` @@ -856,6 +857,7 @@ OpenCode でサポートされるすべての LSP 構成およびカスタム設 | `aggressive_truncation` | `false` | トークン制限を超えた場合、ツール出力を積極的に切り詰めて制限内に収めます。デフォルトの切り詰めより積極的です。不十分な場合は要約/復元にフォールバックします。 | | `auto_resume` | `false` | thinking block エラーや thinking disabled violation からの回復成功後、自動的にセッションを再開します。最後のユーザーメッセージを抽出して続行します。 | | `truncate_all_tool_outputs` | `true` | プロンプトが長くなりすぎるのを防ぐため、コンテキストウィンドウの使用状況に基づいてすべてのツール出力を動的に切り詰めます。完全なツール出力が必要な場合は`false`に設定して無効化します。 | +| `dcp_on_compaction_failure` | `false` | 有効にすると、DCP(Dynamic Context Pruning)はコンパクション(要約)が失敗した後にのみ実行され、その後コンパクションを再試行します。通常時は DCP は実行されません。トークン制限に達した際によりスマートな回復が必要な場合は有効にしてください。 | **警告**:これらの機能は実験的であり、予期しない動作を引き起こす可能性があります。影響を理解した場合にのみ有効にしてください。 diff --git a/README.ko.md b/README.ko.md index 38040922..3343c619 100644 --- a/README.ko.md +++ b/README.ko.md @@ -840,7 +840,8 @@ OpenCode 에서 지원하는 모든 LSP 구성 및 커스텀 설정 (opencode.js "experimental": { "aggressive_truncation": true, "auto_resume": true, - "truncate_all_tool_outputs": false + "truncate_all_tool_outputs": false, + "dcp_on_compaction_failure": true } } ``` @@ -850,6 +851,7 @@ OpenCode 에서 지원하는 모든 LSP 구성 및 커스텀 설정 (opencode.js | `aggressive_truncation` | `false` | 토큰 제한을 초과하면 도구 출력을 공격적으로 잘라내어 제한 내에 맞춥니다. 기본 truncation보다 더 공격적입니다. 부족하면 요약/복구로 fallback합니다. | | `auto_resume` | `false` | thinking block 에러나 thinking disabled violation으로부터 성공적으로 복구한 후 자동으로 세션을 재개합니다. 마지막 사용자 메시지를 추출하여 계속합니다. | | `truncate_all_tool_outputs` | `true` | 프롬프트가 너무 길어지는 것을 방지하기 위해 컨텍스트 윈도우 사용량에 따라 모든 도구 출력을 동적으로 잘라냅니다. 전체 도구 출력이 필요한 경우 `false`로 설정하여 비활성화하세요. | +| `dcp_on_compaction_failure` | `false` | 활성화하면, DCP(Dynamic Context Pruning)가 compaction(요약) 실패 후에만 실행되고 compaction을 재시도합니다. DCP는 평소에는 실행되지 않습니다. 토큰 제한에 도달했을 때 더 스마트한 복구를 원하면 활성화하세요. | **경고**: 이 기능들은 실험적이며 예상치 못한 동작을 유발할 수 있습니다. 의미를 이해한 경우에만 활성화하세요. diff --git a/README.md b/README.md index 6e82ed6b..5044c280 100644 --- a/README.md +++ b/README.md @@ -912,7 +912,8 @@ Opt-in experimental features that may change or be removed in future versions. U "experimental": { "aggressive_truncation": true, "auto_resume": true, - "truncate_all_tool_outputs": false + "truncate_all_tool_outputs": false, + "dcp_on_compaction_failure": true } } ``` @@ -922,6 +923,7 @@ Opt-in experimental features that may change or be removed in future versions. U | `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. | | `truncate_all_tool_outputs` | `true` | Dynamically truncates ALL tool outputs based on context window usage to prevent prompts from becoming too long. Disable by setting to `false` if you need full tool outputs. | +| `dcp_on_compaction_failure` | `false` | When enabled, Dynamic Context Pruning (DCP) runs only after compaction (summarize) fails, then retries compaction. DCP does NOT run during normal operations. Enable this for smarter recovery when hitting token limits. | **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 4a8c9000..ea8254b1 100644 --- a/README.zh-cn.md +++ b/README.zh-cn.md @@ -846,7 +846,8 @@ Oh My OpenCode 送你重构工具(重命名、代码操作)。 "experimental": { "aggressive_truncation": true, "auto_resume": true, - "truncate_all_tool_outputs": false + "truncate_all_tool_outputs": false, + "dcp_on_compaction_failure": true } } ``` @@ -856,6 +857,7 @@ Oh My OpenCode 送你重构工具(重命名、代码操作)。 | `aggressive_truncation` | `false` | 超出 token 限制时,激进地截断工具输出以适应限制。比默认截断更激进。不够的话会回退到摘要/恢复。 | | `auto_resume` | `false` | 从 thinking block 错误或 thinking disabled violation 成功恢复后,自动恢复会话。提取最后一条用户消息继续执行。 | | `truncate_all_tool_outputs` | `true` | 为防止提示过长,根据上下文窗口使用情况动态截断所有工具输出。如需完整工具输出,设置为 `false` 禁用此功能。 | +| `dcp_on_compaction_failure` | `false` | 启用后,DCP(动态上下文剪枝)仅在压缩(摘要)失败后运行,然后重试压缩。平时 DCP 不会运行。当达到 token 限制时需要更智能的恢复请启用此选项。 | **警告**:这些功能是实验性的,可能会导致意外行为。只有在理解其影响的情况下才启用。 diff --git a/assets/oh-my-opencode.schema.json b/assets/oh-my-opencode.schema.json index 84010f1a..200889ce 100644 --- a/assets/oh-my-opencode.schema.json +++ b/assets/oh-my-opencode.schema.json @@ -1486,6 +1486,9 @@ } } } + }, + "dcp_on_compaction_failure": { + "type": "boolean" } } }, diff --git a/src/config/schema.ts b/src/config/schema.ts index db95fb28..5557c463 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -162,6 +162,8 @@ export const ExperimentalConfigSchema = z.object({ truncate_all_tool_outputs: z.boolean().default(true), /** Dynamic context pruning configuration */ dynamic_context_pruning: DynamicContextPruningConfigSchema.optional(), + /** Run DCP only when compaction (summarize) fails, then retry compaction (default: false) */ + dcp_on_compaction_failure: z.boolean().optional(), }) export const OhMyOpenCodeConfigSchema = z.object({ diff --git a/src/hooks/anthropic-auto-compact/executor.test.ts b/src/hooks/anthropic-auto-compact/executor.test.ts index 054f2638..75861aa9 100644 --- a/src/hooks/anthropic-auto-compact/executor.test.ts +++ b/src/hooks/anthropic-auto-compact/executor.test.ts @@ -17,6 +17,7 @@ describe("executeCompact lock management", () => { retryStateBySession: new Map(), fallbackStateBySession: new Map(), truncateStateBySession: new Map(), + dcpStateBySession: new Map(), emptyContentAttemptBySession: new Map(), compactionInProgress: new Set(), } diff --git a/src/hooks/anthropic-auto-compact/executor.ts b/src/hooks/anthropic-auto-compact/executor.ts index 4e98f8f6..2ec7dbaf 100644 --- a/src/hooks/anthropic-auto-compact/executor.ts +++ b/src/hooks/anthropic-auto-compact/executor.ts @@ -1,5 +1,6 @@ import type { AutoCompactState, + DcpState, FallbackState, RetryState, TruncateState, @@ -90,6 +91,18 @@ 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; +} + async function getLastMessagePair( sessionID: string, client: Client, @@ -185,6 +198,7 @@ function clearSessionState( autoCompactState.retryStateBySession.delete(sessionID); autoCompactState.fallbackStateBySession.delete(sessionID); autoCompactState.truncateStateBySession.delete(sessionID); + autoCompactState.dcpStateBySession.delete(sessionID); autoCompactState.emptyContentAttemptBySession.delete(sessionID); autoCompactState.compactionInProgress.delete(sessionID); } @@ -384,40 +398,6 @@ export async function executeCompact( } } - if (experimental?.dynamic_context_pruning?.enabled) { - log("[auto-compact] attempting DCP before truncation", { sessionID }); - - try { - const pruningResult = await executeDynamicContextPruning( - sessionID, - experimental.dynamic_context_pruning, - client - ); - - if (pruningResult.itemsPruned > 0) { - log("[auto-compact] DCP successful, resuming", { - itemsPruned: pruningResult.itemsPruned, - tokensSaved: pruningResult.totalTokensSaved, - }); - - setTimeout(async () => { - try { - await (client as Client).session.prompt_async({ - path: { sessionID }, - body: { parts: [{ type: "text", text: "Continue" }] }, - query: { directory }, - }); - } catch {} - }, 500); - return; - } - } catch (error) { - log("[auto-compact] DCP failed, continuing to truncation", { - error: String(error), - }); - } - } - let skipSummarize = false; if (truncateState.truncateAttempt < TRUNCATE_CONFIG.maxTruncateAttempts) { @@ -602,6 +582,67 @@ export async function executeCompact( } } + // Try DCP after summarize fails - only once per compaction cycle + const dcpState = getOrCreateDcpState(autoCompactState, sessionID); + if (experimental?.dcp_on_compaction_failure && !dcpState.attempted) { + dcpState.attempted = true; + log("[auto-compact] attempting DCP after summarize failed", { sessionID }); + + const dcpConfig = experimental.dynamic_context_pruning ?? { + enabled: true, + notification: "detailed" as const, + protected_tools: ["task", "todowrite", "todoread", "lsp_rename", "lsp_code_action_resolve"], + }; + + try { + const pruningResult = await executeDynamicContextPruning( + sessionID, + dcpConfig, + client + ); + + if (pruningResult.itemsPruned > 0) { + dcpState.itemsPruned = pruningResult.itemsPruned; + log("[auto-compact] DCP successful, retrying compaction", { + 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). Retrying compaction...`, + variant: "success", + duration: 3000, + }, + }) + .catch(() => {}); + + // Reset retry state to allow compaction to retry summarize + retryState.attempt = 0; + + setTimeout(() => { + executeCompact( + sessionID, + msg, + autoCompactState, + client, + directory, + experimental, + ); + }, 500); + return; + } else { + log("[auto-compact] DCP did not prune any items, continuing to revert", { sessionID }); + } + } catch (error) { + log("[auto-compact] DCP failed, continuing to revert", { + error: String(error), + }); + } + } + const fallbackState = getOrCreateFallbackState(autoCompactState, sessionID); if (fallbackState.revertAttempt < FALLBACK_CONFIG.maxRevertAttempts) { diff --git a/src/hooks/anthropic-auto-compact/index.ts b/src/hooks/anthropic-auto-compact/index.ts index e64ce85b..89049209 100644 --- a/src/hooks/anthropic-auto-compact/index.ts +++ b/src/hooks/anthropic-auto-compact/index.ts @@ -16,6 +16,7 @@ function createAutoCompactState(): AutoCompactState { retryStateBySession: new Map(), fallbackStateBySession: new Map(), truncateStateBySession: new Map(), + dcpStateBySession: new Map(), emptyContentAttemptBySession: new Map(), compactionInProgress: new Set(), } @@ -36,6 +37,7 @@ export function createAnthropicAutoCompactHook(ctx: PluginInput, options?: Anthr autoCompactState.retryStateBySession.delete(sessionInfo.id) autoCompactState.fallbackStateBySession.delete(sessionInfo.id) autoCompactState.truncateStateBySession.delete(sessionInfo.id) + autoCompactState.dcpStateBySession.delete(sessionInfo.id) autoCompactState.emptyContentAttemptBySession.delete(sessionInfo.id) autoCompactState.compactionInProgress.delete(sessionInfo.id) } @@ -148,6 +150,6 @@ export function createAnthropicAutoCompactHook(ctx: PluginInput, options?: Anthr } } -export type { AutoCompactState, FallbackState, ParsedTokenLimitError, TruncateState } from "./types" +export type { AutoCompactState, DcpState, FallbackState, ParsedTokenLimitError, TruncateState } from "./types" export { parseAnthropicTokenLimitError } from "./parser" export { executeCompact, getLastAssistant } from "./executor" diff --git a/src/hooks/anthropic-auto-compact/types.ts b/src/hooks/anthropic-auto-compact/types.ts index c97af58d..ae62e46e 100644 --- a/src/hooks/anthropic-auto-compact/types.ts +++ b/src/hooks/anthropic-auto-compact/types.ts @@ -23,12 +23,18 @@ export interface TruncateState { lastTruncatedPartId?: string } +export interface DcpState { + attempted: boolean + itemsPruned: number +} + export interface AutoCompactState { pendingCompact: Set errorDataBySession: Map retryStateBySession: Map fallbackStateBySession: Map truncateStateBySession: Map + dcpStateBySession: Map emptyContentAttemptBySession: Map compactionInProgress: Set }