om-memory-system 4.23.0-next.144 → 4.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/adapters/opencode/user-prompt.js +6 -1
  2. package/dist/config-template.d.ts +6 -0
  3. package/dist/config-template.js +385 -0
  4. package/dist/config.d.ts +27 -1
  5. package/dist/config.js +29 -382
  6. package/dist/core/retrieval.js +1 -1
  7. package/dist/services/chat/chat-answer.d.ts +56 -0
  8. package/dist/services/chat/chat-answer.js +88 -0
  9. package/dist/services/chat/chat-plan.d.ts +60 -0
  10. package/dist/services/chat/chat-plan.js +92 -0
  11. package/dist/services/chat/chat-retrieve.d.ts +36 -0
  12. package/dist/services/chat/chat-retrieve.js +82 -0
  13. package/dist/services/chat/chat-routes.d.ts +12 -0
  14. package/dist/services/chat/chat-routes.js +145 -0
  15. package/dist/services/chat/chat-runtime.d.ts +21 -0
  16. package/dist/services/chat/chat-runtime.js +104 -0
  17. package/dist/services/chat/chat-service.d.ts +42 -0
  18. package/dist/services/chat/chat-service.js +130 -0
  19. package/dist/services/chat/chat-status.d.ts +16 -0
  20. package/dist/services/chat/chat-status.js +23 -0
  21. package/dist/services/chat/chat-store.d.ts +60 -0
  22. package/dist/services/chat/chat-store.js +352 -0
  23. package/dist/services/chat/chat-types.d.ts +59 -0
  24. package/dist/services/chat/chat-types.js +8 -0
  25. package/dist/services/client.d.ts +1 -0
  26. package/dist/services/client.js +38 -5
  27. package/dist/services/context.d.ts +3 -1
  28. package/dist/services/context.js +4 -2
  29. package/dist/services/global-config-writer.js +61 -7
  30. package/dist/services/privacy.js +2 -2
  31. package/dist/services/request-timeouts.d.ts +5 -0
  32. package/dist/services/request-timeouts.js +8 -0
  33. package/dist/services/reranker-test.d.ts +1 -0
  34. package/dist/services/reranker-test.js +94 -0
  35. package/dist/services/reranker.d.ts +45 -0
  36. package/dist/services/reranker.js +133 -0
  37. package/dist/services/retrieval-rerank.d.ts +43 -0
  38. package/dist/services/retrieval-rerank.js +42 -0
  39. package/dist/services/settings-snapshot.js +26 -4
  40. package/dist/services/turso/exact-scan.d.ts +6 -0
  41. package/dist/services/turso/exact-scan.js +23 -0
  42. package/dist/services/turso/keyword-search.d.ts +35 -0
  43. package/dist/services/turso/keyword-search.js +153 -0
  44. package/dist/services/turso/rank-fusion.d.ts +34 -0
  45. package/dist/services/turso/rank-fusion.js +45 -0
  46. package/dist/services/turso/tag-match.d.ts +7 -0
  47. package/dist/services/turso/tag-match.js +29 -0
  48. package/dist/services/turso/types.d.ts +2 -0
  49. package/dist/services/turso/vector-search.d.ts +3 -3
  50. package/dist/services/turso/vector-search.js +23 -39
  51. package/dist/services/user-profile/profile-context.d.ts +6 -1
  52. package/dist/services/user-profile/profile-context.js +43 -7
  53. package/dist/services/user-profile/profile-relevance.d.ts +37 -0
  54. package/dist/services/user-profile/profile-relevance.js +75 -0
  55. package/dist/services/web-route-modules.d.ts +8 -0
  56. package/dist/services/web-route-modules.js +15 -0
  57. package/dist/services/web-server.js +8 -4
  58. package/dist/utils/keyword-boost.d.ts +17 -0
  59. package/dist/utils/keyword-boost.js +32 -0
  60. package/dist/utils/memory-limits.d.ts +7 -8
  61. package/dist/utils/memory-limits.js +10 -4
  62. package/dist/utils/reranker-settings.d.ts +13 -0
  63. package/dist/utils/reranker-settings.js +48 -0
  64. package/dist/utils/threshold-default.d.ts +12 -0
  65. package/dist/utils/threshold-default.js +22 -0
  66. package/dist/web/assets/index-DWk9C9Sm.css +2 -0
  67. package/dist/web/assets/index-rHcTKCkC.js +126 -0
  68. package/dist/web/index.html +2 -2
  69. package/package.json +1 -1
  70. package/dist/web/assets/index-7385lY75.css +0 -2
  71. package/dist/web/assets/index-C9y78EH_.js +0 -127
package/dist/config.js CHANGED
@@ -10,6 +10,10 @@ import { getAutoCaptureProviderStatus } from "./services/ai/live-model-choice.js
10
10
  import { parseBackfillModel } from "./importer/backfill-model.js";
