@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,1143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt Compiler Service (TIN-10)
|
|
3
|
+
*
|
|
4
|
+
* Compiles agent lessons with user comments into Effective Agent Instructions.
|
|
5
|
+
* Uses LLM to intelligently merge:
|
|
6
|
+
* - Base Agent Instructions (from agent.instructions in Kind 4199 event)
|
|
7
|
+
* - Lessons (retrieved from ProjectContext, tagging the Agent Definition Event)
|
|
8
|
+
* - Comments on Lesson Events
|
|
9
|
+
* - Optional additionalSystemPrompt
|
|
10
|
+
*
|
|
11
|
+
* Terminology:
|
|
12
|
+
* - Base Agent Instructions: Raw instructions stored in the agent definition Nostr event (Kind 4199)
|
|
13
|
+
* - Effective Agent Instructions: Final compiled instructions = Base + Lessons + Comments
|
|
14
|
+
*
|
|
15
|
+
* Key behaviors:
|
|
16
|
+
* - Retrieves lessons internally from ProjectContext (not passed as parameter)
|
|
17
|
+
* - Uses generateText (not generateObject) for natural prompt integration
|
|
18
|
+
* - Returns only the Effective Agent Instructions string (not a structured object)
|
|
19
|
+
* - On LLM failure: throws error (consumer handles fallback)
|
|
20
|
+
* - Cache hash (cacheInputsHash) includes: agentDefinitionEventId (nullable), baseAgentInstructions, additionalSystemPrompt
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import * as fs from "node:fs/promises";
|
|
24
|
+
import * as path from "node:path";
|
|
25
|
+
import * as crypto from "node:crypto";
|
|
26
|
+
import type { NDKEvent, NDKSubscription, Hexpubkey } from "@nostr-dev-kit/ndk";
|
|
27
|
+
import { NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
|
|
28
|
+
import type NDK from "@nostr-dev-kit/ndk";
|
|
29
|
+
import { NDKKind } from "@/nostr/kinds";
|
|
30
|
+
import type { NDKAgentLesson } from "@/events/NDKAgentLesson";
|
|
31
|
+
import { AgentProfilePublisher } from "@/nostr/AgentProfilePublisher";
|
|
32
|
+
import { config } from "@/services/ConfigService";
|
|
33
|
+
import { llmServiceFactory } from "@/llm";
|
|
34
|
+
import { logger } from "@/utils/logger";
|
|
35
|
+
import { trace, SpanStatusCode } from "@opentelemetry/api";
|
|
36
|
+
|
|
37
|
+
// =====================================================================================
|
|
38
|
+
// TYPES
|
|
39
|
+
// =====================================================================================
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A comment on a lesson (kind 1111 event per NIP-22)
|
|
43
|
+
*/
|
|
44
|
+
export interface LessonComment {
|
|
45
|
+
/** The comment event ID */
|
|
46
|
+
id: string;
|
|
47
|
+
/** Author pubkey */
|
|
48
|
+
pubkey: Hexpubkey;
|
|
49
|
+
/** Comment content */
|
|
50
|
+
content: string;
|
|
51
|
+
/** The lesson event ID this comment references */
|
|
52
|
+
lessonEventId: string;
|
|
53
|
+
/** Unix timestamp */
|
|
54
|
+
createdAt: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Cache entry for Effective Agent Instructions
|
|
59
|
+
*/
|
|
60
|
+
export interface EffectiveInstructionsCacheEntry {
|
|
61
|
+
/** The Effective Agent Instructions (compiled result) */
|
|
62
|
+
effectiveAgentInstructions: string;
|
|
63
|
+
/** When the cache was written (Unix timestamp) */
|
|
64
|
+
timestamp: number;
|
|
65
|
+
/** max(created_at) of lessons AND comments used */
|
|
66
|
+
maxCreatedAt: number;
|
|
67
|
+
/** SHA-256 hash of cache inputs: agentDefinitionEventId (nullable), baseAgentInstructions, additionalSystemPrompt */
|
|
68
|
+
cacheInputsHash: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Compilation status tracking for eager compilation
|
|
73
|
+
*/
|
|
74
|
+
export type CompilationStatus = "idle" | "compiling" | "completed" | "error";
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Result from getEffectiveInstructionsSync() indicating source of instructions
|
|
78
|
+
*/
|
|
79
|
+
export interface EffectiveInstructionsResult {
|
|
80
|
+
/** The instructions to use */
|
|
81
|
+
instructions: string;
|
|
82
|
+
/** Whether these are compiled (true) or base instructions (false) */
|
|
83
|
+
isCompiled: boolean;
|
|
84
|
+
/** Timestamp of when these instructions were compiled (undefined if using base) */
|
|
85
|
+
compiledAt?: number;
|
|
86
|
+
/** Source of the instructions: "compiled_cache", "base_instructions", or "compilation_in_progress" */
|
|
87
|
+
source: "compiled_cache" | "base_instructions" | "compilation_in_progress";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
// =====================================================================================
|
|
92
|
+
// PROMPT COMPILER SERVICE
|
|
93
|
+
// =====================================================================================
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* PromptCompilerService compiles lessons with their comments into system prompts.
|
|
97
|
+
* One instance per agent.
|
|
98
|
+
*/
|
|
99
|
+
export class PromptCompilerService {
|
|
100
|
+
private ndk: NDK;
|
|
101
|
+
private agentPubkey: Hexpubkey;
|
|
102
|
+
private whitelistedPubkeys: Set<Hexpubkey>;
|
|
103
|
+
|
|
104
|
+
/** Lessons for this agent — set at initialization and refreshed on each triggerCompilation call */
|
|
105
|
+
private lessons: NDKAgentLesson[] = [];
|
|
106
|
+
|
|
107
|
+
/** Subscription for kind 1111 (comment) events */
|
|
108
|
+
private subscription: NDKSubscription | null = null;
|
|
109
|
+
|
|
110
|
+
/** Comments collected from subscription, keyed by lesson event ID */
|
|
111
|
+
private commentsByLesson: Map<string, LessonComment[]> = new Map();
|
|
112
|
+
|
|
113
|
+
/** EOSE tracking */
|
|
114
|
+
private eoseReceived = false;
|
|
115
|
+
private eosePromise: Promise<void> | null = null;
|
|
116
|
+
private eoseResolve: (() => void) | null = null;
|
|
117
|
+
|
|
118
|
+
/** Cache directory */
|
|
119
|
+
private cacheDir: string;
|
|
120
|
+
|
|
121
|
+
// =====================================================================================
|
|
122
|
+
// EAGER COMPILATION STATE (TIN-10 Enhancement)
|
|
123
|
+
// =====================================================================================
|
|
124
|
+
|
|
125
|
+
/** Current compilation status */
|
|
126
|
+
private compilationStatus: CompilationStatus = "idle";
|
|
127
|
+
|
|
128
|
+
/** In-memory cache of compiled effective instructions (loaded from disk or after compilation) */
|
|
129
|
+
private cachedEffectiveInstructions: EffectiveInstructionsCacheEntry | null = null;
|
|
130
|
+
|
|
131
|
+
/** Base agent instructions (stored for sync retrieval and recompilation) */
|
|
132
|
+
private baseAgentInstructions: string = "";
|
|
133
|
+
|
|
134
|
+
/** Agent definition event ID (stored for cache hash calculation) */
|
|
135
|
+
private agentDefinitionEventId?: string;
|
|
136
|
+
|
|
137
|
+
/** Promise for the currently running compilation (if any).
|
|
138
|
+
* Useful for testing or other scenarios that need to await compilation. */
|
|
139
|
+
private currentCompilationPromise: Promise<void> | null = null;
|
|
140
|
+
|
|
141
|
+
/** Timestamp of last compilation trigger (for debouncing) */
|
|
142
|
+
private lastCompilationTrigger: number = 0;
|
|
143
|
+
|
|
144
|
+
/** Minimum interval between compilation triggers (ms) - debounce rapid lesson arrivals */
|
|
145
|
+
private static readonly COMPILATION_DEBOUNCE_MS = 5000;
|
|
146
|
+
|
|
147
|
+
/** Flag indicating a recompilation is pending (set when trigger arrives during active compilation or debounce) */
|
|
148
|
+
private pendingRecompile: boolean = false;
|
|
149
|
+
|
|
150
|
+
/** Timer for debounced compilation (to ensure triggers aren't dropped when idle) */
|
|
151
|
+
private debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
152
|
+
|
|
153
|
+
/** Flag indicating if initialize() has been called */
|
|
154
|
+
private initialized: boolean = false;
|
|
155
|
+
|
|
156
|
+
/** Agent metadata for kind:0 publishing (set via setAgentMetadata) */
|
|
157
|
+
private agentSigner: NDKPrivateKeySigner | null = null;
|
|
158
|
+
private agentName: string | null = null;
|
|
159
|
+
private agentRole: string | null = null;
|
|
160
|
+
private projectTitle: string | null = null;
|
|
161
|
+
|
|
162
|
+
constructor(
|
|
163
|
+
agentPubkey: Hexpubkey,
|
|
164
|
+
whitelistedPubkeys: Hexpubkey[],
|
|
165
|
+
ndk: NDK
|
|
166
|
+
) {
|
|
167
|
+
this.agentPubkey = agentPubkey;
|
|
168
|
+
this.whitelistedPubkeys = new Set(whitelistedPubkeys);
|
|
169
|
+
this.ndk = ndk;
|
|
170
|
+
|
|
171
|
+
// Cache at ~/.tenex/agents/prompts/{agentPubkey}.json
|
|
172
|
+
this.cacheDir = path.dirname(PromptCompilerService.getCachePathForAgent(agentPubkey));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Set agent metadata required for kind:0 publishing after compilation.
|
|
177
|
+
* Must be called before triggerCompilation() to enable kind:0 publishing.
|
|
178
|
+
*
|
|
179
|
+
* @param agentSigner The agent's NDKPrivateKeySigner for signing kind:0 events
|
|
180
|
+
* @param agentName The agent's display name
|
|
181
|
+
* @param agentRole The agent's role description
|
|
182
|
+
* @param projectTitle The project title for the profile description
|
|
183
|
+
*/
|
|
184
|
+
setAgentMetadata(
|
|
185
|
+
agentSigner: NDKPrivateKeySigner,
|
|
186
|
+
agentName: string,
|
|
187
|
+
agentRole: string,
|
|
188
|
+
projectTitle: string
|
|
189
|
+
): void {
|
|
190
|
+
this.agentSigner = agentSigner;
|
|
191
|
+
this.agentName = agentName;
|
|
192
|
+
this.agentRole = agentRole;
|
|
193
|
+
this.projectTitle = projectTitle;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// =====================================================================================
|
|
197
|
+
// SUBSCRIPTION MANAGEMENT
|
|
198
|
+
// =====================================================================================
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Start subscribing to kind 1111 (comment) events for lessons authored by this agent.
|
|
202
|
+
* Filters by:
|
|
203
|
+
* - kind: 1111 (NIP-22 comments)
|
|
204
|
+
* - #K: ["4129"] (comments on lesson events)
|
|
205
|
+
* - authors: whitelisted pubkeys only
|
|
206
|
+
* - #p or #e referencing this agent's pubkey or lesson events
|
|
207
|
+
*/
|
|
208
|
+
subscribe(): void {
|
|
209
|
+
if (this.subscription) {
|
|
210
|
+
logger.warn("PromptCompilerService: subscription already active", {
|
|
211
|
+
agentPubkey: this.agentPubkey.substring(0, 8),
|
|
212
|
+
});
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Reset EOSE state for fresh subscription lifecycle
|
|
217
|
+
this.eoseReceived = false;
|
|
218
|
+
this.eoseResolve = null;
|
|
219
|
+
|
|
220
|
+
// Initialize EOSE promise
|
|
221
|
+
this.eosePromise = new Promise<void>((resolve) => {
|
|
222
|
+
this.eoseResolve = resolve;
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// NIP-22 comment filter:
|
|
226
|
+
// - kind: NDKKind.Comment (1111)
|
|
227
|
+
// - #K: [NDKKind.AgentLesson] (referencing lesson events)
|
|
228
|
+
// - authors: whitelisted pubkeys only
|
|
229
|
+
// - #p: [agentPubkey] (comments mentioning the agent)
|
|
230
|
+
const filter = {
|
|
231
|
+
kinds: [NDKKind.Comment],
|
|
232
|
+
"#K": [String(NDKKind.AgentLesson)], // Comments on kind 4129 (lessons)
|
|
233
|
+
"#p": [this.agentPubkey], // Comments that mention this agent
|
|
234
|
+
authors: Array.from(this.whitelistedPubkeys),
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
logger.debug("PromptCompilerService: starting subscription", {
|
|
238
|
+
agentPubkey: this.agentPubkey.substring(0, 8),
|
|
239
|
+
whitelistSize: this.whitelistedPubkeys.size,
|
|
240
|
+
filter,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
this.subscription = this.ndk.subscribe([filter], {
|
|
244
|
+
closeOnEose: false,
|
|
245
|
+
groupable: true,
|
|
246
|
+
onEvent: (event: NDKEvent) => {
|
|
247
|
+
this.handleCommentEvent(event);
|
|
248
|
+
},
|
|
249
|
+
onEose: () => {
|
|
250
|
+
logger.debug("PromptCompilerService: EOSE received", {
|
|
251
|
+
agentPubkey: this.agentPubkey.substring(0, 8),
|
|
252
|
+
commentsCount: this.getTotalCommentsCount(),
|
|
253
|
+
});
|
|
254
|
+
this.eoseReceived = true;
|
|
255
|
+
if (this.eoseResolve) {
|
|
256
|
+
this.eoseResolve();
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Block until EOSE is received from the subscription.
|
|
264
|
+
* Call this after subscribe() before calling compile().
|
|
265
|
+
*/
|
|
266
|
+
async waitForEOSE(): Promise<void> {
|
|
267
|
+
if (this.eoseReceived) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (!this.eosePromise) {
|
|
272
|
+
throw new Error("PromptCompilerService: waitForEOSE called before subscribe()");
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
await this.eosePromise;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Stop the subscription and reset EOSE state
|
|
280
|
+
*/
|
|
281
|
+
stop(): void {
|
|
282
|
+
if (this.subscription) {
|
|
283
|
+
this.subscription.stop();
|
|
284
|
+
this.subscription = null;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Clear debounce timer to prevent post-shutdown compilation triggers
|
|
288
|
+
if (this.debounceTimer) {
|
|
289
|
+
clearTimeout(this.debounceTimer);
|
|
290
|
+
this.debounceTimer = null;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Reset compilation state to ensure full quiescence
|
|
294
|
+
this.pendingRecompile = false;
|
|
295
|
+
|
|
296
|
+
// Reset EOSE state so waitForEOSE is reliable after restart
|
|
297
|
+
this.eoseReceived = false;
|
|
298
|
+
this.eosePromise = null;
|
|
299
|
+
this.eoseResolve = null;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// =====================================================================================
|
|
303
|
+
// COMMENT HANDLING
|
|
304
|
+
// =====================================================================================
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Add a comment directly (called by Daemon when routing comments)
|
|
308
|
+
* @param comment The lesson comment to add
|
|
309
|
+
*/
|
|
310
|
+
addComment(comment: LessonComment): void {
|
|
311
|
+
// Add to our collection
|
|
312
|
+
const existing = this.commentsByLesson.get(comment.lessonEventId) || [];
|
|
313
|
+
|
|
314
|
+
// Check for duplicates
|
|
315
|
+
if (existing.some(c => c.id === comment.id)) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
existing.push(comment);
|
|
320
|
+
this.commentsByLesson.set(comment.lessonEventId, existing);
|
|
321
|
+
|
|
322
|
+
logger.debug("PromptCompilerService: added comment", {
|
|
323
|
+
commentId: comment.id.substring(0, 8),
|
|
324
|
+
lessonEventId: comment.lessonEventId.substring(0, 8),
|
|
325
|
+
totalCommentsForLesson: existing.length,
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// Trigger recompilation when a new comment arrives (debounced)
|
|
329
|
+
this.onCommentArrived();
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Called when a new comment arrives for a lesson.
|
|
334
|
+
* Triggers recompilation in the background.
|
|
335
|
+
*/
|
|
336
|
+
onCommentArrived(): void {
|
|
337
|
+
const tracer = trace.getTracer("tenex.prompt-compiler");
|
|
338
|
+
tracer.startActiveSpan("tenex.prompt_compilation.comment_trigger", (span) => {
|
|
339
|
+
span.setAttribute("agent.pubkey", this.agentPubkey.substring(0, 8));
|
|
340
|
+
span.setAttribute("trigger.source", "new_comment");
|
|
341
|
+
span.end();
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
logger.debug("PromptCompilerService: new comment arrived, triggering recompilation", {
|
|
345
|
+
agentPubkey: this.agentPubkey.substring(0, 8),
|
|
346
|
+
});
|
|
347
|
+
this.triggerCompilation();
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Handle an incoming kind 1111 comment event from subscription.
|
|
352
|
+
* Delegates to addComment after parsing the event.
|
|
353
|
+
*/
|
|
354
|
+
private handleCommentEvent(event: NDKEvent): void {
|
|
355
|
+
// Extract the lesson event ID using shared helper
|
|
356
|
+
const lessonEventId = this.extractLessonEventId(event);
|
|
357
|
+
if (!lessonEventId) {
|
|
358
|
+
logger.debug("PromptCompilerService: comment missing lesson event reference", {
|
|
359
|
+
eventId: event.id?.substring(0, 8),
|
|
360
|
+
});
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Verify author is in whitelist
|
|
365
|
+
if (!this.whitelistedPubkeys.has(event.pubkey)) {
|
|
366
|
+
logger.debug("PromptCompilerService: comment from non-whitelisted author", {
|
|
367
|
+
eventId: event.id?.substring(0, 8),
|
|
368
|
+
author: event.pubkey.substring(0, 8),
|
|
369
|
+
});
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Delegate to addComment for centralized storage with de-duplication
|
|
374
|
+
this.addComment({
|
|
375
|
+
id: event.id || "",
|
|
376
|
+
pubkey: event.pubkey,
|
|
377
|
+
content: event.content,
|
|
378
|
+
lessonEventId,
|
|
379
|
+
createdAt: event.created_at || 0,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Extract the lesson event ID from a kind 1111 comment event.
|
|
385
|
+
* Per NIP-22, the root 'e' tag references the target event.
|
|
386
|
+
*/
|
|
387
|
+
private extractLessonEventId(event: NDKEvent): string | null {
|
|
388
|
+
// Look for 'e' tag with "root" marker, or first 'e' tag
|
|
389
|
+
const rootETag = event.tags.find(
|
|
390
|
+
(tag) => tag[0] === "e" && tag[3] === "root"
|
|
391
|
+
);
|
|
392
|
+
if (rootETag?.[1]) {
|
|
393
|
+
return rootETag[1];
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Fallback: first 'e' tag
|
|
397
|
+
const firstETag = event.tags.find((tag) => tag[0] === "e");
|
|
398
|
+
return firstETag?.[1] || null;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Get comments for a specific lesson
|
|
403
|
+
*/
|
|
404
|
+
getCommentsForLesson(lessonEventId: string): LessonComment[] {
|
|
405
|
+
return this.commentsByLesson.get(lessonEventId) || [];
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Get total number of comments across all lessons
|
|
410
|
+
*/
|
|
411
|
+
private getTotalCommentsCount(): number {
|
|
412
|
+
let total = 0;
|
|
413
|
+
for (const comments of this.commentsByLesson.values()) {
|
|
414
|
+
total += comments.length;
|
|
415
|
+
}
|
|
416
|
+
return total;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// =====================================================================================
|
|
420
|
+
// COMPILATION
|
|
421
|
+
// =====================================================================================
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Compile lessons and their comments into Effective Agent Instructions.
|
|
425
|
+
* Uses LLM to intelligently merge Base Agent Instructions with lessons.
|
|
426
|
+
*
|
|
427
|
+
* @param baseAgentInstructions The Base Agent Instructions to enhance (from agent.instructions)
|
|
428
|
+
* @param agentDefinitionEventId Optional event ID for cache hash (for non-local agents)
|
|
429
|
+
* @param additionalSystemPrompt Optional additional instructions to integrate
|
|
430
|
+
* @returns The Effective Agent Instructions string
|
|
431
|
+
* @throws Error if LLM compilation fails (consumer should handle fallback)
|
|
432
|
+
*/
|
|
433
|
+
async compile(
|
|
434
|
+
baseAgentInstructions: string,
|
|
435
|
+
agentDefinitionEventId?: string,
|
|
436
|
+
additionalSystemPrompt?: string
|
|
437
|
+
): Promise<string> {
|
|
438
|
+
// Use the lessons set at initialization time
|
|
439
|
+
const lessons = this.lessons;
|
|
440
|
+
|
|
441
|
+
// If no lessons and no additional prompt, return Base Agent Instructions directly
|
|
442
|
+
if (lessons.length === 0 && !additionalSystemPrompt) {
|
|
443
|
+
logger.debug("PromptCompilerService: no lessons or additional prompt, returning Base Agent Instructions", {
|
|
444
|
+
agentPubkey: this.agentPubkey.substring(0, 8),
|
|
445
|
+
});
|
|
446
|
+
return baseAgentInstructions;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Calculate freshness inputs
|
|
450
|
+
const maxCreatedAt = this.calculateMaxCreatedAt(lessons);
|
|
451
|
+
// Create deterministic cache key from all relevant inputs:
|
|
452
|
+
// - agentDefinitionEventId (when provided for non-local agents)
|
|
453
|
+
// - baseAgentInstructions (always included - captures local agent definition changes)
|
|
454
|
+
// - additionalSystemPrompt (captures dynamic context changes)
|
|
455
|
+
const cacheHash = this.hashString(JSON.stringify({
|
|
456
|
+
agentDefinitionEventId: agentDefinitionEventId || null,
|
|
457
|
+
baseAgentInstructions,
|
|
458
|
+
additionalSystemPrompt: additionalSystemPrompt || null,
|
|
459
|
+
}));
|
|
460
|
+
|
|
461
|
+
// Check cache
|
|
462
|
+
const cached = await this.readCache();
|
|
463
|
+
if (cached) {
|
|
464
|
+
const cacheValid =
|
|
465
|
+
cached.maxCreatedAt >= maxCreatedAt &&
|
|
466
|
+
cached.cacheInputsHash === cacheHash;
|
|
467
|
+
|
|
468
|
+
if (cacheValid) {
|
|
469
|
+
logger.debug("PromptCompilerService: returning cached Effective Agent Instructions", {
|
|
470
|
+
agentPubkey: this.agentPubkey.substring(0, 8),
|
|
471
|
+
cacheAge: Date.now() - cached.timestamp * 1000,
|
|
472
|
+
});
|
|
473
|
+
return cached.effectiveAgentInstructions;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
logger.debug("PromptCompilerService: cache stale, recompiling", {
|
|
477
|
+
agentPubkey: this.agentPubkey.substring(0, 8),
|
|
478
|
+
cachedMaxCreatedAt: cached.maxCreatedAt,
|
|
479
|
+
currentMaxCreatedAt: maxCreatedAt,
|
|
480
|
+
cacheHashMatch: cached.cacheInputsHash === cacheHash,
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Compile with LLM
|
|
485
|
+
const effectiveAgentInstructions = await this.compileWithLLM(lessons, baseAgentInstructions, additionalSystemPrompt);
|
|
486
|
+
|
|
487
|
+
// Cache the result
|
|
488
|
+
await this.writeCache({
|
|
489
|
+
effectiveAgentInstructions,
|
|
490
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
491
|
+
maxCreatedAt,
|
|
492
|
+
cacheInputsHash: cacheHash,
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
return effectiveAgentInstructions;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Calculate the max created_at across lessons AND their comments
|
|
500
|
+
*/
|
|
501
|
+
private calculateMaxCreatedAt(lessons: NDKAgentLesson[]): number {
|
|
502
|
+
let max = 0;
|
|
503
|
+
|
|
504
|
+
for (const lesson of lessons) {
|
|
505
|
+
// Lesson's own created_at
|
|
506
|
+
if (lesson.created_at && lesson.created_at > max) {
|
|
507
|
+
max = lesson.created_at;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Comments on this lesson
|
|
511
|
+
const comments = this.getCommentsForLesson(lesson.id || "");
|
|
512
|
+
for (const comment of comments) {
|
|
513
|
+
if (comment.createdAt > max) {
|
|
514
|
+
max = comment.createdAt;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return max;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Compile lessons + comments into Effective Agent Instructions using the LLM.
|
|
524
|
+
* Uses generateText to naturally integrate lessons into the Base Agent Instructions.
|
|
525
|
+
*
|
|
526
|
+
* @param lessons The agent's lessons
|
|
527
|
+
* @param baseAgentInstructions The Base Agent Instructions to enhance
|
|
528
|
+
* @param additionalSystemPrompt Optional additional instructions to integrate
|
|
529
|
+
* @returns The Effective Agent Instructions string
|
|
530
|
+
* @throws Error if LLM compilation fails
|
|
531
|
+
*/
|
|
532
|
+
private async compileWithLLM(
|
|
533
|
+
lessons: NDKAgentLesson[],
|
|
534
|
+
baseAgentInstructions: string,
|
|
535
|
+
additionalSystemPrompt?: string
|
|
536
|
+
): Promise<string> {
|
|
537
|
+
// Get LLM configuration - use promptCompilation config if set, then summarization, then default
|
|
538
|
+
const { llms } = await config.loadConfig();
|
|
539
|
+
const configName = llms.promptCompilation || llms.summarization || llms.default;
|
|
540
|
+
|
|
541
|
+
if (!configName) {
|
|
542
|
+
throw new Error("PromptCompilerService: no LLM config available for prompt compilation");
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const llmConfig = config.getLLMConfig(configName);
|
|
546
|
+
const llmService = llmServiceFactory.createService(llmConfig, {
|
|
547
|
+
agentName: "prompt-compiler",
|
|
548
|
+
sessionId: `prompt-compiler-${this.agentPubkey.substring(0, 8)}`,
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
// Format lessons with their comments (all lessons, regardless of comments)
|
|
552
|
+
const lessonsWithComments = lessons.map((lesson) => {
|
|
553
|
+
const comments = this.getCommentsForLesson(lesson.id || "");
|
|
554
|
+
return {
|
|
555
|
+
title: lesson.title || "Untitled",
|
|
556
|
+
lesson: lesson.lesson,
|
|
557
|
+
category: lesson.category,
|
|
558
|
+
hashtags: lesson.hashtags,
|
|
559
|
+
detailed: lesson.detailed,
|
|
560
|
+
comments: comments.map((c) => c.content),
|
|
561
|
+
};
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
// Build compilation prompt with emphasis on natural integration
|
|
565
|
+
const systemPrompt = `You are a Technical Systems Architect responsible for compiling and upgrading the operating manuals (system instructions) for autonomous AI agents.Your Goal: Create a 'Single Source of Truth' instruction set that is rigorously executable, technically precise, and authoritative.## Input Data1. Base Agent Instructions (Current State)2. Lessons Learned (New requirements, fixes, and configuration changes)## Compilation Rules1. **Preserve Hard Data**: You must NEVER summarize, omit, or generalize specific technical values found in the lessons. If a lesson contains file paths, Hex keys, NSEC/NPUB credentials, or specific CLI flags, they MUST appear verbatim in the final output.2. **Strict Protocol Enforcement**: If a lesson dictates a mandatory workflow (e.g., \"Always do X first\"), this must be elevated to a top-level 'CRITICAL PROTOCOL' section, not buried in a bullet point.3. **Conflict Resolution**: Newer lessons represent the current reality. If a lesson contradicts the Base Instructions, delete the old instruction entirely and replace it with the new logic.4. **Structure for Utility**: Do not force all information into prose. Use dedicated sections for 'Configuration Constants', 'Reference Paths', and 'Forbidden Actions' to make the instructions scannable and executable.5. **Tone**: The output should be imperative and strict. Use 'MUST', 'NEVER', and 'REQUIRED' for constraints.## Output Requirements- Output ONLY the Effective Agent Instructions.- Do NOT add meta-commentary.- Do NOT summarize the compilation process.`;
|
|
566
|
+
|
|
567
|
+
// Build user prompt with all inputs
|
|
568
|
+
let userPrompt = `## Base Agent Instructions
|
|
569
|
+
|
|
570
|
+
${baseAgentInstructions}
|
|
571
|
+
|
|
572
|
+
## Lessons to Integrate
|
|
573
|
+
|
|
574
|
+
${JSON.stringify(lessonsWithComments, null, 2)}`;
|
|
575
|
+
|
|
576
|
+
// Add additional system prompt if provided
|
|
577
|
+
if (additionalSystemPrompt) {
|
|
578
|
+
userPrompt += `
|
|
579
|
+
|
|
580
|
+
## Additional Instructions
|
|
581
|
+
|
|
582
|
+
${additionalSystemPrompt}`;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
userPrompt += `
|
|
586
|
+
|
|
587
|
+
Please rewrite and compile this into unified, cohesive Effective Agent Instructions.`;
|
|
588
|
+
|
|
589
|
+
const { text: effectiveAgentInstructions } = await llmService.generateText([
|
|
590
|
+
{ role: "system", content: systemPrompt },
|
|
591
|
+
{ role: "user", content: userPrompt },
|
|
592
|
+
]);
|
|
593
|
+
|
|
594
|
+
logger.info("PromptCompilerService: compiled Effective Agent Instructions successfully", {
|
|
595
|
+
agentPubkey: this.agentPubkey.substring(0, 8),
|
|
596
|
+
lessonsCount: lessons.length,
|
|
597
|
+
commentsCount: this.getTotalCommentsCount(),
|
|
598
|
+
baseInstructionsLength: baseAgentInstructions.length,
|
|
599
|
+
effectiveInstructionsLength: effectiveAgentInstructions.length,
|
|
600
|
+
hasAdditionalPrompt: !!additionalSystemPrompt,
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
return effectiveAgentInstructions;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// =====================================================================================
|
|
607
|
+
// EAGER COMPILATION API (TIN-10 Enhancement)
|
|
608
|
+
// =====================================================================================
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Initialize the compiler with base agent instructions and current lessons.
|
|
612
|
+
* This MUST be called before triggerCompilation() or getEffectiveInstructionsSync().
|
|
613
|
+
*
|
|
614
|
+
* @param baseAgentInstructions The Base Agent Instructions from agent.instructions
|
|
615
|
+
* @param lessons The agent's current lessons
|
|
616
|
+
* @param agentDefinitionEventId Optional event ID for cache hash (for non-local agents)
|
|
617
|
+
*/
|
|
618
|
+
async initialize(
|
|
619
|
+
baseAgentInstructions: string,
|
|
620
|
+
lessons: NDKAgentLesson[],
|
|
621
|
+
agentDefinitionEventId?: string
|
|
622
|
+
): Promise<void> {
|
|
623
|
+
const tracer = trace.getTracer("tenex.prompt-compiler");
|
|
624
|
+
|
|
625
|
+
return tracer.startActiveSpan("tenex.prompt_compilation.initialize", async (span) => {
|
|
626
|
+
span.setAttribute("agent.pubkey", this.agentPubkey.substring(0, 8));
|
|
627
|
+
|
|
628
|
+
this.baseAgentInstructions = baseAgentInstructions;
|
|
629
|
+
this.lessons = lessons;
|
|
630
|
+
this.agentDefinitionEventId = agentDefinitionEventId;
|
|
631
|
+
this.initialized = true;
|
|
632
|
+
|
|
633
|
+
// Try to load existing cache from disk into memory
|
|
634
|
+
const cached = await this.readCache();
|
|
635
|
+
if (cached) {
|
|
636
|
+
// Validate the cache is for the same inputs
|
|
637
|
+
const cacheHash = this.hashString(JSON.stringify({
|
|
638
|
+
agentDefinitionEventId: agentDefinitionEventId || null,
|
|
639
|
+
baseAgentInstructions,
|
|
640
|
+
additionalSystemPrompt: null,
|
|
641
|
+
}));
|
|
642
|
+
|
|
643
|
+
if (cached.cacheInputsHash === cacheHash) {
|
|
644
|
+
this.cachedEffectiveInstructions = cached;
|
|
645
|
+
this.compilationStatus = "completed";
|
|
646
|
+
span.addEvent("tenex.prompt_compilation.cache_loaded_from_disk", {
|
|
647
|
+
"cache.timestamp": cached.timestamp,
|
|
648
|
+
"cache.max_created_at": cached.maxCreatedAt,
|
|
649
|
+
});
|
|
650
|
+
span.setAttribute("cache.loaded_from_disk", true);
|
|
651
|
+
logger.debug("PromptCompilerService: loaded valid cache into memory", {
|
|
652
|
+
agentPubkey: this.agentPubkey.substring(0, 8),
|
|
653
|
+
cacheTimestamp: cached.timestamp,
|
|
654
|
+
});
|
|
655
|
+
} else {
|
|
656
|
+
span.addEvent("tenex.prompt_compilation.cache_inputs_changed");
|
|
657
|
+
span.setAttribute("cache.loaded_from_disk", false);
|
|
658
|
+
span.setAttribute("cache.inputs_changed", true);
|
|
659
|
+
logger.debug("PromptCompilerService: cache inputs changed, will need recompilation", {
|
|
660
|
+
agentPubkey: this.agentPubkey.substring(0, 8),
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
} else {
|
|
664
|
+
span.setAttribute("cache.loaded_from_disk", false);
|
|
665
|
+
span.setAttribute("cache.exists", false);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
span.end();
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Trigger compilation in the background (fire and forget).
|
|
674
|
+
* This is the key method for EAGER compilation - called at project startup.
|
|
675
|
+
* Does NOT block - compilation happens asynchronously.
|
|
676
|
+
*
|
|
677
|
+
* Safe to call multiple times - uses debouncing and pendingRecompile to ensure
|
|
678
|
+
* no triggers are dropped. If a trigger arrives during active compilation or
|
|
679
|
+
* within the debounce window, a follow-up compilation will be scheduled.
|
|
680
|
+
*/
|
|
681
|
+
triggerCompilation(): void {
|
|
682
|
+
// Guard: ensure initialize() was called
|
|
683
|
+
if (!this.initialized) {
|
|
684
|
+
logger.warn("PromptCompilerService: triggerCompilation called before initialize()", {
|
|
685
|
+
agentPubkey: this.agentPubkey.substring(0, 8),
|
|
686
|
+
});
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const tracer = trace.getTracer("tenex");
|
|
691
|
+
|
|
692
|
+
// Debounce rapid triggers (e.g., multiple lessons arriving quickly)
|
|
693
|
+
const now = Date.now();
|
|
694
|
+
const timeSinceLastTrigger = now - this.lastCompilationTrigger;
|
|
695
|
+
if (timeSinceLastTrigger < PromptCompilerService.COMPILATION_DEBOUNCE_MS) {
|
|
696
|
+
// Within debounce window
|
|
697
|
+
logger.debug("PromptCompilerService: debouncing compilation trigger", {
|
|
698
|
+
agentPubkey: this.agentPubkey.substring(0, 8),
|
|
699
|
+
timeSinceLastTrigger,
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
// If a compilation is in progress, just mark pending (will be handled in finally block)
|
|
703
|
+
if (this.compilationStatus === "compiling") {
|
|
704
|
+
this.pendingRecompile = true;
|
|
705
|
+
logger.debug("PromptCompilerService: compilation in progress, marked pending recompile", {
|
|
706
|
+
agentPubkey: this.agentPubkey.substring(0, 8),
|
|
707
|
+
});
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// If idle (no active compilation), schedule a timer to compile after debounce window expires
|
|
712
|
+
// This ensures triggers aren't dropped when the system is idle
|
|
713
|
+
if (!this.debounceTimer) {
|
|
714
|
+
const remainingDebounce = PromptCompilerService.COMPILATION_DEBOUNCE_MS - timeSinceLastTrigger;
|
|
715
|
+
this.debounceTimer = setTimeout(() => {
|
|
716
|
+
this.debounceTimer = null;
|
|
717
|
+
logger.debug("PromptCompilerService: debounce timer fired, triggering compilation", {
|
|
718
|
+
agentPubkey: this.agentPubkey.substring(0, 8),
|
|
719
|
+
});
|
|
720
|
+
this.triggerCompilation();
|
|
721
|
+
}, remainingDebounce);
|
|
722
|
+
logger.debug("PromptCompilerService: scheduled debounce timer", {
|
|
723
|
+
agentPubkey: this.agentPubkey.substring(0, 8),
|
|
724
|
+
remainingDebounceMs: remainingDebounce,
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
this.lastCompilationTrigger = now;
|
|
730
|
+
|
|
731
|
+
// Clear any pending debounce timer since we're about to compile now
|
|
732
|
+
if (this.debounceTimer) {
|
|
733
|
+
clearTimeout(this.debounceTimer);
|
|
734
|
+
this.debounceTimer = null;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// If already compiling, mark pending and let the running compilation handle it
|
|
738
|
+
if (this.compilationStatus === "compiling") {
|
|
739
|
+
this.pendingRecompile = true;
|
|
740
|
+
logger.debug("PromptCompilerService: compilation in progress, marked pending recompile", {
|
|
741
|
+
agentPubkey: this.agentPubkey.substring(0, 8),
|
|
742
|
+
});
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// Clear pending flag since we're about to compile
|
|
747
|
+
this.pendingRecompile = false;
|
|
748
|
+
|
|
749
|
+
// Fire and forget - don't await
|
|
750
|
+
this.currentCompilationPromise = tracer.startActiveSpan("tenex.prompt_compilation", async (span) => {
|
|
751
|
+
span.setAttribute("agent.pubkey", this.agentPubkey.substring(0, 8));
|
|
752
|
+
|
|
753
|
+
try {
|
|
754
|
+
span.addEvent("tenex.prompt_compilation.started");
|
|
755
|
+
this.compilationStatus = "compiling";
|
|
756
|
+
|
|
757
|
+
const startTime = Date.now();
|
|
758
|
+
|
|
759
|
+
// Wait for EOSE with a timeout to ensure we have comments
|
|
760
|
+
try {
|
|
761
|
+
await Promise.race([
|
|
762
|
+
this.waitForEOSE(),
|
|
763
|
+
new Promise<void>((_, reject) =>
|
|
764
|
+
setTimeout(() => reject(new Error("EOSE timeout")), 5000)
|
|
765
|
+
),
|
|
766
|
+
]);
|
|
767
|
+
} catch {
|
|
768
|
+
// Continue without all comments
|
|
769
|
+
logger.debug("PromptCompilerService: EOSE timeout during eager compilation", {
|
|
770
|
+
agentPubkey: this.agentPubkey.substring(0, 8),
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// Run the actual compilation
|
|
775
|
+
const effectiveInstructions = await this.compile(
|
|
776
|
+
this.baseAgentInstructions,
|
|
777
|
+
this.agentDefinitionEventId
|
|
778
|
+
);
|
|
779
|
+
|
|
780
|
+
// Update in-memory cache
|
|
781
|
+
const maxCreatedAt = this.calculateMaxCreatedAt(this.lessons);
|
|
782
|
+
const cacheHash = this.hashString(JSON.stringify({
|
|
783
|
+
agentDefinitionEventId: this.agentDefinitionEventId || null,
|
|
784
|
+
baseAgentInstructions: this.baseAgentInstructions,
|
|
785
|
+
additionalSystemPrompt: null,
|
|
786
|
+
}));
|
|
787
|
+
|
|
788
|
+
this.cachedEffectiveInstructions = {
|
|
789
|
+
effectiveAgentInstructions: effectiveInstructions,
|
|
790
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
791
|
+
maxCreatedAt,
|
|
792
|
+
cacheInputsHash: cacheHash,
|
|
793
|
+
};
|
|
794
|
+
|
|
795
|
+
this.compilationStatus = "completed";
|
|
796
|
+
|
|
797
|
+
const duration = Date.now() - startTime;
|
|
798
|
+
span.addEvent("tenex.prompt_compilation.completed", {
|
|
799
|
+
"compilation.duration_ms": duration,
|
|
800
|
+
"compilation.lessons_count": this.lessons.length,
|
|
801
|
+
});
|
|
802
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
803
|
+
|
|
804
|
+
logger.info("PromptCompilerService: eager compilation completed", {
|
|
805
|
+
agentPubkey: this.agentPubkey.substring(0, 8),
|
|
806
|
+
durationMs: duration,
|
|
807
|
+
lessonsCount: this.lessons.length,
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
// Publish kind:0 with compiled instructions (fire-and-forget)
|
|
811
|
+
// Only publish if agent metadata was provided
|
|
812
|
+
if (this.agentSigner && this.agentName && this.agentRole && this.projectTitle) {
|
|
813
|
+
void AgentProfilePublisher.publishCompiledInstructions(
|
|
814
|
+
this.agentSigner,
|
|
815
|
+
effectiveInstructions,
|
|
816
|
+
this.agentName,
|
|
817
|
+
this.agentRole,
|
|
818
|
+
this.projectTitle
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
} catch (error) {
|
|
822
|
+
this.compilationStatus = "error";
|
|
823
|
+
span.addEvent("tenex.prompt_compilation.error", {
|
|
824
|
+
"error.message": error instanceof Error ? error.message : String(error),
|
|
825
|
+
});
|
|
826
|
+
span.setStatus({
|
|
827
|
+
code: SpanStatusCode.ERROR,
|
|
828
|
+
message: error instanceof Error ? error.message : String(error),
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
logger.error("PromptCompilerService: eager compilation failed", {
|
|
832
|
+
agentPubkey: this.agentPubkey.substring(0, 8),
|
|
833
|
+
error: error instanceof Error ? error.message : String(error),
|
|
834
|
+
});
|
|
835
|
+
} finally {
|
|
836
|
+
span.end();
|
|
837
|
+
this.currentCompilationPromise = null;
|
|
838
|
+
|
|
839
|
+
// Check if a recompile was requested while we were compiling
|
|
840
|
+
if (this.pendingRecompile) {
|
|
841
|
+
logger.debug("PromptCompilerService: pending recompile detected, scheduling follow-up", {
|
|
842
|
+
agentPubkey: this.agentPubkey.substring(0, 8),
|
|
843
|
+
});
|
|
844
|
+
// Reset timestamp to allow immediate trigger
|
|
845
|
+
this.lastCompilationTrigger = 0;
|
|
846
|
+
// Use setImmediate to avoid deep recursion
|
|
847
|
+
setImmediate(() => this.triggerCompilation());
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
/**
|
|
854
|
+
* Get the effective instructions SYNCHRONOUSLY.
|
|
855
|
+
* This is the key method for agent execution - NEVER blocks on compilation.
|
|
856
|
+
*
|
|
857
|
+
* Priority order:
|
|
858
|
+
* 1. Fresh compiled instructions (cache is valid)
|
|
859
|
+
* 2. Stale compiled instructions (cache exists but is stale - triggers background recompile)
|
|
860
|
+
* 3. Base instructions (no cache available - compilation pending, in progress, or failed)
|
|
861
|
+
*
|
|
862
|
+
* Staleness is determined by:
|
|
863
|
+
* - maxCreatedAt: new lessons have arrived since last compilation
|
|
864
|
+
* - cacheInputsHash: base instructions or agentDefinitionEventId changed
|
|
865
|
+
*
|
|
866
|
+
* Per requirements: agent should NEVER wait. We serve stale compiled instructions
|
|
867
|
+
* (which are better than base) while a recompile runs in the background.
|
|
868
|
+
*
|
|
869
|
+
* Includes telemetry to track which source was used.
|
|
870
|
+
*/
|
|
871
|
+
getEffectiveInstructionsSync(): EffectiveInstructionsResult {
|
|
872
|
+
const tracer = trace.getTracer("tenex");
|
|
873
|
+
|
|
874
|
+
// If we have cached compiled instructions, check freshness
|
|
875
|
+
if (this.cachedEffectiveInstructions) {
|
|
876
|
+
const cached = this.cachedEffectiveInstructions;
|
|
877
|
+
// Check if cache is still fresh by comparing maxCreatedAt AND input hash
|
|
878
|
+
const currentMaxCreatedAt = this.calculateMaxCreatedAt(this.lessons);
|
|
879
|
+
|
|
880
|
+
// Also check if inputs (base instructions, agentDefinitionEventId) have changed
|
|
881
|
+
const currentInputsHash = this.hashString(JSON.stringify({
|
|
882
|
+
agentDefinitionEventId: this.agentDefinitionEventId || null,
|
|
883
|
+
baseAgentInstructions: this.baseAgentInstructions,
|
|
884
|
+
additionalSystemPrompt: null,
|
|
885
|
+
}));
|
|
886
|
+
const inputsMatch = cached.cacheInputsHash === currentInputsHash;
|
|
887
|
+
|
|
888
|
+
const cacheIsFresh = inputsMatch &&
|
|
889
|
+
cached.maxCreatedAt >= currentMaxCreatedAt;
|
|
890
|
+
|
|
891
|
+
if (cacheIsFresh) {
|
|
892
|
+
// Cache is fresh - return immediately (no span needed for happy path)
|
|
893
|
+
logger.debug("PromptCompilerService: returning fresh cached effective instructions", {
|
|
894
|
+
agentPubkey: this.agentPubkey.substring(0, 8),
|
|
895
|
+
cacheTimestamp: cached.timestamp,
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
return {
|
|
899
|
+
instructions: cached.effectiveAgentInstructions,
|
|
900
|
+
isCompiled: true,
|
|
901
|
+
compiledAt: cached.timestamp,
|
|
902
|
+
source: "compiled_cache",
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// Cache is stale - but we can still serve it while recompile is in-flight
|
|
907
|
+
// This follows the requirement: "agent should never wait"
|
|
908
|
+
// Cache can be stale due to: new lessons (maxCreatedAt) OR changed inputs (base instructions)
|
|
909
|
+
const staleReason = !inputsMatch ? "inputs_changed" : "new_lessons";
|
|
910
|
+
logger.debug("PromptCompilerService: cache is stale, serving stale while triggering recompile", {
|
|
911
|
+
agentPubkey: this.agentPubkey.substring(0, 8),
|
|
912
|
+
staleReason,
|
|
913
|
+
inputsMatch,
|
|
914
|
+
cachedMaxCreatedAt: cached.maxCreatedAt,
|
|
915
|
+
currentMaxCreatedAt,
|
|
916
|
+
compilationStatus: this.compilationStatus,
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
// Trigger background recompile if not already compiling
|
|
920
|
+
if (this.compilationStatus !== "compiling") {
|
|
921
|
+
this.triggerCompilation();
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// Return stale cache (better than base instructions)
|
|
925
|
+
tracer.startActiveSpan("tenex.prompt_compilation.cache_stale", (span) => {
|
|
926
|
+
span.setAttribute("compilation.timestamp", cached.timestamp);
|
|
927
|
+
span.setAttribute("compilation.is_compiled", true);
|
|
928
|
+
span.setAttribute("compilation.is_stale", true);
|
|
929
|
+
span.setAttribute("compilation.stale_reason", staleReason);
|
|
930
|
+
span.setAttribute("compilation.inputs_match", inputsMatch);
|
|
931
|
+
span.setAttribute("compilation.cached_max_created_at", cached.maxCreatedAt);
|
|
932
|
+
span.setAttribute("compilation.current_max_created_at", currentMaxCreatedAt);
|
|
933
|
+
span.end();
|
|
934
|
+
});
|
|
935
|
+
|
|
936
|
+
return {
|
|
937
|
+
instructions: cached.effectiveAgentInstructions,
|
|
938
|
+
isCompiled: true,
|
|
939
|
+
compiledAt: cached.timestamp,
|
|
940
|
+
source: "compiled_cache",
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// No compiled instructions available - return base instructions
|
|
945
|
+
// This happens when:
|
|
946
|
+
// 1. Compilation hasn't started yet
|
|
947
|
+
// 2. Compilation is in progress
|
|
948
|
+
// 3. Compilation failed
|
|
949
|
+
|
|
950
|
+
const source = this.compilationStatus === "compiling"
|
|
951
|
+
? "compilation_in_progress"
|
|
952
|
+
: "base_instructions";
|
|
953
|
+
|
|
954
|
+
tracer.startActiveSpan("tenex.prompt_compilation.fallback_to_base", (span) => {
|
|
955
|
+
span.setAttribute("compilation.status", this.compilationStatus);
|
|
956
|
+
span.setAttribute("compilation.is_compiled", false);
|
|
957
|
+
span.setAttribute("compilation.source", source);
|
|
958
|
+
span.end();
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
logger.debug("PromptCompilerService: returning base instructions (no compiled cache)", {
|
|
962
|
+
agentPubkey: this.agentPubkey.substring(0, 8),
|
|
963
|
+
compilationStatus: this.compilationStatus,
|
|
964
|
+
source,
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
return {
|
|
968
|
+
instructions: this.baseAgentInstructions,
|
|
969
|
+
isCompiled: false,
|
|
970
|
+
source,
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
/**
|
|
975
|
+
* Get the current compilation status
|
|
976
|
+
*/
|
|
977
|
+
getCompilationStatus(): CompilationStatus {
|
|
978
|
+
return this.compilationStatus;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
/**
|
|
982
|
+
* Check if compiled instructions are available
|
|
983
|
+
*/
|
|
984
|
+
hasCompiledInstructions(): boolean {
|
|
985
|
+
return this.cachedEffectiveInstructions !== null;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
/**
|
|
989
|
+
* Called when a new lesson arrives for this agent.
|
|
990
|
+
* Triggers recompilation in the background.
|
|
991
|
+
*/
|
|
992
|
+
onLessonArrived(): void {
|
|
993
|
+
const tracer = trace.getTracer("tenex.prompt-compiler");
|
|
994
|
+
tracer.startActiveSpan("tenex.prompt_compilation.lesson_trigger", (span) => {
|
|
995
|
+
span.setAttribute("agent.pubkey", this.agentPubkey.substring(0, 8));
|
|
996
|
+
span.setAttribute("trigger.source", "new_lesson");
|
|
997
|
+
span.end();
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
logger.debug("PromptCompilerService: new lesson arrived, triggering recompilation", {
|
|
1001
|
+
agentPubkey: this.agentPubkey.substring(0, 8),
|
|
1002
|
+
});
|
|
1003
|
+
this.triggerCompilation();
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
/**
|
|
1007
|
+
* Called when a lesson is deleted for this agent.
|
|
1008
|
+
* Triggers recompilation in the background to remove the deleted lesson from compiled prompts.
|
|
1009
|
+
*/
|
|
1010
|
+
onLessonDeleted(): void {
|
|
1011
|
+
const tracer = trace.getTracer("tenex.prompt-compiler");
|
|
1012
|
+
tracer.startActiveSpan("tenex.prompt_compilation.lesson_deleted_trigger", (span) => {
|
|
1013
|
+
span.setAttribute("agent.pubkey", this.agentPubkey.substring(0, 8));
|
|
1014
|
+
span.setAttribute("trigger.source", "lesson_deleted");
|
|
1015
|
+
span.end();
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
logger.debug("PromptCompilerService: lesson deleted, triggering recompilation", {
|
|
1019
|
+
agentPubkey: this.agentPubkey.substring(0, 8),
|
|
1020
|
+
});
|
|
1021
|
+
this.triggerCompilation();
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
/**
|
|
1025
|
+
* Update the lessons for this compiler.
|
|
1026
|
+
* Called by the cache system when lessons may have changed since the compiler was created.
|
|
1027
|
+
* Triggers recompilation if the lesson set has changed.
|
|
1028
|
+
*
|
|
1029
|
+
* @param newLessons The updated set of lessons from ProjectContext
|
|
1030
|
+
*/
|
|
1031
|
+
updateLessons(newLessons: NDKAgentLesson[]): void {
|
|
1032
|
+
// Quick check: if counts differ, definitely changed
|
|
1033
|
+
const previousCount = this.lessons.length;
|
|
1034
|
+
if (newLessons.length !== previousCount) {
|
|
1035
|
+
this.lessons = newLessons;
|
|
1036
|
+
logger.debug("PromptCompilerService: lessons updated (count changed)", {
|
|
1037
|
+
agentPubkey: this.agentPubkey.substring(0, 8),
|
|
1038
|
+
previousCount,
|
|
1039
|
+
newCount: newLessons.length,
|
|
1040
|
+
});
|
|
1041
|
+
this.triggerCompilation();
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// Deep check: compare lesson IDs (lessons are ordered most recent first)
|
|
1046
|
+
const currentIds = new Set(this.lessons.map((l) => l.id));
|
|
1047
|
+
const newIds = new Set(newLessons.map((l) => l.id));
|
|
1048
|
+
|
|
1049
|
+
const changed = newLessons.some((l) => !currentIds.has(l.id)) ||
|
|
1050
|
+
this.lessons.some((l) => !newIds.has(l.id));
|
|
1051
|
+
|
|
1052
|
+
if (changed) {
|
|
1053
|
+
this.lessons = newLessons;
|
|
1054
|
+
logger.debug("PromptCompilerService: lessons updated (IDs changed)", {
|
|
1055
|
+
agentPubkey: this.agentPubkey.substring(0, 8),
|
|
1056
|
+
lessonsCount: newLessons.length,
|
|
1057
|
+
});
|
|
1058
|
+
this.triggerCompilation();
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
/**
|
|
1063
|
+
* Wait for the current compilation to complete (for testing purposes).
|
|
1064
|
+
* Returns immediately if no compilation is in progress.
|
|
1065
|
+
*/
|
|
1066
|
+
async waitForCompilation(): Promise<void> {
|
|
1067
|
+
if (this.currentCompilationPromise) {
|
|
1068
|
+
await this.currentCompilationPromise;
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// =====================================================================================
|
|
1073
|
+
// CACHE MANAGEMENT
|
|
1074
|
+
// =====================================================================================
|
|
1075
|
+
|
|
1076
|
+
/**
|
|
1077
|
+
* Get the cache file path for a given agent pubkey.
|
|
1078
|
+
* Static variant for external consumers that need to read the cache directly.
|
|
1079
|
+
*/
|
|
1080
|
+
static getCachePathForAgent(agentPubkey: string): string {
|
|
1081
|
+
const cacheDir = path.join(config.getConfigPath(), "agents", "prompts");
|
|
1082
|
+
return path.join(cacheDir, `${agentPubkey}.json`);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
/**
|
|
1086
|
+
* Get the cache file path for this agent
|
|
1087
|
+
*/
|
|
1088
|
+
private getCachePath(): string {
|
|
1089
|
+
return PromptCompilerService.getCachePathForAgent(this.agentPubkey);
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
/**
|
|
1093
|
+
* Read cache entry from disk
|
|
1094
|
+
*/
|
|
1095
|
+
private async readCache(): Promise<EffectiveInstructionsCacheEntry | null> {
|
|
1096
|
+
try {
|
|
1097
|
+
const cachePath = this.getCachePath();
|
|
1098
|
+
const data = await fs.readFile(cachePath, "utf-8");
|
|
1099
|
+
return JSON.parse(data) as EffectiveInstructionsCacheEntry;
|
|
1100
|
+
} catch {
|
|
1101
|
+
// File doesn't exist or is invalid - that's fine
|
|
1102
|
+
return null;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
/**
|
|
1107
|
+
* Write cache entry to disk
|
|
1108
|
+
*/
|
|
1109
|
+
private async writeCache(entry: EffectiveInstructionsCacheEntry): Promise<void> {
|
|
1110
|
+
try {
|
|
1111
|
+
await fs.mkdir(this.cacheDir, { recursive: true });
|
|
1112
|
+
const cachePath = this.getCachePath();
|
|
1113
|
+
await fs.writeFile(cachePath, JSON.stringify(entry, null, 2));
|
|
1114
|
+
|
|
1115
|
+
logger.debug("PromptCompilerService: wrote cache", {
|
|
1116
|
+
agentPubkey: this.agentPubkey.substring(0, 8),
|
|
1117
|
+
cachePath,
|
|
1118
|
+
});
|
|
1119
|
+
} catch (error) {
|
|
1120
|
+
logger.error("PromptCompilerService: failed to write cache", {
|
|
1121
|
+
agentPubkey: this.agentPubkey.substring(0, 8),
|
|
1122
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// =====================================================================================
|
|
1128
|
+
// UTILITIES
|
|
1129
|
+
// =====================================================================================
|
|
1130
|
+
|
|
1131
|
+
/**
|
|
1132
|
+
* Hash a string using SHA-256
|
|
1133
|
+
*/
|
|
1134
|
+
private hashString(input: string): string {
|
|
1135
|
+
return crypto.createHash("sha256").update(input).digest("hex");
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
// =====================================================================================
|
|
1140
|
+
// EXPORTS
|
|
1141
|
+
// =====================================================================================
|
|
1142
|
+
|
|
1143
|
+
export default PromptCompilerService;
|