@tenex-chat/backend 0.9.1
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 +194 -0
- package/dist/backend-wrapper.cjs +3 -0
- package/dist/src/index.js +331928 -0
- package/package.json +103 -0
- package/src/agents/AgentRegistry.ts +418 -0
- package/src/agents/AgentStorage.ts +1133 -0
- package/src/agents/ConfigResolver.ts +229 -0
- package/src/agents/agent-installer.ts +236 -0
- package/src/agents/agent-loader.ts +241 -0
- package/src/agents/constants.ts +82 -0
- package/src/agents/errors.ts +48 -0
- package/src/agents/execution/AgentExecutor.ts +561 -0
- package/src/agents/execution/ExecutionContextFactory.ts +112 -0
- package/src/agents/execution/MessageCompiler.ts +597 -0
- package/src/agents/execution/MessageSyncer.ts +100 -0
- package/src/agents/execution/PostCompletionChecker.ts +278 -0
- package/src/agents/execution/ProgressMonitor.ts +50 -0
- package/src/agents/execution/RALResolver.ts +177 -0
- package/src/agents/execution/SessionManager.ts +181 -0
- package/src/agents/execution/StreamCallbacks.ts +312 -0
- package/src/agents/execution/StreamExecutionHandler.ts +579 -0
- package/src/agents/execution/StreamSetup.ts +313 -0
- package/src/agents/execution/ToolEventHandlers.ts +239 -0
- package/src/agents/execution/ToolExecutionTracker.ts +498 -0
- package/src/agents/execution/ToolResultUtils.ts +97 -0
- package/src/agents/execution/ToolSupervisionWrapper.ts +174 -0
- package/src/agents/execution/constants.ts +16 -0
- package/src/agents/execution/index.ts +3 -0
- package/src/agents/execution/types.ts +96 -0
- package/src/agents/execution/utils.ts +26 -0
- package/src/agents/index.ts +4 -0
- package/src/agents/script-installer.ts +266 -0
- package/src/agents/supervision/SupervisorLLMService.ts +253 -0
- package/src/agents/supervision/SupervisorOrchestrator.ts +471 -0
- package/src/agents/supervision/heuristics/ConsecutiveToolsWithoutTodoHeuristic.ts +73 -0
- package/src/agents/supervision/heuristics/DelegationClaimHeuristic.ts +80 -0
- package/src/agents/supervision/heuristics/HeuristicRegistry.ts +114 -0
- package/src/agents/supervision/heuristics/PendingTodosHeuristic.ts +93 -0
- package/src/agents/supervision/heuristics/SilentAgentHeuristic.ts +54 -0
- package/src/agents/supervision/heuristics/index.ts +5 -0
- package/src/agents/supervision/index.ts +28 -0
- package/src/agents/supervision/registerHeuristics.ts +110 -0
- package/src/agents/supervision/supervisionHealthCheck.ts +123 -0
- package/src/agents/supervision/types.ts +171 -0
- package/src/agents/tool-names.ts +46 -0
- package/src/agents/tool-normalization.ts +184 -0
- package/src/agents/types/index.ts +2 -0
- package/src/agents/types/runtime.ts +74 -0
- package/src/agents/types/storage.ts +145 -0
- package/src/commands/agent/import/index.ts +6 -0
- package/src/commands/agent/import/openclaw-distiller.ts +57 -0
- package/src/commands/agent/import/openclaw-reader.ts +141 -0
- package/src/commands/agent/import/openclaw.ts +154 -0
- package/src/commands/agent/index.ts +6 -0
- package/src/commands/agent.ts +215 -0
- package/src/commands/daemon.ts +198 -0
- package/src/commands/doctor.ts +134 -0
- package/src/commands/setup/embed.ts +228 -0
- package/src/commands/setup/global-system-prompt.ts +223 -0
- package/src/commands/setup/image.ts +179 -0
- package/src/commands/setup/index.ts +16 -0
- package/src/commands/setup/interactive.ts +95 -0
- package/src/commands/setup/llm.ts +38 -0
- package/src/commands/setup/onboarding.ts +294 -0
- package/src/commands/setup/providers.ts +27 -0
- package/src/constants.ts +34 -0
- package/src/conversations/ConversationDiskReader.ts +148 -0
- package/src/conversations/ConversationRegistry.ts +728 -0
- package/src/conversations/ConversationStore.ts +868 -0
- package/src/conversations/MessageBuilder.ts +866 -0
- package/src/conversations/executionTime.ts +62 -0
- package/src/conversations/formatters/DelegationXmlFormatter.ts +64 -0
- package/src/conversations/formatters/ThreadedConversationFormatter.ts +303 -0
- package/src/conversations/formatters/index.ts +9 -0
- package/src/conversations/formatters/utils/MessageFormatter.ts +46 -0
- package/src/conversations/formatters/utils/TimestampFormatter.ts +56 -0
- package/src/conversations/formatters/utils/TreeBuilder.ts +131 -0
- package/src/conversations/formatters/utils/TreeRenderer.ts +49 -0
- package/src/conversations/index.ts +2 -0
- package/src/conversations/persistence/ToolMessageStorage.ts +143 -0
- package/src/conversations/search/ConversationIndexManager.ts +393 -0
- package/src/conversations/search/QueryParser.ts +114 -0
- package/src/conversations/search/SearchEngine.ts +175 -0
- package/src/conversations/search/SnippetExtractor.ts +345 -0
- package/src/conversations/search/embeddings/ConversationEmbeddingService.ts +484 -0
- package/src/conversations/search/embeddings/ConversationIndexingJob.ts +320 -0
- package/src/conversations/search/embeddings/IndexingStateManager.ts +338 -0
- package/src/conversations/search/embeddings/index.ts +18 -0
- package/src/conversations/search/index.ts +49 -0
- package/src/conversations/search/types.ts +124 -0
- package/src/conversations/services/CategoryManager.ts +160 -0
- package/src/conversations/services/ConversationResolver.ts +296 -0
- package/src/conversations/services/ConversationSummarizer.ts +234 -0
- package/src/conversations/services/MetadataDebounceManager.ts +188 -0
- package/src/conversations/services/index.ts +2 -0
- package/src/conversations/types.ts +148 -0
- package/src/conversations/utils/content-utils.ts +69 -0
- package/src/conversations/utils/image-placeholder.ts +281 -0
- package/src/conversations/utils/image-url-utils.ts +171 -0
- package/src/conversations/utils/multimodal-content.ts +90 -0
- package/src/conversations/utils/tool-result-truncator.ts +159 -0
- package/src/daemon/Daemon.ts +1883 -0
- package/src/daemon/ProjectRuntime.ts +657 -0
- package/src/daemon/RestartState.ts +152 -0
- package/src/daemon/RuntimeLifecycle.ts +268 -0
- package/src/daemon/SubscriptionManager.ts +305 -0
- package/src/daemon/UnixSocketTransport.ts +318 -0
- package/src/daemon/filters/SubscriptionFilterBuilder.ts +119 -0
- package/src/daemon/index.ts +9 -0
- package/src/daemon/routing/DaemonRouter.ts +491 -0
- package/src/daemon/types.ts +150 -0
- package/src/daemon/utils/routing-log.ts +76 -0
- package/src/daemon/utils/telemetry.ts +173 -0
- package/src/event-handler/agentDeletion.ts +383 -0
- package/src/event-handler/index.ts +749 -0
- package/src/event-handler/newConversation.ts +165 -0
- package/src/event-handler/project.ts +166 -0
- package/src/event-handler/reply.ts +18 -0
- package/src/events/NDKAgentDefinition.ts +292 -0
- package/src/events/NDKAgentLesson.ts +106 -0
- package/src/events/NDKEventMetadata.ts +34 -0
- package/src/events/NDKMCPTool.ts +60 -0
- package/src/events/NDKProjectStatus.ts +384 -0
- package/src/events/index.ts +4 -0
- package/src/index.ts +126 -0
- package/src/lib/agent-home.ts +334 -0
- package/src/lib/error-formatter.ts +200 -0
- package/src/lib/fs/filesystem.ts +128 -0
- package/src/lib/fs/index.ts +1 -0
- package/src/lib/json-parser.ts +30 -0
- package/src/lib/string.ts +15 -0
- package/src/lib/time.ts +74 -0
- package/src/llm/ChunkHandler.ts +277 -0
- package/src/llm/FinishHandler.ts +250 -0
- package/src/llm/LLMConfigEditor.ts +154 -0
- package/src/llm/LLMServiceFactory.ts +230 -0
- package/src/llm/MessageProcessor.ts +90 -0
- package/src/llm/RecordingState.ts +37 -0
- package/src/llm/StreamPublisher.ts +40 -0
- package/src/llm/TracingUtils.ts +77 -0
- package/src/llm/chunk-validators.ts +57 -0
- package/src/llm/constants.ts +6 -0
- package/src/llm/index.ts +12 -0
- package/src/llm/meta/MetaModelResolver.ts +352 -0
- package/src/llm/meta/index.ts +11 -0
- package/src/llm/middleware/flight-recorder.ts +188 -0
- package/src/llm/providers/MockProvider.ts +332 -0
- package/src/llm/providers/agent/ClaudeCodeProvider.ts +343 -0
- package/src/llm/providers/agent/ClaudeCodeToolsAdapter.ts +203 -0
- package/src/llm/providers/agent/CodexAppServerProvider.ts +214 -0
- package/src/llm/providers/agent/CodexAppServerToolsAdapter.ts +91 -0
- package/src/llm/providers/agent/index.ts +10 -0
- package/src/llm/providers/base/AgentProvider.ts +107 -0
- package/src/llm/providers/base/BaseProvider.ts +114 -0
- package/src/llm/providers/base/StandardProvider.ts +38 -0
- package/src/llm/providers/base/index.ts +9 -0
- package/src/llm/providers/index.ts +106 -0
- package/src/llm/providers/key-manager.ts +238 -0
- package/src/llm/providers/ollama-models.ts +105 -0
- package/src/llm/providers/openrouter-models.ts +102 -0
- package/src/llm/providers/provider-ids.ts +18 -0
- package/src/llm/providers/registry/ProviderRegistry.ts +414 -0
- package/src/llm/providers/registry/index.ts +7 -0
- package/src/llm/providers/standard/AnthropicProvider.ts +71 -0
- package/src/llm/providers/standard/OllamaProvider.ts +59 -0
- package/src/llm/providers/standard/OpenAIProvider.ts +44 -0
- package/src/llm/providers/standard/OpenRouterProvider.ts +103 -0
- package/src/llm/providers/standard/index.ts +10 -0
- package/src/llm/providers/types.ts +194 -0
- package/src/llm/providers/usage-metadata.ts +78 -0
- package/src/llm/service.ts +713 -0
- package/src/llm/types.ts +167 -0
- package/src/llm/utils/ConfigurationManager.ts +650 -0
- package/src/llm/utils/ConfigurationTester.ts +229 -0
- package/src/llm/utils/ModelSelector.ts +212 -0
- package/src/llm/utils/ProviderConfigUI.ts +177 -0
- package/src/llm/utils/claudeCodePromptCompiler.ts +141 -0
- package/src/llm/utils/codex-models.ts +53 -0
- package/src/llm/utils/context-window-cache.ts +30 -0
- package/src/llm/utils/models-dev-cache.ts +267 -0
- package/src/llm/utils/provider-setup.ts +50 -0
- package/src/llm/utils/tool-errors.ts +78 -0
- package/src/llm/utils/usage.ts +74 -0
- package/src/logging/EventRoutingLogger.ts +205 -0
- package/src/nostr/AgentEventDecoder.ts +357 -0
- package/src/nostr/AgentEventEncoder.ts +677 -0
- package/src/nostr/AgentProfilePublisher.ts +657 -0
- package/src/nostr/AgentPublisher.ts +437 -0
- package/src/nostr/BlossomService.ts +226 -0
- package/src/nostr/InterventionPublisher.ts +132 -0
- package/src/nostr/TagExtractor.ts +228 -0
- package/src/nostr/collectEvents.ts +83 -0
- package/src/nostr/constants.ts +38 -0
- package/src/nostr/encryption.ts +26 -0
- package/src/nostr/index.ts +31 -0
- package/src/nostr/keys.ts +17 -0
- package/src/nostr/kinds.ts +37 -0
- package/src/nostr/ndkClient.ts +72 -0
- package/src/nostr/relays.ts +43 -0
- package/src/nostr/trace-context.ts +39 -0
- package/src/nostr/types.ts +227 -0
- package/src/nostr/utils.ts +84 -0
- package/src/prompts/core/FragmentRegistry.ts +30 -0
- package/src/prompts/core/PromptBuilder.ts +98 -0
- package/src/prompts/core/index.ts +3 -0
- package/src/prompts/core/types.ts +13 -0
- package/src/prompts/fragments/00-global-system-prompt.ts +44 -0
- package/src/prompts/fragments/01-agent-identity.ts +69 -0
- package/src/prompts/fragments/02-agent-home-directory.ts +114 -0
- package/src/prompts/fragments/03-system-reminders-explanation.ts +14 -0
- package/src/prompts/fragments/04-relay-configuration.ts +38 -0
- package/src/prompts/fragments/05-delegation-chain.ts +45 -0
- package/src/prompts/fragments/06-agent-todos.ts +74 -0
- package/src/prompts/fragments/06-todo-usage-guidance.ts +34 -0
- package/src/prompts/fragments/07-meta-project-context.ts +234 -0
- package/src/prompts/fragments/08-active-conversations.ts +382 -0
- package/src/prompts/fragments/09-recent-conversations.ts +153 -0
- package/src/prompts/fragments/10-referenced-article.ts +21 -0
- package/src/prompts/fragments/11-nudges.ts +134 -0
- package/src/prompts/fragments/12-skills.ts +127 -0
- package/src/prompts/fragments/13-available-nudges.ts +122 -0
- package/src/prompts/fragments/15-available-agents.ts +53 -0
- package/src/prompts/fragments/16-stay-in-your-lane.ts +41 -0
- package/src/prompts/fragments/17-todo-before-delegation.ts +39 -0
- package/src/prompts/fragments/20-voice-mode.ts +62 -0
- package/src/prompts/fragments/22-scheduled-tasks.ts +175 -0
- package/src/prompts/fragments/24-retrieved-lessons.ts +26 -0
- package/src/prompts/fragments/25-rag-instructions.ts +333 -0
- package/src/prompts/fragments/26-mcp-resources.ts +237 -0
- package/src/prompts/fragments/27-memorized-reports.ts +77 -0
- package/src/prompts/fragments/28-agent-directed-monitoring.ts +32 -0
- package/src/prompts/fragments/29-rag-collections.ts +50 -0
- package/src/prompts/fragments/30-worktree-context.ts +98 -0
- package/src/prompts/fragments/31-agents-md-guidance.ts +96 -0
- package/src/prompts/fragments/32-process-metrics.ts +72 -0
- package/src/prompts/fragments/debug-mode.ts +48 -0
- package/src/prompts/fragments/delegation-completion.ts +44 -0
- package/src/prompts/fragments/index.ts +91 -0
- package/src/prompts/index.ts +21 -0
- package/src/prompts/utils/systemPromptBuilder.ts +777 -0
- package/src/scripts/migrate-prefix-index.ts +157 -0
- package/src/services/AgentDefinitionMonitor.ts +701 -0
- package/src/services/ConfigService.ts +723 -0
- package/src/services/CooldownRegistry.ts +199 -0
- package/src/services/LLMOperationsRegistry.ts +424 -0
- package/src/services/OwnerAgentListService.ts +354 -0
- package/src/services/PubkeyService.ts +308 -0
- package/src/services/agents/AgentMetadataStore.ts +72 -0
- package/src/services/agents/AgentResolution.ts +59 -0
- package/src/services/agents/EscalationService.ts +281 -0
- package/src/services/agents/NDKAgentDiscovery.ts +95 -0
- package/src/services/agents/index.ts +7 -0
- package/src/services/agents-md/AgentsMdService.ts +184 -0
- package/src/services/agents-md/SystemReminderInjector.ts +238 -0
- package/src/services/agents-md/index.ts +11 -0
- package/src/services/apns/APNsClient.ts +203 -0
- package/src/services/apns/APNsService.ts +358 -0
- package/src/services/apns/index.ts +11 -0
- package/src/services/apns/types.ts +80 -0
- package/src/services/compression/CompressionService.ts +445 -0
- package/src/services/compression/compression-schema.ts +28 -0
- package/src/services/compression/compression-types.ts +74 -0
- package/src/services/compression/compression-utils.ts +587 -0
- package/src/services/config/types.ts +394 -0
- package/src/services/dispatch/AgentDispatchService.ts +937 -0
- package/src/services/dispatch/AgentRouter.ts +181 -0
- package/src/services/dispatch/DelegationCompletionHandler.ts +232 -0
- package/src/services/embedding/EmbeddingProvider.ts +188 -0
- package/src/services/embedding/index.ts +5 -0
- package/src/services/event-context/EventContextService.ts +108 -0
- package/src/services/event-context/index.ts +2 -0
- package/src/services/heuristics/ContextBuilder.ts +106 -0
- package/src/services/heuristics/HeuristicEngine.ts +200 -0
- package/src/services/heuristics/formatters.ts +58 -0
- package/src/services/heuristics/index.ts +12 -0
- package/src/services/heuristics/rules/index.ts +25 -0
- package/src/services/heuristics/rules/todoBeforeDelegation.ts +69 -0
- package/src/services/heuristics/rules/todoReminderOnToolUse.ts +63 -0
- package/src/services/heuristics/types.ts +144 -0
- package/src/services/image/ImageGenerationService.ts +389 -0
- package/src/services/image/index.ts +12 -0
- package/src/services/intervention/InterventionService.ts +1352 -0
- package/src/services/intervention/index.ts +7 -0
- package/src/services/mcp/MCPManager.ts +683 -0
- package/src/services/mcp/McpNotificationDelivery.ts +139 -0
- package/src/services/mcp/McpSubscriptionService.ts +653 -0
- package/src/services/mcp/mcpInstaller.ts +130 -0
- package/src/services/nip46/Nip46SigningLog.ts +81 -0
- package/src/services/nip46/Nip46SigningService.ts +467 -0
- package/src/services/nip46/index.ts +4 -0
- package/src/services/nudge/NudgeService.ts +224 -0
- package/src/services/nudge/NudgeWhitelistService.ts +382 -0
- package/src/services/nudge/index.ts +5 -0
- package/src/services/nudge/types.ts +83 -0
- package/src/services/projects/ProjectContext.ts +672 -0
- package/src/services/projects/ProjectContextStore.ts +102 -0
- package/src/services/projects/index.ts +6 -0
- package/src/services/prompt-compiler/index.ts +15 -0
- package/src/services/prompt-compiler/prompt-compiler-service.ts +1143 -0
- package/src/services/pubkey-gate/PubkeyGateService.ts +93 -0
- package/src/services/pubkey-gate/index.ts +1 -0
- package/src/services/rag/EmbeddingProviderFactory.ts +292 -0
- package/src/services/rag/LanceDBMaintenanceService.ts +211 -0
- package/src/services/rag/RAGDatabaseService.ts +173 -0
- package/src/services/rag/RAGOperations.ts +682 -0
- package/src/services/rag/RAGService.ts +240 -0
- package/src/services/rag/RagSubscriptionService.ts +618 -0
- package/src/services/rag/rag-utils.ts +174 -0
- package/src/services/ral/PendingDelegationsRegistry.ts +168 -0
- package/src/services/ral/RALRegistry.ts +2782 -0
- package/src/services/ral/index.ts +4 -0
- package/src/services/ral/types.ts +292 -0
- package/src/services/reports/LocalReportStore.ts +380 -0
- package/src/services/reports/ReportEmbeddingService.ts +430 -0
- package/src/services/reports/ReportService.ts +440 -0
- package/src/services/reports/articleUtils.ts +52 -0
- package/src/services/reports/index.ts +7 -0
- package/src/services/scheduling/SchedulerService.ts +1057 -0
- package/src/services/scheduling/errors.ts +14 -0
- package/src/services/scheduling/index.ts +7 -0
- package/src/services/scheduling/utils.ts +77 -0
- package/src/services/search/SearchProviderRegistry.ts +78 -0
- package/src/services/search/UnifiedSearchService.ts +218 -0
- package/src/services/search/index.ts +47 -0
- package/src/services/search/projectFilter.ts +22 -0
- package/src/services/search/providers/ConversationSearchProvider.ts +48 -0
- package/src/services/search/providers/LessonSearchProvider.ts +75 -0
- package/src/services/search/providers/ReportSearchProvider.ts +49 -0
- package/src/services/search/types.ts +144 -0
- package/src/services/skill/SkillService.ts +482 -0
- package/src/services/skill/index.ts +2 -0
- package/src/services/skill/types.ts +70 -0
- package/src/services/status/OperationsStatusService.ts +276 -0
- package/src/services/status/ProjectStatusService.ts +522 -0
- package/src/services/status/index.ts +11 -0
- package/src/services/storage/PrefixKVStore.ts +242 -0
- package/src/services/storage/index.ts +1 -0
- package/src/services/system-reminder/SystemReminderUtils.ts +96 -0
- package/src/services/system-reminder/index.ts +7 -0
- package/src/services/trust-pubkeys/TrustPubkeyService.ts +325 -0
- package/src/services/trust-pubkeys/index.ts +2 -0
- package/src/telemetry/ConversationSpanManager.ts +111 -0
- package/src/telemetry/EventLoopMonitor.ts +206 -0
- package/src/telemetry/LLMSpanRegistry.ts +20 -0
- package/src/telemetry/NostrSpanProcessor.ts +89 -0
- package/src/telemetry/ToolCallSpanProcessor.ts +66 -0
- package/src/telemetry/diagnostics.ts +27 -0
- package/src/telemetry/setup.ts +120 -0
- package/src/tools/implementations/agents_discover.ts +121 -0
- package/src/tools/implementations/agents_hire.ts +127 -0
- package/src/tools/implementations/agents_list.ts +96 -0
- package/src/tools/implementations/agents_publish.ts +611 -0
- package/src/tools/implementations/agents_read.ts +173 -0
- package/src/tools/implementations/agents_write.ts +200 -0
- package/src/tools/implementations/ask.ts +411 -0
- package/src/tools/implementations/change_model.ts +141 -0
- package/src/tools/implementations/conversation_get.ts +661 -0
- package/src/tools/implementations/conversation_list.ts +377 -0
- package/src/tools/implementations/conversation_search.ts +370 -0
- package/src/tools/implementations/delegate.ts +327 -0
- package/src/tools/implementations/delegate_crossproject.ts +209 -0
- package/src/tools/implementations/delegate_followup.ts +300 -0
- package/src/tools/implementations/fs_edit.ts +162 -0
- package/src/tools/implementations/fs_glob.ts +182 -0
- package/src/tools/implementations/fs_grep.ts +513 -0
- package/src/tools/implementations/fs_read.ts +332 -0
- package/src/tools/implementations/fs_write.ts +113 -0
- package/src/tools/implementations/generate_image.ts +259 -0
- package/src/tools/implementations/home_fs.ts +515 -0
- package/src/tools/implementations/kill.ts +651 -0
- package/src/tools/implementations/learn.ts +166 -0
- package/src/tools/implementations/lesson-formatter.ts +38 -0
- package/src/tools/implementations/lesson_delete.ts +164 -0
- package/src/tools/implementations/lesson_get.ts +105 -0
- package/src/tools/implementations/lessons_list.ts +153 -0
- package/src/tools/implementations/mcp_resource_read.ts +161 -0
- package/src/tools/implementations/mcp_subscribe.ts +158 -0
- package/src/tools/implementations/mcp_subscription_stop.ts +85 -0
- package/src/tools/implementations/nostr_fetch.ts +149 -0
- package/src/tools/implementations/nostr_publish_as_user.ts +353 -0
- package/src/tools/implementations/project_list.ts +146 -0
- package/src/tools/implementations/rag_add_documents.ts +573 -0
- package/src/tools/implementations/rag_create_collection.ts +65 -0
- package/src/tools/implementations/rag_delete_collection.ts +68 -0
- package/src/tools/implementations/rag_list_collections.ts +77 -0
- package/src/tools/implementations/rag_query.ts +107 -0
- package/src/tools/implementations/rag_subscription_create.ts +105 -0
- package/src/tools/implementations/rag_subscription_delete.ts +80 -0
- package/src/tools/implementations/rag_subscription_get.ts +123 -0
- package/src/tools/implementations/rag_subscription_list.ts +128 -0
- package/src/tools/implementations/report_delete.ts +79 -0
- package/src/tools/implementations/report_read.ts +160 -0
- package/src/tools/implementations/report_write.ts +278 -0
- package/src/tools/implementations/reports_list.ts +77 -0
- package/src/tools/implementations/schedule_task.ts +104 -0
- package/src/tools/implementations/schedule_task_cancel.ts +62 -0
- package/src/tools/implementations/schedule_task_once.ts +128 -0
- package/src/tools/implementations/schedule_tasks_list.ts +79 -0
- package/src/tools/implementations/search.ts +160 -0
- package/src/tools/implementations/shell.ts +553 -0
- package/src/tools/implementations/todo.ts +260 -0
- package/src/tools/implementations/upload_blob.ts +381 -0
- package/src/tools/implementations/web_fetch.ts +153 -0
- package/src/tools/implementations/web_search.ts +250 -0
- package/src/tools/registry.ts +670 -0
- package/src/tools/types.ts +177 -0
- package/src/tools/utils.ts +256 -0
- package/src/types/event-ids.ts +320 -0
- package/src/types/index.ts +46 -0
- package/src/utils/agentFetcher.ts +107 -0
- package/src/utils/cli-error.ts +29 -0
- package/src/utils/conversation-id.ts +27 -0
- package/src/utils/conversation-utils.ts +1 -0
- package/src/utils/delegation-chain.ts +357 -0
- package/src/utils/error-handler.ts +42 -0
- package/src/utils/git/gitignore.ts +69 -0
- package/src/utils/git/index.ts +2 -0
- package/src/utils/git/initializeGitRepo.ts +204 -0
- package/src/utils/git/worktree.ts +260 -0
- package/src/utils/lessonFormatter.ts +70 -0
- package/src/utils/lessonTrust.ts +24 -0
- package/src/utils/lockfile.ts +123 -0
- package/src/utils/logger.ts +149 -0
- package/src/utils/nostr-entity-parser.ts +365 -0
- package/src/utils/process.ts +49 -0
- package/src/wrapper.ts +262 -0
- package/tsconfig.json +41 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConversationIndexingJob - Automatic batch indexing service
|
|
3
|
+
*
|
|
4
|
+
* This service runs periodically (every 5 minutes) to automatically index
|
|
5
|
+
* conversations that need embedding updates. It tracks which conversations
|
|
6
|
+
* have been indexed and ensures new/updated conversations are processed
|
|
7
|
+
* without requiring manual agent intervention.
|
|
8
|
+
*
|
|
9
|
+
* Key features:
|
|
10
|
+
* - Runs every 5 minutes as a background job
|
|
11
|
+
* - Indexes conversations across all projects
|
|
12
|
+
* - Tracks indexing state durably to avoid redundant work
|
|
13
|
+
* - Re-indexes when conversation metadata changes
|
|
14
|
+
* - Graceful error handling - failures don't break the service
|
|
15
|
+
* - Prevents overlapping batches
|
|
16
|
+
* - Decoupled from ConversationRegistry for multi-project support
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { logger } from "@/utils/logger";
|
|
20
|
+
import { getTenexBasePath } from "@/constants";
|
|
21
|
+
import { join } from "path";
|
|
22
|
+
import { getConversationEmbeddingService } from "./ConversationEmbeddingService";
|
|
23
|
+
import type { BuildDocumentResult } from "./ConversationEmbeddingService";
|
|
24
|
+
import { IndexingStateManager } from "./IndexingStateManager";
|
|
25
|
+
import { listProjectIdsFromDisk, listConversationIdsFromDiskForProject } from "@/conversations/ConversationDiskReader";
|
|
26
|
+
import { RAGService, type RAGDocument } from "@/services/rag/RAGService";
|
|
27
|
+
|
|
28
|
+
/** Default interval: 5 minutes (in milliseconds) */
|
|
29
|
+
const DEFAULT_INTERVAL_MS = 5 * 60 * 1000;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Automatic conversation indexing job service
|
|
33
|
+
*/
|
|
34
|
+
export class ConversationIndexingJob {
|
|
35
|
+
private static instance: ConversationIndexingJob | null = null;
|
|
36
|
+
private timer: NodeJS.Timeout | null = null;
|
|
37
|
+
private isRunning = false;
|
|
38
|
+
private isBatchRunning = false; // Prevent overlapping batches
|
|
39
|
+
private intervalMs: number;
|
|
40
|
+
private projectsBasePath: string;
|
|
41
|
+
private stateManager: IndexingStateManager;
|
|
42
|
+
|
|
43
|
+
private constructor(intervalMs: number = DEFAULT_INTERVAL_MS) {
|
|
44
|
+
this.intervalMs = intervalMs;
|
|
45
|
+
// Use stable projects root from config, not mutable registry
|
|
46
|
+
this.projectsBasePath = join(getTenexBasePath(), "projects");
|
|
47
|
+
this.stateManager = new IndexingStateManager(this.projectsBasePath);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get singleton instance
|
|
52
|
+
*/
|
|
53
|
+
public static getInstance(intervalMs?: number): ConversationIndexingJob {
|
|
54
|
+
if (!ConversationIndexingJob.instance) {
|
|
55
|
+
ConversationIndexingJob.instance = new ConversationIndexingJob(intervalMs);
|
|
56
|
+
}
|
|
57
|
+
return ConversationIndexingJob.instance;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Start the periodic indexing job
|
|
62
|
+
*/
|
|
63
|
+
public start(): void {
|
|
64
|
+
if (this.isRunning) {
|
|
65
|
+
logger.warn("ConversationIndexingJob is already running");
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this.isRunning = true;
|
|
70
|
+
logger.info("Starting ConversationIndexingJob", {
|
|
71
|
+
intervalMs: this.intervalMs,
|
|
72
|
+
intervalMinutes: this.intervalMs / 60000,
|
|
73
|
+
projectsBasePath: this.projectsBasePath,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Run immediately on start
|
|
77
|
+
this.scheduleNextBatch(0);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Schedule the next batch run (self-scheduling to prevent overlaps)
|
|
82
|
+
*/
|
|
83
|
+
private scheduleNextBatch(delayMs: number): void {
|
|
84
|
+
if (!this.isRunning) return;
|
|
85
|
+
|
|
86
|
+
this.timer = setTimeout(async () => {
|
|
87
|
+
try {
|
|
88
|
+
await this.runIndexingBatch();
|
|
89
|
+
} catch (error) {
|
|
90
|
+
logger.error("Indexing batch failed", { error });
|
|
91
|
+
} finally {
|
|
92
|
+
// Schedule next run only after this one completes
|
|
93
|
+
this.scheduleNextBatch(this.intervalMs);
|
|
94
|
+
}
|
|
95
|
+
}, delayMs);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Stop the periodic indexing job
|
|
100
|
+
*/
|
|
101
|
+
public stop(): void {
|
|
102
|
+
if (!this.isRunning) {
|
|
103
|
+
logger.warn("ConversationIndexingJob is not running");
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (this.timer) {
|
|
108
|
+
clearTimeout(this.timer);
|
|
109
|
+
this.timer = null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
this.isRunning = false;
|
|
113
|
+
|
|
114
|
+
// Save state before stopping
|
|
115
|
+
this.stateManager.dispose();
|
|
116
|
+
|
|
117
|
+
logger.info("ConversationIndexingJob stopped");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Run a single indexing batch.
|
|
122
|
+
*
|
|
123
|
+
* Collects all conversations needing re-indexing across all projects,
|
|
124
|
+
* builds RAGDocuments for each, then flushes them via bulkUpsert
|
|
125
|
+
* (mergeInsert). This creates one LanceDB version per chunk of
|
|
126
|
+
* BATCH_SIZE instead of 2N versions (delete+insert per conversation).
|
|
127
|
+
*
|
|
128
|
+
* Failures are isolated per chunk: documents in a successful chunk are
|
|
129
|
+
* marked indexed even when other chunks fail. Only truly failed
|
|
130
|
+
* documents are retried next cycle.
|
|
131
|
+
*/
|
|
132
|
+
private async runIndexingBatch(): Promise<void> {
|
|
133
|
+
// Prevent overlapping batches
|
|
134
|
+
if (this.isBatchRunning) {
|
|
135
|
+
logger.warn("Skipping indexing batch - previous batch still running");
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
this.isBatchRunning = true;
|
|
140
|
+
logger.debug("Running conversation indexing batch");
|
|
141
|
+
|
|
142
|
+
const conversationEmbeddingService = getConversationEmbeddingService();
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
// Ensure the embedding service is initialized
|
|
146
|
+
await conversationEmbeddingService.initialize();
|
|
147
|
+
|
|
148
|
+
let totalChecked = 0;
|
|
149
|
+
let totalSkipped = 0;
|
|
150
|
+
let totalFailed = 0;
|
|
151
|
+
|
|
152
|
+
// Collect all documents that need indexing across all projects
|
|
153
|
+
const pendingDocuments: RAGDocument[] = [];
|
|
154
|
+
// Track which conversations were successfully built (for marking indexed after flush)
|
|
155
|
+
// Indices into pendingDocuments correspond 1:1 with pendingMarkIndexed
|
|
156
|
+
const pendingMarkIndexed: Array<{ projectId: string; conversationId: string }> = [];
|
|
157
|
+
const pendingMarkNoContent: Array<{ projectId: string; conversationId: string }> = [];
|
|
158
|
+
|
|
159
|
+
// Get all projects directly from disk
|
|
160
|
+
const projectIds = listProjectIdsFromDisk(this.projectsBasePath);
|
|
161
|
+
|
|
162
|
+
for (const projectId of projectIds) {
|
|
163
|
+
try {
|
|
164
|
+
const conversationIds = listConversationIdsFromDiskForProject(
|
|
165
|
+
this.projectsBasePath,
|
|
166
|
+
projectId
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
for (const conversationId of conversationIds) {
|
|
170
|
+
totalChecked++;
|
|
171
|
+
|
|
172
|
+
// Check if conversation needs indexing using durable state
|
|
173
|
+
const needsIndexing = this.stateManager.needsIndexing(
|
|
174
|
+
this.projectsBasePath,
|
|
175
|
+
projectId,
|
|
176
|
+
conversationId
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
if (!needsIndexing) {
|
|
180
|
+
totalSkipped++;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Build document without writing
|
|
185
|
+
const result: BuildDocumentResult = conversationEmbeddingService.buildDocument(
|
|
186
|
+
conversationId,
|
|
187
|
+
projectId
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
switch (result.kind) {
|
|
191
|
+
case "ok":
|
|
192
|
+
pendingDocuments.push(result.document);
|
|
193
|
+
pendingMarkIndexed.push({ projectId, conversationId });
|
|
194
|
+
break;
|
|
195
|
+
case "noContent":
|
|
196
|
+
pendingMarkNoContent.push({ projectId, conversationId });
|
|
197
|
+
break;
|
|
198
|
+
case "error":
|
|
199
|
+
// Transient error — leave unmarked so it retries next cycle
|
|
200
|
+
totalFailed++;
|
|
201
|
+
logger.warn("Transient error building document, will retry", {
|
|
202
|
+
conversationId: conversationId.substring(0, 8),
|
|
203
|
+
projectId,
|
|
204
|
+
reason: result.reason,
|
|
205
|
+
});
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
} catch (error) {
|
|
210
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
211
|
+
logger.error("Failed to process project conversations", {
|
|
212
|
+
projectId,
|
|
213
|
+
error: message,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Flush all pending documents via bulkUpsert (per-chunk failure isolation)
|
|
219
|
+
let totalIndexed = 0;
|
|
220
|
+
if (pendingDocuments.length > 0) {
|
|
221
|
+
const ragService = RAGService.getInstance();
|
|
222
|
+
const collectionName = conversationEmbeddingService.getCollectionName();
|
|
223
|
+
const { upsertedCount, failedIndices } = await ragService.bulkUpsert(
|
|
224
|
+
collectionName,
|
|
225
|
+
pendingDocuments
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
totalIndexed = upsertedCount;
|
|
229
|
+
|
|
230
|
+
// Build a set of failed indices for O(1) lookup
|
|
231
|
+
const failedSet = new Set(failedIndices);
|
|
232
|
+
totalFailed += failedIndices.length;
|
|
233
|
+
|
|
234
|
+
// Mark only successfully flushed conversations as indexed
|
|
235
|
+
for (let i = 0; i < pendingMarkIndexed.length; i++) {
|
|
236
|
+
if (failedSet.has(i)) {
|
|
237
|
+
// This document's chunk failed — leave unmarked for retry
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
const { projectId, conversationId } = pendingMarkIndexed[i];
|
|
241
|
+
this.stateManager.markIndexed(
|
|
242
|
+
this.projectsBasePath,
|
|
243
|
+
projectId,
|
|
244
|
+
conversationId
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Mark no-content conversations to avoid re-trying every batch
|
|
250
|
+
for (const { projectId, conversationId } of pendingMarkNoContent) {
|
|
251
|
+
this.stateManager.markIndexed(
|
|
252
|
+
this.projectsBasePath,
|
|
253
|
+
projectId,
|
|
254
|
+
conversationId,
|
|
255
|
+
true
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (totalChecked > 0) {
|
|
260
|
+
logger.info("Conversation indexing batch complete", {
|
|
261
|
+
projectsProcessed: projectIds.length,
|
|
262
|
+
conversationsChecked: totalChecked,
|
|
263
|
+
newlyIndexed: totalIndexed,
|
|
264
|
+
skipped: totalSkipped,
|
|
265
|
+
failed: totalFailed,
|
|
266
|
+
});
|
|
267
|
+
} else {
|
|
268
|
+
logger.debug("No conversations to index in this batch");
|
|
269
|
+
}
|
|
270
|
+
} catch (error) {
|
|
271
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
272
|
+
logger.error("Conversation indexing batch failed", { error: message });
|
|
273
|
+
// Don't throw - we want the job to keep running even if one batch fails
|
|
274
|
+
} finally {
|
|
275
|
+
this.isBatchRunning = false;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Force a full re-index of all conversations
|
|
281
|
+
* This clears the tracking state and re-indexes everything
|
|
282
|
+
*/
|
|
283
|
+
public async forceFullReindex(): Promise<void> {
|
|
284
|
+
logger.info("Forcing full conversation re-index");
|
|
285
|
+
this.stateManager.clearAllState();
|
|
286
|
+
await this.runIndexingBatch();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Get current job status
|
|
291
|
+
*/
|
|
292
|
+
public getStatus(): {
|
|
293
|
+
isRunning: boolean;
|
|
294
|
+
isBatchRunning: boolean;
|
|
295
|
+
intervalMs: number;
|
|
296
|
+
stateStats: ReturnType<IndexingStateManager["getStats"]>;
|
|
297
|
+
} {
|
|
298
|
+
return {
|
|
299
|
+
isRunning: this.isRunning,
|
|
300
|
+
isBatchRunning: this.isBatchRunning,
|
|
301
|
+
intervalMs: this.intervalMs,
|
|
302
|
+
stateStats: this.stateManager.getStats(),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Reset singleton instance (for testing)
|
|
308
|
+
*/
|
|
309
|
+
public static resetInstance(): void {
|
|
310
|
+
if (ConversationIndexingJob.instance) {
|
|
311
|
+
ConversationIndexingJob.instance.stop();
|
|
312
|
+
ConversationIndexingJob.instance = null;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Export lazy getter to avoid eagerly initializing at module load time
|
|
318
|
+
export function getConversationIndexingJob(): ConversationIndexingJob {
|
|
319
|
+
return ConversationIndexingJob.getInstance();
|
|
320
|
+
}
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IndexingStateManager - Durable per-conversation indexing state
|
|
3
|
+
*
|
|
4
|
+
* This manager tracks the indexing state of conversations to determine when
|
|
5
|
+
* they need to be re-indexed. It uses a hash-based approach to detect changes
|
|
6
|
+
* in conversation metadata (title, summary, last_user_message, lastActivity).
|
|
7
|
+
*
|
|
8
|
+
* Key features:
|
|
9
|
+
* - Durable state: persisted to disk in a KV store
|
|
10
|
+
* - Detects metadata changes via hash comparison
|
|
11
|
+
* - Bounded memory: only loads state for active projects
|
|
12
|
+
* - No unbounded growth: old entries naturally expire
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { createHash } from "crypto";
|
|
16
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
17
|
+
import { join } from "path";
|
|
18
|
+
import { logger } from "@/utils/logger";
|
|
19
|
+
import { readLightweightMetadata } from "@/conversations/ConversationDiskReader";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* State for a single conversation
|
|
23
|
+
*/
|
|
24
|
+
interface ConversationIndexState {
|
|
25
|
+
/** Hash of indexed metadata */
|
|
26
|
+
metadataHash: string;
|
|
27
|
+
/** Timestamp of last indexing */
|
|
28
|
+
lastIndexedAt: number;
|
|
29
|
+
/** Last known activity timestamp */
|
|
30
|
+
lastActivity?: number;
|
|
31
|
+
/** Whether this conversation has no indexable content */
|
|
32
|
+
noContent?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* State file structure
|
|
37
|
+
*/
|
|
38
|
+
interface StateFile {
|
|
39
|
+
version: number;
|
|
40
|
+
states: Record<string, ConversationIndexState>; // Key: "projectId:conversationId"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Manager for conversation indexing state with bounded storage
|
|
45
|
+
* Eviction strategy: FIFO based on lastIndexedAt timestamp
|
|
46
|
+
*/
|
|
47
|
+
export class IndexingStateManager {
|
|
48
|
+
private static readonly STATE_VERSION = 1;
|
|
49
|
+
private static readonly MAX_ENTRIES = 10000; // Prevent unbounded growth
|
|
50
|
+
private static readonly EVICTION_BATCH_SIZE = 1000; // Remove oldest 10% when full
|
|
51
|
+
|
|
52
|
+
private stateFilePath: string;
|
|
53
|
+
private states: Map<string, ConversationIndexState> = new Map();
|
|
54
|
+
private dirty = false;
|
|
55
|
+
private saveTimer: NodeJS.Timeout | null = null;
|
|
56
|
+
|
|
57
|
+
constructor(baseDir: string) {
|
|
58
|
+
// Store state file in the projects directory
|
|
59
|
+
this.stateFilePath = join(baseDir, "indexing-state.json");
|
|
60
|
+
this.loadState();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Load state from disk
|
|
65
|
+
*/
|
|
66
|
+
private loadState(): void {
|
|
67
|
+
try {
|
|
68
|
+
if (!existsSync(this.stateFilePath)) {
|
|
69
|
+
logger.debug("No indexing state file found, starting fresh");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const content = readFileSync(this.stateFilePath, "utf-8");
|
|
74
|
+
const stateFile: StateFile = JSON.parse(content);
|
|
75
|
+
|
|
76
|
+
if (stateFile.version !== IndexingStateManager.STATE_VERSION) {
|
|
77
|
+
logger.warn("Indexing state version mismatch, resetting state");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Load into map
|
|
82
|
+
for (const [key, value] of Object.entries(stateFile.states)) {
|
|
83
|
+
this.states.set(key, value);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
logger.info(`Loaded indexing state: ${this.states.size} entries`);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
logger.error("Failed to load indexing state, starting fresh", { error });
|
|
89
|
+
this.states.clear();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Save state to disk (debounced)
|
|
95
|
+
*/
|
|
96
|
+
private scheduleSave(): void {
|
|
97
|
+
if (!this.dirty) {
|
|
98
|
+
this.dirty = true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Clear existing timer
|
|
102
|
+
if (this.saveTimer) {
|
|
103
|
+
clearTimeout(this.saveTimer);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Schedule save in 5 seconds
|
|
107
|
+
this.saveTimer = setTimeout(() => {
|
|
108
|
+
this.saveNow();
|
|
109
|
+
}, 5000);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Immediately save state to disk
|
|
114
|
+
*/
|
|
115
|
+
public saveNow(): void {
|
|
116
|
+
if (!this.dirty) return;
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
// Ensure directory exists
|
|
120
|
+
const dir = join(this.stateFilePath, "..");
|
|
121
|
+
if (!existsSync(dir)) {
|
|
122
|
+
mkdirSync(dir, { recursive: true });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Evict old entries if needed
|
|
126
|
+
if (this.states.size > IndexingStateManager.MAX_ENTRIES) {
|
|
127
|
+
this.evictOldEntries();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Convert map to object
|
|
131
|
+
const statesObj: Record<string, ConversationIndexState> = {};
|
|
132
|
+
for (const [key, value] of this.states.entries()) {
|
|
133
|
+
statesObj[key] = value;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const stateFile: StateFile = {
|
|
137
|
+
version: IndexingStateManager.STATE_VERSION,
|
|
138
|
+
states: statesObj,
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
writeFileSync(this.stateFilePath, JSON.stringify(stateFile, null, 2), "utf-8");
|
|
142
|
+
this.dirty = false;
|
|
143
|
+
|
|
144
|
+
logger.debug(`Saved indexing state: ${this.states.size} entries`);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
logger.error("Failed to save indexing state", { error });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Evict oldest entries when reaching max size
|
|
152
|
+
* Uses FIFO eviction based on lastIndexedAt timestamp (oldest indexed first)
|
|
153
|
+
*/
|
|
154
|
+
private evictOldEntries(): void {
|
|
155
|
+
// Sort by lastIndexedAt (oldest first)
|
|
156
|
+
const entries = Array.from(this.states.entries()).sort(
|
|
157
|
+
(a, b) => a[1].lastIndexedAt - b[1].lastIndexedAt
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
// Remove oldest batch (FIFO)
|
|
161
|
+
const toRemove = entries.slice(0, IndexingStateManager.EVICTION_BATCH_SIZE);
|
|
162
|
+
for (const [key] of toRemove) {
|
|
163
|
+
this.states.delete(key);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
logger.info(
|
|
167
|
+
`Evicted ${toRemove.length} old indexing state entries via FIFO (max: ${IndexingStateManager.MAX_ENTRIES})`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Build a composite key for state lookup
|
|
173
|
+
*/
|
|
174
|
+
private buildKey(projectId: string, conversationId: string): string {
|
|
175
|
+
return `${projectId}:${conversationId}`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Calculate metadata hash for a conversation
|
|
180
|
+
* Hash includes: title, summary, lastUserMessage, lastActivity
|
|
181
|
+
* Returns both hash and lastActivity to avoid double reads
|
|
182
|
+
*/
|
|
183
|
+
private calculateMetadataHash(
|
|
184
|
+
basePath: string,
|
|
185
|
+
projectId: string,
|
|
186
|
+
conversationId: string
|
|
187
|
+
): { hash: string; lastActivity: number } | null {
|
|
188
|
+
try {
|
|
189
|
+
const metadata = readLightweightMetadata(basePath, projectId, conversationId);
|
|
190
|
+
if (!metadata) return null;
|
|
191
|
+
|
|
192
|
+
// Build a stable string representation
|
|
193
|
+
const parts: string[] = [
|
|
194
|
+
metadata.title || "",
|
|
195
|
+
metadata.summary || "",
|
|
196
|
+
metadata.lastUserMessage || "",
|
|
197
|
+
String(metadata.lastActivity || 0),
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
const hashInput = parts.join("|");
|
|
201
|
+
const hash = createHash("sha256").update(hashInput).digest("hex");
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
hash,
|
|
205
|
+
lastActivity: metadata.lastActivity || 0,
|
|
206
|
+
};
|
|
207
|
+
} catch (error) {
|
|
208
|
+
logger.debug(`Failed to calculate metadata hash for ${conversationId.substring(0, 8)}`, {
|
|
209
|
+
error,
|
|
210
|
+
});
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Check if a conversation needs indexing
|
|
217
|
+
* Returns true if:
|
|
218
|
+
* - Never indexed before
|
|
219
|
+
* - Metadata has changed (different hash)
|
|
220
|
+
* - Activity timestamp advanced (only if conversation had content before)
|
|
221
|
+
*/
|
|
222
|
+
public needsIndexing(
|
|
223
|
+
basePath: string,
|
|
224
|
+
projectId: string,
|
|
225
|
+
conversationId: string
|
|
226
|
+
): boolean {
|
|
227
|
+
const key = this.buildKey(projectId, conversationId);
|
|
228
|
+
const currentState = this.states.get(key);
|
|
229
|
+
|
|
230
|
+
// Calculate current metadata hash (includes lastActivity to avoid double read)
|
|
231
|
+
const result = this.calculateMetadataHash(basePath, projectId, conversationId);
|
|
232
|
+
if (!result) {
|
|
233
|
+
// Can't read metadata - skip
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const { hash: currentHash, lastActivity: currentActivity } = result;
|
|
238
|
+
|
|
239
|
+
// Never indexed before
|
|
240
|
+
if (!currentState) {
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// If previously marked as no-content, check if activity changed
|
|
245
|
+
// (which might mean new messages were added)
|
|
246
|
+
if (currentState.noContent) {
|
|
247
|
+
const lastActivity = currentState.lastActivity || 0;
|
|
248
|
+
return currentActivity > lastActivity;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Metadata changed
|
|
252
|
+
if (currentState.metadataHash !== currentHash) {
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Check if activity advanced
|
|
257
|
+
const lastActivity = currentState.lastActivity || 0;
|
|
258
|
+
if (currentActivity > lastActivity) {
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Already indexed and unchanged
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Mark a conversation as indexed
|
|
268
|
+
* @param noContent - Set to true if the conversation has no indexable content
|
|
269
|
+
*/
|
|
270
|
+
public markIndexed(
|
|
271
|
+
basePath: string,
|
|
272
|
+
projectId: string,
|
|
273
|
+
conversationId: string,
|
|
274
|
+
noContent = false
|
|
275
|
+
): void {
|
|
276
|
+
const key = this.buildKey(projectId, conversationId);
|
|
277
|
+
const result = this.calculateMetadataHash(basePath, projectId, conversationId);
|
|
278
|
+
|
|
279
|
+
if (!result) {
|
|
280
|
+
logger.debug(`Cannot mark ${conversationId.substring(0, 8)} as indexed - no metadata`);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const { hash, lastActivity } = result;
|
|
285
|
+
|
|
286
|
+
this.states.set(key, {
|
|
287
|
+
metadataHash: hash,
|
|
288
|
+
lastIndexedAt: Date.now(),
|
|
289
|
+
lastActivity,
|
|
290
|
+
noContent,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
this.scheduleSave();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Clear a conversation's indexing state (forces re-index)
|
|
298
|
+
*/
|
|
299
|
+
public clearState(projectId: string, conversationId: string): void {
|
|
300
|
+
const key = this.buildKey(projectId, conversationId);
|
|
301
|
+
this.states.delete(key);
|
|
302
|
+
this.scheduleSave();
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Clear all state (forces full re-index)
|
|
307
|
+
*/
|
|
308
|
+
public clearAllState(): void {
|
|
309
|
+
this.states.clear();
|
|
310
|
+
this.scheduleSave();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Get statistics about the state
|
|
315
|
+
*/
|
|
316
|
+
public getStats(): {
|
|
317
|
+
totalEntries: number;
|
|
318
|
+
maxEntries: number;
|
|
319
|
+
isDirty: boolean;
|
|
320
|
+
} {
|
|
321
|
+
return {
|
|
322
|
+
totalEntries: this.states.size,
|
|
323
|
+
maxEntries: IndexingStateManager.MAX_ENTRIES,
|
|
324
|
+
isDirty: this.dirty,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Cleanup and save before shutdown
|
|
330
|
+
*/
|
|
331
|
+
public dispose(): void {
|
|
332
|
+
if (this.saveTimer) {
|
|
333
|
+
clearTimeout(this.saveTimer);
|
|
334
|
+
this.saveTimer = null;
|
|
335
|
+
}
|
|
336
|
+
this.saveNow();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conversation Embeddings Module
|
|
3
|
+
*
|
|
4
|
+
* Provides semantic search capabilities for conversations using RAG.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
ConversationEmbeddingService,
|
|
9
|
+
getConversationEmbeddingService,
|
|
10
|
+
type ConversationEmbeddingDocument,
|
|
11
|
+
type SemanticSearchResult,
|
|
12
|
+
type SemanticSearchOptions,
|
|
13
|
+
} from "./ConversationEmbeddingService";
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
ConversationIndexingJob,
|
|
17
|
+
getConversationIndexingJob,
|
|
18
|
+
} from "./ConversationIndexingJob";
|