@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,182 @@
|
|
|
1
|
+
import { glob } from "node:fs/promises";
|
|
2
|
+
import { stat } from "node:fs/promises";
|
|
3
|
+
import { relative, resolve } from "node:path";
|
|
4
|
+
import type { AISdkTool, ToolExecutionContext } from "@/tools/types";
|
|
5
|
+
import { isPathWithinDirectory, isWithinAgentHome } from "@/lib/agent-home";
|
|
6
|
+
import { tool } from "ai";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
|
|
9
|
+
const globSchema = z.object({
|
|
10
|
+
pattern: z
|
|
11
|
+
.string()
|
|
12
|
+
.describe("Glob pattern to match files (e.g., '**/*.ts', 'src/**/*.tsx', '*.json')"),
|
|
13
|
+
description: z
|
|
14
|
+
.string()
|
|
15
|
+
.min(1, "Description is required and cannot be empty")
|
|
16
|
+
.describe(
|
|
17
|
+
"REQUIRED: A clear, concise description of why you're searching for these files (5-10 words). Helps provide human-readable context for the operation."
|
|
18
|
+
),
|
|
19
|
+
path: z
|
|
20
|
+
.string()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe(
|
|
23
|
+
"Absolute path to directory to search in. Defaults to working directory. " +
|
|
24
|
+
"IMPORTANT: Omit this field to use the default. DO NOT pass 'undefined' or 'null'."
|
|
25
|
+
),
|
|
26
|
+
head_limit: z
|
|
27
|
+
.number()
|
|
28
|
+
.default(100)
|
|
29
|
+
.describe("Limit output to first N files. Use 0 for unlimited."),
|
|
30
|
+
offset: z
|
|
31
|
+
.number()
|
|
32
|
+
.default(0)
|
|
33
|
+
.describe("Skip first N files before applying head_limit"),
|
|
34
|
+
allowOutsideWorkingDirectory: z
|
|
35
|
+
.boolean()
|
|
36
|
+
.optional()
|
|
37
|
+
.describe("Set to true to glob outside the working directory. Required when path is not within the project."),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
type GlobInput = z.infer<typeof globSchema>;
|
|
41
|
+
|
|
42
|
+
interface FileWithMtime {
|
|
43
|
+
path: string;
|
|
44
|
+
mtime: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const DEFAULT_EXCLUDES = [
|
|
48
|
+
"**/node_modules/**",
|
|
49
|
+
"**/.git/**",
|
|
50
|
+
"**/dist/**",
|
|
51
|
+
"**/build/**",
|
|
52
|
+
"**/.next/**",
|
|
53
|
+
"**/coverage/**",
|
|
54
|
+
"**/.worktrees/**",
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
function applyPagination<T>(items: T[], offset: number, limit: number): T[] {
|
|
58
|
+
const offsetItems = offset > 0 ? items.slice(offset) : items;
|
|
59
|
+
return limit > 0 ? offsetItems.slice(0, limit) : offsetItems;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function executeGlob(
|
|
63
|
+
input: GlobInput,
|
|
64
|
+
workingDirectory: string,
|
|
65
|
+
agentPubkey: string,
|
|
66
|
+
): Promise<string> {
|
|
67
|
+
const { pattern, path: inputPath, head_limit, offset, allowOutsideWorkingDirectory } = input;
|
|
68
|
+
|
|
69
|
+
// If path is provided, validate it's absolute
|
|
70
|
+
if (inputPath && !inputPath.startsWith("/")) {
|
|
71
|
+
return `Path must be absolute, got: ${inputPath}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Determine search directory
|
|
75
|
+
const searchDir = inputPath ?? workingDirectory;
|
|
76
|
+
|
|
77
|
+
// Check if path is within working directory (using secure path normalization)
|
|
78
|
+
const isWithinWorkDir = isPathWithinDirectory(searchDir, workingDirectory);
|
|
79
|
+
|
|
80
|
+
// Always allow access to agent's home directory without requiring allowOutsideWorkingDirectory
|
|
81
|
+
const isInAgentHome = isWithinAgentHome(searchDir, agentPubkey);
|
|
82
|
+
|
|
83
|
+
if (!isWithinWorkDir && !isInAgentHome && !allowOutsideWorkingDirectory) {
|
|
84
|
+
return `Path "${searchDir}" is outside your working directory "${workingDirectory}". If this was intentional, retry with allowOutsideWorkingDirectory: true`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Collect files with modification times
|
|
88
|
+
const filesWithMtime: FileWithMtime[] = [];
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
// Use Node.js built-in glob (Node 20+)
|
|
92
|
+
const matches = glob(pattern, {
|
|
93
|
+
cwd: searchDir,
|
|
94
|
+
exclude: (name) => DEFAULT_EXCLUDES.some((exclude) => {
|
|
95
|
+
// Simple glob matching for excludes
|
|
96
|
+
if (exclude.includes("**")) {
|
|
97
|
+
const pattern = exclude.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*");
|
|
98
|
+
return new RegExp(pattern).test(name);
|
|
99
|
+
}
|
|
100
|
+
return name.includes(exclude.replace(/\*/g, ""));
|
|
101
|
+
}),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
for await (const match of matches) {
|
|
105
|
+
try {
|
|
106
|
+
// Resolve the full path to handle any ".." in the glob pattern
|
|
107
|
+
const fullPath = resolve(searchDir, match);
|
|
108
|
+
|
|
109
|
+
// Security check: ensure the resolved path is still within allowed directories
|
|
110
|
+
// This prevents patterns like "../**/*" from escaping the allowed boundary
|
|
111
|
+
const isWithinAllowed =
|
|
112
|
+
isPathWithinDirectory(fullPath, searchDir) ||
|
|
113
|
+
isWithinAgentHome(fullPath, agentPubkey) ||
|
|
114
|
+
(allowOutsideWorkingDirectory && isPathWithinDirectory(fullPath, workingDirectory));
|
|
115
|
+
|
|
116
|
+
if (!isWithinAllowed) {
|
|
117
|
+
// Skip files that escaped the allowed directory via ".." in pattern
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const stats = await stat(fullPath);
|
|
122
|
+
if (stats.isFile()) {
|
|
123
|
+
filesWithMtime.push({
|
|
124
|
+
path: relative(workingDirectory, fullPath),
|
|
125
|
+
mtime: stats.mtimeMs,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
} catch {
|
|
129
|
+
// Skip files we can't stat
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
} catch (error) {
|
|
133
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
134
|
+
return `Glob error: ${message}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (filesWithMtime.length === 0) {
|
|
138
|
+
return `No files found matching pattern: ${pattern}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Sort by modification time (most recent first)
|
|
142
|
+
filesWithMtime.sort((a, b) => b.mtime - a.mtime);
|
|
143
|
+
|
|
144
|
+
// Apply pagination
|
|
145
|
+
const paginatedFiles = applyPagination(filesWithMtime, offset, head_limit);
|
|
146
|
+
const truncated = paginatedFiles.length < filesWithMtime.length;
|
|
147
|
+
const result = paginatedFiles.map((f) => f.path).join("\n");
|
|
148
|
+
|
|
149
|
+
if (truncated) {
|
|
150
|
+
return `${result}\n\n[Truncated: showing ${paginatedFiles.length} of ${filesWithMtime.length} files]`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function createFsGlobTool(context: ToolExecutionContext): AISdkTool {
|
|
157
|
+
const toolInstance = tool({
|
|
158
|
+
description:
|
|
159
|
+
"Fast file pattern matching tool that works with any codebase size. " +
|
|
160
|
+
"Supports glob patterns like '**/*.ts' or 'src/**/*.tsx'. " +
|
|
161
|
+
"Returns matching file paths sorted by modification time (most recent first). " +
|
|
162
|
+
"Results limited to 100 files by default (use head_limit to adjust, 0 for unlimited). " +
|
|
163
|
+
"Path must be absolute. Globbing outside the working directory requires allowOutsideWorkingDirectory: true.",
|
|
164
|
+
|
|
165
|
+
inputSchema: globSchema,
|
|
166
|
+
|
|
167
|
+
execute: async (input: GlobInput) => {
|
|
168
|
+
return await executeGlob(input, context.workingDirectory, context.agent.pubkey);
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
Object.defineProperty(toolInstance, "getHumanReadableContent", {
|
|
173
|
+
value: (input: GlobInput) => {
|
|
174
|
+
const pathInfo = input.path ? ` in ${input.path}` : "";
|
|
175
|
+
return `Finding files matching '${input.pattern}'${pathInfo} (${input.description})`;
|
|
176
|
+
},
|
|
177
|
+
enumerable: false,
|
|
178
|
+
configurable: true,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
return toolInstance as AISdkTool;
|
|
182
|
+
}
|
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
import { exec } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { relative } from "node:path";
|
|
4
|
+
import type { AISdkTool, ToolExecutionContext } from "@/tools/types";
|
|
5
|
+
import { isPathWithinDirectory, isWithinAgentHome } from "@/lib/agent-home";
|
|
6
|
+
import { tool } from "ai";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
|
|
9
|
+
const execAsync = promisify(exec);
|
|
10
|
+
|
|
11
|
+
// Maximum content size before auto-fallback to files_with_matches mode
|
|
12
|
+
const MAX_CONTENT_SIZE = 50_000; // 50KB threshold
|
|
13
|
+
|
|
14
|
+
const grepSchema = z.object({
|
|
15
|
+
pattern: z
|
|
16
|
+
.string()
|
|
17
|
+
.describe(
|
|
18
|
+
"Regex pattern to search for in file contents (e.g., 'function\\s+\\w+', 'TODO', 'log.*Error')"
|
|
19
|
+
),
|
|
20
|
+
description: z
|
|
21
|
+
.string()
|
|
22
|
+
.min(1, "Description is required and cannot be empty")
|
|
23
|
+
.describe(
|
|
24
|
+
"REQUIRED: A clear, concise description of why you're searching for this pattern (5-10 words). Helps provide human-readable context for the operation."
|
|
25
|
+
),
|
|
26
|
+
path: z
|
|
27
|
+
.string()
|
|
28
|
+
.optional()
|
|
29
|
+
.describe("Absolute path to file or directory to search. Defaults to working directory."),
|
|
30
|
+
output_mode: z
|
|
31
|
+
.enum(["files_with_matches", "content", "count"])
|
|
32
|
+
.default("files_with_matches")
|
|
33
|
+
.describe(
|
|
34
|
+
"Output mode: 'files_with_matches' (file paths only), 'content' (matching lines with context), 'count' (match counts per file)"
|
|
35
|
+
),
|
|
36
|
+
glob: z
|
|
37
|
+
.string()
|
|
38
|
+
.optional()
|
|
39
|
+
.describe("Glob pattern to filter files (e.g., '*.ts', '**/*.tsx')"),
|
|
40
|
+
type: z
|
|
41
|
+
.string()
|
|
42
|
+
.optional()
|
|
43
|
+
.describe("File type filter for ripgrep (e.g., 'ts', 'py', 'rust', 'js')"),
|
|
44
|
+
"-i": z
|
|
45
|
+
.boolean()
|
|
46
|
+
.optional()
|
|
47
|
+
.describe("Case-insensitive search"),
|
|
48
|
+
"-n": z
|
|
49
|
+
.boolean()
|
|
50
|
+
.default(true)
|
|
51
|
+
.describe("Show line numbers (only with output_mode: 'content')"),
|
|
52
|
+
"-A": z
|
|
53
|
+
.number()
|
|
54
|
+
.optional()
|
|
55
|
+
.describe("Lines to show after each match (only with output_mode: 'content')"),
|
|
56
|
+
"-B": z
|
|
57
|
+
.number()
|
|
58
|
+
.optional()
|
|
59
|
+
.describe("Lines to show before each match (only with output_mode: 'content')"),
|
|
60
|
+
"-C": z
|
|
61
|
+
.number()
|
|
62
|
+
.optional()
|
|
63
|
+
.describe("Lines to show before and after each match (only with output_mode: 'content')"),
|
|
64
|
+
multiline: z
|
|
65
|
+
.boolean()
|
|
66
|
+
.default(false)
|
|
67
|
+
.describe("Enable multiline mode where patterns can span lines"),
|
|
68
|
+
head_limit: z
|
|
69
|
+
.number()
|
|
70
|
+
.default(100)
|
|
71
|
+
.describe("Limit output to first N entries. Use 0 for unlimited."),
|
|
72
|
+
offset: z
|
|
73
|
+
.number()
|
|
74
|
+
.default(0)
|
|
75
|
+
.describe("Skip first N entries before applying head_limit"),
|
|
76
|
+
allowOutsideWorkingDirectory: z
|
|
77
|
+
.boolean()
|
|
78
|
+
.optional()
|
|
79
|
+
.describe("Set to true to search outside the working directory. Required when path is not within the project."),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
type GrepInput = z.infer<typeof grepSchema>;
|
|
83
|
+
|
|
84
|
+
async function isRipgrepAvailable(): Promise<boolean> {
|
|
85
|
+
try {
|
|
86
|
+
await execAsync("which rg", { timeout: 1000 });
|
|
87
|
+
return true;
|
|
88
|
+
} catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function buildRipgrepCommand(input: GrepInput, searchPath: string): string {
|
|
94
|
+
const {
|
|
95
|
+
pattern,
|
|
96
|
+
output_mode,
|
|
97
|
+
glob: globPattern,
|
|
98
|
+
type: fileType,
|
|
99
|
+
"-i": caseInsensitive,
|
|
100
|
+
"-n": showLineNumbers,
|
|
101
|
+
"-A": contextAfter,
|
|
102
|
+
"-B": contextBefore,
|
|
103
|
+
"-C": contextAround,
|
|
104
|
+
multiline,
|
|
105
|
+
} = input;
|
|
106
|
+
|
|
107
|
+
const parts: string[] = ["rg"];
|
|
108
|
+
|
|
109
|
+
// Output mode flags
|
|
110
|
+
if (output_mode === "files_with_matches") {
|
|
111
|
+
parts.push("-l");
|
|
112
|
+
} else if (output_mode === "count") {
|
|
113
|
+
parts.push("-c");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Line numbers (default true for content mode)
|
|
117
|
+
if (output_mode === "content" && showLineNumbers !== false) {
|
|
118
|
+
parts.push("-n");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Case insensitive
|
|
122
|
+
if (caseInsensitive) {
|
|
123
|
+
parts.push("-i");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Multiline
|
|
127
|
+
if (multiline) {
|
|
128
|
+
parts.push("-U", "--multiline-dotall");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Context lines (only for content mode)
|
|
132
|
+
if (output_mode === "content") {
|
|
133
|
+
if (contextAround != null && contextAround > 0) {
|
|
134
|
+
parts.push("-C", `${contextAround}`);
|
|
135
|
+
} else {
|
|
136
|
+
if (contextBefore != null && contextBefore > 0) {
|
|
137
|
+
parts.push("-B", `${contextBefore}`);
|
|
138
|
+
}
|
|
139
|
+
if (contextAfter != null && contextAfter > 0) {
|
|
140
|
+
parts.push("-A", `${contextAfter}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// File type filter
|
|
146
|
+
if (fileType) {
|
|
147
|
+
parts.push("--type", fileType);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Glob pattern filter
|
|
151
|
+
if (globPattern) {
|
|
152
|
+
parts.push("--glob", `'${globPattern}'`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Default exclusions
|
|
156
|
+
parts.push("--glob", "'!node_modules'");
|
|
157
|
+
parts.push("--glob", "'!.git'");
|
|
158
|
+
parts.push("--glob", "'!dist'");
|
|
159
|
+
parts.push("--glob", "'!build'");
|
|
160
|
+
parts.push("--glob", "'!.next'");
|
|
161
|
+
parts.push("--glob", "'!coverage'");
|
|
162
|
+
|
|
163
|
+
// Pattern (escape for shell)
|
|
164
|
+
parts.push(`'${pattern.replace(/'/g, "'\\''")}'`);
|
|
165
|
+
|
|
166
|
+
// Search path
|
|
167
|
+
parts.push(`'${searchPath}'`);
|
|
168
|
+
|
|
169
|
+
return parts.join(" ");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function buildGrepCommand(input: GrepInput, searchPath: string): string {
|
|
173
|
+
const {
|
|
174
|
+
pattern,
|
|
175
|
+
output_mode,
|
|
176
|
+
glob: globPattern,
|
|
177
|
+
"-i": caseInsensitive,
|
|
178
|
+
"-n": showLineNumbers,
|
|
179
|
+
"-A": contextAfter,
|
|
180
|
+
"-B": contextBefore,
|
|
181
|
+
"-C": contextAround,
|
|
182
|
+
} = input;
|
|
183
|
+
|
|
184
|
+
const parts: string[] = ["grep", "-r", "-E"];
|
|
185
|
+
|
|
186
|
+
// Output mode flags
|
|
187
|
+
if (output_mode === "files_with_matches") {
|
|
188
|
+
parts.push("-l");
|
|
189
|
+
} else if (output_mode === "count") {
|
|
190
|
+
parts.push("-c");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Line numbers (default true for content mode)
|
|
194
|
+
if (output_mode === "content" && showLineNumbers !== false) {
|
|
195
|
+
parts.push("-n");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Case insensitive
|
|
199
|
+
if (caseInsensitive) {
|
|
200
|
+
parts.push("-i");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Context lines (only for content mode)
|
|
204
|
+
if (output_mode === "content") {
|
|
205
|
+
if (contextAround != null && contextAround > 0) {
|
|
206
|
+
parts.push("-C", `${contextAround}`);
|
|
207
|
+
} else {
|
|
208
|
+
if (contextBefore != null && contextBefore > 0) {
|
|
209
|
+
parts.push("-B", `${contextBefore}`);
|
|
210
|
+
}
|
|
211
|
+
if (contextAfter != null && contextAfter > 0) {
|
|
212
|
+
parts.push("-A", `${contextAfter}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Glob pattern filter (grep uses --include)
|
|
218
|
+
if (globPattern) {
|
|
219
|
+
parts.push(`--include='${globPattern}'`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Default exclusions
|
|
223
|
+
parts.push("--exclude-dir=node_modules");
|
|
224
|
+
parts.push("--exclude-dir=.git");
|
|
225
|
+
parts.push("--exclude-dir=dist");
|
|
226
|
+
parts.push("--exclude-dir=build");
|
|
227
|
+
parts.push("--exclude-dir=.next");
|
|
228
|
+
parts.push("--exclude-dir=coverage");
|
|
229
|
+
parts.push("--binary-files=without-match");
|
|
230
|
+
|
|
231
|
+
// Pattern
|
|
232
|
+
parts.push(`'${pattern.replace(/'/g, "'\\''")}'`);
|
|
233
|
+
|
|
234
|
+
// Search path
|
|
235
|
+
parts.push(`'${searchPath}'`);
|
|
236
|
+
|
|
237
|
+
return parts.join(" ");
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function applyPagination(lines: string[], offset: number, limit: number): string[] {
|
|
241
|
+
const offsetLines = offset > 0 ? lines.slice(offset) : lines;
|
|
242
|
+
return limit > 0 ? offsetLines.slice(0, limit) : offsetLines;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function runGrepCommand(
|
|
246
|
+
input: GrepInput,
|
|
247
|
+
workingDirectory: string,
|
|
248
|
+
searchPath: string,
|
|
249
|
+
): Promise<string[]> {
|
|
250
|
+
const useRipgrep = await isRipgrepAvailable();
|
|
251
|
+
const command = useRipgrep
|
|
252
|
+
? buildRipgrepCommand(input, searchPath)
|
|
253
|
+
: buildGrepCommand(input, searchPath);
|
|
254
|
+
|
|
255
|
+
try {
|
|
256
|
+
const { stdout } = await execAsync(command, {
|
|
257
|
+
cwd: workingDirectory,
|
|
258
|
+
timeout: 30000,
|
|
259
|
+
maxBuffer: 1024 * 1024 * 10, // 10MB
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
if (!stdout.trim()) {
|
|
263
|
+
return [];
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return stdout.trim().split("\n").filter(Boolean);
|
|
267
|
+
} catch (error) {
|
|
268
|
+
// Exit code 1 from grep/rg means no matches - not an error
|
|
269
|
+
if (error && typeof error === "object" && "code" in error && error.code === 1) {
|
|
270
|
+
return [];
|
|
271
|
+
}
|
|
272
|
+
// maxBuffer error - rethrow to trigger fallback
|
|
273
|
+
if (error && typeof error === "object" && "message" in error &&
|
|
274
|
+
typeof error.message === "string" && error.message.includes("maxBuffer")) {
|
|
275
|
+
throw error;
|
|
276
|
+
}
|
|
277
|
+
throw error;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function extractFilePathsFromContent(contentLines: string[]): string[] {
|
|
282
|
+
const uniquePaths = new Set<string>();
|
|
283
|
+
|
|
284
|
+
for (const line of contentLines) {
|
|
285
|
+
// Content format: filepath:linenum:content or filepath:content
|
|
286
|
+
const firstColon = line.indexOf(":");
|
|
287
|
+
if (firstColon > 0) {
|
|
288
|
+
const filePath = line.substring(0, firstColon);
|
|
289
|
+
uniquePaths.add(filePath);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return Array.from(uniquePaths);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function truncateToMaxSize(text: string, maxBytes: number): { truncated: string; originalLength: number } {
|
|
297
|
+
const originalLength = Buffer.byteLength(text, "utf8");
|
|
298
|
+
|
|
299
|
+
if (originalLength <= maxBytes) {
|
|
300
|
+
return { truncated: text, originalLength };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Binary search for the right character position that fits in maxBytes
|
|
304
|
+
const lines = text.split("\n");
|
|
305
|
+
let left = 0;
|
|
306
|
+
let right = lines.length;
|
|
307
|
+
let bestFit = 0;
|
|
308
|
+
|
|
309
|
+
while (left <= right) {
|
|
310
|
+
const mid = Math.floor((left + right) / 2);
|
|
311
|
+
const candidate = lines.slice(0, mid).join("\n");
|
|
312
|
+
const candidateSize = Buffer.byteLength(candidate, "utf8");
|
|
313
|
+
|
|
314
|
+
if (candidateSize <= maxBytes) {
|
|
315
|
+
bestFit = mid;
|
|
316
|
+
left = mid + 1;
|
|
317
|
+
} else {
|
|
318
|
+
right = mid - 1;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const truncated = lines.slice(0, bestFit).join("\n");
|
|
323
|
+
return { truncated, originalLength };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async function executeGrep(
|
|
327
|
+
input: GrepInput,
|
|
328
|
+
workingDirectory: string,
|
|
329
|
+
agentPubkey: string,
|
|
330
|
+
): Promise<string> {
|
|
331
|
+
const { pattern, path: inputPath, output_mode, head_limit, offset, allowOutsideWorkingDirectory } = input;
|
|
332
|
+
|
|
333
|
+
if (!pattern) {
|
|
334
|
+
return "Error: pattern is required";
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// If path is provided, validate it's absolute
|
|
338
|
+
if (inputPath && !inputPath.startsWith("/")) {
|
|
339
|
+
return `Path must be absolute, got: ${inputPath}`;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Determine search path
|
|
343
|
+
const searchPath = inputPath ?? workingDirectory;
|
|
344
|
+
|
|
345
|
+
// Check if path is within working directory (using secure path normalization)
|
|
346
|
+
const isWithinWorkDir = isPathWithinDirectory(searchPath, workingDirectory);
|
|
347
|
+
|
|
348
|
+
// Always allow access to agent's home directory without requiring allowOutsideWorkingDirectory
|
|
349
|
+
const isInAgentHome = isWithinAgentHome(searchPath, agentPubkey);
|
|
350
|
+
|
|
351
|
+
if (!isWithinWorkDir && !isInAgentHome && !allowOutsideWorkingDirectory) {
|
|
352
|
+
return `Path "${searchPath}" is outside your working directory "${workingDirectory}". If this was intentional, retry with allowOutsideWorkingDirectory: true`;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
try {
|
|
356
|
+
// Execute the search
|
|
357
|
+
let lines: string[];
|
|
358
|
+
|
|
359
|
+
try {
|
|
360
|
+
lines = await runGrepCommand(input, workingDirectory, searchPath);
|
|
361
|
+
} catch (error) {
|
|
362
|
+
// Handle maxBuffer error by falling back to files_with_matches mode
|
|
363
|
+
if (
|
|
364
|
+
output_mode === "content" &&
|
|
365
|
+
error &&
|
|
366
|
+
typeof error === "object" &&
|
|
367
|
+
"message" in error &&
|
|
368
|
+
typeof error.message === "string" &&
|
|
369
|
+
error.message.includes("maxBuffer")
|
|
370
|
+
) {
|
|
371
|
+
// Retry with files_with_matches mode
|
|
372
|
+
const fallbackInput = { ...input, output_mode: "files_with_matches" as const };
|
|
373
|
+
const fallbackLines = await runGrepCommand(fallbackInput, workingDirectory, searchPath);
|
|
374
|
+
|
|
375
|
+
// Convert to relative paths
|
|
376
|
+
const fallbackProcessed = fallbackLines.map((line) => relative(workingDirectory, line));
|
|
377
|
+
|
|
378
|
+
// Apply pagination
|
|
379
|
+
const fallbackPaginated = applyPagination(fallbackProcessed, offset, head_limit);
|
|
380
|
+
const fileListResult = fallbackPaginated.join("\n");
|
|
381
|
+
|
|
382
|
+
// Build message prefix
|
|
383
|
+
const messagePrefix =
|
|
384
|
+
`Content output exceeded buffer limit.\n` +
|
|
385
|
+
`Showing matching files instead (${fallbackLines.length} total):\n\n`;
|
|
386
|
+
|
|
387
|
+
// Calculate available space
|
|
388
|
+
const availableSpace = MAX_CONTENT_SIZE - Buffer.byteLength(messagePrefix, "utf8") - 200;
|
|
389
|
+
|
|
390
|
+
// Enforce hard cap on output size
|
|
391
|
+
const { truncated: finalOutput, originalLength } = truncateToMaxSize(
|
|
392
|
+
fileListResult,
|
|
393
|
+
availableSpace
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
const truncationNote =
|
|
397
|
+
originalLength > availableSpace
|
|
398
|
+
? `\n\n[Output truncated to 50KB limit - ${fallbackPaginated.length} files matched, showing partial list]`
|
|
399
|
+
: "";
|
|
400
|
+
|
|
401
|
+
return messagePrefix + finalOutput + truncationNote;
|
|
402
|
+
}
|
|
403
|
+
throw error;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (lines.length === 0) {
|
|
407
|
+
return `No matches found for pattern: ${pattern}`;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Convert absolute paths to relative
|
|
411
|
+
let processedLines = lines.map((line) => {
|
|
412
|
+
// Handle different output formats
|
|
413
|
+
if (output_mode === "files_with_matches") {
|
|
414
|
+
return relative(workingDirectory, line);
|
|
415
|
+
} else if (output_mode === "count") {
|
|
416
|
+
// Format: /path/to/file:count
|
|
417
|
+
const colonIdx = line.lastIndexOf(":");
|
|
418
|
+
if (colonIdx > 0) {
|
|
419
|
+
const filePath = line.substring(0, colonIdx);
|
|
420
|
+
const count = line.substring(colonIdx + 1);
|
|
421
|
+
return `${relative(workingDirectory, filePath)}:${count}`;
|
|
422
|
+
}
|
|
423
|
+
} else {
|
|
424
|
+
// Content mode: /path/to/file:line:content
|
|
425
|
+
const firstColon = line.indexOf(":");
|
|
426
|
+
if (firstColon > 0) {
|
|
427
|
+
const filePath = line.substring(0, firstColon);
|
|
428
|
+
const rest = line.substring(firstColon);
|
|
429
|
+
return `${relative(workingDirectory, filePath)}${rest}`;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return line;
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
// Apply pagination first
|
|
436
|
+
const paginatedLines = applyPagination(processedLines, offset, head_limit);
|
|
437
|
+
const result = paginatedLines.join("\n");
|
|
438
|
+
|
|
439
|
+
// Auto-fallback logic for content mode when PAGINATED output is too large
|
|
440
|
+
if (output_mode === "content") {
|
|
441
|
+
const paginatedSize = Buffer.byteLength(result, "utf8");
|
|
442
|
+
|
|
443
|
+
if (paginatedSize > MAX_CONTENT_SIZE) {
|
|
444
|
+
// Extract unique file paths from the content we already have
|
|
445
|
+
const filePaths = extractFilePathsFromContent(processedLines);
|
|
446
|
+
|
|
447
|
+
// Apply pagination to file list
|
|
448
|
+
const paginatedFiles = applyPagination(filePaths, offset, head_limit);
|
|
449
|
+
const fileListResult = paginatedFiles.join("\n");
|
|
450
|
+
|
|
451
|
+
// Build message prefix
|
|
452
|
+
const messagePrefix =
|
|
453
|
+
`Content output would exceed 50KB limit (actual: ${(paginatedSize / 1024).toFixed(1)}KB).\n` +
|
|
454
|
+
`Showing ${paginatedFiles.length} matching files instead:\n\n`;
|
|
455
|
+
|
|
456
|
+
// Calculate available space for file list (leave room for prefix and potential truncation note)
|
|
457
|
+
const availableSpace = MAX_CONTENT_SIZE - Buffer.byteLength(messagePrefix, "utf8") - 200;
|
|
458
|
+
|
|
459
|
+
// Enforce hard cap on file list output
|
|
460
|
+
const { truncated: finalOutput, originalLength } = truncateToMaxSize(
|
|
461
|
+
fileListResult,
|
|
462
|
+
availableSpace
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
const truncationNote =
|
|
466
|
+
originalLength > availableSpace
|
|
467
|
+
? `\n\n[File list truncated to 50KB limit - ${filePaths.length} files matched, showing partial list]`
|
|
468
|
+
: "";
|
|
469
|
+
|
|
470
|
+
return messagePrefix + finalOutput + truncationNote;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Normal output (no fallback needed)
|
|
475
|
+
const truncated = paginatedLines.length < processedLines.length;
|
|
476
|
+
if (truncated) {
|
|
477
|
+
return `${result}\n\n[Truncated: showing ${paginatedLines.length} of ${processedLines.length} results]`;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return result;
|
|
481
|
+
} catch (error) {
|
|
482
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
483
|
+
return `Grep error: ${message}`;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
export function createFsGrepTool(context: ToolExecutionContext): AISdkTool {
|
|
488
|
+
const toolInstance = tool({
|
|
489
|
+
description:
|
|
490
|
+
"Powerful content search tool built on ripgrep (with grep fallback). " +
|
|
491
|
+
"Supports full regex syntax (e.g., 'log.*Error', 'function\\s+\\w+'). " +
|
|
492
|
+
"Output modes: 'files_with_matches' (default, file paths only), 'content' (matching lines), 'count' (match counts). " +
|
|
493
|
+
"Filter files with 'glob' parameter (e.g., '*.ts') or 'type' parameter (e.g., 'ts', 'py'). " +
|
|
494
|
+
"Path must be absolute. Searching outside the working directory requires allowOutsideWorkingDirectory: true.",
|
|
495
|
+
|
|
496
|
+
inputSchema: grepSchema,
|
|
497
|
+
|
|
498
|
+
execute: async (input: GrepInput) => {
|
|
499
|
+
return await executeGrep(input, context.workingDirectory, context.agent.pubkey);
|
|
500
|
+
},
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
Object.defineProperty(toolInstance, "getHumanReadableContent", {
|
|
504
|
+
value: (input: GrepInput) => {
|
|
505
|
+
const pathInfo = input.path ? ` in ${input.path}` : "";
|
|
506
|
+
return `Searching for '${input.pattern}'${pathInfo} (${input.description})`;
|
|
507
|
+
},
|
|
508
|
+
enumerable: false,
|
|
509
|
+
configurable: true,
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
return toolInstance as AISdkTool;
|
|
513
|
+
}
|