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.
- package/LICENSE +21 -0
- package/README.md +587 -0
- package/dist/adapters/opencode/auto-capture-host.d.ts +3 -0
- package/dist/adapters/opencode/auto-capture-host.js +29 -0
- package/dist/adapters/opencode/auto-capture-summary.d.ts +4 -0
- package/dist/adapters/opencode/auto-capture-summary.js +147 -0
- package/dist/adapters/opencode/conversation.d.ts +2 -0
- package/dist/adapters/opencode/conversation.js +50 -0
- package/dist/adapters/opencode/user-prompt.d.ts +9 -0
- package/dist/adapters/opencode/user-prompt.js +26 -0
- package/dist/adapters/pi/capture.d.ts +41 -0
- package/dist/adapters/pi/capture.js +91 -0
- package/dist/adapters/pi/conversation.d.ts +38 -0
- package/dist/adapters/pi/conversation.js +118 -0
- package/dist/adapters/pi/extension.d.ts +18 -0
- package/dist/adapters/pi/extension.js +234 -0
- package/dist/adapters/pi/import-command.d.ts +29 -0
- package/dist/adapters/pi/import-command.js +232 -0
- package/dist/adapters/pi/profile.d.ts +39 -0
- package/dist/adapters/pi/profile.js +148 -0
- package/dist/adapters/pi/provider.d.ts +50 -0
- package/dist/adapters/pi/provider.js +77 -0
- package/dist/config.d.ts +290 -0
- package/dist/config.js +742 -0
- package/dist/core/capture-context.d.ts +4 -0
- package/dist/core/capture-context.js +132 -0
- package/dist/core/capture.d.ts +15 -0
- package/dist/core/capture.js +68 -0
- package/dist/core/extraction.d.ts +80 -0
- package/dist/core/extraction.js +140 -0
- package/dist/core/host.d.ts +52 -0
- package/dist/core/host.js +1 -0
- package/dist/core/memory-operations.d.ts +27 -0
- package/dist/core/memory-operations.js +247 -0
- package/dist/core/retrieval.d.ts +12 -0
- package/dist/core/retrieval.js +72 -0
- package/dist/importer/discovery.d.ts +31 -0
- package/dist/importer/discovery.js +88 -0
- package/dist/importer/importer.d.ts +87 -0
- package/dist/importer/importer.js +260 -0
- package/dist/importer/ledger.d.ts +44 -0
- package/dist/importer/ledger.js +137 -0
- package/dist/importer/session-loader.d.ts +19 -0
- package/dist/importer/session-loader.js +12 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +601 -0
- package/dist/plugin.d.ts +9 -0
- package/dist/plugin.js +11 -0
- package/dist/services/ai/ai-provider-factory.d.ts +7 -0
- package/dist/services/ai/ai-provider-factory.js +41 -0
- package/dist/services/ai/api-key-placeholder.d.ts +1 -0
- package/dist/services/ai/api-key-placeholder.js +14 -0
- package/dist/services/ai/internal-capture-sessions.d.ts +6 -0
- package/dist/services/ai/internal-capture-sessions.js +33 -0
- package/dist/services/ai/opencode-diagnostics.d.ts +7 -0
- package/dist/services/ai/opencode-diagnostics.js +36 -0
- package/dist/services/ai/opencode-host-config.d.ts +10 -0
- package/dist/services/ai/opencode-host-config.js +51 -0
- package/dist/services/ai/opencode-provider-loader.d.ts +3 -0
- package/dist/services/ai/opencode-provider-loader.js +4 -0
- package/dist/services/ai/opencode-provider.d.ts +92 -0
- package/dist/services/ai/opencode-provider.js +419 -0
- package/dist/services/ai/opencode-sdk-client.d.ts +6 -0
- package/dist/services/ai/opencode-sdk-client.js +38 -0
- package/dist/services/ai/profile-llm-client.d.ts +2 -0
- package/dist/services/ai/profile-llm-client.js +27 -0
- package/dist/services/ai/provider-config.d.ts +17 -0
- package/dist/services/ai/provider-config.js +30 -0
- package/dist/services/ai/providers/anthropic-messages.d.ts +34 -0
- package/dist/services/ai/providers/anthropic-messages.js +220 -0
- package/dist/services/ai/providers/base-provider.d.ts +24 -0
- package/dist/services/ai/providers/base-provider.js +24 -0
- package/dist/services/ai/providers/google-gemini.d.ts +15 -0
- package/dist/services/ai/providers/google-gemini.js +228 -0
- package/dist/services/ai/providers/minimax.d.ts +35 -0
- package/dist/services/ai/providers/minimax.js +55 -0
- package/dist/services/ai/providers/openai-chat-completion.d.ts +22 -0
- package/dist/services/ai/providers/openai-chat-completion.js +387 -0
- package/dist/services/ai/providers/openai-responses.d.ts +13 -0
- package/dist/services/ai/providers/openai-responses.js +182 -0
- package/dist/services/ai/providers/orcarouter.d.ts +51 -0
- package/dist/services/ai/providers/orcarouter.js +66 -0
- package/dist/services/ai/session/ai-session-manager.d.ts +24 -0
- package/dist/services/ai/session/ai-session-manager.js +211 -0
- package/dist/services/ai/session/session-types.d.ts +42 -0
- package/dist/services/ai/session/session-types.js +1 -0
- package/dist/services/ai/tools/tool-schema.d.ts +40 -0
- package/dist/services/ai/tools/tool-schema.js +24 -0
- package/dist/services/ai/validators/user-profile-validator.d.ts +12 -0
- package/dist/services/ai/validators/user-profile-validator.js +114 -0
- package/dist/services/api-handlers.d.ts +186 -0
- package/dist/services/api-handlers.js +1284 -0
- package/dist/services/auth-token.d.ts +14 -0
- package/dist/services/auth-token.js +59 -0
- package/dist/services/auto-capture.d.ts +3 -0
- package/dist/services/auto-capture.js +127 -0
- package/dist/services/cleanup-service.d.ts +22 -0
- package/dist/services/cleanup-service.js +109 -0
- package/dist/services/client.d.ts +134 -0
- package/dist/services/client.js +327 -0
- package/dist/services/context.d.ts +10 -0
- package/dist/services/context.js +27 -0
- package/dist/services/cors.d.ts +12 -0
- package/dist/services/cors.js +52 -0
- package/dist/services/deduplication-service.d.ts +28 -0
- package/dist/services/deduplication-service.js +111 -0
- package/dist/services/embedding.d.ts +36 -0
- package/dist/services/embedding.js +200 -0
- package/dist/services/jsonc.d.ts +6 -0
- package/dist/services/jsonc.js +76 -0
- package/dist/services/language-detector.d.ts +2 -0
- package/dist/services/language-detector.js +33 -0
- package/dist/services/legacy-migration.d.ts +77 -0
- package/dist/services/legacy-migration.js +343 -0
- package/dist/services/logger.d.ts +1 -0
- package/dist/services/logger.js +119 -0
- package/dist/services/memory-portability-service.d.ts +37 -0
- package/dist/services/memory-portability-service.js +340 -0
- package/dist/services/memory-scope.d.ts +16 -0
- package/dist/services/memory-scope.js +43 -0
- package/dist/services/migration-service.d.ts +42 -0
- package/dist/services/migration-service.js +307 -0
- package/dist/services/onnxruntime-resolve.d.ts +24 -0
- package/dist/services/onnxruntime-resolve.js +174 -0
- package/dist/services/privacy.d.ts +26 -0
- package/dist/services/privacy.js +69 -0
- package/dist/services/request-timeouts.d.ts +9 -0
- package/dist/services/request-timeouts.js +11 -0
- package/dist/services/runtime-require.d.ts +17 -0
- package/dist/services/runtime-require.js +75 -0
- package/dist/services/secret-resolver.d.ts +1 -0
- package/dist/services/secret-resolver.js +65 -0
- package/dist/services/shard-inventory-service.d.ts +69 -0
- package/dist/services/shard-inventory-service.js +315 -0
- package/dist/services/shard-path-migration-service.d.ts +31 -0
- package/dist/services/shard-path-migration-service.js +367 -0
- package/dist/services/tag-prefix-migration.d.ts +60 -0
- package/dist/services/tag-prefix-migration.js +439 -0
- package/dist/services/tag-prefix-scan.d.ts +26 -0
- package/dist/services/tag-prefix-scan.js +91 -0
- package/dist/services/tags.d.ts +29 -0
- package/dist/services/tags.js +251 -0
- package/dist/services/turso/connection-manager.d.ts +15 -0
- package/dist/services/turso/connection-manager.js +163 -0
- package/dist/services/turso/cross-process-write-lock.d.ts +1 -0
- package/dist/services/turso/cross-process-write-lock.js +89 -0
- package/dist/services/turso/legacy-migrator.d.ts +1 -0
- package/dist/services/turso/legacy-migrator.js +540 -0
- package/dist/services/turso/lifecycle.d.ts +1 -0
- package/dist/services/turso/lifecycle.js +14 -0
- package/dist/services/turso/operation-lock.d.ts +2 -0
- package/dist/services/turso/operation-lock.js +61 -0
- package/dist/services/turso/ready.d.ts +2 -0
- package/dist/services/turso/ready.js +36 -0
- package/dist/services/turso/shard-manager.d.ts +38 -0
- package/dist/services/turso/shard-manager.js +496 -0
- package/dist/services/turso/sqlite-handle-release.d.ts +16 -0
- package/dist/services/turso/sqlite-handle-release.js +56 -0
- package/dist/services/turso/turso-db.d.ts +18 -0
- package/dist/services/turso/turso-db.js +53 -0
- package/dist/services/turso/types.d.ts +42 -0
- package/dist/services/turso/types.js +1 -0
- package/dist/services/turso/vector-search.d.ts +41 -0
- package/dist/services/turso/vector-search.js +384 -0
- package/dist/services/turso/vector-utils.d.ts +7 -0
- package/dist/services/turso/vector-utils.js +67 -0
- package/dist/services/user-memory-learning.d.ts +97 -0
- package/dist/services/user-memory-learning.js +673 -0
- package/dist/services/user-profile/ai-cleanup.d.ts +58 -0
- package/dist/services/user-profile/ai-cleanup.js +486 -0
- package/dist/services/user-profile/profile-context.d.ts +1 -0
- package/dist/services/user-profile/profile-context.js +105 -0
- package/dist/services/user-profile/profile-utils.d.ts +2 -0
- package/dist/services/user-profile/profile-utils.js +45 -0
- package/dist/services/user-profile/types.d.ts +92 -0
- package/dist/services/user-profile/types.js +1 -0
- package/dist/services/user-profile/user-profile-manager.d.ts +65 -0
- package/dist/services/user-profile/user-profile-manager.js +1660 -0
- package/dist/services/user-prompt/user-prompt-manager.d.ts +53 -0
- package/dist/services/user-prompt/user-prompt-manager.js +303 -0
- package/dist/services/web-api-auth.d.ts +3 -0
- package/dist/services/web-api-auth.js +25 -0
- package/dist/services/web-auth.d.ts +46 -0
- package/dist/services/web-auth.js +105 -0
- package/dist/services/web-server.d.ts +57 -0
- package/dist/services/web-server.js +659 -0
- package/dist/shared/api/index.d.ts +4 -0
- package/dist/shared/api/index.js +2 -0
- package/dist/shared/api/portability-schemas.d.ts +56 -0
- package/dist/shared/api/portability-schemas.js +39 -0
- package/dist/shared/api/schemas.d.ts +295 -0
- package/dist/shared/api/schemas.js +134 -0
- package/dist/shared/api/types.d.ts +28 -0
- package/dist/shared/api/types.js +1 -0
- package/dist/types/index.d.ts +27 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/context-limit.d.ts +10 -0
- package/dist/utils/context-limit.js +43 -0
- package/dist/utils/math.d.ts +7 -0
- package/dist/utils/math.js +48 -0
- package/dist/utils/profile.d.ts +2 -0
- package/dist/utils/profile.js +15 -0
- package/dist/v2/adapter.d.ts +5 -0
- package/dist/v2/adapter.js +218 -0
- package/dist/v2/legacy-client.d.ts +15 -0
- package/dist/v2/legacy-client.js +257 -0
- package/dist/v2/memory-bridge.d.ts +18 -0
- package/dist/v2/memory-bridge.js +29 -0
- package/dist/v2/plugin.d.ts +3 -0
- package/dist/v2/plugin.js +24 -0
- package/dist/web/assets/index-DPjEpD1h.css +2 -0
- package/dist/web/assets/index-do26c4JJ.js +122 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/index.html +14 -0
- package/dist/web/omms-icon.png +0 -0
- 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;
|