@tuanhung303/opencode-acp 2.1.0

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