@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,354 @@
1
+ import { NDKKind } from "@/nostr/kinds";
2
+ import { getNDK } from "@/nostr/ndkClient";
3
+ import { config } from "@/services/ConfigService";
4
+ import { Nip46SigningService, Nip46SigningLog } from "@/services/nip46";
5
+ import { logger } from "@/utils/logger";
6
+ import { NDKEvent, NDKPrivateKeySigner, type NDKSubscription } from "@nostr-dev-kit/ndk";
7
+
8
+ const DEBOUNCE_MS = 5000;
9
+
10
+ /**
11
+ * Global singleton that owns the in-memory truth of each owner's kind:14199 p-tags.
12
+ *
13
+ * Projects call `registerAgents(projectDTag, pubkeys)` — the service handles
14
+ * deduplication, debouncing, and publishing. Per-project tracking enables
15
+ * `unregisterAgent` to remove pubkeys only when no project references them.
16
+ *
17
+ * Pending additions survive relay echo events to prevent the race condition
18
+ * where a relay event clobbers locally-queued-but-not-yet-published pubkeys.
19
+ *
20
+ * Follows the same singleton + subscription pattern as NudgeSkillWhitelistService.
21
+ */
22
+ export class OwnerAgentListService {
23
+ private static instance: OwnerAgentListService;
24
+
25
+ /** Owner pubkeys to manage (from config) */
26
+ private ownerPubkeys: string[] = [];
27
+
28
+ /** Per-owner in-memory set of agent pubkeys — source of truth */
29
+ private ownerAgentSets: Map<string, Set<string>> = new Map();
30
+
31
+ /** Locally-added pubkeys not yet confirmed by relay echo — survives handleIncomingEvent */
32
+ private pendingAdditions: Set<string> = new Set();
33
+
34
+ /** Per-agent tracking: agentPubkey → set of projectDTags that contributed it */
35
+ private agentProjectSources: Map<string, Set<string>> = new Map();
36
+
37
+ /** Per-owner latest 14199 event from subscription (replaceable semantics) */
38
+ private latestEvents: Map<string, NDKEvent> = new Map();
39
+
40
+ /** Owners needing a publish */
41
+ private pendingOwners: Set<string> = new Set();
42
+
43
+ /** Debounce timer for coalescing rapid registerAgents calls */
44
+ private debounceTimer: ReturnType<typeof setTimeout> | null = null;
45
+
46
+ /** Always-on subscription for kind:14199 */
47
+ private subscription: NDKSubscription | null = null;
48
+
49
+ private initialized = false;
50
+
51
+ private constructor() {}
52
+
53
+ static getInstance(): OwnerAgentListService {
54
+ if (!OwnerAgentListService.instance) {
55
+ OwnerAgentListService.instance = new OwnerAgentListService();
56
+ }
57
+ return OwnerAgentListService.instance;
58
+ }
59
+
60
+ /**
61
+ * Initialize with owner pubkeys and start relay subscription.
62
+ * Returns immediately — relay replay populates initial state.
63
+ */
64
+ initialize(ownerPubkeys: string[]): void {
65
+ if (this.initialized) {
66
+ logger.debug("[OwnerAgentListService] Already initialized, skipping");
67
+ return;
68
+ }
69
+
70
+ this.ownerPubkeys = ownerPubkeys;
71
+ for (const pk of ownerPubkeys) {
72
+ this.ownerAgentSets.set(pk, new Set());
73
+ }
74
+ this.initialized = true;
75
+
76
+ this.startSubscription();
77
+
78
+ logger.info("[OwnerAgentListService] Initialized", {
79
+ ownerCount: ownerPubkeys.length,
80
+ });
81
+ }
82
+
83
+ /**
84
+ * Register agent pubkeys into all owners' 14199 lists.
85
+ * Tracks per-project contributions so removal is possible via unregisterAgent.
86
+ * Debounces publish to coalesce rapid calls.
87
+ */
88
+ registerAgents(projectDTag: string, agentPubkeys: string[]): void {
89
+ if (!this.initialized) {
90
+ logger.warn("[OwnerAgentListService] Not initialized, ignoring registerAgents call");
91
+ return;
92
+ }
93
+
94
+ let anyNew = false;
95
+
96
+ for (const pk of agentPubkeys) {
97
+ // Track project source
98
+ let sources = this.agentProjectSources.get(pk);
99
+ if (!sources) {
100
+ sources = new Set();
101
+ this.agentProjectSources.set(pk, sources);
102
+ }
103
+ sources.add(projectDTag);
104
+
105
+ for (const owner of this.ownerPubkeys) {
106
+ const set = this.ownerAgentSets.get(owner);
107
+ if (!set) continue;
108
+
109
+ if (!set.has(pk)) {
110
+ set.add(pk);
111
+ anyNew = true;
112
+ this.pendingOwners.add(owner);
113
+ }
114
+ }
115
+
116
+ // Track as pending until relay echo confirms
117
+ this.pendingAdditions.add(pk);
118
+ }
119
+
120
+ if (!anyNew) {
121
+ logger.debug("[OwnerAgentListService] All agent pubkeys already registered, skipping");
122
+ return;
123
+ }
124
+
125
+ this.resetDebounce();
126
+ }
127
+
128
+ /**
129
+ * Unregister an agent from a specific project.
130
+ * Only removes the pubkey from 14199 when no other projects reference it.
131
+ */
132
+ unregisterAgent(projectDTag: string, agentPubkey: string): void {
133
+ if (!this.initialized) {
134
+ logger.warn("[OwnerAgentListService] Not initialized, ignoring unregisterAgent call");
135
+ return;
136
+ }
137
+
138
+ const sources = this.agentProjectSources.get(agentPubkey);
139
+ if (!sources) return;
140
+
141
+ sources.delete(projectDTag);
142
+
143
+ if (sources.size > 0) return;
144
+
145
+ // No projects reference this agent — remove everywhere
146
+ this.agentProjectSources.delete(agentPubkey);
147
+ this.pendingAdditions.delete(agentPubkey);
148
+
149
+ for (const owner of this.ownerPubkeys) {
150
+ const set = this.ownerAgentSets.get(owner);
151
+ if (!set) continue;
152
+
153
+ if (set.delete(agentPubkey)) {
154
+ this.pendingOwners.add(owner);
155
+ }
156
+ }
157
+
158
+ if (this.pendingOwners.size > 0) {
159
+ this.resetDebounce();
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Stop subscription, clear debounce timer, clear all state.
165
+ */
166
+ shutdown(): void {
167
+ if (this.subscription) {
168
+ this.subscription.stop();
169
+ this.subscription = null;
170
+ }
171
+ if (this.debounceTimer) {
172
+ clearTimeout(this.debounceTimer);
173
+ this.debounceTimer = null;
174
+ }
175
+ this.ownerAgentSets.clear();
176
+ this.pendingAdditions.clear();
177
+ this.agentProjectSources.clear();
178
+ this.latestEvents.clear();
179
+ this.pendingOwners.clear();
180
+ this.ownerPubkeys = [];
181
+ this.initialized = false;
182
+ }
183
+
184
+ private startSubscription(): void {
185
+ if (this.ownerPubkeys.length === 0) {
186
+ logger.debug("[OwnerAgentListService] No owner pubkeys, skipping subscription");
187
+ return;
188
+ }
189
+
190
+ const ndk = getNDK();
191
+
192
+ this.subscription = ndk.subscribe(
193
+ {
194
+ kinds: [NDKKind.ProjectAgentSnapshot as number],
195
+ authors: this.ownerPubkeys,
196
+ },
197
+ {
198
+ closeOnEose: false,
199
+ onEvent: (event: NDKEvent) => {
200
+ this.handleIncomingEvent(event);
201
+ },
202
+ },
203
+ );
204
+
205
+ logger.debug("[OwnerAgentListService] Started 14199 subscription", {
206
+ ownerCount: this.ownerPubkeys.length,
207
+ });
208
+ }
209
+
210
+ /**
211
+ * Handle incoming 14199 event from relay.
212
+ * Replaceable semantics: only process if newer than stored event.
213
+ * Replaces in-memory set with relay state to stay in sync.
214
+ */
215
+ private handleIncomingEvent(event: NDKEvent): void {
216
+ const existing = this.latestEvents.get(event.pubkey);
217
+ if (
218
+ existing &&
219
+ existing.created_at !== undefined &&
220
+ event.created_at !== undefined &&
221
+ existing.created_at >= event.created_at
222
+ ) {
223
+ return;
224
+ }
225
+
226
+ this.latestEvents.set(event.pubkey, event);
227
+
228
+ // Merge relay state with locally-pending additions to avoid clobbering
229
+ const ptagPubkeys = event.tags
230
+ .filter((t) => t[0] === "p" && t[1])
231
+ .map((t) => t[1]);
232
+
233
+ const merged = new Set(ptagPubkeys);
234
+ for (const pk of this.pendingAdditions) {
235
+ merged.add(pk);
236
+ }
237
+ this.ownerAgentSets.set(event.pubkey, merged);
238
+
239
+ logger.debug("[OwnerAgentListService] Updated from relay event", {
240
+ ownerPubkey: event.pubkey.substring(0, 12),
241
+ pTagCount: ptagPubkeys.length,
242
+ pendingCount: this.pendingAdditions.size,
243
+ });
244
+ }
245
+
246
+ private resetDebounce(): void {
247
+ if (this.debounceTimer) {
248
+ clearTimeout(this.debounceTimer);
249
+ }
250
+
251
+ this.debounceTimer = setTimeout(() => {
252
+ this.debounceTimer = null;
253
+ this.publishPendingUpdates().catch((error) => {
254
+ logger.warn("[OwnerAgentListService] Debounced publish failed", {
255
+ error: error instanceof Error ? error.message : String(error),
256
+ });
257
+ });
258
+ }, DEBOUNCE_MS);
259
+ }
260
+
261
+ /**
262
+ * Publish 14199 events for all pending owners.
263
+ */
264
+ private async publishPendingUpdates(): Promise<void> {
265
+ const owners = Array.from(this.pendingOwners);
266
+ this.pendingOwners.clear();
267
+ this.pendingAdditions.clear();
268
+
269
+ if (owners.length === 0) return;
270
+
271
+ const nip46Service = Nip46SigningService.getInstance();
272
+ const useNip46 = nip46Service.isEnabled();
273
+
274
+ for (const owner of owners) {
275
+ const agentPubkeys = this.ownerAgentSets.get(owner);
276
+ if (!agentPubkeys) continue;
277
+
278
+ const allPubkeys = Array.from(agentPubkeys);
279
+ const ndk = getNDK();
280
+ const ev = new NDKEvent(ndk, {
281
+ kind: NDKKind.ProjectAgentSnapshot,
282
+ });
283
+
284
+ for (const pk of allPubkeys) {
285
+ ev.tag(["p", pk]);
286
+ }
287
+
288
+ if (useNip46) {
289
+ await this.publishWithNip46(owner, ev, allPubkeys.length, nip46Service);
290
+ } else {
291
+ await this.publishWithBackendKey(ev, allPubkeys.length);
292
+ }
293
+ }
294
+ }
295
+
296
+ private async publishWithNip46(
297
+ ownerPubkey: string,
298
+ ev: NDKEvent,
299
+ pTagCount: number,
300
+ nip46Service: Nip46SigningService,
301
+ ): Promise<void> {
302
+ const signingLog = Nip46SigningLog.getInstance();
303
+ const result = await nip46Service.signEvent(ownerPubkey, ev, "14199_snapshot");
304
+
305
+ if (result.outcome === "signed") {
306
+ try {
307
+ await ev.publish();
308
+ signingLog.log({
309
+ op: "event_published",
310
+ ownerPubkey: Nip46SigningLog.truncatePubkey(ownerPubkey),
311
+ eventKind: NDKKind.ProjectAgentSnapshot as number,
312
+ signerType: "nip46",
313
+ pTagCount,
314
+ eventId: ev.id,
315
+ });
316
+ logger.info("[OwnerAgentListService] Published owner-signed 14199", {
317
+ ownerPubkey: ownerPubkey.substring(0, 12),
318
+ eventId: ev.id?.substring(0, 12),
319
+ pTagCount,
320
+ });
321
+ } catch (error) {
322
+ logger.warn("[OwnerAgentListService] Failed to publish owner-signed 14199", {
323
+ ownerPubkey: ownerPubkey.substring(0, 12),
324
+ error: error instanceof Error ? error.message : String(error),
325
+ });
326
+ }
327
+ return;
328
+ }
329
+
330
+ logger.warn("[OwnerAgentListService] Skipping 14199 publish — signing failed", {
331
+ ownerPubkey: ownerPubkey.substring(0, 12),
332
+ outcome: result.outcome,
333
+ reason: "reason" in result ? result.reason : undefined,
334
+ });
335
+ }
336
+
337
+ private async publishWithBackendKey(ev: NDKEvent, pTagCount: number): Promise<void> {
338
+ const tenexNsec = await config.ensureBackendPrivateKey();
339
+ const signer = new NDKPrivateKeySigner(tenexNsec);
340
+
341
+ await ev.sign(signer);
342
+ try {
343
+ await ev.publish();
344
+ logger.info("[OwnerAgentListService] Published backend-signed 14199", {
345
+ pTagCount,
346
+ });
347
+ } catch (error) {
348
+ logger.warn("[OwnerAgentListService] Failed to publish backend-signed 14199", {
349
+ error: error instanceof Error ? error.message : String(error),
350
+ eventId: ev.id?.substring(0, 12),
351
+ });
352
+ }
353
+ }
354
+ }
@@ -0,0 +1,308 @@
1
+ import { getNDK } from "@/nostr";
2
+ import { getProjectContext } from "@/services/projects";
3
+ import { logger } from "@/utils/logger";
4
+ import { PREFIX_LENGTH } from "@/utils/nostr-entity-parser";
5
+ import type { Hexpubkey, NDKEvent } from "@nostr-dev-kit/ndk";
6
+ import { trace } from "@opentelemetry/api";
7
+
8
+ const tracer = trace.getTracer("tenex.pubkey-service");
9
+ // Note: get_name and fetch_profile spans removed as they are trivial O(1) lookups
10
+ // that add noise without debugging value. The warmUserProfiles span is kept for batch operations.
11
+
12
+ interface UserProfile {
13
+ name?: string;
14
+ display_name?: string;
15
+ username?: string;
16
+ about?: string;
17
+ picture?: string;
18
+ fetchedAt: number;
19
+ }
20
+
21
+ interface CacheEntry {
22
+ profile: UserProfile;
23
+ ttl: number;
24
+ }
25
+
26
+ /**
27
+ * Central repository for mapping pubkeys to human-readable names.
28
+ * Handles both agent pubkeys (mapped to slugs) and user pubkeys (fetched from kind:0 events).
29
+ */
30
+ export class PubkeyService {
31
+ private static instance: PubkeyService;
32
+
33
+ private userProfileCache: Map<Hexpubkey, CacheEntry> = new Map();
34
+ private readonly CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes
35
+
36
+ private constructor() {}
37
+
38
+ /**
39
+ * Get singleton instance
40
+ */
41
+ static getInstance(): PubkeyService {
42
+ if (!PubkeyService.instance) {
43
+ PubkeyService.instance = new PubkeyService();
44
+ }
45
+ return PubkeyService.instance;
46
+ }
47
+
48
+ /**
49
+ * Get a display name for any pubkey (agent or user)
50
+ */
51
+ async getName(pubkey: Hexpubkey): Promise<string> {
52
+ // First, check if it's an agent (O(1) lookup)
53
+ const agentSlug = this.getAgentSlug(pubkey);
54
+ if (agentSlug) {
55
+ return agentSlug;
56
+ }
57
+
58
+ // It's a user - fetch their profile
59
+ const profile = await this.getUserProfile(pubkey);
60
+ return this.extractDisplayName(profile, pubkey);
61
+ }
62
+
63
+ /**
64
+ * Get a display name synchronously (uses cache only, no fetching)
65
+ */
66
+ getNameSync(pubkey: Hexpubkey): string {
67
+ // First, check if it's an agent
68
+ const agentSlug = this.getAgentSlug(pubkey);
69
+ if (agentSlug) {
70
+ return agentSlug;
71
+ }
72
+
73
+ // Check cache for user profile
74
+ const cached = this.userProfileCache.get(pubkey);
75
+ if (cached && Date.now() < cached.ttl) {
76
+ return this.extractDisplayName(cached.profile, pubkey);
77
+ }
78
+
79
+ // Return shortened pubkey as fallback using shared PREFIX_LENGTH constant
80
+ return pubkey.substring(0, PREFIX_LENGTH);
81
+ }
82
+
83
+ /**
84
+ * Get agent slug for a pubkey if it belongs to an agent.
85
+ * Uses AgentRegistry's getAgentByPubkey for efficient O(1) lookup.
86
+ */
87
+ private getAgentSlug(pubkey: Hexpubkey): string | undefined {
88
+ const projectCtx = getProjectContext();
89
+
90
+ // Use direct pubkey lookup from AgentRegistry (O(1) instead of O(n))
91
+ const agent = projectCtx.getAgentByPubkey(pubkey);
92
+ if (agent) {
93
+ return agent.slug;
94
+ }
95
+
96
+ return undefined;
97
+ }
98
+
99
+ /**
100
+ * Fetch user profile from kind:0 event
101
+ */
102
+ private async getUserProfile(pubkey: Hexpubkey): Promise<UserProfile> {
103
+ // Check cache first (no span needed for cache hits - trivial operation)
104
+ const cached = this.userProfileCache.get(pubkey);
105
+ if (cached && Date.now() < cached.ttl) {
106
+ return cached.profile;
107
+ }
108
+
109
+ // Cache miss - fetch from network
110
+ try {
111
+ const ndk = getNDK();
112
+
113
+ // Fetch kind:0 (metadata) event for this pubkey
114
+ const profileEvent = await ndk.fetchEvent({
115
+ kinds: [0],
116
+ authors: [pubkey],
117
+ });
118
+
119
+ if (profileEvent) {
120
+ const profile = this.parseProfileEvent(profileEvent);
121
+
122
+ // Cache the result
123
+ this.userProfileCache.set(pubkey, {
124
+ profile,
125
+ ttl: Date.now() + this.CACHE_TTL_MS,
126
+ });
127
+
128
+ logger.debug("[PUBKEY_NAME_REPO] Fetched user profile", {
129
+ pubkey,
130
+ name: profile.name,
131
+ display_name: profile.display_name,
132
+ });
133
+
134
+ return profile;
135
+ }
136
+ } catch (error) {
137
+ logger.warn("[PUBKEY_NAME_REPO] Failed to fetch user profile", {
138
+ pubkey,
139
+ error,
140
+ });
141
+ }
142
+
143
+ // Return empty profile if fetch failed
144
+ const emptyProfile: UserProfile = { fetchedAt: Date.now() };
145
+
146
+ // Cache even empty results to avoid repeated failed fetches
147
+ this.userProfileCache.set(pubkey, {
148
+ profile: emptyProfile,
149
+ ttl: Date.now() + this.CACHE_TTL_MS,
150
+ });
151
+
152
+ return emptyProfile;
153
+ }
154
+
155
+ /**
156
+ * Parse profile data from kind:0 event
157
+ */
158
+ private parseProfileEvent(event: NDKEvent): UserProfile {
159
+ try {
160
+ const content = JSON.parse(event.content);
161
+ return {
162
+ name: content.name,
163
+ display_name: content.display_name,
164
+ username: content.username,
165
+ about: content.about,
166
+ picture: content.picture,
167
+ fetchedAt: Date.now(),
168
+ };
169
+ } catch (error) {
170
+ logger.warn("[PUBKEY_NAME_REPO] Failed to parse profile content", {
171
+ eventId: event.id,
172
+ error,
173
+ });
174
+ return { fetchedAt: Date.now() };
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Extract the best display name from a profile
180
+ * @param profile The user profile to extract a name from
181
+ * @param pubkeyFallback Optional pubkey to use as fallback if no name is found
182
+ */
183
+ private extractDisplayName(profile: UserProfile, pubkeyFallback?: string): string {
184
+ // Priority: name > display_name > username > shortened pubkey
185
+ if (profile.name?.trim()) {
186
+ return profile.name.trim();
187
+ }
188
+ if (profile.display_name?.trim()) {
189
+ return profile.display_name.trim();
190
+ }
191
+ if (profile.username?.trim()) {
192
+ return profile.username.trim();
193
+ }
194
+ // Fallback to shortened pubkey if available
195
+ if (pubkeyFallback) {
196
+ return pubkeyFallback.substring(0, PREFIX_LENGTH);
197
+ }
198
+ // This shouldn't happen as callers should provide pubkeyFallback
199
+ return "Unknown";
200
+ }
201
+
202
+ /**
203
+ * Force refresh a user's profile (bypass cache)
204
+ */
205
+ async refreshUserProfile(pubkey: Hexpubkey): Promise<UserProfile> {
206
+ // Remove from cache to force fresh fetch
207
+ this.userProfileCache.delete(pubkey);
208
+ return this.getUserProfile(pubkey);
209
+ }
210
+
211
+ private readonly MAX_CONCURRENT_FETCHES = 10;
212
+
213
+ /**
214
+ * Warm the cache for multiple user pubkeys.
215
+ * This pre-fetches kind:0 profiles so that getNameSync() returns real names
216
+ * instead of shortened pubkeys.
217
+ *
218
+ * @param pubkeys Array of pubkeys to warm the cache for
219
+ * @returns Map of pubkey to resolved display name
220
+ */
221
+ async warmUserProfiles(pubkeys: Hexpubkey[]): Promise<Map<Hexpubkey, string>> {
222
+ return tracer.startActiveSpan("tenex.pubkey.warm_profiles", async (span) => {
223
+ try {
224
+ const results = new Map<Hexpubkey, string>();
225
+
226
+ // Deduplicate pubkeys and filter out agent pubkeys (they don't need profile warming)
227
+ const uniquePubkeys = [...new Set(pubkeys)];
228
+ const userPubkeys = uniquePubkeys.filter((pk) => !this.getAgentSlug(pk));
229
+
230
+ span.setAttribute("pubkey.count", pubkeys.length);
231
+ span.setAttribute("user_pubkey.count", userPubkeys.length);
232
+ span.setAttribute("batch.size", this.MAX_CONCURRENT_FETCHES);
233
+
234
+ if (userPubkeys.length === 0) {
235
+ span.setAttribute("profiles.warmed_count", 0);
236
+ return results;
237
+ }
238
+
239
+ logger.debug("[PUBKEY_SERVICE] Warming user profile cache", {
240
+ count: userPubkeys.length,
241
+ dedupedFrom: pubkeys.length,
242
+ });
243
+
244
+ // Fetch in batches to avoid thundering-herd fetches
245
+ let batchNumber = 0;
246
+ for (let i = 0; i < userPubkeys.length; i += this.MAX_CONCURRENT_FETCHES) {
247
+ batchNumber++;
248
+ const batch = userPubkeys.slice(i, i + this.MAX_CONCURRENT_FETCHES);
249
+
250
+ await tracer.startActiveSpan("tenex.pubkey.warm_batch", async (batchSpan) => {
251
+ try {
252
+ batchSpan.setAttribute("batch.number", batchNumber);
253
+ batchSpan.setAttribute("batch.size", batch.length);
254
+
255
+ await Promise.all(
256
+ batch.map(async (pubkey) => {
257
+ try {
258
+ const name = await this.getName(pubkey);
259
+ results.set(pubkey, name);
260
+ } catch (error) {
261
+ logger.warn("[PUBKEY_SERVICE] Failed to warm profile", {
262
+ pubkey: pubkey.substring(0, PREFIX_LENGTH),
263
+ error: error instanceof Error ? error.message : String(error),
264
+ });
265
+ results.set(pubkey, pubkey.substring(0, PREFIX_LENGTH));
266
+ }
267
+ })
268
+ );
269
+ } finally {
270
+ batchSpan.end();
271
+ }
272
+ });
273
+ }
274
+
275
+ logger.debug("[PUBKEY_SERVICE] Profile cache warmed", {
276
+ count: results.size,
277
+ });
278
+
279
+ span.setAttribute("profiles.warmed_count", results.size);
280
+ return results;
281
+ } finally {
282
+ span.end();
283
+ }
284
+ });
285
+ }
286
+
287
+ /**
288
+ * Clear the entire cache
289
+ */
290
+ clearCache(): void {
291
+ this.userProfileCache.clear();
292
+ logger.debug("[PUBKEY_NAME_REPO] Cache cleared");
293
+ }
294
+
295
+ /**
296
+ * Get cache statistics for debugging
297
+ */
298
+ getCacheStats(): { size: number; entries: string[] } {
299
+ return {
300
+ size: this.userProfileCache.size,
301
+ entries: Array.from(this.userProfileCache.keys()),
302
+ };
303
+ }
304
+ }
305
+
306
+ // Export singleton instance getter for convenience
307
+ export const getPubkeyService = (): PubkeyService =>
308
+ PubkeyService.getInstance();