@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.
Files changed (427) hide show
  1. package/README.md +194 -0
  2. package/dist/backend-wrapper.cjs +3 -0
  3. package/dist/src/index.js +331928 -0
  4. package/package.json +103 -0
  5. package/src/agents/AgentRegistry.ts +418 -0
  6. package/src/agents/AgentStorage.ts +1133 -0
  7. package/src/agents/ConfigResolver.ts +229 -0
  8. package/src/agents/agent-installer.ts +236 -0
  9. package/src/agents/agent-loader.ts +241 -0
  10. package/src/agents/constants.ts +82 -0
  11. package/src/agents/errors.ts +48 -0
  12. package/src/agents/execution/AgentExecutor.ts +561 -0
  13. package/src/agents/execution/ExecutionContextFactory.ts +112 -0
  14. package/src/agents/execution/MessageCompiler.ts +597 -0
  15. package/src/agents/execution/MessageSyncer.ts +100 -0
  16. package/src/agents/execution/PostCompletionChecker.ts +278 -0
  17. package/src/agents/execution/ProgressMonitor.ts +50 -0
  18. package/src/agents/execution/RALResolver.ts +177 -0
  19. package/src/agents/execution/SessionManager.ts +181 -0
  20. package/src/agents/execution/StreamCallbacks.ts +312 -0
  21. package/src/agents/execution/StreamExecutionHandler.ts +579 -0
  22. package/src/agents/execution/StreamSetup.ts +313 -0
  23. package/src/agents/execution/ToolEventHandlers.ts +239 -0
  24. package/src/agents/execution/ToolExecutionTracker.ts +498 -0
  25. package/src/agents/execution/ToolResultUtils.ts +97 -0
  26. package/src/agents/execution/ToolSupervisionWrapper.ts +174 -0
  27. package/src/agents/execution/constants.ts +16 -0
  28. package/src/agents/execution/index.ts +3 -0
  29. package/src/agents/execution/types.ts +96 -0
  30. package/src/agents/execution/utils.ts +26 -0
  31. package/src/agents/index.ts +4 -0
  32. package/src/agents/script-installer.ts +266 -0
  33. package/src/agents/supervision/SupervisorLLMService.ts +253 -0
  34. package/src/agents/supervision/SupervisorOrchestrator.ts +471 -0
  35. package/src/agents/supervision/heuristics/ConsecutiveToolsWithoutTodoHeuristic.ts +73 -0
  36. package/src/agents/supervision/heuristics/DelegationClaimHeuristic.ts +80 -0
  37. package/src/agents/supervision/heuristics/HeuristicRegistry.ts +114 -0
  38. package/src/agents/supervision/heuristics/PendingTodosHeuristic.ts +93 -0
  39. package/src/agents/supervision/heuristics/SilentAgentHeuristic.ts +54 -0
  40. package/src/agents/supervision/heuristics/index.ts +5 -0
  41. package/src/agents/supervision/index.ts +28 -0
  42. package/src/agents/supervision/registerHeuristics.ts +110 -0
  43. package/src/agents/supervision/supervisionHealthCheck.ts +123 -0
  44. package/src/agents/supervision/types.ts +171 -0
  45. package/src/agents/tool-names.ts +46 -0
  46. package/src/agents/tool-normalization.ts +184 -0
  47. package/src/agents/types/index.ts +2 -0
  48. package/src/agents/types/runtime.ts +74 -0
  49. package/src/agents/types/storage.ts +145 -0
  50. package/src/commands/agent/import/index.ts +6 -0
  51. package/src/commands/agent/import/openclaw-distiller.ts +57 -0
  52. package/src/commands/agent/import/openclaw-reader.ts +141 -0
  53. package/src/commands/agent/import/openclaw.ts +154 -0
  54. package/src/commands/agent/index.ts +6 -0
  55. package/src/commands/agent.ts +215 -0
  56. package/src/commands/daemon.ts +198 -0
  57. package/src/commands/doctor.ts +134 -0
  58. package/src/commands/setup/embed.ts +228 -0
  59. package/src/commands/setup/global-system-prompt.ts +223 -0
  60. package/src/commands/setup/image.ts +179 -0
  61. package/src/commands/setup/index.ts +16 -0
  62. package/src/commands/setup/interactive.ts +95 -0
  63. package/src/commands/setup/llm.ts +38 -0
  64. package/src/commands/setup/onboarding.ts +294 -0
  65. package/src/commands/setup/providers.ts +27 -0
  66. package/src/constants.ts +34 -0
  67. package/src/conversations/ConversationDiskReader.ts +148 -0
  68. package/src/conversations/ConversationRegistry.ts +728 -0
  69. package/src/conversations/ConversationStore.ts +868 -0
  70. package/src/conversations/MessageBuilder.ts +866 -0
  71. package/src/conversations/executionTime.ts +62 -0
  72. package/src/conversations/formatters/DelegationXmlFormatter.ts +64 -0
  73. package/src/conversations/formatters/ThreadedConversationFormatter.ts +303 -0
  74. package/src/conversations/formatters/index.ts +9 -0
  75. package/src/conversations/formatters/utils/MessageFormatter.ts +46 -0
  76. package/src/conversations/formatters/utils/TimestampFormatter.ts +56 -0
  77. package/src/conversations/formatters/utils/TreeBuilder.ts +131 -0
  78. package/src/conversations/formatters/utils/TreeRenderer.ts +49 -0
  79. package/src/conversations/index.ts +2 -0
  80. package/src/conversations/persistence/ToolMessageStorage.ts +143 -0
  81. package/src/conversations/search/ConversationIndexManager.ts +393 -0
  82. package/src/conversations/search/QueryParser.ts +114 -0
  83. package/src/conversations/search/SearchEngine.ts +175 -0
  84. package/src/conversations/search/SnippetExtractor.ts +345 -0
  85. package/src/conversations/search/embeddings/ConversationEmbeddingService.ts +484 -0
  86. package/src/conversations/search/embeddings/ConversationIndexingJob.ts +320 -0
  87. package/src/conversations/search/embeddings/IndexingStateManager.ts +338 -0
  88. package/src/conversations/search/embeddings/index.ts +18 -0
  89. package/src/conversations/search/index.ts +49 -0
  90. package/src/conversations/search/types.ts +124 -0
  91. package/src/conversations/services/CategoryManager.ts +160 -0
  92. package/src/conversations/services/ConversationResolver.ts +296 -0
  93. package/src/conversations/services/ConversationSummarizer.ts +234 -0
  94. package/src/conversations/services/MetadataDebounceManager.ts +188 -0
  95. package/src/conversations/services/index.ts +2 -0
  96. package/src/conversations/types.ts +148 -0
  97. package/src/conversations/utils/content-utils.ts +69 -0
  98. package/src/conversations/utils/image-placeholder.ts +281 -0
  99. package/src/conversations/utils/image-url-utils.ts +171 -0
  100. package/src/conversations/utils/multimodal-content.ts +90 -0
  101. package/src/conversations/utils/tool-result-truncator.ts +159 -0
  102. package/src/daemon/Daemon.ts +1883 -0
  103. package/src/daemon/ProjectRuntime.ts +657 -0
  104. package/src/daemon/RestartState.ts +152 -0
  105. package/src/daemon/RuntimeLifecycle.ts +268 -0
  106. package/src/daemon/SubscriptionManager.ts +305 -0
  107. package/src/daemon/UnixSocketTransport.ts +318 -0
  108. package/src/daemon/filters/SubscriptionFilterBuilder.ts +119 -0
  109. package/src/daemon/index.ts +9 -0
  110. package/src/daemon/routing/DaemonRouter.ts +491 -0
  111. package/src/daemon/types.ts +150 -0
  112. package/src/daemon/utils/routing-log.ts +76 -0
  113. package/src/daemon/utils/telemetry.ts +173 -0
  114. package/src/event-handler/agentDeletion.ts +383 -0
  115. package/src/event-handler/index.ts +749 -0
  116. package/src/event-handler/newConversation.ts +165 -0
  117. package/src/event-handler/project.ts +166 -0
  118. package/src/event-handler/reply.ts +18 -0
  119. package/src/events/NDKAgentDefinition.ts +292 -0
  120. package/src/events/NDKAgentLesson.ts +106 -0
  121. package/src/events/NDKEventMetadata.ts +34 -0
  122. package/src/events/NDKMCPTool.ts +60 -0
  123. package/src/events/NDKProjectStatus.ts +384 -0
  124. package/src/events/index.ts +4 -0
  125. package/src/index.ts +126 -0
  126. package/src/lib/agent-home.ts +334 -0
  127. package/src/lib/error-formatter.ts +200 -0
  128. package/src/lib/fs/filesystem.ts +128 -0
  129. package/src/lib/fs/index.ts +1 -0
  130. package/src/lib/json-parser.ts +30 -0
  131. package/src/lib/string.ts +15 -0
  132. package/src/lib/time.ts +74 -0
  133. package/src/llm/ChunkHandler.ts +277 -0
  134. package/src/llm/FinishHandler.ts +250 -0
  135. package/src/llm/LLMConfigEditor.ts +154 -0
  136. package/src/llm/LLMServiceFactory.ts +230 -0
  137. package/src/llm/MessageProcessor.ts +90 -0
  138. package/src/llm/RecordingState.ts +37 -0
  139. package/src/llm/StreamPublisher.ts +40 -0
  140. package/src/llm/TracingUtils.ts +77 -0
  141. package/src/llm/chunk-validators.ts +57 -0
  142. package/src/llm/constants.ts +6 -0
  143. package/src/llm/index.ts +12 -0
  144. package/src/llm/meta/MetaModelResolver.ts +352 -0
  145. package/src/llm/meta/index.ts +11 -0
  146. package/src/llm/middleware/flight-recorder.ts +188 -0
  147. package/src/llm/providers/MockProvider.ts +332 -0
  148. package/src/llm/providers/agent/ClaudeCodeProvider.ts +343 -0
  149. package/src/llm/providers/agent/ClaudeCodeToolsAdapter.ts +203 -0
  150. package/src/llm/providers/agent/CodexAppServerProvider.ts +214 -0
  151. package/src/llm/providers/agent/CodexAppServerToolsAdapter.ts +91 -0
  152. package/src/llm/providers/agent/index.ts +10 -0
  153. package/src/llm/providers/base/AgentProvider.ts +107 -0
  154. package/src/llm/providers/base/BaseProvider.ts +114 -0
  155. package/src/llm/providers/base/StandardProvider.ts +38 -0
  156. package/src/llm/providers/base/index.ts +9 -0
  157. package/src/llm/providers/index.ts +106 -0
  158. package/src/llm/providers/key-manager.ts +238 -0
  159. package/src/llm/providers/ollama-models.ts +105 -0
  160. package/src/llm/providers/openrouter-models.ts +102 -0
  161. package/src/llm/providers/provider-ids.ts +18 -0
  162. package/src/llm/providers/registry/ProviderRegistry.ts +414 -0
  163. package/src/llm/providers/registry/index.ts +7 -0
  164. package/src/llm/providers/standard/AnthropicProvider.ts +71 -0
  165. package/src/llm/providers/standard/OllamaProvider.ts +59 -0
  166. package/src/llm/providers/standard/OpenAIProvider.ts +44 -0
  167. package/src/llm/providers/standard/OpenRouterProvider.ts +103 -0
  168. package/src/llm/providers/standard/index.ts +10 -0
  169. package/src/llm/providers/types.ts +194 -0
  170. package/src/llm/providers/usage-metadata.ts +78 -0
  171. package/src/llm/service.ts +713 -0
  172. package/src/llm/types.ts +167 -0
  173. package/src/llm/utils/ConfigurationManager.ts +650 -0
  174. package/src/llm/utils/ConfigurationTester.ts +229 -0
  175. package/src/llm/utils/ModelSelector.ts +212 -0
  176. package/src/llm/utils/ProviderConfigUI.ts +177 -0
  177. package/src/llm/utils/claudeCodePromptCompiler.ts +141 -0
  178. package/src/llm/utils/codex-models.ts +53 -0
  179. package/src/llm/utils/context-window-cache.ts +30 -0
  180. package/src/llm/utils/models-dev-cache.ts +267 -0
  181. package/src/llm/utils/provider-setup.ts +50 -0
  182. package/src/llm/utils/tool-errors.ts +78 -0
  183. package/src/llm/utils/usage.ts +74 -0
  184. package/src/logging/EventRoutingLogger.ts +205 -0
  185. package/src/nostr/AgentEventDecoder.ts +357 -0
  186. package/src/nostr/AgentEventEncoder.ts +677 -0
  187. package/src/nostr/AgentProfilePublisher.ts +657 -0
  188. package/src/nostr/AgentPublisher.ts +437 -0
  189. package/src/nostr/BlossomService.ts +226 -0
  190. package/src/nostr/InterventionPublisher.ts +132 -0
  191. package/src/nostr/TagExtractor.ts +228 -0
  192. package/src/nostr/collectEvents.ts +83 -0
  193. package/src/nostr/constants.ts +38 -0
  194. package/src/nostr/encryption.ts +26 -0
  195. package/src/nostr/index.ts +31 -0
  196. package/src/nostr/keys.ts +17 -0
  197. package/src/nostr/kinds.ts +37 -0
  198. package/src/nostr/ndkClient.ts +72 -0
  199. package/src/nostr/relays.ts +43 -0
  200. package/src/nostr/trace-context.ts +39 -0
  201. package/src/nostr/types.ts +227 -0
  202. package/src/nostr/utils.ts +84 -0
  203. package/src/prompts/core/FragmentRegistry.ts +30 -0
  204. package/src/prompts/core/PromptBuilder.ts +98 -0
  205. package/src/prompts/core/index.ts +3 -0
  206. package/src/prompts/core/types.ts +13 -0
  207. package/src/prompts/fragments/00-global-system-prompt.ts +44 -0
  208. package/src/prompts/fragments/01-agent-identity.ts +69 -0
  209. package/src/prompts/fragments/02-agent-home-directory.ts +114 -0
  210. package/src/prompts/fragments/03-system-reminders-explanation.ts +14 -0
  211. package/src/prompts/fragments/04-relay-configuration.ts +38 -0
  212. package/src/prompts/fragments/05-delegation-chain.ts +45 -0
  213. package/src/prompts/fragments/06-agent-todos.ts +74 -0
  214. package/src/prompts/fragments/06-todo-usage-guidance.ts +34 -0
  215. package/src/prompts/fragments/07-meta-project-context.ts +234 -0
  216. package/src/prompts/fragments/08-active-conversations.ts +382 -0
  217. package/src/prompts/fragments/09-recent-conversations.ts +153 -0
  218. package/src/prompts/fragments/10-referenced-article.ts +21 -0
  219. package/src/prompts/fragments/11-nudges.ts +134 -0
  220. package/src/prompts/fragments/12-skills.ts +127 -0
  221. package/src/prompts/fragments/13-available-nudges.ts +122 -0
  222. package/src/prompts/fragments/15-available-agents.ts +53 -0
  223. package/src/prompts/fragments/16-stay-in-your-lane.ts +41 -0
  224. package/src/prompts/fragments/17-todo-before-delegation.ts +39 -0
  225. package/src/prompts/fragments/20-voice-mode.ts +62 -0
  226. package/src/prompts/fragments/22-scheduled-tasks.ts +175 -0
  227. package/src/prompts/fragments/24-retrieved-lessons.ts +26 -0
  228. package/src/prompts/fragments/25-rag-instructions.ts +333 -0
  229. package/src/prompts/fragments/26-mcp-resources.ts +237 -0
  230. package/src/prompts/fragments/27-memorized-reports.ts +77 -0
  231. package/src/prompts/fragments/28-agent-directed-monitoring.ts +32 -0
  232. package/src/prompts/fragments/29-rag-collections.ts +50 -0
  233. package/src/prompts/fragments/30-worktree-context.ts +98 -0
  234. package/src/prompts/fragments/31-agents-md-guidance.ts +96 -0
  235. package/src/prompts/fragments/32-process-metrics.ts +72 -0
  236. package/src/prompts/fragments/debug-mode.ts +48 -0
  237. package/src/prompts/fragments/delegation-completion.ts +44 -0
  238. package/src/prompts/fragments/index.ts +91 -0
  239. package/src/prompts/index.ts +21 -0
  240. package/src/prompts/utils/systemPromptBuilder.ts +777 -0
  241. package/src/scripts/migrate-prefix-index.ts +157 -0
  242. package/src/services/AgentDefinitionMonitor.ts +701 -0
  243. package/src/services/ConfigService.ts +723 -0
  244. package/src/services/CooldownRegistry.ts +199 -0
  245. package/src/services/LLMOperationsRegistry.ts +424 -0
  246. package/src/services/OwnerAgentListService.ts +354 -0
  247. package/src/services/PubkeyService.ts +308 -0
  248. package/src/services/agents/AgentMetadataStore.ts +72 -0
  249. package/src/services/agents/AgentResolution.ts +59 -0
  250. package/src/services/agents/EscalationService.ts +281 -0
  251. package/src/services/agents/NDKAgentDiscovery.ts +95 -0
  252. package/src/services/agents/index.ts +7 -0
  253. package/src/services/agents-md/AgentsMdService.ts +184 -0
  254. package/src/services/agents-md/SystemReminderInjector.ts +238 -0
  255. package/src/services/agents-md/index.ts +11 -0
  256. package/src/services/apns/APNsClient.ts +203 -0
  257. package/src/services/apns/APNsService.ts +358 -0
  258. package/src/services/apns/index.ts +11 -0
  259. package/src/services/apns/types.ts +80 -0
  260. package/src/services/compression/CompressionService.ts +445 -0
  261. package/src/services/compression/compression-schema.ts +28 -0
  262. package/src/services/compression/compression-types.ts +74 -0
  263. package/src/services/compression/compression-utils.ts +587 -0
  264. package/src/services/config/types.ts +394 -0
  265. package/src/services/dispatch/AgentDispatchService.ts +937 -0
  266. package/src/services/dispatch/AgentRouter.ts +181 -0
  267. package/src/services/dispatch/DelegationCompletionHandler.ts +232 -0
  268. package/src/services/embedding/EmbeddingProvider.ts +188 -0
  269. package/src/services/embedding/index.ts +5 -0
  270. package/src/services/event-context/EventContextService.ts +108 -0
  271. package/src/services/event-context/index.ts +2 -0
  272. package/src/services/heuristics/ContextBuilder.ts +106 -0
  273. package/src/services/heuristics/HeuristicEngine.ts +200 -0
  274. package/src/services/heuristics/formatters.ts +58 -0
  275. package/src/services/heuristics/index.ts +12 -0
  276. package/src/services/heuristics/rules/index.ts +25 -0
  277. package/src/services/heuristics/rules/todoBeforeDelegation.ts +69 -0
  278. package/src/services/heuristics/rules/todoReminderOnToolUse.ts +63 -0
  279. package/src/services/heuristics/types.ts +144 -0
  280. package/src/services/image/ImageGenerationService.ts +389 -0
  281. package/src/services/image/index.ts +12 -0
  282. package/src/services/intervention/InterventionService.ts +1352 -0
  283. package/src/services/intervention/index.ts +7 -0
  284. package/src/services/mcp/MCPManager.ts +683 -0
  285. package/src/services/mcp/McpNotificationDelivery.ts +139 -0
  286. package/src/services/mcp/McpSubscriptionService.ts +653 -0
  287. package/src/services/mcp/mcpInstaller.ts +130 -0
  288. package/src/services/nip46/Nip46SigningLog.ts +81 -0
  289. package/src/services/nip46/Nip46SigningService.ts +467 -0
  290. package/src/services/nip46/index.ts +4 -0
  291. package/src/services/nudge/NudgeService.ts +224 -0
  292. package/src/services/nudge/NudgeWhitelistService.ts +382 -0
  293. package/src/services/nudge/index.ts +5 -0
  294. package/src/services/nudge/types.ts +83 -0
  295. package/src/services/projects/ProjectContext.ts +672 -0
  296. package/src/services/projects/ProjectContextStore.ts +102 -0
  297. package/src/services/projects/index.ts +6 -0
  298. package/src/services/prompt-compiler/index.ts +15 -0
  299. package/src/services/prompt-compiler/prompt-compiler-service.ts +1143 -0
  300. package/src/services/pubkey-gate/PubkeyGateService.ts +93 -0
  301. package/src/services/pubkey-gate/index.ts +1 -0
  302. package/src/services/rag/EmbeddingProviderFactory.ts +292 -0
  303. package/src/services/rag/LanceDBMaintenanceService.ts +211 -0
  304. package/src/services/rag/RAGDatabaseService.ts +173 -0
  305. package/src/services/rag/RAGOperations.ts +682 -0
  306. package/src/services/rag/RAGService.ts +240 -0
  307. package/src/services/rag/RagSubscriptionService.ts +618 -0
  308. package/src/services/rag/rag-utils.ts +174 -0
  309. package/src/services/ral/PendingDelegationsRegistry.ts +168 -0
  310. package/src/services/ral/RALRegistry.ts +2782 -0
  311. package/src/services/ral/index.ts +4 -0
  312. package/src/services/ral/types.ts +292 -0
  313. package/src/services/reports/LocalReportStore.ts +380 -0
  314. package/src/services/reports/ReportEmbeddingService.ts +430 -0
  315. package/src/services/reports/ReportService.ts +440 -0
  316. package/src/services/reports/articleUtils.ts +52 -0
  317. package/src/services/reports/index.ts +7 -0
  318. package/src/services/scheduling/SchedulerService.ts +1057 -0
  319. package/src/services/scheduling/errors.ts +14 -0
  320. package/src/services/scheduling/index.ts +7 -0
  321. package/src/services/scheduling/utils.ts +77 -0
  322. package/src/services/search/SearchProviderRegistry.ts +78 -0
  323. package/src/services/search/UnifiedSearchService.ts +218 -0
  324. package/src/services/search/index.ts +47 -0
  325. package/src/services/search/projectFilter.ts +22 -0
  326. package/src/services/search/providers/ConversationSearchProvider.ts +48 -0
  327. package/src/services/search/providers/LessonSearchProvider.ts +75 -0
  328. package/src/services/search/providers/ReportSearchProvider.ts +49 -0
  329. package/src/services/search/types.ts +144 -0
  330. package/src/services/skill/SkillService.ts +482 -0
  331. package/src/services/skill/index.ts +2 -0
  332. package/src/services/skill/types.ts +70 -0
  333. package/src/services/status/OperationsStatusService.ts +276 -0
  334. package/src/services/status/ProjectStatusService.ts +522 -0
  335. package/src/services/status/index.ts +11 -0
  336. package/src/services/storage/PrefixKVStore.ts +242 -0
  337. package/src/services/storage/index.ts +1 -0
  338. package/src/services/system-reminder/SystemReminderUtils.ts +96 -0
  339. package/src/services/system-reminder/index.ts +7 -0
  340. package/src/services/trust-pubkeys/TrustPubkeyService.ts +325 -0
  341. package/src/services/trust-pubkeys/index.ts +2 -0
  342. package/src/telemetry/ConversationSpanManager.ts +111 -0
  343. package/src/telemetry/EventLoopMonitor.ts +206 -0
  344. package/src/telemetry/LLMSpanRegistry.ts +20 -0
  345. package/src/telemetry/NostrSpanProcessor.ts +89 -0
  346. package/src/telemetry/ToolCallSpanProcessor.ts +66 -0
  347. package/src/telemetry/diagnostics.ts +27 -0
  348. package/src/telemetry/setup.ts +120 -0
  349. package/src/tools/implementations/agents_discover.ts +121 -0
  350. package/src/tools/implementations/agents_hire.ts +127 -0
  351. package/src/tools/implementations/agents_list.ts +96 -0
  352. package/src/tools/implementations/agents_publish.ts +611 -0
  353. package/src/tools/implementations/agents_read.ts +173 -0
  354. package/src/tools/implementations/agents_write.ts +200 -0
  355. package/src/tools/implementations/ask.ts +411 -0
  356. package/src/tools/implementations/change_model.ts +141 -0
  357. package/src/tools/implementations/conversation_get.ts +661 -0
  358. package/src/tools/implementations/conversation_list.ts +377 -0
  359. package/src/tools/implementations/conversation_search.ts +370 -0
  360. package/src/tools/implementations/delegate.ts +327 -0
  361. package/src/tools/implementations/delegate_crossproject.ts +209 -0
  362. package/src/tools/implementations/delegate_followup.ts +300 -0
  363. package/src/tools/implementations/fs_edit.ts +162 -0
  364. package/src/tools/implementations/fs_glob.ts +182 -0
  365. package/src/tools/implementations/fs_grep.ts +513 -0
  366. package/src/tools/implementations/fs_read.ts +332 -0
  367. package/src/tools/implementations/fs_write.ts +113 -0
  368. package/src/tools/implementations/generate_image.ts +259 -0
  369. package/src/tools/implementations/home_fs.ts +515 -0
  370. package/src/tools/implementations/kill.ts +651 -0
  371. package/src/tools/implementations/learn.ts +166 -0
  372. package/src/tools/implementations/lesson-formatter.ts +38 -0
  373. package/src/tools/implementations/lesson_delete.ts +164 -0
  374. package/src/tools/implementations/lesson_get.ts +105 -0
  375. package/src/tools/implementations/lessons_list.ts +153 -0
  376. package/src/tools/implementations/mcp_resource_read.ts +161 -0
  377. package/src/tools/implementations/mcp_subscribe.ts +158 -0
  378. package/src/tools/implementations/mcp_subscription_stop.ts +85 -0
  379. package/src/tools/implementations/nostr_fetch.ts +149 -0
  380. package/src/tools/implementations/nostr_publish_as_user.ts +353 -0
  381. package/src/tools/implementations/project_list.ts +146 -0
  382. package/src/tools/implementations/rag_add_documents.ts +573 -0
  383. package/src/tools/implementations/rag_create_collection.ts +65 -0
  384. package/src/tools/implementations/rag_delete_collection.ts +68 -0
  385. package/src/tools/implementations/rag_list_collections.ts +77 -0
  386. package/src/tools/implementations/rag_query.ts +107 -0
  387. package/src/tools/implementations/rag_subscription_create.ts +105 -0
  388. package/src/tools/implementations/rag_subscription_delete.ts +80 -0
  389. package/src/tools/implementations/rag_subscription_get.ts +123 -0
  390. package/src/tools/implementations/rag_subscription_list.ts +128 -0
  391. package/src/tools/implementations/report_delete.ts +79 -0
  392. package/src/tools/implementations/report_read.ts +160 -0
  393. package/src/tools/implementations/report_write.ts +278 -0
  394. package/src/tools/implementations/reports_list.ts +77 -0
  395. package/src/tools/implementations/schedule_task.ts +104 -0
  396. package/src/tools/implementations/schedule_task_cancel.ts +62 -0
  397. package/src/tools/implementations/schedule_task_once.ts +128 -0
  398. package/src/tools/implementations/schedule_tasks_list.ts +79 -0
  399. package/src/tools/implementations/search.ts +160 -0
  400. package/src/tools/implementations/shell.ts +553 -0
  401. package/src/tools/implementations/todo.ts +260 -0
  402. package/src/tools/implementations/upload_blob.ts +381 -0
  403. package/src/tools/implementations/web_fetch.ts +153 -0
  404. package/src/tools/implementations/web_search.ts +250 -0
  405. package/src/tools/registry.ts +670 -0
  406. package/src/tools/types.ts +177 -0
  407. package/src/tools/utils.ts +256 -0
  408. package/src/types/event-ids.ts +320 -0
  409. package/src/types/index.ts +46 -0
  410. package/src/utils/agentFetcher.ts +107 -0
  411. package/src/utils/cli-error.ts +29 -0
  412. package/src/utils/conversation-id.ts +27 -0
  413. package/src/utils/conversation-utils.ts +1 -0
  414. package/src/utils/delegation-chain.ts +357 -0
  415. package/src/utils/error-handler.ts +42 -0
  416. package/src/utils/git/gitignore.ts +69 -0
  417. package/src/utils/git/index.ts +2 -0
  418. package/src/utils/git/initializeGitRepo.ts +204 -0
  419. package/src/utils/git/worktree.ts +260 -0
  420. package/src/utils/lessonFormatter.ts +70 -0
  421. package/src/utils/lessonTrust.ts +24 -0
  422. package/src/utils/lockfile.ts +123 -0
  423. package/src/utils/logger.ts +149 -0
  424. package/src/utils/nostr-entity-parser.ts +365 -0
  425. package/src/utils/process.ts +49 -0
  426. package/src/wrapper.ts +262 -0
  427. package/tsconfig.json +41 -0
