@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,370 @@
|
|
|
1
|
+
import type { ToolExecutionContext } from "@/tools/types";
|
|
2
|
+
import { conversationRegistry } from "@/conversations/ConversationRegistry";
|
|
3
|
+
import { ConversationStore } from "@/conversations/ConversationStore";
|
|
4
|
+
import type { MessageMatch } from "@/conversations/search";
|
|
5
|
+
import type { AISdkTool } from "@/tools/types";
|
|
6
|
+
import { logger } from "@/utils/logger";
|
|
7
|
+
import { tool } from "ai";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import {
|
|
10
|
+
getConversationEmbeddingService,
|
|
11
|
+
type SemanticSearchResult,
|
|
12
|
+
} from "@/conversations/search/embeddings";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Search mode for conversation search
|
|
16
|
+
* - "keyword": Traditional title-based substring matching (fast, exact)
|
|
17
|
+
* - "semantic": Natural language semantic search using embeddings (slower, understands meaning)
|
|
18
|
+
* - "hybrid": Combines both approaches, semantic results first, then keyword fallback
|
|
19
|
+
* - "full-text": Full-text search across all message content (advanced)
|
|
20
|
+
*/
|
|
21
|
+
const SearchMode = z.enum(["keyword", "semantic", "hybrid", "full-text"]).describe(
|
|
22
|
+
"Search mode: 'keyword' (fast title matching), 'semantic' (natural language), 'hybrid' (both), or 'full-text' (all messages)"
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const conversationSearchSchema = z.object({
|
|
26
|
+
query: z.string().describe(
|
|
27
|
+
"Search query. For keyword mode: matches against conversation titles. " +
|
|
28
|
+
"For semantic mode: natural language query like 'conversations about debugging memory issues'. " +
|
|
29
|
+
"For full-text mode: matches against all message content and titles."
|
|
30
|
+
),
|
|
31
|
+
mode: SearchMode.optional().describe(
|
|
32
|
+
"Search mode: 'keyword' (default, fast), 'semantic' (natural language), 'hybrid' (both), or 'full-text' (comprehensive)"
|
|
33
|
+
),
|
|
34
|
+
filters: z
|
|
35
|
+
.object({
|
|
36
|
+
agents: z
|
|
37
|
+
.array(z.string())
|
|
38
|
+
.optional()
|
|
39
|
+
.describe("Filter by agent slugs or pubkeys - conversation must include at least one (exact match)"),
|
|
40
|
+
since: z
|
|
41
|
+
.union([z.string(), z.number()])
|
|
42
|
+
.optional()
|
|
43
|
+
.describe("Return conversations with activity since this date (Unix timestamp or ISO 8601)"),
|
|
44
|
+
after: z
|
|
45
|
+
.union([z.string(), z.number()])
|
|
46
|
+
.optional()
|
|
47
|
+
.describe("Alias for 'since' - return conversations with activity after this date"),
|
|
48
|
+
})
|
|
49
|
+
.optional()
|
|
50
|
+
.describe("Optional filters to narrow search results"),
|
|
51
|
+
limit: z
|
|
52
|
+
.number()
|
|
53
|
+
.optional()
|
|
54
|
+
.default(20)
|
|
55
|
+
.describe("Maximum number of results to return. Defaults to 20."),
|
|
56
|
+
minScore: z
|
|
57
|
+
.number()
|
|
58
|
+
.optional()
|
|
59
|
+
.describe("Minimum relevance score for semantic results (0-1). Defaults to 0.3."),
|
|
60
|
+
projectId: z
|
|
61
|
+
.string()
|
|
62
|
+
.optional()
|
|
63
|
+
.describe("Filter by project ID. Use 'ALL' for all projects."),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
type ConversationSearchInput = z.infer<typeof conversationSearchSchema>;
|
|
67
|
+
|
|
68
|
+
interface ConversationSearchResult {
|
|
69
|
+
id: string;
|
|
70
|
+
projectId?: string;
|
|
71
|
+
title?: string;
|
|
72
|
+
summary?: string;
|
|
73
|
+
messageCount: number;
|
|
74
|
+
createdAt?: number;
|
|
75
|
+
lastActivity?: number;
|
|
76
|
+
matches?: MessageMatch[];
|
|
77
|
+
relevanceScore?: number; // Only present for semantic/hybrid results
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface ConversationSearchOutput {
|
|
81
|
+
success: boolean;
|
|
82
|
+
conversations: ConversationSearchResult[];
|
|
83
|
+
total: number;
|
|
84
|
+
query: string;
|
|
85
|
+
mode: string;
|
|
86
|
+
searchType?: "full-text" | "title-only" | "semantic" | "hybrid";
|
|
87
|
+
semanticAvailable: boolean;
|
|
88
|
+
error?: string;
|
|
89
|
+
warning?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Fallback to legacy title-only search using in-memory stores.
|
|
94
|
+
*/
|
|
95
|
+
function legacyTitleSearch(query: string, limit: number): ConversationSearchResult[] {
|
|
96
|
+
const stores = ConversationStore.search(query);
|
|
97
|
+
return stores.slice(0, limit).map((store) => {
|
|
98
|
+
const messages = store.getAllMessages();
|
|
99
|
+
const firstMessage = messages[0];
|
|
100
|
+
const lastMessage = messages[messages.length - 1];
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
id: store.id,
|
|
104
|
+
title: store.title,
|
|
105
|
+
messageCount: messages.length,
|
|
106
|
+
createdAt: firstMessage?.timestamp,
|
|
107
|
+
lastActivity: lastMessage?.timestamp,
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Perform keyword-based search on conversation titles
|
|
114
|
+
*/
|
|
115
|
+
function keywordSearch(query: string, limit: number): ConversationSearchResult[] {
|
|
116
|
+
return legacyTitleSearch(query, limit);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Perform semantic search using embeddings
|
|
121
|
+
*/
|
|
122
|
+
async function semanticSearch(
|
|
123
|
+
query: string,
|
|
124
|
+
limit: number,
|
|
125
|
+
minScore: number,
|
|
126
|
+
projectId?: string
|
|
127
|
+
): Promise<ConversationSearchResult[]> {
|
|
128
|
+
const conversationEmbeddingService = getConversationEmbeddingService();
|
|
129
|
+
try {
|
|
130
|
+
const results = await conversationEmbeddingService.semanticSearch(query, {
|
|
131
|
+
limit,
|
|
132
|
+
minScore,
|
|
133
|
+
projectId,
|
|
134
|
+
});
|
|
135
|
+
return results.map((result: SemanticSearchResult) => ({
|
|
136
|
+
id: result.conversationId,
|
|
137
|
+
projectId: result.projectId,
|
|
138
|
+
title: result.title,
|
|
139
|
+
summary: result.summary,
|
|
140
|
+
messageCount: result.messageCount,
|
|
141
|
+
createdAt: result.createdAt,
|
|
142
|
+
lastActivity: result.lastActivity,
|
|
143
|
+
relevanceScore: result.relevanceScore,
|
|
144
|
+
}));
|
|
145
|
+
} catch (error) {
|
|
146
|
+
logger.error("Semantic search failed, falling back to empty results", { error });
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Perform hybrid search: semantic first, then keyword fallback
|
|
153
|
+
* Deduplicates results by conversation ID
|
|
154
|
+
*/
|
|
155
|
+
async function hybridSearch(
|
|
156
|
+
query: string,
|
|
157
|
+
limit: number,
|
|
158
|
+
minScore: number,
|
|
159
|
+
projectId?: string
|
|
160
|
+
): Promise<ConversationSearchResult[]> {
|
|
161
|
+
const seen = new Set<string>();
|
|
162
|
+
const combined: ConversationSearchResult[] = [];
|
|
163
|
+
|
|
164
|
+
// First: semantic results (higher quality matches)
|
|
165
|
+
const semanticResults = await semanticSearch(query, limit, minScore, projectId);
|
|
166
|
+
for (const result of semanticResults) {
|
|
167
|
+
if (!seen.has(result.id)) {
|
|
168
|
+
seen.add(result.id);
|
|
169
|
+
combined.push(result);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Second: keyword results to fill up remaining slots
|
|
174
|
+
if (combined.length < limit) {
|
|
175
|
+
const keywordResults = keywordSearch(query, limit);
|
|
176
|
+
for (const result of keywordResults) {
|
|
177
|
+
if (!seen.has(result.id) && combined.length < limit) {
|
|
178
|
+
seen.add(result.id);
|
|
179
|
+
combined.push(result);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return combined.slice(0, limit);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Perform full-text search across all messages
|
|
189
|
+
*/
|
|
190
|
+
async function fullTextSearch(
|
|
191
|
+
query: string,
|
|
192
|
+
filters: ConversationSearchInput["filters"],
|
|
193
|
+
limit: number
|
|
194
|
+
): Promise<{ results: ConversationSearchResult[]; searchType: "full-text" | "title-only"; error?: string }> {
|
|
195
|
+
const advancedResult = conversationRegistry.searchAdvanced({ query, filters }, limit);
|
|
196
|
+
|
|
197
|
+
if (advancedResult.success) {
|
|
198
|
+
if (advancedResult.results.length > 0) {
|
|
199
|
+
return {
|
|
200
|
+
searchType: "full-text",
|
|
201
|
+
results: advancedResult.results.map((result) => ({
|
|
202
|
+
id: result.conversationId,
|
|
203
|
+
title: result.title,
|
|
204
|
+
messageCount: result.messageCount,
|
|
205
|
+
createdAt: result.createdAt,
|
|
206
|
+
lastActivity: result.lastActivity,
|
|
207
|
+
matches: result.matches,
|
|
208
|
+
})),
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Advanced search succeeded but no results - try legacy search too
|
|
213
|
+
const legacyResults = legacyTitleSearch(query, limit);
|
|
214
|
+
if (legacyResults.length > 0) {
|
|
215
|
+
return {
|
|
216
|
+
searchType: "title-only",
|
|
217
|
+
results: legacyResults,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// No results from either search
|
|
222
|
+
return {
|
|
223
|
+
searchType: "full-text",
|
|
224
|
+
results: [],
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Advanced search failed - try fallback
|
|
229
|
+
logger.warn("⚠️ Advanced search failed, falling back to title search", {
|
|
230
|
+
error: advancedResult.error,
|
|
231
|
+
query,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const legacyResults = legacyTitleSearch(query, limit);
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
searchType: "title-only",
|
|
238
|
+
results: legacyResults,
|
|
239
|
+
error: advancedResult.error,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Check if semantic search is available
|
|
245
|
+
*/
|
|
246
|
+
async function isSemanticSearchAvailable(): Promise<boolean> {
|
|
247
|
+
try {
|
|
248
|
+
const conversationEmbeddingService = getConversationEmbeddingService();
|
|
249
|
+
return await conversationEmbeddingService.hasIndexedConversations();
|
|
250
|
+
} catch {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function executeConversationSearch(
|
|
256
|
+
input: ConversationSearchInput,
|
|
257
|
+
context: ToolExecutionContext
|
|
258
|
+
): Promise<ConversationSearchOutput> {
|
|
259
|
+
const { query, mode = "keyword", filters, limit = 20, minScore = 0.3, projectId } = input;
|
|
260
|
+
|
|
261
|
+
logger.info("🔍 Searching conversations", {
|
|
262
|
+
query,
|
|
263
|
+
mode,
|
|
264
|
+
filters,
|
|
265
|
+
limit,
|
|
266
|
+
minScore,
|
|
267
|
+
projectId,
|
|
268
|
+
agent: context.agent.name,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// Check semantic availability
|
|
272
|
+
const semanticAvailable = await isSemanticSearchAvailable();
|
|
273
|
+
|
|
274
|
+
// Determine effective mode
|
|
275
|
+
let effectiveMode = mode;
|
|
276
|
+
if ((mode === "semantic" || mode === "hybrid") && !semanticAvailable) {
|
|
277
|
+
logger.info("Semantic search not available, falling back to keyword search");
|
|
278
|
+
effectiveMode = "keyword";
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
let conversations: ConversationSearchResult[];
|
|
282
|
+
let searchType: "full-text" | "title-only" | "semantic" | "hybrid";
|
|
283
|
+
let warning: string | undefined;
|
|
284
|
+
|
|
285
|
+
switch (effectiveMode) {
|
|
286
|
+
case "semantic":
|
|
287
|
+
conversations = await semanticSearch(query, limit, minScore, projectId);
|
|
288
|
+
searchType = "semantic";
|
|
289
|
+
break;
|
|
290
|
+
|
|
291
|
+
case "hybrid":
|
|
292
|
+
conversations = await hybridSearch(query, limit, minScore, projectId);
|
|
293
|
+
searchType = "hybrid";
|
|
294
|
+
break;
|
|
295
|
+
|
|
296
|
+
case "full-text": {
|
|
297
|
+
const fullTextResult = await fullTextSearch(query, filters, limit);
|
|
298
|
+
conversations = fullTextResult.results;
|
|
299
|
+
searchType = fullTextResult.searchType;
|
|
300
|
+
if (fullTextResult.error) {
|
|
301
|
+
warning = `Full-text search failed: ${fullTextResult.error}. Showing ${fullTextResult.searchType} results.`;
|
|
302
|
+
} else if (fullTextResult.searchType === "title-only" && fullTextResult.results.length > 0) {
|
|
303
|
+
warning = "Full-text search found no results; showing title-only matches";
|
|
304
|
+
}
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
case "keyword":
|
|
309
|
+
default:
|
|
310
|
+
conversations = keywordSearch(query, limit);
|
|
311
|
+
searchType = "full-text";
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
logger.info("✅ Conversation search complete", {
|
|
316
|
+
query,
|
|
317
|
+
mode: effectiveMode,
|
|
318
|
+
found: conversations.length,
|
|
319
|
+
semanticAvailable,
|
|
320
|
+
agent: context.agent.name,
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
return {
|
|
324
|
+
success: true,
|
|
325
|
+
conversations,
|
|
326
|
+
total: conversations.length,
|
|
327
|
+
query,
|
|
328
|
+
mode: effectiveMode,
|
|
329
|
+
searchType,
|
|
330
|
+
semanticAvailable,
|
|
331
|
+
warning,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export function createConversationSearchTool(context: ToolExecutionContext): AISdkTool {
|
|
336
|
+
const aiTool = tool({
|
|
337
|
+
description:
|
|
338
|
+
"Search conversations with multiple search modes. Returns matching conversations with summary information including ID, title, message count, and timestamps. " +
|
|
339
|
+
"Supports four search modes: 'keyword' (fast title matching), 'semantic' (natural language understanding), 'hybrid' (both), and 'full-text' (comprehensive message search). " +
|
|
340
|
+
"Use semantic or hybrid mode for natural language queries that understand meaning.",
|
|
341
|
+
|
|
342
|
+
inputSchema: conversationSearchSchema,
|
|
343
|
+
|
|
344
|
+
execute: async (input: ConversationSearchInput) => {
|
|
345
|
+
return await executeConversationSearch(input, context);
|
|
346
|
+
},
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
Object.defineProperty(aiTool, "getHumanReadableContent", {
|
|
350
|
+
value: ({ query, mode, filters, limit }: ConversationSearchInput) => {
|
|
351
|
+
const parts = [`Searching conversations for "${query}"`];
|
|
352
|
+
const modeStr = mode || "keyword";
|
|
353
|
+
parts.push(`mode=${modeStr}`);
|
|
354
|
+
if (filters?.agents?.length) {
|
|
355
|
+
parts.push(`agents: ${filters.agents.join(", ")}`);
|
|
356
|
+
}
|
|
357
|
+
if (filters?.since || filters?.after) {
|
|
358
|
+
parts.push(`since: ${filters.since || filters.after}`);
|
|
359
|
+
}
|
|
360
|
+
if (limit && limit !== 20) {
|
|
361
|
+
parts.push(`limit: ${limit}`);
|
|
362
|
+
}
|
|
363
|
+
return parts.join(", ");
|
|
364
|
+
},
|
|
365
|
+
enumerable: false,
|
|
366
|
+
configurable: true,
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
return aiTool as AISdkTool;
|
|
370
|
+
}
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delegation Tool Implementation
|
|
3
|
+
*
|
|
4
|
+
* Enables agents to delegate tasks to other agents in the system.
|
|
5
|
+
*
|
|
6
|
+
* ## Circular Delegation Handling
|
|
7
|
+
*
|
|
8
|
+
* The tool detects when a delegation would create a circular chain (A→B→C→A).
|
|
9
|
+
* By default, circular delegations return a soft warning with `success: false`.
|
|
10
|
+
* Set `force: true` on an individual delegation to bypass this check.
|
|
11
|
+
*
|
|
12
|
+
* @module delegate
|
|
13
|
+
*/
|
|
14
|
+
import type { ToolExecutionContext } from "@/tools/types";
|
|
15
|
+
import { getProjectContext } from "@/services/projects";
|
|
16
|
+
import { RALRegistry } from "@/services/ral/RALRegistry";
|
|
17
|
+
import type { PendingDelegation } from "@/services/ral/types";
|
|
18
|
+
import type { AISdkTool } from "@/tools/types";
|
|
19
|
+
import { resolveAgentSlug } from "@/services/agents";
|
|
20
|
+
import { logger } from "@/utils/logger";
|
|
21
|
+
import { createEventContext } from "@/services/event-context";
|
|
22
|
+
import { shortenConversationId } from "@/utils/conversation-id";
|
|
23
|
+
import { wouldCreateCircularDelegation } from "@/utils/delegation-chain";
|
|
24
|
+
import { ConversationStore } from "@/conversations/ConversationStore";
|
|
25
|
+
import type { DelegationMarker } from "@/conversations/types";
|
|
26
|
+
import { AgentEventDecoder } from "@/nostr/AgentEventDecoder";
|
|
27
|
+
import { tool } from "ai";
|
|
28
|
+
import { z } from "zod";
|
|
29
|
+
|
|
30
|
+
const delegationItemSchema = z.object({
|
|
31
|
+
recipient: z
|
|
32
|
+
.string()
|
|
33
|
+
.describe(
|
|
34
|
+
"Agent slug (e.g., 'architect', 'claude-code', 'explore-agent'). Only agent slugs are accepted."
|
|
35
|
+
),
|
|
36
|
+
prompt: z.string().describe("The request or task for this agent"),
|
|
37
|
+
branch: z
|
|
38
|
+
.string()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe("Git branch name for worktree isolation"),
|
|
41
|
+
force: z
|
|
42
|
+
.boolean()
|
|
43
|
+
.optional()
|
|
44
|
+
.describe("Set to true to proceed even if circular delegation is detected"),
|
|
45
|
+
nudges: z
|
|
46
|
+
.array(z.string())
|
|
47
|
+
.optional()
|
|
48
|
+
.describe("Nudge event IDs to apply to this delegated agent. Nudges can modify tool availability and inject additional context."),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
type DelegationItem = z.infer<typeof delegationItemSchema>;
|
|
52
|
+
|
|
53
|
+
interface DelegateInput {
|
|
54
|
+
delegations: DelegationItem[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface CircularDelegationWarning {
|
|
58
|
+
recipient: string;
|
|
59
|
+
chain: string;
|
|
60
|
+
message: string;
|
|
61
|
+
forced?: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface DelegateOutput {
|
|
65
|
+
success: boolean;
|
|
66
|
+
message: string;
|
|
67
|
+
/** Truncated delegation conversation IDs (prefixes for compact display) */
|
|
68
|
+
delegationConversationIds: string[];
|
|
69
|
+
circularDelegationWarning?: CircularDelegationWarning;
|
|
70
|
+
circularDelegationWarnings?: CircularDelegationWarning[];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Check if the agent has created a todo list.
|
|
75
|
+
* Returns { hasTodos: boolean, hasConversation: boolean } to distinguish
|
|
76
|
+
* between "no todos" and "no conversation context" cases.
|
|
77
|
+
*/
|
|
78
|
+
function checkTodoState(context: ToolExecutionContext): { hasTodos: boolean; hasConversation: boolean } {
|
|
79
|
+
const conversation = context.getConversation();
|
|
80
|
+
if (!conversation) {
|
|
81
|
+
// No conversation context available - skip enforcement
|
|
82
|
+
return { hasTodos: true, hasConversation: false };
|
|
83
|
+
}
|
|
84
|
+
const todos = conversation.getTodos(context.agent.pubkey);
|
|
85
|
+
return { hasTodos: todos.length > 0, hasConversation: true };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function executeDelegate(
|
|
89
|
+
input: DelegateInput,
|
|
90
|
+
context: ToolExecutionContext
|
|
91
|
+
): Promise<DelegateOutput> {
|
|
92
|
+
const { delegations } = input;
|
|
93
|
+
|
|
94
|
+
// ENFORCEMENT: Delegation requires a todo list
|
|
95
|
+
// Skip enforcement if no conversation context (e.g., MCP-only mode)
|
|
96
|
+
const todoState = checkTodoState(context);
|
|
97
|
+
if (todoState.hasConversation && !todoState.hasTodos) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
"Delegation requires a todo list. Please use `todo_write()` to create a todo list before delegating tasks. " +
|
|
100
|
+
"This helps track work progress and ensures delegated tasks are properly documented."
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!Array.isArray(delegations) || delegations.length === 0) {
|
|
105
|
+
throw new Error("At least one delegation is required");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const ralRegistry = RALRegistry.getInstance();
|
|
109
|
+
const pendingDelegations: PendingDelegation[] = [];
|
|
110
|
+
const circularWarnings: CircularDelegationWarning[] = [];
|
|
111
|
+
|
|
112
|
+
// Get the delegation chain from the current conversation for cycle detection
|
|
113
|
+
const conversationStore = ConversationStore.get(context.conversationId);
|
|
114
|
+
const delegationChain = conversationStore?.metadata?.delegationChain;
|
|
115
|
+
|
|
116
|
+
// Extract inherited nudges from the triggering event
|
|
117
|
+
// Nudge inheritance: any nudges on the current triggering event are automatically
|
|
118
|
+
// passed forward to delegated agents unless explicitly overridden
|
|
119
|
+
const inheritedNudges = AgentEventDecoder.extractNudgeEventIds(context.triggeringEvent);
|
|
120
|
+
|
|
121
|
+
for (const delegation of delegations) {
|
|
122
|
+
// Resolve slug to pubkey - throws if invalid
|
|
123
|
+
const resolution = resolveAgentSlug(delegation.recipient);
|
|
124
|
+
if (!resolution.pubkey) {
|
|
125
|
+
const availableSlugsStr = resolution.availableSlugs.length > 0
|
|
126
|
+
? `Available agent slugs: ${resolution.availableSlugs.join(", ")}`
|
|
127
|
+
: "No agents available in the current project context.";
|
|
128
|
+
throw new Error(
|
|
129
|
+
`Invalid agent slug: "${delegation.recipient}". Only agent slugs are accepted. ${availableSlugsStr}`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
const pubkey = resolution.pubkey;
|
|
133
|
+
|
|
134
|
+
// Check for circular delegation
|
|
135
|
+
if (delegationChain && wouldCreateCircularDelegation(delegationChain, pubkey)) {
|
|
136
|
+
const projectContext = getProjectContext();
|
|
137
|
+
const targetAgent = projectContext.getAgentByPubkey(pubkey);
|
|
138
|
+
const targetName = targetAgent?.slug || pubkey.substring(0, 8);
|
|
139
|
+
const chainDisplay = delegationChain.map(e => e.displayName).join(" → ");
|
|
140
|
+
|
|
141
|
+
const warning: CircularDelegationWarning = {
|
|
142
|
+
recipient: targetName,
|
|
143
|
+
chain: chainDisplay,
|
|
144
|
+
message: `"${targetName}" is already in the delegation chain (${chainDisplay}). Delegating would create a cycle.`,
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
if (!delegation.force) {
|
|
148
|
+
logger.info("[delegate] Circular delegation detected, skipping (no force flag)", {
|
|
149
|
+
recipient: delegation.recipient,
|
|
150
|
+
targetPubkey: pubkey.substring(0, 8),
|
|
151
|
+
chain: chainDisplay,
|
|
152
|
+
});
|
|
153
|
+
circularWarnings.push(warning);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Force flag set - proceed with warning
|
|
158
|
+
logger.warn("[delegate] Circular delegation proceeding with force flag", {
|
|
159
|
+
recipient: delegation.recipient,
|
|
160
|
+
targetPubkey: pubkey.substring(0, 8),
|
|
161
|
+
chain: chainDisplay,
|
|
162
|
+
});
|
|
163
|
+
circularWarnings.push({ ...warning, forced: true });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Publish delegation event
|
|
167
|
+
const eventContext = createEventContext(context);
|
|
168
|
+
|
|
169
|
+
// Combine inherited nudges with explicitly specified nudges
|
|
170
|
+
// Nudge inheritance: inherited nudges are always passed forward
|
|
171
|
+
// Explicit nudges are added to the inherited set (not replaced)
|
|
172
|
+
const combinedNudges = [
|
|
173
|
+
...inheritedNudges,
|
|
174
|
+
...(delegation.nudges || []),
|
|
175
|
+
];
|
|
176
|
+
// Deduplicate nudges
|
|
177
|
+
const uniqueNudges = [...new Set(combinedNudges)];
|
|
178
|
+
|
|
179
|
+
const eventId = await context.agentPublisher.delegate({
|
|
180
|
+
recipient: pubkey,
|
|
181
|
+
content: delegation.prompt,
|
|
182
|
+
branch: delegation.branch,
|
|
183
|
+
nudges: uniqueNudges.length > 0 ? uniqueNudges : undefined,
|
|
184
|
+
}, eventContext);
|
|
185
|
+
|
|
186
|
+
const pendingDelegation: PendingDelegation = {
|
|
187
|
+
delegationConversationId: eventId,
|
|
188
|
+
recipientPubkey: pubkey,
|
|
189
|
+
senderPubkey: context.agent.pubkey,
|
|
190
|
+
prompt: delegation.prompt,
|
|
191
|
+
ralNumber: context.ralNumber,
|
|
192
|
+
};
|
|
193
|
+
pendingDelegations.push(pendingDelegation);
|
|
194
|
+
|
|
195
|
+
// Register immediately after publishing to prevent orphans
|
|
196
|
+
ralRegistry.mergePendingDelegations(
|
|
197
|
+
context.agent.pubkey,
|
|
198
|
+
context.conversationId,
|
|
199
|
+
context.ralNumber,
|
|
200
|
+
[pendingDelegation]
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
// Create pending marker immediately to show delegation in real-time
|
|
204
|
+
const parentStore = ConversationStore.get(context.conversationId);
|
|
205
|
+
if (parentStore) {
|
|
206
|
+
const initiatedAt = Math.floor(Date.now() / 1000);
|
|
207
|
+
const marker: DelegationMarker = {
|
|
208
|
+
delegationConversationId: eventId,
|
|
209
|
+
recipientPubkey: pubkey,
|
|
210
|
+
parentConversationId: context.conversationId,
|
|
211
|
+
initiatedAt,
|
|
212
|
+
status: "pending",
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// Store marker locally
|
|
216
|
+
parentStore.addDelegationMarker(
|
|
217
|
+
marker,
|
|
218
|
+
context.agent.pubkey,
|
|
219
|
+
context.ralNumber
|
|
220
|
+
);
|
|
221
|
+
await parentStore.save();
|
|
222
|
+
|
|
223
|
+
// Publish marker to Nostr for verification by other agents
|
|
224
|
+
await context.agentPublisher.delegationMarker({
|
|
225
|
+
delegationConversationId: eventId,
|
|
226
|
+
recipientPubkey: pubkey,
|
|
227
|
+
parentConversationId: context.conversationId,
|
|
228
|
+
status: "pending",
|
|
229
|
+
initiatedAt,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const delegationConversationIds = pendingDelegations.map(d => shortenConversationId(d.delegationConversationId));
|
|
235
|
+
const unforcedWarnings = circularWarnings.filter(w => !w.forced);
|
|
236
|
+
|
|
237
|
+
// All delegations were circular (not forced) - return soft warning
|
|
238
|
+
if (pendingDelegations.length === 0 && unforcedWarnings.length > 0) {
|
|
239
|
+
const warningMessages = unforcedWarnings.map(w =>
|
|
240
|
+
`"${w.recipient}" is already in chain (${w.chain})`
|
|
241
|
+
).join("; ");
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
success: false,
|
|
245
|
+
message: `Circular delegation detected: ${warningMessages}. Add \`force: true\` to proceed anyway.`,
|
|
246
|
+
delegationConversationIds: [],
|
|
247
|
+
circularDelegationWarning: unforcedWarnings[0],
|
|
248
|
+
circularDelegationWarnings: unforcedWarnings,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (pendingDelegations.length === 0) {
|
|
253
|
+
throw new Error("No delegations were published.");
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
logger.info("[delegate] Published delegations, agent continues without blocking", {
|
|
257
|
+
count: pendingDelegations.length,
|
|
258
|
+
delegationConversationIds,
|
|
259
|
+
circularWarningsCount: circularWarnings.length,
|
|
260
|
+
inheritedNudgesCount: inheritedNudges.length,
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
let message = `Delegated ${pendingDelegations.length} task(s). The agent(s) will wake you up when ready with the response(s).`;
|
|
264
|
+
if (unforcedWarnings.length > 0) {
|
|
265
|
+
const skipped = unforcedWarnings.map(w => w.recipient).join(", ");
|
|
266
|
+
message += ` Note: Skipped circular delegation(s) to: ${skipped}.`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
success: true,
|
|
271
|
+
message,
|
|
272
|
+
delegationConversationIds,
|
|
273
|
+
...(circularWarnings.length > 0 && {
|
|
274
|
+
circularDelegationWarning: circularWarnings[0],
|
|
275
|
+
circularDelegationWarnings: circularWarnings,
|
|
276
|
+
}),
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function createDelegateTool(context: ToolExecutionContext): AISdkTool {
|
|
281
|
+
const delegateSchema = z.object({
|
|
282
|
+
delegations: z
|
|
283
|
+
.array(delegationItemSchema)
|
|
284
|
+
.min(1)
|
|
285
|
+
.describe("Array of delegations to execute"),
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
const description = `Delegate tasks to one or more agents. Each delegation can have its own prompt and branch. IMPORTANT: Delegated agents ONLY see your prompt - they cannot see any prior conversation. Include ALL necessary context, requirements, and constraints in your prompt.
|
|
289
|
+
|
|
290
|
+
Circular delegation detection: The tool detects when a delegation would create a circular chain (A→B→C→A). By default, circular delegations are skipped with a soft warning. Set \`force: true\` on an individual delegation to bypass this check.
|
|
291
|
+
|
|
292
|
+
Nudge support: Pass nudge event IDs in the \`nudges\` array to apply behavioral nudges to delegated agents. Nudges can modify tool availability (only-tool, allow-tool, deny-tool) and inject additional context. Nudge inheritance: any nudges active on the current agent are automatically forwarded to all delegated agents.`;
|
|
293
|
+
|
|
294
|
+
const aiTool = tool({
|
|
295
|
+
description,
|
|
296
|
+
inputSchema: delegateSchema,
|
|
297
|
+
execute: async (input: unknown) => {
|
|
298
|
+
return await executeDelegate(input as DelegateInput, context);
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
Object.defineProperty(aiTool, "getHumanReadableContent", {
|
|
303
|
+
value: (args: unknown) => {
|
|
304
|
+
if (!args || typeof args !== "object" || !("delegations" in args)) {
|
|
305
|
+
return "Delegating to agent(s)";
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const { delegations } = args as DelegateInput;
|
|
309
|
+
|
|
310
|
+
if (!delegations || !Array.isArray(delegations)) {
|
|
311
|
+
return "Delegating to agent(s)";
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (delegations.length === 1) {
|
|
315
|
+
const d = delegations[0];
|
|
316
|
+
return `Delegating to ${d.recipient}`;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const recipients = delegations.map((d) => d.recipient).join(", ");
|
|
320
|
+
return `Delegating ${delegations.length} tasks to: ${recipients}`;
|
|
321
|
+
},
|
|
322
|
+
enumerable: false,
|
|
323
|
+
configurable: true,
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
return aiTool as AISdkTool;
|
|
327
|
+
}
|