opencode-lcm 0.11.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/CHANGELOG.md +83 -0
- package/LICENSE +21 -0
- package/README.md +207 -0
- package/dist/archive-transform.d.ts +45 -0
- package/dist/archive-transform.js +81 -0
- package/dist/constants.d.ts +12 -0
- package/dist/constants.js +16 -0
- package/dist/doctor.d.ts +22 -0
- package/dist/doctor.js +44 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +306 -0
- package/dist/logging.d.ts +14 -0
- package/dist/logging.js +28 -0
- package/dist/options.d.ts +3 -0
- package/dist/options.js +217 -0
- package/dist/preview-providers.d.ts +20 -0
- package/dist/preview-providers.js +246 -0
- package/dist/privacy.d.ts +16 -0
- package/dist/privacy.js +92 -0
- package/dist/search-ranking.d.ts +12 -0
- package/dist/search-ranking.js +98 -0
- package/dist/sql-utils.d.ts +31 -0
- package/dist/sql-utils.js +80 -0
- package/dist/store-artifacts.d.ts +50 -0
- package/dist/store-artifacts.js +374 -0
- package/dist/store-retention.d.ts +39 -0
- package/dist/store-retention.js +90 -0
- package/dist/store-search.d.ts +37 -0
- package/dist/store-search.js +298 -0
- package/dist/store-snapshot.d.ts +133 -0
- package/dist/store-snapshot.js +325 -0
- package/dist/store-types.d.ts +14 -0
- package/dist/store-types.js +5 -0
- package/dist/store.d.ts +316 -0
- package/dist/store.js +3673 -0
- package/dist/types.d.ts +117 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +35 -0
- package/dist/utils.js +414 -0
- package/dist/workspace-path.d.ts +1 -0
- package/dist/workspace-path.js +15 -0
- package/dist/worktree-key.d.ts +1 -0
- package/dist/worktree-key.js +6 -0
- package/package.json +61 -0
- package/src/archive-transform.ts +147 -0
- package/src/bun-sqlite.d.ts +18 -0
- package/src/constants.ts +20 -0
- package/src/doctor.ts +83 -0
- package/src/index.ts +330 -0
- package/src/logging.ts +41 -0
- package/src/options.ts +297 -0
- package/src/preview-providers.ts +298 -0
- package/src/privacy.ts +122 -0
- package/src/search-ranking.ts +145 -0
- package/src/sql-utils.ts +107 -0
- package/src/store-artifacts.ts +666 -0
- package/src/store-retention.ts +152 -0
- package/src/store-search.ts +440 -0
- package/src/store-snapshot.ts +582 -0
- package/src/store-types.ts +16 -0
- package/src/store.ts +4926 -0
- package/src/types.ts +132 -0
- package/src/utils.ts +444 -0
- package/src/workspace-path.ts +20 -0
- package/src/worktree-key.ts +5 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { withTransaction } from './sql-utils.js';
|
|
4
|
+
import { resolveWorkspacePath } from './workspace-path.js';
|
|
5
|
+
import { normalizeWorktreeKey } from './worktree-key.js';
|
|
6
|
+
export async function exportStoreSnapshot(bindings, input) {
|
|
7
|
+
const scope = bindings.normalizeScope(input.scope) ?? 'session';
|
|
8
|
+
const sessionIDs = bindings.resolveScopeSessionIDs(scope, input.sessionID);
|
|
9
|
+
const sessions = bindings.readScopedSessionRowsSync(sessionIDs);
|
|
10
|
+
const snapshot = {
|
|
11
|
+
version: 1,
|
|
12
|
+
exportedAt: Date.now(),
|
|
13
|
+
scope,
|
|
14
|
+
sessions,
|
|
15
|
+
messages: bindings.readScopedMessageRowsSync(sessionIDs),
|
|
16
|
+
parts: bindings.readScopedPartRowsSync(sessionIDs),
|
|
17
|
+
resumes: bindings.readScopedResumeRowsSync(sessionIDs),
|
|
18
|
+
artifacts: bindings.readScopedArtifactRowsSync(sessionIDs),
|
|
19
|
+
artifact_blobs: bindings.readScopedArtifactBlobRowsSync(sessionIDs),
|
|
20
|
+
summary_nodes: bindings.readScopedSummaryRowsSync(sessionIDs),
|
|
21
|
+
summary_edges: bindings.readScopedSummaryEdgeRowsSync(sessionIDs),
|
|
22
|
+
summary_state: bindings.readScopedSummaryStateRowsSync(sessionIDs),
|
|
23
|
+
};
|
|
24
|
+
const targetPath = path.isAbsolute(input.filePath)
|
|
25
|
+
? path.normalize(input.filePath)
|
|
26
|
+
: resolveWorkspacePath(bindings.workspaceDirectory, input.filePath);
|
|
27
|
+
await writeFile(targetPath, JSON.stringify(snapshot, null, 2), 'utf8');
|
|
28
|
+
return [
|
|
29
|
+
`file=${targetPath}`,
|
|
30
|
+
`scope=${scope}`,
|
|
31
|
+
`sessions=${snapshot.sessions.length}`,
|
|
32
|
+
`worktrees=${listSnapshotWorktreeKeys(sessions).length}`,
|
|
33
|
+
`messages=${snapshot.messages.length}`,
|
|
34
|
+
`parts=${snapshot.parts.length}`,
|
|
35
|
+
`artifacts=${snapshot.artifacts.length}`,
|
|
36
|
+
`artifact_blobs=${snapshot.artifact_blobs.length}`,
|
|
37
|
+
`summary_nodes=${snapshot.summary_nodes.length}`,
|
|
38
|
+
].join('\n');
|
|
39
|
+
}
|
|
40
|
+
export async function importStoreSnapshot(bindings, input) {
|
|
41
|
+
const sourcePath = path.isAbsolute(input.filePath)
|
|
42
|
+
? path.normalize(input.filePath)
|
|
43
|
+
: resolveWorkspacePath(bindings.workspaceDirectory, input.filePath);
|
|
44
|
+
const snapshot = parseSnapshotPayload(await readFile(sourcePath, 'utf8'));
|
|
45
|
+
const db = bindings.getDb();
|
|
46
|
+
const sessionIDs = [...new Set(snapshot.sessions.map((row) => row.session_id))];
|
|
47
|
+
const worktreeMode = resolveSnapshotWorktreeMode(input.worktreeMode);
|
|
48
|
+
const collisionSessionIDs = input.mode === 'merge' ? readExistingSessionIDs(db, sessionIDs) : [];
|
|
49
|
+
if (collisionSessionIDs.length > 0) {
|
|
50
|
+
throw new Error(`Snapshot merge would overwrite existing sessions: ${collisionSessionIDs.slice(0, 5).join(', ')}. Re-run with mode=replace or import a snapshot without those session IDs.`);
|
|
51
|
+
}
|
|
52
|
+
const sourceWorktreeKeys = listSnapshotWorktreeKeys(snapshot.sessions);
|
|
53
|
+
const targetWorktreeKey = normalizeWorktreeKey(bindings.workspaceDirectory);
|
|
54
|
+
const shouldRehome = shouldRehomeImportedSessions(sourceWorktreeKeys, targetWorktreeKey, worktreeMode);
|
|
55
|
+
const importedSessions = shouldRehome
|
|
56
|
+
? snapshot.sessions.map((row) => rehomeImportedSessionRow(row, bindings.workspaceDirectory, targetWorktreeKey))
|
|
57
|
+
: snapshot.sessions;
|
|
58
|
+
withTransaction(db, 'importSnapshot', () => {
|
|
59
|
+
if (input.mode !== 'merge') {
|
|
60
|
+
for (const sessionID of sessionIDs)
|
|
61
|
+
bindings.clearSessionDataSync(sessionID);
|
|
62
|
+
}
|
|
63
|
+
const insertSession = db.prepare(`INSERT INTO sessions (session_id, title, session_directory, worktree_key, parent_session_id, root_session_id, lineage_depth, pinned, pin_reason, updated_at, compacted_at, deleted, event_count)
|
|
64
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
65
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
66
|
+
title = excluded.title,
|
|
67
|
+
session_directory = excluded.session_directory,
|
|
68
|
+
worktree_key = excluded.worktree_key,
|
|
69
|
+
parent_session_id = excluded.parent_session_id,
|
|
70
|
+
root_session_id = excluded.root_session_id,
|
|
71
|
+
lineage_depth = excluded.lineage_depth,
|
|
72
|
+
pinned = excluded.pinned,
|
|
73
|
+
pin_reason = excluded.pin_reason,
|
|
74
|
+
updated_at = excluded.updated_at,
|
|
75
|
+
compacted_at = excluded.compacted_at,
|
|
76
|
+
deleted = excluded.deleted,
|
|
77
|
+
event_count = excluded.event_count`);
|
|
78
|
+
const insertMessage = db.prepare(`INSERT OR REPLACE INTO messages (message_id, session_id, created_at, info_json) VALUES (?, ?, ?, ?)`);
|
|
79
|
+
const insertPart = db.prepare(`INSERT OR REPLACE INTO parts (part_id, session_id, message_id, sort_key, part_json) VALUES (?, ?, ?, ?, ?)`);
|
|
80
|
+
const insertResume = db.prepare(`INSERT OR REPLACE INTO resumes (session_id, note, updated_at) VALUES (?, ?, ?)`);
|
|
81
|
+
const insertBlob = db.prepare(`INSERT OR REPLACE INTO artifact_blobs (content_hash, content_text, char_count, created_at) VALUES (?, ?, ?, ?)`);
|
|
82
|
+
const insertArtifact = db.prepare(`INSERT OR REPLACE INTO artifacts (artifact_id, session_id, message_id, part_id, artifact_kind, field_name, preview_text, content_text, content_hash, metadata_json, char_count, created_at)
|
|
83
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
84
|
+
const insertNode = db.prepare(`INSERT OR REPLACE INTO summary_nodes (node_id, session_id, level, node_kind, start_index, end_index, message_ids_json, summary_text, created_at)
|
|
85
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
86
|
+
const insertEdge = db.prepare(`INSERT OR REPLACE INTO summary_edges (session_id, parent_id, child_id, child_position) VALUES (?, ?, ?, ?)`);
|
|
87
|
+
const insertState = db.prepare(`INSERT OR REPLACE INTO summary_state (session_id, archived_count, latest_message_created, archived_signature, root_node_ids_json, updated_at)
|
|
88
|
+
VALUES (?, ?, ?, ?, ?, ?)`);
|
|
89
|
+
for (const row of importedSessions) {
|
|
90
|
+
insertSession.run(row.session_id, row.title, row.session_directory, row.worktree_key, row.parent_session_id, row.root_session_id, row.lineage_depth, row.pinned ?? 0, row.pin_reason, row.updated_at, row.compacted_at, row.deleted, row.event_count);
|
|
91
|
+
}
|
|
92
|
+
for (const row of snapshot.messages)
|
|
93
|
+
insertMessage.run(row.message_id, row.session_id, row.created_at, row.info_json);
|
|
94
|
+
for (const row of snapshot.parts)
|
|
95
|
+
insertPart.run(row.part_id, row.session_id, row.message_id, row.sort_key, row.part_json);
|
|
96
|
+
for (const row of snapshot.resumes)
|
|
97
|
+
insertResume.run(row.session_id, row.note, row.updated_at);
|
|
98
|
+
for (const row of snapshot.artifact_blobs)
|
|
99
|
+
insertBlob.run(row.content_hash, row.content_text, row.char_count, row.created_at);
|
|
100
|
+
for (const row of snapshot.artifacts) {
|
|
101
|
+
insertArtifact.run(row.artifact_id, row.session_id, row.message_id, row.part_id, row.artifact_kind, row.field_name, row.preview_text, row.content_text, row.content_hash, row.metadata_json, row.char_count, row.created_at);
|
|
102
|
+
}
|
|
103
|
+
for (const row of snapshot.summary_nodes) {
|
|
104
|
+
insertNode.run(row.node_id, row.session_id, row.level, row.node_kind, row.start_index, row.end_index, row.message_ids_json, row.summary_text, row.created_at);
|
|
105
|
+
}
|
|
106
|
+
for (const row of snapshot.summary_edges)
|
|
107
|
+
insertEdge.run(row.session_id, row.parent_id, row.child_id, row.child_position);
|
|
108
|
+
for (const row of snapshot.summary_state) {
|
|
109
|
+
insertState.run(row.session_id, row.archived_count, row.latest_message_created, row.archived_signature ?? '', row.root_node_ids_json, row.updated_at);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
bindings.backfillArtifactBlobsSync();
|
|
113
|
+
bindings.refreshAllLineageSync();
|
|
114
|
+
bindings.syncAllDerivedSessionStateSync(true);
|
|
115
|
+
bindings.refreshSearchIndexesSync(sessionIDs);
|
|
116
|
+
return [
|
|
117
|
+
`file=${sourcePath}`,
|
|
118
|
+
`mode=${input.mode ?? 'replace'}`,
|
|
119
|
+
`worktree_mode=${worktreeMode}`,
|
|
120
|
+
`effective_worktree_mode=${shouldRehome ? 'current' : 'preserve'}`,
|
|
121
|
+
`sessions=${snapshot.sessions.length}`,
|
|
122
|
+
`source_worktrees=${sourceWorktreeKeys.length}`,
|
|
123
|
+
`rehomed_sessions=${shouldRehome ? importedSessions.length : 0}`,
|
|
124
|
+
`messages=${snapshot.messages.length}`,
|
|
125
|
+
`parts=${snapshot.parts.length}`,
|
|
126
|
+
`artifacts=${snapshot.artifacts.length}`,
|
|
127
|
+
`artifact_blobs=${snapshot.artifact_blobs.length}`,
|
|
128
|
+
`summary_nodes=${snapshot.summary_nodes.length}`,
|
|
129
|
+
].join('\n');
|
|
130
|
+
}
|
|
131
|
+
function parseSnapshotPayload(content) {
|
|
132
|
+
const value = JSON.parse(content);
|
|
133
|
+
const record = expectRecord(value, 'Snapshot file');
|
|
134
|
+
const version = record.version;
|
|
135
|
+
if (version !== 1) {
|
|
136
|
+
throw new Error(`Unsupported snapshot version: ${String(version)}`);
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
version: 1,
|
|
140
|
+
exportedAt: expectNumber(record.exportedAt, 'exportedAt'),
|
|
141
|
+
scope: expectString(record.scope, 'scope'),
|
|
142
|
+
sessions: expectArray(record.sessions, 'sessions', parseSessionRow),
|
|
143
|
+
messages: expectArray(record.messages, 'messages', parseMessageRow),
|
|
144
|
+
parts: expectArray(record.parts, 'parts', parsePartRow),
|
|
145
|
+
resumes: expectArray(record.resumes, 'resumes', parseResumeRow),
|
|
146
|
+
artifacts: expectArray(record.artifacts, 'artifacts', parseArtifactRow),
|
|
147
|
+
artifact_blobs: expectArray(record.artifact_blobs, 'artifact_blobs', parseArtifactBlobRow),
|
|
148
|
+
summary_nodes: expectArray(record.summary_nodes, 'summary_nodes', parseSummaryNodeRow),
|
|
149
|
+
summary_edges: expectArray(record.summary_edges, 'summary_edges', parseSummaryEdgeRow),
|
|
150
|
+
summary_state: expectArray(record.summary_state, 'summary_state', parseSummaryStateRow),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function parseSessionRow(value) {
|
|
154
|
+
const row = expectRecord(value, 'sessions[]');
|
|
155
|
+
return {
|
|
156
|
+
session_id: expectString(row.session_id, 'sessions[].session_id'),
|
|
157
|
+
title: expectNullableString(row.title, 'sessions[].title'),
|
|
158
|
+
session_directory: expectNullableString(row.session_directory, 'sessions[].session_directory'),
|
|
159
|
+
worktree_key: expectNullableString(row.worktree_key, 'sessions[].worktree_key'),
|
|
160
|
+
parent_session_id: expectNullableString(row.parent_session_id, 'sessions[].parent_session_id'),
|
|
161
|
+
root_session_id: expectNullableString(row.root_session_id, 'sessions[].root_session_id'),
|
|
162
|
+
lineage_depth: expectNullableNumber(row.lineage_depth, 'sessions[].lineage_depth'),
|
|
163
|
+
pinned: expectNullableNumber(row.pinned, 'sessions[].pinned'),
|
|
164
|
+
pin_reason: expectNullableString(row.pin_reason, 'sessions[].pin_reason'),
|
|
165
|
+
updated_at: expectNumber(row.updated_at, 'sessions[].updated_at'),
|
|
166
|
+
compacted_at: expectNullableNumber(row.compacted_at, 'sessions[].compacted_at'),
|
|
167
|
+
deleted: expectNumber(row.deleted, 'sessions[].deleted'),
|
|
168
|
+
event_count: expectNumber(row.event_count, 'sessions[].event_count'),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function parseMessageRow(value) {
|
|
172
|
+
const row = expectRecord(value, 'messages[]');
|
|
173
|
+
return {
|
|
174
|
+
message_id: expectString(row.message_id, 'messages[].message_id'),
|
|
175
|
+
session_id: expectString(row.session_id, 'messages[].session_id'),
|
|
176
|
+
created_at: expectNumber(row.created_at, 'messages[].created_at'),
|
|
177
|
+
info_json: expectString(row.info_json, 'messages[].info_json'),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function parsePartRow(value) {
|
|
181
|
+
const row = expectRecord(value, 'parts[]');
|
|
182
|
+
return {
|
|
183
|
+
part_id: expectString(row.part_id, 'parts[].part_id'),
|
|
184
|
+
session_id: expectString(row.session_id, 'parts[].session_id'),
|
|
185
|
+
message_id: expectString(row.message_id, 'parts[].message_id'),
|
|
186
|
+
sort_key: expectNumber(row.sort_key, 'parts[].sort_key'),
|
|
187
|
+
part_json: expectString(row.part_json, 'parts[].part_json'),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function parseResumeRow(value) {
|
|
191
|
+
const row = expectRecord(value, 'resumes[]');
|
|
192
|
+
return {
|
|
193
|
+
session_id: expectString(row.session_id, 'resumes[].session_id'),
|
|
194
|
+
note: expectString(row.note, 'resumes[].note'),
|
|
195
|
+
updated_at: expectNumber(row.updated_at, 'resumes[].updated_at'),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function parseArtifactRow(value) {
|
|
199
|
+
const row = expectRecord(value, 'artifacts[]');
|
|
200
|
+
return {
|
|
201
|
+
artifact_id: expectString(row.artifact_id, 'artifacts[].artifact_id'),
|
|
202
|
+
session_id: expectString(row.session_id, 'artifacts[].session_id'),
|
|
203
|
+
message_id: expectString(row.message_id, 'artifacts[].message_id'),
|
|
204
|
+
part_id: expectString(row.part_id, 'artifacts[].part_id'),
|
|
205
|
+
artifact_kind: expectString(row.artifact_kind, 'artifacts[].artifact_kind'),
|
|
206
|
+
field_name: expectString(row.field_name, 'artifacts[].field_name'),
|
|
207
|
+
preview_text: expectString(row.preview_text, 'artifacts[].preview_text'),
|
|
208
|
+
content_text: expectString(row.content_text, 'artifacts[].content_text'),
|
|
209
|
+
content_hash: expectNullableString(row.content_hash, 'artifacts[].content_hash'),
|
|
210
|
+
metadata_json: expectString(row.metadata_json, 'artifacts[].metadata_json'),
|
|
211
|
+
char_count: expectNumber(row.char_count, 'artifacts[].char_count'),
|
|
212
|
+
created_at: expectNumber(row.created_at, 'artifacts[].created_at'),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function parseArtifactBlobRow(value) {
|
|
216
|
+
const row = expectRecord(value, 'artifact_blobs[]');
|
|
217
|
+
return {
|
|
218
|
+
content_hash: expectString(row.content_hash, 'artifact_blobs[].content_hash'),
|
|
219
|
+
content_text: expectString(row.content_text, 'artifact_blobs[].content_text'),
|
|
220
|
+
char_count: expectNumber(row.char_count, 'artifact_blobs[].char_count'),
|
|
221
|
+
created_at: expectNumber(row.created_at, 'artifact_blobs[].created_at'),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function parseSummaryNodeRow(value) {
|
|
225
|
+
const row = expectRecord(value, 'summary_nodes[]');
|
|
226
|
+
return {
|
|
227
|
+
node_id: expectString(row.node_id, 'summary_nodes[].node_id'),
|
|
228
|
+
session_id: expectString(row.session_id, 'summary_nodes[].session_id'),
|
|
229
|
+
level: expectNumber(row.level, 'summary_nodes[].level'),
|
|
230
|
+
node_kind: expectString(row.node_kind, 'summary_nodes[].node_kind'),
|
|
231
|
+
start_index: expectNumber(row.start_index, 'summary_nodes[].start_index'),
|
|
232
|
+
end_index: expectNumber(row.end_index, 'summary_nodes[].end_index'),
|
|
233
|
+
message_ids_json: expectString(row.message_ids_json, 'summary_nodes[].message_ids_json'),
|
|
234
|
+
summary_text: expectString(row.summary_text, 'summary_nodes[].summary_text'),
|
|
235
|
+
created_at: expectNumber(row.created_at, 'summary_nodes[].created_at'),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function parseSummaryEdgeRow(value) {
|
|
239
|
+
const row = expectRecord(value, 'summary_edges[]');
|
|
240
|
+
return {
|
|
241
|
+
session_id: expectString(row.session_id, 'summary_edges[].session_id'),
|
|
242
|
+
parent_id: expectString(row.parent_id, 'summary_edges[].parent_id'),
|
|
243
|
+
child_id: expectString(row.child_id, 'summary_edges[].child_id'),
|
|
244
|
+
child_position: expectNumber(row.child_position, 'summary_edges[].child_position'),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function parseSummaryStateRow(value) {
|
|
248
|
+
const row = expectRecord(value, 'summary_state[]');
|
|
249
|
+
return {
|
|
250
|
+
session_id: expectString(row.session_id, 'summary_state[].session_id'),
|
|
251
|
+
archived_count: expectNumber(row.archived_count, 'summary_state[].archived_count'),
|
|
252
|
+
latest_message_created: expectNumber(row.latest_message_created, 'summary_state[].latest_message_created'),
|
|
253
|
+
archived_signature: expectNullableString(row.archived_signature, 'summary_state[].archived_signature'),
|
|
254
|
+
root_node_ids_json: expectString(row.root_node_ids_json, 'summary_state[].root_node_ids_json'),
|
|
255
|
+
updated_at: expectNumber(row.updated_at, 'summary_state[].updated_at'),
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function expectArray(value, field, parseItem) {
|
|
259
|
+
if (!Array.isArray(value))
|
|
260
|
+
throw new Error(`Snapshot field "${field}" must be an array.`);
|
|
261
|
+
return value.map((item) => parseItem(item));
|
|
262
|
+
}
|
|
263
|
+
function expectRecord(value, field) {
|
|
264
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
265
|
+
throw new Error(`${field} must be an object.`);
|
|
266
|
+
}
|
|
267
|
+
return value;
|
|
268
|
+
}
|
|
269
|
+
function expectString(value, field) {
|
|
270
|
+
if (typeof value !== 'string')
|
|
271
|
+
throw new Error(`Snapshot field "${field}" must be a string.`);
|
|
272
|
+
return value;
|
|
273
|
+
}
|
|
274
|
+
function expectNumber(value, field) {
|
|
275
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
276
|
+
throw new Error(`Snapshot field "${field}" must be a finite number.`);
|
|
277
|
+
}
|
|
278
|
+
return value;
|
|
279
|
+
}
|
|
280
|
+
function expectNullableString(value, field) {
|
|
281
|
+
if (value === null)
|
|
282
|
+
return null;
|
|
283
|
+
return expectString(value, field);
|
|
284
|
+
}
|
|
285
|
+
function expectNullableNumber(value, field) {
|
|
286
|
+
if (value === null)
|
|
287
|
+
return null;
|
|
288
|
+
return expectNumber(value, field);
|
|
289
|
+
}
|
|
290
|
+
function listSnapshotWorktreeKeys(rows) {
|
|
291
|
+
return [
|
|
292
|
+
...new Set(rows
|
|
293
|
+
.map((row) => row.worktree_key ?? normalizeWorktreeKey(row.session_directory ?? undefined))
|
|
294
|
+
.filter(Boolean)),
|
|
295
|
+
];
|
|
296
|
+
}
|
|
297
|
+
function resolveSnapshotWorktreeMode(mode) {
|
|
298
|
+
return mode === 'preserve' || mode === 'current' ? mode : 'auto';
|
|
299
|
+
}
|
|
300
|
+
function shouldRehomeImportedSessions(sourceWorktreeKeys, targetWorktreeKey, worktreeMode) {
|
|
301
|
+
if (worktreeMode === 'preserve')
|
|
302
|
+
return false;
|
|
303
|
+
if (worktreeMode === 'current')
|
|
304
|
+
return Boolean(targetWorktreeKey);
|
|
305
|
+
if (!targetWorktreeKey)
|
|
306
|
+
return false;
|
|
307
|
+
if (sourceWorktreeKeys.length === 0)
|
|
308
|
+
return true;
|
|
309
|
+
return sourceWorktreeKeys.length === 1 && sourceWorktreeKeys[0] !== targetWorktreeKey;
|
|
310
|
+
}
|
|
311
|
+
function rehomeImportedSessionRow(session, directory, worktreeKey) {
|
|
312
|
+
return {
|
|
313
|
+
...session,
|
|
314
|
+
session_directory: directory,
|
|
315
|
+
worktree_key: worktreeKey ?? null,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
function readExistingSessionIDs(db, sessionIDs) {
|
|
319
|
+
if (sessionIDs.length === 0)
|
|
320
|
+
return [];
|
|
321
|
+
const rows = db
|
|
322
|
+
.prepare(`SELECT session_id FROM sessions WHERE session_id IN (${sessionIDs.map(() => '?').join(', ')}) ORDER BY session_id ASC`)
|
|
323
|
+
.all(...sessionIDs);
|
|
324
|
+
return rows.map((row) => row.session_id);
|
|
325
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared type definitions for the SQLite store layer.
|
|
3
|
+
* These types are used across store.ts, sql-utils.ts, and related modules.
|
|
4
|
+
*/
|
|
5
|
+
export type SqlStatementLike = {
|
|
6
|
+
run(...args: unknown[]): unknown;
|
|
7
|
+
get(...args: unknown[]): unknown;
|
|
8
|
+
all(...args: unknown[]): unknown;
|
|
9
|
+
};
|
|
10
|
+
export type SqlDatabaseLike = {
|
|
11
|
+
exec(sql: string): unknown;
|
|
12
|
+
close(): void;
|
|
13
|
+
prepare(sql: string): SqlStatementLike;
|
|
14
|
+
};
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import type { Event } from '@opencode-ai/sdk';
|
|
2
|
+
import { type SnapshotWorktreeMode } from './store-snapshot.js';
|
|
3
|
+
import type { ConversationMessage, OpencodeLcmOptions, SearchResult, StoreStats } from './types.js';
|
|
4
|
+
export type SessionReadRow = {
|
|
5
|
+
session_id: string;
|
|
6
|
+
title: string | null;
|
|
7
|
+
parent_session_id: string | null;
|
|
8
|
+
root_session_id: string | null;
|
|
9
|
+
lineage_depth: number | null;
|
|
10
|
+
session_directory: string | null;
|
|
11
|
+
worktree_key: string | null;
|
|
12
|
+
pinned: number;
|
|
13
|
+
pin_reason: string | null;
|
|
14
|
+
deleted: number;
|
|
15
|
+
updated_at: number;
|
|
16
|
+
created_at: number;
|
|
17
|
+
event_count: number;
|
|
18
|
+
};
|
|
19
|
+
export type MessageReadRow = {
|
|
20
|
+
session_id: string;
|
|
21
|
+
message_id: string;
|
|
22
|
+
role: string;
|
|
23
|
+
created_at: number;
|
|
24
|
+
};
|
|
25
|
+
export type PartReadRow = {
|
|
26
|
+
session_id: string;
|
|
27
|
+
message_id: string;
|
|
28
|
+
part_id: string;
|
|
29
|
+
part_type: string;
|
|
30
|
+
sort_key: number;
|
|
31
|
+
state_json: string;
|
|
32
|
+
created_at: number;
|
|
33
|
+
};
|
|
34
|
+
export type ArtifactReadRow = {
|
|
35
|
+
artifact_id: string;
|
|
36
|
+
session_id: string;
|
|
37
|
+
message_id: string;
|
|
38
|
+
part_id: string;
|
|
39
|
+
artifact_kind: string;
|
|
40
|
+
field_name: string;
|
|
41
|
+
content_hash: string | null;
|
|
42
|
+
preview_text: string;
|
|
43
|
+
metadata_json: string;
|
|
44
|
+
char_count: number;
|
|
45
|
+
created_at: number;
|
|
46
|
+
};
|
|
47
|
+
export type ArtifactBlobReadRow = {
|
|
48
|
+
content_hash: string;
|
|
49
|
+
content_text: string;
|
|
50
|
+
char_count: number;
|
|
51
|
+
created_at: number;
|
|
52
|
+
};
|
|
53
|
+
import type { SqlDatabaseLike } from './store-types.js';
|
|
54
|
+
declare function readSchemaVersionSync(db: SqlDatabaseLike): number;
|
|
55
|
+
declare function assertSupportedSchemaVersionSync(db: SqlDatabaseLike, maxVersion: number): void;
|
|
56
|
+
declare function writeSchemaVersionSync(db: SqlDatabaseLike, version: number): void;
|
|
57
|
+
declare function readSessionHeader(db: SqlDatabaseLike, sessionID: string): SessionReadRow | undefined;
|
|
58
|
+
declare function readAllSessions(db: SqlDatabaseLike): SessionReadRow[];
|
|
59
|
+
declare function readChildSessions(db: SqlDatabaseLike, parentSessionID: string): SessionReadRow[];
|
|
60
|
+
declare function readLineageChain(db: SqlDatabaseLike, sessionID: string): SessionReadRow[];
|
|
61
|
+
declare function readMessagesForSession(db: SqlDatabaseLike, sessionID: string): MessageReadRow[];
|
|
62
|
+
declare function readArtifactsForSession(db: SqlDatabaseLike, sessionID: string): ArtifactReadRow[];
|
|
63
|
+
declare function readArtifact(db: SqlDatabaseLike, artifactID: string): ArtifactReadRow | undefined;
|
|
64
|
+
declare function readArtifactBlob(db: SqlDatabaseLike, contentHash: string): ArtifactBlobReadRow | undefined;
|
|
65
|
+
declare function readLatestSessionID(db: SqlDatabaseLike): string | undefined;
|
|
66
|
+
declare function readSessionStats(db: SqlDatabaseLike): {
|
|
67
|
+
sessionCount: number;
|
|
68
|
+
messageCount: number;
|
|
69
|
+
artifactCount: number;
|
|
70
|
+
summaryNodeCount: number;
|
|
71
|
+
blobCount: number;
|
|
72
|
+
orphanBlobCount: number;
|
|
73
|
+
orphanBlobChars: number;
|
|
74
|
+
};
|
|
75
|
+
type SqliteRuntime = 'bun' | 'node';
|
|
76
|
+
type SqliteRuntimeOptions = {
|
|
77
|
+
envOverride?: string | undefined;
|
|
78
|
+
isBunRuntime?: boolean;
|
|
79
|
+
platform?: string | undefined;
|
|
80
|
+
};
|
|
81
|
+
export declare function resolveSqliteRuntimeCandidates(options?: SqliteRuntimeOptions): SqliteRuntime[];
|
|
82
|
+
export declare function resolveSqliteRuntime(options?: SqliteRuntimeOptions): SqliteRuntime;
|
|
83
|
+
export declare class SqliteLcmStore {
|
|
84
|
+
private readonly options;
|
|
85
|
+
private static readonly deferredPartUpdateDelayMs;
|
|
86
|
+
private readonly baseDir;
|
|
87
|
+
private readonly dbPath;
|
|
88
|
+
private readonly privacy;
|
|
89
|
+
private readonly workspaceDirectory;
|
|
90
|
+
private db?;
|
|
91
|
+
private dbReadyPromise?;
|
|
92
|
+
private readonly pendingPartUpdates;
|
|
93
|
+
private pendingPartUpdateTimer?;
|
|
94
|
+
private pendingPartUpdateFlushPromise?;
|
|
95
|
+
constructor(projectDir: string, options: OpencodeLcmOptions);
|
|
96
|
+
init(): Promise<void>;
|
|
97
|
+
private prepareForRead;
|
|
98
|
+
private scheduleDeferredPartUpdateFlush;
|
|
99
|
+
private clearDeferredPartUpdateTimer;
|
|
100
|
+
private clearDeferredPartUpdatesForSession;
|
|
101
|
+
private clearDeferredPartUpdatesForMessage;
|
|
102
|
+
private clearDeferredPartUpdateForPart;
|
|
103
|
+
captureDeferred(event: Event): Promise<void>;
|
|
104
|
+
flushDeferredPartUpdates(): Promise<void>;
|
|
105
|
+
private ensureDbReady;
|
|
106
|
+
private openAndInitializeDb;
|
|
107
|
+
private deferredInitCompleted;
|
|
108
|
+
private readSchemaVersionSync;
|
|
109
|
+
private assertSupportedSchemaVersionSync;
|
|
110
|
+
private writeSchemaVersionSync;
|
|
111
|
+
private completeDeferredInit;
|
|
112
|
+
private hasPendingArtifactBlobBackfillSync;
|
|
113
|
+
private hasPendingLineageRefreshSync;
|
|
114
|
+
close(): void;
|
|
115
|
+
capture(event: Event): Promise<void>;
|
|
116
|
+
stats(): Promise<StoreStats>;
|
|
117
|
+
grep(input: {
|
|
118
|
+
query: string;
|
|
119
|
+
sessionID?: string;
|
|
120
|
+
scope?: string;
|
|
121
|
+
limit?: number;
|
|
122
|
+
}): Promise<SearchResult[]>;
|
|
123
|
+
describe(input?: {
|
|
124
|
+
sessionID?: string;
|
|
125
|
+
scope?: string;
|
|
126
|
+
}): Promise<string>;
|
|
127
|
+
doctor(input?: {
|
|
128
|
+
sessionID?: string;
|
|
129
|
+
apply?: boolean;
|
|
130
|
+
limit?: number;
|
|
131
|
+
}): Promise<string>;
|
|
132
|
+
private collectDoctorReport;
|
|
133
|
+
private hasDoctorIssues;
|
|
134
|
+
private diagnoseSummarySession;
|
|
135
|
+
private needsLineageRefresh;
|
|
136
|
+
private rebuildSummarySessionsSync;
|
|
137
|
+
private countScopedFtsRows;
|
|
138
|
+
private countScopedOrphanSummaryEdges;
|
|
139
|
+
private shouldRefreshLineageForEvent;
|
|
140
|
+
private shouldPersistSessionForEvent;
|
|
141
|
+
private shouldRecordEvent;
|
|
142
|
+
private shouldSyncDerivedLineageSubtree;
|
|
143
|
+
private shouldCleanupOrphanBlobsForEvent;
|
|
144
|
+
private captureArtifactHydrationMessageIDs;
|
|
145
|
+
private archivedMessageIDs;
|
|
146
|
+
private didArchivedMessagesChange;
|
|
147
|
+
private isArchivedMessage;
|
|
148
|
+
private shouldSyncDerivedSessionStateForEvent;
|
|
149
|
+
private syncAllDerivedSessionStateSync;
|
|
150
|
+
private syncDerivedSessionStateSync;
|
|
151
|
+
private syncDerivedLineageSubtreeSync;
|
|
152
|
+
private writeResumeSync;
|
|
153
|
+
private isManagedResumeNote;
|
|
154
|
+
private resolveRetentionPolicy;
|
|
155
|
+
private retentionCutoff;
|
|
156
|
+
private applyRetentionPruneSync;
|
|
157
|
+
private formatRetentionSessionCandidate;
|
|
158
|
+
private readSessionRetentionCandidates;
|
|
159
|
+
private countSessionRetentionCandidates;
|
|
160
|
+
private readOrphanBlobRetentionCandidates;
|
|
161
|
+
private countOrphanBlobRetentionCandidates;
|
|
162
|
+
private sumOrphanBlobRetentionChars;
|
|
163
|
+
private normalizeScope;
|
|
164
|
+
private resolveConfiguredScope;
|
|
165
|
+
private resolveScopeWorktreeKey;
|
|
166
|
+
private resolveScopeSessionIDs;
|
|
167
|
+
private readScopedSessionRowsSync;
|
|
168
|
+
private readScopedMessageRowsSync;
|
|
169
|
+
private readScopedPartRowsSync;
|
|
170
|
+
private readScopedResumeRowsSync;
|
|
171
|
+
private readScopedSessionsSync;
|
|
172
|
+
private readScopedSummaryRowsSync;
|
|
173
|
+
private readScopedSummaryEdgeRowsSync;
|
|
174
|
+
private readScopedSummaryStateRowsSync;
|
|
175
|
+
private readScopedArtifactRowsSync;
|
|
176
|
+
private readScopedArtifactBlobRowsSync;
|
|
177
|
+
lineage(sessionID?: string): Promise<string>;
|
|
178
|
+
pinSession(input: {
|
|
179
|
+
sessionID?: string;
|
|
180
|
+
reason?: string;
|
|
181
|
+
}): Promise<string>;
|
|
182
|
+
unpinSession(input: {
|
|
183
|
+
sessionID?: string;
|
|
184
|
+
}): Promise<string>;
|
|
185
|
+
artifact(input: {
|
|
186
|
+
artifactID: string;
|
|
187
|
+
chars?: number;
|
|
188
|
+
}): Promise<string>;
|
|
189
|
+
blobStats(input?: {
|
|
190
|
+
limit?: number;
|
|
191
|
+
}): Promise<string>;
|
|
192
|
+
private readOrphanArtifactBlobRowsSync;
|
|
193
|
+
private deleteOrphanArtifactBlobsSync;
|
|
194
|
+
gcBlobs(input?: {
|
|
195
|
+
apply?: boolean;
|
|
196
|
+
limit?: number;
|
|
197
|
+
}): Promise<string>;
|
|
198
|
+
private readPrunableEventTypeCountsSync;
|
|
199
|
+
private readStoreFileSizes;
|
|
200
|
+
compactEventLog(input?: {
|
|
201
|
+
apply?: boolean;
|
|
202
|
+
vacuum?: boolean;
|
|
203
|
+
limit?: number;
|
|
204
|
+
}): Promise<string>;
|
|
205
|
+
retentionReport(input?: {
|
|
206
|
+
staleSessionDays?: number;
|
|
207
|
+
deletedSessionDays?: number;
|
|
208
|
+
orphanBlobDays?: number;
|
|
209
|
+
limit?: number;
|
|
210
|
+
}): Promise<string>;
|
|
211
|
+
retentionPrune(input?: {
|
|
212
|
+
staleSessionDays?: number;
|
|
213
|
+
deletedSessionDays?: number;
|
|
214
|
+
orphanBlobDays?: number;
|
|
215
|
+
apply?: boolean;
|
|
216
|
+
limit?: number;
|
|
217
|
+
}): Promise<string>;
|
|
218
|
+
exportSnapshot(input: {
|
|
219
|
+
filePath: string;
|
|
220
|
+
sessionID?: string;
|
|
221
|
+
scope?: string;
|
|
222
|
+
}): Promise<string>;
|
|
223
|
+
importSnapshot(input: {
|
|
224
|
+
filePath: string;
|
|
225
|
+
mode?: 'replace' | 'merge';
|
|
226
|
+
worktreeMode?: SnapshotWorktreeMode;
|
|
227
|
+
}): Promise<string>;
|
|
228
|
+
resume(sessionID?: string): Promise<string>;
|
|
229
|
+
expand(input: {
|
|
230
|
+
sessionID?: string;
|
|
231
|
+
nodeID?: string;
|
|
232
|
+
query?: string;
|
|
233
|
+
depth?: number;
|
|
234
|
+
messageLimit?: number;
|
|
235
|
+
includeRaw?: boolean;
|
|
236
|
+
}): Promise<string>;
|
|
237
|
+
buildCompactionContext(sessionID: string): Promise<string | undefined>;
|
|
238
|
+
transformMessages(messages: ConversationMessage[]): Promise<boolean>;
|
|
239
|
+
systemHint(): string | undefined;
|
|
240
|
+
private buildAutomaticRetrievalContext;
|
|
241
|
+
private buildAutomaticRetrievalQuery;
|
|
242
|
+
private buildAutomaticRetrievalQueries;
|
|
243
|
+
private buildAutomaticRetrievalScopeOrder;
|
|
244
|
+
private resolveAutomaticRetrievalScopeBudget;
|
|
245
|
+
private resolveAutomaticRetrievalTargetHits;
|
|
246
|
+
private countNewAutomaticRetrievalHits;
|
|
247
|
+
private selectAutomaticRetrievalHits;
|
|
248
|
+
private isAutomaticRetrievalNoiseResult;
|
|
249
|
+
private isFreshAutomaticRetrievalResult;
|
|
250
|
+
private buildResumeNote;
|
|
251
|
+
private compactMessageInPlace;
|
|
252
|
+
private shouldIgnoreTool;
|
|
253
|
+
private summarizeMessages;
|
|
254
|
+
private listTools;
|
|
255
|
+
private buildArchivedSignature;
|
|
256
|
+
private getArchivedMessages;
|
|
257
|
+
private getSummaryRootsForSession;
|
|
258
|
+
private ensureSummaryGraphSync;
|
|
259
|
+
private canReuseSummaryGraphSync;
|
|
260
|
+
private rebuildSummaryGraphSync;
|
|
261
|
+
private readSummaryNodeSync;
|
|
262
|
+
private readSummaryChildrenSync;
|
|
263
|
+
private readArtifactBlobSync;
|
|
264
|
+
private materializeArtifactRow;
|
|
265
|
+
private readArtifactSync;
|
|
266
|
+
private readArtifactsForSessionSync;
|
|
267
|
+
private readArtifactsForMessageSync;
|
|
268
|
+
private findExpandMatches;
|
|
269
|
+
private nodeMatchesQuery;
|
|
270
|
+
private renderRawMessagesForNode;
|
|
271
|
+
private collectTargetedNodeLines;
|
|
272
|
+
private renderTargetedExpansion;
|
|
273
|
+
private renderExpandedNode;
|
|
274
|
+
private buildFtsQuery;
|
|
275
|
+
private searchDeps;
|
|
276
|
+
private artifactDeps;
|
|
277
|
+
private searchWithFts;
|
|
278
|
+
private searchByScan;
|
|
279
|
+
private replaceMessageSearchRowsSync;
|
|
280
|
+
private replaceMessageSearchRowSync;
|
|
281
|
+
private refreshSearchIndexesSync;
|
|
282
|
+
private ensureSessionColumnsSync;
|
|
283
|
+
private ensureSummaryStateColumnsSync;
|
|
284
|
+
private ensureArtifactColumnsSync;
|
|
285
|
+
private backfillArtifactBlobsSync;
|
|
286
|
+
private refreshAllLineageSync;
|
|
287
|
+
private resolveLineageSync;
|
|
288
|
+
private applyEvent;
|
|
289
|
+
private getResumeSync;
|
|
290
|
+
private readSessionHeaderSync;
|
|
291
|
+
private clearSessionDataSync;
|
|
292
|
+
private readChildSessionsSync;
|
|
293
|
+
private readLineageChainSync;
|
|
294
|
+
private readAllSessionsSync;
|
|
295
|
+
private readSessionsBatchSync;
|
|
296
|
+
private readSessionSync;
|
|
297
|
+
private prepareSessionForPersistence;
|
|
298
|
+
private sanitizeParentSessionIDSync;
|
|
299
|
+
private persistCapturedSession;
|
|
300
|
+
private persistSession;
|
|
301
|
+
private persistStoredSessionSync;
|
|
302
|
+
private upsertSessionRowSync;
|
|
303
|
+
private upsertMessageInfoSync;
|
|
304
|
+
private deleteMessageSync;
|
|
305
|
+
private replaceStoredMessageSync;
|
|
306
|
+
private externalizeMessage;
|
|
307
|
+
private formatArtifactMetadataLines;
|
|
308
|
+
private buildArtifactSearchContent;
|
|
309
|
+
private externalizeSession;
|
|
310
|
+
private writeEvent;
|
|
311
|
+
private clearSummaryGraphSync;
|
|
312
|
+
private latestSessionIDSync;
|
|
313
|
+
private migrateLegacyArtifacts;
|
|
314
|
+
private getDb;
|
|
315
|
+
}
|
|
316
|
+
export { assertSupportedSchemaVersionSync, readAllSessions, readArtifact, readArtifactBlob, readArtifactsForSession, readChildSessions, readLatestSessionID, readLineageChain, readMessagesForSession, readSchemaVersionSync, readSessionHeader, readSessionStats, writeSchemaVersionSync, };
|