@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,62 @@
1
+ /**
2
+ * Utility functions for managing conversation execution time
3
+ * Works directly with ConversationStore objects, following DRY principle
4
+ */
5
+
6
+ import type { ConversationStore } from "./ConversationStore";
7
+
8
+ /**
9
+ * Start tracking execution time for a conversation
10
+ */
11
+ export function startExecutionTime(conversation: ConversationStore): void {
12
+ if (conversation.executionTime.isActive) {
13
+ // Already active - don't restart
14
+ return;
15
+ }
16
+
17
+ conversation.executionTime.currentSessionStart = Date.now();
18
+ conversation.executionTime.isActive = true;
19
+ conversation.executionTime.lastUpdated = Date.now();
20
+ }
21
+
22
+ /**
23
+ * Stop tracking execution time and add duration to total
24
+ * @returns Duration of this session in milliseconds
25
+ */
26
+ export function stopExecutionTime(conversation: ConversationStore): number {
27
+ if (!conversation.executionTime.isActive || !conversation.executionTime.currentSessionStart) {
28
+ return 0;
29
+ }
30
+
31
+ const sessionDuration = Date.now() - conversation.executionTime.currentSessionStart;
32
+ const sessionSeconds = Math.round(sessionDuration / 1000);
33
+
34
+ conversation.executionTime.totalSeconds += sessionSeconds;
35
+ conversation.executionTime.currentSessionStart = undefined;
36
+ conversation.executionTime.isActive = false;
37
+ conversation.executionTime.lastUpdated = Date.now();
38
+
39
+ return sessionDuration;
40
+ }
41
+
42
+ /**
43
+ * Get total execution time in seconds including any active session
44
+ */
45
+ export function getTotalExecutionTimeSeconds(conversation: ConversationStore): number {
46
+ let total = conversation.executionTime.totalSeconds;
47
+
48
+ // Add current active session time if any
49
+ if (conversation.executionTime.isActive && conversation.executionTime.currentSessionStart) {
50
+ const activeSeconds = Math.round((Date.now() - conversation.executionTime.currentSessionStart) / 1000);
51
+ total += activeSeconds;
52
+ }
53
+
54
+ return total;
55
+ }
56
+
57
+ /**
58
+ * Check if execution is currently active
59
+ */
60
+ export function isExecutionActive(conversation: ConversationStore): boolean {
61
+ return conversation.executionTime?.isActive ?? false;
62
+ }
@@ -0,0 +1,64 @@
1
+ interface DelegationResponse {
2
+ from: string;
3
+ content: string;
4
+ eventId: string;
5
+ status: "completed" | "error";
6
+ }
7
+
8
+ interface DelegationInfo {
9
+ id: string;
10
+ from: string;
11
+ recipients: string[];
12
+ phase?: string;
13
+ message: string;
14
+ requestEventId: string;
15
+ responses: DelegationResponse[];
16
+ }
17
+
18
+ /**
19
+ * Formats delegation information as structured XML for LLM comprehension
20
+ */
21
+ export class DelegationXmlFormatter {
22
+ /**
23
+ * Render a delegation with its responses as XML
24
+ */
25
+ static render(delegation: DelegationInfo, debug = false): string {
26
+ const eventPrefix = debug ? `[Event ${delegation.requestEventId.substring(0, 8)}] ` : "";
27
+ const recipientsAttr = delegation.recipients.join(",");
28
+ const phaseAttr = delegation.phase ? ` phase="${delegation.phase}"` : "";
29
+
30
+ let xml = `${eventPrefix}<delegation from="${delegation.from}" recipients="${recipientsAttr}" id="${delegation.id}"${phaseAttr}>`;
31
+ xml += `\n <delegation-request>${DelegationXmlFormatter.escapeXml(delegation.message)}</delegation-request>`;
32
+
33
+ // Add responses
34
+ for (const response of delegation.responses) {
35
+ const statusAttr = response.status === "error" ? ' error="true"' : "";
36
+ const eventIdAttr = debug ? ` event-id="${response.eventId.substring(0, 8)}"` : "";
37
+ xml += `\n <response from="${response.from}"${statusAttr}${eventIdAttr}>${DelegationXmlFormatter.escapeXml(response.content)}</response>`;
38
+ }
39
+
40
+ // Add pending placeholders for recipients without responses
41
+ const respondedFrom = new Set(delegation.responses.map((r) => r.from));
42
+ for (const recipient of delegation.recipients) {
43
+ if (!respondedFrom.has(recipient)) {
44
+ xml += `\n <response from="${recipient}" status="pending" />`;
45
+ }
46
+ }
47
+
48
+ xml += "\n</delegation>";
49
+
50
+ return xml;
51
+ }
52
+
53
+ /**
54
+ * Escape XML special characters
55
+ */
56
+ private static escapeXml(text: string): string {
57
+ return text
58
+ .replace(/&/g, "&amp;")
59
+ .replace(/</g, "&lt;")
60
+ .replace(/>/g, "&gt;")
61
+ .replace(/"/g, "&quot;")
62
+ .replace(/'/g, "&apos;");
63
+ }
64
+ }
@@ -0,0 +1,303 @@
1
+ import type { NDKEvent } from "@nostr-dev-kit/ndk";
2
+ import { MessageFormatter } from "./utils/MessageFormatter";
3
+ import { TimestampFormatter } from "./utils/TimestampFormatter";
4
+ import { TreeBuilder } from "./utils/TreeBuilder";
5
+ import { TreeRenderer } from "./utils/TreeRenderer";
6
+
7
+ export interface ThreadNode {
8
+ event: NDKEvent;
9
+ agent?: string; // Agent name/identifier
10
+ timestamp: Date;
11
+ content: string;
12
+ toolCall?: {
13
+ name: string;
14
+ args?: string;
15
+ };
16
+ children: ThreadNode[];
17
+ depth: number;
18
+ }
19
+
20
+ export interface FormatterOptions {
21
+ includeTimestamps: boolean;
22
+ timestampFormat: "relative" | "absolute" | "time-only";
23
+ maxDepth?: number;
24
+ includeToolCalls: boolean;
25
+ treeStyle: "ascii" | "unicode" | "markdown";
26
+ compactMode: boolean; // Single-line per message
27
+ currentAgentPubkey?: string; // The agent we're formatting for (to show "you")
28
+ }
29
+
30
+ export class ThreadedConversationFormatter {
31
+ private treeBuilder: TreeBuilder;
32
+ private messageFormatter: MessageFormatter;
33
+ private timestampFormatter: TimestampFormatter;
34
+ private treeRenderer: TreeRenderer;
35
+
36
+ constructor() {
37
+ this.treeBuilder = new TreeBuilder();
38
+ this.messageFormatter = new MessageFormatter();
39
+ this.timestampFormatter = new TimestampFormatter();
40
+ this.treeRenderer = new TreeRenderer();
41
+ }
42
+
43
+ /**
44
+ * Build tree structure from flat event list
45
+ */
46
+ async buildThreadTree(events: NDKEvent[]): Promise<ThreadNode[]> {
47
+ return this.treeBuilder.buildFromEvents(events);
48
+ }
49
+
50
+ /**
51
+ * Format single thread as ASCII/Unicode tree
52
+ */
53
+ formatThread(root: ThreadNode, options?: FormatterOptions): string {
54
+ const opts = this.getDefaultOptions(options);
55
+ return this.renderNode(root, opts, "", true);
56
+ }
57
+
58
+ /**
59
+ * Extract agent-specific participation branches
60
+ */
61
+ extractAgentBranches(tree: ThreadNode[], agentPubkey: string): ThreadNode[] {
62
+ const relevantNodes: ThreadNode[] = [];
63
+
64
+ for (const root of tree) {
65
+ const extractedBranch = this.extractRelevantBranch(root, agentPubkey);
66
+ if (extractedBranch) {
67
+ relevantNodes.push(extractedBranch);
68
+ }
69
+ }
70
+
71
+ return relevantNodes;
72
+ }
73
+
74
+ /**
75
+ * Format branches where the agent participated, excluding the active branch
76
+ * This is the main entry point for getting "other threads" context
77
+ */
78
+ public async formatOtherBranches(
79
+ allEvents: NDKEvent[],
80
+ agentPubkey: string,
81
+ activeBranchIds: Set<string>
82
+ ): Promise<string | null> {
83
+ // 1. Build complete conversation tree from all events
84
+ const completeTree = await this.buildThreadTree(allEvents);
85
+
86
+ // 2. Prune the active branch from the tree
87
+ const prunedTree = this.pruneBranch(completeTree, activeBranchIds);
88
+
89
+ // 3. Extract branches where agent participated from the pruned tree
90
+ const agentBranches = this.extractRelevantBranches(prunedTree, agentPubkey);
91
+
92
+ // 4. If no relevant branches found, return null
93
+ if (agentBranches.length === 0) {
94
+ return null;
95
+ }
96
+
97
+ // 5. Format the branches into a string
98
+ const options: FormatterOptions = {
99
+ includeTimestamps: true,
100
+ timestampFormat: "time-only",
101
+ includeToolCalls: true,
102
+ treeStyle: "ascii",
103
+ compactMode: true,
104
+ currentAgentPubkey: agentPubkey, // Pass the agent we're formatting for
105
+ };
106
+
107
+ const result: string[] = [];
108
+ for (let i = 0; i < agentBranches.length; i++) {
109
+ if (i > 0) {
110
+ result.push(`\n${"─".repeat(60)}\n`);
111
+ }
112
+ result.push(this.formatThread(agentBranches[i], options));
113
+ }
114
+
115
+ return result.join("\n");
116
+ }
117
+
118
+ /**
119
+ * Remove all nodes that are part of the active branch
120
+ * Returns a new tree with the active branch pruned out
121
+ */
122
+ private pruneBranch(tree: ThreadNode[], activeBranchIds: Set<string>): ThreadNode[] {
123
+ const prunedRoots: ThreadNode[] = [];
124
+
125
+ for (const root of tree) {
126
+ const prunedNode = this.pruneNode(root, activeBranchIds);
127
+ if (prunedNode) {
128
+ prunedRoots.push(prunedNode);
129
+ }
130
+ }
131
+
132
+ return prunedRoots;
133
+ }
134
+
135
+ /**
136
+ * Recursively prune a node and its children
137
+ * Returns null if the entire subtree should be removed
138
+ */
139
+ private pruneNode(node: ThreadNode, activeBranchIds: Set<string>): ThreadNode | null {
140
+ // If this node is in the active branch
141
+ if (activeBranchIds.has(node.event.id)) {
142
+ // Check if ANY child is NOT in the active branch
143
+ // If so, we need to keep this node but prune only active children
144
+ const hasNonActiveBranches = node.children.some(
145
+ (child) => !this.isEntireBranchActive(child, activeBranchIds)
146
+ );
147
+
148
+ if (hasNonActiveBranches) {
149
+ // Keep this node but prune children selectively
150
+ const prunedChildren: ThreadNode[] = [];
151
+ for (const child of node.children) {
152
+ const prunedChild = this.pruneNode(child, activeBranchIds);
153
+ if (prunedChild) {
154
+ prunedChildren.push(prunedChild);
155
+ }
156
+ }
157
+
158
+ // Return the node with only non-active children
159
+ return {
160
+ ...node,
161
+ children: prunedChildren,
162
+ };
163
+ }
164
+ // This node and ALL its descendants are in the active branch
165
+ return null;
166
+ }
167
+
168
+ // Node is not in active branch - keep it and recursively prune children
169
+ const prunedChildren: ThreadNode[] = [];
170
+ for (const child of node.children) {
171
+ const prunedChild = this.pruneNode(child, activeBranchIds);
172
+ if (prunedChild) {
173
+ prunedChildren.push(prunedChild);
174
+ }
175
+ }
176
+
177
+ // Return the node with pruned children
178
+ return {
179
+ ...node,
180
+ children: prunedChildren,
181
+ };
182
+ }
183
+
184
+ /**
185
+ * Check if an entire branch (node and all descendants) is in the active branch
186
+ */
187
+ private isEntireBranchActive(node: ThreadNode, activeBranchIds: Set<string>): boolean {
188
+ if (!activeBranchIds.has(node.event.id)) {
189
+ return false;
190
+ }
191
+
192
+ // Check all children recursively
193
+ for (const child of node.children) {
194
+ if (!this.isEntireBranchActive(child, activeBranchIds)) {
195
+ return false;
196
+ }
197
+ }
198
+
199
+ return true;
200
+ }
201
+
202
+ /**
203
+ * Extract branches where the agent participated (similar to extractAgentBranches)
204
+ * but works on already pruned tree
205
+ */
206
+ private extractRelevantBranches(tree: ThreadNode[], agentPubkey: string): ThreadNode[] {
207
+ const relevantNodes: ThreadNode[] = [];
208
+
209
+ for (const root of tree) {
210
+ const extractedBranch = this.extractRelevantBranch(root, agentPubkey);
211
+ if (extractedBranch) {
212
+ relevantNodes.push(extractedBranch);
213
+ }
214
+ }
215
+
216
+ return relevantNodes;
217
+ }
218
+
219
+ private extractRelevantBranch(node: ThreadNode, agentPubkey: string): ThreadNode | null {
220
+ // Check if this node or any descendant involves the agent
221
+ const involvedInBranch = this.isAgentInvolvedInBranch(node, agentPubkey);
222
+
223
+ if (!involvedInBranch) {
224
+ return null;
225
+ }
226
+
227
+ // If agent is involved in this branch, return the ENTIRE branch with all descendants
228
+ // This preserves full context of conversations the agent participated in
229
+ return this.cloneNode(node);
230
+ }
231
+
232
+ private cloneNode(node: ThreadNode): ThreadNode {
233
+ return {
234
+ ...node,
235
+ children: node.children.map((child) => this.cloneNode(child)),
236
+ };
237
+ }
238
+
239
+ private isAgentInvolvedInBranch(node: ThreadNode, agentPubkey: string): boolean {
240
+ // Check if this node is from the agent
241
+ if (node.event.pubkey === agentPubkey) {
242
+ return true;
243
+ }
244
+
245
+ // Check if any child branch has agent involvement
246
+ for (const child of node.children) {
247
+ if (this.isAgentInvolvedInBranch(child, agentPubkey)) {
248
+ return true;
249
+ }
250
+ }
251
+
252
+ return false;
253
+ }
254
+
255
+ private renderNode(
256
+ node: ThreadNode,
257
+ options: FormatterOptions,
258
+ prefix: string,
259
+ isLast: boolean
260
+ ): string {
261
+ const lines: string[] = [];
262
+
263
+ // Format the current node
264
+ const message = this.messageFormatter.format(node, options);
265
+ const timestamp = options.includeTimestamps
266
+ ? this.timestampFormatter.format(node.timestamp, options.timestampFormat)
267
+ : "";
268
+
269
+ // Add "(you)" if this is the current agent
270
+ let agentName = node.agent || "Unknown";
271
+ if (options.currentAgentPubkey && node.event.pubkey === options.currentAgentPubkey) {
272
+ agentName = `${agentName} (you)`;
273
+ }
274
+
275
+ const connector = this.treeRenderer.getConnector(options.treeStyle, isLast);
276
+
277
+ // Handle multi-line messages by joining with ⏎ separator
278
+ const inlineMessage = message.replace(/\n/g, " ⏎ ");
279
+ lines.push(`${prefix}${connector}${agentName}${timestamp}: ${inlineMessage}`);
280
+
281
+ // Render children with appropriate prefixes
282
+ const childPrefix = prefix + this.treeRenderer.getChildPrefix(options.treeStyle, isLast);
283
+ if (node.children.length > 0 && (!options.maxDepth || node.depth < options.maxDepth)) {
284
+ for (const [index, child] of node.children.entries()) {
285
+ const isLastChild = index === node.children.length - 1;
286
+ lines.push(this.renderNode(child, options, childPrefix, isLastChild));
287
+ }
288
+ }
289
+
290
+ return lines.join("\n");
291
+ }
292
+
293
+ private getDefaultOptions(options?: Partial<FormatterOptions>): FormatterOptions {
294
+ return {
295
+ includeTimestamps: true,
296
+ timestampFormat: "time-only",
297
+ includeToolCalls: true,
298
+ treeStyle: "ascii",
299
+ compactMode: true,
300
+ ...options,
301
+ };
302
+ }
303
+ }
@@ -0,0 +1,9 @@
1
+ export {
2
+ ThreadedConversationFormatter,
3
+ type ThreadNode,
4
+ type FormatterOptions,
5
+ } from "./ThreadedConversationFormatter";
6
+ export { TreeBuilder } from "./utils/TreeBuilder";
7
+ export { MessageFormatter } from "./utils/MessageFormatter";
8
+ export { TimestampFormatter } from "./utils/TimestampFormatter";
9
+ export { TreeRenderer } from "./utils/TreeRenderer";
@@ -0,0 +1,46 @@
1
+ import type { FormatterOptions, ThreadNode } from "../ThreadedConversationFormatter";
2
+
3
+ export class MessageFormatter {
4
+ /**
5
+ * Format a message node according to options
6
+ */
7
+ format(node: ThreadNode, options: FormatterOptions): string {
8
+ let message = node.content;
9
+
10
+ // Truncate if in compact mode
11
+ if (options.compactMode) {
12
+ message = this.truncateMessage(message, 100);
13
+ }
14
+
15
+ // Add tool call information if present
16
+ if (options.includeToolCalls && node.toolCall) {
17
+ const toolCallStr = node.toolCall.args
18
+ ? `[calls tool: ${node.toolCall.name}(${this.truncateMessage(node.toolCall.args, 50)})]`
19
+ : `[calls tool: ${node.toolCall.name}]`;
20
+ message = `${toolCallStr} ${message}`;
21
+ }
22
+
23
+ // Clean up message for single-line display
24
+ if (options.compactMode) {
25
+ message = message.replace(/\n+/g, " ").replace(/\s+/g, " ").trim();
26
+ }
27
+
28
+ return message;
29
+ }
30
+
31
+ private truncateMessage(text: string, maxLength: number): string {
32
+ if (text.length <= maxLength) {
33
+ return text;
34
+ }
35
+
36
+ // Try to truncate at a word boundary
37
+ const truncated = text.substring(0, maxLength);
38
+ const lastSpace = truncated.lastIndexOf(" ");
39
+
40
+ if (lastSpace > maxLength * 0.7) {
41
+ return `${truncated.substring(0, lastSpace)}...`;
42
+ }
43
+
44
+ return `${truncated}...`;
45
+ }
46
+ }
@@ -0,0 +1,56 @@
1
+ export class TimestampFormatter {
2
+ /**
3
+ * Format a timestamp according to the specified format
4
+ */
5
+ format(timestamp: Date, format: "relative" | "absolute" | "time-only"): string {
6
+ switch (format) {
7
+ case "relative":
8
+ return this.formatRelative(timestamp);
9
+ case "absolute":
10
+ return this.formatAbsolute(timestamp);
11
+ case "time-only":
12
+ return this.formatTimeOnly(timestamp);
13
+ default:
14
+ return "";
15
+ }
16
+ }
17
+
18
+ private formatRelative(timestamp: Date): string {
19
+ const now = new Date();
20
+ const diffMs = now.getTime() - timestamp.getTime();
21
+ const diffSec = Math.floor(diffMs / 1000);
22
+ const diffMin = Math.floor(diffSec / 60);
23
+ const diffHour = Math.floor(diffMin / 60);
24
+ const diffDay = Math.floor(diffHour / 24);
25
+
26
+ if (diffSec < 60) {
27
+ return ` [${diffSec}s ago]`;
28
+ }
29
+ if (diffMin < 60) {
30
+ return ` [${diffMin}m ago]`;
31
+ }
32
+ if (diffHour < 24) {
33
+ return ` [${diffHour}h ago]`;
34
+ }
35
+ return ` [${diffDay}d ago]`;
36
+ }
37
+
38
+ private formatAbsolute(timestamp: Date): string {
39
+ const year = timestamp.getFullYear();
40
+ const month = String(timestamp.getMonth() + 1).padStart(2, "0");
41
+ const day = String(timestamp.getDate()).padStart(2, "0");
42
+ const hour = String(timestamp.getHours()).padStart(2, "0");
43
+ const minute = String(timestamp.getMinutes()).padStart(2, "0");
44
+ const second = String(timestamp.getSeconds()).padStart(2, "0");
45
+
46
+ return ` [${year}-${month}-${day} ${hour}:${minute}:${second}]`;
47
+ }
48
+
49
+ private formatTimeOnly(timestamp: Date): string {
50
+ const hour = String(timestamp.getHours()).padStart(2, "0");
51
+ const minute = String(timestamp.getMinutes()).padStart(2, "0");
52
+ const second = String(timestamp.getSeconds()).padStart(2, "0");
53
+
54
+ return ` [${hour}:${minute}:${second}]`;
55
+ }
56
+ }
@@ -0,0 +1,131 @@
1
+ import { getPubkeyService } from "@/services/PubkeyService";
2
+ import type { NDKEvent } from "@nostr-dev-kit/ndk";
3
+ import type { ThreadNode } from "../ThreadedConversationFormatter";
4
+
5
+ export class TreeBuilder {
6
+ /**
7
+ * Build tree structure from flat event list
8
+ */
9
+ async buildFromEvents(events: NDKEvent[]): Promise<ThreadNode[]> {
10
+ if (events.length === 0) {
11
+ return [];
12
+ }
13
+
14
+ // Create node map indexed by event ID
15
+ const nodeMap = new Map<string, ThreadNode>();
16
+ const rootNodes: ThreadNode[] = [];
17
+
18
+ // First pass: create all nodes
19
+ for (const event of events) {
20
+ const node: ThreadNode = {
21
+ event,
22
+ agent: await this.extractAgentName(event),
23
+ timestamp: new Date((event.created_at ?? 0) * 1000),
24
+ content: event.content,
25
+ toolCall: this.extractToolCall(event),
26
+ children: [],
27
+ depth: 0,
28
+ };
29
+ nodeMap.set(event.id, node);
30
+ }
31
+
32
+ // Second pass: establish parent-child relationships
33
+ for (const event of events) {
34
+ const node = nodeMap.get(event.id);
35
+ if (!node) continue;
36
+
37
+ const parentId = this.findParentEventId(event);
38
+
39
+ if (parentId && nodeMap.has(parentId)) {
40
+ const parentNode = nodeMap.get(parentId);
41
+ if (parentNode) {
42
+ parentNode.children.push(node);
43
+ }
44
+ } else {
45
+ // No parent found, this is a root node
46
+ rootNodes.push(node);
47
+ }
48
+ }
49
+
50
+ // Third pass: calculate depths and sort children
51
+ for (const root of rootNodes) {
52
+ this.calculateDepthsAndSort(root, 0);
53
+ }
54
+
55
+ // Sort root nodes by timestamp
56
+ rootNodes.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
57
+
58
+ return rootNodes;
59
+ }
60
+
61
+ private findParentEventId(event: NDKEvent): string | null {
62
+ // Look for 'e' tags that indicate a reply
63
+ const eTags = event.tags.filter((tag) => tag[0] === "e");
64
+
65
+ // The convention is that the first 'e' tag is the root,
66
+ // and the last 'e' tag is the direct parent (if multiple e tags)
67
+ if (eTags.length === 1) {
68
+ return eTags[0][1]; // Single e tag is the parent
69
+ }
70
+ if (eTags.length > 1) {
71
+ // Check for 'reply' marker
72
+ const replyTag = eTags.find((tag) => tag[3] === "reply");
73
+ if (replyTag) {
74
+ return replyTag[1];
75
+ }
76
+ // Otherwise, last e tag is typically the direct parent
77
+ return eTags[eTags.length - 1][1];
78
+ }
79
+
80
+ return null;
81
+ }
82
+
83
+ private async extractAgentName(event: NDKEvent): Promise<string | undefined> {
84
+ // Use the PubkeyService to resolve the actual name
85
+ const nameRepo = getPubkeyService();
86
+ const name = await nameRepo.getName(event.pubkey);
87
+ return name;
88
+ }
89
+
90
+ private extractToolCall(event: NDKEvent): { name: string; args?: string } | undefined {
91
+ // Look for tool call indicators in tags
92
+ const toolTag = event.tags.find((tag) => tag[0] === "tool");
93
+ if (toolTag) {
94
+ return {
95
+ name: toolTag[1],
96
+ args: toolTag[2],
97
+ };
98
+ }
99
+
100
+ // Check if content indicates a tool call - multiple patterns
101
+ const patterns = [
102
+ /(?:calls tool|using tool|executing):\s*(\w+)(?:\(([^)]*)\))?/i,
103
+ /(?:calling tool|call):\s*(\w+)(?:\(([^)]*)\))?/i,
104
+ /(?:Now|now)\s+calling\s+tool:\s*(\w+)(?:\(([^)]*)\))?/i,
105
+ ];
106
+
107
+ for (const pattern of patterns) {
108
+ const match = event.content.match(pattern);
109
+ if (match) {
110
+ return {
111
+ name: match[1],
112
+ args: match[2],
113
+ };
114
+ }
115
+ }
116
+
117
+ return undefined;
118
+ }
119
+
120
+ private calculateDepthsAndSort(node: ThreadNode, depth: number): void {
121
+ node.depth = depth;
122
+
123
+ // Sort children by timestamp
124
+ node.children.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
125
+
126
+ // Recursively process children
127
+ for (const child of node.children) {
128
+ this.calculateDepthsAndSort(child, depth + 1);
129
+ }
130
+ }
131
+ }