diff --git a/README.md b/README.md index 7a1aa27..dae9525 100644 --- a/README.md +++ b/README.md @@ -60,9 +60,35 @@ DCP uses its own config file: "enabled": true, // Enable debug logging to ~/.config/opencode/logs/dcp/ "debug": false, - // Summary display: "off", "minimal", or "detailed" - "pruningSummary": "detailed", - // Strategies for pruning tokens from chat history + // Notification display: "off", "minimal", or "detailed" + "pruneNotification": "detailed", + // Protect from pruning for message turns + "turnProtection": { + "enabled": false, + "turns": 4 + }, + // LLM-driven context pruning tools + "tools": { + // Shared settings for all prune tools + "settings": { + // Nudge the LLM to use prune tools (every tool results) + "nudgeEnabled": true, + "nudgeFrequency": 10, + // Additional tools to protect from pruning + "protectedTools": [] + }, + // Removes tool content from context without preservation (for completed tasks or noise) + "discard": { + "enabled": true + }, + // Distills key findings into preserved knowledge before removing raw content + "extract": { + "enabled": true, + // Show distillation content as an ignored message notification + "showDistillation": false + } + }, + // Automatic pruning strategies "strategies": { // Remove duplicate tool calls (same tool with same arguments) "deduplication": { @@ -74,40 +100,6 @@ DCP uses its own config file: "supersedeWrites": { "enabled": true }, - // Removes tool content from context without preservation (for completed tasks or noise) - "discardTool": { - "enabled": true, - // Additional tools to protect from pruning - "protectedTools": [], - // Protect from pruning for message turns - "turnProtection": { - "enabled": false, - "turns": 4 - }, - // Nudge the LLM to use the discard tool (every tool results) - "nudge": { - "enabled": true, - "frequency": 10 - } - }, - // Distills key findings into preserved knowledge before removing raw content - "extractTool": { - "enabled": true, - // Additional tools to protect from pruning - "protectedTools": [], - // Protect from pruning for message turns - "turnProtection": { - "enabled": false, - "turns": 4 - }, - // Nudge the LLM to use the extract tool (every tool results) - "nudge": { - "enabled": true, - "frequency": 10 - }, - // Show distillation content as an ignored message notification - "showDistillation": false - }, // (Legacy) Run an LLM to analyze what tool calls are no longer relevant on idle "onIdle": { "enabled": false, @@ -126,12 +118,19 @@ DCP uses its own config file: +### Turn Protection + +When enabled, turn protection prevents tool outputs from being pruned for a configurable number of message turns. This gives the AI time to reference recent tool outputs before they become prunable. Applies to both `discard` and `extract` tools, as well as automatic strategies. + ### Protected Tools By default, these tools are always protected from pruning across all strategies: `task`, `todowrite`, `todoread`, `discard`, `extract`, `batch` -The `protectedTools` arrays in each strategy add to this default list. +The `protectedTools` arrays in each section add to this default list: +- `tools.settings.protectedTools` — Protects tools from the `discard` and `extract` tools +- `strategies.deduplication.protectedTools` — Protects tools from deduplication +- `strategies.onIdle.protectedTools` — Protects tools from on-idle analysis ### Config Precedence diff --git a/index.ts b/index.ts index 770c5a6..d5f60de 100644 --- a/index.ts +++ b/index.ts @@ -27,8 +27,8 @@ const plugin: Plugin = (async (ctx) => { return { "experimental.chat.system.transform": async (_input: unknown, output: { system: string[] }) => { - const discardEnabled = config.strategies.discardTool.enabled - const extractEnabled = config.strategies.extractTool.enabled + const discardEnabled = config.tools.discard.enabled + const extractEnabled = config.tools.extract.enabled let promptName: string if (discardEnabled && extractEnabled) { @@ -51,7 +51,7 @@ const plugin: Plugin = (async (ctx) => { config ), tool: { - ...(config.strategies.discardTool.enabled && { + ...(config.tools.discard.enabled && { discard: createDiscardTool({ client: ctx.client, state, @@ -60,7 +60,7 @@ const plugin: Plugin = (async (ctx) => { workingDirectory: ctx.directory }), }), - ...(config.strategies.extractTool.enabled && { + ...(config.tools.extract.enabled && { extract: createExtractTool({ client: ctx.client, state, @@ -74,8 +74,8 @@ const plugin: Plugin = (async (ctx) => { // Add enabled tools to primary_tools by mutating the opencode config // This works because config is cached and passed by reference const toolsToAdd: string[] = [] - if (config.strategies.discardTool.enabled) toolsToAdd.push("discard") - if (config.strategies.extractTool.enabled) toolsToAdd.push("extract") + if (config.tools.discard.enabled) toolsToAdd.push("discard") + if (config.tools.extract.enabled) toolsToAdd.push("extract") if (toolsToAdd.length > 0) { const existingPrimaryTools = opencodeConfig.experimental?.primary_tools ?? [] diff --git a/lib/config.ts b/lib/config.ts index 1ae9ce3..d15f15f 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -17,51 +17,45 @@ export interface OnIdle { protectedTools: string[] } -export interface PruneToolNudge { +export interface DiscardTool { enabled: boolean - frequency: number } -export interface PruneToolTurnProtection { +export interface ExtractTool { enabled: boolean - turns: number + showDistillation: boolean } -export interface PruneTool { - enabled: boolean +export interface ToolSettings { + nudgeEnabled: boolean + nudgeFrequency: number protectedTools: string[] - turnProtection: PruneToolTurnProtection - nudge: PruneToolNudge } -export interface DiscardTool { - enabled: boolean - protectedTools: string[] - turnProtection: PruneToolTurnProtection - nudge: PruneToolNudge +export interface Tools { + settings: ToolSettings + discard: DiscardTool + extract: ExtractTool } -export interface ExtractTool { +export interface SupersedeWrites { enabled: boolean - protectedTools: string[] - turnProtection: PruneToolTurnProtection - nudge: PruneToolNudge - showDistillation: boolean } -export interface SupersedeWrites { +export interface TurnProtection { enabled: boolean + turns: number } export interface PluginConfig { enabled: boolean debug: boolean - pruningSummary: "off" | "minimal" | "detailed" + pruneNotification: "off" | "minimal" | "detailed" + turnProtection: TurnProtection + tools: Tools strategies: { deduplication: Deduplication onIdle: OnIdle - discardTool: DiscardTool - extractTool: ExtractTool supersedeWrites: SupersedeWrites } } @@ -74,7 +68,20 @@ export const VALID_CONFIG_KEYS = new Set([ 'enabled', 'debug', 'showUpdateToasts', // Deprecated but kept for backwards compatibility - 'pruningSummary', + 'pruneNotification', + 'turnProtection', + 'turnProtection.enabled', + 'turnProtection.turns', + 'tools', + 'tools.settings', + 'tools.settings.nudgeEnabled', + 'tools.settings.nudgeFrequency', + 'tools.settings.protectedTools', + 'tools.discard', + 'tools.discard.enabled', + 'tools.extract', + 'tools.extract.enabled', + 'tools.extract.showDistillation', 'strategies', // strategies.deduplication 'strategies.deduplication', @@ -89,28 +96,7 @@ export const VALID_CONFIG_KEYS = new Set([ 'strategies.onIdle.model', 'strategies.onIdle.showModelErrorToasts', 'strategies.onIdle.strictModelSelection', - 'strategies.onIdle.protectedTools', - // strategies.discardTool - 'strategies.discardTool', - 'strategies.discardTool.enabled', - 'strategies.discardTool.protectedTools', - 'strategies.discardTool.turnProtection', - 'strategies.discardTool.turnProtection.enabled', - 'strategies.discardTool.turnProtection.turns', - 'strategies.discardTool.nudge', - 'strategies.discardTool.nudge.enabled', - 'strategies.discardTool.nudge.frequency', - // strategies.extractTool - 'strategies.extractTool', - 'strategies.extractTool.enabled', - 'strategies.extractTool.protectedTools', - 'strategies.extractTool.turnProtection', - 'strategies.extractTool.turnProtection.enabled', - 'strategies.extractTool.turnProtection.turns', - 'strategies.extractTool.nudge', - 'strategies.extractTool.nudge.enabled', - 'strategies.extractTool.nudge.frequency', - 'strategies.extractTool.showDistillation' + 'strategies.onIdle.protectedTools' ]) // Extract all key paths from a config object for validation @@ -149,10 +135,49 @@ function validateConfigTypes(config: Record): ValidationError[] { if (config.debug !== undefined && typeof config.debug !== 'boolean') { errors.push({ key: 'debug', expected: 'boolean', actual: typeof config.debug }) } - if (config.pruningSummary !== undefined) { + if (config.pruneNotification !== undefined) { const validValues = ['off', 'minimal', 'detailed'] - if (!validValues.includes(config.pruningSummary)) { - errors.push({ key: 'pruningSummary', expected: '"off" | "minimal" | "detailed"', actual: JSON.stringify(config.pruningSummary) }) + if (!validValues.includes(config.pruneNotification)) { + errors.push({ key: 'pruneNotification', expected: '"off" | "minimal" | "detailed"', actual: JSON.stringify(config.pruneNotification) }) + } + } + + // Top-level turnProtection validator + if (config.turnProtection) { + if (config.turnProtection.enabled !== undefined && typeof config.turnProtection.enabled !== 'boolean') { + errors.push({ key: 'turnProtection.enabled', expected: 'boolean', actual: typeof config.turnProtection.enabled }) + } + if (config.turnProtection.turns !== undefined && typeof config.turnProtection.turns !== 'number') { + errors.push({ key: 'turnProtection.turns', expected: 'number', actual: typeof config.turnProtection.turns }) + } + } + + // Tools validators + const tools = config.tools + if (tools) { + if (tools.settings) { + if (tools.settings.nudgeEnabled !== undefined && typeof tools.settings.nudgeEnabled !== 'boolean') { + errors.push({ key: 'tools.settings.nudgeEnabled', expected: 'boolean', actual: typeof tools.settings.nudgeEnabled }) + } + if (tools.settings.nudgeFrequency !== undefined && typeof tools.settings.nudgeFrequency !== 'number') { + errors.push({ key: 'tools.settings.nudgeFrequency', expected: 'number', actual: typeof tools.settings.nudgeFrequency }) + } + if (tools.settings.protectedTools !== undefined && !Array.isArray(tools.settings.protectedTools)) { + errors.push({ key: 'tools.settings.protectedTools', expected: 'string[]', actual: typeof tools.settings.protectedTools }) + } + } + if (tools.discard) { + if (tools.discard.enabled !== undefined && typeof tools.discard.enabled !== 'boolean') { + errors.push({ key: 'tools.discard.enabled', expected: 'boolean', actual: typeof tools.discard.enabled }) + } + } + if (tools.extract) { + if (tools.extract.enabled !== undefined && typeof tools.extract.enabled !== 'boolean') { + errors.push({ key: 'tools.extract.enabled', expected: 'boolean', actual: typeof tools.extract.enabled }) + } + if (tools.extract.showDistillation !== undefined && typeof tools.extract.showDistillation !== 'boolean') { + errors.push({ key: 'tools.extract.showDistillation', expected: 'boolean', actual: typeof tools.extract.showDistillation }) + } } } @@ -186,61 +211,6 @@ function validateConfigTypes(config: Record): ValidationError[] { } } - // discardTool - if (strategies.discardTool) { - if (strategies.discardTool.enabled !== undefined && typeof strategies.discardTool.enabled !== 'boolean') { - errors.push({ key: 'strategies.discardTool.enabled', expected: 'boolean', actual: typeof strategies.discardTool.enabled }) - } - if (strategies.discardTool.protectedTools !== undefined && !Array.isArray(strategies.discardTool.protectedTools)) { - errors.push({ key: 'strategies.discardTool.protectedTools', expected: 'string[]', actual: typeof strategies.discardTool.protectedTools }) - } - if (strategies.discardTool.turnProtection) { - if (strategies.discardTool.turnProtection.enabled !== undefined && typeof strategies.discardTool.turnProtection.enabled !== 'boolean') { - errors.push({ key: 'strategies.discardTool.turnProtection.enabled', expected: 'boolean', actual: typeof strategies.discardTool.turnProtection.enabled }) - } - if (strategies.discardTool.turnProtection.turns !== undefined && typeof strategies.discardTool.turnProtection.turns !== 'number') { - errors.push({ key: 'strategies.discardTool.turnProtection.turns', expected: 'number', actual: typeof strategies.discardTool.turnProtection.turns }) - } - } - if (strategies.discardTool.nudge) { - if (strategies.discardTool.nudge.enabled !== undefined && typeof strategies.discardTool.nudge.enabled !== 'boolean') { - errors.push({ key: 'strategies.discardTool.nudge.enabled', expected: 'boolean', actual: typeof strategies.discardTool.nudge.enabled }) - } - if (strategies.discardTool.nudge.frequency !== undefined && typeof strategies.discardTool.nudge.frequency !== 'number') { - errors.push({ key: 'strategies.discardTool.nudge.frequency', expected: 'number', actual: typeof strategies.discardTool.nudge.frequency }) - } - } - } - - // extractTool - if (strategies.extractTool) { - if (strategies.extractTool.enabled !== undefined && typeof strategies.extractTool.enabled !== 'boolean') { - errors.push({ key: 'strategies.extractTool.enabled', expected: 'boolean', actual: typeof strategies.extractTool.enabled }) - } - if (strategies.extractTool.protectedTools !== undefined && !Array.isArray(strategies.extractTool.protectedTools)) { - errors.push({ key: 'strategies.extractTool.protectedTools', expected: 'string[]', actual: typeof strategies.extractTool.protectedTools }) - } - if (strategies.extractTool.turnProtection) { - if (strategies.extractTool.turnProtection.enabled !== undefined && typeof strategies.extractTool.turnProtection.enabled !== 'boolean') { - errors.push({ key: 'strategies.extractTool.turnProtection.enabled', expected: 'boolean', actual: typeof strategies.extractTool.turnProtection.enabled }) - } - if (strategies.extractTool.turnProtection.turns !== undefined && typeof strategies.extractTool.turnProtection.turns !== 'number') { - errors.push({ key: 'strategies.extractTool.turnProtection.turns', expected: 'number', actual: typeof strategies.extractTool.turnProtection.turns }) - } - } - if (strategies.extractTool.nudge) { - if (strategies.extractTool.nudge.enabled !== undefined && typeof strategies.extractTool.nudge.enabled !== 'boolean') { - errors.push({ key: 'strategies.extractTool.nudge.enabled', expected: 'boolean', actual: typeof strategies.extractTool.nudge.enabled }) - } - if (strategies.extractTool.nudge.frequency !== undefined && typeof strategies.extractTool.nudge.frequency !== 'number') { - errors.push({ key: 'strategies.extractTool.nudge.frequency', expected: 'number', actual: typeof strategies.extractTool.nudge.frequency }) - } - } - if (strategies.extractTool.showDistillation !== undefined && typeof strategies.extractTool.showDistillation !== 'boolean') { - errors.push({ key: 'strategies.extractTool.showDistillation', expected: 'boolean', actual: typeof strategies.extractTool.showDistillation }) - } - } - // supersedeWrites if (strategies.supersedeWrites) { if (strategies.supersedeWrites.enabled !== undefined && typeof strategies.supersedeWrites.enabled !== 'boolean') { @@ -301,7 +271,25 @@ function showConfigValidationWarnings( const defaultConfig: PluginConfig = { enabled: true, debug: false, - pruningSummary: 'detailed', + pruneNotification: 'detailed', + turnProtection: { + enabled: false, + turns: 4 + }, + tools: { + settings: { + nudgeEnabled: true, + nudgeFrequency: 10, + protectedTools: [...DEFAULT_PROTECTED_TOOLS] + }, + discard: { + enabled: true + }, + extract: { + enabled: true, + showDistillation: false + } + }, strategies: { deduplication: { enabled: true, @@ -310,31 +298,6 @@ const defaultConfig: PluginConfig = { supersedeWrites: { enabled: true }, - discardTool: { - enabled: true, - protectedTools: [...DEFAULT_PROTECTED_TOOLS], - turnProtection: { - enabled: false, - turns: 4 - }, - nudge: { - enabled: true, - frequency: 10 - } - }, - extractTool: { - enabled: true, - protectedTools: [...DEFAULT_PROTECTED_TOOLS], - turnProtection: { - enabled: false, - turns: 4 - }, - nudge: { - enabled: true, - frequency: 10 - }, - showDistillation: false - }, onIdle: { enabled: false, protectedTools: [...DEFAULT_PROTECTED_TOOLS], @@ -412,9 +375,35 @@ function createDefaultConfig(): void { "enabled": true, // Enable debug logging to ~/.config/opencode/logs/dcp/ "debug": false, - // Summary display: "off", "minimal", or "detailed" - "pruningSummary": "detailed", - // Strategies for pruning tokens from chat history + // Notification display: "off", "minimal", or "detailed" + "pruneNotification": "detailed", + // Protect from pruning for message turns + "turnProtection": { + "enabled": false, + "turns": 4 + }, + // LLM-driven context pruning tools + "tools": { + // Shared settings for all prune tools + "settings": { + // Nudge the LLM to use prune tools (every tool results) + "nudgeEnabled": true, + "nudgeFrequency": 10, + // Additional tools to protect from pruning + "protectedTools": [] + }, + // Removes tool content from context without preservation (for completed tasks or noise) + "discard": { + "enabled": true + }, + // Distills key findings into preserved knowledge before removing raw content + "extract": { + "enabled": true, + // Show distillation content as an ignored message notification + "showDistillation": false + } + }, + // Automatic pruning strategies "strategies": { // Remove duplicate tool calls (same tool with same arguments) "deduplication": { @@ -426,40 +415,6 @@ function createDefaultConfig(): void { "supersedeWrites": { "enabled": true }, - // Removes tool content from context without preservation (for completed tasks or noise) - "discardTool": { - "enabled": true, - // Additional tools to protect from pruning - "protectedTools": [], - // Protect from pruning for message turns - "turnProtection": { - "enabled": false, - "turns": 4 - }, - // Nudge the LLM to use the discard tool (every tool results) - "nudge": { - "enabled": true, - "frequency": 10 - } - }, - // Distills key findings into preserved knowledge before removing raw content - "extractTool": { - "enabled": true, - // Additional tools to protect from pruning - "protectedTools": [], - // Protect from pruning for message turns - "turnProtection": { - "enabled": false, - "turns": 4 - }, - // Nudge the LLM to use the extract tool (every tool results) - "nudge": { - "enabled": true, - "frequency": 10 - }, - // Show distillation content as an ignored message notification - "showDistillation": false - }, // (Legacy) Run an LLM to analyze what tool calls are no longer relevant on idle "onIdle": { "enabled": false, @@ -531,43 +486,35 @@ function mergeStrategies( ]) ] }, - discardTool: { - enabled: override.discardTool?.enabled ?? base.discardTool.enabled, + supersedeWrites: { + enabled: override.supersedeWrites?.enabled ?? base.supersedeWrites.enabled + } + } +} + +function mergeTools( + base: PluginConfig['tools'], + override?: Partial +): PluginConfig['tools'] { + if (!override) return base + + return { + settings: { + nudgeEnabled: override.settings?.nudgeEnabled ?? base.settings.nudgeEnabled, + nudgeFrequency: override.settings?.nudgeFrequency ?? base.settings.nudgeFrequency, protectedTools: [ ...new Set([ - ...base.discardTool.protectedTools, - ...(override.discardTool?.protectedTools ?? []) + ...base.settings.protectedTools, + ...(override.settings?.protectedTools ?? []) ]) - ], - turnProtection: { - enabled: override.discardTool?.turnProtection?.enabled ?? base.discardTool.turnProtection.enabled, - turns: override.discardTool?.turnProtection?.turns ?? base.discardTool.turnProtection.turns - }, - nudge: { - enabled: override.discardTool?.nudge?.enabled ?? base.discardTool.nudge.enabled, - frequency: override.discardTool?.nudge?.frequency ?? base.discardTool.nudge.frequency - } + ] }, - extractTool: { - enabled: override.extractTool?.enabled ?? base.extractTool.enabled, - protectedTools: [ - ...new Set([ - ...base.extractTool.protectedTools, - ...(override.extractTool?.protectedTools ?? []) - ]) - ], - turnProtection: { - enabled: override.extractTool?.turnProtection?.enabled ?? base.extractTool.turnProtection.enabled, - turns: override.extractTool?.turnProtection?.turns ?? base.extractTool.turnProtection.turns - }, - nudge: { - enabled: override.extractTool?.nudge?.enabled ?? base.extractTool.nudge.enabled, - frequency: override.extractTool?.nudge?.frequency ?? base.extractTool.nudge.frequency - }, - showDistillation: override.extractTool?.showDistillation ?? base.extractTool.showDistillation + discard: { + enabled: override.discard?.enabled ?? base.discard.enabled }, - supersedeWrites: { - enabled: override.supersedeWrites?.enabled ?? base.supersedeWrites.enabled + extract: { + enabled: override.extract?.enabled ?? base.extract.enabled, + showDistillation: override.extract?.showDistillation ?? base.extract.showDistillation } } } @@ -575,6 +522,15 @@ function mergeStrategies( function deepCloneConfig(config: PluginConfig): PluginConfig { return { ...config, + turnProtection: { ...config.turnProtection }, + tools: { + settings: { + ...config.tools.settings, + protectedTools: [...config.tools.settings.protectedTools] + }, + discard: { ...config.tools.discard }, + extract: { ...config.tools.extract } + }, strategies: { deduplication: { ...config.strategies.deduplication, @@ -584,19 +540,6 @@ function deepCloneConfig(config: PluginConfig): PluginConfig { ...config.strategies.onIdle, protectedTools: [...config.strategies.onIdle.protectedTools] }, - discardTool: { - ...config.strategies.discardTool, - protectedTools: [...config.strategies.discardTool.protectedTools], - turnProtection: { ...config.strategies.discardTool.turnProtection }, - nudge: { ...config.strategies.discardTool.nudge } - }, - extractTool: { - ...config.strategies.extractTool, - protectedTools: [...config.strategies.extractTool.protectedTools], - turnProtection: { ...config.strategies.extractTool.turnProtection }, - nudge: { ...config.strategies.extractTool.nudge }, - showDistillation: config.strategies.extractTool.showDistillation - }, supersedeWrites: { ...config.strategies.supersedeWrites } @@ -631,7 +574,12 @@ export function getConfig(ctx: PluginInput): PluginConfig { config = { enabled: result.data.enabled ?? config.enabled, debug: result.data.debug ?? config.debug, - pruningSummary: result.data.pruningSummary ?? config.pruningSummary, + pruneNotification: result.data.pruneNotification ?? config.pruneNotification, + turnProtection: { + enabled: result.data.turnProtection?.enabled ?? config.turnProtection.enabled, + turns: result.data.turnProtection?.turns ?? config.turnProtection.turns + }, + tools: mergeTools(config.tools, result.data.tools as any), strategies: mergeStrategies(config.strategies, result.data.strategies as any) } } @@ -662,7 +610,12 @@ export function getConfig(ctx: PluginInput): PluginConfig { config = { enabled: result.data.enabled ?? config.enabled, debug: result.data.debug ?? config.debug, - pruningSummary: result.data.pruningSummary ?? config.pruningSummary, + pruneNotification: result.data.pruneNotification ?? config.pruneNotification, + turnProtection: { + enabled: result.data.turnProtection?.enabled ?? config.turnProtection.enabled, + turns: result.data.turnProtection?.turns ?? config.turnProtection.turns + }, + tools: mergeTools(config.tools, result.data.tools as any), strategies: mergeStrategies(config.strategies, result.data.strategies as any) } } @@ -690,7 +643,12 @@ export function getConfig(ctx: PluginInput): PluginConfig { config = { enabled: result.data.enabled ?? config.enabled, debug: result.data.debug ?? config.debug, - pruningSummary: result.data.pruningSummary ?? config.pruningSummary, + pruneNotification: result.data.pruneNotification ?? config.pruneNotification, + turnProtection: { + enabled: result.data.turnProtection?.enabled ?? config.turnProtection.enabled, + turns: result.data.turnProtection?.turns ?? config.turnProtection.turns + }, + tools: mergeTools(config.tools, result.data.tools as any), strategies: mergeStrategies(config.strategies, result.data.strategies as any) } } diff --git a/lib/messages/prune.ts b/lib/messages/prune.ts index 0257afc..05afc0e 100644 --- a/lib/messages/prune.ts +++ b/lib/messages/prune.ts @@ -9,8 +9,8 @@ import { UserMessage } from "@opencode-ai/sdk" const PRUNED_TOOL_INPUT_REPLACEMENT = '[Input removed to save context]' const PRUNED_TOOL_OUTPUT_REPLACEMENT = '[Output removed to save context - information superseded or no longer needed]' const getNudgeString = (config: PluginConfig): string => { - const discardEnabled = config.strategies.discardTool.enabled - const extractEnabled = config.strategies.extractTool.enabled + const discardEnabled = config.tools.discard.enabled + const extractEnabled = config.tools.extract.enabled if (discardEnabled && extractEnabled) { return loadPrompt("nudge/nudge-both") @@ -28,8 +28,8 @@ ${content} ` const getCooldownMessage = (config: PluginConfig): string => { - const discardEnabled = config.strategies.discardTool.enabled - const extractEnabled = config.strategies.extractTool.enabled + const discardEnabled = config.tools.discard.enabled + const extractEnabled = config.tools.extract.enabled let toolName: string if (discardEnabled && extractEnabled) { @@ -61,10 +61,7 @@ const buildPrunableToolsList = ( if (state.prune.toolIds.includes(toolCallId)) { return } - const allProtectedTools = [ - ...config.strategies.discardTool.protectedTools, - ...config.strategies.extractTool.protectedTools - ] + const allProtectedTools = config.tools.settings.protectedTools if (allProtectedTools.includes(toolParameterEntry.tool)) { return } @@ -92,7 +89,7 @@ export const insertPruneToolContext = ( logger: Logger, messages: WithParts[] ): void => { - if (!config.strategies.discardTool.enabled && !config.strategies.extractTool.enabled) { + if (!config.tools.discard.enabled && !config.tools.extract.enabled) { return } @@ -115,13 +112,7 @@ export const insertPruneToolContext = ( logger.debug("prunable-tools: \n" + prunableToolsList) let nudgeString = "" - // TODO: Using Math.min() means the lower frequency dominates when both tools are enabled. - // Consider using separate counters for each tool's nudge, or documenting this behavior. - const nudgeFrequency = Math.min( - config.strategies.discardTool.nudge.frequency, - config.strategies.extractTool.nudge.frequency - ) - if (state.nudgeCounter >= nudgeFrequency) { + if (config.tools.settings.nudgeEnabled && state.nudgeCounter >= config.tools.settings.nudgeFrequency) { logger.info("Inserting prune nudge message") nudgeString = "\n" + getNudgeString(config) } diff --git a/lib/state/tool-cache.ts b/lib/state/tool-cache.ts index 6e2650b..bc94efe 100644 --- a/lib/state/tool-cache.ts +++ b/lib/state/tool-cache.ts @@ -35,22 +35,15 @@ export async function syncToolCache( continue } - const turnProtectionEnabled = config.strategies.discardTool.turnProtection.enabled || - config.strategies.extractTool.turnProtection.enabled - const turnProtectionTurns = Math.max( - config.strategies.discardTool.turnProtection.turns, - config.strategies.extractTool.turnProtection.turns - ) + const turnProtectionEnabled = config.turnProtection.enabled + const turnProtectionTurns = config.turnProtection.turns const isProtectedByTurn = turnProtectionEnabled && turnProtectionTurns > 0 && (state.currentTurn - turnCounter) < turnProtectionTurns state.lastToolPrune = part.tool === "discard" || part.tool === "extract" - const allProtectedTools = [ - ...config.strategies.discardTool.protectedTools, - ...config.strategies.extractTool.protectedTools - ] + const allProtectedTools = config.tools.settings.protectedTools if (part.tool === "discard" || part.tool === "extract") { state.nudgeCounter = 0 diff --git a/lib/strategies/tools.ts b/lib/strategies/tools.ts index b11eb2e..31502f8 100644 --- a/lib/strategies/tools.ts +++ b/lib/strategies/tools.ts @@ -76,10 +76,7 @@ async function executePruneOperation( logger.debug("Rejecting prune request - ID not in cache (turn-protected or hallucinated)", { index, id }) return "Invalid IDs provided. Only use numeric IDs from the list." } - const allProtectedTools = [ - ...config.strategies.discardTool.protectedTools, - ...config.strategies.extractTool.protectedTools - ] + const allProtectedTools = config.tools.settings.protectedTools if (allProtectedTools.includes(metadata.tool)) { logger.debug("Rejecting prune request - protected tool", { index, id, tool: metadata.tool }) return "Invalid IDs provided. Only use numeric IDs from the list." @@ -114,7 +111,7 @@ async function executePruneOperation( workingDirectory ) - if (distillation && config.strategies.extractTool.showDistillation) { + if (distillation && config.tools.extract.showDistillation) { await sendDistillationNotification( client, logger, diff --git a/lib/ui/notification.ts b/lib/ui/notification.ts index 079d582..60844e9 100644 --- a/lib/ui/notification.ts +++ b/lib/ui/notification.ts @@ -70,11 +70,11 @@ export async function sendUnifiedNotification( return false } - if (config.pruningSummary === 'off') { + if (config.pruneNotification === 'off') { return false } - const message = config.pruningSummary === 'minimal' + const message = config.pruneNotification === 'minimal' ? buildMinimalMessage(state, reason) : buildDetailedMessage(state, reason, pruneToolIds, toolMetadata, workingDirectory)