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
@@ -1,90 +1,22 @@
1
1
  import type { Context } from "@opencode/plugin/promise/plugin";
2
+ import { type SessionEvent } from "./session-events.js";
3
+ import { type OpencodeTools } from "./tools.js";
2
4
  import type { V2MemoryBridge } from "./memory-bridge.js";
3
- export declare const memoryInput: {
4
- readonly type: "object";
5
- readonly properties: {
6
- readonly mode: {
7
- readonly type: "string";
8
- readonly enum: readonly ["add", "memorise", "memorize", "recall", "search", "profile", "list", "forget", "help", "migrate", "list-shards", "export", "import", "space"];
9
- };
10
- readonly content: {
11
- readonly type: "string";
12
- };
13
- readonly query: {
14
- readonly type: "string";
15
- };
16
- readonly tags: {
17
- readonly type: "string";
18
- };
19
- readonly type: {
20
- readonly type: "string";
21
- };
22
- readonly topic: {
23
- readonly type: "string";
24
- };
25
- readonly done: {
26
- readonly type: "string";
27
- };
28
- readonly doing: {
29
- readonly type: "string";
30
- };
31
- readonly next: {
32
- readonly type: "string";
33
- };
34
- readonly memoryId: {
35
- readonly type: "string";
36
- };
37
- readonly limit: {
38
- readonly type: "number";
39
- };
40
- readonly scope: {
41
- readonly type: "string";
42
- readonly enum: readonly ["project", "all-projects"];
43
- };
44
- readonly fromPath: {
45
- readonly type: "string";
46
- };
47
- readonly fromHash: {
48
- readonly type: "string";
49
- };
50
- readonly outputPath: {
51
- readonly type: "string";
52
- };
53
- readonly inputPath: {
54
- readonly type: "string";
55
- };
56
- readonly dryRun: {
57
- readonly type: "boolean";
58
- };
59
- readonly allowLinkedSource: {
60
- readonly type: "boolean";
61
- };
62
- readonly spaceAction: {
63
- readonly type: "string";
64
- };
65
- readonly spaceName: {
66
- readonly type: "string";
67
- };
68
- readonly spaceTo: {
69
- readonly type: "string";
70
- };
71
- readonly spaceSince: {
72
- readonly type: "string";
73
- };
74
- readonly spaceUntil: {
75
- readonly type: "string";
76
- };
77
- readonly spaceProject: {
78
- readonly type: "string";
79
- };
80
- readonly spaceIn: {
81
- readonly type: "string";
82
- };
83
- };
84
- readonly additionalProperties: false;
85
- };
5
+ export { memoryInput } from "./tools.js";
86
6
  /** Per-session state is bounded so long-running servers cannot grow it without limit. */
87
7
  export declare const MAX_TRACKED_SESSIONS = 256;
88
8
  /** Session ids remembered as already seen by this process, for the first-prompt rule. */
89
9
  export declare const MAX_SEEN_SESSIONS = 500;
90
- export declare function registerV2Adapter(ctx: Context, legacy: any, memory: V2MemoryBridge): Promise<() => Promise<void>>;
10
+ /** The OpenCode work the adapter wires into the V2 context. */
11
+ export type V2AdapterDeps = {
12
+ readonly tools: OpencodeTools;
13
+ /** Store a model request's model on the recorded prompt row. */
14
+ readonly recordPromptModel?: (messageID: string, model: {
15
+ readonly providerID: string;
16
+ readonly id: string;
17
+ }) => Promise<void>;
18
+ /** Session events of this plugin's directory, compaction excluded. */
19
+ readonly onEvent?: (event: SessionEvent) => Promise<void>;
20
+ readonly dispose?: () => Promise<void>;
21
+ };
22
+ export declare function registerV2Adapter(ctx: Context, deps: V2AdapterDeps, memory: V2MemoryBridge): Promise<() => Promise<void>>;
@@ -1,67 +1,19 @@
1
1
  import { OPENCODE_IMPORT_COMMAND, OPENCODE_IMPORT_DESCRIPTION, runOpencodeImportCommand, } from "../adapters/opencode/import-command.js";
2
2
  import { addPackageSkill } from "../adapters/opencode/package-skills.js";