11
11
  import { parseIgnoredDirectories, parseImportPathMaps } from "./importer/import-path-maps.js";
12
12
  import { checkedMemoryLimit, MEMORY_LIMITS } from "./utils/memory-limits.js";
13
+ import { defaultSimilarityThreshold } from "./utils/threshold-default.js";
14
+ import { RERANKER_TIMEOUT_DEFAULT_MS } from "./services/request-timeouts.js";
15
+ import { checkedRerankerSettings } from "./utils/reranker-settings.js";
16
+ import { checkedKeywordBoost, checkedKeywordMode, } from "./utils/keyword-boost.js";
13
17
  import { resolveDefaultStoragePath, runLegacyStoreMigration, legacyMigrationPaths, } from "./services/legacy-migration.js";
14
18
  const OMMS_CONFIG_DIR = join(homedir(), ".config", "omms");
15
19
  const DATA_DIR = join(homedir(), ".omms");
@@ -38,8 +42,9 @@ const DEFAULTS = {
38
42
  embeddingModel: "Xenova/nomic-embed-text-v1",
39
43
  embeddingDimensions: 768,
40
44
  embeddingUseTaskPrefixes: false,
41
- similarityThreshold: 0.6,
42
45
  maxMemories: 10,
46
+ rerankerEnabled: false,
47
+ rerankerTimeoutMs: RERANKER_TIMEOUT_DEFAULT_MS,
43
48
  retrievalMaxTokens: 2000,
44
49
  maxProfileItems: 5,
45
50
  injectProfile: true,
@@ -161,384 +166,8 @@ function assertProjectRemoteProviderConfigIsSafe(projectConfig) {
161
166
  `Move them to the global config at ${CONFIG_FILES[0]}.`);
162
167
  }
163
168
  }
