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
package/dist/index.js ADDED
@@ -0,0 +1,601 @@
1
+ import { tool } from "@opencode-ai/plugin";
2
+ import { memoryClient } from "./services/client.js";
3
+ import { formatContextForPrompt } from "./services/context.js";
4
+ import { getTags } from "./services/tags.js";
5
+ import { performAutoCapture } from "./services/auto-capture.js";
6
+ import { createOpenCodeAutoCaptureHost } from "./adapters/opencode/auto-capture-host.js";
7
+ import { isInternalPrompt, isStructuredSummaryPromptMessage, recordUserPrompt, } from "./adapters/opencode/user-prompt.js";
8
+ import { executeMemoryOperation } from "./core/memory-operations.js";
9
+ import { formatMemoriesForCompaction } from "./core/retrieval.js";
10
+ import { performUserProfileLearning } from "./services/user-memory-learning.js";
11
+ import { userPromptManager } from "./services/user-prompt/user-prompt-manager.js";
12
+ import { startWebServer, WebServer } from "./services/web-server.js";
13
+ import { ensureTursoReady } from "./services/turso/ready.js";
14
+ import { tursoConnectionManager } from "./services/turso/connection-manager.js";
15
+ import { WebAuth } from "./services/web-auth.js";
16
+ import { isConfigured, CONFIG, initConfigWithLegacyMigration } from "./config.js";
17
+ import { log } from "./services/logger.js";
18
+ import { getLanguageName } from "./services/language-detector.js";
19
+ import { getHostClientConfig } from "./services/ai/opencode-host-config.js";
20
+ import { loadOpencodeProvider } from "./services/ai/opencode-provider-loader.js";
21
+ import { STRUCTURED_OUTPUT_AGENT, STRUCTURED_OUTPUT_TOOLS, } from "./services/ai/opencode-provider.js";
22
+ import { INTERNAL_CAPTURE_SESSION_TITLE, isInternalCaptureSessionTitle, isTrackedInternalCaptureSession, } from "./services/ai/internal-capture-sessions.js";
23
+ export { INTERNAL_CAPTURE_SESSION_TITLE, isInternalCaptureSessionTitle };
24
+ export { isStructuredSummaryPromptMessage };
25
+ function extractSessionTitle(response) {
26
+ if (!response || typeof response !== "object")
27
+ return undefined;
28
+ const obj = response;
29
+ return obj.data?.title ?? obj.title;
30
+ }
31
+ function unwrapSdkData(response) {
32
+ if (!response || typeof response !== "object")
33
+ return undefined;
34
+ const obj = response;
35
+ return (obj.data ?? response);
36
+ }
37
+ /**
38
+ * Resolve the session's active agent so compaction memory injection does not
39
+ * reset OpenCode to the stock "general-purpose" fallback (issue #236).
40
+ *
41
+ * Preference order:
42
+ * 1. session.get().agent (v2 hosts)
43
+ * 2. Latest non-compaction user message agent
44
+ * 3. Latest non-compaction / non-summary assistant mode (v1) or agent (v2)
45
+ */
46
+ export async function resolveSessionAgent(client, sessionID) {
47
+ const sessionClient = client?.session;
48
+ if (typeof sessionClient?.get === "function") {
49
+ try {
50
+ const session = unwrapSdkData(await sessionClient.get({ path: { id: sessionID } }));
51
+ if (typeof session?.agent === "string" && session.agent.trim()) {
52
+ return session.agent.trim();
53
+ }
54
+ }
55
+ catch (error) {
56
+ log("resolveSessionAgent: session.get failed", { sessionID, error: String(error) });
57
+ }
58
+ }
59
+ if (typeof sessionClient?.messages !== "function") {
60
+ return undefined;
61
+ }
62
+ try {
63
+ const messages = unwrapSdkData(await sessionClient.messages({ path: { id: sessionID } }));
64
+ if (!Array.isArray(messages))
65
+ return undefined;
66
+ for (let i = messages.length - 1; i >= 0; i--) {
67
+ const info = messages[i]?.info;
68
+ if (!info)
69
+ continue;
70
+ if (info.role === "user") {
71
+ if (typeof info.agent === "string" && info.agent.trim()) {
72
+ return info.agent.trim();
73
+ }
74
+ continue;
75
+ }
76
+ if (info.role === "assistant") {
77
+ if (info.summary === true || info.mode === "compaction")
78
+ continue;
79
+ const agent = (typeof info.agent === "string" && info.agent.trim()) ||
80
+ (typeof info.mode === "string" && info.mode.trim()) ||
81
+ undefined;
82
+ if (agent)
83
+ return agent;
84
+ }
85
+ }
86
+ }
87
+ catch (error) {
88
+ log("resolveSessionAgent: session.messages failed", { sessionID, error: String(error) });
89
+ }
90
+ return undefined;
91
+ }
92
+ async function isInternalCaptureSession(client, sessionID) {
93
+ // Fast path: sessions we created ourselves (survives brief post-delete window).
94
+ if (isTrackedInternalCaptureSession(sessionID)) {
95
+ return true;
96
+ }
97
+ const sessionClient = client?.session;
98
+ // Plugin host client uses path-based args (same as session.messages).
99
+ if (typeof sessionClient?.get === "function") {
100
+ try {
101
+ const response = await sessionClient.get({ path: { id: sessionID } });
102
+ const title = extractSessionTitle(response);
103
+ if (isInternalCaptureSessionTitle(title)) {
104
+ return true;
105
+ }
106
+ log("internal capture session check via session.get", {
107
+ sessionID,
108
+ title: title ?? null,
109
+ matched: false,
110
+ });
111
+ }
112
+ catch (error) {
113
+ log("internal capture session check via session.get failed", {
114
+ sessionID,
115
+ error: String(error),
116
+ });
117
+ }
118
+ }
119
+ else {
120
+ log("internal capture session check: session.get unavailable", { sessionID });
121
+ }
122
+ return false;
123
+ }
124
+ /** Least-privilege agent used only by internal structured-output sessions (issue #189). */
125
+ export function applyStructuredOutputAgentConfig(cfg) {
126
+ cfg.agent = {
127
+ ...cfg.agent,
128
+ [STRUCTURED_OUTPUT_AGENT]: {
129
+ description: "Internal least-privilege agent for omms structured output",
130
+ mode: "subagent",
131
+ // OpenCode reads `steps` at runtime; SDK AgentConfig also documents maxSteps.
132
+ steps: 2,
133
+ maxSteps: 2,
134
+ tools: STRUCTURED_OUTPUT_TOOLS,
135
+ permission: {
136
+ "*": "deny",
137
+ StructuredOutput: "allow",
138
+ },
139
+ },
140
+ };
141
+ }
142
+ export async function configureOpencodeHostTransport(ctx) {
143
+ const { createV2Client, resetHostFetch, setHostFetch, setV2Client } = await loadOpencodeProvider();
144
+ resetHostFetch();
145
+ const hostConfig = getHostClientConfig(ctx);
146
+ if (hostConfig.fetch) {
147
+ setHostFetch(hostConfig.fetch);
148
+ }
149
+ else {
150
+ log("OpenCode host fetch unavailable; falling back to global fetch", {
151
+ clientKeys: hostConfig.clientKeys,
152
+ sdkConfigCount: hostConfig.sdkConfigCount,
153
+ });
154
+ }
155
+ const serverUrl = hostConfig.baseUrl ?? ctx.serverUrl;
156
+ if (serverUrl) {
157
+ setV2Client(createV2Client(serverUrl, {
158
+ fetch: hostConfig.fetch,
159
+ headers: hostConfig.headers,
160
+ }));
161
+ }
162
+ }
163
+ function logAutoCaptureProviderStatus() {
164
+ if (!CONFIG.autoCaptureEnabled || CONFIG.autoCaptureProviderStatus.ready)
165
+ return;
166
+ log(`Auto-capture disabled by configuration. Issues: ${CONFIG.autoCaptureProviderStatus.issues.join("; ")}.`);
167
+ }
168
+ export const OmmsPlugin = async (ctx) => {
169
+ const { directory } = ctx;
170
+ initConfigWithLegacyMigration(directory);
171
+ logAutoCaptureProviderStatus();
172
+ const tags = getTags(directory);
173
+ const autoCaptureHost = createOpenCodeAutoCaptureHost(ctx);
174
+ let webServer = null;
175
+ let idleTimeout = null;
176
+ const GLOBAL_PLUGIN_WARMUP_KEY = Symbol.for("omms.plugin.warmedup");
177
+ if (!globalThis[GLOBAL_PLUGIN_WARMUP_KEY] && isConfigured()) {
178
+ // Fire-and-forget: DB ready + embedding model must not block plugin init.
179
+ (async () => {
180
+ try {
181
+ await memoryClient.warmup();
182
+ globalThis[GLOBAL_PLUGIN_WARMUP_KEY] = true;
183
+ }
184
+ catch (error) {
185
+ log("Plugin memory warmup failed", { error: String(error) });
186
+ }
187
+ })();
188
+ }
189
+ await configureOpencodeHostTransport(ctx);
190
+ (async () => {
191
+ try {
192
+ const providerResult = await ctx.client.provider.list();
193
+ if (providerResult.data?.connected) {
194
+ const { setConnectedProviders } = await loadOpencodeProvider();
195
+ setConnectedProviders(providerResult.data.connected);
196
+ log("opencode providers connected", {
197
+ list: providerResult.data.connected,
198
+ configured: CONFIG.opencodeProvider || "(not set)",
199
+ });
200
+ }
201
+ else {
202
+ log("opencode provider list empty or failed", {
203
+ data: JSON.stringify(providerResult.data).substring(0, 100),
204
+ });
205
+ }
206
+ }
207
+ catch (error) {
208
+ log("Failed to initialize opencode provider state", { error: String(error) });
209
+ }
210
+ })();
211
+ let tursoReadyForWeb = !isConfigured();
212
+ if (CONFIG.webServerEnabled && isConfigured()) {
213
+ try {
214
+ await ensureTursoReady();
215
+ tursoReadyForWeb = true;
216
+ }
217
+ catch (error) {
218
+ log("Turso ready gate failed before web server start", { error: String(error) });
219
+ if (ctx.client?.tui) {
220
+ ctx.client.tui
221
+ .showToast({
222
+ body: {
223
+ title: "Memory Explorer",
224
+ message: "Database migration failed; web UI not started",
225
+ variant: "error",
226
+ duration: 8000,
227
+ },
228
+ })
229
+ .catch(() => { });
230
+ }
231
+ }
232
+ }
233
+ if (CONFIG.webServerEnabled && tursoReadyForWeb) {
234
+ const webAuth = new WebAuth({
235
+ password: CONFIG.webServerAuthPassword,
236
+ username: CONFIG.webServerAuthUsername,
237
+ });
238
+ startWebServer({
239
+ port: CONFIG.webServerPort,
240
+ host: CONFIG.webServerHost,
241
+ enabled: CONFIG.webServerEnabled,
242
+ auth: webAuth,
243
+ apiToken: CONFIG.webServerApiToken,
244
+ })
245
+ .then((server) => {
246
+ webServer = server;
247
+ const url = webServer.getUrl();
248
+ webServer.setOnTakeoverCallback(async () => {
249
+ if (ctx.client?.tui) {
250
+ ctx.client.tui
251
+ .showToast({
252
+ body: {
253
+ title: "Memory Explorer",
254
+ message: "Took over web server ownership",
255
+ variant: "success",
256
+ duration: 3000,
257
+ },
258
+ })
259
+ .catch(() => { });
260
+ }
261
+ });
262
+ webServer.setOnPortsExhaustedCallback(() => {
263
+ if (ctx.client?.tui) {
264
+ ctx.client.tui
265
+ .showToast({
266
+ body: {
267
+ title: "Memory Explorer",
268
+ message: `Web UI unavailable: ports ${CONFIG.webServerPort}-${CONFIG.webServerPort + 10} are held by non-responsive processes`,
269
+ variant: "error",
270
+ duration: 5000,
271
+ },
272
+ })
273
+ .catch(() => { });
274
+ }
275
+ });
276
+ if (webServer.isServerOwner()) {
277
+ if (ctx.client?.tui) {
278
+ ctx.client.tui
279
+ .showToast({
280
+ body: {
281
+ title: "Memory Explorer",
282
+ message: webAuth.isEnabled()
283
+ ? `Web UI started at ${url} (auth required)`
284
+ : `Web UI started at ${url}`,
285
+ variant: "success",
286
+ duration: 5000,
287
+ },
288
+ })
289
+ .catch(() => { });
290
+ }
291
+ }
292
+ else {
293
+ if (ctx.client?.tui) {
294
+ ctx.client.tui
295
+ .showToast({
296
+ body: {
297
+ title: "Memory Explorer",
298
+ message: `Web UI available at ${url}`,
299
+ variant: "info",
300
+ duration: 3000,
301
+ },
302
+ })
303
+ .catch(() => { });
304
+ }
305
+ }
306
+ })
307
+ .catch((error) => {
308
+ log("Web server failed to start", { error: String(error) });
309
+ if (ctx.client?.tui) {
310
+ ctx.client.tui
311
+ .showToast({
312
+ body: {
313
+ title: "Memory Explorer Error",
314
+ message: `Failed to start: ${String(error)}`,
315
+ variant: "error",
316
+ duration: 5000,
317
+ },
318
+ })
319
+ .catch(() => { });
320
+ }
321
+ });
322
+ }
323
+ let cleanedUp = false;
324
+ const cleanupPlugin = async () => {
325
+ if (cleanedUp)
326
+ return;
327
+ cleanedUp = true;
328
+ if (idleTimeout) {
329
+ clearTimeout(idleTimeout);
330
+ idleTimeout = null;
331
+ }
332
+ if (webServer)
333
+ await webServer.stop();
334
+ if (memoryClient)
335
+ await memoryClient.close();
336
+ };
337
+ const shutdownHandler = async () => {
338
+ try {
339
+ await cleanupPlugin();
340
+ }
341
+ catch (error) {
342
+ log("Shutdown error", { error: String(error) });
343
+ process.exitCode = 1;
344
+ }
345
+ };
346
+ const beforeExitHandler = () => {
347
+ if (!cleanedUp) {
348
+ void cleanupPlugin();
349
+ }
350
+ };
351
+ const exitHandler = () => {
352
+ // Best-effort sync close when the host exits without SIGINT/SIGTERM.
353
+ if (!cleanedUp) {
354
+ try {
355
+ tursoConnectionManager.closeAllSync();
356
+ }
357
+ catch {
358
+ // ignore — module may already be torn down
359
+ }
360
+ }
361
+ };
362
+ process.on("SIGINT", shutdownHandler);
363
+ process.on("SIGTERM", shutdownHandler);
364
+ process.on("beforeExit", beforeExitHandler);
365
+ process.on("exit", exitHandler);
366
+ const disposePlugin = async () => {
367
+ process.off("SIGINT", shutdownHandler);
368
+ process.off("SIGTERM", shutdownHandler);
369
+ process.off("beforeExit", beforeExitHandler);
370
+ process.off("exit", exitHandler);
371
+ await cleanupPlugin();
372
+ };
373
+ return {
374
+ // V1 ignores this extra hook; the V2 adapter uses it during plugin reload.
375
+ dispose: disposePlugin,
376
+ config: async (cfg) => {
377
+ applyStructuredOutputAgentConfig(cfg);
378
+ },
379
+ "chat.message": async (input, output) => {
380
+ if (!isConfigured() || !CONFIG.chatMessage.enabled)
381
+ return;
382
+ try {
383
+ const textParts = output.parts.filter((p) => p.type === "text");
384
+ if (textParts.length === 0)
385
+ return;
386
+ const userMessage = textParts.map((p) => p.text).join("\n");
387
+ if (!userMessage.trim())
388
+ return;
389
+ if (isInternalPrompt(input.sessionID, userMessage))
390
+ return;
391
+ await recordUserPrompt(input.sessionID, output.message.id, directory, userMessage);
392
+ const messagesResponse = await ctx.client.session.messages({
393
+ path: { id: input.sessionID },
394
+ });
395
+ const messages = messagesResponse.data || [];
396
+ const hasNonSyntheticUserMessages = messages.some((m) => m.info.role === "user" &&
397
+ !m.parts.every((p) => p.type !== "text" || p.synthetic === true));
398
+ const lastMessage = messages.length > 0 ? messages[messages.length - 1] : null;
399
+ const isAfterCompaction = lastMessage?.info?.summary === true;
400
+ const shouldInject = CONFIG.chatMessage.injectOn === "always" ||
401
+ !hasNonSyntheticUserMessages ||
402
+ (isAfterCompaction &&
403
+ messages.filter((m) => m.info.role === "user" &&
404
+ !m.parts.every((p) => p.type !== "text" || p.synthetic === true)).length === 1);
405
+ if (!shouldInject)
406
+ return;
407
+ const listResult = await memoryClient.listMemories(tags.project.tag, CONFIG.chatMessage.maxMemories);
408
+ let memories = listResult.success ? listResult.memories : [];
409
+ if (CONFIG.chatMessage.excludeCurrentSession) {
410
+ memories = memories.filter((m) => m.metadata?.sessionID !== input.sessionID);
411
+ }
412
+ if (CONFIG.chatMessage.maxAgeDays) {
413
+ const cutoffDate = Date.now() - CONFIG.chatMessage.maxAgeDays * 86400000;
414
+ memories = memories.filter((m) => new Date(m.createdAt).getTime() > cutoffDate);
415
+ }
416
+ if (memories.length === 0)
417
+ return;
418
+ const projectMemories = {
419
+ results: memories.map((m) => ({
420
+ similarity: 1.0,
421
+ memory: m.summary,
422
+ })),
423
+ total: memories.length,
424
+ timing: 0,
425
+ };
426
+ const userId = tags.user.userEmail || null;
427
+ const memoryContext = await formatContextForPrompt(userId, projectMemories);
428
+ if (memoryContext) {
429
+ const contextPart = {
430
+ id: `prt-memory-context-${Date.now()}`,
431
+ sessionID: input.sessionID,
432
+ messageID: output.message.id,
433
+ type: "text",
434
+ text: memoryContext,
435
+ synthetic: true,
436
+ };
437
+ output.parts.unshift(contextPart);
438
+ }
439
+ }
440
+ catch (error) {
441
+ log("chat.message: ERROR", { error: String(error) });
442
+ if (ctx.client?.tui && CONFIG.showErrorToasts) {
443
+ await ctx.client.tui
444
+ .showToast({
445
+ body: {
446
+ title: "Memory System Error",
447
+ message: String(error),
448
+ variant: "error",
449
+ duration: 5000,
450
+ },
451
+ })
452
+ .catch(() => { });
453
+ }
454
+ }
455
+ },
456
+ "chat.params": async (input) => {
457
+ if (!isConfigured() || CONFIG.opencodeModel !== "inherit")
458
+ return;
459
+ try {
460
+ await userPromptManager.setPromptModel(input.message.id, input.model.providerID, input.model.id);
461
+ }
462
+ catch (error) {
463
+ log("chat.params: ERROR", { error: String(error) });
464
+ }
465
+ },
466
+ tool: {
467
+ memory: tool({
468
+ description: `Manage and query project memory (MATCH USER LANGUAGE: ${getLanguageName(CONFIG.autoCaptureLanguage || "en")}). Use 'search' with technical keywords/tags, 'add' to store knowledge, 'profile' for preferences. Use migrate/list-shards/export/import when a project directory moves. Search/list scope: project or all-projects.`,
469
+ args: {
470
+ mode: tool.schema
471
+ .enum([
472
+ "add",
473
+ "search",
474
+ "profile",
475
+ "list",
476
+ "forget",
477
+ "help",
478
+ "migrate",
479
+ "list-shards",
480
+ "export",
481
+ "import",
482
+ ])
483
+ .optional(),
484
+ content: tool.schema.string().optional(),
485
+ query: tool.schema.string().optional(),
486
+ tags: tool.schema.string().optional(),
487
+ type: tool.schema.string().optional(),
488
+ memoryId: tool.schema.string().optional(),
489
+ limit: tool.schema.number().optional(),
490
+ scope: tool.schema.enum(["project", "all-projects"]).optional(),
491
+ fromPath: tool.schema.string().optional(),
492
+ fromHash: tool.schema.string().optional(),
493
+ outputPath: tool.schema.string().optional(),
494
+ inputPath: tool.schema.string().optional(),
495
+ dryRun: tool.schema.boolean().optional(),
496
+ allowLinkedSource: tool.schema.boolean().optional(),
497
+ },
498
+ async execute(args) {
499
+ const result = await executeMemoryOperation(args, {
500
+ directory,
501
+ tags,
502
+ host: "opencode",
503
+ });
504
+ return JSON.stringify(result);
505
+ },
506
+ }),
507
+ },
508
+ event: async (input) => {
509
+ const event = input.event;
510
+ if (event.type === "session.idle") {
511
+ if (!isConfigured() || !CONFIG.autoCaptureEnabled)
512
+ return;
513
+ const sessionID = event.properties?.sessionID;
514
+ if (!sessionID)
515
+ return;
516
+ // Transient structured-output sessions must not re-trigger capture/learning
517
+ // (that self-schedules an unbounded idle → LLM → idle loop).
518
+ if (await isInternalCaptureSession(ctx.client, sessionID)) {
519
+ log("Skipping idle processing for internal capture session", { sessionID });
520
+ return;
521
+ }
522
+ if (idleTimeout)
523
+ clearTimeout(idleTimeout);
524
+ idleTimeout = setTimeout(async () => {
525
+ try {
526
+ await performAutoCapture(autoCaptureHost, sessionID, directory);
527
+ if (webServer?.isServerOwner()) {
528
+ await performUserProfileLearning(ctx, directory);
529
+ const { cleanupService } = await import("./services/cleanup-service.js");
530
+ if (await cleanupService.shouldRunCleanup())
531
+ await cleanupService.runCleanup();
532
+ }
533
+ }
534
+ catch (error) {
535
+ log("Idle processing error", { error: String(error) });
536
+ }
537
+ finally {
538
+ idleTimeout = null;
539
+ }
540
+ }, 10000);
541
+ }
542
+ if (event.type === "session.compacted") {
543
+ if (!isConfigured() || !CONFIG.compaction.enabled)
544
+ return;
545
+ const sessionID = event.properties?.sessionID;
546
+ if (!sessionID)
547
+ return;
548
+ try {
549
+ const tags = getTags(directory);
550
+ const memoriesResult = await memoryClient.searchMemoriesBySessionID(sessionID, tags.project.tag, CONFIG.compaction.memoryLimit);
551
+ if (!memoriesResult.success || memoriesResult.results.length === 0) {
552
+ return;
553
+ }
554
+ const memoryContext = formatMemoriesForCompaction(memoriesResult.results);
555
+ const agent = await resolveSessionAgent(ctx.client, sessionID);
556
+ if (!agent) {
557
+ log("Compaction: skipped memory injection because session agent could not be resolved", {
558
+ sessionID,
559
+ });
560
+ return;
561
+ }
562
+ await ctx.client.session.prompt({
563
+ path: { id: sessionID },
564
+ body: {
565
+ parts: [
566
+ {
567
+ id: `prt-compaction-${Date.now()}`,
568
+ type: "text",
569
+ text: memoryContext,
570
+ synthetic: true,
571
+ },
572
+ ],
573
+ noReply: true,
574
+ agent,
575
+ },
576
+ });
577
+ if (ctx.client?.tui) {
578
+ await ctx.client.tui
579
+ .showToast({
580
+ body: {
581
+ title: "Memory Restored",
582
+ message: `${memoriesResult.results.length} memories injected after compaction`,
583
+ variant: "success",
584
+ duration: 3000,
585
+ },
586
+ })
587
+ .catch(() => { });
588
+ }
589
+ log("Compaction memory injected", {
590
+ sessionID,
591
+ count: memoriesResult.results.length,
592
+ agent: agent ?? null,
593
+ });
594
+ }
595
+ catch (error) {
596
+ log("Compaction handler error", { error: String(error) });
597
+ }
598
+ }
599
+ },
600
+ };
601
+ };
@@ -0,0 +1,9 @@
1
+ declare const OmmsPlugin: import("@opencode-ai/plugin").Plugin;
2
+ export declare const id = "omms";
3
+ export { OmmsPlugin };
4
+ declare const _default: {
5
+ id: string;
6
+ server: import("@opencode-ai/plugin").Plugin;
7
+ setup: (context: import("@opencode/plugin/promise/plugin").Context) => Promise<import("@opencode/plugin/promise/plugin").Cleanup | void> | import("@opencode/plugin/promise/plugin").Cleanup | void;
8
+ };
9
+ export default _default;
package/dist/plugin.js ADDED
@@ -0,0 +1,11 @@
1
+ const { OmmsPlugin } = await import("./index.js");
2
+ const { default: OmmsPluginV2 } = await import("./v2/plugin.js");
3
+ // Stable plugin id: OpenCode scopes plugin storage and enable/disable rules by it,
4
+ // so it stays "omms" independent of the npm package name.
5
+ export const id = "omms";
6
+ export { OmmsPlugin };
7
+ export default {
8
+ ...OmmsPluginV2,
9
+ id,
10
+ server: OmmsPlugin,
11
+ };
@@ -0,0 +1,7 @@
1
+ import { BaseAIProvider, type ProviderConfig } from "./providers/base-provider.js";
2
+ import type { AIProviderType } from "./session/session-types.js";
3
+ export declare class AIProviderFactory {
4
+ static createProvider(providerType: AIProviderType, config: ProviderConfig): BaseAIProvider;
5
+ static getSupportedProviders(): AIProviderType[];
6
+ static cleanupExpiredSessions(): Promise<number>;
7
+ }
@@ -0,0 +1,41 @@
1
+ import { BaseAIProvider } from "./providers/base-provider.js";
2
+ import { OpenAIChatCompletionProvider } from "./providers/openai-chat-completion.js";
3
+ import { OpenAIResponsesProvider } from "./providers/openai-responses.js";
4
+ import { AnthropicMessagesProvider } from "./providers/anthropic-messages.js";
5
+ import { MiniMaxProvider } from "./providers/minimax.js";
6
+ import { GoogleGeminiProvider } from "./providers/google-gemini.js";
7
+ import { OrcaRouterProvider } from "./providers/orcarouter.js";
8
+ import { aiSessionManager } from "./session/ai-session-manager.js";
9
+ export class AIProviderFactory {
10
+ static createProvider(providerType, config) {
11
+ switch (providerType) {
12
+ case "openai-chat":
13
+ return new OpenAIChatCompletionProvider(config, aiSessionManager);
14
+ case "openai-responses":
15
+ return new OpenAIResponsesProvider(config, aiSessionManager);
16
+ case "anthropic":
17
+ return new AnthropicMessagesProvider(config, aiSessionManager);
18
+ case "minimax":
19
+ return new MiniMaxProvider(config, aiSessionManager);
20
+ case "google-gemini":
21
+ return new GoogleGeminiProvider(config, aiSessionManager);
22
+ case "orcarouter":
23
+ return new OrcaRouterProvider(config, aiSessionManager);
24
+ default:
25
+ throw new Error(`Unknown provider type: ${providerType}`);
26
+ }
27
+ }
28
+ static getSupportedProviders() {
29
+ return [
30
+ "openai-chat",
31
+ "openai-responses",
32
+ "anthropic",
33
+ "minimax",
34
+ "google-gemini",
35
+ "orcarouter",
36
+ ];
37
+ }
38
+ static async cleanupExpiredSessions() {
39
+ return aiSessionManager.cleanupExpiredSessions();
40
+ }
41
+ }
@@ -0,0 +1 @@
1
+ export declare function isPlaceholderApiKey(value: string | undefined): boolean;