Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
```
Expand All @@ -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 は実行されません。トークン制限に達した際によりスマートな回復が必要な場合は有効にしてください。 |

**警告**:これらの機能は実験的であり、予期しない動作を引き起こす可能性があります。影響を理解した場合にのみ有効にしてください。

Expand Down
4 changes: 3 additions & 1 deletion README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
```
Expand All @@ -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는 평소에는 실행되지 않습니다. 토큰 제한에 도달했을 때 더 스마트한 복구를 원하면 활성화하세요. |

**경고**: 이 기능들은 실험적이며 예상치 못한 동작을 유발할 수 있습니다. 의미를 이해한 경우에만 활성화하세요.

Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
```
Expand All @@ -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.

Expand Down
4 changes: 3 additions & 1 deletion README.zh-cn.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
```
Expand All @@ -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 限制时需要更智能的恢复请启用此选项。 |

**警告**:这些功能是实验性的,可能会导致意外行为。只有在理解其影响的情况下才启用。

Expand Down
3 changes: 3 additions & 0 deletions assets/oh-my-opencode.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1486,6 +1486,9 @@
}
}
}
},
"dcp_on_compaction_failure": {
"type": "boolean"
}
}
},
Expand Down
2 changes: 2 additions & 0 deletions src/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
1 change: 1 addition & 0 deletions src/hooks/anthropic-auto-compact/executor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>(),
}
Expand Down
109 changes: 75 additions & 34 deletions src/hooks/anthropic-auto-compact/executor.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {
AutoCompactState,
DcpState,
FallbackState,
RetryState,
TruncateState,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 3 additions & 1 deletion src/hooks/anthropic-auto-compact/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>(),
}
Expand All @@ -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)
}
Expand Down Expand Up @@ -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"
6 changes: 6 additions & 0 deletions src/hooks/anthropic-auto-compact/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,18 @@ export interface TruncateState {
lastTruncatedPartId?: string
}

export interface DcpState {
attempted: boolean
itemsPruned: number
}

export interface AutoCompactState {
pendingCompact: Set<string>
errorDataBySession: Map<string, ParsedTokenLimitError>
retryStateBySession: Map<string, RetryState>
fallbackStateBySession: Map<string, FallbackState>
truncateStateBySession: Map<string, TruncateState>
dcpStateBySession: Map<string, DcpState>
emptyContentAttemptBySession: Map<string, number>
compactionInProgress: Set<string>
}
Expand Down