@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,682 @@
|
|
|
1
|
+
import type { DocumentMetadata, LanceDBResult, LanceDBStoredDocument } from "@/services/rag/rag-utils";
|
|
2
|
+
import { calculateRelevanceScore, mapLanceResultToDocument } from "@/services/rag/rag-utils";
|
|
3
|
+
import { handleError } from "@/utils/error-handler";
|
|
4
|
+
import { logger } from "@/utils/logger";
|
|
5
|
+
import type { Table, VectorQuery } from "@lancedb/lancedb";
|
|
6
|
+
import type { EmbeddingProvider } from "@/services/embedding";
|
|
7
|
+
import type { RAGDatabaseService } from "./RAGDatabaseService";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Document structure for RAG operations
|
|
11
|
+
*/
|
|
12
|
+
export interface RAGDocument {
|
|
13
|
+
id?: string;
|
|
14
|
+
content: string;
|
|
15
|
+
metadata?: DocumentMetadata;
|
|
16
|
+
vector?: Float32Array;
|
|
17
|
+
timestamp?: number;
|
|
18
|
+
source?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Schema definition for LanceDB collection
|
|
23
|
+
*/
|
|
24
|
+
export interface LanceDBSchema {
|
|
25
|
+
id: string;
|
|
26
|
+
content: string;
|
|
27
|
+
vector: string;
|
|
28
|
+
metadata: string;
|
|
29
|
+
timestamp: string;
|
|
30
|
+
source: string;
|
|
31
|
+
[key: string]: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Collection metadata structure
|
|
36
|
+
*/
|
|
37
|
+
export interface RAGCollection {
|
|
38
|
+
name: string;
|
|
39
|
+
schema?: LanceDBSchema;
|
|
40
|
+
created_at: number;
|
|
41
|
+
updated_at: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Query result with relevance score
|
|
46
|
+
*/
|
|
47
|
+
export interface RAGQueryResult {
|
|
48
|
+
document: RAGDocument;
|
|
49
|
+
score: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Result from bulkUpsert with per-chunk failure isolation.
|
|
54
|
+
* Successfully flushed documents are counted; failed document indices
|
|
55
|
+
* are reported so callers can decide which items to mark as complete.
|
|
56
|
+
*/
|
|
57
|
+
export interface BulkUpsertResult {
|
|
58
|
+
/** Number of documents successfully upserted */
|
|
59
|
+
upsertedCount: number;
|
|
60
|
+
/** 0-based indices into the original input array that failed */
|
|
61
|
+
failedIndices: number[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Custom errors for RAG operations
|
|
66
|
+
*/
|
|
67
|
+
export class RAGValidationError extends Error {
|
|
68
|
+
constructor(message: string) {
|
|
69
|
+
super(message);
|
|
70
|
+
this.name = "RAGValidationError";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export class RAGOperationError extends Error {
|
|
75
|
+
constructor(
|
|
76
|
+
message: string,
|
|
77
|
+
public readonly cause?: Error
|
|
78
|
+
) {
|
|
79
|
+
super(message);
|
|
80
|
+
this.name = "RAGOperationError";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Handles RAG CRUD operations
|
|
86
|
+
* Single Responsibility: Business logic for document storage and retrieval
|
|
87
|
+
*/
|
|
88
|
+
export class RAGOperations {
|
|
89
|
+
private static readonly BATCH_SIZE = 100;
|
|
90
|
+
|
|
91
|
+
constructor(
|
|
92
|
+
private readonly dbManager: RAGDatabaseService,
|
|
93
|
+
private readonly embeddingProvider: EmbeddingProvider
|
|
94
|
+
) {}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Create a new collection with vector schema
|
|
98
|
+
*/
|
|
99
|
+
async createCollection(
|
|
100
|
+
name: string,
|
|
101
|
+
customSchema?: Partial<LanceDBSchema>
|
|
102
|
+
): Promise<RAGCollection> {
|
|
103
|
+
// Validate collection name
|
|
104
|
+
this.validateCollectionName(name);
|
|
105
|
+
|
|
106
|
+
// Check if already exists
|
|
107
|
+
const exists = await this.dbManager.tableExists(name);
|
|
108
|
+
if (exists) {
|
|
109
|
+
throw new RAGOperationError(`Collection '${name}' already exists`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const dimensions = await this.embeddingProvider.getDimensions();
|
|
114
|
+
|
|
115
|
+
// Build schema with vector column
|
|
116
|
+
const defaultSchema = {
|
|
117
|
+
id: "string",
|
|
118
|
+
content: "string",
|
|
119
|
+
vector: `vector(${dimensions})`,
|
|
120
|
+
metadata: "string", // JSON string
|
|
121
|
+
timestamp: "int64",
|
|
122
|
+
source: "string",
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const finalSchema = { ...defaultSchema, ...customSchema };
|
|
126
|
+
|
|
127
|
+
// Create table with initial row (required by LanceDB)
|
|
128
|
+
// Use regular array for vector to match document insertion format
|
|
129
|
+
const initialRow = {
|
|
130
|
+
id: "initial",
|
|
131
|
+
content: "",
|
|
132
|
+
vector: Array(dimensions).fill(0),
|
|
133
|
+
metadata: "{}",
|
|
134
|
+
timestamp: Date.now(),
|
|
135
|
+
source: "system",
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const table = await this.dbManager.createTable(name, [initialRow], {
|
|
139
|
+
mode: "overwrite",
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Delete the initial row
|
|
143
|
+
await table.delete("id = 'initial'");
|
|
144
|
+
|
|
145
|
+
logger.info(`Collection '${name}' created with schema`, { schema: finalSchema });
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
name,
|
|
149
|
+
schema: finalSchema,
|
|
150
|
+
created_at: Date.now(),
|
|
151
|
+
updated_at: Date.now(),
|
|
152
|
+
};
|
|
153
|
+
} catch (error) {
|
|
154
|
+
return this.handleRAGError(error, `Failed to create collection '${name}'`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Add documents to a collection with batching
|
|
160
|
+
*/
|
|
161
|
+
async addDocuments(collectionName: string, documents: RAGDocument[]): Promise<void> {
|
|
162
|
+
if (!documents || documents.length === 0) {
|
|
163
|
+
throw new RAGValidationError("Documents array cannot be empty");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const table = await this.dbManager.getTable(collectionName);
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
// Process in batches for efficiency
|
|
170
|
+
for (let i = 0; i < documents.length; i += RAGOperations.BATCH_SIZE) {
|
|
171
|
+
const batch = documents.slice(i, i + RAGOperations.BATCH_SIZE);
|
|
172
|
+
|
|
173
|
+
const processedDocs = await this.processBatch(batch);
|
|
174
|
+
await table.add(processedDocs as unknown as Record<string, unknown>[]);
|
|
175
|
+
|
|
176
|
+
logger.debug(
|
|
177
|
+
`Added batch of ${processedDocs.length} documents to '${collectionName}'`
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
logger.info(
|
|
182
|
+
`Successfully added ${documents.length} documents to collection '${collectionName}'`
|
|
183
|
+
);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
return this.handleRAGError(
|
|
186
|
+
error,
|
|
187
|
+
`Failed to add documents to collection '${collectionName}'`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Process a batch of documents for insertion
|
|
194
|
+
*/
|
|
195
|
+
private async processBatch(documents: RAGDocument[]): Promise<LanceDBStoredDocument[]> {
|
|
196
|
+
return Promise.all(
|
|
197
|
+
documents.map(async (doc) => {
|
|
198
|
+
// Validate document structure
|
|
199
|
+
this.validateDocument(doc);
|
|
200
|
+
|
|
201
|
+
const vector = doc.vector || (await this.embeddingProvider.embed(doc.content));
|
|
202
|
+
|
|
203
|
+
const storedDoc: LanceDBStoredDocument = {
|
|
204
|
+
id: doc.id || this.generateDocumentId(),
|
|
205
|
+
content: doc.content,
|
|
206
|
+
vector: Array.from(vector),
|
|
207
|
+
metadata: JSON.stringify(doc.metadata || {}),
|
|
208
|
+
timestamp: doc.timestamp || Date.now(),
|
|
209
|
+
source: doc.source || "user",
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
return storedDoc;
|
|
213
|
+
})
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Perform semantic search on a collection
|
|
219
|
+
*/
|
|
220
|
+
async performSemanticSearch(
|
|
221
|
+
collectionName: string,
|
|
222
|
+
queryText: string,
|
|
223
|
+
topK = 5
|
|
224
|
+
): Promise<RAGQueryResult[]> {
|
|
225
|
+
return this.performSemanticSearchWithFilter(collectionName, queryText, topK, undefined);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Perform semantic search with optional SQL prefilter
|
|
230
|
+
* The filter is applied BEFORE vector search for proper isolation
|
|
231
|
+
* @param filter SQL-style filter string, applied as prefilter during vector search
|
|
232
|
+
*/
|
|
233
|
+
async performSemanticSearchWithFilter(
|
|
234
|
+
collectionName: string,
|
|
235
|
+
queryText: string,
|
|
236
|
+
topK = 5,
|
|
237
|
+
filter?: string
|
|
238
|
+
): Promise<RAGQueryResult[]> {
|
|
239
|
+
// Validate inputs early
|
|
240
|
+
this.validateSearchInputs(collectionName, queryText, topK);
|
|
241
|
+
|
|
242
|
+
const table = await this.dbManager.getTable(collectionName);
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
// Generate query embedding
|
|
246
|
+
const queryVector = await this.embeddingProvider.embed(queryText);
|
|
247
|
+
|
|
248
|
+
// Perform vector search with optional filter
|
|
249
|
+
const results = await this.executeVectorSearch(table, queryVector, topK, filter);
|
|
250
|
+
|
|
251
|
+
logger.info(
|
|
252
|
+
`Semantic search completed on '${collectionName}': found ${results.length} results`,
|
|
253
|
+
{ filter: filter || "none" }
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
return results;
|
|
257
|
+
} catch (error) {
|
|
258
|
+
return this.handleRAGError(
|
|
259
|
+
error,
|
|
260
|
+
`Failed to perform semantic search on collection '${collectionName}'`
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Delete a document by its ID
|
|
267
|
+
*/
|
|
268
|
+
async deleteDocumentById(collectionName: string, documentId: string): Promise<void> {
|
|
269
|
+
const table = await this.dbManager.getTable(collectionName);
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
// Escape single quotes in the ID for SQL safety
|
|
273
|
+
const escapedId = documentId.replace(/'/g, "''");
|
|
274
|
+
await table.delete(`id = '${escapedId}'`);
|
|
275
|
+
logger.debug(`Deleted document '${documentId}' from collection '${collectionName}'`);
|
|
276
|
+
} catch (error) {
|
|
277
|
+
// Log but don't throw - document might not exist
|
|
278
|
+
logger.debug(`Could not delete document '${documentId}': ${error}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Execute vector search with optional prefilter and transform results
|
|
284
|
+
*/
|
|
285
|
+
private async executeVectorSearch(
|
|
286
|
+
table: Table,
|
|
287
|
+
queryVector: Float32Array,
|
|
288
|
+
topK: number,
|
|
289
|
+
filter?: string
|
|
290
|
+
): Promise<RAGQueryResult[]> {
|
|
291
|
+
const searchQuery = this.createVectorSearchQuery(table, queryVector, topK, filter);
|
|
292
|
+
const results = await this.executeLanceDBQuery(searchQuery);
|
|
293
|
+
return this.transformSearchResults(results);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Create a vector search query with optional SQL prefilter
|
|
298
|
+
* The filter is applied BEFORE vector search (prefilter by default in LanceDB)
|
|
299
|
+
*/
|
|
300
|
+
private createVectorSearchQuery(
|
|
301
|
+
table: Table,
|
|
302
|
+
queryVector: Float32Array,
|
|
303
|
+
topK: number,
|
|
304
|
+
filter?: string
|
|
305
|
+
): VectorQuery {
|
|
306
|
+
logger.debug(`Creating vector search with topK=${topK}, vector_dims=${queryVector.length}, filter=${filter || "none"}`);
|
|
307
|
+
|
|
308
|
+
let query = table.search(Array.from(queryVector)).limit(topK) as VectorQuery;
|
|
309
|
+
|
|
310
|
+
// Apply prefilter if provided - this filters BEFORE vector search
|
|
311
|
+
if (filter) {
|
|
312
|
+
query = query.where(filter) as VectorQuery;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return query;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Execute LanceDB query with fallback approaches
|
|
320
|
+
*/
|
|
321
|
+
private async executeLanceDBQuery(searchQuery: VectorQuery): Promise<LanceDBResult[]> {
|
|
322
|
+
return this.withQueryErrorHandling(async () => {
|
|
323
|
+
const results =
|
|
324
|
+
(await this.tryToArrayQuery(searchQuery)) ??
|
|
325
|
+
(await this.tryExecuteQuery(searchQuery)) ??
|
|
326
|
+
(await this.tryIterateQuery(searchQuery));
|
|
327
|
+
|
|
328
|
+
this.logQueryResults(results);
|
|
329
|
+
return results;
|
|
330
|
+
}, "Vector search execution failed");
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Try executing query using toArray() method
|
|
335
|
+
*/
|
|
336
|
+
private async tryToArrayQuery(searchQuery: VectorQuery): Promise<LanceDBResult[] | null> {
|
|
337
|
+
if (typeof searchQuery.toArray !== "function") return null;
|
|
338
|
+
|
|
339
|
+
const queryResults = await searchQuery.toArray();
|
|
340
|
+
logger.debug(`Query executed with toArray(), got ${queryResults.length} results`);
|
|
341
|
+
return queryResults;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Try executing query using execute() method
|
|
346
|
+
*/
|
|
347
|
+
private async tryExecuteQuery(searchQuery: VectorQuery): Promise<LanceDBResult[] | null> {
|
|
348
|
+
const queryWithExecute = searchQuery as VectorQuery & { execute?: () => Promise<unknown> };
|
|
349
|
+
if (typeof queryWithExecute.execute !== "function") return null;
|
|
350
|
+
|
|
351
|
+
const queryResults = await queryWithExecute.execute();
|
|
352
|
+
logger.debug("Query executed with execute()");
|
|
353
|
+
|
|
354
|
+
if (Array.isArray(queryResults)) {
|
|
355
|
+
return queryResults;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (queryResults) {
|
|
359
|
+
const results: LanceDBResult[] = [];
|
|
360
|
+
for await (const item of queryResults) {
|
|
361
|
+
results.push(item as unknown as LanceDBResult);
|
|
362
|
+
}
|
|
363
|
+
return results;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Try executing query using direct iteration
|
|
371
|
+
*/
|
|
372
|
+
private async tryIterateQuery(searchQuery: VectorQuery): Promise<LanceDBResult[]> {
|
|
373
|
+
logger.debug("Trying direct iteration");
|
|
374
|
+
const results: LanceDBResult[] = [];
|
|
375
|
+
|
|
376
|
+
for await (const item of searchQuery) {
|
|
377
|
+
results.push(item as unknown as LanceDBResult);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return results;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Higher-order function for consistent error handling in query operations
|
|
385
|
+
*/
|
|
386
|
+
private async withQueryErrorHandling<T>(
|
|
387
|
+
operation: () => Promise<T>,
|
|
388
|
+
errorMessage: string
|
|
389
|
+
): Promise<T> {
|
|
390
|
+
try {
|
|
391
|
+
return await operation();
|
|
392
|
+
} catch (error) {
|
|
393
|
+
logger.error(errorMessage, { error });
|
|
394
|
+
throw new Error(`${errorMessage}: ${error}`, { cause: error });
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Centralized error handling for RAG operations
|
|
400
|
+
* Preserves validation/operation errors, wraps others in RAGOperationError
|
|
401
|
+
*/
|
|
402
|
+
private handleRAGError(error: unknown, message: string): never {
|
|
403
|
+
if (error instanceof RAGValidationError || error instanceof RAGOperationError) {
|
|
404
|
+
throw error;
|
|
405
|
+
}
|
|
406
|
+
handleError(error, message, { logLevel: "error" });
|
|
407
|
+
throw new RAGOperationError(message, error as Error);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Log query results for debugging
|
|
412
|
+
*/
|
|
413
|
+
private logQueryResults(results: LanceDBResult[]): void {
|
|
414
|
+
logger.debug(`Vector search collected ${results.length} results`);
|
|
415
|
+
|
|
416
|
+
if (results.length > 0) {
|
|
417
|
+
logger.debug(`First result structure: ${JSON.stringify(Object.keys(results[0]))}`);
|
|
418
|
+
logger.debug("First result sample:", {
|
|
419
|
+
id: results[0].id,
|
|
420
|
+
content_preview: results[0].content?.substring(0, 50),
|
|
421
|
+
has_vector: !!results[0].vector,
|
|
422
|
+
distance: results[0]._distance,
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Transform LanceDB results to RAGQueryResult format
|
|
429
|
+
*/
|
|
430
|
+
private transformSearchResults(results: LanceDBResult[]): RAGQueryResult[] {
|
|
431
|
+
return results.map((result) => this.transformSingleResult(result));
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Transform a single LanceDB result
|
|
436
|
+
*/
|
|
437
|
+
private transformSingleResult(result: LanceDBResult): RAGQueryResult {
|
|
438
|
+
return {
|
|
439
|
+
document: mapLanceResultToDocument(result),
|
|
440
|
+
score: calculateRelevanceScore(result._distance),
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Delete a collection
|
|
446
|
+
*/
|
|
447
|
+
async deleteCollection(name: string): Promise<void> {
|
|
448
|
+
const exists = await this.dbManager.tableExists(name);
|
|
449
|
+
if (!exists) {
|
|
450
|
+
throw new RAGOperationError(`Collection '${name}' does not exist`);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
await this.dbManager.dropTable(name);
|
|
454
|
+
logger.info(`Collection '${name}' deleted successfully`);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* List all collections
|
|
459
|
+
*/
|
|
460
|
+
async listCollections(): Promise<string[]> {
|
|
461
|
+
return this.dbManager.listTables();
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Get collection statistics including document counts by agent.
|
|
466
|
+
* Uses LanceDB's countRows with SQL filtering for efficiency.
|
|
467
|
+
*
|
|
468
|
+
* @param collectionName Name of the collection to query
|
|
469
|
+
* @param agentPubkey Optional agent pubkey to count their contributions
|
|
470
|
+
* @returns Object with total count and optional agent-specific count
|
|
471
|
+
*/
|
|
472
|
+
/**
|
|
473
|
+
* Escape a string for use in SQL LIKE pattern.
|
|
474
|
+
* Escapes: single quotes ('), double quotes ("), backslashes (\), and LIKE wildcards (%, _).
|
|
475
|
+
*
|
|
476
|
+
* IMPORTANT: DataFusion (used by LanceDB) has NO default escape character.
|
|
477
|
+
* The backslash escapes here only work when paired with ESCAPE '\\' clause.
|
|
478
|
+
* See: https://github.com/apache/datafusion/issues/13291
|
|
479
|
+
*
|
|
480
|
+
* Note: Agent pubkeys are hex strings (0-9, a-f) so most escaping isn't strictly needed,
|
|
481
|
+
* but we escape properly for defense-in-depth and to handle any future metadata fields.
|
|
482
|
+
*/
|
|
483
|
+
private escapeSqlLikeValue(value: string): string {
|
|
484
|
+
return value
|
|
485
|
+
.replace(/\\/g, "\\\\") // Escape backslashes first
|
|
486
|
+
.replace(/'/g, "''") // SQL standard: escape single quote by doubling
|
|
487
|
+
.replace(/"/g, '\\"') // Escape double quotes
|
|
488
|
+
.replace(/%/g, "\\%") // Escape LIKE wildcard %
|
|
489
|
+
.replace(/_/g, "\\_"); // Escape LIKE wildcard _
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
async getCollectionStats(
|
|
493
|
+
collectionName: string,
|
|
494
|
+
agentPubkey?: string
|
|
495
|
+
): Promise<{ totalCount: number; agentCount?: number }> {
|
|
496
|
+
const table = await this.dbManager.getTable(collectionName);
|
|
497
|
+
|
|
498
|
+
try {
|
|
499
|
+
// Get total document count
|
|
500
|
+
const totalCount = await table.countRows();
|
|
501
|
+
|
|
502
|
+
// If agentPubkey provided, count documents attributed to this agent
|
|
503
|
+
// The metadata field is stored as JSON string, so we use LIKE for matching
|
|
504
|
+
let agentCount: number | undefined;
|
|
505
|
+
if (agentPubkey) {
|
|
506
|
+
// SQL filter for JSON string field containing agent_pubkey
|
|
507
|
+
// Format: metadata LIKE '%"agent_pubkey":"<pubkey>"%' ESCAPE '\\'
|
|
508
|
+
// ESCAPE clause is required because DataFusion has no default escape character
|
|
509
|
+
const escapedPubkey = this.escapeSqlLikeValue(agentPubkey);
|
|
510
|
+
const filter = `metadata LIKE '%"agent_pubkey":"${escapedPubkey}"%' ESCAPE '\\\\'`;
|
|
511
|
+
agentCount = await table.countRows(filter);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return { totalCount, agentCount };
|
|
515
|
+
} catch (error) {
|
|
516
|
+
return this.handleRAGError(
|
|
517
|
+
error,
|
|
518
|
+
`Failed to get stats for collection '${collectionName}'`
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Get statistics for all collections with agent attribution.
|
|
525
|
+
* Efficiently retrieves counts for all collections in parallel.
|
|
526
|
+
*
|
|
527
|
+
* @param agentPubkey Agent pubkey to count contributions for
|
|
528
|
+
* @returns Array of collection stats with agent and total counts
|
|
529
|
+
*/
|
|
530
|
+
async getAllCollectionStats(
|
|
531
|
+
agentPubkey: string
|
|
532
|
+
): Promise<Array<{ name: string; agentDocCount: number; totalDocCount: number }>> {
|
|
533
|
+
const collections = await this.listCollections();
|
|
534
|
+
|
|
535
|
+
const stats = await Promise.all(
|
|
536
|
+
collections.map(async (name) => {
|
|
537
|
+
try {
|
|
538
|
+
const { totalCount, agentCount } = await this.getCollectionStats(name, agentPubkey);
|
|
539
|
+
return {
|
|
540
|
+
name,
|
|
541
|
+
agentDocCount: agentCount ?? 0,
|
|
542
|
+
totalDocCount: totalCount,
|
|
543
|
+
};
|
|
544
|
+
} catch (error) {
|
|
545
|
+
// Log but don't fail - return zero counts for problematic collections
|
|
546
|
+
logger.warn(`Failed to get stats for collection '${name}':`, error);
|
|
547
|
+
return {
|
|
548
|
+
name,
|
|
549
|
+
agentDocCount: 0,
|
|
550
|
+
totalDocCount: 0,
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
})
|
|
554
|
+
);
|
|
555
|
+
|
|
556
|
+
return stats;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Bulk upsert documents into a collection using LanceDB mergeInsert.
|
|
561
|
+
*
|
|
562
|
+
* Uses the `id` column as the merge key:
|
|
563
|
+
* - Existing rows with matching `id` are updated in-place
|
|
564
|
+
* - New rows (no matching `id`) are inserted
|
|
565
|
+
*
|
|
566
|
+
* This creates one LanceDB version per chunk of BATCH_SIZE instead of
|
|
567
|
+
* 2N versions (1 delete + 1 insert per document) with the old approach.
|
|
568
|
+
*
|
|
569
|
+
* Failures are isolated per chunk: if one chunk throws, the remaining
|
|
570
|
+
* chunks still proceed. The returned `BulkUpsertResult.failedIndices`
|
|
571
|
+
* tells the caller exactly which input documents failed so only
|
|
572
|
+
* successful ones are marked as complete.
|
|
573
|
+
*/
|
|
574
|
+
async bulkUpsert(collectionName: string, documents: RAGDocument[]): Promise<BulkUpsertResult> {
|
|
575
|
+
if (!documents || documents.length === 0) {
|
|
576
|
+
return { upsertedCount: 0, failedIndices: [] };
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const table = await this.dbManager.getTable(collectionName);
|
|
580
|
+
|
|
581
|
+
let totalUpserted = 0;
|
|
582
|
+
const failedIndices: number[] = [];
|
|
583
|
+
|
|
584
|
+
// Process in batches for embedding generation
|
|
585
|
+
for (let i = 0; i < documents.length; i += RAGOperations.BATCH_SIZE) {
|
|
586
|
+
const chunkEnd = Math.min(i + RAGOperations.BATCH_SIZE, documents.length);
|
|
587
|
+
const batch = documents.slice(i, chunkEnd);
|
|
588
|
+
|
|
589
|
+
try {
|
|
590
|
+
const processedDocs = await this.processBatch(batch);
|
|
591
|
+
|
|
592
|
+
// Use mergeInsert with `id` as merge key for atomic upsert
|
|
593
|
+
const result = await table
|
|
594
|
+
.mergeInsert("id")
|
|
595
|
+
.whenMatchedUpdateAll()
|
|
596
|
+
.whenNotMatchedInsertAll()
|
|
597
|
+
.execute(processedDocs as unknown as Record<string, unknown>[]);
|
|
598
|
+
|
|
599
|
+
totalUpserted += result.numInsertedRows + result.numUpdatedRows;
|
|
600
|
+
|
|
601
|
+
logger.debug(
|
|
602
|
+
`Bulk upsert batch: ${processedDocs.length} docs → ${result.numInsertedRows} inserted, ${result.numUpdatedRows} updated in '${collectionName}'`
|
|
603
|
+
);
|
|
604
|
+
} catch (error) {
|
|
605
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
606
|
+
logger.error(`Bulk upsert chunk failed (indices ${i}..${chunkEnd - 1})`, {
|
|
607
|
+
collectionName,
|
|
608
|
+
chunkSize: batch.length,
|
|
609
|
+
error: message,
|
|
610
|
+
});
|
|
611
|
+
// Record every index in this failed chunk
|
|
612
|
+
for (let idx = i; idx < chunkEnd; idx++) {
|
|
613
|
+
failedIndices.push(idx);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
logger.info(
|
|
619
|
+
`Bulk upsert complete: ${totalUpserted} upserted, ${failedIndices.length} failed in '${collectionName}'`
|
|
620
|
+
);
|
|
621
|
+
|
|
622
|
+
return { upsertedCount: totalUpserted, failedIndices };
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Generate a unique document ID
|
|
627
|
+
*/
|
|
628
|
+
private generateDocumentId(): string {
|
|
629
|
+
return `doc_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Validate collection name format
|
|
634
|
+
*/
|
|
635
|
+
private validateCollectionName(name: string): void {
|
|
636
|
+
if (!name || typeof name !== "string") {
|
|
637
|
+
throw new RAGValidationError("Collection name must be a non-empty string");
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if (!/^[a-zA-Z0-9_]+$/.test(name)) {
|
|
641
|
+
throw new RAGValidationError(
|
|
642
|
+
"Collection name must be alphanumeric with underscores only"
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (name.length > 64) {
|
|
647
|
+
throw new RAGValidationError("Collection name must be 64 characters or less");
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Validate search input parameters
|
|
653
|
+
*/
|
|
654
|
+
private validateSearchInputs(collectionName: string, queryText: string, topK: number): void {
|
|
655
|
+
this.validateCollectionName(collectionName);
|
|
656
|
+
|
|
657
|
+
if (!queryText || queryText.trim().length === 0) {
|
|
658
|
+
throw new RAGValidationError("Query text cannot be empty");
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
if (!Number.isInteger(topK) || topK < 1 || topK > 100) {
|
|
662
|
+
throw new RAGValidationError("topK must be an integer between 1 and 100");
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Validate document structure
|
|
668
|
+
*/
|
|
669
|
+
private validateDocument(doc: RAGDocument): void {
|
|
670
|
+
if (!doc.content || doc.content.trim().length === 0) {
|
|
671
|
+
throw new RAGValidationError("Document content cannot be empty");
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
if (doc.id && typeof doc.id !== "string") {
|
|
675
|
+
throw new RAGValidationError("Document ID must be a string");
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
if (doc.metadata && typeof doc.metadata !== "object") {
|
|
679
|
+
throw new RAGValidationError("Document metadata must be an object");
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|