om-memory-system 5.10.1 → 6.1.0-next.237

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 (87) hide show
  1. package/README.md +1 -1
  2. package/dist/adapters/opencode/auto-capture-host.d.ts +2 -2
  3. package/dist/adapters/opencode/auto-capture-host.js +5 -15
  4. package/dist/adapters/opencode/auto-capture-summary.js +9 -6
  5. package/dist/adapters/opencode/backfill-models.js +1 -1
  6. package/dist/adapters/opencode/backfill-startup.js +5 -4
  7. package/dist/adapters/opencode/conversation.d.ts +5 -1
  8. package/dist/adapters/opencode/conversation.js +23 -23
  9. package/dist/adapters/opencode/import-command.d.ts +0 -12
  10. package/dist/adapters/opencode/import-command.js +1 -17
  11. package/dist/adapters/opencode/internal-capture-sessions.d.ts +0 -3
  12. package/dist/adapters/opencode/internal-capture-sessions.js +0 -26
  13. package/dist/adapters/opencode/memory-tool.d.ts +3 -4
  14. package/dist/adapters/opencode/memory-tool.js +8 -50
  15. package/dist/adapters/opencode/opencode-host.d.ts +27 -0
  16. package/dist/adapters/opencode/opencode-host.js +30 -0
  17. package/dist/adapters/opencode/opencode-import-models.d.ts +2 -2
  18. package/dist/adapters/opencode/opencode-import-models.js +8 -10
  19. package/dist/adapters/opencode/opencode-provider.d.ts +23 -62
  20. package/dist/adapters/opencode/opencode-provider.js +75 -378
  21. package/dist/adapters/opencode/package-skills.d.ts +1 -6
  22. package/dist/adapters/opencode/package-skills.js +2 -17
  23. package/dist/adapters/opencode/profile-learning.d.ts +1 -2
  24. package/dist/adapters/opencode/profile-learning.js +15 -19
  25. package/dist/adapters/opencode/profile-llm-client.d.ts +3 -2
  26. package/dist/adapters/opencode/profile-llm-client.js +8 -17
  27. package/dist/adapters/opencode/profile-model.d.ts +2 -18
  28. package/dist/adapters/opencode/profile-model.js +18 -69
  29. package/dist/adapters/opencode/prompt-model.d.ts +9 -0
  30. package/dist/adapters/opencode/prompt-model.js +21 -0
  31. package/dist/adapters/opencode/skills-tool.d.ts +3 -3
  32. package/dist/adapters/opencode/skills-tool.js +4 -30
  33. package/dist/adapters/opencode/toast.d.ts +11 -0
  34. package/dist/adapters/opencode/toast.js +11 -0
  35. package/dist/adapters/opencode/user-prompt.d.ts +2 -2
  36. package/dist/adapters/opencode/user-prompt.js +4 -5
  37. package/dist/config-template.d.ts +1 -1
  38. package/dist/config-template.js +2 -6
  39. package/dist/config.d.ts +0 -9
  40. package/dist/config.js +0 -9
  41. package/dist/core/retrieval.d.ts +0 -21
  42. package/dist/core/retrieval.js +0 -29
  43. package/dist/importer/handled-lookup.d.ts +17 -0
  44. package/dist/importer/handled-lookup.js +13 -0
  45. package/dist/importer/importer.js +3 -3
  46. package/dist/importer/ledger.d.ts +9 -0
  47. package/dist/importer/ledger.js +21 -0
  48. package/dist/importer/opencode-reader.d.ts +1 -1
  49. package/dist/importer/opencode-reader.js +88 -34
  50. package/dist/importer/opencode-v2-units.d.ts +16 -0
  51. package/dist/importer/opencode-v2-units.js +93 -0
  52. package/dist/importer/settings-models.d.ts +11 -6
  53. package/dist/importer/settings-models.js +15 -11
  54. package/dist/plugin.d.ts +0 -3
  55. package/dist/plugin.js +1 -7
  56. package/dist/services/global-config-writer.js +2 -3
  57. package/dist/services/settings-snapshot.js +6 -6
  58. package/dist/services/turso/vector-search.js +4 -2
  59. package/dist/shared/api/schemas.d.ts +1 -1
  60. package/dist/utils/memory-limits.d.ts +0 -1
  61. package/dist/utils/memory-limits.js +0 -1
  62. package/dist/v2/adapter.d.ts +16 -84
  63. package/dist/v2/adapter.js +17 -88
  64. package/dist/v2/memory-bridge.js +1 -1
  65. package/dist/v2/plugin.js +27 -16
  66. package/dist/v2/session-events.d.ts +30 -0
  67. package/dist/v2/session-events.js +130 -0
  68. package/dist/v2/startup.d.ts +14 -0
  69. package/dist/v2/startup.js +210 -0
  70. package/dist/v2/tools.d.ts +106 -0
  71. package/dist/v2/tools.js +89 -0
  72. package/dist/web/assets/index-DBSeQOs6.js +171 -0
  73. package/dist/web/index.html +1 -1
  74. package/package.json +2 -14
  75. package/dist/adapters/opencode/in-process-client.d.ts +0 -2
  76. package/dist/adapters/opencode/in-process-client.js +0 -16
  77. package/dist/adapters/opencode/opencode-diagnostics.d.ts +0 -7
  78. package/dist/adapters/opencode/opencode-diagnostics.js +0 -37
  79. package/dist/adapters/opencode/opencode-host-config.d.ts +0 -13
  80. package/dist/adapters/opencode/opencode-host-config.js +0 -53
  81. package/dist/adapters/opencode/opencode-sdk-client.d.ts +0 -6
  82. package/dist/adapters/opencode/opencode-sdk-client.js +0 -38
  83. package/dist/index.d.ts +0 -28
  84. package/dist/index.js +0 -620
  85. package/dist/v2/legacy-client.d.ts +0 -15
  86. package/dist/v2/legacy-client.js +0 -275
  87. package/dist/web/assets/index-DQgI670D.js +0 -171
