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,29 +1,20 @@
1
1
  import { CONFIG } from "../../config.js";
2
2
  import { resolveOpencodeHostModel } from "../../services/ai/live-model-choice.js";
3
3
  import { loadOpencodeProvider } from "./opencode-provider-loader.js";
4
- let _cachedClient = null;
5
- let _cachedProvider = null;
6
- let _cachedModel = null;
7
- export async function getOpenCodeClient() {
4
+ /** The OpenCode plugin context for profile calls, once the host model's provider is signed in. */
5
+ export async function getOpencodeProfileHost() {
8
6
  const hostModel = resolveOpencodeHostModel(CONFIG);
9
7
  if (!hostModel) {
10
8
  throw new Error("omms: live calls use the external API; no OpenCode model is configured");
11
9
  }
12
- const { isProviderConnected, getV2Client, resolveOpencodeModelRef } = await loadOpencodeProvider();
10
+ const { isProviderConnected, getOpencodeHost, resolveOpencodeModelRef } = await loadOpencodeProvider();
13
11
  // "inherit" checks the provider of the model it resolves to.
14
- const { providerID: provider, modelID: model } = resolveOpencodeModelRef(hostModel);
15
- if (_cachedClient && _cachedProvider === provider && _cachedModel === model) {
16
- return _cachedClient;
17
- }
12
+ const { providerID: provider } = resolveOpencodeModelRef(hostModel);
18
13
  if (!isProviderConnected(provider)) {
19
14
  throw new Error(`opencode provider '${provider}' is not connected. Check your opencode provider configuration.`);
20
15
  }
21
- const client = getV2Client();
22
- if (!client) {
23
- throw new Error("omms: v2 client not initialized");
24
- }
25
- _cachedClient = client;
26
- _cachedProvider = provider;
27
- _cachedModel = model;
28
- return client;
16
+ const host = getOpencodeHost();
17
+ if (!host)
18
+ throw new Error("omms: the OpenCode plugin context is not ready");
19
+ return host;
29
20
  }
@@ -1,23 +1,7 @@
1
1
  import type { ModelPort } from "../../core/profile-analysis.js";
2
2
  import type { OpencodeModelRef } from "./opencode-import-models.js";
3
- type PromptInfo = {
4
- error?: {
5
- name: string;
6
- data?: {
7
- message?: string;
8
- };
9
- };
10
- };
11
- /**
12
- * Extract assistant text from an OpenCode session.prompt result.
13
- * AssistantMessage has no `text` field; content lives in `parts` (#177).
14
- */
15
- export declare function extractTextFromPromptResult(promptResult: unknown): {
16
- info: PromptInfo | undefined;
17
- rawText: string;
18
- };
3
+ import type { OpencodeHost } from "./opencode-host.js";
19
4
  /** Adapt OpenCode's host model to the shared profile code, as `adaptPiProfileModel` does for Pi. */
20
- export declare function adaptOpencodeProfileModel(v2Client: unknown, hostModel: OpencodeModelRef): ModelPort;
5
+ export declare function adaptOpencodeProfileModel(host: Pick<OpencodeHost, "generate">, hostModel: OpencodeModelRef): ModelPort;
21
6
  /** Route shared profile calls in this process to OpenCode's host model while one is configured. */
22
7
  export declare function registerOpencodeProfileModel(): void;
23
- export {};
@@ -4,22 +4,7 @@ import { log } from "../../services/logger.js";
4
4
  import { OPENCODE_PROFILE_CLEANUP_TIMEOUT_MS } from "../../services/request-timeouts.js";
5
5
  import { registerHostProfileModel } from "../../services/user-profile/profile-model.js";
6
6
  import { loadOpencodeProvider } from "./opencode-provider-loader.js";
7
- import { getOpenCodeClient } from "./profile-llm-client.js";
8
- /**
9
- * Extract assistant text from an OpenCode session.prompt result.
10
- * AssistantMessage has no `text` field; content lives in `parts` (#177).
11
- */
12
- export function extractTextFromPromptResult(promptResult) {
13
- const result = promptResult;
14
- const info = result?.data?.info ?? result?.info;
15
- const parts = result?.data?.parts ?? result?.parts ?? [];
16
- const rawText = parts
17
- .filter((p) => p.type === "text" && p.text)
18
- .map((p) => p.text)
19
- .join("\n")
20
- .trim();
21
- return { info, rawText };
22
- }
7
+ import { getOpencodeProfileHost } from "./profile-llm-client.js";
23
8
  function raceWithTimeout(promise, ms, message) {
24
9
  let timer;
25
10
  const timeout = new Promise((_, reject) => {
@@ -30,68 +15,32 @@ function raceWithTimeout(promise, ms, message) {
30
15
  clearTimeout(timer);
31
16
  });
32
17
  }
33
- /**
34
- * Plain-text reply through a transient OpenCode session. Only AI profile cleanup
35
- * calls it, so the session carries the cleanup title the history reader skips.
36
- */
37
- async function completeViaSession(v2Client, hostModel, systemPrompt, userPrompt) {
18
+ /** Plain-text reply through one sessionless OpenCode generation. Only AI profile cleanup calls it. */
19
+ async function completeViaGeneration(host, hostModel, systemPrompt, userPrompt) {
38
20
  const t0 = Date.now();
39
21
  // Only "inherit" (or no model configured) needs resolving to the session's recent model.
40
22
  const model = hostModel.modelID === "inherit"
41
23
  ? (await loadOpencodeProvider()).resolveOpencodeModelRef(hostModel)
42
24
  : hostModel;
43
- const created = (await raceWithTimeout(v2Client.session.create({
44
- title: "omms profile cleanup",
45
- directory: process.cwd(),
46
- }), 30000, "session.create timeout"));
47
- log("AI cleanup: session.create result", {
48
- rawType: typeof created,
49
- keys: Object.keys(created || {}),
50
- hasData: !!created?.data,
51
- dataId: created?.data?.id,
52
- });
53
- const sessionID = created?.data?.id || created?.id || created?.sessionID;
54
- if (!sessionID)
55
- throw new Error("session.create returned no session id");
56
- log("AI cleanup: session created", { sessionID, createMs: Date.now() - t0 });
57
- try {
58
- const TIMEOUT_MS = OPENCODE_PROFILE_CLEANUP_TIMEOUT_MS;
59
- const promptResult = await raceWithTimeout(v2Client.session.prompt({
60
- sessionID,
61
- model,
62
- system: systemPrompt,
63
- parts: [{ type: "text", text: userPrompt }],
64
- // `noReply` suppresses assistant generation; cleanup needs the JSON reply (#177).
65
- noReply: false,
66
- }), TIMEOUT_MS, `opencodeClient prompt timeout after ${TIMEOUT_MS}ms`);
67
- log("AI cleanup: session.prompt done", { promptMs: Date.now() - t0 });
68
- const { info, rawText } = extractTextFromPromptResult(promptResult);
69
- if (!info)
70
- throw new Error("prompt response missing info");
71
- if (info.error)
72
- throw new Error(`opencode reported ${info.error.name}: ${info.error.data?.message ?? ""}`);
73
- return rawText;
74
- }
75
- finally {
76
- try {
77
- await v2Client.session.delete({ sessionID });
78
- }
79
- catch {
80
- // ignore cleanup failures for ephemeral sessions
81
- }
82
- }
25
+ const timeoutMs = OPENCODE_PROFILE_CLEANUP_TIMEOUT_MS;
26
+ const generated = await raceWithTimeout(host.generate.text({
27
+ prompt: [systemPrompt, userPrompt].filter(Boolean).join("\n\n"),
28
+ model: { providerID: model.providerID, id: model.modelID },
29
+ }), timeoutMs, `opencode generation timeout after ${timeoutMs}ms`);
30
+ log("AI cleanup: generation done", { generateMs: Date.now() - t0 });
31
+ return (generated?.text ?? "").trim();
83
32
  }
84
33
  /** Adapt OpenCode's host model to the shared profile code, as `adaptPiProfileModel` does for Pi. */
85
- export function adaptOpencodeProfileModel(v2Client, hostModel) {
34
+ export function adaptOpencodeProfileModel(host, hostModel) {
86
35
  return {
87
36
  provider: hostModel.providerID,
88
37
  modelId: hostModel.modelID,
89
- complete: (systemPrompt, userPrompt) => completeViaSession(v2Client, hostModel, systemPrompt, userPrompt),
38
+ complete: (systemPrompt, userPrompt) => completeViaGeneration(host, hostModel, systemPrompt, userPrompt),
90
39
  async completeStructured(systemPrompt, userPrompt, schema) {
91
- const client = await getOpenCodeClient();
40
+ const profileHost = await getOpencodeProfileHost();
92
41
  const { generateStructuredOutput } = await loadOpencodeProvider();
93
42
  return generateStructuredOutput({
94
- client,
43
+ host: profileHost,
95
44
  providerID: hostModel.providerID,
96
45
  modelID: hostModel.modelID,
97
46
  systemPrompt,
@@ -107,11 +56,11 @@ export function registerOpencodeProfileModel() {
107
56
  const hostModel = resolveOpencodeHostModel(CONFIG);
108
57
  if (!hostModel)
109
58
  return null;
110
- const v2Client = (await loadOpencodeProvider()).getV2Client();
111
- if (!v2Client) {
112
- log("profile model: opencode client unavailable, falling back to external API", undefined, "warn");
59
+ const host = (await loadOpencodeProvider()).getOpencodeHost();
60
+ if (!host) {
61
+ log("profile model: OpenCode plugin context unavailable, falling back to external API", undefined, "warn");
113
62
  return null;
114
63
  }
115
- return adaptOpencodeProfileModel(v2Client, hostModel);
64
+ return adaptOpencodeProfileModel(host, hostModel);
116
65
  });
117
66
  }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Remember a model request's model as the session model, and store it on its recorded
3
+ * prompt row so capture can follow it. The row is written only while live calls follow
4
+ * the session model ("inherit", or nothing configured).
5
+ */
6
+ export declare function recordOpencodePromptModel(messageID: string, model: {
7
+ readonly providerID: string;
8
+ readonly id: string;
9
+ }): Promise<void>;
@@ -0,0 +1,21 @@
1
+ import { CONFIG, isConfigured } from "../../config.js";
2
+ import { resolveOpencodeHostModel } from "../../services/ai/live-model-choice.js";
3
+ import { log } from "../../services/logger.js";
4
+ import { userPromptManager } from "../../services/user-prompt/user-prompt-manager.js";
5
+ import { rememberOpencodeSessionModel } from "./opencode-host.js";
6
+ /**
7
+ * Remember a model request's model as the session model, and store it on its recorded
8
+ * prompt row so capture can follow it. The row is written only while live calls follow
9
+ * the session model ("inherit", or nothing configured).
10
+ */
11
+ export async function recordOpencodePromptModel(messageID, model) {
12
+ rememberOpencodeSessionModel(model);
13
+ if (!isConfigured() || resolveOpencodeHostModel(CONFIG)?.modelID !== "inherit")
14
+ return;
15
+ try {
16
+ await userPromptManager.setPromptModel(messageID, model.providerID, model.id);
17
+ }
18
+ catch (error) {
19
+ log("v2 context: failed to record the prompt model", { error: String(error) }, "error");
20
+ }
21
+ }
@@ -1,3 +1,3 @@
1
- import { type ToolDefinition } from "@opencode-ai/plugin";
2
- /** The OpenCode `skills` tool. V2 reaches it through the V1 plugin's `tool.skills`. */
3
- export declare function opencodeSkillsTool(directory: string): ToolDefinition;
1
+ import { type SkillsToolArgs } from "../../core/skills-tool.js";
2
+ /** Run the OpenCode `skills` tool. The V2 plugin registers it in `src/v2/tools.ts`. */
3
+ export declare function runOpencodeSkillsTool(args: SkillsToolArgs, directory: string): Promise<string>;
@@ -1,31 +1,5 @@
1
- import { tool } from "@opencode-ai/plugin";
2
- import { SKILLS_TOOL_DESCRIPTION, SKILLS_TOOL_MODES, runSkillsTool, } from "../../core/skills-tool.js";
3
- /** The OpenCode `skills` tool. V2 reaches it through the V1 plugin's `tool.skills`. */
4
- export function opencodeSkillsTool(directory) {
5
- return tool({
6
- description: SKILLS_TOOL_DESCRIPTION,
7
- args: {
8
- mode: tool.schema.enum(SKILLS_TOOL_MODES).optional(),
9
- skill: tool.schema.string().optional(),
10
- id: tool.schema.string().optional(),
11
- state: tool.schema.string().optional(),
12
- order: tool.schema.string().optional(),
13
- folder: tool.schema.string().optional(),
14
- idea: tool.schema
15
- .object({
16
- name: tool.schema.string().optional(),
17
- description: tool.schema.string().optional(),
18
- reason: tool.schema.string().optional(),
19
- evidenceTurnIds: tool.schema.array(tool.schema.number()).optional(),
20
- })
21
- .optional(),
22
- limit: tool.schema.number().optional(),
23
- content: tool.schema.string().optional(),
24
- summary: tool.schema.string().optional(),
25
- evidence: tool.schema.array(tool.schema.number()).optional(),
26
- },
27
- async execute(args) {
28
- return runSkillsTool(args, directory);
29
- },
30
- });
1
+ import { runSkillsTool } from "../../core/skills-tool.js";
2
+ /** Run the OpenCode `skills` tool. The V2 plugin registers it in `src/v2/tools.ts`. */
3
+ export function runOpencodeSkillsTool(args, directory) {
4
+ return runSkillsTool(args, directory);
31
5
  }
@@ -0,0 +1,11 @@
1
+ export type OpencodeNotice = {
2
+ readonly title?: string;
3
+ readonly message: string;
4
+ readonly variant?: "info" | "success" | "warning" | "error";
5
+ readonly duration?: number;
6
+ };
7
+ /**
8
+ * Show a notice from the OpenCode server plugin. The V2 server plugin context has no
9
+ * toast API, so the notice goes to the console, which OpenCode writes to its log.
10
+ */
11
+ export declare function showOpencodeNotice(notice: OpencodeNotice): void;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Show a notice from the OpenCode server plugin. The V2 server plugin context has no
3
+ * toast API, so the notice goes to the console, which OpenCode writes to its log.
4
+ */
5
+ export function showOpencodeNotice(notice) {
6
+ const text = [notice.title, notice.message].filter(Boolean).join(": ");
7
+ if (!text)
8
+ return;
9
+ const method = notice.variant === "error" ? "error" : notice.variant === "warning" ? "warn" : "info";
10
+ console[method](`[omms] ${text}`);
11
+ }
@@ -1,10 +1,10 @@
1
1
  import { isStructuredSummaryPromptMessage } from "../../core/internal-prompt.js";
2
2
  export { isStructuredSummaryPromptMessage };
3
3
  /** True when a prompt is omms's own internal traffic and must not be recorded or searched. */
4
- export declare function isInternalPrompt(sessionID: string, userMessage: string): boolean;
4
+ export declare function isInternalPrompt(userMessage: string): boolean;
5
5
  /**
6
6
  * Record a real user prompt so idle auto-capture can pair it with the session's
7
- * response. Shared by the V1 `chat.message` hook and the v2 `prompt` hook.
7
+ * response. The V2 `context` hook records each admitted prompt.
8
8
  * Returns false when the prompt is empty or internal and was not recorded.
9
9
  */
10
10
  export declare function recordUserPrompt(sessionID: string, messageID: string, directory: string, userMessage: string): Promise<boolean>;
@@ -1,19 +1,18 @@
1
1
  import { isStructuredSummaryPromptMessage } from "../../core/internal-prompt.js";
2
- import { isInternalStructuredSession } from "./opencode-provider.js";
3
2
  import { userPromptManager } from "../../services/user-prompt/user-prompt-manager.js";
4
3
  import { isFullyPrivate, stripPrivateContent } from "../../services/privacy.js";
5
4
  export { isStructuredSummaryPromptMessage };
6
5
  /** True when a prompt is omms's own internal traffic and must not be recorded or searched. */
7
- export function isInternalPrompt(sessionID, userMessage) {
8
- return isStructuredSummaryPromptMessage(userMessage) || isInternalStructuredSession(sessionID);
6
+ export function isInternalPrompt(userMessage) {
7
+ return isStructuredSummaryPromptMessage(userMessage);
9
8
  }
10
9
  /**
11
10
  * Record a real user prompt so idle auto-capture can pair it with the session's
12
- * response. Shared by the V1 `chat.message` hook and the v2 `prompt` hook.
11
+ * response. The V2 `context` hook records each admitted prompt.
13
12
  * Returns false when the prompt is empty or internal and was not recorded.
14
13
  */
15
14
  export async function recordUserPrompt(sessionID, messageID, directory, userMessage) {
16
- if (!userMessage.trim() || isInternalPrompt(sessionID, userMessage))
15
+ if (!userMessage.trim() || isInternalPrompt(userMessage))
17
16
  return false;
18
17
  // Profile learning quotes saved prompts as evidence, so <private> text must
19
18
  // never reach the store (the Claude Code hook strips it the same way).
@@ -3,4 +3,4 @@
3
3
  * src/config.ts until that file reached its size ceiling; the text is
4
4
  * unchanged. Shared by config.ts and the global settings writer.
5
5
  */
6
- export declare const CONFIG_TEMPLATE = "{\n // ============================================\n // omms (Opinionated Modular Memory System) Configuration\n // ============================================\n \n // Storage location for the vector database. Leave unset to use the\n // default ~/.omms/data (a legacy ~/.opencode-mem/data store is migrated\n // there automatically on first start; see docs/omms-migration.md).\n // \"storagePath\": \"~/.omms/data\",\n\n \"userEmailOverride\": \"\",\n \"userNameOverride\": \"\",\n \n // ============================================\n // Embedding Model (for similarity search)\n // ============================================\n // Local = Hugging Face / ONNX via @huggingface/transformers (not Apple MLX).\n // Remote = set embeddingApiUrl (OpenAI-compatible /embeddings); embeddingApiKey is optional.\n \n // Default: Nomic Embed v1 (768 dimensions, 8192 context, multilingual)\n \"embeddingModel\": \"Xenova/nomic-embed-text-v1\",\n\n // Opt-in Nomic task prefixes (search_document: / search_query:). After enabling,\n // re-index existing memories so store and query vectors stay aligned.\n // \"embeddingUseTaskPrefixes\": true,\n \n // Auto-detected dimensions (no need to set manually)\n // \"embeddingDimensions\": 768,\n \n // Other recommended local models:\n // \"embeddingModel\": \"Xenova/jina-embeddings-v2-base-en\", // 768 dims, English-only, 8192 context\n // \"embeddingModel\": \"Xenova/jina-embeddings-v2-small-en\", // 512 dims, faster, 8192 context\n // \"embeddingModel\": \"Xenova/all-MiniLM-L6-v2\", // 384 dims, very fast, 512 context\n // \"embeddingModel\": \"Xenova/all-mpnet-base-v2\", // 768 dims, good quality, 512 context\n \n // Optional: OpenAI-compatible API for embeddings (URL required; key only if the server needs one)\n // \"embeddingApiUrl\": \"https://api.openai.com/v1\",\n // \"embeddingApiKey\": \"env://OPENAI_API_KEY\", // or \"sk-...\" / \"file:///path/to/key\"\n // \"embeddingModel\": \"text-embedding-3-small\", // 1536 dims, auto-detected\n \n // ============================================\n // Web Server Settings\n // ============================================\n \n // Start a background import of past chats on each host's next start.\n \"autoBackfill\": true,\n // \"piBackfillModel\": \"inherit\", // or \"external\", or \"provider/model\"\n // \"opencodeBackfillModel\": \"inherit\", // or \"external\", or \"provider/model\"\n // Directory maps for history recorded in moved or deleted directories.\n // \"importPathMaps\": [{ \"from\": \"~/code/app-feat-x\", \"to\": \"~/code/app\" }],\n // Unresolved directories to hide on the Directory maps page. They stay unimported.\n // \"importIgnoredDirectories\": [\"/private/tmp/scratch\"],\n // Claude Code's folder, when it is not ~/.claude (like CLAUDE_CONFIG_DIR).\n // \"claudeConfigDir\": \"~/.claude-work\",\n\n // Register the web app to start when you log in.\n \"webServerAutoStart\": true,\n // Enable web UI for managing memories (accessible at http://localhost:4747)\n \"webServerEnabled\": true,\n \n // Port for web UI server\n \"webServerPort\": 4747,\n \n // Host address for web UI (use 127.0.0.1 for local only, 0.0.0.0 for network access)\n \"webServerHost\": \"127.0.0.1\",\n\n // Optional HTTP Basic Auth for the web UI. Accepts literal, env://, or file:// secrets.\n // \"webServerAuthPassword\": \"\",\n // \"webServerAuthUsername\": \"\",\n\n // Deprecated: imported once into the API token table, then no longer read.\n // Create API tokens on the Settings page (Keys and access) instead.\n // \"webServerApiToken\": \"env://OMMS_WEB_TOKEN\",\n \n // ============================================\n // Database Settings\n // ============================================\n \n // Maximum vectors per database shard (auto-creates new shard when limit reached)\n \"maxVectorsPerShard\": 50000,\n \n // Automatically delete old memories based on retention period\n \"autoCleanupEnabled\": true,\n \n // Days to keep memories before auto-cleanup (only if autoCleanupEnabled is true)\n \"autoCleanupRetentionDays\": 30,\n \n // Automatically detect and remove duplicate memories\n \"deduplicationEnabled\": true,\n \n // Similarity threshold (0-1) for detecting duplicates (higher = stricter)\n \"deduplicationSimilarityThreshold\": 0.90,\n \n // ============================================\n // Memory Scope Settings\n // ============================================\n\n // Default scope for memory list/search queries\n // \"project\" keeps queries within the current project, \"all-projects\" searches across all project shards\n \"memory\": {\n \"defaultScope\": \"project\"\n },\n\n // ============================================\n // Model for auto-capture and profile learning (same rule in OpenCode and Pi)\n // ============================================\n\n // Which model summarises your work, in this order:\n // 1. The host model below: opencodeProvider/opencodeModel in OpenCode,\n // piProvider/piModel in Pi. Set the model to \"inherit\" to follow\n // whatever model the session is using, or to \"external\" to send\n // every call to the external API below.\n // 2. If no host model is set: the external API (memoryModel/memoryApiUrl/\n // memoryApiKey further down).\n // 3. If neither is set: the session's own model.\n // If the host model fails and the external API is configured, the external\n // API is used instead.\n //\n // Host models go through OpenCode's or Pi's own sign-in (OAuth like Claude\n // Pro/Max, GitHub Copilot, ChatGPT, or your own key there), so no separate API\n // key is needed here.\n //\n // Examples (OpenCode names from 'opencode providers list'; Pi names from its model list):\n // \"opencodeProvider\": \"anthropic\", \"opencodeModel\": \"claude-haiku-4-5-20251001\"\n // \"opencodeProvider\": \"openai\", \"opencodeModel\": \"inherit\"\n // \"piProvider\": \"openai-codex\", \"piModel\": \"gpt-5.6-luna\"\n //\n // \"opencodeProvider\": \"anthropic\",\n // \"opencodeModel\": \"claude-haiku-4-5-20251001\",\n // \"piProvider\": \"openai-codex\",\n // \"piModel\": \"gpt-5.6-luna\",\n\n // ============================================\n // Auto-Capture Settings\n // ============================================\n \n // Auto-capture runs in the background without blocking your main session.\n // Note: Ollama may not support tool calling. Use OpenAI, Anthropic, or Groq for best results.\n \n \"autoCaptureEnabled\": true,\n \n // Provider type: \"openai-chat\" | \"openai-responses\" | \"anthropic\" | \"minimax\" | \"orcarouter\" | \"google-gemini\"\n // Note: \"openai-chat\" is a generic OpenAI API-compatible mode.\n // Any service that follows the OpenAI Chat Completions API can use it via custom \"memoryApiUrl\".\n \"memoryProvider\": \"openai-chat\",\n \n // External API (step 2 above, and the fallback when a host model fails).\n // Uncomment all 3 lines and replace memoryApiKey before use:\n // \"memoryModel\": \"gpt-4o-mini\",\n // \"memoryApiUrl\": \"https://api.openai.com/v1\",\n // \"memoryApiKey\": \"sk-...\",\n\n // API Key Formats:\n // Direct value: \"sk-...\"\n // From file: \"file://~/.config/litellm-key.txt\"\n // From env variable: \"env://LITELLM_API_KEY\"\n \n // Examples for different providers:\n // Any OpenAI-compatible endpoint can use the \"openai-chat\" provider pattern below.\n // Common examples: DeepSeek, Qwen (via Alibaba Cloud ModelStudio),\n // Zhipu GLM (BigModel platform), and Kimi (Moonshot AI platform).\n\n // OpenAI Chat Completion (default, backward compatible):\n // \"memoryProvider\": \"openai-chat\"\n // \"memoryModel\": \"gpt-4o-mini\"\n // \"memoryApiUrl\": \"https://api.openai.com/v1\"\n // \"memoryApiKey\": \"sk-...\"\n\n // DeepSeek (OpenAI-compatible example):\n // \"memoryProvider\": \"openai-chat\"\n // \"memoryModel\": \"deepseek-chat\"\n // \"memoryApiUrl\": \"https://api.deepseek.com/v1\"\n // \"memoryApiKey\": \"sk-...\"\n \n // OpenAI Responses API (recommended, with session support):\n // \"memoryProvider\": \"openai-responses\"\n // \"memoryModel\": \"gpt-4o\"\n // \"memoryApiUrl\": \"https://api.openai.com/v1\"\n // \"memoryApiKey\": \"sk-...\"\n \n // Anthropic (with session support):\n // \"memoryProvider\": \"anthropic\"\n // \"memoryModel\": \"claude-3-5-haiku-20241022\"\n // \"memoryApiUrl\": \"https://api.anthropic.com/v1\"\n // \"memoryApiKey\": \"sk-ant-...\"\n\n // MiniMax (Anthropic Messages-compatible endpoint, with session support):\n // \"memoryProvider\": \"minimax\"\n // \"memoryModel\": \"MiniMax-M3\"\n // \"memoryApiUrl\": \"https://api.minimax.io\" // global endpoint\n // \"memoryApiKey\": \"<MiniMax API key>\"\n // // China endpoint: \"memoryApiUrl\": \"https://api.minimaxi.com\"\n // // Optional adaptive thinking for MiniMax-M3:\n // \"memoryExtraParams\": { \"thinking\": { \"type\": \"adaptive\" } }\n\n // OrcaRouter (OpenAI-compatible gateway, namespaced model IDs, with session support):\n // \"memoryProvider\": \"orcarouter\"\n // \"memoryApiKey\": \"<OrcaRouter API key>\"\n // // memoryApiUrl and memoryModel are optional \u2014 they default to\n // // https://api.orcarouter.ai/v1 and \"orcarouter/auto\" (a routing alias).\n // // OrcaRouter rejects bare model names, so if you set memoryModel, use a\n // // namespaced ID such as \"openai/gpt-5.5\" or \"deepseek/deepseek-v4-flash\".\n // \"memoryModel\": \"openai/gpt-5.5\"\n\n // Groq (OpenAI-compatible, use openai-chat provider):\n // \"memoryProvider\": \"openai-chat\"\n // \"memoryModel\": \"llama-3.3-70b-versatile\"\n // \"memoryApiUrl\": \"https://api.groq.com/openai/v1\"\n // \"memoryApiKey\": \"gsk_...\"\n \n // Maximum iterations for multi-turn AI analysis (for openai-responses, anthropic, and minimax)\n \"autoCaptureMaxIterations\": 5,\n \n // Timeout per iteration in milliseconds (30 seconds default)\n \"autoCaptureIterationTimeout\": 30000,\n\n // Tries for one turn's capture, on Pi and OpenCode, 2 and 4 seconds apart. After the\n // last one fails, a turn the model could not reach goes to the capture retry queue.\n \"autoCaptureMaxRetries\": 3,\n\n // Maximum UTF-8 bytes for the auto-capture markdown context sent to the summary model.\n // Prevents HTTP 400 context overflows on models with ~131K token windows (e.g. Groq Llama).\n // Rough guide: tokens \u2248 bytes / 4 for mixed code/prose.\n \"autoCaptureMaxContextBytes\": 131072,\n \n // Days to keep AI session history before cleanup\n \"aiSessionRetentionDays\": 7,\n\n // Capture diagnostics: every capture attempt always writes one metadata line\n // (model, stop reason, sizes, outcome) to ~/.omms/omms.log, with no\n // conversation text. Set captureTrace to true to also write each attempt's\n // prompt and raw reply to ~/.omms/traces/ for debugging. Traces can contain\n // conversation content: <private> text and common API key formats are\n // redacted, files are readable only by you, and they are deleted after\n // captureTraceRetentionDays. Only this global file can turn tracing on.\n // \"captureTrace\": false,\n // \"captureTraceRetentionDays\": 7,\n // \"captureAttemptRetentionDays\": 30,\n\n // Skill learning: record each full turn (prompt, replies, tool calls with\n // their outcome, and the skills used) in ~/.omms/data/skills.db, so OMMS can\n // rank your skills and draft better versions. Off by default. A project config\n // can turn it off but never on. No model call is made to record a turn.\n // \"skillLearning\": false,\n // Remove <private> text and common API key formats before a turn is stored.\n // Global config only. Turning this off stores text as written.\n // \"skillLearningRedact\": true,\n // Folders OMMS reads skills from. Absolute or starting with ~/. Global config\n // only. Unset: ~/.agents/skills and the Claude Code folder's skills folder.\n // \"skillFolders\": [\"~/.agents/skills\"],\n\n // Visible recall: the OpenCode terminal TUI and Pi's interactive terminal draw\n // a row for each memory recall. On by default. A project config can turn it\n // off but never on.\n // \"visibleRecall\": true,\n\n // Memory injection, on OpenCode V2, Pi and Claude Code. Each prompt gets the\n // memories that match it. openingContext also searches memory with\n // openingContextQuery and adds the matches to the\n // first message of a new session. The query is global only, trimmed, and\n // 500 characters at most. subagentMemory gives each subagent the memories\n // that match its task. For both switches, a project config can only turn these off.\n // \"openingContext\": false,\n // \"openingContextQuery\": \"project architecture, setup, conventions and key decisions\",\n // \"subagentMemory\": true,\n\n // Capture retry queue: when a live capture fails because the model cannot be\n // reached (network error, timeout, HTTP 408, 429 or 5xx), OMMS keeps a copy of\n // the turn in ~/.omms/data and tries it again later. The copy has <private>\n // text and common API key formats removed. Turns are deleted when the retry\n // ends or after captureRetryRetentionHours (0 to 720). 0 turns the queue off.\n // Only this global file can set it.\n // \"captureRetryRetentionHours\": 72,\n\n // Temperature for AI API requests (set to false to omit parameter for models that don't support it)\n // Some reasoning models (like o1, o3, gpt-5) don't support temperature parameter\n // Set to false and add \"memoryTemperature\": false in config when using such models\n \"memoryTemperature\": 0.3,\n\n // Extra parameters to include in API request body\n // Useful for local inference servers (e.g. llama-server with --jinja) that support\n // additional parameters like disabling thinking/reasoning mode\n // Example for Qwen3 models: { \"enable_thinking\": false }\n // \"memoryExtraParams\": {},\n\n // Language for auto-capture summaries (default: \"auto\" for auto-detection)\n // Options: \"auto\", \"en\", \"id\", \"zh\", \"ja\", \"es\", \"fr\", \"de\", \"ru\", \"pt\", \"ar\", \"ko\"\n // \"autoCaptureLanguage\": \"auto\",\n\n // ============================================\n // Toast Notifications\n // ============================================\n\n // Show toast when memory is auto-captured\n \"showAutoCaptureToasts\": true,\n\n // Show toast when user profile is updated\n \"showUserProfileToasts\": true,\n\n // Show toast for error messages\n \"showErrorToasts\": true,\n\n // ============================================\n // User Profile System\n // ============================================\n\n // Analyze user prompts every N prompts to build/update your user profile\n // When N uncaptured prompts accumulate, AI will analyze them to identify:\n // - User preferences (code style, communication style, tool preferences)\n // - User patterns (recurring topics, problem domains, technical interests)\n // - User workflows (development habits, sequences, learning style)\n // - Skill level (overall and per-domain assessment)\n \"userProfileAnalysisInterval\": 10,\n\n // Days before inactive items (all types) are eligible for removal\n \"userProfileStaleDays\": 2,\n\n // Days before an inactive workflow is eligible for removal. A workflow seen\n // userProfileMinEvidenceForRetention times or more is kept.\n \"userProfileWorkflowStaleDays\": 30,\n\n // Number of preferences shown in UI\n \"userProfileDisplayPreferences\": 20,\n \n // Number of patterns shown in UI\n \"userProfileDisplayPatterns\": 15,\n \n // Number of workflows shown in UI\n \"userProfileDisplayWorkflows\": 10,\n \n // Number of preferences injected into LLM conversation context\n // Keep this small \u2014 the strongest signals are enough; more dilute LLM attention\n \"userProfileInjectPreferences\": 5,\n \n // Number of patterns injected into LLM conversation context\n \"userProfileInjectPatterns\": 5,\n \n // Number of workflows injected into LLM conversation context\n \"userProfileInjectWorkflows\": 3,\n \n // Days before preference confidence starts to decay (if not reinforced)\n // Preferences that aren't seen again will gradually lose confidence and be removed\n \"userProfileConfidenceDecayDays\": 30,\n \n // Number of profile versions to keep in changelog (for rollback/debugging)\n // Older versions are automatically cleaned up\n \"userProfileChangelogRetentionCount\": 5,\n\n // Minimum evidence count for a preference/pattern to survive confidence decay\n // Items confirmed fewer times are more likely to be pruned when confidence decays\n \"userProfileMinEvidenceForRetention\": 3,\n\n // Merge duplicate or vague profile items every N analysed prompts (about 1 model call per 100 items)\n \"userProfileAutoCleanupEnabled\": true,\n \"userProfileAutoCleanupInterval\": 500,\n\n // Enable LLM validation of existing preferences against recent behavior.\n // When enabled, each analysis round checks if top-5 preferences still match recent prompts.\n // Experimental \u2014 disabled by default.\n \"userProfileValidationEnabled\": false,\n\n // ============================================\n // Search Settings\n // ============================================\n \n // Minimum similarity score (0-1) for memory search results. Leave unset to\n // follow the default for your embedding model: 0.5 for qwen3-embedding:0.6b\n // (the only model measured), 0.6 for every other embedder.\n // \"similarityThreshold\": 0.5,\n\n // Maximum number of memories to return in search results.\n // Manual searches can ask for fewer; prompt retrieval uses this ceiling.\n \"maxMemories\": 10,\n\n // ============================================\n // Memory Context Controls\n // ============================================\n\n // Recent memories added at fresh session start (OpenCode V1 and Claude Code).\n // Pi and OpenCode V2 search the store with each prompt instead.\n \"chatMessage\": {\n \"enabled\": true,\n // Recent memories injected at session start.\n \"maxMemories\": 3,\n \"excludeCurrentSession\": true,\n // When to inject: \"first\" message of a session or \"always\".\n // \"injectOn\": \"first\",\n },\n\n // OpenCode profile-learning input limit, in UTF-8 bytes. Smaller values can\n // omit prompts from that input. Does not limit Pi or Claude Code profile input.\n \"userProfileMaxContextBytes\": 32768,\n\n // Shared budget for automatic memory context (profile text, memories, and\n // formatting) across all hosts, in approximate tokens: ceil(UTF-8 bytes / 4).\n // Covers prompt retrieval, recent-memory injection, and session-memory\n // restoration. Not an exact provider token limit or a spending limit.\n \"retrievalMaxTokens\": 2000,\n\n // Memorisation settings (global config only).\n // Repeat for a session snapshot: \"add\" or \"replace\" (default \"add\").\n // \"memorisationRepeat\": \"add\",\n // Maximum stored text: 100 to 20,000 characters or 25 to 5,000 tokens.\n // Tokens are estimated with the same rule as retrievalMaxTokens.\n // \"memorisationMaxSize\": 2000,\n // \"memorisationSizeUnit\": \"characters\", // or \"tokens\"\n\n // Restore a session's own memories after compaction and after resume, on\n // OpenCode V2, Pi and Claude Code. memoryLimit is the most memories to\n // restore. A project config can turn it on or off.\n // \"compaction\": { \"enabled\": true, \"memoryLimit\": 10 },\n\n // ============================================\n // Advanced Settings\n // ============================================\n \n // Inject user profile into AI context (preferences, patterns, workflows)\n \"injectProfile\": true\n}\n";
6
+ export declare const CONFIG_TEMPLATE = "{\n // ============================================\n // omms (Opinionated Modular Memory System) Configuration\n // ============================================\n \n // Storage location for the vector database. Leave unset to use the\n // default ~/.omms/data (a legacy ~/.opencode-mem/data store is migrated\n // there automatically on first start; see docs/omms-migration.md).\n // \"storagePath\": \"~/.omms/data\",\n\n \"userEmailOverride\": \"\",\n \"userNameOverride\": \"\",\n \n // ============================================\n // Embedding Model (for similarity search)\n // ============================================\n // Local = Hugging Face / ONNX via @huggingface/transformers (not Apple MLX).\n // Remote = set embeddingApiUrl (OpenAI-compatible /embeddings); embeddingApiKey is optional.\n \n // Default: Nomic Embed v1 (768 dimensions, 8192 context, multilingual)\n \"embeddingModel\": \"Xenova/nomic-embed-text-v1\",\n\n // Opt-in Nomic task prefixes (search_document: / search_query:). After enabling,\n // re-index existing memories so store and query vectors stay aligned.\n // \"embeddingUseTaskPrefixes\": true,\n \n // Auto-detected dimensions (no need to set manually)\n // \"embeddingDimensions\": 768,\n \n // Other recommended local models:\n // \"embeddingModel\": \"Xenova/jina-embeddings-v2-base-en\", // 768 dims, English-only, 8192 context\n // \"embeddingModel\": \"Xenova/jina-embeddings-v2-small-en\", // 512 dims, faster, 8192 context\n // \"embeddingModel\": \"Xenova/all-MiniLM-L6-v2\", // 384 dims, very fast, 512 context\n // \"embeddingModel\": \"Xenova/all-mpnet-base-v2\", // 768 dims, good quality, 512 context\n \n // Optional: OpenAI-compatible API for embeddings (URL required; key only if the server needs one)\n // \"embeddingApiUrl\": \"https://api.openai.com/v1\",\n // \"embeddingApiKey\": \"env://OPENAI_API_KEY\", // or \"sk-...\" / \"file:///path/to/key\"\n // \"embeddingModel\": \"text-embedding-3-small\", // 1536 dims, auto-detected\n \n // ============================================\n // Web Server Settings\n // ============================================\n \n // Start a background import of past chats on each host's next start.\n \"autoBackfill\": true,\n // \"piBackfillModel\": \"inherit\", // or \"external\", or \"provider/model\"\n // \"opencodeBackfillModel\": \"inherit\", // or \"external\", or \"provider/model\"\n // Directory maps for history recorded in moved or deleted directories.\n // \"importPathMaps\": [{ \"from\": \"~/code/app-feat-x\", \"to\": \"~/code/app\" }],\n // Unresolved directories to hide on the Directory maps page. They stay unimported.\n // \"importIgnoredDirectories\": [\"/private/tmp/scratch\"],\n // Claude Code's folder, when it is not ~/.claude (like CLAUDE_CONFIG_DIR).\n // \"claudeConfigDir\": \"~/.claude-work\",\n\n // Register the web app to start when you log in.\n \"webServerAutoStart\": true,\n // Enable web UI for managing memories (accessible at http://localhost:4747)\n \"webServerEnabled\": true,\n \n // Port for web UI server\n \"webServerPort\": 4747,\n \n // Host address for web UI (use 127.0.0.1 for local only, 0.0.0.0 for network access)\n \"webServerHost\": \"127.0.0.1\",\n\n // Optional HTTP Basic Auth for the web UI. Accepts literal, env://, or file:// secrets.\n // \"webServerAuthPassword\": \"\",\n // \"webServerAuthUsername\": \"\",\n\n // Deprecated: imported once into the API token table, then no longer read.\n // Create API tokens on the Settings page (Keys and access) instead.\n // \"webServerApiToken\": \"env://OMMS_WEB_TOKEN\",\n \n // ============================================\n // Database Settings\n // ============================================\n \n // Maximum vectors per database shard (auto-creates new shard when limit reached)\n \"maxVectorsPerShard\": 50000,\n \n // Automatically delete old memories based on retention period\n \"autoCleanupEnabled\": true,\n \n // Days to keep memories before auto-cleanup (only if autoCleanupEnabled is true)\n \"autoCleanupRetentionDays\": 30,\n \n // Automatically detect and remove duplicate memories\n \"deduplicationEnabled\": true,\n \n // Similarity threshold (0-1) for detecting duplicates (higher = stricter)\n \"deduplicationSimilarityThreshold\": 0.90,\n \n // ============================================\n // Memory Scope Settings\n // ============================================\n\n // Default scope for memory list/search queries\n // \"project\" keeps queries within the current project, \"all-projects\" searches across all project shards\n \"memory\": {\n \"defaultScope\": \"project\"\n },\n\n // ============================================\n // Model for auto-capture and profile learning (same rule in OpenCode and Pi)\n // ============================================\n\n // Which model summarises your work, in this order:\n // 1. The host model below: opencodeProvider/opencodeModel in OpenCode,\n // piProvider/piModel in Pi. Set the model to \"inherit\" to follow\n // whatever model the session is using, or to \"external\" to send\n // every call to the external API below.\n // 2. If no host model is set: the external API (memoryModel/memoryApiUrl/\n // memoryApiKey further down).\n // 3. If neither is set: the session's own model.\n // If the host model fails and the external API is configured, the external\n // API is used instead.\n //\n // Host models go through OpenCode's or Pi's own sign-in (OAuth like Claude\n // Pro/Max, GitHub Copilot, ChatGPT, or your own key there), so no separate API\n // key is needed here.\n //\n // Examples (OpenCode names from 'opencode providers list'; Pi names from its model list):\n // \"opencodeProvider\": \"anthropic\", \"opencodeModel\": \"claude-haiku-4-5-20251001\"\n // \"opencodeProvider\": \"openai\", \"opencodeModel\": \"inherit\"\n // \"piProvider\": \"openai-codex\", \"piModel\": \"gpt-5.6-luna\"\n //\n // \"opencodeProvider\": \"anthropic\",\n // \"opencodeModel\": \"claude-haiku-4-5-20251001\",\n // \"piProvider\": \"openai-codex\",\n // \"piModel\": \"gpt-5.6-luna\",\n\n // ============================================\n // Auto-Capture Settings\n // ============================================\n \n // Auto-capture runs in the background without blocking your main session.\n // Note: Ollama may not support tool calling. Use OpenAI, Anthropic, or Groq for best results.\n \n \"autoCaptureEnabled\": true,\n \n // Provider type: \"openai-chat\" | \"openai-responses\" | \"anthropic\" | \"minimax\" | \"orcarouter\" | \"google-gemini\"\n // Note: \"openai-chat\" is a generic OpenAI API-compatible mode.\n // Any service that follows the OpenAI Chat Completions API can use it via custom \"memoryApiUrl\".\n \"memoryProvider\": \"openai-chat\",\n \n // External API (step 2 above, and the fallback when a host model fails).\n // Uncomment all 3 lines and replace memoryApiKey before use:\n // \"memoryModel\": \"gpt-4o-mini\",\n // \"memoryApiUrl\": \"https://api.openai.com/v1\",\n // \"memoryApiKey\": \"sk-...\",\n\n // API Key Formats:\n // Direct value: \"sk-...\"\n // From file: \"file://~/.config/litellm-key.txt\"\n // From env variable: \"env://LITELLM_API_KEY\"\n \n // Examples for different providers:\n // Any OpenAI-compatible endpoint can use the \"openai-chat\" provider pattern below.\n // Common examples: DeepSeek, Qwen (via Alibaba Cloud ModelStudio),\n // Zhipu GLM (BigModel platform), and Kimi (Moonshot AI platform).\n\n // OpenAI Chat Completion (default, backward compatible):\n // \"memoryProvider\": \"openai-chat\"\n // \"memoryModel\": \"gpt-4o-mini\"\n // \"memoryApiUrl\": \"https://api.openai.com/v1\"\n // \"memoryApiKey\": \"sk-...\"\n\n // DeepSeek (OpenAI-compatible example):\n // \"memoryProvider\": \"openai-chat\"\n // \"memoryModel\": \"deepseek-chat\"\n // \"memoryApiUrl\": \"https://api.deepseek.com/v1\"\n // \"memoryApiKey\": \"sk-...\"\n \n // OpenAI Responses API (recommended, with session support):\n // \"memoryProvider\": \"openai-responses\"\n // \"memoryModel\": \"gpt-4o\"\n // \"memoryApiUrl\": \"https://api.openai.com/v1\"\n // \"memoryApiKey\": \"sk-...\"\n \n // Anthropic (with session support):\n // \"memoryProvider\": \"anthropic\"\n // \"memoryModel\": \"claude-3-5-haiku-20241022\"\n // \"memoryApiUrl\": \"https://api.anthropic.com/v1\"\n // \"memoryApiKey\": \"sk-ant-...\"\n\n // MiniMax (Anthropic Messages-compatible endpoint, with session support):\n // \"memoryProvider\": \"minimax\"\n // \"memoryModel\": \"MiniMax-M3\"\n // \"memoryApiUrl\": \"https://api.minimax.io\" // global endpoint\n // \"memoryApiKey\": \"<MiniMax API key>\"\n // // China endpoint: \"memoryApiUrl\": \"https://api.minimaxi.com\"\n // // Optional adaptive thinking for MiniMax-M3:\n // \"memoryExtraParams\": { \"thinking\": { \"type\": \"adaptive\" } }\n\n // OrcaRouter (OpenAI-compatible gateway, namespaced model IDs, with session support):\n // \"memoryProvider\": \"orcarouter\"\n // \"memoryApiKey\": \"<OrcaRouter API key>\"\n // // memoryApiUrl and memoryModel are optional \u2014 they default to\n // // https://api.orcarouter.ai/v1 and \"orcarouter/auto\" (a routing alias).\n // // OrcaRouter rejects bare model names, so if you set memoryModel, use a\n // // namespaced ID such as \"openai/gpt-5.5\" or \"deepseek/deepseek-v4-flash\".\n // \"memoryModel\": \"openai/gpt-5.5\"\n\n // Groq (OpenAI-compatible, use openai-chat provider):\n // \"memoryProvider\": \"openai-chat\"\n // \"memoryModel\": \"llama-3.3-70b-versatile\"\n // \"memoryApiUrl\": \"https://api.groq.com/openai/v1\"\n // \"memoryApiKey\": \"gsk_...\"\n \n // Maximum iterations for multi-turn AI analysis (for openai-responses, anthropic, and minimax)\n \"autoCaptureMaxIterations\": 5,\n \n // Timeout per iteration in milliseconds (30 seconds default)\n \"autoCaptureIterationTimeout\": 30000,\n\n // Tries for one turn's capture, on Pi and OpenCode, 2 and 4 seconds apart. After the\n // last one fails, a turn the model could not reach goes to the capture retry queue.\n \"autoCaptureMaxRetries\": 3,\n\n // Maximum UTF-8 bytes for the auto-capture markdown context sent to the summary model.\n // Prevents HTTP 400 context overflows on models with ~131K token windows (e.g. Groq Llama).\n // Rough guide: tokens \u2248 bytes / 4 for mixed code/prose.\n \"autoCaptureMaxContextBytes\": 131072,\n \n // Days to keep AI session history before cleanup\n \"aiSessionRetentionDays\": 7,\n\n // Capture diagnostics: every capture attempt always writes one metadata line\n // (model, stop reason, sizes, outcome) to ~/.omms/omms.log, with no\n // conversation text. Set captureTrace to true to also write each attempt's\n // prompt and raw reply to ~/.omms/traces/ for debugging. Traces can contain\n // conversation content: <private> text and common API key formats are\n // redacted, files are readable only by you, and they are deleted after\n // captureTraceRetentionDays. Only this global file can turn tracing on.\n // \"captureTrace\": false,\n // \"captureTraceRetentionDays\": 7,\n // \"captureAttemptRetentionDays\": 30,\n\n // Skill learning: record each full turn (prompt, replies, tool calls with\n // their outcome, and the skills used) in ~/.omms/data/skills.db, so OMMS can\n // rank your skills and draft better versions. Off by default. A project config\n // can turn it off but never on. No model call is made to record a turn.\n // \"skillLearning\": false,\n // Remove <private> text and common API key formats before a turn is stored.\n // Global config only. Turning this off stores text as written.\n // \"skillLearningRedact\": true,\n // Folders OMMS reads skills from. Absolute or starting with ~/. Global config\n // only. Unset: ~/.agents/skills and the Claude Code folder's skills folder.\n // \"skillFolders\": [\"~/.agents/skills\"],\n\n // Visible recall: the OpenCode terminal TUI and Pi's interactive terminal draw\n // a row for each memory recall. On by default. A project config can turn it\n // off but never on.\n // \"visibleRecall\": true,\n\n // Memory injection, on OpenCode V2, Pi and Claude Code. Each prompt gets the\n // memories that match it. openingContext also searches memory with\n // openingContextQuery and adds the matches to the\n // first message of a new session. The query is global only, trimmed, and\n // 500 characters at most. subagentMemory gives each subagent the memories\n // that match its task. For both switches, a project config can only turn these off.\n // \"openingContext\": false,\n // \"openingContextQuery\": \"project architecture, setup, conventions and key decisions\",\n // \"subagentMemory\": true,\n\n // Capture retry queue: when a live capture fails because the model cannot be\n // reached (network error, timeout, HTTP 408, 429 or 5xx), OMMS keeps a copy of\n // the turn in ~/.omms/data and tries it again later. The copy has <private>\n // text and common API key formats removed. Turns are deleted when the retry\n // ends or after captureRetryRetentionHours (0 to 720). 0 turns the queue off.\n // Only this global file can set it.\n // \"captureRetryRetentionHours\": 72,\n\n // Temperature for AI API requests (set to false to omit parameter for models that don't support it)\n // Some reasoning models (like o1, o3, gpt-5) don't support temperature parameter\n // Set to false and add \"memoryTemperature\": false in config when using such models\n \"memoryTemperature\": 0.3,\n\n // Extra parameters to include in API request body\n // Useful for local inference servers (e.g. llama-server with --jinja) that support\n // additional parameters like disabling thinking/reasoning mode\n // Example for Qwen3 models: { \"enable_thinking\": false }\n // \"memoryExtraParams\": {},\n\n // Language for auto-capture summaries (default: \"auto\" for auto-detection)\n // Options: \"auto\", \"en\", \"id\", \"zh\", \"ja\", \"es\", \"fr\", \"de\", \"ru\", \"pt\", \"ar\", \"ko\"\n // \"autoCaptureLanguage\": \"auto\",\n\n // ============================================\n // Toast Notifications\n // ============================================\n\n // Show toast when memory is auto-captured\n \"showAutoCaptureToasts\": true,\n\n // Show toast when user profile is updated\n \"showUserProfileToasts\": true,\n\n // Show toast for error messages\n \"showErrorToasts\": true,\n\n // ============================================\n // User Profile System\n // ============================================\n\n // Analyze user prompts every N prompts to build/update your user profile\n // When N uncaptured prompts accumulate, AI will analyze them to identify:\n // - User preferences (code style, communication style, tool preferences)\n // - User patterns (recurring topics, problem domains, technical interests)\n // - User workflows (development habits, sequences, learning style)\n // - Skill level (overall and per-domain assessment)\n \"userProfileAnalysisInterval\": 10,\n\n // Days before inactive items (all types) are eligible for removal\n \"userProfileStaleDays\": 2,\n\n // Days before an inactive workflow is eligible for removal. A workflow seen\n // userProfileMinEvidenceForRetention times or more is kept.\n \"userProfileWorkflowStaleDays\": 30,\n\n // Number of preferences shown in UI\n \"userProfileDisplayPreferences\": 20,\n \n // Number of patterns shown in UI\n \"userProfileDisplayPatterns\": 15,\n \n // Number of workflows shown in UI\n \"userProfileDisplayWorkflows\": 10,\n \n // Number of preferences injected into LLM conversation context\n // Keep this small \u2014 the strongest signals are enough; more dilute LLM attention\n \"userProfileInjectPreferences\": 5,\n \n // Number of patterns injected into LLM conversation context\n \"userProfileInjectPatterns\": 5,\n \n // Number of workflows injected into LLM conversation context\n \"userProfileInjectWorkflows\": 3,\n \n // Days before preference confidence starts to decay (if not reinforced)\n // Preferences that aren't seen again will gradually lose confidence and be removed\n \"userProfileConfidenceDecayDays\": 30,\n \n // Number of profile versions to keep in changelog (for rollback/debugging)\n // Older versions are automatically cleaned up\n \"userProfileChangelogRetentionCount\": 5,\n\n // Minimum evidence count for a preference/pattern to survive confidence decay\n // Items confirmed fewer times are more likely to be pruned when confidence decays\n \"userProfileMinEvidenceForRetention\": 3,\n\n // Merge duplicate or vague profile items every N analysed prompts (about 1 model call per 100 items)\n \"userProfileAutoCleanupEnabled\": true,\n \"userProfileAutoCleanupInterval\": 500,\n\n // Enable LLM validation of existing preferences against recent behavior.\n // When enabled, each analysis round checks if top-5 preferences still match recent prompts.\n // Experimental \u2014 disabled by default.\n \"userProfileValidationEnabled\": false,\n\n // ============================================\n // Search Settings\n // ============================================\n \n // Minimum similarity score (0-1) for memory search results. Leave unset to\n // follow the default for your embedding model: 0.5 for qwen3-embedding:0.6b\n // (the only model measured), 0.6 for every other embedder.\n // \"similarityThreshold\": 0.5,\n\n // Maximum number of memories to return in search results.\n // Manual searches can ask for fewer; prompt retrieval uses this ceiling.\n \"maxMemories\": 10,\n\n // ============================================\n // Memory Context Controls\n // ============================================\n\n // Per-prompt memory retrieval on every host. \"enabled\": false turns it off;\n // \"excludeCurrentSession\" leaves out memories captured in the current session.\n \"chatMessage\": {\n \"enabled\": true,\n \"excludeCurrentSession\": true,\n },\n\n // OpenCode profile-learning input limit, in UTF-8 bytes. Smaller values can\n // omit prompts from that input. Does not limit Pi or Claude Code profile input.\n \"userProfileMaxContextBytes\": 32768,\n\n // Shared budget for automatic memory context (profile text, memories, and\n // formatting) across all hosts, in approximate tokens: ceil(UTF-8 bytes / 4).\n // Covers prompt retrieval, recent-memory injection, and session-memory\n // restoration. Not an exact provider token limit or a spending limit.\n \"retrievalMaxTokens\": 2000,\n\n // Memorisation settings (global config only).\n // Repeat for a session snapshot: \"add\" or \"replace\" (default \"add\").\n // \"memorisationRepeat\": \"add\",\n // Maximum stored text: 100 to 20,000 characters or 25 to 5,000 tokens.\n // Tokens are estimated with the same rule as retrievalMaxTokens.\n // \"memorisationMaxSize\": 2000,\n // \"memorisationSizeUnit\": \"characters\", // or \"tokens\"\n\n // Restore a session's own memories after compaction and after resume, on\n // OpenCode V2, Pi and Claude Code. memoryLimit is the most memories to\n // restore. A project config can turn it on or off.\n // \"compaction\": { \"enabled\": true, \"memoryLimit\": 10 },\n\n // ============================================\n // Advanced Settings\n // ============================================\n \n // Inject user profile into AI context (preferences, patterns, workflows)\n \"injectProfile\": true\n}\n";
@@ -381,15 +381,11 @@ export const CONFIG_TEMPLATE = `{
381
381
  // Memory Context Controls
382
382
  // ============================================
383
383
 
384
- // Recent memories added at fresh session start (OpenCode V1 and Claude Code).
385
- // Pi and OpenCode V2 search the store with each prompt instead.
384
+ // Per-prompt memory retrieval on every host. "enabled": false turns it off;
385
+ // "excludeCurrentSession" leaves out memories captured in the current session.
386
386
  "chatMessage": {
387
387
  "enabled": true,
388
- // Recent memories injected at session start.
389
- "maxMemories": 3,
390
388
  "excludeCurrentSession": true,
391
- // When to inject: "first" message of a session or "always".
392
- // "injectOn": "first",
393
389
  },
394
390
 
395
391
  // OpenCode profile-learning input limit, in UTF-8 bytes. Smaller values can
package/dist/config.d.ts CHANGED
@@ -124,10 +124,7 @@ interface OmmsConfig {
124
124
  };
125
125
  chatMessage?: {
126
126
  enabled?: boolean;
127
- maxMemories?: number;
128
127
  excludeCurrentSession?: boolean;
129
- maxAgeDays?: number;
130
- injectOn?: "first" | "always";
131
128
  };
132
129
  }
133
130
  import { CONFIG_TEMPLATE } from "./config-template.js";
@@ -189,10 +186,7 @@ declare function buildConfig(fileConfig: OmmsConfig): {
189
186
  };
190
187
  chatMessage: {
191
188
  enabled: boolean | undefined;
192
- maxMemories: number;
193
189
  excludeCurrentSession: boolean | undefined;
194
- maxAgeDays: number | undefined;
195
- injectOn: "first" | "always";
196
190
  };
197
191
  openingContext: boolean;
198
192
  openingContextQuery: string;
@@ -306,10 +300,7 @@ export declare let CONFIG: {
306
300
  };
307
301
  chatMessage: {
308
302
  enabled: boolean | undefined;
309
- maxMemories: number;
310
303
  excludeCurrentSession: boolean | undefined;
311
- maxAgeDays: number | undefined;
312
- injectOn: "first" | "always";
313
304
  };
314
305
  openingContext: boolean;
315
306
  openingContextQuery: string;
package/dist/config.js CHANGED
@@ -108,10 +108,7 @@ const DEFAULTS = {
108
108
  },
109
109
  chatMessage: {
110
110
  enabled: true,
111
- maxMemories: 3,
112
111
  excludeCurrentSession: true,
113
- maxAgeDays: undefined,
114
- injectOn: "first",
115
112
  },
116
113
  };
117
114
  /**
@@ -266,9 +263,6 @@ function buildConfig(fileConfig) {
266
263
  const maxMemories = checkedOwnMemoryLimit(MEMORY_LIMITS.maxMemories, fileConfig, "maxMemories", DEFAULTS.maxMemories);
267
264
  const retrievalMaxTokens = checkedOwnMemoryLimit(MEMORY_LIMITS.retrievalMaxTokens, fileConfig, "retrievalMaxTokens", DEFAULTS.retrievalMaxTokens);
268
265
  const userProfileMaxContextBytes = checkedOwnMemoryLimit(MEMORY_LIMITS.userProfileMaxContextBytes, fileConfig, "userProfileMaxContextBytes", DEFAULTS.userProfileMaxContextBytes);
269
- const chatMessageMaxMemories = checkedMemoryLimit(MEMORY_LIMITS["chatMessage.maxMemories"], fileConfig.chatMessage && Object.hasOwn(fileConfig.chatMessage, "maxMemories")
270
- ? fileConfig.chatMessage.maxMemories
271
- : DEFAULTS.chatMessage.maxMemories);
272
266
  const userProfileAutoCleanupInterval = fileConfig.userProfileAutoCleanupInterval ?? DEFAULTS.userProfileAutoCleanupInterval;
273
267
  parseBackfillModel(fileConfig, "pi");
274
268
  parseBackfillModel(fileConfig, "opencode");
@@ -410,10 +404,7 @@ function buildConfig(fileConfig) {
410
404
  },
411
405
  chatMessage: {
412
406
  enabled: fileConfig.chatMessage?.enabled ?? DEFAULTS.chatMessage.enabled,
413
- maxMemories: chatMessageMaxMemories,
414
407
  excludeCurrentSession: fileConfig.chatMessage?.excludeCurrentSession ?? DEFAULTS.chatMessage.excludeCurrentSession,
415
- maxAgeDays: fileConfig.chatMessage?.maxAgeDays,
416
- injectOn: (fileConfig.chatMessage?.injectOn ?? DEFAULTS.chatMessage.injectOn),
417
408
  },
418
409
  };
419
410
  }
@@ -24,27 +24,6 @@ export declare function searchablePrompt(prompt: string): string | null;
24
24
  * profile). Returns null when nothing relevant is found.
25
25
  */
26
26
  export declare function buildRetrievalSection(prompt: string, directory: string, sessionId: string, budget?: ContextBudgetOptions): Promise<string | null>;
27
- export interface RecentMemoriesOptions {
28
- /** Project container tag, from `getTags(directory).project.tag`. */
29
- projectTag: string;
30
- /** Profile owner, from `getTags(directory).user.userEmail`. */
31
- userEmail: string | null | undefined;
32
- sessionId: string;
33
- /** `CONFIG.chatMessage.maxMemories` */
34
- maxMemories?: number;
35
- /** `CONFIG.chatMessage.excludeCurrentSession` */
36
- excludeCurrentSession?: boolean;
37
- /** `CONFIG.chatMessage.maxAgeDays` */
38
- maxAgeDays?: number;
39
- /** Shared packing budget; hosts pass the snapshotted `retrievalMaxTokens`. */
40
- budget?: ContextBudgetOptions;
41
- }
42
- /**
43
- * The project's most recent memories as a context section, shared by every host
44
- * that injects them at session start (OpenCode's first `chat.message`, Claude
45
- * Code `SessionStart`). Returns null when no memory qualifies.
46
- */
47
- export declare function buildRecentMemoriesSection(options: RecentMemoriesOptions): Promise<string | null>;
48
27
  /** Delimit a retrieval section so it is recognisable as injected memory, not user text. */
49
28
  export declare function wrapRetrievalSection(section: string): string;
50
29
  /** Byte overhead the `omms-retrieval` wrapper adds around one section. */
@@ -61,35 +61,6 @@ export async function buildRetrievalSection(prompt, directory, sessionId, budget
61
61
  const memoryContext = await formatContextForPrompt(tags.user.userEmail || null, { results: search.results }, budget, search.queryVector);
62
62
  return memoryContext || null;
63
63
  }
64
- /**
65
- * The project's most recent memories as a context section, shared by every host
66
- * that injects them at session start (OpenCode's first `chat.message`, Claude
67
- * Code `SessionStart`). Returns null when no memory qualifies.
68
- */
69
- export async function buildRecentMemoriesSection(options) {
70
- const listResult = await memoryClient.listMemories(options.projectTag, options.maxMemories, "project", { includeLinked: true });
71
- let memories = listResult.success ? listResult.memories : [];
72
- if (options.excludeCurrentSession) {
73
- memories = memories.filter((m) => m.metadata?.sessionID !== options.sessionId);
74
- }
75
- if (options.maxAgeDays) {
76
- const cutoffDate = Date.now() - options.maxAgeDays * 86400000;
77
- memories = memories.filter((m) => new Date(m.createdAt).getTime() > cutoffDate);
78
- }
79
- if (memories.length === 0)
80
- return null;
81
- const projectMemories = {
82
- results: memories.map((m) => ({
83
- id: m.id,
84
- similarity: 1.0,
85
- memory: m.summary,
86
- })),
87
- total: memories.length,
88
- timing: 0,
89
- };
90
- const memoryContext = await formatContextForPrompt(options.userEmail || null, projectMemories, options.budget);
91
- return memoryContext || null;
92
- }
93
64
  /** Delimit a retrieval section so it is recognisable as injected memory, not user text. */
94
65
  export function wrapRetrievalSection(section) {
95
66
  return `<${RETRIEVAL_SECTION_TAG}>\n${section}\n</${RETRIEVAL_SECTION_TAG}>`;
@@ -0,0 +1,17 @@
1
+ import type { MemoryHost } from "../types/index.js";
2
+ import type { ImportWindow } from "./importer.js";
3
+ import type { ImportLedgerRow, PiImportLedger } from "./ledger.js";
4
+ /**
5
+ * How a host finds a ledger row that already handled a unit. The default is
6
+ * the exact key. OpenCode matches a session and user message with any last
7
+ * answer id: the answer ids of old imports no longer exist in the database,
8
+ * and the 2.x tables can give a unit another last answer.
9
+ */
10
+ export type HandledLookup = (ledger: PiImportLedger, query: {
11
+ key: string;
12
+ sessionId: string;
13
+ window: ImportWindow;
14
+ readOnly: boolean;
15
+ }) => Promise<ImportLedgerRow | null>;
16
+ /** The lookup for a host, chosen once per import run. */
17
+ export declare function handledLookupFor(host: MemoryHost): HandledLookup;