@tarquinen/opencode-dcp 1.1.0-beta.1 → 1.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +64 -64
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +11 -10
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/config.d.ts +17 -23
  6. package/dist/lib/config.d.ts.map +1 -1
  7. package/dist/lib/config.js +316 -274
  8. package/dist/lib/config.js.map +1 -1
  9. package/dist/lib/hooks.d.ts.map +1 -1
  10. package/dist/lib/hooks.js +3 -0
  11. package/dist/lib/hooks.js.map +1 -1
  12. package/dist/lib/logger.d.ts +17 -0
  13. package/dist/lib/logger.d.ts.map +1 -1
  14. package/dist/lib/logger.js +90 -7
  15. package/dist/lib/logger.js.map +1 -1
  16. package/dist/lib/messages/prune.d.ts.map +1 -1
  17. package/dist/lib/messages/prune.js +37 -28
  18. package/dist/lib/messages/prune.js.map +1 -1
  19. package/dist/lib/messages/utils.js +7 -7
  20. package/dist/lib/model-selector.d.ts +3 -3
  21. package/dist/lib/model-selector.d.ts.map +1 -1
  22. package/dist/lib/model-selector.js +34 -34
  23. package/dist/lib/model-selector.js.map +1 -1
  24. package/dist/lib/prompt.d.ts.map +1 -1
  25. package/dist/lib/prompt.js +36 -24
  26. package/dist/lib/prompt.js.map +1 -1
  27. package/dist/lib/shared-utils.d.ts.map +1 -1
  28. package/dist/lib/shared-utils.js +1 -1
  29. package/dist/lib/shared-utils.js.map +1 -1
  30. package/dist/lib/state/persistence.d.ts.map +1 -1
  31. package/dist/lib/state/persistence.js +4 -7
  32. package/dist/lib/state/persistence.js.map +1 -1
  33. package/dist/lib/state/state.d.ts.map +1 -1
  34. package/dist/lib/state/state.js +7 -5
  35. package/dist/lib/state/state.js.map +1 -1
  36. package/dist/lib/state/tool-cache.d.ts.map +1 -1
  37. package/dist/lib/state/tool-cache.js +7 -13
  38. package/dist/lib/state/tool-cache.js.map +1 -1
  39. package/dist/lib/strategies/deduplication.js +3 -3
  40. package/dist/lib/strategies/deduplication.js.map +1 -1
  41. package/dist/lib/strategies/on-idle.d.ts.map +1 -1
  42. package/dist/lib/strategies/on-idle.js +24 -24
  43. package/dist/lib/strategies/on-idle.js.map +1 -1
  44. package/dist/lib/strategies/supersede-writes.js +4 -4
  45. package/dist/lib/strategies/supersede-writes.js.map +1 -1
  46. package/dist/lib/strategies/tools.d.ts.map +1 -1
  47. package/dist/lib/strategies/tools.js +23 -17
  48. package/dist/lib/strategies/tools.js.map +1 -1
  49. package/dist/lib/strategies/utils.d.ts.map +1 -1
  50. package/dist/lib/strategies/utils.js +20 -9
  51. package/dist/lib/strategies/utils.js.map +1 -1
  52. package/dist/lib/ui/notification.d.ts.map +1 -1
  53. package/dist/lib/ui/notification.js +26 -23
  54. package/dist/lib/ui/notification.js.map +1 -1
  55. package/dist/lib/ui/utils.d.ts.map +1 -1
  56. package/dist/lib/ui/utils.js +9 -9
  57. package/dist/lib/ui/utils.js.map +1 -1
  58. package/package.json +61 -58
@@ -1,59 +1,51 @@
1
- import { readFileSync, writeFileSync, existsSync, mkdirSync, statSync } from 'fs';
2
- import { join, dirname } from 'path';
3
- import { homedir } from 'os';
4
- import { parse } from 'jsonc-parser';
5
- const DEFAULT_PROTECTED_TOOLS = ['task', 'todowrite', 'todoread', 'discard', 'extract', 'batch'];
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, statSync } from "fs";
2
+ import { join, dirname } from "path";
3
+ import { homedir } from "os";
4
+ import { parse } from "jsonc-parser";
5
+ const DEFAULT_PROTECTED_TOOLS = ["task", "todowrite", "todoread", "discard", "extract", "batch"];
6
6
  // Valid config keys for validation against user config
