om-memory-system 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +587 -0
  3. package/dist/adapters/opencode/auto-capture-host.d.ts +3 -0
  4. package/dist/adapters/opencode/auto-capture-host.js +29 -0
  5. package/dist/adapters/opencode/auto-capture-summary.d.ts +4 -0
  6. package/dist/adapters/opencode/auto-capture-summary.js +147 -0
  7. package/dist/adapters/opencode/conversation.d.ts +2 -0
  8. package/dist/adapters/opencode/conversation.js +50 -0
  9. package/dist/adapters/opencode/user-prompt.d.ts +9 -0
  10. package/dist/adapters/opencode/user-prompt.js +26 -0
  11. package/dist/adapters/pi/capture.d.ts +41 -0
  12. package/dist/adapters/pi/capture.js +91 -0
  13. package/dist/adapters/pi/conversation.d.ts +38 -0
  14. package/dist/adapters/pi/conversation.js +118 -0
  15. package/dist/adapters/pi/extension.d.ts +18 -0
  16. package/dist/adapters/pi/extension.js +234 -0
  17. package/dist/adapters/pi/import-command.d.ts +29 -0
  18. package/dist/adapters/pi/import-command.js +232 -0
  19. package/dist/adapters/pi/profile.d.ts +39 -0
  20. package/dist/adapters/pi/profile.js +148 -0
  21. package/dist/adapters/pi/provider.d.ts +50 -0
  22. package/dist/adapters/pi/provider.js +77 -0
  23. package/dist/config.d.ts +290 -0
  24. package/dist/config.js +742 -0
  25. package/dist/core/capture-context.d.ts +4 -0
  26. package/dist/core/capture-context.js +132 -0
  27. package/dist/core/capture.d.ts +15 -0
  28. package/dist/core/capture.js +68 -0
  29. package/dist/core/extraction.d.ts +80 -0
  30. package/dist/core/extraction.js +140 -0
  31. package/dist/core/host.d.ts +52 -0
  32. package/dist/core/host.js +1 -0
  33. package/dist/core/memory-operations.d.ts +27 -0
  34. package/dist/core/memory-operations.js +247 -0
  35. package/dist/core/retrieval.d.ts +12 -0
  36. package/dist/core/retrieval.js +72 -0
  37. package/dist/importer/discovery.d.ts +31 -0
  38. package/dist/importer/discovery.js +88 -0
  39. package/dist/importer/importer.d.ts +87 -0
  40. package/dist/importer/importer.js +260 -0
  41. package/dist/importer/ledger.d.ts +44 -0
  42. package/dist/importer/ledger.js +137 -0
  43. package/dist/importer/session-loader.d.ts +19 -0
  44. package/dist/importer/session-loader.js +12 -0
  45. package/dist/index.d.ts +24 -0
  46. package/dist/index.js +601 -0
  47. package/dist/plugin.d.ts +9 -0
  48. package/dist/plugin.js +11 -0
  49. package/dist/services/ai/ai-provider-factory.d.ts +7 -0
  50. package/dist/services/ai/ai-provider-factory.js +41 -0
  51. package/dist/services/ai/api-key-placeholder.d.ts +1 -0
  52. package/dist/services/ai/api-key-placeholder.js +14 -0
  53. package/dist/services/ai/internal-capture-sessions.d.ts +6 -0
  54. package/dist/services/ai/internal-capture-sessions.js +33 -0
  55. package/dist/services/ai/opencode-diagnostics.d.ts +7 -0
  56. package/dist/services/ai/opencode-diagnostics.js +36 -0
  57. package/dist/services/ai/opencode-host-config.d.ts +10 -0
  58. package/dist/services/ai/opencode-host-config.js +51 -0
  59. package/dist/services/ai/opencode-provider-loader.d.ts +3 -0
  60. package/dist/services/ai/opencode-provider-loader.js +4 -0
  61. package/dist/services/ai/opencode-provider.d.ts +92 -0
  62. package/dist/services/ai/opencode-provider.js +419 -0
  63. package/dist/services/ai/opencode-sdk-client.d.ts +6 -0
  64. package/dist/services/ai/opencode-sdk-client.js +38 -0
  65. package/dist/services/ai/profile-llm-client.d.ts +2 -0
  66. package/dist/services/ai/profile-llm-client.js +27 -0
  67. package/dist/services/ai/provider-config.d.ts +17 -0
  68. package/dist/services/ai/provider-config.js +30 -0
  69. package/dist/services/ai/providers/anthropic-messages.d.ts +34 -0
  70. package/dist/services/ai/providers/anthropic-messages.js +220 -0
  71. package/dist/services/ai/providers/base-provider.d.ts +24 -0
  72. package/dist/services/ai/providers/base-provider.js +24 -0
  73. package/dist/services/ai/providers/google-gemini.d.ts +15 -0
  74. package/dist/services/ai/providers/google-gemini.js +228 -0
  75. package/dist/services/ai/providers/minimax.d.ts +35 -0
  76. package/dist/services/ai/providers/minimax.js +55 -0
  77. package/dist/services/ai/providers/openai-chat-completion.d.ts +22 -0
  78. package/dist/services/ai/providers/openai-chat-completion.js +387 -0
  79. package/dist/services/ai/providers/openai-responses.d.ts +13 -0
  80. package/dist/services/ai/providers/openai-responses.js +182 -0
  81. package/dist/services/ai/providers/orcarouter.d.ts +51 -0
  82. package/dist/services/ai/providers/orcarouter.js +66 -0
  83. package/dist/services/ai/session/ai-session-manager.d.ts +24 -0
  84. package/dist/services/ai/session/ai-session-manager.js +211 -0
  85. package/dist/services/ai/session/session-types.d.ts +42 -0
  86. package/dist/services/ai/session/session-types.js +1 -0
  87. package/dist/services/ai/tools/tool-schema.d.ts +40 -0
  88. package/dist/services/ai/tools/tool-schema.js +24 -0
  89. package/dist/services/ai/validators/user-profile-validator.d.ts +12 -0
  90. package/dist/services/ai/validators/user-profile-validator.js +114 -0
  91. package/dist/services/api-handlers.d.ts +186 -0
  92. package/dist/services/api-handlers.js +1284 -0
  93. package/dist/services/auth-token.d.ts +14 -0
  94. package/dist/services/auth-token.js +59 -0
  95. package/dist/services/auto-capture.d.ts +3 -0
  96. package/dist/services/auto-capture.js +127 -0
  97. package/dist/services/cleanup-service.d.ts +22 -0
  98. package/dist/services/cleanup-service.js +109 -0
  99. package/dist/services/client.d.ts +134 -0
  100. package/dist/services/client.js +327 -0
  101. package/dist/services/context.d.ts +10 -0
  102. package/dist/services/context.js +27 -0
  103. package/dist/services/cors.d.ts +12 -0
  104. package/dist/services/cors.js +52 -0
  105. package/dist/services/deduplication-service.d.ts +28 -0
  106. package/dist/services/deduplication-service.js +111 -0
  107. package/dist/services/embedding.d.ts +36 -0
  108. package/dist/services/embedding.js +200 -0
  109. package/dist/services/jsonc.d.ts +6 -0
  110. package/dist/services/jsonc.js +76 -0
  111. package/dist/services/language-detector.d.ts +2 -0
  112. package/dist/services/language-detector.js +33 -0
  113. package/dist/services/legacy-migration.d.ts +77 -0
  114. package/dist/services/legacy-migration.js +343 -0
  115. package/dist/services/logger.d.ts +1 -0
  116. package/dist/services/logger.js +119 -0
  117. package/dist/services/memory-portability-service.d.ts +37 -0
  118. package/dist/services/memory-portability-service.js +340 -0
  119. package/dist/services/memory-scope.d.ts +16 -0
  120. package/dist/services/memory-scope.js +43 -0
  121. package/dist/services/migration-service.d.ts +42 -0
  122. package/dist/services/migration-service.js +307 -0
  123. package/dist/services/onnxruntime-resolve.d.ts +24 -0
  124. package/dist/services/onnxruntime-resolve.js +174 -0
  125. package/dist/services/privacy.d.ts +26 -0
  126. package/dist/services/privacy.js +69 -0
  127. package/dist/services/request-timeouts.d.ts +9 -0
  128. package/dist/services/request-timeouts.js +11 -0
  129. package/dist/services/runtime-require.d.ts +17 -0
  130. package/dist/services/runtime-require.js +75 -0
  131. package/dist/services/secret-resolver.d.ts +1 -0
  132. package/dist/services/secret-resolver.js +65 -0
  133. package/dist/services/shard-inventory-service.d.ts +69 -0
  134. package/dist/services/shard-inventory-service.js +315 -0
  135. package/dist/services/shard-path-migration-service.d.ts +31 -0
  136. package/dist/services/shard-path-migration-service.js +367 -0
  137. package/dist/services/tag-prefix-migration.d.ts +60 -0
  138. package/dist/services/tag-prefix-migration.js +439 -0
  139. package/dist/services/tag-prefix-scan.d.ts +26 -0
  140. package/dist/services/tag-prefix-scan.js +91 -0
  141. package/dist/services/tags.d.ts +29 -0
  142. package/dist/services/tags.js +251 -0
  143. package/dist/services/turso/connection-manager.d.ts +15 -0
  144. package/dist/services/turso/connection-manager.js +163 -0
  145. package/dist/services/turso/cross-process-write-lock.d.ts +1 -0
  146. package/dist/services/turso/cross-process-write-lock.js +89 -0
  147. package/dist/services/turso/legacy-migrator.d.ts +1 -0
  148. package/dist/services/turso/legacy-migrator.js +540 -0
  149. package/dist/services/turso/lifecycle.d.ts +1 -0
  150. package/dist/services/turso/lifecycle.js +14 -0
  151. package/dist/services/turso/operation-lock.d.ts +2 -0
  152. package/dist/services/turso/operation-lock.js +61 -0
  153. package/dist/services/turso/ready.d.ts +2 -0
  154. package/dist/services/turso/ready.js +36 -0
  155. package/dist/services/turso/shard-manager.d.ts +38 -0
  156. package/dist/services/turso/shard-manager.js +496 -0
  157. package/dist/services/turso/sqlite-handle-release.d.ts +16 -0
  158. package/dist/services/turso/sqlite-handle-release.js +56 -0
  159. package/dist/services/turso/turso-db.d.ts +18 -0
  160. package/dist/services/turso/turso-db.js +53 -0
  161. package/dist/services/turso/types.d.ts +42 -0
  162. package/dist/services/turso/types.js +1 -0
  163. package/dist/services/turso/vector-search.d.ts +41 -0
  164. package/dist/services/turso/vector-search.js +384 -0
  165. package/dist/services/turso/vector-utils.d.ts +7 -0
  166. package/dist/services/turso/vector-utils.js +67 -0
  167. package/dist/services/user-memory-learning.d.ts +97 -0
  168. package/dist/services/user-memory-learning.js +673 -0
  169. package/dist/services/user-profile/ai-cleanup.d.ts +58 -0
  170. package/dist/services/user-profile/ai-cleanup.js +486 -0
  171. package/dist/services/user-profile/profile-context.d.ts +1 -0
  172. package/dist/services/user-profile/profile-context.js +105 -0
  173. package/dist/services/user-profile/profile-utils.d.ts +2 -0
  174. package/dist/services/user-profile/profile-utils.js +45 -0
  175. package/dist/services/user-profile/types.d.ts +92 -0
  176. package/dist/services/user-profile/types.js +1 -0
  177. package/dist/services/user-profile/user-profile-manager.d.ts +65 -0
  178. package/dist/services/user-profile/user-profile-manager.js +1660 -0
  179. package/dist/services/user-prompt/user-prompt-manager.d.ts +53 -0
  180. package/dist/services/user-prompt/user-prompt-manager.js +303 -0
  181. package/dist/services/web-api-auth.d.ts +3 -0
  182. package/dist/services/web-api-auth.js +25 -0
  183. package/dist/services/web-auth.d.ts +46 -0
  184. package/dist/services/web-auth.js +105 -0
  185. package/dist/services/web-server.d.ts +57 -0
  186. package/dist/services/web-server.js +659 -0
  187. package/dist/shared/api/index.d.ts +4 -0
  188. package/dist/shared/api/index.js +2 -0
  189. package/dist/shared/api/portability-schemas.d.ts +56 -0
  190. package/dist/shared/api/portability-schemas.js +39 -0
  191. package/dist/shared/api/schemas.d.ts +295 -0
  192. package/dist/shared/api/schemas.js +134 -0
  193. package/dist/shared/api/types.d.ts +28 -0
  194. package/dist/shared/api/types.js +1 -0
  195. package/dist/types/index.d.ts +27 -0
  196. package/dist/types/index.js +1 -0
  197. package/dist/utils/context-limit.d.ts +10 -0
  198. package/dist/utils/context-limit.js +43 -0
  199. package/dist/utils/math.d.ts +7 -0
  200. package/dist/utils/math.js +48 -0
  201. package/dist/utils/profile.d.ts +2 -0
  202. package/dist/utils/profile.js +15 -0
  203. package/dist/v2/adapter.d.ts +5 -0
  204. package/dist/v2/adapter.js +218 -0
  205. package/dist/v2/legacy-client.d.ts +15 -0
  206. package/dist/v2/legacy-client.js +257 -0
  207. package/dist/v2/memory-bridge.d.ts +18 -0
  208. package/dist/v2/memory-bridge.js +29 -0
  209. package/dist/v2/plugin.d.ts +3 -0
  210. package/dist/v2/plugin.js +24 -0
  211. package/dist/web/assets/index-DPjEpD1h.css +2 -0
  212. package/dist/web/assets/index-do26c4JJ.js +122 -0
  213. package/dist/web/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  214. package/dist/web/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  215. package/dist/web/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  216. package/dist/web/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  217. package/dist/web/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  218. package/dist/web/favicon.ico +0 -0
  219. package/dist/web/index.html +14 -0
  220. package/dist/web/omms-icon.png +0 -0
  221. package/package.json +138 -0
