@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,749 @@
1
+ import { NDKKind } from "@/nostr/kinds";
2
+ import { TagExtractor } from "@/nostr/TagExtractor";
3
+ import { formatAnyError } from "@/lib/error-formatter";
4
+ import { type NDKEvent, NDKArticle, NDKProject } from "@nostr-dev-kit/ndk";
5
+ import type { AgentProjectConfig, AgentDefaultConfig } from "@/agents/types";
6
+ import { computeToolsDelta } from "@/agents/ConfigResolver";
7
+ import { expandFsCapabilities } from "@/agents/tool-normalization";
8
+ import { agentStorage } from "../agents/AgentStorage";
9
+ import { AgentExecutor } from "../agents/execution/AgentExecutor";
10
+ import { ConversationStore } from "../conversations/ConversationStore";
11
+ import { NDKAgentLesson } from "@/events/NDKAgentLesson";
12
+ import { NDKEventMetadata } from "../events/NDKEventMetadata";
13
+ import { getProjectContext } from "@/services/projects";
14
+ import { getLocalReportStore } from "@/services/reports";
15
+ import { config } from "@/services/ConfigService";
16
+ import { RALRegistry } from "@/services/ral";
17
+ import { prefixKVStore } from "@/services/storage";
18
+ import { logger } from "../utils/logger";
19
+ import { shortenConversationId } from "@/utils/conversation-id";
20
+ import { shouldTrustLesson } from "@/utils/lessonTrust";
21
+ import { getPubkeyGateService } from "@/services/pubkey-gate";
22
+ import { handleAgentDeletion } from "./agentDeletion";
23
+ import { handleProjectEvent } from "./project";
24
+ import { handleChatMessage } from "./reply";
25
+ import { trace, context as otelContext, TraceFlags } from "@opentelemetry/api";
26
+ /**
27
+ * Index event ID and pubkey into the prefix KV store.
28
+ * Skips ephemeral events (kinds 20000-29999) since their IDs are transient.
29
+ *
30
+ * This is a best-effort operation - indexing failures are logged but do NOT
31
+ * abort event handling. The prefix index is a convenience feature, not critical.
32
+ */
33
+ async function indexEventForPrefixLookup(event: NDKEvent): Promise<void> {
34
+ const kind = event.kind ?? 0;
35
+
36
+ // Skip ephemeral events (kinds 20000-29999)
37
+ if (kind >= 20000 && kind < 30000) {
38
+ return;
39
+ }
40
+
41
+ // Index both event ID and pubkey - best effort, don't let failures bubble up
42
+ if (prefixKVStore.isInitialized()) {
43
+ try {
44
+ await prefixKVStore.addBatch([event.id, event.pubkey]);
45
+ } catch (error) {
46
+ // Log but don't abort - indexing is a sidecar feature
47
+ logger.warn("[EventHandler] Failed to index event for prefix lookup", {
48
+ eventId: event.id?.substring(0, 12),
49
+ error: error instanceof Error ? error.message : String(error),
50
+ });
51
+ }
52
+ }
53
+ }
54
+
55
+ const IGNORED_EVENT_KINDS = [
56
+ NDKKind.Metadata,
57
+ NDKKind.Contacts,
58
+ NDKKind.TenexProjectStatus,
59
+ NDKKind.TenexOperationsStatus,
60
+ ];
61
+
62
+ export class EventHandler {
63
+ private agentExecutor!: AgentExecutor;
64
+ private isUpdatingProject = false;
65
+
66
+ async initialize(): Promise<void> {
67
+ this.agentExecutor = new AgentExecutor();
68
+ }
69
+
70
+ async handleEvent(event: NDKEvent): Promise<void> {
71
+ // Ignore ephemeral status and typing indicator events
72
+ if (IGNORED_EVENT_KINDS.includes(event.kind)) return;
73
+
74
+ // PUBKEY GATE: Only allow events from trusted pubkeys (whitelisted, backend, or known agents)
75
+ // This is the front-door gate — all events must pass through before any routing occurs.
76
+ // Fail-closed: if the check errors, the event is denied.
77
+ if (!getPubkeyGateService().shouldAllowEvent(event)) {
78
+ return;
79
+ }
80
+
81
+ // Index event ID and pubkey for prefix lookups
82
+ await indexEventForPrefixLookup(event);
83
+
84
+ // EMERGENCY STOP: If a whitelisted pubkey sends "EMERGENCY STOP", exit immediately
85
+ if (event.content === "EMERGENCY STOP") {
86
+ const whitelistedPubkeys = config.getConfig().whitelistedPubkeys ?? [];
87
+ if (whitelistedPubkeys.includes(event.pubkey)) {
88
+ logger.warn("EMERGENCY STOP received from whitelisted pubkey", {
89
+ pubkey: event.pubkey.substring(0, 8),
90
+ eventId: event.id,
91
+ });
92
+ process.exit(0);
93
+ }
94
+ }
95
+
96
+ // Try to get agent slug if the event is from an agent
97
+ let fromIdentifier = event.pubkey;
98
+ let forIdentifiers = "without any recipient";
99
+
100
+ try {
101
+ const projectCtx = getProjectContext();
102
+ const agent = projectCtx.getAgentByPubkey(event.pubkey);
103
+ if (agent) {
104
+ fromIdentifier = agent.slug;
105
+ }
106
+
107
+ // Process p-tags to show agent slugs where possible
108
+ let pTags: string[][] = [];
109
+ try {
110
+ pTags = event.getMatchingTags("p");
111
+ } catch (err) {
112
+ logger.error("Failed to get p-tags - event is not a proper NDKEvent!", {
113
+ error: err,
114
+ eventType: typeof event,
115
+ eventConstructor: event?.constructor?.name,
116
+ eventPrototype: Object.getPrototypeOf(event)?.constructor?.name,
117
+ eventKeys: Object.keys(event || {}),
118
+ event: event?.rawEvent ? JSON.stringify(event.rawEvent(), null, 2) : "no rawEvent method",
119
+ });
120
+ throw err;
121
+ }
122
+ if (pTags.length > 0) {
123
+ const recipients = pTags.map((t) => {
124
+ const pubkey = t[1];
125
+ const recipientAgent = projectCtx.getAgentByPubkey(pubkey);
126
+ return recipientAgent ? recipientAgent.slug : pubkey.substring(0, 8);
127
+ });
128
+ forIdentifiers = recipients.join(", ");
129
+ }
130
+ } catch {
131
+ // Project context might not be available, continue with pubkey
132
+ let pTags: string[][];
133
+ try {
134
+ pTags = event.getMatchingTags("p");
135
+ } catch (err) {
136
+ logger.error("Failed to get p-tags (fallback) - event is not a proper NDKEvent!", {
137
+ error: err,
138
+ eventType: typeof event,
139
+ eventConstructor: event?.constructor?.name,
140
+ eventPrototype: Object.getPrototypeOf(event)?.constructor?.name,
141
+ hasGetMatchingTags: typeof event?.getMatchingTags,
142
+ eventKeys: Object.keys(event || {}),
143
+ event: event?.rawEvent ? JSON.stringify(event.rawEvent(), null, 2) : "no rawEvent method",
144
+ });
145
+ throw err;
146
+ }
147
+ if (pTags.length > 0) {
148
+ forIdentifiers = pTags.map((t) => t[1].substring(0, 8)).join(", ");
149
+ }
150
+ }
151
+
152
+ trace.getActiveSpan()?.addEvent("event_handler.received", {
153
+ "event.kind": event.kind,
154
+ "event.id": event.id,
155
+ "event.from": fromIdentifier,
156
+ "event.for": forIdentifiers,
157
+ });
158
+
159
+ switch (event.kind) {
160
+ case NDKKind.Text: // kind 1 - unified conversation format
161
+ await handleChatMessage(event, {
162
+ agentExecutor: this.agentExecutor,
163
+ });
164
+ break;
165
+
166
+ case NDKKind.TenexBootProject: // kind 24000 - project boot request
167
+ // Boot already happened by virtue of event routing - nothing to do
168
+ trace.getActiveSpan()?.addEvent("event_handler.boot_request", {
169
+ "event.id": event.id,
170
+ "event.author": event.pubkey.substring(0, 8),
171
+ });
172
+ break;
173
+
174
+ case NDKProject.kind: // kind 31933
175
+ if (this.isUpdatingProject) {
176
+ logger.warn("Project update already in progress, skipping event", {
177
+ eventId: event.id,
178
+ });
179
+ return;
180
+ }
181
+
182
+ this.isUpdatingProject = true;
183
+ try {
184
+ await handleProjectEvent(event);
185
+ } finally {
186
+ this.isUpdatingProject = false;
187
+ }
188
+ break;
189
+
190
+ case NDKKind.TenexAgentConfigUpdate:
191
+ await this.handleAgentConfigUpdate(event);
192
+ break;
193
+
194
+ case NDKKind.TenexAgentDelete:
195
+ await handleAgentDeletion(event);
196
+ break;
197
+
198
+ case 513: // NDKEventMetadata
199
+ await this.handleMetadataEvent(event);
200
+ break;
201
+
202
+ case NDKKind.TenexStopCommand: // Stop LLM operations
203
+ await this.handleStopEvent(event);
204
+ break;
205
+
206
+ case NDKKind.AgentLesson:
207
+ await this.handleLessonEvent(event);
208
+ break;
209
+
210
+ case 30023: // NDKArticle - Reports
211
+ await this.handleReportEvent(event);
212
+ break;
213
+
214
+ default:
215
+ this.handleDefaultEvent(event);
216
+ }
217
+ }
218
+
219
+ private async handleMetadataEvent(event: NDKEvent): Promise<void> {
220
+ const metadata = NDKEventMetadata.from(event);
221
+ const conversationId = metadata.conversationId;
222
+
223
+ if (!conversationId) {
224
+ logger.error("Metadata event missing conversation ID", event.inspect);
225
+ return;
226
+ }
227
+
228
+ // Only update if we know this conversation
229
+ if (ConversationStore.has(conversationId)) {
230
+ // Collect metadata updates from tags
231
+ const updates: Record<string, string | undefined> = {};
232
+
233
+ const title = metadata.title;
234
+ if (title) {
235
+ updates.title = title;
236
+ }
237
+
238
+ // Parse summary tag
239
+ const summaryTag = event.tags.find((tag: string[]) => tag[0] === "summary");
240
+ if (summaryTag && summaryTag[1]) {
241
+ updates.summary = summaryTag[1];
242
+ }
243
+
244
+ // Parse status-label tag
245
+ const statusLabelTag = event.tags.find((tag: string[]) => tag[0] === "status-label");
246
+ if (statusLabelTag && statusLabelTag[1]) {
247
+ updates.statusLabel = statusLabelTag[1];
248
+ }
249
+
250
+ // Parse status-current-activity tag
251
+ const statusActivityTag = event.tags.find((tag: string[]) => tag[0] === "status-current-activity");
252
+ if (statusActivityTag && statusActivityTag[1]) {
253
+ updates.statusCurrentActivity = statusActivityTag[1];
254
+ }
255
+
256
+ // Apply all updates at once
257
+ if (Object.keys(updates).length > 0) {
258
+ await ConversationStore.updateConversationMetadata(conversationId, updates);
259
+ logger.debug("Updated conversation metadata", {
260
+ conversationId: conversationId.substring(0, 8),
261
+ updates: Object.keys(updates),
262
+ });
263
+ }
264
+ }
265
+ }
266
+
267
+ private async handleAgentConfigUpdate(event: NDKEvent): Promise<void> {
268
+ try {
269
+ // Extract the agent pubkey from the event tags
270
+ const agentPubkey = event.tagValue("p");
271
+ if (!agentPubkey) {
272
+ logger.warn("AGENT_CONFIG_UPDATE event missing agent pubkey", {
273
+ eventId: event.id,
274
+ });
275
+ return;
276
+ }
277
+
278
+ // Get the project context early - needed for a-tag validation and agent lookup
279
+ const projectContext = getProjectContext();
280
+
281
+ // Extract the project a-tag if present
282
+ // Format: ["a", "31990:<pubkey>:<d-tag>"] or just the d-tag portion
283
+ // When present, config is scoped to that project only
284
+ const aTag = event.tagValue("a");
285
+ let projectDTag: string | undefined;
286
+ if (aTag) {
287
+ // Parse the a-tag - format is "kind:pubkey:d-tag"
288
+ const parts = aTag.split(":");
289
+ if (parts.length >= 3) {
290
+ // The d-tag is the third part (and may contain colons)
291
+ projectDTag = parts.slice(2).join(":");
292
+ } else {
293
+ // If not in standard format, treat the whole value as d-tag
294
+ projectDTag = aTag;
295
+ }
296
+
297
+ // Validate that the a-tag matches the current project
298
+ // This prevents config updates meant for other projects from being applied here
299
+ const currentProjectDTag = projectContext.project.dTag || projectContext.project.tagValue("d");
300
+ if (projectDTag !== currentProjectDTag) {
301
+ logger.debug("Ignoring project-scoped config update for different project", {
302
+ eventId: event.id,
303
+ targetProject: projectDTag,
304
+ currentProject: currentProjectDTag,
305
+ });
306
+ return;
307
+ }
308
+ }
309
+
310
+ const isProjectScoped = projectDTag !== undefined;
311
+ const agent = projectContext.getAgentByPubkey(agentPubkey);
312
+
313
+ if (!agent) {
314
+ logger.warn("Agent not found for config change", {
315
+ agentPubkey,
316
+ availableAgents: projectContext.getAgentSlugs(),
317
+ });
318
+ return;
319
+ }
320
+
321
+ // Get the agent registry from ProjectContext (single source of truth)
322
+ const agentRegistry = projectContext.agentRegistry;
323
+
324
+ // Track if any update was made
325
+ let configUpdated = false;
326
+
327
+ // Extract configuration values from the event
328
+ const newModel = event.tagValue("model");
329
+ const toolTags = TagExtractor.getToolTags(event);
330
+ const rawToolNames = toolTags.map((tool) => tool.name).filter((name) => name);
331
+ // Expand FS capability groups: fs_read implies glob+grep, fs_write implies edit
332
+ const newToolNames = expandFsCapabilities(rawToolNames);
333
+ const hasPMTag = event.tags.some((tag) => tag[0] === "pm");
334
+ const hasResetTag = event.tags.some((tag) => tag[0] === "reset");
335
+
336
+ if (isProjectScoped) {
337
+ // PROJECT-SCOPED CONFIG UPDATE (new schema)
338
+ // Uses updateProjectOverride() which handles dedup and delta tools
339
+ logger.info("Processing project-scoped agent config update", {
340
+ agentSlug: agent.slug,
341
+ projectDTag,
342
+ hasModel: !!newModel,
343
+ toolCount: newToolNames.length,
344
+ hasPM: hasPMTag,
345
+ hasReset: hasResetTag,
346
+ });
347
+
348
+ let updated: boolean;
349
+
350
+ if (hasResetTag) {
351
+ // Reset tag: clear entire project override
352
+ updated = await agentStorage.updateProjectOverride(
353
+ agentPubkey,
354
+ projectDTag!,
355
+ {},
356
+ true // reset=true
357
+ );
358
+ } else {
359
+ // Build the project-scoped override.
360
+ // Kind 24020 events carry a FULL tool list (no delta notation in events).
361
+ // The storage layer stores DELTA notation for project overrides.
362
+ // We must convert the full list from the event into a delta against defaults.
363
+ const projectOverride: AgentProjectConfig = {};
364
+
365
+ if (newModel) {
366
+ projectOverride.model = newModel;
367
+ }
368
+
369
+ // Tool tags represent the exhaustive desired list for this project.
370
+ // We convert it to a delta against the agent's default tools for compact storage.
371
+ // Use RAW TAG PRESENCE (event.tags.some) not TagExtractor output length:
372
+ // TagExtractor.getToolTags() filters out empty tool names, so when an event
373
+ // carries ["tool", ""] to "clear all tools", toolTags.length would be 0 and
374
+ // the guard would silently skip delta computation. By checking the raw event
375
+ // tags we correctly detect "tool tag is present" regardless of the name value.
376
+ const hasRawToolTags = event.tags.some((tag) => tag[0] === "tool");
377
+ if (hasRawToolTags) {
378
+ const storedAgent = await agentStorage.loadAgent(agentPubkey);
379
+ const defaultTools = storedAgent?.default?.tools ?? [];
380
+ const toolsDelta = computeToolsDelta(defaultTools, newToolNames);
381
+ // If delta is non-empty, store it. An empty delta means desired == defaults,
382
+ // so no override is needed. Note: "desired = empty list" with non-empty
383
+ // defaults produces removal entries (non-empty delta), so that IS stored.
384
+ if (toolsDelta.length > 0) {
385
+ projectOverride.tools = toolsDelta;
386
+ }
387
+ }
388
+
389
+ updated = await agentStorage.updateProjectOverride(
390
+ agentPubkey,
391
+ projectDTag!,
392
+ projectOverride
393
+ );
394
+ }
395
+
396
+ if (updated) {
397
+ await agentRegistry.reloadAgent(agentPubkey);
398
+ configUpdated = true;
399
+ logger.info("Updated project-scoped config for agent", {
400
+ agentSlug: agent.slug,
401
+ projectDTag,
402
+ reset: hasResetTag,
403
+ });
404
+ }
405
+
406
+ // PM designation is handled separately from projectOverrides.
407
+ // - reset tag: always clears project-scoped PM (full project config wipe)
408
+ // - pm tag present: sets project-scoped PM to true
409
+ // - no pm tag (and no reset): no change to PM
410
+ if (hasResetTag) {
411
+ // A reset clears ALL project config including PM designation
412
+ await agentStorage.updateProjectScopedIsPM(agentPubkey, projectDTag!, undefined);
413
+ await agentRegistry.reloadAgent(agentPubkey);
414
+ configUpdated = true;
415
+ } else if (hasPMTag) {
416
+ await agentStorage.updateProjectScopedIsPM(agentPubkey, projectDTag!, true);
417
+ await agentRegistry.reloadAgent(agentPubkey);
418
+ configUpdated = true;
419
+ }
420
+ } else {
421
+ // GLOBAL (DEFAULT) CONFIG UPDATE
422
+ // A 24020 with no a-tag writes to the agent's default config block
423
+ logger.info("Processing global agent config update", {
424
+ agentSlug: agent.slug,
425
+ hasModel: !!newModel,
426
+ toolCount: newToolNames.length,
427
+ hasPM: hasPMTag,
428
+ });
429
+
430
+ // Build the default config to write.
431
+ // Non-a-tagged 24020 events use PARTIAL UPDATE semantics:
432
+ // - Only fields explicitly present in the event are updated
433
+ // - Omitting a field means "no change" (not "clear")
434
+ // This is consistent with how project-scoped overrides work.
435
+ // Note: PM designation uses authoritative snapshot semantics (absence clears it)
436
+ // because it's a boolean flag, not a config value.
437
+ const defaultUpdates: AgentDefaultConfig = {};
438
+
439
+ // Only update model if a model tag is explicitly present AND has a non-empty value.
440
+ // event.tagValue("model") returns "" for ["model", ""], which would persist an
441
+ // empty string into agent.default.model if not guarded. We treat an empty model
442
+ // tag as a no-op so clients can safely omit/blank the model without clearing it.
443
+ const hasModelTag = event.tags.some((tag) => tag[0] === "model");
444
+ if (hasModelTag && newModel) {
445
+ defaultUpdates.model = newModel;
446
+ }
447
+ // If no model tag (or empty value), leave defaultUpdates.model unset → no change
448
+
449
+ // Only update tools if tool tags are explicitly present in the event
450
+ const hasToolTags = event.tags.some((tag) => tag[0] === "tool");
451
+ if (hasToolTags) {
452
+ // newToolNames may be empty (e.g. tool tags with no values), which clears defaults
453
+ defaultUpdates.tools = newToolNames;
454
+ }
455
+ // If no tool tags, leave defaultUpdates.tools unset → no change
456
+
457
+ // Global config update clears all project overrides
458
+ // This makes semantic sense: a global config update without project specifier
459
+ // resets the agent to have no project-specific overrides
460
+ const defaultUpdated = await agentStorage.updateDefaultConfig(agentPubkey, defaultUpdates, { clearProjectOverrides: true });
461
+
462
+ if (defaultUpdated) {
463
+ await agentRegistry.reloadAgent(agentPubkey);
464
+ configUpdated = true;
465
+ } else {
466
+ logger.warn("Failed to update default config", {
467
+ agentName: agent.slug,
468
+ agentPubkey: agent.pubkey,
469
+ });
470
+ }
471
+
472
+ // Check for PM designation tag: ["pm"] (no value, just the tag itself)
473
+ // Kind 24020 events are authoritative snapshots - presence of ["pm"] tag sets isPM=true,
474
+ // absence clears it (sets isPM=false).
475
+ const pmUpdated = await agentStorage.updateAgentIsPM(agentPubkey, hasPMTag);
476
+
477
+ if (pmUpdated) {
478
+ await agentRegistry.reloadAgent(agentPubkey);
479
+ configUpdated = true;
480
+ logger.info(hasPMTag ? "Set PM designation for agent via kind 24020 event" : "Cleared PM designation for agent via kind 24020 event", {
481
+ agentSlug: agent.slug,
482
+ agentPubkey: agentPubkey.substring(0, 8),
483
+ });
484
+ } else {
485
+ logger.warn("Failed to update PM designation", {
486
+ agentSlug: agent.slug,
487
+ agentPubkey: agentPubkey.substring(0, 8),
488
+ newValue: hasPMTag,
489
+ });
490
+ }
491
+ }
492
+
493
+ // Immediately publish updated project status if config was changed
494
+ if (configUpdated && projectContext.statusPublisher) {
495
+ await projectContext.statusPublisher.publishImmediately();
496
+ logger.info("Published updated project status after agent config change", {
497
+ agentSlug: agent.slug,
498
+ agentPubkey: agentPubkey.substring(0, 8),
499
+ projectScoped: isProjectScoped,
500
+ projectDTag,
501
+ });
502
+ }
503
+ } catch (error) {
504
+ logger.error("Failed to handle config change", {
505
+ eventId: event.id,
506
+ error: formatAnyError(error),
507
+ });
508
+ }
509
+ }
510
+
511
+ private async handleStopEvent(event: NDKEvent): Promise<void> {
512
+ const eTags = event.getMatchingTags("e");
513
+ const pTags = event.getMatchingTags("p");
514
+
515
+ if (eTags.length === 0) {
516
+ logger.warn("[EventHandler] Stop event received with no e-tags", {
517
+ eventId: event.id?.substring(0, 8),
518
+ });
519
+ return;
520
+ }
521
+
522
+ let ralsAborted = 0;
523
+
524
+ const projectCtx = getProjectContext();
525
+ const ralRegistry = RALRegistry.getInstance();
526
+ const stopTracer = trace.getTracer("tenex.event-handler");
527
+ const reason = `stop signal from ${event.pubkey.substring(0, 8)}`;
528
+
529
+ for (const [, conversationId] of eTags) {
530
+ const conversation = ConversationStore.get(conversationId);
531
+ if (!conversation) continue;
532
+ const projectId = conversation.getProjectId();
533
+ if (!projectId) continue;
534
+
535
+ for (const [, agentPubkey] of pTags) {
536
+ const agent = projectCtx.getAgentByPubkey(agentPubkey);
537
+ if (agent) {
538
+ // Get the RAL's trace context to parent the stop span under agent execution
539
+ const activeRals = ralRegistry.getActiveRALs(agentPubkey, conversationId);
540
+ const targetRal = activeRals[0];
541
+
542
+ // Build parent context from stored trace info
543
+ let parentContext = otelContext.active();
544
+ if (targetRal?.traceId && targetRal?.executionSpanId) {
545
+ const parentSpanContext = {
546
+ traceId: targetRal.traceId,
547
+ spanId: targetRal.executionSpanId,
548
+ traceFlags: TraceFlags.SAMPLED,
549
+ isRemote: false,
550
+ };
551
+ parentContext = trace.setSpanContext(otelContext.active(), parentSpanContext);
552
+ }
553
+
554
+ const stopSpan = stopTracer.startSpan(
555
+ "tenex.stop_command",
556
+ {
557
+ attributes: {
558
+ "event.id": event.id,
559
+ "event.kind": event.kind,
560
+ "event.author": event.pubkey.substring(0, 8),
561
+ "stop.agent_slug": agent.slug,
562
+ "stop.agent_pubkey": agentPubkey.substring(0, 8),
563
+ "stop.conversation_id": shortenConversationId(conversationId),
564
+ "stop.active_rals": activeRals.length,
565
+ },
566
+ },
567
+ parentContext
568
+ );
569
+
570
+ const result = await ralRegistry.abortWithCascade(
571
+ agentPubkey, conversationId, projectId, reason
572
+ );
573
+ ralsAborted += result.abortedCount;
574
+
575
+ stopSpan.setAttribute("stop.rals_aborted", result.abortedCount);
576
+ stopSpan.setAttribute("stop.descendants_aborted", result.descendantConversations.length);
577
+ stopSpan.end();
578
+
579
+ logger.info(`[EventHandler] Stopped agent ${agent.slug} in conversation ${conversationId.substring(0, 8)}`, {
580
+ ralsAborted: result.abortedCount,
581
+ descendantsAborted: result.descendantConversations.length,
582
+ });
583
+ }
584
+ }
585
+ }
586
+
587
+ trace.getActiveSpan()?.addEvent("event_handler.stop_operations", {
588
+ "rals.aborted": ralsAborted,
589
+ });
590
+ }
591
+
592
+ private async handleLessonEvent(event: NDKEvent): Promise<void> {
593
+ const lesson = NDKAgentLesson.from(event);
594
+
595
+ // Check if we should trust this lesson
596
+ if (!shouldTrustLesson(lesson, event.pubkey)) {
597
+ return;
598
+ }
599
+
600
+ const agentDefinitionId = lesson.agentDefinitionId;
601
+
602
+ if (!agentDefinitionId) {
603
+ logger.warn("Lesson event missing agent definition ID (e-tag)", {
604
+ eventId: event.id?.substring(0, 8),
605
+ publisher: event.pubkey.substring(0, 8),
606
+ });
607
+ return;
608
+ }
609
+
610
+ try {
611
+ const projectCtx = getProjectContext();
612
+
613
+ // Find the agent(s) that match this definition ID
614
+ const agents = Array.from(projectCtx.agents.values()).filter(
615
+ (agent) => agent.eventId === agentDefinitionId
616
+ );
617
+
618
+ if (agents.length === 0) {
619
+ return;
620
+ }
621
+
622
+ // Store the lesson for each matching agent
623
+ for (const agent of agents) {
624
+ projectCtx.addLesson(agent.pubkey, lesson);
625
+ }
626
+ } catch (error) {
627
+ logger.error("Failed to handle lesson event", {
628
+ eventId: event.id,
629
+ error: formatAnyError(error),
630
+ });
631
+ }
632
+ }
633
+
634
+ private async handleReportEvent(event: NDKEvent): Promise<void> {
635
+ try {
636
+ const projectCtx = getProjectContext();
637
+
638
+ // Verify this report belongs to our project by checking a-tag
639
+ const projectTagId = projectCtx.project.tagId();
640
+ const reportProjectTag = event.tags.find(
641
+ (tag: string[]) => tag[0] === "a" && tag[1] === projectTagId
642
+ );
643
+
644
+ if (!reportProjectTag) {
645
+ // Report doesn't belong to our project, ignore
646
+ return;
647
+ }
648
+
649
+ // Convert to NDKArticle
650
+ const article = NDKArticle.from(event);
651
+
652
+ // Check if report is deleted
653
+ const isDeleted = article.tags.some((tag: string[]) => tag[0] === "deleted");
654
+
655
+ // Add to project context cache
656
+ projectCtx.addReportFromArticle(article);
657
+
658
+ // Hydrate local storage if this event is newer than local copy
659
+ // Skip deleted reports - we don't want to hydrate them
660
+ if (!isDeleted && article.dTag && article.content) {
661
+ const localStore = getLocalReportStore();
662
+ const eventCreatedAt = event.created_at || Math.floor(Date.now() / 1000);
663
+
664
+ // Format content for local storage (matching report_write format)
665
+ const formattedContent = this.formatReportForLocalStorage(article);
666
+
667
+ // Construct addressable reference in NIP-33 format: kind:pubkey:d-tag
668
+ const addressableRef = `${event.kind}:${event.pubkey}:${article.dTag}`;
669
+
670
+ const hydrated = await localStore.hydrateFromNostr(
671
+ article.dTag,
672
+ formattedContent,
673
+ addressableRef,
674
+ eventCreatedAt
675
+ );
676
+
677
+ if (hydrated) {
678
+ trace.getActiveSpan()?.addEvent("event_handler.report_hydrated", {
679
+ "report.slug": article.dTag,
680
+ "report.addressableRef": addressableRef.substring(0, 20),
681
+ });
682
+ }
683
+ }
684
+
685
+ trace.getActiveSpan()?.addEvent("event_handler.report_cached", {
686
+ "report.slug": article.dTag || "",
687
+ "report.author": event.pubkey.substring(0, 8),
688
+ "report.isDeleted": isDeleted,
689
+ "report.isMemorized": article.tags.some(
690
+ (tag: string[]) => tag[0] === "t" && tag[1] === "memorize"
691
+ ),
692
+ });
693
+ } catch (error) {
694
+ logger.error("Failed to handle report event", {
695
+ eventId: event.id,
696
+ error: formatAnyError(error),
697
+ });
698
+ }
699
+ }
700
+
701
+ /**
702
+ * Format an NDKArticle for local storage
703
+ */
704
+ private formatReportForLocalStorage(article: NDKArticle): string {
705
+ const lines: string[] = [];
706
+
707
+ // Add title
708
+ if (article.title) {
709
+ lines.push(`# ${article.title}`);
710
+ lines.push("");
711
+ }
712
+
713
+ // Add summary
714
+ if (article.summary) {
715
+ lines.push(`> ${article.summary}`);
716
+ lines.push("");
717
+ }
718
+
719
+ // Extract hashtags (excluding memorize tag)
720
+ const hashtags = article.tags
721
+ .filter((tag: string[]) => tag[0] === "t" && tag[1] !== "memorize")
722
+ .map((tag: string[]) => tag[1]);
723
+
724
+ if (hashtags.length > 0) {
725
+ lines.push(`**Tags:** ${hashtags.map((t) => `#${t}`).join(" ")}`);
726
+ lines.push("");
727
+ }
728
+
729
+ lines.push("---");
730
+ lines.push("");
731
+
732
+ // Add content
733
+ if (article.content) {
734
+ lines.push(article.content);
735
+ }
736
+
737
+ return lines.join("\n");
738
+ }
739
+
740
+ private handleDefaultEvent(_event: NDKEvent): void {
741
+ // Unhandled event kinds are ignored silently
742
+ }
743
+
744
+ async cleanup(): Promise<void> {
745
+ // Save all conversations before shutting down
746
+ await ConversationStore.cleanup();
747
+ }
748
+
749
+ }