@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,260 @@
1
+ /**
2
+ * Todo Tools - Conversation-scoped todo list management for agents
3
+ *
4
+ * Provides todo_write functionality for full state replacement.
5
+ * Todos are stored on the Conversation object and persisted with it.
6
+ */
7
+
8
+ import type { ConversationToolContext } from "@/tools/types";
9
+ import type { ConversationStore } from "@/conversations/ConversationStore";
10
+ import type { TodoItem, TodoStatus } from "@/services/ral/types";
11
+ import type { AISdkTool } from "@/tools/types";
12
+ import { tool } from "ai";
13
+ import { z } from "zod";
14
+
15
+ // ============================================================================
16
+ // Helper functions
17
+ // ============================================================================
18
+
19
+ function getCurrentTimestamp(): number {
20
+ return Date.now();
21
+ }
22
+
23
+ /**
24
+ * Simple deterministic hash for generating fallback IDs.
25
+ * Returns a hex string based on the input.
26
+ */
27
+ function simpleHash(str: string): string {
28
+ let hash = 0;
29
+ for (let i = 0; i < str.length; i++) {
30
+ const char = str.charCodeAt(i);
31
+ hash = ((hash << 5) - hash) + char;
32
+ hash = hash & hash; // Convert to 32bit integer
33
+ }
34
+ // Convert to positive hex string
35
+ return Math.abs(hash).toString(16);
36
+ }
37
+
38
+ /**
39
+ * Generates a slug-style ID from a title.
40
+ * Converts to lowercase, replaces spaces/special chars with hyphens, and removes consecutive hyphens.
41
+ * Falls back to a deterministic hash if the title produces an empty slug (e.g., emoji-only or non-ASCII titles).
42
+ */
43
+ function generateIdFromTitle(title: string): string {
44
+ const slug = title
45
+ .toLowerCase()
46
+ .replace(/[^a-z0-9\s-]/g, "") // Remove special characters except spaces and hyphens
47
+ .replace(/\s+/g, "-") // Replace spaces with hyphens
48
+ .replace(/-+/g, "-") // Replace consecutive hyphens with single hyphen
49
+ .replace(/^-|-$/g, ""); // Remove leading/trailing hyphens
50
+
51
+ // Fall back to hash if slug is empty (non-ASCII/emoji-only titles)
52
+ if (slug === "") {
53
+ return `todo-${simpleHash(title)}`;
54
+ }
55
+
56
+ return slug;
57
+ }
58
+
59
+ /**
60
+ * Validates and writes the complete todo list, replacing all existing items.
61
+ * Implements safety check to prevent accidental deletions.
62
+ */
63
+ function writeTodosToConversation(
64
+ conversation: ConversationStore,
65
+ agentPubkey: string,
66
+ newItems: Array<{
67
+ id: string;
68
+ title: string;
69
+ description?: string; // undefined means "preserve existing" for updates
70
+ status: TodoStatus;
71
+ skip_reason?: string;
72
+ }>,
73
+ force: boolean
74
+ ): {
75
+ success: boolean;
76
+ items: TodoItem[];
77
+ error?: string;
78
+ missingIds?: string[];
79
+ } {
80
+ const existingTodos = conversation.getTodos(agentPubkey);
81
+ const now = getCurrentTimestamp();
82
+
83
+ // Build a map of new item IDs for quick lookup
84
+ const newItemIds = new Set(newItems.map((item) => item.id));
85
+
86
+ // Check for duplicate IDs in the input array
87
+ if (newItemIds.size !== newItems.length) {
88
+ const seen = new Set<string>();
89
+ const duplicates: string[] = [];
90
+ for (const item of newItems) {
91
+ if (seen.has(item.id)) {
92
+ duplicates.push(item.id);
93
+ }
94
+ seen.add(item.id);
95
+ }
96
+ return {
97
+ success: false,
98
+ items: [],
99
+ error: `Duplicate IDs in input: ${duplicates.join(", ")}`,
100
+ };
101
+ }
102
+
103
+ // Safety check: find any existing IDs that are missing from the new list
104
+ const existingIds = existingTodos.map((t) => t.id);
105
+ const missingIds = existingIds.filter((id) => !newItemIds.has(id));
106
+
107
+ if (missingIds.length > 0 && !force) {
108
+ return {
109
+ success: false,
110
+ items: [],
111
+ error: `Safety check failed: ${missingIds.length} existing item(s) would be removed. Use force=true to allow removal.`,
112
+ missingIds,
113
+ };
114
+ }
115
+
116
+ // Validate skip_reason requirement
117
+ for (const item of newItems) {
118
+ if (item.status === "skipped" && !item.skip_reason) {
119
+ return {
120
+ success: false,
121
+ items: [],
122
+ error: `Validation failed: skip_reason is required when status='skipped' (item: ${item.id})`,
123
+ };
124
+ }
125
+ }
126
+
127
+ // Build the new todo list, preserving timestamps for existing items
128
+ // Array order determines position (index-based ordering)
129
+ const existingMap = new Map(existingTodos.map((t) => [t.id, t]));
130
+ const newTodos: TodoItem[] = newItems.map((item) => {
131
+ const existing = existingMap.get(item.id);
132
+ return {
133
+ id: item.id,
134
+ title: item.title,
135
+ // Preserve existing description if not provided in update (undefined means "keep existing")
136
+ description: item.description ?? existing?.description ?? "",
137
+ status: item.status,
138
+ skipReason: item.skip_reason,
139
+ createdAt: existing?.createdAt ?? now,
140
+ updatedAt: existing && existing.status === item.status ? existing.updatedAt : now,
141
+ };
142
+ });
143
+
144
+ // Persist the new list
145
+ conversation.setTodos(agentPubkey, newTodos);
146
+
147
+ return {
148
+ success: true,
149
+ items: newTodos,
150
+ };
151
+ }
152
+
153
+ // ============================================================================
154
+ // todo_write
155
+ // ============================================================================
156
+
157
+ const todoWriteItemSchema = z.object({
158
+ id: z.string().optional().describe("Optional unique identifier. Auto-generated from title if not provided."),
159
+ title: z.string().describe("Short human-readable title for the todo item"),
160
+ description: z.string().optional().describe("Optional detailed description. When updating existing items, omitting preserves the current description. For new items, defaults to empty string."),
161
+ status: z
162
+ .enum(["pending", "in_progress", "done", "skipped"])
163
+ .describe("Current status of the item"),
164
+ skip_reason: z
165
+ .string()
166
+ .optional()
167
+ .describe("Required when status='skipped' - explain why this item was skipped"),
168
+ });
169
+
170
+ const todoWriteSchema = z.object({
171
+ todos: z.array(todoWriteItemSchema).describe("The complete todo list. All items must be provided - this replaces the entire list."),
172
+ force: z
173
+ .boolean()
174
+ .optional()
175
+ .default(false)
176
+ .describe("When true, allows removing items from the list. Default false (safety check prevents removals)."),
177
+ });
178
+
179
+ type TodoWriteInput = z.infer<typeof todoWriteSchema>;
180
+
181
+ interface TodoWriteOutput {
182
+ success: boolean;
183
+ message: string;
184
+ totalItems: number;
185
+ error?: string;
186
+ missingIds?: string[];
187
+ }
188
+
189
+ async function executeTodoWrite(
190
+ input: TodoWriteInput,
191
+ context: ConversationToolContext
192
+ ): Promise<TodoWriteOutput> {
193
+ const conversation = context.getConversation();
194
+
195
+ const result = writeTodosToConversation(
196
+ conversation,
197
+ context.agent.pubkey,
198
+ input.todos.map((item) => ({
199
+ id: item.id ?? generateIdFromTitle(item.title),
200
+ title: item.title,
201
+ description: item.description, // Pass undefined to preserve existing; handled in writeTodosToConversation
202
+ status: item.status as TodoStatus,
203
+ skip_reason: item.skip_reason,
204
+ })),
205
+ input.force ?? false
206
+ );
207
+
208
+ // Generate a concise message based on the operation
209
+ let message: string;
210
+ if (!result.success) {
211
+ message = result.error || "Failed to write todos";
212
+ } else if (result.items.length === 0) {
213
+ message = "Todo list cleared";
214
+ } else if (result.items.length === 1) {
215
+ message = `Todo list updated with 1 item`;
216
+ } else {
217
+ message = `Todo list updated with ${result.items.length} items`;
218
+ }
219
+
220
+ return {
221
+ success: result.success,
222
+ message,
223
+ totalItems: result.items.length,
224
+ error: result.error,
225
+ missingIds: result.missingIds,
226
+ };
227
+ }
228
+
229
+ export function createTodoWriteTool(context: ConversationToolContext): AISdkTool {
230
+ const aiTool = tool({
231
+ description:
232
+ "Write the complete todo list, replacing all existing items. Provide ALL items you want to exist - " +
233
+ "this is a full state replacement. By default, removing existing items is blocked (safety check). " +
234
+ "Set force=true to allow item removal. Each item requires: title and status. " +
235
+ "Optional: id (auto-generated from title if not provided), description (preserved on update if omitted, empty for new items). " +
236
+ "Use skip_reason when status='skipped'. " +
237
+ "NOTE: If an existing item used a custom ID and you omit the id field, the auto-generated ID won't match, " +
238
+ "which will trigger the safety check (or require force=true). Always include IDs for items with custom IDs.",
239
+ inputSchema: todoWriteSchema,
240
+ execute: async (input: TodoWriteInput) => {
241
+ return await executeTodoWrite(input, context);
242
+ },
243
+ });
244
+
245
+ Object.defineProperty(aiTool, "getHumanReadableContent", {
246
+ value: ({ todos }: TodoWriteInput) => {
247
+ if (todos.length === 0) {
248
+ return "Clearing todo list";
249
+ }
250
+ if (todos.length === 1) {
251
+ return `Writing todo list with 1 item: "${todos[0].title}"`;
252
+ }
253
+ return `Writing todo list with ${todos.length} items`;
254
+ },
255
+ enumerable: false,
256
+ configurable: true,
257
+ });
258
+
259
+ return aiTool as AISdkTool;
260
+ }
@@ -0,0 +1,381 @@
1
+ /**
2
+ * Upload Blob Tool
3
+ *
4
+ * Uploads files, URLs, or base64 blobs to a Blossom server using Nostr authentication.
5
+ * Supports downloading from URLs, reading local files, and handling base64-encoded data.
6
+ *
7
+ * The tool delegates Blossom upload operations to BlossomService in the nostr layer,
8
+ * keeping NDK usage centralized.
9
+ */
10
+
11
+ import * as fs from "node:fs/promises";
12
+ import * as path from "node:path";
13
+ import type { ToolExecutionContext, AISdkTool } from "@/tools/types";
14
+ import { BlossomService } from "@/nostr/BlossomService";
15
+ import { config } from "@/services/ConfigService";
16
+ import { logger } from "@/utils/logger";
17
+ import { tool } from "ai";
18
+ import { z } from "zod";
19
+
20
+ const MAX_BLOB_SIZE_BYTES = 50 * 1024 * 1024; // 50 MB
21
+ const DOWNLOAD_TIMEOUT_MS = 30_000;
22
+ const DEFAULT_BLOSSOM_SERVER = "https://blossom.primal.net";
23
+
24
+ /**
25
+ * Load Blossom server URL from config.
26
+ * Falls back to default if config is unavailable.
27
+ */
28
+ async function loadBlossomServerUrl(): Promise<string> {
29
+ try {
30
+ const tenexConfig = await config.loadTenexConfig(config.getGlobalPath());
31
+ return tenexConfig.blossomServerUrl || DEFAULT_BLOSSOM_SERVER;
32
+ } catch (error) {
33
+ logger.warn("[upload_blob] Failed to load Blossom config, using default", {
34
+ error: error instanceof Error ? error.message : String(error),
35
+ });
36
+ return DEFAULT_BLOSSOM_SERVER;
37
+ }
38
+ }
39
+
40
+ const uploadBlobSchema = z.object({
41
+ input: z
42
+ .string()
43
+ .describe(
44
+ "REQUIRED: The source to upload - can be a file path (e.g., /path/to/file.jpg), URL to download from (e.g., https://example.com/image.jpg), or base64-encoded blob data. This parameter must be named 'input', not 'url' or 'file'."
45
+ ),
46
+ mimeType: z
47
+ .string()
48
+ .nullable()
49
+ .describe(
50
+ "MIME type of the data (e.g., 'image/jpeg', 'video/mp4'). If not provided, it will be detected from the file extension, URL response headers, or data"
51
+ ),
52
+ description: z
53
+ .string()
54
+ .nullable()
55
+ .describe("Optional description of the upload for the authorization event"),
56
+ });
57
+
58
+ type UploadBlobInput = z.infer<typeof uploadBlobSchema>;
59
+
60
+ interface UploadBlobOutput {
61
+ url: string;
62
+ sha256: string;
63
+ size: number;
64
+ type?: string;
65
+ uploaded?: number;
66
+ }
67
+
68
+ /**
69
+ * Enforce size limit on blob data
70
+ */
71
+ function enforceSizeLimit(bytes: number): void {
72
+ if (bytes > MAX_BLOB_SIZE_BYTES) {
73
+ throw new Error(
74
+ `Blob size ${bytes} bytes exceeds limit of ${MAX_BLOB_SIZE_BYTES} bytes. Please provide a smaller file.`
75
+ );
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Detect MIME type from file extension or data magic bytes
81
+ */
82
+ function detectMimeType(filePath?: string, data?: Buffer): string {
83
+ // Try file extension first
84
+ if (filePath) {
85
+ const ext = path.extname(filePath).toLowerCase();
86
+ const mimeTypes: Record<string, string> = {
87
+ ".jpg": "image/jpeg",
88
+ ".jpeg": "image/jpeg",
89
+ ".png": "image/png",
90
+ ".gif": "image/gif",
91
+ ".webp": "image/webp",
92
+ ".mp4": "video/mp4",
93
+ ".mov": "video/quicktime",
94
+ ".avi": "video/x-msvideo",
95
+ ".webm": "video/webm",
96
+ ".mp3": "audio/mpeg",
97
+ ".wav": "audio/wav",
98
+ ".pdf": "application/pdf",
99
+ ".json": "application/json",
100
+ ".txt": "text/plain",
101
+ };
102
+ if (mimeTypes[ext]) {
103
+ return mimeTypes[ext];
104
+ }
105
+ }
106
+
107
+ // Try magic bytes detection
108
+ if (data && data.length > 4) {
109
+ const header = data.slice(0, 4).toString("hex");
110
+ if (header.startsWith("ffd8ff")) return "image/jpeg";
111
+ if (header === "89504e47") return "image/png";
112
+ if (header === "47494638") return "image/gif";
113
+ if (header.startsWith("52494646") && data.length > 12) {
114
+ if (data.slice(8, 12).toString("hex") === "57454250") {
115
+ return "image/webp";
116
+ }
117
+ }
118
+ }
119
+
120
+ return "application/octet-stream";
121
+ }
122
+
123
+ /**
124
+ * Check if input is a URL
125
+ */
126
+ function isURL(input: string): boolean {
127
+ try {
128
+ const url = new URL(input);
129
+ return url.protocol === "http:" || url.protocol === "https:";
130
+ } catch {
131
+ return false;
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Download media from URL
137
+ */
138
+ async function downloadFromURL(
139
+ url: string
140
+ ): Promise<{ data: Buffer; mimeType?: string; filename?: string }> {
141
+ logger.info("[upload_blob] Downloading from URL", { url });
142
+
143
+ const controller = new AbortController();
144
+ const timeout = setTimeout(() => controller.abort(), DOWNLOAD_TIMEOUT_MS);
145
+
146
+ try {
147
+ const response = await fetch(url, {
148
+ headers: {
149
+ "User-Agent": "TENEX/1.0 (Blossom Upload Tool)",
150
+ },
151
+ signal: controller.signal,
152
+ });
153
+
154
+ if (!response.ok) {
155
+ throw new Error(
156
+ `Failed to download from URL: ${response.status} ${response.statusText}`
157
+ );
158
+ }
159
+
160
+ // Check declared size before downloading body
161
+ const declaredSize = response.headers.get("content-length");
162
+ if (declaredSize) {
163
+ const parsed = Number.parseInt(declaredSize, 10);
164
+ if (Number.isFinite(parsed)) {
165
+ enforceSizeLimit(parsed);
166
+ }
167
+ }
168
+
169
+ // Get content type from headers
170
+ const contentType = response.headers.get("content-type");
171
+ const mimeType = contentType?.split(";")[0].trim();
172
+
173
+ // Try to extract filename from Content-Disposition header or URL
174
+ let filename: string | undefined;
175
+ const contentDisposition = response.headers.get("content-disposition");
176
+ if (contentDisposition) {
177
+ const filenameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
178
+ if (filenameMatch) {
179
+ filename = filenameMatch[1].replace(/['"]/g, "");
180
+ }
181
+ }
182
+
183
+ if (!filename) {
184
+ // Try to extract filename from URL
185
+ const urlPath = new URL(url).pathname;
186
+ const pathSegments = urlPath.split("/");
187
+ const lastSegment = pathSegments[pathSegments.length - 1];
188
+ if (lastSegment?.includes(".")) {
189
+ filename = lastSegment;
190
+ }
191
+ }
192
+
193
+ const arrayBuffer = await response.arrayBuffer();
194
+ const data = Buffer.from(arrayBuffer);
195
+ enforceSizeLimit(data.length);
196
+
197
+ logger.info("[upload_blob] Downloaded from URL", {
198
+ size: data.length,
199
+ mimeType,
200
+ filename,
201
+ });
202
+
203
+ return { data, mimeType, filename };
204
+ } catch (error) {
205
+ if (error instanceof Error && error.name === "AbortError") {
206
+ throw new Error(
207
+ `Download timed out after ${DOWNLOAD_TIMEOUT_MS}ms while fetching ${url}`,
208
+ { cause: error }
209
+ );
210
+ }
211
+ throw error;
212
+ } finally {
213
+ clearTimeout(timeout);
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Resolve input to data buffer and MIME type
219
+ */
220
+ async function resolveInput(
221
+ dataInput: string,
222
+ providedMimeType: string | null
223
+ ): Promise<{ data: Buffer; mimeType: string; description: string }> {
224
+ // Handle URL download
225
+ if (isURL(dataInput)) {
226
+ const downloadResult = await downloadFromURL(dataInput);
227
+ return {
228
+ data: downloadResult.data,
229
+ mimeType:
230
+ providedMimeType ||
231
+ downloadResult.mimeType ||
232
+ detectMimeType(downloadResult.filename, downloadResult.data),
233
+ description: downloadResult.filename || "Upload from URL",
234
+ };
235
+ }
236
+
237
+ // Handle base64 data (with or without data URL prefix)
238
+ if (dataInput.startsWith("data:") || dataInput.includes(",")) {
239
+ const base64Data = dataInput.includes(",") ? dataInput.split(",")[1] : dataInput;
240
+ let mimeType: string;
241
+
242
+ // Extract MIME type from data URL if present
243
+ if (dataInput.startsWith("data:")) {
244
+ const matches = dataInput.match(/^data:([^;]+);/);
245
+ mimeType = matches ? matches[1] : (providedMimeType || "application/octet-stream");
246
+ } else {
247
+ mimeType = providedMimeType || "application/octet-stream";
248
+ }
249
+
250
+ const data = Buffer.from(base64Data, "base64");
251
+ enforceSizeLimit(data.length);
252
+
253
+ return {
254
+ data,
255
+ mimeType,
256
+ description: "Upload blob data",
257
+ };
258
+ }
259
+
260
+ // Handle file path
261
+ const filePath = path.resolve(dataInput);
262
+
263
+ try {
264
+ await fs.access(filePath);
265
+ } catch {
266
+ throw new Error(`File not found: ${filePath}`);
267
+ }
268
+
269
+ const stats = await fs.stat(filePath);
270
+ enforceSizeLimit(stats.size);
271
+
272
+ const data = await fs.readFile(filePath);
273
+ const mimeType = providedMimeType || detectMimeType(filePath, data);
274
+
275
+ return {
276
+ data,
277
+ mimeType,
278
+ description: `Upload ${path.basename(filePath)}`,
279
+ };
280
+ }
281
+
282
+ /**
283
+ * Execute the upload_blob tool
284
+ */
285
+ async function executeUploadBlob(
286
+ input: UploadBlobInput,
287
+ context: ToolExecutionContext
288
+ ): Promise<UploadBlobOutput> {
289
+ const { input: dataInput, mimeType: providedMimeType, description: providedDescription } = input;
290
+
291
+ // Validate that input is provided
292
+ if (!dataInput) {
293
+ throw new Error(
294
+ "The 'input' parameter is required. Pass the URL, file path, or base64 data via { input: '...' }. Note: The parameter name is 'input', not 'url' or 'file'."
295
+ );
296
+ }
297
+
298
+ logger.info("[upload_blob] Starting blob upload", {
299
+ isURL: isURL(dataInput),
300
+ hasFilePath: !isURL(dataInput) && !dataInput.startsWith("data:") && !dataInput.includes(","),
301
+ hasMimeType: !!providedMimeType,
302
+ description: providedDescription,
303
+ });
304
+
305
+ // Resolve input to data buffer
306
+ const resolved = await resolveInput(dataInput, providedMimeType);
307
+ const uploadDescription = providedDescription || resolved.description;
308
+
309
+ logger.info("[upload_blob] Resolved input", {
310
+ size: resolved.data.length,
311
+ mimeType: resolved.mimeType,
312
+ description: uploadDescription,
313
+ });
314
+
315
+ // Upload to Blossom using the service (delegates NDK usage to nostr layer)
316
+ // Layer 3 (tools) loads config and passes serverUrl to Layer 2 (nostr)
317
+ const blossomServerUrl = await loadBlossomServerUrl();
318
+ const blossomService = new BlossomService(context.agent);
319
+ const result = await blossomService.upload(resolved.data, resolved.mimeType, {
320
+ serverUrl: blossomServerUrl,
321
+ description: uploadDescription,
322
+ });
323
+
324
+ logger.info("[upload_blob] Upload successful", {
325
+ url: result.url,
326
+ sha256: result.sha256,
327
+ size: result.size,
328
+ });
329
+
330
+ return result;
331
+ }
332
+
333
+ /**
334
+ * Create the upload_blob tool for AI SDK
335
+ */
336
+ export function createUploadBlobTool(context: ToolExecutionContext): AISdkTool {
337
+ const aiTool = tool({
338
+ description: `Upload files, URLs, or base64 blobs to a Blossom server.
339
+
340
+ IMPORTANT: The parameter is named 'input' (not 'url' or 'file').
341
+
342
+ Pass the source via the 'input' parameter:
343
+ - URLs: { input: "https://example.com/image.jpg" }
344
+ - File paths: { input: "/path/to/file.jpg" }
345
+ - Base64 data: { input: "data:image/jpeg;base64,..." } or { input: "<base64_string>" }
346
+
347
+ Optional parameters:
348
+ - mimeType: Specify MIME type (auto-detected if not provided)
349
+ - description: Add a description for the upload
350
+
351
+ The Blossom server URL is configured in .tenex/config.json (default: https://blossom.primal.net).
352
+ Returns the URL of the uploaded media with appropriate file extension.`,
353
+ inputSchema: uploadBlobSchema,
354
+ execute: async (input: UploadBlobInput) => {
355
+ return await executeUploadBlob(input, context);
356
+ },
357
+ });
358
+
359
+ // Add human-readable content generation
360
+ Object.defineProperty(aiTool, "getHumanReadableContent", {
361
+ value: (args: UploadBlobInput | undefined) => {
362
+ if (!args || !args.input) {
363
+ return "Uploading blob data";
364
+ }
365
+ const { input, description } = args;
366
+
367
+ if (isURL(input)) {
368
+ const url = new URL(input);
369
+ return `Downloading and uploading from ${url.hostname}${description ? ` - ${description}` : ""}`;
370
+ }
371
+ if (!input.startsWith("data:") && !input.includes(",")) {
372
+ return `Uploading file: ${path.basename(input)}${description ? ` - ${description}` : ""}`;
373
+ }
374
+ return `Uploading blob data${description ? ` - ${description}` : ""}`;
375
+ },
376
+ enumerable: false,
377
+ configurable: true,
378
+ });
379
+
380
+ return aiTool as AISdkTool;
381
+ }