@tarquinen/opencode-dcp 2.1.6 → 2.2.0-beta0

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 (156) hide show
  1. package/README.md +30 -49
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +6 -29
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/commands/context.d.ts.map +1 -1
  6. package/dist/lib/commands/context.js +36 -8
  7. package/dist/lib/commands/context.js.map +1 -1
  8. package/dist/lib/commands/help.d.ts.map +1 -1
  9. package/dist/lib/commands/help.js +2 -6
  10. package/dist/lib/commands/help.js.map +1 -1
  11. package/dist/lib/commands/manual.d.ts +1 -3
  12. package/dist/lib/commands/manual.d.ts.map +1 -1
  13. package/dist/lib/commands/manual.js +4 -43
  14. package/dist/lib/commands/manual.js.map +1 -1
  15. package/dist/lib/commands/sweep.js +1 -1
  16. package/dist/lib/commands/sweep.js.map +1 -1
  17. package/dist/lib/compress-logger.d.ts +15 -0
  18. package/dist/lib/compress-logger.d.ts.map +1 -0
  19. package/dist/lib/compress-logger.js +132 -0
  20. package/dist/lib/compress-logger.js.map +1 -0
  21. package/dist/lib/config.d.ts +5 -20
  22. package/dist/lib/config.d.ts.map +1 -1
  23. package/dist/lib/config.js +172 -346
  24. package/dist/lib/config.js.map +1 -1
  25. package/dist/lib/hooks.d.ts +7 -0
  26. package/dist/lib/hooks.d.ts.map +1 -1
  27. package/dist/lib/hooks.js +23 -16
  28. package/dist/lib/hooks.js.map +1 -1
  29. package/dist/lib/logger.d.ts.map +1 -1
  30. package/dist/lib/logger.js +6 -0
  31. package/dist/lib/logger.js.map +1 -1
  32. package/dist/lib/messages/index.d.ts +2 -2
  33. package/dist/lib/messages/index.d.ts.map +1 -1
  34. package/dist/lib/messages/index.js +2 -2
  35. package/dist/lib/messages/index.js.map +1 -1
  36. package/dist/lib/messages/inject/inject.d.ts +6 -0
  37. package/dist/lib/messages/inject/inject.d.ts.map +1 -0
  38. package/dist/lib/messages/inject/inject.js +99 -0
  39. package/dist/lib/messages/inject/inject.js.map +1 -0
  40. package/dist/lib/messages/inject/utils.d.ts +20 -0
  41. package/dist/lib/messages/inject/utils.d.ts.map +1 -0
  42. package/dist/lib/messages/inject/utils.js +137 -0
  43. package/dist/lib/messages/inject/utils.js.map +1 -0
  44. package/dist/lib/messages/prune.js +7 -7
  45. package/dist/lib/messages/prune.js.map +1 -1
  46. package/dist/lib/messages/utils.d.ts +16 -28
  47. package/dist/lib/messages/utils.d.ts.map +1 -1
  48. package/dist/lib/messages/utils.js +40 -133
  49. package/dist/lib/messages/utils.js.map +1 -1
  50. package/dist/lib/prompts/_codegen/system.generated.d.ts +1 -1
  51. package/dist/lib/prompts/_codegen/system.generated.d.ts.map +1 -1
  52. package/dist/lib/prompts/_codegen/system.generated.js +36 -32
  53. package/dist/lib/prompts/_codegen/system.generated.js.map +1 -1
  54. package/dist/lib/prompts/compress.d.ts +2 -0
  55. package/dist/lib/prompts/compress.d.ts.map +1 -0
  56. package/dist/lib/prompts/compress.js +122 -0
  57. package/dist/lib/prompts/compress.js.map +1 -0
  58. package/dist/lib/prompts/index.d.ts +6 -10
  59. package/dist/lib/prompts/index.d.ts.map +1 -1
  60. package/dist/lib/prompts/index.js +17 -40
  61. package/dist/lib/prompts/index.js.map +1 -1
  62. package/dist/lib/prompts/iteration-nudge.d.ts +2 -0
  63. package/dist/lib/prompts/iteration-nudge.d.ts.map +1 -0
  64. package/dist/lib/prompts/iteration-nudge.js +9 -0
  65. package/dist/lib/prompts/iteration-nudge.js.map +1 -0
  66. package/dist/lib/prompts/nudge.d.ts +2 -0
  67. package/dist/lib/prompts/nudge.d.ts.map +1 -0
  68. package/dist/lib/prompts/nudge.js +40 -0
  69. package/dist/lib/prompts/nudge.js.map +1 -0
  70. package/dist/lib/prompts/turn-nudge.d.ts +2 -0
  71. package/dist/lib/prompts/turn-nudge.d.ts.map +1 -0
  72. package/dist/lib/prompts/turn-nudge.js +12 -0
  73. package/dist/lib/prompts/turn-nudge.js.map +1 -0
  74. package/dist/lib/state/persistence.d.ts +6 -0
  75. package/dist/lib/state/persistence.d.ts.map +1 -1
  76. package/dist/lib/state/persistence.js +58 -38
  77. package/dist/lib/state/persistence.js.map +1 -1
  78. package/dist/lib/state/state.d.ts.map +1 -1
  79. package/dist/lib/state/state.js +20 -5
  80. package/dist/lib/state/state.js.map +1 -1
  81. package/dist/lib/state/tool-cache.d.ts.map +1 -1
  82. package/dist/lib/state/tool-cache.js +3 -17
  83. package/dist/lib/state/tool-cache.js.map +1 -1
  84. package/dist/lib/state/types.d.ts +7 -2
  85. package/dist/lib/state/types.d.ts.map +1 -1
  86. package/dist/lib/state/utils.d.ts +1 -0
  87. package/dist/lib/state/utils.d.ts.map +1 -1
  88. package/dist/lib/state/utils.js +33 -6
  89. package/dist/lib/state/utils.js.map +1 -1
  90. package/dist/lib/strategies/index.d.ts +0 -1
  91. package/dist/lib/strategies/index.d.ts.map +1 -1
  92. package/dist/lib/strategies/index.js +0 -1
  93. package/dist/lib/strategies/index.js.map +1 -1
  94. package/dist/lib/strategies/utils.d.ts +1 -0
  95. package/dist/lib/strategies/utils.d.ts.map +1 -1
  96. package/dist/lib/strategies/utils.js +15 -0
  97. package/dist/lib/strategies/utils.js.map +1 -1
  98. package/dist/lib/tools/compress-utils.d.ts +7 -4
  99. package/dist/lib/tools/compress-utils.d.ts.map +1 -1
  100. package/dist/lib/tools/compress-utils.js +34 -25
  101. package/dist/lib/tools/compress-utils.js.map +1 -1
  102. package/dist/lib/tools/compress.d.ts +2 -2
  103. package/dist/lib/tools/compress.d.ts.map +1 -1
  104. package/dist/lib/tools/compress.js +21 -58
  105. package/dist/lib/tools/compress.js.map +1 -1
  106. package/dist/lib/tools/index.d.ts +1 -3
  107. package/dist/lib/tools/index.d.ts.map +1 -1
  108. package/dist/lib/tools/index.js +0 -2
  109. package/dist/lib/tools/index.js.map +1 -1
  110. package/dist/lib/tools/types.d.ts +1 -1
  111. package/dist/lib/tools/types.d.ts.map +1 -1
  112. package/dist/lib/ui/notification.d.ts +2 -2
  113. package/dist/lib/ui/notification.d.ts.map +1 -1
  114. package/dist/lib/ui/notification.js +31 -28
  115. package/dist/lib/ui/notification.js.map +1 -1
  116. package/dist/lib/ui/utils.d.ts +3 -4
  117. package/dist/lib/ui/utils.d.ts.map +1 -1
  118. package/dist/lib/ui/utils.js +165 -24
  119. package/dist/lib/ui/utils.js.map +1 -1
  120. package/package.json +2 -1
  121. package/dist/lib/messages/inject.d.ts +0 -14
  122. package/dist/lib/messages/inject.d.ts.map +0 -1
  123. package/dist/lib/messages/inject.js +0 -266
  124. package/dist/lib/messages/inject.js.map +0 -1
  125. package/dist/lib/prompts/_codegen/compress-nudge.generated.d.ts +0 -2
  126. package/dist/lib/prompts/_codegen/compress-nudge.generated.d.ts.map +0 -1
  127. package/dist/lib/prompts/_codegen/compress-nudge.generated.js +0 -15
  128. package/dist/lib/prompts/_codegen/compress-nudge.generated.js.map +0 -1
  129. package/dist/lib/prompts/_codegen/compress.generated.d.ts +0 -2
  130. package/dist/lib/prompts/_codegen/compress.generated.d.ts.map +0 -1
  131. package/dist/lib/prompts/_codegen/compress.generated.js +0 -66
  132. package/dist/lib/prompts/_codegen/compress.generated.js.map +0 -1
  133. package/dist/lib/prompts/_codegen/distill.generated.d.ts +0 -2
  134. package/dist/lib/prompts/_codegen/distill.generated.d.ts.map +0 -1
  135. package/dist/lib/prompts/_codegen/distill.generated.js +0 -33
  136. package/dist/lib/prompts/_codegen/distill.generated.js.map +0 -1
  137. package/dist/lib/prompts/_codegen/nudge.generated.d.ts +0 -2
  138. package/dist/lib/prompts/_codegen/nudge.generated.d.ts.map +0 -1
  139. package/dist/lib/prompts/_codegen/nudge.generated.js +0 -17
  140. package/dist/lib/prompts/_codegen/nudge.generated.js.map +0 -1
  141. package/dist/lib/prompts/_codegen/prune.generated.d.ts +0 -2
  142. package/dist/lib/prompts/_codegen/prune.generated.d.ts.map +0 -1
  143. package/dist/lib/prompts/_codegen/prune.generated.js +0 -23
  144. package/dist/lib/prompts/_codegen/prune.generated.js.map +0 -1
  145. package/dist/lib/tools/distill.d.ts +0 -4
  146. package/dist/lib/tools/distill.d.ts.map +0 -1
  147. package/dist/lib/tools/distill.js +0 -41
  148. package/dist/lib/tools/distill.js.map +0 -1
  149. package/dist/lib/tools/prune-shared.d.ts +0 -6
  150. package/dist/lib/tools/prune-shared.d.ts.map +0 -1
  151. package/dist/lib/tools/prune-shared.js +0 -113
  152. package/dist/lib/tools/prune-shared.js.map +0 -1
  153. package/dist/lib/tools/prune.d.ts +0 -4
  154. package/dist/lib/tools/prune.d.ts.map +0 -1
  155. package/dist/lib/tools/prune.js +0 -28
  156. package/dist/lib/tools/prune.js.map +0 -1
