opencode-mem 2.8.4 → 2.8.8
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/dist/config.d.ts.map +1 -1
- package/dist/config.js +18 -0
- package/dist/services/ai/session/ai-session-manager.d.ts.map +1 -1
- package/dist/services/ai/session/ai-session-manager.js +2 -1
- package/dist/services/sqlite/connection-manager.d.ts +3 -4
- package/dist/services/sqlite/connection-manager.d.ts.map +1 -1
- package/dist/services/sqlite/connection-manager.js +2 -72
- package/dist/services/sqlite/shard-manager.d.ts +11 -0
- package/dist/services/sqlite/shard-manager.d.ts.map +1 -1
- package/dist/services/sqlite/shard-manager.js +71 -2
- package/dist/services/sqlite/sqlite-bootstrap.d.ts +18 -0
- package/dist/services/sqlite/sqlite-bootstrap.d.ts.map +1 -0
- package/dist/services/sqlite/sqlite-bootstrap.js +158 -0
- package/dist/services/sqlite/vector-search.d.ts +15 -13
- package/dist/services/sqlite/vector-search.d.ts.map +1 -1
- package/dist/services/sqlite/vector-search.js +2 -1
- package/dist/services/user-profile/user-profile-manager.d.ts.map +1 -1
- package/dist/services/user-profile/user-profile-manager.js +2 -1
- package/dist/services/user-prompt/user-prompt-manager.d.ts.map +1 -1
- package/dist/services/user-prompt/user-prompt-manager.js +2 -1
- package/dist/services/web-server.d.ts +4 -1
- package/dist/services/web-server.d.ts.map +1 -1
- package/dist/services/web-server.js +251 -93
- package/native/darwin-arm64/libsqlite3.dylib +0 -0
- package/native/darwin-x64/libsqlite3.dylib +0 -0
- package/package.json +2 -1
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAobA,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;oBA2Bb,aAAa,GACb,kBAAkB,GAClB,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAwCT,OAAO,GACP,QAAQ;;CAEf,CAAC;AAEF,wBAAgB,YAAY,IAAI,OAAO,CAEtC"}
|
package/dist/config.js
CHANGED
|
@@ -126,6 +126,7 @@ const CONFIG_TEMPLATE = `{
|
|
|
126
126
|
// Optional: Use OpenAI-compatible API for embeddings
|
|
127
127
|
// "embeddingApiUrl": "https://api.openai.com/v1",
|
|
128
128
|
// "embeddingApiKey": "sk-...",
|
|
129
|
+
// "embeddingModel": "text-embedding-3-small", // 1536 dims, auto-detected
|
|
129
130
|
|
|
130
131
|
// ============================================
|
|
131
132
|
// Web Server Settings
|
|
@@ -302,6 +303,7 @@ function ensureConfigExists() {
|
|
|
302
303
|
ensureConfigExists();
|
|
303
304
|
function getEmbeddingDimensions(model) {
|
|
304
305
|
const dimensionMap = {
|
|
306
|
+
// Local Xenova models
|
|
305
307
|
"Xenova/nomic-embed-text-v1": 768,
|
|
306
308
|
"Xenova/nomic-embed-text-v1-unsupervised": 768,
|
|
307
309
|
"Xenova/nomic-embed-text-v1-ablated": 768,
|
|
@@ -317,6 +319,22 @@ function getEmbeddingDimensions(model) {
|
|
|
317
319
|
"Xenova/gte-small": 384,
|
|
318
320
|
"Xenova/GIST-small-Embedding-v0": 384,
|
|
319
321
|
"Xenova/text-embedding-ada-002": 1536,
|
|
322
|
+
// OpenAI API models
|
|
323
|
+
"text-embedding-3-small": 1536,
|
|
324
|
+
"text-embedding-3-large": 3072,
|
|
325
|
+
"text-embedding-ada-002": 1536,
|
|
326
|
+
// Cohere API models
|
|
327
|
+
"embed-english-v3.0": 1024,
|
|
328
|
+
"embed-multilingual-v3.0": 1024,
|
|
329
|
+
"embed-english-light-v3.0": 384,
|
|
330
|
+
"embed-multilingual-light-v3.0": 384,
|
|
331
|
+
// Google API models
|
|
332
|
+
"text-embedding-004": 768,
|
|
333
|
+
"text-multilingual-embedding-002": 768,
|
|
334
|
+
// Voyage AI models
|
|
335
|
+
"voyage-3": 1024,
|
|
336
|
+
"voyage-3-lite": 512,
|
|
337
|
+
"voyage-code-3": 1024,
|
|
320
338
|
};
|
|
321
339
|
return dimensionMap[model] || 768;
|
|
322
340
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-session-manager.d.ts","sourceRoot":"","sources":["../../../../src/services/ai/session/ai-session-manager.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,SAAS,EACT,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,SAAS,EACV,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"ai-session-manager.d.ts","sourceRoot":"","sources":["../../../../src/services/ai/session/ai-session-manager.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,SAAS,EACT,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,SAAS,EACV,MAAM,oBAAoB,CAAC;AAS5B,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,EAAE,CAAe;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;;IAS5C,OAAO,CAAC,YAAY;IAyCpB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,SAAS,GAAG,IAAI;IAYzE,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,SAAS;IA2BrD,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,OAAO,EAAE,mBAAmB,GAAG,IAAI;IA8B9F,sBAAsB,IAAI,MAAM;IAKhC,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,IAAI;IAOhE,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,GAAG,WAAW,CAAC,GAAG,IAAI;IAmB9D,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE;IAS7C,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAS5C,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAIxC,OAAO,CAAC,YAAY;IAapB,OAAO,CAAC,YAAY;CAarB;AAED,eAAO,MAAM,gBAAgB,kBAAyB,CAAC"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getDatabase } from "../../sqlite/sqlite-bootstrap.js";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { connectionManager } from "../../sqlite/connection-manager.js";
|
|
4
4
|
import { CONFIG } from "../../../config.js";
|
|
5
|
+
const Database = getDatabase();
|
|
5
6
|
const AI_SESSIONS_DB_NAME = "ai-sessions.db";
|
|
6
7
|
export class AISessionManager {
|
|
7
8
|
db;
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
declare const Database: typeof import("bun:sqlite").Database;
|
|
2
2
|
export declare class ConnectionManager {
|
|
3
3
|
private connections;
|
|
4
|
-
private sqliteConfigured;
|
|
5
|
-
private configureSqlite;
|
|
6
4
|
private initDatabase;
|
|
7
5
|
private migrateSchema;
|
|
8
|
-
getConnection(dbPath: string): Database;
|
|
6
|
+
getConnection(dbPath: string): typeof Database.prototype;
|
|
9
7
|
closeConnection(dbPath: string): void;
|
|
10
8
|
closeAll(): void;
|
|
11
9
|
checkpointAll(): void;
|
|
12
10
|
}
|
|
13
11
|
export declare const connectionManager: ConnectionManager;
|
|
12
|
+
export {};
|
|
14
13
|
//# sourceMappingURL=connection-manager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection-manager.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/connection-manager.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"connection-manager.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/connection-manager.ts"],"names":[],"mappings":"AAOA,QAAA,MAAM,QAAQ,sCAAgB,CAAC;AAE/B,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,WAAW,CAAqD;IAExE,OAAO,CAAC,YAAY;IAwBpB,OAAO,CAAC,aAAa;IAoBrB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,QAAQ,CAAC,SAAS;IAiBxD,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IASrC,QAAQ,IAAI,IAAI;IAYhB,aAAa,IAAI,IAAI;CAStB;AAED,eAAO,MAAM,iBAAiB,mBAA0B,CAAC"}
|
|
@@ -1,81 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getDatabase } from "./sqlite-bootstrap.js";
|
|
2
2
|
import * as sqliteVec from "sqlite-vec";
|
|
3
3
|
import { existsSync, mkdirSync } from "node:fs";
|
|
4
4
|
import { dirname } from "node:path";
|
|
5
5
|
import { log } from "../logger.js";
|
|
6
6
|
import { CONFIG } from "../../config.js";
|
|
7
|
+
const Database = getDatabase();
|
|
7
8
|
export class ConnectionManager {
|
|
8
9
|
connections = new Map();
|
|
9
|
-
sqliteConfigured = false;
|
|
10
|
-
configureSqlite() {
|
|
11
|
-
if (this.sqliteConfigured)
|
|
12
|
-
return;
|
|
13
|
-
if (process.platform === "darwin") {
|
|
14
|
-
const customPath = CONFIG.customSqlitePath;
|
|
15
|
-
if (customPath) {
|
|
16
|
-
if (!existsSync(customPath)) {
|
|
17
|
-
throw new Error(`Custom SQLite library not found at: ${customPath}\n` +
|
|
18
|
-
`Please verify the path or install Homebrew SQLite:\n` +
|
|
19
|
-
` brew install sqlite\n` +
|
|
20
|
-
` brew --prefix sqlite`);
|
|
21
|
-
}
|
|
22
|
-
try {
|
|
23
|
-
Database.setCustomSQLite(customPath);
|
|
24
|
-
}
|
|
25
|
-
catch (error) {
|
|
26
|
-
const errorStr = String(error);
|
|
27
|
-
if (errorStr.includes("SQLite already loaded")) {
|
|
28
|
-
}
|
|
29
|
-
else {
|
|
30
|
-
throw new Error(`Failed to load custom SQLite library: ${error}\n` + `Path: ${customPath}`);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
const commonPaths = [
|
|
36
|
-
"/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib",
|
|
37
|
-
"/usr/local/opt/sqlite/lib/libsqlite3.dylib",
|
|
38
|
-
];
|
|
39
|
-
let foundPath = null;
|
|
40
|
-
for (const path of commonPaths) {
|
|
41
|
-
if (existsSync(path)) {
|
|
42
|
-
foundPath = path;
|
|
43
|
-
break;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
if (foundPath) {
|
|
47
|
-
try {
|
|
48
|
-
Database.setCustomSQLite(foundPath);
|
|
49
|
-
}
|
|
50
|
-
catch (error) {
|
|
51
|
-
const errorStr = String(error);
|
|
52
|
-
if (errorStr.includes("SQLite already loaded")) {
|
|
53
|
-
}
|
|
54
|
-
else {
|
|
55
|
-
throw new Error(`Failed to load Homebrew SQLite: ${error}\n` + `Path: ${foundPath}`);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
else {
|
|
60
|
-
throw new Error(`macOS detected but no compatible SQLite library found.\n\n` +
|
|
61
|
-
`Apple's default SQLite does not support extension loading.\n` +
|
|
62
|
-
`Please install Homebrew SQLite and configure the path:\n\n` +
|
|
63
|
-
`1. Install Homebrew SQLite:\n` +
|
|
64
|
-
` brew install sqlite\n\n` +
|
|
65
|
-
`2. Find the library path:\n` +
|
|
66
|
-
` brew --prefix sqlite\n\n` +
|
|
67
|
-
`3. Add to ~/.config/opencode/opencode-mem.jsonc:\n` +
|
|
68
|
-
` {\n` +
|
|
69
|
-
` "customSqlitePath": "/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib"\n` +
|
|
70
|
-
` }\n\n` +
|
|
71
|
-
`Common paths:\n` +
|
|
72
|
-
` - Apple Silicon: /opt/homebrew/opt/sqlite/lib/libsqlite3.dylib\n` +
|
|
73
|
-
` - Intel Mac: /usr/local/opt/sqlite/lib/libsqlite3.dylib`);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
this.sqliteConfigured = true;
|
|
78
|
-
}
|
|
79
10
|
initDatabase(db) {
|
|
80
11
|
db.run("PRAGMA busy_timeout = 5000");
|
|
81
12
|
db.run("PRAGMA journal_mode = WAL");
|
|
@@ -118,7 +49,6 @@ export class ConnectionManager {
|
|
|
118
49
|
if (this.connections.has(dbPath)) {
|
|
119
50
|
return this.connections.get(dbPath);
|
|
120
51
|
}
|
|
121
|
-
this.configureSqlite();
|
|
122
52
|
const dir = dirname(dbPath);
|
|
123
53
|
if (!existsSync(dir)) {
|
|
124
54
|
mkdirSync(dir, { recursive: true });
|
|
@@ -10,6 +10,17 @@ export declare class ShardManager {
|
|
|
10
10
|
getAllShards(scope: "user" | "project", scopeHash: string): ShardInfo[];
|
|
11
11
|
createShard(scope: "user" | "project", scopeHash: string, shardIndex: number): ShardInfo;
|
|
12
12
|
private initShardDb;
|
|
13
|
+
/**
|
|
14
|
+
* Check if the shard DB file exists and contains the required 'memories' table.
|
|
15
|
+
* Returns false if the file is missing or the table doesn't exist.
|
|
16
|
+
*/
|
|
17
|
+
private isShardValid;
|
|
18
|
+
/**
|
|
19
|
+
* Ensure the shard DB has all required tables. If tables are missing,
|
|
20
|
+
* re-initialize them. This handles cases where the DB file exists but
|
|
21
|
+
* was corrupted or partially created.
|
|
22
|
+
*/
|
|
23
|
+
private ensureShardTables;
|
|
13
24
|
getWriteShard(scope: "user" | "project", scopeHash: string): ShardInfo;
|
|
14
25
|
private markShardReadOnly;
|
|
15
26
|
incrementVectorCount(shardId: number): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shard-manager.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/shard-manager.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"shard-manager.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/shard-manager.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAO5C,qBAAa,YAAY;IACvB,OAAO,CAAC,UAAU,CAAe;IACjC,OAAO,CAAC,YAAY,CAAS;;IAQ7B,OAAO,CAAC,cAAc;IAqBtB,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,iBAAiB;IAKzB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAsB9E,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE;IAgCvE,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS;IA2BxF,OAAO,CAAC,WAAW;IA2DnB;;;OAGG;IACH,OAAO,CAAC,YAAY;IA4BpB;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAYzB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS;IAmCtE,OAAO,CAAC,iBAAiB;IAOzB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAO3C,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAO3C,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAkBhD,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;CAwBnC;AAED,eAAO,MAAM,YAAY,cAAqB,CAAC"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getDatabase } from "./sqlite-bootstrap.js";
|
|
2
2
|
import { join, basename, isAbsolute } from "node:path";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
3
4
|
import { CONFIG } from "../../config.js";
|
|
4
5
|
import { connectionManager } from "./connection-manager.js";
|
|
5
6
|
import { log } from "../logger.js";
|
|
7
|
+
const Database = getDatabase();
|
|
6
8
|
const METADATA_DB_NAME = "metadata.db";
|
|
7
9
|
export class ShardManager {
|
|
8
10
|
metadataDb;
|
|
@@ -163,11 +165,75 @@ export class ShardManager {
|
|
|
163
165
|
db.run(`CREATE INDEX IF NOT EXISTS idx_created_at ON memories(created_at DESC)`);
|
|
164
166
|
db.run(`CREATE INDEX IF NOT EXISTS idx_is_pinned ON memories(is_pinned)`);
|
|
165
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Check if the shard DB file exists and contains the required 'memories' table.
|
|
170
|
+
* Returns false if the file is missing or the table doesn't exist.
|
|
171
|
+
*/
|
|
172
|
+
isShardValid(shard) {
|
|
173
|
+
if (!existsSync(shard.dbPath)) {
|
|
174
|
+
log("Shard DB file missing", { dbPath: shard.dbPath, shardId: shard.id });
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
const db = connectionManager.getConnection(shard.dbPath);
|
|
179
|
+
const result = db
|
|
180
|
+
.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='memories'`)
|
|
181
|
+
.get();
|
|
182
|
+
if (!result) {
|
|
183
|
+
log("Shard DB missing 'memories' table", {
|
|
184
|
+
dbPath: shard.dbPath,
|
|
185
|
+
shardId: shard.id,
|
|
186
|
+
});
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
log("Error validating shard DB", {
|
|
193
|
+
dbPath: shard.dbPath,
|
|
194
|
+
error: String(error),
|
|
195
|
+
});
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Ensure the shard DB has all required tables. If tables are missing,
|
|
201
|
+
* re-initialize them. This handles cases where the DB file exists but
|
|
202
|
+
* was corrupted or partially created.
|
|
203
|
+
*/
|
|
204
|
+
ensureShardTables(shard) {
|
|
205
|
+
try {
|
|
206
|
+
const db = connectionManager.getConnection(shard.dbPath);
|
|
207
|
+
this.initShardDb(db);
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
log("Error ensuring shard tables", {
|
|
211
|
+
dbPath: shard.dbPath,
|
|
212
|
+
error: String(error),
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
166
216
|
getWriteShard(scope, scopeHash) {
|
|
167
217
|
let shard = this.getActiveShard(scope, scopeHash);
|
|
168
218
|
if (!shard) {
|
|
169
219
|
return this.createShard(scope, scopeHash, 0);
|
|
170
220
|
}
|
|
221
|
+
// Validate that the shard DB file exists and has required tables
|
|
222
|
+
if (!this.isShardValid(shard)) {
|
|
223
|
+
log("Active shard is invalid, recreating", {
|
|
224
|
+
scope,
|
|
225
|
+
scopeHash,
|
|
226
|
+
shardIndex: shard.shardIndex,
|
|
227
|
+
dbPath: shard.dbPath,
|
|
228
|
+
});
|
|
229
|
+
// Close any cached connection to the invalid shard
|
|
230
|
+
connectionManager.closeConnection(shard.dbPath);
|
|
231
|
+
// Remove the stale metadata record
|
|
232
|
+
const deleteStmt = this.metadataDb.prepare(`DELETE FROM shards WHERE id = ?`);
|
|
233
|
+
deleteStmt.run(shard.id);
|
|
234
|
+
// Create a fresh shard with the same index
|
|
235
|
+
return this.createShard(scope, scopeHash, shard.shardIndex);
|
|
236
|
+
}
|
|
171
237
|
if (shard.vectorCount >= CONFIG.maxVectorsPerShard) {
|
|
172
238
|
this.markShardReadOnly(shard.id);
|
|
173
239
|
return this.createShard(scope, scopeHash, shard.shardIndex + 1);
|
|
@@ -222,7 +288,10 @@ export class ShardManager {
|
|
|
222
288
|
}
|
|
223
289
|
}
|
|
224
290
|
catch (error) {
|
|
225
|
-
log("Error deleting shard file", {
|
|
291
|
+
log("Error deleting shard file", {
|
|
292
|
+
dbPath: fullPath,
|
|
293
|
+
error: String(error),
|
|
294
|
+
});
|
|
226
295
|
}
|
|
227
296
|
const deleteStmt = this.metadataDb.prepare(`DELETE FROM shards WHERE id = ?`);
|
|
228
297
|
deleteStmt.run(shardId);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite Bootstrap Module
|
|
3
|
+
*
|
|
4
|
+
* This module MUST be imported before any other module that uses bun:sqlite.
|
|
5
|
+
* It ensures that setCustomSQLite() is called BEFORE the Database class is
|
|
6
|
+
* instantiated, which is required for custom SQLite paths to work on macOS.
|
|
7
|
+
*
|
|
8
|
+
* Issue: https://github.com/tickernelz/opencode-mem/issues/34
|
|
9
|
+
*
|
|
10
|
+
* Loading priority:
|
|
11
|
+
* 1. Bundled dylib (native/darwin-{arch}/libsqlite3.dylib)
|
|
12
|
+
* 2. Homebrew SQLite (auto-detected common paths)
|
|
13
|
+
* 3. Custom path from config (customSqlitePath)
|
|
14
|
+
*/
|
|
15
|
+
export declare function configureSqlite(): void;
|
|
16
|
+
export declare function getDatabase(): typeof import("bun:sqlite").Database;
|
|
17
|
+
export declare function getSqliteSource(): string | null;
|
|
18
|
+
//# sourceMappingURL=sqlite-bootstrap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-bootstrap.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/sqlite-bootstrap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA2FH,wBAAgB,eAAe,IAAI,IAAI,CAgFtC;AAED,wBAAgB,WAAW,IAAI,cAAc,YAAY,EAAE,QAAQ,CAGlE;AAED,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAE/C"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite Bootstrap Module
|
|
3
|
+
*
|
|
4
|
+
* This module MUST be imported before any other module that uses bun:sqlite.
|
|
5
|
+
* It ensures that setCustomSQLite() is called BEFORE the Database class is
|
|
6
|
+
* instantiated, which is required for custom SQLite paths to work on macOS.
|
|
7
|
+
*
|
|
8
|
+
* Issue: https://github.com/tickernelz/opencode-mem/issues/34
|
|
9
|
+
*
|
|
10
|
+
* Loading priority:
|
|
11
|
+
* 1. Bundled dylib (native/darwin-{arch}/libsqlite3.dylib)
|
|
12
|
+
* 2. Homebrew SQLite (auto-detected common paths)
|
|
13
|
+
* 3. Custom path from config (customSqlitePath)
|
|
14
|
+
*/
|
|
15
|
+
import { existsSync } from "node:fs";
|
|
16
|
+
import { homedir } from "node:os";
|
|
17
|
+
import { join, dirname } from "node:path";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
19
|
+
import { stripJsoncComments } from "../jsonc.js";
|
|
20
|
+
let Database;
|
|
21
|
+
let sqliteConfigured = false;
|
|
22
|
+
let sqliteSource = null;
|
|
23
|
+
const CONFIG_DIR = join(homedir(), ".config", "opencode");
|
|
24
|
+
const CONFIG_FILES = [
|
|
25
|
+
join(CONFIG_DIR, "opencode-mem.jsonc"),
|
|
26
|
+
join(CONFIG_DIR, "opencode-mem.json"),
|
|
27
|
+
];
|
|
28
|
+
function getBundledSqlitePath() {
|
|
29
|
+
if (process.platform !== "darwin")
|
|
30
|
+
return null;
|
|
31
|
+
const arch = process.arch;
|
|
32
|
+
if (arch !== "x64" && arch !== "arm64")
|
|
33
|
+
return null;
|
|
34
|
+
try {
|
|
35
|
+
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
36
|
+
const bundledPath = join(currentDir, "..", "..", "..", "native", `darwin-${arch}`, "libsqlite3.dylib");
|
|
37
|
+
if (existsSync(bundledPath)) {
|
|
38
|
+
return bundledPath;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch { }
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
function loadSqliteConfig() {
|
|
45
|
+
for (const path of CONFIG_FILES) {
|
|
46
|
+
if (existsSync(path)) {
|
|
47
|
+
try {
|
|
48
|
+
const content = require("node:fs").readFileSync(path, "utf-8");
|
|
49
|
+
const json = stripJsoncComments(content);
|
|
50
|
+
return JSON.parse(json);
|
|
51
|
+
}
|
|
52
|
+
catch { }
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
57
|
+
function expandPath(path) {
|
|
58
|
+
if (path.startsWith("~/")) {
|
|
59
|
+
return join(homedir(), path.slice(2));
|
|
60
|
+
}
|
|
61
|
+
if (path === "~") {
|
|
62
|
+
return homedir();
|
|
63
|
+
}
|
|
64
|
+
return path;
|
|
65
|
+
}
|
|
66
|
+
function getHomebrewSqlitePath() {
|
|
67
|
+
const arch = process.arch;
|
|
68
|
+
const paths = arch === "arm64"
|
|
69
|
+
? ["/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib"]
|
|
70
|
+
: [
|
|
71
|
+
"/usr/local/opt/sqlite/lib/libsqlite3.dylib",
|
|
72
|
+
"/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib",
|
|
73
|
+
];
|
|
74
|
+
for (const path of paths) {
|
|
75
|
+
if (existsSync(path)) {
|
|
76
|
+
return path;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
export function configureSqlite() {
|
|
82
|
+
if (sqliteConfigured)
|
|
83
|
+
return;
|
|
84
|
+
const bunSqlite = require("bun:sqlite");
|
|
85
|
+
Database = bunSqlite.Database;
|
|
86
|
+
if (process.platform !== "darwin") {
|
|
87
|
+
sqliteConfigured = true;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const config = loadSqliteConfig();
|
|
91
|
+
const customPath = config.customSqlitePath ? expandPath(config.customSqlitePath) : undefined;
|
|
92
|
+
const trySetCustomSQLite = (path, source) => {
|
|
93
|
+
try {
|
|
94
|
+
Database.setCustomSQLite(path);
|
|
95
|
+
sqliteSource = source;
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
const errorStr = String(error);
|
|
100
|
+
if (errorStr.includes("SQLite already loaded")) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
// Priority 1: Bundled dylib
|
|
107
|
+
const bundledPath = getBundledSqlitePath();
|
|
108
|
+
if (bundledPath) {
|
|
109
|
+
if (trySetCustomSQLite(bundledPath, "bundled")) {
|
|
110
|
+
sqliteConfigured = true;
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Priority 2: Custom path from config
|
|
115
|
+
if (customPath) {
|
|
116
|
+
if (!existsSync(customPath)) {
|
|
117
|
+
throw new Error(`Custom SQLite library not found at: ${customPath}\n` +
|
|
118
|
+
`Please verify the path or install Homebrew SQLite:\n` +
|
|
119
|
+
` brew install sqlite\n` +
|
|
120
|
+
` brew --prefix sqlite`);
|
|
121
|
+
}
|
|
122
|
+
if (trySetCustomSQLite(customPath, "custom")) {
|
|
123
|
+
sqliteConfigured = true;
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Priority 3: Homebrew SQLite
|
|
128
|
+
const homebrewPath = getHomebrewSqlitePath();
|
|
129
|
+
if (homebrewPath) {
|
|
130
|
+
if (trySetCustomSQLite(homebrewPath, "homebrew")) {
|
|
131
|
+
sqliteConfigured = true;
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// No compatible SQLite found
|
|
136
|
+
throw new Error(`macOS detected but no compatible SQLite library found.\n\n` +
|
|
137
|
+
`Apple's default SQLite does not support extension loading.\n` +
|
|
138
|
+
`Solutions:\n\n` +
|
|
139
|
+
`Option 1 - Install Homebrew SQLite (recommended):\n` +
|
|
140
|
+
` brew install sqlite\n\n` +
|
|
141
|
+
`Option 2 - Download manually and configure:\n` +
|
|
142
|
+
` 1. Download SQLite with extension support\n` +
|
|
143
|
+
` 2. Add to ~/.config/opencode/opencode-mem.jsonc:\n` +
|
|
144
|
+
` {\n` +
|
|
145
|
+
` "customSqlitePath": "/path/to/libsqlite3.dylib"\n` +
|
|
146
|
+
` }\n\n` +
|
|
147
|
+
`Common Homebrew paths:\n` +
|
|
148
|
+
` - Apple Silicon: /opt/homebrew/opt/sqlite/lib/libsqlite3.dylib\n` +
|
|
149
|
+
` - Intel Mac: /usr/local/opt/sqlite/lib/libsqlite3.dylib`);
|
|
150
|
+
}
|
|
151
|
+
export function getDatabase() {
|
|
152
|
+
configureSqlite();
|
|
153
|
+
return Database;
|
|
154
|
+
}
|
|
155
|
+
export function getSqliteSource() {
|
|
156
|
+
return sqliteSource;
|
|
157
|
+
}
|
|
158
|
+
configureSqlite();
|
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
import { Database } from "bun:sqlite";
|
|
2
1
|
import type { MemoryRecord, SearchResult, ShardInfo } from "./types.js";
|
|
2
|
+
declare const Database: typeof import("bun:sqlite").Database;
|
|
3
|
+
type DatabaseType = typeof Database.prototype;
|
|
3
4
|
export declare class VectorSearch {
|
|
4
|
-
insertVector(db:
|
|
5
|
+
insertVector(db: DatabaseType, record: MemoryRecord): void;
|
|
5
6
|
searchInShard(shard: ShardInfo, queryVector: Float32Array, containerTag: string, limit: number, queryText?: string): SearchResult[];
|
|
6
7
|
searchAcrossShards(shards: ShardInfo[], queryVector: Float32Array, containerTag: string, limit: number, similarityThreshold: number, queryText?: string): Promise<SearchResult[]>;
|
|
7
|
-
deleteVector(db:
|
|
8
|
-
updateVector(db:
|
|
9
|
-
listMemories(db:
|
|
10
|
-
getAllMemories(db:
|
|
11
|
-
getMemoryById(db:
|
|
12
|
-
getMemoriesBySessionID(db:
|
|
13
|
-
countVectors(db:
|
|
14
|
-
countAllVectors(db:
|
|
15
|
-
getDistinctTags(db:
|
|
16
|
-
pinMemory(db:
|
|
17
|
-
unpinMemory(db:
|
|
8
|
+
deleteVector(db: DatabaseType, memoryId: string): void;
|
|
9
|
+
updateVector(db: DatabaseType, memoryId: string, vector: Float32Array, tagsVector?: Float32Array): void;
|
|
10
|
+
listMemories(db: DatabaseType, containerTag: string, limit: number): any[];
|
|
11
|
+
getAllMemories(db: DatabaseType): any[];
|
|
12
|
+
getMemoryById(db: DatabaseType, memoryId: string): any | null;
|
|
13
|
+
getMemoriesBySessionID(db: DatabaseType, sessionID: string): any[];
|
|
14
|
+
countVectors(db: DatabaseType, containerTag: string): number;
|
|
15
|
+
countAllVectors(db: DatabaseType): number;
|
|
16
|
+
getDistinctTags(db: DatabaseType): any[];
|
|
17
|
+
pinMemory(db: DatabaseType, memoryId: string): void;
|
|
18
|
+
unpinMemory(db: DatabaseType, memoryId: string): void;
|
|
18
19
|
}
|
|
19
20
|
export declare const vectorSearch: VectorSearch;
|
|
21
|
+
export {};
|
|
20
22
|
//# sourceMappingURL=vector-search.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vector-search.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/vector-search.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"vector-search.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/vector-search.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAExE,QAAA,MAAM,QAAQ,sCAAgB,CAAC;AAC/B,KAAK,YAAY,GAAG,OAAO,QAAQ,CAAC,SAAS,CAAC;AAE9C,qBAAa,YAAY;IACvB,YAAY,CAAC,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI;IA0C1D,aAAa,CACX,KAAK,EAAE,SAAS,EAChB,WAAW,EAAE,YAAY,EACzB,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,EACb,SAAS,CAAC,EAAE,MAAM,GACjB,YAAY,EAAE;IA0FX,kBAAkB,CACtB,MAAM,EAAE,SAAS,EAAE,EACnB,WAAW,EAAE,YAAY,EACzB,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,EACb,mBAAmB,EAAE,MAAM,EAC3B,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,YAAY,EAAE,CAAC;IAiB1B,YAAY,CAAC,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAMtD,YAAY,CACV,EAAE,EAAE,YAAY,EAChB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,YAAY,EACpB,UAAU,CAAC,EAAE,YAAY,GACxB,IAAI;IAkBP,YAAY,CAAC,EAAE,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,GAAG,EAAE;IAW1E,cAAc,CAAC,EAAE,EAAE,YAAY,GAAG,GAAG,EAAE;IAKvC,aAAa,CAAC,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI;IAK7D,sBAAsB,CAAC,EAAE,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,GAAG,GAAG,EAAE;IAgBlE,YAAY,CAAC,EAAE,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM;IAM5D,eAAe,CAAC,EAAE,EAAE,YAAY,GAAG,MAAM;IAMzC,eAAe,CAAC,EAAE,EAAE,YAAY,GAAG,GAAG,EAAE;IAexC,SAAS,CAAC,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAKnD,WAAW,CAAC,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;CAItD;AAED,eAAO,MAAM,YAAY,cAAqB,CAAC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getDatabase } from "./sqlite-bootstrap.js";
|
|
2
2
|
import { connectionManager } from "./connection-manager.js";
|
|
3
3
|
import { log } from "../logger.js";
|
|
4
|
+
const Database = getDatabase();
|
|
4
5
|
export class VectorSearch {
|
|
5
6
|
insertVector(db, record) {
|
|
6
7
|
const insertMemory = db.prepare(`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-profile-manager.d.ts","sourceRoot":"","sources":["../../../src/services/user-profile/user-profile-manager.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"user-profile-manager.d.ts","sourceRoot":"","sources":["../../../src/services/user-profile/user-profile-manager.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAQrF,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,EAAE,CAAe;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;;IAQhC,OAAO,CAAC,YAAY;IA0CpB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAapD,aAAa,CACX,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,eAAe,EAC5B,eAAe,EAAE,MAAM,GACtB,MAAM;IAoCT,aAAa,CACX,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,eAAe,EAC5B,yBAAyB,EAAE,MAAM,EACjC,aAAa,EAAE,MAAM,GACpB,IAAI;IAmCP,OAAO,CAAC,YAAY;IAqBpB,OAAO,CAAC,oBAAoB;IAiB5B,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,oBAAoB,EAAE;IAYnF,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IA2B7C,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAKtC,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAOrD,oBAAoB,IAAI,WAAW,EAAE;IAMrC,OAAO,CAAC,YAAY;IAgBpB,OAAO,CAAC,cAAc;IAYtB,gBAAgB,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,eAAe;IA0F/F,OAAO,CAAC,WAAW;CAWpB;AAED,eAAO,MAAM,kBAAkB,oBAA2B,CAAC"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getDatabase } from "../sqlite/sqlite-bootstrap.js";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { connectionManager } from "../sqlite/connection-manager.js";
|
|
4
4
|
import { CONFIG } from "../../config.js";
|
|
5
5
|
import { safeArray, safeObject } from "./profile-utils.js";
|
|
6
|
+
const Database = getDatabase();
|
|
6
7
|
const USER_PROFILES_DB_NAME = "user-profiles.db";
|
|
7
8
|
export class UserProfileManager {
|
|
8
9
|
db;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-prompt-manager.d.ts","sourceRoot":"","sources":["../../../src/services/user-prompt/user-prompt-manager.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"user-prompt-manager.d.ts","sourceRoot":"","sources":["../../../src/services/user-prompt/user-prompt-manager.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,EAAE,CAAe;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;;IAQhC,OAAO,CAAC,YAAY;IAiCpB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM;IAa9F,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IAc7D,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAKpC,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAKtC,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAQtC,sBAAsB,IAAI,MAAM;IAMhC,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,EAAE;IAYjD,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI;IAUjD,8BAA8B,IAAI,MAAM;IAQxC,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,EAAE;IAYtD,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAKlD,kCAAkC,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI;IAU7D,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,EAAE,CAAA;KAAE;IAiBpF,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAK5D,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IAOlD,kBAAkB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,UAAU,EAAE;IAgBtD,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,UAAU,EAAE;IAiBpF,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,UAAU,EAAE;IAQ5C,OAAO,CAAC,WAAW;CAapB;AAED,eAAO,MAAM,iBAAiB,mBAA0B,CAAC"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getDatabase } from "../sqlite/sqlite-bootstrap.js";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { connectionManager } from "../sqlite/connection-manager.js";
|
|
4
4
|
import { CONFIG } from "../../config.js";
|
|
5
|
+
const Database = getDatabase();
|
|
5
6
|
const USER_PROMPTS_DB_NAME = "user-prompts.db";
|
|
6
7
|
export class UserPromptManager {
|
|
7
8
|
db;
|
|
@@ -4,7 +4,7 @@ interface WebServerConfig {
|
|
|
4
4
|
enabled: boolean;
|
|
5
5
|
}
|
|
6
6
|
export declare class WebServer {
|
|
7
|
-
private
|
|
7
|
+
private server;
|
|
8
8
|
private config;
|
|
9
9
|
private isOwner;
|
|
10
10
|
private startPromise;
|
|
@@ -22,6 +22,9 @@ export declare class WebServer {
|
|
|
22
22
|
isServerOwner(): boolean;
|
|
23
23
|
getUrl(): string;
|
|
24
24
|
checkServerAvailable(): Promise<boolean>;
|
|
25
|
+
private handleRequest;
|
|
26
|
+
private serveStaticFile;
|
|
27
|
+
private jsonResponse;
|
|
25
28
|
}
|
|
26
29
|
export declare function startWebServer(config: WebServerConfig): Promise<WebServer>;
|
|
27
30
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-server.d.ts","sourceRoot":"","sources":["../../src/services/web-server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"web-server.d.ts","sourceRoot":"","sources":["../../src/services/web-server.ts"],"names":[],"mappings":"AAiCA,UAAU,eAAe;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAA6C;IAC3D,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,mBAAmB,CAA+B;IAC1D,OAAO,CAAC,kBAAkB,CAAsC;gBAEpD,MAAM,EAAE,eAAe;IAInC,qBAAqB,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAIpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YASd,MAAM;IAgCpB,OAAO,CAAC,oBAAoB;IAe5B,OAAO,CAAC,mBAAmB;YAOb,eAAe;IA+BvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAY3B,SAAS,IAAI,OAAO;IAIpB,aAAa,IAAI,OAAO;IAIxB,MAAM,IAAI,MAAM;IAIV,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC;YAchC,aAAa;IAmN3B,OAAO,CAAC,eAAe;IA4BvB,OAAO,CAAC,YAAY;CAWrB;AAED,wBAAsB,cAAc,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,SAAS,CAAC,CAIhF"}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
1
2
|
import { join, dirname } from "node:path";
|
|
2
3
|
import { fileURLToPath } from "node:url";
|
|
3
4
|
import { log } from "./logger.js";
|
|
5
|
+
import { handleListTags, handleListMemories, handleAddMemory, handleDeleteMemory, handleBulkDelete, handleUpdateMemory, handleSearch, handleStats, handlePinMemory, handleUnpinMemory, handleRunCleanup, handleRunDeduplication, handleDetectMigration, handleRunMigration, handleDetectTagMigration, handleRunTagMigrationBatch, handleGetTagMigrationProgress, handleDeletePrompt, handleBulkDeletePrompts, handleGetUserProfile, handleGetProfileChangelog, handleGetProfileSnapshot, handleRefreshProfile, } from "./api-handlers.js";
|
|
4
6
|
const __filename = fileURLToPath(import.meta.url);
|
|
5
7
|
const __dirname = dirname(__filename);
|
|
6
8
|
export class WebServer {
|
|
7
|
-
|
|
9
|
+
server = null;
|
|
8
10
|
config;
|
|
9
11
|
isOwner = false;
|
|
10
12
|
startPromise = null;
|
|
@@ -28,70 +30,28 @@ export class WebServer {
|
|
|
28
30
|
return;
|
|
29
31
|
}
|
|
30
32
|
try {
|
|
31
|
-
|
|
32
|
-
this.worker = new Worker(workerPath);
|
|
33
|
-
const startedPromise = new Promise((resolve, reject) => {
|
|
34
|
-
const timeout = setTimeout(() => {
|
|
35
|
-
reject(new Error("Worker start timeout"));
|
|
36
|
-
}, 10000);
|
|
37
|
-
this.worker.onmessage = (event) => {
|
|
38
|
-
clearTimeout(timeout);
|
|
39
|
-
const response = event.data;
|
|
40
|
-
if (response.type === "started") {
|
|
41
|
-
this.isOwner = true;
|
|
42
|
-
resolve();
|
|
43
|
-
}
|
|
44
|
-
else if (response.type === "error") {
|
|
45
|
-
const errorMsg = response.error || "Unknown error";
|
|
46
|
-
if (errorMsg.includes("EADDRINUSE") ||
|
|
47
|
-
errorMsg.includes("address already in use") ||
|
|
48
|
-
/^Error: Failed to start server\. Is port \d+ in use\?$/.test(errorMsg)) {
|
|
49
|
-
this.isOwner = false;
|
|
50
|
-
resolve();
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
log("Web server worker error", { error: errorMsg });
|
|
54
|
-
reject(new Error(errorMsg));
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
this.worker.onerror = (error) => {
|
|
59
|
-
clearTimeout(timeout);
|
|
60
|
-
const errorDetails = {
|
|
61
|
-
message: error.message || "Unknown error",
|
|
62
|
-
filename: error.filename || "unknown",
|
|
63
|
-
lineno: error.lineno || 0,
|
|
64
|
-
colno: error.colno || 0,
|
|
65
|
-
error: error.error ? String(error.error) : "no error object",
|
|
66
|
-
type: error.type || "error",
|
|
67
|
-
};
|
|
68
|
-
log("Web server worker error (detailed)", errorDetails);
|
|
69
|
-
const errorMsg = error.message
|
|
70
|
-
? `${error.message} (at ${error.filename}:${error.lineno}:${error.colno})`
|
|
71
|
-
: error.error
|
|
72
|
-
? String(error.error)
|
|
73
|
-
: `Worker failed: ${JSON.stringify(errorDetails)}`;
|
|
74
|
-
reject(new Error(errorMsg));
|
|
75
|
-
};
|
|
76
|
-
});
|
|
77
|
-
this.worker.postMessage({
|
|
78
|
-
type: "start",
|
|
33
|
+
this.server = Bun.serve({
|
|
79
34
|
port: this.config.port,
|
|
80
|
-
|
|
35
|
+
hostname: this.config.host,
|
|
36
|
+
fetch: this.handleRequest.bind(this),
|
|
81
37
|
});
|
|
82
|
-
|
|
83
|
-
if (!this.isOwner) {
|
|
84
|
-
this.startHealthCheckLoop();
|
|
85
|
-
}
|
|
38
|
+
this.isOwner = true;
|
|
86
39
|
}
|
|
87
40
|
catch (error) {
|
|
88
|
-
|
|
89
|
-
if (
|
|
90
|
-
|
|
91
|
-
|
|
41
|
+
const errorMsg = String(error);
|
|
42
|
+
if (errorMsg.includes("EADDRINUSE") ||
|
|
43
|
+
errorMsg.includes("address already in use") ||
|
|
44
|
+
/Failed to start server.*Is port \d+ in use/.test(errorMsg)) {
|
|
45
|
+
this.isOwner = false;
|
|
46
|
+
this.server = null;
|
|
47
|
+
this.startHealthCheckLoop();
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
this.isOwner = false;
|
|
51
|
+
this.server = null;
|
|
52
|
+
log("Web server failed to start", { error: errorMsg });
|
|
53
|
+
throw error;
|
|
92
54
|
}
|
|
93
|
-
log("Web server failed to start", { error: String(error) });
|
|
94
|
-
throw error;
|
|
95
55
|
}
|
|
96
56
|
}
|
|
97
57
|
startHealthCheckLoop() {
|
|
@@ -121,15 +81,18 @@ export class WebServer {
|
|
|
121
81
|
return;
|
|
122
82
|
}
|
|
123
83
|
try {
|
|
84
|
+
// Reset startPromise so _start() can run again
|
|
85
|
+
this.startPromise = null;
|
|
124
86
|
await this._start();
|
|
125
|
-
this.isOwner
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
87
|
+
if (this.isOwner) {
|
|
88
|
+
log("Web server takeover successful", { port: this.config.port });
|
|
89
|
+
if (this.onTakeoverCallback) {
|
|
90
|
+
try {
|
|
91
|
+
await this.onTakeoverCallback();
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
log("Takeover callback error", { error: String(error) });
|
|
95
|
+
}
|
|
133
96
|
}
|
|
134
97
|
}
|
|
135
98
|
}
|
|
@@ -139,35 +102,15 @@ export class WebServer {
|
|
|
139
102
|
}
|
|
140
103
|
async stop() {
|
|
141
104
|
this.stopHealthCheckLoop();
|
|
142
|
-
if (!this.isOwner || !this.
|
|
105
|
+
if (!this.isOwner || !this.server) {
|
|
143
106
|
return;
|
|
144
107
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
this.worker.terminate();
|
|
149
|
-
this.worker = null;
|
|
150
|
-
}
|
|
151
|
-
resolve();
|
|
152
|
-
}, 5000);
|
|
153
|
-
this.worker.onmessage = (event) => {
|
|
154
|
-
clearTimeout(timeout);
|
|
155
|
-
const response = event.data;
|
|
156
|
-
if (response.type === "stopped") {
|
|
157
|
-
if (this.worker) {
|
|
158
|
-
this.worker.terminate();
|
|
159
|
-
this.worker = null;
|
|
160
|
-
}
|
|
161
|
-
resolve();
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
this.worker.postMessage({
|
|
165
|
-
type: "stop",
|
|
166
|
-
});
|
|
167
|
-
});
|
|
108
|
+
this.server.stop();
|
|
109
|
+
this.server = null;
|
|
110
|
+
this.isOwner = false;
|
|
168
111
|
}
|
|
169
112
|
isRunning() {
|
|
170
|
-
return this.
|
|
113
|
+
return this.server !== null;
|
|
171
114
|
}
|
|
172
115
|
isServerOwner() {
|
|
173
116
|
return this.isOwner;
|
|
@@ -187,6 +130,221 @@ export class WebServer {
|
|
|
187
130
|
return false;
|
|
188
131
|
}
|
|
189
132
|
}
|
|
133
|
+
// --- HTTP request handling (inlined from web-server-worker.ts) ---
|
|
134
|
+
async handleRequest(req) {
|
|
135
|
+
const url = new URL(req.url);
|
|
136
|
+
const path = url.pathname;
|
|
137
|
+
const method = req.method;
|
|
138
|
+
try {
|
|
139
|
+
if (path === "/" || path === "/index.html") {
|
|
140
|
+
return this.serveStaticFile("index.html", "text/html");
|
|
141
|
+
}
|
|
142
|
+
if (path === "/styles.css") {
|
|
143
|
+
return this.serveStaticFile("styles.css", "text/css");
|
|
144
|
+
}
|
|
145
|
+
if (path === "/app.js") {
|
|
146
|
+
return this.serveStaticFile("app.js", "application/javascript");
|
|
147
|
+
}
|
|
148
|
+
if (path === "/favicon.ico") {
|
|
149
|
+
return this.serveStaticFile("favicon.ico", "image/x-icon");
|
|
150
|
+
}
|
|
151
|
+
if (path === "/api/tags" && method === "GET") {
|
|
152
|
+
const result = await handleListTags();
|
|
153
|
+
return this.jsonResponse(result);
|
|
154
|
+
}
|
|
155
|
+
if (path === "/api/memories" && method === "GET") {
|
|
156
|
+
const tag = url.searchParams.get("tag") || undefined;
|
|
157
|
+
const page = parseInt(url.searchParams.get("page") || "1");
|
|
158
|
+
const pageSize = parseInt(url.searchParams.get("pageSize") || "20");
|
|
159
|
+
const includePrompts = url.searchParams.get("includePrompts") !== "false";
|
|
160
|
+
const result = await handleListMemories(tag, page, pageSize, includePrompts);
|
|
161
|
+
return this.jsonResponse(result);
|
|
162
|
+
}
|
|
163
|
+
if (path === "/api/memories" && method === "POST") {
|
|
164
|
+
const body = (await req.json());
|
|
165
|
+
const result = await handleAddMemory(body);
|
|
166
|
+
return this.jsonResponse(result);
|
|
167
|
+
}
|
|
168
|
+
if (path.startsWith("/api/memories/") && method === "DELETE") {
|
|
169
|
+
const parts = path.split("/");
|
|
170
|
+
const id = parts[3];
|
|
171
|
+
if (!id || id === "bulk-delete") {
|
|
172
|
+
return this.jsonResponse({ success: false, error: "Invalid ID" });
|
|
173
|
+
}
|
|
174
|
+
const cascade = url.searchParams.get("cascade") === "true";
|
|
175
|
+
const result = await handleDeleteMemory(id, cascade);
|
|
176
|
+
return this.jsonResponse(result);
|
|
177
|
+
}
|
|
178
|
+
if (path.startsWith("/api/memories/") && method === "PUT") {
|
|
179
|
+
const id = path.split("/").pop();
|
|
180
|
+
if (!id) {
|
|
181
|
+
return this.jsonResponse({ success: false, error: "Invalid ID" });
|
|
182
|
+
}
|
|
183
|
+
const body = (await req.json());
|
|
184
|
+
const result = await handleUpdateMemory(id, body);
|
|
185
|
+
return this.jsonResponse(result);
|
|
186
|
+
}
|
|
187
|
+
if (path === "/api/memories/bulk-delete" && method === "POST") {
|
|
188
|
+
const body = (await req.json());
|
|
189
|
+
const cascade = body.cascade !== false;
|
|
190
|
+
const result = await handleBulkDelete(body.ids || [], cascade);
|
|
191
|
+
return this.jsonResponse(result);
|
|
192
|
+
}
|
|
193
|
+
if (path === "/api/search" && method === "GET") {
|
|
194
|
+
const query = url.searchParams.get("q");
|
|
195
|
+
const tag = url.searchParams.get("tag") || undefined;
|
|
196
|
+
const page = parseInt(url.searchParams.get("page") || "1");
|
|
197
|
+
const pageSize = parseInt(url.searchParams.get("pageSize") || "20");
|
|
198
|
+
if (!query) {
|
|
199
|
+
return this.jsonResponse({ success: false, error: "query parameter required" });
|
|
200
|
+
}
|
|
201
|
+
const result = await handleSearch(query, tag, page, pageSize);
|
|
202
|
+
return this.jsonResponse(result);
|
|
203
|
+
}
|
|
204
|
+
if (path === "/api/stats" && method === "GET") {
|
|
205
|
+
const result = await handleStats();
|
|
206
|
+
return this.jsonResponse(result);
|
|
207
|
+
}
|
|
208
|
+
if (path.match(/^\/api\/memories\/[^/]+\/pin$/) && method === "POST") {
|
|
209
|
+
const id = path.split("/")[3];
|
|
210
|
+
if (!id) {
|
|
211
|
+
return this.jsonResponse({ success: false, error: "Invalid ID" });
|
|
212
|
+
}
|
|
213
|
+
const result = await handlePinMemory(id);
|
|
214
|
+
return this.jsonResponse(result);
|
|
215
|
+
}
|
|
216
|
+
if (path.match(/^\/api\/memories\/[^/]+\/unpin$/) && method === "POST") {
|
|
217
|
+
const id = path.split("/")[3];
|
|
218
|
+
if (!id) {
|
|
219
|
+
return this.jsonResponse({ success: false, error: "Invalid ID" });
|
|
220
|
+
}
|
|
221
|
+
const result = await handleUnpinMemory(id);
|
|
222
|
+
return this.jsonResponse(result);
|
|
223
|
+
}
|
|
224
|
+
if (path === "/api/cleanup" && method === "POST") {
|
|
225
|
+
const result = await handleRunCleanup();
|
|
226
|
+
return this.jsonResponse(result);
|
|
227
|
+
}
|
|
228
|
+
if (path === "/api/deduplicate" && method === "POST") {
|
|
229
|
+
const result = await handleRunDeduplication();
|
|
230
|
+
return this.jsonResponse(result);
|
|
231
|
+
}
|
|
232
|
+
if (path === "/api/migration/detect" && method === "GET") {
|
|
233
|
+
const result = await handleDetectMigration();
|
|
234
|
+
return this.jsonResponse(result);
|
|
235
|
+
}
|
|
236
|
+
if (path === "/api/migration/tags/detect" && method === "GET") {
|
|
237
|
+
const result = await handleDetectTagMigration();
|
|
238
|
+
return this.jsonResponse(result);
|
|
239
|
+
}
|
|
240
|
+
if (path === "/api/migration/tags/run-batch" && method === "POST") {
|
|
241
|
+
const body = (await req.json());
|
|
242
|
+
const batchSize = body?.batchSize || 5;
|
|
243
|
+
const result = await handleRunTagMigrationBatch(batchSize);
|
|
244
|
+
return this.jsonResponse(result);
|
|
245
|
+
}
|
|
246
|
+
if (path === "/api/migration/tags/progress" && method === "GET") {
|
|
247
|
+
const result = await handleGetTagMigrationProgress();
|
|
248
|
+
return this.jsonResponse(result);
|
|
249
|
+
}
|
|
250
|
+
if (path === "/api/migration/run" && method === "POST") {
|
|
251
|
+
const body = (await req.json());
|
|
252
|
+
const strategy = body.strategy || "fresh-start";
|
|
253
|
+
if (strategy !== "fresh-start" && strategy !== "re-embed") {
|
|
254
|
+
return this.jsonResponse({ success: false, error: "Invalid strategy" });
|
|
255
|
+
}
|
|
256
|
+
const result = await handleRunMigration(strategy);
|
|
257
|
+
return this.jsonResponse(result);
|
|
258
|
+
}
|
|
259
|
+
if (path.startsWith("/api/prompts/") && method === "DELETE") {
|
|
260
|
+
const parts = path.split("/");
|
|
261
|
+
const id = parts[3];
|
|
262
|
+
if (!id || id === "bulk-delete") {
|
|
263
|
+
return this.jsonResponse({ success: false, error: "Invalid ID" });
|
|
264
|
+
}
|
|
265
|
+
const cascade = url.searchParams.get("cascade") === "true";
|
|
266
|
+
const result = await handleDeletePrompt(id, cascade);
|
|
267
|
+
return this.jsonResponse(result);
|
|
268
|
+
}
|
|
269
|
+
if (path === "/api/prompts/bulk-delete" && method === "POST") {
|
|
270
|
+
const body = (await req.json());
|
|
271
|
+
const cascade = body.cascade !== false;
|
|
272
|
+
const result = await handleBulkDeletePrompts(body.ids || [], cascade);
|
|
273
|
+
return this.jsonResponse(result);
|
|
274
|
+
}
|
|
275
|
+
if (path === "/api/user-profile" && method === "GET") {
|
|
276
|
+
const userId = url.searchParams.get("userId") || undefined;
|
|
277
|
+
const result = await handleGetUserProfile(userId);
|
|
278
|
+
return this.jsonResponse(result);
|
|
279
|
+
}
|
|
280
|
+
if (path === "/api/user-profile/changelog" && method === "GET") {
|
|
281
|
+
const profileId = url.searchParams.get("profileId");
|
|
282
|
+
const limit = parseInt(url.searchParams.get("limit") || "5");
|
|
283
|
+
if (!profileId) {
|
|
284
|
+
return this.jsonResponse({ success: false, error: "profileId parameter required" });
|
|
285
|
+
}
|
|
286
|
+
const result = await handleGetProfileChangelog(profileId, limit);
|
|
287
|
+
return this.jsonResponse(result);
|
|
288
|
+
}
|
|
289
|
+
if (path === "/api/user-profile/snapshot" && method === "GET") {
|
|
290
|
+
const changelogId = url.searchParams.get("chlogId");
|
|
291
|
+
if (!changelogId) {
|
|
292
|
+
return this.jsonResponse({ success: false, error: "changelogId parameter required" });
|
|
293
|
+
}
|
|
294
|
+
const result = await handleGetProfileSnapshot(changelogId);
|
|
295
|
+
return this.jsonResponse(result);
|
|
296
|
+
}
|
|
297
|
+
if (path === "/api/user-profile/refresh" && method === "POST") {
|
|
298
|
+
const body = (await req.json().catch(() => ({})));
|
|
299
|
+
const userId = body.userId || undefined;
|
|
300
|
+
const result = await handleRefreshProfile(userId);
|
|
301
|
+
return this.jsonResponse(result);
|
|
302
|
+
}
|
|
303
|
+
return new Response("Not Found", { status: 404 });
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
return this.jsonResponse({
|
|
307
|
+
success: false,
|
|
308
|
+
error: String(error),
|
|
309
|
+
}, 500);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
serveStaticFile(filename, contentType) {
|
|
313
|
+
try {
|
|
314
|
+
const webDir = join(__dirname, "..", "web");
|
|
315
|
+
const filePath = join(webDir, filename);
|
|
316
|
+
if (contentType.startsWith("image/")) {
|
|
317
|
+
const content = readFileSync(filePath);
|
|
318
|
+
return new Response(content, {
|
|
319
|
+
headers: {
|
|
320
|
+
"Content-Type": contentType,
|
|
321
|
+
"Cache-Control": "public, max-age=86400",
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
const content = readFileSync(filePath, "utf-8");
|
|
326
|
+
return new Response(content, {
|
|
327
|
+
headers: {
|
|
328
|
+
"Content-Type": contentType,
|
|
329
|
+
"Cache-Control": "no-cache",
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
catch (error) {
|
|
334
|
+
return new Response("File not found", { status: 404 });
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
jsonResponse(data, status = 200) {
|
|
338
|
+
return new Response(JSON.stringify(data), {
|
|
339
|
+
status,
|
|
340
|
+
headers: {
|
|
341
|
+
"Content-Type": "application/json",
|
|
342
|
+
"Access-Control-Allow-Origin": "*",
|
|
343
|
+
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
|
344
|
+
"Access-Control-Allow-Headers": "Content-Type",
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
}
|
|
190
348
|
}
|
|
191
349
|
export async function startWebServer(config) {
|
|
192
350
|
const server = new WebServer(config);
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-mem",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.8",
|
|
4
4
|
"description": "OpenCode plugin that gives coding agents persistent memory using local vector database",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/plugin.js",
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
"files": [
|
|
57
57
|
"dist",
|
|
58
|
+
"native",
|
|
58
59
|
"package.json"
|
|
59
60
|
],
|
|
60
61
|
"lint-staged": {
|