om-memory-system 3.4.0-next.23 → 3.4.0-next.27
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.
- package/README.md +21 -7
- package/dist/adapters/opencode/auto-capture-summary.js +1 -1
- package/dist/adapters/opencode/backfill-models.d.ts +1 -1
- package/dist/adapters/opencode/backfill-models.js +6 -2
- package/dist/adapters/opencode/backfill-startup.d.ts +8 -0
- package/dist/adapters/opencode/backfill-startup.js +19 -0
- package/dist/adapters/opencode/import-command.d.ts +3 -3
- package/dist/adapters/opencode/import-command.js +4 -3
- package/dist/adapters/opencode/internal-capture-sessions.d.ts +4 -0
- package/dist/{services/ai → adapters/opencode}/internal-capture-sessions.js +1 -14
- package/dist/{services/ai → adapters/opencode}/opencode-import-models.js +1 -1
- package/dist/{services/user-memory-learning.d.ts → adapters/opencode/profile-learning.d.ts} +1 -1
- package/dist/{services/user-memory-learning.js → adapters/opencode/profile-learning.js} +14 -14
- package/dist/{services/ai → adapters/opencode}/profile-llm-client.js +1 -1
- package/dist/adapters/opencode/profile-model.d.ts +23 -0
- package/dist/adapters/opencode/profile-model.js +117 -0
- package/dist/adapters/opencode/user-prompt.d.ts +2 -1
- package/dist/adapters/opencode/user-prompt.js +3 -10
- package/dist/adapters/pi/backfill-models.d.ts +4 -0
- package/dist/adapters/pi/backfill-models.js +3 -0
- package/dist/adapters/pi/capture.d.ts +1 -1
- package/dist/adapters/pi/capture.js +1 -1
- package/dist/adapters/pi/extension.js +7 -0
- package/dist/adapters/pi/import-command.js +1 -0
- package/dist/cli/index.js +13 -1
- package/dist/config.d.ts +11 -4
- package/dist/config.js +29 -6
- package/dist/core/internal-prompt.d.ts +6 -0
- package/dist/core/internal-prompt.js +11 -0
- package/dist/core/profile-analysis.d.ts +3 -0
- package/dist/importer/auto-backfill.d.ts +5 -0
- package/dist/importer/auto-backfill.js +29 -10
- package/dist/importer/backfill-controls.d.ts +53 -0
- package/dist/importer/backfill-controls.js +126 -0
- package/dist/importer/backfill-lock.d.ts +1 -0
- package/dist/importer/backfill-lock.js +2 -2
- package/dist/importer/backfill-model.d.ts +1 -1
- package/dist/importer/backfill-model.js +2 -0
- package/dist/importer/external-api-test.d.ts +18 -0
- package/dist/importer/external-api-test.js +50 -0
- package/dist/importer/external-backfill-models.d.ts +15 -0
- package/dist/importer/external-backfill-models.js +14 -0
- package/dist/importer/import-path-maps.d.ts +14 -0
- package/dist/importer/import-path-maps.js +49 -0
- package/dist/importer/import-progress.d.ts +27 -0
- package/dist/importer/import-progress.js +38 -0
- package/dist/importer/import-readiness.js +1 -1
- package/dist/importer/import-runs.d.ts +43 -0
- package/dist/importer/import-runs.js +141 -0
- package/dist/importer/import-sessions.js +9 -9
- package/dist/importer/importer.d.ts +2 -1
- package/dist/importer/importer.js +2 -2
- package/dist/importer/map-suggestions.d.ts +39 -0
- package/dist/importer/map-suggestions.js +133 -0
- package/dist/importer/opencode-import.d.ts +2 -1
- package/dist/{services/ai/internal-capture-sessions.d.ts → importer/opencode-internal-sessions.d.ts} +0 -3
- package/dist/importer/opencode-internal-sessions.js +14 -0
- package/dist/importer/opencode-reader.js +1 -1
- package/dist/{adapters/pi/conversation.d.ts → importer/pi-conversation.d.ts} +1 -1
- package/dist/importer/profile-import.js +3 -2
- package/dist/importer/run-import.d.ts +21 -3
- package/dist/importer/run-import.js +82 -2
- package/dist/importer/session-loader.d.ts +1 -1
- package/dist/importer/settings-health.js +8 -1
- package/dist/{services → importer}/settings-models.d.ts +4 -5
- package/dist/{services → importer}/settings-models.js +24 -13
- package/dist/importer/web-import-api.d.ts +3 -0
- package/dist/importer/web-import-api.js +3 -0
- package/dist/importer/web-import-jobs.js +5 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js +22 -9
- package/dist/services/ai/live-model-choice.d.ts +4 -1
- package/dist/services/ai/live-model-choice.js +18 -0
- package/dist/services/api-handlers.d.ts +6 -1
- package/dist/services/api-handlers.js +71 -62
- package/dist/services/backfill-state.d.ts +30 -0
- package/dist/services/backfill-state.js +47 -0
- package/dist/services/capture-diagnostics.d.ts +2 -8
- package/dist/services/capture-diagnostics.js +3 -49
- package/dist/services/global-config-writer.d.ts +2 -0
- package/dist/services/global-config-writer.js +37 -6
- package/dist/services/global-version.d.ts +31 -0
- package/dist/services/global-version.js +78 -0
- package/dist/services/memory-key-source.d.ts +35 -0
- package/dist/services/memory-key-source.js +123 -0
- package/dist/services/package-version.d.ts +2 -0
- package/dist/services/package-version.js +21 -0
- package/dist/services/private-path.d.ts +13 -0
- package/dist/services/private-path.js +54 -0
- package/dist/services/settings-snapshot.d.ts +17 -0
- package/dist/services/settings-snapshot.js +51 -14
- package/dist/services/turso/vector-search.d.ts +1 -1
- package/dist/services/turso/vector-search.js +5 -2
- package/dist/services/user-profile/ai-cleanup.d.ts +0 -16
- package/dist/services/user-profile/ai-cleanup.js +17 -91
- package/dist/services/user-profile/profile-model.d.ts +8 -0
- package/dist/services/user-profile/profile-model.js +31 -0
- package/dist/services/user-profile/user-profile-manager.d.ts +1 -1
- package/dist/services/user-profile/user-profile-manager.js +20 -74
- package/dist/services/web-server.d.ts +2 -0
- package/dist/services/web-server.js +75 -2
- package/dist/shared/api/schemas.d.ts +1 -1
- package/dist/v2/plugin.js +1 -1
- package/dist/web/assets/index-DvZhF1BJ.css +2 -0
- package/dist/web/assets/index-jqmX5i_i.js +124 -0
- package/dist/web/index.html +2 -2
- package/package.json +6 -1
- package/dist/web/assets/index-B-_gMSn9.js +0 -124
- package/dist/web/assets/index-D5ak5KsI.css +0 -2
- /package/dist/{services/ai → adapters/opencode}/opencode-diagnostics.d.ts +0 -0
- /package/dist/{services/ai → adapters/opencode}/opencode-diagnostics.js +0 -0
- /package/dist/{services/ai → adapters/opencode}/opencode-host-config.d.ts +0 -0
- /package/dist/{services/ai → adapters/opencode}/opencode-host-config.js +0 -0
- /package/dist/{services/ai → adapters/opencode}/opencode-import-models.d.ts +0 -0
- /package/dist/{services/ai → adapters/opencode}/opencode-provider-loader.d.ts +0 -0
- /package/dist/{services/ai → adapters/opencode}/opencode-provider-loader.js +0 -0
- /package/dist/{services/ai → adapters/opencode}/opencode-provider.d.ts +0 -0
- /package/dist/{services/ai → adapters/opencode}/opencode-provider.js +0 -0
- /package/dist/{services/ai → adapters/opencode}/opencode-sdk-client.d.ts +0 -0
- /package/dist/{services/ai → adapters/opencode}/opencode-sdk-client.js +0 -0
- /package/dist/{services/ai → adapters/opencode}/profile-llm-client.d.ts +0 -0
- /package/dist/{adapters/pi/conversation.js → importer/pi-conversation.js} +0 -0
package/dist/config.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ interface OmmsConfig {
|
|
|
23
23
|
autoCaptureMaxRetries?: number;
|
|
24
24
|
autoCaptureMaxContextBytes?: number;
|
|
25
25
|
autoCaptureLanguage?: string;
|
|
26
|
-
memoryProvider?: "openai-chat" | "openai-responses" | "anthropic" | "minimax" | "orcarouter";
|
|
26
|
+
memoryProvider?: "openai-chat" | "openai-responses" | "anthropic" | "minimax" | "orcarouter" | "google-gemini";
|
|
27
27
|
memoryModel?: string;
|
|
28
28
|
memoryApiUrl?: string;
|
|
29
29
|
memoryApiKey?: string;
|
|
@@ -45,6 +45,11 @@ interface OmmsConfig {
|
|
|
45
45
|
autoBackfill?: boolean;
|
|
46
46
|
opencodeBackfillModel?: string;
|
|
47
47
|
piBackfillModel?: string;
|
|
48
|
+
/** Saved directory maps for every history import. Global config only. */
|
|
49
|
+
importPathMaps?: Array<{
|
|
50
|
+
from: string;
|
|
51
|
+
to: string;
|
|
52
|
+
}>;
|
|
48
53
|
webServerAutoStart?: boolean;
|
|
49
54
|
webServerEnabled?: boolean;
|
|
50
55
|
webServerPort?: number;
|
|
@@ -93,7 +98,7 @@ interface OmmsConfig {
|
|
|
93
98
|
injectOn?: "first" | "always";
|
|
94
99
|
};
|
|
95
100
|
}
|
|
96
|
-
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 BOTH embeddingApiUrl and embeddingApiKey (OpenAI-compatible /embeddings).\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 (both URL and key required)\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 \"provider/model\"\n // \"opencodeBackfillModel\": \"inherit\", // or \"provider/model\"\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 // Required when webServerHost is not loopback. Protects /api/* with Bearer / X-Opencode-Mem-Token.\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.\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\"\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 // Maximum number of times to retry capturing a prompt if it fails (due to network, API errors, etc.)\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 // 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 // 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 // Periodically merge duplicate or irrelevant profile items with the configured AI provider\n \"userProfileAutoCleanupEnabled\": true,\n // Number of analyzed user prompts between automatic AI cleanup runs\n \"userProfileAutoCleanupInterval\": 100,\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\n \"similarityThreshold\": 0.6,\n\n // Maximum number of memories to return in search results\n \"maxMemories\": 10,\n\n // ============================================\n // Advanced Settings\n // ============================================\n \n // Inject user profile into AI context (preferences, patterns, workflows)\n \"injectProfile\": true\n}\n";
|
|
101
|
+
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 BOTH embeddingApiUrl and embeddingApiKey (OpenAI-compatible /embeddings).\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 (both URL and key required)\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\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 // Required when webServerHost is not loopback. Protects /api/* with Bearer / X-Omms-Token (legacy X-Opencode-Mem-Token still works).\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 // Maximum number of times to retry capturing a prompt if it fails (due to network, API errors, etc.)\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 // 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 // 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 // Periodically merge duplicate or irrelevant profile items with the configured AI provider\n \"userProfileAutoCleanupEnabled\": true,\n // Number of analyzed user prompts between automatic AI cleanup runs\n \"userProfileAutoCleanupInterval\": 100,\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\n \"similarityThreshold\": 0.6,\n\n // Maximum number of memories to return in search results\n \"maxMemories\": 10,\n\n // ============================================\n // Advanced Settings\n // ============================================\n \n // Inject user profile into AI context (preferences, patterns, workflows)\n \"injectProfile\": true\n}\n";
|
|
97
102
|
export declare function normalizeAutoCaptureMaxContextBytes(value: number): number;
|
|
98
103
|
export declare function normalizeAutoCleanupRetentionDays(value: number): number;
|
|
99
104
|
declare function buildConfig(fileConfig: OmmsConfig): {
|
|
@@ -116,7 +121,7 @@ declare function buildConfig(fileConfig: OmmsConfig): {
|
|
|
116
121
|
autoCaptureMaxRetries: number;
|
|
117
122
|
autoCaptureMaxContextBytes: number;
|
|
118
123
|
autoCaptureLanguage: string | undefined;
|
|
119
|
-
memoryProvider: "openai-chat" | "openai-responses" | "anthropic" | "minimax" | "orcarouter";
|
|
124
|
+
memoryProvider: "openai-chat" | "openai-responses" | "anthropic" | "minimax" | "orcarouter" | "google-gemini";
|
|
120
125
|
memoryModel: string | undefined;
|
|
121
126
|
memoryApiUrl: string | undefined;
|
|
122
127
|
memoryApiKey: string | undefined;
|
|
@@ -134,6 +139,7 @@ declare function buildConfig(fileConfig: OmmsConfig): {
|
|
|
134
139
|
autoBackfill: boolean;
|
|
135
140
|
opencodeBackfillModel: string;
|
|
136
141
|
piBackfillModel: string;
|
|
142
|
+
importPathMaps: import("./importer/importer.js").ImportPathMap[];
|
|
137
143
|
webServerAutoStart: boolean;
|
|
138
144
|
webServerEnabled: boolean;
|
|
139
145
|
webServerPort: number;
|
|
@@ -208,7 +214,7 @@ export declare let CONFIG: {
|
|
|
208
214
|
autoCaptureMaxRetries: number;
|
|
209
215
|
autoCaptureMaxContextBytes: number;
|
|
210
216
|
autoCaptureLanguage: string | undefined;
|
|
211
|
-
memoryProvider: "openai-chat" | "openai-responses" | "anthropic" | "minimax" | "orcarouter";
|
|
217
|
+
memoryProvider: "openai-chat" | "openai-responses" | "anthropic" | "minimax" | "orcarouter" | "google-gemini";
|
|
212
218
|
memoryModel: string | undefined;
|
|
213
219
|
memoryApiUrl: string | undefined;
|
|
214
220
|
memoryApiKey: string | undefined;
|
|
@@ -226,6 +232,7 @@ export declare let CONFIG: {
|
|
|
226
232
|
autoBackfill: boolean;
|
|
227
233
|
opencodeBackfillModel: string;
|
|
228
234
|
piBackfillModel: string;
|
|
235
|
+
importPathMaps: import("./importer/importer.js").ImportPathMap[];
|
|
229
236
|
webServerAutoStart: boolean;
|
|
230
237
|
webServerEnabled: boolean;
|
|
231
238
|
webServerPort: number;
|
package/dist/config.js
CHANGED
|
@@ -7,6 +7,7 @@ import { resolveSecretValue } from "./services/secret-resolver.js";
|
|
|
7
7
|
import { isPlaceholderApiKey } from "./services/ai/api-key-placeholder.js";
|
|
8
8
|
import { getAutoCaptureProviderStatus } from "./services/ai/live-model-choice.js";
|
|
9
9
|
import { parseBackfillModel } from "./importer/backfill-model.js";
|
|
10
|
+
import { parseImportPathMaps } from "./importer/import-path-maps.js";
|
|
10
11
|
import { resolveDefaultStoragePath, runLegacyStoreMigration, legacyMigrationPaths, } from "./services/legacy-migration.js";
|
|
11
12
|
const OMMS_CONFIG_DIR = join(homedir(), ".config", "omms");
|
|
12
13
|
const DATA_DIR = join(homedir(), ".omms");
|
|
@@ -55,6 +56,7 @@ const DEFAULTS = {
|
|
|
55
56
|
autoBackfill: true,
|
|
56
57
|
opencodeBackfillModel: "inherit",
|
|
57
58
|
piBackfillModel: "inherit",
|
|
59
|
+
importPathMaps: [],
|
|
58
60
|
webServerAutoStart: true,
|
|
59
61
|
webServerEnabled: true,
|
|
60
62
|
webServerPort: 4747,
|
|
@@ -198,8 +200,10 @@ export const CONFIG_TEMPLATE = `{
|
|
|
198
200
|
|
|
199
201
|
// Start a background import of past chats on each host's next start.
|
|
200
202
|
"autoBackfill": true,
|
|
201
|
-
// "piBackfillModel": "inherit", // or "provider/model"
|
|
202
|
-
// "opencodeBackfillModel": "inherit", // or "provider/model"
|
|
203
|
+
// "piBackfillModel": "inherit", // or "external", or "provider/model"
|
|
204
|
+
// "opencodeBackfillModel": "inherit", // or "external", or "provider/model"
|
|
205
|
+
// Directory maps for history recorded in moved or deleted directories.
|
|
206
|
+
// "importPathMaps": [{ "from": "~/code/app-feat-x", "to": "~/code/app" }],
|
|
203
207
|
|
|
204
208
|
// Register the web app to start when you log in.
|
|
205
209
|
"webServerAutoStart": true,
|
|
@@ -216,7 +220,7 @@ export const CONFIG_TEMPLATE = `{
|
|
|
216
220
|
// "webServerAuthPassword": "",
|
|
217
221
|
// "webServerAuthUsername": "",
|
|
218
222
|
|
|
219
|
-
// Required when webServerHost is not loopback. Protects /api/* with Bearer / X-Opencode-Mem-Token.
|
|
223
|
+
// Required when webServerHost is not loopback. Protects /api/* with Bearer / X-Omms-Token (legacy X-Opencode-Mem-Token still works).
|
|
220
224
|
// "webServerApiToken": "env://OMMS_WEB_TOKEN",
|
|
221
225
|
|
|
222
226
|
// ============================================
|
|
@@ -255,7 +259,8 @@ export const CONFIG_TEMPLATE = `{
|
|
|
255
259
|
// Which model summarises your work, in this order:
|
|
256
260
|
// 1. The host model below: opencodeProvider/opencodeModel in OpenCode,
|
|
257
261
|
// piProvider/piModel in Pi. Set the model to "inherit" to follow
|
|
258
|
-
// whatever model the session is using
|
|
262
|
+
// whatever model the session is using, or to "external" to send
|
|
263
|
+
// every call to the external API below.
|
|
259
264
|
// 2. If no host model is set: the external API (memoryModel/memoryApiUrl/
|
|
260
265
|
// memoryApiKey further down).
|
|
261
266
|
// 3. If neither is set: the session's own model.
|
|
@@ -285,7 +290,7 @@ export const CONFIG_TEMPLATE = `{
|
|
|
285
290
|
|
|
286
291
|
"autoCaptureEnabled": true,
|
|
287
292
|
|
|
288
|
-
// Provider type: "openai-chat" | "openai-responses" | "anthropic" | "minimax" | "orcarouter"
|
|
293
|
+
// Provider type: "openai-chat" | "openai-responses" | "anthropic" | "minimax" | "orcarouter" | "google-gemini"
|
|
289
294
|
// Note: "openai-chat" is a generic OpenAI API-compatible mode.
|
|
290
295
|
// Any service that follows the OpenAI Chat Completions API can use it via custom "memoryApiUrl".
|
|
291
296
|
"memoryProvider": "openai-chat",
|
|
@@ -554,14 +559,29 @@ export function normalizeAutoCleanupRetentionDays(value) {
|
|
|
554
559
|
}
|
|
555
560
|
return value;
|
|
556
561
|
}
|
|
562
|
+
/**
|
|
563
|
+
* The external API key, or undefined when its `env://` or `file://` source does
|
|
564
|
+
* not resolve in this process (a login web app does not see shell-profile
|
|
565
|
+
* variables). Readiness then reports the key as missing instead of the whole
|
|
566
|
+
* config failing to load.
|
|
567
|
+
*/
|
|
568
|
+
function resolveMemoryApiKey(value) {
|
|
569
|
+
try {
|
|
570
|
+
return resolveSecretValue(value);
|
|
571
|
+
}
|
|
572
|
+
catch {
|
|
573
|
+
return undefined;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
557
576
|
function buildConfig(fileConfig) {
|
|
558
|
-
const memoryApiKey =
|
|
577
|
+
const memoryApiKey = resolveMemoryApiKey(fileConfig.memoryApiKey);
|
|
559
578
|
const embeddingDimensions = fileConfig.embeddingDimensions ??
|
|
560
579
|
getEmbeddingDimensions(fileConfig.embeddingModel ?? DEFAULTS.embeddingModel);
|
|
561
580
|
const autoCaptureMaxContextBytes = normalizeAutoCaptureMaxContextBytes(fileConfig.autoCaptureMaxContextBytes ?? DEFAULTS.autoCaptureMaxContextBytes);
|
|
562
581
|
const userProfileAutoCleanupInterval = fileConfig.userProfileAutoCleanupInterval ?? DEFAULTS.userProfileAutoCleanupInterval;
|
|
563
582
|
parseBackfillModel(fileConfig, "pi");
|
|
564
583
|
parseBackfillModel(fileConfig, "opencode");
|
|
584
|
+
const importPathMaps = parseImportPathMaps(fileConfig.importPathMaps);
|
|
565
585
|
for (const key of ["autoBackfill", "webServerAutoStart"]) {
|
|
566
586
|
if (fileConfig[key] !== undefined && typeof fileConfig[key] !== "boolean") {
|
|
567
587
|
throw new Error(`Invalid ${key} config`);
|
|
@@ -612,6 +632,7 @@ function buildConfig(fileConfig) {
|
|
|
612
632
|
autoCaptureProviderStatus: getAutoCaptureProviderStatus({
|
|
613
633
|
opencodeProvider: fileConfig.opencodeProvider,
|
|
614
634
|
opencodeModel: fileConfig.opencodeModel,
|
|
635
|
+
memoryProvider: fileConfig.memoryProvider,
|
|
615
636
|
memoryModel: fileConfig.memoryModel,
|
|
616
637
|
memoryApiUrl: fileConfig.memoryApiUrl,
|
|
617
638
|
memoryApiKey,
|
|
@@ -623,6 +644,7 @@ function buildConfig(fileConfig) {
|
|
|
623
644
|
autoBackfill: fileConfig.autoBackfill ?? DEFAULTS.autoBackfill,
|
|
624
645
|
opencodeBackfillModel: fileConfig.opencodeBackfillModel ?? DEFAULTS.opencodeBackfillModel,
|
|
625
646
|
piBackfillModel: fileConfig.piBackfillModel ?? DEFAULTS.piBackfillModel,
|
|
647
|
+
importPathMaps,
|
|
626
648
|
webServerAutoStart: fileConfig.webServerAutoStart ?? DEFAULTS.webServerAutoStart,
|
|
627
649
|
webServerEnabled: fileConfig.webServerEnabled ?? DEFAULTS.webServerEnabled,
|
|
628
650
|
webServerPort: fileConfig.webServerPort ?? DEFAULTS.webServerPort,
|
|
@@ -777,6 +799,7 @@ export function initConfig(directory, options = {}) {
|
|
|
777
799
|
delete projectOverrides.autoBackfill;
|
|
778
800
|
delete projectOverrides.opencodeBackfillModel;
|
|
779
801
|
delete projectOverrides.piBackfillModel;
|
|
802
|
+
delete projectOverrides.importPathMaps;
|
|
780
803
|
delete projectOverrides.webServerAutoStart;
|
|
781
804
|
delete projectOverrides.webServerEnabled;
|
|
782
805
|
const merged = { ...globalConfig, ...projectOverrides };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True when a prompt is omms's own structured-summary or profile-analysis
|
|
3
|
+
* request. Both hosts echo it back like a user message; recording or learning
|
|
4
|
+
* from it would create self-referential memories and a learning loop.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isStructuredSummaryPromptMessage(userMessage: string): boolean;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True when a prompt is omms's own structured-summary or profile-analysis
|
|
3
|
+
* request. Both hosts echo it back like a user message; recording or learning
|
|
4
|
+
* from it would create self-referential memories and a learning loop.
|
|
5
|
+
*/
|
|
6
|
+
export function isStructuredSummaryPromptMessage(userMessage) {
|
|
7
|
+
if (userMessage.includes("# User Profile Analysis")) {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
return userMessage.includes("Analyze this conversation.") && userMessage.includes('type="skip"');
|
|
11
|
+
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
1
2
|
import type { UserProfileData } from "../services/user-profile/types.js";
|
|
2
3
|
export interface ModelPort {
|
|
3
4
|
provider: string;
|
|
4
5
|
modelId: string;
|
|
5
6
|
complete(systemPrompt: string, userPrompt: string): Promise<string>;
|
|
7
|
+
/** Host-enforced JSON output; callers fall back to `complete` and parse when absent. */
|
|
8
|
+
completeStructured?<T>(systemPrompt: string, userPrompt: string, schema: z.ZodType<T>): Promise<T>;
|
|
6
9
|
}
|
|
7
10
|
export interface ExistingProfileInput {
|
|
8
11
|
id: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { BackfillHost } from "./backfill-model.js";
|
|
2
2
|
import { runHistoryImport, type HistoryImportModels } from "./run-import.js";
|
|
3
|
+
import { type ImportSurface } from "./import-runs.js";
|
|
3
4
|
type Run = typeof runHistoryImport;
|
|
4
5
|
export interface AutoBackfillOptions {
|
|
5
6
|
host: BackfillHost;
|
|
@@ -14,6 +15,10 @@ export interface AutoBackfillOptions {
|
|
|
14
15
|
now?: () => number;
|
|
15
16
|
enabled?: () => boolean;
|
|
16
17
|
signal?: AbortSignal;
|
|
18
|
+
/** `web` for Run now and Resume on the Settings page. */
|
|
19
|
+
surface?: ImportSurface;
|
|
20
|
+
/** Skip the start-up delay and the autoBackfill switch: the user asked for this run. */
|
|
21
|
+
userStarted?: boolean;
|
|
17
22
|
}
|
|
18
23
|
/** Schedule one host's history import after startup; callers do not await it. */
|
|
19
24
|
export declare function scheduleAutoBackfill(options: AutoBackfillOptions): Promise<void>;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { CONFIG, refreshConfigIfChanged } from "../config.js";
|
|
2
2
|
import { log } from "../services/logger.js";
|
|
3
3
|
import { tryAcquireBackfillLock } from "./backfill-lock.js";
|
|
4
|
-
import { getBackfillCutoff, updateBackfillStatus, } from "../services/backfill-state.js";
|
|
4
|
+
import { getBackfillCutoff, recordUnresolvedDirectories, unresolvedDirectoriesOf, updateBackfillStatus, } from "../services/backfill-state.js";
|
|
5
5
|
import { runHistoryImport, } from "./run-import.js";
|
|
6
6
|
import { isManualImportRunning } from "./manual-import-guard.js";
|
|
7
|
+
import { isBackfillPaused } from "./import-runs.js";
|
|
8
|
+
import { workProgress } from "./import-progress.js";
|
|
7
9
|
const zero = () => ({ imported: 0, skipped: 0, failed: 0, pending: 0, unresolved: 0 });
|
|
8
10
|
function counts(report) {
|
|
9
11
|
return {
|
|
@@ -27,14 +29,19 @@ const delay = (signal) => new Promise((resolve) => {
|
|
|
27
29
|
});
|
|
28
30
|
/** Schedule one host's history import after startup; callers do not await it. */
|
|
29
31
|
export async function scheduleAutoBackfill(options) {
|
|
30
|
-
|
|
32
|
+
if (!options.userStarted)
|
|
33
|
+
await (options.wait ?? (() => delay(options.signal)))();
|
|
31
34
|
if (options.signal?.aborted)
|
|
32
35
|
return;
|
|
33
|
-
|
|
34
|
-
(() => CONFIG.autoBackfill && process.env.OMMS_DISABLE_AUTO_BACKFILL !== "1")
|
|
36
|
+
const enabled = options.enabled ??
|
|
37
|
+
(() => CONFIG.autoBackfill && process.env.OMMS_DISABLE_AUTO_BACKFILL !== "1");
|
|
38
|
+
if (!options.userStarted && !enabled())
|
|
35
39
|
return;
|
|
36
40
|
if (isManualImportRunning(options.host))
|
|
37
41
|
return;
|
|
42
|
+
// A paused backfill waits for Resume, across host starts.
|
|
43
|
+
if (await isBackfillPaused(options.host).catch(() => false))
|
|
44
|
+
return;
|
|
38
45
|
const release = await tryAcquireBackfillLock(options.host);
|
|
39
46
|
if (!release)
|
|
40
47
|
return;
|
|
@@ -72,6 +79,10 @@ export async function scheduleAutoBackfill(options) {
|
|
|
72
79
|
signal: controller.signal,
|
|
73
80
|
});
|
|
74
81
|
current = counts(dry);
|
|
82
|
+
await recordUnresolvedDirectories(options.host, unresolvedDirectoriesOf(dry)).catch((error) => log("Backfill unresolved directories write failed", {
|
|
83
|
+
host: options.host,
|
|
84
|
+
error: error instanceof Error ? error.message : String(error),
|
|
85
|
+
}));
|
|
75
86
|
if (controller.signal.aborted) {
|
|
76
87
|
state = "stopped";
|
|
77
88
|
return;
|
|
@@ -122,16 +133,22 @@ export async function scheduleAutoBackfill(options) {
|
|
|
122
133
|
cwd: options.cwd,
|
|
123
134
|
models,
|
|
124
135
|
signal: controller.signal,
|
|
125
|
-
|
|
126
|
-
|
|
136
|
+
track: {
|
|
137
|
+
surface: options.surface ?? "auto",
|
|
138
|
+
lockHeld: true,
|
|
139
|
+
expectedTotal: dry.unitsWouldImport,
|
|
140
|
+
},
|
|
141
|
+
onProgress: (done, total, _preview, alreadyHandled = 0) => {
|
|
142
|
+
// Count only units that needed work; ledger hits are not pending work.
|
|
143
|
+
processed = workProgress(done, total, alreadyHandled, dry.unitsWouldImport).done;
|
|
127
144
|
const now = clock();
|
|
128
145
|
if (now - checkedAt < 5_000)
|
|
129
146
|
return;
|
|
130
147
|
checkedAt = now;
|
|
131
148
|
refreshConfigIfChanged(options.cwd);
|
|
132
|
-
if (!CONFIG.autoBackfill)
|
|
149
|
+
if (!options.userStarted && !CONFIG.autoBackfill)
|
|
133
150
|
controller.abort();
|
|
134
|
-
current = { ...current, pending: Math.max(0,
|
|
151
|
+
current = { ...current, pending: Math.max(0, dry.unitsWouldImport - processed) };
|
|
135
152
|
pendingWrite = pendingWrite
|
|
136
153
|
.then(() => updateBackfillStatus(options.host, { state: "running", model, counts: current }))
|
|
137
154
|
.catch((error) => {
|
|
@@ -143,8 +160,10 @@ export async function scheduleAutoBackfill(options) {
|
|
|
143
160
|
},
|
|
144
161
|
});
|
|
145
162
|
await pendingWrite;
|
|
146
|
-
|
|
147
|
-
|
|
163
|
+
const finished = real.unitsImported + real.unitsSkipped + real.unitsFailed;
|
|
164
|
+
current = { ...counts(real), pending: Math.max(0, dry.unitsWouldImport - finished) };
|
|
165
|
+
const paused = await isBackfillPaused(options.host).catch(() => false);
|
|
166
|
+
state = controller.signal.aborted || paused ? "stopped" : real.unitsFailed ? "failed" : "done";
|
|
148
167
|
if (!options.signal?.aborted) {
|
|
149
168
|
options.notify(`${options.host} automatic import ${state}: ${current.imported} imported, ${current.failed} failed. autoBackfill controls the next run.`);
|
|
150
169
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type BackfillHost } from "./backfill-model.js";
|
|
2
|
+
import { type ImportRun } from "./import-runs.js";
|
|
3
|
+
import type { HistoryImportModels } from "./run-import.js";
|
|
4
|
+
type ResolveModels = () => Promise<{
|
|
5
|
+
model: string;
|
|
6
|
+
models: HistoryImportModels;
|
|
7
|
+
}>;
|
|
8
|
+
export declare function registerHostBackfillModels(host: BackfillHost, resolve: ResolveModels): void;
|
|
9
|
+
export interface SettingsModel {
|
|
10
|
+
provider: string;
|
|
11
|
+
model: string;
|
|
12
|
+
name: string;
|
|
13
|
+
}
|
|
14
|
+
/** OpenCode's model access for web imports, Health, and Settings, when OpenCode runs this process. */
|
|
15
|
+
export interface OpencodeHostModels {
|
|
16
|
+
isProviderConnected(providerID: string): boolean;
|
|
17
|
+
createImportModels(ref: {
|
|
18
|
+
providerID: string;
|
|
19
|
+
modelID: string;
|
|
20
|
+
}, directory: string): Promise<Required<HistoryImportModels>>;
|
|
21
|
+
listSettingsModels(): Promise<SettingsModel[] | null>;
|
|
22
|
+
}
|
|
23
|
+
export declare function registerOpencodeHostModels(models: OpencodeHostModels | null): void;
|
|
24
|
+
export declare function getOpencodeHostModels(): OpencodeHostModels | null;
|
|
25
|
+
/** A control request the page cannot carry out; `status` is the HTTP status. */
|
|
26
|
+
export declare class BackfillControlError extends Error {
|
|
27
|
+
readonly status: number;
|
|
28
|
+
constructor(message: string, status: number);
|
|
29
|
+
}
|
|
30
|
+
/** True when the host's backfill model is the external API, directly or through the live rule. */
|
|
31
|
+
export declare function backfillUsesExternal(host: BackfillHost): boolean;
|
|
32
|
+
/** Why Run now is not available for the host in this process, or null when it is. */
|
|
33
|
+
export declare function runNowUnavailableReason(host: BackfillHost): string | null;
|
|
34
|
+
export interface BackfillHostStatus {
|
|
35
|
+
run: ImportRun | null;
|
|
36
|
+
runNowUnavailable: string | null;
|
|
37
|
+
}
|
|
38
|
+
/** Run now, Pause, and Resume for each host's backfill, run in this process. */
|
|
39
|
+
export declare class BackfillControls {
|
|
40
|
+
private readonly controllers;
|
|
41
|
+
status(): Promise<Record<BackfillHost, BackfillHostStatus>>;
|
|
42
|
+
runNow(host: BackfillHost, cwd: string): Promise<{
|
|
43
|
+
started: true;
|
|
44
|
+
}>;
|
|
45
|
+
/** Stop after the current exchange; a run in another process sees the flag at its next write. */
|
|
46
|
+
pause(host: BackfillHost): Promise<{
|
|
47
|
+
paused: true;
|
|
48
|
+
}>;
|
|
49
|
+
resume(host: BackfillHost, cwd: string): Promise<{
|
|
50
|
+
started: true;
|
|
51
|
+
}>;
|
|
52
|
+
}
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { CONFIG } from "../config.js";
|
|
2
|
+
import { log } from "../services/logger.js";
|
|
3
|
+
import { getAutoCaptureProviderStatus, resolvePiLiveModel, } from "../services/ai/live-model-choice.js";
|
|
4
|
+
import { parseBackfillModel } from "./backfill-model.js";
|
|
5
|
+
import { readImportRun, setBackfillPaused } from "./import-runs.js";
|
|
6
|
+
/** A host runtime in this process that can resolve its signed-in backfill models. */
|
|
7
|
+
const hostResolvers = new Map();
|
|
8
|
+
export function registerHostBackfillModels(host, resolve) {
|
|
9
|
+
hostResolvers.set(host, resolve);
|
|
10
|
+
}
|
|
11
|
+
let opencodeHostModels = null;
|
|
12
|
+
export function registerOpencodeHostModels(models) {
|
|
13
|
+
opencodeHostModels = models;
|
|
14
|
+
}
|
|
15
|
+
export function getOpencodeHostModels() {
|
|
16
|
+
return opencodeHostModels;
|
|
17
|
+
}
|
|
18
|
+
/** A control request the page cannot carry out; `status` is the HTTP status. */
|
|
19
|
+
export class BackfillControlError extends Error {
|
|
20
|
+
status;
|
|
21
|
+
constructor(message, status) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.status = status;
|
|
24
|
+
this.name = "BackfillControlError";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const label = (host) => (host === "pi" ? "Pi" : "OpenCode");
|
|
28
|
+
/** True when the host's backfill model is the external API, directly or through the live rule. */
|
|
29
|
+
export function backfillUsesExternal(host) {
|
|
30
|
+
const choice = parseBackfillModel(CONFIG, host);
|
|
31
|
+
if (choice === "external")
|
|
32
|
+
return true;
|
|
33
|
+
if (choice !== "inherit")
|
|
34
|
+
return false;
|
|
35
|
+
if (host === "pi")
|
|
36
|
+
return resolvePiLiveModel(CONFIG).kind === "manual";
|
|
37
|
+
const live = getAutoCaptureProviderStatus(CONFIG);
|
|
38
|
+
return live.ready && live.mode === "manual";
|
|
39
|
+
}
|
|
40
|
+
/** Why Run now is not available for the host in this process, or null when it is. */
|
|
41
|
+
export function runNowUnavailableReason(host) {
|
|
42
|
+
if (hostResolvers.has(host))
|
|
43
|
+
return null;
|
|
44
|
+
if (!backfillUsesExternal(host)) {
|
|
45
|
+
return `Open ${label(host)}, or choose the external API for ${label(host)}'s backfill`;
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
async function resolveModels(host) {
|
|
50
|
+
const hostResolver = hostResolvers.get(host);
|
|
51
|
+
if (hostResolver && !backfillUsesExternal(host))
|
|
52
|
+
return hostResolver();
|
|
53
|
+
const { resolveExternalBackfillModels } = await import("./external-backfill-models.js");
|
|
54
|
+
return resolveExternalBackfillModels(host, CONFIG);
|
|
55
|
+
}
|
|
56
|
+
/** Run now, Pause, and Resume for each host's backfill, run in this process. */
|
|
57
|
+
export class BackfillControls {
|
|
58
|
+
controllers = new Map();
|
|
59
|
+
async status() {
|
|
60
|
+
const entry = async (host) => ({
|
|
61
|
+
run: await readImportRun(host),
|
|
62
|
+
runNowUnavailable: runNowUnavailableReason(host),
|
|
63
|
+
});
|
|
64
|
+
return { pi: await entry("pi"), opencode: await entry("opencode") };
|
|
65
|
+
}
|
|
66
|
+
async runNow(host, cwd) {
|
|
67
|
+
const reason = runNowUnavailableReason(host);
|
|
68
|
+
if (reason)
|
|
69
|
+
throw new BackfillControlError(reason, 409);
|
|
70
|
+
const current = await readImportRun(host);
|
|
71
|
+
if (current?.state === "running" || this.controllers.has(host)) {
|
|
72
|
+
throw new BackfillControlError(`${host === "pi" ? "A" : "An"} ${label(host)} import is already running`, 409);
|
|
73
|
+
}
|
|
74
|
+
if (current?.paused) {
|
|
75
|
+
throw new BackfillControlError(`${label(host)}'s backfill is paused; resume it first`, 409);
|
|
76
|
+
}
|
|
77
|
+
// Fail fast with the missing setting instead of starting a run that cannot call a model.
|
|
78
|
+
if (backfillUsesExternal(host) || !hostResolvers.has(host)) {
|
|
79
|
+
const { externalModelIssues } = await import("../services/ai/live-model-choice.js");
|
|
80
|
+
const issues = externalModelIssues(CONFIG);
|
|
81
|
+
if (issues.length > 0)
|
|
82
|
+
throw new BackfillControlError(issues.join("; "), 409);
|
|
83
|
+
}
|
|
84
|
+
const controller = new AbortController();
|
|
85
|
+
this.controllers.set(host, controller);
|
|
86
|
+
const { scheduleAutoBackfill } = await import("./auto-backfill.js");
|
|
87
|
+
void scheduleAutoBackfill({
|
|
88
|
+
host,
|
|
89
|
+
cwd,
|
|
90
|
+
signal: controller.signal,
|
|
91
|
+
surface: "web",
|
|
92
|
+
userStarted: true,
|
|
93
|
+
resolveModels: () => resolveModels(host),
|
|
94
|
+
notify: (message) => log("Backfill from the Settings page", { host, message: message.slice(0, 200) }),
|
|
95
|
+
})
|
|
96
|
+
.catch((error) => log("Backfill from the Settings page failed", {
|
|
97
|
+
host,
|
|
98
|
+
error: error instanceof Error ? error.name : "unknown",
|
|
99
|
+
}))
|
|
100
|
+
.finally(() => {
|
|
101
|
+
if (this.controllers.get(host) === controller)
|
|
102
|
+
this.controllers.delete(host);
|
|
103
|
+
});
|
|
104
|
+
return { started: true };
|
|
105
|
+
}
|
|
106
|
+
/** Stop after the current exchange; a run in another process sees the flag at its next write. */
|
|
107
|
+
async pause(host) {
|
|
108
|
+
await setBackfillPaused(host, true);
|
|
109
|
+
this.controllers.get(host)?.abort();
|
|
110
|
+
return { paused: true };
|
|
111
|
+
}
|
|
112
|
+
async resume(host, cwd) {
|
|
113
|
+
const reason = runNowUnavailableReason(host);
|
|
114
|
+
if (reason)
|
|
115
|
+
throw new BackfillControlError(reason, 409);
|
|
116
|
+
await setBackfillPaused(host, false);
|
|
117
|
+
try {
|
|
118
|
+
return await this.runNow(host, cwd);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
// Run now refused (a run is active, or a setting is missing): stay paused.
|
|
122
|
+
await setBackfillPaused(host, true);
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { BackfillHost } from "./backfill-model.js";
|
|
2
|
+
export declare function isProcessAlive(pid: number): boolean;
|
|
2
3
|
/** Claim one host's import without holding a database transaction during the run. */
|
|
3
4
|
export declare function tryAcquireBackfillLock(host: BackfillHost, storagePath?: string): Promise<(() => Promise<void>) | null>;
|
|
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { CONFIG } from "../config.js";
|
|
4
4
|
import { tursoConnectionManager } from "../services/turso/connection-manager.js";
|
|
5
|
-
function
|
|
5
|
+
export function isProcessAlive(pid) {
|
|
6
6
|
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
7
7
|
return false;
|
|
8
8
|
try {
|
|
@@ -26,7 +26,7 @@ export async function tryAcquireBackfillLock(host, storagePath = CONFIG.storageP
|
|
|
26
26
|
const holder = await db.get("SELECT pid, token FROM backfill_locks WHERE host = ?", [host]);
|
|
27
27
|
if (!holder)
|
|
28
28
|
continue;
|
|
29
|
-
if (
|
|
29
|
+
if (isProcessAlive(holder.pid))
|
|
30
30
|
return null;
|
|
31
31
|
const replaced = await db.run("UPDATE backfill_locks SET pid = ?, token = ? WHERE host = ? AND pid = ? AND token = ?", [state.pid, state.token, host, holder.pid, holder.token]);
|
|
32
32
|
if (!replaced)
|
|
@@ -3,6 +3,8 @@ export function parseBackfillModel(config, host) {
|
|
|
3
3
|
const value = host === "pi" ? config.piBackfillModel : config.opencodeBackfillModel;
|
|
4
4
|
if (value === undefined || value === "inherit")
|
|
5
5
|
return "inherit";
|
|
6
|
+
if (value === "external")
|
|
7
|
+
return "external";
|
|
6
8
|
if (typeof value !== "string")
|
|
7
9
|
throw new Error(`Invalid ${host}BackfillModel config`);
|
|
8
10
|
const slash = value.indexOf("/");
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { CONFIG } from "../config.js";
|
|
2
|
+
export type ExternalApiTestResult = {
|
|
3
|
+
ok: true;
|
|
4
|
+
model: string;
|
|
5
|
+
} | {
|
|
6
|
+
ok: false;
|
|
7
|
+
error: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* One small call to the saved external API from this process, with a small
|
|
11
|
+
* output limit. The error never contains the key.
|
|
12
|
+
*/
|
|
13
|
+
export declare function testExternalApi(config?: typeof CONFIG, createProvider?: (provider: string, providerConfig: Record<string, unknown>) => {
|
|
14
|
+
executeToolCall: (...args: any[]) => Promise<{
|
|
15
|
+
success: boolean;
|
|
16
|
+
error?: string;
|
|
17
|
+
}>;
|
|
18
|
+
}): Promise<ExternalApiTestResult>;
|