@@ -6,20 +6,16 @@ const DEFAULT_PROTECTED_TOOLS = [
6
6
  "task",
7
7
  "todowrite",
8
8
  "todoread",
9
- "distill",
10
9
  "compress",
11
- "prune",
12
10
  "batch",
13
11
  "plan_enter",
14
12
  "plan_exit",
15
13
  ];
16
- // Valid config keys for validation against user config
17
14
  export const VALID_CONFIG_KEYS = new Set([
18
- // Top-level keys
19
15
  "$schema",
20
16
  "enabled",
21
17
  "debug",
22
- "showUpdateToasts", // Deprecated but kept for backwards compatibility
18
+ "showUpdateToasts",
23
19
  "pruneNotification",
24
20
  "pruneNotificationType",
25
21
  "turnProtection",
@@ -32,43 +28,31 @@ export const VALID_CONFIG_KEYS = new Set([
32
28
  "manualMode",
33
29
  "manualMode.enabled",
34
30
  "manualMode.automaticStrategies",
35
- "tools",
36
- "tools.settings",
37
- "tools.settings.nudgeEnabled",
38
- "tools.settings.nudgeFrequency",
39
- "tools.settings.protectedTools",
40
- "tools.settings.contextLimit",
41
- "tools.settings.modelLimits",
42
- "tools.distill",
43
- "tools.distill.permission",
44
- "tools.distill.showDistillation",
45
- "tools.compress",
46
- "tools.compress.permission",
47
- "tools.compress.showCompression",
48
- "tools.prune",
49
- "tools.prune.permission",
31
+ "compress",
32
+ "compress.permission",
33
+ "compress.showCompression",
34
+ "compress.contextLimit",
35
+ "compress.modelLimits",
36
+ "compress.nudgeFrequency",
37
+ "compress.iterationNudgeThreshold",
50
38
  "strategies",
51
- // strategies.deduplication
52
39
  "strategies.deduplication",
53
40
  "strategies.deduplication.enabled",
54
41
  "strategies.deduplication.protectedTools",
55
- // strategies.supersedeWrites
56
42
  "strategies.supersedeWrites",
57
43
  "strategies.supersedeWrites.enabled",
58
- // strategies.purgeErrors
59
44
  "strategies.purgeErrors",
60
45
  "strategies.purgeErrors.enabled",
61
46
  "strategies.purgeErrors.turns",
62
47
  "strategies.purgeErrors.protectedTools",
63
48
  ]);
64
- // Extract all key paths from a config object for validation
65
49
  function getConfigKeyPaths(obj, prefix = "") {
66
50
  const keys = [];
67
51
  for (const key of Object.keys(obj)) {
68
52
  const fullKey = prefix ? `${prefix}.${key}` : key;
69
53
  keys.push(fullKey);
70
54
  // modelLimits is a dynamic map keyed by providerID/modelID; do not recurse into arbitrary IDs.
71
- if (fullKey === "tools.settings.modelLimits") {
55
+ if (fullKey === "compress.modelLimits") {
72
56
  continue;
73
57
  }
74
58
  if (obj[key] && typeof obj[key] === "object" && !Array.isArray(obj[key])) {
@@ -77,14 +61,12 @@ function getConfigKeyPaths(obj, prefix = "") {
77
61
  }
78
62
  return keys;
79
63
  }
80
- // Returns invalid keys found in user config
81
64
  export function getInvalidConfigKeys(userConfig) {
82
65
  const userKeys = getConfigKeyPaths(userConfig);
83
66
  return userKeys.filter((key) => !VALID_CONFIG_KEYS.has(key));
84
67
  }
85
68
  export function validateConfigTypes(config) {
86
69
  const errors = [];
87
- // Top-level validators
88
70
  if (config.enabled !== undefined && typeof config.enabled !== "boolean") {
89
71
  errors.push({ key: "enabled", expected: "boolean", actual: typeof config.enabled });
90
72
  }
@@ -127,7 +109,6 @@ export function validateConfigTypes(config) {
127
109
  });
128
110
  }
129
111
  }
130
- // Top-level turnProtection validator
131
112
  if (config.turnProtection) {
132
113
  if (config.turnProtection.enabled !== undefined &&
133
114
  typeof config.turnProtection.enabled !== "boolean") {
@@ -153,10 +134,16 @@ export function validateConfigTypes(config) {
153
134
  });
154
135
  }
155
136
  }
156
- // Commands validator
157
137
  const commands = config.commands;
158
138
  if (commands !== undefined) {
159
- if (typeof commands === "object") {
139
+ if (typeof commands !== "object" || commands === null || Array.isArray(commands)) {
140
+ errors.push({
141
+ key: "commands",
142
+ expected: "object",
143
+ actual: typeof commands,
144
+ });
145
+ }
146
+ else {
160
147
  if (commands.enabled !== undefined && typeof commands.enabled !== "boolean") {
161
148
  errors.push({
162
149
  key: "commands.enabled",
@@ -172,18 +159,17 @@ export function validateConfigTypes(config) {
172
159
  });
173
160
  }
174
161
  }
175
- else {
176
- errors.push({
177
- key: "commands",
178
- expected: "{ enabled: boolean, protectedTools: string[] }",
179
- actual: typeof commands,
180
- });
181
- }
182
162
  }
183
- // Manual mode validator
184
163
  const manualMode = config.manualMode;
185
164
  if (manualMode !== undefined) {
186
- if (typeof manualMode === "object") {
165
+ if (typeof manualMode !== "object" || manualMode === null || Array.isArray(manualMode)) {
166
+ errors.push({
167
+ key: "manualMode",
168
+ expected: "object",
169
+ actual: typeof manualMode,
170
+ });
171
+ }
172
+ else {
187
173
  if (manualMode.enabled !== undefined && typeof manualMode.enabled !== "boolean") {
188
174
  errors.push({
189
175
  key: "manualMode.enabled",
@@ -200,78 +186,76 @@ export function validateConfigTypes(config) {
200
186
  });
201
187
  }
202
188
  }
203
- else {
189
+ }
190
+ const compress = config.compress;
191
+ if (compress !== undefined) {
192
+ if (typeof compress !== "object" || compress === null || Array.isArray(compress)) {
204
193
  errors.push({
205
- key: "manualMode",
206
- expected: "{ enabled: boolean, automaticStrategies: boolean }",
207
- actual: typeof manualMode,
194
+ key: "compress",
195
+ expected: "object",
196
+ actual: typeof compress,
208
197
  });
209
198
  }
210
- }
211
- // Tools validators
212
- const tools = config.tools;
213
- if (tools) {
214
- if (tools.settings) {
215
- if (tools.settings.nudgeEnabled !== undefined &&
216
- typeof tools.settings.nudgeEnabled !== "boolean") {
199
+ else {
200
+ if (compress.nudgeFrequency !== undefined &&
201
+ typeof compress.nudgeFrequency !== "number") {
217
202
  errors.push({
218
- key: "tools.settings.nudgeEnabled",
219
- expected: "boolean",
220
- actual: typeof tools.settings.nudgeEnabled,
203
+ key: "compress.nudgeFrequency",
204
+ expected: "number",
205
+ actual: typeof compress.nudgeFrequency,
221
206
  });
222
207
  }
223
- if (tools.settings.nudgeFrequency !== undefined &&
224
- typeof tools.settings.nudgeFrequency !== "number") {
208
+ if (typeof compress.nudgeFrequency === "number" && compress.nudgeFrequency < 1) {
225
209
  errors.push({
226
- key: "tools.settings.nudgeFrequency",
227
- expected: "number",
228
- actual: typeof tools.settings.nudgeFrequency,
210
+ key: "compress.nudgeFrequency",
211
+ expected: "positive number (>= 1)",
212
+ actual: `${compress.nudgeFrequency} (will be clamped to 1)`,
229
213
  });
230
214
  }
231
- if (typeof tools.settings.nudgeFrequency === "number" &&
232
- tools.settings.nudgeFrequency < 1) {
215
+ if (compress.iterationNudgeThreshold !== undefined &&
216
+ typeof compress.iterationNudgeThreshold !== "number") {
233
217
  errors.push({
234
- key: "tools.settings.nudgeFrequency",
235
- expected: "positive number (>= 1)",
236
- actual: `${tools.settings.nudgeFrequency} (will be clamped to 1)`,
218
+ key: "compress.iterationNudgeThreshold",
219
+ expected: "number",
220
+ actual: typeof compress.iterationNudgeThreshold,
237
221
  });
238
222
  }
239
- if (tools.settings.protectedTools !== undefined &&
240
- !Array.isArray(tools.settings.protectedTools)) {
223
+ if (typeof compress.iterationNudgeThreshold === "number" &&
224
+ compress.iterationNudgeThreshold < 1) {
241
225
  errors.push({
242
- key: "tools.settings.protectedTools",
243
- expected: "string[]",
244
- actual: typeof tools.settings.protectedTools,
226
+ key: "compress.iterationNudgeThreshold",
227
+ expected: "positive number (>= 1)",
228
+ actual: `${compress.iterationNudgeThreshold} (will be clamped to 1)`,
245
229
  });
246
230
  }
247
- if (tools.settings.contextLimit !== undefined) {
248
- const isValidNumber = typeof tools.settings.contextLimit === "number";
249
- const isPercentString = typeof tools.settings.contextLimit === "string" &&
250
- tools.settings.contextLimit.endsWith("%");
231
+ if (compress.contextLimit !== undefined) {
232
+ const isValidNumber = typeof compress.contextLimit === "number";
233
+ const isPercentString = typeof compress.contextLimit === "string" && compress.contextLimit.endsWith("%");
251
234
  if (!isValidNumber && !isPercentString) {
252
235
  errors.push({
253
- key: "tools.settings.contextLimit",
236
+ key: "compress.contextLimit",
254
237
  expected: 'number | "${number}%"',
255
- actual: JSON.stringify(tools.settings.contextLimit),
238
+ actual: JSON.stringify(compress.contextLimit),
256
239
  });
257
240
  }
258
241
  }
259
- if (tools.settings.modelLimits !== undefined) {
260
- if (typeof tools.settings.modelLimits !== "object" ||
261
- Array.isArray(tools.settings.modelLimits)) {
242
+ if (compress.modelLimits !== undefined) {
243
+ if (typeof compress.modelLimits !== "object" ||
244
+ compress.modelLimits === null ||
245
+ Array.isArray(compress.modelLimits)) {
262
246
  errors.push({
263
- key: "tools.settings.modelLimits",
247
+ key: "compress.modelLimits",
264
248
  expected: "Record<string, number | ${number}%>",
265
- actual: typeof tools.settings.modelLimits,
249
+ actual: typeof compress.modelLimits,
266
250
  });
267
251
  }
268
252
  else {
269
- for (const [providerModelKey, limit] of Object.entries(tools.settings.modelLimits)) {
253
+ for (const [providerModelKey, limit] of Object.entries(compress.modelLimits)) {
270
254
  const isValidNumber = typeof limit === "number";
271
255
  const isPercentString = typeof limit === "string" && /^\d+(?:\.\d+)?%$/.test(limit);
272
256
  if (!isValidNumber && !isPercentString) {
273
257
  errors.push({
274
- key: `tools.settings.modelLimits.${providerModelKey}`,
258
+ key: `compress.modelLimits.${providerModelKey}`,
275
259
  expected: 'number | "${number}%"',
276
260
  actual: JSON.stringify(limit),
277
261
  });
@@ -279,64 +263,26 @@ export function validateConfigTypes(config) {
279
263
  }
280
264
  }
281
265
  }
282
- if (tools.distill) {
283
- if (tools.distill.permission !== undefined) {
284
- const validValues = ["ask", "allow", "deny"];
285
- if (!validValues.includes(tools.distill.permission)) {
286
- errors.push({
287
- key: "tools.distill.permission",
288
- expected: '"ask" | "allow" | "deny"',
289
- actual: JSON.stringify(tools.distill.permission),
290
- });
291
- }
292
- }
293
- if (tools.distill.showDistillation !== undefined &&
294
- typeof tools.distill.showDistillation !== "boolean") {
295
- errors.push({
296
- key: "tools.distill.showDistillation",
297
- expected: "boolean",
298
- actual: typeof tools.distill.showDistillation,
299
- });
300
- }
301
- }
302
- }
303
- if (tools.compress) {
304
- if (tools.compress.permission !== undefined) {
305
- const validValues = ["ask", "allow", "deny"];
306
- if (!validValues.includes(tools.compress.permission)) {
307
- errors.push({
308
- key: "tools.compress.permission",
309
- expected: '"ask" | "allow" | "deny"',
310
- actual: JSON.stringify(tools.compress.permission),
311
- });
312
- }
266
+ const validValues = ["ask", "allow", "deny"];
267
+ if (compress.permission !== undefined && !validValues.includes(compress.permission)) {
268
+ errors.push({
269
+ key: "compress.permission",
270
+ expected: '"ask" | "allow" | "deny"',
271
+ actual: JSON.stringify(compress.permission),
272
+ });
313
273
  }
314
- if (tools.compress.showCompression !== undefined &&
315
- typeof tools.compress.showCompression !== "boolean") {
274
+ if (compress.showCompression !== undefined &&
275
+ typeof compress.showCompression !== "boolean") {
316
276
  errors.push({
317
- key: "tools.compress.showCompression",
277
+ key: "compress.showCompression",
318
278
  expected: "boolean",
319
- actual: typeof tools.compress.showCompression,
279
+ actual: typeof compress.showCompression,
320
280
  });
321
281
  }
322
282
  }
323
- if (tools.prune) {
324
- if (tools.prune.permission !== undefined) {
325
- const validValues = ["ask", "allow", "deny"];
326
- if (!validValues.includes(tools.prune.permission)) {
327
- errors.push({
328
- key: "tools.prune.permission",
329
- expected: '"ask" | "allow" | "deny"',
330
- actual: JSON.stringify(tools.prune.permission),
331
- });
332
- }
333
- }
334
- }
335
283
  }
336
- // Strategies validators
337
284
  const strategies = config.strategies;
338
285
  if (strategies) {
339
- // deduplication
340
286
  if (strategies.deduplication?.enabled !== undefined &&
341
287
  typeof strategies.deduplication.enabled !== "boolean") {
342
288
  errors.push({
@@ -353,7 +299,6 @@ export function validateConfigTypes(config) {
353
299
  actual: typeof strategies.deduplication.protectedTools,
354
300
  });
355
301
  }
356
- // supersedeWrites
357
302
  if (strategies.supersedeWrites) {
358
303
  if (strategies.supersedeWrites.enabled !== undefined &&
359
304
  typeof strategies.supersedeWrites.enabled !== "boolean") {
@@ -364,7 +309,6 @@ export function validateConfigTypes(config) {
364
309
  });
365
310
  }
366
311
  }
367
- // purgeErrors
368
312
  if (strategies.purgeErrors) {
369
313
  if (strategies.purgeErrors.enabled !== undefined &&
370
314
  typeof strategies.purgeErrors.enabled !== "boolean") {
@@ -403,7 +347,6 @@ export function validateConfigTypes(config) {
403
347
  }
404
348
  return errors;
405
349
  }
406
- // Show validation warnings for a config file
407
350
  function showConfigValidationWarnings(ctx, configPath, configData, isProject) {
408
351
  const invalidKeys = getInvalidConfigKeys(configData);
409
352
  const typeErrors = validateConfigTypes(configData);
@@ -429,7 +372,7 @@ function showConfigValidationWarnings(ctx, configPath, configData, isProject) {
429
372
  try {
430
373
  ctx.client.tui.showToast({
431
374
  body: {
432
- title: `DCP: Invalid ${configType}`,
375
+ title: `DCP: ${configType} warning`,
433
376
  message: `${configPath}\n${messages.join("\n")}`,
434
377
  variant: "warning",
435
378
  duration: 7000,
@@ -457,24 +400,12 @@ const defaultConfig = {
457
400
  turns: 4,
458
401
  },
459
402
  protectedFilePatterns: [],
460
- tools: {
461
- settings: {
462
- nudgeEnabled: true,
463
- nudgeFrequency: 10,
464
- protectedTools: [...DEFAULT_PROTECTED_TOOLS],
465
- contextLimit: 100000,
466
- },
467
- distill: {
468
- permission: "allow",
469
- showDistillation: false,
470
- },
471
- compress: {
472
- permission: "deny",
473
- showCompression: false,
474
- },
475
- prune: {
476
- permission: "allow",
477
- },
403
+ compress: {
404
+ permission: "allow",
405
+ showCompression: false,
406
+ contextLimit: 100000,
407
+ nudgeFrequency: 5,
408
+ iterationNudgeThreshold: 15,
478
409
  },
479
410
  strategies: {
480
411
  deduplication: {
@@ -504,50 +435,44 @@ function findOpencodeDir(startDir) {
504
435
  return candidate;
505
436
  }
506
437
  const parent = dirname(current);
507
- if (parent === current)
438
+ if (parent === current) {
508
439
  break;
440
+ }
509
441
  current = parent;
510
442
  }
511
443
  return null;
512
444
  }
513
445
  function getConfigPaths(ctx) {
514
- // Global: ~/.config/opencode/dcp.jsonc|json
515
- let globalPath = null;
516
- if (existsSync(GLOBAL_CONFIG_PATH_JSONC)) {
517
- globalPath = GLOBAL_CONFIG_PATH_JSONC;
518
- }
519
- else if (existsSync(GLOBAL_CONFIG_PATH_JSON)) {
520
- globalPath = GLOBAL_CONFIG_PATH_JSON;
521
- }
522
- // Custom config directory: $OPENCODE_CONFIG_DIR/dcp.jsonc|json
523
- let configDirPath = null;
446
+ const global = existsSync(GLOBAL_CONFIG_PATH_JSONC)
447
+ ? GLOBAL_CONFIG_PATH_JSONC
448
+ : existsSync(GLOBAL_CONFIG_PATH_JSON)
449
+ ? GLOBAL_CONFIG_PATH_JSON
450
+ : null;
451
+ let configDir = null;
524
452
  const opencodeConfigDir = process.env.OPENCODE_CONFIG_DIR;
525
453
  if (opencodeConfigDir) {
526
454
  const configJsonc = join(opencodeConfigDir, "dcp.jsonc");
527
455
  const configJson = join(opencodeConfigDir, "dcp.json");
528
- if (existsSync(configJsonc)) {
529
- configDirPath = configJsonc;
530
- }
531
- else if (existsSync(configJson)) {
532
- configDirPath = configJson;
533
- }
456
+ configDir = existsSync(configJsonc)
457
+ ? configJsonc
458
+ : existsSync(configJson)
459
+ ? configJson
460
+ : null;
534
461
  }
535
- // Project: <project>/.opencode/dcp.jsonc|json
536
- let projectPath = null;
462
+ let project = null;
537
463
  if (ctx?.directory) {
538
464
  const opencodeDir = findOpencodeDir(ctx.directory);
539
465
  if (opencodeDir) {
540
466
  const projectJsonc = join(opencodeDir, "dcp.jsonc");
541
467
  const projectJson = join(opencodeDir, "dcp.json");
542
- if (existsSync(projectJsonc)) {
543
- projectPath = projectJsonc;
544
- }
545
- else if (existsSync(projectJson)) {
546
- projectPath = projectJson;
547
- }
468
+ project = existsSync(projectJsonc)
469
+ ? projectJsonc
470
+ : existsSync(projectJson)
471
+ ? projectJson
472
+ : null;
548
473
  }
549
474
  }
550
- return { global: globalPath, configDir: configDirPath, project: projectPath };
475
+ return { global, configDir, project };
551
476
  }
552
477
  function createDefaultConfig() {
553
478
  if (!existsSync(GLOBAL_CONFIG_DIR)) {
@@ -560,16 +485,15 @@ function createDefaultConfig() {
560
485
  writeFileSync(GLOBAL_CONFIG_PATH_JSONC, configContent, "utf-8");
561
486
  }
562
487
  function loadConfigFile(configPath) {
563
- let fileContent;
488
+ let fileContent = "";
564
489
  try {
565
490
  fileContent = readFileSync(configPath, "utf-8");
566
491
  }
567
492
  catch {
568
- // File doesn't exist or can't be read - not a parse error
569
493
  return { data: null };
570
494
  }
571
495
  try {
572
- const parsed = parse(fileContent);
496
+ const parsed = parse(fileContent, undefined, { allowTrailingComma: true });
573
497
  if (parsed === undefined || parsed === null) {
574
498
  return { data: null, parseError: "Config file is empty or invalid" };
575
499
  }
@@ -580,8 +504,9 @@ function loadConfigFile(configPath) {
580
504
  }
581
505
  }
582
506
  function mergeStrategies(base, override) {
583
- if (!override)
507
+ if (!override) {
584
508
  return base;
509
+ }
585
510
  return {
586
511
  deduplication: {
587
512
  enabled: override.deduplication?.enabled ?? base.deduplication.enabled,
@@ -607,38 +532,23 @@ function mergeStrategies(base, override) {
607
532
  },
608
533
  };
609
534
  }
610
- function mergeTools(base, override) {
611
- if (!override)
535
+ function mergeCompress(base, override) {
536
+ if (!override) {
612
537
  return base;
538
+ }
613
539
  return {
614
- settings: {
615
- nudgeEnabled: override.settings?.nudgeEnabled ?? base.settings.nudgeEnabled,
616
- nudgeFrequency: override.settings?.nudgeFrequency ?? base.settings.nudgeFrequency,
617
- protectedTools: [
618
- ...new Set([
619
- ...base.settings.protectedTools,
620
- ...(override.settings?.protectedTools ?? []),
621
- ]),
622
- ],
623
- contextLimit: override.settings?.contextLimit ?? base.settings.contextLimit,
624
- modelLimits: override.settings?.modelLimits ?? base.settings.modelLimits,
625
- },
626
- distill: {
627
- permission: override.distill?.permission ?? base.distill.permission,
628
- showDistillation: override.distill?.showDistillation ?? base.distill.showDistillation,
629
- },
630
- compress: {
631
- permission: override.compress?.permission ?? base.compress.permission,
632
- showCompression: override.compress?.showCompression ?? base.compress.showCompression,
633
- },
634
- prune: {
635
- permission: override.prune?.permission ?? base.prune.permission,
636
- },
540
+ permission: override.permission ?? base.permission,
541
+ showCompression: override.showCompression ?? base.showCompression,
542
+ contextLimit: override.contextLimit ?? base.contextLimit,
543
+ modelLimits: override.modelLimits ?? base.modelLimits,
544
+ nudgeFrequency: override.nudgeFrequency ?? base.nudgeFrequency,
545
+ iterationNudgeThreshold: override.iterationNudgeThreshold ?? base.iterationNudgeThreshold,
637
546
  };
638
547
  }
639
548
  function mergeCommands(base, override) {
640
- if (override === undefined)
549
+ if (!override) {
641
550
  return base;
551
+ }
642
552
  return {
643
553
  enabled: override.enabled ?? base.enabled,
644
554
  protectedTools: [...new Set([...base.protectedTools, ...(override.protectedTools ?? [])])],
@@ -665,24 +575,16 @@ function deepCloneConfig(config) {
665
575
  },
666
576
  turnProtection: { ...config.turnProtection },
667
577
  protectedFilePatterns: [...config.protectedFilePatterns],
668
- tools: {
669
- settings: {
670
- ...config.tools.settings,
671
- protectedTools: [...config.tools.settings.protectedTools],
672
- modelLimits: { ...config.tools.settings.modelLimits },
673
- },
674
- distill: { ...config.tools.distill },
675
- compress: { ...config.tools.compress },
676
- prune: { ...config.tools.prune },
578
+ compress: {
579
+ ...config.compress,
580
+ modelLimits: { ...config.compress.modelLimits },
677
581
  },
678
582
  strategies: {
679
583
  deduplication: {
680
584
  ...config.strategies.deduplication,
681
585
  protectedTools: [...config.strategies.deduplication.protectedTools],
682
586
  },
683
- supersedeWrites: {
684
- ...config.strategies.supersedeWrites,
685
- },
587
+ supersedeWrites: { ...config.strategies.supersedeWrites },
686
588
  purgeErrors: {
687
589
  ...config.strategies.purgeErrors,
688
590
  protectedTools: [...config.strategies.purgeErrors.protectedTools],
@@ -690,141 +592,65 @@ function deepCloneConfig(config) {
690
592
  },
691
593
  };
692
594
  }
595
+ function mergeLayer(config, data) {
596
+ return {
597
+ enabled: data.enabled ?? config.enabled,
598
+ debug: data.debug ?? config.debug,
599
+ pruneNotification: data.pruneNotification ?? config.pruneNotification,
600
+ pruneNotificationType: data.pruneNotificationType ?? config.pruneNotificationType,
601
+ commands: mergeCommands(config.commands, data.commands),
602
+ manualMode: mergeManualMode(config.manualMode, data.manualMode),
603
+ turnProtection: {
604
+ enabled: data.turnProtection?.enabled ?? config.turnProtection.enabled,
605
+ turns: data.turnProtection?.turns ?? config.turnProtection.turns,
606
+ },
607
+ protectedFilePatterns: [
608
+ ...new Set([...config.protectedFilePatterns, ...(data.protectedFilePatterns ?? [])]),
609
+ ],
610
+ compress: mergeCompress(config.compress, data.compress),
611
+ strategies: mergeStrategies(config.strategies, data.strategies),
612
+ };
613
+ }
614
+ function scheduleParseWarning(ctx, title, message) {
615
+ setTimeout(() => {
616
+ try {
617
+ ctx.client.tui.showToast({
618
+ body: {
619
+ title,
620
+ message,
621
+ variant: "warning",
622
+ duration: 7000,
623
+ },
624
+ });
625
+ }
626
+ catch { }
627
+ }, 7000);
628
+ }
693
629
  export function getConfig(ctx) {
694
630
  let config = deepCloneConfig(defaultConfig);
695
631
  const configPaths = getConfigPaths(ctx);
696
- // Load and merge global config
697
- if (configPaths.global) {
698
- const result = loadConfigFile(configPaths.global);
699
- if (result.parseError) {
700
- setTimeout(async () => {
701
- try {
702
- ctx.client.tui.showToast({
703
- body: {
704
- title: "DCP: Invalid config",
705
- message: `${configPaths.global}\n${result.parseError}\nUsing default values`,
706
- variant: "warning",
707
- duration: 7000,
708
- },
709
- });
710
- }
711
- catch { }
712
- }, 7000);
713
- }
714
- else if (result.data) {
715
- // Validate config keys and types
716
- showConfigValidationWarnings(ctx, configPaths.global, result.data, false);
717
- config = {
718
- enabled: result.data.enabled ?? config.enabled,
719
- debug: result.data.debug ?? config.debug,
720
- pruneNotification: result.data.pruneNotification ?? config.pruneNotification,
721
- pruneNotificationType: result.data.pruneNotificationType ?? config.pruneNotificationType,
722
- commands: mergeCommands(config.commands, result.data.commands),
723
- manualMode: mergeManualMode(config.manualMode, result.data.manualMode),
724
- turnProtection: {
725
- enabled: result.data.turnProtection?.enabled ?? config.turnProtection.enabled,
726
- turns: result.data.turnProtection?.turns ?? config.turnProtection.turns,
727
- },
728
- protectedFilePatterns: [
729
- ...new Set([
730
- ...config.protectedFilePatterns,
731
- ...(result.data.protectedFilePatterns ?? []),
732
- ]),
733
- ],
734
- tools: mergeTools(config.tools, result.data.tools),
735
- strategies: mergeStrategies(config.strategies, result.data.strategies),
736
- };
737
- }
738
- }
739
- else {
740
- // No config exists, create default
632
+ if (!configPaths.global) {
741
633
  createDefaultConfig();
742
634
  }
743
- // Load and merge $OPENCODE_CONFIG_DIR/dcp.jsonc|json (overrides global)
744
- if (configPaths.configDir) {
745
- const result = loadConfigFile(configPaths.configDir);
746
- if (result.parseError) {
747
- setTimeout(async () => {
748
- try {
749
- ctx.client.tui.showToast({
750
- body: {
751
- title: "DCP: Invalid configDir config",
752
- message: `${configPaths.configDir}\n${result.parseError}\nUsing global/default values`,
753
- variant: "warning",
754
- duration: 7000,
755
- },
756
- });
757
- }
758
- catch { }
759
- }, 7000);
760
- }
761
- else if (result.data) {
762
- // Validate config keys and types
763
- showConfigValidationWarnings(ctx, configPaths.configDir, result.data, true);
764
- config = {
765
- enabled: result.data.enabled ?? config.enabled,
766
- debug: result.data.debug ?? config.debug,
767
- pruneNotification: result.data.pruneNotification ?? config.pruneNotification,
768
- pruneNotificationType: result.data.pruneNotificationType ?? config.pruneNotificationType,
769
- commands: mergeCommands(config.commands, result.data.commands),
770
- manualMode: mergeManualMode(config.manualMode, result.data.manualMode),
771
- turnProtection: {
772
- enabled: result.data.turnProtection?.enabled ?? config.turnProtection.enabled,
773
- turns: result.data.turnProtection?.turns ?? config.turnProtection.turns,
774
- },
775
- protectedFilePatterns: [
776
- ...new Set([
777
- ...config.protectedFilePatterns,
778
- ...(result.data.protectedFilePatterns ?? []),
779
- ]),
780
- ],
781
- tools: mergeTools(config.tools, result.data.tools),
782
- strategies: mergeStrategies(config.strategies, result.data.strategies),
783
- };
635
+ const layers = [
636
+ { path: configPaths.global, name: "config", isProject: false },
637
+ { path: configPaths.configDir, name: "configDir config", isProject: true },
638
+ { path: configPaths.project, name: "project config", isProject: true },
639
+ ];
640
+ for (const layer of layers) {
641
+ if (!layer.path) {
642
+ continue;
784
643
  }
785
- }
786
- // Load and merge project config (overrides global)
787
- if (configPaths.project) {
788
- const result = loadConfigFile(configPaths.project);
644
+ const result = loadConfigFile(layer.path);
789
645
  if (result.parseError) {
790
- setTimeout(async () => {
791
- try {
792
- ctx.client.tui.showToast({
793
- body: {
794
- title: "DCP: Invalid project config",
795
- message: `${configPaths.project}\n${result.parseError}\nUsing global/default values`,
796
- variant: "warning",
797
- duration: 7000,
798
- },
799
- });
800
- }
801
- catch { }
802
- }, 7000);
646
+ scheduleParseWarning(ctx, `DCP: Invalid ${layer.name}`, `${layer.path}\n${result.parseError}\nUsing previous/default values`);
647
+ continue;
803
648
  }
804
- else if (result.data) {
805
- // Validate config keys and types
806
- showConfigValidationWarnings(ctx, configPaths.project, result.data, true);
807
- config = {
808
- enabled: result.data.enabled ?? config.enabled,
809
- debug: result.data.debug ?? config.debug,
810
- pruneNotification: result.data.pruneNotification ?? config.pruneNotification,
811
- pruneNotificationType: result.data.pruneNotificationType ?? config.pruneNotificationType,
812
- commands: mergeCommands(config.commands, result.data.commands),
813
- manualMode: mergeManualMode(config.manualMode, result.data.manualMode),
814
- turnProtection: {
815
- enabled: result.data.turnProtection?.enabled ?? config.turnProtection.enabled,
816
- turns: result.data.turnProtection?.turns ?? config.turnProtection.turns,
817
- },
818
- protectedFilePatterns: [
819
- ...new Set([
820
- ...config.protectedFilePatterns,
821
- ...(result.data.protectedFilePatterns ?? []),
822
- ]),
823
- ],
824
- tools: mergeTools(config.tools, result.data.tools),
825
- strategies: mergeStrategies(config.strategies, result.data.strategies),
826
- };
649
+ if (!result.data) {
650
+ continue;
827
651
  }
652
+ showConfigValidationWarnings(ctx, layer.path, result.data, layer.isProject);
653
+ config = mergeLayer(config, result.data);
828
654
  }
829
655
  return config;
830
656
  }