package/dist/index.js DELETED
@@ -1,620 +0,0 @@
1
- import { memoryClient } from "./services/client.js";
2
- import { getTags } from "./services/tags.js";
3
- import { performAutoCapture } from "./services/auto-capture.js";
4
- import { createOpenCodeAutoCaptureHost } from "./adapters/opencode/auto-capture-host.js";
5
- import { isInternalPrompt, isStructuredSummaryPromptMessage, recordUserPrompt, } from "./adapters/opencode/user-prompt.js";
6
- import { memoryToolDescription } from "./core/memory-tool-text.js";
7
- import { OPENCODE_IMPORT_COMMAND, OPENCODE_IMPORT_DESCRIPTION, latestUserMessageModel, runOpencodeImportCommand, } from "./adapters/opencode/import-command.js";
8
- import { buildRecentMemoriesSection, formatMemoriesForCompaction } from "./core/retrieval.js";
9
- import { performUserProfileLearning } from "./adapters/opencode/profile-learning.js";
10
- import { userPromptManager } from "./services/user-prompt/user-prompt-manager.js";
11
- import { pruneTraces } from "./services/capture-diagnostics.js";
12
- import { ensureTursoReady } from "./services/turso/ready.js";
13
- import { tursoConnectionManager } from "./services/turso/connection-manager.js";
14
- import { isConfigured, CONFIG, initConfigWithLegacyMigration, refreshConfigIfChanged, } from "./config.js";
15
- import { DEFAULT_RETRIEVAL_MAX_TOKENS, tokensToByteCeiling } from "./core/context-budget.js";
16
- import { resolveOpencodeHostModel } from "./services/ai/live-model-choice.js";
17
- import { log } from "./services/logger.js";
18
- import { getLanguageName } from "./services/language-detector.js";
19
- import { getHostClientConfig } from "./adapters/opencode/opencode-host-config.js";
20
- import { applySkillsPathConfig } from "./adapters/opencode/package-skills.js";
21
- import { loadOpencodeProvider } from "./adapters/opencode/opencode-provider-loader.js";
22
- import { opencodeSkillsTool } from "./adapters/opencode/skills-tool.js";
23
- import { opencodeMemoryTool } from "./adapters/opencode/memory-tool.js";
24
- import { STRUCTURED_OUTPUT_AGENT, STRUCTURED_OUTPUT_TOOLS, } from "./adapters/opencode/opencode-provider.js";
25
- import { INTERNAL_CAPTURE_SESSION_TITLE, isInternalCaptureSessionTitle, isTrackedInternalCaptureSession, } from "./adapters/opencode/internal-capture-sessions.js";
26
- import { registerOpencodeProfileModel } from "./adapters/opencode/profile-model.js";
27
- export { INTERNAL_CAPTURE_SESSION_TITLE, isInternalCaptureSessionTitle };
28
- export { isStructuredSummaryPromptMessage };
29
- function extractSessionTitle(response) {
30
- if (!response || typeof response !== "object")
31
- return undefined;
32
- const obj = response;
33
- return obj.data?.title ?? obj.title;
34
- }
35
- function unwrapSdkData(response) {
36
- if (!response || typeof response !== "object")
37
- return undefined;
38
- const obj = response;
39
- return (obj.data ?? response);
40
- }
41
- /**
42
- * Resolve the session's active agent so compaction memory injection does not
43
- * reset OpenCode to the stock "general-purpose" fallback (issue #236).
44
- *
45
- * Preference order:
46
- * 1. session.get().agent (v2 hosts)
47
- * 2. Latest non-compaction user message agent
48
- * 3. Latest non-compaction / non-summary assistant mode (v1) or agent (v2)
49
- */
50
- export async function resolveSessionAgent(client, sessionID) {
51
- const sessionClient = client?.session;
52
- if (typeof sessionClient?.get === "function") {
53
- try {
54
- const session = unwrapSdkData(await sessionClient.get({ path: { id: sessionID } }));
55
- if (typeof session?.agent === "string" && session.agent.trim()) {
56
- return session.agent.trim();
57
- }
58
- }
59
- catch (error) {
60
- log("resolveSessionAgent: session.get failed", { sessionID, error: String(error) }, "error");
61
- }
62
- }
63
- if (typeof sessionClient?.messages !== "function") {
64
- return undefined;
65
- }
66
- try {
67
- const messages = unwrapSdkData(await sessionClient.messages({ path: { id: sessionID } }));
68
- if (!Array.isArray(messages))
69
- return undefined;
70
- for (let i = messages.length - 1; i >= 0; i--) {
71
- const info = messages[i]?.info;
72
- if (!info)
73
- continue;
74
- if (info.role === "user") {
75
- if (typeof info.agent === "string" && info.agent.trim()) {
76
- return info.agent.trim();
77
- }
78
- continue;
79
- }
80
- if (info.role === "assistant") {
81
- if (info.summary === true || info.mode === "compaction")
82
- continue;
83
- const agent = (typeof info.agent === "string" && info.agent.trim()) ||
84
- (typeof info.mode === "string" && info.mode.trim()) ||
85
- undefined;
86
- if (agent)
87
- return agent;
88
- }
89
- }
90
- }
91
- catch (error) {
92
- log("resolveSessionAgent: session.messages failed", { sessionID, error: String(error) }, "error");
93
- }
94
- return undefined;
95
- }
96
- async function isInternalCaptureSession(client, sessionID) {
97
- // Fast path: sessions we created ourselves (survives brief post-delete window).
98
- if (isTrackedInternalCaptureSession(sessionID)) {
99
- return true;
100
- }
101
- const sessionClient = client?.session;
102
- // Plugin host client uses path-based args (same as session.messages).
103
- if (typeof sessionClient?.get === "function") {
104
- try {
105
- const response = await sessionClient.get({ path: { id: sessionID } });
106
- const title = extractSessionTitle(response);
107
- if (isInternalCaptureSessionTitle(title)) {
108
- return true;
109
- }
110
- log("internal capture session check via session.get", {
111
- sessionID,
112
- title: title ?? null,
113
- matched: false,
114
- });
115
- }
116
- catch (error) {
117
- log("internal capture session check via session.get failed", {
118
- sessionID,
119
- error: String(error),
120
- }, "error");
121
- }
122
- }
123
- else {
124
- log("internal capture session check: session.get unavailable", { sessionID }, "warn");
125
- }
126
- return false;
127
- }
128
- /** Register `/memory-import-opencode-history`; its work happens in command.execute.before. */
129
- export function applyHistoryImportCommandConfig(cfg) {
130
- cfg.command = {
131
- ...cfg.command,
132
- [OPENCODE_IMPORT_COMMAND]: {
133
- template: "Import OpenCode history into omms: $ARGUMENTS",
134
- description: OPENCODE_IMPORT_DESCRIPTION,
135
- },
136
- };
137
- }
138
- /** Least-privilege agent used only by internal structured-output sessions (issue #189). */
139
- export function applyStructuredOutputAgentConfig(cfg) {
140
- cfg.agent = {
141
- ...cfg.agent,
142
- [STRUCTURED_OUTPUT_AGENT]: {
143
- description: "Internal least-privilege agent for omms structured output",
144
- mode: "subagent",
145
- // OpenCode reads `steps` at runtime; SDK AgentConfig also documents maxSteps.
146
- steps: 2,
147
- maxSteps: 2,
148
- tools: STRUCTURED_OUTPUT_TOOLS,
149
- permission: {
150
- "*": "deny",
151
- StructuredOutput: "allow",
152
- },
153
- },
154
- };
155
- }
156
- export async function configureOpencodeHostTransport(ctx) {
157
- const { createV2Client, resetHostFetch, setHostFetch, setV2Client } = await loadOpencodeProvider();
158
- resetHostFetch();
159
- const hostConfig = getHostClientConfig(ctx);
160
- if (hostConfig.fetch) {
161
- setHostFetch(hostConfig.fetch);
162
- }
163
- else if (!hostConfig.inProcess) {
164
- log("OpenCode host fetch unavailable; falling back to global fetch", {
165
- clientKeys: hostConfig.clientKeys,
166
- sdkConfigCount: hostConfig.sdkConfigCount,
167
- }, "warn");
168
- }
169
- const serverUrl = hostConfig.baseUrl ?? ctx.serverUrl;
170
- if (serverUrl) {
171
- setV2Client(createV2Client(serverUrl, {
172
- fetch: hostConfig.fetch,
173
- headers: hostConfig.headers,
174
- }));
175
- }
176
- }
177
- function logAutoCaptureProviderStatus() {
178
- if (!CONFIG.autoCaptureEnabled || CONFIG.autoCaptureProviderStatus.ready)
179
- return;
180
- log(`Auto-capture disabled by configuration. Issues: ${CONFIG.autoCaptureProviderStatus.issues.join("; ")}.`);
181
- }
182
- export const OmmsPlugin = async (ctx) => {
183
- const { directory } = ctx;
184
- initConfigWithLegacyMigration(directory);
185
- /** One injection operation's byte allowance, read after a config refresh. */
186
- const currentRetrievalBudgetBytes = () => {
187
- refreshConfigIfChanged(directory);
188
- // The default guards partial CONFIG stubs in tests.
189
- return tokensToByteCeiling(CONFIG.retrievalMaxTokens ?? DEFAULT_RETRIEVAL_MAX_TOKENS);
190
- };
191
- // Runs even with tracing off, so turning it off does not leave old traces behind.
192
- pruneTraces(CONFIG);
193
- // Record this copy first: the login item and the other hosts run the newest recorded copy.
194
- void import("./services/runtime-handoff.js")
195
- .then(({ registerOwnCopy }) => registerOwnCopy())
196
- .catch((error) => log("OpenCode runtime record failed", {
197
- code: error instanceof Error ? error.name : "unknown",
198
- }, "error"));
199
- if (CONFIG.webServerAutoStart !== undefined && process.env.OMMS_DISABLE_WEB_AUTOSTART !== "1") {
200
- void import("./services/web-autostart.js")
201
- .then(({ reconcileWebAutostart }) => reconcileWebAutostart(CONFIG))
202
- .catch((error) => log("OpenCode login item reconciliation failed", {
203
- code: error instanceof Error ? error.name : "unknown",
204
- }, "error"));
205
- }
206
- logAutoCaptureProviderStatus();
207
- const tags = getTags(directory);
208
- const autoCaptureHost = createOpenCodeAutoCaptureHost(ctx);
209
- let idleTimeout = null;
210
- const GLOBAL_PLUGIN_WARMUP_KEY = Symbol.for("omms.plugin.warmedup");
211
- if (!globalThis[GLOBAL_PLUGIN_WARMUP_KEY] && isConfigured()) {
212
- // Fire-and-forget: DB ready + embedding model must not block plugin init.
213
- (async () => {
214
- try {
215
- await memoryClient.warmup();
216
- globalThis[GLOBAL_PLUGIN_WARMUP_KEY] = true;
217
- }
218
- catch (error) {
219
- log("Plugin memory warmup failed", { error: String(error) }, "error");
220
- }
221
- })();
222
- }
223
- await configureOpencodeHostTransport(ctx);
224
- // Shared profile dedup, conflict, description, and cleanup calls use OpenCode's host model.
225
- registerOpencodeProfileModel();
226
- // In-process backfill, capture, and the capture retry drain use OpenCode's models.
227
- // Awaited so those paths never run before the registry is filled.
228
- await import("./adapters/opencode/backfill-startup.js")
229
- .then(({ registerOpencodeImportModels, registerOpencodeCaptureRetryDrain }) => {
230
- registerOpencodeImportModels();
231
- registerOpencodeCaptureRetryDrain(autoCaptureHost, directory);
232
- })
233
- .catch(() => { });
234
- (async () => {
235
- let connected = [];
236
- try {
237
- const providerResult = await ctx.client.provider.list();
238
- if (providerResult.data?.connected) {
239
- connected = providerResult.data.connected;
240
- const { setConnectedProviders } = await loadOpencodeProvider();
241
- setConnectedProviders(providerResult.data.connected);
242
- log("opencode providers connected", {
243
- list: providerResult.data.connected,
244
- configured: CONFIG.opencodeProvider || "(not set)",
245
- });
246
- }
247
- else {
248
- log("opencode provider list empty or failed", {
249
- data: JSON.stringify(providerResult.data).substring(0, 100),
250
- }, "warn");
251
- }
252
- }
253
- catch (error) {
254
- log("Failed to initialize opencode provider state", { error: String(error) }, "error");
255
- }
256
- const configModel = async () => {
257
- const response = await ctx.client.config.get();
258
- return unwrapSdkData(response)?.model ?? null;
259
- };
260
- // Run now on a Settings page served by this process can use OpenCode's signed-in models.
261
- void import("./adapters/opencode/backfill-startup.js")
262
- .then(({ registerOpencodeBackfillModels }) => registerOpencodeBackfillModels({ connected, directory, configModel }))
263
- .catch(() => { });
264
- if (CONFIG.autoBackfill && process.env.OMMS_DISABLE_AUTO_BACKFILL !== "1" && isConfigured()) {
265
- void import("./adapters/opencode/backfill-startup.js")
266
- .then(({ startOpencodeBackfill }) => startOpencodeBackfill({
267
- connected,
268
- directory,
269
- configModel,
270
- notify: (message) => {
271
- void ctx.client.tui
272
- ?.showToast({
273
- body: { title: "Automatic import", message, variant: "info", duration: 5000 },
274
- })
275
- .catch(() => { });
276
- },
277
- }))
278
- .catch((error) => log("OpenCode backfill failed", {
279
- error: error instanceof Error ? error.message : String(error),
280
- }, "error"));
281
- }
282
- })();
283
- let tursoReadyForWeb = !isConfigured();
284
- if (CONFIG.webServerEnabled && isConfigured()) {
285
- try {
286
- await ensureTursoReady();
287
- tursoReadyForWeb = true;
288
- }
289
- catch (error) {
290
- log("Turso ready gate failed before web server start", { error: String(error) }, "error");
291
- if (ctx.client?.tui) {
292
- ctx.client.tui
293
- .showToast({
294
- body: {
295
- title: "Memory Explorer",
296
- message: "Database migration failed; web UI not started",
297
- variant: "error",
298
- duration: 8000,
299
- },
300
- })
301
- .catch(() => { });
302
- }
303
- }
304
- }
305
- if (CONFIG.webServerEnabled &&
306
- tursoReadyForWeb &&
307
- process.env.OMMS_DISABLE_WEB_AUTOSTART !== "1") {
308
- const showToast = (message, variant, duration) => {
309
- if (!ctx.client?.tui)
310
- return;
311
- ctx.client.tui
312
- .showToast({ body: { title: "Memory Explorer", message, variant, duration } })
313
- .catch(() => { });
314
- };
315
- // The shared web app serves the page. This plugin starts it only when none runs.
316
- void Promise.all([
317
- import("./services/web-ensure.js"),
318
- import("./services/web-api-auth.js"),
319
- import("./services/runtime-handoff.js"),
320
- ])
321
- .then(async ([{ ensureWebApp }, { webServerUrl }, { hostReplaceOlder }]) => {
322
- const baseUrl = webServerUrl(CONFIG.webServerHost, CONFIG.webServerPort);
323
- // A web app older than the newest recorded copy steps aside for it.
324
- const replaceOlder = await hostReplaceOlder(CONFIG).catch(() => undefined);
325
- return ensureWebApp({
326
- settings: { enabled: CONFIG.webServerEnabled, baseUrl },
327
- budgetMs: 10_000,
328
- ...(replaceOlder ? { replaceOlder } : {}),
329
- }).then((result) => {
330
- if (result === "running" || result === "started") {
331
- showToast(`Web UI available at ${baseUrl}`, "info", 3000);
332
- }
333
- else if (result === "port-busy") {
334
- showToast(`Web UI unavailable: another program uses port ${CONFIG.webServerPort}`, "error", 5000);
335
- }
336
- else if (result === "no-runtime") {
337
- showToast("Web UI unavailable: no Node or Bun runtime found to start it", "error", 5000);
338
- }
339
- });
340
- })
341
- .catch((error) => log("Web app start failed", { error: String(error) }, "error"));
342
- }
343
- let cleanedUp = false;
344
- const cleanupPlugin = async () => {
345
- if (cleanedUp)
346
- return;
347
- cleanedUp = true;
348
- if (idleTimeout) {
349
- clearTimeout(idleTimeout);
350
- idleTimeout = null;
351
- }
352
- if (memoryClient)
353
- await memoryClient.close();
354
- };
355
- const shutdownHandler = async () => {
356
- try {
357
- await cleanupPlugin();
358
- }
359
- catch (error) {
360
- log("Shutdown error", { error: String(error) }, "error");
361
- process.exitCode = 1;
362
- }
363
- };
364
- const beforeExitHandler = () => {
365
- if (!cleanedUp) {
366
- void cleanupPlugin();
367
- }
368
- };
369
- const exitHandler = () => {
370
- // Best-effort sync close when the host exits without SIGINT/SIGTERM.
371
- if (!cleanedUp) {
372
- try {
373
- tursoConnectionManager.closeAllSync();
374
- }
375
- catch {
376
- // ignore — module may already be torn down
377
- }
378
- }
379
- };
380
- process.on("SIGINT", shutdownHandler);
381
- process.on("SIGTERM", shutdownHandler);
382
- process.on("beforeExit", beforeExitHandler);
383
- process.on("exit", exitHandler);
384
- const disposePlugin = async () => {
385
- process.off("SIGINT", shutdownHandler);
386
- process.off("SIGTERM", shutdownHandler);
387
- process.off("beforeExit", beforeExitHandler);
388
- process.off("exit", exitHandler);
389
- await cleanupPlugin();
390
- };
391
- return {
392
- // V1 ignores this extra hook; the V2 adapter uses it during plugin reload.
393
- dispose: disposePlugin,
394
- config: async (cfg) => {
395
- applyStructuredOutputAgentConfig(cfg);
396
- applyHistoryImportCommandConfig(cfg);
397
- applySkillsPathConfig(cfg);
398
- },
399
- // V1 runs slash commands as a model turn; do the import here and hand the
400
- // turn only the finished report to relay.
401
- "command.execute.before": async (input, output) => {
402
- if (input.command !== OPENCODE_IMPORT_COMMAND)
403
- return;
404
- const report = await runOpencodeImportCommand({
405
- argsText: input.arguments ?? "",
406
- directory,
407
- sessionModel: () => latestUserMessageModel(ctx.client, input.sessionID),
408
- notify: (message) => {
409
- void ctx.client?.tui
410
- ?.showToast({
411
- body: { title: "omms import", message, variant: "info", duration: 3000 },
412
- })
413
- .catch(() => { });
414
- },
415
- });
416
- output.parts.splice(0, output.parts.length, {
417
- type: "text",
418
- text: `Reply with this omms history import report exactly as written and nothing else:\n\n\`\`\`\n${report}\n\`\`\``,
419
- });
420
- },
421
- "chat.message": async (input, output) => {
422
- // Refresh first so the enabled checks read the current file, then capture
423
- // this injection's limits before the first await: a later config edit
424
- // must not change an operation already in progress.
425
- const budgetBytes = currentRetrievalBudgetBytes();
426
- const chatLimits = {
427
- maxMemories: CONFIG.chatMessage.maxMemories,
428
- excludeCurrentSession: CONFIG.chatMessage.excludeCurrentSession,
429
- maxAgeDays: CONFIG.chatMessage.maxAgeDays,
430
- };
431
- if (!isConfigured() || !CONFIG.chatMessage.enabled)
432
- return;
433
- try {
434
- const textParts = output.parts.filter((p) => p.type === "text");
435
- if (textParts.length === 0)
436
- return;
437
- const userMessage = textParts.map((p) => p.text).join("\n");
438
- if (!userMessage.trim())
439
- return;
440
- if (isInternalPrompt(input.sessionID, userMessage))
441
- return;
442
- await recordUserPrompt(input.sessionID, output.message.id, directory, userMessage);
443
- const messagesResponse = await ctx.client.session.messages({
444
- path: { id: input.sessionID },
445
- });
446
- const messages = messagesResponse.data || [];
447
- const hasNonSyntheticUserMessages = messages.some((m) => m.info.role === "user" &&
448
- !m.parts.every((p) => p.type !== "text" || p.synthetic === true));
449
- const lastMessage = messages.length > 0 ? messages[messages.length - 1] : null;
450
- const isAfterCompaction = lastMessage?.info?.summary === true;
451
- const shouldInject = CONFIG.chatMessage.injectOn === "always" ||
452
- !hasNonSyntheticUserMessages ||
453
- (isAfterCompaction &&
454
- messages.filter((m) => m.info.role === "user" &&
455
- !m.parts.every((p) => p.type !== "text" || p.synthetic === true)).length === 1);
456
- if (!shouldInject)
457
- return;
458
- // The section is injected as a plain synthetic part, so no retrieval
459
- // wrapper is reserved.
460
- const memoryContext = await buildRecentMemoriesSection({
461
- projectTag: tags.project.tag,
462
- userEmail: tags.user.userEmail,
463
- sessionId: input.sessionID,
464
- ...chatLimits,
465
- budget: { maxBytes: budgetBytes },
466
- });
467
- if (memoryContext) {
468
- const contextPart = {
469
- id: `prt-memory-context-${Date.now()}`,
470
- sessionID: input.sessionID,
471
- messageID: output.message.id,
472
- type: "text",
473
- text: memoryContext,
474
- synthetic: true,
475
- };
476
- output.parts.unshift(contextPart);
477
- }
478
- }
479
- catch (error) {
480
- log("chat.message: ERROR", { error: String(error) }, "error");
481
- if (ctx.client?.tui && CONFIG.showErrorToasts) {
482
- await ctx.client.tui
483
- .showToast({
484
- body: {
485
- title: "Memory System Error",
486
- message: String(error),
487
- variant: "error",
488
- duration: 5000,
489
- },
490
- })
491
- .catch(() => { });
492
- }
493
- }
494
- },
495
- "chat.params": async (input) => {
496
- // Record the session model whenever live calls follow it ("inherit" or nothing configured).
497
- if (!isConfigured() || resolveOpencodeHostModel(CONFIG)?.modelID !== "inherit")
498
- return;
499
- try {
500
- await userPromptManager.setPromptModel(input.message.id, input.model.providerID, input.model.id);
501
- }
502
- catch (error) {
503
- log("chat.params: ERROR", { error: String(error) }, "error");
504
- }
505
- },
506
- tool: {
507
- memory: opencodeMemoryTool(memoryToolDescription(getLanguageName(CONFIG.autoCaptureLanguage || "en")), { directory, tags }),
508
- skills: opencodeSkillsTool(directory),
509
- },
510
- event: async (input) => {
511
- const event = input.event;
512
- if (event.type === "session.created") {
513
- if (!isConfigured() || !CONFIG.autoCaptureEnabled)
514
- return;
515
- const sessionID = event.properties?.info?.id ?? event.properties?.sessionID;
516
- // OMMS's own structured-output sessions must not start a retry pass.
517
- if (sessionID && (await isInternalCaptureSession(ctx.client, sessionID)))
518
- return;
519
- const { startCaptureRetryDrain } = await import("./services/capture-retry-drain.js");
520
- startCaptureRetryDrain("opencode");
521
- return;
522
- }
523
- if (event.type === "session.idle") {
524
- // Skill learning records turns even when memory capture is off (ADR-036).
525
- if (!isConfigured() || !(CONFIG.autoCaptureEnabled || CONFIG.skillLearning))
526
- return;
527
- const sessionID = event.properties?.sessionID;
528
- if (!sessionID)
529
- return;
530
- // Transient structured-output sessions must not re-trigger capture/learning
531
- // (that self-schedules an unbounded idle → LLM → idle loop).
532
- if (await isInternalCaptureSession(ctx.client, sessionID)) {
533
- log("Skipping idle processing for internal capture session", { sessionID });
534
- return;
535
- }
536
- if (idleTimeout)
537
- clearTimeout(idleTimeout);
538
- idleTimeout = setTimeout(async () => {
539
- try {
540
- await performAutoCapture(autoCaptureHost, sessionID, directory);
541
- if (!CONFIG.autoCaptureEnabled)
542
- return; // Skill turns only; the memory work stays off.
543
- await performUserProfileLearning(ctx, directory);
544
- const { cleanupService } = await import("./services/cleanup-service.js");
545
- if (await cleanupService.shouldRunCleanup())
546
- await cleanupService.runCleanup();
547
- }
548
- catch (error) {
549
- log("Idle processing error", { error: String(error) }, "error");
550
- }
551
- finally {
552
- idleTimeout = null;
553
- }
554
- }, 10000);
555
- }
556
- if (event.type === "session.compacted") {
557
- // Refresh first so the enabled checks read the current file, then keep
558
- // one captured allowance for the whole restore operation.
559
- const budgetBytes = currentRetrievalBudgetBytes();
560
- if (!isConfigured() || !CONFIG.compaction.enabled)
561
- return;
562
- const sessionID = event.properties?.sessionID;
563
- if (!sessionID)
564
- return;
565
- try {
566
- const tags = getTags(directory);
567
- const memoriesResult = await memoryClient.searchMemoriesBySessionID(sessionID, tags.project.tag, CONFIG.compaction.memoryLimit);
568
- if (!memoriesResult.success || memoriesResult.results.length === 0) {
569
- return;
570
- }
571
- const memoryContext = formatMemoriesForCompaction(memoriesResult.results, {
572
- maxBytes: budgetBytes,
573
- });
574
- const agent = await resolveSessionAgent(ctx.client, sessionID);
575
- if (!agent) {
576
- log("Compaction: skipped memory injection because session agent could not be resolved", {
577
- sessionID,
578
- }, "warn");
579
- return;
580
- }
581
- await ctx.client.session.prompt({
582
- path: { id: sessionID },
583
- body: {
584
- parts: [
585
- {
586
- id: `prt-compaction-${Date.now()}`,
587
- type: "text",
588
- text: memoryContext,
589
- synthetic: true,
590
- },
591
- ],
592
- noReply: true,
593
- agent,
594
- },
595
- });
596
- if (ctx.client?.tui) {
597
- await ctx.client.tui
598
- .showToast({
599
- body: {
600
- title: "Memory Restored",
601
- message: `${memoriesResult.results.length} memories injected after compaction`,
602
- variant: "success",
603
- duration: 3000,
604
- },
605
- })
606
- .catch(() => { });
607
- }
608
- log("Compaction memory injected", {
609
- sessionID,
610
- count: memoriesResult.results.length,
611
- agent: agent ?? null,
612
- });
613
- }
614
- catch (error) {
615
- log("Compaction handler error", { error: String(error) }, "error");
616
- }
617
- }
618
- },
619
- };
620
- };
@@ -1,15 +0,0 @@
1
- import type { Context } from "@opencode/plugin/promise/plugin";
2
- /**
3
- * Adapts the released OpenCode v2 plugin context to the V1 client shape used
4
- * by the shared omms implementation.
5
- */
6
- export declare function createLegacyClient(ctx: Context): any;
7
- export declare function legacyToolResult(value: unknown): {
8
- content: string;
9
- metadata?: unknown;
10
- };
11
- export declare function toLegacyEvent(raw: any): {
12
- type: string;
13
- properties: any;
14
- };
15
- export declare function eventBelongsToLocation(ctx: Context, raw: any): Promise<boolean>;