@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,199 @@
1
+ /**
2
+ * CooldownRegistry - Tracks aborted conversation:agent tuples to prevent immediate re-routing
3
+ *
4
+ * After an agent is aborted in a conversation, this registry enforces a 15-second cooldown
5
+ * period to prevent the dispatch service from routing new messages to that agent in that
6
+ * conversation. This prevents race conditions where an abort signal crosses with new messages.
7
+ *
8
+ * Key design:
9
+ * - Tuple-based: Cooldown is specific to (conversationId, agentPubkey) pair
10
+ * - Time-based expiry: Tuples expire after COOLDOWN_DURATION_MS
11
+ * - Automatic cleanup: Expired entries are periodically removed
12
+ * - OTL traces: Emits trace events when routing is blocked
13
+ */
14
+
15
+ import { logger } from "@/utils/logger";
16
+ import { shortenConversationId } from "@/utils/conversation-id";
17
+ import { trace } from "@opentelemetry/api";
18
+
19
+ /** Cooldown duration in milliseconds (15 seconds) */
20
+ const COOLDOWN_DURATION_MS = 15000;
21
+
22
+ /** Cleanup interval for expired entries (30 seconds) */
23
+ const CLEANUP_INTERVAL_MS = 30000;
24
+
25
+ interface CooldownEntry {
26
+ projectId: string;
27
+ conversationId: string;
28
+ agentPubkey: string;
29
+ abortedAt: number;
30
+ reason?: string;
31
+ }
32
+
33
+ export class CooldownRegistry {
34
+ private static instance: CooldownRegistry;
35
+
36
+ /** Map from "projectId:conversationId:agentPubkey" to cooldown entry */
37
+ private cooldowns: Map<string, CooldownEntry> = new Map();
38
+
39
+ /** Cleanup interval handle */
40
+ private cleanupInterval: ReturnType<typeof setInterval> | null = null;
41
+
42
+ private constructor() {
43
+ this.startCleanupInterval();
44
+ }
45
+
46
+ static getInstance(): CooldownRegistry {
47
+ if (!CooldownRegistry.instance) {
48
+ CooldownRegistry.instance = new CooldownRegistry();
49
+ }
50
+ return CooldownRegistry.instance;
51
+ }
52
+
53
+ /**
54
+ * Start periodic cleanup of expired cooldown entries
55
+ */
56
+ private startCleanupInterval(): void {
57
+ this.cleanupInterval = setInterval(() => {
58
+ this.cleanupExpiredEntries();
59
+ }, CLEANUP_INTERVAL_MS);
60
+ this.cleanupInterval.unref();
61
+ }
62
+
63
+ /**
64
+ * Remove expired cooldown entries
65
+ */
66
+ private cleanupExpiredEntries(): void {
67
+ const now = Date.now();
68
+ let cleanedCount = 0;
69
+
70
+ for (const [key, entry] of this.cooldowns.entries()) {
71
+ if (now - entry.abortedAt > COOLDOWN_DURATION_MS) {
72
+ this.cooldowns.delete(key);
73
+ cleanedCount++;
74
+ }
75
+ }
76
+
77
+ if (cleanedCount > 0) {
78
+ trace.getActiveSpan()?.addEvent("cooldown.cleanup_expired", {
79
+ "cooldown.cleaned_count": cleanedCount,
80
+ "cooldown.remaining_count": this.cooldowns.size,
81
+ });
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Make a unique key for a project:conversation:agent tuple
87
+ */
88
+ private makeKey(projectId: string, conversationId: string, agentPubkey: string): string {
89
+ return `${projectId}:${conversationId}:${agentPubkey}`;
90
+ }
91
+
92
+ /**
93
+ * Add a project:conversation:agent tuple to the cooldown registry
94
+ *
95
+ * @param projectId - The project ID
96
+ * @param conversationId - The conversation ID
97
+ * @param agentPubkey - The agent's pubkey
98
+ * @param reason - Optional reason for the abort (for debugging)
99
+ */
100
+ add(projectId: string, conversationId: string, agentPubkey: string, reason?: string): void {
101
+ const key = this.makeKey(projectId, conversationId, agentPubkey);
102
+ const entry: CooldownEntry = {
103
+ projectId,
104
+ conversationId,
105
+ agentPubkey,
106
+ abortedAt: Date.now(),
107
+ reason,
108
+ };
109
+
110
+ this.cooldowns.set(key, entry);
111
+
112
+ trace.getActiveSpan()?.addEvent("cooldown.added", {
113
+ "cooldown.project_id": projectId.substring(0, 12),
114
+ "cooldown.conversation_id": shortenConversationId(conversationId),
115
+ "cooldown.agent_pubkey": agentPubkey.substring(0, 12),
116
+ "cooldown.reason": reason ?? "unknown",
117
+ "cooldown.total_count": this.cooldowns.size,
118
+ });
119
+
120
+ logger.debug("[CooldownRegistry] Added cooldown entry", {
121
+ projectId: projectId.substring(0, 12),
122
+ conversationId: shortenConversationId(conversationId),
123
+ agentPubkey: agentPubkey.substring(0, 12),
124
+ reason,
125
+ });
126
+ }
127
+
128
+ /**
129
+ * Check if a project:conversation:agent tuple is in cooldown
130
+ *
131
+ * @param projectId - The project ID
132
+ * @param conversationId - The conversation ID
133
+ * @param agentPubkey - The agent's pubkey
134
+ * @returns true if the tuple is currently in cooldown, false otherwise
135
+ */
136
+ isInCooldown(projectId: string, conversationId: string, agentPubkey: string): boolean {
137
+ const key = this.makeKey(projectId, conversationId, agentPubkey);
138
+ const entry = this.cooldowns.get(key);
139
+
140
+ if (!entry) {
141
+ return false;
142
+ }
143
+
144
+ const now = Date.now();
145
+ const elapsed = now - entry.abortedAt;
146
+
147
+ // Check if cooldown has expired
148
+ if (elapsed > COOLDOWN_DURATION_MS) {
149
+ // Expired - remove and return false
150
+ this.cooldowns.delete(key);
151
+ return false;
152
+ }
153
+
154
+ // Still in cooldown
155
+ trace.getActiveSpan()?.addEvent("cooldown.check_blocked", {
156
+ "cooldown.project_id": projectId.substring(0, 12),
157
+ "cooldown.conversation_id": shortenConversationId(conversationId),
158
+ "cooldown.agent_pubkey": agentPubkey.substring(0, 12),
159
+ "cooldown.elapsed_ms": elapsed,
160
+ "cooldown.remaining_ms": COOLDOWN_DURATION_MS - elapsed,
161
+ "cooldown.reason": entry.reason ?? "unknown",
162
+ });
163
+
164
+ return true;
165
+ }
166
+
167
+ /**
168
+ * Get all active cooldown entries (for debugging)
169
+ */
170
+ getActiveCooldowns(): CooldownEntry[] {
171
+ const now = Date.now();
172
+ const active: CooldownEntry[] = [];
173
+
174
+ for (const entry of this.cooldowns.values()) {
175
+ if (now - entry.abortedAt <= COOLDOWN_DURATION_MS) {
176
+ active.push(entry);
177
+ }
178
+ }
179
+
180
+ return active;
181
+ }
182
+
183
+ /**
184
+ * Clear all cooldown entries (for testing)
185
+ */
186
+ clearAll(): void {
187
+ this.cooldowns.clear();
188
+ }
189
+
190
+ /**
191
+ * Stop the cleanup interval (for testing/shutdown)
192
+ */
193
+ destroy(): void {
194
+ if (this.cleanupInterval) {
195
+ clearInterval(this.cleanupInterval);
196
+ this.cleanupInterval = null;
197
+ }
198
+ }
199
+ }
@@ -0,0 +1,424 @@
1
+ import type { ExecutionContext } from "@/agents/execution/types";
2
+ import type { MessageInjector } from "@/llm/types";
3
+ import { logger } from "@/utils/logger";
4
+ import { trace } from "@opentelemetry/api";
5
+ import { EventEmitter } from "tseep";
6
+
7
+ /**
8
+ * Constant used as the abort reason when aborting for mid-execution injection.
9
+ * Allows AgentExecutor to distinguish injection aborts from user-initiated stops.
10
+ */
11
+ export const INJECTION_ABORT_REASON = "INJECTION_ABORT";
12
+
13
+ /** RAL state representing the current phase of agent execution */
14
+ export type RALState = "IDLE" | "REASONING" | "ACTING" | "STREAMING" | "ERROR";
15
+
16
+ // Store essential operation metadata
17
+ export interface LLMOperation {
18
+ id: string;
19
+ abortController: AbortController;
20
+ eventEmitter: EventEmitter; // For message injection into running executions
21
+ eventId: string; // The event being processed
22
+ agentPubkey: string; // Agent doing the work
23
+ conversationId: string; // Root event ID for conversation
24
+ registeredAt: number; // Timestamp
25
+ /** MessageInjector for Claude Code streams (set via onStreamStart callback) */
26
+ messageInjector?: MessageInjector;
27
+ /** Current RAL state for this operation */
28
+ ralState?: RALState;
29
+ }
30
+
31
+ export class LLMOperationsRegistry {
32
+ private static instance: LLMOperationsRegistry;
33
+ private operations = new Map<string, LLMOperation>();
34
+ private byEvent = new Map<string, Set<string>>();
35
+ private operationsByContext = new Map<string, string>(); // contextKey -> operationId
36
+ private changeListeners = new Set<() => void>();
37
+
38
+ // DIAGNOSTIC: Concurrent streaming metrics
39
+ private peakConcurrentOperations = 0;
40
+ private totalOperationsRegistered = 0;
41
+ private concurrencyHistogram = new Map<number, number>(); // concurrency level -> count
42
+
43
+ static getInstance(): LLMOperationsRegistry {
44
+ if (!LLMOperationsRegistry.instance) {
45
+ LLMOperationsRegistry.instance = new LLMOperationsRegistry();
46
+ }
47
+ return LLMOperationsRegistry.instance;
48
+ }
49
+
50
+ registerOperation(context: ExecutionContext): AbortSignal {
51
+ const operationId = crypto.randomUUID();
52
+ const conversation = context.getConversation();
53
+ const rootEventId = conversation?.getRootEventId() || context.triggeringEvent.id;
54
+
55
+ // Create operation with metadata
56
+ const operation: LLMOperation = {
57
+ id: operationId,
58
+ abortController: new AbortController(),
59
+ eventEmitter: new EventEmitter(),
60
+ eventId: context.triggeringEvent.id,
61
+ agentPubkey: context.agent.pubkey,
62
+ conversationId: rootEventId,
63
+ registeredAt: Date.now(),
64
+ };
65
+
66
+ // Store the operation
67
+ this.operations.set(operationId, operation);
68
+
69
+ // Index by both root event and triggering event
70
+ this.indexOperation(operationId, rootEventId);
71
+ if (context.triggeringEvent.id !== rootEventId) {
72
+ this.indexOperation(operationId, context.triggeringEvent.id);
73
+ }
74
+
75
+ // Also index by context for easy lookup on completion
76
+ this.operationsByContext.set(this.getContextKey(context), operationId);
77
+
78
+ // Auto-cleanup on abort (for cancellation cases)
79
+ operation.abortController.signal.addEventListener("abort", () => {
80
+ this.cleanupOperation(operationId);
81
+ });
82
+
83
+ logger.debug("[LLMOpsRegistry] Registered operation", {
84
+ operationId: operationId.substring(0, 8),
85
+ rootEvent: rootEventId.substring(0, 8),
86
+ triggeringEvent: context.triggeringEvent.id.substring(0, 8),
87
+ agent: context.agent.name,
88
+ agentPubkey: context.agent.pubkey.substring(0, 8),
89
+ });
90
+
91
+ // DIAGNOSTIC: Track concurrent streaming metrics
92
+ this.totalOperationsRegistered++;
93
+ const currentConcurrency = this.operations.size;
94
+ if (currentConcurrency > this.peakConcurrentOperations) {
95
+ this.peakConcurrentOperations = currentConcurrency;
96
+ }
97
+ this.concurrencyHistogram.set(
98
+ currentConcurrency,
99
+ (this.concurrencyHistogram.get(currentConcurrency) || 0) + 1
100
+ );
101
+
102
+ // Emit OTL event for concurrent operation tracking
103
+ const activeSpan = trace.getActiveSpan();
104
+ activeSpan?.addEvent("llm_ops.operation_registered", {
105
+ "concurrent.current_count": currentConcurrency,
106
+ "concurrent.peak_count": this.peakConcurrentOperations,
107
+ "concurrent.total_registered": this.totalOperationsRegistered,
108
+ "operation.id": operationId.substring(0, 8),
109
+ "operation.agent_name": context.agent.name,
110
+ "operation.agent_pubkey": context.agent.pubkey.substring(0, 8),
111
+ "process.memory_heap_used_mb": Math.round(process.memoryUsage().heapUsed / 1024 / 1024),
112
+ "process.memory_rss_mb": Math.round(process.memoryUsage().rss / 1024 / 1024),
113
+ });
114
+
115
+ // Notify listeners of new operation
116
+ this.notifyChange();
117
+
118
+ return operation.abortController.signal;
119
+ }
120
+
121
+ completeOperation(context: ExecutionContext): void {
122
+ const contextKey = this.getContextKey(context);
123
+ const operationId = this.operationsByContext.get(contextKey);
124
+
125
+ if (!operationId) {
126
+ // Operation was never registered or already completed
127
+ return;
128
+ }
129
+
130
+ // Remove context mapping
131
+ this.operationsByContext.delete(contextKey);
132
+
133
+ // Do the actual cleanup
134
+ this.cleanupOperation(operationId);
135
+ }
136
+
137
+ private cleanupOperation(operationId: string): void {
138
+ const operation = this.operations.get(operationId);
139
+ if (!operation) {
140
+ // Already cleaned up
141
+ return;
142
+ }
143
+
144
+ // Remove from main map
145
+ this.operations.delete(operationId);
146
+
147
+ // Remove from all indices
148
+ this.unindexOperation(operationId, operation.conversationId);
149
+ if (operation.eventId !== operation.conversationId) {
150
+ this.unindexOperation(operationId, operation.eventId);
151
+ }
152
+
153
+ const operationDuration = Date.now() - operation.registeredAt;
154
+ logger.debug("[LLMOpsRegistry] Completed operation", {
155
+ operationId: operationId.substring(0, 8),
156
+ eventId: operation.eventId.substring(0, 8),
157
+ conversationId: operation.conversationId.substring(0, 8),
158
+ duration: operationDuration,
159
+ });
160
+
161
+ // DIAGNOSTIC: Track operation completion with concurrency context
162
+ const remainingConcurrency = this.operations.size;
163
+ const activeSpan = trace.getActiveSpan();
164
+ activeSpan?.addEvent("llm_ops.operation_completed", {
165
+ "concurrent.remaining_count": remainingConcurrency,
166
+ "concurrent.peak_count": this.peakConcurrentOperations,
167
+ "operation.id": operationId.substring(0, 8),
168
+ "operation.duration_ms": operationDuration,
169
+ "operation.agent_pubkey": operation.agentPubkey.substring(0, 8),
170
+ "process.memory_heap_used_mb": Math.round(process.memoryUsage().heapUsed / 1024 / 1024),
171
+ });
172
+
173
+ // Notify listeners of change
174
+ this.notifyChange();
175
+ }
176
+
177
+ private getContextKey(context: ExecutionContext): string {
178
+ // Create a unique key from the context that identifies this specific operation
179
+ return `${context.triggeringEvent.id}:${context.agent.pubkey}`;
180
+ }
181
+
182
+ stopByEventId(eventId: string): number {
183
+ const operationIds = this.byEvent.get(eventId) || new Set();
184
+ let stopped = 0;
185
+
186
+ for (const opId of operationIds) {
187
+ const operation = this.operations.get(opId);
188
+ if (operation && !operation.abortController.signal.aborted) {
189
+ operation.abortController.abort();
190
+ stopped++;
191
+ }
192
+ }
193
+
194
+ if (stopped > 0) {
195
+ logger.info("[LLMOpsRegistry] Stopped operations", {
196
+ eventId: eventId.substring(0, 8),
197
+ count: stopped,
198
+ });
199
+ }
200
+
201
+ return stopped;
202
+ }
203
+
204
+ /**
205
+ * Stop operations for a specific agent in a conversation.
206
+ * Used to abort streaming execution when mid-stream injection arrives.
207
+ *
208
+ * @param agentPubkey - The agent's public key
209
+ * @param conversationId - The conversation (root event) ID
210
+ * @param reason - Reason for the abort (passed to AbortController)
211
+ * @returns Whether an operation was aborted
212
+ */
213
+ stopByAgentAndConversation(agentPubkey: string, conversationId: string, reason?: string): boolean {
214
+ for (const operation of this.operations.values()) {
215
+ if (
216
+ operation.agentPubkey === agentPubkey &&
217
+ operation.conversationId === conversationId &&
218
+ !operation.abortController.signal.aborted
219
+ ) {
220
+ operation.abortController.abort(reason);
221
+ logger.info("[LLMOpsRegistry] Stopped operation for injection", {
222
+ operationId: operation.id.substring(0, 8),
223
+ agentPubkey: agentPubkey.substring(0, 8),
224
+ conversationId: conversationId.substring(0, 8),
225
+ reason,
226
+ });
227
+ return true;
228
+ }
229
+ }
230
+ return false;
231
+ }
232
+
233
+ /**
234
+ * Set the message injector for a streaming Claude Code operation.
235
+ * Called from onStreamStart callback when the stream starts.
236
+ *
237
+ * @param agentPubkey - The agent's public key
238
+ * @param conversationId - The conversation (root event) ID
239
+ * @param injector - The MessageInjector instance
240
+ * @returns Whether an operation was found and updated
241
+ */
242
+ setMessageInjector(agentPubkey: string, conversationId: string, injector: MessageInjector): boolean {
243
+ for (const operation of this.operations.values()) {
244
+ if (
245
+ operation.agentPubkey === agentPubkey &&
246
+ operation.conversationId === conversationId &&
247
+ !operation.abortController.signal.aborted
248
+ ) {
249
+ operation.messageInjector = injector;
250
+ logger.debug("[LLMOpsRegistry] Set message injector", {
251
+ operationId: operation.id.substring(0, 8),
252
+ agentPubkey: agentPubkey.substring(0, 8),
253
+ conversationId: conversationId.substring(0, 8),
254
+ });
255
+ return true;
256
+ }
257
+ }
258
+ return false;
259
+ }
260
+
261
+ /**
262
+ * Get the message injector for a streaming Claude Code operation.
263
+ * Returns undefined if no active operation exists or no injector is set.
264
+ *
265
+ * @param agentPubkey - The agent's public key
266
+ * @param conversationId - The conversation (root event) ID
267
+ * @returns The MessageInjector or undefined
268
+ */
269
+ getMessageInjector(agentPubkey: string, conversationId: string): MessageInjector | undefined {
270
+ for (const operation of this.operations.values()) {
271
+ if (
272
+ operation.agentPubkey === agentPubkey &&
273
+ operation.conversationId === conversationId &&
274
+ !operation.abortController.signal.aborted &&
275
+ operation.messageInjector
276
+ ) {
277
+ return operation.messageInjector;
278
+ }
279
+ }
280
+ return undefined;
281
+ }
282
+
283
+ /**
284
+ * Update the RAL state for an operation identified by agent and conversation.
285
+ * This is called by RALRegistry when state transitions occur (streaming, tool execution, etc.)
286
+ *
287
+ * @param agentPubkey - The agent's public key
288
+ * @param conversationId - The conversation (root event) ID
289
+ * @param state - The new RAL state
290
+ * @returns Whether an operation was found and updated
291
+ */
292
+ updateRALState(agentPubkey: string, conversationId: string, state: RALState): boolean {
293
+ for (const operation of this.operations.values()) {
294
+ if (
295
+ operation.agentPubkey === agentPubkey &&
296
+ operation.conversationId === conversationId &&
297
+ !operation.abortController.signal.aborted
298
+ ) {
299
+ // Only update and notify if state actually changed
300
+ const previousState = operation.ralState;
301
+ if (previousState === state) {
302
+ return true; // Found but no change needed
303
+ }
304
+
305
+ operation.ralState = state;
306
+ logger.debug("[LLMOpsRegistry] Updated RAL state", {
307
+ operationId: operation.id.substring(0, 8),
308
+ agentPubkey: agentPubkey.substring(0, 8),
309
+ conversationId: conversationId.substring(0, 8),
310
+ previousState,
311
+ state,
312
+ });
313
+ // Notify listeners of state change
314
+ this.notifyChange();
315
+ return true;
316
+ }
317
+ }
318
+ return false;
319
+ }
320
+
321
+ getActiveOperationsCount(): number {
322
+ return this.operations.size;
323
+ }
324
+
325
+ /**
326
+ * DIAGNOSTIC: Get concurrency statistics for bottleneck analysis
327
+ */
328
+ getConcurrencyStats(): {
329
+ current: number;
330
+ peak: number;
331
+ total: number;
332
+ histogram: Record<number, number>;
333
+ activeAgents: string[];
334
+ } {
335
+ return {
336
+ current: this.operations.size,
337
+ peak: this.peakConcurrentOperations,
338
+ total: this.totalOperationsRegistered,
339
+ histogram: Object.fromEntries(this.concurrencyHistogram),
340
+ activeAgents: Array.from(this.operations.values()).map(
341
+ (op) => `${op.agentPubkey.substring(0, 8)}:${op.ralState || "unknown"}`
342
+ ),
343
+ };
344
+ }
345
+
346
+ /**
347
+ * DIAGNOSTIC: Reset concurrency metrics (for testing)
348
+ */
349
+ resetConcurrencyMetrics(): void {
350
+ this.peakConcurrentOperations = 0;
351
+ this.totalOperationsRegistered = 0;
352
+ this.concurrencyHistogram.clear();
353
+ }
354
+
355
+ /**
356
+ * Get operations grouped by conversation ID (thread root) for publishing.
357
+ * Operations are grouped by conversationId only, not by individual message eventId.
358
+ * This ensures kind:24133 status events use the thread root in the e-tag,
359
+ * allowing clients to look up working agents by conversation ID.
360
+ */
361
+ getOperationsByConversation(): Map<string, LLMOperation[]> {
362
+ const byConversation = new Map<string, LLMOperation[]>();
363
+
364
+ for (const operation of this.operations.values()) {
365
+ this.addOperationToConversationMap(byConversation, operation.conversationId, operation);
366
+ }
367
+
368
+ return byConversation;
369
+ }
370
+
371
+ // NOTE: getOperationsByEvent() was removed - use getOperationsByConversation() instead.
372
+ // If you need per-event grouping, iterate operations and group manually.
373
+
374
+ /**
375
+ * Helper to add an operation to the conversation map.
376
+ * Creates the array if it doesn't exist, then appends the operation.
377
+ */
378
+ private addOperationToConversationMap(
379
+ map: Map<string, LLMOperation[]>,
380
+ conversationId: string,
381
+ operation: LLMOperation
382
+ ): void {
383
+ let operations = map.get(conversationId);
384
+ if (!operations) {
385
+ operations = [];
386
+ map.set(conversationId, operations);
387
+ }
388
+ operations.push(operation);
389
+ }
390
+
391
+ // Subscribe to changes
392
+ onChange(listener: () => void): () => void {
393
+ this.changeListeners.add(listener);
394
+ return () => this.changeListeners.delete(listener);
395
+ }
396
+
397
+ private notifyChange(): void {
398
+ for (const listener of this.changeListeners) {
399
+ listener();
400
+ }
401
+ }
402
+
403
+ /**
404
+ * Index an operation by event ID for fast lookup.
405
+ * Creates the Set if it doesn't exist, then adds the operation ID.
406
+ */
407
+ private indexOperation(operationId: string, eventId: string): void {
408
+ let eventOperations = this.byEvent.get(eventId);
409
+ if (!eventOperations) {
410
+ eventOperations = new Set();
411
+ this.byEvent.set(eventId, eventOperations);
412
+ }
413
+ eventOperations.add(operationId);
414
+ }
415
+
416
+ private unindexOperation(operationId: string, eventId: string): void {
417
+ this.byEvent.get(eventId)?.delete(operationId);
418
+ if (this.byEvent.get(eventId)?.size === 0) {
419
+ this.byEvent.delete(eventId);
420
+ }
421
+ }
422
+ }
423
+
424
+ export const llmOpsRegistry = LLMOperationsRegistry.getInstance();