@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,365 @@
|
|
|
1
|
+
import type NDK from "@nostr-dev-kit/ndk";
|
|
2
|
+
import { type NDKEvent, NDKUser } from "@nostr-dev-kit/ndk";
|
|
3
|
+
import { prefixKVStore } from "@/services/storage";
|
|
4
|
+
import { nip19 } from "nostr-tools";
|
|
5
|
+
import type { NDKAgentLesson } from "@/events/NDKAgentLesson";
|
|
6
|
+
import type { FullEventId, ShortEventId } from "@/types/event-ids";
|
|
7
|
+
import { isFullEventId, isShortEventId } from "@/types/event-ids";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The standard prefix length used for shortened hex IDs throughout the system.
|
|
11
|
+
* Used for delegation IDs, conversation IDs, and other nostr identifiers.
|
|
12
|
+
*/
|
|
13
|
+
export const PREFIX_LENGTH = 12;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Parses various Nostr user identifier formats into a pubkey
|
|
17
|
+
* Handles: npub, nprofile, hex pubkey, with or without "nostr:" prefix
|
|
18
|
+
*
|
|
19
|
+
* @param input - The user identifier in various formats
|
|
20
|
+
* @param ndk - NDK instance for validation
|
|
21
|
+
* @returns The parsed pubkey or null if invalid
|
|
22
|
+
*/
|
|
23
|
+
export function parseNostrUser(input: string | undefined): string | null {
|
|
24
|
+
if (!input) return null;
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
// Strip nostr: prefix if present
|
|
28
|
+
let cleaned = input.trim();
|
|
29
|
+
if (cleaned.startsWith("nostr:")) {
|
|
30
|
+
cleaned = cleaned.substring(6);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Handle npub format
|
|
34
|
+
if (cleaned.startsWith("npub1")) {
|
|
35
|
+
const user = new NDKUser({ npub: cleaned });
|
|
36
|
+
return user.pubkey;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Handle nprofile format
|
|
40
|
+
if (cleaned.startsWith("nprofile1")) {
|
|
41
|
+
const user = new NDKUser({ nprofile: cleaned });
|
|
42
|
+
return user.pubkey;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Assume it's a hex pubkey - validate format
|
|
46
|
+
if (/^[0-9a-fA-F]{64}$/.test(cleaned)) {
|
|
47
|
+
return cleaned.toLowerCase();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Try to create user anyway in case it's a valid format we didn't check
|
|
51
|
+
try {
|
|
52
|
+
const user = new NDKUser({ pubkey: cleaned });
|
|
53
|
+
if (user.pubkey && /^[0-9a-fA-F]{64}$/.test(user.pubkey)) {
|
|
54
|
+
return user.pubkey;
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
// Ignore and return null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return null;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.debug("Failed to parse Nostr user identifier:", input, error);
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Parses various Nostr event identifier formats and fetches the event
|
|
69
|
+
* Handles: nevent, note, naddr, hex event id, with or without "nostr:" prefix
|
|
70
|
+
*
|
|
71
|
+
* @param input - The event identifier in various formats
|
|
72
|
+
* @param ndk - NDK instance for fetching
|
|
73
|
+
* @returns The fetched event or null if not found/invalid
|
|
74
|
+
*/
|
|
75
|
+
export async function parseNostrEvent(
|
|
76
|
+
input: string | undefined,
|
|
77
|
+
ndk: NDK
|
|
78
|
+
): Promise<NDKEvent | null> {
|
|
79
|
+
if (!input) return null;
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
// Strip nostr: prefix if present
|
|
83
|
+
let cleaned = input.trim();
|
|
84
|
+
if (cleaned.startsWith("nostr:")) {
|
|
85
|
+
cleaned = cleaned.substring(6);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Try to fetch directly - NDK handles various formats
|
|
89
|
+
if (
|
|
90
|
+
cleaned.startsWith("nevent1") ||
|
|
91
|
+
cleaned.startsWith("note1") ||
|
|
92
|
+
cleaned.startsWith("naddr1")
|
|
93
|
+
) {
|
|
94
|
+
const event = await ndk.fetchEvent(cleaned);
|
|
95
|
+
return event;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Try as hex event ID
|
|
99
|
+
if (/^[0-9a-fA-F]{64}$/.test(cleaned)) {
|
|
100
|
+
const event = await ndk.fetchEvent(cleaned);
|
|
101
|
+
return event;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Last attempt - try to fetch as-is
|
|
105
|
+
const event = await ndk.fetchEvent(cleaned);
|
|
106
|
+
return event;
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.debug("Failed to parse/fetch Nostr event:", input, error);
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Validates and normalizes a Nostr identifier, removing prefixes
|
|
115
|
+
* Returns the cleaned identifier or null if invalid
|
|
116
|
+
*/
|
|
117
|
+
export function normalizeNostrIdentifier(input: string | undefined): string | null {
|
|
118
|
+
if (!input) return null;
|
|
119
|
+
|
|
120
|
+
let cleaned = input.trim();
|
|
121
|
+
if (cleaned.startsWith("nostr:")) {
|
|
122
|
+
cleaned = cleaned.substring(6);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Basic validation - should be bech32 or hex
|
|
126
|
+
if (
|
|
127
|
+
cleaned.match(/^(npub1|nprofile1|nevent1|note1|nsec1|naddr1)[0-9a-z]+$/i) ||
|
|
128
|
+
cleaned.match(/^[0-9a-fA-F]{64}$/)
|
|
129
|
+
) {
|
|
130
|
+
return cleaned;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Checks if a string looks like a 12-char hex prefix (potential shorthand ID).
|
|
138
|
+
* Note: This is a pure format check - it doesn't do any lookup.
|
|
139
|
+
* For resolving prefixes to actual IDs, use the appropriate service.
|
|
140
|
+
*/
|
|
141
|
+
export function isHexPrefix(input: string | undefined): boolean {
|
|
142
|
+
if (!input) return false;
|
|
143
|
+
return /^[0-9a-fA-F]{12}$/.test(input.trim());
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Resolves a 12-character hex prefix to a full 64-character ID using PrefixKVStore.
|
|
148
|
+
* This enables shorthand references to event IDs and pubkeys.
|
|
149
|
+
*
|
|
150
|
+
* IMPORTANT: This function is TYPE-AGNOSTIC - it returns any matching ID without
|
|
151
|
+
* validating whether it's an event ID or pubkey. For resolving specifically to
|
|
152
|
+
* agent pubkeys, use `resolveAgentSlug` from the AgentResolution service.
|
|
153
|
+
*
|
|
154
|
+
* @param prefix - A 12-character hex string prefix
|
|
155
|
+
* @returns The full 64-character ID, or null if not found or invalid input
|
|
156
|
+
*/
|
|
157
|
+
export function resolvePrefixToId(prefix: string | undefined): string | null {
|
|
158
|
+
if (!prefix) return null;
|
|
159
|
+
|
|
160
|
+
const cleaned = prefix.trim().toLowerCase();
|
|
161
|
+
|
|
162
|
+
// Must be exactly 12 hex characters
|
|
163
|
+
if (!/^[0-9a-f]{12}$/.test(cleaned)) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Check if store is initialized (best-effort lookup)
|
|
168
|
+
if (!prefixKVStore.isInitialized()) {
|
|
169
|
+
console.debug("[resolvePrefixToId] PrefixKVStore not initialized, cannot resolve prefix");
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Wrap lookup in try/catch - LMDB can throw
|
|
174
|
+
try {
|
|
175
|
+
return prefixKVStore.lookup(cleaned);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
178
|
+
console.debug("[resolvePrefixToId] Prefix lookup failed:", cleaned, message);
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Resolves various event ID formats to a typed FullEventId.
|
|
185
|
+
*
|
|
186
|
+
* Accepts:
|
|
187
|
+
* - Full 64-character hex IDs (returns as-is after validation)
|
|
188
|
+
* - 12-character hex prefixes (resolved via PrefixKVStore)
|
|
189
|
+
* - NIP-19 formats: note1..., nevent1...
|
|
190
|
+
* - nostr: prefixed versions of all the above
|
|
191
|
+
*
|
|
192
|
+
* @param input - The event ID in any supported format
|
|
193
|
+
* @returns A typed FullEventId, or null if resolution failed
|
|
194
|
+
*/
|
|
195
|
+
export function resolveToFullEventId(input: string | undefined): FullEventId | null {
|
|
196
|
+
if (!input) return null;
|
|
197
|
+
|
|
198
|
+
const trimmed = input.trim();
|
|
199
|
+
|
|
200
|
+
// Strip nostr: prefix if present
|
|
201
|
+
const cleaned = trimmed.startsWith("nostr:") ? trimmed.slice(6) : trimmed;
|
|
202
|
+
const normalized = cleaned.toLowerCase();
|
|
203
|
+
|
|
204
|
+
// 1. Check for full 64-char hex ID
|
|
205
|
+
if (isFullEventId(normalized)) {
|
|
206
|
+
return normalized;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// 2. Check for 12-char hex prefix
|
|
210
|
+
if (isShortEventId(normalized)) {
|
|
211
|
+
const resolved = resolvePrefixToId(normalized);
|
|
212
|
+
if (resolved && isFullEventId(resolved)) {
|
|
213
|
+
return resolved;
|
|
214
|
+
}
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// 3. Try NIP-19 decoding (note1..., nevent1...)
|
|
219
|
+
try {
|
|
220
|
+
const decoded = nip19.decode(cleaned);
|
|
221
|
+
if (decoded.type === "note" && typeof decoded.data === "string") {
|
|
222
|
+
const eventId = decoded.data.toLowerCase();
|
|
223
|
+
if (isFullEventId(eventId)) {
|
|
224
|
+
return eventId;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (decoded.type === "nevent" && typeof decoded.data === "object" && decoded.data !== null) {
|
|
228
|
+
const data = decoded.data as { id: string };
|
|
229
|
+
const eventId = data.id.toLowerCase();
|
|
230
|
+
if (isFullEventId(eventId)) {
|
|
231
|
+
return eventId;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
} catch {
|
|
235
|
+
// Not a valid NIP-19 format
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Type-safe version of resolvePrefixToId that returns a typed FullEventId
|
|
243
|
+
*
|
|
244
|
+
* @param prefix - A ShortEventId (12-char hex prefix)
|
|
245
|
+
* @returns A typed FullEventId, or null if not found
|
|
246
|
+
*/
|
|
247
|
+
export function resolvePrefixToFullEventId(prefix: ShortEventId): FullEventId | null {
|
|
248
|
+
const resolved = resolvePrefixToId(prefix);
|
|
249
|
+
if (resolved && isFullEventId(resolved)) {
|
|
250
|
+
return resolved;
|
|
251
|
+
}
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Result type for normalizeLessonEventId - either success with eventId or error with message
|
|
257
|
+
*/
|
|
258
|
+
export type NormalizeLessonEventIdResult =
|
|
259
|
+
| { success: true; eventId: string }
|
|
260
|
+
| { success: false; error: string; errorType: "invalid_format" | "prefix_not_found" | "store_not_initialized" };
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Normalizes various lesson event ID formats to a canonical 64-char lowercase hex ID.
|
|
264
|
+
*
|
|
265
|
+
* Accepts:
|
|
266
|
+
* - Full 64-character hex IDs
|
|
267
|
+
* - 12-character hex prefixes (resolved via PrefixKVStore or in-memory fallback)
|
|
268
|
+
* - NIP-19 formats: note1..., nevent1...
|
|
269
|
+
* - nostr: prefixed versions of all the above
|
|
270
|
+
*
|
|
271
|
+
* This bridges the contract mismatch between lesson_learn (which emits NIP-19 encoded IDs)
|
|
272
|
+
* and lesson_get (which needs hex IDs for lookup).
|
|
273
|
+
*
|
|
274
|
+
* @param input - The event ID in any supported format
|
|
275
|
+
* @param allLessons - Optional array of all lessons for in-memory prefix fallback
|
|
276
|
+
* @returns Result object with either normalized eventId or error details
|
|
277
|
+
*/
|
|
278
|
+
export function normalizeLessonEventId(
|
|
279
|
+
input: string,
|
|
280
|
+
allLessons?: NDKAgentLesson[]
|
|
281
|
+
): NormalizeLessonEventIdResult {
|
|
282
|
+
const trimmed = input.trim();
|
|
283
|
+
|
|
284
|
+
// Strip nostr: prefix if present
|
|
285
|
+
const cleaned = trimmed.startsWith("nostr:") ? trimmed.slice(6) : trimmed;
|
|
286
|
+
|
|
287
|
+
// 1. Check for full 64-char hex ID
|
|
288
|
+
if (/^[0-9a-f]{64}$/i.test(cleaned)) {
|
|
289
|
+
return { success: true, eventId: cleaned.toLowerCase() };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// 2. Check for 12-char hex prefix
|
|
293
|
+
if (isHexPrefix(cleaned)) {
|
|
294
|
+
const prefix = cleaned.toLowerCase();
|
|
295
|
+
|
|
296
|
+
// Try PrefixKVStore first
|
|
297
|
+
if (prefixKVStore.isInitialized()) {
|
|
298
|
+
try {
|
|
299
|
+
const resolved = prefixKVStore.lookup(prefix);
|
|
300
|
+
if (resolved) {
|
|
301
|
+
return { success: true, eventId: resolved };
|
|
302
|
+
}
|
|
303
|
+
} catch (error) {
|
|
304
|
+
console.debug("[normalizeLessonEventId] PrefixKVStore lookup error:", error);
|
|
305
|
+
// Fall through to in-memory fallback
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// In-memory fallback: scan lessons for unique prefix match
|
|
310
|
+
if (allLessons && allLessons.length > 0) {
|
|
311
|
+
const matches = allLessons.filter((l) => l.id?.toLowerCase().startsWith(prefix));
|
|
312
|
+
if (matches.length === 1 && matches[0].id) {
|
|
313
|
+
return { success: true, eventId: matches[0].id.toLowerCase() };
|
|
314
|
+
}
|
|
315
|
+
if (matches.length > 1) {
|
|
316
|
+
return {
|
|
317
|
+
success: false,
|
|
318
|
+
error: `Prefix "${input}" is ambiguous - matches ${matches.length} lessons. Use a longer prefix or full event ID.`,
|
|
319
|
+
errorType: "prefix_not_found",
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Distinguish between "store not initialized" vs "not found"
|
|
325
|
+
if (!prefixKVStore.isInitialized()) {
|
|
326
|
+
return {
|
|
327
|
+
success: false,
|
|
328
|
+
error: `Could not resolve prefix "${input}" - PrefixKVStore is not initialized and no in-memory matches found.`,
|
|
329
|
+
errorType: "store_not_initialized",
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return {
|
|
334
|
+
success: false,
|
|
335
|
+
error: `Could not resolve prefix "${input}" to a full event ID. No matching lesson found.`,
|
|
336
|
+
errorType: "prefix_not_found",
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// 3. Try NIP-19 decoding (note1..., nevent1...)
|
|
341
|
+
try {
|
|
342
|
+
const decoded = nip19.decode(cleaned);
|
|
343
|
+
if (decoded.type === "note") {
|
|
344
|
+
return { success: true, eventId: (decoded.data as string).toLowerCase() };
|
|
345
|
+
}
|
|
346
|
+
if (decoded.type === "nevent") {
|
|
347
|
+
return { success: true, eventId: (decoded.data as { id: string }).id.toLowerCase() };
|
|
348
|
+
}
|
|
349
|
+
// Other NIP-19 types (npub, nprofile, naddr) are not valid event IDs
|
|
350
|
+
return {
|
|
351
|
+
success: false,
|
|
352
|
+
error: `Invalid lesson event ID format: "${input}". Got NIP-19 type "${decoded.type}" but expected "note" or "nevent".`,
|
|
353
|
+
errorType: "invalid_format",
|
|
354
|
+
};
|
|
355
|
+
} catch {
|
|
356
|
+
// Not a valid NIP-19 format, fall through to error
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// 4. Invalid format
|
|
360
|
+
return {
|
|
361
|
+
success: false,
|
|
362
|
+
error: `Invalid lesson event ID format: "${input}". Expected 64-char hex, 12-char hex prefix, or NIP-19 (note1.../nevent1...).`,
|
|
363
|
+
errorType: "invalid_format",
|
|
364
|
+
};
|
|
365
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { logger } from "@/utils/logger";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Handler function called during graceful shutdown
|
|
5
|
+
*/
|
|
6
|
+
export type ShutdownHandler = (signal: string) => Promise<void>;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Sets up graceful shutdown handlers for various termination signals
|
|
10
|
+
* @param shutdownHandler - Async function to handle cleanup during shutdown
|
|
11
|
+
* @description Handles SIGTERM, SIGINT, SIGHUP signals and uncaught exceptions/rejections
|
|
12
|
+
*/
|
|
13
|
+
export function setupGracefulShutdown(shutdownHandler: ShutdownHandler): void {
|
|
14
|
+
let isShuttingDown = false;
|
|
15
|
+
|
|
16
|
+
const shutdown = async (signal: string): Promise<void> => {
|
|
17
|
+
if (isShuttingDown) return;
|
|
18
|
+
isShuttingDown = true;
|
|
19
|
+
|
|
20
|
+
logger.info(`Received ${signal}, shutting down gracefully...`);
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
await shutdownHandler(signal);
|
|
24
|
+
logger.info("Shutdown complete");
|
|
25
|
+
process.exit(0);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
logger.error("Error during shutdown", { error });
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Handle various termination signals
|
|
33
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
34
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
35
|
+
process.on("SIGHUP", () => shutdown("SIGHUP"));
|
|
36
|
+
|
|
37
|
+
// Handle uncaught errors
|
|
38
|
+
process.on("uncaughtException", (error) => {
|
|
39
|
+
logger.error("Uncaught exception", { error });
|
|
40
|
+
shutdown("uncaughtException");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
process.on("unhandledRejection", (reason, promise) => {
|
|
44
|
+
logger.error("Unhandled rejection", { reason, promise });
|
|
45
|
+
// Don't shutdown for unhandled rejections - they're usually not critical
|
|
46
|
+
// e.g., relay rejections like "replaced: have newer event"
|
|
47
|
+
// Let the daemon's handler deal with these instead
|
|
48
|
+
});
|
|
49
|
+
}
|
package/src/wrapper.ts
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* TENEX Daemon Wrapper Process
|
|
5
|
+
*
|
|
6
|
+
* A supervisor process that manages the TENEX daemon lifecycle.
|
|
7
|
+
* Features:
|
|
8
|
+
* - Spawns the daemon as a child process with --supervised flag
|
|
9
|
+
* - Forwards SIGHUP to child to trigger graceful restart
|
|
10
|
+
* - Respawns daemon on clean exit (exit code 0)
|
|
11
|
+
* - Crash loop detection to prevent rapid respawns
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* bun src/wrapper.ts [daemon options]
|
|
15
|
+
*
|
|
16
|
+
* The wrapper passes all arguments to the daemon command.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { spawn, type ChildProcess } from "node:child_process";
|
|
20
|
+
import { existsSync } from "node:fs";
|
|
21
|
+
import * as path from "node:path";
|
|
22
|
+
import { fileURLToPath } from "node:url";
|
|
23
|
+
|
|
24
|
+
// Get __filename and __dirname equivalent in ESM
|
|
25
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
26
|
+
const __dirname = path.dirname(__filename);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the daemon entry point dynamically.
|
|
30
|
+
* Handles both development (src/index.ts) and production (dist/index.js) scenarios.
|
|
31
|
+
*
|
|
32
|
+
* Resolution order:
|
|
33
|
+
* 1. If running from src/, use src/index.ts (sibling of wrapper.ts)
|
|
34
|
+
* 2. If running from dist/, use dist/index.js
|
|
35
|
+
* 3. Fallback: look for index.ts/index.js in the same directory as wrapper
|
|
36
|
+
*/
|
|
37
|
+
function resolveEntryPoint(): string {
|
|
38
|
+
// Check if we're in src/ directory (development)
|
|
39
|
+
const srcIndex = path.join(__dirname, "index.ts");
|
|
40
|
+
if (existsSync(srcIndex)) {
|
|
41
|
+
return srcIndex;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Check if we're in dist/ directory (production)
|
|
45
|
+
const distIndex = path.join(__dirname, "index.js");
|
|
46
|
+
if (existsSync(distIndex)) {
|
|
47
|
+
return distIndex;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Fallback: try parent directories (in case wrapper is in a subdirectory)
|
|
51
|
+
const parentSrcIndex = path.join(path.dirname(__dirname), "src", "index.ts");
|
|
52
|
+
if (existsSync(parentSrcIndex)) {
|
|
53
|
+
return parentSrcIndex;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const parentDistIndex = path.join(path.dirname(__dirname), "dist", "index.js");
|
|
57
|
+
if (existsSync(parentDistIndex)) {
|
|
58
|
+
return parentDistIndex;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Last resort: assume src/index.ts relative to current directory
|
|
62
|
+
// This maintains backward compatibility with existing behavior
|
|
63
|
+
return srcIndex;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Configuration
|
|
67
|
+
const MIN_UPTIME_MS = 5000; // Minimum uptime to reset crash counter
|
|
68
|
+
const MAX_CRASHES = 5; // Maximum crashes before giving up
|
|
69
|
+
const CRASH_WINDOW_MS = 60000; // Time window for crash counting
|
|
70
|
+
|
|
71
|
+
interface CrashRecord {
|
|
72
|
+
timestamp: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
class DaemonWrapper {
|
|
76
|
+
private child: ChildProcess | null = null;
|
|
77
|
+
private crashHistory: CrashRecord[] = [];
|
|
78
|
+
private startTime: number = 0;
|
|
79
|
+
private isShuttingDown = false;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Start the daemon wrapper
|
|
83
|
+
*/
|
|
84
|
+
async start(args: string[]): Promise<void> {
|
|
85
|
+
console.log("[Wrapper] Starting TENEX daemon supervisor");
|
|
86
|
+
console.log("[Wrapper] Daemon arguments:", args.join(" ") || "(none)");
|
|
87
|
+
|
|
88
|
+
// Setup signal handlers
|
|
89
|
+
this.setupSignalHandlers();
|
|
90
|
+
|
|
91
|
+
// Start the daemon loop
|
|
92
|
+
await this.runDaemonLoop(args);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Setup signal handlers for the wrapper process
|
|
97
|
+
*/
|
|
98
|
+
private setupSignalHandlers(): void {
|
|
99
|
+
// Forward SIGHUP to child process
|
|
100
|
+
process.on("SIGHUP", () => {
|
|
101
|
+
if (this.child && this.child.pid) {
|
|
102
|
+
console.log("[Wrapper] SIGHUP received - forwarding to daemon");
|
|
103
|
+
this.child.kill("SIGHUP");
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Handle SIGTERM/SIGINT - shutdown gracefully
|
|
108
|
+
const handleTermination = (signal: string) => {
|
|
109
|
+
if (this.isShuttingDown) {
|
|
110
|
+
console.log(`[Wrapper] ${signal} received during shutdown - forcing exit`);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
console.log(`[Wrapper] ${signal} received - shutting down`);
|
|
115
|
+
this.isShuttingDown = true;
|
|
116
|
+
|
|
117
|
+
if (this.child && this.child.pid) {
|
|
118
|
+
// Forward the signal to child
|
|
119
|
+
this.child.kill(signal === "SIGTERM" ? "SIGTERM" : "SIGINT");
|
|
120
|
+
|
|
121
|
+
// Give child some time to shutdown, then force kill
|
|
122
|
+
setTimeout(() => {
|
|
123
|
+
if (this.child && this.child.pid) {
|
|
124
|
+
console.log("[Wrapper] Child still running after timeout - forcing kill");
|
|
125
|
+
this.child.kill("SIGKILL");
|
|
126
|
+
}
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}, 30000);
|
|
129
|
+
} else {
|
|
130
|
+
process.exit(0);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
process.on("SIGTERM", () => handleTermination("SIGTERM"));
|
|
135
|
+
process.on("SIGINT", () => handleTermination("SIGINT"));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Main daemon loop - spawn daemon and respawn on clean exit
|
|
140
|
+
*/
|
|
141
|
+
private async runDaemonLoop(args: string[]): Promise<void> {
|
|
142
|
+
while (!this.isShuttingDown) {
|
|
143
|
+
// Check crash loop
|
|
144
|
+
if (this.isInCrashLoop()) {
|
|
145
|
+
console.error("[Wrapper] Crash loop detected - too many crashes in short period");
|
|
146
|
+
console.error(`[Wrapper] ${this.crashHistory.length} crashes in last ${CRASH_WINDOW_MS / 1000}s`);
|
|
147
|
+
console.error("[Wrapper] Giving up - please check daemon logs");
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Spawn daemon
|
|
152
|
+
this.startTime = Date.now();
|
|
153
|
+
const exitCode = await this.spawnDaemon(args);
|
|
154
|
+
const uptime = Date.now() - this.startTime;
|
|
155
|
+
|
|
156
|
+
console.log(`[Wrapper] Daemon exited with code ${exitCode} after ${Math.round(uptime / 1000)}s`);
|
|
157
|
+
|
|
158
|
+
if (this.isShuttingDown) {
|
|
159
|
+
// Wrapper is shutting down, don't respawn
|
|
160
|
+
console.log("[Wrapper] Wrapper shutting down, not respawning");
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (exitCode === 0) {
|
|
165
|
+
// Clean exit - this is a graceful restart
|
|
166
|
+
console.log("[Wrapper] Daemon exited cleanly - respawning for graceful restart");
|
|
167
|
+
|
|
168
|
+
// Reset crash counter on clean exit after sufficient uptime
|
|
169
|
+
if (uptime >= MIN_UPTIME_MS) {
|
|
170
|
+
this.crashHistory = [];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Small delay to prevent tight loop in edge cases
|
|
174
|
+
await this.sleep(100);
|
|
175
|
+
} else {
|
|
176
|
+
// Non-zero exit - this is a crash
|
|
177
|
+
console.error(`[Wrapper] Daemon crashed with exit code ${exitCode}`);
|
|
178
|
+
this.recordCrash();
|
|
179
|
+
|
|
180
|
+
// Exponential backoff on crashes
|
|
181
|
+
const backoffMs = Math.min(1000 * Math.pow(2, this.crashHistory.length - 1), 30000);
|
|
182
|
+
console.log(`[Wrapper] Waiting ${backoffMs / 1000}s before respawn...`);
|
|
183
|
+
await this.sleep(backoffMs);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
console.log("[Wrapper] Exiting");
|
|
188
|
+
process.exit(0);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Spawn the daemon process and wait for it to exit
|
|
193
|
+
*/
|
|
194
|
+
private spawnDaemon(args: string[]): Promise<number> {
|
|
195
|
+
return new Promise((resolve) => {
|
|
196
|
+
// Build command: bun <entry-point> daemon --supervised [args]
|
|
197
|
+
const indexPath = resolveEntryPoint();
|
|
198
|
+
const daemonArgs = [indexPath, "daemon", "--supervised", ...args];
|
|
199
|
+
|
|
200
|
+
console.log(`[Wrapper] Spawning: bun ${daemonArgs.join(" ")}`);
|
|
201
|
+
|
|
202
|
+
this.child = spawn("bun", daemonArgs, {
|
|
203
|
+
stdio: "inherit",
|
|
204
|
+
env: process.env,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
this.child.on("exit", (code) => {
|
|
208
|
+
this.child = null;
|
|
209
|
+
resolve(code ?? 1);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
this.child.on("error", (error) => {
|
|
213
|
+
console.error("[Wrapper] Failed to spawn daemon:", error.message);
|
|
214
|
+
this.child = null;
|
|
215
|
+
resolve(1);
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Record a crash for crash loop detection
|
|
222
|
+
*/
|
|
223
|
+
private recordCrash(): void {
|
|
224
|
+
const now = Date.now();
|
|
225
|
+
this.crashHistory.push({ timestamp: now });
|
|
226
|
+
|
|
227
|
+
// Prune old crashes outside the window
|
|
228
|
+
this.crashHistory = this.crashHistory.filter(
|
|
229
|
+
(c) => now - c.timestamp < CRASH_WINDOW_MS
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Check if we're in a crash loop
|
|
235
|
+
*/
|
|
236
|
+
private isInCrashLoop(): boolean {
|
|
237
|
+
const now = Date.now();
|
|
238
|
+
const recentCrashes = this.crashHistory.filter(
|
|
239
|
+
(c) => now - c.timestamp < CRASH_WINDOW_MS
|
|
240
|
+
);
|
|
241
|
+
return recentCrashes.length >= MAX_CRASHES;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Sleep for a given duration
|
|
246
|
+
*/
|
|
247
|
+
private sleep(ms: number): Promise<void> {
|
|
248
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Main entry point
|
|
253
|
+
const wrapper = new DaemonWrapper();
|
|
254
|
+
|
|
255
|
+
// Get arguments after wrapper.ts
|
|
256
|
+
// Process args are: ["bun", "wrapper.ts", ...args]
|
|
257
|
+
const args = process.argv.slice(2);
|
|
258
|
+
|
|
259
|
+
wrapper.start(args).catch((error) => {
|
|
260
|
+
console.error("[Wrapper] Fatal error:", error);
|
|
261
|
+
process.exit(1);
|
|
262
|
+
});
|