@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.
- package/README.md +194 -0
- package/dist/backend-wrapper.cjs +3 -0
- package/dist/src/index.js +331928 -0
- package/package.json +103 -0
- package/src/agents/AgentRegistry.ts +418 -0
- package/src/agents/AgentStorage.ts +1133 -0
- package/src/agents/ConfigResolver.ts +229 -0
- package/src/agents/agent-installer.ts +236 -0
- package/src/agents/agent-loader.ts +241 -0
- package/src/agents/constants.ts +82 -0
- package/src/agents/errors.ts +48 -0
- package/src/agents/execution/AgentExecutor.ts +561 -0
- package/src/agents/execution/ExecutionContextFactory.ts +112 -0
- package/src/agents/execution/MessageCompiler.ts +597 -0
- package/src/agents/execution/MessageSyncer.ts +100 -0
- package/src/agents/execution/PostCompletionChecker.ts +278 -0
- package/src/agents/execution/ProgressMonitor.ts +50 -0
- package/src/agents/execution/RALResolver.ts +177 -0
- package/src/agents/execution/SessionManager.ts +181 -0
- package/src/agents/execution/StreamCallbacks.ts +312 -0
- package/src/agents/execution/StreamExecutionHandler.ts +579 -0
- package/src/agents/execution/StreamSetup.ts +313 -0
- package/src/agents/execution/ToolEventHandlers.ts +239 -0
- package/src/agents/execution/ToolExecutionTracker.ts +498 -0
- package/src/agents/execution/ToolResultUtils.ts +97 -0
- package/src/agents/execution/ToolSupervisionWrapper.ts +174 -0
- package/src/agents/execution/constants.ts +16 -0
- package/src/agents/execution/index.ts +3 -0
- package/src/agents/execution/types.ts +96 -0
- package/src/agents/execution/utils.ts +26 -0
- package/src/agents/index.ts +4 -0
- package/src/agents/script-installer.ts +266 -0
- package/src/agents/supervision/SupervisorLLMService.ts +253 -0
- package/src/agents/supervision/SupervisorOrchestrator.ts +471 -0
- package/src/agents/supervision/heuristics/ConsecutiveToolsWithoutTodoHeuristic.ts +73 -0
- package/src/agents/supervision/heuristics/DelegationClaimHeuristic.ts +80 -0
- package/src/agents/supervision/heuristics/HeuristicRegistry.ts +114 -0
- package/src/agents/supervision/heuristics/PendingTodosHeuristic.ts +93 -0
- package/src/agents/supervision/heuristics/SilentAgentHeuristic.ts +54 -0
- package/src/agents/supervision/heuristics/index.ts +5 -0
- package/src/agents/supervision/index.ts +28 -0
- package/src/agents/supervision/registerHeuristics.ts +110 -0
- package/src/agents/supervision/supervisionHealthCheck.ts +123 -0
- package/src/agents/supervision/types.ts +171 -0
- package/src/agents/tool-names.ts +46 -0
- package/src/agents/tool-normalization.ts +184 -0
- package/src/agents/types/index.ts +2 -0
- package/src/agents/types/runtime.ts +74 -0
- package/src/agents/types/storage.ts +145 -0
- package/src/commands/agent/import/index.ts +6 -0
- package/src/commands/agent/import/openclaw-distiller.ts +57 -0
- package/src/commands/agent/import/openclaw-reader.ts +141 -0
- package/src/commands/agent/import/openclaw.ts +154 -0
- package/src/commands/agent/index.ts +6 -0
- package/src/commands/agent.ts +215 -0
- package/src/commands/daemon.ts +198 -0
- package/src/commands/doctor.ts +134 -0
- package/src/commands/setup/embed.ts +228 -0
- package/src/commands/setup/global-system-prompt.ts +223 -0
- package/src/commands/setup/image.ts +179 -0
- package/src/commands/setup/index.ts +16 -0
- package/src/commands/setup/interactive.ts +95 -0
- package/src/commands/setup/llm.ts +38 -0
- package/src/commands/setup/onboarding.ts +294 -0
- package/src/commands/setup/providers.ts +27 -0
- package/src/constants.ts +34 -0
- package/src/conversations/ConversationDiskReader.ts +148 -0
- package/src/conversations/ConversationRegistry.ts +728 -0
- package/src/conversations/ConversationStore.ts +868 -0
- package/src/conversations/MessageBuilder.ts +866 -0
- package/src/conversations/executionTime.ts +62 -0
- package/src/conversations/formatters/DelegationXmlFormatter.ts +64 -0
- package/src/conversations/formatters/ThreadedConversationFormatter.ts +303 -0
- package/src/conversations/formatters/index.ts +9 -0
- package/src/conversations/formatters/utils/MessageFormatter.ts +46 -0
- package/src/conversations/formatters/utils/TimestampFormatter.ts +56 -0
- package/src/conversations/formatters/utils/TreeBuilder.ts +131 -0
- package/src/conversations/formatters/utils/TreeRenderer.ts +49 -0
- package/src/conversations/index.ts +2 -0
- package/src/conversations/persistence/ToolMessageStorage.ts +143 -0
- package/src/conversations/search/ConversationIndexManager.ts +393 -0
- package/src/conversations/search/QueryParser.ts +114 -0
- package/src/conversations/search/SearchEngine.ts +175 -0
- package/src/conversations/search/SnippetExtractor.ts +345 -0
- package/src/conversations/search/embeddings/ConversationEmbeddingService.ts +484 -0
- package/src/conversations/search/embeddings/ConversationIndexingJob.ts +320 -0
- package/src/conversations/search/embeddings/IndexingStateManager.ts +338 -0
- package/src/conversations/search/embeddings/index.ts +18 -0
- package/src/conversations/search/index.ts +49 -0
- package/src/conversations/search/types.ts +124 -0
- package/src/conversations/services/CategoryManager.ts +160 -0
- package/src/conversations/services/ConversationResolver.ts +296 -0
- package/src/conversations/services/ConversationSummarizer.ts +234 -0
- package/src/conversations/services/MetadataDebounceManager.ts +188 -0
- package/src/conversations/services/index.ts +2 -0
- package/src/conversations/types.ts +148 -0
- package/src/conversations/utils/content-utils.ts +69 -0
- package/src/conversations/utils/image-placeholder.ts +281 -0
- package/src/conversations/utils/image-url-utils.ts +171 -0
- package/src/conversations/utils/multimodal-content.ts +90 -0
- package/src/conversations/utils/tool-result-truncator.ts +159 -0
- package/src/daemon/Daemon.ts +1883 -0
- package/src/daemon/ProjectRuntime.ts +657 -0
- package/src/daemon/RestartState.ts +152 -0
- package/src/daemon/RuntimeLifecycle.ts +268 -0
- package/src/daemon/SubscriptionManager.ts +305 -0
- package/src/daemon/UnixSocketTransport.ts +318 -0
- package/src/daemon/filters/SubscriptionFilterBuilder.ts +119 -0
- package/src/daemon/index.ts +9 -0
- package/src/daemon/routing/DaemonRouter.ts +491 -0
- package/src/daemon/types.ts +150 -0
- package/src/daemon/utils/routing-log.ts +76 -0
- package/src/daemon/utils/telemetry.ts +173 -0
- package/src/event-handler/agentDeletion.ts +383 -0
- package/src/event-handler/index.ts +749 -0
- package/src/event-handler/newConversation.ts +165 -0
- package/src/event-handler/project.ts +166 -0
- package/src/event-handler/reply.ts +18 -0
- package/src/events/NDKAgentDefinition.ts +292 -0
- package/src/events/NDKAgentLesson.ts +106 -0
- package/src/events/NDKEventMetadata.ts +34 -0
- package/src/events/NDKMCPTool.ts +60 -0
- package/src/events/NDKProjectStatus.ts +384 -0
- package/src/events/index.ts +4 -0
- package/src/index.ts +126 -0
- package/src/lib/agent-home.ts +334 -0
- package/src/lib/error-formatter.ts +200 -0
- package/src/lib/fs/filesystem.ts +128 -0
- package/src/lib/fs/index.ts +1 -0
- package/src/lib/json-parser.ts +30 -0
- package/src/lib/string.ts +15 -0
- package/src/lib/time.ts +74 -0
- package/src/llm/ChunkHandler.ts +277 -0
- package/src/llm/FinishHandler.ts +250 -0
- package/src/llm/LLMConfigEditor.ts +154 -0
- package/src/llm/LLMServiceFactory.ts +230 -0
- package/src/llm/MessageProcessor.ts +90 -0
- package/src/llm/RecordingState.ts +37 -0
- package/src/llm/StreamPublisher.ts +40 -0
- package/src/llm/TracingUtils.ts +77 -0
- package/src/llm/chunk-validators.ts +57 -0
- package/src/llm/constants.ts +6 -0
- package/src/llm/index.ts +12 -0
- package/src/llm/meta/MetaModelResolver.ts +352 -0
- package/src/llm/meta/index.ts +11 -0
- package/src/llm/middleware/flight-recorder.ts +188 -0
- package/src/llm/providers/MockProvider.ts +332 -0
- package/src/llm/providers/agent/ClaudeCodeProvider.ts +343 -0
- package/src/llm/providers/agent/ClaudeCodeToolsAdapter.ts +203 -0
- package/src/llm/providers/agent/CodexAppServerProvider.ts +214 -0
- package/src/llm/providers/agent/CodexAppServerToolsAdapter.ts +91 -0
- package/src/llm/providers/agent/index.ts +10 -0
- package/src/llm/providers/base/AgentProvider.ts +107 -0
- package/src/llm/providers/base/BaseProvider.ts +114 -0
- package/src/llm/providers/base/StandardProvider.ts +38 -0
- package/src/llm/providers/base/index.ts +9 -0
- package/src/llm/providers/index.ts +106 -0
- package/src/llm/providers/key-manager.ts +238 -0
- package/src/llm/providers/ollama-models.ts +105 -0
- package/src/llm/providers/openrouter-models.ts +102 -0
- package/src/llm/providers/provider-ids.ts +18 -0
- package/src/llm/providers/registry/ProviderRegistry.ts +414 -0
- package/src/llm/providers/registry/index.ts +7 -0
- package/src/llm/providers/standard/AnthropicProvider.ts +71 -0
- package/src/llm/providers/standard/OllamaProvider.ts +59 -0
- package/src/llm/providers/standard/OpenAIProvider.ts +44 -0
- package/src/llm/providers/standard/OpenRouterProvider.ts +103 -0
- package/src/llm/providers/standard/index.ts +10 -0
- package/src/llm/providers/types.ts +194 -0
- package/src/llm/providers/usage-metadata.ts +78 -0
- package/src/llm/service.ts +713 -0
- package/src/llm/types.ts +167 -0
- package/src/llm/utils/ConfigurationManager.ts +650 -0
- package/src/llm/utils/ConfigurationTester.ts +229 -0
- package/src/llm/utils/ModelSelector.ts +212 -0
- package/src/llm/utils/ProviderConfigUI.ts +177 -0
- package/src/llm/utils/claudeCodePromptCompiler.ts +141 -0
- package/src/llm/utils/codex-models.ts +53 -0
- package/src/llm/utils/context-window-cache.ts +30 -0
- package/src/llm/utils/models-dev-cache.ts +267 -0
- package/src/llm/utils/provider-setup.ts +50 -0
- package/src/llm/utils/tool-errors.ts +78 -0
- package/src/llm/utils/usage.ts +74 -0
- package/src/logging/EventRoutingLogger.ts +205 -0
- package/src/nostr/AgentEventDecoder.ts +357 -0
- package/src/nostr/AgentEventEncoder.ts +677 -0
- package/src/nostr/AgentProfilePublisher.ts +657 -0
- package/src/nostr/AgentPublisher.ts +437 -0
- package/src/nostr/BlossomService.ts +226 -0
- package/src/nostr/InterventionPublisher.ts +132 -0
- package/src/nostr/TagExtractor.ts +228 -0
- package/src/nostr/collectEvents.ts +83 -0
- package/src/nostr/constants.ts +38 -0
- package/src/nostr/encryption.ts +26 -0
- package/src/nostr/index.ts +31 -0
- package/src/nostr/keys.ts +17 -0
- package/src/nostr/kinds.ts +37 -0
- package/src/nostr/ndkClient.ts +72 -0
- package/src/nostr/relays.ts +43 -0
- package/src/nostr/trace-context.ts +39 -0
- package/src/nostr/types.ts +227 -0
- package/src/nostr/utils.ts +84 -0
- package/src/prompts/core/FragmentRegistry.ts +30 -0
- package/src/prompts/core/PromptBuilder.ts +98 -0
- package/src/prompts/core/index.ts +3 -0
- package/src/prompts/core/types.ts +13 -0
- package/src/prompts/fragments/00-global-system-prompt.ts +44 -0
- package/src/prompts/fragments/01-agent-identity.ts +69 -0
- package/src/prompts/fragments/02-agent-home-directory.ts +114 -0
- package/src/prompts/fragments/03-system-reminders-explanation.ts +14 -0
- package/src/prompts/fragments/04-relay-configuration.ts +38 -0
- package/src/prompts/fragments/05-delegation-chain.ts +45 -0
- package/src/prompts/fragments/06-agent-todos.ts +74 -0
- package/src/prompts/fragments/06-todo-usage-guidance.ts +34 -0
- package/src/prompts/fragments/07-meta-project-context.ts +234 -0
- package/src/prompts/fragments/08-active-conversations.ts +382 -0
- package/src/prompts/fragments/09-recent-conversations.ts +153 -0
- package/src/prompts/fragments/10-referenced-article.ts +21 -0
- package/src/prompts/fragments/11-nudges.ts +134 -0
- package/src/prompts/fragments/12-skills.ts +127 -0
- package/src/prompts/fragments/13-available-nudges.ts +122 -0
- package/src/prompts/fragments/15-available-agents.ts +53 -0
- package/src/prompts/fragments/16-stay-in-your-lane.ts +41 -0
- package/src/prompts/fragments/17-todo-before-delegation.ts +39 -0
- package/src/prompts/fragments/20-voice-mode.ts +62 -0
- package/src/prompts/fragments/22-scheduled-tasks.ts +175 -0
- package/src/prompts/fragments/24-retrieved-lessons.ts +26 -0
- package/src/prompts/fragments/25-rag-instructions.ts +333 -0
- package/src/prompts/fragments/26-mcp-resources.ts +237 -0
- package/src/prompts/fragments/27-memorized-reports.ts +77 -0
- package/src/prompts/fragments/28-agent-directed-monitoring.ts +32 -0
- package/src/prompts/fragments/29-rag-collections.ts +50 -0
- package/src/prompts/fragments/30-worktree-context.ts +98 -0
- package/src/prompts/fragments/31-agents-md-guidance.ts +96 -0
- package/src/prompts/fragments/32-process-metrics.ts +72 -0
- package/src/prompts/fragments/debug-mode.ts +48 -0
- package/src/prompts/fragments/delegation-completion.ts +44 -0
- package/src/prompts/fragments/index.ts +91 -0
- package/src/prompts/index.ts +21 -0
- package/src/prompts/utils/systemPromptBuilder.ts +777 -0
- package/src/scripts/migrate-prefix-index.ts +157 -0
- package/src/services/AgentDefinitionMonitor.ts +701 -0
- package/src/services/ConfigService.ts +723 -0
- package/src/services/CooldownRegistry.ts +199 -0
- package/src/services/LLMOperationsRegistry.ts +424 -0
- package/src/services/OwnerAgentListService.ts +354 -0
- package/src/services/PubkeyService.ts +308 -0
- package/src/services/agents/AgentMetadataStore.ts +72 -0
- package/src/services/agents/AgentResolution.ts +59 -0
- package/src/services/agents/EscalationService.ts +281 -0
- package/src/services/agents/NDKAgentDiscovery.ts +95 -0
- package/src/services/agents/index.ts +7 -0
- package/src/services/agents-md/AgentsMdService.ts +184 -0
- package/src/services/agents-md/SystemReminderInjector.ts +238 -0
- package/src/services/agents-md/index.ts +11 -0
- package/src/services/apns/APNsClient.ts +203 -0
- package/src/services/apns/APNsService.ts +358 -0
- package/src/services/apns/index.ts +11 -0
- package/src/services/apns/types.ts +80 -0
- package/src/services/compression/CompressionService.ts +445 -0
- package/src/services/compression/compression-schema.ts +28 -0
- package/src/services/compression/compression-types.ts +74 -0
- package/src/services/compression/compression-utils.ts +587 -0
- package/src/services/config/types.ts +394 -0
- package/src/services/dispatch/AgentDispatchService.ts +937 -0
- package/src/services/dispatch/AgentRouter.ts +181 -0
- package/src/services/dispatch/DelegationCompletionHandler.ts +232 -0
- package/src/services/embedding/EmbeddingProvider.ts +188 -0
- package/src/services/embedding/index.ts +5 -0
- package/src/services/event-context/EventContextService.ts +108 -0
- package/src/services/event-context/index.ts +2 -0
- package/src/services/heuristics/ContextBuilder.ts +106 -0
- package/src/services/heuristics/HeuristicEngine.ts +200 -0
- package/src/services/heuristics/formatters.ts +58 -0
- package/src/services/heuristics/index.ts +12 -0
- package/src/services/heuristics/rules/index.ts +25 -0
- package/src/services/heuristics/rules/todoBeforeDelegation.ts +69 -0
- package/src/services/heuristics/rules/todoReminderOnToolUse.ts +63 -0
- package/src/services/heuristics/types.ts +144 -0
- package/src/services/image/ImageGenerationService.ts +389 -0
- package/src/services/image/index.ts +12 -0
- package/src/services/intervention/InterventionService.ts +1352 -0
- package/src/services/intervention/index.ts +7 -0
- package/src/services/mcp/MCPManager.ts +683 -0
- package/src/services/mcp/McpNotificationDelivery.ts +139 -0
- package/src/services/mcp/McpSubscriptionService.ts +653 -0
- package/src/services/mcp/mcpInstaller.ts +130 -0
- package/src/services/nip46/Nip46SigningLog.ts +81 -0
- package/src/services/nip46/Nip46SigningService.ts +467 -0
- package/src/services/nip46/index.ts +4 -0
- package/src/services/nudge/NudgeService.ts +224 -0
- package/src/services/nudge/NudgeWhitelistService.ts +382 -0
- package/src/services/nudge/index.ts +5 -0
- package/src/services/nudge/types.ts +83 -0
- package/src/services/projects/ProjectContext.ts +672 -0
- package/src/services/projects/ProjectContextStore.ts +102 -0
- package/src/services/projects/index.ts +6 -0
- package/src/services/prompt-compiler/index.ts +15 -0
- package/src/services/prompt-compiler/prompt-compiler-service.ts +1143 -0
- package/src/services/pubkey-gate/PubkeyGateService.ts +93 -0
- package/src/services/pubkey-gate/index.ts +1 -0
- package/src/services/rag/EmbeddingProviderFactory.ts +292 -0
- package/src/services/rag/LanceDBMaintenanceService.ts +211 -0
- package/src/services/rag/RAGDatabaseService.ts +173 -0
- package/src/services/rag/RAGOperations.ts +682 -0
- package/src/services/rag/RAGService.ts +240 -0
- package/src/services/rag/RagSubscriptionService.ts +618 -0
- package/src/services/rag/rag-utils.ts +174 -0
- package/src/services/ral/PendingDelegationsRegistry.ts +168 -0
- package/src/services/ral/RALRegistry.ts +2782 -0
- package/src/services/ral/index.ts +4 -0
- package/src/services/ral/types.ts +292 -0
- package/src/services/reports/LocalReportStore.ts +380 -0
- package/src/services/reports/ReportEmbeddingService.ts +430 -0
- package/src/services/reports/ReportService.ts +440 -0
- package/src/services/reports/articleUtils.ts +52 -0
- package/src/services/reports/index.ts +7 -0
- package/src/services/scheduling/SchedulerService.ts +1057 -0
- package/src/services/scheduling/errors.ts +14 -0
- package/src/services/scheduling/index.ts +7 -0
- package/src/services/scheduling/utils.ts +77 -0
- package/src/services/search/SearchProviderRegistry.ts +78 -0
- package/src/services/search/UnifiedSearchService.ts +218 -0
- package/src/services/search/index.ts +47 -0
- package/src/services/search/projectFilter.ts +22 -0
- package/src/services/search/providers/ConversationSearchProvider.ts +48 -0
- package/src/services/search/providers/LessonSearchProvider.ts +75 -0
- package/src/services/search/providers/ReportSearchProvider.ts +49 -0
- package/src/services/search/types.ts +144 -0
- package/src/services/skill/SkillService.ts +482 -0
- package/src/services/skill/index.ts +2 -0
- package/src/services/skill/types.ts +70 -0
- package/src/services/status/OperationsStatusService.ts +276 -0
- package/src/services/status/ProjectStatusService.ts +522 -0
- package/src/services/status/index.ts +11 -0
- package/src/services/storage/PrefixKVStore.ts +242 -0
- package/src/services/storage/index.ts +1 -0
- package/src/services/system-reminder/SystemReminderUtils.ts +96 -0
- package/src/services/system-reminder/index.ts +7 -0
- package/src/services/trust-pubkeys/TrustPubkeyService.ts +325 -0
- package/src/services/trust-pubkeys/index.ts +2 -0
- package/src/telemetry/ConversationSpanManager.ts +111 -0
- package/src/telemetry/EventLoopMonitor.ts +206 -0
- package/src/telemetry/LLMSpanRegistry.ts +20 -0
- package/src/telemetry/NostrSpanProcessor.ts +89 -0
- package/src/telemetry/ToolCallSpanProcessor.ts +66 -0
- package/src/telemetry/diagnostics.ts +27 -0
- package/src/telemetry/setup.ts +120 -0
- package/src/tools/implementations/agents_discover.ts +121 -0
- package/src/tools/implementations/agents_hire.ts +127 -0
- package/src/tools/implementations/agents_list.ts +96 -0
- package/src/tools/implementations/agents_publish.ts +611 -0
- package/src/tools/implementations/agents_read.ts +173 -0
- package/src/tools/implementations/agents_write.ts +200 -0
- package/src/tools/implementations/ask.ts +411 -0
- package/src/tools/implementations/change_model.ts +141 -0
- package/src/tools/implementations/conversation_get.ts +661 -0
- package/src/tools/implementations/conversation_list.ts +377 -0
- package/src/tools/implementations/conversation_search.ts +370 -0
- package/src/tools/implementations/delegate.ts +327 -0
- package/src/tools/implementations/delegate_crossproject.ts +209 -0
- package/src/tools/implementations/delegate_followup.ts +300 -0
- package/src/tools/implementations/fs_edit.ts +162 -0
- package/src/tools/implementations/fs_glob.ts +182 -0
- package/src/tools/implementations/fs_grep.ts +513 -0
- package/src/tools/implementations/fs_read.ts +332 -0
- package/src/tools/implementations/fs_write.ts +113 -0
- package/src/tools/implementations/generate_image.ts +259 -0
- package/src/tools/implementations/home_fs.ts +515 -0
- package/src/tools/implementations/kill.ts +651 -0
- package/src/tools/implementations/learn.ts +166 -0
- package/src/tools/implementations/lesson-formatter.ts +38 -0
- package/src/tools/implementations/lesson_delete.ts +164 -0
- package/src/tools/implementations/lesson_get.ts +105 -0
- package/src/tools/implementations/lessons_list.ts +153 -0
- package/src/tools/implementations/mcp_resource_read.ts +161 -0
- package/src/tools/implementations/mcp_subscribe.ts +158 -0
- package/src/tools/implementations/mcp_subscription_stop.ts +85 -0
- package/src/tools/implementations/nostr_fetch.ts +149 -0
- package/src/tools/implementations/nostr_publish_as_user.ts +353 -0
- package/src/tools/implementations/project_list.ts +146 -0
- package/src/tools/implementations/rag_add_documents.ts +573 -0
- package/src/tools/implementations/rag_create_collection.ts +65 -0
- package/src/tools/implementations/rag_delete_collection.ts +68 -0
- package/src/tools/implementations/rag_list_collections.ts +77 -0
- package/src/tools/implementations/rag_query.ts +107 -0
- package/src/tools/implementations/rag_subscription_create.ts +105 -0
- package/src/tools/implementations/rag_subscription_delete.ts +80 -0
- package/src/tools/implementations/rag_subscription_get.ts +123 -0
- package/src/tools/implementations/rag_subscription_list.ts +128 -0
- package/src/tools/implementations/report_delete.ts +79 -0
- package/src/tools/implementations/report_read.ts +160 -0
- package/src/tools/implementations/report_write.ts +278 -0
- package/src/tools/implementations/reports_list.ts +77 -0
- package/src/tools/implementations/schedule_task.ts +104 -0
- package/src/tools/implementations/schedule_task_cancel.ts +62 -0
- package/src/tools/implementations/schedule_task_once.ts +128 -0
- package/src/tools/implementations/schedule_tasks_list.ts +79 -0
- package/src/tools/implementations/search.ts +160 -0
- package/src/tools/implementations/shell.ts +553 -0
- package/src/tools/implementations/todo.ts +260 -0
- package/src/tools/implementations/upload_blob.ts +381 -0
- package/src/tools/implementations/web_fetch.ts +153 -0
- package/src/tools/implementations/web_search.ts +250 -0
- package/src/tools/registry.ts +670 -0
- package/src/tools/types.ts +177 -0
- package/src/tools/utils.ts +256 -0
- package/src/types/event-ids.ts +320 -0
- package/src/types/index.ts +46 -0
- package/src/utils/agentFetcher.ts +107 -0
- package/src/utils/cli-error.ts +29 -0
- package/src/utils/conversation-id.ts +27 -0
- package/src/utils/conversation-utils.ts +1 -0
- package/src/utils/delegation-chain.ts +357 -0
- package/src/utils/error-handler.ts +42 -0
- package/src/utils/git/gitignore.ts +69 -0
- package/src/utils/git/index.ts +2 -0
- package/src/utils/git/initializeGitRepo.ts +204 -0
- package/src/utils/git/worktree.ts +260 -0
- package/src/utils/lessonFormatter.ts +70 -0
- package/src/utils/lessonTrust.ts +24 -0
- package/src/utils/lockfile.ts +123 -0
- package/src/utils/logger.ts +149 -0
- package/src/utils/nostr-entity-parser.ts +365 -0
- package/src/utils/process.ts +49 -0
- package/src/wrapper.ts +262 -0
- 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,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
|
+
}
|