@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,260 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
import { exec } from "node:child_process";
|
|
5
|
+
import { logger } from "@/utils/logger";
|
|
6
|
+
import { ensureWorktreesGitignore } from "./gitignore";
|
|
7
|
+
|
|
8
|
+
const execAsync = promisify(exec);
|
|
9
|
+
|
|
10
|
+
/** Directory name for worktrees (relative to project root) */
|
|
11
|
+
export const WORKTREES_DIR = ".worktrees";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Sanitize a branch name for use as a directory name.
|
|
15
|
+
* Replaces forward slashes with underscores to avoid nested directories.
|
|
16
|
+
* @example sanitizeBranchName("feature/whatever") => "feature_whatever"
|
|
17
|
+
*/
|
|
18
|
+
export function sanitizeBranchName(branch: string): string {
|
|
19
|
+
return branch.replace(/\//g, "_");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Metadata for a git worktree
|
|
24
|
+
*/
|
|
25
|
+
export interface WorktreeMetadata {
|
|
26
|
+
path: string;
|
|
27
|
+
branch: string;
|
|
28
|
+
createdBy: string; // Agent pubkey
|
|
29
|
+
conversationId: string;
|
|
30
|
+
parentBranch: string;
|
|
31
|
+
createdAt: number;
|
|
32
|
+
mergedAt?: number;
|
|
33
|
+
deletedAt?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ============================================================================
|
|
37
|
+
// Core Worktree Operations
|
|
38
|
+
// ============================================================================
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* List all git worktrees for a project.
|
|
42
|
+
* The main repository is always included as the first entry.
|
|
43
|
+
* Additional worktrees are located in .worktrees/ subdirectory.
|
|
44
|
+
*
|
|
45
|
+
* @param projectPath - Root project directory (normal git repo)
|
|
46
|
+
* @returns Array of worktrees with branch name and path
|
|
47
|
+
*/
|
|
48
|
+
export async function listWorktrees(projectPath: string): Promise<Array<{ branch: string; path: string }>> {
|
|
49
|
+
try {
|
|
50
|
+
const { stdout } = await execAsync("git worktree list --porcelain", { cwd: projectPath });
|
|
51
|
+
|
|
52
|
+
const worktrees: Array<{ branch: string; path: string }> = [];
|
|
53
|
+
const lines = stdout.trim().split("\n");
|
|
54
|
+
|
|
55
|
+
let currentWorktree: { path?: string; branch?: string } = {};
|
|
56
|
+
|
|
57
|
+
for (const line of lines) {
|
|
58
|
+
if (line.startsWith("worktree ")) {
|
|
59
|
+
currentWorktree.path = line.substring(9);
|
|
60
|
+
} else if (line.startsWith("branch ")) {
|
|
61
|
+
currentWorktree.branch = line.substring(7).replace("refs/heads/", "");
|
|
62
|
+
} else if (line === "") {
|
|
63
|
+
// Empty line marks end of worktree entry
|
|
64
|
+
if (currentWorktree.path && currentWorktree.branch) {
|
|
65
|
+
worktrees.push({
|
|
66
|
+
path: currentWorktree.path,
|
|
67
|
+
branch: currentWorktree.branch,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
currentWorktree = {};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Handle last entry if no trailing newline
|
|
75
|
+
if (currentWorktree.path && currentWorktree.branch) {
|
|
76
|
+
worktrees.push({
|
|
77
|
+
path: currentWorktree.path,
|
|
78
|
+
branch: currentWorktree.branch,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return worktrees;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
logger.error("Failed to list worktrees", { projectPath, error });
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Create a new git worktree in the .worktrees/ directory.
|
|
91
|
+
* Branch names are sanitized (slashes replaced with underscores) for directory names.
|
|
92
|
+
* Also ensures .worktrees is added to .gitignore.
|
|
93
|
+
*
|
|
94
|
+
* @param projectPath - Root project directory (normal git repo)
|
|
95
|
+
* @param branchName - Name for the new branch (can contain slashes)
|
|
96
|
+
* @param baseBranch - Branch to create from (typically current branch)
|
|
97
|
+
* @returns Path to the new worktree
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* // Creates worktree at ~/tenex/project/.worktrees/feature_auth/
|
|
101
|
+
* await createWorktree("~/tenex/project", "feature/auth", "main");
|
|
102
|
+
*/
|
|
103
|
+
export async function createWorktree(
|
|
104
|
+
projectPath: string,
|
|
105
|
+
branchName: string,
|
|
106
|
+
baseBranch: string
|
|
107
|
+
): Promise<string> {
|
|
108
|
+
try {
|
|
109
|
+
// Ensure .worktrees is in .gitignore before creating any worktrees
|
|
110
|
+
await ensureWorktreesGitignore(projectPath);
|
|
111
|
+
|
|
112
|
+
// Create .worktrees directory if it doesn't exist
|
|
113
|
+
const worktreesDir = path.join(projectPath, WORKTREES_DIR);
|
|
114
|
+
await fs.mkdir(worktreesDir, { recursive: true });
|
|
115
|
+
|
|
116
|
+
// Sanitize branch name for directory (feature/whatever -> feature_whatever)
|
|
117
|
+
const sanitizedName = sanitizeBranchName(branchName);
|
|
118
|
+
const worktreePath = path.join(worktreesDir, sanitizedName);
|
|
119
|
+
|
|
120
|
+
// Check if worktree already exists
|
|
121
|
+
const existingWorktrees = await listWorktrees(projectPath);
|
|
122
|
+
if (existingWorktrees.some((wt) => wt.branch === branchName)) {
|
|
123
|
+
logger.info("Worktree already exists", { branchName, path: worktreePath });
|
|
124
|
+
return worktreePath;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Check if path exists on filesystem
|
|
128
|
+
try {
|
|
129
|
+
await fs.access(worktreePath);
|
|
130
|
+
// Path exists but not in worktree list - this is an error state
|
|
131
|
+
throw new Error(
|
|
132
|
+
`Directory "${worktreePath}" exists but is not a registered git worktree. ` +
|
|
133
|
+
"Remove it manually or use a different branch name."
|
|
134
|
+
);
|
|
135
|
+
} catch (err: unknown) {
|
|
136
|
+
if (err instanceof Error && "code" in err && err.code !== "ENOENT") throw err;
|
|
137
|
+
// Path doesn't exist - safe to create
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Create worktree from repository
|
|
141
|
+
// Wrap in try-catch to handle race conditions where another process creates the worktree
|
|
142
|
+
try {
|
|
143
|
+
await execAsync(
|
|
144
|
+
`git worktree add -b ${JSON.stringify(branchName)} ${JSON.stringify(worktreePath)} ${JSON.stringify(baseBranch)}`,
|
|
145
|
+
{ cwd: projectPath }
|
|
146
|
+
);
|
|
147
|
+
} catch (createError: unknown) {
|
|
148
|
+
// Check if worktree was created by another process (race condition)
|
|
149
|
+
const refreshedWorktrees = await listWorktrees(projectPath);
|
|
150
|
+
if (refreshedWorktrees.some((wt) => wt.branch === branchName)) {
|
|
151
|
+
logger.info("Worktree was created by another process", { branchName, path: worktreePath });
|
|
152
|
+
return worktreePath;
|
|
153
|
+
}
|
|
154
|
+
// Re-throw if it's a different error
|
|
155
|
+
throw createError;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
logger.info("Created worktree", {
|
|
159
|
+
branchName,
|
|
160
|
+
sanitizedName,
|
|
161
|
+
path: worktreePath,
|
|
162
|
+
baseBranch
|
|
163
|
+
});
|
|
164
|
+
return worktreePath;
|
|
165
|
+
} catch (error) {
|
|
166
|
+
logger.error("Failed to create worktree", { projectPath, branchName, baseBranch, error });
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ============================================================================
|
|
172
|
+
// Worktree Metadata Management
|
|
173
|
+
// ============================================================================
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Get the path to the worktree metadata file for a project
|
|
177
|
+
* @param projectPath - The base project directory
|
|
178
|
+
* @param projectsConfigPath - The base path for project configs (e.g., ~/.tenex/projects)
|
|
179
|
+
* @returns Path to the worktree metadata JSON file
|
|
180
|
+
*/
|
|
181
|
+
export async function getWorktreeMetadataPath(projectPath: string, projectsConfigPath: string): Promise<string> {
|
|
182
|
+
// Extract dTag from project path (last segment of path)
|
|
183
|
+
const dTag = path.basename(projectPath);
|
|
184
|
+
const metadataDir = path.join(projectsConfigPath, dTag);
|
|
185
|
+
|
|
186
|
+
// Ensure the metadata directory exists
|
|
187
|
+
await fs.mkdir(metadataDir, { recursive: true });
|
|
188
|
+
|
|
189
|
+
return path.join(metadataDir, "worktrees.json");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Load worktree metadata for a project
|
|
194
|
+
* @param projectPath - The base project directory
|
|
195
|
+
* @param projectsConfigPath - The base path for project configs (e.g., ~/.tenex/projects)
|
|
196
|
+
* @returns Map of branch names to metadata
|
|
197
|
+
*/
|
|
198
|
+
export async function loadWorktreeMetadata(
|
|
199
|
+
projectPath: string,
|
|
200
|
+
projectsConfigPath: string
|
|
201
|
+
): Promise<Record<string, WorktreeMetadata>> {
|
|
202
|
+
try {
|
|
203
|
+
const metadataPath = await getWorktreeMetadataPath(projectPath, projectsConfigPath);
|
|
204
|
+
const content = await fs.readFile(metadataPath, "utf-8");
|
|
205
|
+
return JSON.parse(content);
|
|
206
|
+
} catch (error) {
|
|
207
|
+
// File doesn't exist or invalid JSON - return empty object
|
|
208
|
+
if (error && typeof error === "object" && "code" in error && error.code !== "ENOENT") {
|
|
209
|
+
logger.warn("Failed to load worktree metadata", {
|
|
210
|
+
projectPath,
|
|
211
|
+
error: error instanceof Error ? error.message : String(error)
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
return {};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Save worktree metadata for a project
|
|
220
|
+
* @param projectPath - The base project directory
|
|
221
|
+
* @param projectsConfigPath - The base path for project configs
|
|
222
|
+
* @param metadata - Map of branch names to metadata
|
|
223
|
+
*/
|
|
224
|
+
async function saveWorktreeMetadata(
|
|
225
|
+
projectPath: string,
|
|
226
|
+
projectsConfigPath: string,
|
|
227
|
+
metadata: Record<string, WorktreeMetadata>
|
|
228
|
+
): Promise<void> {
|
|
229
|
+
const metadataPath = await getWorktreeMetadataPath(projectPath, projectsConfigPath);
|
|
230
|
+
await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Track the creation of a new worktree
|
|
235
|
+
* @param projectPath - The base project directory
|
|
236
|
+
* @param projectsConfigPath - The base path for project configs
|
|
237
|
+
* @param metadata - The worktree metadata
|
|
238
|
+
*/
|
|
239
|
+
export async function trackWorktreeCreation(
|
|
240
|
+
projectPath: string,
|
|
241
|
+
projectsConfigPath: string,
|
|
242
|
+
metadata: Omit<WorktreeMetadata, "createdAt">
|
|
243
|
+
): Promise<void> {
|
|
244
|
+
const allMetadata = await loadWorktreeMetadata(projectPath, projectsConfigPath);
|
|
245
|
+
|
|
246
|
+
// Add timestamp and save
|
|
247
|
+
allMetadata[metadata.branch] = {
|
|
248
|
+
...metadata,
|
|
249
|
+
createdAt: Date.now()
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
await saveWorktreeMetadata(projectPath, projectsConfigPath, allMetadata);
|
|
253
|
+
|
|
254
|
+
logger.info("Tracked worktree creation", {
|
|
255
|
+
branch: metadata.branch,
|
|
256
|
+
createdBy: metadata.createdBy.substring(0, 8),
|
|
257
|
+
conversationId: metadata.conversationId.substring(0, 8)
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { NDKAgentLesson } from "@/events/NDKAgentLesson";
|
|
2
|
+
import { logger } from "@/utils/logger";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Format agent lessons into a concise string without using LLM
|
|
6
|
+
* This is a simple concatenation with minimal formatting
|
|
7
|
+
*/
|
|
8
|
+
export function formatLessonsForAgent(lessons: NDKAgentLesson[]): string {
|
|
9
|
+
if (lessons.length === 0) {
|
|
10
|
+
return "";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
logger.debug("Formatting lessons for agent", {
|
|
14
|
+
lessonsCount: lessons.length,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// Format each lesson concisely - ALL OF THEM!
|
|
18
|
+
const formattedLessons = lessons
|
|
19
|
+
.map((lesson, index) => {
|
|
20
|
+
const title = lesson.title || "Untitled Lesson";
|
|
21
|
+
const content = lesson.lesson;
|
|
22
|
+
const category = lesson.category;
|
|
23
|
+
const hashtags = lesson.hashtags;
|
|
24
|
+
const hasDetailed = !!lesson.detailed;
|
|
25
|
+
// Get 12-char prefix for convenient lookup (lesson_get accepts prefixes)
|
|
26
|
+
const idPrefix = lesson.id ? lesson.id.substring(0, 12) : null;
|
|
27
|
+
|
|
28
|
+
// Build metadata line
|
|
29
|
+
let metadata = "";
|
|
30
|
+
if (category) metadata += ` [${category}]`;
|
|
31
|
+
if (hasDetailed) metadata += " [detailed available]";
|
|
32
|
+
if (hashtags && hashtags.length > 0) metadata += ` #${hashtags.join(" #")}`;
|
|
33
|
+
|
|
34
|
+
// Create a concise format for each lesson
|
|
35
|
+
// Show ID prefix for lesson_get if detailed version available
|
|
36
|
+
const detailedHint = hasDetailed && idPrefix
|
|
37
|
+
? `\n↳ Use lesson_get("${idPrefix}") for detailed version`
|
|
38
|
+
: "";
|
|
39
|
+
return `#${index + 1}: ${title} ${metadata}\n${content}${detailedHint}`;
|
|
40
|
+
})
|
|
41
|
+
.join("\n\n");
|
|
42
|
+
|
|
43
|
+
// Add header for context
|
|
44
|
+
const header = `## Lessons Learned (${lessons.length} total)\n\n`;
|
|
45
|
+
|
|
46
|
+
return header + formattedLessons;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The standard lesson_learn tool reminder to encourage agents to continue learning.
|
|
51
|
+
*/
|
|
52
|
+
export const LESSON_LEARN_REMINDER =
|
|
53
|
+
"Remember to use the `lesson_learn` tool when you discover new insights or patterns.";
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Format lessons for inclusion in a system prompt.
|
|
57
|
+
* Includes the formatted lessons + the lesson_learn tool reminder.
|
|
58
|
+
* This is the single source of truth for lesson prompt formatting.
|
|
59
|
+
*
|
|
60
|
+
* @param lessons The agent's lessons
|
|
61
|
+
* @returns Formatted lessons with reminder, or empty string if no lessons
|
|
62
|
+
*/
|
|
63
|
+
export function formatLessonsWithReminder(lessons: NDKAgentLesson[]): string {
|
|
64
|
+
if (lessons.length === 0) {
|
|
65
|
+
return "";
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const formattedLessons = formatLessonsForAgent(lessons);
|
|
69
|
+
return `${formattedLessons}\n\n${LESSON_LEARN_REMINDER}`;
|
|
70
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { NDKAgentLesson } from "@/events/NDKAgentLesson";
|
|
2
|
+
import type { Hexpubkey } from "@nostr-dev-kit/ndk";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Assess whether to trust a pubkey publishing a lesson event.
|
|
6
|
+
*
|
|
7
|
+
* This function determines if a lesson should be accepted and stored based on:
|
|
8
|
+
* - The pubkey that published the lesson
|
|
9
|
+
* - The lesson content and metadata
|
|
10
|
+
* - The agent definition the lesson is for
|
|
11
|
+
*
|
|
12
|
+
* @param _lesson The lesson event to assess (currently unused)
|
|
13
|
+
* @param _publisherPubkey The pubkey that published the lesson (currently unused)
|
|
14
|
+
* @returns true if the lesson should be trusted and stored, false otherwise
|
|
15
|
+
*/
|
|
16
|
+
export function shouldTrustLesson(_lesson: NDKAgentLesson, _publisherPubkey: Hexpubkey): boolean {
|
|
17
|
+
// For now, trust all lessons
|
|
18
|
+
// Future implementations may add:
|
|
19
|
+
// - Whitelist/blacklist checks
|
|
20
|
+
// - Reputation scoring
|
|
21
|
+
// - Cryptographic verification
|
|
22
|
+
// - Content validation
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import { logger } from "./logger";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Lockfile information
|
|
7
|
+
*/
|
|
8
|
+
interface LockInfo {
|
|
9
|
+
pid: number;
|
|
10
|
+
hostname: string;
|
|
11
|
+
startedAt: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Lockfile manager for preventing multiple daemon instances
|
|
16
|
+
*/
|
|
17
|
+
export class Lockfile {
|
|
18
|
+
private lockfilePath: string;
|
|
19
|
+
private currentPid: number;
|
|
20
|
+
|
|
21
|
+
constructor(lockfilePath: string) {
|
|
22
|
+
this.lockfilePath = lockfilePath;
|
|
23
|
+
this.currentPid = process.pid;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Acquire the lock. Throws if lock cannot be acquired.
|
|
28
|
+
*/
|
|
29
|
+
async acquire(): Promise<void> {
|
|
30
|
+
// Check if lockfile exists using fs.stat
|
|
31
|
+
let lockfileExists = false;
|
|
32
|
+
try {
|
|
33
|
+
await fs.stat(this.lockfilePath);
|
|
34
|
+
lockfileExists = true;
|
|
35
|
+
} catch (error) {
|
|
36
|
+
const err = error as NodeJS.ErrnoException;
|
|
37
|
+
if (err.code !== "ENOENT") {
|
|
38
|
+
// Unexpected error accessing lockfile
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
// File doesn't exist - we can proceed to create it
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// If lockfile exists, check if the process is still running
|
|
45
|
+
if (lockfileExists) {
|
|
46
|
+
const content = await fs.readFile(this.lockfilePath, "utf-8");
|
|
47
|
+
const lockInfo: LockInfo = JSON.parse(content);
|
|
48
|
+
|
|
49
|
+
if (this.isProcessRunning(lockInfo.pid)) {
|
|
50
|
+
// Process is running - cannot acquire lock
|
|
51
|
+
throw new Error(
|
|
52
|
+
`Daemon is already running (PID: ${lockInfo.pid}, started at: ${new Date(lockInfo.startedAt).toISOString()})`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Stale lockfile - previous process crashed or was killed
|
|
57
|
+
logger.warn("Found stale lockfile, removing it", {
|
|
58
|
+
stalePid: lockInfo.pid,
|
|
59
|
+
startedAt: new Date(lockInfo.startedAt).toISOString(),
|
|
60
|
+
});
|
|
61
|
+
await this.release();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Create new lockfile
|
|
65
|
+
const lockInfo: LockInfo = {
|
|
66
|
+
pid: this.currentPid,
|
|
67
|
+
hostname: os.hostname(),
|
|
68
|
+
startedAt: Date.now(),
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
await fs.writeFile(this.lockfilePath, JSON.stringify(lockInfo, null, 2), "utf-8");
|
|
72
|
+
|
|
73
|
+
logger.debug("Lockfile acquired", {
|
|
74
|
+
lockfilePath: this.lockfilePath,
|
|
75
|
+
pid: this.currentPid,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Release the lock
|
|
81
|
+
*/
|
|
82
|
+
async release(): Promise<void> {
|
|
83
|
+
try {
|
|
84
|
+
await fs.unlink(this.lockfilePath);
|
|
85
|
+
logger.debug("Lockfile released", { lockfilePath: this.lockfilePath });
|
|
86
|
+
} catch (error) {
|
|
87
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
88
|
+
logger.warn("Failed to remove lockfile", {
|
|
89
|
+
lockfilePath: this.lockfilePath,
|
|
90
|
+
error: error instanceof Error ? error.message : String(error),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Check if a process is running by PID
|
|
98
|
+
*/
|
|
99
|
+
private isProcessRunning(pid: number): boolean {
|
|
100
|
+
try {
|
|
101
|
+
// Sending signal 0 doesn't actually send a signal,
|
|
102
|
+
// it just checks if the process exists
|
|
103
|
+
process.kill(pid, 0);
|
|
104
|
+
return true;
|
|
105
|
+
} catch (error) {
|
|
106
|
+
const err = error as NodeJS.ErrnoException;
|
|
107
|
+
|
|
108
|
+
// ESRCH means process doesn't exist
|
|
109
|
+
if (err.code === "ESRCH") {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// EPERM means process exists but we lack permission to signal it
|
|
114
|
+
if (err.code === "EPERM") {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Unexpected error - re-throw
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
|
|
5
|
+
const levels: Record<string, number> = {
|
|
6
|
+
silent: 0,
|
|
7
|
+
error: 1,
|
|
8
|
+
warn: 2,
|
|
9
|
+
info: 3,
|
|
10
|
+
debug: 4,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// Helper to get current log level dynamically
|
|
14
|
+
function getCurrentLevel(): number {
|
|
15
|
+
const LOG_LEVEL = process.env.LOG_LEVEL || "info";
|
|
16
|
+
return levels[LOG_LEVEL] || levels.info;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Helper to check if debug is enabled
|
|
20
|
+
function isDebugEnabled(): boolean {
|
|
21
|
+
return process.env.DEBUG === "true";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Color configuration for consistent output
|
|
25
|
+
const colors = {
|
|
26
|
+
error: chalk.red,
|
|
27
|
+
warn: chalk.yellow,
|
|
28
|
+
info: chalk.blue,
|
|
29
|
+
success: chalk.green,
|
|
30
|
+
debug: chalk.gray,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const emojis = {
|
|
34
|
+
error: "❌",
|
|
35
|
+
warn: "⚠️",
|
|
36
|
+
info: "ℹ️",
|
|
37
|
+
success: "✅",
|
|
38
|
+
debug: "🔍",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// File logging state
|
|
42
|
+
let logFilePath: string | null = null;
|
|
43
|
+
|
|
44
|
+
// Helper to format timestamp for file output
|
|
45
|
+
function formatTimestamp(): string {
|
|
46
|
+
const now = new Date();
|
|
47
|
+
return now.toISOString().replace("T", " ").split(".")[0];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Helper to write to log file
|
|
51
|
+
function writeToFile(level: string, message: string, args: unknown[]): void {
|
|
52
|
+
if (!logFilePath) return;
|
|
53
|
+
|
|
54
|
+
const timestamp = formatTimestamp();
|
|
55
|
+
const argsStr =
|
|
56
|
+
args.length > 0
|
|
57
|
+
? ` ${args
|
|
58
|
+
.map((arg) => (typeof arg === "object" ? JSON.stringify(arg) : String(arg)))
|
|
59
|
+
.join(" ")}`
|
|
60
|
+
: "";
|
|
61
|
+
|
|
62
|
+
const logLine = `[${timestamp}] ${level.toUpperCase()}: ${message}${argsStr}\n`;
|
|
63
|
+
|
|
64
|
+
fs.appendFileSync(logFilePath, logLine);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Initialize daemon logging
|
|
68
|
+
async function initDaemonLogging(): Promise<void> {
|
|
69
|
+
// Lazy-load config to avoid circular dependency
|
|
70
|
+
const { config } = await import("@/services/ConfigService");
|
|
71
|
+
|
|
72
|
+
const tenexConfig = config.getConfig();
|
|
73
|
+
const defaultLogPath = path.join(config.getConfigPath("daemon"), "daemon.log");
|
|
74
|
+
logFilePath = tenexConfig.logging?.logFile || defaultLogPath;
|
|
75
|
+
|
|
76
|
+
// Ensure directory exists
|
|
77
|
+
const logDir = path.dirname(logFilePath);
|
|
78
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Main logger object
|
|
82
|
+
export const logger = {
|
|
83
|
+
initDaemonLogging,
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Check if a specific log level is enabled
|
|
87
|
+
* Useful for conditional expensive operations (e.g., stack traces)
|
|
88
|
+
*/
|
|
89
|
+
isLevelEnabled: (level: "error" | "warn" | "info" | "debug"): boolean => {
|
|
90
|
+
if (level === "debug") {
|
|
91
|
+
return isDebugEnabled() && getCurrentLevel() >= levels.debug;
|
|
92
|
+
}
|
|
93
|
+
return getCurrentLevel() >= levels[level];
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
error: (message: string, error?: unknown) => {
|
|
97
|
+
if (getCurrentLevel() >= levels.error) {
|
|
98
|
+
if (logFilePath) {
|
|
99
|
+
writeToFile("error", message, error ? [error] : []);
|
|
100
|
+
} else {
|
|
101
|
+
console.error(colors.error(`${emojis.error} ${message}`), error || "");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
warn: (message: string, ...args: unknown[]) => {
|
|
107
|
+
if (getCurrentLevel() >= levels.warn) {
|
|
108
|
+
if (logFilePath) {
|
|
109
|
+
writeToFile("warn", message, args);
|
|
110
|
+
} else {
|
|
111
|
+
console.warn(colors.warn(`${emojis.warn} ${message}`), ...args);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
warning: (message: string, ...args: unknown[]) => {
|
|
117
|
+
logger.warn(message, ...args);
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
info: (message: string, ...args: unknown[]) => {
|
|
121
|
+
if (getCurrentLevel() >= levels.info) {
|
|
122
|
+
if (logFilePath) {
|
|
123
|
+
writeToFile("info", message, args);
|
|
124
|
+
} else {
|
|
125
|
+
console.log(colors.info(`${emojis.info} ${message}`), ...args);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
success: (message: string, ...args: unknown[]) => {
|
|
131
|
+
if (getCurrentLevel() >= levels.info) {
|
|
132
|
+
if (logFilePath) {
|
|
133
|
+
writeToFile("success", message, args);
|
|
134
|
+
} else {
|
|
135
|
+
console.log(colors.success(`${emojis.success} ${message}`), ...args);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
debug: (message: string, ...args: unknown[]) => {
|
|
141
|
+
if (isDebugEnabled() && getCurrentLevel() >= levels.debug) {
|
|
142
|
+
if (logFilePath) {
|
|
143
|
+
writeToFile("debug", message, args);
|
|
144
|
+
} else {
|
|
145
|
+
console.log(colors.debug(`${emojis.debug} ${message}`), ...args);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
};
|