@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,723 @@
1
+ import { homedir } from "node:os";
2
+ import * as path from "node:path";
3
+ import { CONFIG_FILE, LLMS_FILE, MCP_CONFIG_FILE, PROVIDERS_FILE, TENEX_DIR, getTenexBasePath } from "@/constants";
4
+ import { ensureDirectory, fileExists, readJsonFile, writeJsonFile } from "@/lib/fs";
5
+ import { llmServiceFactory } from "@/llm/LLMServiceFactory";
6
+ import { MetaModelResolver } from "@/llm/meta";
7
+ import { ensureCacheLoaded as ensureModelsDevCacheLoaded } from "@/llm/utils/models-dev-cache";
8
+ import type { MCPConfig } from "@/llm/providers/types";
9
+ import type { LLMService } from "@/llm/service";
10
+ import type { OnStreamStartCallback } from "@/llm/types";
11
+ import type {
12
+ ConfigFile,
13
+ LLMConfiguration,
14
+ LoadedConfig,
15
+ MetaModelConfiguration,
16
+ TenexConfig,
17
+ TenexLLMs,
18
+ TenexMCP,
19
+ TenexProviders,
20
+ } from "@/services/config/types";
21
+ import { isMetaModelConfiguration, TenexConfigSchema, TenexLLMsSchema, TenexMCPSchema, TenexProvidersSchema } from "@/services/config/types";
22
+ import { formatAnyError } from "@/lib/error-formatter";
23
+ import { logger } from "@/utils/logger";
24
+ import { NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
25
+ import type { z } from "zod";
26
+
27
+ /**
28
+ * Result of resolving a meta model configuration
29
+ */
30
+ export interface MetaModelResolutionResult {
31
+ /** The resolved LLM configuration to use */
32
+ config: LLMConfiguration;
33
+ /** The resolved configuration name */
34
+ configName: string;
35
+ /** The message with keywords stripped (if applicable) */
36
+ strippedMessage?: string;
37
+ /** Additional system prompt to inject from the variant */
38
+ variantSystemPrompt?: string;
39
+ /** System prompt fragment describing available variants */
40
+ metaModelSystemPrompt?: string;
41
+ /** Whether this was a meta model resolution */
42
+ isMetaModel: boolean;
43
+ /** The variant name that was selected (if meta model) */
44
+ variantName?: string;
45
+ }
46
+
47
+ /**
48
+ * Subdirectory paths under ~/.tenex
49
+ */
50
+ export type TenexSubdir =
51
+ | "agents"
52
+ | "daemon"
53
+ | "conversations"
54
+ | "data"
55
+ | "projects"
56
+ | "tool-messages";
57
+
58
+ /**
59
+ * Centralized configuration service for TENEX
60
+ * Handles loading and saving of all configuration files
61
+ * All configurations are stored in ~/.tenex
62
+ */
63
+ export class ConfigService {
64
+ private cache = new Map<string, { data: unknown; timestamp: number }>();
65
+ private loadedConfig?: LoadedConfig;
66
+
67
+ // =====================================================================================
68
+ // PATH UTILITIES
69
+ // =====================================================================================
70
+
71
+ /**
72
+ * Get a path under the global TENEX directory (defaults to ~/.tenex)
73
+ * Respects TENEX_BASE_DIR environment variable for instance isolation.
74
+ * @param subdir Optional subdirectory (e.g., "agents", "daemon")
75
+ * @returns Full path to the directory or subdirectory
76
+ */
77
+ getConfigPath(subdir?: TenexSubdir | string): string {
78
+ const basePath = getTenexBasePath();
79
+ return subdir ? path.join(basePath, subdir) : basePath;
80
+ }
81
+
82
+ getGlobalPath(): string {
83
+ return this.getConfigPath();
84
+ }
85
+
86
+ getProjectPath(projectPath: string): string {
87
+ return path.join(projectPath, TENEX_DIR);
88
+ }
89
+
90
+ /**
91
+ * Get the base directory for all projects
92
+ * Defaults to ~/tenex if not configured
93
+ */
94
+ getProjectsBase(): string {
95
+ const config = this.loadedConfig?.config;
96
+ return config?.projectsBase
97
+ ? path.resolve(config.projectsBase)
98
+ : path.join(homedir(), "tenex");
99
+ }
100
+
101
+ private getConfigFilePath(basePath: string, configFile: ConfigFile): string {
102
+ return path.join(basePath, configFile);
103
+ }
104
+
105
+ // =====================================================================================
106
+ // COMPLETE CONFIGURATION LOADING
107
+ // =====================================================================================
108
+
109
+ /**
110
+ * Get the currently loaded config
111
+ */
112
+ getConfig(): TenexConfig {
113
+ if (!this.loadedConfig) {
114
+ throw new Error("Config not loaded. Call loadConfig() first.");
115
+ }
116
+ return this.loadedConfig.config;
117
+ }
118
+
119
+ getMCP(): TenexMCP {
120
+ if (!this.loadedConfig) {
121
+ throw new Error("Config not loaded. Call loadConfig() first.");
122
+ }
123
+ return this.loadedConfig.mcp;
124
+ }
125
+
126
+ /**
127
+ * Load all TENEX configuration
128
+ * @param metadataPath Optional project metadata path (~/.tenex/projects/{dTag}) for project MCP config
129
+ */
130
+ async loadConfig(metadataPath?: string): Promise<LoadedConfig> {
131
+ const globalPath = this.getGlobalPath();
132
+
133
+ // Load global config only (no project-level config.json)
134
+ const config = await this.loadTenexConfig(globalPath);
135
+
136
+ // Load providers from providers.json
137
+ const providers = await this.loadTenexProviders(globalPath);
138
+
139
+ // Load global LLMs only (no project-level llms.json)
140
+ const llms = await this.loadTenexLLMs(globalPath);
141
+
142
+ // Validate provider references
143
+ this.validateProviderReferences(llms, providers);
144
+
145
+ // Load MCP (merge global and project metadata path)
146
+ const globalMCP = await this.loadTenexMCP(globalPath);
147
+ const projectMCP = metadataPath
148
+ ? await this.loadTenexMCP(metadataPath)
149
+ : { servers: {}, enabled: true };
150
+ const mcp: TenexMCP = {
151
+ servers: { ...globalMCP.servers, ...projectMCP.servers },
152
+ enabled: projectMCP.enabled !== undefined ? projectMCP.enabled : globalMCP.enabled,
153
+ };
154
+
155
+ const loadedConfig = { config, llms, mcp, providers };
156
+ this.loadedConfig = loadedConfig;
157
+
158
+ // Initialize the LLM factory with provider configs and global settings
159
+ await llmServiceFactory.initializeProviders(providers.providers);
160
+
161
+ // Load models.dev cache for model metadata (context windows, output limits)
162
+ await ensureModelsDevCacheLoaded();
163
+
164
+ return loadedConfig;
165
+ }
166
+
167
+ // =====================================================================================
168
+ // INDIVIDUAL FILE LOADING
169
+ // =====================================================================================
170
+
171
+ async loadTenexConfig(basePath: string): Promise<TenexConfig> {
172
+ return this.loadConfigFile(
173
+ this.getConfigFilePath(basePath, CONFIG_FILE),
174
+ TenexConfigSchema,
175
+ {}
176
+ );
177
+ }
178
+
179
+ async loadTenexLLMs(basePath: string): Promise<TenexLLMs> {
180
+ return this.loadConfigFile(this.getConfigFilePath(basePath, LLMS_FILE), TenexLLMsSchema, {
181
+ configurations: {},
182
+ default: undefined,
183
+ });
184
+ }
185
+
186
+ async loadTenexMCP(basePath: string): Promise<TenexMCP> {
187
+ const result = await this.loadConfigFile(
188
+ this.getConfigFilePath(basePath, MCP_CONFIG_FILE),
189
+ TenexMCPSchema,
190
+ {
191
+ servers: {},
192
+ enabled: true,
193
+ }
194
+ );
195
+ // Ensure servers is always defined
196
+ return {
197
+ servers: result.servers || {},
198
+ enabled: result.enabled ?? true,
199
+ };
200
+ }
201
+
202
+ async loadTenexProviders(basePath: string): Promise<TenexProviders> {
203
+ return this.loadConfigFile(
204
+ this.getConfigFilePath(basePath, PROVIDERS_FILE),
205
+ TenexProvidersSchema,
206
+ { providers: {} }
207
+ );
208
+ }
209
+
210
+ // =====================================================================================
211
+ // INDIVIDUAL FILE SAVING
212
+ // =====================================================================================
213
+
214
+ async saveTenexConfig(basePath: string, config: TenexConfig): Promise<void> {
215
+ await this.saveConfigFile(
216
+ this.getConfigFilePath(basePath, CONFIG_FILE),
217
+ config,
218
+ TenexConfigSchema
219
+ );
220
+ }
221
+
222
+ async saveTenexLLMs(basePath: string, llms: TenexLLMs): Promise<void> {
223
+ await this.saveConfigFile(
224
+ this.getConfigFilePath(basePath, LLMS_FILE),
225
+ llms,
226
+ TenexLLMsSchema
227
+ );
228
+ }
229
+
230
+ async saveTenexMCP(basePath: string, mcp: TenexMCP): Promise<void> {
231
+ await this.saveConfigFile(
232
+ this.getConfigFilePath(basePath, MCP_CONFIG_FILE),
233
+ mcp,
234
+ TenexMCPSchema
235
+ );
236
+ }
237
+
238
+ async saveTenexProviders(basePath: string, providers: TenexProviders): Promise<void> {
239
+ await this.saveConfigFile(
240
+ this.getConfigFilePath(basePath, PROVIDERS_FILE),
241
+ providers,
242
+ TenexProvidersSchema
243
+ );
244
+ }
245
+
246
+ // =====================================================================================
247
+ // LLM SERVICE CREATION
248
+ // =====================================================================================
249
+
250
+ /**
251
+ * Resolve a configuration name to an actual name, handling defaults and fallbacks.
252
+ * This is the single source of truth for config name resolution logic.
253
+ *
254
+ * @param configName - The requested configuration name (may be undefined or "default")
255
+ * @param options - Resolution options
256
+ * @returns Object with resolved name and optional warning message
257
+ */
258
+ private resolveConfigName(
259
+ configName: string | undefined,
260
+ options: { allowFallback?: boolean; warnOnFallback?: boolean } = {}
261
+ ): { name: string; warning?: string } {
262
+ const { allowFallback = true, warnOnFallback = true } = options;
263
+
264
+ if (!this.loadedConfig) {
265
+ throw new Error("Config not loaded. Call loadConfig() first.");
266
+ }
267
+
268
+ const llms = this.loadedConfig.llms;
269
+ const available = Object.keys(llms.configurations);
270
+
271
+ // If configName is "default" or not provided, use the actual default from config
272
+ let name = configName;
273
+ if (!name || name === "default") {
274
+ name = llms.default;
275
+ if (!name) {
276
+ if (available.length > 0) {
277
+ name = available[0];
278
+ if (warnOnFallback) {
279
+ return { name, warning: `No default LLM configured, using first available: ${name}` };
280
+ }
281
+ return { name };
282
+ }
283
+ throw new Error("No LLM configurations available");
284
+ }
285
+ }
286
+
287
+ // Check if the requested config exists
288
+ if (llms.configurations[name]) {
289
+ return { name };
290
+ }
291
+
292
+ // Config not found - try fallback if allowed
293
+ if (!allowFallback) {
294
+ throw new Error(`LLM configuration "${name}" not found`);
295
+ }
296
+
297
+ // Try default
298
+ const defaultName = llms.default;
299
+ if (defaultName && llms.configurations[defaultName]) {
300
+ const warning = warnOnFallback
301
+ ? `LLM configuration "${name}" not found, falling back to default: ${defaultName}`
302
+ : undefined;
303
+ return { name: defaultName, warning };
304
+ }
305
+
306
+ // Try first available
307
+ if (available.length > 0) {
308
+ const fallbackName = available[0];
309
+ const warning = warnOnFallback
310
+ ? `LLM configuration "${name}" not found, using first available: ${fallbackName}`
311
+ : undefined;
312
+ return { name: fallbackName, warning };
313
+ }
314
+
315
+ throw new Error(
316
+ `No valid LLM configuration found. Requested: "${configName || "default"}". ` +
317
+ `Available: ${available.length > 0 ? available.join(", ") : "none"}`
318
+ );
319
+ }
320
+
321
+ /**
322
+ * Get LLM configuration by name.
323
+ * If the configuration is a meta model, automatically resolves to the default variant.
324
+ * Use resolveMetaModel() for keyword-based variant selection.
325
+ */
326
+ getLLMConfig(configName?: string): LLMConfiguration {
327
+ const { name, warning } = this.resolveConfigName(configName, {
328
+ allowFallback: true,
329
+ warnOnFallback: true,
330
+ });
331
+
332
+ if (warning) {
333
+ logger.warn(warning);
334
+ }
335
+
336
+ const loadedConfig = this.loadedConfig;
337
+ if (!loadedConfig) {
338
+ throw new Error("Config not loaded. Call loadConfig() first.");
339
+ }
340
+ const config = loadedConfig.llms.configurations[name];
341
+
342
+ // If it's a meta model, resolve to the default variant
343
+ if (isMetaModelConfiguration(config)) {
344
+ const resolution = MetaModelResolver.resolve(config);
345
+ // Recursively get the underlying config
346
+ return this.getLLMConfig(resolution.configName);
347
+ }
348
+
349
+ return config;
350
+ }
351
+
352
+ /**
353
+ * Get raw LLM configuration by name without type coercion.
354
+ * Returns the configuration as stored, which may be either a standard
355
+ * LLMConfiguration or a MetaModelConfiguration.
356
+ */
357
+ getRawLLMConfig(configName?: string): LLMConfiguration | MetaModelConfiguration {
358
+ const { name } = this.resolveConfigName(configName, {
359
+ allowFallback: false,
360
+ warnOnFallback: false,
361
+ });
362
+
363
+ const loadedConfig = this.loadedConfig;
364
+ if (!loadedConfig) {
365
+ throw new Error("Config not loaded. Call loadConfig() first.");
366
+ }
367
+ return loadedConfig.llms.configurations[name];
368
+ }
369
+
370
+ /**
371
+ * Check if a configuration name refers to a meta model.
372
+ */
373
+ isMetaModelConfig(configName?: string): boolean {
374
+ try {
375
+ const config = this.getRawLLMConfig(configName);
376
+ return isMetaModelConfiguration(config);
377
+ } catch {
378
+ return false;
379
+ }
380
+ }
381
+
382
+ /**
383
+ * Resolve a meta model configuration based on the first message or variant override.
384
+ * If the configuration is not a meta model, returns the configuration directly.
385
+ *
386
+ * @param configName The configuration name to resolve
387
+ * @param firstMessage Optional first user message for keyword detection
388
+ * @param variantOverride Optional variant name to use (bypasses keyword detection)
389
+ * @returns Resolution result with the actual configuration to use
390
+ */
391
+ resolveMetaModel(configName?: string, firstMessage?: string, variantOverride?: string): MetaModelResolutionResult {
392
+ const rawConfig = this.getRawLLMConfig(configName);
393
+
394
+ // If not a meta model, return as-is
395
+ if (!isMetaModelConfiguration(rawConfig)) {
396
+ return {
397
+ config: rawConfig,
398
+ configName: configName || "default",
399
+ isMetaModel: false,
400
+ };
401
+ }
402
+
403
+ // It's a meta model - resolve based on override or keywords
404
+ const metaConfig = rawConfig as MetaModelConfiguration;
405
+
406
+ let resolution;
407
+ if (variantOverride) {
408
+ // Use the override variant directly (from change_model tool)
409
+ resolution = MetaModelResolver.resolveToVariant(metaConfig, variantOverride);
410
+ } else {
411
+ // Resolve based on keywords in the message
412
+ resolution = MetaModelResolver.resolve(metaConfig, firstMessage, {
413
+ stripKeywords: true,
414
+ });
415
+ }
416
+
417
+ // Get the resolved underlying configuration
418
+ const resolvedConfig = this.getLLMConfig(resolution.configName);
419
+
420
+ // Generate the system prompt fragment for model descriptions
421
+ const metaModelSystemPrompt = MetaModelResolver.generateSystemPromptFragment(metaConfig);
422
+
423
+ logger.info("[ConfigService] Resolved meta model", {
424
+ originalConfig: configName,
425
+ resolvedVariant: resolution.variantName,
426
+ resolvedConfig: resolution.configName,
427
+ matchedKeywords: resolution.matchedKeywords,
428
+ usedOverride: !!variantOverride,
429
+ });
430
+
431
+ return {
432
+ config: resolvedConfig,
433
+ configName: resolution.configName,
434
+ strippedMessage: resolution.strippedMessage,
435
+ variantSystemPrompt: resolution.systemPrompt,
436
+ metaModelSystemPrompt,
437
+ isMetaModel: true,
438
+ variantName: resolution.variantName,
439
+ };
440
+ }
441
+
442
+ /**
443
+ * Create an LLM service for a named configuration
444
+ */
445
+ createLLMService(
446
+ configName?: string,
447
+ context?: {
448
+ tools?: Record<string, unknown>;
449
+ agentName?: string;
450
+ /** Working directory path for Claude Code execution */
451
+ workingDirectory?: string;
452
+ sessionId?: string;
453
+ /** Agent-specific MCP configuration to merge with project/global config */
454
+ mcpConfig?: MCPConfig;
455
+ /** Conversation ID for OpenRouter correlation */
456
+ conversationId?: string;
457
+ /** Callback invoked when Claude Code stream starts, providing the message injector */
458
+ onStreamStart?: OnStreamStartCallback;
459
+ }
460
+ ): LLMService {
461
+ const llmConfig = this.getLLMConfig(configName);
462
+
463
+ // Merge agent MCP config with project/global MCP config
464
+ // Agent config overrides project config for the same server names
465
+ let finalMcpConfig: MCPConfig | undefined;
466
+
467
+ if (this.loadedConfig?.mcp && context?.mcpConfig) {
468
+ // Merge: agent config overrides project config
469
+ finalMcpConfig = {
470
+ enabled: context.mcpConfig.enabled ?? this.loadedConfig.mcp.enabled,
471
+ servers: {
472
+ ...this.loadedConfig.mcp.servers,
473
+ ...context.mcpConfig.servers,
474
+ },
475
+ };
476
+ } else if (context?.mcpConfig) {
477
+ finalMcpConfig = context.mcpConfig;
478
+ } else if (this.loadedConfig?.mcp) {
479
+ finalMcpConfig = {
480
+ enabled: this.loadedConfig.mcp.enabled,
481
+ servers: this.loadedConfig.mcp.servers,
482
+ };
483
+ }
484
+
485
+ return llmServiceFactory.createService(llmConfig, {
486
+ ...context,
487
+ mcpConfig: finalMcpConfig,
488
+ } as Parameters<typeof llmServiceFactory.createService>[1]);
489
+ }
490
+
491
+ // =====================================================================================
492
+ // BUSINESS LOGIC METHODS
493
+ // =====================================================================================
494
+
495
+ /**
496
+ * Get the configured search model name if available.
497
+ * This is a typed accessor for the search configuration.
498
+ * @returns The search model config name or undefined if not configured
499
+ */
500
+ getSearchModelName(): string | undefined {
501
+ return this.loadedConfig?.llms?.search;
502
+ }
503
+
504
+ /**
505
+ * Ensures that a backend private key exists for TENEX
506
+ * Generates a new one if not present
507
+ */
508
+ async ensureBackendPrivateKey(): Promise<string> {
509
+ const globalPath = this.getGlobalPath();
510
+ const config = await this.loadTenexConfig(globalPath);
511
+
512
+ if (!config.tenexPrivateKey) {
513
+ // Generate new private key
514
+ const signer = NDKPrivateKeySigner.generate();
515
+ config.tenexPrivateKey = signer.privateKey;
516
+
517
+ // Save config with new key
518
+ await this.saveGlobalConfig(config);
519
+ logger.info("Generated new TENEX backend private key");
520
+ }
521
+
522
+ return config.tenexPrivateKey;
523
+ }
524
+
525
+ /**
526
+ * Get the TENEX backend signer for publishing system-level events
527
+ */
528
+ async getBackendSigner(): Promise<NDKPrivateKeySigner> {
529
+ const privateKey = await this.ensureBackendPrivateKey();
530
+ return new NDKPrivateKeySigner(privateKey);
531
+ }
532
+
533
+ /**
534
+ * Get whitelisted pubkeys with CLI override support
535
+ * If CLI option is provided, it ONLY uses those pubkeys (doesn't merge with config)
536
+ * Otherwise, returns pubkeys from the configuration
537
+ */
538
+ getWhitelistedPubkeys(cliOption?: string, config?: TenexConfig): string[] {
539
+ const pubkeys: Set<string> = new Set();
540
+
541
+ // If CLI option is provided, ONLY use those pubkeys (don't merge with config)
542
+ if (cliOption) {
543
+ for (const pk of cliOption.split(",")) {
544
+ const trimmed = pk.trim();
545
+ if (trimmed) pubkeys.add(trimmed);
546
+ }
547
+ return Array.from(pubkeys);
548
+ }
549
+
550
+ // Otherwise, use config pubkeys
551
+ if (config?.whitelistedPubkeys) {
552
+ if (Array.isArray(config.whitelistedPubkeys)) {
553
+ for (const pk of config.whitelistedPubkeys) {
554
+ if (pk) pubkeys.add(pk);
555
+ }
556
+ }
557
+ }
558
+
559
+ return Array.from(pubkeys);
560
+ }
561
+
562
+ // =====================================================================================
563
+ // CONVENIENCE METHODS
564
+ // =====================================================================================
565
+
566
+ async saveGlobalConfig(config: TenexConfig): Promise<void> {
567
+ const globalPath = this.getGlobalPath();
568
+ await ensureDirectory(globalPath);
569
+ await this.saveTenexConfig(globalPath, config);
570
+ }
571
+
572
+ async saveGlobalLLMs(llms: TenexLLMs): Promise<void> {
573
+ const globalPath = this.getGlobalPath();
574
+ await ensureDirectory(globalPath);
575
+ await this.saveTenexLLMs(globalPath, llms);
576
+ }
577
+
578
+ async saveGlobalMCP(mcp: TenexMCP): Promise<void> {
579
+ const globalPath = this.getGlobalPath();
580
+ await ensureDirectory(globalPath);
581
+ await this.saveTenexMCP(globalPath, mcp);
582
+ }
583
+
584
+ async saveGlobalProviders(providers: TenexProviders): Promise<void> {
585
+ const globalPath = this.getGlobalPath();
586
+ await ensureDirectory(globalPath);
587
+ await this.saveTenexProviders(globalPath, providers);
588
+ }
589
+
590
+ // =====================================================================================
591
+ // FILE EXISTENCE CHECKS
592
+ // =====================================================================================
593
+
594
+ async configExists(basePath: string, configFile: ConfigFile): Promise<boolean> {
595
+ return fileExists(this.getConfigFilePath(basePath, configFile));
596
+ }
597
+
598
+ async globalConfigExists(configFile: ConfigFile): Promise<boolean> {
599
+ return this.configExists(this.getGlobalPath(), configFile);
600
+ }
601
+
602
+ // =====================================================================================
603
+ // PRIVATE IMPLEMENTATION
604
+ // =====================================================================================
605
+
606
+ private validateProviderReferences(llms: TenexLLMs, providers: TenexProviders): void {
607
+ const missingProviders = new Set<string>();
608
+
609
+ for (const configValue of Object.values(llms.configurations)) {
610
+ if (configValue.provider === "meta") continue;
611
+
612
+ const providerName = configValue.provider;
613
+ if (!providers.providers[providerName]) {
614
+ missingProviders.add(providerName);
615
+ }
616
+ }
617
+
618
+ if (missingProviders.size > 0) {
619
+ logger.warn(
620
+ `LLM configurations reference providers not in providers.json: ${Array.from(missingProviders).join(", ")}`
621
+ );
622
+ }
623
+ }
624
+
625
+ private async loadConfigFile<T>(
626
+ filePath: string,
627
+ schema: z.ZodSchema<T>,
628
+ defaultValue: T
629
+ ): Promise<T> {
630
+ // Check cache first
631
+ const cached = this.getFromCache<T>(filePath);
632
+ if (cached) {
633
+ return cached;
634
+ }
635
+
636
+ // Check if file exists - if not, return default (this is expected)
637
+ if (!(await fileExists(filePath))) {
638
+ logger.debug(`Config file not found, using default: ${filePath}`);
639
+ return defaultValue;
640
+ }
641
+
642
+ // File exists - any error from here is a real problem that should propagate
643
+ try {
644
+ const data = await readJsonFile(filePath);
645
+ const validated = schema.parse(data);
646
+
647
+ this.addToCache(filePath, validated);
648
+ return validated;
649
+ } catch (error) {
650
+ // File exists but is corrupt/invalid - this is a real error, not a missing file
651
+ const errorMessage = formatAnyError(error);
652
+ logger.error(`Config file is corrupt or invalid: ${filePath}`, {
653
+ error: errorMessage,
654
+ });
655
+ throw new Error(
656
+ `Failed to load config file "${filePath}": ${errorMessage}. ` +
657
+ "Fix the file or delete it to use defaults.",
658
+ { cause: error }
659
+ );
660
+ }
661
+ }
662
+
663
+ private async saveConfigFile<T>(
664
+ filePath: string,
665
+ data: T,
666
+ schema: z.ZodSchema<T>
667
+ ): Promise<void> {
668
+ try {
669
+ // Ensure directory exists
670
+ await ensureDirectory(path.dirname(filePath));
671
+
672
+ // Validate before saving
673
+ const validated = schema.parse(data);
674
+
675
+ // Save to file
676
+ await writeJsonFile(filePath, validated);
677
+
678
+ // Update cache
679
+ this.addToCache(filePath, validated);
680
+
681
+ logger.debug(`Configuration saved: ${filePath}`);
682
+ } catch (error) {
683
+ logger.error(`Failed to save config file: ${filePath}`, {
684
+ error: formatAnyError(error),
685
+ });
686
+ throw error;
687
+ }
688
+ }
689
+
690
+ private getFromCache<T>(filePath: string): T | null {
691
+ const entry = this.cache.get(filePath);
692
+ if (!entry) {
693
+ return null;
694
+ }
695
+
696
+ const now = Date.now();
697
+ if (now - entry.timestamp > 5000) {
698
+ // 5 seconds TTL
699
+ this.cache.delete(filePath);
700
+ return null;
701
+ }
702
+
703
+ return entry.data as T;
704
+ }
705
+
706
+ private addToCache<T>(filePath: string, data: T): void {
707
+ this.cache.set(filePath, {
708
+ data,
709
+ timestamp: Date.now(),
710
+ });
711
+ }
712
+
713
+ clearCache(filePath?: string): void {
714
+ if (filePath) {
715
+ this.cache.delete(filePath);
716
+ } else {
717
+ this.cache.clear();
718
+ }
719
+ }
720
+ }
721
+
722
+ // Export instance
723
+ export const config = new ConfigService();