@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,651 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified kill tool - Abort agents or background shell processes
|
|
3
|
+
*
|
|
4
|
+
* This tool provides a unified interface for killing:
|
|
5
|
+
* 1. Agent executions (by conversation_id) - with cascading abort support
|
|
6
|
+
* 2. Background shell processes (by shell_id)
|
|
7
|
+
*
|
|
8
|
+
* When killing an agent by conversation_id, this tool will:
|
|
9
|
+
* - Abort the agent's execution in that conversation
|
|
10
|
+
* - Cascade the abort to all nested delegations (agents this agent delegated to)
|
|
11
|
+
* - Add aborted tuples to cooldown registry to prevent immediate re-routing
|
|
12
|
+
* - Block until all cascade aborts complete
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { AISdkTool, ToolExecutionContext } from "@/tools/types";
|
|
16
|
+
import { killBackgroundTask, getBackgroundTaskInfo, getAllBackgroundTasks } from "./shell";
|
|
17
|
+
import { RALRegistry } from "@/services/ral";
|
|
18
|
+
import { CooldownRegistry } from "@/services/CooldownRegistry";
|
|
19
|
+
import { ConversationStore } from "@/conversations/ConversationStore";
|
|
20
|
+
import { tool } from "ai";
|
|
21
|
+
import { z } from "zod";
|
|
22
|
+
import { logger } from "@/utils/logger";
|
|
23
|
+
import { shortenConversationId } from "@/utils/conversation-id";
|
|
24
|
+
import { trace } from "@opentelemetry/api";
|
|
25
|
+
import { resolvePrefixToId, normalizeNostrIdentifier } from "@/utils/nostr-entity-parser";
|
|
26
|
+
import { nip19 } from "nostr-tools";
|
|
27
|
+
import { isFullEventId, isShortEventId, isShellTaskId } from "@/types/event-ids";
|
|
28
|
+
|
|
29
|
+
const killSchema = z.object({
|
|
30
|
+
target: z
|
|
31
|
+
.string()
|
|
32
|
+
.min(1, "target is required")
|
|
33
|
+
.describe(
|
|
34
|
+
"The target to kill. Can be either:\n" +
|
|
35
|
+
"- A conversation ID (to abort an agent and cascade to nested delegations)\n" +
|
|
36
|
+
"- A shell task ID (to terminate a background shell process)"
|
|
37
|
+
),
|
|
38
|
+
reason: z
|
|
39
|
+
.string()
|
|
40
|
+
.optional()
|
|
41
|
+
.describe("Optional reason for the kill (used for agent aborts)"),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
type KillInput = z.infer<typeof killSchema>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Resolve a target ID from various formats to a canonical full ID.
|
|
48
|
+
*
|
|
49
|
+
* Supports:
|
|
50
|
+
* - 64-char hex: Already a full ID, returned as-is
|
|
51
|
+
* - 12-char hex: Resolve via PrefixKVStore or RALRegistry fallback
|
|
52
|
+
* - 7-char alphanumeric: Shell task ID (returned as-is for separate handling)
|
|
53
|
+
* - NIP-19 formats (nevent, note): Decode to 64-char hex
|
|
54
|
+
*
|
|
55
|
+
* @param input - The target identifier in any supported format
|
|
56
|
+
* @returns Object with resolved ID and type, or null if resolution failed
|
|
57
|
+
*/
|
|
58
|
+
async function resolveTargetId(input: string): Promise<{
|
|
59
|
+
id: string;
|
|
60
|
+
type: 'conversation' | 'shell' | 'unknown';
|
|
61
|
+
wasResolved: boolean;
|
|
62
|
+
} | null> {
|
|
63
|
+
const trimmed = input.trim().toLowerCase();
|
|
64
|
+
|
|
65
|
+
// 64-char hex: already a full ID (use typed guard)
|
|
66
|
+
if (isFullEventId(trimmed)) {
|
|
67
|
+
return { id: trimmed, type: 'conversation', wasResolved: false };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 12-char hex: resolve via prefix store or RALRegistry fallback (use typed guard)
|
|
71
|
+
if (isShortEventId(trimmed)) {
|
|
72
|
+
// Try PrefixKVStore first (O(1) lookup)
|
|
73
|
+
const resolved = resolvePrefixToId(trimmed);
|
|
74
|
+
if (resolved) {
|
|
75
|
+
logger.debug("[kill.resolveTargetId] Resolved 12-char prefix via PrefixKVStore", {
|
|
76
|
+
prefix: trimmed,
|
|
77
|
+
fullId: resolved.substring(0, 12),
|
|
78
|
+
});
|
|
79
|
+
return { id: resolved, type: 'conversation', wasResolved: true };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Fallback: scan RALRegistry for active delegations
|
|
83
|
+
const ralRegistry = RALRegistry.getInstance();
|
|
84
|
+
const ralResolved = ralRegistry.resolveDelegationPrefix(trimmed);
|
|
85
|
+
if (ralResolved) {
|
|
86
|
+
logger.debug("[kill.resolveTargetId] Resolved 12-char prefix via RALRegistry", {
|
|
87
|
+
prefix: trimmed,
|
|
88
|
+
fullId: ralResolved.substring(0, 12),
|
|
89
|
+
});
|
|
90
|
+
return { id: ralResolved, type: 'conversation', wasResolved: true };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Prefix not found - could still be valid but unknown
|
|
94
|
+
logger.debug("[kill.resolveTargetId] Could not resolve 12-char prefix", {
|
|
95
|
+
prefix: trimmed,
|
|
96
|
+
});
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 7-char alphanumeric: shell task ID (different ID space, use typed guard)
|
|
101
|
+
if (isShellTaskId(trimmed)) {
|
|
102
|
+
return { id: trimmed, type: 'shell', wasResolved: false };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// NIP-19 formats (nevent, note)
|
|
106
|
+
const normalized = normalizeNostrIdentifier(input);
|
|
107
|
+
if (normalized && (normalized.startsWith("nevent1") || normalized.startsWith("note1"))) {
|
|
108
|
+
try {
|
|
109
|
+
const decoded = nip19.decode(normalized);
|
|
110
|
+
if (decoded.type === "note" && typeof decoded.data === "string") {
|
|
111
|
+
const eventId = decoded.data.toLowerCase();
|
|
112
|
+
return { id: eventId, type: 'conversation', wasResolved: true };
|
|
113
|
+
}
|
|
114
|
+
if (decoded.type === "nevent" && typeof decoded.data === "object" && decoded.data !== null) {
|
|
115
|
+
const data = decoded.data as { id: string };
|
|
116
|
+
const eventId = data.id.toLowerCase();
|
|
117
|
+
return { id: eventId, type: 'conversation', wasResolved: true };
|
|
118
|
+
}
|
|
119
|
+
} catch (error) {
|
|
120
|
+
logger.debug("[kill.resolveTargetId] Failed to decode NIP-19 identifier", {
|
|
121
|
+
input,
|
|
122
|
+
error: error instanceof Error ? error.message : String(error),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// UUID format (legacy shell task IDs) - check as fallback
|
|
128
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(trimmed)) {
|
|
129
|
+
return { id: trimmed, type: 'shell', wasResolved: false };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
interface KillOutput {
|
|
136
|
+
success: boolean;
|
|
137
|
+
message: string;
|
|
138
|
+
target: string;
|
|
139
|
+
targetType: "agent" | "shell";
|
|
140
|
+
/** For agent kills: number of agents aborted in cascade */
|
|
141
|
+
cascadeAbortCount?: number;
|
|
142
|
+
/** For agent kills: list of aborted conversation:agent tuples */
|
|
143
|
+
abortedTuples?: Array<{ conversationId: string; agentPubkey: string }>;
|
|
144
|
+
/** For shell kills: process info */
|
|
145
|
+
pid?: number;
|
|
146
|
+
taskInfo?: {
|
|
147
|
+
command: string;
|
|
148
|
+
description: string | null;
|
|
149
|
+
outputFile: string;
|
|
150
|
+
startTime: string;
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Core implementation of the unified kill functionality
|
|
156
|
+
*/
|
|
157
|
+
async function executeKill(input: KillInput, context: ToolExecutionContext): Promise<KillOutput> {
|
|
158
|
+
const { target, reason } = input;
|
|
159
|
+
|
|
160
|
+
// Normalize target once for consistent matching throughout
|
|
161
|
+
const normalizedTarget = target.trim().toLowerCase();
|
|
162
|
+
|
|
163
|
+
// Get caller's project ID for filtering (prevents cross-project metadata leakage)
|
|
164
|
+
const callerConversation = context.getConversation?.();
|
|
165
|
+
const callerProjectId = callerConversation?.getProjectId();
|
|
166
|
+
|
|
167
|
+
// First, try to resolve the target ID to a canonical format
|
|
168
|
+
const resolved = await resolveTargetId(target);
|
|
169
|
+
|
|
170
|
+
if (resolved) {
|
|
171
|
+
// Successfully resolved to a known format
|
|
172
|
+
if (resolved.type === 'shell') {
|
|
173
|
+
// Shell task ID - check if it exists and handle
|
|
174
|
+
const taskInfo = getBackgroundTaskInfo(resolved.id);
|
|
175
|
+
if (taskInfo) {
|
|
176
|
+
return killShellTask(resolved.id, context);
|
|
177
|
+
}
|
|
178
|
+
// Shell ID format but task not found - fall through to error
|
|
179
|
+
} else if (resolved.type === 'conversation') {
|
|
180
|
+
// Conversation ID (64-char hex) - check if it exists
|
|
181
|
+
const isConversationId = ConversationStore.has(resolved.id);
|
|
182
|
+
if (isConversationId) {
|
|
183
|
+
return await killAgent(resolved.id, reason, context);
|
|
184
|
+
}
|
|
185
|
+
// Also try prefix match on the resolved ID (in case it's a prefix itself)
|
|
186
|
+
const allConversations = ConversationStore.getAll();
|
|
187
|
+
const matchingConv = allConversations.find((c) => c.id.startsWith(resolved.id));
|
|
188
|
+
if (matchingConv) {
|
|
189
|
+
return await killAgent(matchingConv.id, reason, context);
|
|
190
|
+
}
|
|
191
|
+
// Resolved ID but not found in store - fall through to error
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Resolution failed or target not found
|
|
196
|
+
// Legacy fallback: try direct lookup with normalized target (handles edge cases)
|
|
197
|
+
const isDirectConversationId = ConversationStore.has(normalizedTarget);
|
|
198
|
+
// Note: getBackgroundTaskInfo returns undefined for non-existent tasks, so use truthiness check
|
|
199
|
+
const isDirectShellTaskId = !isDirectConversationId && !!getBackgroundTaskInfo(normalizedTarget);
|
|
200
|
+
|
|
201
|
+
if (isDirectConversationId) {
|
|
202
|
+
return await killAgent(normalizedTarget, reason, context);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (isDirectShellTaskId) {
|
|
206
|
+
return killShellTask(normalizedTarget, context);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Try partial match for conversation ID (prefix lookup - legacy fallback)
|
|
210
|
+
// Use normalized target to handle uppercase input
|
|
211
|
+
const allConversations = ConversationStore.getAll();
|
|
212
|
+
const matchingConv = allConversations.find((c) => c.id.startsWith(normalizedTarget));
|
|
213
|
+
|
|
214
|
+
if (matchingConv) {
|
|
215
|
+
// Found a conversation by prefix - use it
|
|
216
|
+
return await killAgent(matchingConv.id, reason, context);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Not found - provide helpful error with project-filtered listings
|
|
220
|
+
// SECURITY: Only show tasks/conversations from caller's project to prevent metadata leakage
|
|
221
|
+
let errorMessage = `Target '${target}' not found.`;
|
|
222
|
+
|
|
223
|
+
if (callerProjectId) {
|
|
224
|
+
// Filter by caller's project to prevent cross-project information disclosure
|
|
225
|
+
const projectTasks = getAllBackgroundTasks().filter(
|
|
226
|
+
(t) => t.projectId === callerProjectId
|
|
227
|
+
);
|
|
228
|
+
const projectConversations = allConversations.filter(
|
|
229
|
+
(c) => c.getProjectId() === callerProjectId
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
if (projectTasks.length > 0) {
|
|
233
|
+
errorMessage += ` Available background tasks in this project: ${projectTasks
|
|
234
|
+
.map((t) => t.taskId)
|
|
235
|
+
.join(", ")}.`;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (projectConversations.length > 0) {
|
|
239
|
+
errorMessage += ` Active conversations in this project: ${projectConversations
|
|
240
|
+
.map((c) => c.id.substring(0, 12))
|
|
241
|
+
.join(", ")}.`;
|
|
242
|
+
} else if (projectTasks.length === 0) {
|
|
243
|
+
errorMessage += " No active tasks or conversations found in this project.";
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
// No project context - return generic error without enumeration
|
|
247
|
+
errorMessage += " Unable to list available targets without project context.";
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
success: false,
|
|
252
|
+
message: errorMessage,
|
|
253
|
+
target,
|
|
254
|
+
targetType: "agent",
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Kill an agent execution with cascading abort
|
|
260
|
+
*/
|
|
261
|
+
async function killAgent(
|
|
262
|
+
conversationId: string,
|
|
263
|
+
reason: string | undefined,
|
|
264
|
+
context: ToolExecutionContext
|
|
265
|
+
): Promise<KillOutput> {
|
|
266
|
+
const ralRegistry = RALRegistry.getInstance();
|
|
267
|
+
const cooldownRegistry = CooldownRegistry.getInstance();
|
|
268
|
+
const conversation = ConversationStore.get(conversationId);
|
|
269
|
+
|
|
270
|
+
if (!conversation) {
|
|
271
|
+
return {
|
|
272
|
+
success: false,
|
|
273
|
+
message: `Conversation ${conversationId.substring(0, 12)} not found`,
|
|
274
|
+
target: conversationId,
|
|
275
|
+
targetType: "agent",
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Find the agent executing in this conversation
|
|
280
|
+
// Check BOTH ConversationStore (where RALs are registered during stream setup)
|
|
281
|
+
// AND RALRegistry (where RALs are created earlier during RAL resolution)
|
|
282
|
+
// This prevents a race condition where kill is called after RAL creation but before stream setup
|
|
283
|
+
const activeRals = conversation.getAllActiveRals();
|
|
284
|
+
const ralRegistryEntries = ralRegistry.getConversationEntries(conversationId);
|
|
285
|
+
|
|
286
|
+
// Build a combined set of agent pubkeys that have active RALs
|
|
287
|
+
const activeAgentPubkeys = new Set<string>();
|
|
288
|
+
for (const [pubkey] of activeRals) {
|
|
289
|
+
activeAgentPubkeys.add(pubkey);
|
|
290
|
+
}
|
|
291
|
+
for (const entry of ralRegistryEntries) {
|
|
292
|
+
activeAgentPubkeys.add(entry.agentPubkey);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Get projectId for proper cooldown isolation (needed for both active and pre-emptive kills)
|
|
296
|
+
const projectId = conversation.getProjectId();
|
|
297
|
+
if (!projectId) {
|
|
298
|
+
return {
|
|
299
|
+
success: false,
|
|
300
|
+
message: `Cannot abort: conversation ${conversationId.substring(0, 12)} has no project ID`,
|
|
301
|
+
target: conversationId,
|
|
302
|
+
targetType: "agent",
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// === AUTHORIZATION: Project Isolation Check (BEFORE pre-emptive or active kill) ===
|
|
307
|
+
// This check MUST happen before any kill operation to prevent cross-project kills.
|
|
308
|
+
// Moving this check before the pre-emptive kill branch fixes the security bug where
|
|
309
|
+
// a caller with no matching project context could kill a pending delegation.
|
|
310
|
+
const callerConversation = context.getConversation?.();
|
|
311
|
+
const callerProjectId = callerConversation?.getProjectId();
|
|
312
|
+
|
|
313
|
+
if (!callerProjectId) {
|
|
314
|
+
logger.warn("[kill] Authorization check failed: caller has no project context", {
|
|
315
|
+
callerAgent: context.agent.slug,
|
|
316
|
+
targetConversationId: conversationId.substring(0, 12),
|
|
317
|
+
targetProjectId: projectId.substring(0, 12),
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
trace.getActiveSpan()?.addEvent("kill.authorization_failed", {
|
|
321
|
+
"kill.reason": "caller_no_project",
|
|
322
|
+
"kill.caller_agent": context.agent.slug,
|
|
323
|
+
"kill.target_conversation_id": shortenConversationId(conversationId),
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
success: false,
|
|
328
|
+
message: `Authorization failed: cannot kill agents without project context`,
|
|
329
|
+
target: conversationId,
|
|
330
|
+
targetType: "agent",
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (callerProjectId !== projectId) {
|
|
335
|
+
logger.warn("[kill] Authorization check failed: cross-project kill attempt blocked", {
|
|
336
|
+
callerAgent: context.agent.slug,
|
|
337
|
+
callerProjectId: callerProjectId.substring(0, 12),
|
|
338
|
+
targetConversationId: conversationId.substring(0, 12),
|
|
339
|
+
targetProjectId: projectId.substring(0, 12),
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
trace.getActiveSpan()?.addEvent("kill.authorization_failed", {
|
|
343
|
+
"kill.reason": "cross_project_kill_blocked",
|
|
344
|
+
"kill.caller_agent": context.agent.slug,
|
|
345
|
+
"kill.caller_project_id": callerProjectId.substring(0, 12),
|
|
346
|
+
"kill.target_project_id": projectId.substring(0, 12),
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
success: false,
|
|
351
|
+
message: `Authorization failed: cannot kill agents in other projects (target: ${projectId.substring(0, 12)}, caller: ${callerProjectId.substring(0, 12)})`,
|
|
352
|
+
target: conversationId,
|
|
353
|
+
targetType: "agent",
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// If no active agents found, try PRE-EMPTIVE KILL:
|
|
358
|
+
// The conversation may be a delegation that hasn't started executing yet.
|
|
359
|
+
// Look up the recipient agent from the delegation info and mark it as killed
|
|
360
|
+
// so when it eventually starts, it will abort immediately.
|
|
361
|
+
if (activeAgentPubkeys.size === 0) {
|
|
362
|
+
const delegationRecipient = ralRegistry.getDelegationRecipientPubkey(conversationId);
|
|
363
|
+
|
|
364
|
+
if (delegationRecipient) {
|
|
365
|
+
// Pre-emptive kill: Mark the agent+conversation as killed before it starts
|
|
366
|
+
ralRegistry.markAgentConversationKilled(delegationRecipient, conversationId);
|
|
367
|
+
|
|
368
|
+
trace.getActiveSpan()?.addEvent("kill.pre_emptive_kill", {
|
|
369
|
+
"kill.conversation_id": shortenConversationId(conversationId),
|
|
370
|
+
"kill.recipient_pubkey": delegationRecipient.substring(0, 12),
|
|
371
|
+
"kill.reason": reason ?? "pre-emptive kill via kill tool",
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
logger.info("[kill] Pre-emptive kill: agent will be aborted when it starts", {
|
|
375
|
+
conversationId: shortenConversationId(conversationId),
|
|
376
|
+
recipientPubkey: delegationRecipient.substring(0, 12),
|
|
377
|
+
reason,
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// Also mark the parent delegation as killed
|
|
381
|
+
ralRegistry.markParentDelegationKilled(conversationId);
|
|
382
|
+
|
|
383
|
+
// Add to cooldown registry
|
|
384
|
+
const cooldownRegistry = CooldownRegistry.getInstance();
|
|
385
|
+
cooldownRegistry.add(projectId, conversationId, delegationRecipient, reason);
|
|
386
|
+
|
|
387
|
+
return {
|
|
388
|
+
success: true,
|
|
389
|
+
message: `Pre-emptive kill: agent will be aborted when it starts in conversation ${conversationId.substring(0, 12)}`,
|
|
390
|
+
target: conversationId,
|
|
391
|
+
targetType: "agent",
|
|
392
|
+
cascadeAbortCount: 1,
|
|
393
|
+
abortedTuples: [{ conversationId, agentPubkey: delegationRecipient }],
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// No active agents and not a known delegation - nothing to kill
|
|
398
|
+
return {
|
|
399
|
+
success: false,
|
|
400
|
+
message: `No active agents found in conversation ${conversationId.substring(0, 12)}`,
|
|
401
|
+
target: conversationId,
|
|
402
|
+
targetType: "agent",
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// NOTE: Authorization check was already performed at the top of killAgent(),
|
|
407
|
+
// before the pre-emptive kill branch. No duplicate check needed here.
|
|
408
|
+
|
|
409
|
+
// Get the first active agent (in multi-agent conversations, we abort all)
|
|
410
|
+
// Use the combined set that includes both ConversationStore and RALRegistry
|
|
411
|
+
const agentPubkey = Array.from(activeAgentPubkeys)[0];
|
|
412
|
+
|
|
413
|
+
trace.getActiveSpan()?.addEvent("kill.agent_abort_starting", {
|
|
414
|
+
"kill.project_id": projectId.substring(0, 12),
|
|
415
|
+
"kill.conversation_id": shortenConversationId(conversationId),
|
|
416
|
+
"kill.agent_pubkey": agentPubkey.substring(0, 12),
|
|
417
|
+
"kill.reason": reason ?? "manual kill",
|
|
418
|
+
"kill.cascade_enabled": true,
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
logger.info("[kill] Aborting agent with cascade", {
|
|
422
|
+
projectId: projectId.substring(0, 12),
|
|
423
|
+
conversationId: shortenConversationId(conversationId),
|
|
424
|
+
agentPubkey: agentPubkey.substring(0, 12),
|
|
425
|
+
reason,
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
// Perform cascading abort (blocks until all descendants are aborted)
|
|
429
|
+
const result = await ralRegistry.abortWithCascade(
|
|
430
|
+
agentPubkey,
|
|
431
|
+
conversationId,
|
|
432
|
+
projectId,
|
|
433
|
+
reason ?? "manual kill via kill tool",
|
|
434
|
+
cooldownRegistry
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
trace.getActiveSpan()?.addEvent("kill.agent_abort_completed", {
|
|
438
|
+
"kill.conversation_id": shortenConversationId(conversationId),
|
|
439
|
+
"kill.agent_pubkey": agentPubkey.substring(0, 12),
|
|
440
|
+
"kill.direct_aborted": result.abortedCount,
|
|
441
|
+
"kill.cascade_aborted": result.descendantConversations.length,
|
|
442
|
+
"kill.total_aborted": result.abortedCount + result.descendantConversations.length,
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
const totalAborted = result.abortedCount + result.descendantConversations.length;
|
|
446
|
+
|
|
447
|
+
return {
|
|
448
|
+
success: true,
|
|
449
|
+
message: `Aborted agent in conversation ${conversationId.substring(0, 12)} with ${result.descendantConversations.length} cascaded aborts`,
|
|
450
|
+
target: conversationId,
|
|
451
|
+
targetType: "agent",
|
|
452
|
+
cascadeAbortCount: totalAborted,
|
|
453
|
+
abortedTuples: [
|
|
454
|
+
{ conversationId, agentPubkey },
|
|
455
|
+
...result.descendantConversations,
|
|
456
|
+
],
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Kill a background shell task
|
|
462
|
+
*/
|
|
463
|
+
function killShellTask(taskId: string, context: ToolExecutionContext): KillOutput {
|
|
464
|
+
// Get task info before killing (for reporting)
|
|
465
|
+
const taskInfo = getBackgroundTaskInfo(taskId);
|
|
466
|
+
|
|
467
|
+
// === AUTHORIZATION: Shell Task Project Isolation ===
|
|
468
|
+
// Shell tasks are bound to projectId at creation time.
|
|
469
|
+
// Verify caller's projectId matches task's projectId before allowing kill operation.
|
|
470
|
+
const callerConversation = context.getConversation?.();
|
|
471
|
+
const callerProjectId = callerConversation?.getProjectId();
|
|
472
|
+
|
|
473
|
+
if (!callerProjectId) {
|
|
474
|
+
logger.warn("[kill] Authorization check failed: caller has no project context for shell kill", {
|
|
475
|
+
callerAgent: context.agent?.slug ?? "unknown",
|
|
476
|
+
targetTaskId: taskId,
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
trace.getActiveSpan()?.addEvent("kill.shell_authorization_failed", {
|
|
480
|
+
"kill.reason": "caller_no_project",
|
|
481
|
+
"kill.caller_agent": context.agent?.slug ?? "unknown",
|
|
482
|
+
"kill.target_task_id": taskId,
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
return {
|
|
486
|
+
success: false,
|
|
487
|
+
message: `Authorization failed: cannot kill shell tasks without project context`,
|
|
488
|
+
target: taskId,
|
|
489
|
+
targetType: "shell",
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Verify task exists and belongs to caller's project
|
|
494
|
+
if (!taskInfo) {
|
|
495
|
+
logger.warn("[kill] Task not found", {
|
|
496
|
+
callerAgent: context.agent?.slug ?? "unknown",
|
|
497
|
+
targetTaskId: taskId,
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
return {
|
|
501
|
+
success: false,
|
|
502
|
+
message: `Task ${taskId} not found`,
|
|
503
|
+
target: taskId,
|
|
504
|
+
targetType: "shell",
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// CRITICAL: Enforce project isolation - deny kill if projectId doesn't match
|
|
509
|
+
if (taskInfo.projectId !== callerProjectId) {
|
|
510
|
+
logger.warn("[kill] Authorization check failed: project isolation violation", {
|
|
511
|
+
callerAgent: context.agent?.slug ?? "unknown",
|
|
512
|
+
callerProjectId: callerProjectId.substring(0, 12),
|
|
513
|
+
taskProjectId: taskInfo.projectId.substring(0, 12),
|
|
514
|
+
targetTaskId: taskId,
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
trace.getActiveSpan()?.addEvent("kill.shell_authorization_failed", {
|
|
518
|
+
"kill.reason": "project_mismatch",
|
|
519
|
+
"kill.caller_agent": context.agent?.slug ?? "unknown",
|
|
520
|
+
"kill.caller_project_id": callerProjectId.substring(0, 12),
|
|
521
|
+
"kill.task_project_id": taskInfo.projectId.substring(0, 12),
|
|
522
|
+
"kill.target_task_id": taskId,
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
return {
|
|
526
|
+
success: false,
|
|
527
|
+
message: `Authorization failed: task ${taskId} belongs to a different project`,
|
|
528
|
+
target: taskId,
|
|
529
|
+
targetType: "shell",
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Log audit trail for shell kill
|
|
534
|
+
logger.info("[kill] Shell task kill requested", {
|
|
535
|
+
callerAgent: context.agent?.slug ?? "unknown",
|
|
536
|
+
callerConversationId: context.conversationId?.substring(0, 12) ?? "unknown",
|
|
537
|
+
callerProjectId: callerProjectId.substring(0, 12),
|
|
538
|
+
targetTaskId: taskId,
|
|
539
|
+
taskCommand: taskInfo?.command,
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
trace.getActiveSpan()?.addEvent("kill.shell_task_killing", {
|
|
543
|
+
"kill.caller_agent": context.agent?.slug ?? "unknown",
|
|
544
|
+
"kill.caller_project_id": callerProjectId.substring(0, 12),
|
|
545
|
+
"kill.target_task_id": taskId,
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
// Attempt to kill the task
|
|
549
|
+
const result = killBackgroundTask(taskId);
|
|
550
|
+
|
|
551
|
+
const output: KillOutput = {
|
|
552
|
+
success: result.success,
|
|
553
|
+
message: result.message,
|
|
554
|
+
target: taskId,
|
|
555
|
+
targetType: "shell",
|
|
556
|
+
pid: result.pid,
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
// Include task info if it was found
|
|
560
|
+
if (taskInfo) {
|
|
561
|
+
output.taskInfo = {
|
|
562
|
+
command: taskInfo.command,
|
|
563
|
+
description: taskInfo.description,
|
|
564
|
+
outputFile: taskInfo.outputFile,
|
|
565
|
+
startTime: taskInfo.startTime.toISOString(),
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// If task not found, suggest listing tasks (filtered by project to prevent metadata leakage)
|
|
570
|
+
if (!result.success && !taskInfo && callerProjectId) {
|
|
571
|
+
// SECURITY: Only show tasks from caller's project to prevent cross-project information disclosure
|
|
572
|
+
const projectTasks = getAllBackgroundTasks().filter(
|
|
573
|
+
(t) => t.projectId === callerProjectId
|
|
574
|
+
);
|
|
575
|
+
if (projectTasks.length > 0) {
|
|
576
|
+
output.message += `\n\nAvailable background tasks in this project: ${projectTasks
|
|
577
|
+
.map((t) => t.taskId)
|
|
578
|
+
.join(", ")}`;
|
|
579
|
+
} else {
|
|
580
|
+
output.message += "\n\nNo background tasks are currently running in this project.";
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Log audit trail for result
|
|
585
|
+
if (result.success) {
|
|
586
|
+
logger.info("[kill] Shell task killed successfully", {
|
|
587
|
+
taskId,
|
|
588
|
+
pid: result.pid,
|
|
589
|
+
callerAgent: context.agent?.slug ?? "unknown",
|
|
590
|
+
projectId: callerProjectId.substring(0, 12),
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
return output;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Create an AI SDK tool for the unified kill command
|
|
599
|
+
*/
|
|
600
|
+
export function createKillTool(context: ToolExecutionContext): AISdkTool {
|
|
601
|
+
const aiTool = tool({
|
|
602
|
+
description:
|
|
603
|
+
"Terminate an agent execution or background shell process. " +
|
|
604
|
+
"For agents: aborts the agent and all nested delegations with 15s cooldown. " +
|
|
605
|
+
"For shells: terminates the background process. " +
|
|
606
|
+
"IMPORTANT: This tool blocks until all cascade aborts complete.",
|
|
607
|
+
|
|
608
|
+
inputSchema: killSchema,
|
|
609
|
+
|
|
610
|
+
execute: async (input: KillInput) => {
|
|
611
|
+
return await executeKill(input, context);
|
|
612
|
+
},
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
Object.defineProperty(aiTool, "getHumanReadableContent", {
|
|
616
|
+
value: ({ target }: KillInput) => {
|
|
617
|
+
// Use the same detection logic as resolveTargetId for consistent classification
|
|
618
|
+
const trimmed = target.trim().toLowerCase();
|
|
619
|
+
|
|
620
|
+
// Check ID format to determine type
|
|
621
|
+
// Shell task IDs are 7-char alphanumeric
|
|
622
|
+
if (isShellTaskId(trimmed)) {
|
|
623
|
+
return `Killing background task ${target}`;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// Full event IDs (64-char hex) or short prefixes (12-char hex)
|
|
627
|
+
// are conversation/agent targets
|
|
628
|
+
if (isFullEventId(trimmed) || isShortEventId(trimmed)) {
|
|
629
|
+
return `Killing agent in conversation ${trimmed.substring(0, 12)} (with cascade)`;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// NIP-19 formats (nevent, note) are also agent targets
|
|
633
|
+
const normalized = normalizeNostrIdentifier(target);
|
|
634
|
+
if (normalized && (normalized.startsWith("nevent1") || normalized.startsWith("note1"))) {
|
|
635
|
+
return `Killing agent from ${target.substring(0, 20)}... (with cascade)`;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// UUID format is shell task (legacy)
|
|
639
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(trimmed)) {
|
|
640
|
+
return `Killing background task ${target}`;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// Unknown format - let executeKill determine and error appropriately
|
|
644
|
+
return `Killing target ${target.substring(0, 12)}...`;
|
|
645
|
+
},
|
|
646
|
+
enumerable: false,
|
|
647
|
+
configurable: true,
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
return aiTool as AISdkTool;
|
|
651
|
+
}
|