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,340 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { CONFIG } from "../config.js";
|
|
5
|
+
import { MemoryExportDocumentSchema, PORTABILITY_SCHEMA_VERSION, } from "../shared/api/portability-schemas.js";
|
|
6
|
+
import { extractScopeFromContainerTag } from "./memory-scope.js";
|
|
7
|
+
import { getProjectTagInfo } from "./tags.js";
|
|
8
|
+
import { embeddingService } from "./embedding.js";
|
|
9
|
+
import { ensureTursoReady } from "./turso/ready.js";
|
|
10
|
+
import { acquireTursoOperationLock } from "./turso/operation-lock.js";
|
|
11
|
+
import { tursoShardManager } from "./turso/shard-manager.js";
|
|
12
|
+
import { tursoConnectionManager } from "./turso/connection-manager.js";
|
|
13
|
+
import { tursoVectorSearch } from "./turso/vector-search.js";
|
|
14
|
+
import { formatTagsForEmbedding } from "./turso/vector-utils.js";
|
|
15
|
+
import { stripPrivateContent, isFullyPrivate } from "./privacy.js";
|
|
16
|
+
import { log } from "./logger.js";
|
|
17
|
+
function getPluginVersion() {
|
|
18
|
+
try {
|
|
19
|
+
const pkgPath = join(dirname(fileURLToPath(import.meta.url)), "../../package.json");
|
|
20
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
21
|
+
return pkg.version ?? "unknown";
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return "unknown";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const MAX_IMPORT_BYTES = 100 * 1024 * 1024;
|
|
28
|
+
function parseMetadata(value) {
|
|
29
|
+
if (!value || typeof value !== "string")
|
|
30
|
+
return undefined;
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(value);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function parseTags(value) {
|
|
39
|
+
if (!value || typeof value !== "string")
|
|
40
|
+
return undefined;
|
|
41
|
+
const tags = value
|
|
42
|
+
.split(",")
|
|
43
|
+
.map((tag) => tag.trim())
|
|
44
|
+
.filter(Boolean);
|
|
45
|
+
return tags.length > 0 ? tags : undefined;
|
|
46
|
+
}
|
|
47
|
+
function rowToExportedMemory(row) {
|
|
48
|
+
const content = stripPrivateContent(String(row.content ?? ""));
|
|
49
|
+
if (!content.trim() || isFullyPrivate(String(row.content ?? ""))) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
id: String(row.id),
|
|
54
|
+
content,
|
|
55
|
+
type: row.type ? String(row.type) : undefined,
|
|
56
|
+
tags: parseTags(row.tags),
|
|
57
|
+
createdAt: Number(row.created_at),
|
|
58
|
+
updatedAt: Number(row.updated_at ?? row.created_at),
|
|
59
|
+
isPinned: Number(row.is_pinned ?? 0) === 1,
|
|
60
|
+
metadata: parseMetadata(row.metadata),
|
|
61
|
+
displayName: row.display_name ? String(row.display_name) : undefined,
|
|
62
|
+
userName: row.user_name ? String(row.user_name) : undefined,
|
|
63
|
+
userEmail: row.user_email ? String(row.user_email) : undefined,
|
|
64
|
+
projectPath: row.project_path ? String(row.project_path) : undefined,
|
|
65
|
+
projectName: row.project_name ? String(row.project_name) : undefined,
|
|
66
|
+
gitRepoUrl: row.git_repo_url ? String(row.git_repo_url) : undefined,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export class MemoryPortabilityService {
|
|
70
|
+
async exportMemories(options) {
|
|
71
|
+
await ensureTursoReady();
|
|
72
|
+
const outputPath = resolve(options.outputPath);
|
|
73
|
+
const project = getProjectTagInfo(options.currentDirectory);
|
|
74
|
+
const { hash: scopeHash } = extractScopeFromContainerTag(project.tag);
|
|
75
|
+
const shards = await tursoShardManager.getAllShards("project", scopeHash);
|
|
76
|
+
const memories = [];
|
|
77
|
+
for (const shard of shards) {
|
|
78
|
+
if (!existsSync(shard.dbPath))
|
|
79
|
+
continue;
|
|
80
|
+
const db = await tursoConnectionManager.getConnection(shard.dbPath);
|
|
81
|
+
const rows = await tursoVectorSearch.getAllMemories(db);
|
|
82
|
+
for (const row of rows) {
|
|
83
|
+
const exported = rowToExportedMemory(row);
|
|
84
|
+
if (exported)
|
|
85
|
+
memories.push(exported);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
memories.sort((a, b) => b.createdAt - a.createdAt);
|
|
89
|
+
const document = {
|
|
90
|
+
schemaVersion: PORTABILITY_SCHEMA_VERSION,
|
|
91
|
+
exportedAt: new Date().toISOString(),
|
|
92
|
+
plugin: {
|
|
93
|
+
package: "omms",
|
|
94
|
+
version: getPluginVersion(),
|
|
95
|
+
},
|
|
96
|
+
source: {
|
|
97
|
+
containerTag: project.tag,
|
|
98
|
+
scope: "project",
|
|
99
|
+
scopeHash,
|
|
100
|
+
projectPath: project.projectPath,
|
|
101
|
+
projectName: project.projectName,
|
|
102
|
+
gitRepoUrl: project.gitRepoUrl,
|
|
103
|
+
},
|
|
104
|
+
embedding: {
|
|
105
|
+
model: CONFIG.embeddingModel,
|
|
106
|
+
dimensions: CONFIG.embeddingDimensions,
|
|
107
|
+
},
|
|
108
|
+
memories,
|
|
109
|
+
};
|
|
110
|
+
// Validate before writing so we never emit invalid documents.
|
|
111
|
+
MemoryExportDocumentSchema.parse(document);
|
|
112
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
113
|
+
const tmpPath = `${outputPath}.tmp-${process.pid}-${Date.now()}`;
|
|
114
|
+
try {
|
|
115
|
+
writeFileSync(tmpPath, JSON.stringify(document, null, 2), "utf-8");
|
|
116
|
+
renameSync(tmpPath, outputPath);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
if (existsSync(tmpPath)) {
|
|
120
|
+
try {
|
|
121
|
+
unlinkSync(tmpPath);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// ignore cleanup errors
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
success: true,
|
|
131
|
+
outputPath,
|
|
132
|
+
count: memories.length,
|
|
133
|
+
containerTag: project.tag,
|
|
134
|
+
scopeHash,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
async importMemories(options) {
|
|
138
|
+
await ensureTursoReady();
|
|
139
|
+
await embeddingService.warmup();
|
|
140
|
+
const inputPath = resolve(options.inputPath);
|
|
141
|
+
if (!existsSync(inputPath)) {
|
|
142
|
+
return {
|
|
143
|
+
success: false,
|
|
144
|
+
dryRun: Boolean(options.dryRun),
|
|
145
|
+
error: `Import file not found: ${inputPath}`,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (statSync(inputPath).size > MAX_IMPORT_BYTES) {
|
|
149
|
+
return {
|
|
150
|
+
success: false,
|
|
151
|
+
dryRun: Boolean(options.dryRun),
|
|
152
|
+
error: `Import file exceeds ${MAX_IMPORT_BYTES} byte limit`,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
let parsed;
|
|
156
|
+
try {
|
|
157
|
+
parsed = JSON.parse(readFileSync(inputPath, "utf-8"));
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
return {
|
|
161
|
+
success: false,
|
|
162
|
+
dryRun: Boolean(options.dryRun),
|
|
163
|
+
error: `Invalid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (parsed &&
|
|
167
|
+
typeof parsed === "object" &&
|
|
168
|
+
"schemaVersion" in parsed &&
|
|
169
|
+
typeof parsed.schemaVersion === "number" &&
|
|
170
|
+
parsed.schemaVersion > PORTABILITY_SCHEMA_VERSION) {
|
|
171
|
+
return {
|
|
172
|
+
success: false,
|
|
173
|
+
dryRun: Boolean(options.dryRun),
|
|
174
|
+
error: `Unsupported export schemaVersion ${parsed.schemaVersion}. Upgrade omms to import this file.`,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
const validated = MemoryExportDocumentSchema.safeParse(parsed);
|
|
178
|
+
if (!validated.success) {
|
|
179
|
+
return {
|
|
180
|
+
success: false,
|
|
181
|
+
dryRun: Boolean(options.dryRun),
|
|
182
|
+
error: `Invalid export document: ${validated.error.issues.map((issue) => issue.message).join("; ")}`,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
const document = validated.data;
|
|
186
|
+
const target = getProjectTagInfo(options.currentDirectory);
|
|
187
|
+
const { scope, hash } = extractScopeFromContainerTag(target.tag);
|
|
188
|
+
// Pre-scan for ID conflicts across all project shards of the target.
|
|
189
|
+
const targetShards = await tursoShardManager.getAllShards(scope, hash);
|
|
190
|
+
const rejected = [];
|
|
191
|
+
const skipped = [];
|
|
192
|
+
for (const memory of document.memories) {
|
|
193
|
+
for (const shard of targetShards) {
|
|
194
|
+
if (!existsSync(shard.dbPath))
|
|
195
|
+
continue;
|
|
196
|
+
const db = await tursoConnectionManager.getConnection(shard.dbPath);
|
|
197
|
+
const existing = await tursoVectorSearch.getMemoryById(db, memory.id);
|
|
198
|
+
if (existing) {
|
|
199
|
+
rejected.push({
|
|
200
|
+
id: memory.id,
|
|
201
|
+
reason: "memory id already exists in target project",
|
|
202
|
+
});
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (rejected.length > 0) {
|
|
208
|
+
return {
|
|
209
|
+
success: false,
|
|
210
|
+
dryRun: Boolean(options.dryRun),
|
|
211
|
+
imported: 0,
|
|
212
|
+
rejected,
|
|
213
|
+
containerTag: target.tag,
|
|
214
|
+
error: `Import aborted: ${rejected.length} memory id(s) already exist in the target project`,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
if (options.dryRun) {
|
|
218
|
+
return {
|
|
219
|
+
success: true,
|
|
220
|
+
dryRun: true,
|
|
221
|
+
imported: document.memories.length,
|
|
222
|
+
skipped,
|
|
223
|
+
rejected,
|
|
224
|
+
containerTag: target.tag,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
const releaseLock = acquireTursoOperationLock("memory-import");
|
|
228
|
+
const insertedIds = [];
|
|
229
|
+
try {
|
|
230
|
+
// Phase A: compute all embeddings before any DB write.
|
|
231
|
+
const prepared = [];
|
|
232
|
+
for (const memory of document.memories) {
|
|
233
|
+
const tags = memory.tags ?? [];
|
|
234
|
+
const vector = await embeddingService.embedWithTimeout(memory.content, {
|
|
235
|
+
task: "document",
|
|
236
|
+
});
|
|
237
|
+
const tagsVector = tags.length > 0
|
|
238
|
+
? await embeddingService.embedWithTimeout(formatTagsForEmbedding(tags), {
|
|
239
|
+
task: "document",
|
|
240
|
+
})
|
|
241
|
+
: undefined;
|
|
242
|
+
const metadata = {
|
|
243
|
+
...(memory.metadata ?? {}),
|
|
244
|
+
source: "import",
|
|
245
|
+
importedAt: Date.now(),
|
|
246
|
+
exportSchemaVersion: document.schemaVersion,
|
|
247
|
+
originalContainerTag: document.source.containerTag,
|
|
248
|
+
originalProjectPath: document.source.projectPath,
|
|
249
|
+
};
|
|
250
|
+
prepared.push({
|
|
251
|
+
record: {
|
|
252
|
+
id: memory.id,
|
|
253
|
+
content: memory.content,
|
|
254
|
+
vector,
|
|
255
|
+
tagsVector,
|
|
256
|
+
containerTag: target.tag,
|
|
257
|
+
tags: tags.length > 0 ? tags.join(",") : undefined,
|
|
258
|
+
type: memory.type,
|
|
259
|
+
createdAt: memory.createdAt,
|
|
260
|
+
updatedAt: memory.updatedAt ?? memory.createdAt,
|
|
261
|
+
metadata: JSON.stringify(metadata),
|
|
262
|
+
displayName: target.displayName,
|
|
263
|
+
userName: memory.userName,
|
|
264
|
+
userEmail: memory.userEmail,
|
|
265
|
+
projectPath: target.projectPath,
|
|
266
|
+
projectName: target.projectName,
|
|
267
|
+
gitRepoUrl: target.gitRepoUrl,
|
|
268
|
+
},
|
|
269
|
+
isPinned: Boolean(memory.isPinned),
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
// Phase B: single transactional write. Avoid getWriteShard() here because it
|
|
273
|
+
// refuses writes while our own operation lock is held.
|
|
274
|
+
await tursoShardManager.withScopeWriteLock(scope, hash, async () => {
|
|
275
|
+
let shard = await tursoShardManager.getActiveShard(scope, hash);
|
|
276
|
+
if (!shard) {
|
|
277
|
+
shard = await tursoShardManager.createShard(scope, hash, 0);
|
|
278
|
+
}
|
|
279
|
+
const db = await tursoConnectionManager.getConnection(shard.dbPath);
|
|
280
|
+
await db.transaction("write", async (tx) => {
|
|
281
|
+
for (const item of prepared) {
|
|
282
|
+
await tursoVectorSearch.insertVectorInTransaction(tx, item.record);
|
|
283
|
+
if (item.isPinned) {
|
|
284
|
+
await tx.execute({
|
|
285
|
+
sql: `UPDATE memories SET is_pinned = 1 WHERE id = ?`,
|
|
286
|
+
args: [item.record.id],
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
insertedIds.push(item.record.id);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
await tursoShardManager.setVectorCount(shard.id, await tursoVectorSearch.countAllVectors(db));
|
|
293
|
+
});
|
|
294
|
+
log("Memory import completed", {
|
|
295
|
+
imported: insertedIds.length,
|
|
296
|
+
containerTag: target.tag,
|
|
297
|
+
});
|
|
298
|
+
return {
|
|
299
|
+
success: true,
|
|
300
|
+
dryRun: false,
|
|
301
|
+
imported: insertedIds.length,
|
|
302
|
+
skipped,
|
|
303
|
+
rejected,
|
|
304
|
+
containerTag: target.tag,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
// Best-effort cleanup if a partial insert somehow escaped the transaction.
|
|
309
|
+
if (insertedIds.length > 0) {
|
|
310
|
+
try {
|
|
311
|
+
const shards = await tursoShardManager.getAllShards(scope, hash);
|
|
312
|
+
for (const shard of shards) {
|
|
313
|
+
const db = await tursoConnectionManager.getConnection(shard.dbPath);
|
|
314
|
+
for (const id of insertedIds) {
|
|
315
|
+
const existing = await tursoVectorSearch.getMemoryById(db, id);
|
|
316
|
+
if (existing) {
|
|
317
|
+
await tursoVectorSearch.deleteVector(db, id);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
await tursoShardManager.setVectorCount(shard.id, await tursoVectorSearch.countAllVectors(db));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
catch (cleanupError) {
|
|
324
|
+
log("Memory import cleanup failed", { error: String(cleanupError) });
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
success: false,
|
|
329
|
+
dryRun: false,
|
|
330
|
+
imported: 0,
|
|
331
|
+
error: error instanceof Error ? error.message : String(error),
|
|
332
|
+
containerTag: target.tag,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
finally {
|
|
336
|
+
releaseLock();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
export const memoryPortabilityService = new MemoryPortabilityService();
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const SCOPE_HASH_PATTERN: RegExp;
|
|
2
|
+
export declare function isValidScopeHash(hash: string): boolean;
|
|
3
|
+
export declare function assertSafeScopeHash(scopeHash: string): void;
|
|
4
|
+
export declare function extractScopeFromContainerTag(containerTag: string): {
|
|
5
|
+
scope: "user" | "project";
|
|
6
|
+
hash: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function tryExtractScopeFromContainerTag(containerTag: string): {
|
|
9
|
+
scope: "user" | "project";
|
|
10
|
+
hash: string;
|
|
11
|
+
} | null;
|
|
12
|
+
export interface MemoryScopeRef {
|
|
13
|
+
scope: "user" | "project";
|
|
14
|
+
hash: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function resolveMemoryScope(scope: "project" | "all-projects", containerTag: string): MemoryScopeRef[];
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export const SCOPE_HASH_PATTERN = /^[a-f0-9]{16}$/;
|
|
2
|
+
export function isValidScopeHash(hash) {
|
|
3
|
+
return SCOPE_HASH_PATTERN.test(hash);
|
|
4
|
+
}
|
|
5
|
+
export function assertSafeScopeHash(scopeHash) {
|
|
6
|
+
if (!isValidScopeHash(scopeHash)) {
|
|
7
|
+
throw new Error(`Invalid scope hash: expected 16 lowercase hex characters, got "${scopeHash}"`);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function extractScopeFromContainerTag(containerTag) {
|
|
11
|
+
const parts = containerTag.split("_");
|
|
12
|
+
if (parts.length < 3) {
|
|
13
|
+
throw new Error(`Invalid containerTag: expected format {prefix}_{user|project}_{16hex}, got "${containerTag}"`);
|
|
14
|
+
}
|
|
15
|
+
const hash = parts[parts.length - 1];
|
|
16
|
+
const scope = parts[parts.length - 2];
|
|
17
|
+
if (scope !== "user" && scope !== "project") {
|
|
18
|
+
throw new Error(`Invalid containerTag scope: "${scope}" in "${containerTag}"`);
|
|
19
|
+
}
|
|
20
|
+
if (!isValidScopeHash(hash)) {
|
|
21
|
+
throw new Error(`Invalid containerTag hash: expected 16 lowercase hex characters in "${containerTag}"`);
|
|
22
|
+
}
|
|
23
|
+
return { scope, hash };
|
|
24
|
+
}
|
|
25
|
+
export function tryExtractScopeFromContainerTag(containerTag) {
|
|
26
|
+
try {
|
|
27
|
+
return extractScopeFromContainerTag(containerTag);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function resolveMemoryScope(scope, containerTag) {
|
|
34
|
+
// "all-projects" must span both canonical scopes: user-scope memories live in
|
|
35
|
+
// user shards and would be silently excluded if only project shards were walked.
|
|
36
|
+
if (scope === "all-projects") {
|
|
37
|
+
return [
|
|
38
|
+
{ scope: "user", hash: "" },
|
|
39
|
+
{ scope: "project", hash: "" },
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
return [extractScopeFromContainerTag(containerTag)];
|
|
43
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface DimensionMismatch {
|
|
2
|
+
needsMigration: boolean;
|
|
3
|
+
configDimensions: number;
|
|
4
|
+
configModel: string;
|
|
5
|
+
shardMismatches: Array<{
|
|
6
|
+
shardId: number;
|
|
7
|
+
dbPath: string;
|
|
8
|
+
storedDimensions: number;
|
|
9
|
+
storedModel: string;
|
|
10
|
+
vectorCount: number;
|
|
11
|
+
}>;
|
|
12
|
+
}
|
|
13
|
+
export interface MigrationProgress {
|
|
14
|
+
phase: "preparing" | "re-embedding" | "cleanup" | "complete";
|
|
15
|
+
processed: number;
|
|
16
|
+
total: number;
|
|
17
|
+
currentShard?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface MigrationResult {
|
|
20
|
+
success: boolean;
|
|
21
|
+
strategy: "fresh-start" | "re-embed";
|
|
22
|
+
deletedShards: number;
|
|
23
|
+
reEmbeddedMemories: number;
|
|
24
|
+
duration: number;
|
|
25
|
+
error?: string;
|
|
26
|
+
}
|
|
27
|
+
export declare class MigrationService {
|
|
28
|
+
private isRunning;
|
|
29
|
+
private progressCallback?;
|
|
30
|
+
detectDimensionMismatch(): Promise<DimensionMismatch>;
|
|
31
|
+
migrateToNewModel(strategy: "fresh-start" | "re-embed", progressCallback?: (progress: MigrationProgress) => void): Promise<MigrationResult>;
|
|
32
|
+
private freshStartMigration;
|
|
33
|
+
private reEmbedMigration;
|
|
34
|
+
private rebuildShardSafely;
|
|
35
|
+
private reportProgress;
|
|
36
|
+
getStatus(): {
|
|
37
|
+
isRunning: boolean;
|
|
38
|
+
configModel: string;
|
|
39
|
+
configDimensions: number;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export declare const migrationService: MigrationService;
|