@haisto/opencode-mem 2.14.3-beta.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.
Files changed (152) hide show
  1. package/README.md +165 -0
  2. package/dist/config.d.ts +62 -0
  3. package/dist/config.d.ts.map +1 -0
  4. package/dist/config.js +457 -0
  5. package/dist/index.d.ts +3 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +486 -0
  8. package/dist/plugin.d.ts +9 -0
  9. package/dist/plugin.d.ts.map +1 -0
  10. package/dist/plugin.js +5 -0
  11. package/dist/services/ai/ai-provider-factory.d.ts +8 -0
  12. package/dist/services/ai/ai-provider-factory.d.ts.map +1 -0
  13. package/dist/services/ai/ai-provider-factory.js +28 -0
  14. package/dist/services/ai/opencode-provider.d.ts +36 -0
  15. package/dist/services/ai/opencode-provider.d.ts.map +1 -0
  16. package/dist/services/ai/opencode-provider.js +92 -0
  17. package/dist/services/ai/provider-config.d.ts +17 -0
  18. package/dist/services/ai/provider-config.d.ts.map +1 -0
  19. package/dist/services/ai/provider-config.js +14 -0
  20. package/dist/services/ai/providers/anthropic-messages.d.ts +12 -0
  21. package/dist/services/ai/providers/anthropic-messages.d.ts.map +1 -0
  22. package/dist/services/ai/providers/anthropic-messages.js +184 -0
  23. package/dist/services/ai/providers/base-provider.d.ts +25 -0
  24. package/dist/services/ai/providers/base-provider.d.ts.map +1 -0
  25. package/dist/services/ai/providers/base-provider.js +23 -0
  26. package/dist/services/ai/providers/google-gemini.d.ts +16 -0
  27. package/dist/services/ai/providers/google-gemini.d.ts.map +1 -0
  28. package/dist/services/ai/providers/google-gemini.js +228 -0
  29. package/dist/services/ai/providers/openai-chat-completion.d.ts +14 -0
  30. package/dist/services/ai/providers/openai-chat-completion.d.ts.map +1 -0
  31. package/dist/services/ai/providers/openai-chat-completion.js +318 -0
  32. package/dist/services/ai/providers/openai-responses.d.ts +14 -0
  33. package/dist/services/ai/providers/openai-responses.d.ts.map +1 -0
  34. package/dist/services/ai/providers/openai-responses.js +182 -0
  35. package/dist/services/ai/session/ai-session-manager.d.ts +21 -0
  36. package/dist/services/ai/session/ai-session-manager.d.ts.map +1 -0
  37. package/dist/services/ai/session/ai-session-manager.js +166 -0
  38. package/dist/services/ai/session/session-types.d.ts +43 -0
  39. package/dist/services/ai/session/session-types.d.ts.map +1 -0
  40. package/dist/services/ai/session/session-types.js +1 -0
  41. package/dist/services/ai/tools/tool-schema.d.ts +41 -0
  42. package/dist/services/ai/tools/tool-schema.d.ts.map +1 -0
  43. package/dist/services/ai/tools/tool-schema.js +24 -0
  44. package/dist/services/ai/validators/user-profile-validator.d.ts +13 -0
  45. package/dist/services/ai/validators/user-profile-validator.d.ts.map +1 -0
  46. package/dist/services/ai/validators/user-profile-validator.js +111 -0
  47. package/dist/services/api-handlers.d.ts +164 -0
  48. package/dist/services/api-handlers.d.ts.map +1 -0
  49. package/dist/services/api-handlers.js +927 -0
  50. package/dist/services/auto-capture.d.ts +3 -0
  51. package/dist/services/auto-capture.d.ts.map +1 -0
  52. package/dist/services/auto-capture.js +309 -0
  53. package/dist/services/cleanup-service.d.ts +23 -0
  54. package/dist/services/cleanup-service.d.ts.map +1 -0
  55. package/dist/services/cleanup-service.js +102 -0
  56. package/dist/services/client.d.ts +119 -0
  57. package/dist/services/client.d.ts.map +1 -0
  58. package/dist/services/client.js +257 -0
  59. package/dist/services/context.d.ts +11 -0
  60. package/dist/services/context.d.ts.map +1 -0
  61. package/dist/services/context.js +34 -0
  62. package/dist/services/deduplication-service.d.ts +30 -0
  63. package/dist/services/deduplication-service.d.ts.map +1 -0
  64. package/dist/services/deduplication-service.js +124 -0
  65. package/dist/services/embedding.d.ts +15 -0
  66. package/dist/services/embedding.d.ts.map +1 -0
  67. package/dist/services/embedding.js +127 -0
  68. package/dist/services/jsonc.d.ts +7 -0
  69. package/dist/services/jsonc.d.ts.map +1 -0
  70. package/dist/services/jsonc.js +76 -0
  71. package/dist/services/language-detector.d.ts +3 -0
  72. package/dist/services/language-detector.d.ts.map +1 -0
  73. package/dist/services/language-detector.js +33 -0
  74. package/dist/services/logger.d.ts +11 -0
  75. package/dist/services/logger.d.ts.map +1 -0
  76. package/dist/services/logger.js +97 -0
  77. package/dist/services/migration-service.d.ts +42 -0
  78. package/dist/services/migration-service.d.ts.map +1 -0
  79. package/dist/services/migration-service.js +250 -0
  80. package/dist/services/privacy.d.ts +3 -0
  81. package/dist/services/privacy.d.ts.map +1 -0
  82. package/dist/services/privacy.js +7 -0
  83. package/dist/services/secret-resolver.d.ts +2 -0
  84. package/dist/services/secret-resolver.d.ts.map +1 -0
  85. package/dist/services/secret-resolver.js +55 -0
  86. package/dist/services/sqlite/connection-manager.d.ts +13 -0
  87. package/dist/services/sqlite/connection-manager.d.ts.map +1 -0
  88. package/dist/services/sqlite/connection-manager.js +74 -0
  89. package/dist/services/sqlite/shard-manager.d.ts +23 -0
  90. package/dist/services/sqlite/shard-manager.d.ts.map +1 -0
  91. package/dist/services/sqlite/shard-manager.js +288 -0
  92. package/dist/services/sqlite/sqlite-bootstrap.d.ts +2 -0
  93. package/dist/services/sqlite/sqlite-bootstrap.d.ts.map +1 -0
  94. package/dist/services/sqlite/sqlite-bootstrap.js +8 -0
  95. package/dist/services/sqlite/types.d.ts +42 -0
  96. package/dist/services/sqlite/types.d.ts.map +1 -0
  97. package/dist/services/sqlite/types.js +1 -0
  98. package/dist/services/sqlite/vector-search.d.ts +29 -0
  99. package/dist/services/sqlite/vector-search.d.ts.map +1 -0
  100. package/dist/services/sqlite/vector-search.js +279 -0
  101. package/dist/services/tags.d.ts +24 -0
  102. package/dist/services/tags.d.ts.map +1 -0
  103. package/dist/services/tags.js +145 -0
  104. package/dist/services/user-memory-learning.d.ts +3 -0
  105. package/dist/services/user-memory-learning.d.ts.map +1 -0
  106. package/dist/services/user-memory-learning.js +235 -0
  107. package/dist/services/user-profile/profile-context.d.ts +2 -0
  108. package/dist/services/user-profile/profile-context.d.ts.map +1 -0
  109. package/dist/services/user-profile/profile-context.js +40 -0
  110. package/dist/services/user-profile/profile-utils.d.ts +3 -0
  111. package/dist/services/user-profile/profile-utils.d.ts.map +1 -0
  112. package/dist/services/user-profile/profile-utils.js +45 -0
  113. package/dist/services/user-profile/types.d.ts +46 -0
  114. package/dist/services/user-profile/types.d.ts.map +1 -0
  115. package/dist/services/user-profile/types.js +1 -0
  116. package/dist/services/user-profile/user-profile-manager.d.ts +23 -0
  117. package/dist/services/user-profile/user-profile-manager.d.ts.map +1 -0
  118. package/dist/services/user-profile/user-profile-manager.js +337 -0
  119. package/dist/services/user-prompt/user-prompt-manager.d.ts +41 -0
  120. package/dist/services/user-prompt/user-prompt-manager.d.ts.map +1 -0
  121. package/dist/services/user-prompt/user-prompt-manager.js +192 -0
  122. package/dist/services/vector-backends/backend-factory.d.ts +3 -0
  123. package/dist/services/vector-backends/backend-factory.d.ts.map +1 -0
  124. package/dist/services/vector-backends/backend-factory.js +104 -0
  125. package/dist/services/vector-backends/exact-scan-backend.d.ts +39 -0
  126. package/dist/services/vector-backends/exact-scan-backend.d.ts.map +1 -0
  127. package/dist/services/vector-backends/exact-scan-backend.js +63 -0
  128. package/dist/services/vector-backends/types.d.ts +51 -0
  129. package/dist/services/vector-backends/types.d.ts.map +1 -0
  130. package/dist/services/vector-backends/types.js +1 -0
  131. package/dist/services/vector-backends/usearch-backend.d.ts +47 -0
  132. package/dist/services/vector-backends/usearch-backend.d.ts.map +1 -0
  133. package/dist/services/vector-backends/usearch-backend.js +174 -0
  134. package/dist/services/web-server-worker.d.ts +2 -0
  135. package/dist/services/web-server-worker.d.ts.map +1 -0
  136. package/dist/services/web-server-worker.js +283 -0
  137. package/dist/services/web-server.d.ts +31 -0
  138. package/dist/services/web-server.d.ts.map +1 -0
  139. package/dist/services/web-server.js +356 -0
  140. package/dist/types/index.d.ts +19 -0
  141. package/dist/types/index.d.ts.map +1 -0
  142. package/dist/types/index.js +1 -0
  143. package/dist/web/app.d.ts +2 -0
  144. package/dist/web/app.d.ts.map +1 -0
  145. package/dist/web/app.js +1238 -0
  146. package/dist/web/favicon.ico +0 -0
  147. package/dist/web/i18n.d.ts +2 -0
  148. package/dist/web/i18n.d.ts.map +1 -0
  149. package/dist/web/i18n.js +312 -0
  150. package/dist/web/index.html +293 -0
  151. package/dist/web/styles.css +1786 -0
  152. package/package.json +78 -0
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Strips comments from JSONC content while respecting string boundaries.
3
+ * Handles // and /* comments, URLs in strings, and escaped quotes.
4
+ * Also removes trailing commas to support more relaxed JSONC format.
5
+ */
6
+ export function stripJsoncComments(content) {
7
+ let result = "";
8
+ let i = 0;
9
+ let inString = false;
10
+ let inSingleLineComment = false;
11
+ let inMultiLineComment = false;
12
+ while (i < content.length) {
13
+ const char = content[i];
14
+ const nextChar = content[i + 1];
15
+ if (!inSingleLineComment && !inMultiLineComment) {
16
+ if (char === '"') {
17
+ // Count consecutive backslashes before this quote
18
+ let backslashCount = 0;
19
+ let j = i - 1;
20
+ while (j >= 0 && content[j] === "\\") {
21
+ backslashCount++;
22
+ j--;
23
+ }
24
+ // Quote is escaped only if preceded by ODD number of backslashes
25
+ // e.g., \" = escaped, \\" = not escaped (escaped backslash + quote)
26
+ if (backslashCount % 2 === 0) {
27
+ inString = !inString;
28
+ }
29
+ result += char;
30
+ i++;
31
+ continue;
32
+ }
33
+ }
34
+ if (inString) {
35
+ result += char;
36
+ i++;
37
+ continue;
38
+ }
39
+ if (!inSingleLineComment && !inMultiLineComment) {
40
+ if (char === "/" && nextChar === "/") {
41
+ inSingleLineComment = true;
42
+ i += 2;
43
+ continue;
44
+ }
45
+ if (char === "/" && nextChar === "*") {
46
+ inMultiLineComment = true;
47
+ i += 2;
48
+ continue;
49
+ }
50
+ }
51
+ if (inSingleLineComment) {
52
+ if (char === "\n") {
53
+ inSingleLineComment = false;
54
+ result += char;
55
+ }
56
+ i++;
57
+ continue;
58
+ }
59
+ if (inMultiLineComment) {
60
+ if (char === "*" && nextChar === "/") {
61
+ inMultiLineComment = false;
62
+ i += 2;
63
+ continue;
64
+ }
65
+ if (char === "\n") {
66
+ result += char;
67
+ }
68
+ i++;
69
+ continue;
70
+ }
71
+ result += char;
72
+ i++;
73
+ }
74
+ // Remove trailing commas before } or ]
75
+ return result.replace(/,\s*([}\]])/g, "$1");
76
+ }
@@ -0,0 +1,3 @@
1
+ export declare function detectLanguage(text: string): string;
2
+ export declare function getLanguageName(code: string): string;
3
+ //# sourceMappingURL=language-detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"language-detector.d.ts","sourceRoot":"","sources":["../../src/services/language-detector.ts"],"names":[],"mappings":"AAWA,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAiBnD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQpD"}
@@ -0,0 +1,33 @@
1
+ import { franc } from "franc-min";
2
+ import { iso6393, iso6393To1 } from "iso-639-3";
3
+ // 3-letter codes without ISO 639-1 equivalents
4
+ const FALLBACK_MAP = {
5
+ cmn: "zh", // Mandarin Chinese
6
+ yue: "zh", // Cantonese
7
+ arz: "ar", // Egyptian Arabic
8
+ hbs: "sr", // Serbo-Croatian
9
+ };
10
+ export function detectLanguage(text) {
11
+ if (!text || text.trim().length === 0) {
12
+ return "en";
13
+ }
14
+ const detected = franc(text, { minLength: 5 });
15
+ if (detected === "und") {
16
+ return "en";
17
+ }
18
+ // Try 2-letter mapping first
19
+ const twoLetter = iso6393To1[detected];
20
+ if (twoLetter)
21
+ return twoLetter;
22
+ // Fallback for 3-letter codes without 2-letter equivalent
23
+ return FALLBACK_MAP[detected] || "en";
24
+ }
25
+ export function getLanguageName(code) {
26
+ // Try 2-letter lookup first
27
+ let lang = iso6393.find((l) => l.iso6391 === code);
28
+ // Fallback to 3-letter lookup
29
+ if (!lang) {
30
+ lang = iso6393.find((l) => l.iso6393 === code);
31
+ }
32
+ return lang?.name || "English";
33
+ }
@@ -0,0 +1,11 @@
1
+ export type LogLevel = "trace" | "debug" | "info" | "warn" | "error";
2
+ export declare function setLogLevel(level: LogLevel): void;
3
+ export declare function getLogLevel(): LogLevel;
4
+ /** @deprecated Use logInfo/logWarn/logError/logDebug instead */
5
+ export declare function log(message: string, data?: unknown): void;
6
+ export declare function logTrace(message: string, data?: unknown): void;
7
+ export declare function logDebug(message: string, data?: unknown): void;
8
+ export declare function logInfo(message: string, data?: unknown): void;
9
+ export declare function logWarn(message: string, data?: unknown): void;
10
+ export declare function logError(message: string, data?: unknown): void;
11
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/services/logger.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAiBrE,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAEjD;AAED,wBAAgB,WAAW,IAAI,QAAQ,CAItC;AA6DD,gEAAgE;AAChE,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,QAElD;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,QAEvD;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,QAEvD;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,QAEtD;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,QAEtD;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,QAEvD"}
@@ -0,0 +1,97 @@
1
+ import { appendFileSync, writeFileSync, existsSync, mkdirSync, statSync, renameSync, unlinkSync, } from "fs";
2
+ import { homedir } from "os";
3
+ import { join } from "path";
4
+ const LOG_LEVEL_ORDER = {
5
+ trace: 0,
6
+ debug: 1,
7
+ info: 2,
8
+ warn: 3,
9
+ error: 4,
10
+ };
11
+ function getInitialLogLevel() {
12
+ if (process.env.OPENCODE_MEM_DEBUG)
13
+ return LOG_LEVEL_ORDER.debug;
14
+ return LOG_LEVEL_ORDER.info;
15
+ }
16
+ let currentLogLevel = getInitialLogLevel();
17
+ export function setLogLevel(level) {
18
+ currentLogLevel = LOG_LEVEL_ORDER[level];
19
+ }
20
+ export function getLogLevel() {
21
+ return Object.entries(LOG_LEVEL_ORDER).find(([_, v]) => v === currentLogLevel)?.[0] || "info";
22
+ }
23
+ function getLogFilePath() {
24
+ return process.env.OPENCODE_MEM_LOG_FILE || join(homedir(), ".opencode-mem", "opencode-mem.log");
25
+ }
26
+ function getLogDirPath() {
27
+ const logFile = getLogFilePath();
28
+ const lastSlash = Math.max(logFile.lastIndexOf("/"), logFile.lastIndexOf("\\"));
29
+ return lastSlash === -1 ? "." : logFile.slice(0, lastSlash);
30
+ }
31
+ const MAX_LOG_SIZE = 5 * 1024 * 1024;
32
+ const GLOBAL_LOGGER_KEY = Symbol.for("opencode-mem.logger.initialized");
33
+ function localTimestamp() {
34
+ const d = new Date();
35
+ const pad = (n, len = 2) => String(n).padStart(len, "0");
36
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${pad(d.getMilliseconds(), 3)}`;
37
+ }
38
+ function rotateLog() {
39
+ const logFile = getLogFilePath();
40
+ try {
41
+ if (!existsSync(logFile))
42
+ return;
43
+ const stats = statSync(logFile);
44
+ if (stats.size < MAX_LOG_SIZE)
45
+ return;
46
+ const oldLog = logFile + ".old";
47
+ if (existsSync(oldLog))
48
+ unlinkSync(oldLog);
49
+ renameSync(logFile, oldLog);
50
+ }
51
+ catch { }
52
+ }
53
+ function ensureLoggerInitialized() {
54
+ if (globalThis[GLOBAL_LOGGER_KEY])
55
+ return;
56
+ const logDir = getLogDirPath();
57
+ const logFile = getLogFilePath();
58
+ if (!existsSync(logDir)) {
59
+ mkdirSync(logDir, { recursive: true });
60
+ }
61
+ rotateLog();
62
+ writeFileSync(logFile, `\n--- Session started: ${localTimestamp()} ---\n`, {
63
+ flag: "a",
64
+ });
65
+ globalThis[GLOBAL_LOGGER_KEY] = true;
66
+ }
67
+ function writeLog(level, message, data) {
68
+ if (LOG_LEVEL_ORDER[level] < currentLogLevel)
69
+ return;
70
+ ensureLoggerInitialized();
71
+ const logFile = getLogFilePath();
72
+ const timestamp = localTimestamp();
73
+ const levelTag = level.toUpperCase().padEnd(5);
74
+ const line = data
75
+ ? `[${timestamp}] [${levelTag}] ${message}: ${JSON.stringify(data)}\n`
76
+ : `[${timestamp}] [${levelTag}] ${message}\n`;
77
+ appendFileSync(logFile, line);
78
+ }
79
+ /** @deprecated Use logInfo/logWarn/logError/logDebug instead */
80
+ export function log(message, data) {
81
+ writeLog("info", message, data);
82
+ }
83
+ export function logTrace(message, data) {
84
+ writeLog("trace", message, data);
85
+ }
86
+ export function logDebug(message, data) {
87
+ writeLog("debug", message, data);
88
+ }
89
+ export function logInfo(message, data) {
90
+ writeLog("info", message, data);
91
+ }
92
+ export function logWarn(message, data) {
93
+ writeLog("warn", message, data);
94
+ }
95
+ export function logError(message, data) {
96
+ writeLog("error", message, data);
97
+ }
@@ -0,0 +1,42 @@
1
+ export interface DimensionMismatch {
2
+ needsMigration: boolean;
3
+ configDimensions: number;
4
+ configModel: string;
5
+ shardMismatches: Array<{
6
+ shardId: number;
7
+ dbPath: string;
8
+ storedDimensions: number;
9
+ storedModel: string;
10
+ vectorCount: number;
11
+ }>;
12
+ }
13
+ export interface MigrationProgress {
14
+ phase: "preparing" | "re-embedding" | "cleanup" | "complete";
15
+ processed: number;
16
+ total: number;
17
+ currentShard?: string;
18
+ }
19
+ export interface MigrationResult {
20
+ success: boolean;
21
+ strategy: "fresh-start" | "re-embed";
22
+ deletedShards: number;
23
+ reEmbeddedMemories: number;
24
+ duration: number;
25
+ error?: string;
26
+ }
27
+ export declare class MigrationService {
28
+ private isRunning;
29
+ private progressCallback?;
30
+ detectDimensionMismatch(): Promise<DimensionMismatch>;
31
+ migrateToNewModel(strategy: "fresh-start" | "re-embed", progressCallback?: (progress: MigrationProgress) => void): Promise<MigrationResult>;
32
+ private freshStartMigration;
33
+ private reEmbedMigration;
34
+ private reportProgress;
35
+ getStatus(): {
36
+ isRunning: boolean;
37
+ configModel: string;
38
+ configDimensions: number;
39
+ };
40
+ }
41
+ export declare const migrationService: MigrationService;
42
+ //# sourceMappingURL=migration-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration-service.d.ts","sourceRoot":"","sources":["../../src/services/migration-service.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,OAAO,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,KAAK,CAAC;QACrB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,gBAAgB,EAAE,MAAM,CAAC;QACzB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,WAAW,GAAG,cAAc,GAAG,SAAS,GAAG,UAAU,CAAC;IAC7D,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,aAAa,GAAG,UAAU,CAAC;IACrC,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,gBAAgB,CAAC,CAAwC;IAE3D,uBAAuB,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAoDrD,iBAAiB,CACrB,QAAQ,EAAE,aAAa,GAAG,UAAU,EACpC,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,iBAAiB,KAAK,IAAI,GACvD,OAAO,CAAC,eAAe,CAAC;YA2Cb,mBAAmB;YA8CnB,gBAAgB;IA+I9B,OAAO,CAAC,cAAc;IAMtB,SAAS;;;;;CAOV;AAED,eAAO,MAAM,gBAAgB,kBAAyB,CAAC"}
@@ -0,0 +1,250 @@
1
+ import { shardManager } from "./sqlite/shard-manager.js";
2
+ import { connectionManager } from "./sqlite/connection-manager.js";
3
+ import { vectorSearch } from "./sqlite/vector-search.js";
4
+ import { embeddingService } from "./embedding.js";
5
+ import { CONFIG } from "../config.js";
6
+ import { log } from "./logger.js";
7
+ export class MigrationService {
8
+ isRunning = false;
9
+ progressCallback;
10
+ async detectDimensionMismatch() {
11
+ const userShards = shardManager.getAllShards("user", "");
12
+ const projectShards = shardManager.getAllShards("project", "");
13
+ const allShards = [...userShards, ...projectShards];
14
+ const mismatches = [];
15
+ for (const shard of allShards) {
16
+ try {
17
+ const db = connectionManager.getConnection(shard.dbPath);
18
+ const metadataResult = db
19
+ .prepare(`
20
+ SELECT key, value FROM shard_metadata
21
+ WHERE key IN ('embedding_dimensions', 'embedding_model')
22
+ `)
23
+ .all();
24
+ const metadata = Object.fromEntries(metadataResult.map((row) => [row.key, row.value]));
25
+ const storedDimensions = parseInt(metadata.embedding_dimensions || "0");
26
+ const storedModel = metadata.embedding_model || "unknown";
27
+ if (storedDimensions !== CONFIG.embeddingDimensions) {
28
+ const vectorCount = vectorSearch.countAllVectors(db);
29
+ mismatches.push({
30
+ shardId: shard.id,
31
+ dbPath: shard.dbPath,
32
+ storedDimensions,
33
+ storedModel,
34
+ vectorCount,
35
+ });
36
+ }
37
+ }
38
+ catch (error) {
39
+ log("Migration: error checking shard", {
40
+ shardId: shard.id,
41
+ error: String(error),
42
+ });
43
+ }
44
+ }
45
+ return {
46
+ needsMigration: mismatches.length > 0,
47
+ configDimensions: CONFIG.embeddingDimensions,
48
+ configModel: CONFIG.embeddingModel,
49
+ shardMismatches: mismatches,
50
+ };
51
+ }
52
+ async migrateToNewModel(strategy, progressCallback) {
53
+ if (this.isRunning) {
54
+ throw new Error("Migration already running");
55
+ }
56
+ this.isRunning = true;
57
+ this.progressCallback = progressCallback;
58
+ const startTime = Date.now();
59
+ try {
60
+ const mismatch = await this.detectDimensionMismatch();
61
+ if (!mismatch.needsMigration) {
62
+ return {
63
+ success: true,
64
+ strategy,
65
+ deletedShards: 0,
66
+ reEmbeddedMemories: 0,
67
+ duration: Date.now() - startTime,
68
+ };
69
+ }
70
+ if (strategy === "fresh-start") {
71
+ return await this.freshStartMigration(mismatch, startTime);
72
+ }
73
+ else {
74
+ return await this.reEmbedMigration(mismatch, startTime);
75
+ }
76
+ }
77
+ catch (error) {
78
+ log("Migration: failed", { error: String(error) });
79
+ return {
80
+ success: false,
81
+ strategy,
82
+ deletedShards: 0,
83
+ reEmbeddedMemories: 0,
84
+ duration: Date.now() - startTime,
85
+ error: String(error),
86
+ };
87
+ }
88
+ finally {
89
+ this.isRunning = false;
90
+ this.progressCallback = undefined;
91
+ }
92
+ }
93
+ async freshStartMigration(mismatch, startTime) {
94
+ this.reportProgress({
95
+ phase: "preparing",
96
+ processed: 0,
97
+ total: mismatch.shardMismatches.length,
98
+ });
99
+ let deletedShards = 0;
100
+ for (const [index, shardInfo] of mismatch.shardMismatches.entries()) {
101
+ try {
102
+ this.reportProgress({
103
+ phase: "cleanup",
104
+ processed: index,
105
+ total: mismatch.shardMismatches.length,
106
+ currentShard: String(shardInfo.shardId),
107
+ });
108
+ await shardManager.deleteShard(shardInfo.shardId);
109
+ deletedShards++;
110
+ }
111
+ catch (error) {
112
+ log("Migration: error deleting shard", {
113
+ shardId: shardInfo.shardId,
114
+ error: String(error),
115
+ });
116
+ }
117
+ }
118
+ this.reportProgress({
119
+ phase: "complete",
120
+ processed: mismatch.shardMismatches.length,
121
+ total: mismatch.shardMismatches.length,
122
+ });
123
+ return {
124
+ success: true,
125
+ strategy: "fresh-start",
126
+ deletedShards,
127
+ reEmbeddedMemories: 0,
128
+ duration: Date.now() - startTime,
129
+ };
130
+ }
131
+ async reEmbedMigration(mismatch, startTime) {
132
+ await embeddingService.warmup();
133
+ embeddingService.clearCache();
134
+ const totalMemories = mismatch.shardMismatches.reduce((sum, s) => sum + s.vectorCount, 0);
135
+ this.reportProgress({
136
+ phase: "preparing",
137
+ processed: 0,
138
+ total: totalMemories,
139
+ });
140
+ let reEmbeddedCount = 0;
141
+ let processedCount = 0;
142
+ for (const shardInfo of mismatch.shardMismatches) {
143
+ this.reportProgress({
144
+ phase: "re-embedding",
145
+ processed: processedCount,
146
+ total: totalMemories,
147
+ currentShard: String(shardInfo.shardId),
148
+ });
149
+ try {
150
+ const db = connectionManager.getConnection(shardInfo.dbPath);
151
+ const memories = vectorSearch.getAllMemories(db);
152
+ const tempMemories = [];
153
+ for (const memory of memories) {
154
+ tempMemories.push({
155
+ id: memory.id,
156
+ content: memory.content,
157
+ containerTag: memory.container_tag,
158
+ type: memory.type,
159
+ createdAt: memory.created_at,
160
+ updatedAt: memory.updated_at,
161
+ metadata: memory.metadata,
162
+ displayName: memory.display_name,
163
+ userName: memory.user_name,
164
+ userEmail: memory.user_email,
165
+ projectPath: memory.project_path,
166
+ projectName: memory.project_name,
167
+ gitRepoUrl: memory.git_repo_url,
168
+ isPinned: memory.is_pinned || 0,
169
+ });
170
+ }
171
+ await shardManager.deleteShard(shardInfo.shardId);
172
+ for (const memory of tempMemories) {
173
+ try {
174
+ const vector = await embeddingService.embedWithTimeout(memory.content);
175
+ const scope = memory.containerTag.includes("_user_") ? "user" : "project";
176
+ const hash = memory.containerTag.split("_").slice(2).join("_");
177
+ const newShard = shardManager.getWriteShard(scope, hash);
178
+ const newDb = connectionManager.getConnection(newShard.dbPath);
179
+ await vectorSearch.insertVector(newDb, {
180
+ id: memory.id,
181
+ content: memory.content,
182
+ vector,
183
+ containerTag: memory.containerTag,
184
+ type: memory.type || undefined,
185
+ createdAt: memory.createdAt,
186
+ updatedAt: memory.updatedAt,
187
+ metadata: memory.metadata || undefined,
188
+ displayName: memory.displayName || undefined,
189
+ userName: memory.userName || undefined,
190
+ userEmail: memory.userEmail || undefined,
191
+ projectPath: memory.projectPath || undefined,
192
+ projectName: memory.projectName || undefined,
193
+ gitRepoUrl: memory.gitRepoUrl || undefined,
194
+ }, newShard);
195
+ if (memory.isPinned === 1) {
196
+ vectorSearch.pinMemory(newDb, memory.id);
197
+ }
198
+ shardManager.incrementVectorCount(newShard.id);
199
+ reEmbeddedCount++;
200
+ processedCount++;
201
+ this.reportProgress({
202
+ phase: "re-embedding",
203
+ processed: processedCount,
204
+ total: totalMemories,
205
+ currentShard: String(shardInfo.shardId),
206
+ });
207
+ }
208
+ catch (error) {
209
+ log("Migration: error re-embedding memory", {
210
+ memoryId: memory.id,
211
+ error: String(error),
212
+ });
213
+ processedCount++;
214
+ }
215
+ }
216
+ }
217
+ catch (error) {
218
+ log("Migration: error processing shard", {
219
+ shardId: shardInfo.shardId,
220
+ error: String(error),
221
+ });
222
+ }
223
+ }
224
+ this.reportProgress({
225
+ phase: "complete",
226
+ processed: totalMemories,
227
+ total: totalMemories,
228
+ });
229
+ return {
230
+ success: true,
231
+ strategy: "re-embed",
232
+ deletedShards: mismatch.shardMismatches.length,
233
+ reEmbeddedMemories: reEmbeddedCount,
234
+ duration: Date.now() - startTime,
235
+ };
236
+ }
237
+ reportProgress(progress) {
238
+ if (this.progressCallback) {
239
+ this.progressCallback(progress);
240
+ }
241
+ }
242
+ getStatus() {
243
+ return {
244
+ isRunning: this.isRunning,
245
+ configModel: CONFIG.embeddingModel,
246
+ configDimensions: CONFIG.embeddingDimensions,
247
+ };
248
+ }
249
+ }
250
+ export const migrationService = new MigrationService();
@@ -0,0 +1,3 @@
1
+ export declare function stripPrivateContent(content: string): string;
2
+ export declare function isFullyPrivate(content: string): boolean;
3
+ //# sourceMappingURL=privacy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"privacy.d.ts","sourceRoot":"","sources":["../../src/services/privacy.ts"],"names":[],"mappings":"AAAA,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAGvD"}
@@ -0,0 +1,7 @@
1
+ export function stripPrivateContent(content) {
2
+ return content.replace(/<private>[\s\S]*?<\/private>/gi, "[REDACTED]");
3
+ }
4
+ export function isFullyPrivate(content) {
5
+ const stripped = stripPrivateContent(content).trim();
6
+ return stripped === "[REDACTED]" || stripped === "";
7
+ }
@@ -0,0 +1,2 @@
1
+ export declare function resolveSecretValue(value: string | undefined): string | undefined;
2
+ //# sourceMappingURL=secret-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret-resolver.d.ts","sourceRoot":"","sources":["../../src/services/secret-resolver.ts"],"names":[],"mappings":"AAiCA,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAkChF"}
@@ -0,0 +1,55 @@
1
+ import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { homedir, platform } from "node:os";
4
+ function expandPath(path) {
5
+ if (path.startsWith("~/")) {
6
+ return join(homedir(), path.slice(2));
7
+ }
8
+ if (path === "~") {
9
+ return homedir();
10
+ }
11
+ return path;
12
+ }
13
+ function checkFilePermissions(filePath) {
14
+ if (platform() === "win32") {
15
+ return;
16
+ }
17
+ try {
18
+ const stats = statSync(filePath);
19
+ const mode = stats.mode & 0o777;
20
+ if (mode > 0o600) {
21
+ console.warn(`Warning: Secret file ${filePath} has permissive permissions (${mode.toString(8)}). Recommend chmod 600.`);
22
+ }
23
+ }
24
+ catch (error) {
25
+ console.warn(`Warning: Could not check file permissions for ${filePath}`);
26
+ }
27
+ }
28
+ export function resolveSecretValue(value) {
29
+ if (!value) {
30
+ return undefined;
31
+ }
32
+ if (value.startsWith("file://")) {
33
+ const filePath = expandPath(value.slice(7));
34
+ if (!existsSync(filePath)) {
35
+ throw new Error(`Secret file not found: ${filePath}`);
36
+ }
37
+ try {
38
+ checkFilePermissions(filePath);
39
+ const content = readFileSync(filePath, "utf-8");
40
+ return content.trim();
41
+ }
42
+ catch (error) {
43
+ throw new Error(`Failed to read secret file ${filePath}: ${error}`);
44
+ }
45
+ }
46
+ if (value.startsWith("env://")) {
47
+ const envVar = value.slice(6);
48
+ const envValue = process.env[envVar];
49
+ if (!envValue) {
50
+ throw new Error(`Environment variable not found: ${envVar}`);
51
+ }
52
+ return envValue;
53
+ }
54
+ return value;
55
+ }
@@ -0,0 +1,13 @@
1
+ declare const Database: typeof import("bun:sqlite").Database;
2
+ export declare class ConnectionManager {
3
+ private connections;
4
+ private initDatabase;
5
+ private migrateSchema;
6
+ getConnection(dbPath: string): typeof Database.prototype;
7
+ closeConnection(dbPath: string): void;
8
+ closeAll(): void;
9
+ checkpointAll(): void;
10
+ }
11
+ export declare const connectionManager: ConnectionManager;
12
+ export {};
13
+ //# sourceMappingURL=connection-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection-manager.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/connection-manager.ts"],"names":[],"mappings":"AAMA,QAAA,MAAM,QAAQ,sCAAgB,CAAC;AAE/B,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,WAAW,CAAqD;IAExE,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,aAAa;IAarB,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"}