@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,937 @@
1
+ import type { AgentExecutor } from "@/agents/execution/AgentExecutor";
2
+ import { createExecutionContext } from "@/agents/execution/ExecutionContextFactory";
3
+ import type { AgentInstance } from "@/agents/types";
4
+ import { ConversationStore } from "@/conversations/ConversationStore";
5
+ import { ConversationResolver } from "@/conversations/services/ConversationResolver";
6
+ import { ConversationSummarizer } from "@/conversations/services/ConversationSummarizer";
7
+ import { metadataDebounceManager } from "@/conversations/services/MetadataDebounceManager";
8
+ import type { DelegationMarker } from "@/conversations/types";
9
+ import { formatAnyError } from "@/lib/error-formatter";
10
+ import { shortenConversationId } from "@/utils/conversation-id";
11
+ import { AgentEventDecoder } from "@/nostr/AgentEventDecoder";
12
+ import { config } from "@/services/ConfigService";
13
+ import { PROVIDER_IDS } from "@/llm/providers/provider-ids";
14
+ import { llmOpsRegistry, INJECTION_ABORT_REASON } from "@/services/LLMOperationsRegistry";
15
+ import { getProjectContext, type ProjectContext } from "@/services/projects";
16
+ import { RALRegistry } from "@/services/ral";
17
+ import type { RALRegistryEntry } from "@/services/ral/types";
18
+ import { logger } from "@/utils/logger";
19
+ import type { NDKEvent } from "@nostr-dev-kit/ndk";
20
+ import { ROOT_CONTEXT, SpanStatusCode, context as otelContext, trace } from "@opentelemetry/api";
21
+ import { AgentRouter } from "@/services/dispatch/AgentRouter";
22
+ import { handleDelegationCompletion } from "@/services/dispatch/DelegationCompletionHandler";
23
+
24
+ const tracer = trace.getTracer("tenex.dispatch");
25
+ // Coalesce back-to-back delegation completions so we resume once with a stable snapshot.
26
+ const DELEGATION_COMPLETION_DEBOUNCE_MS = 2500;
27
+ const getSafeContext = (): ReturnType<typeof otelContext.active> => {
28
+ const activeContext = otelContext.active();
29
+ // Defensive fallback for test mocks or non-standard context managers.
30
+ return typeof (activeContext as { getValue?: unknown }).getValue === "function"
31
+ ? activeContext
32
+ : ROOT_CONTEXT;
33
+ };
34
+ const getSafeActiveSpan = (): ReturnType<typeof trace.getActiveSpan> => {
35
+ try {
36
+ return trace.getActiveSpan();
37
+ } catch {
38
+ return undefined;
39
+ }
40
+ };
41
+
42
+ interface DispatchContext {
43
+ agentExecutor: AgentExecutor;
44
+ }
45
+
46
+ interface DelegationTarget {
47
+ agent: AgentInstance;
48
+ conversationId: string;
49
+ }
50
+
51
+ export class AgentDispatchService {
52
+ private static instance: AgentDispatchService;
53
+ private readonly delegationDebounceState = new Map<
54
+ string,
55
+ { timeout: ReturnType<typeof setTimeout>; promise: Promise<void>; resolve: () => void }
56
+ >();
57
+ private readonly delegationDebounceSequence = new Map<string, number>();
58
+
59
+ private constructor() {}
60
+
61
+ static getInstance(): AgentDispatchService {
62
+ if (!AgentDispatchService.instance) {
63
+ AgentDispatchService.instance = new AgentDispatchService();
64
+ }
65
+ return AgentDispatchService.instance;
66
+ }
67
+
68
+ async dispatch(event: NDKEvent, context: DispatchContext): Promise<void> {
69
+ const span = tracer.startSpan(
70
+ "tenex.dispatch.chat_message",
71
+ {
72
+ attributes: {
73
+ "event.id": event.id ?? "",
74
+ "event.pubkey": event.pubkey ?? "",
75
+ "event.kind": event.kind ?? 0,
76
+ "event.content_length": event.content?.length ?? 0,
77
+ },
78
+ },
79
+ getSafeContext()
80
+ );
81
+
82
+ try {
83
+ await this.handleChatMessage(event, context, span);
84
+ span.setStatus({ code: SpanStatusCode.OK });
85
+ } catch (error) {
86
+ span.recordException(error as Error);
87
+ span.setStatus({
88
+ code: SpanStatusCode.ERROR,
89
+ message: (error as Error).message,
90
+ });
91
+ logger.error("Failed to route reply", {
92
+ error: formatAnyError(error),
93
+ eventId: event.id,
94
+ });
95
+ } finally {
96
+ span.end();
97
+ }
98
+ }
99
+
100
+ private async handleChatMessage(
101
+ event: NDKEvent,
102
+ { agentExecutor }: DispatchContext,
103
+ span: ReturnType<typeof tracer.startSpan>
104
+ ): Promise<void> {
105
+ const projectCtx = getProjectContext();
106
+
107
+ const isDirectedToSystem = AgentEventDecoder.isDirectedToSystem(event, projectCtx.agents);
108
+ const isFromAgent = AgentEventDecoder.isEventFromAgent(event, projectCtx.agents);
109
+
110
+ span.setAttributes({
111
+ "routing.is_directed_to_system": isDirectedToSystem,
112
+ "routing.is_from_agent": isFromAgent,
113
+ });
114
+
115
+ getSafeActiveSpan()?.addEvent("reply.message_received", {
116
+ "event.id": event.id ?? "",
117
+ "event.pubkey": event.pubkey?.substring(0, 8) ?? "",
118
+ "message.preview": event.content.substring(0, 100),
119
+ "routing.is_directed_to_system": isDirectedToSystem,
120
+ "routing.is_from_agent": isFromAgent,
121
+ });
122
+
123
+ span.addEvent("dispatch.message_received", {
124
+ "routing.is_directed_to_system": isDirectedToSystem,
125
+ "routing.is_from_agent": isFromAgent,
126
+ });
127
+
128
+ if (!isDirectedToSystem && isFromAgent) {
129
+ getSafeActiveSpan()?.addEvent("reply.agent_event_not_directed", {
130
+ "event.id": event.id ?? "",
131
+ });
132
+ span.addEvent("dispatch.agent_event_not_directed");
133
+
134
+ const resolver = new ConversationResolver();
135
+ const result = await resolver.resolveConversationForEvent(event);
136
+
137
+ if (result.conversation) {
138
+ await ConversationStore.addEvent(result.conversation.id, event);
139
+ getSafeActiveSpan()?.addEvent("reply.added_to_history", {
140
+ "conversation.id": shortenConversationId(result.conversation.id),
141
+ });
142
+ span.addEvent("dispatch.agent_event_added_to_history", {
143
+ "conversation.id": shortenConversationId(result.conversation.id),
144
+ });
145
+ } else {
146
+ getSafeActiveSpan()?.addEvent("reply.no_conversation_found", {
147
+ "event.id": event.id ?? "",
148
+ });
149
+ span.addEvent("dispatch.agent_event_no_conversation");
150
+ }
151
+ return;
152
+ }
153
+
154
+ await this.handleReplyLogic(event, agentExecutor, projectCtx, span);
155
+ }
156
+
157
+ private async handleReplyLogic(
158
+ event: NDKEvent,
159
+ agentExecutor: AgentExecutor,
160
+ projectCtx: ProjectContext,
161
+ span: ReturnType<typeof tracer.startSpan>
162
+ ): Promise<void> {
163
+ const delegationResult = await handleDelegationCompletion(event);
164
+ const delegationTarget = AgentRouter.resolveDelegationTarget(delegationResult, projectCtx);
165
+
166
+ if (delegationTarget) {
167
+ span.addEvent("dispatch.delegation_completion_routed", {
168
+ "delegation.agent_slug": delegationTarget.agent.slug,
169
+ "delegation.conversation_id": shortenConversationId(delegationTarget.conversationId),
170
+ });
171
+ await this.handleDelegationResponse(event, delegationTarget, agentExecutor, projectCtx, span);
172
+ return;
173
+ }
174
+
175
+ if (AgentEventDecoder.isDelegationCompletion(event)) {
176
+ const activeSpan = getSafeActiveSpan();
177
+ activeSpan?.addEvent("reply.completion_dropped_no_waiting_ral", {
178
+ "event.id": event.id ?? "",
179
+ "event.pubkey": event.pubkey.substring(0, 8),
180
+ });
181
+ activeSpan?.setStatus({
182
+ code: SpanStatusCode.ERROR,
183
+ message: "Delegation completion dropped: no waiting RAL found. This indicates a delegation registration bug.",
184
+ });
185
+ logger.error("[reply] Delegation completion dropped - no waiting RAL", {
186
+ eventId: event.id,
187
+ eventPubkey: event.pubkey.substring(0, 8),
188
+ });
189
+ span.addEvent("dispatch.delegation_completion_dropped");
190
+ return;
191
+ }
192
+
193
+ const conversationResolver = new ConversationResolver();
194
+ const { conversation, isNew } = await conversationResolver.resolveConversationForEvent(event);
195
+
196
+ if (!conversation) {
197
+ logger.error("No conversation found or created for event", {
198
+ eventId: event.id,
199
+ replyTarget: AgentEventDecoder.getReplyTarget(event),
200
+ });
201
+ span.addEvent("dispatch.conversation_missing", {
202
+ "event.id": event.id ?? "",
203
+ });
204
+ return;
205
+ }
206
+
207
+ span.setAttributes({
208
+ "conversation.id": shortenConversationId(conversation.id),
209
+ "conversation.is_new": isNew,
210
+ });
211
+
212
+ if (!isNew && event.id && conversation.hasEventId(event.id)) {
213
+ getSafeActiveSpan()?.addEvent("reply.skipped_duplicate_event", {
214
+ "event.id": event.id,
215
+ "conversation.id": shortenConversationId(conversation.id),
216
+ });
217
+ span.addEvent("dispatch.duplicate_event_skipped", {
218
+ "conversation.id": shortenConversationId(conversation.id),
219
+ });
220
+
221
+ if (!AgentEventDecoder.isAgentInternalMessage(event)) {
222
+ await ConversationStore.addEvent(conversation.id, event);
223
+ }
224
+ return;
225
+ }
226
+
227
+ if (!isNew && !AgentEventDecoder.isAgentInternalMessage(event)) {
228
+ await ConversationStore.addEvent(conversation.id, event);
229
+ }
230
+
231
+ if (isNew && !AgentEventDecoder.isAgentInternalMessage(event)) {
232
+ metadataDebounceManager.markFirstPublishDone(conversation.id);
233
+
234
+ const summarizer = new ConversationSummarizer(projectCtx);
235
+ summarizer.summarizeAndPublish(conversation).catch((error) => {
236
+ logger.error("Failed to generate initial metadata for new conversation", {
237
+ conversationId: conversation.id,
238
+ error: formatAnyError(error),
239
+ });
240
+ });
241
+ getSafeActiveSpan()?.addEvent("reply.initial_metadata_scheduled", {
242
+ "conversation.id": shortenConversationId(conversation.id),
243
+ });
244
+ span.addEvent("dispatch.initial_metadata_scheduled", {
245
+ "conversation.id": shortenConversationId(conversation.id),
246
+ });
247
+ }
248
+
249
+ const whitelistedPubkeys = config.getConfig().whitelistedPubkeys ?? [];
250
+ const whitelist = new Set(whitelistedPubkeys);
251
+ if (whitelist.has(event.pubkey)) {
252
+ const { unblocked } = AgentRouter.unblockAgent(event, conversation, projectCtx, whitelist);
253
+ if (unblocked) {
254
+ getSafeActiveSpan()?.addEvent("reply.agent_unblocked_by_whitelist", {
255
+ "event.pubkey": event.pubkey.substring(0, 8),
256
+ });
257
+ span.addEvent("dispatch.agent_unblocked", {
258
+ "event.pubkey": event.pubkey.substring(0, 8),
259
+ });
260
+ }
261
+ }
262
+
263
+ getSafeActiveSpan()?.addEvent("reply.before_agent_routing");
264
+ const targetAgents = AgentRouter.resolveTargetAgents(event, projectCtx, conversation);
265
+
266
+ const activeSpan = getSafeActiveSpan();
267
+ if (activeSpan) {
268
+ const mentionedPubkeys = AgentEventDecoder.getMentionedPubkeys(event);
269
+ activeSpan.addEvent("agent_routing", {
270
+ "routing.mentioned_pubkeys_count": mentionedPubkeys.length,
271
+ "routing.resolved_agent_count": targetAgents.length,
272
+ "routing.agent_names": targetAgents.map((a) => a.name).join(", "),
273
+ "routing.agent_roles": targetAgents.map((a) => a.role).join(", "),
274
+ });
275
+ }
276
+
277
+ span.addEvent("dispatch.routing_complete", {
278
+ "routing.resolved_agent_count": targetAgents.length,
279
+ });
280
+ span.setAttributes({
281
+ "routing.target_agent_count": targetAgents.length,
282
+ });
283
+
284
+ if (targetAgents.length === 0) {
285
+ activeSpan?.addEvent("reply.no_target_agents", {
286
+ "event.id": event.id ?? "",
287
+ });
288
+ span.addEvent("dispatch.no_target_agents");
289
+ return;
290
+ }
291
+
292
+ metadataDebounceManager.onAgentStart(conversation.id);
293
+
294
+ await this.dispatchToAgents({
295
+ targetAgents,
296
+ event,
297
+ conversationId: conversation.id,
298
+ projectCtx,
299
+ agentExecutor,
300
+ parentSpan: span,
301
+ });
302
+
303
+ if (!AgentEventDecoder.isAgentInternalMessage(event)) {
304
+ metadataDebounceManager.schedulePublish(
305
+ conversation.id,
306
+ false,
307
+ async () => {
308
+ const summarizer = new ConversationSummarizer(projectCtx);
309
+ await summarizer.summarizeAndPublish(conversation);
310
+ }
311
+ );
312
+ getSafeActiveSpan()?.addEvent("reply.summarization_scheduled", {
313
+ "conversation.id": shortenConversationId(conversation.id),
314
+ "debounced": true,
315
+ });
316
+ span.addEvent("dispatch.summarization_scheduled", {
317
+ "conversation.id": shortenConversationId(conversation.id),
318
+ });
319
+ }
320
+ }
321
+
322
+ private async handleDelegationResponse(
323
+ event: NDKEvent,
324
+ delegationTarget: DelegationTarget,
325
+ agentExecutor: AgentExecutor,
326
+ projectCtx: ProjectContext,
327
+ parentSpan: ReturnType<typeof tracer.startSpan>
328
+ ): Promise<void> {
329
+ const span = tracer.startSpan(
330
+ "tenex.dispatch.delegation_response",
331
+ {
332
+ attributes: {
333
+ "delegation.agent_slug": delegationTarget.agent.slug,
334
+ "delegation.conversation_id": shortenConversationId(delegationTarget.conversationId),
335
+ },
336
+ },
337
+ trace.setSpan(getSafeContext(), parentSpan)
338
+ );
339
+
340
+ try {
341
+ // Check if this agent is in cooldown for this conversation
342
+ // Non-null assertion: delegationTarget.conversationId and dTag are guaranteed to be defined (checked in resolveDelegationTarget)
343
+ const isInCooldown = await this.checkAndBlockIfCooldown(
344
+ projectCtx.project.dTag!,
345
+ delegationTarget.conversationId!,
346
+ delegationTarget.agent.pubkey,
347
+ delegationTarget.agent.slug,
348
+ span,
349
+ "delegation_completion"
350
+ );
351
+
352
+ if (isInCooldown) {
353
+ return;
354
+ }
355
+
356
+ const ralRegistry = RALRegistry.getInstance();
357
+ const activeRal = ralRegistry.getState(
358
+ delegationTarget.agent.pubkey,
359
+ delegationTarget.conversationId
360
+ );
361
+
362
+ // NOTE: We intentionally do NOT abort streaming executions when delegation completes.
363
+ // The delegator might be mid-stream (waiting for LLM response after a tool call).
364
+ // Aborting would kill the stream before it can finish naturally.
365
+ // Instead, we let the debounce run, then either:
366
+ // - Resume the finished RAL with delegation results
367
+ // - Queue results for an active stream to pick up via prepareStep
368
+ // See trace-detective report on executor.result_undefined_error for details.
369
+ if (activeRal) {
370
+ span.addEvent("dispatch.delegation_completion_received", {
371
+ "ral.number": activeRal.ralNumber,
372
+ "ral.is_streaming": activeRal.isStreaming,
373
+ });
374
+ }
375
+ const debounceKey = `${delegationTarget.agent.pubkey}:${delegationTarget.conversationId}`;
376
+ const debounceSequence = await this.waitForDelegationDebounce(debounceKey, span);
377
+ if (this.delegationDebounceSequence.get(debounceKey) !== debounceSequence) {
378
+ span.addEvent("dispatch.delegation_debounce_skipped", {
379
+ "debounce.sequence": debounceSequence,
380
+ });
381
+ span.setStatus({ code: SpanStatusCode.OK });
382
+ return;
383
+ }
384
+ this.delegationDebounceSequence.delete(debounceKey);
385
+
386
+ // Check if there's still an active streaming RAL after debounce.
387
+ // If so, just queue the delegation results - the prepareStep callback will pick them up.
388
+ // This prevents starting a second execution while the first is still streaming.
389
+ const currentRal = ralRegistry.getState(
390
+ delegationTarget.agent.pubkey,
391
+ delegationTarget.conversationId
392
+ );
393
+ if (currentRal?.isStreaming) {
394
+ // Insert delegation markers directly into ConversationStore
395
+ // The active stream will pick up markers when it rebuilds messages
396
+ const completedDelegations = ralRegistry.getConversationCompletedDelegations(
397
+ delegationTarget.agent.pubkey,
398
+ delegationTarget.conversationId,
399
+ currentRal.ralNumber
400
+ );
401
+ const pendingDelegations = ralRegistry.getConversationPendingDelegations(
402
+ delegationTarget.agent.pubkey,
403
+ delegationTarget.conversationId,
404
+ currentRal.ralNumber
405
+ );
406
+
407
+ // Update markers in the parent conversation (or create if not found)
408
+ const parentStore = ConversationStore.get(delegationTarget.conversationId);
409
+ if (parentStore && completedDelegations.length > 0) {
410
+ for (const completion of completedDelegations) {
411
+ // Try to update existing pending marker first
412
+ const updated = parentStore.updateDelegationMarker(
413
+ completion.delegationConversationId,
414
+ {
415
+ status: completion.status,
416
+ completedAt: completion.completedAt,
417
+ abortReason: completion.status === "aborted" ? completion.abortReason : undefined,
418
+ }
419
+ );
420
+
421
+ // If no pending marker found, create a new one (backward compatibility)
422
+ if (!updated) {
423
+ const marker: DelegationMarker = {
424
+ delegationConversationId: completion.delegationConversationId,
425
+ recipientPubkey: completion.recipientPubkey,
426
+ parentConversationId: delegationTarget.conversationId,
427
+ completedAt: completion.completedAt,
428
+ status: completion.status,
429
+ abortReason: completion.status === "aborted" ? completion.abortReason : undefined,
430
+ };
431
+ parentStore.addDelegationMarker(
432
+ marker,
433
+ delegationTarget.agent.pubkey,
434
+ currentRal.ralNumber
435
+ );
436
+ }
437
+ }
438
+ await parentStore.save();
439
+
440
+ // Clear completed delegations after inserting markers
441
+ ralRegistry.clearCompletedDelegations(
442
+ delegationTarget.agent.pubkey,
443
+ delegationTarget.conversationId,
444
+ currentRal.ralNumber
445
+ );
446
+ }
447
+
448
+ span.addEvent("dispatch.delegation_markers_inserted_for_active_stream", {
449
+ "ral.number": currentRal.ralNumber,
450
+ "delegation.completed_count": completedDelegations.length,
451
+ "delegation.pending_count": pendingDelegations.length,
452
+ });
453
+ span.setStatus({ code: SpanStatusCode.OK });
454
+ return;
455
+ }
456
+
457
+ const resumableRal = ralRegistry.findResumableRAL(
458
+ delegationTarget.agent.pubkey,
459
+ delegationTarget.conversationId
460
+ );
461
+
462
+ let triggeringEventForContext = event;
463
+
464
+ if (resumableRal?.originalTriggeringEventId) {
465
+ const originalEvent = ConversationStore.getCachedEvent(resumableRal.originalTriggeringEventId);
466
+ if (originalEvent) {
467
+ triggeringEventForContext = originalEvent;
468
+ getSafeActiveSpan()?.addEvent("reply.restored_original_trigger_for_delegation", {
469
+ "original.event_id": resumableRal.originalTriggeringEventId,
470
+ "completion.event_id": event.id || "",
471
+ });
472
+ span.addEvent("dispatch.delegation_restored_trigger", {
473
+ "original.event_id": resumableRal.originalTriggeringEventId,
474
+ });
475
+ }
476
+ }
477
+
478
+ getSafeActiveSpan()?.addEvent("reply.delegation_routing_to_original", {
479
+ "delegation.agent_slug": delegationTarget.agent.slug,
480
+ "delegation.original_conversation_id": shortenConversationId(delegationTarget.conversationId),
481
+ });
482
+
483
+ const pendingDelegations = ralRegistry.getConversationPendingDelegations(
484
+ delegationTarget.agent.pubkey,
485
+ delegationTarget.conversationId,
486
+ resumableRal?.ralNumber
487
+ );
488
+ const hasPendingDelegations = pendingDelegations.length > 0;
489
+
490
+ // DIAGNOSTIC: Trace the moment hasPendingDelegations is captured for context
491
+ span.addEvent("dispatch.hasPendingDelegations_captured", {
492
+ "hasPendingDelegations": hasPendingDelegations,
493
+ "pendingDelegations.count": pendingDelegations.length,
494
+ "pendingDelegations.ids": pendingDelegations.map(d => d.delegationConversationId).join(","),
495
+ "ral.number": resumableRal?.ralNumber ?? -1,
496
+ });
497
+
498
+ span.setAttributes({
499
+ "delegation.pending_count": pendingDelegations.length,
500
+ });
501
+
502
+ const executionContext = await createExecutionContext({
503
+ agent: delegationTarget.agent,
504
+ conversationId: delegationTarget.conversationId,
505
+ projectBasePath: projectCtx.agentRegistry.getBasePath(),
506
+ triggeringEvent: triggeringEventForContext,
507
+ isDelegationCompletion: true,
508
+ hasPendingDelegations,
509
+ mcpManager: projectCtx.mcpManager,
510
+ });
511
+
512
+ metadataDebounceManager.onAgentStart(delegationTarget.conversationId);
513
+
514
+ // Execute within span context so agent.execute becomes a child span
515
+ await otelContext.with(trace.setSpan(otelContext.active(), span), async () => {
516
+ await agentExecutor.execute(executionContext);
517
+ });
518
+
519
+ metadataDebounceManager.schedulePublish(
520
+ delegationTarget.conversationId,
521
+ false,
522
+ async () => {
523
+ const summarizer = new ConversationSummarizer(projectCtx);
524
+ const originalConversation = ConversationStore.get(delegationTarget.conversationId);
525
+ if (originalConversation) {
526
+ await summarizer.summarizeAndPublish(originalConversation);
527
+ }
528
+ }
529
+ );
530
+
531
+ span.setStatus({ code: SpanStatusCode.OK });
532
+ } catch (error) {
533
+ span.recordException(error as Error);
534
+ span.setStatus({
535
+ code: SpanStatusCode.ERROR,
536
+ message: (error as Error).message,
537
+ });
538
+ throw error;
539
+ } finally {
540
+ span.end();
541
+ }
542
+ }
543
+
544
+ private async waitForDelegationDebounce(
545
+ key: string,
546
+ span: ReturnType<typeof tracer.startSpan>
547
+ ): Promise<number> {
548
+ const nextSequence = (this.delegationDebounceSequence.get(key) ?? 0) + 1;
549
+ this.delegationDebounceSequence.set(key, nextSequence);
550
+
551
+ let state = this.delegationDebounceState.get(key);
552
+ if (!state) {
553
+ let resolveFn: (() => void) | undefined;
554
+ const promise = new Promise<void>((resolve) => {
555
+ resolveFn = resolve;
556
+ });
557
+ const timeout = setTimeout(() => {
558
+ this.delegationDebounceState.delete(key);
559
+ resolveFn?.();
560
+ }, DELEGATION_COMPLETION_DEBOUNCE_MS);
561
+ state = {
562
+ timeout,
563
+ promise,
564
+ resolve: resolveFn ?? (() => {}),
565
+ };
566
+ this.delegationDebounceState.set(key, state);
567
+ } else {
568
+ const activeState = state;
569
+ clearTimeout(activeState.timeout);
570
+ activeState.timeout = setTimeout(() => {
571
+ this.delegationDebounceState.delete(key);
572
+ activeState.resolve();
573
+ }, DELEGATION_COMPLETION_DEBOUNCE_MS);
574
+ }
575
+
576
+ span.addEvent("dispatch.delegation_debounce_scheduled", {
577
+ "debounce.ms": DELEGATION_COMPLETION_DEBOUNCE_MS,
578
+ "debounce.sequence": nextSequence,
579
+ });
580
+
581
+ await state.promise;
582
+ return nextSequence;
583
+ }
584
+
585
+ private async dispatchToAgents(params: {
586
+ targetAgents: AgentInstance[];
587
+ event: NDKEvent;
588
+ conversationId: string;
589
+ projectCtx: ProjectContext;
590
+ agentExecutor: AgentExecutor;
591
+ parentSpan: ReturnType<typeof tracer.startSpan>;
592
+ }): Promise<void> {
593
+ const {
594
+ targetAgents,
595
+ event,
596
+ conversationId,
597
+ projectCtx,
598
+ agentExecutor,
599
+ parentSpan,
600
+ } = params;
601
+ const ralRegistry = RALRegistry.getInstance();
602
+ const dispatchContext = trace.setSpan(getSafeContext(), parentSpan);
603
+
604
+ // DIAGNOSTIC: Track concurrent execution metrics for bottleneck analysis
605
+ const dispatchStartTime = Date.now();
606
+ const currentActiveOps = llmOpsRegistry.getActiveOperationsCount();
607
+ parentSpan.addEvent("dispatch.concurrent_execution_starting", {
608
+ "concurrent.target_agents_count": targetAgents.length,
609
+ "concurrent.existing_active_ops": currentActiveOps,
610
+ "concurrent.total_after_dispatch": currentActiveOps + targetAgents.length,
611
+ "concurrent.dispatch_start_time": dispatchStartTime,
612
+ "concurrent.event_loop_lag_ms": this.measureEventLoopLag(),
613
+ });
614
+
615
+ const executionPromises = targetAgents.map(async (targetAgent) => {
616
+ const agentSpan = tracer.startSpan(
617
+ "tenex.dispatch.agent",
618
+ {
619
+ attributes: {
620
+ "agent.slug": targetAgent.slug,
621
+ "agent.pubkey": targetAgent.pubkey,
622
+ "conversation.id": shortenConversationId(conversationId),
623
+ },
624
+ },
625
+ dispatchContext
626
+ );
627
+
628
+ try {
629
+ // Check if this agent is in cooldown for this conversation
630
+ // Non-null assertion: conversationId and dTag are guaranteed to be defined from function params
631
+ const isInCooldown = await this.checkAndBlockIfCooldown(
632
+ projectCtx.project.dTag!,
633
+ conversationId!,
634
+ targetAgent.pubkey,
635
+ targetAgent.slug,
636
+ agentSpan,
637
+ "routing"
638
+ );
639
+
640
+ if (isInCooldown) {
641
+ return;
642
+ }
643
+
644
+ const activeRal = ralRegistry.getState(targetAgent.pubkey, conversationId);
645
+
646
+ agentSpan.setAttributes({
647
+ "ral.is_active": !!activeRal,
648
+ "ral.is_streaming": activeRal?.isStreaming ?? false,
649
+ "ral.number": activeRal?.ralNumber ?? 0,
650
+ });
651
+
652
+ const shouldSkipExecution = this.handleDeliveryInjection({
653
+ activeRal,
654
+ agent: targetAgent,
655
+ conversationId,
656
+ message: event.content,
657
+ senderPubkey: event.pubkey,
658
+ eventId: event.id,
659
+ agentSpan,
660
+ });
661
+
662
+ if (shouldSkipExecution) {
663
+ // Message was queued for an active streaming execution.
664
+ // Don't spawn a new execution - the active one will pick it up.
665
+ agentSpan.addEvent("dispatch.execution_skipped_injection_queued");
666
+ agentSpan.setStatus({ code: SpanStatusCode.OK });
667
+ return;
668
+ }
669
+
670
+ let triggeringEventForContext = event;
671
+ const resumableRal = ralRegistry.findResumableRAL(targetAgent.pubkey, conversationId);
672
+
673
+ if (resumableRal?.originalTriggeringEventId) {
674
+ const originalEvent = ConversationStore.getCachedEvent(resumableRal.originalTriggeringEventId);
675
+ if (originalEvent) {
676
+ triggeringEventForContext = originalEvent;
677
+ getSafeActiveSpan()?.addEvent("reply.restored_original_trigger", {
678
+ "agent.slug": targetAgent.slug,
679
+ "original.event_id": resumableRal.originalTriggeringEventId,
680
+ "resumption.event_id": event.id || "",
681
+ });
682
+ agentSpan.addEvent("dispatch.restored_original_trigger", {
683
+ "original.event_id": resumableRal.originalTriggeringEventId,
684
+ });
685
+ }
686
+ }
687
+
688
+ const executionContext = await createExecutionContext({
689
+ agent: targetAgent,
690
+ conversationId,
691
+ projectBasePath: projectCtx.agentRegistry.getBasePath(),
692
+ triggeringEvent: triggeringEventForContext,
693
+ mcpManager: projectCtx.mcpManager,
694
+ });
695
+
696
+ // Execute within agentSpan context so agent.execute becomes a child span
697
+ await otelContext.with(trace.setSpan(otelContext.active(), agentSpan), async () => {
698
+ await agentExecutor.execute(executionContext);
699
+ });
700
+
701
+ agentSpan.setStatus({ code: SpanStatusCode.OK });
702
+ } catch (error) {
703
+ agentSpan.recordException(error as Error);
704
+ agentSpan.setStatus({
705
+ code: SpanStatusCode.ERROR,
706
+ message: (error as Error).message,
707
+ });
708
+ throw error;
709
+ } finally {
710
+ agentSpan.end();
711
+ }
712
+ });
713
+
714
+ await Promise.all(executionPromises);
715
+
716
+ // DIAGNOSTIC: Track concurrent execution completion metrics
717
+ const dispatchEndTime = Date.now();
718
+ const dispatchDuration = dispatchEndTime - dispatchStartTime;
719
+ const finalActiveOps = llmOpsRegistry.getActiveOperationsCount();
720
+ parentSpan.addEvent("dispatch.concurrent_execution_completed", {
721
+ "concurrent.dispatch_duration_ms": dispatchDuration,
722
+ "concurrent.agents_executed": targetAgents.length,
723
+ "concurrent.final_active_ops": finalActiveOps,
724
+ "concurrent.event_loop_lag_ms": this.measureEventLoopLag(),
725
+ "concurrent.avg_per_agent_ms": Math.round(dispatchDuration / targetAgents.length),
726
+ });
727
+ }
728
+
729
+ /**
730
+ * Measure event loop lag to detect blocking operations.
731
+ * Returns the lag in milliseconds - high values indicate event loop blocking.
732
+ */
733
+ private measureEventLoopLag(): number {
734
+ const start = process.hrtime.bigint();
735
+ // This is synchronous - it just measures how long since we scheduled vs now
736
+ // In real monitoring, you'd use setImmediate to measure actual lag
737
+ // For diagnostic purposes, this gives us a baseline timestamp
738
+ return Number((process.hrtime.bigint() - start) / 1000000n);
739
+ }
740
+
741
+ /**
742
+ * Check if an agent is in cooldown for a conversation and block routing if so.
743
+ * Returns true if the agent is in cooldown and routing should be blocked.
744
+ * Returns false if the agent is not in cooldown and routing should proceed.
745
+ *
746
+ * This helper consolidates the cooldown check logic used in multiple dispatch paths.
747
+ */
748
+ private async checkAndBlockIfCooldown(
749
+ projectId: string,
750
+ conversationId: string,
751
+ agentPubkey: string,
752
+ agentSlug: string,
753
+ span: ReturnType<typeof tracer.startSpan>,
754
+ eventType: "delegation_completion" | "routing"
755
+ ): Promise<boolean> {
756
+ const { CooldownRegistry } = await import("@/services/CooldownRegistry");
757
+ const cooldownRegistry = CooldownRegistry.getInstance();
758
+
759
+ if (cooldownRegistry.isInCooldown(projectId, conversationId, agentPubkey)) {
760
+ // Agent is in cooldown - skip routing
761
+ logger.info(`[dispatch] ${eventType === "delegation_completion" ? "Delegation completion routing" : "Routing"} blocked due to cooldown`, {
762
+ projectId: projectId.substring(0, 12),
763
+ conversationId: conversationId.substring(0, 12),
764
+ agentSlug,
765
+ agentPubkey: agentPubkey.substring(0, 12),
766
+ });
767
+
768
+ span.addEvent(`dispatch.${eventType}_blocked_cooldown`, {
769
+ "cooldown.project_id": projectId.substring(0, 12),
770
+ "cooldown.conversation_id": shortenConversationId(conversationId),
771
+ "cooldown.agent_pubkey": agentPubkey.substring(0, 12),
772
+ "cooldown.agent_slug": agentSlug,
773
+ });
774
+ span.setStatus({ code: SpanStatusCode.OK });
775
+ return true;
776
+ }
777
+
778
+ return false;
779
+ }
780
+
781
+ /**
782
+ * Handle injection of a message into an active RAL.
783
+ * Returns true if execution should be SKIPPED (message queued for active streaming execution).
784
+ * Returns false if a new execution should be spawned.
785
+ */
786
+ private handleDeliveryInjection(params: {
787
+ activeRal: RALRegistryEntry | undefined;
788
+ agent: AgentInstance;
789
+ conversationId: string;
790
+ message: string;
791
+ senderPubkey: string;
792
+ eventId?: string;
793
+ agentSpan: ReturnType<typeof tracer.startSpan>;
794
+ }): boolean {
795
+ const {
796
+ activeRal,
797
+ agent,
798
+ conversationId,
799
+ message,
800
+ senderPubkey,
801
+ eventId,
802
+ agentSpan,
803
+ } = params;
804
+
805
+ if (!activeRal) {
806
+ return false; // No active RAL, spawn new execution
807
+ }
808
+
809
+ const ralRegistry = RALRegistry.getInstance();
810
+ const messageLength = message.length;
811
+
812
+ ralRegistry.queueUserMessage(
813
+ agent.pubkey,
814
+ conversationId,
815
+ activeRal.ralNumber,
816
+ message,
817
+ { senderPubkey, eventId }
818
+ );
819
+
820
+ if (activeRal.isStreaming) {
821
+ // Check if this is a Claude Code agent
822
+ const llmConfig = config.getLLMConfig(agent.llmConfig);
823
+ const isClaudeCodeProvider = llmConfig.provider === PROVIDER_IDS.CLAUDE_CODE;
824
+
825
+ if (isClaudeCodeProvider) {
826
+ // Try to use message injection if available
827
+ const injector = llmOpsRegistry.getMessageInjector(agent.pubkey, conversationId);
828
+
829
+ if (injector) {
830
+ // Message injector available - use it instead of abort-restart
831
+ injector.inject(message, (delivered) => {
832
+ if (delivered) {
833
+ // Clear queued injections since MessageInjector delivered the message directly.
834
+ // This prevents hasOutstandingWork() from incorrectly reporting pending work.
835
+ // See: naddr1qvzqqqr4gupzqkmm302xww6uyne99rnhl5kjj53wthjypm2qaem9uz9fdf3hzcf0qyghwumn8ghj7ar9dejhstnrdpshgtcq9p382emxd9uz6en0d3kx7am4wqkkjmn2v43hg6t0dckhzat9w4jj6cmvv4shy6twvullqw7x
836
+ ralRegistry.clearQueuedInjections(agent.pubkey, conversationId);
837
+
838
+ logger.info("[dispatch] Message injected via Claude Code MessageInjector", {
839
+ agent: agent.slug,
840
+ ralNumber: activeRal.ralNumber,
841
+ messageLength,
842
+ });
843
+ } else {
844
+ // Injection failed - the message is already queued in RALRegistry,
845
+ // so the next execution will pick it up
846
+ logger.warn("[dispatch] Message injection delivery failed (message already queued)", {
847
+ agent: agent.slug,
848
+ ralNumber: activeRal.ralNumber,
849
+ messageLength,
850
+ });
851
+ }
852
+ });
853
+
854
+ getSafeActiveSpan()?.addEvent("reply.message_injected_via_injector", {
855
+ "agent.slug": agent.slug,
856
+ "ral.number": activeRal.ralNumber,
857
+ "message.length": messageLength,
858
+ });
859
+ agentSpan.addEvent("dispatch.injection_via_message_injector", {
860
+ "message.length": messageLength,
861
+ });
862
+
863
+ // Skip spawning new execution - the active execution will pick up the injected message
864
+ return true;
865
+ }
866
+
867
+ // No injector available - fall back to abort-restart
868
+ const aborted = llmOpsRegistry.stopByAgentAndConversation(
869
+ agent.pubkey,
870
+ conversationId,
871
+ INJECTION_ABORT_REASON
872
+ );
873
+
874
+ if (aborted) {
875
+ getSafeActiveSpan()?.addEvent("reply.aborted_for_injection", {
876
+ "agent.slug": agent.slug,
877
+ "ral.number": activeRal.ralNumber,
878
+ "message.length": messageLength,
879
+ });
880
+ logger.info("[reply] Aborted Claude Code execution for injection (no injector)", {
881
+ agent: agent.slug,
882
+ ralNumber: activeRal.ralNumber,
883
+ injectionLength: messageLength,
884
+ });
885
+ agentSpan.addEvent("dispatch.injection_stream_abort_fallback", {
886
+ "message.length": messageLength,
887
+ });
888
+ } else {
889
+ getSafeActiveSpan()?.addEvent("reply.message_queued_during_streaming", {
890
+ "agent.slug": agent.slug,
891
+ "ral.number": activeRal.ralNumber,
892
+ "message.length": messageLength,
893
+ });
894
+ agentSpan.addEvent("dispatch.injection_stream_queue_only", {
895
+ "message.length": messageLength,
896
+ });
897
+ }
898
+ // Spawn new execution (aborted or will pick up on restart)
899
+ return false;
900
+ } else {
901
+ // Non-Claude-Code provider: just queue the message, don't abort.
902
+ // The active execution will pick it up on its next prepareStep.
903
+ // IMPORTANT: Skip spawning a new execution to avoid duplicate completions.
904
+ // See report: "injection-race-condition-hybrid-fix" for known limitations.
905
+ getSafeActiveSpan()?.addEvent("reply.message_injected_no_abort", {
906
+ "agent.slug": agent.slug,
907
+ "ral.number": activeRal.ralNumber,
908
+ "message.length": messageLength,
909
+ "provider": llmConfig.provider,
910
+ });
911
+ logger.info("[reply] Queued message for non-Claude-Code provider (no abort, skipping execution)", {
912
+ agent: agent.slug,
913
+ ralNumber: activeRal.ralNumber,
914
+ injectionLength: messageLength,
915
+ provider: llmConfig.provider,
916
+ });
917
+ agentSpan.addEvent("dispatch.injection_stream_no_abort_skip_execution", {
918
+ "message.length": messageLength,
919
+ "provider": llmConfig.provider,
920
+ });
921
+ // Non-Claude: skip execution, trust active execution to pick up injection
922
+ return true;
923
+ }
924
+ }
925
+
926
+ // Not streaming (waiting for delegations) - need new execution to wake up
927
+ getSafeActiveSpan()?.addEvent("reply.message_queued_for_resumption", {
928
+ "agent.slug": agent.slug,
929
+ "ral.number": activeRal.ralNumber,
930
+ "message.length": messageLength,
931
+ });
932
+ agentSpan.addEvent("dispatch.injection_resumption", {
933
+ "message.length": messageLength,
934
+ });
935
+ return false; // Spawn new execution to wake up the waiting RAL
936
+ }
937
+ }