@@ -0,0 +1,247 @@
1
+ import { CONFIG, isConfigured } from "../config.js";
2
+ import { memoryClient } from "../services/client.js";
3
+ import { getLanguageName } from "../services/language-detector.js";
4
+ import { stripPrivateContent, isFullyPrivate } from "../services/privacy.js";
5
+ import { getTags } from "../services/tags.js";
6
+ function formatSearchResults(query, results, limit) {
7
+ const memoryResults = results.results || [];
8
+ return {
9
+ success: true,
10
+ query,
11
+ count: memoryResults.length,
12
+ results: memoryResults.slice(0, limit || 10).map((result) => ({
13
+ id: result.id,
14
+ content: result.memory || result.chunk,
15
+ similarity: Math.round(result.similarity * 100),
16
+ })),
17
+ };
18
+ }
19
+ export async function executeMemoryOperation(args, context) {
20
+ if (!isConfigured()) {
21
+ return {
22
+ success: false,
23
+ error: "Memory system not configured properly.",
24
+ };
25
+ }
26
+ const mode = args.mode || "help";
27
+ const needsEmbedding = !["help", "list-shards", "migrate", "export"].includes(mode);
28
+ if (needsEmbedding) {
29
+ const embeddingInitError = memoryClient.getEmbeddingInitError?.();
30
+ if (embeddingInitError) {
31
+ return { success: false, error: embeddingInitError };
32
+ }
33
+ }
34
+ try {
35
+ if (needsEmbedding) {
36
+ await memoryClient.warmup();
37
+ }
38
+ else if (mode !== "help") {
39
+ await memoryClient.ensureStorageReady();
40
+ }
41
+ }
42
+ catch (error) {
43
+ const message = error instanceof Error ? error.message : String(error);
44
+ return {
45
+ success: false,
46
+ error: `Memory system failed to initialize: ${message}`,
47
+ };
48
+ }
49
+ const tags = context.tags ?? getTags(context.directory);
50
+ const langName = getLanguageName(CONFIG.autoCaptureLanguage || "en");
51
+ try {
52
+ switch (mode) {
53
+ case "help":
54
+ return {
55
+ success: true,
56
+ message: "Memory System Usage Guide",
57
+ commands: [
58
+ {
59
+ command: "add",
60
+ description: `Store new memory (MATCH USER LANGUAGE: ${langName})`,
61
+ args: ["content", "type?", "tags?"],
62
+ },
63
+ {
64
+ command: "search",
65
+ description: `Search memories via keywords (MATCH USER LANGUAGE: ${langName})`,
66
+ args: ["query"],
67
+ },
68
+ {
69
+ command: "profile",
70
+ description: "View user profile or save an explicit preference (provide content to write)",
71
+ args: ["content?"],
72
+ },
73
+ { command: "list", description: "List recent memories", args: ["limit?"] },
74
+ { command: "forget", description: "Remove memory", args: ["memoryId"] },
75
+ {
76
+ command: "list-shards",
77
+ description: "List project memory shards and orphaned path associations",
78
+ args: [],
79
+ },
80
+ {
81
+ command: "migrate",
82
+ description: "Reassociate orphaned project shards after a directory move (target must be empty)",
83
+ args: ["fromPath?", "fromHash?", "dryRun?", "allowLinkedSource?"],
84
+ },
85
+ {
86
+ command: "export",
87
+ description: "Export current project memories to a portable JSON file",
88
+ args: ["outputPath"],
89
+ },
90
+ {
91
+ command: "import",
92
+ description: "Import memories from a portable JSON file (re-embeds; aborts on duplicate ids)",
93
+ args: ["inputPath", "dryRun?"],
94
+ },
95
+ ],
96
+ tagGuidance: "Use technical keywords for search. Tags rank highest.",
97
+ };
98
+ case "add": {
99
+ if (!args.content)
100
+ return { success: false, error: "content required" };
101
+ const sanitizedContent = stripPrivateContent(args.content);
102
+ if (isFullyPrivate(args.content)) {
103
+ return { success: false, error: "Private content blocked" };
104
+ }
105
+ const tagInfo = tags.project;
106
+ const parsedTags = args.tags
107
+ ? args.tags.split(",").map((tag) => tag.trim().toLowerCase())
108
+ : undefined;
109
+ const result = await memoryClient.addMemory(sanitizedContent, tagInfo.tag, {
110
+ type: args.type,
111
+ tags: parsedTags,
112
+ host: context.host,
113
+ hostSessionId: context.hostSessionId,
114
+ displayName: tagInfo.displayName,
115
+ userName: tagInfo.userName,
116
+ userEmail: tagInfo.userEmail,
117
+ projectPath: tagInfo.projectPath,
118
+ projectName: tagInfo.projectName,
119
+ gitRepoUrl: tagInfo.gitRepoUrl,
120
+ });
121
+ return {
122
+ success: result.success,
123
+ message: result.success ? "Memory added" : result.error,
124
+ id: result.success ? result.id : undefined,
125
+ tags: parsedTags,
126
+ };
127
+ }
128
+ case "search": {
129
+ if (!args.query)
130
+ return { success: false, error: "query required" };
131
+ const result = await memoryClient.searchMemories(args.query, tags.project.tag, args.scope ?? CONFIG.memory.defaultScope);
132
+ if (!result.success)
133
+ return { success: false, error: result.error };
134
+ return formatSearchResults(args.query, result, args.limit);
135
+ }
136
+ case "profile": {
137
+ if (args.query) {
138
+ return {
139
+ success: false,
140
+ error: "query is not valid for profile mode. Use content to write a preference or omit all args to read.",
141
+ };
142
+ }
143
+ const { userProfileManager } = await import("../services/user-profile/user-profile-manager.js");
144
+ const userId = tags.user.userEmail || "unknown";
145
+ if (args.content !== undefined) {
146
+ const trimmed = args.content.trim();
147
+ if (!trimmed)
148
+ return { success: false, error: "content must not be blank" };
149
+ if (!tags.user.userEmail) {
150
+ return {
151
+ success: false,
152
+ error: "Cannot save profile preference because no user email could be resolved. Configure userEmailOverride or git user.email.",
153
+ };
154
+ }
155
+ const sanitizedContent = stripPrivateContent(trimmed);
156
+ const hasNonPrivateContent = sanitizedContent.replace(/\[REDACTED\]/g, "").trim().length > 0;
157
+ if (isFullyPrivate(trimmed) || !hasNonPrivateContent) {
158
+ return { success: false, error: "Private content blocked" };
159
+ }
160
+ const newPreference = {
161
+ category: "explicit",
162
+ description: sanitizedContent,
163
+ confidence: 1.0,
164
+ frequency: 1,
165
+ evidence: ["manual-write"],
166
+ lastSeen: Date.now(),
167
+ };
168
+ const existingProfile = await userProfileManager.getActiveProfile(userId);
169
+ if (existingProfile) {
170
+ const existingData = JSON.parse(existingProfile.profileData);
171
+ const mergedData = await userProfileManager.mergeProfileData(existingData, { preferences: [newPreference] }, undefined, existingProfile.id);
172
+ await userProfileManager.updateProfile(existingProfile.id, mergedData, 0, `Explicit preference added: ${sanitizedContent.slice(0, 80)}`);
173
+ return { success: true, message: "Preference saved to profile" };
174
+ }
175
+ await userProfileManager.createProfile(userId, tags.user.displayName || userId, tags.user.userName || userId, tags.user.userEmail || userId, { preferences: [newPreference], patterns: [], workflows: [] }, 0);
176
+ return { success: true, message: "Profile created with preference" };
177
+ }
178
+ const profile = await userProfileManager.getActiveProfile(userId);
179
+ if (!profile)
180
+ return { success: true, profile: null };
181
+ const profileData = JSON.parse(profile.profileData);
182
+ return {
183
+ success: true,
184
+ profile: {
185
+ ...profileData,
186
+ version: profile.version,
187
+ lastAnalyzed: profile.lastAnalyzedAt,
188
+ },
189
+ };
190
+ }
191
+ case "list": {
192
+ const result = await memoryClient.listMemories(tags.project.tag, args.limit || 20, args.scope ?? CONFIG.memory.defaultScope);
193
+ if (!result.success)
194
+ return { success: false, error: result.error };
195
+ return {
196
+ success: true,
197
+ count: result.memories?.length,
198
+ memories: result.memories?.map((memory) => ({
199
+ id: memory.id,
200
+ content: memory.summary,
201
+ createdAt: memory.createdAt,
202
+ })),
203
+ };
204
+ }
205
+ case "forget": {
206
+ if (!args.memoryId)
207
+ return { success: false, error: "memoryId required" };
208
+ const result = await memoryClient.deleteMemory(args.memoryId);
209
+ return { success: result.success, message: "Memory removed" };
210
+ }
211
+ case "list-shards":
212
+ return { ...(await memoryClient.listShards(context.directory)) };
213
+ case "migrate": {
214
+ if (!args.fromPath && !args.fromHash) {
215
+ return {
216
+ success: false,
217
+ error: "fromPath or fromHash required. Run memory list-shards to discover orphaned shards.",
218
+ };
219
+ }
220
+ return {
221
+ ...(await memoryClient.migrateProjectPath({
222
+ currentDirectory: context.directory,
223
+ fromPath: args.fromPath,
224
+ fromHash: args.fromHash,
225
+ dryRun: args.dryRun,
226
+ allowLinkedSource: args.allowLinkedSource,
227
+ })),
228
+ };
229
+ }
230
+ case "export": {
231
+ if (!args.outputPath)
232
+ return { success: false, error: "outputPath required" };
233
+ return { ...(await memoryClient.exportMemories(context.directory, args.outputPath)) };
234
+ }
235
+ case "import": {
236
+ if (!args.inputPath)
237
+ return { success: false, error: "inputPath required" };
238
+ return {
239
+ ...(await memoryClient.importMemories(context.directory, args.inputPath, args.dryRun)),
240
+ };
241
+ }
242
+ }
243
+ }
244
+ catch (error) {
245
+ return { success: false, error: String(error) };
246
+ }
247
+ }
@@ -0,0 +1,12 @@
1
+ export declare const RETRIEVAL_SECTION_TAG = "omms-retrieval";
2
+ /**
3
+ * Prompt-aware semantic retrieval shared by every host (Pi `before_agent_start`,
4
+ * OpenCode v2 `prompt`/`context` hooks): search the shared project memory with
5
+ * the incoming prompt and format a bounded context section (memory + user
6
+ * profile). Returns null when nothing relevant is found.
7
+ */
8
+ export declare function buildRetrievalSection(prompt: string, directory: string, sessionId: string): Promise<string | null>;
9
+ /** Delimit a retrieval section so it is recognisable as injected memory, not user text. */
10
+ export declare function wrapRetrievalSection(section: string): string;
11
+ /** Format a session's own memories for re-injection after compaction. */
12
+ export declare function formatMemoriesForCompaction(memories: any[]): string;
@@ -0,0 +1,72 @@
1
+ import { CONFIG } from "../config.js";
2
+ import { formatContextForPrompt } from "../services/context.js";
3
+ import { memoryClient } from "../services/client.js";
4
+ import { getTags } from "../services/tags.js";
5
+ export const RETRIEVAL_SECTION_TAG = "omms-retrieval";
6
+ /**
7
+ * Prompt-aware semantic retrieval shared by every host (Pi `before_agent_start`,
8
+ * OpenCode v2 `prompt`/`context` hooks): search the shared project memory with
9
+ * the incoming prompt and format a bounded context section (memory + user
10
+ * profile). Returns null when nothing relevant is found.
11
+ */
12
+ export async function buildRetrievalSection(prompt, directory, sessionId) {
13
+ if (!prompt.trim())
14
+ return null;
15
+ const tags = getTags(directory);
16
+ const search = await memoryClient.searchMemories(prompt, tags.project.tag);
17
+ if (!search.success)
18
+ return null;
19
+ const results = CONFIG.chatMessage.excludeCurrentSession
20
+ ? search.results.filter((result) => result.metadata?.sessionID !== sessionId)
21
+ : search.results;
22
+ if (results.length === 0)
23
+ return null;
24
+ const memoryContext = await formatContextForPrompt(tags.user.userEmail || null, {
25
+ results,
26
+ });
27
+ return memoryContext || null;
28
+ }
29
+ /** Delimit a retrieval section so it is recognisable as injected memory, not user text. */
30
+ export function wrapRetrievalSection(section) {
31
+ return `<${RETRIEVAL_SECTION_TAG}>\n${section}\n</${RETRIEVAL_SECTION_TAG}>`;
32
+ }
33
+ const EMBEDDED_TAGS_FOOTER_RE = /\n*Tags: ([^\n]*)\s*$/;
34
+ function normalizeTagsKey(tags) {
35
+ return tags
36
+ .map((tag) => tag.trim())
37
+ .filter((tag) => tag.length > 0)
38
+ .sort()
39
+ .join("\0");
40
+ }
41
+ function stripMatchingEmbeddedTagsFooter(memory, tags) {
42
+ const match = memory.match(EMBEDDED_TAGS_FOOTER_RE);
43
+ if (!match) {
44
+ return memory;
45
+ }
46
+ const footerValue = match[1];
47
+ if (footerValue === undefined) {
48
+ return memory;
49
+ }
50
+ const embeddedTags = footerValue
51
+ .split(",")
52
+ .map((tag) => tag.trim())
53
+ .filter((tag) => tag.length > 0);
54
+ if (normalizeTagsKey(embeddedTags) !== normalizeTagsKey(tags)) {
55
+ return memory;
56
+ }
57
+ return memory.replace(EMBEDDED_TAGS_FOOTER_RE, "");
58
+ }
59
+ /** Format a session's own memories for re-injection after compaction. */
60
+ export function formatMemoriesForCompaction(memories) {
61
+ let output = `## Restored Session Memory\n\n`;
62
+ memories.forEach((m, i) => {
63
+ const tags = Array.isArray(m.tags) ? m.tags : [];
64
+ const body = tags.length > 0 ? stripMatchingEmbeddedTagsFooter(m.memory ?? "", tags) : (m.memory ?? "");
65
+ output += `### Memory ${i + 1}\n`;
66
+ output += `${body}\n\n`;
67
+ if (tags.length > 0) {
68
+ output += `Tags: ${tags.join(", ")}\n\n`;
69
+ }
70
+ });
71
+ return output;
72
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Discovery of Pi session files for the historical importer. Read-only: only
3
+ * the first line (the session header) is parsed here, so discovery stays cheap
4
+ * and full parsing happens later per candidate session.
5
+ *
6
+ * Files that are not Pi session format (for example subagent artifacts, which
7
+ * use a different record format without a `type: "session"` header) are
8
+ * reported as unrecognized and skipped rather than treated as errors.
9
+ */
10
+ export declare const DEFAULT_PI_SESSION_ROOT: string;
11
+ export interface DiscoveredPiSession {
12
+ file: string;
13
+ sessionId: string | null;
14
+ cwd: string | null;
15
+ version: number | null;
16
+ /** Header ISO timestamp converted to epoch ms, when present. */
17
+ timestamp: number | null;
18
+ }
19
+ export interface UnrecognizedFile {
20
+ file: string;
21
+ reason: string;
22
+ }
23
+ export interface DiscoveryResult {
24
+ sessions: DiscoveredPiSession[];
25
+ unrecognized: UnrecognizedFile[];
26
+ }
27
+ export interface DiscoveryOptions {
28
+ root?: string;
29
+ maxSessions?: number;
30
+ }
31
+ export declare function discoverPiSessions(options?: DiscoveryOptions): DiscoveryResult;
@@ -0,0 +1,88 @@
1
+ import { readdirSync, readFileSync } from "node:fs";
2
+ import { existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { homedir } from "node:os";
5
+ /**
6
+ * Discovery of Pi session files for the historical importer. Read-only: only
7
+ * the first line (the session header) is parsed here, so discovery stays cheap
8
+ * and full parsing happens later per candidate session.
9
+ *
10
+ * Files that are not Pi session format (for example subagent artifacts, which
11
+ * use a different record format without a `type: "session"` header) are
12
+ * reported as unrecognized and skipped rather than treated as errors.
13
+ */
14
+ export const DEFAULT_PI_SESSION_ROOT = join(homedir(), ".pi", "agent", "sessions");
15
+ function listJsonlFiles(root) {
16
+ const files = [];
17
+ const stack = [root];
18
+ while (stack.length > 0) {
19
+ const dir = stack.pop();
20
+ let entries;
21
+ try {
22
+ entries = readdirSync(dir, { withFileTypes: true });
23
+ }
24
+ catch {
25
+ continue;
26
+ }
27
+ for (const entry of entries) {
28
+ const full = join(dir, entry.name);
29
+ if (entry.isDirectory()) {
30
+ stack.push(full);
31
+ }
32
+ else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
33
+ files.push(full);
34
+ }
35
+ }
36
+ }
37
+ return files.sort();
38
+ }
39
+ function readHeaderLine(file) {
40
+ let firstLine;
41
+ try {
42
+ const stream = readFileSync(file, "utf8");
43
+ const newline = stream.indexOf("\n");
44
+ firstLine = (newline === -1 ? stream : stream.slice(0, newline)).trim();
45
+ if (!firstLine)
46
+ return { error: "empty file" };
47
+ return { header: JSON.parse(firstLine) };
48
+ }
49
+ catch (error) {
50
+ if (error instanceof SyntaxError)
51
+ return { error: `malformed header: ${String(error)}` };
52
+ return { error: `unreadable: ${String(error)}` };
53
+ }
54
+ }
55
+ export function discoverPiSessions(options = {}) {
56
+ const root = options.root ?? DEFAULT_PI_SESSION_ROOT;
57
+ const sessions = [];
58
+ const unrecognized = [];
59
+ if (!existsSync(root)) {
60
+ return { sessions, unrecognized };
61
+ }
62
+ for (const file of listJsonlFiles(root)) {
63
+ const parsed = readHeaderLine(file);
64
+ if ("error" in parsed) {
65
+ unrecognized.push({ file, reason: parsed.error });
66
+ continue;
67
+ }
68
+ const header = parsed.header;
69
+ if (header?.type !== "session") {
70
+ unrecognized.push({ file, reason: "not a Pi session file (no session header)" });
71
+ continue;
72
+ }
73
+ const timestamp = typeof header.timestamp === "string" ? Date.parse(header.timestamp) : NaN;
74
+ sessions.push({
75
+ file,
76
+ sessionId: typeof header.id === "string" ? header.id : null,
77
+ cwd: typeof header.cwd === "string" ? header.cwd : null,
78
+ version: typeof header.version === "number" ? header.version : null,
79
+ timestamp: Number.isNaN(timestamp) ? null : timestamp,
80
+ });
81
+ }
82
+ // Chronological order, oldest first; files without a timestamp sort last.
83
+ sessions.sort((a, b) => (a.timestamp ?? Infinity) - (b.timestamp ?? Infinity));
84
+ const limited = options.maxSessions !== undefined && options.maxSessions >= 0
85
+ ? sessions.slice(0, options.maxSessions)
86
+ : sessions;
87
+ return { sessions: limited, unrecognized };
88
+ }
@@ -0,0 +1,87 @@
1
+ import type { CaptureSummaryProvider } from "../core/host.js";
2
+ import { type PiConversationWindow } from "../adapters/pi/conversation.js";
3
+ import { PiImportLedger } from "./ledger.js";
4
+ import type { LoadedPiSession } from "./session-loader.js";
5
+ /**
6
+ * Host-neutral orchestration for the Pi historical-session backfill.
7
+ *
8
+ * Per unit: deterministic key -> ledger state -> (reconcile | capture ->
9
+ * terminal ledger state). Per session: load through Pi's session model,
10
+ * resolve the recorded cwd through the shared project identity, select the
11
+ * active branch, and rebuild the same work units live capture would have
12
+ * produced. Source files are only ever read.
13
+ */
14
+ export interface ImportPathMap {
15
+ from: string;
16
+ to: string;
17
+ }
18
+ export interface ImportFilters {
19
+ scope: "current-project" | "all-projects";
20
+ currentDirectory: string;
21
+ session?: string;
22
+ /** Inclusive lower bound on the work unit's user-entry timestamp (epoch ms). */
23
+ since?: number;
24
+ /** Inclusive upper bound on the work unit's user-entry timestamp (epoch ms). */
25
+ until?: number;
26
+ maxSessions?: number;
27
+ force?: boolean;
28
+ dryRun?: boolean;
29
+ pathMaps?: ImportPathMap[];
30
+ root?: string;
31
+ }
32
+ export type ImportUnitStatus = "imported" | "would-import" | "skipped" | "failed" | "already-handled";
33
+ export interface ImportUnitReport {
34
+ key: string;
35
+ sessionId: string;
36
+ userEntryId: string;
37
+ promptPreview: string;
38
+ status: ImportUnitStatus;
39
+ reason?: string;
40
+ memoryId?: string;
41
+ }
42
+ export interface ImportProjectReport {
43
+ tag: string;
44
+ hash: string;
45
+ directory: string;
46
+ sessions: number;
47
+ units: number;
48
+ }
49
+ export interface ImportReport {
50
+ dryRun: boolean;
51
+ root: string;
52
+ sessionsDiscovered: number;
53
+ sessionsLoaded: number;
54
+ sessionsFilteredOut: number;
55
+ sessionsUnrecognized: number;
56
+ unresolvableSessions: Array<{
57
+ file: string;
58
+ cwd: string | null;
59
+ }>;
60
+ loadErrors: Array<{
61
+ file: string;
62
+ error: string;
63
+ }>;
64
+ unitsTotal: number;
65
+ unitsImported: number;
66
+ unitsWouldImport: number;
67
+ unitsSkipped: number;
68
+ unitsFailed: number;
69
+ unitsAlreadyHandled: number;
70
+ skipReasons: Record<string, number>;
71
+ projects: ImportProjectReport[];
72
+ units: ImportUnitReport[];
73
+ }
74
+ export interface ImporterDeps {
75
+ loadSession: (file: string) => LoadedPiSession;
76
+ provider: CaptureSummaryProvider;
77
+ onProgress?: (processed: number, total: number, promptPreview: string) => void;
78
+ ledger?: PiImportLedger;
79
+ }
80
+ export declare function buildImportKey(sessionId: string, window: PiConversationWindow): string | null;
81
+ /**
82
+ * Find a stored memory by its deterministic import identity. Used to
83
+ * reconcile crash windows: the memory insert landed but the ledger update did
84
+ * not commit.
85
+ */
86
+ export declare function findMemoryIdByImportId(projectHash: string, importId: string): Promise<string | null>;
87
+ export declare function importPiHistory(deps: ImporterDeps, filters: ImportFilters): Promise<ImportReport>;