Skip to content

Commit 8c420f0

Browse files
authored
Merge pull request #234 from Opencode-DCP/dev
merge dev into master
2 parents 60bb054 + 7216dff commit 8c420f0

File tree

11 files changed

+377
-4
lines changed

11 files changed

+377
-4
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ DCP uses its own config file:
7575
"enabled": false,
7676
"turns": 4,
7777
},
78+
// Protect file operations from pruning via glob patterns
79+
// Patterns match tool parameters.filePath (e.g. read/write/edit)
80+
"protectedFilePatterns": [],
7881
// LLM-driven context pruning tools
7982
"tools": {
8083
// Shared settings for all prune tools
@@ -141,6 +144,10 @@ Each level overrides the previous, so project settings take priority over config
141144

142145
Restart OpenCode after making config changes.
143146

147+
## Limitations
148+
149+
**Subagents** — DCP is disabled for subagents. Subagents are not designed to be token efficient; what matters is that the final message returned to the main agent is a concise summary of findings. DCP's pruning could interfere with this summarization behavior.
150+
144151
## License
145152

146153
MIT

dcp.schema.json

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"$id": "https://raw.githubusercontent.com/Opencode-DCP/opencode-dynamic-context-pruning/main/dcp.schema.json",
4+
"title": "DCP Plugin Configuration",
5+
"description": "Configuration schema for the OpenCode Dynamic Context Pruning plugin",
6+
"type": "object",
7+
"additionalProperties": false,
8+
"properties": {
9+
"$schema": {
10+
"type": "string",
11+
"description": "JSON Schema reference for IDE autocomplete"
12+
},
13+
"enabled": {
14+
"type": "boolean",
15+
"default": true,
16+
"description": "Enable or disable the DCP plugin"
17+
},
18+
"debug": {
19+
"type": "boolean",
20+
"default": false,
21+
"description": "Enable debug logging"
22+
},
23+
"pruneNotification": {
24+
"type": "string",
25+
"enum": ["off", "minimal", "detailed"],
26+
"default": "detailed",
27+
"description": "Level of notification shown when pruning occurs"
28+
},
29+
"turnProtection": {
30+
"type": "object",
31+
"description": "Protect recent tool outputs from being pruned",
32+
"additionalProperties": false,
33+
"properties": {
34+
"enabled": {
35+
"type": "boolean",
36+
"default": false,
37+
"description": "Enable turn-based protection"
38+
},
39+
"turns": {
40+
"type": "number",
41+
"default": 4,
42+
"description": "Number of recent turns to protect from pruning"
43+
}
44+
}
45+
},
46+
"protectedFilePatterns": {
47+
"type": "array",
48+
"items": {
49+
"type": "string"
50+
},
51+
"default": [],
52+
"description": "Glob patterns for files that should be protected from pruning (e.g., '**/*.config.ts')"
53+
},
54+
"tools": {
55+
"type": "object",
56+
"description": "Configuration for pruning tools",
57+
"additionalProperties": false,
58+
"properties": {
59+
"settings": {
60+
"type": "object",
61+
"description": "General tool settings",
62+
"additionalProperties": false,
63+
"properties": {
64+
"nudgeEnabled": {
65+
"type": "boolean",
66+
"default": true,
67+
"description": "Enable nudge reminders to prune context"
68+
},
69+
"nudgeFrequency": {
70+
"type": "number",
71+
"default": 10,
72+
"description": "Frequency of nudge reminders (in turns)"
73+
},
74+
"protectedTools": {
75+
"type": "array",
76+
"items": {
77+
"type": "string"
78+
},
79+
"default": [
80+
"task",
81+
"todowrite",
82+
"todoread",
83+
"discard",
84+
"extract",
85+
"batch",
86+
"write",
87+
"edit"
88+
],
89+
"description": "Tool names that should be protected from automatic pruning"
90+
}
91+
}
92+
},
93+
"discard": {
94+
"type": "object",
95+
"description": "Configuration for the discard tool",
96+
"additionalProperties": false,
97+
"properties": {
98+
"enabled": {
99+
"type": "boolean",
100+
"default": true,
101+
"description": "Enable the discard tool"
102+
}
103+
}
104+
},
105+
"extract": {
106+
"type": "object",
107+
"description": "Configuration for the extract tool",
108+
"additionalProperties": false,
109+
"properties": {
110+
"enabled": {
111+
"type": "boolean",
112+
"default": true,
113+
"description": "Enable the extract tool"
114+
},
115+
"showDistillation": {
116+
"type": "boolean",
117+
"default": false,
118+
"description": "Show distillation output in the UI"
119+
}
120+
}
121+
}
122+
}
123+
},
124+
"strategies": {
125+
"type": "object",
126+
"description": "Automatic pruning strategies",
127+
"additionalProperties": false,
128+
"properties": {
129+
"deduplication": {
130+
"type": "object",
131+
"description": "Remove duplicate tool outputs",
132+
"additionalProperties": false,
133+
"properties": {
134+
"enabled": {
135+
"type": "boolean",
136+
"default": true,
137+
"description": "Enable deduplication strategy"
138+
},
139+
"protectedTools": {
140+
"type": "array",
141+
"items": {
142+
"type": "string"
143+
},
144+
"default": [
145+
"task",
146+
"todowrite",
147+
"todoread",
148+
"discard",
149+
"extract",
150+
"batch",
151+
"write",
152+
"edit"
153+
],
154+
"description": "Tool names excluded from deduplication"
155+
}
156+
}
157+
},
158+
"supersedeWrites": {
159+
"type": "object",
160+
"description": "Replace older write/edit outputs when new ones target the same file",
161+
"additionalProperties": false,
162+
"properties": {
163+
"enabled": {
164+
"type": "boolean",
165+
"default": false,
166+
"description": "Enable supersede writes strategy"
167+
}
168+
}
169+
},
170+
"purgeErrors": {
171+
"type": "object",
172+
"description": "Remove tool outputs that resulted in errors",
173+
"additionalProperties": false,
174+
"properties": {
175+
"enabled": {
176+
"type": "boolean",
177+
"default": true,
178+
"description": "Enable purge errors strategy"
179+
},
180+
"turns": {
181+
"type": "number",
182+
"default": 4,
183+
"description": "Number of turns after which errors are purged"
184+
},
185+
"protectedTools": {
186+
"type": "array",
187+
"items": {
188+
"type": "string"
189+
},
190+
"default": [
191+
"task",
192+
"todowrite",
193+
"todoread",
194+
"discard",
195+
"extract",
196+
"batch",
197+
"write",
198+
"edit"
199+
],
200+
"description": "Tool names excluded from error purging"
201+
}
202+
}
203+
}
204+
}
205+
}
206+
}
207+
}

