@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,49 @@
1
+ /**
2
+ * Conversation Search Module
3
+ *
4
+ * Provides full-text, keyword, and semantic search capabilities for conversations.
5
+ */
6
+
7
+ // Types
8
+ export type {
9
+ AdvancedSearchResult,
10
+ ConversationIndex,
11
+ ConversationIndexEntry,
12
+ IndexMetadata,
13
+ MessageIndexEntry,
14
+ MessageMatch,
15
+ SearchFilters,
16
+ SearchQuery,
17
+ SearchResult,
18
+ } from "./types";
19
+
20
+ // Query Parser
21
+ export {
22
+ getEffectiveSinceTimestamp,
23
+ parseQuery,
24
+ parseTimestamp,
25
+ type RawSearchInput,
26
+ } from "./QueryParser";
27
+
28
+ // Snippet Extractor
29
+ export {
30
+ extractAllSnippets,
31
+ extractSnippet,
32
+ type SnippetResult,
33
+ } from "./SnippetExtractor";
34
+
35
+ // Search Engine
36
+ export {
37
+ search,
38
+ searchByTitleOnly,
39
+ } from "./SearchEngine";
40
+
41
+ // Index Manager
42
+ export {
43
+ clearIndexManagerInstances,
44
+ ConversationIndexManager,
45
+ getIndexManager,
46
+ } from "./ConversationIndexManager";
47
+
48
+ // Embeddings and Semantic Search
49
+ export * from "./embeddings";
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Type definitions for the conversation search module.
3
+ */
4
+
5
+ /**
6
+ * Search filters for narrowing down results.
7
+ * Note: 'after' in input is a pure alias for 'since' - only 'since' is stored after parsing.
8
+ */
9
+ export interface SearchFilters {
10
+ /** Filter by agent slugs or pubkeys - conversation must include at least one */
11
+ agents?: string[];
12
+ /** Return conversations with activity since this timestamp (Unix seconds) */
13
+ since?: number;
14
+ }
15
+
16
+ /**
17
+ * Parsed and validated search query.
18
+ */
19
+ export interface SearchQuery {
20
+ /** The text to search for in message content */
21
+ text: string;
22
+ /** Optional filters to narrow results */
23
+ filters: SearchFilters;
24
+ }
25
+
26
+ /**
27
+ * A single message match with snippet context.
28
+ */
29
+ export interface MessageMatch {
30
+ /** The ID of the matching message */
31
+ messageId: string;
32
+ /** Snippet showing the match with 50-75 char context */
33
+ snippet: string;
34
+ }
35
+
36
+ /**
37
+ * A conversation search result.
38
+ */
39
+ export interface SearchResult {
40
+ /** The conversation ID */
41
+ conversationId: string;
42
+ /** The conversation title */
43
+ title?: string;
44
+ /** Total number of messages in the conversation */
45
+ messageCount: number;
46
+ /** When the conversation was created (Unix timestamp in seconds) */
47
+ createdAt?: number;
48
+ /** When the last activity occurred (Unix timestamp in seconds) */
49
+ lastActivity?: number;
50
+ /** Array of matching messages with snippets */
51
+ matches: MessageMatch[];
52
+ }
53
+
54
+ /**
55
+ * Index entry for a single message.
56
+ */
57
+ export interface MessageIndexEntry {
58
+ /** Unique message identifier (index within conversation) */
59
+ messageId: string;
60
+ /** Full message text content for searching */
61
+ content: string;
62
+ /** Message timestamp (Unix seconds) */
63
+ timestamp?: number;
64
+ /** Sender identifier (pubkey or agent slug) */
65
+ from?: string;
66
+ /** Target recipient identifier */
67
+ to?: string;
68
+ }
69
+
70
+ /**
71
+ * Index entry for a conversation.
72
+ */
73
+ export interface ConversationIndexEntry {
74
+ /** The conversation ID */
75
+ conversationId: string;
76
+ /** Project slug/ID this conversation belongs to */
77
+ slug: string;
78
+ /** Conversation title */
79
+ title?: string;
80
+ /** Total message count */
81
+ messageCount: number;
82
+ /** Last message timestamp (Unix seconds) */
83
+ lastMessageAt?: number;
84
+ /** List of agent pubkeys that participated */
85
+ agents: string[];
86
+ /** Indexed messages */
87
+ messages: MessageIndexEntry[];
88
+ }
89
+
90
+ /**
91
+ * The full search index structure.
92
+ */
93
+ export interface ConversationIndex {
94
+ /** Index format version for migration compatibility */
95
+ version: string;
96
+ /** When the index was last updated (ISO 8601) */
97
+ lastUpdated: string;
98
+ /** All indexed conversations */
99
+ conversations: ConversationIndexEntry[];
100
+ }
101
+
102
+ /**
103
+ * Index file metadata for staleness checks.
104
+ */
105
+ export interface IndexMetadata {
106
+ version: string;
107
+ lastUpdated: string;
108
+ conversationCount: number;
109
+ }
110
+
111
+ /**
112
+ * Result of an advanced search operation.
113
+ * Includes explicit error information when applicable.
114
+ */
115
+ export interface AdvancedSearchResult {
116
+ /** Whether the search completed successfully */
117
+ success: boolean;
118
+ /** Search results (empty array if error) */
119
+ results: SearchResult[];
120
+ /** Error message if success is false */
121
+ error?: string;
122
+ /** Whether fallback to title-only search was used */
123
+ usedFallback?: boolean;
124
+ }
@@ -0,0 +1,160 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import { ensureDirectory, fileExists } from "@/lib/fs";
4
+
5
+ /**
6
+ * CategoryTally represents the stored format: category name -> usage count
7
+ * @example { "authentication": 15, "storage": 8, "testing": 12 }
8
+ */
9
+ export type CategoryTally = Record<string, number>;
10
+
11
+ /**
12
+ * CategoryManager - Manages conversation categories with usage tracking
13
+ *
14
+ * ## Responsibility
15
+ * Maintains a tally of conversation categories stored in a local JSON file.
16
+ * Categories track usage counts to identify frequently used categorizations.
17
+ *
18
+ * ## Storage Location
19
+ * Categories are stored in `data/conversation-categories.json` relative to the
20
+ * project root. The data directory is created automatically if it doesn't exist.
21
+ *
22
+ * ## Category Format Requirements
23
+ * - Lowercase (e.g., "authentication" not "Authentication")
24
+ * - Singular nouns (e.g., "authentication" not "auth")
25
+ * - No special characters or spaces (e.g., "error-handling" not "error handling")
26
+ *
27
+ * ## Thread Safety
28
+ * All file operations use async/await to ensure proper sequencing.
29
+ * The class maintains no in-memory cache to avoid stale data issues.
30
+ *
31
+ * @example
32
+ * const manager = new CategoryManager('/path/to/project');
33
+ * await manager.initialize();
34
+ *
35
+ * // Get existing categories
36
+ * const categories = await manager.getCategories();
37
+ * // Returns: ["authentication", "storage", "testing"]
38
+ *
39
+ * // Update with new categories
40
+ * await manager.updateCategories(["authentication", "database"]);
41
+ * // Increments "authentication" count, adds "database" with count 1
42
+ */
43
+ export class CategoryManager {
44
+ private dataDir: string;
45
+ private categoriesFilePath: string;
46
+
47
+ /**
48
+ * Creates a new CategoryManager instance
49
+ * @param projectRoot - The root directory of the project
50
+ */
51
+ constructor(projectRoot: string) {
52
+ this.dataDir = path.join(projectRoot, "data");
53
+ this.categoriesFilePath = path.join(this.dataDir, "conversation-categories.json");
54
+ }
55
+
56
+ /**
57
+ * Initialize the CategoryManager by ensuring the data directory exists
58
+ */
59
+ async initialize(): Promise<void> {
60
+ await ensureDirectory(this.dataDir);
61
+ }
62
+
63
+ /**
64
+ * Load the category tally from disk
65
+ * @returns The current category tally, or empty object if file doesn't exist
66
+ */
67
+ private async loadTally(): Promise<CategoryTally> {
68
+ try {
69
+ if (!(await fileExists(this.categoriesFilePath))) {
70
+ return {};
71
+ }
72
+ const content = await fs.readFile(this.categoriesFilePath, "utf-8");
73
+ return JSON.parse(content) as CategoryTally;
74
+ } catch (error) {
75
+ // If file is corrupted or unreadable, start fresh
76
+ console.error("Failed to load category tally, starting fresh:", error);
77
+ return {};
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Save the category tally to disk
83
+ * @param tally - The category tally to save
84
+ */
85
+ private async saveTally(tally: CategoryTally): Promise<void> {
86
+ await ensureDirectory(this.dataDir);
87
+ await fs.writeFile(this.categoriesFilePath, JSON.stringify(tally, null, 2), "utf-8");
88
+ }
89
+
90
+ /**
91
+ * Get all existing categories as an array
92
+ * Categories are sorted by usage count (highest first)
93
+ * @returns Array of category names
94
+ */
95
+ async getCategories(): Promise<string[]> {
96
+ const tally = await this.loadTally();
97
+ // Sort by usage count (descending) to prioritize frequently used categories
98
+ return Object.entries(tally)
99
+ .sort(([, countA], [, countB]) => countB - countA)
100
+ .map(([category]) => category);
101
+ }
102
+
103
+ /**
104
+ * Get the full category tally including usage counts
105
+ * @returns The category tally object
106
+ */
107
+ async getCategoryTally(): Promise<CategoryTally> {
108
+ return this.loadTally();
109
+ }
110
+
111
+ /**
112
+ * Update categories by adding new ones and incrementing counts for existing ones
113
+ *
114
+ * @param newCategories - Array of category names to add/increment
115
+ *
116
+ * @example
117
+ * // If tally is { "authentication": 5, "storage": 2 }
118
+ * await manager.updateCategories(["authentication", "database"]);
119
+ * // Tally becomes { "authentication": 6, "storage": 2, "database": 1 }
120
+ */
121
+ async updateCategories(newCategories: string[]): Promise<void> {
122
+ if (newCategories.length === 0) {
123
+ return;
124
+ }
125
+
126
+ const tally = await this.loadTally();
127
+
128
+ for (const category of newCategories) {
129
+ // Normalize: lowercase, trim whitespace
130
+ const normalizedCategory = category.toLowerCase().trim();
131
+
132
+ if (!normalizedCategory) {
133
+ continue;
134
+ }
135
+
136
+ // Increment existing or initialize to 1
137
+ tally[normalizedCategory] = (tally[normalizedCategory] || 0) + 1;
138
+ }
139
+
140
+ await this.saveTally(tally);
141
+ }
142
+
143
+ /**
144
+ * Remove a category from the tally
145
+ * @param category - The category to remove
146
+ * @returns true if the category was removed, false if it didn't exist
147
+ */
148
+ async removeCategory(category: string): Promise<boolean> {
149
+ const tally = await this.loadTally();
150
+ const normalizedCategory = category.toLowerCase().trim();
151
+
152
+ if (!(normalizedCategory in tally)) {
153
+ return false;
154
+ }
155
+
156
+ delete tally[normalizedCategory];
157
+ await this.saveTally(tally);
158
+ return true;
159
+ }
160
+ }
@@ -0,0 +1,296 @@
1
+ import { ConversationStore } from "../ConversationStore";
2
+ import type { ConversationMetadata } from "../types";
3
+ import { AgentEventDecoder } from "@/nostr/AgentEventDecoder";
4
+ import { shortenConversationId } from "@/utils/conversation-id";
5
+ import { getNDK } from "@/nostr/ndkClient";
6
+ import { getProjectContext } from "@/services/projects";
7
+ import { logger } from "@/utils/logger";
8
+ import { buildDelegationChain } from "@/utils/delegation-chain";
9
+ import type { NDKEvent } from "@nostr-dev-kit/ndk";
10
+ import { NDKArticle } from "@nostr-dev-kit/ndk";
11
+ import { trace } from "@opentelemetry/api";
12
+ import chalk from "chalk";
13
+ import { TagExtractor } from "@/nostr/TagExtractor";
14
+ import { formatAnyError } from "@/lib/error-formatter";
15
+
16
+ /**
17
+ * Fetch a kind 30023 (NDKArticle) from an a-tag reference.
18
+ * @param aTagValue - The a-tag value in format "30023:pubkey:d-tag"
19
+ * @returns The article metadata or null if not found
20
+ */
21
+ async function fetchReferencedArticle(
22
+ aTagValue: string
23
+ ): Promise<ConversationMetadata["referencedArticle"] | null> {
24
+ try {
25
+ const parts = aTagValue.split(":");
26
+ if (parts.length < 3 || parts[0] !== "30023") {
27
+ return null;
28
+ }
29
+
30
+ const [, pubkey, ...dTagParts] = parts;
31
+ const dTag = dTagParts.join(":"); // Handle d-tags that contain colons
32
+
33
+ const ndk = getNDK();
34
+ const filter = {
35
+ kinds: [30023],
36
+ authors: [pubkey],
37
+ "#d": [dTag],
38
+ };
39
+
40
+ const events = await ndk.fetchEvents(filter);
41
+ if (events.size === 0) {
42
+ logger.debug(chalk.yellow(`Referenced article not found: ${aTagValue}`));
43
+ return null;
44
+ }
45
+
46
+ const event = Array.from(events)[0];
47
+ const article = NDKArticle.from(event);
48
+
49
+ logger.info(chalk.cyan(`📄 Fetched referenced article: "${article.title || dTag}"`));
50
+
51
+ return {
52
+ title: article.title || dTag,
53
+ content: article.content || "",
54
+ dTag,
55
+ };
56
+ } catch (error) {
57
+ logger.debug(chalk.yellow(`Failed to fetch referenced article: ${formatAnyError(error)}`));
58
+ return null;
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Extract and fetch the first kind 30023 article reference from an event's a-tags.
64
+ * @param event - The event to extract article references from
65
+ * @returns The article metadata or null if none found
66
+ */
67
+ async function extractReferencedArticle(
68
+ event: NDKEvent
69
+ ): Promise<ConversationMetadata["referencedArticle"] | null> {
70
+ const aTags = TagExtractor.getATags(event);
71
+
72
+ // Find the first a-tag referencing a kind 30023 (article)
73
+ const articleATag = aTags.find((tag) => tag.startsWith("30023:"));
74
+ if (!articleATag) {
75
+ return null;
76
+ }
77
+
78
+ return fetchReferencedArticle(articleATag);
79
+ }
80
+
81
+ export interface ConversationResolutionResult {
82
+ conversation: ConversationStore | undefined;
83
+ isNew?: boolean;
84
+ }
85
+
86
+ /**
87
+ * ConversationResolver encapsulates all logic for finding or creating conversations
88
+ * based on incoming Nostr events.
89
+ */
90
+ export class ConversationResolver {
91
+
92
+ /**
93
+ * Resolve the conversation for an incoming event.
94
+ */
95
+ async resolveConversationForEvent(event: NDKEvent): Promise<ConversationResolutionResult> {
96
+ const activeSpan = trace.getActiveSpan();
97
+ const replyTarget = AgentEventDecoder.getReplyTarget(event);
98
+
99
+ // If event has an 'e' tag (reply), try to find existing conversation
100
+ if (replyTarget) {
101
+ const conversation = ConversationStore.findByEventId(replyTarget);
102
+ if (conversation) {
103
+ activeSpan?.addEvent("conversation.resolved", {
104
+ "resolution.type": "found_existing",
105
+ "conversation.id": shortenConversationId(conversation.id),
106
+ "conversation.message_count": conversation.getAllMessages().length,
107
+ });
108
+ return { conversation };
109
+ }
110
+
111
+ // Has e tag but conversation not found - try orphaned reply handling
112
+ const mentionedPubkeys = AgentEventDecoder.getMentionedPubkeys(event);
113
+ const newConversation = await this.handleOrphanedReply(event, replyTarget, mentionedPubkeys);
114
+ if (newConversation) {
115
+ activeSpan?.addEvent("conversation.resolved", {
116
+ "resolution.type": "created_from_orphan",
117
+ "conversation.id": shortenConversationId(newConversation.id),
118
+ "conversation.message_count": newConversation.getAllMessages().length,
119
+ });
120
+ return { conversation: newConversation, isNew: true };
121
+ }
122
+
123
+ activeSpan?.addEvent("conversation.resolution_failed", {
124
+ reason: "conversation_not_found_for_reply_target",
125
+ "reply_target.id": replyTarget,
126
+ });
127
+ return { conversation: undefined };
128
+ }
129
+
130
+ // No 'e' tag - this is a NEW conversation, create it
131
+ const mentionedPubkeys = AgentEventDecoder.getMentionedPubkeys(event);
132
+ const projectCtx = getProjectContext();
133
+ const isDirectedToAgent = mentionedPubkeys.some((pubkey) =>
134
+ Array.from(projectCtx.agents.values()).some((a) => a.pubkey === pubkey)
135
+ );
136
+
137
+ if (!isDirectedToAgent) {
138
+ activeSpan?.addEvent("conversation.resolution_failed", {
139
+ reason: "not_directed_to_agent",
140
+ });
141
+ return { conversation: undefined };
142
+ }
143
+
144
+ const conversation = await ConversationStore.create(event);
145
+ if (conversation) {
146
+ // Check for referenced kind 30023 articles and populate metadata
147
+ const referencedArticle = await extractReferencedArticle(event);
148
+ if (referencedArticle) {
149
+ conversation.updateMetadata({ referencedArticle });
150
+ await conversation.save();
151
+
152
+ activeSpan?.addEvent("referenced_article_loaded", {
153
+ "article.title": referencedArticle.title,
154
+ "article.dTag": referencedArticle.dTag,
155
+ "article.content_length": referencedArticle.content.length,
156
+ });
157
+ }
158
+
159
+ // Build and store delegation chain if this is a delegated conversation
160
+ // For delegation events, the first p-tag is always the intended recipient.
161
+ // getMentionedPubkeys returns p-tags in order, so the first agent pubkey
162
+ // found is the correct target.
163
+ const targetAgentPubkey = mentionedPubkeys.find((pubkey) =>
164
+ Array.from(projectCtx.agents.values()).some((a) => a.pubkey === pubkey)
165
+ );
166
+
167
+ if (targetAgentPubkey) {
168
+ const delegationChain = buildDelegationChain(
169
+ event,
170
+ targetAgentPubkey,
171
+ projectCtx.project.pubkey, // Project owner is the human user
172
+ conversation.id // The conversation being created for the current agent
173
+ );
174
+
175
+ if (delegationChain && delegationChain.length > 0) {
176
+ conversation.updateMetadata({ delegationChain });
177
+ await conversation.save();
178
+
179
+ activeSpan?.addEvent("delegation_chain_built", {
180
+ "chain.length": delegationChain.length,
181
+ "chain.display": delegationChain.map(c => c.displayName).join(" → "),
182
+ });
183
+
184
+ logger.debug("[ConversationResolver] Built delegation chain for new conversation", {
185
+ conversationId: conversation.id.substring(0, 8),
186
+ chainLength: delegationChain.length,
187
+ chain: delegationChain.map(c => c.displayName).join(" → "),
188
+ });
189
+ }
190
+ }
191
+
192
+ logger.info(chalk.green(`Created new conversation ${conversation.id.substring(0, 8)} from kind:1 event`));
193
+ activeSpan?.addEvent("conversation.resolved", {
194
+ "resolution.type": "created_new",
195
+ "conversation.id": shortenConversationId(conversation.id),
196
+ });
197
+ return { conversation, isNew: true };
198
+ }
199
+
200
+ activeSpan?.addEvent("conversation.resolution_failed", {
201
+ reason: "failed_to_create_conversation",
202
+ });
203
+ return { conversation: undefined };
204
+ }
205
+
206
+ /**
207
+ * Handle orphaned replies by fetching the thread from the network
208
+ */
209
+ private async handleOrphanedReply(
210
+ event: NDKEvent,
211
+ replyTargetId: string,
212
+ mentionedPubkeys: string[]
213
+ ): Promise<ConversationStore | undefined> {
214
+ if (mentionedPubkeys.length === 0) {
215
+ return undefined;
216
+ }
217
+
218
+ const projectCtx = getProjectContext();
219
+ const isDirectedToAgent = mentionedPubkeys.some((pubkey) =>
220
+ Array.from(projectCtx.agents.values()).some((a) => a.pubkey === pubkey)
221
+ );
222
+
223
+ if (!isDirectedToAgent) {
224
+ return undefined;
225
+ }
226
+
227
+ logger.info(
228
+ chalk.yellow(
229
+ `Fetching conversation thread for orphaned reply, target: ${replyTargetId.substring(0, 8)}`
230
+ )
231
+ );
232
+
233
+ const activeSpan = trace.getActiveSpan();
234
+ activeSpan?.addEvent("conversation.fetching_orphaned_thread", {
235
+ reply_target_id: replyTargetId,
236
+ });
237
+
238
+ const ndk = getNDK();
239
+
240
+ // Fetch the reply target event and any events that also reply to it
241
+ const events = await ndk.fetchEvents([
242
+ { ids: [replyTargetId] },
243
+ { "#e": [replyTargetId] },
244
+ ]);
245
+
246
+ const eventsArray = Array.from(events);
247
+ const rootEvent = eventsArray.find((e) => e.id === replyTargetId);
248
+
249
+ if (!rootEvent) {
250
+ logger.warn(chalk.yellow(`Could not fetch target event ${replyTargetId.substring(0, 8)} from network`));
251
+ activeSpan?.addEvent("conversation.fetch_failed", {
252
+ reason: "target_event_not_found",
253
+ reply_target_id: replyTargetId,
254
+ });
255
+ return undefined;
256
+ }
257
+
258
+ const replies = eventsArray.filter((e) => e.id !== replyTargetId);
259
+
260
+ logger.info(chalk.green(`Fetched target event and ${replies.length} replies`));
261
+ activeSpan?.addEvent("conversation.thread_fetched", {
262
+ "fetched.reply_count": replies.length,
263
+ "fetched.total_events": eventsArray.length,
264
+ });
265
+
266
+ const conversation = await ConversationStore.create(rootEvent);
267
+ if (!conversation) {
268
+ return undefined;
269
+ }
270
+
271
+ // Check for referenced kind 30023 articles in the root event and populate metadata
272
+ const referencedArticle = await extractReferencedArticle(rootEvent);
273
+ if (referencedArticle) {
274
+ conversation.updateMetadata({ referencedArticle });
275
+ await conversation.save();
276
+
277
+ activeSpan?.addEvent("referenced_article_loaded", {
278
+ "article.title": referencedArticle.title,
279
+ "article.dTag": referencedArticle.dTag,
280
+ "article.content_length": referencedArticle.content.length,
281
+ });
282
+ }
283
+
284
+ replies.sort((a, b) => (a.created_at || 0) - (b.created_at || 0));
285
+
286
+ for (const reply of replies) {
287
+ await ConversationStore.addEvent(conversation.id, reply);
288
+ }
289
+
290
+ if (event.id !== rootEvent.id && !replies.some((r) => r.id === event.id)) {
291
+ await ConversationStore.addEvent(conversation.id, event);
292
+ }
293
+
294
+ return conversation;
295
+ }
296
+ }