7
7
  export const VALID_CONFIG_KEYS = new Set([
8
8
  // Top-level keys
9
- 'enabled',
10
- 'debug',
11
- 'showUpdateToasts', // Deprecated but kept for backwards compatibility
12
- 'pruningSummary',
13
- 'strategies',
9
+ "enabled",
10
+ "debug",
11
+ "showUpdateToasts", // Deprecated but kept for backwards compatibility
12
+ "pruneNotification",
13
+ "turnProtection",
14
+ "turnProtection.enabled",
15
+ "turnProtection.turns",
16
+ "tools",
17
+ "tools.settings",
18
+ "tools.settings.nudgeEnabled",
19
+ "tools.settings.nudgeFrequency",
20
+ "tools.settings.protectedTools",
21
+ "tools.discard",
22
+ "tools.discard.enabled",
23
+ "tools.extract",
24
+ "tools.extract.enabled",
25
+ "tools.extract.showDistillation",
26
+ "strategies",
14
27
  // strategies.deduplication
15
- 'strategies.deduplication',
16
- 'strategies.deduplication.enabled',
17
- 'strategies.deduplication.protectedTools',
28
+ "strategies.deduplication",
29
+ "strategies.deduplication.enabled",
30
+ "strategies.deduplication.protectedTools",
18
31
  // strategies.supersedeWrites
19
- 'strategies.supersedeWrites',
20
- 'strategies.supersedeWrites.enabled',
32
+ "strategies.supersedeWrites",
33
+ "strategies.supersedeWrites.enabled",
21
34
  // strategies.onIdle
22
- 'strategies.onIdle',
23
- 'strategies.onIdle.enabled',
24
- 'strategies.onIdle.model',
25
- 'strategies.onIdle.showModelErrorToasts',
26
- 'strategies.onIdle.strictModelSelection',
27
- 'strategies.onIdle.protectedTools',
28
- // strategies.discardTool
29
- 'strategies.discardTool',
30
- 'strategies.discardTool.enabled',
31
- 'strategies.discardTool.protectedTools',
32
- 'strategies.discardTool.turnProtection',
33
- 'strategies.discardTool.turnProtection.enabled',
34
- 'strategies.discardTool.turnProtection.turns',
35
- 'strategies.discardTool.nudge',
36
- 'strategies.discardTool.nudge.enabled',
37
- 'strategies.discardTool.nudge.frequency',
38
- // strategies.extractTool
39
- 'strategies.extractTool',
40
- 'strategies.extractTool.enabled',
41
- 'strategies.extractTool.protectedTools',
42
- 'strategies.extractTool.turnProtection',
43
- 'strategies.extractTool.turnProtection.enabled',
44
- 'strategies.extractTool.turnProtection.turns',
45
- 'strategies.extractTool.nudge',
46
- 'strategies.extractTool.nudge.enabled',
47
- 'strategies.extractTool.nudge.frequency',
48
- 'strategies.extractTool.showDistillation'
35
+ "strategies.onIdle",
36
+ "strategies.onIdle.enabled",
37
+ "strategies.onIdle.model",
38
+ "strategies.onIdle.showModelErrorToasts",
39
+ "strategies.onIdle.strictModelSelection",
40
+ "strategies.onIdle.protectedTools",
49
41
  ]);
50
42
  // Extract all key paths from a config object for validation
