@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,209 @@
|
|
|
1
|
+
import type { ToolExecutionContext } from "@/tools/types";
|
|
2
|
+
import { agentStorage } from "@/agents/AgentStorage";
|
|
3
|
+
import { getDaemon } from "@/daemon";
|
|
4
|
+
import { getNDK } from "@/nostr/ndkClient";
|
|
5
|
+
import { PendingDelegationsRegistry, RALRegistry } from "@/services/ral";
|
|
6
|
+
import type { PendingDelegation } from "@/services/ral/types";
|
|
7
|
+
import type { AISdkTool } from "@/tools/types";
|
|
8
|
+
import { shortenConversationId } from "@/utils/conversation-id";
|
|
9
|
+
import { logger } from "@/utils/logger";
|
|
10
|
+
import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
|
|
11
|
+
import { tool } from "ai";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
|
|
14
|
+
const delegateCrossProjectSchema = z.object({
|
|
15
|
+
content: z.string().describe("The content of the chat message to send"),
|
|
16
|
+
projectId: z
|
|
17
|
+
.string()
|
|
18
|
+
.describe(
|
|
19
|
+
"The project ID (dTag) to delegate to. Use project_list to discover available projects."
|
|
20
|
+
),
|
|
21
|
+
agentSlug: z
|
|
22
|
+
.string()
|
|
23
|
+
.describe(
|
|
24
|
+
"The slug of the agent within the target project to delegate to. Use project_list to see available agents."
|
|
25
|
+
),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
type DelegateCrossProjectInput = z.infer<typeof delegateCrossProjectSchema>;
|
|
29
|
+
|
|
30
|
+
interface DelegateCrossProjectOutput {
|
|
31
|
+
success: boolean;
|
|
32
|
+
message: string;
|
|
33
|
+
delegationConversationId: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Check if the agent has created a todo list.
|
|
38
|
+
* Returns { hasTodos: boolean, hasConversation: boolean } to distinguish
|
|
39
|
+
* between "no todos" and "no conversation context" cases.
|
|
40
|
+
*/
|
|
41
|
+
function checkTodoState(context: ToolExecutionContext): { hasTodos: boolean; hasConversation: boolean } {
|
|
42
|
+
const conversation = context.getConversation();
|
|
43
|
+
if (!conversation) {
|
|
44
|
+
// No conversation context available - skip enforcement
|
|
45
|
+
return { hasTodos: true, hasConversation: false };
|
|
46
|
+
}
|
|
47
|
+
const todos = conversation.getTodos(context.agent.pubkey);
|
|
48
|
+
return { hasTodos: todos.length > 0, hasConversation: true };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function executeDelegateCrossProject(
|
|
52
|
+
input: DelegateCrossProjectInput,
|
|
53
|
+
context: ToolExecutionContext
|
|
54
|
+
): Promise<DelegateCrossProjectOutput> {
|
|
55
|
+
const { content, projectId, agentSlug } = input;
|
|
56
|
+
|
|
57
|
+
// ENFORCEMENT: Delegation requires a todo list
|
|
58
|
+
// Skip enforcement if no conversation context (e.g., MCP-only mode)
|
|
59
|
+
const todoState = checkTodoState(context);
|
|
60
|
+
if (todoState.hasConversation && !todoState.hasTodos) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
"Delegation requires a todo list. Please use `todo_write()` to create a todo list before delegating tasks. " +
|
|
63
|
+
"This helps track work progress and ensures delegated tasks are properly documented."
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Get known projects from daemon
|
|
68
|
+
const daemon = getDaemon();
|
|
69
|
+
const knownProjects = daemon.getKnownProjects();
|
|
70
|
+
const activeRuntimes = daemon.getActiveRuntimes();
|
|
71
|
+
|
|
72
|
+
// Find project by id (dTag) - projectId in knownProjects is "31933:pubkey:dTag"
|
|
73
|
+
let project = null;
|
|
74
|
+
let fullProjectId = "";
|
|
75
|
+
for (const [pId, p] of knownProjects) {
|
|
76
|
+
const dTag = pId.split(":")[2];
|
|
77
|
+
if (dTag === projectId) {
|
|
78
|
+
project = p;
|
|
79
|
+
fullProjectId = pId;
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!project) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`Project '${projectId}' not found. Use project_list to see available projects.`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Find agent pubkey
|
|
91
|
+
let agentPubkey: string | null = null;
|
|
92
|
+
|
|
93
|
+
// Try runtime first (if project is running)
|
|
94
|
+
const runtime = activeRuntimes.get(fullProjectId);
|
|
95
|
+
if (runtime) {
|
|
96
|
+
const runtimeContext = runtime.getContext();
|
|
97
|
+
const agentMap = runtimeContext?.agentRegistry.getAllAgentsMap();
|
|
98
|
+
if (agentMap) {
|
|
99
|
+
for (const agent of agentMap.values()) {
|
|
100
|
+
if (agent.slug === agentSlug) {
|
|
101
|
+
agentPubkey = agent.pubkey;
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Fall back to storage
|
|
109
|
+
if (!agentPubkey) {
|
|
110
|
+
const agents = await agentStorage.getProjectAgents(projectId);
|
|
111
|
+
const agent = agents.find((a) => a.slug === agentSlug);
|
|
112
|
+
if (agent) {
|
|
113
|
+
const signer = new NDKPrivateKeySigner(agent.nsec);
|
|
114
|
+
agentPubkey = signer.pubkey;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!agentPubkey) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`Agent '${agentSlug}' not found in project '${projectId}'. Use project_list to see available agents.`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const ndk = getNDK();
|
|
125
|
+
|
|
126
|
+
logger.info("[delegate_crossproject] Publishing cross-project delegation", {
|
|
127
|
+
agent: context.agent.name,
|
|
128
|
+
targetProject: projectId,
|
|
129
|
+
targetAgent: agentSlug,
|
|
130
|
+
recipientPubkey: agentPubkey.substring(0, 8),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Create delegation event
|
|
134
|
+
const chatEvent = new NDKEvent(ndk);
|
|
135
|
+
chatEvent.kind = 1;
|
|
136
|
+
chatEvent.content = content;
|
|
137
|
+
chatEvent.tags.push(["p", agentPubkey]);
|
|
138
|
+
// Add "a" tag referencing the target project
|
|
139
|
+
chatEvent.tags.push(["a", `31933:${project.pubkey}:${projectId}`]);
|
|
140
|
+
|
|
141
|
+
await context.agent.sign(chatEvent);
|
|
142
|
+
await chatEvent.publish();
|
|
143
|
+
|
|
144
|
+
// Register with PendingDelegationsRegistry for q-tag correlation
|
|
145
|
+
PendingDelegationsRegistry.register(context.agent.pubkey, context.conversationId, chatEvent.id);
|
|
146
|
+
|
|
147
|
+
// Register pending delegation in RALRegistry for response routing
|
|
148
|
+
// Uses atomic merge to safely handle concurrent delegation calls
|
|
149
|
+
const ralRegistry = RALRegistry.getInstance();
|
|
150
|
+
const newDelegation: PendingDelegation = {
|
|
151
|
+
type: "external" as const,
|
|
152
|
+
delegationConversationId: chatEvent.id,
|
|
153
|
+
recipientPubkey: agentPubkey,
|
|
154
|
+
senderPubkey: context.agent.pubkey,
|
|
155
|
+
prompt: content,
|
|
156
|
+
projectId: fullProjectId,
|
|
157
|
+
ralNumber: context.ralNumber,
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
ralRegistry.mergePendingDelegations(
|
|
161
|
+
context.agent.pubkey,
|
|
162
|
+
context.conversationId,
|
|
163
|
+
context.ralNumber,
|
|
164
|
+
[newDelegation]
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
logger.info("[delegate_crossproject] Delegation registered, agent continues without blocking", {
|
|
168
|
+
delegationConversationId: chatEvent.id,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Return normal result - agent continues without blocking
|
|
172
|
+
return {
|
|
173
|
+
success: true,
|
|
174
|
+
message: `Delegated to agent '${agentSlug}' in project '${projectId}'. The agent will respond when ready.`,
|
|
175
|
+
delegationConversationId: shortenConversationId(chatEvent.id),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function createDelegateCrossProjectTool(context: ToolExecutionContext): AISdkTool {
|
|
180
|
+
const aiTool = tool({
|
|
181
|
+
description: `Delegate a task to an agent in another project. Use project_list first to discover available projects and their agents.
|
|
182
|
+
|
|
183
|
+
When using this tool, provide context to the recipient, introduce yourself and explain you are an agent and the project you are working on.`,
|
|
184
|
+
inputSchema: delegateCrossProjectSchema,
|
|
185
|
+
execute: async (input: DelegateCrossProjectInput) => {
|
|
186
|
+
return await executeDelegateCrossProject(input, context);
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
Object.defineProperty(aiTool, "getHumanReadableContent", {
|
|
191
|
+
value: (args: unknown) => {
|
|
192
|
+
if (!args || typeof args !== "object") {
|
|
193
|
+
return "Delegating to cross-project agent";
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const { projectId, agentSlug } = args as Partial<DelegateCrossProjectInput>;
|
|
197
|
+
|
|
198
|
+
if (!projectId || !agentSlug) {
|
|
199
|
+
return "Delegating to cross-project agent";
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return `Delegating to agent '${agentSlug}' in project '${projectId}'`;
|
|
203
|
+
},
|
|
204
|
+
enumerable: false,
|
|
205
|
+
configurable: true,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
return aiTool as AISdkTool;
|
|
209
|
+
}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import type { ToolExecutionContext } from "@/tools/types";
|
|
2
|
+
import { getNDK } from "@/nostr";
|
|
3
|
+
import { RALRegistry } from "@/services/ral/RALRegistry";
|
|
4
|
+
import type { AISdkTool } from "@/tools/types";
|
|
5
|
+
import { shortenConversationId } from "@/utils/conversation-id";
|
|
6
|
+
import { logger } from "@/utils/logger";
|
|
7
|
+
import { isHexPrefix, resolvePrefixToId, PREFIX_LENGTH } from "@/utils/nostr-entity-parser";
|
|
8
|
+
import { createEventContext } from "@/services/event-context";
|
|
9
|
+
import { tool } from "ai";
|
|
10
|
+
import { nip19 } from "nostr-tools";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Attempts to decode a NIP-19 event ID format to a hex event ID.
|
|
15
|
+
* Supports: note1..., nevent1..., with or without 'nostr:' prefix.
|
|
16
|
+
*
|
|
17
|
+
* @param input - A potential NIP-19 event ID
|
|
18
|
+
* @returns The decoded 64-char hex event ID, or null if not a valid NIP-19 event format
|
|
19
|
+
*/
|
|
20
|
+
function decodeNip19EventId(input: string): string | null {
|
|
21
|
+
try {
|
|
22
|
+
// Strip nostr: prefix if present
|
|
23
|
+
let cleaned = input.trim();
|
|
24
|
+
if (cleaned.toLowerCase().startsWith("nostr:")) {
|
|
25
|
+
cleaned = cleaned.substring(6);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Only attempt decode for note1 or nevent1 prefixes
|
|
29
|
+
if (!cleaned.startsWith("note1") && !cleaned.startsWith("nevent1")) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const decoded = nip19.decode(cleaned);
|
|
34
|
+
|
|
35
|
+
if (decoded.type === "note") {
|
|
36
|
+
return (decoded.data as string).toLowerCase();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (decoded.type === "nevent") {
|
|
40
|
+
return (decoded.data as { id: string }).id.toLowerCase();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Other NIP-19 types (npub, nprofile, naddr) are not valid event IDs
|
|
44
|
+
return null;
|
|
45
|
+
} catch {
|
|
46
|
+
// Not a valid NIP-19 format
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Checks if a string is a 64-character hex ID.
|
|
53
|
+
*/
|
|
54
|
+
function isFullHexId(input: string): boolean {
|
|
55
|
+
return /^[0-9a-fA-F]{64}$/.test(input.trim());
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Fallback resolver for 12-char hex prefixes when PrefixKVStore is not initialized.
|
|
60
|
+
*
|
|
61
|
+
* This handles edge cases where:
|
|
62
|
+
* 1. MCP-only execution mode - PrefixKVStore may not be initialized in pure MCP contexts
|
|
63
|
+
* 2. Timing races with event indexing - the event may exist in RAL but not yet indexed in KV store
|
|
64
|
+
*
|
|
65
|
+
* Scans RALRegistry.pending and RALRegistry.completed for matching delegation conversation IDs.
|
|
66
|
+
* Returns the full 64-char canonical delegation ID if a unique match is found, null otherwise.
|
|
67
|
+
* Note: Unlike PrefixKVStore, this fallback already canonicalizes followup IDs.
|
|
68
|
+
*
|
|
69
|
+
* @param prefix - 12-character hex prefix to resolve
|
|
70
|
+
* @param ralRegistry - RALRegistry instance to scan
|
|
71
|
+
* @returns Full 64-char canonical delegation ID if unique match found, null otherwise
|
|
72
|
+
*/
|
|
73
|
+
function resolveFromRALFallback(prefix: string, ralRegistry: RALRegistry): string | null {
|
|
74
|
+
const normalizedPrefix = prefix.toLowerCase();
|
|
75
|
+
return ralRegistry.resolveDelegationPrefix(normalizedPrefix);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Attempts to resolve a 12-char hex prefix to a full delegation conversation ID.
|
|
80
|
+
* Uses PrefixKVStore first, falls back to RALRegistry scan if needed.
|
|
81
|
+
*
|
|
82
|
+
* IMPORTANT: This function always returns the canonical delegation conversation ID.
|
|
83
|
+
* When PrefixKVStore resolves a followup event ID prefix, the result is canonicalized
|
|
84
|
+
* to the original delegation conversation ID. This ensures consistent behavior across
|
|
85
|
+
* daemon mode (PrefixKVStore available) and MCP-only mode (RAL fallback only).
|
|
86
|
+
*
|
|
87
|
+
* @param prefix - 12-character hex prefix to resolve
|
|
88
|
+
* @returns Full 64-char canonical delegation ID or null if not found
|
|
89
|
+
*/
|
|
90
|
+
function resolveDelegationPrefix(prefix: string): string | null {
|
|
91
|
+
const ralRegistry = RALRegistry.getInstance();
|
|
92
|
+
|
|
93
|
+
// Try PrefixKVStore first (primary resolution path)
|
|
94
|
+
const resolved = resolvePrefixToId(prefix);
|
|
95
|
+
if (resolved) {
|
|
96
|
+
// Post-resolution canonicalization: PrefixKVStore may return a followup event ID
|
|
97
|
+
// when the user provides a followup ID prefix. Canonicalize to the original
|
|
98
|
+
// delegation conversation ID for consistent e-tag and routing behavior.
|
|
99
|
+
const canonicalized = ralRegistry.canonicalizeDelegationId(resolved);
|
|
100
|
+
if (canonicalized !== resolved) {
|
|
101
|
+
logger.info("[delegate_followup] Canonicalized followup ID from PrefixKVStore", {
|
|
102
|
+
followupId: resolved.substring(0, PREFIX_LENGTH),
|
|
103
|
+
canonicalId: canonicalized.substring(0, PREFIX_LENGTH),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return canonicalized;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Fallback: scan RALRegistry for matching delegation conversation IDs
|
|
110
|
+
// This handles MCP-only execution mode and timing races with event indexing
|
|
111
|
+
// Note: RAL fallback already canonicalizes followup IDs internally
|
|
112
|
+
const fallbackResolved = resolveFromRALFallback(prefix, ralRegistry);
|
|
113
|
+
|
|
114
|
+
if (fallbackResolved) {
|
|
115
|
+
// Use info level - MCP-only execution is an expected deployment mode, not a warning condition.
|
|
116
|
+
// PrefixKVStore may intentionally not be initialized in pure MCP contexts.
|
|
117
|
+
logger.info("[delegate_followup] Resolved prefix via RAL fallback", {
|
|
118
|
+
prefix: prefix.substring(0, PREFIX_LENGTH),
|
|
119
|
+
resolvedId: fallbackResolved.substring(0, PREFIX_LENGTH),
|
|
120
|
+
});
|
|
121
|
+
return fallbackResolved;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const delegateFollowupSchema = z.object({
|
|
128
|
+
delegation_conversation_id: z
|
|
129
|
+
.string()
|
|
130
|
+
.describe(
|
|
131
|
+
"The ID of the delegation to follow up on. Accepts: delegationConversationId (from delegate response), " +
|
|
132
|
+
"followupEventId (from delegate_followup response), full 64-char hex, 12-char prefix, or NIP-19 formats " +
|
|
133
|
+
"(note1..., nevent1...) with or without 'nostr:' prefix. Followup IDs are automatically canonicalized " +
|
|
134
|
+
"to the original delegation conversation ID."
|
|
135
|
+
),
|
|
136
|
+
message: z.string().describe("Your follow-up question or clarification request"),
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
type DelegateFollowupInput = z.infer<typeof delegateFollowupSchema>;
|
|
140
|
+
|
|
141
|
+
interface DelegateFollowupOutput {
|
|
142
|
+
success: boolean;
|
|
143
|
+
message: string;
|
|
144
|
+
delegationConversationId: string;
|
|
145
|
+
followupEventId: string;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function executeDelegateFollowup(
|
|
149
|
+
input: DelegateFollowupInput,
|
|
150
|
+
context: ToolExecutionContext
|
|
151
|
+
): Promise<DelegateFollowupOutput> {
|
|
152
|
+
const { delegation_conversation_id: inputConversationId, message } = input;
|
|
153
|
+
|
|
154
|
+
// Resolve input to full canonical delegation conversation ID.
|
|
155
|
+
// Handles all input formats: 12-char prefixes, full 64-char hex, NIP-19 formats.
|
|
156
|
+
// All formats are canonicalized to the original delegation conversation ID.
|
|
157
|
+
const ralRegistry = RALRegistry.getInstance();
|
|
158
|
+
let delegation_conversation_id = inputConversationId;
|
|
159
|
+
|
|
160
|
+
// Step 1: Handle 12-char hex prefix resolution
|
|
161
|
+
if (isHexPrefix(inputConversationId)) {
|
|
162
|
+
const resolved = resolveDelegationPrefix(inputConversationId);
|
|
163
|
+
if (!resolved) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`Could not resolve prefix "${inputConversationId}" to a delegation. Valid inputs include: ` +
|
|
166
|
+
"delegationConversationId (from delegate response), followupEventId (from delegate_followup response), " +
|
|
167
|
+
"full 64-char hex IDs, 12-char prefixes, or NIP-19 formats (note1..., nevent1...) with or without 'nostr:' prefix. " +
|
|
168
|
+
"The prefix may be ambiguous or no matching delegation was found."
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
delegation_conversation_id = resolved;
|
|
172
|
+
}
|
|
173
|
+
// Step 2: Handle NIP-19 formats (nostr:nevent1..., note1..., etc.)
|
|
174
|
+
else {
|
|
175
|
+
const decodedNip19 = decodeNip19EventId(inputConversationId);
|
|
176
|
+
if (decodedNip19) {
|
|
177
|
+
// Successfully decoded NIP-19 to hex, now canonicalize
|
|
178
|
+
const canonicalized = ralRegistry.canonicalizeDelegationId(decodedNip19);
|
|
179
|
+
if (canonicalized !== decodedNip19) {
|
|
180
|
+
logger.info("[delegate_followup] Canonicalized NIP-19 followup ID", {
|
|
181
|
+
inputFormat: inputConversationId.substring(0, 20) + "...",
|
|
182
|
+
decodedHex: decodedNip19.substring(0, PREFIX_LENGTH),
|
|
183
|
+
canonicalId: canonicalized.substring(0, PREFIX_LENGTH),
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
delegation_conversation_id = canonicalized;
|
|
187
|
+
}
|
|
188
|
+
// Step 3: Handle full 64-char hex IDs that might be followup event IDs
|
|
189
|
+
else if (isFullHexId(inputConversationId)) {
|
|
190
|
+
const normalized = inputConversationId.toLowerCase();
|
|
191
|
+
const canonicalized = ralRegistry.canonicalizeDelegationId(normalized);
|
|
192
|
+
if (canonicalized !== normalized) {
|
|
193
|
+
logger.info("[delegate_followup] Canonicalized full hex followup ID", {
|
|
194
|
+
followupId: normalized.substring(0, PREFIX_LENGTH),
|
|
195
|
+
canonicalId: canonicalized.substring(0, PREFIX_LENGTH),
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
delegation_conversation_id = canonicalized;
|
|
199
|
+
}
|
|
200
|
+
// Step 4: Unknown format - pass through unchanged with debug hint
|
|
201
|
+
else {
|
|
202
|
+
logger.debug("[delegate_followup] Unknown input format, using as-is", {
|
|
203
|
+
input: inputConversationId.substring(0, 20),
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Find the delegation in conversation storage (persists even after RAL is cleared)
|
|
209
|
+
const delegationInfo = ralRegistry.findDelegation(delegation_conversation_id);
|
|
210
|
+
|
|
211
|
+
let recipientPubkey = delegationInfo?.pending?.recipientPubkey ?? delegationInfo?.completed?.recipientPubkey;
|
|
212
|
+
|
|
213
|
+
// Fall back to NDK fetch if not found locally (e.g., external delegations or stale state)
|
|
214
|
+
if (!recipientPubkey) {
|
|
215
|
+
const ndk = getNDK();
|
|
216
|
+
const delegationEvent = await ndk.fetchEvent(delegation_conversation_id);
|
|
217
|
+
|
|
218
|
+
if (!delegationEvent) {
|
|
219
|
+
throw new Error(
|
|
220
|
+
`Could not fetch delegation conversation ${delegation_conversation_id}. Check the delegationConversationIds from your delegate call.`
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
recipientPubkey = delegationEvent.tagValue("p") ?? undefined;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (!recipientPubkey) {
|
|
228
|
+
throw new Error(
|
|
229
|
+
`Delegation conversation ${delegation_conversation_id} has no recipient. Cannot determine who to send follow-up to.`
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Always use the CURRENT RAL number from context.
|
|
234
|
+
// The delegation's stored ralNumber refers to the RAL that created it, which may have
|
|
235
|
+
// been cleared since then. We need to register on the CURRENT RAL so it resumes correctly.
|
|
236
|
+
const effectiveRalNumber = context.ralNumber;
|
|
237
|
+
|
|
238
|
+
logger.info("[delegate_followup] Publishing follow-up", {
|
|
239
|
+
fromAgent: context.agent.slug,
|
|
240
|
+
delegationConversationId: delegation_conversation_id,
|
|
241
|
+
recipientPubkey: recipientPubkey.substring(0, 8),
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const eventContext = createEventContext(context);
|
|
245
|
+
const followupEventId = await context.agentPublisher.delegateFollowup({
|
|
246
|
+
recipient: recipientPubkey,
|
|
247
|
+
content: message,
|
|
248
|
+
delegationEventId: delegation_conversation_id,
|
|
249
|
+
}, eventContext);
|
|
250
|
+
|
|
251
|
+
// Register the followup as a pending delegation for response routing
|
|
252
|
+
// Use atomic merge to safely handle concurrent delegation calls
|
|
253
|
+
// Note: followup delegations use the same delegationConversationId as the original,
|
|
254
|
+
// but include the followupEventId for routing responses to the new event
|
|
255
|
+
const newDelegation = {
|
|
256
|
+
type: "followup" as const,
|
|
257
|
+
delegationConversationId: delegation_conversation_id,
|
|
258
|
+
recipientPubkey,
|
|
259
|
+
senderPubkey: context.agent.pubkey,
|
|
260
|
+
prompt: message,
|
|
261
|
+
followupEventId,
|
|
262
|
+
ralNumber: effectiveRalNumber,
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
// Use atomic merge - this handles concurrent updates safely and merges
|
|
266
|
+
// the followupEventId into existing entries instead of dropping them
|
|
267
|
+
ralRegistry.mergePendingDelegations(
|
|
268
|
+
context.agent.pubkey,
|
|
269
|
+
context.conversationId,
|
|
270
|
+
effectiveRalNumber,
|
|
271
|
+
[newDelegation]
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
// Return normal result - agent continues without blocking
|
|
275
|
+
return {
|
|
276
|
+
success: true,
|
|
277
|
+
message: "Follow-up sent. The agent will respond when ready.",
|
|
278
|
+
delegationConversationId: shortenConversationId(delegation_conversation_id),
|
|
279
|
+
followupEventId, // Keep full event ID - this is a Nostr event ID, not a conversation ID
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export function createDelegateFollowupTool(context: ToolExecutionContext): AISdkTool {
|
|
284
|
+
const aiTool = tool({
|
|
285
|
+
description:
|
|
286
|
+
"Send a follow-up question to an agent you previously delegated to. Use after delegate to ask clarifying questions about their response.",
|
|
287
|
+
inputSchema: delegateFollowupSchema,
|
|
288
|
+
execute: async (input: DelegateFollowupInput) => {
|
|
289
|
+
return await executeDelegateFollowup(input, context);
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
Object.defineProperty(aiTool, "getHumanReadableContent", {
|
|
294
|
+
value: () => "Sending follow-up question",
|
|
295
|
+
enumerable: false,
|
|
296
|
+
configurable: true,
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
return aiTool as AISdkTool;
|
|
300
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import type { AISdkTool, ToolExecutionContext } from "@/tools/types";
|
|
3
|
+
import { isPathWithinDirectory, isWithinAgentHome } from "@/lib/agent-home";
|
|
4
|
+
import { formatAnyError } from "@/lib/error-formatter";
|
|
5
|
+
import {
|
|
6
|
+
createExpectedError,
|
|
7
|
+
type ExpectedErrorResult,
|
|
8
|
+
getFsErrorDescription,
|
|
9
|
+
isExpectedFsError,
|
|
10
|
+
} from "@/tools/utils";
|
|
11
|
+
import { tool } from "ai";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
|
|
14
|
+
const editSchema = z.object({
|
|
15
|
+
path: z
|
|
16
|
+
.string()
|
|
17
|
+
.describe("The absolute path to the file to edit"),
|
|
18
|
+
description: z
|
|
19
|
+
.string()
|
|
20
|
+
.min(1, "Description is required and cannot be empty")
|
|
21
|
+
.describe(
|
|
22
|
+
"REQUIRED: A clear, concise description of why you're editing this file (5-10 words). Helps provide human-readable context for the operation."
|
|
23
|
+
),
|
|
24
|
+
old_string: z.string().describe("The exact text to replace"),
|
|
25
|
+
new_string: z.string().describe("The text to replace it with (must be different from old_string)"),
|
|
26
|
+
replace_all: z
|
|
27
|
+
.boolean()
|
|
28
|
+
.optional()
|
|
29
|
+
.default(false)
|
|
30
|
+
.describe("Replace all occurrences of old_string (default false)"),
|
|
31
|
+
allowOutsideWorkingDirectory: z
|
|
32
|
+
.boolean()
|
|
33
|
+
.optional()
|
|
34
|
+
.describe("Set to true to edit files outside the working directory. Required when path is not within the project."),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Core implementation of the edit functionality
|
|
39
|
+
*/
|
|
40
|
+
async function executeEdit(
|
|
41
|
+
path: string,
|
|
42
|
+
oldString: string,
|
|
43
|
+
newString: string,
|
|
44
|
+
replaceAll: boolean,
|
|
45
|
+
workingDirectory: string,
|
|
46
|
+
agentPubkey: string,
|
|
47
|
+
allowOutsideWorkingDirectory?: boolean,
|
|
48
|
+
): Promise<string | ExpectedErrorResult> {
|
|
49
|
+
if (!path.startsWith("/")) {
|
|
50
|
+
throw new Error(`Path must be absolute, got: ${path}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Check if path is within working directory (using secure path normalization)
|
|
54
|
+
const isWithinWorkDir = isPathWithinDirectory(path, workingDirectory);
|
|
55
|
+
|
|
56
|
+
// Always allow access to agent's home directory without requiring allowOutsideWorkingDirectory
|
|
57
|
+
const isInAgentHome = isWithinAgentHome(path, agentPubkey);
|
|
58
|
+
|
|
59
|
+
if (!isWithinWorkDir && !isInAgentHome && !allowOutsideWorkingDirectory) {
|
|
60
|
+
return `Path "${path}" is outside your working directory "${workingDirectory}". If this was intentional, retry with allowOutsideWorkingDirectory: true`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Read the file
|
|
64
|
+
const content = await readFile(path, "utf-8");
|
|
65
|
+
|
|
66
|
+
// Check if old_string exists - return as expected error since this is user input validation
|
|
67
|
+
if (!content.includes(oldString)) {
|
|
68
|
+
return createExpectedError(
|
|
69
|
+
`old_string not found in ${path}. Make sure you're using the exact string from the file.`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let newContent: string;
|
|
74
|
+
let replacementCount: number;
|
|
75
|
+
|
|
76
|
+
if (replaceAll) {
|
|
77
|
+
// Replace all occurrences
|
|
78
|
+
const regex = new RegExp(oldString.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g");
|
|
79
|
+
newContent = content.replace(regex, newString);
|
|
80
|
+
replacementCount = (content.match(regex) || []).length;
|
|
81
|
+
} else {
|
|
82
|
+
// Check for uniqueness
|
|
83
|
+
const firstIndex = content.indexOf(oldString);
|
|
84
|
+
const lastIndex = content.lastIndexOf(oldString);
|
|
85
|
+
|
|
86
|
+
// Multiple matches - return as expected error since this is user input validation
|
|
87
|
+
if (firstIndex !== lastIndex) {
|
|
88
|
+
return createExpectedError(
|
|
89
|
+
`old_string appears multiple times in ${path}. Either provide a larger string with more surrounding context to make it unique or use replace_all to change every instance.`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Replace single occurrence
|
|
94
|
+
newContent = content.replace(oldString, newString);
|
|
95
|
+
replacementCount = 1;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Write the file
|
|
99
|
+
await writeFile(path, newContent, "utf-8");
|
|
100
|
+
|
|
101
|
+
return `Successfully replaced ${replacementCount} occurrence(s) in ${path}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Create an AI SDK tool for editing files
|
|
106
|
+
*/
|
|
107
|
+
export function createFsEditTool(context: ToolExecutionContext): AISdkTool {
|
|
108
|
+
const toolInstance = tool({
|
|
109
|
+
description:
|
|
110
|
+
"Performs exact string replacements in files. The edit will FAIL if old_string is not unique in the file. Either provide a larger string with more surrounding context to make it unique or use replace_all to change every instance of old_string. Path must be absolute. Editing outside the working directory requires allowOutsideWorkingDirectory: true.",
|
|
111
|
+
|
|
112
|
+
inputSchema: editSchema,
|
|
113
|
+
|
|
114
|
+
execute: async ({
|
|
115
|
+
path,
|
|
116
|
+
description: _description,
|
|
117
|
+
old_string,
|
|
118
|
+
new_string,
|
|
119
|
+
replace_all = false,
|
|
120
|
+
allowOutsideWorkingDirectory,
|
|
121
|
+
}: {
|
|
122
|
+
path: string;
|
|
123
|
+
description: string;
|
|
124
|
+
old_string: string;
|
|
125
|
+
new_string: string;
|
|
126
|
+
replace_all?: boolean;
|
|
127
|
+
allowOutsideWorkingDirectory?: boolean;
|
|
128
|
+
}) => {
|
|
129
|
+
try {
|
|
130
|
+
// Validate input - same strings is an expected user error
|
|
131
|
+
if (old_string === new_string) {
|
|
132
|
+
return createExpectedError("old_string and new_string must be different");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const result = await executeEdit(path, old_string, new_string, replace_all, context.workingDirectory, context.agent.pubkey, allowOutsideWorkingDirectory);
|
|
136
|
+
// executeEdit may return an ExpectedErrorResult for validation errors
|
|
137
|
+
return result;
|
|
138
|
+
} catch (error: unknown) {
|
|
139
|
+
// Expected errors (file not found, permission denied, etc.) return error-text
|
|
140
|
+
// This ensures the error is properly communicated to the LLM without stream failures
|
|
141
|
+
if (isExpectedFsError(error)) {
|
|
142
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
143
|
+
const description = getFsErrorDescription(code);
|
|
144
|
+
return createExpectedError(`${description}: ${path}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Unexpected errors still throw (they'll be caught by the SDK)
|
|
148
|
+
throw new Error(`Failed to edit ${path}: ${formatAnyError(error)}`, { cause: error });
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
Object.defineProperty(toolInstance, "getHumanReadableContent", {
|
|
154
|
+
value: ({ path, description }: { path: string; description: string }) => {
|
|
155
|
+
return `Editing ${path} (${description})`;
|
|
156
|
+
},
|
|
157
|
+
enumerable: false,
|
|
158
|
+
configurable: true,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
return toolInstance as AISdkTool;
|
|
162
|
+
}
|