@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,357 @@
1
+ /**
2
+ * Delegation Chain Utilities
3
+ *
4
+ * This module provides utilities for tracking and displaying the delegation chain
5
+ * in multi-agent workflows. The delegation chain shows agents their position in
6
+ * the hierarchy, helping them understand context and prevent circular delegations.
7
+ *
8
+ * Example chain:
9
+ * [User -> architect-orchestrator] [conversation 4f69d3302cf2]
10
+ * -> [architect-orchestrator -> execution-coordinator] [conversation 8a2bc1e45678]
11
+ * -> [execution-coordinator -> claude-code (you)] [conversation 1234567890ab]
12
+ *
13
+ * SEMANTIC MODEL (Option B - Store on Recipient):
14
+ * Each entry's `conversationId` represents "the conversation where this agent was
15
+ * DELEGATED TO" (i.e., where they received the delegation from their delegator).
16
+ *
17
+ * Example with conversations:
18
+ * User (in user-conv) delegates to pm-wip
19
+ * pm-wip (in pm-conv) delegates to exec
20
+ * exec (in exec-conv) delegates to claude-code
21
+ *
22
+ * Chain entries:
23
+ * - User: conversationId = undefined (origin, wasn't delegated)
24
+ * - pm-wip: conversationId = user-conv (was delegated to in user-conv)
25
+ * - exec: conversationId = pm-conv (was delegated to in pm-conv)
26
+ * - claude-code: conversationId = exec-conv (was delegated to in exec-conv)
27
+ *
28
+ * When displaying [A -> B] [conversation X], X = B.conversationId
29
+ * (the conversation where B was delegated to, i.e., where A delegated to B)
30
+ */
31
+
32
+ import type { NDKEvent } from "@nostr-dev-kit/ndk";
33
+ import { ConversationStore } from "@/conversations/ConversationStore";
34
+ import type { DelegationChainEntry } from "@/conversations/types";
35
+ import { getProjectContext } from "@/services/projects";
36
+ import { getPubkeyService } from "@/services/PubkeyService";
37
+ import { logger } from "@/utils/logger";
38
+ import { PREFIX_LENGTH } from "@/utils/nostr-entity-parser";
39
+ import { shortenConversationId } from "@/utils/conversation-id";
40
+
41
+ /**
42
+ * Build a delegation chain from a triggering event.
43
+ *
44
+ * The chain is built by:
45
+ * 1. Looking for the "delegation" tag in the event (points to parent conversation)
46
+ * 2. Recursively following parent conversations to build the full chain
47
+ * 3. Including the sender as the immediate delegator
48
+ *
49
+ * SEMANTICS: Each entry's `conversationId` represents "the conversation where this agent
50
+ * was DELEGATED TO". When displaying [A -> B] [conversation X], X comes from B.conversationId.
51
+ * - Origin agent has no conversationId (they started the chain, weren't delegated)
52
+ * - Current agent's conversationId = currentConversationId (the conversation being created for them)
53
+ *
54
+ * @param event - The event that triggered this conversation
55
+ * @param currentAgentPubkey - The pubkey of the agent receiving the delegation
56
+ * @param projectOwnerPubkey - The pubkey of the project owner (human user)
57
+ * @param currentConversationId - The ID of the conversation being created for the current agent (required to ensure correct semantics)
58
+ * @returns The delegation chain entries, or undefined if this is a direct user message
59
+ */
60
+ export function buildDelegationChain(
61
+ event: NDKEvent,
62
+ currentAgentPubkey: string,
63
+ projectOwnerPubkey: string,
64
+ currentConversationId: string
65
+ ): DelegationChainEntry[] | undefined {
66
+ // Check for delegation tag - if not present, this is a direct user conversation
67
+ const delegationTag = event.tags.find(t => t[0] === "delegation");
68
+ if (!delegationTag || !delegationTag[1]) {
69
+ // Direct user message - no delegation chain needed
70
+ return undefined;
71
+ }
72
+
73
+ const parentConversationId = delegationTag[1];
74
+ const chain: DelegationChainEntry[] = [];
75
+
76
+ // Get project context for agent resolution
77
+ const projectContext = getProjectContext();
78
+
79
+ /**
80
+ * Helper to resolve a pubkey to a display name.
81
+ * Returns the agent slug if known, or the user's name from their Nostr profile if it's the project owner.
82
+ * Uses PubkeyService.getNameSync() which returns cached profile name or shortened pubkey as fallback.
83
+ */
84
+ const resolveDisplayName = (pubkey: string): { displayName: string; isUser: boolean } => {
85
+ if (pubkey === projectOwnerPubkey) {
86
+ // Use PubkeyService to get the user's display name from their Nostr profile
87
+ const displayName = getPubkeyService().getNameSync(pubkey);
88
+ return { displayName, isUser: true };
89
+ }
90
+
91
+ const agent = projectContext.getAgentByPubkey(pubkey);
92
+ if (agent) {
93
+ return { displayName: agent.slug, isUser: false };
94
+ }
95
+
96
+ // Unknown pubkey - use truncated version
97
+ return { displayName: pubkey.substring(0, PREFIX_LENGTH), isUser: false };
98
+ };
99
+
100
+ // Build the chain by walking up through parent conversations
101
+ //
102
+ // ALGORITHM: We walk from child to parent conversations, tracking where each
103
+ // agent was "delegated TO". When we visit conversation C:
104
+ // - The initiator of C was delegated TO in the conversation that led us TO C
105
+ // (i.e., the conversation we came from, which had a delegation tag pointing to C)
106
+ //
107
+ // Example: claude-code <- exec-conv <- pm-conv <- user-conv
108
+ // - claude was delegated TO in exec-conv (parentConversationId)
109
+ // - We visit exec-conv, find initiator=exec. exec was delegated TO in pm-conv
110
+ // (the conv that pointed to exec-conv)
111
+ // - We visit pm-conv, find initiator=pm. pm was delegated TO in user-conv
112
+ // - We visit user-conv, find initiator=User. User is origin (no conversationId)
113
+
114
+ let currentParentId: string | undefined = parentConversationId;
115
+ const visitedConversations = new Set<string>();
116
+ const seenPubkeys = new Set<string>();
117
+
118
+ // Collect ancestors as we walk up (newest -> oldest)
119
+ interface CollectedEntry {
120
+ pubkey: string;
121
+ displayName: string;
122
+ isUser: boolean;
123
+ delegatedToInConvId?: string; // The conversation where this agent received delegation
124
+ }
125
+ const collectedAncestors: CollectedEntry[] = [];
126
+
127
+ // Track where the immediate delegator (event.pubkey) was delegated TO
128
+ // This will be discovered as we walk up
129
+ let immediateDelegatorConvId: string | undefined;
130
+
131
+ while (currentParentId && !visitedConversations.has(currentParentId)) {
132
+ visitedConversations.add(currentParentId);
133
+
134
+ const parentStore = ConversationStore.get(currentParentId);
135
+ if (!parentStore) {
136
+ // Parent conversation not found - we've reached the end of our knowledge
137
+ break;
138
+ }
139
+
140
+ // Check if this parent conversation has its own delegation chain already computed
141
+ const parentChain = parentStore.metadata.delegationChain;
142
+ if (parentChain && parentChain.length > 0) {
143
+ // The stored chain is authoritative - use it
144
+ // Clear any collectedAncestors that overlap with the stored chain
145
+ const storedPubkeys = new Set(parentChain.map(e => e.pubkey));
146
+ for (let i = collectedAncestors.length - 1; i >= 0; i--) {
147
+ if (storedPubkeys.has(collectedAncestors[i].pubkey)) {
148
+ seenPubkeys.delete(collectedAncestors[i].pubkey);
149
+ collectedAncestors.splice(i, 1);
150
+ }
151
+ }
152
+
153
+ // Use the parent's already-computed chain (it's already in oldest-first order)
154
+ // Clone entries to avoid mutating stored chain
155
+ for (const entry of parentChain) {
156
+ if (!seenPubkeys.has(entry.pubkey)) {
157
+ seenPubkeys.add(entry.pubkey);
158
+ chain.push({ ...entry });
159
+ }
160
+ }
161
+
162
+ // The immediate delegator (event.pubkey) was delegated TO in currentParentId
163
+ // (this is the conversation where the stored chain ends, where they work)
164
+ immediateDelegatorConvId = currentParentId;
165
+
166
+ // We have the full ancestry from the stored chain - stop walking
167
+ break;
168
+ }
169
+
170
+ // Get the first message to find who initiated this conversation
171
+ const messages = parentStore.getAllMessages();
172
+ if (messages.length === 0) {
173
+ break;
174
+ }
175
+
176
+ const firstMessage = messages[0];
177
+
178
+ // Try to find if this parent conversation itself was a delegation
179
+ // This tells us where the initiator of THIS conversation was delegated TO
180
+ const rootEventId = parentStore.getRootEventId();
181
+ let nextParentId: string | undefined;
182
+ if (rootEventId) {
183
+ const rootEvent = ConversationStore.getCachedEvent(rootEventId);
184
+ if (rootEvent) {
185
+ const parentDelegationTag = rootEvent.tags.find(t => t[0] === "delegation");
186
+ if (parentDelegationTag && parentDelegationTag[1]) {
187
+ nextParentId = parentDelegationTag[1];
188
+ }
189
+ }
190
+ }
191
+
192
+ // Add this conversation's initiator to our collected ancestors (if not seen)
193
+ // The initiator was delegated TO in nextParentId (the conv that led to this one)
194
+ // If nextParentId is undefined, this is the origin (wasn't delegated)
195
+ if (!seenPubkeys.has(firstMessage.pubkey)) {
196
+ seenPubkeys.add(firstMessage.pubkey);
197
+ const { displayName, isUser } = resolveDisplayName(firstMessage.pubkey);
198
+ collectedAncestors.push({
199
+ pubkey: firstMessage.pubkey,
200
+ displayName,
201
+ isUser,
202
+ delegatedToInConvId: nextParentId, // Where this agent was delegated TO (or undefined for origin)
203
+ });
204
+ }
205
+
206
+ // If the initiator is the immediate delegator, record where they were delegated TO
207
+ if (firstMessage.pubkey === event.pubkey && immediateDelegatorConvId === undefined) {
208
+ immediateDelegatorConvId = nextParentId;
209
+ }
210
+
211
+ if (nextParentId) {
212
+ // Continue walking up
213
+ currentParentId = nextParentId;
214
+ } else {
215
+ // No further delegation found - we've reached the origin
216
+ break;
217
+ }
218
+ }
219
+
220
+ // Reverse collected ancestors to get oldest-first order (origin first)
221
+ collectedAncestors.reverse();
222
+
223
+ // Convert collectedAncestors to chain entries
224
+ // SEMANTICS: entry.conversationId = "where this agent was delegated TO"
225
+ // Store FULL conversation IDs - truncation happens at display time in formatDelegationChain
226
+ for (const ancestor of collectedAncestors) {
227
+ if (!chain.some(e => e.pubkey === ancestor.pubkey)) {
228
+ chain.push({
229
+ pubkey: ancestor.pubkey,
230
+ displayName: ancestor.displayName,
231
+ isUser: ancestor.isUser,
232
+ conversationId: ancestor.delegatedToInConvId, // Store full ID
233
+ });
234
+ }
235
+ }
236
+
237
+ // Add the immediate delegator (event.pubkey) if not already in chain.
238
+ // If chain is empty, delegator is the origin (no conversationId)
239
+ // Otherwise, delegator was delegated TO in immediateDelegatorConvId (or parentConversationId for legacy)
240
+ if (!seenPubkeys.has(event.pubkey) && !chain.some(e => e.pubkey === event.pubkey)) {
241
+ const { displayName, isUser } = resolveDisplayName(event.pubkey);
242
+ seenPubkeys.add(event.pubkey);
243
+
244
+ // If chain is empty, this delegator is the origin (no conversationId)
245
+ // Otherwise, their conversationId = where they were delegated TO
246
+ // Use immediateDelegatorConvId if set (from stored chain), or parentConversationId (legacy path)
247
+ // Store FULL conversation ID - truncation happens at display time
248
+ const isOrigin = chain.length === 0;
249
+ const delegatorConvId = isOrigin ? undefined : (immediateDelegatorConvId || parentConversationId);
250
+ chain.push({
251
+ pubkey: event.pubkey,
252
+ displayName,
253
+ isUser,
254
+ conversationId: delegatorConvId, // Store full ID
255
+ });
256
+ }
257
+
258
+ // Always add the current agent as the terminal entry.
259
+ // For self-delegation (A → A), A appears twice: [..., A(delegator), A(current)]
260
+ // resolveCompletionRecipient picks chain[length-2] which MUST be the delegator.
261
+ // Current agent was delegated TO in currentConversationId (the conversation being created for them)
262
+ // Store FULL conversation ID - truncation happens at display time
263
+ {
264
+ const currentAgentInfo = resolveDisplayName(currentAgentPubkey);
265
+ chain.push({
266
+ pubkey: currentAgentPubkey,
267
+ displayName: currentAgentInfo.displayName,
268
+ isUser: false,
269
+ conversationId: currentConversationId,
270
+ });
271
+ }
272
+
273
+ logger.debug("[delegation-chain] Built delegation chain", {
274
+ chainLength: chain.length,
275
+ chain: chain.map(c => `${c.displayName}(${c.conversationId || "origin"})`).join(" → "),
276
+ });
277
+
278
+ return chain;
279
+ }
280
+
281
+ /**
282
+ * Format a delegation chain as a multi-line tree showing delegation relationships.
283
+ *
284
+ * Each line shows: [sender -> recipient] [conversation <id>]
285
+ * With indentation showing the delegation depth.
286
+ *
287
+ * SEMANTICS: The conversation ID shown for [A -> B] comes from B.conversationId
288
+ * (the conversation where B was delegated to, i.e., where A delegated to B).
289
+ * This is consistent for ALL links, including the final link.
290
+ *
291
+ * @param chain - The delegation chain entries (each entry has full conversation ID stored)
292
+ * @param currentAgentPubkey - The pubkey of the current agent (to mark with "(you)")
293
+ * @returns A formatted multi-line string showing the delegation tree
294
+ *
295
+ * Example output:
296
+ * ```
297
+ * [User -> architect-orchestrator] [conversation 4f69d3302cf2]
298
+ * -> [architect-orchestrator -> execution-coordinator] [conversation 8a2bc1e45678]
299
+ * -> [execution-coordinator -> claude-code (you)] [conversation 1234567890ab]
300
+ * ```
301
+ */
302
+ export function formatDelegationChain(
303
+ chain: DelegationChainEntry[],
304
+ currentAgentPubkey: string
305
+ ): string {
306
+ if (chain.length === 0) {
307
+ return "";
308
+ }
309
+
310
+ if (chain.length === 1) {
311
+ // Single entry - just show the agent with (you) marker if applicable
312
+ const entry = chain[0];
313
+ const suffix = entry.pubkey === currentAgentPubkey ? " (you)" : "";
314
+ return `${entry.displayName}${suffix}`;
315
+ }
316
+
317
+ const lines: string[] = [];
318
+
319
+ // Build delegation links: each link is from entry[i] -> entry[i+1]
320
+ // SEMANTICS: recipient.conversationId = "where recipient was delegated TO"
321
+ // So for [A -> B], we use B.conversationId (recipient.conversationId)
322
+ for (let i = 0; i < chain.length - 1; i++) {
323
+ const sender = chain[i];
324
+ const recipient = chain[i + 1];
325
+
326
+ // Add (you) marker if recipient is current agent
327
+ const recipientSuffix = recipient.pubkey === currentAgentPubkey ? " (you)" : "";
328
+ const recipientName = `${recipient.displayName}${recipientSuffix}`;
329
+
330
+ // Get the conversation ID for this link from RECIPIENT.conversationId
331
+ // Truncate to PREFIX_LENGTH chars for display (full IDs are stored in chain entries)
332
+ const convId = recipient.conversationId
333
+ ? shortenConversationId(recipient.conversationId)
334
+ : "unknown";
335
+
336
+ // Build the line with proper indentation
337
+ const indent = i === 0 ? "" : " ".repeat(i) + "-> ";
338
+ const line = `${indent}[${sender.displayName} -> ${recipientName}] [conversation ${convId}]`;
339
+ lines.push(line);
340
+ }
341
+
342
+ return lines.join("\n");
343
+ }
344
+
345
+ /**
346
+ * Check if adding an agent to the chain would create a circular delegation.
347
+ *
348
+ * @param chain - The current delegation chain
349
+ * @param agentPubkey - The pubkey of the agent to add
350
+ * @returns true if adding this agent would create a cycle
351
+ */
352
+ export function wouldCreateCircularDelegation(
353
+ chain: DelegationChainEntry[],
354
+ agentPubkey: string
355
+ ): boolean {
356
+ return chain.some(entry => entry.pubkey === agentPubkey);
357
+ }
@@ -0,0 +1,42 @@
1
+ import { formatAnyError } from "@/lib/error-formatter";
2
+ import { logger } from "./logger";
3
+
4
+ /**
5
+ * Standard error handling utility for consistent error management
6
+ * across the codebase
7
+ */
8
+ export function handleError(
9
+ error: unknown,
10
+ context: string,
11
+ options?: {
12
+ logLevel?: "error" | "warn" | "debug";
13
+ rethrow?: boolean;
14
+ exitCode?: number;
15
+ }
16
+ ): string {
17
+ const message = formatAnyError(error);
18
+ const logLevel = options?.logLevel ?? "error";
19
+
20
+ switch (logLevel) {
21
+ case "error":
22
+ logger.error(`${context}: ${message}`);
23
+ break;
24
+ case "warn":
25
+ logger.warn(`${context}: ${message}`);
26
+ break;
27
+ case "debug":
28
+ logger.debug(`${context}: ${message}`);
29
+ break;
30
+ }
31
+
32
+ if (options?.exitCode !== undefined) {
33
+ process.exit(options.exitCode);
34
+ }
35
+
36
+ if (options?.rethrow) {
37
+ throw error;
38
+ }
39
+
40
+ return message;
41
+ }
42
+
@@ -0,0 +1,69 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import { logger } from "@/utils/logger";
4
+
5
+ /**
6
+ * Check if a gitignore entry already exists (handles various formats)
7
+ */
8
+ function hasGitignoreEntry(content: string, entry: string): boolean {
9
+ const lines = content.split("\n");
10
+ const normalizedEntry = entry.replace(/^\//, "").replace(/\/$/, "");
11
+ return lines.some((line) => {
12
+ const normalizedLine = line.trim().replace(/^\//, "").replace(/\/$/, "");
13
+ return normalizedLine === normalizedEntry;
14
+ });
15
+ }
16
+
17
+ /**
18
+ * Ensures .tenex is in the project's .gitignore file
19
+ */
20
+ export async function ensureTenexInGitignore(projectPath: string): Promise<void> {
21
+ await ensureGitignoreEntry(projectPath, ".tenex/", "TENEX project files");
22
+ }
23
+
24
+ /**
25
+ * Ensures .worktrees is in the project's .gitignore file.
26
+ * This must be called when creating worktrees to prevent them from being committed.
27
+ */
28
+ export async function ensureWorktreesGitignore(projectPath: string): Promise<void> {
29
+ await ensureGitignoreEntry(projectPath, ".worktrees/", "Git worktrees");
30
+ }
31
+
32
+ /**
33
+ * Generic function to ensure an entry exists in .gitignore
34
+ */
35
+ async function ensureGitignoreEntry(
36
+ projectPath: string,
37
+ entry: string,
38
+ comment: string
39
+ ): Promise<void> {
40
+ const gitignorePath = path.join(projectPath, ".gitignore");
41
+
42
+ try {
43
+ let gitignoreContent = "";
44
+
45
+ // Check if .gitignore exists
46
+ try {
47
+ gitignoreContent = await fs.readFile(gitignorePath, "utf-8");
48
+ } catch {
49
+ // .gitignore doesn't exist, we'll create it
50
+ logger.debug("No .gitignore found, will create one");
51
+ }
52
+
53
+ // Check if entry is already in .gitignore
54
+ if (!hasGitignoreEntry(gitignoreContent, entry)) {
55
+ // Add entry to .gitignore
56
+ const updatedContent = gitignoreContent.trim()
57
+ ? `${gitignoreContent.trim()}\n\n# ${comment}\n${entry}\n`
58
+ : `# ${comment}\n${entry}\n`;
59
+
60
+ await fs.writeFile(gitignorePath, updatedContent);
61
+ logger.info(`Added ${entry} to .gitignore`);
62
+ } else {
63
+ logger.debug(`${entry} already in .gitignore`);
64
+ }
65
+ } catch (error) {
66
+ logger.error("Failed to update .gitignore", { error, entry });
67
+ throw error;
68
+ }
69
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./gitignore";
2
+ export * from "./initializeGitRepo";
@@ -0,0 +1,204 @@
1
+ import { exec } from "node:child_process";
2
+ import * as fs from "node:fs/promises";
3
+ import * as path from "node:path";
4
+ import { promisify } from "node:util";
5
+ import { logger } from "@/utils/logger";
6
+
7
+ const execAsync = promisify(exec);
8
+
9
+ /**
10
+ * Result from git repository initialization or cloning.
11
+ */
12
+ export interface GitRepoResult {
13
+ /**
14
+ * Project directory (the git repository root).
15
+ * Example: ~/tenex/{dTag}
16
+ */
17
+ projectPath: string;
18
+ /**
19
+ * Name of the default/current branch.
20
+ * Example: "main" or "master"
21
+ */
22
+ branch: string;
23
+ }
24
+
25
+ /**
26
+ * Get the default branch name for a git repository.
27
+ * Tries to detect from remote HEAD, falls back to common defaults.
28
+ */
29
+ export async function getDefaultBranchName(repoPath: string): Promise<string> {
30
+ try {
31
+ // Try to get the default branch from the remote
32
+ const { stdout } = await execAsync("git symbolic-ref refs/remotes/origin/HEAD", {
33
+ cwd: repoPath,
34
+ });
35
+ const match = stdout.trim().match(/refs\/remotes\/origin\/(.+)/);
36
+ if (match) {
37
+ return match[1];
38
+ }
39
+ } catch {
40
+ // If that fails, try to get it from the local default branch
41
+ try {
42
+ const { stdout } = await execAsync("git rev-parse --abbrev-ref HEAD", {
43
+ cwd: repoPath,
44
+ });
45
+ const branch = stdout.trim();
46
+ if (branch && branch !== "HEAD") {
47
+ return branch;
48
+ }
49
+ } catch {
50
+ // Fall back to checking git config
51
+ try {
52
+ const { stdout } = await execAsync("git config --get init.defaultBranch");
53
+ if (stdout.trim()) {
54
+ return stdout.trim();
55
+ }
56
+ } catch {
57
+ // Final fallback to 'main'
58
+ }
59
+ }
60
+ }
61
+
62
+ // Default to 'main' as it's the modern standard
63
+ return "main";
64
+ }
65
+
66
+ /**
67
+ * Check if a directory is a Git repository
68
+ */
69
+ export async function isGitRepository(dir?: string): Promise<boolean> {
70
+ try {
71
+ const cwd = dir || process.cwd();
72
+ await execAsync("git rev-parse --git-dir", { cwd });
73
+ return true;
74
+ } catch {
75
+ return false;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Initialize a new Git repository.
81
+ * Creates a standard git repository at the specified directory.
82
+ *
83
+ * @param projectDir - The project directory to initialize
84
+ * @returns GitRepoResult with projectPath and branch
85
+ */
86
+ export async function initializeGitRepository(projectDir?: string): Promise<GitRepoResult> {
87
+ const targetDir = projectDir || process.cwd();
88
+
89
+ // Check if already a git repository
90
+ if (await isGitRepository(targetDir)) {
91
+ logger.info("Git repository already exists", { projectDir: targetDir });
92
+ const branch = await getDefaultBranchName(targetDir);
93
+ return { projectPath: targetDir, branch };
94
+ }
95
+
96
+ // Ensure directory exists
97
+ await fs.mkdir(targetDir, { recursive: true });
98
+
99
+ // Get the configured default branch name
100
+ let branchName = "main";
101
+ try {
102
+ const { stdout } = await execAsync("git config --get init.defaultBranch");
103
+ if (stdout.trim()) {
104
+ branchName = stdout.trim();
105
+ }
106
+ } catch {
107
+ // Use 'main' as default
108
+ }
109
+
110
+ // Initialize git repository
111
+ await execAsync("git init", { cwd: targetDir });
112
+ logger.info("Initialized git repository", { projectDir: targetDir, branch: branchName });
113
+
114
+ return { projectPath: targetDir, branch: branchName };
115
+ }
116
+
117
+ /**
118
+ * Clone a git repository.
119
+ * Clones the repository to the specified directory.
120
+ *
121
+ * @param repoUrl - The git repository URL to clone
122
+ * @param projectDir - The directory to clone into
123
+ * @returns GitRepoResult with projectPath and branch, or null if failed
124
+ */
125
+ export async function cloneGitRepository(
126
+ repoUrl: string,
127
+ projectDir: string
128
+ ): Promise<GitRepoResult | null> {
129
+ try {
130
+ // Check if already a git repository
131
+ if (await isGitRepository(projectDir)) {
132
+ logger.info("Git repository already exists", { projectDir });
133
+ const branch = await getDefaultBranchName(projectDir);
134
+ return { projectPath: projectDir, branch };
135
+ }
136
+
137
+ // Ensure parent directory exists
138
+ await fs.mkdir(path.dirname(projectDir), { recursive: true });
139
+
140
+ // Clone the repository
141
+ logger.info("Cloning git repository", { repoUrl, projectDir });
142
+ await execAsync(`git clone ${JSON.stringify(repoUrl)} ${JSON.stringify(projectDir)}`, {
143
+ maxBuffer: 1024 * 1024 * 10, // 10MB buffer for large repos
144
+ });
145
+
146
+ // Detect the default branch name
147
+ const branchName = await getDefaultBranchName(projectDir);
148
+
149
+ logger.info("Git repository cloned successfully", {
150
+ repoUrl,
151
+ projectDir,
152
+ branch: branchName
153
+ });
154
+
155
+ return { projectPath: projectDir, branch: branchName };
156
+ } catch (error) {
157
+ logger.error("Failed to clone git repository", {
158
+ error: error instanceof Error ? error.message : String(error),
159
+ repoUrl,
160
+ projectDir,
161
+ });
162
+
163
+ // Clean up directory if clone failed partially
164
+ try {
165
+ await fs.rm(projectDir, { recursive: true, force: true });
166
+ } catch {
167
+ // Ignore cleanup errors
168
+ }
169
+
170
+ return null;
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Get current git branch name
176
+ */
177
+ export async function getCurrentBranch(repoPath: string): Promise<string> {
178
+ try {
179
+ const { stdout } = await execAsync("git branch --show-current", { cwd: repoPath });
180
+ return stdout.trim();
181
+ } catch (error) {
182
+ logger.error("Failed to get current branch", { repoPath, error });
183
+ throw error;
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Get current branch with fallback to main/master if detection fails
189
+ */
190
+ export async function getCurrentBranchWithFallback(projectPath: string): Promise<string> {
191
+ try {
192
+ return await getCurrentBranch(projectPath);
193
+ } catch (error) {
194
+ logger.warn("Failed to get current branch, trying fallbacks", { projectPath, error });
195
+
196
+ // Try fallback branch names
197
+ try {
198
+ await fs.access(path.join(projectPath, ".git/refs/heads/main"));
199
+ return "main";
200
+ } catch {
201
+ return "master";
202
+ }
203
+ }
204
+ }