@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,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SearchEngine - Pure search logic for conversation index.
|
|
3
|
+
*
|
|
4
|
+
* Handles:
|
|
5
|
+
* - Agent filtering (conversation must have at least one matching agent)
|
|
6
|
+
* - Date filtering (lastMessageAt >= since)
|
|
7
|
+
* - Case-insensitive text matching on message content
|
|
8
|
+
* - Snippet extraction for matches
|
|
9
|
+
* - Sorting by lastActivity descending
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type {
|
|
13
|
+
ConversationIndex,
|
|
14
|
+
ConversationIndexEntry,
|
|
15
|
+
MessageMatch,
|
|
16
|
+
SearchQuery,
|
|
17
|
+
SearchResult,
|
|
18
|
+
} from "./types";
|
|
19
|
+
import { extractSnippet } from "./SnippetExtractor";
|
|
20
|
+
import { getEffectiveSinceTimestamp } from "./QueryParser";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Check if a conversation matches the agent filter.
|
|
24
|
+
* Returns true if no filter is specified or if at least one agent EXACTLY matches.
|
|
25
|
+
* Uses case-insensitive exact matching for agent slugs/pubkeys.
|
|
26
|
+
*/
|
|
27
|
+
function matchesAgentFilter(conversation: ConversationIndexEntry, agentFilter?: string[]): boolean {
|
|
28
|
+
if (!agentFilter || agentFilter.length === 0) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Convert agent filter to lowercase for case-insensitive exact matching
|
|
33
|
+
const filterLower = new Set(agentFilter.map((a) => a.toLowerCase().trim()));
|
|
34
|
+
|
|
35
|
+
// Check if any conversation agent exactly matches any filter agent
|
|
36
|
+
return conversation.agents.some((agent) => {
|
|
37
|
+
const agentLower = agent.toLowerCase().trim();
|
|
38
|
+
return filterLower.has(agentLower);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check if a conversation matches the date filter.
|
|
44
|
+
* Returns true if no filter is specified or if lastMessageAt >= since.
|
|
45
|
+
*/
|
|
46
|
+
function matchesDateFilter(conversation: ConversationIndexEntry, sinceTimestamp?: number): boolean {
|
|
47
|
+
if (sinceTimestamp === undefined) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const lastMessageAt = conversation.lastMessageAt ?? 0;
|
|
52
|
+
return lastMessageAt >= sinceTimestamp;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Find matching messages in a conversation and extract snippets.
|
|
57
|
+
*/
|
|
58
|
+
function findMatchingMessages(
|
|
59
|
+
conversation: ConversationIndexEntry,
|
|
60
|
+
searchText: string,
|
|
61
|
+
maxMatches: number = 3
|
|
62
|
+
): MessageMatch[] {
|
|
63
|
+
const matches: MessageMatch[] = [];
|
|
64
|
+
const searchLower = searchText.toLowerCase();
|
|
65
|
+
|
|
66
|
+
for (const message of conversation.messages) {
|
|
67
|
+
if (matches.length >= maxMatches) break;
|
|
68
|
+
|
|
69
|
+
const contentLower = message.content.toLowerCase();
|
|
70
|
+
if (contentLower.includes(searchLower)) {
|
|
71
|
+
const snippetResult = extractSnippet(message.content, searchText);
|
|
72
|
+
if (snippetResult) {
|
|
73
|
+
matches.push({
|
|
74
|
+
messageId: message.messageId,
|
|
75
|
+
snippet: snippetResult.snippet,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return matches;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Search the conversation index.
|
|
86
|
+
*
|
|
87
|
+
* @param query - Parsed search query with text and filters
|
|
88
|
+
* @param index - The conversation index to search
|
|
89
|
+
* @param limit - Maximum number of results to return
|
|
90
|
+
* @returns Array of search results sorted by lastActivity descending
|
|
91
|
+
*/
|
|
92
|
+
export function search(
|
|
93
|
+
query: SearchQuery,
|
|
94
|
+
index: ConversationIndex,
|
|
95
|
+
limit: number = 20
|
|
96
|
+
): SearchResult[] {
|
|
97
|
+
const results: SearchResult[] = [];
|
|
98
|
+
const sinceTimestamp = getEffectiveSinceTimestamp(query.filters);
|
|
99
|
+
|
|
100
|
+
for (const conversation of index.conversations) {
|
|
101
|
+
// Apply filters
|
|
102
|
+
if (!matchesAgentFilter(conversation, query.filters.agents)) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!matchesDateFilter(conversation, sinceTimestamp)) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Search for text matches
|
|
111
|
+
const matches = findMatchingMessages(conversation, query.text);
|
|
112
|
+
|
|
113
|
+
// Only include conversations with at least one match
|
|
114
|
+
if (matches.length === 0) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
results.push({
|
|
119
|
+
conversationId: conversation.conversationId,
|
|
120
|
+
title: conversation.title,
|
|
121
|
+
messageCount: conversation.messageCount,
|
|
122
|
+
createdAt: conversation.messages[0]?.timestamp,
|
|
123
|
+
lastActivity: conversation.lastMessageAt,
|
|
124
|
+
matches,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Sort by lastActivity descending (most recent first)
|
|
129
|
+
results.sort((a, b) => {
|
|
130
|
+
const aTime = a.lastActivity ?? 0;
|
|
131
|
+
const bTime = b.lastActivity ?? 0;
|
|
132
|
+
return bTime - aTime;
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Apply limit
|
|
136
|
+
return results.slice(0, limit);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Search with title-only fallback for backward compatibility.
|
|
141
|
+
* This searches only by title, not message content.
|
|
142
|
+
*/
|
|
143
|
+
export function searchByTitleOnly(
|
|
144
|
+
query: string,
|
|
145
|
+
index: ConversationIndex,
|
|
146
|
+
limit: number = 20
|
|
147
|
+
): SearchResult[] {
|
|
148
|
+
const results: SearchResult[] = [];
|
|
149
|
+
const queryLower = query.toLowerCase();
|
|
150
|
+
|
|
151
|
+
for (const conversation of index.conversations) {
|
|
152
|
+
if (conversation.title && conversation.title.toLowerCase().includes(queryLower)) {
|
|
153
|
+
results.push({
|
|
154
|
+
conversationId: conversation.conversationId,
|
|
155
|
+
title: conversation.title,
|
|
156
|
+
messageCount: conversation.messageCount,
|
|
157
|
+
createdAt: conversation.messages[0]?.timestamp,
|
|
158
|
+
lastActivity: conversation.lastMessageAt,
|
|
159
|
+
matches: [{
|
|
160
|
+
messageId: "title",
|
|
161
|
+
snippet: conversation.title,
|
|
162
|
+
}],
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Sort by lastActivity descending
|
|
168
|
+
results.sort((a, b) => {
|
|
169
|
+
const aTime = a.lastActivity ?? 0;
|
|
170
|
+
const bTime = b.lastActivity ?? 0;
|
|
171
|
+
return bTime - aTime;
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
return results.slice(0, limit);
|
|
175
|
+
}
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SnippetExtractor - Pure utility for extracting search result snippets.
|
|
3
|
+
*
|
|
4
|
+
* Extracts snippets with 50-75 character TOTAL length (including matched text),
|
|
5
|
+
* finding word boundaries for cleaner snippets.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Maximum total snippet length (50-75 char target range) */
|
|
9
|
+
const MAX_SNIPPET_LENGTH = 75;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Result of snippet extraction.
|
|
13
|
+
*/
|
|
14
|
+
export interface SnippetResult {
|
|
15
|
+
/** The full snippet including context */
|
|
16
|
+
snippet: string;
|
|
17
|
+
/** The actual matched text */
|
|
18
|
+
matchedText: string;
|
|
19
|
+
/** Start index of match in original content */
|
|
20
|
+
matchStart: number;
|
|
21
|
+
/** End index of match in original content */
|
|
22
|
+
matchEnd: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Find the nearest word boundary before the given index.
|
|
27
|
+
* Returns the index of the character after the boundary (space, punctuation, etc.)
|
|
28
|
+
*/
|
|
29
|
+
function findWordBoundaryBefore(text: string, index: number, minIndex: number): number {
|
|
30
|
+
if (index <= minIndex) return minIndex;
|
|
31
|
+
|
|
32
|
+
// Look for a space or punctuation within a reasonable range
|
|
33
|
+
let pos = index;
|
|
34
|
+
const lookbackLimit = Math.max(minIndex, index - 10); // Don't look too far back
|
|
35
|
+
|
|
36
|
+
while (pos > lookbackLimit) {
|
|
37
|
+
const char = text[pos - 1];
|
|
38
|
+
if (char === " " || char === "\n" || char === "\t") {
|
|
39
|
+
return pos; // Return position after the whitespace
|
|
40
|
+
}
|
|
41
|
+
if (char === "." || char === "," || char === ";" || char === ":" || char === "!" || char === "?") {
|
|
42
|
+
return pos; // Return position after punctuation
|
|
43
|
+
}
|
|
44
|
+
pos--;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// No clean boundary found, use the calculated index
|
|
48
|
+
return Math.max(minIndex, index);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Find the nearest word boundary after the given index.
|
|
53
|
+
* Returns the index of the boundary character.
|
|
54
|
+
*/
|
|
55
|
+
function findWordBoundaryAfter(text: string, index: number, maxIndex: number): number {
|
|
56
|
+
if (index >= maxIndex) return maxIndex;
|
|
57
|
+
|
|
58
|
+
// Look for a space or punctuation within a reasonable range
|
|
59
|
+
let pos = index;
|
|
60
|
+
const lookaheadLimit = Math.min(maxIndex, index + 10); // Don't look too far ahead
|
|
61
|
+
|
|
62
|
+
while (pos < lookaheadLimit) {
|
|
63
|
+
const char = text[pos];
|
|
64
|
+
if (char === " " || char === "\n" || char === "\t") {
|
|
65
|
+
return pos; // Return position of the whitespace
|
|
66
|
+
}
|
|
67
|
+
if (char === "." || char === "," || char === ";" || char === ":" || char === "?") {
|
|
68
|
+
return pos + 1; // Include the punctuation
|
|
69
|
+
}
|
|
70
|
+
if (char === "!") {
|
|
71
|
+
return pos + 1; // Include the punctuation
|
|
72
|
+
}
|
|
73
|
+
pos++;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// No clean boundary found, use the calculated index
|
|
77
|
+
return Math.min(maxIndex, index);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Extract a snippet from content around a search match.
|
|
82
|
+
* Ensures TOTAL snippet length is between 50-75 characters.
|
|
83
|
+
*
|
|
84
|
+
* @param content - The full message content
|
|
85
|
+
* @param searchText - The text that was searched for
|
|
86
|
+
* @returns SnippetResult if match found, null otherwise
|
|
87
|
+
*/
|
|
88
|
+
export function extractSnippet(content: string, searchText: string): SnippetResult | null {
|
|
89
|
+
if (!content || !searchText) return null;
|
|
90
|
+
|
|
91
|
+
// Case-insensitive search
|
|
92
|
+
const contentLower = content.toLowerCase();
|
|
93
|
+
const searchLower = searchText.toLowerCase();
|
|
94
|
+
|
|
95
|
+
const matchStart = contentLower.indexOf(searchLower);
|
|
96
|
+
if (matchStart === -1) return null;
|
|
97
|
+
|
|
98
|
+
const matchEnd = matchStart + searchText.length;
|
|
99
|
+
const matchedText = content.substring(matchStart, matchEnd);
|
|
100
|
+
const matchLength = matchedText.length;
|
|
101
|
+
|
|
102
|
+
// If the match is exactly MAX_SNIPPET_LENGTH, return it as-is (no truncation needed)
|
|
103
|
+
if (matchLength === MAX_SNIPPET_LENGTH) {
|
|
104
|
+
return {
|
|
105
|
+
snippet: matchedText,
|
|
106
|
+
matchedText,
|
|
107
|
+
matchStart,
|
|
108
|
+
matchEnd,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// If the match exceeds MAX_SNIPPET_LENGTH, truncate to 72 chars + "..." = 75 total
|
|
113
|
+
if (matchLength > MAX_SNIPPET_LENGTH) {
|
|
114
|
+
const truncateLength = MAX_SNIPPET_LENGTH - 3;
|
|
115
|
+
const truncated = matchedText.substring(0, truncateLength);
|
|
116
|
+
return {
|
|
117
|
+
snippet: truncated + "...",
|
|
118
|
+
matchedText: truncated,
|
|
119
|
+
matchStart,
|
|
120
|
+
matchEnd: matchStart + truncated.length,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Calculate available space for context (total snippet should be 50-75 chars)
|
|
125
|
+
// Target: use up to MAX_SNIPPET_LENGTH total, at least MIN_SNIPPET_LENGTH if possible
|
|
126
|
+
const availableContext = MAX_SNIPPET_LENGTH - matchLength;
|
|
127
|
+
const contextPerSide = Math.floor(availableContext / 2);
|
|
128
|
+
|
|
129
|
+
// Calculate ideal context boundaries
|
|
130
|
+
let contextStart = Math.max(0, matchStart - contextPerSide);
|
|
131
|
+
let contextEnd = Math.min(content.length, matchEnd + contextPerSide);
|
|
132
|
+
|
|
133
|
+
// Adjust to word boundaries (but stay within our budget)
|
|
134
|
+
if (contextStart > 0) {
|
|
135
|
+
const adjustedStart = findWordBoundaryBefore(content, contextStart, Math.max(0, matchStart - contextPerSide - 5));
|
|
136
|
+
// Only use adjusted start if it doesn't blow our budget
|
|
137
|
+
if (matchEnd - adjustedStart + (contextEnd - matchEnd) <= MAX_SNIPPET_LENGTH) {
|
|
138
|
+
contextStart = adjustedStart;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (contextEnd < content.length) {
|
|
142
|
+
const adjustedEnd = findWordBoundaryAfter(content, contextEnd, Math.min(content.length, matchEnd + contextPerSide + 5));
|
|
143
|
+
// Only use adjusted end if it doesn't blow our budget
|
|
144
|
+
if (adjustedEnd - contextStart <= MAX_SNIPPET_LENGTH) {
|
|
145
|
+
contextEnd = adjustedEnd;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Final enforcement: hard cap at MAX_SNIPPET_LENGTH
|
|
150
|
+
if (contextEnd - contextStart > MAX_SNIPPET_LENGTH) {
|
|
151
|
+
// Trim from whichever side has more context, prioritizing keeping the match centered
|
|
152
|
+
const beforeMatch = matchStart - contextStart;
|
|
153
|
+
const afterMatch = contextEnd - matchEnd;
|
|
154
|
+
|
|
155
|
+
if (beforeMatch > afterMatch) {
|
|
156
|
+
// Trim from start
|
|
157
|
+
contextStart = contextEnd - MAX_SNIPPET_LENGTH;
|
|
158
|
+
} else {
|
|
159
|
+
// Trim from end
|
|
160
|
+
contextEnd = contextStart + MAX_SNIPPET_LENGTH;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Ensure we don't cut into the match
|
|
165
|
+
if (contextStart > matchStart) contextStart = matchStart;
|
|
166
|
+
if (contextEnd < matchEnd) contextEnd = matchEnd;
|
|
167
|
+
|
|
168
|
+
// Build snippet
|
|
169
|
+
let snippet = content.substring(contextStart, contextEnd);
|
|
170
|
+
|
|
171
|
+
// Normalize whitespace
|
|
172
|
+
snippet = snippet.replace(/\s+/g, " ").trim();
|
|
173
|
+
|
|
174
|
+
// Add ellipsis if truncated (but count them in length)
|
|
175
|
+
const needsPrefixEllipsis = contextStart > 0;
|
|
176
|
+
const needsSuffixEllipsis = contextEnd < content.length;
|
|
177
|
+
|
|
178
|
+
// Adjust for ellipsis length (3 chars each)
|
|
179
|
+
const ellipsisLength = (needsPrefixEllipsis ? 3 : 0) + (needsSuffixEllipsis ? 3 : 0);
|
|
180
|
+
if (snippet.length + ellipsisLength > MAX_SNIPPET_LENGTH) {
|
|
181
|
+
// Trim snippet to make room for ellipsis
|
|
182
|
+
const targetLength = MAX_SNIPPET_LENGTH - ellipsisLength;
|
|
183
|
+
if (snippet.length > targetLength) {
|
|
184
|
+
// Trim evenly from both sides if possible
|
|
185
|
+
const excess = snippet.length - targetLength;
|
|
186
|
+
const trimStart = Math.floor(excess / 2);
|
|
187
|
+
const trimEnd = excess - trimStart;
|
|
188
|
+
snippet = snippet.substring(trimStart, snippet.length - trimEnd);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (needsPrefixEllipsis) {
|
|
193
|
+
snippet = "..." + snippet;
|
|
194
|
+
}
|
|
195
|
+
if (needsSuffixEllipsis) {
|
|
196
|
+
snippet = snippet + "...";
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
snippet,
|
|
201
|
+
matchedText,
|
|
202
|
+
matchStart,
|
|
203
|
+
matchEnd,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Extract a snippet from content at a specific match position.
|
|
209
|
+
* Used by extractAllSnippets to get snippets with proper context.
|
|
210
|
+
*
|
|
211
|
+
* @param content - The full message content
|
|
212
|
+
* @param matchStart - Start index of the match in the original content
|
|
213
|
+
* @param matchLength - Length of the matched text
|
|
214
|
+
* @returns SnippetResult
|
|
215
|
+
*/
|
|
216
|
+
function extractSnippetAtPosition(content: string, matchStart: number, matchLength: number): SnippetResult {
|
|
217
|
+
const matchEnd = matchStart + matchLength;
|
|
218
|
+
const matchedText = content.substring(matchStart, matchEnd);
|
|
219
|
+
|
|
220
|
+
// If the match is exactly MAX_SNIPPET_LENGTH, return it as-is (no truncation needed)
|
|
221
|
+
if (matchLength === MAX_SNIPPET_LENGTH) {
|
|
222
|
+
return {
|
|
223
|
+
snippet: matchedText,
|
|
224
|
+
matchedText,
|
|
225
|
+
matchStart,
|
|
226
|
+
matchEnd,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// If the match exceeds MAX_SNIPPET_LENGTH, truncate to 72 chars + "..." = 75 total
|
|
231
|
+
if (matchLength > MAX_SNIPPET_LENGTH) {
|
|
232
|
+
const truncateLength = MAX_SNIPPET_LENGTH - 3;
|
|
233
|
+
const truncated = matchedText.substring(0, truncateLength);
|
|
234
|
+
return {
|
|
235
|
+
snippet: truncated + "...",
|
|
236
|
+
matchedText: truncated,
|
|
237
|
+
matchStart,
|
|
238
|
+
matchEnd: matchStart + truncated.length,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Calculate available space for context
|
|
243
|
+
const availableContext = MAX_SNIPPET_LENGTH - matchLength;
|
|
244
|
+
const contextPerSide = Math.floor(availableContext / 2);
|
|
245
|
+
|
|
246
|
+
// Calculate ideal context boundaries
|
|
247
|
+
let contextStart = Math.max(0, matchStart - contextPerSide);
|
|
248
|
+
let contextEnd = Math.min(content.length, matchEnd + contextPerSide);
|
|
249
|
+
|
|
250
|
+
// Adjust to word boundaries (but stay within our budget)
|
|
251
|
+
if (contextStart > 0) {
|
|
252
|
+
const adjustedStart = findWordBoundaryBefore(content, contextStart, Math.max(0, matchStart - contextPerSide - 5));
|
|
253
|
+
if (matchEnd - adjustedStart + (contextEnd - matchEnd) <= MAX_SNIPPET_LENGTH) {
|
|
254
|
+
contextStart = adjustedStart;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (contextEnd < content.length) {
|
|
258
|
+
const adjustedEnd = findWordBoundaryAfter(content, contextEnd, Math.min(content.length, matchEnd + contextPerSide + 5));
|
|
259
|
+
if (adjustedEnd - contextStart <= MAX_SNIPPET_LENGTH) {
|
|
260
|
+
contextEnd = adjustedEnd;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Final enforcement: hard cap at MAX_SNIPPET_LENGTH
|
|
265
|
+
if (contextEnd - contextStart > MAX_SNIPPET_LENGTH) {
|
|
266
|
+
const beforeMatch = matchStart - contextStart;
|
|
267
|
+
const afterMatch = contextEnd - matchEnd;
|
|
268
|
+
|
|
269
|
+
if (beforeMatch > afterMatch) {
|
|
270
|
+
contextStart = contextEnd - MAX_SNIPPET_LENGTH;
|
|
271
|
+
} else {
|
|
272
|
+
contextEnd = contextStart + MAX_SNIPPET_LENGTH;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Ensure we don't cut into the match
|
|
277
|
+
if (contextStart > matchStart) contextStart = matchStart;
|
|
278
|
+
if (contextEnd < matchEnd) contextEnd = matchEnd;
|
|
279
|
+
|
|
280
|
+
// Build snippet
|
|
281
|
+
let snippet = content.substring(contextStart, contextEnd);
|
|
282
|
+
snippet = snippet.replace(/\s+/g, " ").trim();
|
|
283
|
+
|
|
284
|
+
const needsPrefixEllipsis = contextStart > 0;
|
|
285
|
+
const needsSuffixEllipsis = contextEnd < content.length;
|
|
286
|
+
|
|
287
|
+
const ellipsisLength = (needsPrefixEllipsis ? 3 : 0) + (needsSuffixEllipsis ? 3 : 0);
|
|
288
|
+
if (snippet.length + ellipsisLength > MAX_SNIPPET_LENGTH) {
|
|
289
|
+
const targetLength = MAX_SNIPPET_LENGTH - ellipsisLength;
|
|
290
|
+
if (snippet.length > targetLength) {
|
|
291
|
+
const excess = snippet.length - targetLength;
|
|
292
|
+
const trimStart = Math.floor(excess / 2);
|
|
293
|
+
const trimEnd = excess - trimStart;
|
|
294
|
+
snippet = snippet.substring(trimStart, snippet.length - trimEnd);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (needsPrefixEllipsis) {
|
|
299
|
+
snippet = "..." + snippet;
|
|
300
|
+
}
|
|
301
|
+
if (needsSuffixEllipsis) {
|
|
302
|
+
snippet = snippet + "...";
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
snippet,
|
|
307
|
+
matchedText,
|
|
308
|
+
matchStart,
|
|
309
|
+
matchEnd,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Extract all snippets from content for a search term.
|
|
315
|
+
* Computes snippets against full content with proper offsets.
|
|
316
|
+
*
|
|
317
|
+
* @param content - The full message content
|
|
318
|
+
* @param searchText - The text to search for
|
|
319
|
+
* @param maxSnippets - Maximum number of snippets to return (default: 3)
|
|
320
|
+
*/
|
|
321
|
+
export function extractAllSnippets(
|
|
322
|
+
content: string,
|
|
323
|
+
searchText: string,
|
|
324
|
+
maxSnippets: number = 3
|
|
325
|
+
): SnippetResult[] {
|
|
326
|
+
if (!content || !searchText) return [];
|
|
327
|
+
|
|
328
|
+
const results: SnippetResult[] = [];
|
|
329
|
+
const contentLower = content.toLowerCase();
|
|
330
|
+
const searchLower = searchText.toLowerCase();
|
|
331
|
+
|
|
332
|
+
let startPos = 0;
|
|
333
|
+
while (results.length < maxSnippets) {
|
|
334
|
+
const matchStart = contentLower.indexOf(searchLower, startPos);
|
|
335
|
+
if (matchStart === -1) break;
|
|
336
|
+
|
|
337
|
+
// Extract snippet using the full content with the correct match position
|
|
338
|
+
const result = extractSnippetAtPosition(content, matchStart, searchText.length);
|
|
339
|
+
results.push(result);
|
|
340
|
+
|
|
341
|
+
startPos = matchStart + searchText.length;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return results;
|
|
345
|
+
}
|