lib/config.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export interface PluginConfig {
5050
debug: boolean
5151
pruneNotification: "off" | "minimal" | "detailed"
5252
turnProtection: TurnProtection
53+
protectedFilePatterns: string[]
5354
tools: Tools
5455
strategies: {
5556
deduplication: Deduplication
@@ -79,6 +80,7 @@ export const VALID_CONFIG_KEYS = new Set([
7980
"turnProtection",
8081
"turnProtection.enabled",
8182
"turnProtection.turns",
83+
"protectedFilePatterns",
8284
"tools",
8385
"tools.settings",
8486
"tools.settings.nudgeEnabled",
@@ -151,6 +153,22 @@ function validateConfigTypes(config: Record<string, any>): ValidationError[] {
151153
}
152154
}
153155

156+
if (config.protectedFilePatterns !== undefined) {
157+
if (!Array.isArray(config.protectedFilePatterns)) {
158+
errors.push({
159+
key: "protectedFilePatterns",
160+
expected: "string[]",
161+
actual: typeof config.protectedFilePatterns,
162+
})
163+
} else if (!config.protectedFilePatterns.every((v) => typeof v === "string")) {
164+
errors.push({
165+
key: "protectedFilePatterns",
166+
expected: "string[]",
167+
actual: "non-string entries",
168+
})
169+
}
170+
}
171+
154172
// Top-level turnProtection validator
155173
if (config.turnProtection) {
156174
if (
@@ -371,6 +389,7 @@ const defaultConfig: PluginConfig = {
371389
enabled: false,
372390
turns: 4,
373391
},
392+
protectedFilePatterns: [],
374393
tools: {
375394
settings: {
376395
nudgeEnabled: true,
@@ -469,6 +488,7 @@ function createDefaultConfig(): void {
469488
}
470489

471490
const configContent = `{
491+
"$schema": "https://raw.githubusercontent.com/Opencode-DCP/opencode-dynamic-context-pruning/main/dcp.schema.json",
472492
// Enable or disable the plugin
473493
"enabled": true,
474494
// Enable debug logging to ~/.config/opencode/logs/dcp/
@@ -480,6 +500,9 @@ function createDefaultConfig(): void {
480500
"enabled": false,
481501
"turns": 4
482502
},
503+
// Protect file operations from pruning via glob patterns
504+
// Patterns match tool parameters.filePath (e.g. read/write/edit)
505+
"protectedFilePatterns": [],
483506
// LLM-driven context pruning tools
484507
"tools": {
485508
// Shared settings for all prune tools
@@ -615,6 +638,7 @@ function deepCloneConfig(config: PluginConfig): PluginConfig {
615638
return {
616639
...config,
617640
turnProtection: { ...config.turnProtection },
641+
protectedFilePatterns: [...config.protectedFilePatterns],
618642
tools: {
619643
settings: {
620644
...config.tools.settings,
@@ -670,6 +694,12 @@ export function getConfig(ctx: PluginInput): PluginConfig {
670694
enabled: result.data.turnProtection?.enabled ?? config.turnProtection.enabled,
671695
turns: result.data.turnProtection?.turns ?? config.turnProtection.turns,
672696
},
697+
protectedFilePatterns: [
698+
...new Set([
699+
...config.protectedFilePatterns,
700+
...(result.data.protectedFilePatterns ?? []),
701+
]),
702+
],
673703
tools: mergeTools(config.tools, result.data.tools as any),
674704
strategies: mergeStrategies(config.strategies, result.data.strategies as any),
675705
}
@@ -706,6 +736,12 @@ export function getConfig(ctx: PluginInput): PluginConfig {
706736
enabled: result.data.turnProtection?.enabled ?? config.turnProtection.enabled,
707737
turns: result.data.turnProtection?.turns ?? config.turnProtection.turns,
708738
},
739+
protectedFilePatterns: [
740+
...new Set([
741+
...config.protectedFilePatterns,
742+
...(result.data.protectedFilePatterns ?? []),
743+
]),
744+
],
709745
tools: mergeTools(config.tools, result.data.tools as any),
710746
strategies: mergeStrategies(config.strategies, result.data.strategies as any),
711747
}
@@ -739,6 +775,12 @@ export function getConfig(ctx: PluginInput): PluginConfig {
739775
enabled: result.data.turnProtection?.enabled ?? config.turnProtection.enabled,
740776
turns: result.data.turnProtection?.turns ?? config.turnProtection.turns,
741777
},
778+
protectedFilePatterns: [
779+
...new Set([
780+
...config.protectedFilePatterns,
781+
...(result.data.protectedFilePatterns ?? []),
782+
]),
783+
],
742784
tools: mergeTools(config.tools, result.data.tools as any),
743785
strategies: mergeStrategies(config.strategies, result.data.strategies as any),
744786
}

lib/messages/inject.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { PluginConfig } from "../config"
44
import type { UserMessage } from "@opencode-ai/sdk/v2"
55
import { loadPrompt } from "../prompts"
66
import { extractParameterKey, buildToolIdList, createSyntheticUserMessage } from "./utils"
7+
import { getFilePathFromParameters, isProtectedFilePath } from "../protected-file-patterns"
78
import { getLastUserMessage } from "../shared-utils"
89

910
const getNudgeString = (config: PluginConfig): string => {
@@ -62,6 +63,11 @@ const buildPrunableToolsList = (
6263
return
6364
}
6465

66+
const filePath = getFilePathFromParameters(toolParameterEntry.parameters)
67+
if (isProtectedFilePath(filePath, config.protectedFilePatterns)) {
68+
return
69+
}
70+
6571
const numericId = toolIdList.indexOf(toolCallId)
6672
if (numericId === -1) {
6773
logger.warn(`Tool in cache but not in toolIdList - possible stale entry`, {

0 commit comments

Comments
 (0)