51
- function getConfigKeyPaths(obj, prefix = '') {
43
+ function getConfigKeyPaths(obj, prefix = "") {
52
44
  const keys = [];
53
45
  for (const key of Object.keys(obj)) {
54
46
  const fullKey = prefix ? `${prefix}.${key}` : key;
55
47
  keys.push(fullKey);
56
- if (obj[key] && typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
48
+ if (obj[key] && typeof obj[key] === "object" && !Array.isArray(obj[key])) {
57
49
  keys.push(...getConfigKeyPaths(obj[key], fullKey));
58
50
  }
59
51
  }
@@ -62,108 +54,174 @@ function getConfigKeyPaths(obj, prefix = '') {
62
54
  // Returns invalid keys found in user config
63
55
  export function getInvalidConfigKeys(userConfig) {
64
56
  const userKeys = getConfigKeyPaths(userConfig);
65
- return userKeys.filter(key => !VALID_CONFIG_KEYS.has(key));
57
+ return userKeys.filter((key) => !VALID_CONFIG_KEYS.has(key));
66
58
  }
67
59
  function validateConfigTypes(config) {
68
60
  const errors = [];
69
61
  // Top-level validators
70
- if (config.enabled !== undefined && typeof config.enabled !== 'boolean') {
71
- errors.push({ key: 'enabled', expected: 'boolean', actual: typeof config.enabled });
62
+ if (config.enabled !== undefined && typeof config.enabled !== "boolean") {
63
+ errors.push({ key: "enabled", expected: "boolean", actual: typeof config.enabled });
72
64
  }
73
- if (config.debug !== undefined && typeof config.debug !== 'boolean') {
74
- errors.push({ key: 'debug', expected: 'boolean', actual: typeof config.debug });
65
+ if (config.debug !== undefined && typeof config.debug !== "boolean") {
66
+ errors.push({ key: "debug", expected: "boolean", actual: typeof config.debug });
75
67
  }
76
- if (config.pruningSummary !== undefined) {
77
- const validValues = ['off', 'minimal', 'detailed'];
78
- if (!validValues.includes(config.pruningSummary)) {
79
- errors.push({ key: 'pruningSummary', expected: '"off" | "minimal" | "detailed"', actual: JSON.stringify(config.pruningSummary) });
68
+ if (config.pruneNotification !== undefined) {
69
+ const validValues = ["off", "minimal", "detailed"];
70
+ if (!validValues.includes(config.pruneNotification)) {
71
+ errors.push({
72
+ key: "pruneNotification",
73
+ expected: '"off" | "minimal" | "detailed"',
74
+ actual: JSON.stringify(config.pruneNotification),
75
+ });
80
76
  }
81
77
  }
82
- // Strategies validators
83
- const strategies = config.strategies;
84
- if (strategies) {
85
- // deduplication
86
- if (strategies.deduplication?.enabled !== undefined && typeof strategies.deduplication.enabled !== 'boolean') {
87
- errors.push({ key: 'strategies.deduplication.enabled', expected: 'boolean', actual: typeof strategies.deduplication.enabled });
78
+ // Top-level turnProtection validator
79
+ if (config.turnProtection) {
80
+ if (config.turnProtection.enabled !== undefined &&
81
+ typeof config.turnProtection.enabled !== "boolean") {
82
+ errors.push({
83
+ key: "turnProtection.enabled",
84
+ expected: "boolean",
85
+ actual: typeof config.turnProtection.enabled,
86
+ });
88
87
  }
89
- if (strategies.deduplication?.protectedTools !== undefined && !Array.isArray(strategies.deduplication.protectedTools)) {
90
- errors.push({ key: 'strategies.deduplication.protectedTools', expected: 'string[]', actual: typeof strategies.deduplication.protectedTools });
88
+ if (config.turnProtection.turns !== undefined &&
89
+ typeof config.turnProtection.turns !== "number") {
90
+ errors.push({
91
+ key: "turnProtection.turns",
92
+ expected: "number",
93
+ actual: typeof config.turnProtection.turns,
94
+ });
91
95
  }
92
- // onIdle
93
- if (strategies.onIdle) {
94
- if (strategies.onIdle.enabled !== undefined && typeof strategies.onIdle.enabled !== 'boolean') {
95
- errors.push({ key: 'strategies.onIdle.enabled', expected: 'boolean', actual: typeof strategies.onIdle.enabled });
96
- }
97
- if (strategies.onIdle.model !== undefined && typeof strategies.onIdle.model !== 'string') {
98
- errors.push({ key: 'strategies.onIdle.model', expected: 'string', actual: typeof strategies.onIdle.model });
99
- }
100
- if (strategies.onIdle.showModelErrorToasts !== undefined && typeof strategies.onIdle.showModelErrorToasts !== 'boolean') {
101
- errors.push({ key: 'strategies.onIdle.showModelErrorToasts', expected: 'boolean', actual: typeof strategies.onIdle.showModelErrorToasts });
96
+ }
97
+ // Tools validators
98
+ const tools = config.tools;
99
+ if (tools) {
100
+ if (tools.settings) {
101
+ if (tools.settings.nudgeEnabled !== undefined &&
102
+ typeof tools.settings.nudgeEnabled !== "boolean") {
103
+ errors.push({
104
+ key: "tools.settings.nudgeEnabled",
105
+ expected: "boolean",
106
+ actual: typeof tools.settings.nudgeEnabled,
107
+ });
102
108
  }
103
- if (strategies.onIdle.strictModelSelection !== undefined && typeof strategies.onIdle.strictModelSelection !== 'boolean') {
104
- errors.push({ key: 'strategies.onIdle.strictModelSelection', expected: 'boolean', actual: typeof strategies.onIdle.strictModelSelection });
109
+ if (tools.settings.nudgeFrequency !== undefined &&
110
+ typeof tools.settings.nudgeFrequency !== "number") {
111
+ errors.push({
112
+ key: "tools.settings.nudgeFrequency",
113
+ expected: "number",
114
+ actual: typeof tools.settings.nudgeFrequency,
115
+ });
105
116
  }
106
- if (strategies.onIdle.protectedTools !== undefined && !Array.isArray(strategies.onIdle.protectedTools)) {
107
- errors.push({ key: 'strategies.onIdle.protectedTools', expected: 'string[]', actual: typeof strategies.onIdle.protectedTools });
117
+ if (tools.settings.protectedTools !== undefined &&
118
+ !Array.isArray(tools.settings.protectedTools)) {
119
+ errors.push({
120
+ key: "tools.settings.protectedTools",
121
+ expected: "string[]",
122
+ actual: typeof tools.settings.protectedTools,
123
+ });
108
124
  }
109
125
  }
110
- // discardTool
111
- if (strategies.discardTool) {
112
- if (strategies.discardTool.enabled !== undefined && typeof strategies.discardTool.enabled !== 'boolean') {
113
- errors.push({ key: 'strategies.discardTool.enabled', expected: 'boolean', actual: typeof strategies.discardTool.enabled });
114
- }
115
- if (strategies.discardTool.protectedTools !== undefined && !Array.isArray(strategies.discardTool.protectedTools)) {
116
- errors.push({ key: 'strategies.discardTool.protectedTools', expected: 'string[]', actual: typeof strategies.discardTool.protectedTools });
126
+ if (tools.discard) {
127
+ if (tools.discard.enabled !== undefined && typeof tools.discard.enabled !== "boolean") {
128
+ errors.push({
129
+ key: "tools.discard.enabled",
130
+ expected: "boolean",
131
+ actual: typeof tools.discard.enabled,
132
+ });
117
133
  }
118
- if (strategies.discardTool.turnProtection) {
119
- if (strategies.discardTool.turnProtection.enabled !== undefined && typeof strategies.discardTool.turnProtection.enabled !== 'boolean') {
120
- errors.push({ key: 'strategies.discardTool.turnProtection.enabled', expected: 'boolean', actual: typeof strategies.discardTool.turnProtection.enabled });
121
- }
122
- if (strategies.discardTool.turnProtection.turns !== undefined && typeof strategies.discardTool.turnProtection.turns !== 'number') {
123
- errors.push({ key: 'strategies.discardTool.turnProtection.turns', expected: 'number', actual: typeof strategies.discardTool.turnProtection.turns });
124
- }
134
+ }
135
+ if (tools.extract) {
136
+ if (tools.extract.enabled !== undefined && typeof tools.extract.enabled !== "boolean") {
137
+ errors.push({
138
+ key: "tools.extract.enabled",
139
+ expected: "boolean",
140
+ actual: typeof tools.extract.enabled,
141
+ });
125
142
  }
126
- if (strategies.discardTool.nudge) {
127
- if (strategies.discardTool.nudge.enabled !== undefined && typeof strategies.discardTool.nudge.enabled !== 'boolean') {
128
- errors.push({ key: 'strategies.discardTool.nudge.enabled', expected: 'boolean', actual: typeof strategies.discardTool.nudge.enabled });
129
- }
130
- if (strategies.discardTool.nudge.frequency !== undefined && typeof strategies.discardTool.nudge.frequency !== 'number') {
131
- errors.push({ key: 'strategies.discardTool.nudge.frequency', expected: 'number', actual: typeof strategies.discardTool.nudge.frequency });
132
- }
143
+ if (tools.extract.showDistillation !== undefined &&
144
+ typeof tools.extract.showDistillation !== "boolean") {
145
+ errors.push({
146
+ key: "tools.extract.showDistillation",
147
+ expected: "boolean",
148
+ actual: typeof tools.extract.showDistillation,
149
+ });
133
150
  }
134
151
  }
135
- // extractTool
136
- if (strategies.extractTool) {
137
- if (strategies.extractTool.enabled !== undefined && typeof strategies.extractTool.enabled !== 'boolean') {
138
- errors.push({ key: 'strategies.extractTool.enabled', expected: 'boolean', actual: typeof strategies.extractTool.enabled });
152
+ }
153
+ // Strategies validators
154
+ const strategies = config.strategies;
155
+ if (strategies) {
156
+ // deduplication
157
+ if (strategies.deduplication?.enabled !== undefined &&
158
+ typeof strategies.deduplication.enabled !== "boolean") {
159
+ errors.push({
160
+ key: "strategies.deduplication.enabled",
161
+ expected: "boolean",
162
+ actual: typeof strategies.deduplication.enabled,
163
+ });
164
+ }
165
+ if (strategies.deduplication?.protectedTools !== undefined &&
166
+ !Array.isArray(strategies.deduplication.protectedTools)) {
167
+ errors.push({
168
+ key: "strategies.deduplication.protectedTools",
169
+ expected: "string[]",
170
+ actual: typeof strategies.deduplication.protectedTools,
171
+ });
172
+ }
173
+ // onIdle
174
+ if (strategies.onIdle) {
175
+ if (strategies.onIdle.enabled !== undefined &&
176
+ typeof strategies.onIdle.enabled !== "boolean") {
177
+ errors.push({
178
+ key: "strategies.onIdle.enabled",
179
+ expected: "boolean",
180
+ actual: typeof strategies.onIdle.enabled,
181
+ });
139
182
  }
140
- if (strategies.extractTool.protectedTools !== undefined && !Array.isArray(strategies.extractTool.protectedTools)) {
141
- errors.push({ key: 'strategies.extractTool.protectedTools', expected: 'string[]', actual: typeof strategies.extractTool.protectedTools });
183
+ if (strategies.onIdle.model !== undefined &&
184
+ typeof strategies.onIdle.model !== "string") {
185
+ errors.push({
186
+ key: "strategies.onIdle.model",
187
+ expected: "string",
188
+ actual: typeof strategies.onIdle.model,
189
+ });
142
190
  }
143
- if (strategies.extractTool.turnProtection) {
144
- if (strategies.extractTool.turnProtection.enabled !== undefined && typeof strategies.extractTool.turnProtection.enabled !== 'boolean') {
145
- errors.push({ key: 'strategies.extractTool.turnProtection.enabled', expected: 'boolean', actual: typeof strategies.extractTool.turnProtection.enabled });
146
- }
147
- if (strategies.extractTool.turnProtection.turns !== undefined && typeof strategies.extractTool.turnProtection.turns !== 'number') {
148
- errors.push({ key: 'strategies.extractTool.turnProtection.turns', expected: 'number', actual: typeof strategies.extractTool.turnProtection.turns });
149
- }
191
+ if (strategies.onIdle.showModelErrorToasts !== undefined &&
192
+ typeof strategies.onIdle.showModelErrorToasts !== "boolean") {
193
+ errors.push({
194
+ key: "strategies.onIdle.showModelErrorToasts",
195
+ expected: "boolean",
196
+ actual: typeof strategies.onIdle.showModelErrorToasts,
197
+ });
150
198
  }
151
- if (strategies.extractTool.nudge) {
152
- if (strategies.extractTool.nudge.enabled !== undefined && typeof strategies.extractTool.nudge.enabled !== 'boolean') {
153
- errors.push({ key: 'strategies.extractTool.nudge.enabled', expected: 'boolean', actual: typeof strategies.extractTool.nudge.enabled });
154
- }
155
- if (strategies.extractTool.nudge.frequency !== undefined && typeof strategies.extractTool.nudge.frequency !== 'number') {
156
- errors.push({ key: 'strategies.extractTool.nudge.frequency', expected: 'number', actual: typeof strategies.extractTool.nudge.frequency });
157
- }
199
+ if (strategies.onIdle.strictModelSelection !== undefined &&
200
+ typeof strategies.onIdle.strictModelSelection !== "boolean") {
201
+ errors.push({
202
+ key: "strategies.onIdle.strictModelSelection",
203
+ expected: "boolean",
204
+ actual: typeof strategies.onIdle.strictModelSelection,
205
+ });
158
206
  }
159
- if (strategies.extractTool.showDistillation !== undefined && typeof strategies.extractTool.showDistillation !== 'boolean') {
160
- errors.push({ key: 'strategies.extractTool.showDistillation', expected: 'boolean', actual: typeof strategies.extractTool.showDistillation });
207
+ if (strategies.onIdle.protectedTools !== undefined &&
208
+ !Array.isArray(strategies.onIdle.protectedTools)) {
209
+ errors.push({
210
+ key: "strategies.onIdle.protectedTools",
211
+ expected: "string[]",
212
+ actual: typeof strategies.onIdle.protectedTools,
213
+ });
161
214
  }
162
215
  }
163
216
  // supersedeWrites
164
217
  if (strategies.supersedeWrites) {
165
- if (strategies.supersedeWrites.enabled !== undefined && typeof strategies.supersedeWrites.enabled !== 'boolean') {
166
- errors.push({ key: 'strategies.supersedeWrites.enabled', expected: 'boolean', actual: typeof strategies.supersedeWrites.enabled });
218
+ if (strategies.supersedeWrites.enabled !== undefined &&
219
+ typeof strategies.supersedeWrites.enabled !== "boolean") {
220
+ errors.push({
221
+ key: "strategies.supersedeWrites.enabled",
222
+ expected: "boolean",
223
+ actual: typeof strategies.supersedeWrites.enabled,
224
+ });
167
225
  }
168
226
  }
169
227
  }
@@ -176,11 +234,11 @@ function showConfigValidationWarnings(ctx, configPath, configData, isProject) {
176
234
  if (invalidKeys.length === 0 && typeErrors.length === 0) {
177
235
  return;
178
236
  }
179
- const configType = isProject ? 'project config' : 'config';
237
+ const configType = isProject ? "project config" : "config";
180
238
  const messages = [];
181
239
  if (invalidKeys.length > 0) {
182
- const keyList = invalidKeys.slice(0, 3).join(', ');
183
- const suffix = invalidKeys.length > 3 ? ` (+${invalidKeys.length - 3} more)` : '';
240
+ const keyList = invalidKeys.slice(0, 3).join(", ");
241
+ const suffix = invalidKeys.length > 3 ? ` (+${invalidKeys.length - 3} more)` : "";
184
242
  messages.push(`Unknown keys: ${keyList}${suffix}`);
185
243
  }
186
244
  if (typeErrors.length > 0) {
@@ -196,10 +254,10 @@ function showConfigValidationWarnings(ctx, configPath, configData, isProject) {
196
254
  ctx.client.tui.showToast({
197
255
  body: {
198
256
  title: `DCP: Invalid ${configType}`,
199
- message: `${configPath}\n${messages.join('\n')}`,
257
+ message: `${configPath}\n${messages.join("\n")}`,
200
258
  variant: "warning",
201
- duration: 7000
202
- }
259
+ duration: 7000,
260
+ },
203
261
  });
204
262
  }
205
263
  catch { }
@@ -208,55 +266,48 @@ function showConfigValidationWarnings(ctx, configPath, configData, isProject) {
208
266
  const defaultConfig = {
209
267
  enabled: true,
210
268
  debug: false,
211
- pruningSummary: 'detailed',
212
- strategies: {
213
- deduplication: {
269
+ pruneNotification: "detailed",
270
+ turnProtection: {
271
+ enabled: false,
272
+ turns: 4,
273
+ },
274
+ tools: {
275
+ settings: {
276
+ nudgeEnabled: true,
277
+ nudgeFrequency: 10,
278
+ protectedTools: [...DEFAULT_PROTECTED_TOOLS],
279
+ },
280
+ discard: {
214
281
  enabled: true,
215
- protectedTools: [...DEFAULT_PROTECTED_TOOLS]
216
282
  },
217
- supersedeWrites: {
218
- enabled: true
283
+ extract: {
284
+ enabled: true,
285
+ showDistillation: false,
219
286
  },
220
- discardTool: {
287
+ },
288
+ strategies: {
289
+ deduplication: {
221
290
  enabled: true,
222
291
  protectedTools: [...DEFAULT_PROTECTED_TOOLS],
223
- turnProtection: {
224
- enabled: false,
225
- turns: 4
226
- },
227
- nudge: {
228
- enabled: true,
229
- frequency: 10
230
- }
231
292
  },
232
- extractTool: {
293
+ supersedeWrites: {
233
294
  enabled: true,
234
- protectedTools: [...DEFAULT_PROTECTED_TOOLS],
235
- turnProtection: {
236
- enabled: false,
237
- turns: 4
238
- },
239
- nudge: {
240
- enabled: true,
241
- frequency: 10
242
- },
243
- showDistillation: false
244
295
  },
245
296
  onIdle: {
246
297
  enabled: false,
247
298
  protectedTools: [...DEFAULT_PROTECTED_TOOLS],
248
299
  showModelErrorToasts: true,
249
- strictModelSelection: false
250
- }
251
- }
300
+ strictModelSelection: false,
301
+ },
302
+ },
252
303
  };
253
- const GLOBAL_CONFIG_DIR = join(homedir(), '.config', 'opencode');
254
- const GLOBAL_CONFIG_PATH_JSONC = join(GLOBAL_CONFIG_DIR, 'dcp.jsonc');
255
- const GLOBAL_CONFIG_PATH_JSON = join(GLOBAL_CONFIG_DIR, 'dcp.json');
304
+ const GLOBAL_CONFIG_DIR = join(homedir(), ".config", "opencode");
305
+ const GLOBAL_CONFIG_PATH_JSONC = join(GLOBAL_CONFIG_DIR, "dcp.jsonc");
306
+ const GLOBAL_CONFIG_PATH_JSON = join(GLOBAL_CONFIG_DIR, "dcp.json");
256
307
  function findOpencodeDir(startDir) {
257
308
  let current = startDir;
258
- while (current !== '/') {
259
- const candidate = join(current, '.opencode');
309
+ while (current !== "/") {
310
+ const candidate = join(current, ".opencode");
260
311
  if (existsSync(candidate) && statSync(candidate).isDirectory()) {
261
312
  return candidate;
262
313
  }
@@ -280,8 +331,8 @@ function getConfigPaths(ctx) {
280
331
  let configDirPath = null;
281
332
  const opencodeConfigDir = process.env.OPENCODE_CONFIG_DIR;
282
333
  if (opencodeConfigDir) {
283
- const configJsonc = join(opencodeConfigDir, 'dcp.jsonc');
284
- const configJson = join(opencodeConfigDir, 'dcp.json');
334
+ const configJsonc = join(opencodeConfigDir, "dcp.jsonc");
335
+ const configJson = join(opencodeConfigDir, "dcp.json");
285
336
  if (existsSync(configJsonc)) {
286
337
  configDirPath = configJsonc;
287
338
  }
@@ -294,8 +345,8 @@ function getConfigPaths(ctx) {
294
345
  if (ctx?.directory) {
295
346
  const opencodeDir = findOpencodeDir(ctx.directory);
296
347
  if (opencodeDir) {
297
- const projectJsonc = join(opencodeDir, 'dcp.jsonc');
298
- const projectJson = join(opencodeDir, 'dcp.json');
348
+ const projectJsonc = join(opencodeDir, "dcp.jsonc");
349
+ const projectJson = join(opencodeDir, "dcp.json");
299
350
  if (existsSync(projectJsonc)) {
300
351
  projectPath = projectJsonc;
301
352
  }
@@ -315,9 +366,35 @@ function createDefaultConfig() {
315
366
  "enabled": true,
316
367
  // Enable debug logging to ~/.config/opencode/logs/dcp/
317
368
  "debug": false,
318
- // Summary display: "off", "minimal", or "detailed"
319
- "pruningSummary": "detailed",
320
- // Strategies for pruning tokens from chat history
369
+ // Notification display: "off", "minimal", or "detailed"
370
+ "pruneNotification": "detailed",
371
+ // Protect from pruning for <turns> message turns
372
+ "turnProtection": {
373
+ "enabled": false,
374
+ "turns": 4
375
+ },
376
+ // LLM-driven context pruning tools
377
+ "tools": {
378
+ // Shared settings for all prune tools
379
+ "settings": {
380
+ // Nudge the LLM to use prune tools (every <nudgeFrequency> tool results)
381
+ "nudgeEnabled": true,
382
+ "nudgeFrequency": 10,
383
+ // Additional tools to protect from pruning
384
+ "protectedTools": []
385
+ },
386
+ // Removes tool content from context without preservation (for completed tasks or noise)
387
+ "discard": {
388
+ "enabled": true
389
+ },
390
+ // Distills key findings into preserved knowledge before removing raw content
391
+ "extract": {
392
+ "enabled": true,
393
+ // Show distillation content as an ignored message notification
394
+ "showDistillation": false
395
+ }
396
+ },
397
+ // Automatic pruning strategies
321
398
  "strategies": {
322
399
  // Remove duplicate tool calls (same tool with same arguments)
323
400
  "deduplication": {
@@ -329,40 +406,6 @@ function createDefaultConfig() {
329
406
  "supersedeWrites": {
330
407
  "enabled": true
331
408
  },
332
- // Removes tool content from context without preservation (for completed tasks or noise)
333
- "discardTool": {
334
- "enabled": true,
335
- // Additional tools to protect from pruning
336
- "protectedTools": [],
337
- // Protect from pruning for <turn protection> message turns
338
- "turnProtection": {
339
- "enabled": false,
340
- "turns": 4
341
- },
342
- // Nudge the LLM to use the discard tool (every <frequency> tool results)
343
- "nudge": {
344
- "enabled": true,
345
- "frequency": 10
346
- }
347
- },
348
- // Distills key findings into preserved knowledge before removing raw content
349
- "extractTool": {
350
- "enabled": true,
351
- // Additional tools to protect from pruning
352
- "protectedTools": [],
353
- // Protect from pruning for <turn protection> message turns
354
- "turnProtection": {
355
- "enabled": false,
356
- "turns": 4
357
- },
358
- // Nudge the LLM to use the extract tool (every <frequency> tool results)
359
- "nudge": {
360
- "enabled": true,
361
- "frequency": 10
362
- },
363
- // Show distillation content as an ignored message notification
364
- "showDistillation": false
365
- },
366
409
  // (Legacy) Run an LLM to analyze what tool calls are no longer relevant on idle
367
410
  "onIdle": {
368
411
  "enabled": false,
@@ -378,12 +421,12 @@ function createDefaultConfig() {
378
421
  }
379
422
  }
380
423
  `;
381
- writeFileSync(GLOBAL_CONFIG_PATH_JSONC, configContent, 'utf-8');
424
+ writeFileSync(GLOBAL_CONFIG_PATH_JSONC, configContent, "utf-8");
382
425
  }
383
426
  function loadConfigFile(configPath) {
384
427
  let fileContent;
385
428
  try {
386
- fileContent = readFileSync(configPath, 'utf-8');
429
+ fileContent = readFileSync(configPath, "utf-8");
387
430
  }
388
431
  catch {
389
432
  // File doesn't exist or can't be read - not a parse error
@@ -392,12 +435,12 @@ function loadConfigFile(configPath) {
392
435
  try {
393
436
  const parsed = parse(fileContent);
394
437
  if (parsed === undefined || parsed === null) {
395
- return { data: null, parseError: 'Config file is empty or invalid' };
438
+ return { data: null, parseError: "Config file is empty or invalid" };
396
439
  }
397
440
  return { data: parsed };
398
441
  }
399
442
  catch (error) {
400
- return { data: null, parseError: error.message || 'Failed to parse config' };
443
+ return { data: null, parseError: error.message || "Failed to parse config" };
401
444
  }
402
445
  }
403
446
  function mergeStrategies(base, override) {
@@ -409,9 +452,9 @@ function mergeStrategies(base, override) {
409
452
  protectedTools: [
410
453
  ...new Set([
411
454
  ...base.deduplication.protectedTools,
412
- ...(override.deduplication?.protectedTools ?? [])
413
- ])
414
- ]
455
+ ...(override.deduplication?.protectedTools ?? []),
456
+ ]),
457
+ ],
415
458
  },
416
459
  onIdle: {
417
460
  enabled: override.onIdle?.enabled ?? base.onIdle.enabled,
@@ -421,79 +464,63 @@ function mergeStrategies(base, override) {
421
464
  protectedTools: [
422
465
  ...new Set([
423
466
  ...base.onIdle.protectedTools,
424
- ...(override.onIdle?.protectedTools ?? [])
425
- ])
426
- ]
427
- },
428
- discardTool: {
429
- enabled: override.discardTool?.enabled ?? base.discardTool.enabled,
430
- protectedTools: [
431
- ...new Set([
432
- ...base.discardTool.protectedTools,
433
- ...(override.discardTool?.protectedTools ?? [])
434
- ])
467
+ ...(override.onIdle?.protectedTools ?? []),
468
+ ]),
435
469
  ],
436
- turnProtection: {
437
- enabled: override.discardTool?.turnProtection?.enabled ?? base.discardTool.turnProtection.enabled,
438
- turns: override.discardTool?.turnProtection?.turns ?? base.discardTool.turnProtection.turns
439
- },
440
- nudge: {
441
- enabled: override.discardTool?.nudge?.enabled ?? base.discardTool.nudge.enabled,
442
- frequency: override.discardTool?.nudge?.frequency ?? base.discardTool.nudge.frequency
443
- }
444
470
  },
445
- extractTool: {
446
- enabled: override.extractTool?.enabled ?? base.extractTool.enabled,
471
+ supersedeWrites: {
472
+ enabled: override.supersedeWrites?.enabled ?? base.supersedeWrites.enabled,
473
+ },
474
+ };
475
+ }
476
+ function mergeTools(base, override) {
477
+ if (!override)
478
+ return base;
479
+ return {
480
+ settings: {
481
+ nudgeEnabled: override.settings?.nudgeEnabled ?? base.settings.nudgeEnabled,
482
+ nudgeFrequency: override.settings?.nudgeFrequency ?? base.settings.nudgeFrequency,
447
483
  protectedTools: [
448
484
  ...new Set([
449
- ...base.extractTool.protectedTools,
450
- ...(override.extractTool?.protectedTools ?? [])
451
- ])
485
+ ...base.settings.protectedTools,
486
+ ...(override.settings?.protectedTools ?? []),
487
+ ]),
452
488
  ],
453
- turnProtection: {
454
- enabled: override.extractTool?.turnProtection?.enabled ?? base.extractTool.turnProtection.enabled,
455
- turns: override.extractTool?.turnProtection?.turns ?? base.extractTool.turnProtection.turns
456
- },
457
- nudge: {
458
- enabled: override.extractTool?.nudge?.enabled ?? base.extractTool.nudge.enabled,
459
- frequency: override.extractTool?.nudge?.frequency ?? base.extractTool.nudge.frequency
460
- },
461
- showDistillation: override.extractTool?.showDistillation ?? base.extractTool.showDistillation
462
489
  },
463
- supersedeWrites: {
464
- enabled: override.supersedeWrites?.enabled ?? base.supersedeWrites.enabled
465
- }
490
+ discard: {
491
+ enabled: override.discard?.enabled ?? base.discard.enabled,
492
+ },
493
+ extract: {
494
+ enabled: override.extract?.enabled ?? base.extract.enabled,
495
+ showDistillation: override.extract?.showDistillation ?? base.extract.showDistillation,
496
+ },
466
497
  };
467
498
  }
468
499
  function deepCloneConfig(config) {
469
500
  return {
470
501
  ...config,
502
+ turnProtection: { ...config.turnProtection },
503
+ tools: {
504
+ settings: {
505
+ ...config.tools.settings,
506
+ protectedTools: [...config.tools.settings.protectedTools],
507
+ },
508
+ discard: { ...config.tools.discard },
509
+ extract: { ...config.tools.extract },
510
+ },
471
511
  strategies: {
472
512
  deduplication: {
473
513
  ...config.strategies.deduplication,
474
- protectedTools: [...config.strategies.deduplication.protectedTools]
514
+ protectedTools: [...config.strategies.deduplication.protectedTools],
475
515
  },
476
516
  onIdle: {
477
517
  ...config.strategies.onIdle,
478
- protectedTools: [...config.strategies.onIdle.protectedTools]
479
- },
480
- discardTool: {
481
- ...config.strategies.discardTool,
482
- protectedTools: [...config.strategies.discardTool.protectedTools],
483
- turnProtection: { ...config.strategies.discardTool.turnProtection },
484
- nudge: { ...config.strategies.discardTool.nudge }
485
- },
486
- extractTool: {
487
- ...config.strategies.extractTool,
488
- protectedTools: [...config.strategies.extractTool.protectedTools],
489
- turnProtection: { ...config.strategies.extractTool.turnProtection },
490
- nudge: { ...config.strategies.extractTool.nudge },
491
- showDistillation: config.strategies.extractTool.showDistillation
518
+ protectedTools: [...config.strategies.onIdle.protectedTools],
492
519
  },
493
520
  supersedeWrites: {
494
- ...config.strategies.supersedeWrites
495
- }
496
- }
521
+ ...config.strategies.supersedeWrites,
522
+ },
523
+ },
497
524
  };
498
525
  }
499
526
  export function getConfig(ctx) {
@@ -510,8 +537,8 @@ export function getConfig(ctx) {
510
537
  title: "DCP: Invalid config",
511
538
  message: `${configPaths.global}\n${result.parseError}\nUsing default values`,
512
539
  variant: "warning",
513
- duration: 7000
514
- }
540
+ duration: 7000,
541
+ },
515
542
  });
516
543
  }
517
544
  catch { }
@@ -523,8 +550,13 @@ export function getConfig(ctx) {
523
550
  config = {
524
551
  enabled: result.data.enabled ?? config.enabled,
525
552
  debug: result.data.debug ?? config.debug,
526
- pruningSummary: result.data.pruningSummary ?? config.pruningSummary,
527
- strategies: mergeStrategies(config.strategies, result.data.strategies)
553
+ pruneNotification: result.data.pruneNotification ?? config.pruneNotification,
554
+ turnProtection: {
555
+ enabled: result.data.turnProtection?.enabled ?? config.turnProtection.enabled,
556
+ turns: result.data.turnProtection?.turns ?? config.turnProtection.turns,
557
+ },
558
+ tools: mergeTools(config.tools, result.data.tools),
559
+ strategies: mergeStrategies(config.strategies, result.data.strategies),
528
560
  };
529
561
  }
530
562
  }
@@ -543,8 +575,8 @@ export function getConfig(ctx) {
543
575
  title: "DCP: Invalid configDir config",
544
576
  message: `${configPaths.configDir}\n${result.parseError}\nUsing global/default values`,
545
577
  variant: "warning",
546
- duration: 7000
547
- }
578
+ duration: 7000,
579
+ },
548
580
  });
549
581
  }
550
582
  catch { }
@@ -556,8 +588,13 @@ export function getConfig(ctx) {
556
588
  config = {
557
589
  enabled: result.data.enabled ?? config.enabled,
558
590
  debug: result.data.debug ?? config.debug,
559
- pruningSummary: result.data.pruningSummary ?? config.pruningSummary,
560
- strategies: mergeStrategies(config.strategies, result.data.strategies)
591
+ pruneNotification: result.data.pruneNotification ?? config.pruneNotification,
592
+ turnProtection: {
593
+ enabled: result.data.turnProtection?.enabled ?? config.turnProtection.enabled,
594
+ turns: result.data.turnProtection?.turns ?? config.turnProtection.turns,
595
+ },
596
+ tools: mergeTools(config.tools, result.data.tools),
597
+ strategies: mergeStrategies(config.strategies, result.data.strategies),
561
598
  };
562
599
  }
563
600
  }
@@ -572,8 +609,8 @@ export function getConfig(ctx) {
572
609
  title: "DCP: Invalid project config",
573
610
  message: `${configPaths.project}\n${result.parseError}\nUsing global/default values`,
574
611
  variant: "warning",
575
- duration: 7000
576
- }
612
+ duration: 7000,
613
+ },
577
614
  });
578
615
  }
579
616
  catch { }
@@ -585,8 +622,13 @@ export function getConfig(ctx) {
585
622
  config = {
586
623
  enabled: result.data.enabled ?? config.enabled,
587
624
  debug: result.data.debug ?? config.debug,
588
- pruningSummary: result.data.pruningSummary ?? config.pruningSummary,
589
- strategies: mergeStrategies(config.strategies, result.data.strategies)
625
+ pruneNotification: result.data.pruneNotification ?? config.pruneNotification,
626
+ turnProtection: {
627
+ enabled: result.data.turnProtection?.enabled ?? config.turnProtection.enabled,
628
+ turns: result.data.turnProtection?.turns ?? config.turnProtection.turns,
629
+ },
630
+ tools: mergeTools(config.tools, result.data.tools),
631
+ strategies: mergeStrategies(config.strategies, result.data.strategies),
590
632
  };
591
633
  }
592
634
  }