3
- import { SKILLS_TOOL_INPUT_SCHEMA } from "../core/skills-tool.js";
4
3
  import { log } from "../services/logger.js";
5
4
  import { utf8ByteLength } from "../utils/context-limit.js";
6
- import { eventBelongsToLocation, legacyToolResult, toLegacyEvent } from "./legacy-client.js";
5
+ import { eventBelongsToLocation, toSessionEvent } from "./session-events.js";
6
+ import { registerOpencodeTools } from "./tools.js";
7
7
  import { createRecallReporter } from "./recall-reporter.js";
8
8
  import { createSessionParents } from "./session-parent.js";
9
- export const memoryInput = {
10
- type: "object",
11
- properties: {
12
- mode: {
13
- type: "string",
14
- enum: [
15
- "add",
16
- "memorise",
17
- "memorize",
18
- "recall",
19
- "search",
20
- "profile",
21
- "list",
22
- "forget",
23
- "help",
24
- "migrate",
25
- "list-shards",
26
- "export",
27
- "import",
28
- "space",
29
- ],
30
- },
31
- content: { type: "string" },
32
- query: { type: "string" },
33
- tags: { type: "string" },
34
- type: { type: "string" },
35
- topic: { type: "string" },
36
- done: { type: "string" },
37
- doing: { type: "string" },
38
- next: { type: "string" },
39
- memoryId: { type: "string" },
40
- limit: { type: "number" },
41
- scope: { type: "string", enum: ["project", "all-projects"] },
42
- fromPath: { type: "string" },
43
- fromHash: { type: "string" },
44
- outputPath: { type: "string" },
45
- inputPath: { type: "string" },
46
- dryRun: { type: "boolean" },
47
- allowLinkedSource: { type: "boolean" },
48
- spaceAction: { type: "string" },
49
- spaceName: { type: "string" },
50
- spaceTo: { type: "string" },
51
- spaceSince: { type: "string" },
52
- spaceUntil: { type: "string" },
53
- spaceProject: { type: "string" },
54
- spaceIn: { type: "string" },
55
- },
56
- additionalProperties: false,
57
- };
9
+ export { memoryInput } from "./tools.js";
58
10
  /** Per-session state is bounded so long-running servers cannot grow it without limit. */
59
11
  export const MAX_TRACKED_SESSIONS = 256;
60
12
  /** Session ids remembered as already seen by this process, for the first-prompt rule. */
61
13
  export const MAX_SEEN_SESSIONS = 500;
62
14
  /** Unadmitted prompts kept per session before the oldest is dropped. */
63
15
  const MAX_PENDING_PROMPTS = 16;
64
- /** Compaction is restored natively through the `compaction` hook, never via the V1 event path. */
16
+ /** Compaction is restored through the `compaction` hook, never from the event stream. */
65
17
  const COMPACTION_EVENT_TYPES = new Set(["session.compacted", "session.compaction.ended"]);
