fix(anthropic-auto-compact): run DCP only on compaction failure and retry after pruning (#284)
Make DCP behavior opt-in via new 'dcp_on_compaction_failure' experimental flag (disabled by default). When enabled, Dynamic Context Pruning only executes after summarization fails, then retries compaction. By default, DCP runs before truncation as before. Changes: - Add 'dcp_on_compaction_failure' boolean flag to experimental config (default: false) - Update executor.ts to check flag before running DCP behavior - Add corresponding documentation to all 4 README files (EN, KO, JA, ZH-CN) - Update JSON schema 🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
@@ -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>(),
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>(),
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user