@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
package/src/index.ts ADDED
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env bun
2
+
3
+ // TENEX CLI Entry Point
4
+ // This is a CLI application - NOT a library. Zero exports.
5
+
6
+ // MUST BE FIRST - Initialize OpenTelemetry before any other imports
7
+ // Read config synchronously to check telemetry setting before importing anything else
8
+ import { existsSync, readFileSync } from "node:fs";
9
+ import { homedir } from "node:os";
10
+ import { join } from "node:path";
11
+ import { initializeTelemetry } from "@/telemetry/setup";
12
+
13
+ /**
14
+ * Get the base TENEX directory path for early initialization.
15
+ * Respects TENEX_BASE_DIR environment variable for running multiple isolated instances.
16
+ * This is a minimal inline version used before other imports are available.
17
+ */
18
+ function getBasePath(): string {
19
+ return process.env.TENEX_BASE_DIR || join(homedir(), ".tenex");
20
+ }
21
+
22
+ interface TelemetryConfig {
23
+ enabled: boolean;
24
+ serviceName: string;
25
+ endpoint: string;
26
+ }
27
+
28
+ function getTelemetryConfig(): TelemetryConfig {
29
+ const configPath = join(getBasePath(), "config.json");
30
+ const defaults: TelemetryConfig = {
31
+ enabled: true,
32
+ serviceName: "tenex-daemon",
33
+ endpoint: "http://localhost:4318/v1/traces",
34
+ };
35
+
36
+ if (!existsSync(configPath)) return defaults;
37
+ try {
38
+ const config = JSON.parse(readFileSync(configPath, "utf-8"));
39
+ return {
40
+ enabled: config.telemetry?.enabled !== false,
41
+ serviceName: config.telemetry?.serviceName || defaults.serviceName,
42
+ endpoint: config.telemetry?.endpoint || defaults.endpoint,
43
+ };
44
+ } catch (error) {
45
+ // Issue #6: Make config parse errors visible to users
46
+ console.warn(`[TENEX] Warning: Failed to parse config at ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
47
+ return defaults; // default on parse error
48
+ }
49
+ }
50
+
51
+ // Initialize telemetry before any other imports
52
+ const telemetryConfig = getTelemetryConfig();
53
+ initializeTelemetry(telemetryConfig.enabled, telemetryConfig.serviceName, telemetryConfig.endpoint);
54
+
55
+ // Main execution - all imports happen AFTER telemetry is initialized
56
+ // This ensures OpenTelemetry can properly instrument all imported modules
57
+ async function main(): Promise<void> {
58
+ // Issue #1 & #4: Dynamic imports after telemetry initialization, using @/ aliases
59
+ const [
60
+ { Command },
61
+ { getHeuristicEngine, getDefaultHeuristics },
62
+ { daemonCommand },
63
+ { setupCommand },
64
+ { doctorCommand },
65
+ { agentCommand },
66
+ { handleCliError },
67
+ ] = await Promise.all([
68
+ import("commander"),
69
+ import("@/services/heuristics"),
70
+ import("@/commands/daemon"),
71
+ import("@/commands/setup/index"),
72
+ import("@/commands/doctor"),
73
+ import("@/commands/agent"),
74
+ import("@/utils/cli-error"),
75
+ ]);
76
+
77
+ // Initialize heuristics system with default rules
78
+ const heuristicEngine = getHeuristicEngine({
79
+ debug: process.env.DEBUG_HEURISTICS === "true",
80
+ });
81
+ for (const heuristic of getDefaultHeuristics()) {
82
+ heuristicEngine.register(heuristic);
83
+ }
84
+
85
+ // CLI setup
86
+ const program = new Command();
87
+
88
+ // Issue #5: Use npm_package_version with fallback
89
+ program
90
+ .name("tenex")
91
+ .description("TENEX Command Line Interface")
92
+ .version(process.env.npm_package_version || "0.8.0");
93
+
94
+ // Register subcommands
95
+ program.addCommand(daemonCommand);
96
+ program.addCommand(setupCommand);
97
+ program.addCommand(doctorCommand);
98
+ program.addCommand(agentCommand);
99
+
100
+ // Issue #2: Enable exitOverride so errors are thrown instead of calling process.exit
101
+ program.exitOverride();
102
+
103
+ try {
104
+ // Issue #2: Use parseAsync to properly catch async command errors
105
+ await program.parseAsync(process.argv);
106
+ } catch (error) {
107
+ // Commander throws CommanderError for --help, --version, and actual errors
108
+ // Check if it's a "help" or "version" exit - these are not real errors
109
+ if (
110
+ error instanceof Error &&
111
+ "code" in error &&
112
+ (error.code === "commander.helpDisplayed" || error.code === "commander.version")
113
+ ) {
114
+ // Normal exit for help/version - exit cleanly
115
+ process.exit(0);
116
+ }
117
+ handleCliError(error, "Fatal error in TENEX CLI");
118
+ }
119
+ }
120
+
121
+ // Execute CLI - this is an application, not a library
122
+ main().catch((error) => {
123
+ // Fallback error handler for errors during dynamic imports
124
+ console.error("Fatal error during TENEX CLI initialization:", error);
125
+ process.exit(1);
126
+ });
@@ -0,0 +1,334 @@
1
+ import { mkdirSync, readdirSync, realpathSync, existsSync, lstatSync, openSync, readSync, closeSync, constants as fsConstants } from "node:fs";
2
+ import { isAbsolute, join, normalize, relative, resolve, dirname } from "node:path";
3
+ import { getTenexBasePath } from "@/constants";
4
+ import { logger } from "@/utils/logger";
5
+
6
+ /**
7
+ * Error thrown when a path escapes the agent's home directory scope.
8
+ */
9
+ export class HomeScopeViolationError extends Error {
10
+ constructor(message: string) {
11
+ super(message);
12
+ this.name = "HomeScopeViolationError";
13
+ }
14
+ }
15
+
16
+ /**
17
+ * Maximum number of +prefixed files to inject into system prompt.
18
+ * Prevents prompt bloat if an agent creates many files.
19
+ */
20
+ const MAX_INJECTED_FILES = 10;
21
+
22
+ /**
23
+ * Maximum content length for injected files before truncation.
24
+ */
25
+ const MAX_INJECTED_FILE_LENGTH = 1500;
26
+
27
+ /**
28
+ * Represents a file to be injected into the agent's system prompt.
29
+ */
30
+ export interface InjectedFile {
31
+ filename: string;
32
+ content: string;
33
+ truncated: boolean;
34
+ }
35
+
36
+ /**
37
+ * Get the short pubkey (first 8 characters) for an agent.
38
+ */
39
+ function getShortPubkey(pubkey: string): string {
40
+ return pubkey.slice(0, 8);
41
+ }
42
+
43
+ /**
44
+ * Get the home directory path for an agent.
45
+ * This is the canonical source of truth for agent home directory paths.
46
+ */
47
+ export function getAgentHomeDirectory(agentPubkey: string): string {
48
+ const shortPubkey = getShortPubkey(agentPubkey);
49
+ return join(getTenexBasePath(), "home", shortPubkey);
50
+ }
51
+
52
+ /**
53
+ * Normalize and resolve a path to prevent path traversal attacks.
54
+ * Resolves .., ., and normalizes the path to an absolute form.
55
+ * @param inputPath - The path to normalize (should be absolute)
56
+ * @returns The normalized absolute path
57
+ */
58
+ export function normalizePath(inputPath: string): string {
59
+ // Resolve handles .., ., and makes the path absolute
60
+ const resolved = resolve(inputPath);
61
+ // Normalize handles redundant separators
62
+ return normalize(resolved);
63
+ }
64
+
65
+ /**
66
+ * Safely call realpathSync, returning null if it fails due to permissions or other errors.
67
+ */
68
+ function safeRealpathSync(path: string): string | null {
69
+ try {
70
+ return realpathSync(path);
71
+ } catch {
72
+ // Permission denied, or other errors - fall back to normalized path
73
+ return null;
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Resolve the real path of a file or directory, following symlinks.
79
+ * If the path doesn't exist, resolves the parent directory and appends the filename.
80
+ * This ensures symlinks are resolved even for files that will be created.
81
+ * Falls back to normalized path if realpath resolution fails (e.g., permission denied).
82
+ * @param inputPath - The path to resolve
83
+ * @returns The resolved real path with symlinks followed
84
+ */
85
+ function resolveRealPath(inputPath: string): string {
86
+ const normalized = normalizePath(inputPath);
87
+
88
+ // If the path exists, resolve it directly
89
+ if (existsSync(normalized)) {
90
+ const realPath = safeRealpathSync(normalized);
91
+ return realPath ?? normalized;
92
+ }
93
+
94
+ // Path doesn't exist - resolve the parent directory instead
95
+ // This catches symlinks in the parent chain
96
+ const parentDir = dirname(normalized);
97
+ const filename = normalized.slice(parentDir.length + 1); // Get the filename portion
98
+
99
+ if (existsSync(parentDir)) {
100
+ const realParent = safeRealpathSync(parentDir);
101
+ return realParent ? join(realParent, filename) : normalized;
102
+ }
103
+
104
+ // Neither path nor parent exists - walk up until we find an existing ancestor
105
+ let currentPath = parentDir;
106
+ const pathParts: string[] = [filename];
107
+
108
+ while (currentPath && currentPath !== dirname(currentPath)) {
109
+ const parent = dirname(currentPath);
110
+ pathParts.unshift(currentPath.slice(parent.length + 1));
111
+ currentPath = parent;
112
+
113
+ if (existsSync(currentPath)) {
114
+ const realAncestor = safeRealpathSync(currentPath);
115
+ return realAncestor ? join(realAncestor, ...pathParts) : normalized;
116
+ }
117
+ }
118
+
119
+ // No ancestor exists, return normalized path as-is
120
+ return normalized;
121
+ }
122
+
123
+ /**
124
+ * Check if a path is within a given directory.
125
+ * Both paths are normalized and symlinks are resolved to prevent escape attacks.
126
+ * Uses path.relative for cross-platform separator handling.
127
+ * @param inputPath - The path to check (will be normalized, symlinks resolved)
128
+ * @param directory - The directory to check against (will be normalized, symlinks resolved)
129
+ * @returns true if path is within or equal to directory
130
+ */
131
+ export function isPathWithinDirectory(inputPath: string, directory: string): boolean {
132
+ // Resolve symlinks to prevent symlink escape attacks
133
+ const realPath = resolveRealPath(inputPath);
134
+ const realDir = resolveRealPath(directory);
135
+
136
+ // Use path.relative for cross-platform handling
137
+ // If the relative path starts with ".." or is absolute, the path is outside
138
+ const relativePath = relative(realDir, realPath);
139
+
140
+ // Path is within directory if:
141
+ // 1. It doesn't start with ".." (would mean escaping the directory)
142
+ // 2. It's not absolute (would mean completely different path on Windows)
143
+ // 3. Empty string means it's the same directory
144
+ return !relativePath.startsWith("..") && !isAbsolute(relativePath);
145
+ }
146
+
147
+ /**
148
+ * Check if a path is within the agent's home directory.
149
+ * Normalizes the input path to prevent path traversal attacks.
150
+ * Used by filesystem tools to grant automatic access to agent's own home.
151
+ */
152
+ export function isWithinAgentHome(inputPath: string, agentPubkey: string): boolean {
153
+ const homeDir = getAgentHomeDirectory(agentPubkey);
154
+ return isPathWithinDirectory(inputPath, homeDir);
155
+ }
156
+
157
+ /**
158
+ * Ensure the agent's home directory exists.
159
+ * Creates it if it doesn't exist.
160
+ * @returns true if directory exists or was created, false if creation failed
161
+ */
162
+ export function ensureAgentHomeDirectory(agentPubkey: string): boolean {
163
+ const homeDir = getAgentHomeDirectory(agentPubkey);
164
+ try {
165
+ mkdirSync(homeDir, { recursive: true });
166
+ return true;
167
+ } catch (error) {
168
+ console.error("Failed to create agent home dir:", error);
169
+ return false;
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Maximum file size to read for injected files (prevents memory spikes).
175
+ * We only need first MAX_INJECTED_FILE_LENGTH chars, so read slightly more to detect truncation.
176
+ */
177
+ const MAX_INJECTED_FILE_READ_SIZE = MAX_INJECTED_FILE_LENGTH + 100;
178
+
179
+ /**
180
+ * Read a bounded amount from a file safely, preventing symlink attacks and memory spikes.
181
+ * Uses lstat + realpath validation and bounded reads.
182
+ *
183
+ * @param filePath - Path to the file
184
+ * @param homeDir - The home directory (for containment validation)
185
+ * @param maxBytes - Maximum bytes to read
186
+ * @returns Object with content and whether it was truncated, or null if file should be skipped
187
+ */
188
+ function safeReadBoundedFile(
189
+ filePath: string,
190
+ homeDir: string,
191
+ maxBytes: number
192
+ ): { content: string; truncated: boolean; fileSize: number } | null {
193
+ try {
194
+ // TOCTOU protection: Use lstat (not stat) to check actual file type without following symlinks
195
+ const lstats = lstatSync(filePath);
196
+
197
+ // Skip symlinks entirely (security: prevents symlink race attacks)
198
+ if (lstats.isSymbolicLink()) {
199
+ logger.warn(`Skipping symlink in agent home: ${filePath}`);
200
+ return null;
201
+ }
202
+
203
+ // Skip if not a regular file
204
+ if (!lstats.isFile()) {
205
+ return null;
206
+ }
207
+
208
+ // Additional safety: verify realpath is within home directory
209
+ // This catches any edge cases where the file might resolve outside home
210
+ const realPath = realpathSync(filePath);
211
+ const realHomeDir = realpathSync(homeDir);
212
+ const relativePath = relative(realHomeDir, realPath);
213
+ if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
214
+ logger.warn(`Skipping file that resolves outside home: ${filePath} -> ${realPath}`);
215
+ return null;
216
+ }
217
+
218
+ const fileSize = lstats.size;
219
+
220
+ // Bounded read: Only read what we need to prevent memory spikes
221
+ const bytesToRead = Math.min(fileSize, maxBytes);
222
+ const buffer = Buffer.alloc(bytesToRead);
223
+
224
+ // Use low-level open/read for precise control
225
+ const fd = openSync(filePath, fsConstants.O_RDONLY);
226
+ try {
227
+ const bytesRead = readSync(fd, buffer, 0, bytesToRead, 0);
228
+ const content = buffer.slice(0, bytesRead).toString("utf-8");
229
+ const truncated = fileSize > maxBytes;
230
+
231
+ return { content, truncated, fileSize };
232
+ } finally {
233
+ closeSync(fd);
234
+ }
235
+ } catch (error) {
236
+ // File may have been deleted/changed between lstat and read - that's OK
237
+ logger.warn(`Failed to safely read file ${filePath}:`, error);
238
+ return null;
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Get files in the agent's home directory that start with '+' prefix.
244
+ * These files are auto-injected into the agent's system prompt.
245
+ *
246
+ * Security:
247
+ * - Only reads regular files (skips symlinks and directories)
248
+ * - Uses lstat + realpath validation to prevent TOCTOU/symlink race attacks
249
+ * - Bounded reads to prevent memory spikes from large files
250
+ *
251
+ * Limits: Max 10 files, max 1500 chars per file (truncated if longer).
252
+ *
253
+ * @param agentPubkey - The agent's pubkey
254
+ * @returns Array of injected file objects with filename, content, and truncated flag
255
+ */
256
+ export function getAgentHomeInjectedFiles(agentPubkey: string): InjectedFile[] {
257
+ const homeDir = getAgentHomeDirectory(agentPubkey);
258
+
259
+ // Ensure directory exists
260
+ if (!ensureAgentHomeDirectory(agentPubkey)) {
261
+ return [];
262
+ }
263
+
264
+ try {
265
+ const entries = readdirSync(homeDir, { withFileTypes: true });
266
+
267
+ // Filter for +prefixed entries that appear to be files
268
+ // Note: We re-validate each file before reading due to TOCTOU concerns
269
+ const plusCandidates = entries
270
+ .filter((entry) => entry.name.startsWith("+") && entry.isFile())
271
+ .sort((a, b) => a.name.localeCompare(b.name))
272
+ .slice(0, MAX_INJECTED_FILES);
273
+
274
+ const injectedFiles: InjectedFile[] = [];
275
+
276
+ for (const entry of plusCandidates) {
277
+ const filePath = join(homeDir, entry.name);
278
+
279
+ // Use safe bounded read with TOCTOU protection
280
+ const result = safeReadBoundedFile(filePath, homeDir, MAX_INJECTED_FILE_READ_SIZE);
281
+ if (!result) {
282
+ continue; // Skip files that couldn't be safely read
283
+ }
284
+
285
+ // Apply the content length limit
286
+ const truncated = result.content.length > MAX_INJECTED_FILE_LENGTH || result.truncated;
287
+ const content = result.content.slice(0, MAX_INJECTED_FILE_LENGTH);
288
+
289
+ injectedFiles.push({
290
+ filename: entry.name,
291
+ content,
292
+ truncated,
293
+ });
294
+ }
295
+
296
+ return injectedFiles;
297
+ } catch (error) {
298
+ logger.warn("Failed to scan agent home for injected files:", error);
299
+ return [];
300
+ }
301
+ }
302
+
303
+ /**
304
+ * Resolve a path that must be within the agent's home directory.
305
+ * Accepts both relative paths (resolved against home) and absolute paths.
306
+ *
307
+ * Security: Uses isPathWithinDirectory() which handles symlinks and path traversal.
308
+ *
309
+ * @param inputPath - Relative or absolute path
310
+ * @param agentPubkey - The agent's pubkey
311
+ * @returns The resolved absolute path within the agent's home
312
+ * @throws HomeScopeViolationError if path escapes home directory
313
+ */
314
+ export function resolveHomeScopedPath(inputPath: string, agentPubkey: string): string {
315
+ const homeDir = getAgentHomeDirectory(agentPubkey);
316
+
317
+ // Ensure home directory exists
318
+ ensureAgentHomeDirectory(agentPubkey);
319
+
320
+ // Resolve path: treat relative paths as relative to home directory
321
+ const resolvedPath = isAbsolute(inputPath)
322
+ ? inputPath
323
+ : join(homeDir, inputPath);
324
+
325
+ // Validate the resolved path is within home directory
326
+ if (!isPathWithinDirectory(resolvedPath, homeDir)) {
327
+ throw new HomeScopeViolationError(
328
+ `Path "${inputPath}" is outside your home directory. ` +
329
+ `You can only access files within your home directory.`
330
+ );
331
+ }
332
+
333
+ return resolvedPath;
334
+ }
@@ -0,0 +1,200 @@
1
+ // ToolError removed - define it locally if needed
2
+ interface ToolError {
3
+ kind: "validation" | "execution" | "system";
4
+ message: string;
5
+ field?: string;
6
+ tool?: string;
7
+ }
8
+
9
+ /**
10
+ * Comprehensive error formatter that handles all error types in the codebase
11
+ * Consolidates error formatting logic from various parts of the system
12
+ */
13
+ export function formatAnyError(error: unknown): string {
14
+ // Handle null/undefined
15
+ if (error == null) {
16
+ return "Unknown error";
17
+ }
18
+
19
+ // Handle strings
20
+ if (typeof error === "string") {
21
+ return error;
22
+ }
23
+
24
+ // Handle Error instances
25
+ if (error instanceof Error) {
26
+ return error.message;
27
+ }
28
+
29
+ // Handle objects
30
+ if (typeof error === "object") {
31
+ const errorObj = error as Record<string, unknown>;
32
+
33
+ // Check for ToolError structure (with type guard)
34
+ if ("kind" in errorObj && "message" in errorObj) {
35
+ const kind = errorObj.kind;
36
+ if (kind === "validation" || kind === "execution" || kind === "system") {
37
+ return formatToolError(errorObj as unknown as ToolError);
38
+ }
39
+ }
40
+
41
+ // Check for simple message property
42
+ if ("message" in errorObj && typeof errorObj.message === "string") {
43
+ return errorObj.message;
44
+ }
45
+
46
+ // Try to extract meaningful properties from the error object
47
+ const parts: string[] = [];
48
+
49
+ // Common error properties
50
+ if ("kind" in errorObj) parts.push(`kind: ${errorObj.kind}`);
51
+ if ("field" in errorObj) parts.push(`field: ${errorObj.field}`);
52
+ if ("tool" in errorObj) parts.push(`tool: ${errorObj.tool}`);
53
+ if ("code" in errorObj) parts.push(`code: ${errorObj.code}`);
54
+ if ("statusCode" in errorObj) parts.push(`statusCode: ${errorObj.statusCode}`);
55
+ if ("errno" in errorObj) parts.push(`errno: ${errorObj.errno}`);
56
+ if ("syscall" in errorObj) parts.push(`syscall: ${errorObj.syscall}`);
57
+
58
+ // If we found specific properties, use them
59
+ if (parts.length > 0) {
60
+ return parts.join(", ");
61
+ }
62
+
63
+ // Otherwise, try to stringify the object
64
+ try {
65
+ const str = JSON.stringify(error);
66
+ // Don't return huge JSON strings
67
+ if (str.length > 200) {
68
+ return "[Complex Error Object]";
69
+ }
70
+ return str;
71
+ } catch {
72
+ return "[Complex Error Object]";
73
+ }
74
+ }
75
+
76
+ // Fallback to String conversion
77
+ return String(error);
78
+ }
79
+
80
+ /**
81
+ * Format ToolError objects into human-readable strings
82
+ */
83
+ export function formatToolError(error: ToolError): string {
84
+ switch (error.kind) {
85
+ case "validation":
86
+ // If the field is empty and message is just "Required", make it clearer
87
+ if (error.field === "" && error.message === "Required") {
88
+ return "Validation error: Missing required parameter";
89
+ }
90
+ return error.field
91
+ ? `Validation error in ${error.field}: ${error.message}`
92
+ : `Validation error: ${error.message}`;
93
+ case "execution":
94
+ return error.tool
95
+ ? `Execution error in ${error.tool}: ${error.message}`
96
+ : `Execution error: ${error.message}`;
97
+ case "system":
98
+ return `System error: ${error.message}`;
99
+ default: {
100
+ // This should never happen with proper ToolError types
101
+ const unknownError = error as unknown as Record<string, unknown>;
102
+ return (
103
+ (typeof unknownError.message === "string" ? unknownError.message : null) ||
104
+ "Unknown error"
105
+ );
106
+ }
107
+ }
108
+ }
109
+
110
+ // Constants for AI error markers
111
+ const AI_API_CALL_ERROR = "AI_APICallError";
112
+ const PROVIDER_RETURNED_ERROR = "Provider returned error";
113
+ const OPENROUTER_MARKER = "openrouter";
114
+ const HTTP_422_STATUS = "422";
115
+
116
+ /**
117
+ * Generic prefixes that indicate the message itself is not meaningful
118
+ * and we should try to extract details from toString() instead.
119
+ * These will be checked with startsWith, so "AI_APICallError" will match
120
+ * "AI_APICallError: some details" etc.
121
+ */
122
+ const GENERIC_ERROR_PREFIXES = [
123
+ AI_API_CALL_ERROR,
124
+ PROVIDER_RETURNED_ERROR,
125
+ HTTP_422_STATUS,
126
+ "Unprocessable Entity",
127
+ "Error:",
128
+ ] as const;
129
+
130
+ /**
131
+ * Check if an error message is meaningful enough to use directly,
132
+ * or if it's a generic wrapper that requires regex extraction for details.
133
+ *
134
+ * A message is considered "meaningful" if:
135
+ * 1. It's not empty
136
+ * 2. It doesn't start with any known generic prefix
137
+ * 3. It's not just the error class name
138
+ */
139
+ export function isMeaningfulAiMessage(message: string | undefined): boolean {
140
+ if (!message || message.trim() === "") {
141
+ return false;
142
+ }
143
+
144
+ const trimmedMessage = message.trim();
145
+
146
+ // Check if message starts with any generic prefix
147
+ for (const prefix of GENERIC_ERROR_PREFIXES) {
148
+ if (trimmedMessage.startsWith(prefix)) {
149
+ return false;
150
+ }
151
+ }
152
+
153
+ // Check for HTTP status code patterns (e.g., "422", "500 Internal Server Error")
154
+ if (/^\d{3}\b/.test(trimmedMessage)) {
155
+ return false;
156
+ }
157
+
158
+ return true;
159
+ }
160
+
161
+ /**
162
+ * Format error for stream/execution errors from LLM providers
163
+ */
164
+ export function formatStreamError(error: unknown): { message: string; errorType: string } {
165
+ let errorMessage = "An error occurred while processing your request.";
166
+ let errorType = "system";
167
+
168
+ if (error instanceof Error) {
169
+ const errorStr = error.toString();
170
+ if (
171
+ errorStr.includes(AI_API_CALL_ERROR) ||
172
+ errorStr.includes(PROVIDER_RETURNED_ERROR) ||
173
+ errorStr.includes(HTTP_422_STATUS) ||
174
+ errorStr.includes(OPENROUTER_MARKER)
175
+ ) {
176
+ errorType = "ai_api";
177
+
178
+ // Check if error.message is meaningful (not a generic wrapper)
179
+ // Claude Code errors often have the real error in error.message
180
+ if (isMeaningfulAiMessage(error.message)) {
181
+ errorMessage = `AI Error: ${error.message}`;
182
+ } else {
183
+ // Fall back to regex extraction for OpenRouter-style errors
184
+ const providerMatch = errorStr.match(/provider_name":"([^"]+)"/);
185
+ const provider = providerMatch ? providerMatch[1] : "AI provider";
186
+ errorMessage = `Failed to process request with ${provider}. The AI service returned an error.`;
187
+
188
+ // Add raw error details if available
189
+ const rawMatch = errorStr.match(/raw":"([^"]+)"/);
190
+ if (rawMatch) {
191
+ errorMessage += ` Details: ${rawMatch[1]}`;
192
+ }
193
+ }
194
+ } else {
195
+ errorMessage = `Error: ${error.message}`;
196
+ }
197
+ }
198
+
199
+ return { message: errorMessage, errorType };
200
+ }