164
- export const CONFIG_TEMPLATE = `{
165
- // ============================================
166
- // omms (Opinionated Modular Memory System) Configuration
167
- // ============================================
168
-
169
- // Storage location for the vector database. Leave unset to use the
170
- // default ~/.omms/data (a legacy ~/.opencode-mem/data store is migrated
171
- // there automatically on first start; see docs/omms-migration.md).
172
- // "storagePath": "~/.omms/data",
173
-
174
- "userEmailOverride": "",
175
- "userNameOverride": "",
176
-
177
- // ============================================
178
- // Embedding Model (for similarity search)
179
- // ============================================
180
- // Local = Hugging Face / ONNX via @huggingface/transformers (not Apple MLX).
181
- // Remote = set embeddingApiUrl (OpenAI-compatible /embeddings); embeddingApiKey is optional.
182
-
183
- // Default: Nomic Embed v1 (768 dimensions, 8192 context, multilingual)
184
- "embeddingModel": "Xenova/nomic-embed-text-v1",
185
-
186
- // Opt-in Nomic task prefixes (search_document: / search_query:). After enabling,
187
- // re-index existing memories so store and query vectors stay aligned.
188
- // "embeddingUseTaskPrefixes": true,
189
-
190
- // Auto-detected dimensions (no need to set manually)
191
- // "embeddingDimensions": 768,
192
-
193
- // Other recommended local models:
194
- // "embeddingModel": "Xenova/jina-embeddings-v2-base-en", // 768 dims, English-only, 8192 context
195
- // "embeddingModel": "Xenova/jina-embeddings-v2-small-en", // 512 dims, faster, 8192 context
196
- // "embeddingModel": "Xenova/all-MiniLM-L6-v2", // 384 dims, very fast, 512 context
197
- // "embeddingModel": "Xenova/all-mpnet-base-v2", // 768 dims, good quality, 512 context
198
-
199
- // Optional: OpenAI-compatible API for embeddings (URL required; key only if the server needs one)
200
- // "embeddingApiUrl": "https://api.openai.com/v1",
201
- // "embeddingApiKey": "env://OPENAI_API_KEY", // or "sk-..." / "file:///path/to/key"
202
- // "embeddingModel": "text-embedding-3-small", // 1536 dims, auto-detected
203
-
204
- // ============================================
205
- // Web Server Settings
206
- // ============================================
207
-
208
- // Start a background import of past chats on each host's next start.
209
- "autoBackfill": true,
210
- // "piBackfillModel": "inherit", // or "external", or "provider/model"
211
- // "opencodeBackfillModel": "inherit", // or "external", or "provider/model"
212
- // Directory maps for history recorded in moved or deleted directories.
213
- // "importPathMaps": [{ "from": "~/code/app-feat-x", "to": "~/code/app" }],
214
- // Unresolved directories to hide on the Directory maps page. They stay unimported.
215
- // "importIgnoredDirectories": ["/private/tmp/scratch"],
216
- // Claude Code's folder, when it is not ~/.claude (like CLAUDE_CONFIG_DIR).
217
- // "claudeConfigDir": "~/.claude-work",
218
-
219
- // Register the web app to start when you log in.
220
- "webServerAutoStart": true,
221
- // Enable web UI for managing memories (accessible at http://localhost:4747)
222
- "webServerEnabled": true,
223
-
224
- // Port for web UI server
225
- "webServerPort": 4747,
226
-
227
- // Host address for web UI (use 127.0.0.1 for local only, 0.0.0.0 for network access)
228
- "webServerHost": "127.0.0.1",
229
-
230
- // Optional HTTP Basic Auth for the web UI. Accepts literal, env://, or file:// secrets.
231
- // "webServerAuthPassword": "",
232
- // "webServerAuthUsername": "",
233
-
234
- // Deprecated: imported once into the API token table, then no longer read.
235
- // Create API tokens on the Settings page (Keys and access) instead.
236
- // "webServerApiToken": "env://OMMS_WEB_TOKEN",
237
-
238
- // ============================================
239
- // Database Settings
240
- // ============================================
241
-
242
- // Maximum vectors per database shard (auto-creates new shard when limit reached)
243
- "maxVectorsPerShard": 50000,
244
-
245
- // Automatically delete old memories based on retention period
246
- "autoCleanupEnabled": true,
247
-
248
- // Days to keep memories before auto-cleanup (only if autoCleanupEnabled is true)
249
- "autoCleanupRetentionDays": 30,
250
-
251
- // Automatically detect and remove duplicate memories
252
- "deduplicationEnabled": true,
253
-
254
- // Similarity threshold (0-1) for detecting duplicates (higher = stricter)
255
- "deduplicationSimilarityThreshold": 0.90,
256
-
257
- // ============================================
258
- // Memory Scope Settings
259
- // ============================================
260
-
261
- // Default scope for memory list/search queries
262
- // "project" keeps queries within the current project, "all-projects" searches across all project shards
263
- "memory": {
264
- "defaultScope": "project"
265
- },
266
-
267
- // ============================================
268
- // Model for auto-capture and profile learning (same rule in OpenCode and Pi)
269
- // ============================================
270
-
271
- // Which model summarises your work, in this order:
272
- // 1. The host model below: opencodeProvider/opencodeModel in OpenCode,
273
- // piProvider/piModel in Pi. Set the model to "inherit" to follow
274
- // whatever model the session is using, or to "external" to send
275
- // every call to the external API below.
276
- // 2. If no host model is set: the external API (memoryModel/memoryApiUrl/
277
- // memoryApiKey further down).
278
- // 3. If neither is set: the session's own model.
279
- // If the host model fails and the external API is configured, the external
280
- // API is used instead.
281
- //
282
- // Host models go through OpenCode's or Pi's own sign-in (OAuth like Claude
283
- // Pro/Max, GitHub Copilot, ChatGPT, or your own key there), so no separate API
284
- // key is needed here.
285
- //
286
- // Examples (OpenCode names from 'opencode providers list'; Pi names from its model list):
287
- // "opencodeProvider": "anthropic", "opencodeModel": "claude-haiku-4-5-20251001"
288
- // "opencodeProvider": "openai", "opencodeModel": "inherit"
289
- // "piProvider": "openai-codex", "piModel": "gpt-5.6-luna"
290
- //
291
- // "opencodeProvider": "anthropic",
292
- // "opencodeModel": "claude-haiku-4-5-20251001",
293
- // "piProvider": "openai-codex",
294
- // "piModel": "gpt-5.6-luna",
295
-
296
- // ============================================
297
- // Auto-Capture Settings
298
- // ============================================
299
-
300
- // Auto-capture runs in the background without blocking your main session.
301
- // Note: Ollama may not support tool calling. Use OpenAI, Anthropic, or Groq for best results.
302
-
303
- "autoCaptureEnabled": true,
304
-
305
- // Provider type: "openai-chat" | "openai-responses" | "anthropic" | "minimax" | "orcarouter" | "google-gemini"
306
- // Note: "openai-chat" is a generic OpenAI API-compatible mode.
307
- // Any service that follows the OpenAI Chat Completions API can use it via custom "memoryApiUrl".
308
- "memoryProvider": "openai-chat",
309
-
310
- // External API (step 2 above, and the fallback when a host model fails).
311
- // Uncomment all 3 lines and replace memoryApiKey before use:
312
- // "memoryModel": "gpt-4o-mini",
313
- // "memoryApiUrl": "https://api.openai.com/v1",
314
- // "memoryApiKey": "sk-...",
315
-
316
- // API Key Formats:
317
- // Direct value: "sk-..."
318
- // From file: "file://~/.config/litellm-key.txt"
319
- // From env variable: "env://LITELLM_API_KEY"
320
-
321
- // Examples for different providers:
322
- // Any OpenAI-compatible endpoint can use the "openai-chat" provider pattern below.
323
- // Common examples: DeepSeek, Qwen (via Alibaba Cloud ModelStudio),
324
- // Zhipu GLM (BigModel platform), and Kimi (Moonshot AI platform).
325
-
326
- // OpenAI Chat Completion (default, backward compatible):
327
- // "memoryProvider": "openai-chat"
328
- // "memoryModel": "gpt-4o-mini"
329
- // "memoryApiUrl": "https://api.openai.com/v1"
330
- // "memoryApiKey": "sk-..."
331
-
332
- // DeepSeek (OpenAI-compatible example):
333
- // "memoryProvider": "openai-chat"
334
- // "memoryModel": "deepseek-chat"
335
- // "memoryApiUrl": "https://api.deepseek.com/v1"
336
- // "memoryApiKey": "sk-..."
337
-
338
- // OpenAI Responses API (recommended, with session support):
339
- // "memoryProvider": "openai-responses"
340
- // "memoryModel": "gpt-4o"
341
- // "memoryApiUrl": "https://api.openai.com/v1"
342
- // "memoryApiKey": "sk-..."
343
-
344
- // Anthropic (with session support):
345
- // "memoryProvider": "anthropic"
346
- // "memoryModel": "claude-3-5-haiku-20241022"
347
- // "memoryApiUrl": "https://api.anthropic.com/v1"
348
- // "memoryApiKey": "sk-ant-..."
349
-
350
- // MiniMax (Anthropic Messages-compatible endpoint, with session support):
351
- // "memoryProvider": "minimax"
352
- // "memoryModel": "MiniMax-M3"
353
- // "memoryApiUrl": "https://api.minimax.io" // global endpoint
354
- // "memoryApiKey": "<MiniMax API key>"
355
- // // China endpoint: "memoryApiUrl": "https://api.minimaxi.com"
356
- // // Optional adaptive thinking for MiniMax-M3:
357
- // "memoryExtraParams": { "thinking": { "type": "adaptive" } }
358
-
359
- // OrcaRouter (OpenAI-compatible gateway, namespaced model IDs, with session support):
360
- // "memoryProvider": "orcarouter"
361
- // "memoryApiKey": "<OrcaRouter API key>"
362
- // // memoryApiUrl and memoryModel are optional — they default to
363
- // // https://api.orcarouter.ai/v1 and "orcarouter/auto" (a routing alias).
364
- // // OrcaRouter rejects bare model names, so if you set memoryModel, use a
365
- // // namespaced ID such as "openai/gpt-5.5" or "deepseek/deepseek-v4-flash".
366
- // "memoryModel": "openai/gpt-5.5"
367
-
368
- // Groq (OpenAI-compatible, use openai-chat provider):
369
- // "memoryProvider": "openai-chat"
370
- // "memoryModel": "llama-3.3-70b-versatile"
371
- // "memoryApiUrl": "https://api.groq.com/openai/v1"
372
- // "memoryApiKey": "gsk_..."
373
-
374
- // Maximum iterations for multi-turn AI analysis (for openai-responses, anthropic, and minimax)
375
- "autoCaptureMaxIterations": 5,
376
-
377
- // Timeout per iteration in milliseconds (30 seconds default)
378
- "autoCaptureIterationTimeout": 30000,
379
-
380
- // Tries for one turn's capture, on Pi and OpenCode, 2 and 4 seconds apart. After the
381
- // last one fails, a turn the model could not reach goes to the capture retry queue.
382
- "autoCaptureMaxRetries": 3,
383
-
384
- // Maximum UTF-8 bytes for the auto-capture markdown context sent to the summary model.
385
- // Prevents HTTP 400 context overflows on models with ~131K token windows (e.g. Groq Llama).
386
- // Rough guide: tokens ≈ bytes / 4 for mixed code/prose.
387
- "autoCaptureMaxContextBytes": 131072,
388
-
389
- // Days to keep AI session history before cleanup
390
- "aiSessionRetentionDays": 7,
391
-
392
- // Capture diagnostics: every capture attempt always writes one metadata line
393
- // (model, stop reason, sizes, outcome) to ~/.omms/omms.log, with no
394
- // conversation text. Set captureTrace to true to also write each attempt's
395
- // prompt and raw reply to ~/.omms/traces/ for debugging. Traces can contain
396
- // conversation content: <private> text and common API key formats are
397
- // redacted, files are readable only by you, and they are deleted after
398
- // captureTraceRetentionDays. Only this global file can turn tracing on.
399
- // "captureTrace": false,
400
- // "captureTraceRetentionDays": 7,
401
- // "captureAttemptRetentionDays": 30,
402
-
403
- // Capture retry queue: when a live capture fails because the model cannot be
404
- // reached (network error, timeout, HTTP 408, 429 or 5xx), OMMS keeps a copy of
405
- // the turn in ~/.omms/data and tries it again later. The copy has <private>
406
- // text and common API key formats removed. Turns are deleted when the retry
407
- // ends or after captureRetryRetentionHours (0 to 720). 0 turns the queue off.
408
- // Only this global file can set it.
409
- // "captureRetryRetentionHours": 72,
410
-
411
- // Temperature for AI API requests (set to false to omit parameter for models that don't support it)
412
- // Some reasoning models (like o1, o3, gpt-5) don't support temperature parameter
413
- // Set to false and add "memoryTemperature": false in config when using such models
414
- "memoryTemperature": 0.3,
415
-
416
- // Extra parameters to include in API request body
417
- // Useful for local inference servers (e.g. llama-server with --jinja) that support
418
- // additional parameters like disabling thinking/reasoning mode
419
- // Example for Qwen3 models: { "enable_thinking": false }
420
- // "memoryExtraParams": {},
421
-
422
- // Language for auto-capture summaries (default: "auto" for auto-detection)
423
- // Options: "auto", "en", "id", "zh", "ja", "es", "fr", "de", "ru", "pt", "ar", "ko"
424
- // "autoCaptureLanguage": "auto",
425
-
426
- // ============================================
427
- // Toast Notifications
428
- // ============================================
429
-
430
- // Show toast when memory is auto-captured
431
- "showAutoCaptureToasts": true,
432
-
433
- // Show toast when user profile is updated
434
- "showUserProfileToasts": true,
435
-
436
- // Show toast for error messages
437
- "showErrorToasts": true,
438
-
439
- // ============================================
440
- // User Profile System
441
- // ============================================
442
-
443
- // Analyze user prompts every N prompts to build/update your user profile
444
- // When N uncaptured prompts accumulate, AI will analyze them to identify:
445
- // - User preferences (code style, communication style, tool preferences)
446
- // - User patterns (recurring topics, problem domains, technical interests)
447
- // - User workflows (development habits, sequences, learning style)
448
- // - Skill level (overall and per-domain assessment)
449
- "userProfileAnalysisInterval": 10,
450
-
451
- // Days before inactive items (all types) are eligible for removal
452
- "userProfileStaleDays": 2,
453
-
454
- // Days before an inactive workflow is eligible for removal. A workflow seen
455
- // userProfileMinEvidenceForRetention times or more is kept.
456
- "userProfileWorkflowStaleDays": 30,
457
-
458
- // Number of preferences shown in UI
459
- "userProfileDisplayPreferences": 20,
460
-
461
- // Number of patterns shown in UI
462
- "userProfileDisplayPatterns": 15,
463
-
464
- // Number of workflows shown in UI
465
- "userProfileDisplayWorkflows": 10,
466
-
467
- // Number of preferences injected into LLM conversation context
468
- // Keep this small — the strongest signals are enough; more dilute LLM attention
469
- "userProfileInjectPreferences": 5,
470
-
471
- // Number of patterns injected into LLM conversation context
472
- "userProfileInjectPatterns": 5,
473
-
474
- // Number of workflows injected into LLM conversation context
475
- "userProfileInjectWorkflows": 3,
476
-
477
- // Days before preference confidence starts to decay (if not reinforced)
478
- // Preferences that aren't seen again will gradually lose confidence and be removed
479
- "userProfileConfidenceDecayDays": 30,
480
-
481
- // Number of profile versions to keep in changelog (for rollback/debugging)
482
- // Older versions are automatically cleaned up
483
- "userProfileChangelogRetentionCount": 5,
484
-
485
- // Minimum evidence count for a preference/pattern to survive confidence decay
486
- // Items confirmed fewer times are more likely to be pruned when confidence decays
487
- "userProfileMinEvidenceForRetention": 3,
488
-
489
- // Merge duplicate or vague profile items every N analysed prompts (about 1 model call per 100 items)
490
- "userProfileAutoCleanupEnabled": true,
491
- "userProfileAutoCleanupInterval": 500,
492
-
493
- // Enable LLM validation of existing preferences against recent behavior.
494
- // When enabled, each analysis round checks if top-5 preferences still match recent prompts.
495
- // Experimental — disabled by default.
496
- "userProfileValidationEnabled": false,
497
-
498
- // ============================================
499
- // Search Settings
500
- // ============================================
501
-
502
- // Minimum similarity score (0-1) for memory search results
503
- "similarityThreshold": 0.6,
504
-
505
- // Maximum number of memories to return in search results.
506
- // Manual searches can ask for fewer; prompt retrieval uses this ceiling.
507
- "maxMemories": 10,
508
-
509
- // ============================================
510
- // Memory Context Controls
511
- // ============================================
512
-
513
- // Recent memories added at fresh session start (OpenCode V1 and Claude Code).
514
- // Pi and OpenCode V2 search the store with each prompt instead.
515
- "chatMessage": {
516
- "enabled": true,
517
- // Recent memories injected at session start.
518
- "maxMemories": 3,
519
- "excludeCurrentSession": true,
520
- // When to inject: "first" message of a session or "always".
521
- // "injectOn": "first",
522
- },
523
-
524
- // OpenCode profile-learning input limit, in UTF-8 bytes. Smaller values can
525
- // omit prompts from that input. Does not limit Pi or Claude Code profile input.
526
- "userProfileMaxContextBytes": 32768,
527
-
528
- // Shared budget for automatic memory context (profile text, memories, and
529
- // formatting) across all hosts, in approximate tokens: ceil(UTF-8 bytes / 4).
530
- // Covers prompt retrieval, recent-memory injection, and session-memory
531
- // restoration. Not an exact provider token limit or a spending limit.
532
- "retrievalMaxTokens": 2000,
533
-
534
- // ============================================
535
- // Advanced Settings
536
- // ============================================
537
-
538
- // Inject user profile into AI context (preferences, patterns, workflows)
539
- "injectProfile": true
540
- }
541
- `;
169
+ import { CONFIG_TEMPLATE } from "./config-template.js";
170
+ export { CONFIG_TEMPLATE };
542
171
  function ensureConfigExists() {
543
172
  const configPath = join(OMMS_CONFIG_DIR, "omms.jsonc");
544
173
  const hasOmmsConfig = CONFIG_FILES.slice(0, 2).some((path) => existsSync(path));
@@ -640,8 +269,11 @@ function resolveMemoryApiKey(value) {
640
269
  }
641
270
  function buildConfig(fileConfig) {
642
271
  const memoryApiKey = resolveMemoryApiKey(fileConfig.memoryApiKey);
643
- const embeddingDimensions = fileConfig.embeddingDimensions ??
644
- getEmbeddingDimensions(fileConfig.embeddingModel ?? DEFAULTS.embeddingModel);
272
+ checkedRerankerSettings(fileConfig);
273
+ const embeddingModel = fileConfig.embeddingModel ?? DEFAULTS.embeddingModel;
274
+ // The default follows the embedder; a value present in the file always wins.
275
+ const similarityThreshold = checkedOwnMemoryLimit(MEMORY_LIMITS.similarityThreshold, fileConfig, "similarityThreshold", defaultSimilarityThreshold(embeddingModel));
276
+ const embeddingDimensions = fileConfig.embeddingDimensions ?? getEmbeddingDimensions(embeddingModel);
645
277
  const autoCaptureMaxContextBytes = checkedOwnMemoryLimit(MEMORY_LIMITS.autoCaptureMaxContextBytes, fileConfig, "autoCaptureMaxContextBytes", DEFAULTS.autoCaptureMaxContextBytes);
646
278
  const maxMemories = checkedOwnMemoryLimit(MEMORY_LIMITS.maxMemories, fileConfig, "maxMemories", DEFAULTS.maxMemories);
647
279
  const retrievalMaxTokens = checkedOwnMemoryLimit(MEMORY_LIMITS.retrievalMaxTokens, fileConfig, "retrievalMaxTokens", DEFAULTS.retrievalMaxTokens);
@@ -685,7 +317,14 @@ function buildConfig(fileConfig) {
685
317
  ? // No ambient OPENAI_API_KEY: an unset key means no Authorization header.
686
318
  resolveSecretValue(fileConfig.embeddingApiKey)
687
319
  : undefined,
688
- similarityThreshold: fileConfig.similarityThreshold ?? DEFAULTS.similarityThreshold,
320
+ similarityThreshold,
321
+ rerankerEnabled: fileConfig.rerankerEnabled ?? DEFAULTS.rerankerEnabled,
322
+ rerankerApiUrl: fileConfig.rerankerApiUrl?.trim() || undefined,
323
+ rerankerModel: fileConfig.rerankerModel?.trim() || undefined,
324
+ rerankerApiKey: fileConfig.rerankerApiKey,
325
+ rerankerTimeoutMs: fileConfig.rerankerTimeoutMs ?? DEFAULTS.rerankerTimeoutMs,
326
+ keywordBoost: checkedKeywordBoost(fileConfig.keywordBoost),
327
+ keywordMode: checkedKeywordMode(fileConfig.keywordMode),
689
328
  maxMemories,
690
329
  retrievalMaxTokens,
691
330
  maxProfileItems: fileConfig.maxProfileItems ?? DEFAULTS.maxProfileItems,
@@ -887,6 +526,14 @@ export function initConfig(directory, options = {}) {
887
526
  delete projectOverrides.piBackfillModel;
888
527
  delete projectOverrides.importPathMaps;
889
528
  delete projectOverrides.importIgnoredDirectories;
529
+ // The reranker decides where memory text is sent, so only the global file
530
+ // may set it; a checked-in project file must not redirect a user's
531
+ // memories to its own server.
532
+ delete projectOverrides.rerankerEnabled;
533
+ delete projectOverrides.rerankerApiUrl;
534
+ delete projectOverrides.rerankerModel;
535
+ delete projectOverrides.rerankerApiKey;
536
+ delete projectOverrides.rerankerTimeoutMs;
890
537
  // A checked-in project file must not point the web app at another folder.
891
538
  delete projectOverrides.claudeConfigDir;
892
539
  delete projectOverrides.webServerAutoStart;
@@ -26,7 +26,7 @@ export async function buildRetrievalSection(prompt, directory, sessionId, budget
26
26
  : search.results;
27
27
  if (results.length === 0)
28
28
  return null;
29
- const memoryContext = await formatContextForPrompt(tags.user.userEmail || null, { results }, budget);
29
+ const memoryContext = await formatContextForPrompt(tags.user.userEmail || null, { results }, budget, search.queryVector);
30
30
  return memoryContext || null;
31
31
  }
32
32
  /**
@@ -0,0 +1,56 @@
1
+ import type { RetrievedSources } from "./chat-retrieve.js";
2
+ import type { ChatRole, ChatSource } from "./chat-types.js";
3
+ /** Each source is cut to this many characters in the prompt. Stored snapshots keep it whole. */
4
+ export declare const MAX_SOURCE_CHARS = 2000;
5
+ export declare const ANSWER_TOOL: {
6
+ readonly type: "function";
7
+ readonly function: {
8
+ readonly name: "answer";
9
+ readonly description: "Answer the question from the numbered sources";
10
+ readonly parameters: {
11
+ readonly type: "object";
12
+ readonly properties: {
13
+ readonly answer: {
14
+ readonly type: "string";
15
+ readonly description: "The answer, with a marker such as [S1] or [P2] after each claim";
16
+ };
17
+ readonly citations: {
18
+ readonly type: "array";
19
+ readonly items: {
20
+ readonly type: "string";
21
+ };
22
+ };
23
+ readonly insufficient: {
24
+ readonly type: "boolean";
25
+ readonly description: "True when the sources do not answer the question";
26
+ };
27
+ };
28
+ readonly required: readonly ["answer", "citations", "insufficient"];
29
+ };
30
+ };
31
+ };
32
+ export declare function numberSources(found: RetrievedSources): ChatSource[];
33
+ export declare function buildAnswerPrompt(question: string, turns: Array<{
34
+ role: ChatRole;
35
+ content: string;
36
+ }>, sources: ChatSource[]): {
37
+ system: string;
38
+ user: string;
39
+ };
40
+ /**
41
+ * Removes `[S9]`-style markers that name a source that was not sent, and lists
42
+ * the valid ones in order of first use. Other bracketed text is left alone.
43
+ */
44
+ export declare function validateCitations(answer: string, sentLabels: string[]): {
45
+ text: string;
46
+ cited: string[];
47
+ };
48
+ export interface FinalAnswer {
49
+ text: string;
50
+ cited: string[];
51
+ insufficient: boolean;
52
+ /** True when a full answer cites no valid source; the page shows a note. */
53
+ uncited: boolean;
54
+ }
55
+ /** The checked answer, or null when the reply holds no answer text. */
56
+ export declare function finaliseAnswer(raw: unknown, sources: ChatSource[]): FinalAnswer | null;
@@ -0,0 +1,88 @@
1
+ import { MAX_PLAN_TURNS, MAX_TURN_CHARS } from "./chat-plan.js";
2
+ /** Each source is cut to this many characters in the prompt. Stored snapshots keep it whole. */
3
+ export const MAX_SOURCE_CHARS = 2000;
4
+ export const ANSWER_TOOL = {
5
+ type: "function",
6
+ function: {
7
+ name: "answer",
8
+ description: "Answer the question from the numbered sources",
9
+ parameters: {
10
+ type: "object",
11
+ properties: {
12
+ answer: {
13
+ type: "string",
14
+ description: "The answer, with a marker such as [S1] or [P2] after each claim",
15
+ },
16
+ citations: { type: "array", items: { type: "string" } },
17
+ insufficient: {
18
+ type: "boolean",
19
+ description: "True when the sources do not answer the question",
20
+ },
21
+ },
22
+ required: ["answer", "citations", "insufficient"],
23
+ },
24
+ },
25
+ };
26
+ const SYSTEM = [
27
+ "You answer questions about one user's saved memories of past coding sessions and their profile.",
28
+ "Use only the numbered sources given. Do not use outside knowledge.",
29
+ "Put the source marker, such as [S1] or [P2], after each claim it supports.",
30
+ "When the sources do not answer the question, say so briefly and set insufficient to true.",
31
+ "Write plain, short British English. Call the answer tool.",
32
+ ].join(" ");
33
+ export function numberSources(found) {
34
+ return [
35
+ ...found.memories.map((source, i) => ({ ...source, label: `S${i + 1}` })),
36
+ ...found.profile.map((source, i) => ({ ...source, label: `P${i + 1}` })),
37
+ ];
38
+ }
39
+ function describeSource(source) {
40
+ const meta = source.kind === "memory"
41
+ ? [source.projectName, source.tags?.length ? `tags: ${source.tags.join(", ")}` : ""]
42
+ : [source.section, source.category];
43
+ const header = meta.filter(Boolean).join("; ");
44
+ return `[${source.label}] ${source.text.slice(0, MAX_SOURCE_CHARS)}${header ? `\n(${header})` : ""}`;
45
+ }
46
+ export function buildAnswerPrompt(question, turns, sources) {
47
+ const history = turns
48
+ .slice(-MAX_PLAN_TURNS)
49
+ .map((turn) => `${turn.role}: ${turn.content.slice(0, MAX_TURN_CHARS)}`);
50
+ const user = [
51
+ history.length ? `Earlier turns:\n${history.join("\n")}` : "",
52
+ `Sources:\n${sources.map(describeSource).join("\n\n")}`,
53
+ `Question: ${question}`,
54
+ ]
55
+ .filter(Boolean)
56
+ .join("\n\n");
57
+ return { system: SYSTEM, user };
58
+ }
59
+ const MARKER = /\s*\[([SP]\d+)\]/g;
60
+ /**
61
+ * Removes `[S9]`-style markers that name a source that was not sent, and lists
62
+ * the valid ones in order of first use. Other bracketed text is left alone.
63
+ */
64
+ export function validateCitations(answer, sentLabels) {
65
+ const sent = new Set(sentLabels);
66
+ const cited = [];
67
+ const text = answer
68
+ .replace(MARKER, (match, label) => {
69
+ if (!sent.has(label))
70
+ return "";
71
+ if (!cited.includes(label))
72
+ cited.push(label);
73
+ return match;
74
+ })
75
+ .trim();
76
+ return { text, cited };
77
+ }
78
+ /** The checked answer, or null when the reply holds no answer text. */
79
+ export function finaliseAnswer(raw, sources) {
80
+ if (!raw || typeof raw !== "object")
81
+ return null;
82
+ const reply = raw;
83
+ if (typeof reply.answer !== "string" || !reply.answer.trim())
84
+ return null;
85
+ const insufficient = reply.insufficient === true;
86
+ const { text, cited } = validateCitations(reply.answer, sources.map((source) => source.label));
87
+ return { text, cited, insufficient, uncited: !insufficient && cited.length === 0 };
88
+ }
@@ -0,0 +1,60 @@
1
+ import type { ChatPlanRecord, ChatRole } from "./chat-types.js";
2
+ export declare const MAX_PLAN_TAGS = 200;
3
+ export declare const MAX_PLAN_TURNS = 6;
4
+ export declare const MAX_TURN_CHARS = 500;
5
+ export declare const MAX_PLAN_QUERIES = 3;
6
+ export interface PlanInput {
7
+ question: string;
8
+ turns: Array<{
9
+ role: ChatRole;
10
+ content: string;
11
+ }>;
12
+ tags: Array<{
13
+ keyword: string;
14
+ count: number;
15
+ }>;
16
+ categories: string[];
17
+ }
18
+ export declare const PLAN_TOOL: {
19
+ readonly type: "function";
20
+ readonly function: {
21
+ readonly name: "plan_search";
22
+ readonly description: "Choose how to search the user's stored memories and profile";
23
+ readonly parameters: {
24
+ readonly type: "object";
25
+ readonly properties: {
26
+ readonly queries: {
27
+ readonly type: "array";
28
+ readonly items: {
29
+ readonly type: "string";
30
+ };
31
+ readonly description: "1 to 3 short search phrases";
32
+ };
33
+ readonly tags: {
34
+ readonly type: "array";
35
+ readonly items: {
36
+ readonly type: "string";
37
+ };
38
+ readonly description: "Tags from the given list that match the question";
39
+ };
40
+ readonly profileCategories: {
41
+ readonly type: "array";
42
+ readonly items: {
43
+ readonly type: "string";
44
+ };
45
+ readonly description: "Profile categories from the given list that match the question";
46
+ };
47
+ };
48
+ readonly required: readonly ["queries", "tags", "profileCategories"];
49
+ };
50
+ };
51
+ };
52
+ export declare function buildPlanPrompt(input: PlanInput): {
53
+ system: string;
54
+ user: string;
55
+ };
56
+ /**
57
+ * The model's plan with unknown tags and categories removed. A missing or
58
+ * broken plan searches with the question alone and is marked skipped.
59
+ */
60
+ export declare function validatePlan(raw: unknown, input: PlanInput): ChatPlanRecord;