opencode-recall 0.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 (163) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +161 -0
  3. package/dist/index.d.ts +4 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +123 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/lib/config.d.ts +73 -0
  8. package/dist/lib/config.d.ts.map +1 -0
  9. package/dist/lib/config.js +779 -0
  10. package/dist/lib/config.js.map +1 -0
  11. package/dist/lib/explore-tool.d.ts +11 -0
  12. package/dist/lib/explore-tool.d.ts.map +1 -0
  13. package/dist/lib/explore-tool.js +240 -0
  14. package/dist/lib/explore-tool.js.map +1 -0
  15. package/dist/lib/hooks.d.ts +10 -0
  16. package/dist/lib/hooks.d.ts.map +1 -0
  17. package/dist/lib/hooks.js +58 -0
  18. package/dist/lib/hooks.js.map +1 -0
  19. package/dist/lib/logger.d.ts +31 -0
  20. package/dist/lib/logger.d.ts.map +1 -0
  21. package/dist/lib/logger.js +183 -0
  22. package/dist/lib/logger.js.map +1 -0
  23. package/dist/lib/messages/index.d.ts +3 -0
  24. package/dist/lib/messages/index.d.ts.map +1 -0
  25. package/dist/lib/messages/index.js +3 -0
  26. package/dist/lib/messages/index.js.map +1 -0
  27. package/dist/lib/messages/inject.d.ts +5 -0
  28. package/dist/lib/messages/inject.d.ts.map +1 -0
  29. package/dist/lib/messages/inject.js +114 -0
  30. package/dist/lib/messages/inject.js.map +1 -0
  31. package/dist/lib/messages/prune.d.ts +5 -0
  32. package/dist/lib/messages/prune.d.ts.map +1 -0
  33. package/dist/lib/messages/prune.js +179 -0
  34. package/dist/lib/messages/prune.js.map +1 -0
  35. package/dist/lib/messages/utils.d.ts +10 -0
  36. package/dist/lib/messages/utils.d.ts.map +1 -0
  37. package/dist/lib/messages/utils.js +193 -0
  38. package/dist/lib/messages/utils.js.map +1 -0
  39. package/dist/lib/prompts/agent/rcl-explorer.d.ts +2 -0
  40. package/dist/lib/prompts/agent/rcl-explorer.d.ts.map +1 -0
  41. package/dist/lib/prompts/agent/rcl-explorer.js +39 -0
  42. package/dist/lib/prompts/agent/rcl-explorer.js.map +1 -0
  43. package/dist/lib/prompts/discard-tool-spec.d.ts +2 -0
  44. package/dist/lib/prompts/discard-tool-spec.d.ts.map +1 -0
  45. package/dist/lib/prompts/discard-tool-spec.js +41 -0
  46. package/dist/lib/prompts/discard-tool-spec.js.map +1 -0
  47. package/dist/lib/prompts/extract-tool-spec.d.ts +2 -0
  48. package/dist/lib/prompts/extract-tool-spec.d.ts.map +1 -0
  49. package/dist/lib/prompts/extract-tool-spec.js +48 -0
  50. package/dist/lib/prompts/extract-tool-spec.js.map +1 -0
  51. package/dist/lib/prompts/index.d.ts +2 -0
  52. package/dist/lib/prompts/index.d.ts.map +1 -0
  53. package/dist/lib/prompts/index.js +41 -0
  54. package/dist/lib/prompts/index.js.map +1 -0
  55. package/dist/lib/prompts/nudge/both.d.ts +2 -0
  56. package/dist/lib/prompts/nudge/both.d.ts.map +1 -0
  57. package/dist/lib/prompts/nudge/both.js +11 -0
  58. package/dist/lib/prompts/nudge/both.js.map +1 -0
  59. package/dist/lib/prompts/nudge/discard.d.ts +2 -0
  60. package/dist/lib/prompts/nudge/discard.d.ts.map +1 -0
  61. package/dist/lib/prompts/nudge/discard.js +10 -0
  62. package/dist/lib/prompts/nudge/discard.js.map +1 -0
  63. package/dist/lib/prompts/nudge/extract.d.ts +2 -0
  64. package/dist/lib/prompts/nudge/extract.d.ts.map +1 -0
  65. package/dist/lib/prompts/nudge/extract.js +10 -0
  66. package/dist/lib/prompts/nudge/extract.js.map +1 -0
  67. package/dist/lib/prompts/restore-tool-spec.d.ts +2 -0
  68. package/dist/lib/prompts/restore-tool-spec.d.ts.map +1 -0
  69. package/dist/lib/prompts/restore-tool-spec.js +21 -0
  70. package/dist/lib/prompts/restore-tool-spec.js.map +1 -0
  71. package/dist/lib/prompts/system/both.d.ts +2 -0
  72. package/dist/lib/prompts/system/both.d.ts.map +1 -0
  73. package/dist/lib/prompts/system/both.js +72 -0
  74. package/dist/lib/prompts/system/both.js.map +1 -0
  75. package/dist/lib/prompts/system/core.d.ts +2 -0
  76. package/dist/lib/prompts/system/core.d.ts.map +1 -0
  77. package/dist/lib/prompts/system/core.js +26 -0
  78. package/dist/lib/prompts/system/core.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 +63 -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 +63 -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/pruned-blobs.d.ts +19 -0
  92. package/dist/lib/pruned-blobs.d.ts.map +1 -0
  93. package/dist/lib/pruned-blobs.js +111 -0
  94. package/dist/lib/pruned-blobs.js.map +1 -0
  95. package/dist/lib/rcl-pointer.d.ts +3 -0
  96. package/dist/lib/rcl-pointer.d.ts.map +1 -0
  97. package/dist/lib/rcl-pointer.js +63 -0
  98. package/dist/lib/rcl-pointer.js.map +1 -0
  99. package/dist/lib/restore-tool.d.ts +15 -0
  100. package/dist/lib/restore-tool.d.ts.map +1 -0
  101. package/dist/lib/restore-tool.js +142 -0
  102. package/dist/lib/restore-tool.js.map +1 -0
  103. package/dist/lib/shared-utils.d.ts +4 -0
  104. package/dist/lib/shared-utils.d.ts.map +1 -0
  105. package/dist/lib/shared-utils.js +14 -0
  106. package/dist/lib/shared-utils.js.map +1 -0
  107. package/dist/lib/state/index.d.ts +4 -0
  108. package/dist/lib/state/index.d.ts.map +1 -0
  109. package/dist/lib/state/index.js +4 -0
  110. package/dist/lib/state/index.js.map +1 -0
  111. package/dist/lib/state/persistence.d.ts +16 -0
  112. package/dist/lib/state/persistence.d.ts.map +1 -0
  113. package/dist/lib/state/persistence.js +73 -0
  114. package/dist/lib/state/persistence.js.map +1 -0
  115. package/dist/lib/state/state.d.ts +8 -0
  116. package/dist/lib/state/state.d.ts.map +1 -0
  117. package/dist/lib/state/state.js +114 -0
  118. package/dist/lib/state/state.js.map +1 -0
  119. package/dist/lib/state/tool-cache.d.ts +13 -0
  120. package/dist/lib/state/tool-cache.d.ts.map +1 -0
  121. package/dist/lib/state/tool-cache.js +79 -0
  122. package/dist/lib/state/tool-cache.js.map +1 -0
  123. package/dist/lib/state/types.d.ts +33 -0
  124. package/dist/lib/state/types.d.ts.map +1 -0
  125. package/dist/lib/state/types.js +2 -0
  126. package/dist/lib/state/types.js.map +1 -0
  127. package/dist/lib/state/utils.d.ts +2 -0
  128. package/dist/lib/state/utils.d.ts.map +1 -0
  129. package/dist/lib/state/utils.js +10 -0
  130. package/dist/lib/state/utils.js.map +1 -0
  131. package/dist/lib/strategies/deduplication.d.ts +10 -0
  132. package/dist/lib/strategies/deduplication.d.ts.map +1 -0
  133. package/dist/lib/strategies/deduplication.js +94 -0
  134. package/dist/lib/strategies/deduplication.js.map +1 -0
  135. package/dist/lib/strategies/index.d.ts +5 -0
  136. package/dist/lib/strategies/index.d.ts.map +1 -0
  137. package/dist/lib/strategies/index.js +5 -0
  138. package/dist/lib/strategies/index.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/supersede-writes.d.ts +13 -0
  144. package/dist/lib/strategies/supersede-writes.d.ts.map +1 -0
  145. package/dist/lib/strategies/supersede-writes.js +84 -0
  146. package/dist/lib/strategies/supersede-writes.js.map +1 -0
  147. package/dist/lib/strategies/tools.d.ts +14 -0
  148. package/dist/lib/strategies/tools.d.ts.map +1 -0
  149. package/dist/lib/strategies/tools.js +135 -0
  150. package/dist/lib/strategies/tools.js.map +1 -0
  151. package/dist/lib/strategies/utils.d.ts +13 -0
  152. package/dist/lib/strategies/utils.d.ts.map +1 -0
  153. package/dist/lib/strategies/utils.js +93 -0
  154. package/dist/lib/strategies/utils.js.map +1 -0
  155. package/dist/lib/ui/notification.d.ts +8 -0
  156. package/dist/lib/ui/notification.d.ts.map +1 -0
  157. package/dist/lib/ui/notification.js +54 -0
  158. package/dist/lib/ui/notification.js.map +1 -0
  159. package/dist/lib/ui/utils.d.ts +15 -0
  160. package/dist/lib/ui/utils.d.ts.map +1 -0
  161. package/dist/lib/ui/utils.js +87 -0
  162. package/dist/lib/ui/utils.js.map +1 -0
  163. package/package.json +63 -0