@@ -0,0 +1,7 @@
1
+ export { InterventionService } from "./InterventionService";
2
+ export type {
3
+ PendingIntervention,
4
+ AgentResolutionResult,
5
+ AgentResolverFn,
6
+ ActiveDelegationCheckerFn,
7
+ } from "./InterventionService";
@@ -0,0 +1,683 @@
1
+ /**
2
+ * MCPManager - Official MCP SDK Integration
3
+ *
4
+ * Uses the official @modelcontextprotocol/sdk for full MCP spec compliance
5
+ */
6
+
7
+ import * as path from "node:path";
8
+ import type { MCPServerConfig, TenexMCP } from "@/services/config/types";
9
+ import { formatAnyError } from "@/lib/error-formatter";
10
+ import { logger } from "@/utils/logger";
11
+ import { trace } from "@opentelemetry/api";
12
+ import { config as configService } from "@/services/ConfigService";
13
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
14
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
15
+ import type {
16
+ Tool as MCPTool,
17
+ ReadResourceResult,
18
+ Resource,
19
+ ResourceTemplate,
20
+ } from "@modelcontextprotocol/sdk/types.js";
21
+ import { ResourceUpdatedNotificationSchema } from "@modelcontextprotocol/sdk/types.js";
22
+ import { jsonSchema, tool } from "ai";
23
+ import type { Tool as CoreTool } from "ai";
24
+
25
+ type MCPToolSet = Record<string, CoreTool<Record<string, unknown>, string>>;
26
+
27
+ interface MCPClientEntry {
28
+ client: Client;
29
+ transport: StdioClientTransport;
30
+ serverName: string;
31
+ config: MCPServerConfig;
32
+ }
33
+
34
+ type ResourceNotificationHandler = (notification: { uri: string }) => void | Promise<void>;
35
+
36
+ export class MCPManager {
37
+ private clients: Map<string, MCPClientEntry> = new Map();
38
+ private isInitialized = false;
39
+ private metadataPath?: string;
40
+ private workingDirectory?: string;
41
+ private cachedTools: MCPToolSet = {};
42
+ /** Per-server list of notification handlers (dispatcher pattern to avoid clobbering) */
43
+ private resourceNotificationHandlers: Map<string, ResourceNotificationHandler[]> = new Map();
44
+
45
+ /**
46
+ * Convert an MCP tool (JSON Schema) to an AI SDK tool.
47
+ * Uses `jsonSchema()` to pass the MCP JSON Schema directly to the AI SDK.
48
+ */
49
+ private convertMCPToolToAISdkTool(
50
+ mcpTool: MCPTool,
51
+ serverName: string,
52
+ toolName: string
53
+ ): CoreTool<Record<string, unknown>, string> {
54
+ return tool<Record<string, unknown>, string>({
55
+ description: mcpTool.description || `Tool ${toolName} from ${serverName}`,
56
+ inputSchema: jsonSchema<Record<string, unknown>>(mcpTool.inputSchema as any),
57
+ execute: async (args) => {
58
+ const entry = this.clients.get(serverName);
59
+ if (!entry) {
60
+ throw new Error(`MCP server '${serverName}' not found`);
61
+ }
62
+
63
+ try {
64
+ const callResult = await entry.client.callTool({
65
+ name: toolName,
66
+ arguments: args
67
+ });
68
+
69
+ // Extract text content from MCP CallToolResult
70
+ if (callResult.content && Array.isArray(callResult.content)) {
71
+ const textContent = callResult.content
72
+ .filter((c): c is { type: 'text'; text: string } =>
73
+ typeof c === 'object' && 'text' in c
74
+ )
75
+ .map(c => c.text)
76
+ .join('\n');
77
+ return textContent || JSON.stringify(callResult);
78
+ }
79
+
80
+ return JSON.stringify(callResult);
81
+ } catch (error) {
82
+ logger.error(
83
+ `Failed to call MCP tool '${toolName}':`,
84
+ formatAnyError(error)
85
+ );
86
+ throw error;
87
+ }
88
+ }
89
+ });
90
+ }
91
+
92
+ /**
93
+ * Initialize MCP manager with project paths
94
+ * @param metadataPath The project metadata path (~/.tenex/projects/{dTag}) for config loading
95
+ * @param workingDirectory The project working directory (~/tenex/{dTag}) for MCP server CWD
96
+ */
97
+ async initialize(metadataPath?: string, workingDirectory?: string): Promise<void> {
98
+ if (this.isInitialized) {
99
+ return;
100
+ }
101
+
102
+ try {
103
+ this.metadataPath = metadataPath;
104
+ this.workingDirectory = workingDirectory;
105
+
106
+ // Load and merge global + project MCP configs
107
+ const globalPath = configService.getGlobalPath();
108
+ const globalMCP = await configService.loadTenexMCP(globalPath);
109
+ const projectMCP = metadataPath
110
+ ? await configService.loadTenexMCP(metadataPath)
111
+ : { servers: {}, enabled: true };
112
+
113
+ const mergedMCP: TenexMCP = {
114
+ servers: { ...globalMCP.servers, ...projectMCP.servers },
115
+ enabled: projectMCP.enabled !== undefined ? projectMCP.enabled : globalMCP.enabled,
116
+ };
117
+
118
+ if (!mergedMCP.enabled) {
119
+ this.isInitialized = true;
120
+ return;
121
+ }
122
+
123
+ if (mergedMCP.servers && Object.keys(mergedMCP.servers).length > 0) {
124
+ await this.startServers(mergedMCP);
125
+ await this.refreshToolCache();
126
+ }
127
+ this.isInitialized = true;
128
+
129
+ trace.getActiveSpan()?.addEvent("mcp.initialized", {
130
+ "servers.count": this.clients.size,
131
+ });
132
+ } catch (error) {
133
+ logger.error("Failed to initialize MCP manager:", error);
134
+ // Don't throw - allow the system to continue without MCP
135
+ }
136
+ }
137
+
138
+ private async startServers(mcpConfig: TenexMCP): Promise<void> {
139
+ const startPromises = Object.entries(mcpConfig.servers)
140
+ .filter(([name]) => {
141
+ if (!name || name.trim() === "") {
142
+ logger.warn("Skipping MCP server with empty or invalid name");
143
+ return false;
144
+ }
145
+ return true;
146
+ })
147
+ .map(([name, config]) =>
148
+ this.startServer(name, config).catch((error) => {
149
+ logger.error(`Failed to start MCP server '${name}':`, formatAnyError(error));
150
+ // Continue with other servers
151
+ })
152
+ );
153
+
154
+ await Promise.all(startPromises);
155
+ }
156
+
157
+ private async startServer(name: string, config: MCPServerConfig): Promise<void> {
158
+ if (this.clients.has(name)) {
159
+ logger.warn(`MCP server '${name}' is already running`);
160
+ return;
161
+ }
162
+
163
+ // SECURITY CHECK: Enforce allowedPaths
164
+ if (config.allowedPaths && config.allowedPaths.length > 0 && this.workingDirectory) {
165
+ const resolvedWorkingDir = path.resolve(this.workingDirectory);
166
+ // Filter out undefined/null values from allowedPaths
167
+ const validAllowedPaths = config.allowedPaths.filter(
168
+ (p): p is string => typeof p === "string" && p.length > 0
169
+ );
170
+ const isAllowed = validAllowedPaths.some((allowedPath) => {
171
+ const resolvedAllowedPath = path.resolve(allowedPath);
172
+ return (
173
+ resolvedWorkingDir.startsWith(resolvedAllowedPath) ||
174
+ resolvedAllowedPath.startsWith(resolvedWorkingDir)
175
+ );
176
+ });
177
+
178
+ if (!isAllowed) {
179
+ logger.warn(
180
+ `Skipping MCP server '${name}' due to path restrictions. Working directory '${this.workingDirectory}' is not in allowedPaths: ${validAllowedPaths.join(", ")}`
181
+ );
182
+ return;
183
+ }
184
+ }
185
+
186
+ const mergedEnv: Record<string, string> = {};
187
+ // Only include defined environment variables
188
+ for (const [key, value] of Object.entries(process.env)) {
189
+ if (value !== undefined) {
190
+ mergedEnv[key] = value;
191
+ }
192
+ }
193
+ // Override with config env
194
+ if (config.env) {
195
+ Object.assign(mergedEnv, config.env);
196
+ }
197
+
198
+ trace.getActiveSpan()?.addEvent("mcp.server_starting", {
199
+ "server.name": name,
200
+ "server.command": config.command,
201
+ });
202
+
203
+ // Create transport
204
+ const transport = new StdioClientTransport({
205
+ command: config.command,
206
+ args: config.args,
207
+ env: mergedEnv,
208
+ cwd: this.workingDirectory,
209
+ });
210
+
211
+ try {
212
+ // Create client
213
+ const client = new Client(
214
+ {
215
+ name: `tenex-${name}`,
216
+ version: '1.0.0'
217
+ },
218
+ {
219
+ capabilities: {}
220
+ }
221
+ );
222
+
223
+ // Connect to server
224
+ await client.connect(transport);
225
+
226
+ // Health check - try listing tools with timeout
227
+ const timeoutPromise = new Promise((_, reject) =>
228
+ setTimeout(() => reject(new Error("Health check timeout")), 5000)
229
+ );
230
+
231
+ try {
232
+ await Promise.race([client.listTools(), timeoutPromise]);
233
+ } catch (error) {
234
+ logger.error(`MCP server '${name}' failed health check:`, error);
235
+ await client.close();
236
+ return;
237
+ }
238
+
239
+ // Store client entry
240
+ this.clients.set(name, {
241
+ client,
242
+ transport,
243
+ serverName: name,
244
+ config,
245
+ });
246
+
247
+ logger.info(`MCP server '${name}' started successfully`);
248
+
249
+ // Emit telemetry
250
+ trace.getActiveSpan()?.addEvent("mcp.server_started", {
251
+ "server.name": name,
252
+ });
253
+ } catch (error) {
254
+ logger.error(`Failed to start MCP server '${name}':`, formatAnyError(error));
255
+ throw error;
256
+ }
257
+ }
258
+
259
+ private async refreshToolCache(): Promise<void> {
260
+ const tools: MCPToolSet = {};
261
+
262
+ for (const [serverName, entry] of this.clients) {
263
+ try {
264
+ // List tools from MCP server (official SDK method)
265
+ const { tools: mcpTools } = await entry.client.listTools();
266
+
267
+ // Convert each MCP tool to AI SDK tool
268
+ for (const mcpTool of mcpTools) {
269
+ const namespacedName = `mcp__${serverName}__${mcpTool.name}`;
270
+ tools[namespacedName] = this.convertMCPToolToAISdkTool(
271
+ mcpTool,
272
+ serverName,
273
+ mcpTool.name
274
+ );
275
+ }
276
+
277
+ trace.getActiveSpan()?.addEvent("mcp.tools_discovered", {
278
+ "server.name": serverName,
279
+ "tools.count": mcpTools.length,
280
+ });
281
+ } catch (error) {
282
+ logger.error(
283
+ `Failed to get tools from MCP server '${serverName}':`,
284
+ formatAnyError(error)
285
+ );
286
+ }
287
+ }
288
+
289
+ this.cachedTools = tools;
290
+ trace.getActiveSpan()?.addEvent("mcp.tools_cached", {
291
+ "tools.total": Object.keys(tools).length,
292
+ "servers.count": this.clients.size,
293
+ });
294
+ }
295
+
296
+ /**
297
+ * Refresh the tool cache
298
+ */
299
+ async refreshTools(): Promise<void> {
300
+ await this.refreshToolCache();
301
+ }
302
+
303
+ /**
304
+ * Get all cached MCP tools as an object keyed by tool name
305
+ */
306
+ getCachedTools(): MCPToolSet {
307
+ return this.cachedTools;
308
+ }
309
+
310
+ async shutdown(): Promise<void> {
311
+ const shutdownPromises: Promise<void>[] = [];
312
+
313
+ for (const [name, entry] of this.clients) {
314
+ shutdownPromises.push(this.shutdownServer(name, entry));
315
+ }
316
+
317
+ await Promise.all(shutdownPromises);
318
+ this.clients.clear();
319
+ this.cachedTools = {};
320
+ // Clear notification handlers so reload() re-registers them on new clients
321
+ this.resourceNotificationHandlers.clear();
322
+ this.isInitialized = false;
323
+ }
324
+
325
+ private async shutdownServer(name: string, entry: MCPClientEntry): Promise<void> {
326
+ try {
327
+ // Official SDK: close() closes both client and transport
328
+ await entry.client.close();
329
+
330
+ trace.getActiveSpan()?.addEvent("mcp.server_shutdown", {
331
+ "server.name": name,
332
+ });
333
+ } catch (error) {
334
+ logger.error(`Error shutting down MCP server '${name}':`, formatAnyError(error));
335
+ }
336
+ }
337
+
338
+ /**
339
+ * Check if a server is running
340
+ */
341
+ isServerRunning(name: string): boolean {
342
+ return this.clients.has(name);
343
+ }
344
+
345
+ /**
346
+ * Get list of running servers
347
+ */
348
+ getRunningServers(): string[] {
349
+ return Array.from(this.clients.keys());
350
+ }
351
+
352
+ /**
353
+ * Get configuration for all running MCP servers.
354
+ * Used to pass server configs to LLM providers (like Claude Code)
355
+ * that need to spawn their own instances of these servers.
356
+ *
357
+ * @returns Record of server name to server configuration
358
+ */
359
+ getServerConfigs(): Record<string, MCPServerConfig> {
360
+ const configs: Record<string, MCPServerConfig> = {};
361
+ for (const [name, entry] of this.clients) {
362
+ configs[name] = entry.config;
363
+ }
364
+ return configs;
365
+ }
366
+
367
+ /**
368
+ * Reload MCP service configuration and restart servers
369
+ * @param metadataPath The project metadata path (~/.tenex/projects/{dTag})
370
+ * @param workingDirectory The project working directory (~/tenex/{dTag})
371
+ */
372
+ async reload(metadataPath?: string, workingDirectory?: string): Promise<void> {
373
+ trace.getActiveSpan()?.addEvent("mcp.reloading");
374
+
375
+ // Shutdown existing servers
376
+ await this.shutdown();
377
+
378
+ // Re-initialize with the new configuration
379
+ await this.initialize(
380
+ metadataPath || this.metadataPath,
381
+ workingDirectory || this.workingDirectory
382
+ );
383
+
384
+ trace.getActiveSpan()?.addEvent("mcp.reloaded", {
385
+ "servers.running": this.getRunningServers().length,
386
+ "tools.available": Object.keys(this.cachedTools).length,
387
+ });
388
+ }
389
+
390
+ /**
391
+ * List resources from a specific MCP server
392
+ * @param serverName - Name of the MCP server
393
+ * @returns Array of resources from that server
394
+ */
395
+ async listResources(serverName: string): Promise<Resource[]> {
396
+ const entry = this.clients.get(serverName);
397
+ if (!entry) {
398
+ const validServers = this.getRunningServers();
399
+ const serverList =
400
+ validServers.length > 0
401
+ ? `Valid servers: ${validServers.join(", ")}`
402
+ : "No MCP servers are currently running";
403
+ throw new Error(`MCP server '${serverName}' not found. ${serverList}`);
404
+ }
405
+
406
+ try {
407
+ const result = await entry.client.listResources();
408
+ return result.resources;
409
+ } catch (error) {
410
+ logger.error(`Failed to list resources from '${serverName}':`, formatAnyError(error));
411
+ throw error;
412
+ }
413
+ }
414
+
415
+ /**
416
+ * List all resources from all connected MCP servers
417
+ * @returns Map of server names to their resources
418
+ */
419
+ async listAllResources(): Promise<Map<string, Resource[]>> {
420
+ const resourcesMap = new Map<string, Resource[]>();
421
+
422
+ for (const [serverName] of this.clients) {
423
+ try {
424
+ const resources = await this.listResources(serverName);
425
+ resourcesMap.set(serverName, resources);
426
+ } catch (error) {
427
+ logger.error(
428
+ `Failed to list resources from '${serverName}':`,
429
+ formatAnyError(error)
430
+ );
431
+ // Continue with other servers
432
+ }
433
+ }
434
+
435
+ return resourcesMap;
436
+ }
437
+
438
+ /**
439
+ * List resource templates from a specific MCP server
440
+ * @param serverName - Name of the MCP server
441
+ * @returns Array of resource templates from that server
442
+ */
443
+ async listResourceTemplates(serverName: string): Promise<ResourceTemplate[]> {
444
+ const entry = this.clients.get(serverName);
445
+ if (!entry) {
446
+ const validServers = this.getRunningServers();
447
+ const serverList =
448
+ validServers.length > 0
449
+ ? `Valid servers: ${validServers.join(", ")}`
450
+ : "No MCP servers are currently running";
451
+ throw new Error(`MCP server '${serverName}' not found. ${serverList}`);
452
+ }
453
+
454
+ try {
455
+ const result = await entry.client.listResourceTemplates();
456
+ return result.resourceTemplates;
457
+ } catch (error) {
458
+ logger.error(
459
+ `Failed to list resource templates from '${serverName}':`,
460
+ formatAnyError(error)
461
+ );
462
+ throw error;
463
+ }
464
+ }
465
+
466
+ /**
467
+ * List all resource templates from all connected MCP servers
468
+ * @returns Map of server names to their resource templates
469
+ */
470
+ async listAllResourceTemplates(): Promise<Map<string, ResourceTemplate[]>> {
471
+ const templatesMap = new Map<string, ResourceTemplate[]>();
472
+
473
+ for (const [serverName] of this.clients) {
474
+ try {
475
+ const templates = await this.listResourceTemplates(serverName);
476
+ templatesMap.set(serverName, templates);
477
+ } catch (error) {
478
+ logger.error(
479
+ `Failed to list resource templates from '${serverName}':`,
480
+ formatAnyError(error)
481
+ );
482
+ // Continue with other servers
483
+ }
484
+ }
485
+
486
+ return templatesMap;
487
+ }
488
+
489
+ /**
490
+ * Read a resource from a specific MCP server
491
+ * @param serverName - Name of the MCP server
492
+ * @param uri - URI of the resource to read
493
+ * @returns Resource content
494
+ */
495
+ async readResource(
496
+ serverName: string,
497
+ uri: string
498
+ ): Promise<ReadResourceResult> {
499
+ const entry = this.clients.get(serverName);
500
+ if (!entry) {
501
+ const validServers = this.getRunningServers();
502
+ const serverList =
503
+ validServers.length > 0
504
+ ? `Valid servers: ${validServers.join(", ")}`
505
+ : "No MCP servers are currently running";
506
+ throw new Error(`MCP server '${serverName}' not found. ${serverList}`);
507
+ }
508
+
509
+ try {
510
+ return await entry.client.readResource({ uri });
511
+ } catch (error) {
512
+ logger.error(
513
+ `Failed to read resource '${uri}' from '${serverName}':`,
514
+ formatAnyError(error)
515
+ );
516
+ throw error;
517
+ }
518
+ }
519
+
520
+ /**
521
+ * Get resource context as a formatted string for RAG pattern
522
+ * @param serverName - Name of the MCP server
523
+ * @param resourceUris - Array of resource URIs to fetch
524
+ * @returns Formatted context string
525
+ */
526
+ async getResourceContext(serverName: string, resourceUris: string[]): Promise<string> {
527
+ const contents: string[] = [];
528
+
529
+ for (const uri of resourceUris) {
530
+ try {
531
+ const result = await this.readResource(serverName, uri);
532
+
533
+ for (const content of result.contents) {
534
+ if ("text" in content) {
535
+ contents.push(`Resource: ${uri}\n${content.text}`);
536
+ } else if ("blob" in content) {
537
+ contents.push(
538
+ `Resource: ${uri}\n[Binary content: ${content.blob.length} bytes]`
539
+ );
540
+ }
541
+ }
542
+ } catch (error) {
543
+ logger.error(`Failed to read resource '${uri}':`, formatAnyError(error));
544
+ // Continue with other resources
545
+ }
546
+ }
547
+
548
+ return contents.join("\n\n---\n\n");
549
+ }
550
+
551
+ /**
552
+ * Subscribe to resource updates from an MCP server
553
+ * @param serverName - Name of the MCP server
554
+ * @param resourceUri - URI of the resource to subscribe to
555
+ */
556
+ async subscribeToResource(serverName: string, resourceUri: string): Promise<void> {
557
+ const entry = this.clients.get(serverName);
558
+ if (!entry) {
559
+ const validServers = this.getRunningServers();
560
+ const serverList =
561
+ validServers.length > 0
562
+ ? `Valid servers: ${validServers.join(", ")}`
563
+ : "No MCP servers are currently running";
564
+ throw new Error(`MCP server '${serverName}' not found. ${serverList}`);
565
+ }
566
+
567
+ try {
568
+ // Official SDK method - properly supported
569
+ await entry.client.subscribeResource({ uri: resourceUri });
570
+
571
+ trace.getActiveSpan()?.addEvent("mcp.resource_subscribed", {
572
+ "server.name": serverName,
573
+ "resource.uri": resourceUri,
574
+ });
575
+ } catch (error) {
576
+ logger.error(
577
+ `Failed to subscribe to resource '${resourceUri}' from '${serverName}':`,
578
+ formatAnyError(error)
579
+ );
580
+ throw error;
581
+ }
582
+ }
583
+
584
+ /**
585
+ * Unsubscribe from resource updates
586
+ * @param serverName - Name of the MCP server
587
+ * @param resourceUri - URI of the resource to unsubscribe from
588
+ */
589
+ async unsubscribeFromResource(serverName: string, resourceUri: string): Promise<void> {
590
+ const entry = this.clients.get(serverName);
591
+ if (!entry) {
592
+ const validServers = this.getRunningServers();
593
+ const serverList =
594
+ validServers.length > 0
595
+ ? `Valid servers: ${validServers.join(", ")}`
596
+ : "No MCP servers are currently running";
597
+ throw new Error(`MCP server '${serverName}' not found. ${serverList}`);
598
+ }
599
+
600
+ try {
601
+ // Official SDK method - properly supported
602
+ await entry.client.unsubscribeResource({ uri: resourceUri });
603
+
604
+ trace.getActiveSpan()?.addEvent("mcp.resource_unsubscribed", {
605
+ "server.name": serverName,
606
+ "resource.uri": resourceUri,
607
+ });
608
+ } catch (error) {
609
+ logger.error(
610
+ `Failed to unsubscribe from resource '${resourceUri}' from '${serverName}':`,
611
+ formatAnyError(error)
612
+ );
613
+ throw error;
614
+ }
615
+ }
616
+
617
+ /**
618
+ * Add a handler for resource update notifications on a server.
619
+ * Multiple handlers can be registered per server (dispatcher pattern).
620
+ * Must be called BEFORE subscribeToResource.
621
+ *
622
+ * @returns A removal function to unregister this specific handler
623
+ */
624
+ addResourceNotificationHandler(
625
+ serverName: string,
626
+ handler: ResourceNotificationHandler
627
+ ): () => void {
628
+ const entry = this.clients.get(serverName);
629
+ if (!entry) {
630
+ throw new Error(`MCP server '${serverName}' not found`);
631
+ }
632
+
633
+ // Initialize handler list for this server if needed
634
+ if (!this.resourceNotificationHandlers.has(serverName)) {
635
+ this.resourceNotificationHandlers.set(serverName, []);
636
+
637
+ // Register the SDK-level notification handler ONCE per server.
638
+ // This dispatcher fans out to all registered handlers.
639
+ entry.client.setNotificationHandler(
640
+ ResourceUpdatedNotificationSchema,
641
+ async (notification) => {
642
+ const uri = notification.params.uri;
643
+ if (!uri) return;
644
+
645
+ const handlers = this.resourceNotificationHandlers.get(serverName) ?? [];
646
+ for (const h of handlers) {
647
+ try {
648
+ await h({ uri });
649
+ } catch (error) {
650
+ logger.error("Resource notification handler error", {
651
+ server: serverName,
652
+ uri,
653
+ error: error instanceof Error ? error.message : String(error),
654
+ });
655
+ }
656
+ }
657
+ }
658
+ );
659
+ }
660
+
661
+ const handlers = this.resourceNotificationHandlers.get(serverName)!;
662
+ handlers.push(handler);
663
+
664
+ trace.getActiveSpan()?.addEvent("mcp.resource_handler_registered", {
665
+ "server.name": serverName,
666
+ "handlers.count": handlers.length,
667
+ });
668
+
669
+ // Return removal function
670
+ return () => {
671
+ const currentHandlers = this.resourceNotificationHandlers.get(serverName);
672
+ if (currentHandlers) {
673
+ const index = currentHandlers.indexOf(handler);
674
+ if (index !== -1) {
675
+ currentHandlers.splice(index, 1);
676
+ }
677
+ }
678
+ };
679
+ }
680
+ }
681
+
682
+ // MCPManager is now per-project - create instances in ProjectRuntime
683
+ // No more singleton export