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,367 @@
|
|
|
1
|
+
import { copyFileSync, existsSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
2
|
+
import { basename, join, relative, resolve } from "node:path";
|
|
3
|
+
import { CONFIG } from "../config.js";
|
|
4
|
+
import { extractScopeFromContainerTag } from "./memory-scope.js";
|
|
5
|
+
import { getProjectTagInfo } from "./tags.js";
|
|
6
|
+
import { ensureTursoReady } from "./turso/ready.js";
|
|
7
|
+
import { acquireTursoOperationLock } from "./turso/operation-lock.js";
|
|
8
|
+
import { tursoShardManager } from "./turso/shard-manager.js";
|
|
9
|
+
import { tursoConnectionManager } from "./turso/connection-manager.js";
|
|
10
|
+
import { tursoVectorSearch } from "./turso/vector-search.js";
|
|
11
|
+
import { withSqliteFileLockRetry } from "./turso/sqlite-handle-release.js";
|
|
12
|
+
import { log } from "./logger.js";
|
|
13
|
+
import { shardInventoryService, } from "./shard-inventory-service.js";
|
|
14
|
+
import { userPromptManager } from "./user-prompt/user-prompt-manager.js";
|
|
15
|
+
function swapStatePath() {
|
|
16
|
+
return join(CONFIG.storagePath, ".path-migrate-swap.json");
|
|
17
|
+
}
|
|
18
|
+
function writeSwapState(path, state) {
|
|
19
|
+
writeFileSync(path, JSON.stringify(state), "utf-8");
|
|
20
|
+
}
|
|
21
|
+
function assertRecoveryPathInsideStorage(path) {
|
|
22
|
+
const rel = relative(resolve(CONFIG.storagePath), resolve(path));
|
|
23
|
+
if (rel === "" || rel.startsWith("..") || rel.includes("\0")) {
|
|
24
|
+
throw new Error(`Path-migrate recovery path escapes storage: ${path}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async function countLiveMemories(dbPath) {
|
|
28
|
+
if (!existsSync(dbPath))
|
|
29
|
+
return 0;
|
|
30
|
+
const db = await tursoConnectionManager.getConnection(dbPath);
|
|
31
|
+
return tursoVectorSearch.countAllVectors(db);
|
|
32
|
+
}
|
|
33
|
+
async function removeFile(path) {
|
|
34
|
+
await tursoConnectionManager.closeConnection(path);
|
|
35
|
+
if (existsSync(path)) {
|
|
36
|
+
await withSqliteFileLockRetry(() => unlinkSync(path));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export class ShardPathMigrationService {
|
|
40
|
+
async migrate(options) {
|
|
41
|
+
await ensureTursoReady();
|
|
42
|
+
const target = getProjectTagInfo(options.currentDirectory);
|
|
43
|
+
const newHash = extractScopeFromContainerTag(target.tag).hash;
|
|
44
|
+
const resolveResult = await shardInventoryService.resolveMigrationSource(options.currentDirectory, { fromPath: options.fromPath, fromHash: options.fromHash });
|
|
45
|
+
if (!resolveResult.success || !resolveResult.source) {
|
|
46
|
+
return {
|
|
47
|
+
success: false,
|
|
48
|
+
dryRun: Boolean(options.dryRun),
|
|
49
|
+
error: resolveResult.error,
|
|
50
|
+
candidates: resolveResult.candidates,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const source = resolveResult.source;
|
|
54
|
+
const oldHash = source.scopeHash;
|
|
55
|
+
const resultBase = {
|
|
56
|
+
dryRun: Boolean(options.dryRun),
|
|
57
|
+
oldHash,
|
|
58
|
+
newHash,
|
|
59
|
+
oldPath: source.group.projectPath,
|
|
60
|
+
newPath: target.projectPath,
|
|
61
|
+
matchedBy: source.matchedBy,
|
|
62
|
+
};
|
|
63
|
+
if (oldHash === newHash) {
|
|
64
|
+
return {
|
|
65
|
+
success: true,
|
|
66
|
+
...resultBase,
|
|
67
|
+
migratedShards: 0,
|
|
68
|
+
migratedMemories: 0,
|
|
69
|
+
actions: [],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (source.group.pathExists && !options.allowLinkedSource) {
|
|
73
|
+
return {
|
|
74
|
+
success: false,
|
|
75
|
+
...resultBase,
|
|
76
|
+
error: `Migration refused: source project path still exists (${source.group.projectPath}). ` +
|
|
77
|
+
"Set allowLinkedSource: true only after confirming that this active project should be reassociated.",
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// Inventory may discover a valid shard added after startup reconciliation.
|
|
81
|
+
// Register it so list-shards and migrate operate on the same set.
|
|
82
|
+
for (const descriptor of source.group.shards) {
|
|
83
|
+
if (descriptor.shardId === null && descriptor.fileExists) {
|
|
84
|
+
await tursoShardManager.registerExistingShard("project", oldHash, descriptor.shardIndex, descriptor.dbPath, descriptor.memoryCount, descriptor.isActive);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const sourceShards = await tursoShardManager.getAllShards("project", oldHash);
|
|
88
|
+
if (sourceShards.length === 0) {
|
|
89
|
+
return {
|
|
90
|
+
success: false,
|
|
91
|
+
...resultBase,
|
|
92
|
+
error: `No usable shards found for source hash ${oldHash}`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
// Strict conflict policy: any non-empty target aborts unchanged.
|
|
96
|
+
const targetShards = await tursoShardManager.getAllShards("project", newHash);
|
|
97
|
+
for (const shard of targetShards) {
|
|
98
|
+
const liveCount = await countLiveMemories(shard.dbPath);
|
|
99
|
+
if (liveCount > 0 || shard.vectorCount > 0) {
|
|
100
|
+
return {
|
|
101
|
+
success: false,
|
|
102
|
+
...resultBase,
|
|
103
|
+
error: `Migration aborted: target project already has ${Math.max(liveCount, shard.vectorCount)} memories ` +
|
|
104
|
+
`(shard ${basename(shard.dbPath)}). Delete or forget those memories first, or use export/import.`,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const oldContainerTag = source.group.containerTag ?? `${CONFIG.containerTagPrefix}_project_${oldHash}`;
|
|
109
|
+
const nonce = `${process.pid}-${Date.now()}`;
|
|
110
|
+
const moves = sourceShards.map((shard) => {
|
|
111
|
+
const newPath = tursoShardManager.getShardPath("project", newHash, shard.shardIndex);
|
|
112
|
+
const inventoryShard = source.group.shards.find((candidate) => candidate.shardIndex === shard.shardIndex);
|
|
113
|
+
return {
|
|
114
|
+
shardId: shard.id,
|
|
115
|
+
shardIndex: shard.shardIndex,
|
|
116
|
+
oldPath: shard.dbPath,
|
|
117
|
+
newPath,
|
|
118
|
+
stagedPath: `${newPath}.path-migrate-${nonce}.tmp`,
|
|
119
|
+
backupPath: `${shard.dbPath}.pre-path-migrate-${nonce}.bak`,
|
|
120
|
+
isActive: shard.isActive,
|
|
121
|
+
vectorCount: inventoryShard?.memoryCount ?? shard.vectorCount,
|
|
122
|
+
oldContainerTag,
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
for (const move of moves) {
|
|
126
|
+
if (existsSync(move.newPath) && !targetShards.some((s) => s.dbPath === move.newPath)) {
|
|
127
|
+
if ((await countLiveMemories(move.newPath)) > 0) {
|
|
128
|
+
return {
|
|
129
|
+
success: false,
|
|
130
|
+
...resultBase,
|
|
131
|
+
error: `Migration aborted: target shard file already contains memories (${basename(move.newPath)}).`,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const actions = moves.map((move) => ({
|
|
137
|
+
type: "rename",
|
|
138
|
+
detail: `${basename(move.oldPath)} -> ${basename(move.newPath)}`,
|
|
139
|
+
}));
|
|
140
|
+
for (const shard of targetShards) {
|
|
141
|
+
actions.push({ type: "archive-empty-target", detail: basename(shard.dbPath) });
|
|
142
|
+
}
|
|
143
|
+
const migratedMemories = (await Promise.all(moves.map((move) => countLiveMemories(move.oldPath)))).reduce((sum, count) => sum + count, 0);
|
|
144
|
+
actions.push({
|
|
145
|
+
type: "update-memories",
|
|
146
|
+
detail: `Remap container_tag and project metadata for ${migratedMemories} memories`,
|
|
147
|
+
});
|
|
148
|
+
if (source.group.projectPath && target.projectPath) {
|
|
149
|
+
actions.push({
|
|
150
|
+
type: "update-prompts",
|
|
151
|
+
detail: `${source.group.projectPath} -> ${target.projectPath}`,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
if (options.dryRun) {
|
|
155
|
+
return {
|
|
156
|
+
success: true,
|
|
157
|
+
...resultBase,
|
|
158
|
+
migratedShards: moves.length,
|
|
159
|
+
migratedMemories,
|
|
160
|
+
actions,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
const releaseLock = acquireTursoOperationLock("path-migrate");
|
|
164
|
+
const statePath = swapStatePath();
|
|
165
|
+
const archivedTargets = targetShards.map((shard) => ({
|
|
166
|
+
shardId: shard.id,
|
|
167
|
+
shardIndex: shard.shardIndex,
|
|
168
|
+
originalPath: shard.dbPath,
|
|
169
|
+
archivePath: `${shard.dbPath}.pre-path-migrate-${nonce}.bak`,
|
|
170
|
+
isActive: shard.isActive,
|
|
171
|
+
vectorCount: shard.vectorCount,
|
|
172
|
+
}));
|
|
173
|
+
const state = {
|
|
174
|
+
operation: "path-migrate",
|
|
175
|
+
oldHash,
|
|
176
|
+
newHash,
|
|
177
|
+
moves,
|
|
178
|
+
archivedTargets,
|
|
179
|
+
};
|
|
180
|
+
try {
|
|
181
|
+
// Stage complete, updated copies while originals remain untouched.
|
|
182
|
+
for (const move of moves) {
|
|
183
|
+
await tursoConnectionManager.closeConnection(move.oldPath);
|
|
184
|
+
if (!existsSync(move.oldPath)) {
|
|
185
|
+
throw new Error(`Source shard file missing: ${move.oldPath}`);
|
|
186
|
+
}
|
|
187
|
+
await removeFile(move.stagedPath);
|
|
188
|
+
copyFileSync(move.oldPath, move.stagedPath);
|
|
189
|
+
const stagedDb = await tursoConnectionManager.getConnection(move.stagedPath);
|
|
190
|
+
await tursoVectorSearch.updateProjectAssociation(stagedDb, move.oldContainerTag, {
|
|
191
|
+
containerTag: target.tag,
|
|
192
|
+
projectPath: target.projectPath,
|
|
193
|
+
projectName: target.projectName,
|
|
194
|
+
displayName: target.displayName,
|
|
195
|
+
gitRepoUrl: target.gitRepoUrl ?? null,
|
|
196
|
+
});
|
|
197
|
+
await stagedDb.run(`UPDATE memories SET
|
|
198
|
+
container_tag = ?, project_path = ?, project_name = ?,
|
|
199
|
+
display_name = ?, git_repo_url = ?
|
|
200
|
+
WHERE container_tag != ?`, [
|
|
201
|
+
target.tag,
|
|
202
|
+
target.projectPath ?? null,
|
|
203
|
+
target.projectName ?? null,
|
|
204
|
+
target.displayName ?? null,
|
|
205
|
+
target.gitRepoUrl ?? null,
|
|
206
|
+
target.tag,
|
|
207
|
+
]);
|
|
208
|
+
const stagedCount = await tursoVectorSearch.countAllVectors(stagedDb);
|
|
209
|
+
if (stagedCount !== move.vectorCount) {
|
|
210
|
+
throw new Error(`Staged shard count mismatch for ${basename(move.oldPath)}: expected ${move.vectorCount}, got ${stagedCount}`);
|
|
211
|
+
}
|
|
212
|
+
await tursoConnectionManager.closeConnection(move.stagedPath);
|
|
213
|
+
}
|
|
214
|
+
writeSwapState(statePath, state);
|
|
215
|
+
// Empty targets remain recoverable until the complete swap succeeds.
|
|
216
|
+
for (let index = 0; index < targetShards.length; index++) {
|
|
217
|
+
await tursoShardManager.archiveShard(targetShards[index].id, "pre-path-migrate", archivedTargets[index].archivePath);
|
|
218
|
+
}
|
|
219
|
+
for (const move of moves) {
|
|
220
|
+
await tursoConnectionManager.closeConnection(move.oldPath);
|
|
221
|
+
await withSqliteFileLockRetry(() => renameSync(move.oldPath, move.backupPath));
|
|
222
|
+
try {
|
|
223
|
+
await withSqliteFileLockRetry(() => renameSync(move.stagedPath, move.newPath));
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
await withSqliteFileLockRetry(() => renameSync(move.backupPath, move.oldPath));
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
await tursoShardManager.reassignShardScope(move.shardId, newHash, move.newPath, move.vectorCount, move.isActive);
|
|
230
|
+
actions.push({
|
|
231
|
+
type: "reassign-metadata",
|
|
232
|
+
detail: `shard ${move.shardId} -> ${newHash}#${move.shardIndex}`,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
unlinkSync(statePath);
|
|
236
|
+
// Prompt metadata is secondary and must not invalidate a completed,
|
|
237
|
+
// recoverable memory migration.
|
|
238
|
+
if (source.group.projectPath && target.projectPath) {
|
|
239
|
+
try {
|
|
240
|
+
await userPromptManager.updateProjectPath(source.group.projectPath, target.projectPath);
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
log("Path migration prompt metadata update failed", { error: String(error) });
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
log("Path migration completed", {
|
|
247
|
+
oldHash,
|
|
248
|
+
newHash,
|
|
249
|
+
migratedShards: moves.length,
|
|
250
|
+
migratedMemories,
|
|
251
|
+
});
|
|
252
|
+
return {
|
|
253
|
+
success: true,
|
|
254
|
+
...resultBase,
|
|
255
|
+
dryRun: false,
|
|
256
|
+
migratedShards: moves.length,
|
|
257
|
+
migratedMemories,
|
|
258
|
+
actions,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
await this.rollbackSwap(state, statePath);
|
|
263
|
+
return {
|
|
264
|
+
success: false,
|
|
265
|
+
...resultBase,
|
|
266
|
+
dryRun: false,
|
|
267
|
+
error: error instanceof Error ? error.message : String(error),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
finally {
|
|
271
|
+
releaseLock();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
async rollbackSwap(state, statePath) {
|
|
275
|
+
let rollbackFailed = false;
|
|
276
|
+
for (const move of [...state.moves].reverse()) {
|
|
277
|
+
try {
|
|
278
|
+
await tursoConnectionManager.closeConnection(move.newPath);
|
|
279
|
+
await tursoConnectionManager.closeConnection(move.stagedPath);
|
|
280
|
+
if (existsSync(move.backupPath)) {
|
|
281
|
+
await removeFile(move.newPath);
|
|
282
|
+
await removeFile(move.oldPath);
|
|
283
|
+
await withSqliteFileLockRetry(() => renameSync(move.backupPath, move.oldPath));
|
|
284
|
+
}
|
|
285
|
+
await removeFile(move.stagedPath);
|
|
286
|
+
if (existsSync(move.oldPath)) {
|
|
287
|
+
await tursoShardManager.reassignShardScope(move.shardId, state.oldHash, move.oldPath, move.vectorCount, move.isActive);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
rollbackFailed = true;
|
|
292
|
+
log("Path migration source rollback failed", { move, error: String(error) });
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
for (const archived of state.archivedTargets) {
|
|
296
|
+
try {
|
|
297
|
+
if (archived.archivePath && existsSync(archived.archivePath)) {
|
|
298
|
+
await removeFile(archived.originalPath);
|
|
299
|
+
await withSqliteFileLockRetry(() => renameSync(archived.archivePath, archived.originalPath));
|
|
300
|
+
await tursoShardManager.registerExistingShard("project", state.newHash, archived.shardIndex, archived.originalPath, archived.vectorCount, archived.isActive);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
rollbackFailed = true;
|
|
305
|
+
log("Path migration target rollback failed", { archived, error: String(error) });
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// Startup reconciliation may have registered a newly renamed shard before
|
|
309
|
+
// recovery ran. Remove only registry rows whose files no longer exist.
|
|
310
|
+
try {
|
|
311
|
+
const staleTargets = await tursoShardManager.getAllShards("project", state.newHash);
|
|
312
|
+
for (const shard of staleTargets) {
|
|
313
|
+
if (!existsSync(shard.dbPath)) {
|
|
314
|
+
await tursoShardManager.deleteShard(shard.id);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
rollbackFailed = true;
|
|
320
|
+
log("Path migration stale target cleanup failed", { error: String(error) });
|
|
321
|
+
}
|
|
322
|
+
if (!rollbackFailed && existsSync(statePath)) {
|
|
323
|
+
unlinkSync(statePath);
|
|
324
|
+
}
|
|
325
|
+
else if (rollbackFailed && !existsSync(statePath)) {
|
|
326
|
+
writeSwapState(statePath, state);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
/** Roll back an interrupted staged swap before normal storage access resumes. */
|
|
330
|
+
async recoverInterruptedSwap() {
|
|
331
|
+
const statePath = swapStatePath();
|
|
332
|
+
if (!existsSync(statePath))
|
|
333
|
+
return;
|
|
334
|
+
try {
|
|
335
|
+
const state = JSON.parse(readFileSync(statePath, "utf-8"));
|
|
336
|
+
if (state.operation !== "path-migrate" ||
|
|
337
|
+
!Array.isArray(state.moves) ||
|
|
338
|
+
!Array.isArray(state.archivedTargets)) {
|
|
339
|
+
throw new Error("invalid path-migrate swap state");
|
|
340
|
+
}
|
|
341
|
+
for (const move of state.moves) {
|
|
342
|
+
for (const path of [move.oldPath, move.newPath, move.stagedPath, move.backupPath]) {
|
|
343
|
+
assertRecoveryPathInsideStorage(path);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
for (const archived of state.archivedTargets) {
|
|
347
|
+
assertRecoveryPathInsideStorage(archived.originalPath);
|
|
348
|
+
if (archived.archivePath)
|
|
349
|
+
assertRecoveryPathInsideStorage(archived.archivePath);
|
|
350
|
+
}
|
|
351
|
+
await this.rollbackSwap(state, statePath);
|
|
352
|
+
if (existsSync(statePath)) {
|
|
353
|
+
throw new Error("rollback could not restore every shard");
|
|
354
|
+
}
|
|
355
|
+
log("Recovered interrupted path migration", {
|
|
356
|
+
oldHash: state.oldHash,
|
|
357
|
+
newHash: state.newHash,
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
catch (error) {
|
|
361
|
+
throw new Error(`Failed to recover path-migrate swap ${statePath}: ${String(error)}`, {
|
|
362
|
+
cause: error,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
export const shardPathMigrationService = new ShardPathMigrationService();
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export declare const TAG_PREFIX_MIGRATION_LOCK_FILE = ".tag-prefix-migration.lock";
|
|
2
|
+
export declare const SHARD_MARKER_KEY = "tag_prefix_migration";
|
|
3
|
+
export { countOpencodePrefixedRows } from "./tag-prefix-scan.js";
|
|
4
|
+
export interface TagPrefixShardOutcome {
|
|
5
|
+
dbPath: string;
|
|
6
|
+
scope: "user" | "project";
|
|
7
|
+
scopeHash: string;
|
|
8
|
+
shardIndex: number;
|
|
9
|
+
rewritten: number;
|
|
10
|
+
skipped: boolean;
|
|
11
|
+
}
|
|
12
|
+
export type TagPrefixMigrationResult = {
|
|
13
|
+
status: "migrated";
|
|
14
|
+
backupPath: string;
|
|
15
|
+
shardsScanned: number;
|
|
16
|
+
shardsRewritten: number;
|
|
17
|
+
rowsRewritten: number;
|
|
18
|
+
shards: TagPrefixShardOutcome[];
|
|
19
|
+
startedAt: string;
|
|
20
|
+
completedAt: string;
|
|
21
|
+
} | {
|
|
22
|
+
status: "not-needed";
|
|
23
|
+
shardsScanned: number;
|
|
24
|
+
} | {
|
|
25
|
+
status: "already-migrated";
|
|
26
|
+
};
|
|
27
|
+
export interface TagPrefixMigrationCompletion {
|
|
28
|
+
completedAt: string;
|
|
29
|
+
backupPath: string | null;
|
|
30
|
+
shardsRewritten: number;
|
|
31
|
+
rowsRewritten: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Read the store-level completion marker from `<storePath>/metadata.db`.
|
|
35
|
+
* Returns null when the store has not completed the tag prefix migration.
|
|
36
|
+
* `storePath` must equal CONFIG.storagePath (the connection manager refuses
|
|
37
|
+
* paths outside it).
|
|
38
|
+
*/
|
|
39
|
+
export declare function readTagPrefixMigrationCompletion(storePath: string): Promise<TagPrefixMigrationCompletion | null>;
|
|
40
|
+
/**
|
|
41
|
+
* Run the one-time container tag prefix migration against CONFIG.storagePath.
|
|
42
|
+
* See the module comment for the safety rules. Throws on any failure; a throw
|
|
43
|
+
* leaves already-committed shards migrated (resume on the next run) and the
|
|
44
|
+
* completion marker absent.
|
|
45
|
+
*/
|
|
46
|
+
export declare function runTagPrefixMigration(): Promise<TagPrefixMigrationResult>;
|
|
47
|
+
/**
|
|
48
|
+
* D6 warning for an explicit `containerTagPrefix: "opencode"` override after
|
|
49
|
+
* the migration: stored rows carry omms_, so the override matches no rows.
|
|
50
|
+
* Pure function; returns null for every other value (including unset).
|
|
51
|
+
*/
|
|
52
|
+
export declare function explicitOpencodePrefixWarning(explicitPrefix: string | undefined): string | null;
|
|
53
|
+
/**
|
|
54
|
+
* Startup gate: before any read or write is served, migrate any remaining
|
|
55
|
+
* opencode_ rows to omms_ (or abort loudly). Also emits the D6 warning once
|
|
56
|
+
* per start when the config explicitly overrides the prefix to "opencode".
|
|
57
|
+
* Disabled under OMMS_SKIP_TAG_PREFIX_MIGRATION=1 (set by tests/preload.ts so
|
|
58
|
+
* bun test never rewrites the developer's real store).
|
|
59
|
+
*/
|
|
60
|
+
export declare function runStartupTagPrefixGate(): Promise<void>;
|