@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,749 @@
|
|
|
1
|
+
import { NDKKind } from "@/nostr/kinds";
|
|
2
|
+
import { TagExtractor } from "@/nostr/TagExtractor";
|
|
3
|
+
import { formatAnyError } from "@/lib/error-formatter";
|
|
4
|
+
import { type NDKEvent, NDKArticle, NDKProject } from "@nostr-dev-kit/ndk";
|
|
5
|
+
import type { AgentProjectConfig, AgentDefaultConfig } from "@/agents/types";
|
|
6
|
+
import { computeToolsDelta } from "@/agents/ConfigResolver";
|
|
7
|
+
import { expandFsCapabilities } from "@/agents/tool-normalization";
|
|
8
|
+
import { agentStorage } from "../agents/AgentStorage";
|
|
9
|
+
import { AgentExecutor } from "../agents/execution/AgentExecutor";
|
|
10
|
+
import { ConversationStore } from "../conversations/ConversationStore";
|
|
11
|
+
import { NDKAgentLesson } from "@/events/NDKAgentLesson";
|
|
12
|
+
import { NDKEventMetadata } from "../events/NDKEventMetadata";
|
|
13
|
+
import { getProjectContext } from "@/services/projects";
|
|
14
|
+
import { getLocalReportStore } from "@/services/reports";
|
|
15
|
+
import { config } from "@/services/ConfigService";
|
|
16
|
+
import { RALRegistry } from "@/services/ral";
|
|
17
|
+
import { prefixKVStore } from "@/services/storage";
|
|
18
|
+
import { logger } from "../utils/logger";
|
|
19
|
+
import { shortenConversationId } from "@/utils/conversation-id";
|
|
20
|
+
import { shouldTrustLesson } from "@/utils/lessonTrust";
|
|
21
|
+
import { getPubkeyGateService } from "@/services/pubkey-gate";
|
|
22
|
+
import { handleAgentDeletion } from "./agentDeletion";
|
|
23
|
+
import { handleProjectEvent } from "./project";
|
|
24
|
+
import { handleChatMessage } from "./reply";
|
|
25
|
+
import { trace, context as otelContext, TraceFlags } from "@opentelemetry/api";
|
|
26
|
+
/**
|
|
27
|
+
* Index event ID and pubkey into the prefix KV store.
|
|
28
|
+
* Skips ephemeral events (kinds 20000-29999) since their IDs are transient.
|
|
29
|
+
*
|
|
30
|
+
* This is a best-effort operation - indexing failures are logged but do NOT
|
|
31
|
+
* abort event handling. The prefix index is a convenience feature, not critical.
|
|
32
|
+
*/
|
|
33
|
+
async function indexEventForPrefixLookup(event: NDKEvent): Promise<void> {
|
|
34
|
+
const kind = event.kind ?? 0;
|
|
35
|
+
|
|
36
|
+
// Skip ephemeral events (kinds 20000-29999)
|
|
37
|
+
if (kind >= 20000 && kind < 30000) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Index both event ID and pubkey - best effort, don't let failures bubble up
|
|
42
|
+
if (prefixKVStore.isInitialized()) {
|
|
43
|
+
try {
|
|
44
|
+
await prefixKVStore.addBatch([event.id, event.pubkey]);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
// Log but don't abort - indexing is a sidecar feature
|
|
47
|
+
logger.warn("[EventHandler] Failed to index event for prefix lookup", {
|
|
48
|
+
eventId: event.id?.substring(0, 12),
|
|
49
|
+
error: error instanceof Error ? error.message : String(error),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const IGNORED_EVENT_KINDS = [
|
|
56
|
+
NDKKind.Metadata,
|
|
57
|
+
NDKKind.Contacts,
|
|
58
|
+
NDKKind.TenexProjectStatus,
|
|
59
|
+
NDKKind.TenexOperationsStatus,
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
export class EventHandler {
|
|
63
|
+
private agentExecutor!: AgentExecutor;
|
|
64
|
+
private isUpdatingProject = false;
|
|
65
|
+
|
|
66
|
+
async initialize(): Promise<void> {
|
|
67
|
+
this.agentExecutor = new AgentExecutor();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async handleEvent(event: NDKEvent): Promise<void> {
|
|
71
|
+
// Ignore ephemeral status and typing indicator events
|
|
72
|
+
if (IGNORED_EVENT_KINDS.includes(event.kind)) return;
|
|
73
|
+
|
|
74
|
+
// PUBKEY GATE: Only allow events from trusted pubkeys (whitelisted, backend, or known agents)
|
|
75
|
+
// This is the front-door gate — all events must pass through before any routing occurs.
|
|
76
|
+
// Fail-closed: if the check errors, the event is denied.
|
|
77
|
+
if (!getPubkeyGateService().shouldAllowEvent(event)) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Index event ID and pubkey for prefix lookups
|
|
82
|
+
await indexEventForPrefixLookup(event);
|
|
83
|
+
|
|
84
|
+
// EMERGENCY STOP: If a whitelisted pubkey sends "EMERGENCY STOP", exit immediately
|
|
85
|
+
if (event.content === "EMERGENCY STOP") {
|
|
86
|
+
const whitelistedPubkeys = config.getConfig().whitelistedPubkeys ?? [];
|
|
87
|
+
if (whitelistedPubkeys.includes(event.pubkey)) {
|
|
88
|
+
logger.warn("EMERGENCY STOP received from whitelisted pubkey", {
|
|
89
|
+
pubkey: event.pubkey.substring(0, 8),
|
|
90
|
+
eventId: event.id,
|
|
91
|
+
});
|
|
92
|
+
process.exit(0);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Try to get agent slug if the event is from an agent
|
|
97
|
+
let fromIdentifier = event.pubkey;
|
|
98
|
+
let forIdentifiers = "without any recipient";
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const projectCtx = getProjectContext();
|
|
102
|
+
const agent = projectCtx.getAgentByPubkey(event.pubkey);
|
|
103
|
+
if (agent) {
|
|
104
|
+
fromIdentifier = agent.slug;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Process p-tags to show agent slugs where possible
|
|
108
|
+
let pTags: string[][] = [];
|
|
109
|
+
try {
|
|
110
|
+
pTags = event.getMatchingTags("p");
|
|
111
|
+
} catch (err) {
|
|
112
|
+
logger.error("Failed to get p-tags - event is not a proper NDKEvent!", {
|
|
113
|
+
error: err,
|
|
114
|
+
eventType: typeof event,
|
|
115
|
+
eventConstructor: event?.constructor?.name,
|
|
116
|
+
eventPrototype: Object.getPrototypeOf(event)?.constructor?.name,
|
|
117
|
+
eventKeys: Object.keys(event || {}),
|
|
118
|
+
event: event?.rawEvent ? JSON.stringify(event.rawEvent(), null, 2) : "no rawEvent method",
|
|
119
|
+
});
|
|
120
|
+
throw err;
|
|
121
|
+
}
|
|
122
|
+
if (pTags.length > 0) {
|
|
123
|
+
const recipients = pTags.map((t) => {
|
|
124
|
+
const pubkey = t[1];
|
|
125
|
+
const recipientAgent = projectCtx.getAgentByPubkey(pubkey);
|
|
126
|
+
return recipientAgent ? recipientAgent.slug : pubkey.substring(0, 8);
|
|
127
|
+
});
|
|
128
|
+
forIdentifiers = recipients.join(", ");
|
|
129
|
+
}
|
|
130
|
+
} catch {
|
|
131
|
+
// Project context might not be available, continue with pubkey
|
|
132
|
+
let pTags: string[][];
|
|
133
|
+
try {
|
|
134
|
+
pTags = event.getMatchingTags("p");
|
|
135
|
+
} catch (err) {
|
|
136
|
+
logger.error("Failed to get p-tags (fallback) - event is not a proper NDKEvent!", {
|
|
137
|
+
error: err,
|
|
138
|
+
eventType: typeof event,
|
|
139
|
+
eventConstructor: event?.constructor?.name,
|
|
140
|
+
eventPrototype: Object.getPrototypeOf(event)?.constructor?.name,
|
|
141
|
+
hasGetMatchingTags: typeof event?.getMatchingTags,
|
|
142
|
+
eventKeys: Object.keys(event || {}),
|
|
143
|
+
event: event?.rawEvent ? JSON.stringify(event.rawEvent(), null, 2) : "no rawEvent method",
|
|
144
|
+
});
|
|
145
|
+
throw err;
|
|
146
|
+
}
|
|
147
|
+
if (pTags.length > 0) {
|
|
148
|
+
forIdentifiers = pTags.map((t) => t[1].substring(0, 8)).join(", ");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
trace.getActiveSpan()?.addEvent("event_handler.received", {
|
|
153
|
+
"event.kind": event.kind,
|
|
154
|
+
"event.id": event.id,
|
|
155
|
+
"event.from": fromIdentifier,
|
|
156
|
+
"event.for": forIdentifiers,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
switch (event.kind) {
|
|
160
|
+
case NDKKind.Text: // kind 1 - unified conversation format
|
|
161
|
+
await handleChatMessage(event, {
|
|
162
|
+
agentExecutor: this.agentExecutor,
|
|
163
|
+
});
|
|
164
|
+
break;
|
|
165
|
+
|
|
166
|
+
case NDKKind.TenexBootProject: // kind 24000 - project boot request
|
|
167
|
+
// Boot already happened by virtue of event routing - nothing to do
|
|
168
|
+
trace.getActiveSpan()?.addEvent("event_handler.boot_request", {
|
|
169
|
+
"event.id": event.id,
|
|
170
|
+
"event.author": event.pubkey.substring(0, 8),
|
|
171
|
+
});
|
|
172
|
+
break;
|
|
173
|
+
|
|
174
|
+
case NDKProject.kind: // kind 31933
|
|
175
|
+
if (this.isUpdatingProject) {
|
|
176
|
+
logger.warn("Project update already in progress, skipping event", {
|
|
177
|
+
eventId: event.id,
|
|
178
|
+
});
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
this.isUpdatingProject = true;
|
|
183
|
+
try {
|
|
184
|
+
await handleProjectEvent(event);
|
|
185
|
+
} finally {
|
|
186
|
+
this.isUpdatingProject = false;
|
|
187
|
+
}
|
|
188
|
+
break;
|
|
189
|
+
|
|
190
|
+
case NDKKind.TenexAgentConfigUpdate:
|
|
191
|
+
await this.handleAgentConfigUpdate(event);
|
|
192
|
+
break;
|
|
193
|
+
|
|
194
|
+
case NDKKind.TenexAgentDelete:
|
|
195
|
+
await handleAgentDeletion(event);
|
|
196
|
+
break;
|
|
197
|
+
|
|
198
|
+
case 513: // NDKEventMetadata
|
|
199
|
+
await this.handleMetadataEvent(event);
|
|
200
|
+
break;
|
|
201
|
+
|
|
202
|
+
case NDKKind.TenexStopCommand: // Stop LLM operations
|
|
203
|
+
await this.handleStopEvent(event);
|
|
204
|
+
break;
|
|
205
|
+
|
|
206
|
+
case NDKKind.AgentLesson:
|
|
207
|
+
await this.handleLessonEvent(event);
|
|
208
|
+
break;
|
|
209
|
+
|
|
210
|
+
case 30023: // NDKArticle - Reports
|
|
211
|
+
await this.handleReportEvent(event);
|
|
212
|
+
break;
|
|
213
|
+
|
|
214
|
+
default:
|
|
215
|
+
this.handleDefaultEvent(event);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private async handleMetadataEvent(event: NDKEvent): Promise<void> {
|
|
220
|
+
const metadata = NDKEventMetadata.from(event);
|
|
221
|
+
const conversationId = metadata.conversationId;
|
|
222
|
+
|
|
223
|
+
if (!conversationId) {
|
|
224
|
+
logger.error("Metadata event missing conversation ID", event.inspect);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Only update if we know this conversation
|
|
229
|
+
if (ConversationStore.has(conversationId)) {
|
|
230
|
+
// Collect metadata updates from tags
|
|
231
|
+
const updates: Record<string, string | undefined> = {};
|
|
232
|
+
|
|
233
|
+
const title = metadata.title;
|
|
234
|
+
if (title) {
|
|
235
|
+
updates.title = title;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Parse summary tag
|
|
239
|
+
const summaryTag = event.tags.find((tag: string[]) => tag[0] === "summary");
|
|
240
|
+
if (summaryTag && summaryTag[1]) {
|
|
241
|
+
updates.summary = summaryTag[1];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Parse status-label tag
|
|
245
|
+
const statusLabelTag = event.tags.find((tag: string[]) => tag[0] === "status-label");
|
|
246
|
+
if (statusLabelTag && statusLabelTag[1]) {
|
|
247
|
+
updates.statusLabel = statusLabelTag[1];
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Parse status-current-activity tag
|
|
251
|
+
const statusActivityTag = event.tags.find((tag: string[]) => tag[0] === "status-current-activity");
|
|
252
|
+
if (statusActivityTag && statusActivityTag[1]) {
|
|
253
|
+
updates.statusCurrentActivity = statusActivityTag[1];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Apply all updates at once
|
|
257
|
+
if (Object.keys(updates).length > 0) {
|
|
258
|
+
await ConversationStore.updateConversationMetadata(conversationId, updates);
|
|
259
|
+
logger.debug("Updated conversation metadata", {
|
|
260
|
+
conversationId: conversationId.substring(0, 8),
|
|
261
|
+
updates: Object.keys(updates),
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
private async handleAgentConfigUpdate(event: NDKEvent): Promise<void> {
|
|
268
|
+
try {
|
|
269
|
+
// Extract the agent pubkey from the event tags
|
|
270
|
+
const agentPubkey = event.tagValue("p");
|
|
271
|
+
if (!agentPubkey) {
|
|
272
|
+
logger.warn("AGENT_CONFIG_UPDATE event missing agent pubkey", {
|
|
273
|
+
eventId: event.id,
|
|
274
|
+
});
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Get the project context early - needed for a-tag validation and agent lookup
|
|
279
|
+
const projectContext = getProjectContext();
|
|
280
|
+
|
|
281
|
+
// Extract the project a-tag if present
|
|
282
|
+
// Format: ["a", "31990:<pubkey>:<d-tag>"] or just the d-tag portion
|
|
283
|
+
// When present, config is scoped to that project only
|
|
284
|
+
const aTag = event.tagValue("a");
|
|
285
|
+
let projectDTag: string | undefined;
|
|
286
|
+
if (aTag) {
|
|
287
|
+
// Parse the a-tag - format is "kind:pubkey:d-tag"
|
|
288
|
+
const parts = aTag.split(":");
|
|
289
|
+
if (parts.length >= 3) {
|
|
290
|
+
// The d-tag is the third part (and may contain colons)
|
|
291
|
+
projectDTag = parts.slice(2).join(":");
|
|
292
|
+
} else {
|
|
293
|
+
// If not in standard format, treat the whole value as d-tag
|
|
294
|
+
projectDTag = aTag;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Validate that the a-tag matches the current project
|
|
298
|
+
// This prevents config updates meant for other projects from being applied here
|
|
299
|
+
const currentProjectDTag = projectContext.project.dTag || projectContext.project.tagValue("d");
|
|
300
|
+
if (projectDTag !== currentProjectDTag) {
|
|
301
|
+
logger.debug("Ignoring project-scoped config update for different project", {
|
|
302
|
+
eventId: event.id,
|
|
303
|
+
targetProject: projectDTag,
|
|
304
|
+
currentProject: currentProjectDTag,
|
|
305
|
+
});
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const isProjectScoped = projectDTag !== undefined;
|
|
311
|
+
const agent = projectContext.getAgentByPubkey(agentPubkey);
|
|
312
|
+
|
|
313
|
+
if (!agent) {
|
|
314
|
+
logger.warn("Agent not found for config change", {
|
|
315
|
+
agentPubkey,
|
|
316
|
+
availableAgents: projectContext.getAgentSlugs(),
|
|
317
|
+
});
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Get the agent registry from ProjectContext (single source of truth)
|
|
322
|
+
const agentRegistry = projectContext.agentRegistry;
|
|
323
|
+
|
|
324
|
+
// Track if any update was made
|
|
325
|
+
let configUpdated = false;
|
|
326
|
+
|
|
327
|
+
// Extract configuration values from the event
|
|
328
|
+
const newModel = event.tagValue("model");
|
|
329
|
+
const toolTags = TagExtractor.getToolTags(event);
|
|
330
|
+
const rawToolNames = toolTags.map((tool) => tool.name).filter((name) => name);
|
|
331
|
+
// Expand FS capability groups: fs_read implies glob+grep, fs_write implies edit
|
|
332
|
+
const newToolNames = expandFsCapabilities(rawToolNames);
|
|
333
|
+
const hasPMTag = event.tags.some((tag) => tag[0] === "pm");
|
|
334
|
+
const hasResetTag = event.tags.some((tag) => tag[0] === "reset");
|
|
335
|
+
|
|
336
|
+
if (isProjectScoped) {
|
|
337
|
+
// PROJECT-SCOPED CONFIG UPDATE (new schema)
|
|
338
|
+
// Uses updateProjectOverride() which handles dedup and delta tools
|
|
339
|
+
logger.info("Processing project-scoped agent config update", {
|
|
340
|
+
agentSlug: agent.slug,
|
|
341
|
+
projectDTag,
|
|
342
|
+
hasModel: !!newModel,
|
|
343
|
+
toolCount: newToolNames.length,
|
|
344
|
+
hasPM: hasPMTag,
|
|
345
|
+
hasReset: hasResetTag,
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
let updated: boolean;
|
|
349
|
+
|
|
350
|
+
if (hasResetTag) {
|
|
351
|
+
// Reset tag: clear entire project override
|
|
352
|
+
updated = await agentStorage.updateProjectOverride(
|
|
353
|
+
agentPubkey,
|
|
354
|
+
projectDTag!,
|
|
355
|
+
{},
|
|
356
|
+
true // reset=true
|
|
357
|
+
);
|
|
358
|
+
} else {
|
|
359
|
+
// Build the project-scoped override.
|
|
360
|
+
// Kind 24020 events carry a FULL tool list (no delta notation in events).
|
|
361
|
+
// The storage layer stores DELTA notation for project overrides.
|
|
362
|
+
// We must convert the full list from the event into a delta against defaults.
|
|
363
|
+
const projectOverride: AgentProjectConfig = {};
|
|
364
|
+
|
|
365
|
+
if (newModel) {
|
|
366
|
+
projectOverride.model = newModel;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Tool tags represent the exhaustive desired list for this project.
|
|
370
|
+
// We convert it to a delta against the agent's default tools for compact storage.
|
|
371
|
+
// Use RAW TAG PRESENCE (event.tags.some) not TagExtractor output length:
|
|
372
|
+
// TagExtractor.getToolTags() filters out empty tool names, so when an event
|
|
373
|
+
// carries ["tool", ""] to "clear all tools", toolTags.length would be 0 and
|
|
374
|
+
// the guard would silently skip delta computation. By checking the raw event
|
|
375
|
+
// tags we correctly detect "tool tag is present" regardless of the name value.
|
|
376
|
+
const hasRawToolTags = event.tags.some((tag) => tag[0] === "tool");
|
|
377
|
+
if (hasRawToolTags) {
|
|
378
|
+
const storedAgent = await agentStorage.loadAgent(agentPubkey);
|
|
379
|
+
const defaultTools = storedAgent?.default?.tools ?? [];
|
|
380
|
+
const toolsDelta = computeToolsDelta(defaultTools, newToolNames);
|
|
381
|
+
// If delta is non-empty, store it. An empty delta means desired == defaults,
|
|
382
|
+
// so no override is needed. Note: "desired = empty list" with non-empty
|
|
383
|
+
// defaults produces removal entries (non-empty delta), so that IS stored.
|
|
384
|
+
if (toolsDelta.length > 0) {
|
|
385
|
+
projectOverride.tools = toolsDelta;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
updated = await agentStorage.updateProjectOverride(
|
|
390
|
+
agentPubkey,
|
|
391
|
+
projectDTag!,
|
|
392
|
+
projectOverride
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (updated) {
|
|
397
|
+
await agentRegistry.reloadAgent(agentPubkey);
|
|
398
|
+
configUpdated = true;
|
|
399
|
+
logger.info("Updated project-scoped config for agent", {
|
|
400
|
+
agentSlug: agent.slug,
|
|
401
|
+
projectDTag,
|
|
402
|
+
reset: hasResetTag,
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// PM designation is handled separately from projectOverrides.
|
|
407
|
+
// - reset tag: always clears project-scoped PM (full project config wipe)
|
|
408
|
+
// - pm tag present: sets project-scoped PM to true
|
|
409
|
+
// - no pm tag (and no reset): no change to PM
|
|
410
|
+
if (hasResetTag) {
|
|
411
|
+
// A reset clears ALL project config including PM designation
|
|
412
|
+
await agentStorage.updateProjectScopedIsPM(agentPubkey, projectDTag!, undefined);
|
|
413
|
+
await agentRegistry.reloadAgent(agentPubkey);
|
|
414
|
+
configUpdated = true;
|
|
415
|
+
} else if (hasPMTag) {
|
|
416
|
+
await agentStorage.updateProjectScopedIsPM(agentPubkey, projectDTag!, true);
|
|
417
|
+
await agentRegistry.reloadAgent(agentPubkey);
|
|
418
|
+
configUpdated = true;
|
|
419
|
+
}
|
|
420
|
+
} else {
|
|
421
|
+
// GLOBAL (DEFAULT) CONFIG UPDATE
|
|
422
|
+
// A 24020 with no a-tag writes to the agent's default config block
|
|
423
|
+
logger.info("Processing global agent config update", {
|
|
424
|
+
agentSlug: agent.slug,
|
|
425
|
+
hasModel: !!newModel,
|
|
426
|
+
toolCount: newToolNames.length,
|
|
427
|
+
hasPM: hasPMTag,
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
// Build the default config to write.
|
|
431
|
+
// Non-a-tagged 24020 events use PARTIAL UPDATE semantics:
|
|
432
|
+
// - Only fields explicitly present in the event are updated
|
|
433
|
+
// - Omitting a field means "no change" (not "clear")
|
|
434
|
+
// This is consistent with how project-scoped overrides work.
|
|
435
|
+
// Note: PM designation uses authoritative snapshot semantics (absence clears it)
|
|
436
|
+
// because it's a boolean flag, not a config value.
|
|
437
|
+
const defaultUpdates: AgentDefaultConfig = {};
|
|
438
|
+
|
|
439
|
+
// Only update model if a model tag is explicitly present AND has a non-empty value.
|
|
440
|
+
// event.tagValue("model") returns "" for ["model", ""], which would persist an
|
|
441
|
+
// empty string into agent.default.model if not guarded. We treat an empty model
|
|
442
|
+
// tag as a no-op so clients can safely omit/blank the model without clearing it.
|
|
443
|
+
const hasModelTag = event.tags.some((tag) => tag[0] === "model");
|
|
444
|
+
if (hasModelTag && newModel) {
|
|
445
|
+
defaultUpdates.model = newModel;
|
|
446
|
+
}
|
|
447
|
+
// If no model tag (or empty value), leave defaultUpdates.model unset → no change
|
|
448
|
+
|
|
449
|
+
// Only update tools if tool tags are explicitly present in the event
|
|
450
|
+
const hasToolTags = event.tags.some((tag) => tag[0] === "tool");
|
|
451
|
+
if (hasToolTags) {
|
|
452
|
+
// newToolNames may be empty (e.g. tool tags with no values), which clears defaults
|
|
453
|
+
defaultUpdates.tools = newToolNames;
|
|
454
|
+
}
|
|
455
|
+
// If no tool tags, leave defaultUpdates.tools unset → no change
|
|
456
|
+
|
|
457
|
+
// Global config update clears all project overrides
|
|
458
|
+
// This makes semantic sense: a global config update without project specifier
|
|
459
|
+
// resets the agent to have no project-specific overrides
|
|
460
|
+
const defaultUpdated = await agentStorage.updateDefaultConfig(agentPubkey, defaultUpdates, { clearProjectOverrides: true });
|
|
461
|
+
|
|
462
|
+
if (defaultUpdated) {
|
|
463
|
+
await agentRegistry.reloadAgent(agentPubkey);
|
|
464
|
+
configUpdated = true;
|
|
465
|
+
} else {
|
|
466
|
+
logger.warn("Failed to update default config", {
|
|
467
|
+
agentName: agent.slug,
|
|
468
|
+
agentPubkey: agent.pubkey,
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Check for PM designation tag: ["pm"] (no value, just the tag itself)
|
|
473
|
+
// Kind 24020 events are authoritative snapshots - presence of ["pm"] tag sets isPM=true,
|
|
474
|
+
// absence clears it (sets isPM=false).
|
|
475
|
+
const pmUpdated = await agentStorage.updateAgentIsPM(agentPubkey, hasPMTag);
|
|
476
|
+
|
|
477
|
+
if (pmUpdated) {
|
|
478
|
+
await agentRegistry.reloadAgent(agentPubkey);
|
|
479
|
+
configUpdated = true;
|
|
480
|
+
logger.info(hasPMTag ? "Set PM designation for agent via kind 24020 event" : "Cleared PM designation for agent via kind 24020 event", {
|
|
481
|
+
agentSlug: agent.slug,
|
|
482
|
+
agentPubkey: agentPubkey.substring(0, 8),
|
|
483
|
+
});
|
|
484
|
+
} else {
|
|
485
|
+
logger.warn("Failed to update PM designation", {
|
|
486
|
+
agentSlug: agent.slug,
|
|
487
|
+
agentPubkey: agentPubkey.substring(0, 8),
|
|
488
|
+
newValue: hasPMTag,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Immediately publish updated project status if config was changed
|
|
494
|
+
if (configUpdated && projectContext.statusPublisher) {
|
|
495
|
+
await projectContext.statusPublisher.publishImmediately();
|
|
496
|
+
logger.info("Published updated project status after agent config change", {
|
|
497
|
+
agentSlug: agent.slug,
|
|
498
|
+
agentPubkey: agentPubkey.substring(0, 8),
|
|
499
|
+
projectScoped: isProjectScoped,
|
|
500
|
+
projectDTag,
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
} catch (error) {
|
|
504
|
+
logger.error("Failed to handle config change", {
|
|
505
|
+
eventId: event.id,
|
|
506
|
+
error: formatAnyError(error),
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
private async handleStopEvent(event: NDKEvent): Promise<void> {
|
|
512
|
+
const eTags = event.getMatchingTags("e");
|
|
513
|
+
const pTags = event.getMatchingTags("p");
|
|
514
|
+
|
|
515
|
+
if (eTags.length === 0) {
|
|
516
|
+
logger.warn("[EventHandler] Stop event received with no e-tags", {
|
|
517
|
+
eventId: event.id?.substring(0, 8),
|
|
518
|
+
});
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
let ralsAborted = 0;
|
|
523
|
+
|
|
524
|
+
const projectCtx = getProjectContext();
|
|
525
|
+
const ralRegistry = RALRegistry.getInstance();
|
|
526
|
+
const stopTracer = trace.getTracer("tenex.event-handler");
|
|
527
|
+
const reason = `stop signal from ${event.pubkey.substring(0, 8)}`;
|
|
528
|
+
|
|
529
|
+
for (const [, conversationId] of eTags) {
|
|
530
|
+
const conversation = ConversationStore.get(conversationId);
|
|
531
|
+
if (!conversation) continue;
|
|
532
|
+
const projectId = conversation.getProjectId();
|
|
533
|
+
if (!projectId) continue;
|
|
534
|
+
|
|
535
|
+
for (const [, agentPubkey] of pTags) {
|
|
536
|
+
const agent = projectCtx.getAgentByPubkey(agentPubkey);
|
|
537
|
+
if (agent) {
|
|
538
|
+
// Get the RAL's trace context to parent the stop span under agent execution
|
|
539
|
+
const activeRals = ralRegistry.getActiveRALs(agentPubkey, conversationId);
|
|
540
|
+
const targetRal = activeRals[0];
|
|
541
|
+
|
|
542
|
+
// Build parent context from stored trace info
|
|
543
|
+
let parentContext = otelContext.active();
|
|
544
|
+
if (targetRal?.traceId && targetRal?.executionSpanId) {
|
|
545
|
+
const parentSpanContext = {
|
|
546
|
+
traceId: targetRal.traceId,
|
|
547
|
+
spanId: targetRal.executionSpanId,
|
|
548
|
+
traceFlags: TraceFlags.SAMPLED,
|
|
549
|
+
isRemote: false,
|
|
550
|
+
};
|
|
551
|
+
parentContext = trace.setSpanContext(otelContext.active(), parentSpanContext);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const stopSpan = stopTracer.startSpan(
|
|
555
|
+
"tenex.stop_command",
|
|
556
|
+
{
|
|
557
|
+
attributes: {
|
|
558
|
+
"event.id": event.id,
|
|
559
|
+
"event.kind": event.kind,
|
|
560
|
+
"event.author": event.pubkey.substring(0, 8),
|
|
561
|
+
"stop.agent_slug": agent.slug,
|
|
562
|
+
"stop.agent_pubkey": agentPubkey.substring(0, 8),
|
|
563
|
+
"stop.conversation_id": shortenConversationId(conversationId),
|
|
564
|
+
"stop.active_rals": activeRals.length,
|
|
565
|
+
},
|
|
566
|
+
},
|
|
567
|
+
parentContext
|
|
568
|
+
);
|
|
569
|
+
|
|
570
|
+
const result = await ralRegistry.abortWithCascade(
|
|
571
|
+
agentPubkey, conversationId, projectId, reason
|
|
572
|
+
);
|
|
573
|
+
ralsAborted += result.abortedCount;
|
|
574
|
+
|
|
575
|
+
stopSpan.setAttribute("stop.rals_aborted", result.abortedCount);
|
|
576
|
+
stopSpan.setAttribute("stop.descendants_aborted", result.descendantConversations.length);
|
|
577
|
+
stopSpan.end();
|
|
578
|
+
|
|
579
|
+
logger.info(`[EventHandler] Stopped agent ${agent.slug} in conversation ${conversationId.substring(0, 8)}`, {
|
|
580
|
+
ralsAborted: result.abortedCount,
|
|
581
|
+
descendantsAborted: result.descendantConversations.length,
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
trace.getActiveSpan()?.addEvent("event_handler.stop_operations", {
|
|
588
|
+
"rals.aborted": ralsAborted,
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
private async handleLessonEvent(event: NDKEvent): Promise<void> {
|
|
593
|
+
const lesson = NDKAgentLesson.from(event);
|
|
594
|
+
|
|
595
|
+
// Check if we should trust this lesson
|
|
596
|
+
if (!shouldTrustLesson(lesson, event.pubkey)) {
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
const agentDefinitionId = lesson.agentDefinitionId;
|
|
601
|
+
|
|
602
|
+
if (!agentDefinitionId) {
|
|
603
|
+
logger.warn("Lesson event missing agent definition ID (e-tag)", {
|
|
604
|
+
eventId: event.id?.substring(0, 8),
|
|
605
|
+
publisher: event.pubkey.substring(0, 8),
|
|
606
|
+
});
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
try {
|
|
611
|
+
const projectCtx = getProjectContext();
|
|
612
|
+
|
|
613
|
+
// Find the agent(s) that match this definition ID
|
|
614
|
+
const agents = Array.from(projectCtx.agents.values()).filter(
|
|
615
|
+
(agent) => agent.eventId === agentDefinitionId
|
|
616
|
+
);
|
|
617
|
+
|
|
618
|
+
if (agents.length === 0) {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Store the lesson for each matching agent
|
|
623
|
+
for (const agent of agents) {
|
|
624
|
+
projectCtx.addLesson(agent.pubkey, lesson);
|
|
625
|
+
}
|
|
626
|
+
} catch (error) {
|
|
627
|
+
logger.error("Failed to handle lesson event", {
|
|
628
|
+
eventId: event.id,
|
|
629
|
+
error: formatAnyError(error),
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
private async handleReportEvent(event: NDKEvent): Promise<void> {
|
|
635
|
+
try {
|
|
636
|
+
const projectCtx = getProjectContext();
|
|
637
|
+
|
|
638
|
+
// Verify this report belongs to our project by checking a-tag
|
|
639
|
+
const projectTagId = projectCtx.project.tagId();
|
|
640
|
+
const reportProjectTag = event.tags.find(
|
|
641
|
+
(tag: string[]) => tag[0] === "a" && tag[1] === projectTagId
|
|
642
|
+
);
|
|
643
|
+
|
|
644
|
+
if (!reportProjectTag) {
|
|
645
|
+
// Report doesn't belong to our project, ignore
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Convert to NDKArticle
|
|
650
|
+
const article = NDKArticle.from(event);
|
|
651
|
+
|
|
652
|
+
// Check if report is deleted
|
|
653
|
+
const isDeleted = article.tags.some((tag: string[]) => tag[0] === "deleted");
|
|
654
|
+
|
|
655
|
+
// Add to project context cache
|
|
656
|
+
projectCtx.addReportFromArticle(article);
|
|
657
|
+
|
|
658
|
+
// Hydrate local storage if this event is newer than local copy
|
|
659
|
+
// Skip deleted reports - we don't want to hydrate them
|
|
660
|
+
if (!isDeleted && article.dTag && article.content) {
|
|
661
|
+
const localStore = getLocalReportStore();
|
|
662
|
+
const eventCreatedAt = event.created_at || Math.floor(Date.now() / 1000);
|
|
663
|
+
|
|
664
|
+
// Format content for local storage (matching report_write format)
|
|
665
|
+
const formattedContent = this.formatReportForLocalStorage(article);
|
|
666
|
+
|
|
667
|
+
// Construct addressable reference in NIP-33 format: kind:pubkey:d-tag
|
|
668
|
+
const addressableRef = `${event.kind}:${event.pubkey}:${article.dTag}`;
|
|
669
|
+
|
|
670
|
+
const hydrated = await localStore.hydrateFromNostr(
|
|
671
|
+
article.dTag,
|
|
672
|
+
formattedContent,
|
|
673
|
+
addressableRef,
|
|
674
|
+
eventCreatedAt
|
|
675
|
+
);
|
|
676
|
+
|
|
677
|
+
if (hydrated) {
|
|
678
|
+
trace.getActiveSpan()?.addEvent("event_handler.report_hydrated", {
|
|
679
|
+
"report.slug": article.dTag,
|
|
680
|
+
"report.addressableRef": addressableRef.substring(0, 20),
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
trace.getActiveSpan()?.addEvent("event_handler.report_cached", {
|
|
686
|
+
"report.slug": article.dTag || "",
|
|
687
|
+
"report.author": event.pubkey.substring(0, 8),
|
|
688
|
+
"report.isDeleted": isDeleted,
|
|
689
|
+
"report.isMemorized": article.tags.some(
|
|
690
|
+
(tag: string[]) => tag[0] === "t" && tag[1] === "memorize"
|
|
691
|
+
),
|
|
692
|
+
});
|
|
693
|
+
} catch (error) {
|
|
694
|
+
logger.error("Failed to handle report event", {
|
|
695
|
+
eventId: event.id,
|
|
696
|
+
error: formatAnyError(error),
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* Format an NDKArticle for local storage
|
|
703
|
+
*/
|
|
704
|
+
private formatReportForLocalStorage(article: NDKArticle): string {
|
|
705
|
+
const lines: string[] = [];
|
|
706
|
+
|
|
707
|
+
// Add title
|
|
708
|
+
if (article.title) {
|
|
709
|
+
lines.push(`# ${article.title}`);
|
|
710
|
+
lines.push("");
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// Add summary
|
|
714
|
+
if (article.summary) {
|
|
715
|
+
lines.push(`> ${article.summary}`);
|
|
716
|
+
lines.push("");
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// Extract hashtags (excluding memorize tag)
|
|
720
|
+
const hashtags = article.tags
|
|
721
|
+
.filter((tag: string[]) => tag[0] === "t" && tag[1] !== "memorize")
|
|
722
|
+
.map((tag: string[]) => tag[1]);
|
|
723
|
+
|
|
724
|
+
if (hashtags.length > 0) {
|
|
725
|
+
lines.push(`**Tags:** ${hashtags.map((t) => `#${t}`).join(" ")}`);
|
|
726
|
+
lines.push("");
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
lines.push("---");
|
|
730
|
+
lines.push("");
|
|
731
|
+
|
|
732
|
+
// Add content
|
|
733
|
+
if (article.content) {
|
|
734
|
+
lines.push(article.content);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
return lines.join("\n");
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
private handleDefaultEvent(_event: NDKEvent): void {
|
|
741
|
+
// Unhandled event kinds are ignored silently
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
async cleanup(): Promise<void> {
|
|
745
|
+
// Save all conversations before shutting down
|
|
746
|
+
await ConversationStore.cleanup();
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
}
|