@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,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified Search Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* Interfaces for the search provider pattern that enables querying
|
|
5
|
+
* across multiple RAG collections (reports, conversations, lessons).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A single search result from any collection.
|
|
10
|
+
* Contains enough metadata for the agent to:
|
|
11
|
+
* 1. Understand what the result is about
|
|
12
|
+
* 2. Retrieve the full document via the appropriate tool
|
|
13
|
+
* (report_read, lesson_get, conversation_get)
|
|
14
|
+
*/
|
|
15
|
+
export interface SearchResult {
|
|
16
|
+
/** Source collection name (e.g., "reports", "conversations", "lessons") */
|
|
17
|
+
source: string;
|
|
18
|
+
|
|
19
|
+
/** Identifier for retrieving the full document.
|
|
20
|
+
* - Reports: slug
|
|
21
|
+
* - Lessons: nevent/note encoded ID
|
|
22
|
+
* - Conversations: conversation ID
|
|
23
|
+
*/
|
|
24
|
+
id: string;
|
|
25
|
+
|
|
26
|
+
/** Project ID this result belongs to */
|
|
27
|
+
projectId: string;
|
|
28
|
+
|
|
29
|
+
/** Relevance score from vector search (0-1, higher is better) */
|
|
30
|
+
relevanceScore: number;
|
|
31
|
+
|
|
32
|
+
/** Human-readable title */
|
|
33
|
+
title: string;
|
|
34
|
+
|
|
35
|
+
/** Brief summary or snippet of the content */
|
|
36
|
+
summary: string;
|
|
37
|
+
|
|
38
|
+
/** Unix timestamp of when the content was created/published */
|
|
39
|
+
createdAt?: number;
|
|
40
|
+
|
|
41
|
+
/** Unix timestamp of last update/activity */
|
|
42
|
+
updatedAt?: number;
|
|
43
|
+
|
|
44
|
+
/** Author pubkey */
|
|
45
|
+
author?: string;
|
|
46
|
+
|
|
47
|
+
/** Author display name */
|
|
48
|
+
authorName?: string;
|
|
49
|
+
|
|
50
|
+
/** Hashtags/categories for additional context */
|
|
51
|
+
tags?: string[];
|
|
52
|
+
|
|
53
|
+
/** Which tool to use to retrieve full content */
|
|
54
|
+
retrievalTool: "report_read" | "lesson_get" | "conversation_get";
|
|
55
|
+
|
|
56
|
+
/** The argument to pass to the retrieval tool */
|
|
57
|
+
retrievalArg: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Options for performing a unified search.
|
|
62
|
+
*/
|
|
63
|
+
export interface SearchOptions {
|
|
64
|
+
/** Natural language search query */
|
|
65
|
+
query: string;
|
|
66
|
+
|
|
67
|
+
/** Project ID for project-scoped isolation */
|
|
68
|
+
projectId: string;
|
|
69
|
+
|
|
70
|
+
/** Maximum results per collection */
|
|
71
|
+
limit?: number;
|
|
72
|
+
|
|
73
|
+
/** Minimum relevance score threshold (0-1) */
|
|
74
|
+
minScore?: number;
|
|
75
|
+
|
|
76
|
+
/** Optional prompt for LLM-based extraction/focusing.
|
|
77
|
+
* When provided, results are processed through a fast LLM to
|
|
78
|
+
* extract information relevant to this specific prompt.
|
|
79
|
+
*/
|
|
80
|
+
prompt?: string;
|
|
81
|
+
|
|
82
|
+
/** Which collections to search (defaults to all) */
|
|
83
|
+
collections?: string[];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Interface that each search provider must implement.
|
|
88
|
+
* Each provider wraps a specific RAG collection.
|
|
89
|
+
*/
|
|
90
|
+
export interface SearchProvider {
|
|
91
|
+
/** Unique name for this provider (matches collection concept) */
|
|
92
|
+
readonly name: string;
|
|
93
|
+
|
|
94
|
+
/** Human-readable description */
|
|
95
|
+
readonly description: string;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Perform semantic search within this provider's collection.
|
|
99
|
+
*
|
|
100
|
+
* @param query - Natural language search query
|
|
101
|
+
* @param projectId - Project ID for isolation
|
|
102
|
+
* @param limit - Maximum number of results
|
|
103
|
+
* @param minScore - Minimum relevance score threshold
|
|
104
|
+
* @returns Array of search results
|
|
105
|
+
*/
|
|
106
|
+
search(
|
|
107
|
+
query: string,
|
|
108
|
+
projectId: string,
|
|
109
|
+
limit: number,
|
|
110
|
+
minScore: number
|
|
111
|
+
): Promise<SearchResult[]>;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Output from the unified search tool.
|
|
116
|
+
*/
|
|
117
|
+
export interface UnifiedSearchOutput {
|
|
118
|
+
/** Whether the search succeeded */
|
|
119
|
+
success: boolean;
|
|
120
|
+
|
|
121
|
+
/** Combined results from all collections, sorted by relevance */
|
|
122
|
+
results: SearchResult[];
|
|
123
|
+
|
|
124
|
+
/** Total number of results found */
|
|
125
|
+
totalResults: number;
|
|
126
|
+
|
|
127
|
+
/** The original query */
|
|
128
|
+
query: string;
|
|
129
|
+
|
|
130
|
+
/** Which collections were searched */
|
|
131
|
+
collectionsSearched: string[];
|
|
132
|
+
|
|
133
|
+
/** Which collections had errors (graceful degradation) */
|
|
134
|
+
collectionsErrored?: string[];
|
|
135
|
+
|
|
136
|
+
/** Warnings (e.g., collection failures) */
|
|
137
|
+
warnings?: string[];
|
|
138
|
+
|
|
139
|
+
/** Extracted/focused content when a prompt was provided */
|
|
140
|
+
extraction?: string;
|
|
141
|
+
|
|
142
|
+
/** Error message if the entire search failed */
|
|
143
|
+
error?: string;
|
|
144
|
+
}
|
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import * as crypto from "node:crypto";
|
|
4
|
+
import { getNDK } from "@/nostr";
|
|
5
|
+
import { NDKKind } from "@/nostr/kinds";
|
|
6
|
+
import { getTenexBasePath } from "@/constants";
|
|
7
|
+
import { ensureDirectory } from "@/lib/fs";
|
|
8
|
+
import { logger } from "@/utils/logger";
|
|
9
|
+
import type { NDKEvent } from "@nostr-dev-kit/ndk";
|
|
10
|
+
import { SpanStatusCode, context as otelContext, trace } from "@opentelemetry/api";
|
|
11
|
+
import type { SkillResult, SkillData, SkillFileInfo, SkillFileInstallResult } from "./types";
|
|
12
|
+
|
|
13
|
+
const tracer = trace.getTracer("tenex.skill-service");
|
|
14
|
+
|
|
15
|
+
const DOWNLOAD_TIMEOUT_MS = 30_000;
|
|
16
|
+
/** Maximum file download size: 10MB */
|
|
17
|
+
const MAX_DOWNLOAD_SIZE_BYTES = 10 * 1024 * 1024;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Service for fetching and processing Agent Skill events (kind:4202)
|
|
21
|
+
* Single Responsibility: Retrieve skill content, download attached files,
|
|
22
|
+
* and prepare for system prompt injection.
|
|
23
|
+
*
|
|
24
|
+
* Skills are stored in .tenex/skills/<short-id>/ directory structure.
|
|
25
|
+
*/
|
|
26
|
+
export class SkillService {
|
|
27
|
+
private static instance: SkillService;
|
|
28
|
+
|
|
29
|
+
private constructor() {}
|
|
30
|
+
|
|
31
|
+
static getInstance(): SkillService {
|
|
32
|
+
if (!SkillService.instance) {
|
|
33
|
+
SkillService.instance = new SkillService();
|
|
34
|
+
}
|
|
35
|
+
return SkillService.instance;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get the base directory for skill files
|
|
40
|
+
* @returns Path to .tenex/skills/
|
|
41
|
+
*/
|
|
42
|
+
private async getSkillsBaseDir(): Promise<string> {
|
|
43
|
+
const basePath = getTenexBasePath();
|
|
44
|
+
const skillsDir = path.join(basePath, "skills");
|
|
45
|
+
await ensureDirectory(skillsDir);
|
|
46
|
+
return skillsDir;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get the directory for a specific skill
|
|
51
|
+
* @param shortId Short event ID (first 12 chars)
|
|
52
|
+
* @returns Path to .tenex/skills/<short-id>/
|
|
53
|
+
*/
|
|
54
|
+
private async getSkillDir(shortId: string): Promise<string> {
|
|
55
|
+
const baseDir = await this.getSkillsBaseDir();
|
|
56
|
+
const skillDir = path.join(baseDir, shortId);
|
|
57
|
+
await ensureDirectory(skillDir);
|
|
58
|
+
return skillDir;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Fetch skill events by IDs and process their content and attached files.
|
|
63
|
+
*
|
|
64
|
+
* @param eventIds Array of skill event IDs to fetch
|
|
65
|
+
* @returns SkillResult with skills data and concatenated content
|
|
66
|
+
*/
|
|
67
|
+
async fetchSkills(eventIds: string[]): Promise<SkillResult> {
|
|
68
|
+
const emptyResult: SkillResult = {
|
|
69
|
+
skills: [],
|
|
70
|
+
content: "",
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
if (eventIds.length === 0) {
|
|
74
|
+
return emptyResult;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const span = tracer.startSpan("tenex.skill.fetch_skills", {
|
|
78
|
+
attributes: {
|
|
79
|
+
"skill.requested_count": eventIds.length,
|
|
80
|
+
},
|
|
81
|
+
}, otelContext.active());
|
|
82
|
+
|
|
83
|
+
return otelContext.with(trace.setSpan(otelContext.active(), span), async () => {
|
|
84
|
+
try {
|
|
85
|
+
const ndk = getNDK();
|
|
86
|
+
const skillEvents = await ndk.fetchEvents({
|
|
87
|
+
ids: eventIds,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const skills = Array.from(skillEvents);
|
|
91
|
+
|
|
92
|
+
// Filter to only kind:4202 events
|
|
93
|
+
const validSkills = skills.filter((event) => event.kind === NDKKind.AgentSkill);
|
|
94
|
+
|
|
95
|
+
// Process each skill and its attached files
|
|
96
|
+
const skillDataArray: SkillData[] = [];
|
|
97
|
+
|
|
98
|
+
for (const skill of validSkills) {
|
|
99
|
+
const skillData = await this.processSkillEvent(skill);
|
|
100
|
+
if (skillData) {
|
|
101
|
+
skillDataArray.push(skillData);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Concatenate content for backward compatibility
|
|
106
|
+
const concatenated = skillDataArray
|
|
107
|
+
.map((data) => data.content)
|
|
108
|
+
.filter((content) => content.length > 0)
|
|
109
|
+
.join("\n\n");
|
|
110
|
+
|
|
111
|
+
const skillTitles = skillDataArray
|
|
112
|
+
.map((s) => s.title || s.name || "untitled")
|
|
113
|
+
.join(", ");
|
|
114
|
+
|
|
115
|
+
span.setAttributes({
|
|
116
|
+
"skill.fetched_count": validSkills.length,
|
|
117
|
+
"skill.content_length": concatenated.length,
|
|
118
|
+
"skill.titles": skillTitles,
|
|
119
|
+
"skill.total_files": skillDataArray.reduce(
|
|
120
|
+
(acc, s) => acc + s.installedFiles.length,
|
|
121
|
+
0
|
|
122
|
+
),
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
126
|
+
span.end();
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
skills: skillDataArray,
|
|
130
|
+
content: concatenated,
|
|
131
|
+
};
|
|
132
|
+
} catch (error) {
|
|
133
|
+
span.recordException(error as Error);
|
|
134
|
+
span.setStatus({
|
|
135
|
+
code: SpanStatusCode.ERROR,
|
|
136
|
+
message: (error as Error).message,
|
|
137
|
+
});
|
|
138
|
+
span.end();
|
|
139
|
+
logger.error("[SkillService] Failed to fetch skills", { error });
|
|
140
|
+
return emptyResult;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Process a single skill event: extract content, metadata, and download attached files.
|
|
147
|
+
*
|
|
148
|
+
* @param event The skill event (kind:4202)
|
|
149
|
+
* @returns SkillData or null if invalid
|
|
150
|
+
*/
|
|
151
|
+
private async processSkillEvent(event: NDKEvent): Promise<SkillData | null> {
|
|
152
|
+
const content = event.content.trim();
|
|
153
|
+
const title = event.tagValue("title") || undefined;
|
|
154
|
+
const name = event.tagValue("name") || undefined;
|
|
155
|
+
const shortId = event.id.substring(0, 12);
|
|
156
|
+
|
|
157
|
+
// Extract e-tags that reference kind:1063 (NIP-94 file metadata) events
|
|
158
|
+
const fileETags = this.extractFileETags(event);
|
|
159
|
+
|
|
160
|
+
// Download and install attached files
|
|
161
|
+
const installedFiles = await this.installSkillFiles(fileETags, shortId);
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
content,
|
|
165
|
+
title,
|
|
166
|
+
name,
|
|
167
|
+
shortId,
|
|
168
|
+
installedFiles,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Extract e-tags from a skill event that reference kind:1063 file metadata events.
|
|
174
|
+
* Format: ["e", "<event-id>", "<relay-url>?"]
|
|
175
|
+
*
|
|
176
|
+
* NOTE: Relay hints (tag[2]) are parsed but not currently used. NDK's fetchEvent
|
|
177
|
+
* handles relay discovery automatically through our connected relay pool. Adding
|
|
178
|
+
* explicit relay hints would require significant refactoring and the current approach
|
|
179
|
+
* works well for events that are available on commonly-connected relays.
|
|
180
|
+
*
|
|
181
|
+
* @param event The skill event
|
|
182
|
+
* @returns Array of event IDs to fetch
|
|
183
|
+
*/
|
|
184
|
+
private extractFileETags(event: NDKEvent): string[] {
|
|
185
|
+
return event.tags
|
|
186
|
+
.filter((tag) => tag[0] === "e" && tag[1])
|
|
187
|
+
.map((tag) => tag[1]);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Fetch and install files referenced by e-tags in a skill event.
|
|
192
|
+
*
|
|
193
|
+
* @param fileEventIds Array of event IDs referencing kind:1063 events
|
|
194
|
+
* @param shortId Short skill ID for directory naming
|
|
195
|
+
* @returns Array of installation results
|
|
196
|
+
*/
|
|
197
|
+
private async installSkillFiles(
|
|
198
|
+
fileEventIds: string[],
|
|
199
|
+
shortId: string
|
|
200
|
+
): Promise<SkillFileInstallResult[]> {
|
|
201
|
+
if (fileEventIds.length === 0) {
|
|
202
|
+
return [];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const results: SkillFileInstallResult[] = [];
|
|
206
|
+
const ndk = getNDK();
|
|
207
|
+
const skillDir = await this.getSkillDir(shortId);
|
|
208
|
+
|
|
209
|
+
for (const eventId of fileEventIds) {
|
|
210
|
+
try {
|
|
211
|
+
// Fetch the kind:1063 file metadata event
|
|
212
|
+
const fileEvent = await ndk.fetchEvent(eventId, { groupable: false });
|
|
213
|
+
|
|
214
|
+
if (!fileEvent) {
|
|
215
|
+
results.push({
|
|
216
|
+
eventId,
|
|
217
|
+
relativePath: "unknown",
|
|
218
|
+
absolutePath: "unknown",
|
|
219
|
+
success: false,
|
|
220
|
+
error: `Could not fetch event ${eventId}`,
|
|
221
|
+
});
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Verify it's a kind:1063 event
|
|
226
|
+
if (fileEvent.kind !== 1063) {
|
|
227
|
+
results.push({
|
|
228
|
+
eventId,
|
|
229
|
+
relativePath: "unknown",
|
|
230
|
+
absolutePath: "unknown",
|
|
231
|
+
success: false,
|
|
232
|
+
error: `Event ${eventId} is not kind:1063 (got kind:${fileEvent.kind})`,
|
|
233
|
+
});
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Extract file info from the event
|
|
238
|
+
const fileInfo = this.extractFileInfo(fileEvent);
|
|
239
|
+
if (!fileInfo) {
|
|
240
|
+
results.push({
|
|
241
|
+
eventId,
|
|
242
|
+
relativePath: "unknown",
|
|
243
|
+
absolutePath: "unknown",
|
|
244
|
+
success: false,
|
|
245
|
+
error: "Missing required tags (url, name) in kind:1063 event",
|
|
246
|
+
});
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Download and install the file
|
|
251
|
+
const result = await this.installFile(fileInfo, skillDir);
|
|
252
|
+
results.push(result);
|
|
253
|
+
} catch (error) {
|
|
254
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
255
|
+
results.push({
|
|
256
|
+
eventId,
|
|
257
|
+
relativePath: "unknown",
|
|
258
|
+
absolutePath: "unknown",
|
|
259
|
+
success: false,
|
|
260
|
+
error: errorMessage,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Log summary
|
|
266
|
+
const successCount = results.filter((r) => r.success).length;
|
|
267
|
+
const failCount = results.filter((r) => !r.success).length;
|
|
268
|
+
|
|
269
|
+
if (failCount > 0) {
|
|
270
|
+
logger.warn(`[SkillService] Skill file installation completed with errors`, {
|
|
271
|
+
skillId: shortId,
|
|
272
|
+
success: successCount,
|
|
273
|
+
failed: failCount,
|
|
274
|
+
});
|
|
275
|
+
} else if (successCount > 0) {
|
|
276
|
+
logger.info(`[SkillService] All skill files installed successfully`, {
|
|
277
|
+
skillId: shortId,
|
|
278
|
+
count: successCount,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return results;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Extract file information from a kind:1063 (NIP-94) event.
|
|
287
|
+
*
|
|
288
|
+
* Expected tags:
|
|
289
|
+
* - ["url", "https://blossom.server/sha256"] - Required: Blossom download URL
|
|
290
|
+
* - ["name", "relative/path/file.ext"] - Required: Relative filepath
|
|
291
|
+
* - ["m", "text/plain"] - Optional: MIME type
|
|
292
|
+
* - ["x", "sha256hash"] - Optional: SHA-256 hash for verification
|
|
293
|
+
*
|
|
294
|
+
* @param event The kind:1063 event
|
|
295
|
+
* @returns SkillFileInfo or null if required tags are missing
|
|
296
|
+
*/
|
|
297
|
+
private extractFileInfo(event: NDKEvent): SkillFileInfo | null {
|
|
298
|
+
const url = event.tagValue("url");
|
|
299
|
+
const relativePath = event.tagValue("name");
|
|
300
|
+
|
|
301
|
+
if (!url || !relativePath) {
|
|
302
|
+
logger.warn(`[SkillService] Kind 1063 event ${event.id} missing required tags`, {
|
|
303
|
+
hasUrl: !!url,
|
|
304
|
+
hasName: !!relativePath,
|
|
305
|
+
});
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
eventId: event.id,
|
|
311
|
+
url,
|
|
312
|
+
relativePath,
|
|
313
|
+
mimeType: event.tagValue("m") || undefined,
|
|
314
|
+
sha256: event.tagValue("x") || undefined,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Download and install a single file to the skill directory.
|
|
320
|
+
*
|
|
321
|
+
* @param fileInfo Information about the file to install
|
|
322
|
+
* @param skillDir Base directory for this skill
|
|
323
|
+
* @returns Installation result
|
|
324
|
+
*/
|
|
325
|
+
private async installFile(
|
|
326
|
+
fileInfo: SkillFileInfo,
|
|
327
|
+
skillDir: string
|
|
328
|
+
): Promise<SkillFileInstallResult> {
|
|
329
|
+
// Resolve to absolute path, normalizing any ../ components
|
|
330
|
+
const resolvedSkillDir = path.resolve(skillDir);
|
|
331
|
+
const absolutePath = path.resolve(skillDir, fileInfo.relativePath);
|
|
332
|
+
|
|
333
|
+
try {
|
|
334
|
+
// Security check: ensure the resolved path stays within the skill directory
|
|
335
|
+
// Using path.relative and checking for ".." prefix is more robust than startsWith
|
|
336
|
+
const relativeToBoundary = path.relative(resolvedSkillDir, absolutePath);
|
|
337
|
+
if (relativeToBoundary.startsWith("..") || path.isAbsolute(relativeToBoundary)) {
|
|
338
|
+
throw new Error(
|
|
339
|
+
`Security violation: path "${fileInfo.relativePath}" would escape skill directory`
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Create parent directories
|
|
344
|
+
const parentDir = path.dirname(absolutePath);
|
|
345
|
+
await ensureDirectory(parentDir);
|
|
346
|
+
|
|
347
|
+
// Download the file with size limit
|
|
348
|
+
logger.debug(`[SkillService] Downloading file from ${fileInfo.url}`);
|
|
349
|
+
const content = await this.downloadFile(fileInfo.url);
|
|
350
|
+
|
|
351
|
+
// Verify SHA-256 hash if provided (NIP-94 "x" tag)
|
|
352
|
+
if (fileInfo.sha256) {
|
|
353
|
+
const actualHash = crypto.createHash("sha256").update(content).digest("hex");
|
|
354
|
+
if (actualHash.toLowerCase() !== fileInfo.sha256.toLowerCase()) {
|
|
355
|
+
throw new Error(
|
|
356
|
+
`SHA-256 hash mismatch: expected ${fileInfo.sha256}, got ${actualHash}`
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
logger.debug(`[SkillService] SHA-256 verification passed for ${fileInfo.relativePath}`);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Write the file
|
|
363
|
+
await fs.writeFile(absolutePath, content);
|
|
364
|
+
|
|
365
|
+
logger.info(`[SkillService] Installed skill file: ${fileInfo.relativePath}`, {
|
|
366
|
+
eventId: fileInfo.eventId,
|
|
367
|
+
absolutePath,
|
|
368
|
+
size: content.length,
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
eventId: fileInfo.eventId,
|
|
373
|
+
relativePath: fileInfo.relativePath,
|
|
374
|
+
absolutePath,
|
|
375
|
+
success: true,
|
|
376
|
+
};
|
|
377
|
+
} catch (error) {
|
|
378
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
379
|
+
logger.error(`[SkillService] Failed to install skill file: ${fileInfo.relativePath}`, {
|
|
380
|
+
eventId: fileInfo.eventId,
|
|
381
|
+
error: errorMessage,
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
return {
|
|
385
|
+
eventId: fileInfo.eventId,
|
|
386
|
+
relativePath: fileInfo.relativePath,
|
|
387
|
+
absolutePath,
|
|
388
|
+
success: false,
|
|
389
|
+
error: errorMessage,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Download a file from a Blossom URL with size limit enforcement.
|
|
396
|
+
*
|
|
397
|
+
* @param url The Blossom URL to download from
|
|
398
|
+
* @returns The downloaded file content as a Buffer
|
|
399
|
+
* @throws Error if download exceeds MAX_DOWNLOAD_SIZE_BYTES
|
|
400
|
+
*/
|
|
401
|
+
private async downloadFile(url: string): Promise<Buffer> {
|
|
402
|
+
const controller = new AbortController();
|
|
403
|
+
const timeout = setTimeout(() => controller.abort(), DOWNLOAD_TIMEOUT_MS);
|
|
404
|
+
|
|
405
|
+
try {
|
|
406
|
+
const response = await fetch(url, {
|
|
407
|
+
headers: {
|
|
408
|
+
"User-Agent": "TENEX/1.0 (Skill Service)",
|
|
409
|
+
},
|
|
410
|
+
signal: controller.signal,
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
if (!response.ok) {
|
|
414
|
+
throw new Error(`Failed to download: ${response.status} ${response.statusText}`);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Check Content-Length header first if available
|
|
418
|
+
const contentLength = response.headers.get("Content-Length");
|
|
419
|
+
if (contentLength) {
|
|
420
|
+
const declaredSize = parseInt(contentLength, 10);
|
|
421
|
+
if (declaredSize > MAX_DOWNLOAD_SIZE_BYTES) {
|
|
422
|
+
throw new Error(
|
|
423
|
+
`File too large: ${declaredSize} bytes exceeds ${MAX_DOWNLOAD_SIZE_BYTES} byte limit`
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Stream the response and enforce size limit during download
|
|
429
|
+
const reader = response.body?.getReader();
|
|
430
|
+
if (!reader) {
|
|
431
|
+
throw new Error("Response body is not readable");
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const chunks: Uint8Array[] = [];
|
|
435
|
+
let totalSize = 0;
|
|
436
|
+
|
|
437
|
+
while (true) {
|
|
438
|
+
const { done, value } = await reader.read();
|
|
439
|
+
if (done) break;
|
|
440
|
+
|
|
441
|
+
totalSize += value.length;
|
|
442
|
+
if (totalSize > MAX_DOWNLOAD_SIZE_BYTES) {
|
|
443
|
+
reader.cancel();
|
|
444
|
+
throw new Error(
|
|
445
|
+
`File too large: exceeded ${MAX_DOWNLOAD_SIZE_BYTES} byte limit during download`
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
chunks.push(value);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return Buffer.concat(chunks);
|
|
452
|
+
} catch (error) {
|
|
453
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
454
|
+
throw new Error(`Download timed out after ${DOWNLOAD_TIMEOUT_MS}ms`);
|
|
455
|
+
}
|
|
456
|
+
throw error;
|
|
457
|
+
} finally {
|
|
458
|
+
clearTimeout(timeout);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Fetch a single skill event by ID.
|
|
464
|
+
*
|
|
465
|
+
* @param eventId The skill event ID
|
|
466
|
+
* @returns The skill event or null if not found
|
|
467
|
+
*/
|
|
468
|
+
async fetchSkill(eventId: string): Promise<NDKEvent | null> {
|
|
469
|
+
try {
|
|
470
|
+
const ndk = getNDK();
|
|
471
|
+
const events = await ndk.fetchEvents({
|
|
472
|
+
ids: [eventId],
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
const skill = Array.from(events).find((event) => event.kind === NDKKind.AgentSkill);
|
|
476
|
+
return skill || null;
|
|
477
|
+
} catch (error) {
|
|
478
|
+
logger.error("[SkillService] Failed to fetch skill", { error, eventId });
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill Event Types
|
|
3
|
+
*
|
|
4
|
+
* Defines the structure for skill events (kind:4202).
|
|
5
|
+
* Skills are transient capabilities that can be injected into agent system prompts.
|
|
6
|
+
*
|
|
7
|
+
* Unlike nudges, skills do NOT have tool permissions (only-tool, allow-tool, deny-tool).
|
|
8
|
+
* Skills are focused on providing additional context, instructions, and attached files.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Information about a file attached to a skill via NIP-94 (kind 1063) event reference
|
|
13
|
+
*/
|
|
14
|
+
export interface SkillFileInfo {
|
|
15
|
+
/** Event ID of the kind:1063 event */
|
|
16
|
+
eventId: string;
|
|
17
|
+
/** Blossom URL to download the file */
|
|
18
|
+
url: string;
|
|
19
|
+
/** Relative path where the file should be stored (from NIP-94 "name" tag) */
|
|
20
|
+
relativePath: string;
|
|
21
|
+
/** Optional MIME type (from NIP-94 "m" tag) */
|
|
22
|
+
mimeType?: string;
|
|
23
|
+
/** Optional SHA-256 hash for verification (from NIP-94 "x" tag) */
|
|
24
|
+
sha256?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Result of downloading and installing a skill file
|
|
29
|
+
*/
|
|
30
|
+
export interface SkillFileInstallResult {
|
|
31
|
+
/** Event ID of the source kind:1063 event */
|
|
32
|
+
eventId: string;
|
|
33
|
+
/** Relative path within the skill directory */
|
|
34
|
+
relativePath: string;
|
|
35
|
+
/** Absolute path where the file was installed */
|
|
36
|
+
absolutePath: string;
|
|
37
|
+
/** Whether the installation succeeded */
|
|
38
|
+
success: boolean;
|
|
39
|
+
/** Error message if installation failed */
|
|
40
|
+
error?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Individual skill data with content, title, and attached files
|
|
45
|
+
*/
|
|
46
|
+
export interface SkillData {
|
|
47
|
+
/** The skill content/instructions */
|
|
48
|
+
content: string;
|
|
49
|
+
/** The skill title (from "title" tag) */
|
|
50
|
+
title?: string;
|
|
51
|
+
/** The skill name/identifier (from "name" tag) */
|
|
52
|
+
name?: string;
|
|
53
|
+
/** Short ID for directory naming (first 12 chars of event ID) */
|
|
54
|
+
shortId: string;
|
|
55
|
+
/** List of installed files for this skill */
|
|
56
|
+
installedFiles: SkillFileInstallResult[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Result from fetching skills with their attached files.
|
|
61
|
+
* Contains both the concatenated content for system prompt injection
|
|
62
|
+
* and the individual skill data with file information.
|
|
63
|
+
*/
|
|
64
|
+
export interface SkillResult {
|
|
65
|
+
/** Individual skill data for rendering in the fragment */
|
|
66
|
+
skills: SkillData[];
|
|
67
|
+
|
|
68
|
+
/** Concatenated content from all skills (for backward compatibility) */
|
|
69
|
+
content: string;
|
|
70
|
+
}
|