om-memory-system 3.0.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 (221) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +587 -0
  3. package/dist/adapters/opencode/auto-capture-host.d.ts +3 -0
  4. package/dist/adapters/opencode/auto-capture-host.js +29 -0
  5. package/dist/adapters/opencode/auto-capture-summary.d.ts +4 -0
  6. package/dist/adapters/opencode/auto-capture-summary.js +147 -0
  7. package/dist/adapters/opencode/conversation.d.ts +2 -0
  8. package/dist/adapters/opencode/conversation.js +50 -0
  9. package/dist/adapters/opencode/user-prompt.d.ts +9 -0
  10. package/dist/adapters/opencode/user-prompt.js +26 -0
  11. package/dist/adapters/pi/capture.d.ts +41 -0
  12. package/dist/adapters/pi/capture.js +91 -0
  13. package/dist/adapters/pi/conversation.d.ts +38 -0
  14. package/dist/adapters/pi/conversation.js +118 -0
  15. package/dist/adapters/pi/extension.d.ts +18 -0
  16. package/dist/adapters/pi/extension.js +234 -0
  17. package/dist/adapters/pi/import-command.d.ts +29 -0
  18. package/dist/adapters/pi/import-command.js +232 -0
  19. package/dist/adapters/pi/profile.d.ts +39 -0
  20. package/dist/adapters/pi/profile.js +148 -0
  21. package/dist/adapters/pi/provider.d.ts +50 -0
  22. package/dist/adapters/pi/provider.js +77 -0
  23. package/dist/config.d.ts +290 -0
  24. package/dist/config.js +742 -0
  25. package/dist/core/capture-context.d.ts +4 -0
  26. package/dist/core/capture-context.js +132 -0
  27. package/dist/core/capture.d.ts +15 -0
  28. package/dist/core/capture.js +68 -0
  29. package/dist/core/extraction.d.ts +80 -0
  30. package/dist/core/extraction.js +140 -0
  31. package/dist/core/host.d.ts +52 -0
  32. package/dist/core/host.js +1 -0
  33. package/dist/core/memory-operations.d.ts +27 -0
  34. package/dist/core/memory-operations.js +247 -0
  35. package/dist/core/retrieval.d.ts +12 -0
  36. package/dist/core/retrieval.js +72 -0
  37. package/dist/importer/discovery.d.ts +31 -0
  38. package/dist/importer/discovery.js +88 -0
  39. package/dist/importer/importer.d.ts +87 -0
  40. package/dist/importer/importer.js +260 -0
  41. package/dist/importer/ledger.d.ts +44 -0
  42. package/dist/importer/ledger.js +137 -0
  43. package/dist/importer/session-loader.d.ts +19 -0
  44. package/dist/importer/session-loader.js +12 -0
  45. package/dist/index.d.ts +24 -0
  46. package/dist/index.js +601 -0
  47. package/dist/plugin.d.ts +9 -0
  48. package/dist/plugin.js +11 -0
  49. package/dist/services/ai/ai-provider-factory.d.ts +7 -0
  50. package/dist/services/ai/ai-provider-factory.js +41 -0
  51. package/dist/services/ai/api-key-placeholder.d.ts +1 -0
  52. package/dist/services/ai/api-key-placeholder.js +14 -0
  53. package/dist/services/ai/internal-capture-sessions.d.ts +6 -0
  54. package/dist/services/ai/internal-capture-sessions.js +33 -0
  55. package/dist/services/ai/opencode-diagnostics.d.ts +7 -0
  56. package/dist/services/ai/opencode-diagnostics.js +36 -0
  57. package/dist/services/ai/opencode-host-config.d.ts +10 -0
  58. package/dist/services/ai/opencode-host-config.js +51 -0
  59. package/dist/services/ai/opencode-provider-loader.d.ts +3 -0
  60. package/dist/services/ai/opencode-provider-loader.js +4 -0
  61. package/dist/services/ai/opencode-provider.d.ts +92 -0
  62. package/dist/services/ai/opencode-provider.js +419 -0
  63. package/dist/services/ai/opencode-sdk-client.d.ts +6 -0
  64. package/dist/services/ai/opencode-sdk-client.js +38 -0
  65. package/dist/services/ai/profile-llm-client.d.ts +2 -0
  66. package/dist/services/ai/profile-llm-client.js +27 -0
  67. package/dist/services/ai/provider-config.d.ts +17 -0
  68. package/dist/services/ai/provider-config.js +30 -0
  69. package/dist/services/ai/providers/anthropic-messages.d.ts +34 -0
  70. package/dist/services/ai/providers/anthropic-messages.js +220 -0
  71. package/dist/services/ai/providers/base-provider.d.ts +24 -0
  72. package/dist/services/ai/providers/base-provider.js +24 -0
  73. package/dist/services/ai/providers/google-gemini.d.ts +15 -0
  74. package/dist/services/ai/providers/google-gemini.js +228 -0
  75. package/dist/services/ai/providers/minimax.d.ts +35 -0
  76. package/dist/services/ai/providers/minimax.js +55 -0
  77. package/dist/services/ai/providers/openai-chat-completion.d.ts +22 -0
  78. package/dist/services/ai/providers/openai-chat-completion.js +387 -0
  79. package/dist/services/ai/providers/openai-responses.d.ts +13 -0
  80. package/dist/services/ai/providers/openai-responses.js +182 -0
  81. package/dist/services/ai/providers/orcarouter.d.ts +51 -0
  82. package/dist/services/ai/providers/orcarouter.js +66 -0
  83. package/dist/services/ai/session/ai-session-manager.d.ts +24 -0
  84. package/dist/services/ai/session/ai-session-manager.js +211 -0
  85. package/dist/services/ai/session/session-types.d.ts +42 -0
  86. package/dist/services/ai/session/session-types.js +1 -0
  87. package/dist/services/ai/tools/tool-schema.d.ts +40 -0
  88. package/dist/services/ai/tools/tool-schema.js +24 -0
  89. package/dist/services/ai/validators/user-profile-validator.d.ts +12 -0
  90. package/dist/services/ai/validators/user-profile-validator.js +114 -0
  91. package/dist/services/api-handlers.d.ts +186 -0
  92. package/dist/services/api-handlers.js +1284 -0
  93. package/dist/services/auth-token.d.ts +14 -0
  94. package/dist/services/auth-token.js +59 -0
  95. package/dist/services/auto-capture.d.ts +3 -0
  96. package/dist/services/auto-capture.js +127 -0
  97. package/dist/services/cleanup-service.d.ts +22 -0
  98. package/dist/services/cleanup-service.js +109 -0
  99. package/dist/services/client.d.ts +134 -0
  100. package/dist/services/client.js +327 -0
  101. package/dist/services/context.d.ts +10 -0
  102. package/dist/services/context.js +27 -0
  103. package/dist/services/cors.d.ts +12 -0
  104. package/dist/services/cors.js +52 -0
  105. package/dist/services/deduplication-service.d.ts +28 -0
  106. package/dist/services/deduplication-service.js +111 -0
  107. package/dist/services/embedding.d.ts +36 -0
  108. package/dist/services/embedding.js +200 -0
  109. package/dist/services/jsonc.d.ts +6 -0
  110. package/dist/services/jsonc.js +76 -0
  111. package/dist/services/language-detector.d.ts +2 -0
  112. package/dist/services/language-detector.js +33 -0
  113. package/dist/services/legacy-migration.d.ts +77 -0
  114. package/dist/services/legacy-migration.js +343 -0
  115. package/dist/services/logger.d.ts +1 -0
  116. package/dist/services/logger.js +119 -0
  117. package/dist/services/memory-portability-service.d.ts +37 -0
  118. package/dist/services/memory-portability-service.js +340 -0
  119. package/dist/services/memory-scope.d.ts +16 -0
  120. package/dist/services/memory-scope.js +43 -0
  121. package/dist/services/migration-service.d.ts +42 -0
  122. package/dist/services/migration-service.js +307 -0
  123. package/dist/services/onnxruntime-resolve.d.ts +24 -0
  124. package/dist/services/onnxruntime-resolve.js +174 -0
  125. package/dist/services/privacy.d.ts +26 -0
  126. package/dist/services/privacy.js +69 -0
  127. package/dist/services/request-timeouts.d.ts +9 -0
  128. package/dist/services/request-timeouts.js +11 -0
  129. package/dist/services/runtime-require.d.ts +17 -0
  130. package/dist/services/runtime-require.js +75 -0
  131. package/dist/services/secret-resolver.d.ts +1 -0
  132. package/dist/services/secret-resolver.js +65 -0
  133. package/dist/services/shard-inventory-service.d.ts +69 -0
  134. package/dist/services/shard-inventory-service.js +315 -0
  135. package/dist/services/shard-path-migration-service.d.ts +31 -0
  136. package/dist/services/shard-path-migration-service.js +367 -0
  137. package/dist/services/tag-prefix-migration.d.ts +60 -0
  138. package/dist/services/tag-prefix-migration.js +439 -0
  139. package/dist/services/tag-prefix-scan.d.ts +26 -0
  140. package/dist/services/tag-prefix-scan.js +91 -0
  141. package/dist/services/tags.d.ts +29 -0
  142. package/dist/services/tags.js +251 -0
  143. package/dist/services/turso/connection-manager.d.ts +15 -0
  144. package/dist/services/turso/connection-manager.js +163 -0
  145. package/dist/services/turso/cross-process-write-lock.d.ts +1 -0
  146. package/dist/services/turso/cross-process-write-lock.js +89 -0
  147. package/dist/services/turso/legacy-migrator.d.ts +1 -0
  148. package/dist/services/turso/legacy-migrator.js +540 -0
  149. package/dist/services/turso/lifecycle.d.ts +1 -0
  150. package/dist/services/turso/lifecycle.js +14 -0
  151. package/dist/services/turso/operation-lock.d.ts +2 -0
  152. package/dist/services/turso/operation-lock.js +61 -0
  153. package/dist/services/turso/ready.d.ts +2 -0
  154. package/dist/services/turso/ready.js +36 -0
  155. package/dist/services/turso/shard-manager.d.ts +38 -0
  156. package/dist/services/turso/shard-manager.js +496 -0
  157. package/dist/services/turso/sqlite-handle-release.d.ts +16 -0
  158. package/dist/services/turso/sqlite-handle-release.js +56 -0
  159. package/dist/services/turso/turso-db.d.ts +18 -0
  160. package/dist/services/turso/turso-db.js +53 -0
  161. package/dist/services/turso/types.d.ts +42 -0
  162. package/dist/services/turso/types.js +1 -0
  163. package/dist/services/turso/vector-search.d.ts +41 -0
  164. package/dist/services/turso/vector-search.js +384 -0
  165. package/dist/services/turso/vector-utils.d.ts +7 -0
  166. package/dist/services/turso/vector-utils.js +67 -0
  167. package/dist/services/user-memory-learning.d.ts +97 -0
  168. package/dist/services/user-memory-learning.js +673 -0
  169. package/dist/services/user-profile/ai-cleanup.d.ts +58 -0
  170. package/dist/services/user-profile/ai-cleanup.js +486 -0
  171. package/dist/services/user-profile/profile-context.d.ts +1 -0
  172. package/dist/services/user-profile/profile-context.js +105 -0
  173. package/dist/services/user-profile/profile-utils.d.ts +2 -0
  174. package/dist/services/user-profile/profile-utils.js +45 -0
  175. package/dist/services/user-profile/types.d.ts +92 -0
  176. package/dist/services/user-profile/types.js +1 -0
  177. package/dist/services/user-profile/user-profile-manager.d.ts +65 -0
  178. package/dist/services/user-profile/user-profile-manager.js +1660 -0
  179. package/dist/services/user-prompt/user-prompt-manager.d.ts +53 -0
  180. package/dist/services/user-prompt/user-prompt-manager.js +303 -0
  181. package/dist/services/web-api-auth.d.ts +3 -0
  182. package/dist/services/web-api-auth.js +25 -0
  183. package/dist/services/web-auth.d.ts +46 -0
  184. package/dist/services/web-auth.js +105 -0
  185. package/dist/services/web-server.d.ts +57 -0
  186. package/dist/services/web-server.js +659 -0
  187. package/dist/shared/api/index.d.ts +4 -0
  188. package/dist/shared/api/index.js +2 -0
  189. package/dist/shared/api/portability-schemas.d.ts +56 -0
  190. package/dist/shared/api/portability-schemas.js +39 -0
  191. package/dist/shared/api/schemas.d.ts +295 -0
  192. package/dist/shared/api/schemas.js +134 -0
  193. package/dist/shared/api/types.d.ts +28 -0
  194. package/dist/shared/api/types.js +1 -0
  195. package/dist/types/index.d.ts +27 -0
  196. package/dist/types/index.js +1 -0
  197. package/dist/utils/context-limit.d.ts +10 -0
  198. package/dist/utils/context-limit.js +43 -0
  199. package/dist/utils/math.d.ts +7 -0
  200. package/dist/utils/math.js +48 -0
  201. package/dist/utils/profile.d.ts +2 -0
  202. package/dist/utils/profile.js +15 -0
  203. package/dist/v2/adapter.d.ts +5 -0
  204. package/dist/v2/adapter.js +218 -0
  205. package/dist/v2/legacy-client.d.ts +15 -0
  206. package/dist/v2/legacy-client.js +257 -0
  207. package/dist/v2/memory-bridge.d.ts +18 -0
  208. package/dist/v2/memory-bridge.js +29 -0
  209. package/dist/v2/plugin.d.ts +3 -0
  210. package/dist/v2/plugin.js +24 -0
  211. package/dist/web/assets/index-DPjEpD1h.css +2 -0
  212. package/dist/web/assets/index-do26c4JJ.js +122 -0
  213. package/dist/web/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  214. package/dist/web/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  215. package/dist/web/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  216. package/dist/web/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  217. package/dist/web/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  218. package/dist/web/favicon.ico +0 -0
  219. package/dist/web/index.html +14 -0
  220. package/dist/web/omms-icon.png +0 -0
  221. package/package.json +138 -0
