@tenex-chat/backend 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (427) hide show
  1. package/README.md +194 -0
  2. package/dist/backend-wrapper.cjs +3 -0
  3. package/dist/src/index.js +331928 -0
  4. package/package.json +103 -0
  5. package/src/agents/AgentRegistry.ts +418 -0
  6. package/src/agents/AgentStorage.ts +1133 -0
  7. package/src/agents/ConfigResolver.ts +229 -0
  8. package/src/agents/agent-installer.ts +236 -0
  9. package/src/agents/agent-loader.ts +241 -0
  10. package/src/agents/constants.ts +82 -0
  11. package/src/agents/errors.ts +48 -0
  12. package/src/agents/execution/AgentExecutor.ts +561 -0
  13. package/src/agents/execution/ExecutionContextFactory.ts +112 -0
  14. package/src/agents/execution/MessageCompiler.ts +597 -0
  15. package/src/agents/execution/MessageSyncer.ts +100 -0
  16. package/src/agents/execution/PostCompletionChecker.ts +278 -0
  17. package/src/agents/execution/ProgressMonitor.ts +50 -0
  18. package/src/agents/execution/RALResolver.ts +177 -0
  19. package/src/agents/execution/SessionManager.ts +181 -0
  20. package/src/agents/execution/StreamCallbacks.ts +312 -0
  21. package/src/agents/execution/StreamExecutionHandler.ts +579 -0
  22. package/src/agents/execution/StreamSetup.ts +313 -0
  23. package/src/agents/execution/ToolEventHandlers.ts +239 -0
  24. package/src/agents/execution/ToolExecutionTracker.ts +498 -0
  25. package/src/agents/execution/ToolResultUtils.ts +97 -0
  26. package/src/agents/execution/ToolSupervisionWrapper.ts +174 -0
  27. package/src/agents/execution/constants.ts +16 -0
  28. package/src/agents/execution/index.ts +3 -0
  29. package/src/agents/execution/types.ts +96 -0
  30. package/src/agents/execution/utils.ts +26 -0
  31. package/src/agents/index.ts +4 -0
  32. package/src/agents/script-installer.ts +266 -0
  33. package/src/agents/supervision/SupervisorLLMService.ts +253 -0
  34. package/src/agents/supervision/SupervisorOrchestrator.ts +471 -0
  35. package/src/agents/supervision/heuristics/ConsecutiveToolsWithoutTodoHeuristic.ts +73 -0
  36. package/src/agents/supervision/heuristics/DelegationClaimHeuristic.ts +80 -0
  37. package/src/agents/supervision/heuristics/HeuristicRegistry.ts +114 -0
  38. package/src/agents/supervision/heuristics/PendingTodosHeuristic.ts +93 -0
  39. package/src/agents/supervision/heuristics/SilentAgentHeuristic.ts +54 -0
  40. package/src/agents/supervision/heuristics/index.ts +5 -0
  41. package/src/agents/supervision/index.ts +28 -0
  42. package/src/agents/supervision/registerHeuristics.ts +110 -0
  43. package/src/agents/supervision/supervisionHealthCheck.ts +123 -0
  44. package/src/agents/supervision/types.ts +171 -0
  45. package/src/agents/tool-names.ts +46 -0
  46. package/src/agents/tool-normalization.ts +184 -0
  47. package/src/agents/types/index.ts +2 -0
  48. package/src/agents/types/runtime.ts +74 -0
  49. package/src/agents/types/storage.ts +145 -0
  50. package/src/commands/agent/import/index.ts +6 -0
  51. package/src/commands/agent/import/openclaw-distiller.ts +57 -0
  52. package/src/commands/agent/import/openclaw-reader.ts +141 -0
  53. package/src/commands/agent/import/openclaw.ts +154 -0
  54. package/src/commands/agent/index.ts +6 -0
  55. package/src/commands/agent.ts +215 -0
  56. package/src/commands/daemon.ts +198 -0
  57. package/src/commands/doctor.ts +134 -0
  58. package/src/commands/setup/embed.ts +228 -0
  59. package/src/commands/setup/global-system-prompt.ts +223 -0
  60. package/src/commands/setup/image.ts +179 -0
  61. package/src/commands/setup/index.ts +16 -0
  62. package/src/commands/setup/interactive.ts +95 -0
  63. package/src/commands/setup/llm.ts +38 -0
  64. package/src/commands/setup/onboarding.ts +294 -0
  65. package/src/commands/setup/providers.ts +27 -0
  66. package/src/constants.ts +34 -0
  67. package/src/conversations/ConversationDiskReader.ts +148 -0
  68. package/src/conversations/ConversationRegistry.ts +728 -0
  69. package/src/conversations/ConversationStore.ts +868 -0
  70. package/src/conversations/MessageBuilder.ts +866 -0
  71. package/src/conversations/executionTime.ts +62 -0
  72. package/src/conversations/formatters/DelegationXmlFormatter.ts +64 -0
  73. package/src/conversations/formatters/ThreadedConversationFormatter.ts +303 -0
  74. package/src/conversations/formatters/index.ts +9 -0
  75. package/src/conversations/formatters/utils/MessageFormatter.ts +46 -0
  76. package/src/conversations/formatters/utils/TimestampFormatter.ts +56 -0
  77. package/src/conversations/formatters/utils/TreeBuilder.ts +131 -0
  78. package/src/conversations/formatters/utils/TreeRenderer.ts +49 -0
  79. package/src/conversations/index.ts +2 -0
  80. package/src/conversations/persistence/ToolMessageStorage.ts +143 -0
  81. package/src/conversations/search/ConversationIndexManager.ts +393 -0
  82. package/src/conversations/search/QueryParser.ts +114 -0
  83. package/src/conversations/search/SearchEngine.ts +175 -0
  84. package/src/conversations/search/SnippetExtractor.ts +345 -0
  85. package/src/conversations/search/embeddings/ConversationEmbeddingService.ts +484 -0
  86. package/src/conversations/search/embeddings/ConversationIndexingJob.ts +320 -0
  87. package/src/conversations/search/embeddings/IndexingStateManager.ts +338 -0
  88. package/src/conversations/search/embeddings/index.ts +18 -0
  89. package/src/conversations/search/index.ts +49 -0
  90. package/src/conversations/search/types.ts +124 -0
  91. package/src/conversations/services/CategoryManager.ts +160 -0
  92. package/src/conversations/services/ConversationResolver.ts +296 -0
  93. package/src/conversations/services/ConversationSummarizer.ts +234 -0
  94. package/src/conversations/services/MetadataDebounceManager.ts +188 -0
  95. package/src/conversations/services/index.ts +2 -0
  96. package/src/conversations/types.ts +148 -0
  97. package/src/conversations/utils/content-utils.ts +69 -0
  98. package/src/conversations/utils/image-placeholder.ts +281 -0
  99. package/src/conversations/utils/image-url-utils.ts +171 -0
  100. package/src/conversations/utils/multimodal-content.ts +90 -0
  101. package/src/conversations/utils/tool-result-truncator.ts +159 -0
  102. package/src/daemon/Daemon.ts +1883 -0
  103. package/src/daemon/ProjectRuntime.ts +657 -0
  104. package/src/daemon/RestartState.ts +152 -0
  105. package/src/daemon/RuntimeLifecycle.ts +268 -0
  106. package/src/daemon/SubscriptionManager.ts +305 -0
  107. package/src/daemon/UnixSocketTransport.ts +318 -0
  108. package/src/daemon/filters/SubscriptionFilterBuilder.ts +119 -0
  109. package/src/daemon/index.ts +9 -0
  110. package/src/daemon/routing/DaemonRouter.ts +491 -0
  111. package/src/daemon/types.ts +150 -0
  112. package/src/daemon/utils/routing-log.ts +76 -0
  113. package/src/daemon/utils/telemetry.ts +173 -0
  114. package/src/event-handler/agentDeletion.ts +383 -0
  115. package/src/event-handler/index.ts +749 -0
  116. package/src/event-handler/newConversation.ts +165 -0
  117. package/src/event-handler/project.ts +166 -0
  118. package/src/event-handler/reply.ts +18 -0
  119. package/src/events/NDKAgentDefinition.ts +292 -0
  120. package/src/events/NDKAgentLesson.ts +106 -0
  121. package/src/events/NDKEventMetadata.ts +34 -0
  122. package/src/events/NDKMCPTool.ts +60 -0
  123. package/src/events/NDKProjectStatus.ts +384 -0
  124. package/src/events/index.ts +4 -0
  125. package/src/index.ts +126 -0
  126. package/src/lib/agent-home.ts +334 -0
  127. package/src/lib/error-formatter.ts +200 -0
  128. package/src/lib/fs/filesystem.ts +128 -0
  129. package/src/lib/fs/index.ts +1 -0
  130. package/src/lib/json-parser.ts +30 -0
  131. package/src/lib/string.ts +15 -0
  132. package/src/lib/time.ts +74 -0
  133. package/src/llm/ChunkHandler.ts +277 -0
  134. package/src/llm/FinishHandler.ts +250 -0
  135. package/src/llm/LLMConfigEditor.ts +154 -0
  136. package/src/llm/LLMServiceFactory.ts +230 -0
  137. package/src/llm/MessageProcessor.ts +90 -0
  138. package/src/llm/RecordingState.ts +37 -0
  139. package/src/llm/StreamPublisher.ts +40 -0
  140. package/src/llm/TracingUtils.ts +77 -0
  141. package/src/llm/chunk-validators.ts +57 -0
  142. package/src/llm/constants.ts +6 -0
  143. package/src/llm/index.ts +12 -0
  144. package/src/llm/meta/MetaModelResolver.ts +352 -0
  145. package/src/llm/meta/index.ts +11 -0
  146. package/src/llm/middleware/flight-recorder.ts +188 -0
  147. package/src/llm/providers/MockProvider.ts +332 -0
  148. package/src/llm/providers/agent/ClaudeCodeProvider.ts +343 -0
  149. package/src/llm/providers/agent/ClaudeCodeToolsAdapter.ts +203 -0
  150. package/src/llm/providers/agent/CodexAppServerProvider.ts +214 -0
  151. package/src/llm/providers/agent/CodexAppServerToolsAdapter.ts +91 -0
  152. package/src/llm/providers/agent/index.ts +10 -0
  153. package/src/llm/providers/base/AgentProvider.ts +107 -0
  154. package/src/llm/providers/base/BaseProvider.ts +114 -0
  155. package/src/llm/providers/base/StandardProvider.ts +38 -0
  156. package/src/llm/providers/base/index.ts +9 -0
  157. package/src/llm/providers/index.ts +106 -0
  158. package/src/llm/providers/key-manager.ts +238 -0
  159. package/src/llm/providers/ollama-models.ts +105 -0
  160. package/src/llm/providers/openrouter-models.ts +102 -0
  161. package/src/llm/providers/provider-ids.ts +18 -0
  162. package/src/llm/providers/registry/ProviderRegistry.ts +414 -0
  163. package/src/llm/providers/registry/index.ts +7 -0
  164. package/src/llm/providers/standard/AnthropicProvider.ts +71 -0
  165. package/src/llm/providers/standard/OllamaProvider.ts +59 -0
  166. package/src/llm/providers/standard/OpenAIProvider.ts +44 -0
  167. package/src/llm/providers/standard/OpenRouterProvider.ts +103 -0
  168. package/src/llm/providers/standard/index.ts +10 -0
  169. package/src/llm/providers/types.ts +194 -0
  170. package/src/llm/providers/usage-metadata.ts +78 -0
  171. package/src/llm/service.ts +713 -0
  172. package/src/llm/types.ts +167 -0
  173. package/src/llm/utils/ConfigurationManager.ts +650 -0
  174. package/src/llm/utils/ConfigurationTester.ts +229 -0
  175. package/src/llm/utils/ModelSelector.ts +212 -0
  176. package/src/llm/utils/ProviderConfigUI.ts +177 -0
  177. package/src/llm/utils/claudeCodePromptCompiler.ts +141 -0
  178. package/src/llm/utils/codex-models.ts +53 -0
  179. package/src/llm/utils/context-window-cache.ts +30 -0
  180. package/src/llm/utils/models-dev-cache.ts +267 -0
  181. package/src/llm/utils/provider-setup.ts +50 -0
  182. package/src/llm/utils/tool-errors.ts +78 -0
  183. package/src/llm/utils/usage.ts +74 -0
  184. package/src/logging/EventRoutingLogger.ts +205 -0
  185. package/src/nostr/AgentEventDecoder.ts +357 -0
  186. package/src/nostr/AgentEventEncoder.ts +677 -0
  187. package/src/nostr/AgentProfilePublisher.ts +657 -0
  188. package/src/nostr/AgentPublisher.ts +437 -0
  189. package/src/nostr/BlossomService.ts +226 -0
  190. package/src/nostr/InterventionPublisher.ts +132 -0
  191. package/src/nostr/TagExtractor.ts +228 -0
  192. package/src/nostr/collectEvents.ts +83 -0
  193. package/src/nostr/constants.ts +38 -0
  194. package/src/nostr/encryption.ts +26 -0
  195. package/src/nostr/index.ts +31 -0
  196. package/src/nostr/keys.ts +17 -0
  197. package/src/nostr/kinds.ts +37 -0
  198. package/src/nostr/ndkClient.ts +72 -0
  199. package/src/nostr/relays.ts +43 -0
  200. package/src/nostr/trace-context.ts +39 -0
  201. package/src/nostr/types.ts +227 -0
  202. package/src/nostr/utils.ts +84 -0
  203. package/src/prompts/core/FragmentRegistry.ts +30 -0
  204. package/src/prompts/core/PromptBuilder.ts +98 -0
  205. package/src/prompts/core/index.ts +3 -0
  206. package/src/prompts/core/types.ts +13 -0
  207. package/src/prompts/fragments/00-global-system-prompt.ts +44 -0
  208. package/src/prompts/fragments/01-agent-identity.ts +69 -0
  209. package/src/prompts/fragments/02-agent-home-directory.ts +114 -0
  210. package/src/prompts/fragments/03-system-reminders-explanation.ts +14 -0
  211. package/src/prompts/fragments/04-relay-configuration.ts +38 -0
  212. package/src/prompts/fragments/05-delegation-chain.ts +45 -0
  213. package/src/prompts/fragments/06-agent-todos.ts +74 -0
  214. package/src/prompts/fragments/06-todo-usage-guidance.ts +34 -0
  215. package/src/prompts/fragments/07-meta-project-context.ts +234 -0
  216. package/src/prompts/fragments/08-active-conversations.ts +382 -0
  217. package/src/prompts/fragments/09-recent-conversations.ts +153 -0
  218. package/src/prompts/fragments/10-referenced-article.ts +21 -0
  219. package/src/prompts/fragments/11-nudges.ts +134 -0
  220. package/src/prompts/fragments/12-skills.ts +127 -0
  221. package/src/prompts/fragments/13-available-nudges.ts +122 -0
  222. package/src/prompts/fragments/15-available-agents.ts +53 -0
  223. package/src/prompts/fragments/16-stay-in-your-lane.ts +41 -0
  224. package/src/prompts/fragments/17-todo-before-delegation.ts +39 -0
  225. package/src/prompts/fragments/20-voice-mode.ts +62 -0
  226. package/src/prompts/fragments/22-scheduled-tasks.ts +175 -0
  227. package/src/prompts/fragments/24-retrieved-lessons.ts +26 -0
  228. package/src/prompts/fragments/25-rag-instructions.ts +333 -0
  229. package/src/prompts/fragments/26-mcp-resources.ts +237 -0
  230. package/src/prompts/fragments/27-memorized-reports.ts +77 -0
  231. package/src/prompts/fragments/28-agent-directed-monitoring.ts +32 -0
  232. package/src/prompts/fragments/29-rag-collections.ts +50 -0
  233. package/src/prompts/fragments/30-worktree-context.ts +98 -0
  234. package/src/prompts/fragments/31-agents-md-guidance.ts +96 -0
  235. package/src/prompts/fragments/32-process-metrics.ts +72 -0
  236. package/src/prompts/fragments/debug-mode.ts +48 -0
  237. package/src/prompts/fragments/delegation-completion.ts +44 -0
  238. package/src/prompts/fragments/index.ts +91 -0
  239. package/src/prompts/index.ts +21 -0
  240. package/src/prompts/utils/systemPromptBuilder.ts +777 -0
  241. package/src/scripts/migrate-prefix-index.ts +157 -0
  242. package/src/services/AgentDefinitionMonitor.ts +701 -0
  243. package/src/services/ConfigService.ts +723 -0
  244. package/src/services/CooldownRegistry.ts +199 -0
  245. package/src/services/LLMOperationsRegistry.ts +424 -0
  246. package/src/services/OwnerAgentListService.ts +354 -0
  247. package/src/services/PubkeyService.ts +308 -0
  248. package/src/services/agents/AgentMetadataStore.ts +72 -0
  249. package/src/services/agents/AgentResolution.ts +59 -0
  250. package/src/services/agents/EscalationService.ts +281 -0
  251. package/src/services/agents/NDKAgentDiscovery.ts +95 -0
  252. package/src/services/agents/index.ts +7 -0
  253. package/src/services/agents-md/AgentsMdService.ts +184 -0
  254. package/src/services/agents-md/SystemReminderInjector.ts +238 -0
  255. package/src/services/agents-md/index.ts +11 -0
  256. package/src/services/apns/APNsClient.ts +203 -0
  257. package/src/services/apns/APNsService.ts +358 -0
  258. package/src/services/apns/index.ts +11 -0
  259. package/src/services/apns/types.ts +80 -0
  260. package/src/services/compression/CompressionService.ts +445 -0
  261. package/src/services/compression/compression-schema.ts +28 -0
  262. package/src/services/compression/compression-types.ts +74 -0
  263. package/src/services/compression/compression-utils.ts +587 -0
  264. package/src/services/config/types.ts +394 -0
  265. package/src/services/dispatch/AgentDispatchService.ts +937 -0
  266. package/src/services/dispatch/AgentRouter.ts +181 -0
  267. package/src/services/dispatch/DelegationCompletionHandler.ts +232 -0
  268. package/src/services/embedding/EmbeddingProvider.ts +188 -0
  269. package/src/services/embedding/index.ts +5 -0
  270. package/src/services/event-context/EventContextService.ts +108 -0
  271. package/src/services/event-context/index.ts +2 -0
  272. package/src/services/heuristics/ContextBuilder.ts +106 -0
  273. package/src/services/heuristics/HeuristicEngine.ts +200 -0
  274. package/src/services/heuristics/formatters.ts +58 -0
  275. package/src/services/heuristics/index.ts +12 -0
  276. package/src/services/heuristics/rules/index.ts +25 -0
  277. package/src/services/heuristics/rules/todoBeforeDelegation.ts +69 -0
  278. package/src/services/heuristics/rules/todoReminderOnToolUse.ts +63 -0
  279. package/src/services/heuristics/types.ts +144 -0
  280. package/src/services/image/ImageGenerationService.ts +389 -0
  281. package/src/services/image/index.ts +12 -0
  282. package/src/services/intervention/InterventionService.ts +1352 -0
  283. package/src/services/intervention/index.ts +7 -0
  284. package/src/services/mcp/MCPManager.ts +683 -0
  285. package/src/services/mcp/McpNotificationDelivery.ts +139 -0
  286. package/src/services/mcp/McpSubscriptionService.ts +653 -0
  287. package/src/services/mcp/mcpInstaller.ts +130 -0
  288. package/src/services/nip46/Nip46SigningLog.ts +81 -0
  289. package/src/services/nip46/Nip46SigningService.ts +467 -0
  290. package/src/services/nip46/index.ts +4 -0
  291. package/src/services/nudge/NudgeService.ts +224 -0
  292. package/src/services/nudge/NudgeWhitelistService.ts +382 -0
  293. package/src/services/nudge/index.ts +5 -0
  294. package/src/services/nudge/types.ts +83 -0
  295. package/src/services/projects/ProjectContext.ts +672 -0
  296. package/src/services/projects/ProjectContextStore.ts +102 -0
  297. package/src/services/projects/index.ts +6 -0
  298. package/src/services/prompt-compiler/index.ts +15 -0
  299. package/src/services/prompt-compiler/prompt-compiler-service.ts +1143 -0
  300. package/src/services/pubkey-gate/PubkeyGateService.ts +93 -0
  301. package/src/services/pubkey-gate/index.ts +1 -0
  302. package/src/services/rag/EmbeddingProviderFactory.ts +292 -0
  303. package/src/services/rag/LanceDBMaintenanceService.ts +211 -0
  304. package/src/services/rag/RAGDatabaseService.ts +173 -0
  305. package/src/services/rag/RAGOperations.ts +682 -0
  306. package/src/services/rag/RAGService.ts +240 -0
  307. package/src/services/rag/RagSubscriptionService.ts +618 -0
  308. package/src/services/rag/rag-utils.ts +174 -0
  309. package/src/services/ral/PendingDelegationsRegistry.ts +168 -0
  310. package/src/services/ral/RALRegistry.ts +2782 -0
  311. package/src/services/ral/index.ts +4 -0
  312. package/src/services/ral/types.ts +292 -0
  313. package/src/services/reports/LocalReportStore.ts +380 -0
  314. package/src/services/reports/ReportEmbeddingService.ts +430 -0
  315. package/src/services/reports/ReportService.ts +440 -0
  316. package/src/services/reports/articleUtils.ts +52 -0
  317. package/src/services/reports/index.ts +7 -0
  318. package/src/services/scheduling/SchedulerService.ts +1057 -0
  319. package/src/services/scheduling/errors.ts +14 -0
  320. package/src/services/scheduling/index.ts +7 -0
  321. package/src/services/scheduling/utils.ts +77 -0
  322. package/src/services/search/SearchProviderRegistry.ts +78 -0
  323. package/src/services/search/UnifiedSearchService.ts +218 -0
  324. package/src/services/search/index.ts +47 -0
  325. package/src/services/search/projectFilter.ts +22 -0
  326. package/src/services/search/providers/ConversationSearchProvider.ts +48 -0
  327. package/src/services/search/providers/LessonSearchProvider.ts +75 -0
  328. package/src/services/search/providers/ReportSearchProvider.ts +49 -0
  329. package/src/services/search/types.ts +144 -0
  330. package/src/services/skill/SkillService.ts +482 -0
  331. package/src/services/skill/index.ts +2 -0
  332. package/src/services/skill/types.ts +70 -0
  333. package/src/services/status/OperationsStatusService.ts +276 -0
  334. package/src/services/status/ProjectStatusService.ts +522 -0
  335. package/src/services/status/index.ts +11 -0
  336. package/src/services/storage/PrefixKVStore.ts +242 -0
  337. package/src/services/storage/index.ts +1 -0
  338. package/src/services/system-reminder/SystemReminderUtils.ts +96 -0
  339. package/src/services/system-reminder/index.ts +7 -0
  340. package/src/services/trust-pubkeys/TrustPubkeyService.ts +325 -0
  341. package/src/services/trust-pubkeys/index.ts +2 -0
  342. package/src/telemetry/ConversationSpanManager.ts +111 -0
  343. package/src/telemetry/EventLoopMonitor.ts +206 -0
  344. package/src/telemetry/LLMSpanRegistry.ts +20 -0
  345. package/src/telemetry/NostrSpanProcessor.ts +89 -0
  346. package/src/telemetry/ToolCallSpanProcessor.ts +66 -0
  347. package/src/telemetry/diagnostics.ts +27 -0
  348. package/src/telemetry/setup.ts +120 -0
  349. package/src/tools/implementations/agents_discover.ts +121 -0
  350. package/src/tools/implementations/agents_hire.ts +127 -0
  351. package/src/tools/implementations/agents_list.ts +96 -0
  352. package/src/tools/implementations/agents_publish.ts +611 -0
  353. package/src/tools/implementations/agents_read.ts +173 -0
  354. package/src/tools/implementations/agents_write.ts +200 -0
  355. package/src/tools/implementations/ask.ts +411 -0
  356. package/src/tools/implementations/change_model.ts +141 -0
  357. package/src/tools/implementations/conversation_get.ts +661 -0
  358. package/src/tools/implementations/conversation_list.ts +377 -0
  359. package/src/tools/implementations/conversation_search.ts +370 -0
  360. package/src/tools/implementations/delegate.ts +327 -0
  361. package/src/tools/implementations/delegate_crossproject.ts +209 -0
  362. package/src/tools/implementations/delegate_followup.ts +300 -0
  363. package/src/tools/implementations/fs_edit.ts +162 -0
  364. package/src/tools/implementations/fs_glob.ts +182 -0
  365. package/src/tools/implementations/fs_grep.ts +513 -0
  366. package/src/tools/implementations/fs_read.ts +332 -0
  367. package/src/tools/implementations/fs_write.ts +113 -0
  368. package/src/tools/implementations/generate_image.ts +259 -0
  369. package/src/tools/implementations/home_fs.ts +515 -0
  370. package/src/tools/implementations/kill.ts +651 -0
  371. package/src/tools/implementations/learn.ts +166 -0
  372. package/src/tools/implementations/lesson-formatter.ts +38 -0
  373. package/src/tools/implementations/lesson_delete.ts +164 -0
  374. package/src/tools/implementations/lesson_get.ts +105 -0
  375. package/src/tools/implementations/lessons_list.ts +153 -0
  376. package/src/tools/implementations/mcp_resource_read.ts +161 -0
  377. package/src/tools/implementations/mcp_subscribe.ts +158 -0
  378. package/src/tools/implementations/mcp_subscription_stop.ts +85 -0
  379. package/src/tools/implementations/nostr_fetch.ts +149 -0
  380. package/src/tools/implementations/nostr_publish_as_user.ts +353 -0
  381. package/src/tools/implementations/project_list.ts +146 -0
  382. package/src/tools/implementations/rag_add_documents.ts +573 -0
  383. package/src/tools/implementations/rag_create_collection.ts +65 -0
  384. package/src/tools/implementations/rag_delete_collection.ts +68 -0
  385. package/src/tools/implementations/rag_list_collections.ts +77 -0
  386. package/src/tools/implementations/rag_query.ts +107 -0
  387. package/src/tools/implementations/rag_subscription_create.ts +105 -0
  388. package/src/tools/implementations/rag_subscription_delete.ts +80 -0
  389. package/src/tools/implementations/rag_subscription_get.ts +123 -0
  390. package/src/tools/implementations/rag_subscription_list.ts +128 -0
  391. package/src/tools/implementations/report_delete.ts +79 -0
  392. package/src/tools/implementations/report_read.ts +160 -0
  393. package/src/tools/implementations/report_write.ts +278 -0
  394. package/src/tools/implementations/reports_list.ts +77 -0
  395. package/src/tools/implementations/schedule_task.ts +104 -0
  396. package/src/tools/implementations/schedule_task_cancel.ts +62 -0
  397. package/src/tools/implementations/schedule_task_once.ts +128 -0
  398. package/src/tools/implementations/schedule_tasks_list.ts +79 -0
  399. package/src/tools/implementations/search.ts +160 -0
  400. package/src/tools/implementations/shell.ts +553 -0
  401. package/src/tools/implementations/todo.ts +260 -0
  402. package/src/tools/implementations/upload_blob.ts +381 -0
  403. package/src/tools/implementations/web_fetch.ts +153 -0
  404. package/src/tools/implementations/web_search.ts +250 -0
  405. package/src/tools/registry.ts +670 -0
  406. package/src/tools/types.ts +177 -0
  407. package/src/tools/utils.ts +256 -0
  408. package/src/types/event-ids.ts +320 -0
  409. package/src/types/index.ts +46 -0
  410. package/src/utils/agentFetcher.ts +107 -0
  411. package/src/utils/cli-error.ts +29 -0
  412. package/src/utils/conversation-id.ts +27 -0
  413. package/src/utils/conversation-utils.ts +1 -0
  414. package/src/utils/delegation-chain.ts +357 -0
  415. package/src/utils/error-handler.ts +42 -0
  416. package/src/utils/git/gitignore.ts +69 -0
  417. package/src/utils/git/index.ts +2 -0
  418. package/src/utils/git/initializeGitRepo.ts +204 -0
  419. package/src/utils/git/worktree.ts +260 -0
  420. package/src/utils/lessonFormatter.ts +70 -0
  421. package/src/utils/lessonTrust.ts +24 -0
  422. package/src/utils/lockfile.ts +123 -0
  423. package/src/utils/logger.ts +149 -0
  424. package/src/utils/nostr-entity-parser.ts +365 -0
  425. package/src/utils/process.ts +49 -0
  426. package/src/wrapper.ts +262 -0
  427. package/tsconfig.json +41 -0
@@ -0,0 +1,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;