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