@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,332 @@
|
|
|
1
|
+
import { readFile, readdir, stat } from "node:fs/promises";
|
|
2
|
+
import type { AISdkTool, ToolExecutionContext } from "@/tools/types";
|
|
3
|
+
import { toolMessageStorage } from "@/conversations/persistence/ToolMessageStorage";
|
|
4
|
+
import { isPathWithinDirectory, isWithinAgentHome } from "@/lib/agent-home";
|
|
5
|
+
import { formatAnyError } from "@/lib/error-formatter";
|
|
6
|
+
import { llmServiceFactory } from "@/llm";
|
|
7
|
+
import { config } from "@/services/ConfigService";
|
|
8
|
+
import {
|
|
9
|
+
createExpectedError,
|
|
10
|
+
getFsErrorDescription,
|
|
11
|
+
isExpectedFsError,
|
|
12
|
+
isExpectedNotFoundError,
|
|
13
|
+
} from "@/tools/utils";
|
|
14
|
+
import { logger } from "@/utils/logger";
|
|
15
|
+
import { tool } from "ai";
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
|
|
18
|
+
const DEFAULT_LINE_LIMIT = 2000;
|
|
19
|
+
const MAX_LINE_LENGTH = 2000;
|
|
20
|
+
|
|
21
|
+
const readPathSchema = z.object({
|
|
22
|
+
path: z
|
|
23
|
+
.string()
|
|
24
|
+
.optional()
|
|
25
|
+
.describe("The absolute path to the file or directory to read. Required unless using 'tool' parameter."),
|
|
26
|
+
description: z
|
|
27
|
+
.string()
|
|
28
|
+
.min(1, "Description is required and cannot be empty")
|
|
29
|
+
.describe(
|
|
30
|
+
"REQUIRED: A clear, concise description of why you're reading this file (5-10 words). Helps provide human-readable context for the operation."
|
|
31
|
+
),
|
|
32
|
+
offset: z
|
|
33
|
+
.number()
|
|
34
|
+
.min(1)
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Line number to start reading from (1-based). If omitted, starts from line 1."),
|
|
37
|
+
limit: z
|
|
38
|
+
.number()
|
|
39
|
+
.min(1)
|
|
40
|
+
.optional()
|
|
41
|
+
.describe(`Maximum number of lines to read. Defaults to ${DEFAULT_LINE_LIMIT}.`),
|
|
42
|
+
allowOutsideWorkingDirectory: z
|
|
43
|
+
.boolean()
|
|
44
|
+
.optional()
|
|
45
|
+
.describe("Set to true to read files outside the working directory. Required when path is not within the project."),
|
|
46
|
+
tool: z
|
|
47
|
+
.string()
|
|
48
|
+
.optional()
|
|
49
|
+
.describe("Event ID of a tool execution to read its result. When provided, 'path' is ignored and the tool's output is returned. Useful for retrieving truncated tool results."),
|
|
50
|
+
prompt: z
|
|
51
|
+
.string()
|
|
52
|
+
.optional()
|
|
53
|
+
.describe("Optional prompt to analyze the content. When provided, the content (file or tool result) will be processed through an LLM which will provide an explanation based on this prompt. Useful for extracting specific information or getting a summary."),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Fetch and format a tool execution result by event ID
|
|
58
|
+
*/
|
|
59
|
+
async function executeReadToolResult(eventId: string): Promise<string> {
|
|
60
|
+
const messages = await toolMessageStorage.load(eventId);
|
|
61
|
+
|
|
62
|
+
if (!messages) {
|
|
63
|
+
throw new Error(`No tool result found for event ID: ${eventId}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Extract tool call and result from messages
|
|
67
|
+
// Format: [{ role: "assistant", content: [{ type: "tool-call", ... }] }, { role: "tool", content: [{ type: "tool-result", ... }] }]
|
|
68
|
+
const assistantMessage = messages.find((m) => m.role === "assistant");
|
|
69
|
+
const toolMessage = messages.find((m) => m.role === "tool");
|
|
70
|
+
|
|
71
|
+
if (!assistantMessage || !toolMessage) {
|
|
72
|
+
throw new Error(`Invalid tool result format for event ID: ${eventId}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Extract tool call details
|
|
76
|
+
const toolCallContent = Array.isArray(assistantMessage.content)
|
|
77
|
+
? assistantMessage.content.find((c) => typeof c === "object" && "type" in c && c.type === "tool-call")
|
|
78
|
+
: null;
|
|
79
|
+
|
|
80
|
+
// Extract tool result
|
|
81
|
+
const toolResultContent = Array.isArray(toolMessage.content)
|
|
82
|
+
? toolMessage.content.find((c) => typeof c === "object" && "type" in c && c.type === "tool-result")
|
|
83
|
+
: null;
|
|
84
|
+
|
|
85
|
+
if (!toolCallContent || !toolResultContent) {
|
|
86
|
+
throw new Error(`Could not extract tool call/result for event ID: ${eventId}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Format output
|
|
90
|
+
const toolName = "toolName" in toolCallContent ? toolCallContent.toolName : "unknown";
|
|
91
|
+
const input = "input" in toolCallContent ? toolCallContent.input : {};
|
|
92
|
+
const output = "output" in toolResultContent ? toolResultContent.output : null;
|
|
93
|
+
|
|
94
|
+
// Extract the actual output value
|
|
95
|
+
let outputValue: string;
|
|
96
|
+
if (output && typeof output === "object" && "value" in output) {
|
|
97
|
+
outputValue = String(output.value);
|
|
98
|
+
} else if (typeof output === "string") {
|
|
99
|
+
outputValue = output;
|
|
100
|
+
} else {
|
|
101
|
+
outputValue = JSON.stringify(output, null, 2);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const inputStr = typeof input === "object" ? JSON.stringify(input, null, 2) : String(input);
|
|
105
|
+
|
|
106
|
+
return `Tool: ${toolName}\nEvent ID: ${eventId}\n\n--- Input ---\n${inputStr}\n\n--- Output ---\n${outputValue}`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Core implementation of the fs_read functionality
|
|
111
|
+
*/
|
|
112
|
+
async function executeFsRead(
|
|
113
|
+
path: string,
|
|
114
|
+
workingDirectory: string,
|
|
115
|
+
agentPubkey: string,
|
|
116
|
+
offset?: number,
|
|
117
|
+
limit?: number,
|
|
118
|
+
allowOutsideWorkingDirectory?: boolean,
|
|
119
|
+
): Promise<string> {
|
|
120
|
+
if (!path.startsWith("/")) {
|
|
121
|
+
throw new Error(`Path must be absolute, got: ${path}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Check if path is within working directory (using secure path normalization)
|
|
125
|
+
const isWithinWorkDir = isPathWithinDirectory(path, workingDirectory);
|
|
126
|
+
|
|
127
|
+
// Always allow access to agent's home directory without requiring allowOutsideWorkingDirectory
|
|
128
|
+
const isInAgentHome = isWithinAgentHome(path, agentPubkey);
|
|
129
|
+
|
|
130
|
+
if (!isWithinWorkDir && !isInAgentHome && !allowOutsideWorkingDirectory) {
|
|
131
|
+
return `Path "${path}" is outside your working directory "${workingDirectory}". If this was intentional, retry with allowOutsideWorkingDirectory: true`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const stats = await stat(path);
|
|
135
|
+
|
|
136
|
+
if (stats.isDirectory()) {
|
|
137
|
+
const files = await readdir(path);
|
|
138
|
+
const fileList = files.map((file) => ` - ${file}`).join("\n");
|
|
139
|
+
return `Directory listing for ${path}:\n${fileList}\n\nTo read a specific file, please specify the full path to the file.`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const rawContent = await readFile(path, "utf-8");
|
|
143
|
+
const lines = rawContent.split("\n");
|
|
144
|
+
const totalLines = lines.length;
|
|
145
|
+
|
|
146
|
+
// 1-based offset, default to line 1
|
|
147
|
+
const startLine = offset ?? 1;
|
|
148
|
+
const startIndex = startLine - 1;
|
|
149
|
+
|
|
150
|
+
if (startIndex >= totalLines) {
|
|
151
|
+
return `File has only ${totalLines} line(s), but offset ${offset} was requested.`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Apply limit (default to DEFAULT_LINE_LIMIT)
|
|
155
|
+
const effectiveLimit = limit ?? DEFAULT_LINE_LIMIT;
|
|
156
|
+
const endIndex = Math.min(startIndex + effectiveLimit, totalLines);
|
|
157
|
+
const selectedLines = lines.slice(startIndex, endIndex);
|
|
158
|
+
|
|
159
|
+
// Format with line numbers and truncate long lines
|
|
160
|
+
const numberedLines = selectedLines
|
|
161
|
+
.map((line, idx) => {
|
|
162
|
+
const lineNum = startIndex + idx + 1;
|
|
163
|
+
const truncatedLine = line.length > MAX_LINE_LENGTH
|
|
164
|
+
? line.slice(0, MAX_LINE_LENGTH) + "..."
|
|
165
|
+
: line;
|
|
166
|
+
return `${lineNum.toString().padStart(6)}\t${truncatedLine}`;
|
|
167
|
+
})
|
|
168
|
+
.join("\n");
|
|
169
|
+
|
|
170
|
+
// Add info about truncation if we didn't read the whole file
|
|
171
|
+
const remainingLines = totalLines - endIndex;
|
|
172
|
+
if (remainingLines > 0) {
|
|
173
|
+
return `${numberedLines}\n\n[Showing lines ${startLine}-${endIndex} of ${totalLines}. ${remainingLines} more lines available. Use offset=${endIndex + 1} to continue.]`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return numberedLines;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Synthesize content using an LLM based on a prompt
|
|
181
|
+
*/
|
|
182
|
+
async function synthesizeContent(content: string, prompt: string, source: string): Promise<string> {
|
|
183
|
+
// Get LLM configuration - use summarization config if set, otherwise default
|
|
184
|
+
const { llms } = await config.loadConfig();
|
|
185
|
+
const configName = llms.summarization || llms.default;
|
|
186
|
+
|
|
187
|
+
if (!configName) {
|
|
188
|
+
logger.warn("No LLM configuration available for content synthesis");
|
|
189
|
+
return content;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const llmConfig = config.getLLMConfig(configName);
|
|
193
|
+
|
|
194
|
+
// Create LLM service
|
|
195
|
+
const llmService = llmServiceFactory.createService(llmConfig, {
|
|
196
|
+
agentName: "content-synthesizer",
|
|
197
|
+
sessionId: `synthesizer-${Date.now()}`,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Generate synthesis using the LLM
|
|
201
|
+
const { object: result } = await llmService.generateObject(
|
|
202
|
+
[
|
|
203
|
+
{
|
|
204
|
+
role: "system",
|
|
205
|
+
content: `You are a helpful assistant that analyzes content and provides explanations based on user prompts.
|
|
206
|
+
|
|
207
|
+
CRITICAL REQUIREMENTS:
|
|
208
|
+
1. VERBATIM QUOTES: You MUST include relevant parts of the content verbatim in your response. Quote the exact text that supports your analysis.
|
|
209
|
+
2. PRESERVE IDENTIFIERS: All IDs, file paths, function names, variable names, and other technical identifiers must be preserved exactly as they appear.
|
|
210
|
+
3. Base your analysis ONLY on what is explicitly present in the content.
|
|
211
|
+
|
|
212
|
+
FORMAT: Structure your response with:
|
|
213
|
+
- Your analysis/explanation
|
|
214
|
+
- Verbatim quotes from relevant parts (use quotation marks)
|
|
215
|
+
- All referenced identifiers preserved exactly`,
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
role: "user",
|
|
219
|
+
content: `Please analyze the following content based on this prompt: "${prompt}"
|
|
220
|
+
|
|
221
|
+
IMPORTANT: Include verbatim quotes from the relevant parts that support your analysis.
|
|
222
|
+
|
|
223
|
+
SOURCE: ${source}
|
|
224
|
+
|
|
225
|
+
CONTENT:
|
|
226
|
+
${content}`,
|
|
227
|
+
},
|
|
228
|
+
],
|
|
229
|
+
z.object({
|
|
230
|
+
explanation: z
|
|
231
|
+
.string()
|
|
232
|
+
.describe(
|
|
233
|
+
"A detailed explanation based on the user's prompt. MUST include verbatim quotes from relevant content and preserve all important identifiers."
|
|
234
|
+
),
|
|
235
|
+
})
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
logger.info("✅ Content synthesized with prompt", {
|
|
239
|
+
source,
|
|
240
|
+
promptLength: prompt.length,
|
|
241
|
+
contentLength: content.length,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
return result.explanation;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Create an AI SDK tool for reading paths
|
|
249
|
+
*/
|
|
250
|
+
export function createFsReadTool(context: ToolExecutionContext): AISdkTool {
|
|
251
|
+
const toolInstance = tool({
|
|
252
|
+
description:
|
|
253
|
+
`Read a file, directory, or tool result. For files: returns contents with line numbers, up to ${DEFAULT_LINE_LIMIT} lines by default. Use offset (1-based) and limit to paginate. Lines over ${MAX_LINE_LENGTH} chars are truncated. Path must be absolute. Reading outside working directory requires allowOutsideWorkingDirectory: true. For tool results: use the 'tool' parameter with an event ID to read a tool execution's output (useful for retrieving results from other agents' tool calls).`,
|
|
254
|
+
|
|
255
|
+
inputSchema: readPathSchema,
|
|
256
|
+
|
|
257
|
+
execute: async ({ path, description, offset, limit, allowOutsideWorkingDirectory, tool: toolEventId, prompt }: { path?: string; description: string; offset?: number; limit?: number; allowOutsideWorkingDirectory?: boolean; tool?: string; prompt?: string }) => {
|
|
258
|
+
// Log the read operation with context
|
|
259
|
+
logger.info("Reading file or tool result", {
|
|
260
|
+
path: path || undefined,
|
|
261
|
+
description,
|
|
262
|
+
tool: toolEventId || undefined,
|
|
263
|
+
hasPrompt: !!prompt,
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
let content: string;
|
|
268
|
+
let source: string;
|
|
269
|
+
|
|
270
|
+
// If tool parameter is provided, fetch tool result instead of reading a file
|
|
271
|
+
if (toolEventId) {
|
|
272
|
+
content = await executeReadToolResult(toolEventId);
|
|
273
|
+
source = `tool result ${toolEventId}`;
|
|
274
|
+
} else {
|
|
275
|
+
// Otherwise, read the file/directory
|
|
276
|
+
if (!path) {
|
|
277
|
+
throw new Error("Either 'path' or 'tool' parameter is required");
|
|
278
|
+
}
|
|
279
|
+
content = await executeFsRead(path, context.workingDirectory, context.agent.pubkey, offset, limit, allowOutsideWorkingDirectory);
|
|
280
|
+
source = path;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// If a prompt is provided, synthesize the content
|
|
284
|
+
if (prompt) {
|
|
285
|
+
try {
|
|
286
|
+
return await synthesizeContent(content, prompt, source);
|
|
287
|
+
} catch (synthError) {
|
|
288
|
+
logger.error("Failed to synthesize content, returning raw content", {
|
|
289
|
+
source,
|
|
290
|
+
error: synthError instanceof Error ? synthError.message : String(synthError),
|
|
291
|
+
});
|
|
292
|
+
return `[Synthesis failed: ${synthError instanceof Error ? synthError.message : String(synthError)}]\n\n${content}`;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return content;
|
|
297
|
+
} catch (error: unknown) {
|
|
298
|
+
const target = toolEventId ? `tool result ${toolEventId}` : path;
|
|
299
|
+
|
|
300
|
+
// Expected FS errors (file not found, permission denied, etc.) return error-text
|
|
301
|
+
// This ensures the error is properly communicated to the LLM without stream failures
|
|
302
|
+
if (isExpectedFsError(error)) {
|
|
303
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
304
|
+
const description = getFsErrorDescription(code);
|
|
305
|
+
return createExpectedError(`${description}: ${target}`);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Expected "not found" errors (e.g., tool result not found) also return error-text
|
|
309
|
+
if (isExpectedNotFoundError(error)) {
|
|
310
|
+
return createExpectedError(error instanceof Error ? error.message : `Not found: ${target}`);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Unexpected errors still throw (they'll be caught by the SDK)
|
|
314
|
+
throw new Error(`Failed to read ${target}: ${formatAnyError(error)}`, { cause: error });
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
Object.defineProperty(toolInstance, "getHumanReadableContent", {
|
|
320
|
+
value: ({ path, description, tool: toolEventId, prompt }: { path?: string; description: string; tool?: string; prompt?: string }) => {
|
|
321
|
+
const action = prompt ? "Analyzing" : "Reading";
|
|
322
|
+
if (toolEventId) {
|
|
323
|
+
return `${action} tool result ${toolEventId.substring(0, 16)}... (${description})`;
|
|
324
|
+
}
|
|
325
|
+
return `${action} ${path} (${description})`;
|
|
326
|
+
},
|
|
327
|
+
enumerable: false,
|
|
328
|
+
configurable: true,
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
return toolInstance as AISdkTool;
|
|
332
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import type { AISdkTool, ToolExecutionContext } from "@/tools/types";
|
|
4
|
+
import { isPathWithinDirectory, isWithinAgentHome } from "@/lib/agent-home";
|
|
5
|
+
import { formatAnyError } from "@/lib/error-formatter";
|
|
6
|
+
import { getLocalReportStore } from "@/services/reports";
|
|
7
|
+
import {
|
|
8
|
+
createExpectedError,
|
|
9
|
+
getFsErrorDescription,
|
|
10
|
+
isExpectedFsError,
|
|
11
|
+
} from "@/tools/utils";
|
|
12
|
+
import { tool } from "ai";
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
|
|
15
|
+
const writeFileSchema = z.object({
|
|
16
|
+
path: z
|
|
17
|
+
.string()
|
|
18
|
+
.describe("The absolute path to the file to write"),
|
|
19
|
+
content: z.string().describe("The content to write to the file"),
|
|
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 writing this file (5-10 words). Helps provide human-readable context for the operation."
|
|
25
|
+
),
|
|
26
|
+
allowOutsideWorkingDirectory: z
|
|
27
|
+
.boolean()
|
|
28
|
+
.optional()
|
|
29
|
+
.describe("Set to true to write files outside the working directory. Required when path is not within the project."),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Core implementation of the write_file functionality
|
|
34
|
+
*/
|
|
35
|
+
async function executeWriteFile(
|
|
36
|
+
path: string,
|
|
37
|
+
content: string,
|
|
38
|
+
workingDirectory: string,
|
|
39
|
+
agentPubkey: string,
|
|
40
|
+
allowOutsideWorkingDirectory?: boolean,
|
|
41
|
+
): Promise<string> {
|
|
42
|
+
if (!path.startsWith("/")) {
|
|
43
|
+
throw new Error(`Path must be absolute, got: ${path}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Block writes to the reports directory - agents must use report_write instead
|
|
47
|
+
const localReportStore = getLocalReportStore();
|
|
48
|
+
if (localReportStore.isPathInReportsDir(path)) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
"Cannot write to reports directory directly. " +
|
|
51
|
+
`Path "${path}" is within the protected reports directory. ` +
|
|
52
|
+
"Use the report_write tool instead to create or update reports."
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Check if path is within working directory (using secure path normalization)
|
|
57
|
+
const isWithinWorkDir = isPathWithinDirectory(path, workingDirectory);
|
|
58
|
+
|
|
59
|
+
// Always allow access to agent's home directory without requiring allowOutsideWorkingDirectory
|
|
60
|
+
const isInAgentHome = isWithinAgentHome(path, agentPubkey);
|
|
61
|
+
|
|
62
|
+
if (!isWithinWorkDir && !isInAgentHome && !allowOutsideWorkingDirectory) {
|
|
63
|
+
return `Path "${path}" is outside your working directory "${workingDirectory}". If this was intentional, retry with allowOutsideWorkingDirectory: true`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Create parent directories if they don't exist
|
|
67
|
+
const parentDir = dirname(path);
|
|
68
|
+
await mkdir(parentDir, { recursive: true });
|
|
69
|
+
|
|
70
|
+
// Write the file
|
|
71
|
+
await writeFile(path, content, "utf-8");
|
|
72
|
+
|
|
73
|
+
return `Successfully wrote ${content.length} bytes to ${path}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Create an AI SDK tool for writing files
|
|
78
|
+
*/
|
|
79
|
+
export function createFsWriteTool(context: ToolExecutionContext): AISdkTool {
|
|
80
|
+
const toolInstance = tool({
|
|
81
|
+
description:
|
|
82
|
+
"Write content to a file in the filesystem. Creates parent directories automatically if they don't exist. Overwrites existing files. Path must be absolute. Writing outside the working directory requires allowOutsideWorkingDirectory: true.",
|
|
83
|
+
|
|
84
|
+
inputSchema: writeFileSchema,
|
|
85
|
+
|
|
86
|
+
execute: async ({ path, content, description: _description, allowOutsideWorkingDirectory }: { path: string; content: string; description: string; allowOutsideWorkingDirectory?: boolean }) => {
|
|
87
|
+
try {
|
|
88
|
+
return await executeWriteFile(path, content, context.workingDirectory, context.agent.pubkey, allowOutsideWorkingDirectory);
|
|
89
|
+
} catch (error: unknown) {
|
|
90
|
+
// Expected errors (permission denied, etc.) return error-text
|
|
91
|
+
// This ensures the error is properly communicated to the LLM without stream failures
|
|
92
|
+
if (isExpectedFsError(error)) {
|
|
93
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
94
|
+
const description = getFsErrorDescription(code);
|
|
95
|
+
return createExpectedError(`${description}: ${path}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Unexpected errors still throw (they'll be caught by the SDK)
|
|
99
|
+
throw new Error(`Failed to write ${path}: ${formatAnyError(error)}`, { cause: error });
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
Object.defineProperty(toolInstance, "getHumanReadableContent", {
|
|
105
|
+
value: ({ path, description }: { path: string; description: string }) => {
|
|
106
|
+
return `Writing ${path} (${description})`;
|
|
107
|
+
},
|
|
108
|
+
enumerable: false,
|
|
109
|
+
configurable: true,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return toolInstance as AISdkTool;
|
|
113
|
+
}
|