66
18
  const NO_SECTIONS = {
67
19
  promptSection: null,
@@ -91,7 +43,7 @@ function deletedSessionID(event) {
91
43
  const props = event.properties ?? {};
92
44
  return props.sessionID ?? props.info?.id ?? props.session?.id;
93
45
  }
94
- export async function registerV2Adapter(ctx, legacy, memory) {
46
+ export async function registerV2Adapter(ctx, deps, memory) {
95
47
  const retrievals = new Map();
96
48
  const restored = new Map();
97
49
  // Sessions this process has seen a prompt or compaction for. The first prompt of a session not
@@ -184,31 +136,10 @@ export async function registerV2Adapter(ctx, legacy, memory) {
184
136
  if (!subagent)
185
137
  await loadRestored(sessionID, memory.retrievalTimeoutMs);
186
138
  };
187
- // Each V1 tool becomes a direct V2 tool, like OpenCode's own read/grep/skill, not a
188
- // Code Mode catalog entry, so the model can always see and call it (as on V1 and Pi).
189
- const legacyTool = (name, input) => ({
190
- name,
191
- description: legacy.tool[name].description,
192
- input: input,
193
- options: { codemode: false },
194
- execute: async (args, toolContext) => legacyToolResult(await legacy.tool[name].execute(args, {
195
- sessionID: toolContext.sessionID,
196
- messageID: toolContext.messageID,
197
- agent: toolContext.agent,
198
- directory: ctx.location.directory,
199
- worktree: ctx.location.project.directory,
200
- abort: new AbortController().signal,
201
- metadata() { },
202
- async ask() { },
203
- })),
204
- });
205
- await ctx.tool.transform((editor) => {
206
- editor.add(legacyTool("memory", memoryInput));
207
- editor.add(legacyTool("skills", SKILLS_TOOL_INPUT_SCHEMA));
208
- });
209
- // The omms-memory skill ships in the package; V1 loads it through the config hook.
139
+ await registerOpencodeTools(ctx, deps.tools);
140
+ // The omms-memory and omms-remember skills ship in the package.
210
141
  await ctx.skill?.transform?.((editor) => addPackageSkill(editor));
211
- // Same command as V1 and Pi. V2 runs it natively, so no model turn relays the report.
142
+ // Same command as Pi. V2 runs it natively, so no model turn relays the report.
212
143
  await ctx.command?.transform?.((editor) => editor.add({
213
144
  name: OPENCODE_IMPORT_COMMAND,
214
145
  description: OPENCODE_IMPORT_DESCRIPTION,
@@ -284,7 +215,7 @@ export async function registerV2Adapter(ctx, legacy, memory) {
284
215
  const sessionID = event.sessionID;
285
216
  const retrieval = retrievals.get(sessionID);
286
217
  const budgetBytes = retrieval?.budgetBytes ?? memory.snapshotRequestBudget();
287
- // Before chat.params, which stores the model on the recorded prompt row.
218
+ // Before the model is stored on the recorded prompt row, below.
288
219
  await recordAdmittedPrompts(sessionID, event.messages);
289
220
  await decideFirstRequest(sessionID, event.messages, retrieval);
290
221
  // The retrieval sections and the restored section share one request budget: the
@@ -312,12 +243,10 @@ export async function registerV2Adapter(ctx, legacy, memory) {
312
243
  recall.record(sessionID, restoredEntry, "restore", null, section);
313
244
  }
314
245
  }
315
- if (legacy["chat.params"]) {
316
- await legacy["chat.params"]({
317
- message: { id: messageIDs.get(sessionID) ?? sessionID },
318
- model: { providerID: event.model.providerID, id: event.model.id },
319
- });
320
- }
246
+ await deps.recordPromptModel?.(messageIDs.get(sessionID) ?? sessionID, {
247
+ providerID: event.model.providerID,
248
+ id: event.model.id,
249
+ });
321
250
  });
322
251
  await ctx.session.hook("compaction", async (event) => {
323
252
  memory.snapshotRequestBudget();
@@ -332,14 +261,14 @@ export async function registerV2Adapter(ctx, legacy, memory) {
332
261
  const watcher = (async () => {
333
262
  try {
334
263
  for await (const raw of ctx.event.subscribe({ signal: controller.signal })) {
335
- const event = toLegacyEvent(raw);
264
+ const event = toSessionEvent(raw);
336
265
  const deleted = deletedSessionID(event);
337
266
  if (deleted)
338
267
  forgetSession(deleted);
339
- if (!legacy.event || COMPACTION_EVENT_TYPES.has(event.type))
268
+ if (!deps.onEvent || COMPACTION_EVENT_TYPES.has(event.type))
340
269
  continue;
341
270
  if (await eventBelongsToLocation(ctx, raw)) {
342
- await legacy.event({ event });
271
+ await deps.onEvent(event);
343
272
  }
344
273
  }
345
274
  }
@@ -360,6 +289,6 @@ export async function registerV2Adapter(ctx, legacy, memory) {
360
289
  firstPending.clear();
361
290
  parents.clear();
362
291
  recall.clear();
363
- await legacy.dispose?.();
292
+ await deps.dispose?.();
364
293
  };
365
294
  }
@@ -17,7 +17,7 @@ export function createV2MemoryBridge(directory) {
17
17
  return CONFIG.visibleRecall !== false;
18
18
  },
19
19
  isSubagentMemoryEnabled: () => CONFIG.subagentMemory !== false,
20
- isInternalPrompt,
20
+ isInternalPrompt: (_sessionID, text) => isInternalPrompt(text),
21
21
  async recordPrompt(sessionID, messageID, text) {
22
22
  await recordUserPrompt(sessionID, messageID, directory, text);
23
23
  },
package/dist/v2/plugin.js CHANGED
@@ -1,9 +1,14 @@
1
1
  import { fileURLToPath } from "node:url";
2
- import { OmmsPlugin } from "../index.js";
3
- import { loadOpencodeProvider } from "../adapters/opencode/opencode-provider-loader.js";
2
+ import { setOpencodeHost } from "../adapters/opencode/opencode-host.js";
3
+ import { CONFIG } from "../config.js";
4
+ import { recordOpencodePromptModel } from "../adapters/opencode/prompt-model.js";
5
+ import { performUserProfileLearning } from "../adapters/opencode/profile-learning.js";
6
+ import { getLanguageName } from "../services/language-detector.js";
4
7
  import { registerV2Adapter } from "./adapter.js";
5
8
  import { createV2MemoryBridge } from "./memory-bridge.js";
6
- import { createLegacyClient } from "./legacy-client.js";
9
+ import { createOpencodeSessionEvents } from "./session-events.js";
10
+ import { startOpencodeRuntime } from "./startup.js";
11
+ import { createOpencodeTools } from "./tools.js";
7
12
  /**
8
13
  * Remove old OMMS copies from the OpenCode cache. It runs in the background and
9
14
  * is not awaited, so a slow or failing clean-up never delays or breaks setup.
@@ -62,19 +67,25 @@ function startServiceSelfUpdate() {
62
67
  const OmmsPluginV2 = {
63
68
  id: "omms",
64
69
  async setup(ctx) {
65
- const legacyClient = createLegacyClient(ctx);
66
- const legacy = (await OmmsPlugin({
67
- client: legacyClient,
68
- directory: ctx.location.directory,
69
- worktree: ctx.location.project.directory,
70
- project: ctx.location.project,
71
- serverUrl: undefined,
72
- }));
73
- // The V1 initializer cannot discover a server URL from a native V2
74
- // context. Route internal structured-output calls through the adapter.
75
- const { setV2Client } = await loadOpencodeProvider();
76
- setV2Client(legacyClient);
77
- const adapter = await registerV2Adapter(ctx, legacy, createV2MemoryBridge(ctx.location.directory));
70
+ const directory = ctx.location.directory;
71
+ // Capture, profile learning, imports and Settings reach OpenCode through the context.
72
+ setOpencodeHost(ctx);
73
+ // The runtime's shutdown drops pending idle work, so it needs the handler created after it.
74
+ const holder = {};
75
+ const runtime = await startOpencodeRuntime(ctx, () => holder.events?.dispose());
76
+ const events = createOpencodeSessionEvents({
77
+ session: ctx.session,
78
+ directory,
79
+ autoCaptureHost: runtime.autoCaptureHost,
80
+ learnProfile: () => performUserProfileLearning(directory),
81
+ });
82
+ holder.events = events;
83
+ const adapter = await registerV2Adapter(ctx, {
84
+ tools: createOpencodeTools({ directory, tags: runtime.tags }, getLanguageName(CONFIG.autoCaptureLanguage || "en")),
85
+ recordPromptModel: recordOpencodePromptModel,
86
+ onEvent: (event) => events.handle(event),
87
+ dispose: () => runtime.dispose(),
88
+ }, createV2MemoryBridge(directory));
78
89
  startCacheCleanup();
79
90
  startServiceSelfUpdate();
80
91
  return adapter;
@@ -0,0 +1,30 @@
1
+ import type { Context } from "@opencode/plugin/promise/plugin";
2
+ import type { AutoCaptureHost } from "../core/host.js";
3
+ /** Capture waits this long after a session goes idle, so a quick follow-up prompt wins. */
4
+ export declare const IDLE_CAPTURE_DELAY_MS = 10000;
5
+ export type SessionEvent = {
6
+ readonly type: string;
7
+ readonly properties: any;
8
+ };
9
+ /** Map a raw V2 event onto the names the capture path reads. */
10
+ export declare function toSessionEvent(raw: any): SessionEvent;
11
+ /** True when a raw event names this plugin's directory, directly or through its session. */
12
+ export declare function eventBelongsToLocation(ctx: Context, raw: any): Promise<boolean>;
13
+ /**
14
+ * True for a session that OMMS created for its own model calls. Current versions generate
15
+ * without a session; older versions left sessions titled `omms capture`.
16
+ */
17
+ export declare function isInternalOpencodeSession(session: Pick<Context["session"], "get">, sessionID: string): Promise<boolean>;
18
+ export type SessionEventDeps = {
19
+ readonly session: Pick<Context["session"], "get">;
20
+ readonly directory: string;
21
+ readonly autoCaptureHost: AutoCaptureHost;
22
+ /** Profile learning for this plugin's directory; runs after capture. */
23
+ readonly learnProfile: () => Promise<void>;
24
+ };
25
+ /** Start capture, skill-turn recording, profile learning and the retry drain from session events. */
26
+ export declare function createOpencodeSessionEvents(deps: SessionEventDeps): {
27
+ handle(event: SessionEvent): Promise<void>;
28
+ /** Drop pending idle work; the plugin is unloading. */
29
+ dispose(): void;
30
+ };
@@ -0,0 +1,130 @@
1
+ import { resolve } from "node:path";
2
+ import { CONFIG, isConfigured } from "../config.js";
3
+ import { isInternalCaptureSessionTitle } from "../adapters/opencode/internal-capture-sessions.js";
4
+ import { log } from "../services/logger.js";
5
+ /** Capture waits this long after a session goes idle, so a quick follow-up prompt wins. */
6
+ export const IDLE_CAPTURE_DELAY_MS = 10_000;
7
+ /** Map a raw V2 event onto the names the capture path reads. */
8
+ export function toSessionEvent(raw) {
9
+ const envelope = raw?.payload ?? raw;
10
+ const source = envelope?.type === "sync" && envelope.syncEvent ? envelope.syncEvent : envelope;
11
+ const rawType = typeof source?.type === "string" ? source.type.replace(/\.1$/, "") : source?.type;
12
+ const data = source?.data ?? {};
13
+ // OpenCode 2.0.14 ends a turn with `session.execution.succeeded` and emits no
14
+ // `session.idle`; some builds report idleness through `session.status`. Both
15
+ // mean the session is idle. Subscribed events carry their payload in `data`;
16
+ // bus-style ones in `properties`.
17
+ const payload = source?.data ?? source?.properties ?? {};
18
+ if (rawType === "session.execution.succeeded" ||
19
+ (rawType === "session.status" && payload?.status?.type === "idle")) {
20
+ return { type: "session.idle", properties: { sessionID: payload.sessionID } };
21
+ }
22
+ const type = rawType === "session.compaction.ended" ? "session.compacted" : rawType;
23
+ if (source && typeof source === "object" && "properties" in source) {
24
+ return { type, properties: source.properties };
25
+ }
26
+ if (type === "session.created" || type === "session.updated") {
27
+ return { type, properties: { info: data.session ?? data.info ?? data } };
28
+ }
29
+ return { type, properties: data };
30
+ }
31
+ /** True when a raw event names this plugin's directory, directly or through its session. */
32
+ export async function eventBelongsToLocation(ctx, raw) {
33
+ const directory = raw?.location?.directory ??
34
+ raw?.directory ??
35
+ raw?.payload?.location?.directory ??
36
+ raw?.payload?.directory ??
37
+ raw?.data?.info?.directory;
38
+ if (typeof directory === "string") {
39
+ return resolve(directory) === resolve(ctx.location.directory);
40
+ }
41
+ const data = raw?.data ?? raw?.payload?.data ?? raw?.properties;
42
+ const sessionID = data?.sessionID ?? data?.session?.id ?? data?.info?.id;
43
+ if (!sessionID)
44
+ return false;
45
+ try {
46
+ const session = await ctx.session.get({ sessionID });
47
+ const sessionDirectory = session?.location?.directory ?? session?.directory ?? session?.data?.directory;
48
+ return (typeof sessionDirectory === "string" &&
49
+ resolve(sessionDirectory) === resolve(ctx.location.directory));
50
+ }
51
+ catch {
52
+ return false;
53
+ }
54
+ }
55
+ /**
56
+ * True for a session that OMMS created for its own model calls. Current versions generate
57
+ * without a session; older versions left sessions titled `omms capture`.
58
+ */
59
+ export async function isInternalOpencodeSession(session, sessionID) {
60
+ try {
61
+ const info = await session.get({ sessionID });
62
+ return isInternalCaptureSessionTitle(info?.title);
63
+ }
64
+ catch (error) {
65
+ log("internal capture session check failed", { sessionID, error: String(error) }, "error");
66
+ return false;
67
+ }
68
+ }
69
+ /** Start capture, skill-turn recording, profile learning and the retry drain from session events. */
70
+ export function createOpencodeSessionEvents(deps) {
71
+ let idleTimeout = null;
72
+ const onCreated = async (event) => {
73
+ if (!isConfigured() || !CONFIG.autoCaptureEnabled)
74
+ return;
75
+ const sessionID = event.properties?.info?.id ?? event.properties?.sessionID;
76
+ // OMMS's own structured-output sessions must not start a retry pass.
77
+ if (sessionID && (await isInternalOpencodeSession(deps.session, sessionID)))
78
+ return;
79
+ const { startCaptureRetryDrain } = await import("../services/capture-retry-drain.js");
80
+ startCaptureRetryDrain("opencode");
81
+ };
82
+ const onIdle = async (event) => {
83
+ // Skill learning records turns even when memory capture is off (ADR-036).
84
+ if (!isConfigured() || !(CONFIG.autoCaptureEnabled || CONFIG.skillLearning))
85
+ return;
86
+ const sessionID = event.properties?.sessionID;
87
+ if (!sessionID)
88
+ return;
89
+ // Internal sessions must not re-trigger capture or learning: that would loop
90
+ // idle, model call, idle without end.
91
+ if (await isInternalOpencodeSession(deps.session, sessionID)) {
92
+ log("Skipping idle processing for internal capture session", { sessionID });
93
+ return;
94
+ }
95
+ if (idleTimeout)
96
+ clearTimeout(idleTimeout);
97
+ idleTimeout = setTimeout(async () => {
98
+ try {
99
+ const { performAutoCapture } = await import("../services/auto-capture.js");
100
+ await performAutoCapture(deps.autoCaptureHost, sessionID, deps.directory);
101
+ if (!CONFIG.autoCaptureEnabled)
102
+ return; // Skill turns only; the memory work stays off.
103
+ await deps.learnProfile();
104
+ const { cleanupService } = await import("../services/cleanup-service.js");
105
+ if (await cleanupService.shouldRunCleanup())
106
+ await cleanupService.runCleanup();
107
+ }
108
+ catch (error) {
109
+ log("Idle processing error", { error: String(error) }, "error");
110
+ }
111
+ finally {
112
+ idleTimeout = null;
113
+ }
114
+ }, IDLE_CAPTURE_DELAY_MS);
115
+ };
116
+ return {
117
+ async handle(event) {
118
+ if (event.type === "session.created")
119
+ await onCreated(event);
120
+ else if (event.type === "session.idle")
121
+ await onIdle(event);
122
+ },
123
+ /** Drop pending idle work; the plugin is unloading. */
124
+ dispose() {
125
+ if (idleTimeout)
126
+ clearTimeout(idleTimeout);
127
+ idleTimeout = null;
128
+ },
129
+ };
130
+ }
@@ -0,0 +1,14 @@
1
+ import type { Context } from "@opencode/plugin/promise/plugin";
2
+ /**
3
+ * Start OMMS for an OpenCode V2 plugin: config, traces, runtime record, warm-up, models,
4
+ * backfill, web app and shutdown handlers. Returns the dispose function for plugin unload.
5
+ */
6
+ export declare function startOpencodeRuntime(ctx: Context, onCleanup?: () => void): Promise<{
7
+ directory: string & import("effect/Brand").Brand<"AbsolutePath">;
8
+ tags: {
9
+ user: import("../services/tags.js").TagInfo;
10
+ project: import("../services/tags.js").TagInfo;
11
+ };
12
+ autoCaptureHost: import("../core/host.js").AutoCaptureHost;
13
+ dispose(): Promise<void>;
14
+ }>;