@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,209 @@
1
+ import type { ToolExecutionContext } from "@/tools/types";
2
+ import { agentStorage } from "@/agents/AgentStorage";
3
+ import { getDaemon } from "@/daemon";
4
+ import { getNDK } from "@/nostr/ndkClient";
5
+ import { PendingDelegationsRegistry, RALRegistry } from "@/services/ral";
6
+ import type { PendingDelegation } from "@/services/ral/types";
7
+ import type { AISdkTool } from "@/tools/types";
8
+ import { shortenConversationId } from "@/utils/conversation-id";
9
+ import { logger } from "@/utils/logger";
10
+ import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
11
+ import { tool } from "ai";
12
+ import { z } from "zod";
13
+
14
+ const delegateCrossProjectSchema = z.object({
15
+ content: z.string().describe("The content of the chat message to send"),
16
+ projectId: z
17
+ .string()
18
+ .describe(
19
+ "The project ID (dTag) to delegate to. Use project_list to discover available projects."
20
+ ),
21
+ agentSlug: z
22
+ .string()
23
+ .describe(
24
+ "The slug of the agent within the target project to delegate to. Use project_list to see available agents."
25
+ ),
26
+ });
27
+
28
+ type DelegateCrossProjectInput = z.infer<typeof delegateCrossProjectSchema>;
29
+
30
+ interface DelegateCrossProjectOutput {
31
+ success: boolean;
32
+ message: string;
33
+ delegationConversationId: string;
34
+ }
35
+
36
+ /**
37
+ * Check if the agent has created a todo list.
38
+ * Returns { hasTodos: boolean, hasConversation: boolean } to distinguish
39
+ * between "no todos" and "no conversation context" cases.
40
+ */
41
+ function checkTodoState(context: ToolExecutionContext): { hasTodos: boolean; hasConversation: boolean } {
42
+ const conversation = context.getConversation();
43
+ if (!conversation) {
44
+ // No conversation context available - skip enforcement
45
+ return { hasTodos: true, hasConversation: false };
46
+ }
47
+ const todos = conversation.getTodos(context.agent.pubkey);
48
+ return { hasTodos: todos.length > 0, hasConversation: true };
49
+ }
50
+
51
+ async function executeDelegateCrossProject(
52
+ input: DelegateCrossProjectInput,
53
+ context: ToolExecutionContext
54
+ ): Promise<DelegateCrossProjectOutput> {
55
+ const { content, projectId, agentSlug } = input;
56
+
57
+ // ENFORCEMENT: Delegation requires a todo list
58
+ // Skip enforcement if no conversation context (e.g., MCP-only mode)
59
+ const todoState = checkTodoState(context);
60
+ if (todoState.hasConversation && !todoState.hasTodos) {
61
+ throw new Error(
62
+ "Delegation requires a todo list. Please use `todo_write()` to create a todo list before delegating tasks. " +
63
+ "This helps track work progress and ensures delegated tasks are properly documented."
64
+ );
65
+ }
66
+
67
+ // Get known projects from daemon
68
+ const daemon = getDaemon();
69
+ const knownProjects = daemon.getKnownProjects();
70
+ const activeRuntimes = daemon.getActiveRuntimes();
71
+
72
+ // Find project by id (dTag) - projectId in knownProjects is "31933:pubkey:dTag"
73
+ let project = null;
74
+ let fullProjectId = "";
75
+ for (const [pId, p] of knownProjects) {
76
+ const dTag = pId.split(":")[2];
77
+ if (dTag === projectId) {
78
+ project = p;
79
+ fullProjectId = pId;
80
+ break;
81
+ }
82
+ }
83
+
84
+ if (!project) {
85
+ throw new Error(
86
+ `Project '${projectId}' not found. Use project_list to see available projects.`
87
+ );
88
+ }
89
+
90
+ // Find agent pubkey
91
+ let agentPubkey: string | null = null;
92
+
93
+ // Try runtime first (if project is running)
94
+ const runtime = activeRuntimes.get(fullProjectId);
95
+ if (runtime) {
96
+ const runtimeContext = runtime.getContext();
97
+ const agentMap = runtimeContext?.agentRegistry.getAllAgentsMap();
98
+ if (agentMap) {
99
+ for (const agent of agentMap.values()) {
100
+ if (agent.slug === agentSlug) {
101
+ agentPubkey = agent.pubkey;
102
+ break;
103
+ }
104
+ }
105
+ }
106
+ }
107
+
108
+ // Fall back to storage
109
+ if (!agentPubkey) {
110
+ const agents = await agentStorage.getProjectAgents(projectId);
111
+ const agent = agents.find((a) => a.slug === agentSlug);
112
+ if (agent) {
113
+ const signer = new NDKPrivateKeySigner(agent.nsec);
114
+ agentPubkey = signer.pubkey;
115
+ }
116
+ }
117
+
118
+ if (!agentPubkey) {
119
+ throw new Error(
120
+ `Agent '${agentSlug}' not found in project '${projectId}'. Use project_list to see available agents.`
121
+ );
122
+ }
123
+
124
+ const ndk = getNDK();
125
+
126
+ logger.info("[delegate_crossproject] Publishing cross-project delegation", {
127
+ agent: context.agent.name,
128
+ targetProject: projectId,
129
+ targetAgent: agentSlug,
130
+ recipientPubkey: agentPubkey.substring(0, 8),
131
+ });
132
+
133
+ // Create delegation event
134
+ const chatEvent = new NDKEvent(ndk);
135
+ chatEvent.kind = 1;
136
+ chatEvent.content = content;
137
+ chatEvent.tags.push(["p", agentPubkey]);
138
+ // Add "a" tag referencing the target project
139
+ chatEvent.tags.push(["a", `31933:${project.pubkey}:${projectId}`]);
140
+
141
+ await context.agent.sign(chatEvent);
142
+ await chatEvent.publish();
143
+
144
+ // Register with PendingDelegationsRegistry for q-tag correlation
145
+ PendingDelegationsRegistry.register(context.agent.pubkey, context.conversationId, chatEvent.id);
146
+
147
+ // Register pending delegation in RALRegistry for response routing
148
+ // Uses atomic merge to safely handle concurrent delegation calls
149
+ const ralRegistry = RALRegistry.getInstance();
150
+ const newDelegation: PendingDelegation = {
151
+ type: "external" as const,
152
+ delegationConversationId: chatEvent.id,
153
+ recipientPubkey: agentPubkey,
154
+ senderPubkey: context.agent.pubkey,
155
+ prompt: content,
156
+ projectId: fullProjectId,
157
+ ralNumber: context.ralNumber,
158
+ };
159
+
160
+ ralRegistry.mergePendingDelegations(
161
+ context.agent.pubkey,
162
+ context.conversationId,
163
+ context.ralNumber,
164
+ [newDelegation]
165
+ );
166
+
167
+ logger.info("[delegate_crossproject] Delegation registered, agent continues without blocking", {
168
+ delegationConversationId: chatEvent.id,
169
+ });
170
+
171
+ // Return normal result - agent continues without blocking
172
+ return {
173
+ success: true,
174
+ message: `Delegated to agent '${agentSlug}' in project '${projectId}'. The agent will respond when ready.`,
175
+ delegationConversationId: shortenConversationId(chatEvent.id),
176
+ };
177
+ }
178
+
179
+ export function createDelegateCrossProjectTool(context: ToolExecutionContext): AISdkTool {
180
+ const aiTool = tool({
181
+ description: `Delegate a task to an agent in another project. Use project_list first to discover available projects and their agents.
182
+
183
+ When using this tool, provide context to the recipient, introduce yourself and explain you are an agent and the project you are working on.`,
184
+ inputSchema: delegateCrossProjectSchema,
185
+ execute: async (input: DelegateCrossProjectInput) => {
186
+ return await executeDelegateCrossProject(input, context);
187
+ },
188
+ });
189
+
190
+ Object.defineProperty(aiTool, "getHumanReadableContent", {
191
+ value: (args: unknown) => {
192
+ if (!args || typeof args !== "object") {
193
+ return "Delegating to cross-project agent";
194
+ }
195
+
196
+ const { projectId, agentSlug } = args as Partial<DelegateCrossProjectInput>;
197
+
198
+ if (!projectId || !agentSlug) {
199
+ return "Delegating to cross-project agent";
200
+ }
201
+
202
+ return `Delegating to agent '${agentSlug}' in project '${projectId}'`;
203
+ },
204
+ enumerable: false,
205
+ configurable: true,
206
+ });
207
+
208
+ return aiTool as AISdkTool;
209
+ }
@@ -0,0 +1,300 @@
1
+ import type { ToolExecutionContext } from "@/tools/types";
2
+ import { getNDK } from "@/nostr";
3
+ import { RALRegistry } from "@/services/ral/RALRegistry";
4
+ import type { AISdkTool } from "@/tools/types";
5
+ import { shortenConversationId } from "@/utils/conversation-id";
6
+ import { logger } from "@/utils/logger";
7
+ import { isHexPrefix, resolvePrefixToId, PREFIX_LENGTH } from "@/utils/nostr-entity-parser";
8
+ import { createEventContext } from "@/services/event-context";
9
+ import { tool } from "ai";
10
+ import { nip19 } from "nostr-tools";
11
+ import { z } from "zod";
12
+
13
+ /**
14
+ * Attempts to decode a NIP-19 event ID format to a hex event ID.
15
+ * Supports: note1..., nevent1..., with or without 'nostr:' prefix.
16
+ *
17
+ * @param input - A potential NIP-19 event ID
18
+ * @returns The decoded 64-char hex event ID, or null if not a valid NIP-19 event format
19
+ */
20
+ function decodeNip19EventId(input: string): string | null {
21
+ try {
22
+ // Strip nostr: prefix if present
23
+ let cleaned = input.trim();
24
+ if (cleaned.toLowerCase().startsWith("nostr:")) {
25
+ cleaned = cleaned.substring(6);
26
+ }
27
+
28
+ // Only attempt decode for note1 or nevent1 prefixes
29
+ if (!cleaned.startsWith("note1") && !cleaned.startsWith("nevent1")) {
30
+ return null;
31
+ }
32
+
33
+ const decoded = nip19.decode(cleaned);
34
+
35
+ if (decoded.type === "note") {
36
+ return (decoded.data as string).toLowerCase();
37
+ }
38
+
39
+ if (decoded.type === "nevent") {
40
+ return (decoded.data as { id: string }).id.toLowerCase();
41
+ }
42
+
43
+ // Other NIP-19 types (npub, nprofile, naddr) are not valid event IDs
44
+ return null;
45
+ } catch {
46
+ // Not a valid NIP-19 format
47
+ return null;
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Checks if a string is a 64-character hex ID.
53
+ */
54
+ function isFullHexId(input: string): boolean {
55
+ return /^[0-9a-fA-F]{64}$/.test(input.trim());
56
+ }
57
+
58
+ /**
59
+ * Fallback resolver for 12-char hex prefixes when PrefixKVStore is not initialized.
60
+ *
61
+ * This handles edge cases where:
62
+ * 1. MCP-only execution mode - PrefixKVStore may not be initialized in pure MCP contexts
63
+ * 2. Timing races with event indexing - the event may exist in RAL but not yet indexed in KV store
64
+ *
65
+ * Scans RALRegistry.pending and RALRegistry.completed for matching delegation conversation IDs.
66
+ * Returns the full 64-char canonical delegation ID if a unique match is found, null otherwise.
67
+ * Note: Unlike PrefixKVStore, this fallback already canonicalizes followup IDs.
68
+ *
69
+ * @param prefix - 12-character hex prefix to resolve
70
+ * @param ralRegistry - RALRegistry instance to scan
71
+ * @returns Full 64-char canonical delegation ID if unique match found, null otherwise
72
+ */
73
+ function resolveFromRALFallback(prefix: string, ralRegistry: RALRegistry): string | null {
74
+ const normalizedPrefix = prefix.toLowerCase();
75
+ return ralRegistry.resolveDelegationPrefix(normalizedPrefix);
76
+ }
77
+
78
+ /**
79
+ * Attempts to resolve a 12-char hex prefix to a full delegation conversation ID.
80
+ * Uses PrefixKVStore first, falls back to RALRegistry scan if needed.
81
+ *
82
+ * IMPORTANT: This function always returns the canonical delegation conversation ID.
83
+ * When PrefixKVStore resolves a followup event ID prefix, the result is canonicalized
84
+ * to the original delegation conversation ID. This ensures consistent behavior across
85
+ * daemon mode (PrefixKVStore available) and MCP-only mode (RAL fallback only).
86
+ *
87
+ * @param prefix - 12-character hex prefix to resolve
88
+ * @returns Full 64-char canonical delegation ID or null if not found
89
+ */
90
+ function resolveDelegationPrefix(prefix: string): string | null {
91
+ const ralRegistry = RALRegistry.getInstance();
92
+
93
+ // Try PrefixKVStore first (primary resolution path)
94
+ const resolved = resolvePrefixToId(prefix);
95
+ if (resolved) {
96
+ // Post-resolution canonicalization: PrefixKVStore may return a followup event ID
97
+ // when the user provides a followup ID prefix. Canonicalize to the original
98
+ // delegation conversation ID for consistent e-tag and routing behavior.
99
+ const canonicalized = ralRegistry.canonicalizeDelegationId(resolved);
100
+ if (canonicalized !== resolved) {
101
+ logger.info("[delegate_followup] Canonicalized followup ID from PrefixKVStore", {
102
+ followupId: resolved.substring(0, PREFIX_LENGTH),
103
+ canonicalId: canonicalized.substring(0, PREFIX_LENGTH),
104
+ });
105
+ }
106
+ return canonicalized;
107
+ }
108
+
109
+ // Fallback: scan RALRegistry for matching delegation conversation IDs
110
+ // This handles MCP-only execution mode and timing races with event indexing
111
+ // Note: RAL fallback already canonicalizes followup IDs internally
112
+ const fallbackResolved = resolveFromRALFallback(prefix, ralRegistry);
113
+
114
+ if (fallbackResolved) {
115
+ // Use info level - MCP-only execution is an expected deployment mode, not a warning condition.
116
+ // PrefixKVStore may intentionally not be initialized in pure MCP contexts.
117
+ logger.info("[delegate_followup] Resolved prefix via RAL fallback", {
118
+ prefix: prefix.substring(0, PREFIX_LENGTH),
119
+ resolvedId: fallbackResolved.substring(0, PREFIX_LENGTH),
120
+ });
121
+ return fallbackResolved;
122
+ }
123
+
124
+ return null;
125
+ }
126
+
127
+ const delegateFollowupSchema = z.object({
128
+ delegation_conversation_id: z
129
+ .string()
130
+ .describe(
131
+ "The ID of the delegation to follow up on. Accepts: delegationConversationId (from delegate response), " +
132
+ "followupEventId (from delegate_followup response), full 64-char hex, 12-char prefix, or NIP-19 formats " +
133
+ "(note1..., nevent1...) with or without 'nostr:' prefix. Followup IDs are automatically canonicalized " +
134
+ "to the original delegation conversation ID."
135
+ ),
136
+ message: z.string().describe("Your follow-up question or clarification request"),
137
+ });
138
+
139
+ type DelegateFollowupInput = z.infer<typeof delegateFollowupSchema>;
140
+
141
+ interface DelegateFollowupOutput {
142
+ success: boolean;
143
+ message: string;
144
+ delegationConversationId: string;
145
+ followupEventId: string;
146
+ }
147
+
148
+ async function executeDelegateFollowup(
149
+ input: DelegateFollowupInput,
150
+ context: ToolExecutionContext
151
+ ): Promise<DelegateFollowupOutput> {
152
+ const { delegation_conversation_id: inputConversationId, message } = input;
153
+
154
+ // Resolve input to full canonical delegation conversation ID.
155
+ // Handles all input formats: 12-char prefixes, full 64-char hex, NIP-19 formats.
156
+ // All formats are canonicalized to the original delegation conversation ID.
157
+ const ralRegistry = RALRegistry.getInstance();
158
+ let delegation_conversation_id = inputConversationId;
159
+
160
+ // Step 1: Handle 12-char hex prefix resolution
161
+ if (isHexPrefix(inputConversationId)) {
162
+ const resolved = resolveDelegationPrefix(inputConversationId);
163
+ if (!resolved) {
164
+ throw new Error(
165
+ `Could not resolve prefix "${inputConversationId}" to a delegation. Valid inputs include: ` +
166
+ "delegationConversationId (from delegate response), followupEventId (from delegate_followup response), " +
167
+ "full 64-char hex IDs, 12-char prefixes, or NIP-19 formats (note1..., nevent1...) with or without 'nostr:' prefix. " +
168
+ "The prefix may be ambiguous or no matching delegation was found."
169
+ );
170
+ }
171
+ delegation_conversation_id = resolved;
172
+ }
173
+ // Step 2: Handle NIP-19 formats (nostr:nevent1..., note1..., etc.)
174
+ else {
175
+ const decodedNip19 = decodeNip19EventId(inputConversationId);
176
+ if (decodedNip19) {
177
+ // Successfully decoded NIP-19 to hex, now canonicalize
178
+ const canonicalized = ralRegistry.canonicalizeDelegationId(decodedNip19);
179
+ if (canonicalized !== decodedNip19) {
180
+ logger.info("[delegate_followup] Canonicalized NIP-19 followup ID", {
181
+ inputFormat: inputConversationId.substring(0, 20) + "...",
182
+ decodedHex: decodedNip19.substring(0, PREFIX_LENGTH),
183
+ canonicalId: canonicalized.substring(0, PREFIX_LENGTH),
184
+ });
185
+ }
186
+ delegation_conversation_id = canonicalized;
187
+ }
188
+ // Step 3: Handle full 64-char hex IDs that might be followup event IDs
189
+ else if (isFullHexId(inputConversationId)) {
190
+ const normalized = inputConversationId.toLowerCase();
191
+ const canonicalized = ralRegistry.canonicalizeDelegationId(normalized);
192
+ if (canonicalized !== normalized) {
193
+ logger.info("[delegate_followup] Canonicalized full hex followup ID", {
194
+ followupId: normalized.substring(0, PREFIX_LENGTH),
195
+ canonicalId: canonicalized.substring(0, PREFIX_LENGTH),
196
+ });
197
+ }
198
+ delegation_conversation_id = canonicalized;
199
+ }
200
+ // Step 4: Unknown format - pass through unchanged with debug hint
201
+ else {
202
+ logger.debug("[delegate_followup] Unknown input format, using as-is", {
203
+ input: inputConversationId.substring(0, 20),
204
+ });
205
+ }
206
+ }
207
+
208
+ // Find the delegation in conversation storage (persists even after RAL is cleared)
209
+ const delegationInfo = ralRegistry.findDelegation(delegation_conversation_id);
210
+
211
+ let recipientPubkey = delegationInfo?.pending?.recipientPubkey ?? delegationInfo?.completed?.recipientPubkey;
212
+
213
+ // Fall back to NDK fetch if not found locally (e.g., external delegations or stale state)
214
+ if (!recipientPubkey) {
215
+ const ndk = getNDK();
216
+ const delegationEvent = await ndk.fetchEvent(delegation_conversation_id);
217
+
218
+ if (!delegationEvent) {
219
+ throw new Error(
220
+ `Could not fetch delegation conversation ${delegation_conversation_id}. Check the delegationConversationIds from your delegate call.`
221
+ );
222
+ }
223
+
224
+ recipientPubkey = delegationEvent.tagValue("p") ?? undefined;
225
+ }
226
+
227
+ if (!recipientPubkey) {
228
+ throw new Error(
229
+ `Delegation conversation ${delegation_conversation_id} has no recipient. Cannot determine who to send follow-up to.`
230
+ );
231
+ }
232
+
233
+ // Always use the CURRENT RAL number from context.
234
+ // The delegation's stored ralNumber refers to the RAL that created it, which may have
235
+ // been cleared since then. We need to register on the CURRENT RAL so it resumes correctly.
236
+ const effectiveRalNumber = context.ralNumber;
237
+
238
+ logger.info("[delegate_followup] Publishing follow-up", {
239
+ fromAgent: context.agent.slug,
240
+ delegationConversationId: delegation_conversation_id,
241
+ recipientPubkey: recipientPubkey.substring(0, 8),
242
+ });
243
+
244
+ const eventContext = createEventContext(context);
245
+ const followupEventId = await context.agentPublisher.delegateFollowup({
246
+ recipient: recipientPubkey,
247
+ content: message,
248
+ delegationEventId: delegation_conversation_id,
249
+ }, eventContext);
250
+
251
+ // Register the followup as a pending delegation for response routing
252
+ // Use atomic merge to safely handle concurrent delegation calls
253
+ // Note: followup delegations use the same delegationConversationId as the original,
254
+ // but include the followupEventId for routing responses to the new event
255
+ const newDelegation = {
256
+ type: "followup" as const,
257
+ delegationConversationId: delegation_conversation_id,
258
+ recipientPubkey,
259
+ senderPubkey: context.agent.pubkey,
260
+ prompt: message,
261
+ followupEventId,
262
+ ralNumber: effectiveRalNumber,
263
+ };
264
+
265
+ // Use atomic merge - this handles concurrent updates safely and merges
266
+ // the followupEventId into existing entries instead of dropping them
267
+ ralRegistry.mergePendingDelegations(
268
+ context.agent.pubkey,
269
+ context.conversationId,
270
+ effectiveRalNumber,
271
+ [newDelegation]
272
+ );
273
+
274
+ // Return normal result - agent continues without blocking
275
+ return {
276
+ success: true,
277
+ message: "Follow-up sent. The agent will respond when ready.",
278
+ delegationConversationId: shortenConversationId(delegation_conversation_id),
279
+ followupEventId, // Keep full event ID - this is a Nostr event ID, not a conversation ID
280
+ };
281
+ }
282
+
283
+ export function createDelegateFollowupTool(context: ToolExecutionContext): AISdkTool {
284
+ const aiTool = tool({
285
+ description:
286
+ "Send a follow-up question to an agent you previously delegated to. Use after delegate to ask clarifying questions about their response.",
287
+ inputSchema: delegateFollowupSchema,
288
+ execute: async (input: DelegateFollowupInput) => {
289
+ return await executeDelegateFollowup(input, context);
290
+ },
291
+ });
292
+
293
+ Object.defineProperty(aiTool, "getHumanReadableContent", {
294
+ value: () => "Sending follow-up question",
295
+ enumerable: false,
296
+ configurable: true,
297
+ });
298
+
299
+ return aiTool as AISdkTool;
300
+ }
@@ -0,0 +1,162 @@
1
+ import { readFile, writeFile } from "node:fs/promises";
2
+ import type { AISdkTool, ToolExecutionContext } from "@/tools/types";
3
+ import { isPathWithinDirectory, isWithinAgentHome } from "@/lib/agent-home";
4
+ import { formatAnyError } from "@/lib/error-formatter";
5
+ import {
6
+ createExpectedError,
7
+ type ExpectedErrorResult,
8
+ getFsErrorDescription,
9
+ isExpectedFsError,
10
+ } from "@/tools/utils";
11
+ import { tool } from "ai";
12
+ import { z } from "zod";
13
+
14
+ const editSchema = z.object({
15
+ path: z
16
+ .string()
17
+ .describe("The absolute path to the file to edit"),
18
+ description: z
19
+ .string()
20
+ .min(1, "Description is required and cannot be empty")
21
+ .describe(
22
+ "REQUIRED: A clear, concise description of why you're editing this file (5-10 words). Helps provide human-readable context for the operation."
23
+ ),
24
+ old_string: z.string().describe("The exact text to replace"),
25
+ new_string: z.string().describe("The text to replace it with (must be different from old_string)"),
26
+ replace_all: z
27
+ .boolean()
28
+ .optional()
29
+ .default(false)
30
+ .describe("Replace all occurrences of old_string (default false)"),
31
+ allowOutsideWorkingDirectory: z
32
+ .boolean()
33
+ .optional()
34
+ .describe("Set to true to edit files outside the working directory. Required when path is not within the project."),
35
+ });
36
+
37
+ /**
38
+ * Core implementation of the edit functionality
39
+ */
40
+ async function executeEdit(
41
+ path: string,
42
+ oldString: string,
43
+ newString: string,
44
+ replaceAll: boolean,
45
+ workingDirectory: string,
46
+ agentPubkey: string,
47
+ allowOutsideWorkingDirectory?: boolean,
48
+ ): Promise<string | ExpectedErrorResult> {
49
+ if (!path.startsWith("/")) {
50
+ throw new Error(`Path must be absolute, got: ${path}`);
51
+ }
52
+
53
+ // Check if path is within working directory (using secure path normalization)
54
+ const isWithinWorkDir = isPathWithinDirectory(path, workingDirectory);
55
+
56
+ // Always allow access to agent's home directory without requiring allowOutsideWorkingDirectory
57
+ const isInAgentHome = isWithinAgentHome(path, agentPubkey);
58
+
59
+ if (!isWithinWorkDir && !isInAgentHome && !allowOutsideWorkingDirectory) {
60
+ return `Path "${path}" is outside your working directory "${workingDirectory}". If this was intentional, retry with allowOutsideWorkingDirectory: true`;
61
+ }
62
+
63
+ // Read the file
64
+ const content = await readFile(path, "utf-8");
65
+
66
+ // Check if old_string exists - return as expected error since this is user input validation
67
+ if (!content.includes(oldString)) {
68
+ return createExpectedError(
69
+ `old_string not found in ${path}. Make sure you're using the exact string from the file.`
70
+ );
71
+ }
72
+
73
+ let newContent: string;
74
+ let replacementCount: number;
75
+
76
+ if (replaceAll) {
77
+ // Replace all occurrences
78
+ const regex = new RegExp(oldString.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g");
79
+ newContent = content.replace(regex, newString);
80
+ replacementCount = (content.match(regex) || []).length;
81
+ } else {
82
+ // Check for uniqueness
83
+ const firstIndex = content.indexOf(oldString);
84
+ const lastIndex = content.lastIndexOf(oldString);
85
+
86
+ // Multiple matches - return as expected error since this is user input validation
87
+ if (firstIndex !== lastIndex) {
88
+ return createExpectedError(
89
+ `old_string appears multiple times in ${path}. Either provide a larger string with more surrounding context to make it unique or use replace_all to change every instance.`
90
+ );
91
+ }
92
+
93
+ // Replace single occurrence
94
+ newContent = content.replace(oldString, newString);
95
+ replacementCount = 1;
96
+ }
97
+
98
+ // Write the file
99
+ await writeFile(path, newContent, "utf-8");
100
+
101
+ return `Successfully replaced ${replacementCount} occurrence(s) in ${path}`;
102
+ }
103
+
104
+ /**
105
+ * Create an AI SDK tool for editing files
106
+ */
107
+ export function createFsEditTool(context: ToolExecutionContext): AISdkTool {
108
+ const toolInstance = tool({
109
+ description:
110
+ "Performs exact string replacements in files. The edit will FAIL if old_string is not unique in the file. Either provide a larger string with more surrounding context to make it unique or use replace_all to change every instance of old_string. Path must be absolute. Editing outside the working directory requires allowOutsideWorkingDirectory: true.",
111
+
112
+ inputSchema: editSchema,
113
+
114
+ execute: async ({
115
+ path,
116
+ description: _description,
117
+ old_string,
118
+ new_string,
119
+ replace_all = false,
120
+ allowOutsideWorkingDirectory,
121
+ }: {
122
+ path: string;
123
+ description: string;
124
+ old_string: string;
125
+ new_string: string;
126
+ replace_all?: boolean;
127
+ allowOutsideWorkingDirectory?: boolean;
128
+ }) => {
129
+ try {
130
+ // Validate input - same strings is an expected user error
131
+ if (old_string === new_string) {
132
+ return createExpectedError("old_string and new_string must be different");
133
+ }
134
+
135
+ const result = await executeEdit(path, old_string, new_string, replace_all, context.workingDirectory, context.agent.pubkey, allowOutsideWorkingDirectory);
136
+ // executeEdit may return an ExpectedErrorResult for validation errors
137
+ return result;
138
+ } catch (error: unknown) {
139
+ // Expected errors (file not found, permission denied, etc.) return error-text
140
+ // This ensures the error is properly communicated to the LLM without stream failures
141
+ if (isExpectedFsError(error)) {
142
+ const code = (error as NodeJS.ErrnoException).code;
143
+ const description = getFsErrorDescription(code);
144
+ return createExpectedError(`${description}: ${path}`);
145
+ }
146
+
147
+ // Unexpected errors still throw (they'll be caught by the SDK)
148
+ throw new Error(`Failed to edit ${path}: ${formatAnyError(error)}`, { cause: error });
149
+ }
150
+ },
151
+ });
152
+
153
+ Object.defineProperty(toolInstance, "getHumanReadableContent", {
154
+ value: ({ path, description }: { path: string; description: string }) => {
155
+ return `Editing ${path} (${description})`;
156
+ },
157
+ enumerable: false,
158
+ configurable: true,
159
+ });
160
+
161
+ return toolInstance as AISdkTool;
162
+ }