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
@@ -0,0 +1,343 @@
1
+ import { createHash } from "node:crypto";
2
+ import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { log } from "./logger.js";
6
+ /**
7
+ * One-time migration from the legacy opencode-mem layout to the omms layout.
8
+ *
9
+ * Safety rules (decision D13, in priority order):
10
+ * 1. A timestamped, checksum-verified backup of the ENTIRE legacy directory
11
+ * is created before anything is copied. Backup failure aborts the
12
+ * migration; the system keeps resolving storage against the legacy layout.
13
+ * 2. The store is COPIED to ~/.omms/data. The legacy directory is never
14
+ * moved, renamed, modified, or deleted.
15
+ * 3. Every copied file is checksum-verified against its source. Any mismatch
16
+ * aborts and marks the migration failed.
17
+ * 4. A marker file records source, destination, backup path, file count, and
18
+ * timestamp. Reruns with a marker present are no-ops (including failed
19
+ * markers: retry is an explicit operator action, documented in
20
+ * docs/omms-migration.md).
21
+ * 5. Fresh installs (no legacy directory) start directly at the new paths.
22
+ *
23
+ * The opencode_ container tag prefix is untouched: it stays the on-disk
24
+ * format so no memory row is rewritten by this migration.
25
+ */
26
+ const LEGACY_DIR_NAME = ".opencode-mem";
27
+ const OMMS_DIR_NAME = ".omms";
28
+ const DATA_SUBDIR = "data";
29
+ const BACKUPS_SUBDIR = "backups";
30
+ const MARKER_FILE = "migration-marker.json";
31
+ const LOCK_FILE = ".legacy-migration.lock";
32
+ const MANIFEST_FILE = "manifest.json";
33
+ export function legacyMigrationPaths(home = homedir()) {
34
+ const legacyDir = join(home, LEGACY_DIR_NAME);
35
+ const ommsDir = join(home, OMMS_DIR_NAME);
36
+ return {
37
+ legacyDir,
38
+ legacyDataDir: join(legacyDir, DATA_SUBDIR),
39
+ ommsDir,
40
+ ommsDataDir: join(ommsDir, DATA_SUBDIR),
41
+ backupsDir: join(ommsDir, BACKUPS_SUBDIR),
42
+ markerPath: join(ommsDir, MARKER_FILE),
43
+ lockPath: join(ommsDir, LOCK_FILE),
44
+ };
45
+ }
46
+ export function readLegacyMigrationMarker(home = homedir()) {
47
+ const { markerPath } = legacyMigrationPaths(home);
48
+ if (!existsSync(markerPath))
49
+ return null;
50
+ try {
51
+ const raw = JSON.parse(readFileSync(markerPath, "utf-8"));
52
+ if (raw &&
53
+ (raw.status === "migrated" || raw.status === "failed") &&
54
+ typeof raw.source === "string" &&
55
+ typeof raw.destination === "string") {
56
+ return { ...raw, version: 1 };
57
+ }
58
+ }
59
+ catch {
60
+ // A corrupt marker is treated as absent; rerun logic then retries.
61
+ }
62
+ return null;
63
+ }
64
+ /**
65
+ * Default storage resolution shared with config.ts. While a legacy store
66
+ * exists and the migration has not succeeded, storage keeps resolving to the
67
+ * legacy layout (covering both "migration pending" and "migration failed").
68
+ * After a successful migration, or on a fresh install, the omms path wins.
69
+ */
70
+ export function resolveDefaultStoragePath(home = homedir()) {
71
+ const { legacyDataDir, ommsDataDir } = legacyMigrationPaths(home);
72
+ if (existsSync(legacyDataDir)) {
73
+ const marker = readLegacyMigrationMarker(home);
74
+ if (marker?.status === "migrated")
75
+ return ommsDataDir;
76
+ return legacyDataDir;
77
+ }
78
+ return ommsDataDir;
79
+ }
80
+ function sha256File(path) {
81
+ return createHash("sha256").update(readFileSync(path)).digest("hex");
82
+ }
83
+ export function walkTree(root) {
84
+ const files = [];
85
+ const dirs = [];
86
+ const visit = (dir, rel) => {
87
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
88
+ const childRel = rel ? `${rel}/${entry.name}` : entry.name;
89
+ if (entry.isDirectory()) {
90
+ dirs.push(childRel);
91
+ visit(join(dir, entry.name), childRel);
92
+ }
93
+ else {
94
+ // Symlinks and other non-directory entries are copied as files.
95
+ files.push(childRel);
96
+ }
97
+ }
98
+ };
99
+ visit(root, "");
100
+ return { files, dirs };
101
+ }
102
+ function timestampSlug(date = new Date()) {
103
+ return date
104
+ .toISOString()
105
+ .replace(/[-:]/g, "")
106
+ .replace(/\.\d{3}Z$/, "Z");
107
+ }
108
+ export { timestampSlug };
109
+ function isProcessAlive(pid) {
110
+ try {
111
+ process.kill(pid, 0);
112
+ return true;
113
+ }
114
+ catch {
115
+ return false;
116
+ }
117
+ }
118
+ /**
119
+ * Claim the migration with a PID-liveness lock file so two hosts starting at
120
+ * the same time cannot copy into the destination concurrently. A stale lock
121
+ * (dead holder) is taken over; a live one defers this run.
122
+ */
123
+ function acquireMigrationLock(paths) {
124
+ const state = JSON.stringify({ pid: process.pid, timestamp: new Date().toISOString() });
125
+ for (let attempt = 0; attempt < 2; attempt++) {
126
+ try {
127
+ writeFileSync(paths.lockPath, state, { flag: "wx" });
128
+ return true;
129
+ }
130
+ catch {
131
+ try {
132
+ const holder = JSON.parse(readFileSync(paths.lockPath, "utf-8"));
133
+ if (typeof holder.pid === "number" && Number.isInteger(holder.pid)) {
134
+ if (isProcessAlive(holder.pid)) {
135
+ return false;
136
+ }
137
+ }
138
+ unlinkSync(paths.lockPath);
139
+ }
140
+ catch {
141
+ return false;
142
+ }
143
+ }
144
+ }
145
+ return false;
146
+ }
147
+ function releaseMigrationLock(paths) {
148
+ try {
149
+ unlinkSync(paths.lockPath);
150
+ }
151
+ catch {
152
+ // Best effort: a stale lock file is cleaned up by the next acquirer.
153
+ }
154
+ }
155
+ function writeMarker(paths, marker) {
156
+ writeFileSync(paths.markerPath, JSON.stringify(marker, null, 2), "utf-8");
157
+ }
158
+ /**
159
+ * Copy the listed files from `sourceRoot` into `destRoot` (mirroring their
160
+ * relative paths), producing a checksum manifest, then verify every copied
161
+ * file by re-reading it and comparing size and sha256. Throws on any failure.
162
+ * The caller is responsible for creating any missing parent directories and
163
+ * for holding any locks that keep the source quiescent during the copy.
164
+ */
165
+ export function copyFilesWithVerification(sourceRoot, destRoot, relPaths) {
166
+ const manifest = [];
167
+ for (const rel of relPaths) {
168
+ const sourcePath = join(sourceRoot, rel);
169
+ const destPath = join(destRoot, rel);
170
+ const size = statSync(sourcePath).size;
171
+ copyFileSync(sourcePath, destPath);
172
+ const sha256 = sha256File(destPath);
173
+ manifest.push({ path: rel, size, sha256 });
174
+ }
175
+ verifyManifest(destRoot, manifest);
176
+ return manifest;
177
+ }
178
+ /**
179
+ * Copy every file under `sourceRoot` into `destRoot` (mirroring structure),
180
+ * producing a checksum manifest, then verify every copied file by re-reading
181
+ * it and comparing size and sha256. Throws on any failure.
182
+ */
183
+ function copyTreeWithVerification(sourceRoot, destRoot) {
184
+ const { files, dirs } = walkTree(sourceRoot);
185
+ mkdirSync(destRoot, { recursive: true });
186
+ for (const rel of dirs) {
187
+ mkdirSync(join(destRoot, rel), { recursive: true });
188
+ }
189
+ return copyFilesWithVerification(sourceRoot, destRoot, files);
190
+ }
191
+ function verifyManifest(root, manifest) {
192
+ for (const entry of manifest) {
193
+ const path = join(root, entry.path);
194
+ const size = statSync(path).size;
195
+ if (size !== entry.size) {
196
+ throw new Error(`size mismatch for ${entry.path}: ${size} != ${entry.size}`);
197
+ }
198
+ const sha256 = sha256File(path);
199
+ if (sha256 !== entry.sha256) {
200
+ throw new Error(`checksum mismatch for ${entry.path}`);
201
+ }
202
+ }
203
+ }
204
+ /**
205
+ * Copy the store from the legacy data directory to the omms data directory,
206
+ * verifying every copied file against its source checksum. Returns the
207
+ * number of files copied. Throws on any failure.
208
+ */
209
+ function copyStoreWithVerification(sourceRoot, destRoot) {
210
+ const { files, dirs } = walkTree(sourceRoot);
211
+ mkdirSync(destRoot, { recursive: true });
212
+ for (const rel of dirs) {
213
+ mkdirSync(join(destRoot, rel), { recursive: true });
214
+ }
215
+ for (const rel of files) {
216
+ const sourcePath = join(sourceRoot, rel);
217
+ const destPath = join(destRoot, rel);
218
+ const sourceHash = sha256File(sourcePath);
219
+ copyFileSync(sourcePath, destPath);
220
+ const destHash = sha256File(destPath);
221
+ if (destHash !== sourceHash) {
222
+ throw new Error(`checksum mismatch for ${rel}`);
223
+ }
224
+ }
225
+ return files.length;
226
+ }
227
+ /**
228
+ * Run the one-time legacy store migration for the given home directory.
229
+ * Pure module: no environment-variable switches, no CONFIG dependency.
230
+ * Hosts integrate it through initConfigWithLegacyMigration in config.ts.
231
+ */
232
+ export function runLegacyStoreMigration(home = homedir()) {
233
+ const paths = legacyMigrationPaths(home);
234
+ const existingMarker = readLegacyMigrationMarker(home);
235
+ if (existingMarker) {
236
+ return existingMarker.status === "migrated"
237
+ ? { kind: "already-migrated", marker: existingMarker }
238
+ : { kind: "failed-marker", marker: existingMarker };
239
+ }
240
+ if (!existsSync(paths.legacyDataDir)) {
241
+ return { kind: "fresh" };
242
+ }
243
+ if (existsSync(paths.ommsDataDir)) {
244
+ // A destination that exists without a marker is not overwritten; storage
245
+ // resolution keeps the legacy layout (marker absent). Recovery is manual:
246
+ // docs/omms-migration.md.
247
+ return { kind: "destination-exists" };
248
+ }
249
+ const startedAt = new Date().toISOString();
250
+ let backupPath;
251
+ mkdirSync(paths.ommsDir, { recursive: true });
252
+ if (!acquireMigrationLock(paths)) {
253
+ return { kind: "in-progress" };
254
+ }
255
+ try {
256
+ // Stage 1: verified backup of the ENTIRE legacy directory.
257
+ backupPath = join(paths.backupsDir, `opencode-mem-${timestampSlug()}`);
258
+ try {
259
+ const manifest = copyTreeWithVerification(paths.legacyDir, backupPath);
260
+ writeFileSync(join(backupPath, MANIFEST_FILE), JSON.stringify({
261
+ createdAt: startedAt,
262
+ source: paths.legacyDir,
263
+ fileCount: manifest.length,
264
+ files: manifest,
265
+ }, null, 2), "utf-8");
266
+ }
267
+ catch (error) {
268
+ // Backup failure aborts before anything is migrated.
269
+ try {
270
+ if (existsSync(backupPath))
271
+ rmSync(backupPath, { recursive: true, force: true });
272
+ }
273
+ catch {
274
+ // Best-effort cleanup of the partial backup.
275
+ }
276
+ const marker = {
277
+ version: 1,
278
+ status: "failed",
279
+ stage: "backup",
280
+ source: paths.legacyDataDir,
281
+ destination: paths.ommsDataDir,
282
+ startedAt,
283
+ completedAt: new Date().toISOString(),
284
+ error: String(error),
285
+ };
286
+ writeMarker(paths, marker);
287
+ log("Legacy migration aborted during backup", { error: String(error) });
288
+ return { kind: "failed", marker };
289
+ }
290
+ // Stage 2: copy the store, verifying every file against its source.
291
+ try {
292
+ const fileCount = copyStoreWithVerification(paths.legacyDataDir, paths.ommsDataDir);
293
+ const marker = {
294
+ version: 1,
295
+ status: "migrated",
296
+ source: paths.legacyDataDir,
297
+ destination: paths.ommsDataDir,
298
+ backupPath,
299
+ fileCount,
300
+ startedAt,
301
+ completedAt: new Date().toISOString(),
302
+ };
303
+ writeMarker(paths, marker);
304
+ log("Legacy opencode-mem store migrated to omms layout", {
305
+ source: marker.source,
306
+ destination: marker.destination,
307
+ backupPath,
308
+ fileCount,
309
+ });
310
+ return { kind: "migrated", marker };
311
+ }
312
+ catch (error) {
313
+ // Verification failure aborts and marks the migration failed. The
314
+ // legacy directory was only ever read; the partial destination is
315
+ // removed so an explicit retry can proceed cleanly.
316
+ try {
317
+ if (existsSync(paths.ommsDataDir)) {
318
+ rmSync(paths.ommsDataDir, { recursive: true, force: true });
319
+ }
320
+ }
321
+ catch {
322
+ // Best-effort cleanup; recovery is documented.
323
+ }
324
+ const marker = {
325
+ version: 1,
326
+ status: "failed",
327
+ stage: "copy",
328
+ source: paths.legacyDataDir,
329
+ destination: paths.ommsDataDir,
330
+ backupPath,
331
+ startedAt,
332
+ completedAt: new Date().toISOString(),
333
+ error: String(error),
334
+ };
335
+ writeMarker(paths, marker);
336
+ log("Legacy migration aborted during store copy", { error: String(error) });
337
+ return { kind: "failed", marker };
338
+ }
339
+ }
340
+ finally {
341
+ releaseMigrationLock(paths);
342
+ }
343
+ }
@@ -0,0 +1 @@
1
+ export declare function log(message: string, data?: unknown): void;
@@ -0,0 +1,119 @@
1
+ import { appendFileSync, writeFileSync, existsSync, mkdirSync, statSync, renameSync, unlinkSync, readdirSync, } from "fs";
2
+ import { homedir } from "os";
3
+ import { join } from "path";
4
+ function getLogFilePath() {
5
+ return (process.env.OMMS_LOG_FILE ||
6
+ // Legacy override, still honoured from the opencode-mem days.
7
+ process.env.OPENCODE_MEM_LOG_FILE ||
8
+ join(homedir(), ".omms", "omms.log"));
9
+ }
10
+ function getLogDirPath() {
11
+ const logFile = getLogFilePath();
12
+ const lastSlash = Math.max(logFile.lastIndexOf("/"), logFile.lastIndexOf("\\"));
13
+ return lastSlash === -1 ? "." : logFile.slice(0, lastSlash);
14
+ }
15
+ const MAX_LOG_SIZE = 5 * 1024 * 1024;
16
+ const MAX_LOG_DAYS = 30;
17
+ const GLOBAL_LOGGER_KEY = Symbol.for("omms.logger.initialized");
18
+ const LAST_ROTATE_DATE_KEY = Symbol.for("omms.logger.lastRotateDate");
19
+ function formatTimestamp(d) {
20
+ const pad = (n, w = 2) => String(n).padStart(w, "0");
21
+ const offset = -d.getTimezoneOffset();
22
+ const sign = offset >= 0 ? "+" : "-";
23
+ const oh = pad(Math.floor(Math.abs(offset) / 60));
24
+ const om = pad(Math.abs(offset) % 60);
25
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${pad(d.getMilliseconds(), 3)}${sign}${oh}:${om}`;
26
+ }
27
+ function formatDate(d) {
28
+ const pad = (n) => String(n).padStart(2, "0");
29
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
30
+ }
31
+ function getDateStamp() {
32
+ return formatDate(new Date());
33
+ }
34
+ function rotateLog() {
35
+ const logFile = getLogFilePath();
36
+ try {
37
+ if (!existsSync(logFile))
38
+ return;
39
+ const stats = statSync(logFile);
40
+ const dateStamp = getDateStamp();
41
+ const logDir = getLogDirPath();
42
+ const needRotate = (() => {
43
+ if (stats.size >= MAX_LOG_SIZE)
44
+ return true;
45
+ const lastModified = formatDate(stats.mtime);
46
+ return dateStamp !== lastModified;
47
+ })();
48
+ if (!needRotate)
49
+ return;
50
+ const archiveName = join(logDir, `omms-${getArchiveDate(stats)}.log`);
51
+ if (!existsSync(archiveName)) {
52
+ renameSync(logFile, archiveName);
53
+ }
54
+ else {
55
+ const oldLog = logFile + ".old";
56
+ if (existsSync(oldLog))
57
+ unlinkSync(oldLog);
58
+ renameSync(logFile, oldLog);
59
+ }
60
+ cleanupOldLogs();
61
+ }
62
+ catch {
63
+ // ignore log rotation failures
64
+ }
65
+ }
66
+ function getArchiveDate(stats) {
67
+ return formatDate(stats.mtime);
68
+ }
69
+ function cleanupOldLogs() {
70
+ const logDir = getLogDirPath();
71
+ const cutoff = Date.now() - MAX_LOG_DAYS * 24 * 60 * 60 * 1000;
72
+ try {
73
+ if (!existsSync(logDir))
74
+ return;
75
+ const files = readdirSync(logDir);
76
+ for (const file of files) {
77
+ const match = file.match(/^omms-(\d{4}-\d{2}-\d{2})\.log$/);
78
+ if (!match)
79
+ continue;
80
+ const dateStr = match[1];
81
+ const [y, m, d] = dateStr.split("-").map(Number);
82
+ const fileDate = new Date(y, m - 1, d);
83
+ if (fileDate.getTime() < cutoff) {
84
+ unlinkSync(join(logDir, file));
85
+ }
86
+ }
87
+ }
88
+ catch {
89
+ // ignore stale-log cleanup failures
90
+ }
91
+ }
92
+ function ensureLoggerInitialized() {
93
+ if (globalThis[GLOBAL_LOGGER_KEY])
94
+ return;
95
+ const logDir = getLogDirPath();
96
+ const logFile = getLogFilePath();
97
+ if (!existsSync(logDir)) {
98
+ mkdirSync(logDir, { recursive: true });
99
+ }
100
+ rotateLog();
101
+ writeFileSync(logFile, `\n--- Session started: ${formatTimestamp(new Date())} ---\n`, {
102
+ flag: "a",
103
+ });
104
+ globalThis[GLOBAL_LOGGER_KEY] = true;
105
+ }
106
+ export function log(message, data) {
107
+ ensureLoggerInitialized();
108
+ const today = formatDate(new Date());
109
+ if (globalThis[LAST_ROTATE_DATE_KEY] !== today) {
110
+ globalThis[LAST_ROTATE_DATE_KEY] = today;
111
+ rotateLog();
112
+ }
113
+ const logFile = getLogFilePath();
114
+ const timestamp = formatTimestamp(new Date());
115
+ const line = data
116
+ ? `[${timestamp}] ${message}: ${JSON.stringify(data)}\n`
117
+ : `[${timestamp}] ${message}\n`;
118
+ appendFileSync(logFile, line);
119
+ }
@@ -0,0 +1,37 @@
1
+ export interface ExportMemoriesOptions {
2
+ currentDirectory: string;
3
+ outputPath: string;
4
+ }
5
+ export interface ExportMemoriesResult {
6
+ success: boolean;
7
+ outputPath?: string;
8
+ count?: number;
9
+ containerTag?: string;
10
+ scopeHash?: string;
11
+ error?: string;
12
+ }
13
+ export interface ImportMemoriesOptions {
14
+ currentDirectory: string;
15
+ inputPath: string;
16
+ dryRun?: boolean;
17
+ }
18
+ export interface ImportMemoriesResult {
19
+ success: boolean;
20
+ dryRun: boolean;
21
+ imported?: number;
22
+ skipped?: Array<{
23
+ id: string;
24
+ reason: string;
25
+ }>;
26
+ rejected?: Array<{
27
+ id: string;
28
+ reason: string;
29
+ }>;
30
+ containerTag?: string;
31
+ error?: string;
32
+ }
33
+ export declare class MemoryPortabilityService {
34
+ exportMemories(options: ExportMemoriesOptions): Promise<ExportMemoriesResult>;
35
+ importMemories(options: ImportMemoriesOptions): Promise<ImportMemoriesResult>;
36
+ }
37
+ export declare const memoryPortabilityService: MemoryPortabilityService;