package/dist/config.js ADDED
@@ -0,0 +1,742 @@
1
+ import { existsSync, readFileSync, mkdirSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { homedir } from "node:os";
4
+ import { stripJsoncComments } from "./services/jsonc.js";
5
+ import { log } from "./services/logger.js";
6
+ import { resolveSecretValue } from "./services/secret-resolver.js";
7
+ import { isPlaceholderApiKey } from "./services/ai/api-key-placeholder.js";
8
+ import { resolveDefaultStoragePath, runLegacyStoreMigration, legacyMigrationPaths, } from "./services/legacy-migration.js";
9
+ const OMMS_CONFIG_DIR = join(homedir(), ".config", "omms");
10
+ const DATA_DIR = join(homedir(), ".omms");
11
+ const LEGACY_CONFIG_FILES = [
12
+ join(homedir(), ".config", "opencode", "opencode-mem.jsonc"),
13
+ join(homedir(), ".config", "opencode", "opencode-mem.json"),
14
+ ];
15
+ // Dual-read: the omms config wins; the legacy opencode-mem config is read
16
+ // only when no omms config file exists (decision D13 rule 6). The legacy
17
+ // file is never written.
18
+ const CONFIG_FILES = [
19
+ join(OMMS_CONFIG_DIR, "omms.jsonc"),
20
+ join(OMMS_CONFIG_DIR, "omms.json"),
21
+ ...LEGACY_CONFIG_FILES,
22
+ ];
23
+ if (!existsSync(OMMS_CONFIG_DIR)) {
24
+ mkdirSync(OMMS_CONFIG_DIR, { recursive: true });
25
+ }
26
+ if (!existsSync(DATA_DIR)) {
27
+ mkdirSync(DATA_DIR, { recursive: true });
28
+ }
29
+ const DEFAULTS = {
30
+ // Default store: ~/.omms/data (see resolveDefaultStoragePath for the
31
+ // legacy-layout fallback while a legacy store exists unmigrated).
32
+ storagePath: join(DATA_DIR, "data"),
33
+ embeddingModel: "Xenova/nomic-embed-text-v1",
34
+ embeddingDimensions: 768,
35
+ embeddingUseTaskPrefixes: false,
36
+ similarityThreshold: 0.6,
37
+ maxMemories: 10,
38
+ maxProfileItems: 5,
39
+ injectProfile: true,
40
+ // omms since the tag prefix migration: new memories carry the fork's
41
+ // identity end to end, and the startup gate (tag-prefix-migration.ts)
42
+ // rewrites any remaining opencode_ rows before the first write is served.
43
+ containerTagPrefix: "omms",
44
+ autoCaptureEnabled: true,
45
+ autoCaptureMaxIterations: 5,
46
+ autoCaptureIterationTimeout: 30000,
47
+ autoCaptureMaxRetries: 3,
48
+ autoCaptureMaxContextBytes: 131072,
49
+ aiSessionRetentionDays: 7,
50
+ webServerEnabled: true,
51
+ webServerPort: 4747,
52
+ webServerHost: "127.0.0.1",
53
+ maxVectorsPerShard: 50000,
54
+ autoCleanupEnabled: true,
55
+ autoCleanupRetentionDays: 30,
56
+ deduplicationEnabled: true,
57
+ deduplicationSimilarityThreshold: 0.9,
58
+ userProfileAnalysisInterval: 10,
59
+ userProfileMaxContextBytes: 32768,
60
+ userProfileDisplayPreferences: 20,
61
+ userProfileDisplayPatterns: 15,
62
+ userProfileDisplayWorkflows: 10,
63
+ userProfileStaleDays: 2,
64
+ userProfileInjectPreferences: 5,
65
+ userProfileInjectPatterns: 5,
66
+ userProfileInjectWorkflows: 3,
67
+ userProfileConfidenceDecayDays: 30,
68
+ userProfileChangelogRetentionCount: 5,
69
+ userProfileEmbeddingThresholdSameCat: 0.8,
70
+ userProfileEmbeddingThresholdSameCatWeak: 0.5,
71
+ userProfileEmbeddingThresholdCrossCat: 0.9,
72
+ userProfileEmbeddingThresholdCrossCatWeak: 0.8,
73
+ userProfileCentroidDriftThreshold: 0.65,
74
+ userProfileEmbeddingMinDescriptionLength: 5,
75
+ userProfileMinEvidenceForRetention: 3,
76
+ userProfileAutoCleanupEnabled: true,
77
+ userProfileAutoCleanupInterval: 100,
78
+ userProfileValidationEnabled: false,
79
+ showAutoCaptureToasts: true,
80
+ showUserProfileToasts: true,
81
+ showErrorToasts: true,
82
+ memory: {
83
+ defaultScope: "project",
84
+ },
85
+ compaction: {
86
+ enabled: true,
87
+ memoryLimit: 10,
88
+ },
89
+ chatMessage: {
90
+ enabled: true,
91
+ maxMemories: 3,
92
+ excludeCurrentSession: true,
93
+ maxAgeDays: undefined,
94
+ injectOn: "first",
95
+ },
96
+ };
97
+ function expandPath(path) {
98
+ if (path.startsWith("~/")) {
99
+ return join(homedir(), path.slice(2));
100
+ }
101
+ if (path === "~") {
102
+ return homedir();
103
+ }
104
+ return path;
105
+ }
106
+ /**
107
+ * Load the first config file that exists, in priority order. A file that
108
+ * exists but cannot be read or parsed is not skipped: falling through would let
109
+ * a lower-priority (legacy) file silently supply settings such as storagePath.
110
+ */
111
+ function loadConfigFromPaths(paths) {
112
+ const path = paths.find((candidate) => existsSync(candidate));
113
+ if (!path)
114
+ return {};
115
+ try {
116
+ const content = readFileSync(path, "utf-8");
117
+ const json = stripJsoncComments(content);
118
+ return JSON.parse(json);
119
+ }
120
+ catch (error) {
121
+ log("Config file is invalid; using defaults instead of lower-priority files", {
122
+ path,
123
+ error: String(error),
124
+ });
125
+ return {};
126
+ }
127
+ }
128
+ const GLOBAL_ONLY_REMOTE_PROVIDER_FIELDS = [
129
+ "embeddingApiUrl",
130
+ "embeddingApiKey",
131
+ "memoryProvider",
132
+ "memoryApiUrl",
133
+ "memoryApiKey",
134
+ ];
135
+ function assertProjectRemoteProviderConfigIsSafe(projectConfig) {
136
+ const configuredFields = GLOBAL_ONLY_REMOTE_PROVIDER_FIELDS.filter((field) => Object.prototype.hasOwnProperty.call(projectConfig, field));
137
+ if (configuredFields.length > 0) {
138
+ throw new Error(`Project config cannot set remote provider fields: ${configuredFields.join(", ")}. ` +
139
+ `Move them to the global config at ${CONFIG_FILES[0]}.`);
140
+ }
141
+ }
142
+ const CONFIG_TEMPLATE = `{
143
+ // ============================================
144
+ // omms (Opinionated Modular Memory System) Configuration
145
+ // ============================================
146
+
147
+ // Storage location for the vector database. Leave unset to use the
148
+ // default ~/.omms/data (a legacy ~/.opencode-mem/data store is migrated
149
+ // there automatically on first start; see docs/omms-migration.md).
150
+ // "storagePath": "~/.omms/data",
151
+
152
+ "userEmailOverride": "",
153
+ "userNameOverride": "",
154
+
155
+ // ============================================
156
+ // Embedding Model (for similarity search)
157
+ // ============================================
158
+ // Local = Hugging Face / ONNX via @huggingface/transformers (not Apple MLX).
159
+ // Remote = set BOTH embeddingApiUrl and embeddingApiKey (OpenAI-compatible /embeddings).
160
+
161
+ // Default: Nomic Embed v1 (768 dimensions, 8192 context, multilingual)
162
+ "embeddingModel": "Xenova/nomic-embed-text-v1",
163
+
164
+ // Opt-in Nomic task prefixes (search_document: / search_query:). After enabling,
165
+ // re-index existing memories so store and query vectors stay aligned.
166
+ // "embeddingUseTaskPrefixes": true,
167
+
168
+ // Auto-detected dimensions (no need to set manually)
169
+ // "embeddingDimensions": 768,
170
+
171
+ // Other recommended local models:
172
+ // "embeddingModel": "Xenova/jina-embeddings-v2-base-en", // 768 dims, English-only, 8192 context
173
+ // "embeddingModel": "Xenova/jina-embeddings-v2-small-en", // 512 dims, faster, 8192 context
174
+ // "embeddingModel": "Xenova/all-MiniLM-L6-v2", // 384 dims, very fast, 512 context
175
+ // "embeddingModel": "Xenova/all-mpnet-base-v2", // 768 dims, good quality, 512 context
176
+
177
+ // Optional: OpenAI-compatible API for embeddings (both URL and key required)
178
+ // "embeddingApiUrl": "https://api.openai.com/v1",
179
+ // "embeddingApiKey": "env://OPENAI_API_KEY", // or "sk-..." / "file:///path/to/key"
180
+ // "embeddingModel": "text-embedding-3-small", // 1536 dims, auto-detected
181
+
182
+ // ============================================
183
+ // Web Server Settings
184
+ // ============================================
185
+
186
+ // Enable web UI for managing memories (accessible at http://localhost:4747)
187
+ "webServerEnabled": true,
188
+
189
+ // Port for web UI server
190
+ "webServerPort": 4747,
191
+
192
+ // Host address for web UI (use 127.0.0.1 for local only, 0.0.0.0 for network access)
193
+ "webServerHost": "127.0.0.1",
194
+
195
+ // Optional HTTP Basic Auth for the web UI. Accepts literal, env://, or file:// secrets.
196
+ // "webServerAuthPassword": "",
197
+ // "webServerAuthUsername": "",
198
+
199
+ // Required when webServerHost is not loopback. Protects /api/* with Bearer / X-Opencode-Mem-Token.
200
+ // "webServerApiToken": "env://OMMS_WEB_TOKEN",
201
+
202
+ // ============================================
203
+ // Database Settings
204
+ // ============================================
205
+
206
+ // Maximum vectors per database shard (auto-creates new shard when limit reached)
207
+ "maxVectorsPerShard": 50000,
208
+
209
+ // Automatically delete old memories based on retention period
210
+ "autoCleanupEnabled": true,
211
+
212
+ // Days to keep memories before auto-cleanup (only if autoCleanupEnabled is true)
213
+ "autoCleanupRetentionDays": 30,
214
+
215
+ // Automatically detect and remove duplicate memories
216
+ "deduplicationEnabled": true,
217
+
218
+ // Similarity threshold (0-1) for detecting duplicates (higher = stricter)
219
+ "deduplicationSimilarityThreshold": 0.90,
220
+
221
+ // ============================================
222
+ // Memory Scope Settings
223
+ // ============================================
224
+
225
+ // Default scope for memory list/search queries
226
+ // "project" keeps queries within the current project, "all-projects" searches across all project shards
227
+ "memory": {
228
+ "defaultScope": "project"
229
+ },
230
+
231
+ // ============================================
232
+ // OpenCode Provider Settings (RECOMMENDED)
233
+ // ============================================
234
+
235
+ // Use any provider that is already authenticated in opencode for auto-capture
236
+ // and user profile learning. The plugin calls opencode's session.prompt API
237
+ // (with structured output) instead of talking to provider HTTPS endpoints
238
+ // directly, so opencode owns the auth, token refresh, and provider routing.
239
+ //
240
+ // No separate API key is needed in this plugin — whatever you configured in
241
+ // opencode (OAuth like Claude Pro/Max, GitHub Copilot personal/business,
242
+ // bring-your-own API key, custom provider, ...) just works.
243
+ //
244
+ // If NOT set, falls back to the manual config (memoryApiKey/memoryApiUrl/memoryModel below).
245
+ //
246
+ // Examples (the provider name must be one returned by 'opencode providers list'):
247
+ // Anthropic (OAuth/API key): "opencodeProvider": "anthropic", "opencodeModel": "claude-haiku-4-5-20251001"
248
+ // OpenAI (API key): "opencodeProvider": "openai", "opencodeModel": "gpt-4o-mini"
249
+ // GitHub Copilot: "opencodeProvider": "github-copilot", "opencodeModel": "gpt-4o-mini"
250
+ //
251
+ // "opencodeProvider": "anthropic",
252
+ // "opencodeModel": "claude-haiku-4-5-20251001",
253
+
254
+ // ============================================
255
+ // Auto-Capture Settings
256
+ // ============================================
257
+
258
+ // IMPORTANT: Auto-capture only runs after either opencodeProvider/opencodeModel
259
+ // above is configured, or the manual fallback below is uncommented with real values.
260
+ // It runs in background without blocking your main session
261
+ // Note: Ollama may not support tool calling. Use OpenAI, Anthropic, or Groq for best results.
262
+
263
+ "autoCaptureEnabled": true,
264
+
265
+ // Provider type: "openai-chat" | "openai-responses" | "anthropic" | "minimax" | "orcarouter"
266
+ // Note: "openai-chat" is a generic OpenAI API-compatible mode.
267
+ // Any service that follows the OpenAI Chat Completions API can use it via custom "memoryApiUrl".
268
+ "memoryProvider": "openai-chat",
269
+
270
+ // Manual fallback. Uncomment all 3 lines and replace memoryApiKey before use:
271
+ // "memoryModel": "gpt-4o-mini",
272
+ // "memoryApiUrl": "https://api.openai.com/v1",
273
+ // "memoryApiKey": "sk-...",
274
+
275
+ // API Key Formats:
276
+ // Direct value: "sk-..."
277
+ // From file: "file://~/.config/litellm-key.txt"
278
+ // From env variable: "env://LITELLM_API_KEY"
279
+
280
+ // Examples for different providers:
281
+ // Any OpenAI-compatible endpoint can use the "openai-chat" provider pattern below.
282
+ // Common examples: DeepSeek, Qwen (via Alibaba Cloud ModelStudio),
283
+ // Zhipu GLM (BigModel platform), and Kimi (Moonshot AI platform).
284
+
285
+ // OpenAI Chat Completion (default, backward compatible):
286
+ // "memoryProvider": "openai-chat"
287
+ // "memoryModel": "gpt-4o-mini"
288
+ // "memoryApiUrl": "https://api.openai.com/v1"
289
+ // "memoryApiKey": "sk-..."
290
+
291
+ // DeepSeek (OpenAI-compatible example):
292
+ // "memoryProvider": "openai-chat"
293
+ // "memoryModel": "deepseek-chat"
294
+ // "memoryApiUrl": "https://api.deepseek.com/v1"
295
+ // "memoryApiKey": "sk-..."
296
+
297
+ // OpenAI Responses API (recommended, with session support):
298
+ // "memoryProvider": "openai-responses"
299
+ // "memoryModel": "gpt-4o"
300
+ // "memoryApiUrl": "https://api.openai.com/v1"
301
+ // "memoryApiKey": "sk-..."
302
+
303
+ // Anthropic (with session support):
304
+ // "memoryProvider": "anthropic"
305
+ // "memoryModel": "claude-3-5-haiku-20241022"
306
+ // "memoryApiUrl": "https://api.anthropic.com/v1"
307
+ // "memoryApiKey": "sk-ant-..."
308
+
309
+ // MiniMax (Anthropic Messages-compatible endpoint, with session support):
310
+ // "memoryProvider": "minimax"
311
+ // "memoryModel": "MiniMax-M3"
312
+ // "memoryApiUrl": "https://api.minimax.io" // global endpoint
313
+ // "memoryApiKey": "<MiniMax API key>"
314
+ // // China endpoint: "memoryApiUrl": "https://api.minimaxi.com"
315
+ // // Optional adaptive thinking for MiniMax-M3:
316
+ // "memoryExtraParams": { "thinking": { "type": "adaptive" } }
317
+
318
+ // OrcaRouter (OpenAI-compatible gateway, namespaced model IDs, with session support):
319
+ // "memoryProvider": "orcarouter"
320
+ // "memoryApiKey": "<OrcaRouter API key>"
321
+ // // memoryApiUrl and memoryModel are optional — they default to
322
+ // // https://api.orcarouter.ai/v1 and "orcarouter/auto" (a routing alias).
323
+ // // OrcaRouter rejects bare model names, so if you set memoryModel, use a
324
+ // // namespaced ID such as "openai/gpt-5.5" or "deepseek/deepseek-v4-flash".
325
+ // "memoryModel": "openai/gpt-5.5"
326
+
327
+ // Groq (OpenAI-compatible, use openai-chat provider):
328
+ // "memoryProvider": "openai-chat"
329
+ // "memoryModel": "llama-3.3-70b-versatile"
330
+ // "memoryApiUrl": "https://api.groq.com/openai/v1"
331
+ // "memoryApiKey": "gsk_..."
332
+
333
+ // Maximum iterations for multi-turn AI analysis (for openai-responses, anthropic, and minimax)
334
+ "autoCaptureMaxIterations": 5,
335
+
336
+ // Timeout per iteration in milliseconds (30 seconds default)
337
+ "autoCaptureIterationTimeout": 30000,
338
+
339
+ // Maximum number of times to retry capturing a prompt if it fails (due to network, API errors, etc.)
340
+ "autoCaptureMaxRetries": 3,
341
+
342
+ // Maximum UTF-8 bytes for the auto-capture markdown context sent to the summary model.
343
+ // Prevents HTTP 400 context overflows on models with ~131K token windows (e.g. Groq Llama).
344
+ // Rough guide: tokens ≈ bytes / 4 for mixed code/prose.
345
+ "autoCaptureMaxContextBytes": 131072,
346
+
347
+ // Days to keep AI session history before cleanup
348
+ "aiSessionRetentionDays": 7,
349
+
350
+ // Temperature for AI API requests (set to false to omit parameter for models that don't support it)
351
+ // Some reasoning models (like o1, o3, gpt-5) don't support temperature parameter
352
+ // Set to false and add "memoryTemperature": false in config when using such models
353
+ "memoryTemperature": 0.3,
354
+
355
+ // Extra parameters to include in API request body
356
+ // Useful for local inference servers (e.g. llama-server with --jinja) that support
357
+ // additional parameters like disabling thinking/reasoning mode
358
+ // Example for Qwen3 models: { "enable_thinking": false }
359
+ // "memoryExtraParams": {},
360
+
361
+ // Language for auto-capture summaries (default: "auto" for auto-detection)
362
+ // Options: "auto", "en", "id", "zh", "ja", "es", "fr", "de", "ru", "pt", "ar", "ko"
363
+ // "autoCaptureLanguage": "auto",
364
+
365
+ // ============================================
366
+ // Toast Notifications
367
+ // ============================================
368
+
369
+ // Show toast when memory is auto-captured
370
+ "showAutoCaptureToasts": true,
371
+
372
+ // Show toast when user profile is updated
373
+ "showUserProfileToasts": true,
374
+
375
+ // Show toast for error messages
376
+ "showErrorToasts": true,
377
+
378
+ // ============================================
379
+ // User Profile System
380
+ // ============================================
381
+
382
+ // Analyze user prompts every N prompts to build/update your user profile
383
+ // When N uncaptured prompts accumulate, AI will analyze them to identify:
384
+ // - User preferences (code style, communication style, tool preferences)
385
+ // - User patterns (recurring topics, problem domains, technical interests)
386
+ // - User workflows (development habits, sequences, learning style)
387
+ // - Skill level (overall and per-domain assessment)
388
+ "userProfileAnalysisInterval": 10,
389
+
390
+ // Days before inactive items (all types) are eligible for removal
391
+ "userProfileStaleDays": 2,
392
+
393
+ // Number of preferences shown in UI
394
+ "userProfileDisplayPreferences": 20,
395
+
396
+ // Number of patterns shown in UI
397
+ "userProfileDisplayPatterns": 15,
398
+
399
+ // Number of workflows shown in UI
400
+ "userProfileDisplayWorkflows": 10,
401
+
402
+ // Number of preferences injected into LLM conversation context
403
+ // Keep this small — the strongest signals are enough; more dilute LLM attention
404
+ "userProfileInjectPreferences": 5,
405
+
406
+ // Number of patterns injected into LLM conversation context
407
+ "userProfileInjectPatterns": 5,
408
+
409
+ // Number of workflows injected into LLM conversation context
410
+ "userProfileInjectWorkflows": 3,
411
+
412
+ // Days before preference confidence starts to decay (if not reinforced)
413
+ // Preferences that aren't seen again will gradually lose confidence and be removed
414
+ "userProfileConfidenceDecayDays": 30,
415
+
416
+ // Number of profile versions to keep in changelog (for rollback/debugging)
417
+ // Older versions are automatically cleaned up
418
+ "userProfileChangelogRetentionCount": 5,
419
+
420
+ // Minimum evidence count for a preference/pattern to survive confidence decay
421
+ // Items confirmed fewer times are more likely to be pruned when confidence decays
422
+ "userProfileMinEvidenceForRetention": 3,
423
+
424
+ // Periodically merge duplicate or irrelevant profile items with the configured AI provider
425
+ "userProfileAutoCleanupEnabled": true,
426
+ // Number of analyzed user prompts between automatic AI cleanup runs
427
+ "userProfileAutoCleanupInterval": 100,
428
+
429
+ // Enable LLM validation of existing preferences against recent behavior.
430
+ // When enabled, each analysis round checks if top-5 preferences still match recent prompts.
431
+ // Experimental — disabled by default.
432
+ "userProfileValidationEnabled": false,
433
+
434
+ // ============================================
435
+ // Search Settings
436
+ // ============================================
437
+
438
+ // Minimum similarity score (0-1) for memory search results
439
+ "similarityThreshold": 0.6,
440
+
441
+ // Maximum number of memories to return in search results
442
+ "maxMemories": 10,
443
+
444
+ // ============================================
445
+ // Advanced Settings
446
+ // ============================================
447
+
448
+ // Inject user profile into AI context (preferences, patterns, workflows)
449
+ "injectProfile": true
450
+ }
451
+ `;
452
+ function ensureConfigExists() {
453
+ const configPath = join(OMMS_CONFIG_DIR, "omms.jsonc");
454
+ const hasOmmsConfig = CONFIG_FILES.slice(0, 2).some((path) => existsSync(path));
455
+ const hasLegacyConfig = LEGACY_CONFIG_FILES.some((path) => existsSync(path));
456
+ // The template is created only when NO config exists at all. When a legacy
457
+ // opencode-mem config is present, dual-read keeps using it and no omms
458
+ // config file is written (writing one would permanently shadow the legacy
459
+ // fallback on the next start).
460
+ if (hasOmmsConfig || hasLegacyConfig) {
461
+ return;
462
+ }
463
+ try {
464
+ writeFileSync(configPath, CONFIG_TEMPLATE, "utf-8");
465
+ console.log(`\n✓ Created config template: ${configPath}`);
466
+ console.log(" Edit this file to customise omms settings.\n");
467
+ }
468
+ catch {
469
+ // ignore if the template cannot be written
470
+ }
471
+ }
472
+ ensureConfigExists();
473
+ function getEmbeddingDimensions(model) {
474
+ const dimensionMap = {
475
+ // Local Xenova models
476
+ "Xenova/nomic-embed-text-v1": 768,
477
+ "Xenova/nomic-embed-text-v1-unsupervised": 768,
478
+ "Xenova/nomic-embed-text-v1-ablated": 768,
479
+ "Xenova/jina-embeddings-v2-base-en": 768,
480
+ "Xenova/jina-embeddings-v2-base-zh": 768,
481
+ "Xenova/jina-embeddings-v2-base-de": 768,
482
+ "Xenova/jina-embeddings-v2-small-en": 512,
483
+ "Xenova/all-MiniLM-L6-v2": 384,
484
+ "Xenova/all-MiniLM-L12-v2": 384,
485
+ "Xenova/all-mpnet-base-v2": 768,
486
+ "Xenova/bge-base-en-v1.5": 768,
487
+ "Xenova/bge-small-en-v1.5": 384,
488
+ "Xenova/gte-small": 384,
489
+ "Xenova/GIST-small-Embedding-v0": 384,
490
+ "Xenova/text-embedding-ada-002": 1536,
491
+ // OpenAI API models
492
+ "text-embedding-3-small": 1536,
493
+ "text-embedding-3-large": 3072,
494
+ "text-embedding-ada-002": 1536,
495
+ // Cohere API models
496
+ "embed-english-v3.0": 1024,
497
+ "embed-multilingual-v3.0": 1024,
498
+ "embed-english-light-v3.0": 384,
499
+ "embed-multilingual-light-v3.0": 384,
500
+ // Google API models
501
+ "text-embedding-004": 768,
502
+ "text-multilingual-embedding-002": 768,
503
+ // Voyage AI models
504
+ "voyage-3": 1024,
505
+ "voyage-3-lite": 512,
506
+ "voyage-code-3": 1024,
507
+ };
508
+ return dimensionMap[model] || 768;
509
+ }
510
+ export function normalizeAutoCaptureMaxContextBytes(value) {
511
+ if (!Number.isInteger(value) || value < 16384 || value > 16 * 1024 * 1024) {
512
+ throw new Error(`Invalid autoCaptureMaxContextBytes config: ${value}`);
513
+ }
514
+ return value;
515
+ }
516
+ export function normalizeAutoCleanupRetentionDays(value) {
517
+ if (!Number.isSafeInteger(value) || value <= 0) {
518
+ throw new Error(`Invalid autoCleanupRetentionDays config: ${value}`);
519
+ }
520
+ return value;
521
+ }
522
+ function buildConfig(fileConfig) {
523
+ const memoryApiKey = resolveSecretValue(fileConfig.memoryApiKey);
524
+ const embeddingDimensions = fileConfig.embeddingDimensions ??
525
+ getEmbeddingDimensions(fileConfig.embeddingModel ?? DEFAULTS.embeddingModel);
526
+ const autoCaptureMaxContextBytes = normalizeAutoCaptureMaxContextBytes(fileConfig.autoCaptureMaxContextBytes ?? DEFAULTS.autoCaptureMaxContextBytes);
527
+ const userProfileAutoCleanupInterval = fileConfig.userProfileAutoCleanupInterval ?? DEFAULTS.userProfileAutoCleanupInterval;
528
+ if (!Number.isInteger(embeddingDimensions) ||
529
+ embeddingDimensions <= 0 ||
530
+ embeddingDimensions > 65536) {
531
+ throw new Error(`Invalid embeddingDimensions config: ${embeddingDimensions}`);
532
+ }
533
+ if (!Number.isInteger(userProfileAutoCleanupInterval) || userProfileAutoCleanupInterval <= 0) {
534
+ throw new Error(`Invalid userProfileAutoCleanupInterval config: ${userProfileAutoCleanupInterval}`);
535
+ }
536
+ return {
537
+ storagePath: fileConfig.storagePath
538
+ ? expandPath(fileConfig.storagePath)
539
+ : resolveDefaultStoragePath(),
540
+ userEmailOverride: fileConfig.userEmailOverride,
541
+ userNameOverride: fileConfig.userNameOverride,
542
+ embeddingModel: fileConfig.embeddingModel ?? DEFAULTS.embeddingModel,
543
+ embeddingDimensions,
544
+ embeddingUseTaskPrefixes: fileConfig.embeddingUseTaskPrefixes ?? DEFAULTS.embeddingUseTaskPrefixes,
545
+ embeddingApiUrl: fileConfig.embeddingApiUrl,
546
+ embeddingApiKey: fileConfig.embeddingApiUrl
547
+ ? resolveSecretValue(fileConfig.embeddingApiKey ?? process.env.OPENAI_API_KEY)
548
+ : undefined,
549
+ similarityThreshold: fileConfig.similarityThreshold ?? DEFAULTS.similarityThreshold,
550
+ maxMemories: fileConfig.maxMemories ?? DEFAULTS.maxMemories,
551
+ maxProfileItems: fileConfig.maxProfileItems ?? DEFAULTS.maxProfileItems,
552
+ injectProfile: fileConfig.injectProfile ?? DEFAULTS.injectProfile,
553
+ containerTagPrefix: fileConfig.containerTagPrefix ?? DEFAULTS.containerTagPrefix,
554
+ autoCaptureEnabled: fileConfig.autoCaptureEnabled ?? DEFAULTS.autoCaptureEnabled,
555
+ autoCaptureMaxIterations: fileConfig.autoCaptureMaxIterations ?? DEFAULTS.autoCaptureMaxIterations,
556
+ autoCaptureIterationTimeout: fileConfig.autoCaptureIterationTimeout ?? DEFAULTS.autoCaptureIterationTimeout,
557
+ autoCaptureMaxRetries: fileConfig.autoCaptureMaxRetries ?? DEFAULTS.autoCaptureMaxRetries,
558
+ autoCaptureMaxContextBytes,
559
+ autoCaptureLanguage: fileConfig.autoCaptureLanguage,
560
+ memoryProvider: (fileConfig.memoryProvider ?? "openai-chat"),
561
+ memoryModel: fileConfig.memoryModel,
562
+ memoryApiUrl: fileConfig.memoryApiUrl,
563
+ memoryApiKey,
564
+ memoryTemperature: fileConfig.memoryTemperature,
565
+ memoryExtraParams: fileConfig.memoryExtraParams,
566
+ opencodeProvider: fileConfig.opencodeProvider,
567
+ opencodeModel: fileConfig.opencodeModel,
568
+ piProvider: fileConfig.piProvider,
569
+ piModel: fileConfig.piModel,
570
+ autoCaptureProviderStatus: getAutoCaptureProviderStatus({
571
+ opencodeProvider: fileConfig.opencodeProvider,
572
+ opencodeModel: fileConfig.opencodeModel,
573
+ memoryModel: fileConfig.memoryModel,
574
+ memoryApiUrl: fileConfig.memoryApiUrl,
575
+ memoryApiKey,
576
+ }),
577
+ aiSessionRetentionDays: fileConfig.aiSessionRetentionDays ?? DEFAULTS.aiSessionRetentionDays,
578
+ webServerEnabled: fileConfig.webServerEnabled ?? DEFAULTS.webServerEnabled,
579
+ webServerPort: fileConfig.webServerPort ?? DEFAULTS.webServerPort,
580
+ webServerHost: fileConfig.webServerHost ?? DEFAULTS.webServerHost,
581
+ webServerAuthPassword: resolveSecretValue(fileConfig.webServerAuthPassword),
582
+ webServerAuthUsername: fileConfig.webServerAuthUsername,
583
+ webServerApiToken: fileConfig.webServerApiToken
584
+ ? resolveSecretValue(fileConfig.webServerApiToken)
585
+ : undefined,
586
+ maxVectorsPerShard: fileConfig.maxVectorsPerShard ?? DEFAULTS.maxVectorsPerShard,
587
+ autoCleanupEnabled: fileConfig.autoCleanupEnabled ?? DEFAULTS.autoCleanupEnabled,
588
+ autoCleanupRetentionDays: normalizeAutoCleanupRetentionDays(fileConfig.autoCleanupRetentionDays ?? DEFAULTS.autoCleanupRetentionDays),
589
+ deduplicationEnabled: fileConfig.deduplicationEnabled ?? DEFAULTS.deduplicationEnabled,
590
+ deduplicationSimilarityThreshold: fileConfig.deduplicationSimilarityThreshold ?? DEFAULTS.deduplicationSimilarityThreshold,
591
+ userProfileAnalysisInterval: fileConfig.userProfileAnalysisInterval ?? DEFAULTS.userProfileAnalysisInterval,
592
+ userProfileMaxContextBytes: fileConfig.userProfileMaxContextBytes ?? DEFAULTS.userProfileMaxContextBytes,
593
+ userProfileDisplayPreferences: fileConfig.userProfileDisplayPreferences ?? DEFAULTS.userProfileDisplayPreferences,
594
+ userProfileDisplayPatterns: fileConfig.userProfileDisplayPatterns ?? DEFAULTS.userProfileDisplayPatterns,
595
+ userProfileDisplayWorkflows: fileConfig.userProfileDisplayWorkflows ?? DEFAULTS.userProfileDisplayWorkflows,
596
+ userProfileInjectPreferences: fileConfig.userProfileInjectPreferences ?? DEFAULTS.userProfileInjectPreferences,
597
+ userProfileInjectPatterns: fileConfig.userProfileInjectPatterns ?? DEFAULTS.userProfileInjectPatterns,
598
+ userProfileInjectWorkflows: fileConfig.userProfileInjectWorkflows ?? DEFAULTS.userProfileInjectWorkflows,
599
+ userProfileConfidenceDecayDays: fileConfig.userProfileConfidenceDecayDays ?? DEFAULTS.userProfileConfidenceDecayDays,
600
+ userProfileChangelogRetentionCount: fileConfig.userProfileChangelogRetentionCount ?? DEFAULTS.userProfileChangelogRetentionCount,
601
+ userProfileEmbeddingThresholdSameCat: fileConfig.userProfileEmbeddingThresholdSameCat ??
602
+ DEFAULTS.userProfileEmbeddingThresholdSameCat,
603
+ userProfileEmbeddingThresholdSameCatWeak: fileConfig.userProfileEmbeddingThresholdSameCatWeak ??
604
+ DEFAULTS.userProfileEmbeddingThresholdSameCatWeak,
605
+ userProfileEmbeddingThresholdCrossCat: fileConfig.userProfileEmbeddingThresholdCrossCat ??
606
+ DEFAULTS.userProfileEmbeddingThresholdCrossCat,
607
+ userProfileEmbeddingThresholdCrossCatWeak: fileConfig.userProfileEmbeddingThresholdCrossCatWeak ??
608
+ DEFAULTS.userProfileEmbeddingThresholdCrossCatWeak,
609
+ userProfileCentroidDriftThreshold: fileConfig.userProfileCentroidDriftThreshold ?? DEFAULTS.userProfileCentroidDriftThreshold,
610
+ userProfileEmbeddingMinDescriptionLength: fileConfig.userProfileEmbeddingMinDescriptionLength ??
611
+ DEFAULTS.userProfileEmbeddingMinDescriptionLength,
612
+ userProfileMinEvidenceForRetention: fileConfig.userProfileMinEvidenceForRetention ?? DEFAULTS.userProfileMinEvidenceForRetention,
613
+ userProfileAutoCleanupEnabled: fileConfig.userProfileAutoCleanupEnabled ?? DEFAULTS.userProfileAutoCleanupEnabled,
614
+ userProfileAutoCleanupInterval,
615
+ userProfileValidationEnabled: fileConfig.userProfileValidationEnabled ?? DEFAULTS.userProfileValidationEnabled,
616
+ userProfileStaleDays: fileConfig.userProfileStaleDays ?? DEFAULTS.userProfileStaleDays,
617
+ showAutoCaptureToasts: fileConfig.showAutoCaptureToasts ?? DEFAULTS.showAutoCaptureToasts,
618
+ showUserProfileToasts: fileConfig.showUserProfileToasts ?? DEFAULTS.showUserProfileToasts,
619
+ showErrorToasts: fileConfig.showErrorToasts ?? DEFAULTS.showErrorToasts,
620
+ memory: {
621
+ defaultScope: fileConfig.memory?.defaultScope ?? DEFAULTS.memory.defaultScope,
622
+ },
623
+ compaction: {
624
+ enabled: fileConfig.compaction?.enabled ?? DEFAULTS.compaction.enabled,
625
+ memoryLimit: fileConfig.compaction?.memoryLimit ?? DEFAULTS.compaction.memoryLimit,
626
+ },
627
+ chatMessage: {
628
+ enabled: fileConfig.chatMessage?.enabled ?? DEFAULTS.chatMessage.enabled,
629
+ maxMemories: fileConfig.chatMessage?.maxMemories ?? DEFAULTS.chatMessage.maxMemories,
630
+ excludeCurrentSession: fileConfig.chatMessage?.excludeCurrentSession ?? DEFAULTS.chatMessage.excludeCurrentSession,
631
+ maxAgeDays: fileConfig.chatMessage?.maxAgeDays,
632
+ injectOn: (fileConfig.chatMessage?.injectOn ?? DEFAULTS.chatMessage.injectOn),
633
+ },
634
+ };
635
+ }
636
+ const _globalFileConfig = loadConfigFromPaths(CONFIG_FILES);
637
+ let lastFileConfig = _globalFileConfig;
638
+ export let CONFIG = buildConfig(_globalFileConfig);
639
+ function hasValue(value) {
640
+ return typeof value === "string" && value.trim().length > 0;
641
+ }
642
+ export { isPlaceholderApiKey };
643
+ export function getAutoCaptureProviderStatus(config) {
644
+ const hasOpencodeProvider = hasValue(config.opencodeProvider);
645
+ const hasOpencodeModel = hasValue(config.opencodeModel);
646
+ if (hasOpencodeProvider && hasOpencodeModel) {
647
+ return { ready: true, mode: "opencode", issues: [] };
648
+ }
649
+ const issues = [];
650
+ if (!hasOpencodeProvider)
651
+ issues.push("opencodeProvider is not configured");
652
+ if (!hasOpencodeModel)
653
+ issues.push("opencodeModel is not configured");
654
+ const hasMemoryModel = hasValue(config.memoryModel);
655
+ const hasMemoryApiUrl = hasValue(config.memoryApiUrl);
656
+ const hasMemoryApiKey = hasValue(config.memoryApiKey);
657
+ const hasPlaceholderMemoryApiKey = isPlaceholderApiKey(config.memoryApiKey);
658
+ // The orcarouter provider presets its endpoint and default model, so only
659
+ // an API key is required for the manual fallback path.
660
+ if (config.memoryProvider === "orcarouter") {
661
+ if (!hasMemoryApiKey)
662
+ issues.push("memoryApiKey is not configured");
663
+ if (hasPlaceholderMemoryApiKey)
664
+ issues.push("memoryApiKey contains a placeholder value");
665
+ if (hasMemoryApiKey && !hasPlaceholderMemoryApiKey) {
666
+ return { ready: true, mode: "manual", issues: [] };
667
+ }
668
+ return { ready: false, issues };
669
+ }
670
+ if (!hasMemoryModel)
671
+ issues.push("memoryModel is not configured");
672
+ if (!hasMemoryApiUrl)
673
+ issues.push("memoryApiUrl is not configured");
674
+ if (!hasMemoryApiKey)
675
+ issues.push("memoryApiKey is not configured");
676
+ if (hasPlaceholderMemoryApiKey)
677
+ issues.push("memoryApiKey contains a placeholder value");
678
+ if (hasMemoryModel && hasMemoryApiUrl && hasMemoryApiKey && !hasPlaceholderMemoryApiKey) {
679
+ return { ready: true, mode: "manual", issues: [] };
680
+ }
681
+ return { ready: false, issues };
682
+ }
683
+ export function hasAutoCaptureProviderConfig(config = CONFIG) {
684
+ return getAutoCaptureProviderStatus(config).ready;
685
+ }
686
+ export function initConfig(directory) {
687
+ // omms project overrides win; the legacy opencode-mem file is still read.
688
+ const projectPaths = [
689
+ join(directory, ".opencode", "omms.jsonc"),
690
+ join(directory, ".opencode", "omms.json"),
691
+ join(directory, ".opencode", "opencode-mem.jsonc"),
692
+ join(directory, ".opencode", "opencode-mem.json"),
693
+ ];
694
+ const globalConfig = loadConfigFromPaths(CONFIG_FILES);
695
+ const projectConfig = loadConfigFromPaths(projectPaths);
696
+ assertProjectRemoteProviderConfigIsSafe(projectConfig);
697
+ const projectOverrides = { ...projectConfig };
698
+ delete projectOverrides.autoCleanupEnabled;
699
+ delete projectOverrides.autoCleanupRetentionDays;
700
+ const merged = { ...globalConfig, ...projectOverrides };
701
+ lastFileConfig = merged;
702
+ CONFIG = buildConfig(merged);
703
+ }
704
+ /**
705
+ * Rebuild CONFIG from the last loaded file config. Used after the legacy
706
+ * migration runs so the storage default can flip from the legacy layout to
707
+ * ~/.omms/data when the migration marker says it succeeded.
708
+ */
709
+ function refreshStorageResolution() {
710
+ CONFIG = buildConfig(lastFileConfig);
711
+ }
712
+ /**
713
+ * initConfig plus the one-time legacy store migration (decision D13).
714
+ *
715
+ * The migration only applies while the resolved store sits on a DEFAULT
716
+ * layout (legacy ~/.opencode-mem/data or omms ~/.omms/data). The legacy
717
+ * config template pinned "storagePath": "~/.opencode-mem/data" explicitly,
718
+ * so key-presence cannot be the skip signal; a genuinely custom store path
719
+ * (the user pointing storage elsewhere) skips migration entirely.
720
+ * OMMS_SKIP_LEGACY_MIGRATION=1 (set by the test preload) disables the
721
+ * automatic run without affecting resolution.
722
+ */
723
+ export function initConfigWithLegacyMigration(directory) {
724
+ initConfig(directory);
725
+ const { legacyDataDir, ommsDataDir } = legacyMigrationPaths();
726
+ const usesDefaultLayout = CONFIG.storagePath === legacyDataDir || CONFIG.storagePath === ommsDataDir;
727
+ if (usesDefaultLayout && process.env.OMMS_SKIP_LEGACY_MIGRATION !== "1") {
728
+ runLegacyStoreMigration();
729
+ refreshStorageResolution();
730
+ }
731
+ }
732
+ export function isConfigured() {
733
+ return true;
734
+ }
735
+ /**
736
+ * The containerTagPrefix explicitly set in the merged config files, or
737
+ * undefined when only the default applies. The tag prefix migration warns
738
+ * when this is "opencode" after migration (the override matches no rows).
739
+ */
740
+ export function getExplicitContainerTagPrefix() {
741
+ return lastFileConfig.containerTagPrefix;
742
+ }