@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,182 @@
1
+ import { glob } from "node:fs/promises";
2
+ import { stat } from "node:fs/promises";
3
+ import { relative, resolve } from "node:path";
4
+ import type { AISdkTool, ToolExecutionContext } from "@/tools/types";
5
+ import { isPathWithinDirectory, isWithinAgentHome } from "@/lib/agent-home";
6
+ import { tool } from "ai";
7
+ import { z } from "zod";
8
+
9
+ const globSchema = z.object({
10
+ pattern: z
11
+ .string()
12
+ .describe("Glob pattern to match files (e.g., '**/*.ts', 'src/**/*.tsx', '*.json')"),
13
+ description: z
14
+ .string()
15
+ .min(1, "Description is required and cannot be empty")
16
+ .describe(
17
+ "REQUIRED: A clear, concise description of why you're searching for these files (5-10 words). Helps provide human-readable context for the operation."
18
+ ),
19
+ path: z
20
+ .string()
21
+ .optional()
22
+ .describe(
23
+ "Absolute path to directory to search in. Defaults to working directory. " +
24
+ "IMPORTANT: Omit this field to use the default. DO NOT pass 'undefined' or 'null'."
25
+ ),
26
+ head_limit: z
27
+ .number()
28
+ .default(100)
29
+ .describe("Limit output to first N files. Use 0 for unlimited."),
30
+ offset: z
31
+ .number()
32
+ .default(0)
33
+ .describe("Skip first N files before applying head_limit"),
34
+ allowOutsideWorkingDirectory: z
35
+ .boolean()
36
+ .optional()
37
+ .describe("Set to true to glob outside the working directory. Required when path is not within the project."),
38
+ });
39
+
40
+ type GlobInput = z.infer<typeof globSchema>;
41
+
42
+ interface FileWithMtime {
43
+ path: string;
44
+ mtime: number;
45
+ }
46
+
47
+ const DEFAULT_EXCLUDES = [
48
+ "**/node_modules/**",
49
+ "**/.git/**",
50
+ "**/dist/**",
51
+ "**/build/**",
52
+ "**/.next/**",
53
+ "**/coverage/**",
54
+ "**/.worktrees/**",
55
+ ];
56
+
57
+ function applyPagination<T>(items: T[], offset: number, limit: number): T[] {
58
+ const offsetItems = offset > 0 ? items.slice(offset) : items;
59
+ return limit > 0 ? offsetItems.slice(0, limit) : offsetItems;
60
+ }
61
+
62
+ async function executeGlob(
63
+ input: GlobInput,
64
+ workingDirectory: string,
65
+ agentPubkey: string,
66
+ ): Promise<string> {
67
+ const { pattern, path: inputPath, head_limit, offset, allowOutsideWorkingDirectory } = input;
68
+
69
+ // If path is provided, validate it's absolute
70
+ if (inputPath && !inputPath.startsWith("/")) {
71
+ return `Path must be absolute, got: ${inputPath}`;
72
+ }
73
+
74
+ // Determine search directory
75
+ const searchDir = inputPath ?? workingDirectory;
76
+
77
+ // Check if path is within working directory (using secure path normalization)
78
+ const isWithinWorkDir = isPathWithinDirectory(searchDir, workingDirectory);
79
+
80
+ // Always allow access to agent's home directory without requiring allowOutsideWorkingDirectory
81
+ const isInAgentHome = isWithinAgentHome(searchDir, agentPubkey);
82
+
83
+ if (!isWithinWorkDir && !isInAgentHome && !allowOutsideWorkingDirectory) {
84
+ return `Path "${searchDir}" is outside your working directory "${workingDirectory}". If this was intentional, retry with allowOutsideWorkingDirectory: true`;
85
+ }
86
+
87
+ // Collect files with modification times
88
+ const filesWithMtime: FileWithMtime[] = [];
89
+
90
+ try {
91
+ // Use Node.js built-in glob (Node 20+)
92
+ const matches = glob(pattern, {
93
+ cwd: searchDir,
94
+ exclude: (name) => DEFAULT_EXCLUDES.some((exclude) => {
95
+ // Simple glob matching for excludes
96
+ if (exclude.includes("**")) {
97
+ const pattern = exclude.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*");
98
+ return new RegExp(pattern).test(name);
99
+ }
100
+ return name.includes(exclude.replace(/\*/g, ""));
101
+ }),
102
+ });
103
+
104
+ for await (const match of matches) {
105
+ try {
106
+ // Resolve the full path to handle any ".." in the glob pattern
107
+ const fullPath = resolve(searchDir, match);
108
+
109
+ // Security check: ensure the resolved path is still within allowed directories
110
+ // This prevents patterns like "../**/*" from escaping the allowed boundary
111
+ const isWithinAllowed =
112
+ isPathWithinDirectory(fullPath, searchDir) ||
113
+ isWithinAgentHome(fullPath, agentPubkey) ||
114
+ (allowOutsideWorkingDirectory && isPathWithinDirectory(fullPath, workingDirectory));
115
+
116
+ if (!isWithinAllowed) {
117
+ // Skip files that escaped the allowed directory via ".." in pattern
118
+ continue;
119
+ }
120
+
121
+ const stats = await stat(fullPath);
122
+ if (stats.isFile()) {
123
+ filesWithMtime.push({
124
+ path: relative(workingDirectory, fullPath),
125
+ mtime: stats.mtimeMs,
126
+ });
127
+ }
128
+ } catch {
129
+ // Skip files we can't stat
130
+ }
131
+ }
132
+ } catch (error) {
133
+ const message = error instanceof Error ? error.message : String(error);
134
+ return `Glob error: ${message}`;
135
+ }
136
+
137
+ if (filesWithMtime.length === 0) {
138
+ return `No files found matching pattern: ${pattern}`;
139
+ }
140
+
141
+ // Sort by modification time (most recent first)
142
+ filesWithMtime.sort((a, b) => b.mtime - a.mtime);
143
+
144
+ // Apply pagination
145
+ const paginatedFiles = applyPagination(filesWithMtime, offset, head_limit);
146
+ const truncated = paginatedFiles.length < filesWithMtime.length;
147
+ const result = paginatedFiles.map((f) => f.path).join("\n");
148
+
149
+ if (truncated) {
150
+ return `${result}\n\n[Truncated: showing ${paginatedFiles.length} of ${filesWithMtime.length} files]`;
151
+ }
152
+
153
+ return result;
154
+ }
155
+
156
+ export function createFsGlobTool(context: ToolExecutionContext): AISdkTool {
157
+ const toolInstance = tool({
158
+ description:
159
+ "Fast file pattern matching tool that works with any codebase size. " +
160
+ "Supports glob patterns like '**/*.ts' or 'src/**/*.tsx'. " +
161
+ "Returns matching file paths sorted by modification time (most recent first). " +
162
+ "Results limited to 100 files by default (use head_limit to adjust, 0 for unlimited). " +
163
+ "Path must be absolute. Globbing outside the working directory requires allowOutsideWorkingDirectory: true.",
164
+
165
+ inputSchema: globSchema,
166
+
167
+ execute: async (input: GlobInput) => {
168
+ return await executeGlob(input, context.workingDirectory, context.agent.pubkey);
169
+ },
170
+ });
171
+
172
+ Object.defineProperty(toolInstance, "getHumanReadableContent", {
173
+ value: (input: GlobInput) => {
174
+ const pathInfo = input.path ? ` in ${input.path}` : "";
175
+ return `Finding files matching '${input.pattern}'${pathInfo} (${input.description})`;
176
+ },
177
+ enumerable: false,
178
+ configurable: true,
179
+ });
180
+
181
+ return toolInstance as AISdkTool;
182
+ }
@@ -0,0 +1,513 @@
1
+ import { exec } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { relative } from "node:path";
4
+ import type { AISdkTool, ToolExecutionContext } from "@/tools/types";
5
+ import { isPathWithinDirectory, isWithinAgentHome } from "@/lib/agent-home";
6
+ import { tool } from "ai";
7
+ import { z } from "zod";
8
+
9
+ const execAsync = promisify(exec);
10
+
11
+ // Maximum content size before auto-fallback to files_with_matches mode
12
+ const MAX_CONTENT_SIZE = 50_000; // 50KB threshold
13
+
14
+ const grepSchema = z.object({
15
+ pattern: z
16
+ .string()
17
+ .describe(
18
+ "Regex pattern to search for in file contents (e.g., 'function\\s+\\w+', 'TODO', 'log.*Error')"
19
+ ),
20
+ description: z
21
+ .string()
22
+ .min(1, "Description is required and cannot be empty")
23
+ .describe(
24
+ "REQUIRED: A clear, concise description of why you're searching for this pattern (5-10 words). Helps provide human-readable context for the operation."
25
+ ),
26
+ path: z
27
+ .string()
28
+ .optional()
29
+ .describe("Absolute path to file or directory to search. Defaults to working directory."),
30
+ output_mode: z
31
+ .enum(["files_with_matches", "content", "count"])
32
+ .default("files_with_matches")
33
+ .describe(
34
+ "Output mode: 'files_with_matches' (file paths only), 'content' (matching lines with context), 'count' (match counts per file)"
35
+ ),
36
+ glob: z
37
+ .string()
38
+ .optional()
39
+ .describe("Glob pattern to filter files (e.g., '*.ts', '**/*.tsx')"),
40
+ type: z
41
+ .string()
42
+ .optional()
43
+ .describe("File type filter for ripgrep (e.g., 'ts', 'py', 'rust', 'js')"),
44
+ "-i": z
45
+ .boolean()
46
+ .optional()
47
+ .describe("Case-insensitive search"),
48
+ "-n": z
49
+ .boolean()
50
+ .default(true)
51
+ .describe("Show line numbers (only with output_mode: 'content')"),
52
+ "-A": z
53
+ .number()
54
+ .optional()
55
+ .describe("Lines to show after each match (only with output_mode: 'content')"),
56
+ "-B": z
57
+ .number()
58
+ .optional()
59
+ .describe("Lines to show before each match (only with output_mode: 'content')"),
60
+ "-C": z
61
+ .number()
62
+ .optional()
63
+ .describe("Lines to show before and after each match (only with output_mode: 'content')"),
64
+ multiline: z
65
+ .boolean()
66
+ .default(false)
67
+ .describe("Enable multiline mode where patterns can span lines"),
68
+ head_limit: z
69
+ .number()
70
+ .default(100)
71
+ .describe("Limit output to first N entries. Use 0 for unlimited."),
72
+ offset: z
73
+ .number()
74
+ .default(0)
75
+ .describe("Skip first N entries before applying head_limit"),
76
+ allowOutsideWorkingDirectory: z
77
+ .boolean()
78
+ .optional()
79
+ .describe("Set to true to search outside the working directory. Required when path is not within the project."),
80
+ });
81
+
82
+ type GrepInput = z.infer<typeof grepSchema>;
83
+
84
+ async function isRipgrepAvailable(): Promise<boolean> {
85
+ try {
86
+ await execAsync("which rg", { timeout: 1000 });
87
+ return true;
88
+ } catch {
89
+ return false;
90
+ }
91
+ }
92
+
93
+ function buildRipgrepCommand(input: GrepInput, searchPath: string): string {
94
+ const {
95
+ pattern,
96
+ output_mode,
97
+ glob: globPattern,
98
+ type: fileType,
99
+ "-i": caseInsensitive,
100
+ "-n": showLineNumbers,
101
+ "-A": contextAfter,
102
+ "-B": contextBefore,
103
+ "-C": contextAround,
104
+ multiline,
105
+ } = input;
106
+
107
+ const parts: string[] = ["rg"];
108
+
109
+ // Output mode flags
110
+ if (output_mode === "files_with_matches") {
111
+ parts.push("-l");
112
+ } else if (output_mode === "count") {
113
+ parts.push("-c");
114
+ }
115
+
116
+ // Line numbers (default true for content mode)
117
+ if (output_mode === "content" && showLineNumbers !== false) {
118
+ parts.push("-n");
119
+ }
120
+
121
+ // Case insensitive
122
+ if (caseInsensitive) {
123
+ parts.push("-i");
124
+ }
125
+
126
+ // Multiline
127
+ if (multiline) {
128
+ parts.push("-U", "--multiline-dotall");
129
+ }
130
+
131
+ // Context lines (only for content mode)
132
+ if (output_mode === "content") {
133
+ if (contextAround != null && contextAround > 0) {
134
+ parts.push("-C", `${contextAround}`);
135
+ } else {
136
+ if (contextBefore != null && contextBefore > 0) {
137
+ parts.push("-B", `${contextBefore}`);
138
+ }
139
+ if (contextAfter != null && contextAfter > 0) {
140
+ parts.push("-A", `${contextAfter}`);
141
+ }
142
+ }
143
+ }
144
+
145
+ // File type filter
146
+ if (fileType) {
147
+ parts.push("--type", fileType);
148
+ }
149
+
150
+ // Glob pattern filter
151
+ if (globPattern) {
152
+ parts.push("--glob", `'${globPattern}'`);
153
+ }
154
+
155
+ // Default exclusions
156
+ parts.push("--glob", "'!node_modules'");
157
+ parts.push("--glob", "'!.git'");
158
+ parts.push("--glob", "'!dist'");
159
+ parts.push("--glob", "'!build'");
160
+ parts.push("--glob", "'!.next'");
161
+ parts.push("--glob", "'!coverage'");
162
+
163
+ // Pattern (escape for shell)
164
+ parts.push(`'${pattern.replace(/'/g, "'\\''")}'`);
165
+
166
+ // Search path
167
+ parts.push(`'${searchPath}'`);
168
+
169
+ return parts.join(" ");
170
+ }
171
+
172
+ function buildGrepCommand(input: GrepInput, searchPath: string): string {
173
+ const {
174
+ pattern,
175
+ output_mode,
176
+ glob: globPattern,
177
+ "-i": caseInsensitive,
178
+ "-n": showLineNumbers,
179
+ "-A": contextAfter,
180
+ "-B": contextBefore,
181
+ "-C": contextAround,
182
+ } = input;
183
+
184
+ const parts: string[] = ["grep", "-r", "-E"];
185
+
186
+ // Output mode flags
187
+ if (output_mode === "files_with_matches") {
188
+ parts.push("-l");
189
+ } else if (output_mode === "count") {
190
+ parts.push("-c");
191
+ }
192
+
193
+ // Line numbers (default true for content mode)
194
+ if (output_mode === "content" && showLineNumbers !== false) {
195
+ parts.push("-n");
196
+ }
197
+
198
+ // Case insensitive
199
+ if (caseInsensitive) {
200
+ parts.push("-i");
201
+ }
202
+
203
+ // Context lines (only for content mode)
204
+ if (output_mode === "content") {
205
+ if (contextAround != null && contextAround > 0) {
206
+ parts.push("-C", `${contextAround}`);
207
+ } else {
208
+ if (contextBefore != null && contextBefore > 0) {
209
+ parts.push("-B", `${contextBefore}`);
210
+ }
211
+ if (contextAfter != null && contextAfter > 0) {
212
+ parts.push("-A", `${contextAfter}`);
213
+ }
214
+ }
215
+ }
216
+
217
+ // Glob pattern filter (grep uses --include)
218
+ if (globPattern) {
219
+ parts.push(`--include='${globPattern}'`);
220
+ }
221
+
222
+ // Default exclusions
223
+ parts.push("--exclude-dir=node_modules");
224
+ parts.push("--exclude-dir=.git");
225
+ parts.push("--exclude-dir=dist");
226
+ parts.push("--exclude-dir=build");
227
+ parts.push("--exclude-dir=.next");
228
+ parts.push("--exclude-dir=coverage");
229
+ parts.push("--binary-files=without-match");
230
+
231
+ // Pattern
232
+ parts.push(`'${pattern.replace(/'/g, "'\\''")}'`);
233
+
234
+ // Search path
235
+ parts.push(`'${searchPath}'`);
236
+
237
+ return parts.join(" ");
238
+ }
239
+
240
+ function applyPagination(lines: string[], offset: number, limit: number): string[] {
241
+ const offsetLines = offset > 0 ? lines.slice(offset) : lines;
242
+ return limit > 0 ? offsetLines.slice(0, limit) : offsetLines;
243
+ }
244
+
245
+ async function runGrepCommand(
246
+ input: GrepInput,
247
+ workingDirectory: string,
248
+ searchPath: string,
249
+ ): Promise<string[]> {
250
+ const useRipgrep = await isRipgrepAvailable();
251
+ const command = useRipgrep
252
+ ? buildRipgrepCommand(input, searchPath)
253
+ : buildGrepCommand(input, searchPath);
254
+
255
+ try {
256
+ const { stdout } = await execAsync(command, {
257
+ cwd: workingDirectory,
258
+ timeout: 30000,
259
+ maxBuffer: 1024 * 1024 * 10, // 10MB
260
+ });
261
+
262
+ if (!stdout.trim()) {
263
+ return [];
264
+ }
265
+
266
+ return stdout.trim().split("\n").filter(Boolean);
267
+ } catch (error) {
268
+ // Exit code 1 from grep/rg means no matches - not an error
269
+ if (error && typeof error === "object" && "code" in error && error.code === 1) {
270
+ return [];
271
+ }
272
+ // maxBuffer error - rethrow to trigger fallback
273
+ if (error && typeof error === "object" && "message" in error &&
274
+ typeof error.message === "string" && error.message.includes("maxBuffer")) {
275
+ throw error;
276
+ }
277
+ throw error;
278
+ }
279
+ }
280
+
281
+ function extractFilePathsFromContent(contentLines: string[]): string[] {
282
+ const uniquePaths = new Set<string>();
283
+
284
+ for (const line of contentLines) {
285
+ // Content format: filepath:linenum:content or filepath:content
286
+ const firstColon = line.indexOf(":");
287
+ if (firstColon > 0) {
288
+ const filePath = line.substring(0, firstColon);
289
+ uniquePaths.add(filePath);
290
+ }
291
+ }
292
+
293
+ return Array.from(uniquePaths);
294
+ }
295
+
296
+ function truncateToMaxSize(text: string, maxBytes: number): { truncated: string; originalLength: number } {
297
+ const originalLength = Buffer.byteLength(text, "utf8");
298
+
299
+ if (originalLength <= maxBytes) {
300
+ return { truncated: text, originalLength };
301
+ }
302
+
303
+ // Binary search for the right character position that fits in maxBytes
304
+ const lines = text.split("\n");
305
+ let left = 0;
306
+ let right = lines.length;
307
+ let bestFit = 0;
308
+
309
+ while (left <= right) {
310
+ const mid = Math.floor((left + right) / 2);
311
+ const candidate = lines.slice(0, mid).join("\n");
312
+ const candidateSize = Buffer.byteLength(candidate, "utf8");
313
+
314
+ if (candidateSize <= maxBytes) {
315
+ bestFit = mid;
316
+ left = mid + 1;
317
+ } else {
318
+ right = mid - 1;
319
+ }
320
+ }
321
+
322
+ const truncated = lines.slice(0, bestFit).join("\n");
323
+ return { truncated, originalLength };
324
+ }
325
+
326
+ async function executeGrep(
327
+ input: GrepInput,
328
+ workingDirectory: string,
329
+ agentPubkey: string,
330
+ ): Promise<string> {
331
+ const { pattern, path: inputPath, output_mode, head_limit, offset, allowOutsideWorkingDirectory } = input;
332
+
333
+ if (!pattern) {
334
+ return "Error: pattern is required";
335
+ }
336
+
337
+ // If path is provided, validate it's absolute
338
+ if (inputPath && !inputPath.startsWith("/")) {
339
+ return `Path must be absolute, got: ${inputPath}`;
340
+ }
341
+
342
+ // Determine search path
343
+ const searchPath = inputPath ?? workingDirectory;
344
+
345
+ // Check if path is within working directory (using secure path normalization)
346
+ const isWithinWorkDir = isPathWithinDirectory(searchPath, workingDirectory);
347
+
348
+ // Always allow access to agent's home directory without requiring allowOutsideWorkingDirectory
349
+ const isInAgentHome = isWithinAgentHome(searchPath, agentPubkey);
350
+
351
+ if (!isWithinWorkDir && !isInAgentHome && !allowOutsideWorkingDirectory) {
352
+ return `Path "${searchPath}" is outside your working directory "${workingDirectory}". If this was intentional, retry with allowOutsideWorkingDirectory: true`;
353
+ }
354
+
355
+ try {
356
+ // Execute the search
357
+ let lines: string[];
358
+
359
+ try {
360
+ lines = await runGrepCommand(input, workingDirectory, searchPath);
361
+ } catch (error) {
362
+ // Handle maxBuffer error by falling back to files_with_matches mode
363
+ if (
364
+ output_mode === "content" &&
365
+ error &&
366
+ typeof error === "object" &&
367
+ "message" in error &&
368
+ typeof error.message === "string" &&
369
+ error.message.includes("maxBuffer")
370
+ ) {
371
+ // Retry with files_with_matches mode
372
+ const fallbackInput = { ...input, output_mode: "files_with_matches" as const };
373
+ const fallbackLines = await runGrepCommand(fallbackInput, workingDirectory, searchPath);
374
+
375
+ // Convert to relative paths
376
+ const fallbackProcessed = fallbackLines.map((line) => relative(workingDirectory, line));
377
+
378
+ // Apply pagination
379
+ const fallbackPaginated = applyPagination(fallbackProcessed, offset, head_limit);
380
+ const fileListResult = fallbackPaginated.join("\n");
381
+
382
+ // Build message prefix
383
+ const messagePrefix =
384
+ `Content output exceeded buffer limit.\n` +
385
+ `Showing matching files instead (${fallbackLines.length} total):\n\n`;
386
+
387
+ // Calculate available space
388
+ const availableSpace = MAX_CONTENT_SIZE - Buffer.byteLength(messagePrefix, "utf8") - 200;
389
+
390
+ // Enforce hard cap on output size
391
+ const { truncated: finalOutput, originalLength } = truncateToMaxSize(
392
+ fileListResult,
393
+ availableSpace
394
+ );
395
+
396
+ const truncationNote =
397
+ originalLength > availableSpace
398
+ ? `\n\n[Output truncated to 50KB limit - ${fallbackPaginated.length} files matched, showing partial list]`
399
+ : "";
400
+
401
+ return messagePrefix + finalOutput + truncationNote;
402
+ }
403
+ throw error;
404
+ }
405
+
406
+ if (lines.length === 0) {
407
+ return `No matches found for pattern: ${pattern}`;
408
+ }
409
+
410
+ // Convert absolute paths to relative
411
+ let processedLines = lines.map((line) => {
412
+ // Handle different output formats
413
+ if (output_mode === "files_with_matches") {
414
+ return relative(workingDirectory, line);
415
+ } else if (output_mode === "count") {
416
+ // Format: /path/to/file:count
417
+ const colonIdx = line.lastIndexOf(":");
418
+ if (colonIdx > 0) {
419
+ const filePath = line.substring(0, colonIdx);
420
+ const count = line.substring(colonIdx + 1);
421
+ return `${relative(workingDirectory, filePath)}:${count}`;
422
+ }
423
+ } else {
424
+ // Content mode: /path/to/file:line:content
425
+ const firstColon = line.indexOf(":");
426
+ if (firstColon > 0) {
427
+ const filePath = line.substring(0, firstColon);
428
+ const rest = line.substring(firstColon);
429
+ return `${relative(workingDirectory, filePath)}${rest}`;
430
+ }
431
+ }
432
+ return line;
433
+ });
434
+
435
+ // Apply pagination first
436
+ const paginatedLines = applyPagination(processedLines, offset, head_limit);
437
+ const result = paginatedLines.join("\n");
438
+
439
+ // Auto-fallback logic for content mode when PAGINATED output is too large
440
+ if (output_mode === "content") {
441
+ const paginatedSize = Buffer.byteLength(result, "utf8");
442
+
443
+ if (paginatedSize > MAX_CONTENT_SIZE) {
444
+ // Extract unique file paths from the content we already have
445
+ const filePaths = extractFilePathsFromContent(processedLines);
446
+
447
+ // Apply pagination to file list
448
+ const paginatedFiles = applyPagination(filePaths, offset, head_limit);
449
+ const fileListResult = paginatedFiles.join("\n");
450
+
451
+ // Build message prefix
452
+ const messagePrefix =
453
+ `Content output would exceed 50KB limit (actual: ${(paginatedSize / 1024).toFixed(1)}KB).\n` +
454
+ `Showing ${paginatedFiles.length} matching files instead:\n\n`;
455
+
456
+ // Calculate available space for file list (leave room for prefix and potential truncation note)
457
+ const availableSpace = MAX_CONTENT_SIZE - Buffer.byteLength(messagePrefix, "utf8") - 200;
458
+
459
+ // Enforce hard cap on file list output
460
+ const { truncated: finalOutput, originalLength } = truncateToMaxSize(
461
+ fileListResult,
462
+ availableSpace
463
+ );
464
+
465
+ const truncationNote =
466
+ originalLength > availableSpace
467
+ ? `\n\n[File list truncated to 50KB limit - ${filePaths.length} files matched, showing partial list]`
468
+ : "";
469
+
470
+ return messagePrefix + finalOutput + truncationNote;
471
+ }
472
+ }
473
+
474
+ // Normal output (no fallback needed)
475
+ const truncated = paginatedLines.length < processedLines.length;
476
+ if (truncated) {
477
+ return `${result}\n\n[Truncated: showing ${paginatedLines.length} of ${processedLines.length} results]`;
478
+ }
479
+
480
+ return result;
481
+ } catch (error) {
482
+ const message = error instanceof Error ? error.message : String(error);
483
+ return `Grep error: ${message}`;
484
+ }
485
+ }
486
+
487
+ export function createFsGrepTool(context: ToolExecutionContext): AISdkTool {
488
+ const toolInstance = tool({
489
+ description:
490
+ "Powerful content search tool built on ripgrep (with grep fallback). " +
491
+ "Supports full regex syntax (e.g., 'log.*Error', 'function\\s+\\w+'). " +
492
+ "Output modes: 'files_with_matches' (default, file paths only), 'content' (matching lines), 'count' (match counts). " +
493
+ "Filter files with 'glob' parameter (e.g., '*.ts') or 'type' parameter (e.g., 'ts', 'py'). " +
494
+ "Path must be absolute. Searching outside the working directory requires allowOutsideWorkingDirectory: true.",
495
+
496
+ inputSchema: grepSchema,
497
+
498
+ execute: async (input: GrepInput) => {
499
+ return await executeGrep(input, context.workingDirectory, context.agent.pubkey);
500
+ },
501
+ });
502
+
503
+ Object.defineProperty(toolInstance, "getHumanReadableContent", {
504
+ value: (input: GrepInput) => {
505
+ const pathInfo = input.path ? ` in ${input.path}` : "";
506
+ return `Searching for '${input.pattern}'${pathInfo} (${input.description})`;
507
+ },
508
+ enumerable: false,
509
+ configurable: true,
510
+ });
511
+
512
+ return toolInstance as AISdkTool;
513
+ }