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,260 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { captureConversation } from "../core/capture.js";
|
|
3
|
+
import { memoryClient } from "../services/client.js";
|
|
4
|
+
import { extractScopeFromContainerTag } from "../services/memory-scope.js";
|
|
5
|
+
import { getTags } from "../services/tags.js";
|
|
6
|
+
import { tursoConnectionManager } from "../services/turso/connection-manager.js";
|
|
7
|
+
import { tursoShardManager } from "../services/turso/shard-manager.js";
|
|
8
|
+
import { extractPiConversationWindows, } from "../adapters/pi/conversation.js";
|
|
9
|
+
import { discoverPiSessions } from "./discovery.js";
|
|
10
|
+
import { PiImportLedger, importLedgerDbPath } from "./ledger.js";
|
|
11
|
+
export function buildImportKey(sessionId, window) {
|
|
12
|
+
const terminalEntryId = window.sourceEntryIds?.[window.sourceEntryIds.length - 1];
|
|
13
|
+
if (!terminalEntryId)
|
|
14
|
+
return null;
|
|
15
|
+
return `pi:${sessionId}:${window.userEntryId}:${terminalEntryId}`;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Find a stored memory by its deterministic import identity. Used to
|
|
19
|
+
* reconcile crash windows: the memory insert landed but the ledger update did
|
|
20
|
+
* not commit.
|
|
21
|
+
*/
|
|
22
|
+
export async function findMemoryIdByImportId(projectHash, importId) {
|
|
23
|
+
const shards = await tursoShardManager.getAllShards("project", projectHash);
|
|
24
|
+
const escaped = importId.replace(/[\\%_]/g, (char) => `\\${char}`);
|
|
25
|
+
for (const shard of shards) {
|
|
26
|
+
const db = await tursoConnectionManager.getConnection(shard.dbPath);
|
|
27
|
+
const row = await db.get(`SELECT id FROM memories WHERE metadata LIKE ? ESCAPE '\\'`, [
|
|
28
|
+
`%"importId":"${escaped}"%`,
|
|
29
|
+
]);
|
|
30
|
+
if (row)
|
|
31
|
+
return String(row.id);
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
function resolveSessionDirectory(cwd, pathMaps) {
|
|
36
|
+
if (!cwd)
|
|
37
|
+
return null;
|
|
38
|
+
const mapped = pathMaps.find((map) => map.from === cwd)?.to;
|
|
39
|
+
const directory = mapped ?? cwd;
|
|
40
|
+
return existsSync(directory) ? directory : null;
|
|
41
|
+
}
|
|
42
|
+
function preview(text) {
|
|
43
|
+
const trimmed = text.trim().replace(/\s+/g, " ");
|
|
44
|
+
return trimmed.length > 60 ? `${trimmed.slice(0, 60)}...` : trimmed;
|
|
45
|
+
}
|
|
46
|
+
function windowWithinDateRange(window, filters) {
|
|
47
|
+
if (window.userTimestamp === undefined)
|
|
48
|
+
return true;
|
|
49
|
+
if (filters.since !== undefined && window.userTimestamp < filters.since)
|
|
50
|
+
return false;
|
|
51
|
+
if (filters.until !== undefined && window.userTimestamp > filters.until)
|
|
52
|
+
return false;
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
async function lookupExistingMemory(projectHash, key, options) {
|
|
56
|
+
if (!options.storageAccessible)
|
|
57
|
+
return null;
|
|
58
|
+
try {
|
|
59
|
+
return await findMemoryIdByImportId(projectHash, key);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export async function importPiHistory(deps, filters) {
|
|
66
|
+
const dryRun = Boolean(filters.dryRun);
|
|
67
|
+
const pathMaps = filters.pathMaps ?? [];
|
|
68
|
+
const ledger = deps.ledger ?? new PiImportLedger();
|
|
69
|
+
// Dry-run performs no ledger writes: only read state from an existing
|
|
70
|
+
// ledger file, and never create one.
|
|
71
|
+
const ledgerFileExists = existsSync(importLedgerDbPath());
|
|
72
|
+
const ledgerAccessible = !dryRun || ledgerFileExists;
|
|
73
|
+
if (ledgerAccessible) {
|
|
74
|
+
await ledger.get("__warmup__");
|
|
75
|
+
}
|
|
76
|
+
if (!dryRun) {
|
|
77
|
+
const embeddingInitError = memoryClient.getEmbeddingInitError?.();
|
|
78
|
+
if (embeddingInitError) {
|
|
79
|
+
throw new Error(`Memory system is not ready for import: ${embeddingInitError}. Start the extension and retry.`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const currentTag = filters.scope === "current-project" ? getTags(filters.currentDirectory).project.tag : null;
|
|
83
|
+
const discovery = discoverPiSessions({ root: filters.root, maxSessions: filters.maxSessions });
|
|
84
|
+
const report = {
|
|
85
|
+
dryRun,
|
|
86
|
+
root: filters.root ?? "",
|
|
87
|
+
sessionsDiscovered: discovery.sessions.length,
|
|
88
|
+
sessionsLoaded: 0,
|
|
89
|
+
sessionsFilteredOut: 0,
|
|
90
|
+
sessionsUnrecognized: discovery.unrecognized.length,
|
|
91
|
+
unresolvableSessions: [],
|
|
92
|
+
loadErrors: [],
|
|
93
|
+
unitsTotal: 0,
|
|
94
|
+
unitsImported: 0,
|
|
95
|
+
unitsWouldImport: 0,
|
|
96
|
+
unitsSkipped: 0,
|
|
97
|
+
unitsFailed: 0,
|
|
98
|
+
unitsAlreadyHandled: 0,
|
|
99
|
+
skipReasons: {},
|
|
100
|
+
projects: [],
|
|
101
|
+
units: [],
|
|
102
|
+
};
|
|
103
|
+
const projectAggregates = new Map();
|
|
104
|
+
const candidates = [];
|
|
105
|
+
for (const discovered of discovery.sessions) {
|
|
106
|
+
if (filters.session) {
|
|
107
|
+
const wanted = filters.session;
|
|
108
|
+
if (discovered.sessionId !== wanted && discovered.file !== wanted) {
|
|
109
|
+
report.sessionsFilteredOut++;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
let session;
|
|
114
|
+
try {
|
|
115
|
+
session = deps.loadSession(discovered.file);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
report.loadErrors.push({
|
|
119
|
+
file: discovered.file,
|
|
120
|
+
error: error instanceof Error ? error.message : String(error),
|
|
121
|
+
});
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const directory = resolveSessionDirectory(session.cwd, pathMaps);
|
|
125
|
+
if (!directory) {
|
|
126
|
+
report.unresolvableSessions.push({ file: session.sourceFile, cwd: session.cwd });
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const tags = getTags(directory);
|
|
130
|
+
if (currentTag && tags.project.tag !== currentTag) {
|
|
131
|
+
report.sessionsFilteredOut++;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const { hash } = extractScopeFromContainerTag(tags.project.tag);
|
|
135
|
+
report.sessionsLoaded++;
|
|
136
|
+
let aggregate = projectAggregates.get(tags.project.tag);
|
|
137
|
+
if (!aggregate) {
|
|
138
|
+
aggregate = {
|
|
139
|
+
tag: tags.project.tag,
|
|
140
|
+
hash,
|
|
141
|
+
directory,
|
|
142
|
+
sessions: new Set(),
|
|
143
|
+
units: 0,
|
|
144
|
+
};
|
|
145
|
+
projectAggregates.set(tags.project.tag, aggregate);
|
|
146
|
+
}
|
|
147
|
+
aggregate.sessions.add(session.sessionId);
|
|
148
|
+
const windows = extractPiConversationWindows(session.branch);
|
|
149
|
+
for (const window of windows) {
|
|
150
|
+
if (!windowWithinDateRange(window, filters))
|
|
151
|
+
continue;
|
|
152
|
+
const key = buildImportKey(session.sessionId, window);
|
|
153
|
+
if (!key)
|
|
154
|
+
continue;
|
|
155
|
+
candidates.push({ session, directory, tag: tags.project.tag, hash, window });
|
|
156
|
+
aggregate.units++;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
report.unitsTotal = candidates.length;
|
|
160
|
+
const storageAccessible = ledgerFileExists;
|
|
161
|
+
let processed = 0;
|
|
162
|
+
for (const candidate of candidates) {
|
|
163
|
+
const { session, directory, hash, window } = candidate;
|
|
164
|
+
const key = buildImportKey(session.sessionId, window);
|
|
165
|
+
processed++;
|
|
166
|
+
deps.onProgress?.(processed, report.unitsTotal, preview(window.userPrompt));
|
|
167
|
+
const unit = {
|
|
168
|
+
key,
|
|
169
|
+
sessionId: session.sessionId,
|
|
170
|
+
userEntryId: window.userEntryId,
|
|
171
|
+
promptPreview: preview(window.userPrompt),
|
|
172
|
+
status: "would-import",
|
|
173
|
+
};
|
|
174
|
+
const existing = ledgerAccessible ? await ledger.get(key) : null;
|
|
175
|
+
const terminalHandled = existing &&
|
|
176
|
+
(existing.status === "imported" || existing.status === "skipped") &&
|
|
177
|
+
!filters.force;
|
|
178
|
+
if (terminalHandled) {
|
|
179
|
+
unit.status = "already-handled";
|
|
180
|
+
unit.reason = existing.status;
|
|
181
|
+
report.unitsAlreadyHandled++;
|
|
182
|
+
report.units.push(unit);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
// Crash reconciliation: the memory landed but the ledger never committed.
|
|
186
|
+
const storedMemoryId = await lookupExistingMemory(hash, key, { storageAccessible });
|
|
187
|
+
if (storedMemoryId) {
|
|
188
|
+
if (!dryRun) {
|
|
189
|
+
await ledger.complete(key, storedMemoryId);
|
|
190
|
+
}
|
|
191
|
+
unit.status = "already-handled";
|
|
192
|
+
unit.reason = "reconciled";
|
|
193
|
+
unit.memoryId = storedMemoryId;
|
|
194
|
+
report.unitsAlreadyHandled++;
|
|
195
|
+
report.units.push(unit);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (dryRun) {
|
|
199
|
+
report.unitsWouldImport++;
|
|
200
|
+
report.units.push(unit);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
await ledger.begin({
|
|
204
|
+
key,
|
|
205
|
+
sessionId: session.sessionId,
|
|
206
|
+
sourceFile: session.sourceFile,
|
|
207
|
+
projectHash: hash,
|
|
208
|
+
});
|
|
209
|
+
try {
|
|
210
|
+
const result = await captureConversation({
|
|
211
|
+
host: "pi",
|
|
212
|
+
hostSessionId: session.sessionId,
|
|
213
|
+
sourceType: "history-import",
|
|
214
|
+
projectDirectory: directory,
|
|
215
|
+
userPrompt: window.userPrompt,
|
|
216
|
+
promptId: window.userEntryId,
|
|
217
|
+
textResponses: window.textResponses,
|
|
218
|
+
toolCalls: window.toolCalls,
|
|
219
|
+
sourceEntryIds: window.sourceEntryIds,
|
|
220
|
+
...(window.userTimestamp !== undefined
|
|
221
|
+
? { sourceTimestamp: window.userTimestamp }
|
|
222
|
+
: window.sourceTimestamp !== undefined
|
|
223
|
+
? { sourceTimestamp: window.sourceTimestamp }
|
|
224
|
+
: {}),
|
|
225
|
+
sourceFile: session.sourceFile,
|
|
226
|
+
importId: key,
|
|
227
|
+
}, deps.provider);
|
|
228
|
+
if (result.status === "skipped") {
|
|
229
|
+
await ledger.skip(key, `extractor-skip:${result.type ?? "unknown"}`);
|
|
230
|
+
unit.status = "skipped";
|
|
231
|
+
unit.reason = `extractor-skip:${result.type ?? "unknown"}`;
|
|
232
|
+
report.unitsSkipped++;
|
|
233
|
+
const reason = unit.reason;
|
|
234
|
+
report.skipReasons[reason] = (report.skipReasons[reason] ?? 0) + 1;
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
await ledger.complete(key, result.memoryId);
|
|
238
|
+
unit.status = "imported";
|
|
239
|
+
unit.memoryId = result.memoryId;
|
|
240
|
+
report.unitsImported++;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
245
|
+
await ledger.fail(key, message);
|
|
246
|
+
unit.status = "failed";
|
|
247
|
+
unit.reason = message;
|
|
248
|
+
report.unitsFailed++;
|
|
249
|
+
}
|
|
250
|
+
report.units.push(unit);
|
|
251
|
+
}
|
|
252
|
+
report.projects = [...projectAggregates.values()].map((aggregate) => ({
|
|
253
|
+
tag: aggregate.tag,
|
|
254
|
+
hash: aggregate.hash,
|
|
255
|
+
directory: aggregate.directory,
|
|
256
|
+
sessions: aggregate.sessions.size,
|
|
257
|
+
units: aggregate.units,
|
|
258
|
+
}));
|
|
259
|
+
return report;
|
|
260
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable ledger for the Pi historical-session import.
|
|
3
|
+
*
|
|
4
|
+
* One row per deterministic import key. Records terminal states so reruns
|
|
5
|
+
* never reprocess handled work, plus in-progress states so a crash between
|
|
6
|
+
* the memory insert and the ledger write can be reconciled against the stored
|
|
7
|
+
* import identity instead of inserting a duplicate.
|
|
8
|
+
*
|
|
9
|
+
* The ledger lives inside `storagePath` (import-ledger.db) so idempotency
|
|
10
|
+
* travels with the memory store on machine moves.
|
|
11
|
+
*/
|
|
12
|
+
export declare const IMPORT_LEDGER_DB_NAME = "import-ledger.db";
|
|
13
|
+
export type ImportLedgerStatus = "in-progress" | "imported" | "skipped" | "failed";
|
|
14
|
+
export interface ImportLedgerRow {
|
|
15
|
+
key: string;
|
|
16
|
+
sessionId: string;
|
|
17
|
+
sourceFile: string;
|
|
18
|
+
projectHash: string;
|
|
19
|
+
status: ImportLedgerStatus;
|
|
20
|
+
memoryId: string | null;
|
|
21
|
+
skipReason: string | null;
|
|
22
|
+
updatedAt: number;
|
|
23
|
+
}
|
|
24
|
+
export declare function importLedgerDbPath(storagePath?: string): string;
|
|
25
|
+
export declare class PiImportLedger {
|
|
26
|
+
private initPromise;
|
|
27
|
+
private ready;
|
|
28
|
+
private db;
|
|
29
|
+
get(key: string): Promise<ImportLedgerRow | null>;
|
|
30
|
+
countByStatus(): Promise<Record<string, number>>;
|
|
31
|
+
/** Upsert an in-progress marker before processing a unit. */
|
|
32
|
+
begin(input: {
|
|
33
|
+
key: string;
|
|
34
|
+
sessionId: string;
|
|
35
|
+
sourceFile: string;
|
|
36
|
+
projectHash: string;
|
|
37
|
+
}): Promise<void>;
|
|
38
|
+
/** Terminal success: a memory row exists for the key. */
|
|
39
|
+
complete(key: string, memoryId: string): Promise<void>;
|
|
40
|
+
/** Terminal skip (for example deterministic extractor skip): never reprocess. */
|
|
41
|
+
skip(key: string, reason: string): Promise<void>;
|
|
42
|
+
/** Retryable failure: the unit is reprocessed on the next run. */
|
|
43
|
+
fail(key: string, error: string): Promise<void>;
|
|
44
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { CONFIG } from "../config.js";
|
|
3
|
+
import { tursoConnectionManager } from "../services/turso/connection-manager.js";
|
|
4
|
+
/**
|
|
5
|
+
* Durable ledger for the Pi historical-session import.
|
|
6
|
+
*
|
|
7
|
+
* One row per deterministic import key. Records terminal states so reruns
|
|
8
|
+
* never reprocess handled work, plus in-progress states so a crash between
|
|
9
|
+
* the memory insert and the ledger write can be reconciled against the stored
|
|
10
|
+
* import identity instead of inserting a duplicate.
|
|
11
|
+
*
|
|
12
|
+
* The ledger lives inside `storagePath` (import-ledger.db) so idempotency
|
|
13
|
+
* travels with the memory store on machine moves.
|
|
14
|
+
*/
|
|
15
|
+
export const IMPORT_LEDGER_DB_NAME = "import-ledger.db";
|
|
16
|
+
export function importLedgerDbPath(storagePath = CONFIG.storagePath) {
|
|
17
|
+
return join(storagePath, IMPORT_LEDGER_DB_NAME);
|
|
18
|
+
}
|
|
19
|
+
function rowToEntry(row) {
|
|
20
|
+
return {
|
|
21
|
+
key: String(row.key),
|
|
22
|
+
sessionId: String(row.session_id),
|
|
23
|
+
sourceFile: String(row.source_file),
|
|
24
|
+
projectHash: String(row.project_hash),
|
|
25
|
+
status: row.status,
|
|
26
|
+
memoryId: row.memory_id === null || row.memory_id === undefined ? null : String(row.memory_id),
|
|
27
|
+
skipReason: row.skip_reason === null || row.skip_reason === undefined ? null : String(row.skip_reason),
|
|
28
|
+
updatedAt: Number(row.updated_at),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export class PiImportLedger {
|
|
32
|
+
initPromise = null;
|
|
33
|
+
async ready() {
|
|
34
|
+
if (this.initPromise)
|
|
35
|
+
return this.initPromise;
|
|
36
|
+
this.initPromise = (async () => {
|
|
37
|
+
const db = await tursoConnectionManager.getConnection(importLedgerDbPath());
|
|
38
|
+
await db.batch([
|
|
39
|
+
{
|
|
40
|
+
sql: `
|
|
41
|
+
CREATE TABLE IF NOT EXISTS import_ledger (
|
|
42
|
+
key TEXT PRIMARY KEY,
|
|
43
|
+
session_id TEXT NOT NULL,
|
|
44
|
+
source_file TEXT NOT NULL,
|
|
45
|
+
project_hash TEXT NOT NULL,
|
|
46
|
+
status TEXT NOT NULL,
|
|
47
|
+
memory_id TEXT,
|
|
48
|
+
skip_reason TEXT,
|
|
49
|
+
updated_at INTEGER NOT NULL
|
|
50
|
+
)
|
|
51
|
+
`,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
sql: `CREATE INDEX IF NOT EXISTS idx_import_ledger_session ON import_ledger(session_id)`,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
sql: `CREATE INDEX IF NOT EXISTS idx_import_ledger_status ON import_ledger(status)`,
|
|
58
|
+
},
|
|
59
|
+
]);
|
|
60
|
+
})();
|
|
61
|
+
try {
|
|
62
|
+
await this.initPromise;
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
this.initPromise = null;
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async db() {
|
|
70
|
+
await this.ready();
|
|
71
|
+
return tursoConnectionManager.getConnection(importLedgerDbPath());
|
|
72
|
+
}
|
|
73
|
+
async get(key) {
|
|
74
|
+
const db = await this.db();
|
|
75
|
+
const row = await db.get(`SELECT * FROM import_ledger WHERE key = ?`, [key]);
|
|
76
|
+
return row ? rowToEntry(row) : null;
|
|
77
|
+
}
|
|
78
|
+
async countByStatus() {
|
|
79
|
+
const db = await this.db();
|
|
80
|
+
const rows = await db.all(`SELECT status, COUNT(*) AS count FROM import_ledger GROUP BY status`);
|
|
81
|
+
const counts = {};
|
|
82
|
+
for (const row of rows) {
|
|
83
|
+
counts[String(row.status)] = Number(row.count);
|
|
84
|
+
}
|
|
85
|
+
return counts;
|
|
86
|
+
}
|
|
87
|
+
/** Upsert an in-progress marker before processing a unit. */
|
|
88
|
+
async begin(input) {
|
|
89
|
+
const db = await this.db();
|
|
90
|
+
await db.run(`
|
|
91
|
+
INSERT INTO import_ledger (key, session_id, source_file, project_hash, status, memory_id, skip_reason, updated_at)
|
|
92
|
+
VALUES (?, ?, ?, ?, 'in-progress', NULL, NULL, ?)
|
|
93
|
+
ON CONFLICT(key) DO UPDATE SET
|
|
94
|
+
status = 'in-progress',
|
|
95
|
+
memory_id = NULL,
|
|
96
|
+
skip_reason = NULL,
|
|
97
|
+
updated_at = excluded.updated_at
|
|
98
|
+
`, [input.key, input.sessionId, input.sourceFile, input.projectHash, Date.now()]);
|
|
99
|
+
}
|
|
100
|
+
/** Terminal success: a memory row exists for the key. */
|
|
101
|
+
async complete(key, memoryId) {
|
|
102
|
+
const db = await this.db();
|
|
103
|
+
await db.run(`
|
|
104
|
+
INSERT INTO import_ledger (key, session_id, source_file, project_hash, status, memory_id, skip_reason, updated_at)
|
|
105
|
+
VALUES (?, '', '', '', 'imported', ?, NULL, ?)
|
|
106
|
+
ON CONFLICT(key) DO UPDATE SET
|
|
107
|
+
status = 'imported',
|
|
108
|
+
memory_id = excluded.memory_id,
|
|
109
|
+
skip_reason = NULL,
|
|
110
|
+
updated_at = excluded.updated_at
|
|
111
|
+
`, [key, memoryId, Date.now()]);
|
|
112
|
+
}
|
|
113
|
+
/** Terminal skip (for example deterministic extractor skip): never reprocess. */
|
|
114
|
+
async skip(key, reason) {
|
|
115
|
+
const db = await this.db();
|
|
116
|
+
await db.run(`
|
|
117
|
+
INSERT INTO import_ledger (key, session_id, source_file, project_hash, status, memory_id, skip_reason, updated_at)
|
|
118
|
+
VALUES (?, '', '', '', 'skipped', NULL, ?, ?)
|
|
119
|
+
ON CONFLICT(key) DO UPDATE SET
|
|
120
|
+
status = 'skipped',
|
|
121
|
+
skip_reason = excluded.skip_reason,
|
|
122
|
+
updated_at = excluded.updated_at
|
|
123
|
+
`, [key, reason, Date.now()]);
|
|
124
|
+
}
|
|
125
|
+
/** Retryable failure: the unit is reprocessed on the next run. */
|
|
126
|
+
async fail(key, error) {
|
|
127
|
+
const db = await this.db();
|
|
128
|
+
await db.run(`
|
|
129
|
+
INSERT INTO import_ledger (key, session_id, source_file, project_hash, status, memory_id, skip_reason, updated_at)
|
|
130
|
+
VALUES (?, '', '', '', 'failed', NULL, ?, ?)
|
|
131
|
+
ON CONFLICT(key) DO UPDATE SET
|
|
132
|
+
status = 'failed',
|
|
133
|
+
skip_reason = excluded.skip_reason,
|
|
134
|
+
updated_at = excluded.updated_at
|
|
135
|
+
`, [key, error, Date.now()]);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PiSessionEntry } from "../adapters/pi/conversation.js";
|
|
2
|
+
/**
|
|
3
|
+
* Loads a Pi session file for import through Pi's own exported session model
|
|
4
|
+
* (`SessionManager.open`), which handles parsing and legacy-version migration
|
|
5
|
+
* in memory. Read-only: nothing on this code path appends or rewrites the
|
|
6
|
+
* source file.
|
|
7
|
+
*
|
|
8
|
+
* Only this module (and the import command that calls it) imports the Pi
|
|
9
|
+
* package; the importer orchestration stays host-neutral and receives loaded
|
|
10
|
+
* sessions through the loader function so tests can inject fixtures.
|
|
11
|
+
*/
|
|
12
|
+
export interface LoadedPiSession {
|
|
13
|
+
sessionId: string;
|
|
14
|
+
sourceFile: string;
|
|
15
|
+
cwd: string | null;
|
|
16
|
+
/** Entries of the session's active/current branch, root to leaf. */
|
|
17
|
+
branch: PiSessionEntry[];
|
|
18
|
+
}
|
|
19
|
+
export declare function loadPiSessionForImport(file: string): LoadedPiSession;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
export function loadPiSessionForImport(file) {
|
|
3
|
+
const manager = SessionManager.open(file);
|
|
4
|
+
const header = manager.getHeader();
|
|
5
|
+
const branch = (manager.getBranch() ?? []);
|
|
6
|
+
return {
|
|
7
|
+
sessionId: manager.getSessionId(),
|
|
8
|
+
sourceFile: file,
|
|
9
|
+
cwd: header?.cwd ?? null,
|
|
10
|
+
branch,
|
|
11
|
+
};
|
|
12
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Plugin } from "@opencode-ai/plugin";
|
|
2
|
+
import { isStructuredSummaryPromptMessage } from "./adapters/opencode/user-prompt.js";
|
|
3
|
+
import { INTERNAL_CAPTURE_SESSION_TITLE, isInternalCaptureSessionTitle } from "./services/ai/internal-capture-sessions.js";
|
|
4
|
+
export { INTERNAL_CAPTURE_SESSION_TITLE, isInternalCaptureSessionTitle };
|
|
5
|
+
export { isStructuredSummaryPromptMessage };
|
|
6
|
+
/**
|
|
7
|
+
* Resolve the session's active agent so compaction memory injection does not
|
|
8
|
+
* reset OpenCode to the stock "general-purpose" fallback (issue #236).
|
|
9
|
+
*
|
|
10
|
+
* Preference order:
|
|
11
|
+
* 1. session.get().agent (v2 hosts)
|
|
12
|
+
* 2. Latest non-compaction user message agent
|
|
13
|
+
* 3. Latest non-compaction / non-summary assistant mode (v1) or agent (v2)
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveSessionAgent(client: unknown, sessionID: string): Promise<string | undefined>;
|
|
16
|
+
/** Least-privilege agent used only by internal structured-output sessions (issue #189). */
|
|
17
|
+
export declare function applyStructuredOutputAgentConfig(cfg: {
|
|
18
|
+
agent?: Record<string, unknown>;
|
|
19
|
+
}): void;
|
|
20
|
+
export declare function configureOpencodeHostTransport(ctx: {
|
|
21
|
+
readonly client: unknown;
|
|
22
|
+
readonly serverUrl?: string | URL;
|
|
23
|
+
}): Promise<void>;
|
|
24
|
+
export declare const OmmsPlugin: Plugin;
|