@@ -0,0 +1,779 @@
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
+ "rcl_explore",
12
+ "batch",
13
+ "write",
14
+ "edit",
15
+ "plan_enter",
16
+ "plan_exit",
17
+ ];
18
+ // Valid config keys for validation against user config
19
+ export const VALID_CONFIG_KEYS = new Set([
20
+ // Top-level keys
21
+ "$schema",
22
+ "enabled",
23
+ "debug",
24
+ "showUpdateToasts", // Deprecated but kept for backwards compatibility
25
+ "pruneNotification",
26
+ "turnProtection",
27
+ "turnProtection.enabled",
28
+ "turnProtection.turns",
29
+ "protectedFilePatterns",
30
+ "explore",
31
+ "explore.enabled",
32
+ "explore.agentModel",
33
+ "explore.agentVariant",
34
+ "explore.timeoutMs",
35
+ "explore.maxResultChars",
36
+ "tools",
37
+ "tools.settings",
38
+ "tools.settings.nudgeEnabled",
39
+ "tools.settings.nudgeFrequency",
40
+ "tools.settings.restoreProtectionTurns",
41
+ "tools.settings.restoreMaxBytes",
42
+ "tools.settings.protectedTools",
43
+ "tools.discard",
44
+ "tools.discard.enabled",
45
+ "tools.extract",
46
+ "tools.extract.enabled",
47
+ "tools.extract.showDistillation",
48
+ "strategies",
49
+ // strategies.deduplication
50
+ "strategies.deduplication",
51
+ "strategies.deduplication.enabled",
52
+ "strategies.deduplication.protectedTools",
53
+ // strategies.supersedeWrites
54
+ "strategies.supersedeWrites",
55
+ "strategies.supersedeWrites.enabled",
56
+ // strategies.purgeErrors
57
+ "strategies.purgeErrors",
58
+ "strategies.purgeErrors.enabled",
59
+ "strategies.purgeErrors.turns",
60
+ "strategies.purgeErrors.protectedTools",
61
+ ]);
62
+ // Extract all key paths from a config object for validation
63
+ function getConfigKeyPaths(obj, prefix = "") {
64
+ const keys = [];
65
+ for (const key of Object.keys(obj)) {
66
+ const fullKey = prefix ? `${prefix}.${key}` : key;
67
+ keys.push(fullKey);
68
+ if (obj[key] && typeof obj[key] === "object" && !Array.isArray(obj[key])) {
69
+ keys.push(...getConfigKeyPaths(obj[key], fullKey));
70
+ }
71
+ }
72
+ return keys;
73
+ }
74
+ // Returns invalid keys found in user config
75
+ export function getInvalidConfigKeys(userConfig) {
76
+ const userKeys = getConfigKeyPaths(userConfig);
77
+ return userKeys.filter((key) => !VALID_CONFIG_KEYS.has(key));
78
+ }
79
+ function validateConfigTypes(config) {
80
+ const errors = [];
81
+ // Top-level validators
82
+ if (config.enabled !== undefined && typeof config.enabled !== "boolean") {
83
+ errors.push({ key: "enabled", expected: "boolean", actual: typeof config.enabled });
84
+ }
85
+ if (config.debug !== undefined && typeof config.debug !== "boolean") {
86
+ errors.push({ key: "debug", expected: "boolean", actual: typeof config.debug });
87
+ }
88
+ if (config.pruneNotification !== undefined) {
89
+ const validValues = ["off", "minimal", "detailed"];
90
+ if (!validValues.includes(config.pruneNotification)) {
91
+ errors.push({
92
+ key: "pruneNotification",
93
+ expected: '"off" | "minimal" | "detailed"',
94
+ actual: JSON.stringify(config.pruneNotification),
95
+ });
96
+ }
97
+ }
98
+ if (config.protectedFilePatterns !== undefined) {
99
+ if (!Array.isArray(config.protectedFilePatterns)) {
100
+ errors.push({
101
+ key: "protectedFilePatterns",
102
+ expected: "string[]",
103
+ actual: typeof config.protectedFilePatterns,
104
+ });
105
+ }
106
+ else if (!config.protectedFilePatterns.every((v) => typeof v === "string")) {
107
+ errors.push({
108
+ key: "protectedFilePatterns",
109
+ expected: "string[]",
110
+ actual: "non-string entries",
111
+ });
112
+ }
113
+ }
114
+ // Explore (sub-agent) validators
115
+ if (config.explore) {
116
+ if (config.explore.enabled !== undefined && typeof config.explore.enabled !== "boolean") {
117
+ errors.push({
118
+ key: "explore.enabled",
119
+ expected: "boolean",
120
+ actual: typeof config.explore.enabled,
121
+ });
122
+ }
123
+ if (config.explore.agentModel !== undefined &&
124
+ typeof config.explore.agentModel !== "string") {
125
+ errors.push({
126
+ key: "explore.agentModel",
127
+ expected: "string",
128
+ actual: typeof config.explore.agentModel,
129
+ });
130
+ }
131
+ if (config.explore.agentVariant !== undefined &&
132
+ typeof config.explore.agentVariant !== "string") {
133
+ errors.push({
134
+ key: "explore.agentVariant",
135
+ expected: "string",
136
+ actual: typeof config.explore.agentVariant,
137
+ });
138
+ }
139
+ if (config.explore.timeoutMs !== undefined &&
140
+ typeof config.explore.timeoutMs !== "number") {
141
+ errors.push({
142
+ key: "explore.timeoutMs",
143
+ expected: "number",
144
+ actual: typeof config.explore.timeoutMs,
145
+ });
146
+ }
147
+ if (config.explore.maxResultChars !== undefined &&
148
+ typeof config.explore.maxResultChars !== "number") {
149
+ errors.push({
150
+ key: "explore.maxResultChars",
151
+ expected: "number",
152
+ actual: typeof config.explore.maxResultChars,
153
+ });
154
+ }
155
+ }
156
+ // Top-level turnProtection validator
157
+ if (config.turnProtection) {
158
+ if (config.turnProtection.enabled !== undefined &&
159
+ typeof config.turnProtection.enabled !== "boolean") {
160
+ errors.push({
161
+ key: "turnProtection.enabled",
162
+ expected: "boolean",
163
+ actual: typeof config.turnProtection.enabled,
164
+ });
165
+ }
166
+ if (config.turnProtection.turns !== undefined &&
167
+ typeof config.turnProtection.turns !== "number") {
168
+ errors.push({
169
+ key: "turnProtection.turns",
170
+ expected: "number",
171
+ actual: typeof config.turnProtection.turns,
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.restoreProtectionTurns !== undefined &&
196
+ typeof tools.settings.restoreProtectionTurns !== "number") {
197
+ errors.push({
198
+ key: "tools.settings.restoreProtectionTurns",
199
+ expected: "number",
200
+ actual: typeof tools.settings.restoreProtectionTurns,
201
+ });
202
+ }
203
+ if (tools.settings.restoreMaxBytes !== undefined &&
204
+ typeof tools.settings.restoreMaxBytes !== "number") {
205
+ errors.push({
206
+ key: "tools.settings.restoreMaxBytes",
207
+ expected: "number",
208
+ actual: typeof tools.settings.restoreMaxBytes,
209
+ });
210
+ }
211
+ if (tools.settings.protectedTools !== undefined &&
212
+ !Array.isArray(tools.settings.protectedTools)) {
213
+ errors.push({
214
+ key: "tools.settings.protectedTools",
215
+ expected: "string[]",
216
+ actual: typeof tools.settings.protectedTools,
217
+ });
218
+ }
219
+ }
220
+ if (tools.discard) {
221
+ if (tools.discard.enabled !== undefined && typeof tools.discard.enabled !== "boolean") {
222
+ errors.push({
223
+ key: "tools.discard.enabled",
224
+ expected: "boolean",
225
+ actual: typeof tools.discard.enabled,
226
+ });
227
+ }
228
+ }
229
+ if (tools.extract) {
230
+ if (tools.extract.enabled !== undefined && typeof tools.extract.enabled !== "boolean") {
231
+ errors.push({
232
+ key: "tools.extract.enabled",
233
+ expected: "boolean",
234
+ actual: typeof tools.extract.enabled,
235
+ });
236
+ }
237
+ if (tools.extract.showDistillation !== undefined &&
238
+ typeof tools.extract.showDistillation !== "boolean") {
239
+ errors.push({
240
+ key: "tools.extract.showDistillation",
241
+ expected: "boolean",
242
+ actual: typeof tools.extract.showDistillation,
243
+ });
244
+ }
245
+ }
246
+ }
247
+ // Strategies validators
248
+ const strategies = config.strategies;
249
+ if (strategies) {
250
+ // deduplication
251
+ if (strategies.deduplication?.enabled !== undefined &&
252
+ typeof strategies.deduplication.enabled !== "boolean") {
253
+ errors.push({
254
+ key: "strategies.deduplication.enabled",
255
+ expected: "boolean",
256
+ actual: typeof strategies.deduplication.enabled,
257
+ });
258
+ }
259
+ if (strategies.deduplication?.protectedTools !== undefined &&
260
+ !Array.isArray(strategies.deduplication.protectedTools)) {
261
+ errors.push({
262
+ key: "strategies.deduplication.protectedTools",
263
+ expected: "string[]",
264
+ actual: typeof strategies.deduplication.protectedTools,
265
+ });
266
+ }
267
+ // supersedeWrites
268
+ if (strategies.supersedeWrites) {
269
+ if (strategies.supersedeWrites.enabled !== undefined &&
270
+ typeof strategies.supersedeWrites.enabled !== "boolean") {
271
+ errors.push({
272
+ key: "strategies.supersedeWrites.enabled",
273
+ expected: "boolean",
274
+ actual: typeof strategies.supersedeWrites.enabled,
275
+ });
276
+ }
277
+ }
278
+ // purgeErrors
279
+ if (strategies.purgeErrors) {
280
+ if (strategies.purgeErrors.enabled !== undefined &&
281
+ typeof strategies.purgeErrors.enabled !== "boolean") {
282
+ errors.push({
283
+ key: "strategies.purgeErrors.enabled",
284
+ expected: "boolean",
285
+ actual: typeof strategies.purgeErrors.enabled,
286
+ });
287
+ }
288
+ if (strategies.purgeErrors.turns !== undefined &&
289
+ typeof strategies.purgeErrors.turns !== "number") {
290
+ errors.push({
291
+ key: "strategies.purgeErrors.turns",
292
+ expected: "number",
293
+ actual: typeof strategies.purgeErrors.turns,
294
+ });
295
+ }
296
+ if (strategies.purgeErrors.protectedTools !== undefined &&
297
+ !Array.isArray(strategies.purgeErrors.protectedTools)) {
298
+ errors.push({
299
+ key: "strategies.purgeErrors.protectedTools",
300
+ expected: "string[]",
301
+ actual: typeof strategies.purgeErrors.protectedTools,
302
+ });
303
+ }
304
+ }
305
+ }
306
+ return errors;
307
+ }
308
+ // Show validation warnings for a config file
309
+ function showConfigValidationWarnings(ctx, configPath, configData, isProject) {
310
+ const invalidKeys = getInvalidConfigKeys(configData);
311
+ const typeErrors = validateConfigTypes(configData);
312
+ if (invalidKeys.length === 0 && typeErrors.length === 0) {
313
+ return;
314
+ }
315
+ const configType = isProject ? "project config" : "config";
316
+ const messages = [];
317
+ if (invalidKeys.length > 0) {
318
+ const keyList = invalidKeys.slice(0, 3).join(", ");
319
+ const suffix = invalidKeys.length > 3 ? ` (+${invalidKeys.length - 3} more)` : "";
320
+ messages.push(`Unknown keys: ${keyList}${suffix}`);
321
+ }
322
+ if (typeErrors.length > 0) {
323
+ for (const err of typeErrors.slice(0, 2)) {
324
+ messages.push(`${err.key}: expected ${err.expected}, got ${err.actual}`);
325
+ }
326
+ if (typeErrors.length > 2) {
327
+ messages.push(`(+${typeErrors.length - 2} more type errors)`);
328
+ }
329
+ }
330
+ setTimeout(() => {
331
+ try {
332
+ ctx.client.tui.showToast({
333
+ body: {
334
+ title: `RCL: Invalid ${configType}`,
335
+ message: `${configPath}\n${messages.join("\n")}`,
336
+ variant: "warning",
337
+ duration: 7000,
338
+ },
339
+ });
340
+ }
341
+ catch { }
342
+ }, 7000);
343
+ }
344
+ const defaultConfig = {
345
+ enabled: true,
346
+ debug: false,
347
+ pruneNotification: "detailed",
348
+ turnProtection: {
349
+ enabled: false,
350
+ turns: 4,
351
+ },
352
+ protectedFilePatterns: [],
353
+ explore: {
354
+ enabled: true,
355
+ // When omitted, Recall will infer a model from your OpenCode agent config.
356
+ agentModel: undefined,
357
+ // Keep this low by default - rcl_explore is meant to be cheap.
358
+ agentVariant: "low",
359
+ timeoutMs: 120_000,
360
+ maxResultChars: 4000,
361
+ },
362
+ tools: {
363
+ settings: {
364
+ nudgeEnabled: true,
365
+ nudgeFrequency: 10,
366
+ restoreProtectionTurns: 4,
367
+ restoreMaxBytes: 8000,
368
+ protectedTools: [...DEFAULT_PROTECTED_TOOLS],
369
+ },
370
+ discard: {
371
+ enabled: true,
372
+ },
373
+ extract: {
374
+ enabled: true,
375
+ showDistillation: false,
376
+ },
377
+ },
378
+ strategies: {
379
+ deduplication: {
380
+ enabled: true,
381
+ protectedTools: [...DEFAULT_PROTECTED_TOOLS],
382
+ },
383
+ supersedeWrites: {
384
+ enabled: true,
385
+ },
386
+ purgeErrors: {
387
+ enabled: true,
388
+ turns: 4,
389
+ protectedTools: [...DEFAULT_PROTECTED_TOOLS],
390
+ },
391
+ },
392
+ };
393
+ const GLOBAL_CONFIG_DIR = join(homedir(), ".config", "opencode");
394
+ const GLOBAL_CONFIG_PATH_JSONC = join(GLOBAL_CONFIG_DIR, "rcl.jsonc");
395
+ const GLOBAL_CONFIG_PATH_JSON = join(GLOBAL_CONFIG_DIR, "rcl.json");
396
+ function findOpencodeDir(startDir) {
397
+ let current = startDir;
398
+ while (current !== "/") {
399
+ const candidate = join(current, ".opencode");
400
+ if (existsSync(candidate) && statSync(candidate).isDirectory()) {
401
+ return candidate;
402
+ }
403
+ const parent = dirname(current);
404
+ if (parent === current)
405
+ break;
406
+ current = parent;
407
+ }
408
+ return null;
409
+ }
410
+ function getConfigPaths(ctx) {
411
+ // Global: ~/.config/opencode/rcl.jsonc|json
412
+ let globalPath = null;
413
+ if (existsSync(GLOBAL_CONFIG_PATH_JSONC)) {
414
+ globalPath = GLOBAL_CONFIG_PATH_JSONC;
415
+ }
416
+ else if (existsSync(GLOBAL_CONFIG_PATH_JSON)) {
417
+ globalPath = GLOBAL_CONFIG_PATH_JSON;
418
+ }
419
+ // Custom config directory: $OPENCODE_CONFIG_DIR/rcl.jsonc|json
420
+ let configDirPath = null;
421
+ const opencodeConfigDir = process.env.OPENCODE_CONFIG_DIR;
422
+ if (opencodeConfigDir) {
423
+ const configJsonc = join(opencodeConfigDir, "rcl.jsonc");
424
+ const configJson = join(opencodeConfigDir, "rcl.json");
425
+ if (existsSync(configJsonc)) {
426
+ configDirPath = configJsonc;
427
+ }
428
+ else if (existsSync(configJson)) {
429
+ configDirPath = configJson;
430
+ }
431
+ }
432
+ // Project: <project>/.opencode/rcl.jsonc|json
433
+ let projectPath = null;
434
+ if (ctx?.directory) {
435
+ const opencodeDir = findOpencodeDir(ctx.directory);
436
+ if (opencodeDir) {
437
+ const projectJsonc = join(opencodeDir, "rcl.jsonc");
438
+ const projectJson = join(opencodeDir, "rcl.json");
439
+ if (existsSync(projectJsonc)) {
440
+ projectPath = projectJsonc;
441
+ }
442
+ else if (existsSync(projectJson)) {
443
+ projectPath = projectJson;
444
+ }
445
+ }
446
+ }
447
+ return { global: globalPath, configDir: configDirPath, project: projectPath };
448
+ }
449
+ function createDefaultConfig() {
450
+ if (!existsSync(GLOBAL_CONFIG_DIR)) {
451
+ mkdirSync(GLOBAL_CONFIG_DIR, { recursive: true });
452
+ }
453
+ const configContent = `{
454
+ "$schema": "https://raw.githubusercontent.com/SleepingBag945/opencode-recall/master/rcl.schema.json",
455
+ // Enable or disable the plugin
456
+ "enabled": true,
457
+ // Enable debug logging to ~/.config/opencode/logs/rcl/
458
+ "debug": false,
459
+ // Notification display: "off", "minimal", or "detailed"
460
+ "pruneNotification": "detailed",
461
+ // Protect from pruning for <turns> message turns
462
+ "turnProtection": {
463
+ "enabled": false,
464
+ "turns": 4
465
+ },
466
+ // Protect file operations from pruning via glob patterns
467
+ // Patterns match tool parameters.filePath (e.g. read/write/edit)
468
+ "protectedFilePatterns": [],
469
+ // Sub-agent based blob exploration (restore in a child session; return a small distilled answer)
470
+ "explore": {
471
+ "enabled": true,
472
+ // Optional: override the model for the rcl_explorer sub-agent.
473
+ // If omitted, Recall will infer a default from your OpenCode agent config.
474
+ // "agentModel": "openai/gpt-4o-mini",
475
+ // Optional: override reasoning effort for the sub-agent (if your OpenCode supports variants)
476
+ "agentVariant": "low",
477
+ // Timeout for the sub-agent session in milliseconds
478
+ "timeoutMs": 120000,
479
+ // Hard cap for how many characters rcl_explore returns to the main session
480
+ "maxResultChars": 4000
481
+ },
482
+ // LLM-driven context pruning tools
483
+ "tools": {
484
+ // Shared settings for all prune tools
485
+ "settings": {
486
+ // Nudge the LLM to use prune tools (every <nudgeFrequency> tool results)
487
+ "nudgeEnabled": true,
488
+ "nudgeFrequency": 10,
489
+ // Protect rcl_restore outputs for N turns before they can be pruned
490
+ "restoreProtectionTurns": 4,
491
+ // Block rcl_restore when content exceeds this size (bytes)
492
+ "restoreMaxBytes": 8000,
493
+ // Additional tools to protect from pruning
494
+ "protectedTools": []
495
+
496
+ },
497
+ // Removes tool content from context without preservation (for completed tasks or noise)
498
+ "discard": {
499
+ "enabled": true
500
+ },
501
+ // Distills key findings into preserved knowledge before removing raw content
502
+ "extract": {
503
+ "enabled": true,
504
+ // Show distillation content in the toast notification
505
+ "showDistillation": false
506
+ }
507
+ },
508
+ // Automatic pruning strategies
509
+ "strategies": {
510
+ // Remove duplicate tool calls (same tool with same arguments)
511
+ "deduplication": {
512
+ "enabled": true,
513
+ // Additional tools to protect from pruning
514
+ "protectedTools": []
515
+ },
516
+ // Prune write tool inputs when the file has been subsequently read
517
+ "supersedeWrites": {
518
+ "enabled": true
519
+ },
520
+
521
+ // Prune tool inputs for errored tools after X turns
522
+ "purgeErrors": {
523
+ "enabled": true,
524
+ // Number of turns before errored tool inputs are pruned
525
+ "turns": 4,
526
+ // Additional tools to protect from pruning
527
+ "protectedTools": []
528
+ }
529
+ }
530
+ }
531
+ `;
532
+ writeFileSync(GLOBAL_CONFIG_PATH_JSONC, configContent, "utf-8");
533
+ }
534
+ function loadConfigFile(configPath) {
535
+ let fileContent;
536
+ try {
537
+ fileContent = readFileSync(configPath, "utf-8");
538
+ }
539
+ catch {
540
+ // File doesn't exist or can't be read - not a parse error
541
+ return { data: null };
542
+ }
543
+ try {
544
+ const parsed = parse(fileContent);
545
+ if (parsed === undefined || parsed === null) {
546
+ return { data: null, parseError: "Config file is empty or invalid" };
547
+ }
548
+ return { data: parsed };
549
+ }
550
+ catch (error) {
551
+ return { data: null, parseError: error.message || "Failed to parse config" };
552
+ }
553
+ }
554
+ function mergeStrategies(base, override) {
555
+ if (!override)
556
+ return base;
557
+ return {
558
+ deduplication: {
559
+ enabled: override.deduplication?.enabled ?? base.deduplication.enabled,
560
+ protectedTools: [
561
+ ...new Set([
562
+ ...base.deduplication.protectedTools,
563
+ ...(override.deduplication?.protectedTools ?? []),
564
+ ]),
565
+ ],
566
+ },
567
+ supersedeWrites: {
568
+ enabled: override.supersedeWrites?.enabled ?? base.supersedeWrites.enabled,
569
+ },
570
+ purgeErrors: {
571
+ enabled: override.purgeErrors?.enabled ?? base.purgeErrors.enabled,
572
+ turns: override.purgeErrors?.turns ?? base.purgeErrors.turns,
573
+ protectedTools: [
574
+ ...new Set([
575
+ ...base.purgeErrors.protectedTools,
576
+ ...(override.purgeErrors?.protectedTools ?? []),
577
+ ]),
578
+ ],
579
+ },
580
+ };
581
+ }
582
+ function mergeTools(base, override) {
583
+ if (!override)
584
+ return base;
585
+ return {
586
+ settings: {
587
+ nudgeEnabled: override.settings?.nudgeEnabled ?? base.settings.nudgeEnabled,
588
+ nudgeFrequency: override.settings?.nudgeFrequency ?? base.settings.nudgeFrequency,
589
+ restoreProtectionTurns: override.settings?.restoreProtectionTurns ?? base.settings.restoreProtectionTurns,
590
+ restoreMaxBytes: override.settings?.restoreMaxBytes ?? base.settings.restoreMaxBytes,
591
+ protectedTools: [
592
+ ...new Set([
593
+ ...base.settings.protectedTools,
594
+ ...(override.settings?.protectedTools ?? []),
595
+ ]),
596
+ ],
597
+ },
598
+ discard: {
599
+ enabled: override.discard?.enabled ?? base.discard.enabled,
600
+ },
601
+ extract: {
602
+ enabled: override.extract?.enabled ?? base.extract.enabled,
603
+ showDistillation: override.extract?.showDistillation ?? base.extract.showDistillation,
604
+ },
605
+ };
606
+ }
607
+ function mergeExplore(base, override) {
608
+ if (!override)
609
+ return base;
610
+ return {
611
+ enabled: override.enabled ?? base.enabled,
612
+ agentModel: override.agentModel ?? base.agentModel,
613
+ agentVariant: override.agentVariant ?? base.agentVariant,
614
+ timeoutMs: override.timeoutMs ?? base.timeoutMs,
615
+ maxResultChars: override.maxResultChars ?? base.maxResultChars,
616
+ };
617
+ }
618
+ function deepCloneConfig(config) {
619
+ return {
620
+ ...config,
621
+ turnProtection: { ...config.turnProtection },
622
+ protectedFilePatterns: [...config.protectedFilePatterns],
623
+ explore: { ...config.explore },
624
+ tools: {
625
+ settings: {
626
+ ...config.tools.settings,
627
+ protectedTools: [...config.tools.settings.protectedTools],
628
+ },
629
+ discard: { ...config.tools.discard },
630
+ extract: { ...config.tools.extract },
631
+ },
632
+ strategies: {
633
+ deduplication: {
634
+ ...config.strategies.deduplication,
635
+ protectedTools: [...config.strategies.deduplication.protectedTools],
636
+ },
637
+ supersedeWrites: {
638
+ ...config.strategies.supersedeWrites,
639
+ },
640
+ purgeErrors: {
641
+ ...config.strategies.purgeErrors,
642
+ protectedTools: [...config.strategies.purgeErrors.protectedTools],
643
+ },
644
+ },
645
+ };
646
+ }
647
+ export function getConfig(ctx) {
648
+ let config = deepCloneConfig(defaultConfig);
649
+ const configPaths = getConfigPaths(ctx);
650
+ // Load and merge global config
651
+ if (configPaths.global) {
652
+ const result = loadConfigFile(configPaths.global);
653
+ if (result.parseError) {
654
+ setTimeout(async () => {
655
+ try {
656
+ ctx.client.tui.showToast({
657
+ body: {
658
+ title: "RCL: Invalid config",
659
+ message: `${configPaths.global}\n${result.parseError}\nUsing default values`,
660
+ variant: "warning",
661
+ duration: 7000,
662
+ },
663
+ });
664
+ }
665
+ catch { }
666
+ }, 7000);
667
+ }
668
+ else if (result.data) {
669
+ // Validate config keys and types
670
+ showConfigValidationWarnings(ctx, configPaths.global, result.data, false);
671
+ config = {
672
+ enabled: result.data.enabled ?? config.enabled,
673
+ debug: result.data.debug ?? config.debug,
674
+ pruneNotification: result.data.pruneNotification ?? config.pruneNotification,
675
+ turnProtection: {
676
+ enabled: result.data.turnProtection?.enabled ?? config.turnProtection.enabled,
677
+ turns: result.data.turnProtection?.turns ?? config.turnProtection.turns,
678
+ },
679
+ protectedFilePatterns: [
680
+ ...new Set([
681
+ ...config.protectedFilePatterns,
682
+ ...(result.data.protectedFilePatterns ?? []),
683
+ ]),
684
+ ],
685
+ explore: mergeExplore(config.explore, result.data.explore),
686
+ tools: mergeTools(config.tools, result.data.tools),
687
+ strategies: mergeStrategies(config.strategies, result.data.strategies),
688
+ };
689
+ }
690
+ }
691
+ else {
692
+ // No config exists, create default
693
+ createDefaultConfig();
694
+ }
695
+ // Load and merge $OPENCODE_CONFIG_DIR/rcl.jsonc|json (overrides global)
696
+ if (configPaths.configDir) {
697
+ const result = loadConfigFile(configPaths.configDir);
698
+ if (result.parseError) {
699
+ setTimeout(async () => {
700
+ try {
701
+ ctx.client.tui.showToast({
702
+ body: {
703
+ title: "RCL: Invalid configDir config",
704
+ message: `${configPaths.configDir}\n${result.parseError}\nUsing global/default values`,
705
+ variant: "warning",
706
+ duration: 7000,
707
+ },
708
+ });
709
+ }
710
+ catch { }
711
+ }, 7000);
712
+ }
713
+ else if (result.data) {
714
+ // Validate config keys and types
715
+ showConfigValidationWarnings(ctx, configPaths.configDir, result.data, true);
716
+ config = {
717
+ enabled: result.data.enabled ?? config.enabled,
718
+ debug: result.data.debug ?? config.debug,
719
+ pruneNotification: result.data.pruneNotification ?? config.pruneNotification,
720
+ turnProtection: {
721
+ enabled: result.data.turnProtection?.enabled ?? config.turnProtection.enabled,
722
+ turns: result.data.turnProtection?.turns ?? config.turnProtection.turns,
723
+ },
724
+ protectedFilePatterns: [
725
+ ...new Set([
726
+ ...config.protectedFilePatterns,
727
+ ...(result.data.protectedFilePatterns ?? []),
728
+ ]),
729
+ ],
730
+ explore: mergeExplore(config.explore, result.data.explore),
731
+ tools: mergeTools(config.tools, result.data.tools),
732
+ strategies: mergeStrategies(config.strategies, result.data.strategies),
733
+ };
734
+ }
735
+ }
736
+ // Load and merge project config (overrides global)
737
+ if (configPaths.project) {
738
+ const result = loadConfigFile(configPaths.project);
739
+ if (result.parseError) {
740
+ setTimeout(async () => {
741
+ try {
742
+ ctx.client.tui.showToast({
743
+ body: {
744
+ title: "RCL: Invalid project config",
745
+ message: `${configPaths.project}\n${result.parseError}\nUsing global/default values`,
746
+ variant: "warning",
747
+ duration: 7000,
748
+ },
749
+ });
750
+ }
751
+ catch { }
752
+ }, 7000);
753
+ }
754
+ else if (result.data) {
755
+ // Validate config keys and types
756
+ showConfigValidationWarnings(ctx, configPaths.project, result.data, true);
757
+ config = {
758
+ enabled: result.data.enabled ?? config.enabled,
759
+ debug: result.data.debug ?? config.debug,
760
+ pruneNotification: result.data.pruneNotification ?? config.pruneNotification,
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
+ explore: mergeExplore(config.explore, result.data.explore),
772
+ tools: mergeTools(config.tools, result.data.tools),
773
+ strategies: mergeStrategies(config.strategies, result.data.strategies),
774
+ };
775
+ }
776
+ }
777
+ return config;
778
+ }
779
+ //# sourceMappingURL=config.js.map