@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,437 @@
1
+ import type { AgentInstance } from "@/agents/types";
2
+ import { NDKKind } from "@/nostr/kinds";
3
+ import { getNDK } from "@/nostr/ndkClient";
4
+ import { PendingDelegationsRegistry, RALRegistry } from "@/services/ral";
5
+ import { logger } from "@/utils/logger";
6
+ import { NDKEvent } from "@nostr-dev-kit/ndk";
7
+ import { trace } from "@opentelemetry/api";
8
+ import { AgentEventEncoder } from "./AgentEventEncoder";
9
+ import { injectTraceContext } from "./trace-context";
10
+ import type {
11
+ AskConfig,
12
+ CompletionIntent,
13
+ ConversationIntent,
14
+ DelegateConfig,
15
+ DelegationMarkerIntent,
16
+ ErrorIntent,
17
+ EventContext,
18
+ LessonIntent,
19
+ ToolUseIntent,
20
+ } from "./types";
21
+
22
+ /**
23
+ * Comprehensive publisher for all agent-related Nostr events.
24
+ * Handles agent creation, responses, completions, and delegations.
25
+ */
26
+ export class AgentPublisher {
27
+ private agent: AgentInstance;
28
+ private encoder: AgentEventEncoder;
29
+
30
+ constructor(agent: AgentInstance) {
31
+ this.agent = agent;
32
+ this.encoder = new AgentEventEncoder();
33
+ }
34
+
35
+ /**
36
+ * Consume unreported runtime from RAL and enhance context with it.
37
+ * This ensures each published event gets the incremental runtime since last publish.
38
+ *
39
+ * IMPORTANT: Always consumes from RAL to advance lastReportedRuntime, even when
40
+ * explicit llmRuntime is provided. This prevents double-counting on subsequent events.
41
+ */
42
+ private consumeAndEnhanceContext(context: EventContext): EventContext {
43
+ const ralRegistry = RALRegistry.getInstance();
44
+
45
+ // Always consume to advance lastReportedRuntime (prevents double-counting)
46
+ const unreportedRuntime = ralRegistry.consumeUnreportedRuntime(
47
+ this.agent.pubkey,
48
+ context.conversationId,
49
+ context.ralNumber
50
+ );
51
+
52
+ // DEBUG: Temporary logging to diagnose llm-runtime issue
53
+ logger.info("[AgentPublisher.consumeAndEnhanceContext]", {
54
+ agent: this.agent.slug,
55
+ pubkey: this.agent.pubkey.substring(0, 8),
56
+ conv: context.conversationId.substring(0, 8),
57
+ ral: context.ralNumber,
58
+ unreportedMs: unreportedRuntime,
59
+ });
60
+
61
+ // If context already has llmRuntime set explicitly, use that value
62
+ // (but we still consumed above to advance the counter)
63
+ if (context.llmRuntime !== undefined) {
64
+ return context;
65
+ }
66
+
67
+ return {
68
+ ...context,
69
+ llmRuntime: unreportedRuntime > 0 ? unreportedRuntime : undefined,
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Safely publish an event with error handling and comprehensive logging.
75
+ * Throws an error if no relays accept the event.
76
+ */
77
+ private async safePublish(event: NDKEvent, eventType: string): Promise<void> {
78
+ try {
79
+ const relaySet = await event.publish();
80
+
81
+ // Log relay responses
82
+ const successRelays: string[] = [];
83
+ for (const relay of relaySet) {
84
+ successRelays.push(relay.url);
85
+ logger.debug(`Relay accepted ${eventType} event`, {
86
+ eventId: event.id?.substring(0, 8),
87
+ eventType,
88
+ relayUrl: relay.url,
89
+ agent: this.agent.slug,
90
+ });
91
+ }
92
+
93
+ // Fail explicitly when no relays accept the event
94
+ if (successRelays.length === 0) {
95
+ const error = new Error(
96
+ `Event published to 0 relays - all relays rejected the ${eventType} event`
97
+ );
98
+ logger.error("Event published to 0 relays", {
99
+ eventId: event.id?.substring(0, 8),
100
+ eventType,
101
+ agent: this.agent.slug,
102
+ kind: event.kind,
103
+ contentLength: event.content?.length || 0,
104
+ tagCount: event.tags?.length || 0,
105
+ rawEvent: JSON.stringify(event.rawEvent()),
106
+ });
107
+ throw error;
108
+ }
109
+
110
+ logger.info(`Published ${eventType} event to ${successRelays.length} relay(s)`, {
111
+ eventId: event.id?.substring(0, 8),
112
+ eventType,
113
+ agent: this.agent.slug,
114
+ relays: successRelays,
115
+ });
116
+ } catch (error) {
117
+ logger.error(`Failed to publish ${eventType}`, {
118
+ error,
119
+ eventId: event.id?.substring(0, 8),
120
+ agent: this.agent.slug,
121
+ kind: event.kind,
122
+ contentLength: event.content?.length || 0,
123
+ tagCount: event.tags?.length || 0,
124
+ rawEvent: JSON.stringify(event.rawEvent()),
125
+ });
126
+ throw error;
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Add delegation tag to an event, linking it to the parent conversation.
132
+ * This method is used by delegate() and ask() to establish the parent-child
133
+ * relationship between conversations.
134
+ *
135
+ * @param event - The event to add the delegation tag to
136
+ * @param context - The event context containing the conversationId
137
+ * @throws Error if context.conversationId is missing
138
+ */
139
+ private addDelegationTag(event: NDKEvent, context: EventContext): void {
140
+ if (!context.conversationId) {
141
+ throw new Error("Cannot add delegation tag: conversationId is required in context for delegation events");
142
+ }
143
+ event.tags.push(["delegation", context.conversationId]);
144
+ }
145
+
146
+ /**
147
+ * Publish a completion event.
148
+ * Creates and publishes a properly tagged completion event with p-tag.
149
+ * Includes both incremental runtime (llm-runtime) and total runtime (llm-runtime-total).
150
+ *
151
+ * DELEGATION CHAIN ROUTING: For conversations with a delegation chain, the completion
152
+ * is routed to the immediate delegator (second-to-last in chain), not triggeringEvent.pubkey.
153
+ * The recipient pubkey is pre-resolved by createEventContext (layer 3) and passed via
154
+ * context.completionRecipientPubkey. This avoids a layer violation - AgentPublisher (layer 2)
155
+ * cannot import ConversationStore directly.
156
+ *
157
+ * RACE CONDITION GUARD: If this conversation was killed via the kill tool,
158
+ * this method returns undefined instead of publishing. This prevents the scenario
159
+ * where an agent continues running (e.g., in a long tool execution) after being
160
+ * killed and then publishes a completion that triggers the parent to process it.
161
+ */
162
+ async complete(intent: CompletionIntent, context: EventContext): Promise<NDKEvent | undefined> {
163
+ const ralRegistry = RALRegistry.getInstance();
164
+
165
+ // RACE CONDITION GUARD: Check if this agent+conversation was killed
166
+ // ISSUE 3 FIX: Using agent-scoped check ensures killing one agent
167
+ // doesn't suppress completions for other agents in the same conversation.
168
+ if (ralRegistry.isAgentConversationKilled(this.agent.pubkey, context.conversationId)) {
169
+ logger.warn("[AgentPublisher.complete] Skipping completion - agent+conversation was killed", {
170
+ agent: this.agent.slug,
171
+ agentPubkey: this.agent.pubkey.substring(0, 12),
172
+ conversationId: context.conversationId.substring(0, 12),
173
+ ralNumber: context.ralNumber,
174
+ });
175
+
176
+ trace.getActiveSpan()?.addEvent("publisher.completion_skipped_killed", {
177
+ "agent.slug": this.agent.slug,
178
+ "agent.pubkey": this.agent.pubkey.substring(0, 12),
179
+ "conversation.id": context.conversationId.substring(0, 12),
180
+ "ral.number": context.ralNumber,
181
+ });
182
+
183
+ return undefined;
184
+ }
185
+
186
+ const enhancedContext = this.consumeAndEnhanceContext(context);
187
+
188
+ // For completion events, include the total accumulated runtime for the entire RAL
189
+ // This allows delegation aggregation to get the correct total runtime
190
+ const totalRuntime = ralRegistry.getAccumulatedRuntime(
191
+ this.agent.pubkey,
192
+ context.conversationId,
193
+ context.ralNumber
194
+ );
195
+
196
+ // completionRecipientPubkey is pre-resolved by createEventContext (layer 3)
197
+ // from the delegation chain stored in ConversationStore. This avoids a layer
198
+ // violation - AgentPublisher (layer 2) cannot import ConversationStore directly.
199
+ const contextWithExtras: EventContext = {
200
+ ...enhancedContext,
201
+ llmRuntimeTotal: totalRuntime > 0 ? totalRuntime : undefined,
202
+ };
203
+
204
+ const event = this.encoder.encodeCompletion(intent, contextWithExtras);
205
+
206
+ injectTraceContext(event);
207
+ await this.agent.sign(event);
208
+ await this.safePublish(event, "completion");
209
+
210
+ return event;
211
+ }
212
+
213
+ /**
214
+ * Publish a conversation event (mid-loop response without p-tag).
215
+ * Used when agent has text output but delegations are still pending.
216
+ */
217
+ async conversation(intent: ConversationIntent, context: EventContext): Promise<NDKEvent> {
218
+ const enhancedContext = this.consumeAndEnhanceContext(context);
219
+ const event = this.encoder.encodeConversation(intent, enhancedContext);
220
+
221
+ injectTraceContext(event);
222
+ await this.agent.sign(event);
223
+ await this.safePublish(event, "conversation");
224
+
225
+ return event;
226
+ }
227
+
228
+ /**
229
+ * Publish a delegation event
230
+ */
231
+ async delegate(config: DelegateConfig, context: EventContext): Promise<string> {
232
+ const enhancedContext = this.consumeAndEnhanceContext(context);
233
+ const ndk = getNDK();
234
+ const event = new NDKEvent(ndk);
235
+ event.kind = NDKKind.Text; // kind:1 - unified conversation format
236
+ event.content = config.content;
237
+
238
+ // Add recipient p-tag
239
+ event.tags.push(["p", config.recipient]);
240
+
241
+ // No e-tag: delegation events start separate conversations
242
+
243
+ if (config.branch) {
244
+ event.tags.push(["branch", config.branch]);
245
+ }
246
+
247
+ // Add nudge tags for the delegated agent (deduplicated for robustness)
248
+ if (config.nudges && config.nudges.length > 0) {
249
+ const uniqueNudges = [...new Set(config.nudges)];
250
+ for (const nudgeId of uniqueNudges) {
251
+ event.tags.push(["nudge", nudgeId]);
252
+ }
253
+ }
254
+
255
+ // Add standard metadata (project tag, model, cost, execution time, etc)
256
+ this.encoder.addStandardTags(event, enhancedContext);
257
+
258
+ // Forward branch tag from triggering event if not explicitly set
259
+ if (!config.branch) {
260
+ this.encoder.forwardBranchTag(event, enhancedContext);
261
+ }
262
+
263
+ // Add delegation tag linking to parent conversation
264
+ this.addDelegationTag(event, enhancedContext);
265
+
266
+ injectTraceContext(event);
267
+ await this.agent.sign(event);
268
+ await this.safePublish(event, "delegation");
269
+
270
+ // Register with PendingDelegationsRegistry for q-tag correlation
271
+ PendingDelegationsRegistry.register(this.agent.pubkey, context.conversationId, event.id);
272
+
273
+ return event.id;
274
+ }
275
+
276
+ /**
277
+ * Publish an ask event using the multi-question format.
278
+ * Returns the published NDKEvent so callers can create a ConversationStore.
279
+ */
280
+ async ask(config: AskConfig, context: EventContext): Promise<NDKEvent> {
281
+ const enhancedContext = this.consumeAndEnhanceContext(context);
282
+ const ndk = getNDK();
283
+ const event = new NDKEvent(ndk);
284
+ event.kind = NDKKind.Text; // kind:1 - unified conversation format
285
+ // Content is just the context (user has no access to conversation history)
286
+ event.content = config.context;
287
+
288
+ // Add title tag
289
+ event.tags.push(["title", config.title]);
290
+
291
+ // Add question/multiselect tags
292
+ for (const question of config.questions) {
293
+ if (question.type === "question") {
294
+ const tag = ["question", question.title, question.question];
295
+ if (question.suggestions) {
296
+ tag.push(...question.suggestions);
297
+ }
298
+ event.tags.push(tag);
299
+ } else if (question.type === "multiselect") {
300
+ const tag = ["multiselect", question.title, question.question];
301
+ if (question.options) {
302
+ tag.push(...question.options);
303
+ }
304
+ event.tags.push(tag);
305
+ }
306
+ }
307
+
308
+ // Add recipient p-tag
309
+ event.tags.push(["p", config.recipient]);
310
+
311
+ // No e-tag: ask events start separate conversations (like delegate)
312
+
313
+ // Add standard metadata (project tag, model, cost, execution time, runtime, etc)
314
+ this.encoder.addStandardTags(event, enhancedContext);
315
+
316
+ // Add ask marker
317
+ event.tags.push(["ask", "true"]);
318
+
319
+ // Add t-tag for ask events
320
+ event.tags.push(["t", "ask"]);
321
+
322
+ // Add delegation tag linking to parent conversation
323
+ this.addDelegationTag(event, enhancedContext);
324
+
325
+ injectTraceContext(event);
326
+ await this.agent.sign(event);
327
+ await this.safePublish(event, "ask");
328
+
329
+ // Register with PendingDelegationsRegistry for q-tag correlation
330
+ PendingDelegationsRegistry.register(this.agent.pubkey, enhancedContext.conversationId, event.id);
331
+
332
+ return event;
333
+ }
334
+
335
+ /**
336
+ * Publish a delegation follow-up event
337
+ */
338
+ async delegateFollowup(
339
+ params: {
340
+ recipient: string;
341
+ content: string;
342
+ delegationEventId: string;
343
+ replyToEventId?: string;
344
+ },
345
+ context: EventContext
346
+ ): Promise<string> {
347
+ const enhancedContext = this.consumeAndEnhanceContext(context);
348
+ const ndk = getNDK();
349
+ const event = new NDKEvent(ndk);
350
+ event.kind = NDKKind.Text; // kind:1 - unified conversation format
351
+ event.content = params.content;
352
+
353
+ // Add recipient p-tag
354
+ event.tags.push(["p", params.recipient]);
355
+
356
+ // Add reference to the original delegation event
357
+ event.tags.push(["e", params.delegationEventId]);
358
+
359
+ // Reply to specific response event if provided (for threading)
360
+ if (params.replyToEventId) {
361
+ event.tags.push(["e", params.replyToEventId]);
362
+ }
363
+
364
+ // Add standard metadata (project tag, model, cost, execution time, runtime, etc)
365
+ this.encoder.addStandardTags(event, enhancedContext);
366
+
367
+ // Forward branch tag from triggering event
368
+ this.encoder.forwardBranchTag(event, enhancedContext);
369
+
370
+ injectTraceContext(event);
371
+ await this.agent.sign(event);
372
+ await this.safePublish(event, "followup");
373
+
374
+ return event.id;
375
+ }
376
+
377
+ /**
378
+ * Publish an error event.
379
+ * Creates and publishes an error notification event.
380
+ */
381
+ async error(intent: ErrorIntent, context: EventContext): Promise<NDKEvent> {
382
+ const enhancedContext = this.consumeAndEnhanceContext(context);
383
+ const event = this.encoder.encodeError(intent, enhancedContext);
384
+
385
+ injectTraceContext(event);
386
+ await this.agent.sign(event);
387
+ await this.safePublish(event, "error");
388
+
389
+ return event;
390
+ }
391
+
392
+ /**
393
+ * Publish a lesson learned event.
394
+ */
395
+ async lesson(intent: LessonIntent, context: EventContext): Promise<NDKEvent> {
396
+ const enhancedContext = this.consumeAndEnhanceContext(context);
397
+ const lessonEvent = this.encoder.encodeLesson(intent, enhancedContext, this.agent);
398
+
399
+ injectTraceContext(lessonEvent);
400
+ await this.agent.sign(lessonEvent);
401
+ await this.safePublish(lessonEvent, "lesson");
402
+
403
+ return lessonEvent;
404
+ }
405
+
406
+ /**
407
+ * Publish a tool usage event.
408
+ * Creates and publishes an event with tool name and output tags.
409
+ */
410
+ async toolUse(intent: ToolUseIntent, context: EventContext): Promise<NDKEvent> {
411
+ const enhancedContext = this.consumeAndEnhanceContext(context);
412
+ const event = this.encoder.encodeToolUse(intent, enhancedContext);
413
+
414
+ injectTraceContext(event);
415
+ await this.agent.sign(event);
416
+ await this.safePublish(event, `tool:${intent.toolName}`);
417
+
418
+ return event;
419
+ }
420
+
421
+ /**
422
+ * Publish a delegation marker event.
423
+ * Delegation markers track the lifecycle of delegation conversations.
424
+ *
425
+ * Note: This does NOT consume runtime from RAL since markers are not part of
426
+ * the agent's reasoning/action loop. They are metadata events for tracking.
427
+ */
428
+ async delegationMarker(intent: DelegationMarkerIntent): Promise<NDKEvent> {
429
+ const event = this.encoder.encodeDelegationMarker(intent);
430
+
431
+ injectTraceContext(event);
432
+ await this.agent.sign(event);
433
+ await this.safePublish(event, `delegation-marker:${intent.status}`);
434
+
435
+ return event;
436
+ }
437
+ }
@@ -0,0 +1,226 @@
1
+ /**
2
+ * BlossomService - Handles Blossom media uploads with Nostr authentication
3
+ *
4
+ * This service encapsulates all Blossom upload logic including:
5
+ * - SHA256 hash calculation
6
+ * - Kind 24242 authorization event creation and signing
7
+ * - HTTP upload to Blossom servers
8
+ *
9
+ * Centralizes NDK usage in the nostr layer, allowing tools to delegate
10
+ * Blossom operations without directly importing NDK.
11
+ */
12
+
13
+ import * as crypto from "node:crypto";
14
+ import type { NDKEvent } from "@nostr-dev-kit/ndk";
15
+ import { logger } from "@/utils/logger";
16
+ import { NDKEvent as NDKEventClass } from "@nostr-dev-kit/ndk";
17
+
18
+ /**
19
+ * Minimal signer interface needed for Blossom authentication.
20
+ * This allows both AgentInstance and ToolAgentInfo to be used.
21
+ */
22
+ export interface BlossomSigner {
23
+ sign(event: NDKEvent): Promise<void>;
24
+ }
25
+
26
+ const UPLOAD_TIMEOUT_MS = 60_000;
27
+
28
+ /**
29
+ * Result of a successful Blossom upload
30
+ */
31
+ export interface BlossomUploadResult {
32
+ /** URL of the uploaded blob */
33
+ url: string;
34
+ /** SHA256 hash of the uploaded data */
35
+ sha256: string;
36
+ /** Size in bytes */
37
+ size: number;
38
+ /** MIME type (if returned by server) */
39
+ type?: string;
40
+ /** Timestamp of upload */
41
+ uploaded?: number;
42
+ }
43
+
44
+ /**
45
+ * Options for uploading to Blossom
46
+ */
47
+ export interface BlossomUploadOptions {
48
+ /** Blossom server URL (required - callers must load from config) */
49
+ serverUrl: string;
50
+ /** Description for the authorization event content */
51
+ description?: string;
52
+ }
53
+
54
+ /**
55
+ * MIME type to file extension mapping
56
+ */
57
+ const MIME_TO_EXTENSION: Record<string, string> = {
58
+ "image/jpeg": ".jpg",
59
+ "image/png": ".png",
60
+ "image/gif": ".gif",
61
+ "image/webp": ".webp",
62
+ "video/mp4": ".mp4",
63
+ "video/quicktime": ".mov",
64
+ "video/x-msvideo": ".avi",
65
+ "video/webm": ".webm",
66
+ "audio/mpeg": ".mp3",
67
+ "audio/wav": ".wav",
68
+ "application/pdf": ".pdf",
69
+ "application/json": ".json",
70
+ "text/plain": ".txt",
71
+ };
72
+
73
+ /**
74
+ * Get file extension from MIME type
75
+ */
76
+ export function getExtensionFromMimeType(mimeType: string): string {
77
+ return MIME_TO_EXTENSION[mimeType] || "";
78
+ }
79
+
80
+ /**
81
+ * Calculate SHA256 hash of data
82
+ */
83
+ export function calculateSHA256(data: Buffer): string {
84
+ return crypto.createHash("sha256").update(data).digest("hex");
85
+ }
86
+
87
+ /**
88
+ * BlossomService handles uploading media to Blossom servers with Nostr authentication.
89
+ *
90
+ * This service:
91
+ * 1. Calculates SHA256 hash of the data
92
+ * 2. Creates and signs a kind 24242 authorization event
93
+ * 3. Uploads the data to the Blossom server
94
+ * 4. Returns the result with URL and metadata
95
+ */
96
+ export class BlossomService {
97
+ private signer: BlossomSigner;
98
+
99
+ constructor(signer: BlossomSigner) {
100
+ this.signer = signer;
101
+ }
102
+
103
+ /**
104
+ * Upload data to Blossom server with Nostr authentication.
105
+ *
106
+ * @param data - The binary data to upload
107
+ * @param mimeType - MIME type of the data (e.g., "image/png")
108
+ * @param options - Upload configuration including serverUrl (required)
109
+ * @returns Upload result with URL and metadata
110
+ */
111
+ async upload(
112
+ data: Buffer,
113
+ mimeType: string,
114
+ options: BlossomUploadOptions
115
+ ): Promise<BlossomUploadResult> {
116
+ const { serverUrl, description = "Blossom upload" } = options;
117
+
118
+ // Calculate hash
119
+ const sha256Hash = calculateSHA256(data);
120
+
121
+ logger.debug("[BlossomService] Preparing upload", {
122
+ size: data.length,
123
+ mimeType,
124
+ sha256: sha256Hash.slice(0, 12) + "...",
125
+ serverUrl,
126
+ });
127
+
128
+ // Create and sign authorization event
129
+ const authEvent = await this.createAuthEvent(sha256Hash, description);
130
+
131
+ // Upload to server
132
+ const result = await this.uploadToServer(serverUrl, data, mimeType, authEvent);
133
+
134
+ logger.info("[BlossomService] Upload successful", {
135
+ url: result.url,
136
+ sha256: result.sha256,
137
+ size: result.size,
138
+ });
139
+
140
+ return result;
141
+ }
142
+
143
+ /**
144
+ * Create Blossom authorization event (kind 24242)
145
+ *
146
+ * Per Blossom protocol, this event authorizes the upload:
147
+ * - kind: 24242
148
+ * - content: description of the upload
149
+ * - tags: ["t", "upload"], ["x", sha256], ["expiration", timestamp]
150
+ */
151
+ private async createAuthEvent(sha256Hash: string, description: string): Promise<NDKEvent> {
152
+ const event = new NDKEventClass();
153
+ event.kind = 24242;
154
+ event.content = description;
155
+ event.created_at = Math.floor(Date.now() / 1000);
156
+ event.tags = [
157
+ ["t", "upload"],
158
+ ["x", sha256Hash],
159
+ ["expiration", String(Math.floor(Date.now() / 1000) + 3600)], // 1 hour expiration
160
+ ];
161
+
162
+ await this.signer.sign(event);
163
+ return event;
164
+ }
165
+
166
+ /**
167
+ * Upload data to Blossom server
168
+ */
169
+ private async uploadToServer(
170
+ serverUrl: string,
171
+ data: Buffer,
172
+ mimeType: string,
173
+ authEvent: NDKEvent
174
+ ): Promise<BlossomUploadResult> {
175
+ // Encode the auth event as base64 for the Authorization header
176
+ const authHeader = `Nostr ${Buffer.from(JSON.stringify(authEvent.rawEvent())).toString("base64")}`;
177
+
178
+ const controller = new AbortController();
179
+ const timeout = setTimeout(() => controller.abort(), UPLOAD_TIMEOUT_MS);
180
+
181
+ try {
182
+ const response = await fetch(`${serverUrl}/upload`, {
183
+ method: "PUT",
184
+ headers: {
185
+ Authorization: authHeader,
186
+ "Content-Type": mimeType,
187
+ "Content-Length": String(data.length),
188
+ },
189
+ body: data,
190
+ signal: controller.signal,
191
+ });
192
+
193
+ if (!response.ok) {
194
+ let errorMessage = `Upload failed with status ${response.status}`;
195
+ try {
196
+ const errorData = (await response.json()) as { message?: string };
197
+ if (errorData.message) {
198
+ errorMessage = `Upload failed: ${errorData.message}`;
199
+ }
200
+ } catch {
201
+ // If parsing JSON fails, use the default error message
202
+ }
203
+ throw new Error(errorMessage);
204
+ }
205
+
206
+ const result = (await response.json()) as BlossomUploadResult;
207
+
208
+ // Add extension to URL if not present
209
+ if (result.url && !result.url.match(/\.\w+$/)) {
210
+ const ext = getExtensionFromMimeType(mimeType);
211
+ if (ext) {
212
+ result.url = result.url + ext;
213
+ }
214
+ }
215
+
216
+ return result;
217
+ } catch (error) {
218
+ if (error instanceof Error && error.name === "AbortError") {
219
+ throw new Error(`Upload timed out after ${UPLOAD_TIMEOUT_MS}ms`, { cause: error });
220
+ }
221
+ throw error;
222
+ } finally {
223
+ clearTimeout(timeout);
224
+ }
225
+ }
226
+ }