refactor: remove dcp_for_compaction and preemptive_compaction features

- Delete src/hooks/preemptive-compaction/ entirely
- Remove dcp_for_compaction from schema and executor
- Clean up related imports, options, and test code
- Update READMEs to remove experimental options docs
This commit is contained in:
justsisyphus
2026-01-16 10:51:59 +09:00
parent bf28b3e711
commit b933992e36
14 changed files with 32 additions and 418 deletions

View File

@@ -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 からの回復成功後、自動的にセッションを再開します。最後のユーザーメッセージを抽出して続行します。 |
**警告**:これらの機能は実験的であり、予期しない動作を引き起こす可能性があります。影響を理解した場合にのみ有効にしてください。

View File

@@ -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.

View File

@@ -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` | 从思考块错误或禁用思考违规成功恢复后自动恢复会话。提取最后一条用户消息并继续。 |
**警告**:这些功能是实验性的,可能导致意外行为。只有在理解其影响后才启用。

View File

@@ -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([

View File

@@ -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<string>(),
}
@@ -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

View File

@@ -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<void> {
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;

View File

@@ -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<string, ParsedTokenLimitError>(),
retryStateBySession: new Map(),
truncateStateBySession: new Map(),
dcpStateBySession: new Map(),
emptyContentAttemptBySession: new Map(),
compactionInProgress: new Set<string>(),
}
@@ -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<string, unknown> | 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"

View File

@@ -18,17 +18,11 @@ 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>
truncateStateBySession: Map<string, TruncateState>
dcpStateBySession: Map<string, DcpState>
emptyContentAttemptBySession: Map<string, number>
compactionInProgress: Set<string>
}

View File

@@ -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:

View File

@@ -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";

View File

@@ -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

View File

@@ -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> | 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<void> => {
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<string, unknown> | 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,
}
}

View File

@@ -1,16 +0,0 @@
export interface PreemptiveCompactionState {
lastCompactionTime: Map<string, number>
compactionInProgress: Set<string>
}
export interface TokenInfo {
input: number
output: number
reasoning: number
cache: { read: number; write: number }
}
export interface ModelLimits {
context: number
output: number
}

View File

@@ -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);