@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,130 @@
1
+ import type { NDKMCPTool } from "@/events/NDKMCPTool";
2
+ import { config } from "@/services/ConfigService";
3
+ import type { MCPServerConfig } from "@/services/config/types";
4
+ import { logger } from "@/utils/logger";
5
+
6
+ /**
7
+ * Installs an MCP server from an NDKMCPTool event into a project's configuration
8
+ * @param metadataPath The project metadata path (~/.tenex/projects/{dTag})
9
+ */
10
+ export async function installMCPServerFromEvent(
11
+ metadataPath: string,
12
+ mcpTool: NDKMCPTool
13
+ ): Promise<void> {
14
+ const serverName = mcpTool.slug;
15
+ const command = mcpTool.command;
16
+
17
+ if (!command) {
18
+ throw new Error(`MCP tool event ${mcpTool.id} is missing command tag`);
19
+ }
20
+
21
+ // Parse command and args
22
+ const [cmd, ...args] = command.split(" ");
23
+
24
+ // Build server config with event ID
25
+ const serverConfig: MCPServerConfig = {
26
+ command: cmd || "",
27
+ args,
28
+ description: mcpTool.description,
29
+ eventId: mcpTool.id, // Track the event ID
30
+ };
31
+
32
+ // Load existing MCP config from the metadata path
33
+ const mcpConfig = await config.loadTenexMCP(metadataPath);
34
+
35
+ // Check if this event ID is already installed (only if we have an event ID)
36
+ if (mcpTool.id && (await isMCPToolInstalled(metadataPath, mcpTool.id))) {
37
+ logger.info(`MCP tool with event ID ${mcpTool.id} already installed`, { metadataPath });
38
+ return;
39
+ }
40
+
41
+ // Check if server with same name already exists
42
+ if (mcpConfig.servers[serverName]) {
43
+ // If it exists without an event ID and we're adding one with an event ID, update it
44
+ if (!mcpConfig.servers[serverName].eventId && mcpTool.id) {
45
+ logger.info(`Updating existing MCP server '${serverName}' with event ID`, {
46
+ metadataPath,
47
+ eventId: mcpTool.id,
48
+ });
49
+ } else {
50
+ logger.info(`MCP server '${serverName}' already exists`, { metadataPath });
51
+ return;
52
+ }
53
+ }
54
+
55
+ // Add new server
56
+ mcpConfig.servers[serverName] = serverConfig;
57
+
58
+ // Save config directly to metadata path
59
+ await config.saveTenexMCP(metadataPath, mcpConfig);
60
+
61
+ logger.info(`Auto-installed MCP server: ${serverName}`, {
62
+ metadataPath,
63
+ command: cmd,
64
+ args,
65
+ eventId: mcpTool.id,
66
+ });
67
+ }
68
+
69
+ /**
70
+ * Checks if an MCP tool with a given event ID is already installed
71
+ * @param metadataPath The project metadata path (~/.tenex/projects/{dTag})
72
+ */
73
+ export async function isMCPToolInstalled(metadataPath: string, eventId: string): Promise<boolean> {
74
+ // Load from the metadata path directly
75
+ const mcpConfig = await config.loadTenexMCP(metadataPath);
76
+
77
+ // Check if any server has this event ID
78
+ for (const serverConfig of Object.values(mcpConfig.servers)) {
79
+ if (serverConfig.eventId === eventId) {
80
+ return true;
81
+ }
82
+ }
83
+
84
+ return false;
85
+ }
86
+
87
+ /**
88
+ * Gets all installed MCP tool event IDs (only those that have event IDs)
89
+ * @param metadataPath The project metadata path (~/.tenex/projects/{dTag})
90
+ */
91
+ export async function getInstalledMCPEventIds(metadataPath: string): Promise<Set<string>> {
92
+ // Load from the metadata path directly
93
+ const mcpConfig = await config.loadTenexMCP(metadataPath);
94
+ const eventIds = new Set<string>();
95
+
96
+ for (const serverConfig of Object.values(mcpConfig.servers)) {
97
+ // Only add if eventId exists (some MCP tools are manually installed without event IDs)
98
+ if (serverConfig.eventId) {
99
+ eventIds.add(serverConfig.eventId);
100
+ }
101
+ }
102
+
103
+ return eventIds;
104
+ }
105
+
106
+ /**
107
+ * Removes an MCP server by its event ID
108
+ * @param metadataPath The project metadata path (~/.tenex/projects/{dTag})
109
+ */
110
+ export async function removeMCPServerByEventId(metadataPath: string, eventId: string): Promise<void> {
111
+ // Load from the metadata path directly
112
+ const mcpConfig = await config.loadTenexMCP(metadataPath);
113
+
114
+ // Find and remove servers with this event ID
115
+ let removed = false;
116
+ for (const [serverName, serverConfig] of Object.entries(mcpConfig.servers)) {
117
+ if (serverConfig.eventId === eventId) {
118
+ delete mcpConfig.servers[serverName];
119
+ removed = true;
120
+ logger.info(`Removed MCP server '${serverName}' with event ID ${eventId}`);
121
+ }
122
+ }
123
+
124
+ if (removed) {
125
+ // Save updated config directly to metadata path
126
+ await config.saveTenexMCP(metadataPath, mcpConfig);
127
+ } else {
128
+ logger.warn(`No MCP server found with event ID ${eventId}`);
129
+ }
130
+ }
@@ -0,0 +1,81 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { config } from "@/services/ConfigService";
4
+
5
+ /**
6
+ * Operations logged by the NIP-46 signing system.
7
+ */
8
+ export type Nip46LogOperation =
9
+ | "signer_connect"
10
+ | "sign_request"
11
+ | "sign_success"
12
+ | "sign_timeout"
13
+ | "sign_rejected"
14
+ | "sign_error"
15
+ | "event_published";
16
+
17
+ /**
18
+ * A single log entry for NIP-46 signing activity.
19
+ */
20
+ export interface Nip46LogEntry {
21
+ ts: string;
22
+ op: Nip46LogOperation;
23
+ requestId?: string;
24
+ ownerPubkey?: string; // First 12 chars
25
+ eventKind?: number;
26
+ agentAction?: string;
27
+ agentPubkey?: string; // First 12 chars
28
+ pTagCount?: number;
29
+ signerType?: "nip46";
30
+ durationMs?: number;
31
+ error?: string;
32
+ eventId?: string;
33
+ trigger?: string; // What triggered this signing request
34
+ eventTags?: string[][]; // Full tags array of the event being signed
35
+ eventContent?: string; // Content field of the event being signed
36
+ }
37
+
38
+ /**
39
+ * Dedicated JSONL log writer for NIP-46 signing operations.
40
+ * Writes to ~/.tenex/daemon/nip46-signing.log, independent from daemon.log.
41
+ */
42
+ export class Nip46SigningLog {
43
+ private static instance: Nip46SigningLog | null = null;
44
+ private logPath: string;
45
+
46
+ private constructor() {
47
+ const daemonDir = config.getConfigPath("daemon");
48
+ fs.mkdirSync(daemonDir, { recursive: true });
49
+ this.logPath = path.join(daemonDir, "nip46-signing.log");
50
+ }
51
+
52
+ static getInstance(): Nip46SigningLog {
53
+ if (!Nip46SigningLog.instance) {
54
+ Nip46SigningLog.instance = new Nip46SigningLog();
55
+ }
56
+ return Nip46SigningLog.instance;
57
+ }
58
+
59
+ /**
60
+ * Append a log entry as a single JSONL line.
61
+ */
62
+ log(entry: Omit<Nip46LogEntry, "ts">): void {
63
+ const fullEntry: Nip46LogEntry = {
64
+ ts: new Date().toISOString(),
65
+ ...entry,
66
+ };
67
+
68
+ try {
69
+ fs.appendFileSync(this.logPath, JSON.stringify(fullEntry) + "\n");
70
+ } catch {
71
+ // Silent failure - logging should never crash the daemon
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Convenience: truncate a pubkey to 12 chars for logging.
77
+ */
78
+ static truncatePubkey(pubkey: string): string {
79
+ return pubkey.substring(0, 12);
80
+ }
81
+ }
@@ -0,0 +1,467 @@
1
+ import * as crypto from "node:crypto";
2
+ import { getNDK } from "@/nostr/ndkClient";
3
+ import { getRelayUrls } from "@/nostr/relays";
4
+ import { config } from "@/services/ConfigService";
5
+ import { logger } from "@/utils/logger";
6
+ import {
7
+ type NDKEvent,
8
+ NDKNip46Signer,
9
+ NDKPrivateKeySigner,
10
+ } from "@nostr-dev-kit/ndk";
11
+ import { Nip46SigningLog } from "./Nip46SigningLog";
12
+
13
+ /**
14
+ * Default configuration values for NIP-46 signing.
15
+ */
16
+ const DEFAULTS = {
17
+ SIGNING_TIMEOUT_MS: 30_000,
18
+ CONNECT_TIMEOUT_MS: 30_000,
19
+ MAX_RETRIES: 2,
20
+ } as const;
21
+
22
+ /**
23
+ * Result of a NIP-46 signing attempt.
24
+ *
25
+ * - `signed` — signing succeeded
26
+ * - `user_rejected` — user/bunker explicitly rejected
27
+ * - `failed` — timeout/network error; event will not be published
28
+ */
29
+ export type SignResult =
30
+ | { outcome: "signed" }
31
+ | { outcome: "user_rejected"; reason: string }
32
+ | { outcome: "failed"; reason: string };
33
+
34
+ /**
35
+ * Race a promise against a timeout, cleaning up the timer regardless of outcome.
36
+ * Prevents leaked timers that cause unhandled promise rejections.
37
+ */
38
+ function withTimeout<T>(promise: Promise<T>, ms: number, message: string): Promise<T> {
39
+ let timer: ReturnType<typeof setTimeout>;
40
+ return Promise.race([
41
+ promise,
42
+ new Promise<never>((_, reject) => {
43
+ timer = setTimeout(() => reject(new Error(message)), ms);
44
+ }),
45
+ ]).finally(() => clearTimeout(timer));
46
+ }
47
+
48
+ /**
49
+ * Singleton service that manages NIP-46 remote signing for kind 14199 events.
50
+ *
51
+ * Responsibilities:
52
+ * - Lazy initialization of NDKNip46Signer per owner pubkey
53
+ * - Timeout-wrapped signing with configurable retries
54
+ * - Per-owner mutex to serialize signing requests
55
+ * - Clear failure logging when signing fails (no fallback to backend key)
56
+ */
57
+ export class Nip46SigningService {
58
+ private static instance: Nip46SigningService | null = null;
59
+
60
+ /** One NIP-46 signer per owner pubkey */
61
+ private signers = new Map<string, NDKNip46Signer>();
62
+
63
+ /** Per-owner promise chain for serialized signing */
64
+ private ownerLocks = new Map<string, Promise<void>>();
65
+
66
+ /** Track connection state per owner */
67
+ private connectedOwners = new Set<string>();
68
+
69
+ private signingLog: Nip46SigningLog;
70
+
71
+ private constructor() {
72
+ this.signingLog = Nip46SigningLog.getInstance();
73
+ }
74
+
75
+ static getInstance(): Nip46SigningService {
76
+ if (!Nip46SigningService.instance) {
77
+ Nip46SigningService.instance = new Nip46SigningService();
78
+ }
79
+ return Nip46SigningService.instance;
80
+ }
81
+
82
+ // =====================================================================================
83
+ // CONFIGURATION HELPERS
84
+ // =====================================================================================
85
+
86
+ /**
87
+ * Check if NIP-46 signing is globally enabled.
88
+ */
89
+ isEnabled(): boolean {
90
+ try {
91
+ const cfg = config.getConfig();
92
+ return cfg.nip46?.enabled === true;
93
+ } catch {
94
+ return false;
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Check if NIP-46 is enabled and configured for a specific owner.
100
+ */
101
+ isEnabledForOwner(_ownerPubkey: string): boolean {
102
+ if (!this.isEnabled()) return false;
103
+ // NIP-46 is enabled for all whitelisted owners;
104
+ // we auto-construct bunker URIs when not explicitly configured
105
+ return true;
106
+ }
107
+
108
+ /**
109
+ * Get the bunker URI for an owner. If not explicitly configured,
110
+ * auto-constructs: bunker://<ownerPubkey>?relay=<firstRelay>
111
+ */
112
+ getBunkerUri(ownerPubkey: string): string {
113
+ const cfg = config.getConfig();
114
+ const ownerConfig = cfg.nip46?.owners?.[ownerPubkey];
115
+
116
+ if (ownerConfig?.bunkerUri) {
117
+ return ownerConfig.bunkerUri;
118
+ }
119
+
120
+ // Auto-construct bunker URI
121
+ const relays = getRelayUrls();
122
+ const relay = relays[0] || "wss://tenex.chat";
123
+ return `bunker://${ownerPubkey}?relay=${encodeURIComponent(relay)}`;
124
+ }
125
+
126
+ private getSigningTimeout(): number {
127
+ try {
128
+ return config.getConfig().nip46?.signingTimeoutMs ?? DEFAULTS.SIGNING_TIMEOUT_MS;
129
+ } catch {
130
+ return DEFAULTS.SIGNING_TIMEOUT_MS;
131
+ }
132
+ }
133
+
134
+ private getMaxRetries(): number {
135
+ try {
136
+ return config.getConfig().nip46?.maxRetries ?? DEFAULTS.MAX_RETRIES;
137
+ } catch {
138
+ return DEFAULTS.MAX_RETRIES;
139
+ }
140
+ }
141
+
142
+ // =====================================================================================
143
+ // SIGNER MANAGEMENT
144
+ // =====================================================================================
145
+
146
+ /**
147
+ * Get or create an NDKNip46Signer for a specific owner.
148
+ * Lazy initialization — signer created on first request, not at daemon startup.
149
+ */
150
+ private async getOrCreateSigner(ownerPubkey: string): Promise<NDKNip46Signer> {
151
+ const existing = this.signers.get(ownerPubkey);
152
+ if (existing) return existing;
153
+
154
+ const ndk = getNDK();
155
+ const backendNsec = await config.ensureBackendPrivateKey();
156
+ const localSigner = new NDKPrivateKeySigner(backendNsec);
157
+ const bunkerUri = this.getBunkerUri(ownerPubkey);
158
+
159
+ logger.info("[NIP-46] Creating signer for owner", {
160
+ ownerPubkey: ownerPubkey.substring(0, 12),
161
+ bunkerUri: bunkerUri.substring(0, 60),
162
+ });
163
+
164
+ const signer = NDKNip46Signer.bunker(ndk, bunkerUri, localSigner);
165
+
166
+ // Handle auth URL — log it so operator can approve if needed
167
+ signer.on("authUrl", (url: string) => {
168
+ logger.info("[NIP-46] Auth URL required", {
169
+ ownerPubkey: ownerPubkey.substring(0, 12),
170
+ url,
171
+ });
172
+ this.signingLog.log({
173
+ op: "signer_connect",
174
+ ownerPubkey: Nip46SigningLog.truncatePubkey(ownerPubkey),
175
+ error: `auth_url_required: ${url}`,
176
+ });
177
+ });
178
+
179
+ this.signers.set(ownerPubkey, signer);
180
+ return signer;
181
+ }
182
+
183
+ /**
184
+ * Ensure the signer for an owner is connected (blockUntilReady).
185
+ * Wrapped with timeout to prevent hanging forever.
186
+ */
187
+ private async ensureConnected(ownerPubkey: string): Promise<NDKNip46Signer> {
188
+ const signer = await this.getOrCreateSigner(ownerPubkey);
189
+
190
+ if (this.connectedOwners.has(ownerPubkey)) {
191
+ return signer;
192
+ }
193
+
194
+ const startMs = Date.now();
195
+ const requestId = crypto.randomUUID().substring(0, 8);
196
+
197
+ this.signingLog.log({
198
+ op: "signer_connect",
199
+ requestId,
200
+ ownerPubkey: Nip46SigningLog.truncatePubkey(ownerPubkey),
201
+ });
202
+
203
+ try {
204
+ await withTimeout(
205
+ signer.blockUntilReady(),
206
+ DEFAULTS.CONNECT_TIMEOUT_MS,
207
+ "NIP-46 connect timed out",
208
+ );
209
+
210
+ this.connectedOwners.add(ownerPubkey);
211
+ const durationMs = Date.now() - startMs;
212
+
213
+ logger.info("[NIP-46] Signer connected", {
214
+ ownerPubkey: ownerPubkey.substring(0, 12),
215
+ durationMs,
216
+ });
217
+
218
+ this.signingLog.log({
219
+ op: "signer_connect",
220
+ requestId,
221
+ ownerPubkey: Nip46SigningLog.truncatePubkey(ownerPubkey),
222
+ durationMs,
223
+ });
224
+
225
+ return signer;
226
+ } catch (error) {
227
+ const durationMs = Date.now() - startMs;
228
+ const errorMsg = error instanceof Error ? error.message : String(error);
229
+
230
+ logger.error("[NIP-46] Signer connection failed", {
231
+ ownerPubkey: ownerPubkey.substring(0, 12),
232
+ error: errorMsg,
233
+ durationMs,
234
+ });
235
+
236
+ this.signingLog.log({
237
+ op: "sign_error",
238
+ requestId,
239
+ ownerPubkey: Nip46SigningLog.truncatePubkey(ownerPubkey),
240
+ error: `connect_failed: ${errorMsg}`,
241
+ durationMs,
242
+ });
243
+
244
+ // Remove from cache so next attempt creates a fresh signer
245
+ this.signers.delete(ownerPubkey);
246
+ throw error;
247
+ }
248
+ }
249
+
250
+ // =====================================================================================
251
+ // PER-OWNER MUTEX
252
+ // =====================================================================================
253
+
254
+ /**
255
+ * Serialize operations per owner pubkey to avoid concurrent NIP-46 signing.
256
+ */
257
+ private async withOwnerLock<T>(ownerPubkey: string, fn: () => Promise<T>): Promise<T> {
258
+ const existing = this.ownerLocks.get(ownerPubkey) ?? Promise.resolve();
259
+ let resolve: () => void;
260
+ const next = new Promise<void>((r) => (resolve = r));
261
+ this.ownerLocks.set(ownerPubkey, next);
262
+
263
+ await existing;
264
+
265
+ try {
266
+ return await fn();
267
+ } finally {
268
+ resolve!();
269
+ if (this.ownerLocks.get(ownerPubkey) === next) {
270
+ this.ownerLocks.delete(ownerPubkey);
271
+ }
272
+ }
273
+ }
274
+
275
+ // =====================================================================================
276
+ // SIGNING
277
+ // =====================================================================================
278
+
279
+ /**
280
+ * Sign an NDKEvent using NIP-46 remote signing for the given owner.
281
+ *
282
+ * @param ownerPubkey - The owner's hex pubkey (the "user" in NIP-46 terms)
283
+ * @param event - The NDKEvent to sign (will be mutated: pubkey set to ownerPubkey)
284
+ * @returns SignResult indicating success, explicit rejection, or transient failure
285
+ */
286
+ async signEvent(ownerPubkey: string, event: NDKEvent, trigger?: string): Promise<SignResult> {
287
+ return this.withOwnerLock(ownerPubkey, () =>
288
+ this.signEventInternal(ownerPubkey, event, trigger)
289
+ );
290
+ }
291
+
292
+ private async signEventInternal(ownerPubkey: string, event: NDKEvent, trigger?: string): Promise<SignResult> {
293
+ const requestId = crypto.randomUUID().substring(0, 8);
294
+ const signingTimeout = this.getSigningTimeout();
295
+ const maxRetries = this.getMaxRetries();
296
+ const startMs = Date.now();
297
+
298
+ this.signingLog.log({
299
+ op: "sign_request",
300
+ requestId,
301
+ ownerPubkey: Nip46SigningLog.truncatePubkey(ownerPubkey),
302
+ eventKind: event.kind,
303
+ pTagCount: event.tags.filter((t) => t[0] === "p").length,
304
+ trigger,
305
+ eventTags: event.tags,
306
+ eventContent: event.content,
307
+ });
308
+
309
+ let lastError: Error | null = null;
310
+
311
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
312
+ try {
313
+ const signer = await this.ensureConnected(ownerPubkey);
314
+
315
+ // Set the event pubkey to the owner before signing
316
+ event.pubkey = ownerPubkey;
317
+
318
+ logger.info("[NIP-46] Pre-sign payload", {
319
+ requestId,
320
+ attempt,
321
+ kind: event.kind,
322
+ pubkey: event.pubkey?.substring(0, 12),
323
+ tags: JSON.stringify(event.tags),
324
+ content: event.content,
325
+ });
326
+
327
+ await withTimeout(
328
+ event.sign(signer),
329
+ signingTimeout,
330
+ "NIP-46 sign timed out",
331
+ );
332
+
333
+ const durationMs = Date.now() - startMs;
334
+
335
+ this.signingLog.log({
336
+ op: "sign_success",
337
+ requestId,
338
+ ownerPubkey: Nip46SigningLog.truncatePubkey(ownerPubkey),
339
+ eventKind: event.kind,
340
+ signerType: "nip46",
341
+ durationMs,
342
+ eventId: event.id,
343
+ });
344
+
345
+ logger.info("[NIP-46] Event signed successfully", {
346
+ ownerPubkey: ownerPubkey.substring(0, 12),
347
+ eventKind: event.kind,
348
+ eventId: event.id?.substring(0, 12),
349
+ durationMs,
350
+ attempt,
351
+ });
352
+
353
+ return { outcome: "signed" };
354
+ } catch (error) {
355
+ lastError = error instanceof Error ? error : new Error(String(error));
356
+ const errorMsg = lastError.message;
357
+
358
+ // User rejection — never retry
359
+ if (this.isUserRejection(errorMsg)) {
360
+ const durationMs = Date.now() - startMs;
361
+
362
+ this.signingLog.log({
363
+ op: "sign_rejected",
364
+ requestId,
365
+ ownerPubkey: Nip46SigningLog.truncatePubkey(ownerPubkey),
366
+ eventKind: event.kind,
367
+ error: errorMsg,
368
+ durationMs,
369
+ });
370
+
371
+ logger.warn("[NIP-46] Signing rejected by user", {
372
+ ownerPubkey: ownerPubkey.substring(0, 12),
373
+ eventKind: event.kind,
374
+ error: errorMsg,
375
+ });
376
+
377
+ return { outcome: "user_rejected", reason: errorMsg };
378
+ }
379
+
380
+ // Timeout or relay error — retry if attempts remain
381
+ if (attempt < maxRetries) {
382
+ logger.warn("[NIP-46] Signing failed, retrying", {
383
+ ownerPubkey: ownerPubkey.substring(0, 12),
384
+ eventKind: event.kind,
385
+ attempt: attempt + 1,
386
+ maxRetries,
387
+ error: errorMsg,
388
+ });
389
+
390
+ // Reset signer on connection errors
391
+ if (errorMsg.includes("connect") || errorMsg.includes("timed out")) {
392
+ this.signers.delete(ownerPubkey);
393
+ this.connectedOwners.delete(ownerPubkey);
394
+ }
395
+
396
+ continue;
397
+ }
398
+ }
399
+ }
400
+
401
+ // All retries exhausted
402
+ const durationMs = Date.now() - startMs;
403
+ const errorMsg = lastError?.message ?? "unknown error";
404
+
405
+ if (errorMsg.includes("timed out")) {
406
+ this.signingLog.log({
407
+ op: "sign_timeout",
408
+ requestId,
409
+ ownerPubkey: Nip46SigningLog.truncatePubkey(ownerPubkey),
410
+ eventKind: event.kind,
411
+ error: errorMsg,
412
+ durationMs,
413
+ });
414
+ } else {
415
+ this.signingLog.log({
416
+ op: "sign_error",
417
+ requestId,
418
+ ownerPubkey: Nip46SigningLog.truncatePubkey(ownerPubkey),
419
+ eventKind: event.kind,
420
+ error: errorMsg,
421
+ durationMs,
422
+ });
423
+ }
424
+
425
+ logger.error("[NIP-46] Signing failed after all retries", {
426
+ ownerPubkey: ownerPubkey.substring(0, 12),
427
+ eventKind: event.kind,
428
+ retries: maxRetries,
429
+ error: errorMsg,
430
+ durationMs,
431
+ });
432
+
433
+ return { outcome: "failed", reason: errorMsg };
434
+ }
435
+
436
+ /**
437
+ * Detect user rejection errors — these should never be retried.
438
+ */
439
+ private isUserRejection(errorMsg: string): boolean {
440
+ const rejectionPatterns = [
441
+ "rejected",
442
+ "denied",
443
+ "refused",
444
+ "not authorized",
445
+ "user declined",
446
+ ];
447
+ const lower = errorMsg.toLowerCase();
448
+ return rejectionPatterns.some((p) => lower.includes(p));
449
+ }
450
+
451
+ // =====================================================================================
452
+ // LIFECYCLE
453
+ // =====================================================================================
454
+
455
+ /**
456
+ * Clean up all NIP-46 signer connections.
457
+ */
458
+ async shutdown(): Promise<void> {
459
+ logger.info("[NIP-46] Shutting down signing service", {
460
+ activeSingers: this.signers.size,
461
+ });
462
+
463
+ this.signers.clear();
464
+ this.connectedOwners.clear();
465
+ this.ownerLocks.clear();
466
+ }
467
+ }
@@ -0,0 +1,4 @@
1
+ export { Nip46SigningService } from "./Nip46SigningService";
2
+ export type { SignResult } from "./Nip46SigningService";
3
+ export { Nip46SigningLog } from "./Nip46SigningLog";
4
+ export type { Nip46LogEntry, Nip46LogOperation } from "./Nip46SigningLog";