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,147 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { CONFIG } from "../../config.js";
3
+ import { buildBoundedSummaryPrompt } from "../../core/capture-context.js";
4
+ import { log } from "../../services/logger.js";
5
+ import { loadOpencodeProvider } from "../../services/ai/opencode-provider-loader.js";
6
+ function buildSystemPrompt(languageName) {
7
+ return `You are a technical memory recorder for a software development project.
8
+
9
+ RULES:
10
+ 1. ONLY capture technical work (code, bugs, features, architecture, config)
11
+ 2. SKIP non-technical by returning type="skip"
12
+ 3. NO meta-commentary or behavior analysis
13
+ 4. Include specific file names, functions, technical details
14
+ 5. Generate 2-4 technical tags (e.g., "react", "auth", "bug-fix")
15
+ 6. You MUST write the summary in ${languageName}.
16
+
17
+ FORMAT:
18
+ ## Request
19
+ [1-2 sentences: what was requested, in ${languageName}]
20
+
21
+ ## Outcome
22
+ [1-2 sentences: what was done, include files/functions, in ${languageName}]
23
+
24
+ SKIP if: greetings, casual chat, no code/decisions made
25
+ CAPTURE if: code changed, bug fixed, feature added, decision made`;
26
+ }
27
+ export async function generateOpenCodeAutoCaptureSummary(request, notify) {
28
+ let opencodeProviderError;
29
+ if (CONFIG.opencodeProvider && CONFIG.opencodeModel) {
30
+ try {
31
+ if (CONFIG.memoryModel) {
32
+ log("opencodeProvider takes precedence over memoryModel for auto-capture");
33
+ }
34
+ const { isProviderConnected, getV2Client, generateStructuredOutput } = await loadOpencodeProvider();
35
+ let providerID = CONFIG.opencodeProvider;
36
+ let modelID = CONFIG.opencodeModel;
37
+ if (modelID === "inherit") {
38
+ if (!request.prompt?.providerId || !request.prompt?.modelId) {
39
+ throw new Error("omms: opencodeModel is 'inherit' but no session model was recorded for this prompt");
40
+ }
41
+ providerID = request.prompt.providerId;
42
+ modelID = request.prompt.modelId;
43
+ }
44
+ if (!isProviderConnected(providerID)) {
45
+ throw new Error(`opencode provider '${providerID}' is not connected. Check your opencode provider configuration.`);
46
+ }
47
+ const v2Client = getV2Client();
48
+ if (!v2Client) {
49
+ throw new Error("omms: v2 client not initialized; cannot perform structured-output capture");
50
+ }
51
+ const { detectLanguage, getLanguageName } = await import("../../services/language-detector.js");
52
+ const targetLang = CONFIG.autoCaptureLanguage === "auto" || !CONFIG.autoCaptureLanguage
53
+ ? detectLanguage(request.userPrompt)
54
+ : CONFIG.autoCaptureLanguage;
55
+ const systemPrompt = buildSystemPrompt(getLanguageName(targetLang));
56
+ const { z } = await import("zod");
57
+ const schema = z.object({
58
+ summary: z.string(),
59
+ type: z.string(),
60
+ tags: z.array(z.string()),
61
+ });
62
+ const aiPrompt = buildBoundedSummaryPrompt(request.context, systemPrompt, z.toJSONSchema(schema));
63
+ const result = await generateStructuredOutput({
64
+ client: v2Client,
65
+ providerID,
66
+ modelID,
67
+ systemPrompt,
68
+ userPrompt: aiPrompt,
69
+ schema,
70
+ });
71
+ return {
72
+ summary: result.summary,
73
+ type: result.type,
74
+ tags: (result.tags || []).map((tag) => tag.toLowerCase().trim()),
75
+ };
76
+ }
77
+ catch (error) {
78
+ opencodeProviderError = error;
79
+ log("auto-capture: opencode provider failed, falling back to external API", {
80
+ error: String(error),
81
+ });
82
+ }
83
+ }
84
+ if (!CONFIG.memoryModel || !CONFIG.memoryApiUrl) {
85
+ if (opencodeProviderError)
86
+ throw opencodeProviderError;
87
+ throw new Error("External API not configured for auto-capture");
88
+ }
89
+ if (opencodeProviderError && CONFIG.showErrorToasts && notify) {
90
+ const errMsg = opencodeProviderError instanceof Error
91
+ ? opencodeProviderError.message
92
+ : String(opencodeProviderError);
93
+ const shortReason = errMsg.length > 100 ? errMsg.substring(0, 100) + "..." : errMsg;
94
+ await Promise.resolve(notify({
95
+ title: "Using fallback provider",
96
+ message: `OpenCode provider failed (${shortReason}); using configured fallback.`,
97
+ variant: "warning",
98
+ duration: 5000,
99
+ })).catch(() => { });
100
+ }
101
+ const { AIProviderFactory } = await import("../../services/ai/ai-provider-factory.js");
102
+ const { buildMemoryProviderConfig } = await import("../../services/ai/provider-config.js");
103
+ const { detectLanguage, getLanguageName } = await import("../../services/language-detector.js");
104
+ const providerConfig = buildMemoryProviderConfig(CONFIG);
105
+ const provider = AIProviderFactory.createProvider(CONFIG.memoryProvider, providerConfig);
106
+ const targetLang = CONFIG.autoCaptureLanguage === "auto" || !CONFIG.autoCaptureLanguage
107
+ ? detectLanguage(request.userPrompt)
108
+ : CONFIG.autoCaptureLanguage;
109
+ const systemPrompt = buildSystemPrompt(getLanguageName(targetLang));
110
+ const toolSchema = {
111
+ type: "function",
112
+ function: {
113
+ name: "save_memory",
114
+ description: "Save the conversation summary as a memory",
115
+ parameters: {
116
+ type: "object",
117
+ properties: {
118
+ summary: {
119
+ type: "string",
120
+ description: "Markdown-formatted summary of the conversation",
121
+ },
122
+ type: {
123
+ type: "string",
124
+ description: "Type of memory: 'skip' for non-technical conversations, or technical type (feature, bug-fix, refactor, analysis, configuration, discussion, other)",
125
+ },
126
+ tags: {
127
+ type: "array",
128
+ items: { type: "string" },
129
+ description: "List of 2-4 technical tags related to the memory",
130
+ },
131
+ },
132
+ required: ["summary", "type", "tags"],
133
+ },
134
+ },
135
+ };
136
+ const aiPrompt = buildBoundedSummaryPrompt(request.context, systemPrompt, toolSchema);
137
+ const captureSessionID = `auto-capture-${request.prompt?.id ?? request.sessionId}-${randomUUID()}`;
138
+ const result = await provider.executeToolCall(systemPrompt, aiPrompt, toolSchema, captureSessionID);
139
+ if (!result.success || !result.data) {
140
+ throw new Error(result.error || "Failed to generate summary");
141
+ }
142
+ return {
143
+ summary: result.data.summary,
144
+ type: result.data.type,
145
+ tags: (result.data.tags || []).map((tag) => tag.toLowerCase().trim()),
146
+ };
147
+ }
@@ -0,0 +1,2 @@
1
+ import type { CaptureConversation } from "../../core/host.js";
2
+ export declare function extractOpenCodeConversation(messages: any[], promptMessageId: string): CaptureConversation | null;
@@ -0,0 +1,50 @@
1
+ const MAX_TOOL_INPUT_LENGTH = 100;
2
+ export function extractOpenCodeConversation(messages, promptMessageId) {
3
+ const promptIndex = messages.findIndex((message) => message.info?.id === promptMessageId);
4
+ if (promptIndex === -1)
5
+ return null;
6
+ const responseMessages = [];
7
+ for (const message of messages.slice(promptIndex + 1)) {
8
+ if (message.info?.role === "user")
9
+ break;
10
+ responseMessages.push(message);
11
+ }
12
+ const textResponses = [];
13
+ const toolCalls = [];
14
+ const sourceEntryIds = [];
15
+ for (const message of responseMessages) {
16
+ if (message.info?.role !== "assistant")
17
+ continue;
18
+ if (typeof message.info?.id === "string")
19
+ sourceEntryIds.push(message.info.id);
20
+ if (!Array.isArray(message.parts))
21
+ continue;
22
+ const textParts = message.parts.filter((part) => part.type === "text" && typeof part.text === "string" && part.text.trim());
23
+ if (textParts.length > 0) {
24
+ textResponses.push(textParts
25
+ .map((part) => part.text)
26
+ .join("\n")
27
+ .trim());
28
+ }
29
+ for (const tool of message.parts.filter((part) => part.type === "tool")) {
30
+ const name = tool.tool || "unknown";
31
+ let input = "";
32
+ if (tool.state?.input) {
33
+ const inputValue = tool.state.input;
34
+ if (typeof inputValue === "string") {
35
+ input = inputValue;
36
+ }
37
+ else if (typeof inputValue === "object") {
38
+ input = Object.entries(inputValue)
39
+ .map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
40
+ .join(", ");
41
+ }
42
+ }
43
+ if (input.length > MAX_TOOL_INPUT_LENGTH) {
44
+ input = input.substring(0, MAX_TOOL_INPUT_LENGTH) + "...";
45
+ }
46
+ toolCalls.push({ name, input });
47
+ }
48
+ }
49
+ return { textResponses, toolCalls, sourceEntryIds };
50
+ }
@@ -0,0 +1,9 @@
1
+ export declare function isStructuredSummaryPromptMessage(userMessage: string): boolean;
2
+ /** True when a prompt is omms's own internal traffic and must not be recorded or searched. */
3
+ export declare function isInternalPrompt(sessionID: string, userMessage: string): boolean;
4
+ /**
5
+ * Record a real user prompt so idle auto-capture can pair it with the session's
6
+ * response. Shared by the V1 `chat.message` hook and the v2 `prompt` hook.
7
+ * Returns false when the prompt is empty or internal and was not recorded.
8
+ */
9
+ export declare function recordUserPrompt(sessionID: string, messageID: string, directory: string, userMessage: string): Promise<boolean>;
@@ -0,0 +1,26 @@
1
+ import { isInternalStructuredSession } from "../../services/ai/opencode-provider.js";
2
+ import { userPromptManager } from "../../services/user-prompt/user-prompt-manager.js";
3
+ export function isStructuredSummaryPromptMessage(userMessage) {
4
+ // This is the plugin's own structured-summary or profile-analysis request.
5
+ // OpenCode echoes it through chat.message like a normal user message, but
6
+ // capturing it would create self-referential memories / an infinite learning loop.
7
+ if (userMessage.includes("# User Profile Analysis")) {
8
+ return true;
9
+ }
10
+ return userMessage.includes("Analyze this conversation.") && userMessage.includes('type="skip"');
11
+ }
12
+ /** True when a prompt is omms's own internal traffic and must not be recorded or searched. */
13
+ export function isInternalPrompt(sessionID, userMessage) {
14
+ return isStructuredSummaryPromptMessage(userMessage) || isInternalStructuredSession(sessionID);
15
+ }
16
+ /**
17
+ * Record a real user prompt so idle auto-capture can pair it with the session's
18
+ * response. Shared by the V1 `chat.message` hook and the v2 `prompt` hook.
19
+ * Returns false when the prompt is empty or internal and was not recorded.
20
+ */
21
+ export async function recordUserPrompt(sessionID, messageID, directory, userMessage) {
22
+ if (!userMessage.trim() || isInternalPrompt(sessionID, userMessage))
23
+ return false;
24
+ await userPromptManager.savePrompt(sessionID, messageID, directory, userMessage);
25
+ return true;
26
+ }
@@ -0,0 +1,41 @@
1
+ import type { CaptureSummaryProvider } from "../../core/host.js";
2
+ import { type PiSessionEntry } from "./conversation.js";
3
+ export interface PiCaptureState {
4
+ /** User entry IDs terminally handled by a settled capture for this session. */
5
+ handledUserEntries: Set<string>;
6
+ running: boolean;
7
+ }
8
+ export declare function createPiCaptureState(): PiCaptureState;
9
+ export interface PiSettledCaptureInput {
10
+ sessionId: string;
11
+ directory: string;
12
+ entries: PiSessionEntry[];
13
+ provider: CaptureSummaryProvider;
14
+ state: PiCaptureState;
15
+ prompt?: {
16
+ providerId?: string | null;
17
+ modelId?: string | null;
18
+ };
19
+ notify?: (notification: {
20
+ title: string;
21
+ message: string;
22
+ variant: "success" | "warning" | "error" | "info";
23
+ }) => Promise<void> | void;
24
+ }
25
+ export type PiSettledCaptureResult = {
26
+ status: "captured";
27
+ memoryId: string;
28
+ } | {
29
+ status: "skipped";
30
+ reason: "disabled" | "busy" | "not-ready" | "no-window" | "already-handled" | "extractor-skip";
31
+ } | {
32
+ status: "failed";
33
+ error: string;
34
+ };
35
+ /**
36
+ * Capture the work unit completed by the most recent user prompt on the
37
+ * settled branch. Safe to call for every `agent_settled` event: a work unit is
38
+ * processed at most once per user entry ID (live source identity), so retries,
39
+ * compaction continuation, and repeated settled events never double-capture.
40
+ */
41
+ export declare function capturePiSettledWorkUnit(input: PiSettledCaptureInput): Promise<PiSettledCaptureResult>;
@@ -0,0 +1,91 @@
1
+ import { CONFIG } from "../../config.js";
2
+ import { captureConversation } from "../../core/capture.js";
3
+ import { log } from "../../services/logger.js";
4
+ import { memoryClient } from "../../services/client.js";
5
+ import { extractPiConversation } from "./conversation.js";
6
+ export function createPiCaptureState() {
7
+ return { handledUserEntries: new Set(), running: false };
8
+ }
9
+ /**
10
+ * Capture the work unit completed by the most recent user prompt on the
11
+ * settled branch. Safe to call for every `agent_settled` event: a work unit is
12
+ * processed at most once per user entry ID (live source identity), so retries,
13
+ * compaction continuation, and repeated settled events never double-capture.
14
+ */
15
+ export async function capturePiSettledWorkUnit(input) {
16
+ if (!CONFIG.autoCaptureEnabled)
17
+ return { status: "skipped", reason: "disabled" };
18
+ if (input.state.running)
19
+ return { status: "skipped", reason: "busy" };
20
+ input.state.running = true;
21
+ try {
22
+ const window = extractPiConversation(input.entries);
23
+ if (!window)
24
+ return { status: "skipped", reason: "no-window" };
25
+ if (input.state.handledUserEntries.has(window.userEntryId)) {
26
+ return { status: "skipped", reason: "already-handled" };
27
+ }
28
+ const embeddingInitError = memoryClient.getEmbeddingInitError?.();
29
+ if (embeddingInitError) {
30
+ return { status: "skipped", reason: "not-ready" };
31
+ }
32
+ const captureResult = await captureConversation({
33
+ host: "pi",
34
+ hostSessionId: input.sessionId,
35
+ sourceType: "live-capture",
36
+ projectDirectory: input.directory,
37
+ userPrompt: window.userPrompt,
38
+ promptId: window.userEntryId,
39
+ prompt: {
40
+ id: window.userEntryId,
41
+ providerId: input.prompt?.providerId ?? null,
42
+ modelId: input.prompt?.modelId ?? null,
43
+ },
44
+ textResponses: window.textResponses,
45
+ toolCalls: window.toolCalls,
46
+ sourceEntryIds: window.sourceEntryIds,
47
+ ...(window.sourceTimestamp !== undefined
48
+ ? { sourceTimestamp: window.sourceTimestamp }
49
+ : {}),
50
+ }, input.provider);
51
+ input.state.handledUserEntries.add(window.userEntryId);
52
+ if (captureResult.status === "skipped") {
53
+ log("Pi auto-capture skipped by extractor", {
54
+ sessionID: input.sessionId,
55
+ userEntryId: window.userEntryId,
56
+ type: captureResult.type,
57
+ });
58
+ return { status: "skipped", reason: "extractor-skip" };
59
+ }
60
+ log("Pi auto-capture memory persisted", {
61
+ sessionID: input.sessionId,
62
+ userEntryId: window.userEntryId,
63
+ memoryId: captureResult.memoryId,
64
+ host: "pi",
65
+ });
66
+ if (CONFIG.showAutoCaptureToasts && input.notify) {
67
+ await Promise.resolve(input.notify({
68
+ title: "Memory Captured",
69
+ message: "Project memory saved from conversation",
70
+ variant: "success",
71
+ })).catch(() => { });
72
+ }
73
+ return { status: "captured", memoryId: captureResult.memoryId };
74
+ }
75
+ catch (error) {
76
+ const message = error instanceof Error ? error.message : String(error);
77
+ log(`Pi auto-capture error: ${message}`, { sessionID: input.sessionId });
78
+ if (CONFIG.showErrorToasts && input.notify) {
79
+ const shortReason = message.length > 100 ? message.substring(0, 100) + "..." : message;
80
+ await Promise.resolve(input.notify({
81
+ title: "Auto Capture Failed",
82
+ message: shortReason,
83
+ variant: "error",
84
+ })).catch(() => { });
85
+ }
86
+ return { status: "failed", error: message };
87
+ }
88
+ finally {
89
+ input.state.running = false;
90
+ }
91
+ }
@@ -0,0 +1,38 @@
1
+ import type { CaptureConversation } from "../../core/host.js";
2
+ /**
3
+ * Minimal structural view of Pi session entries (see Pi session-format docs).
4
+ * Kept structural so the normaliser stays testable without importing Pi types.
5
+ */
6
+ export interface PiSessionEntry {
7
+ type: string;
8
+ id: string;
9
+ parentId: string | null;
10
+ timestamp: string;
11
+ message?: {
12
+ role?: string;
13
+ content?: string | PiContentBlock[];
14
+ };
15
+ }
16
+ export interface PiContentBlock {
17
+ type: string;
18
+ text?: string;
19
+ thinking?: string;
20
+ name?: string;
21
+ arguments?: Record<string, unknown>;
22
+ }
23
+ export interface PiConversationWindow extends CaptureConversation {
24
+ userEntryId: string;
25
+ userPrompt: string;
26
+ userTimestamp?: number;
27
+ }
28
+ /**
29
+ * All capture windows on the branch, oldest first: one per user prompt that
30
+ * produced assistant or tool work. Used by live capture (last window) and by
31
+ * the historical importer (all windows).
32
+ */
33
+ export declare function extractPiConversationWindows(entries: PiSessionEntry[]): PiConversationWindow[];
34
+ /**
35
+ * The capture window of the most recent user prompt on the branch. Returns
36
+ * null when the branch has no user prompt with a response window.
37
+ */
38
+ export declare function extractPiConversation(entries: PiSessionEntry[]): PiConversationWindow | null;
@@ -0,0 +1,118 @@
1
+ const MAX_TOOL_INPUT_LENGTH = 100;
2
+ function entryText(content) {
3
+ if (typeof content === "string")
4
+ return content;
5
+ if (!Array.isArray(content))
6
+ return "";
7
+ return content
8
+ .filter((block) => block.type === "text" && typeof block.text === "string")
9
+ .map((block) => block.text)
10
+ .join("\n")
11
+ .trim();
12
+ }
13
+ function isUserEntry(entry) {
14
+ return entry.type === "message" && entry.message?.role === "user";
15
+ }
16
+ function isAssistantEntry(entry) {
17
+ return entry.type === "message" && entry.message?.role === "assistant";
18
+ }
19
+ function formatToolInput(args) {
20
+ if (!args)
21
+ return "";
22
+ return Object.entries(args)
23
+ .map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
24
+ .join(", ");
25
+ }
26
+ function boundToolInput(input) {
27
+ if (input.length > MAX_TOOL_INPUT_LENGTH) {
28
+ return input.substring(0, MAX_TOOL_INPUT_LENGTH) + "...";
29
+ }
30
+ return input;
31
+ }
32
+ /**
33
+ * Build the capture window for the user entry at `userIndex`: only that
34
+ * prompt's assistant/tool work, stopping at the next user message. Hidden
35
+ * thinking blocks are never included. Tool inputs are bounded by the same
36
+ * truncation policy as the OpenCode adapter.
37
+ *
38
+ * Returns null when the prompt is empty or has no assistant/tool response
39
+ * window yet.
40
+ */
41
+ function buildWindow(entries, userIndex) {
42
+ const userEntry = entries[userIndex];
43
+ const userPrompt = entryText(userEntry.message?.content);
44
+ if (!userPrompt)
45
+ return null;
46
+ const textResponses = [];
47
+ const toolCalls = [];
48
+ const sourceEntryIds = [];
49
+ let sourceTimestamp;
50
+ for (const entry of entries.slice(userIndex + 1)) {
51
+ if (isUserEntry(entry))
52
+ break;
53
+ if (!isAssistantEntry(entry))
54
+ continue;
55
+ if (typeof entry.id === "string")
56
+ sourceEntryIds.push(entry.id);
57
+ const timestampMs = Date.parse(entry.timestamp);
58
+ if (!Number.isNaN(timestampMs))
59
+ sourceTimestamp = timestampMs;
60
+ const blocks = entry.message?.content;
61
+ if (!Array.isArray(blocks))
62
+ continue;
63
+ const textParts = blocks.filter((block) => block.type === "text" && typeof block.text === "string" && block.text.trim());
64
+ if (textParts.length > 0) {
65
+ textResponses.push(textParts
66
+ .map((block) => block.text)
67
+ .join("\n")
68
+ .trim());
69
+ }
70
+ for (const block of blocks.filter((item) => item.type === "toolCall")) {
71
+ const name = typeof block.name === "string" ? block.name : "unknown";
72
+ toolCalls.push({
73
+ name,
74
+ input: boundToolInput(formatToolInput(block.arguments)),
75
+ });
76
+ }
77
+ }
78
+ if (textResponses.length === 0 && toolCalls.length === 0)
79
+ return null;
80
+ const userTimestampMs = Date.parse(userEntry.timestamp);
81
+ return {
82
+ userEntryId: userEntry.id,
83
+ userPrompt,
84
+ textResponses,
85
+ toolCalls,
86
+ sourceEntryIds,
87
+ ...(sourceTimestamp !== undefined ? { sourceTimestamp } : {}),
88
+ ...(Number.isNaN(userTimestampMs) ? {} : { userTimestamp: userTimestampMs }),
89
+ };
90
+ }
91
+ /**
92
+ * All capture windows on the branch, oldest first: one per user prompt that
93
+ * produced assistant or tool work. Used by live capture (last window) and by
94
+ * the historical importer (all windows).
95
+ */
96
+ export function extractPiConversationWindows(entries) {
97
+ const windows = [];
98
+ for (let i = 0; i < entries.length; i++) {
99
+ if (!isUserEntry(entries[i]))
100
+ continue;
101
+ const window = buildWindow(entries, i);
102
+ if (window)
103
+ windows.push(window);
104
+ }
105
+ return windows;
106
+ }
107
+ /**
108
+ * The capture window of the most recent user prompt on the branch. Returns
109
+ * null when the branch has no user prompt with a response window.
110
+ */
111
+ export function extractPiConversation(entries) {
112
+ for (let i = entries.length - 1; i >= 0; i--) {
113
+ if (!isUserEntry(entries[i]))
114
+ continue;
115
+ return buildWindow(entries, i);
116
+ }
117
+ return null;
118
+ }
@@ -0,0 +1,18 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ /**
3
+ * Pi extension entry point for the shared omms engine.
4
+ *
5
+ * Lifecycle mapping:
6
+ * - `session_start` -> load shared config for ctx.cwd, warm storage
7
+ * - `before_agent_start` -> prompt-aware semantic retrieval, injected as a
8
+ * delimited system-prompt section (never a fake
9
+ * user message)
10
+ * - `agent_settled` -> automatic capture of the settled work unit
11
+ * - `session_shutdown` -> idempotent session-scoped cleanup
12
+ * - `memory` tool -> shared add/search/profile/list/forget/help and
13
+ * portability operations
14
+ *
15
+ * Pi types are imported type-only: the compiled extension carries no Pi
16
+ * runtime dependency and uses the host Pi runtime provided at load time.
17
+ */
18
+ export default function ommsPiExtension(pi: ExtensionAPI): void;