@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,868 @@
1
+ /**
2
+ * ConversationStore - Single source of truth for conversation state
3
+ *
4
+ * This class manages persistent storage of conversation messages, RAL lifecycle,
5
+ * and message visibility rules. Nostr events hydrate the store, and the store
6
+ * is used to build messages for agent execution.
7
+ *
8
+ * Static methods delegate to ConversationRegistry singleton for global management.
9
+ * Instance methods handle individual conversation state.
10
+ *
11
+ * File location: ~/.tenex/projects/{projectId}/conversations/{conversationId}.json
12
+ */
13
+
14
+ import { existsSync, mkdirSync, readFileSync } from "fs";
15
+ import { writeFile } from "fs/promises";
16
+ import { join } from "path";
17
+ import type { ModelMessage, ToolCallPart, ToolResultPart } from "ai";
18
+ import type { NDKEvent } from "@nostr-dev-kit/ndk";
19
+ import type { TodoItem } from "@/services/ral/types";
20
+ import { buildMessagesFromEntries } from "./MessageBuilder";
21
+ import { conversationRegistry } from "./ConversationRegistry";
22
+ import type {
23
+ ConversationEntry,
24
+ ConversationMetadata,
25
+ ConversationState,
26
+ DeferredInjection,
27
+ DelegationMarker,
28
+ ExecutionTime,
29
+ Injection,
30
+ } from "./types";
31
+ import type { CompressionSegment, CompressionLog } from "@/services/compression/compression-types.js";
32
+ import { applySegmentsToEntries } from "@/services/compression/compression-utils.js";
33
+ import { logger } from "@/utils/logger";
34
+ import type { FullEventId } from "@/types/event-ids";
35
+
36
+ /**
37
+ * Type alias for conversation IDs accepted by ConversationStore methods.
38
+ * Accepts both typed FullEventId and plain strings for backward compatibility.
39
+ */
40
+ export type ConversationIdInput = string | FullEventId;
41
+
42
+ // Re-export types for convenience
43
+ export type {
44
+ ConversationEntry,
45
+ ConversationMetadata,
46
+ DeferredInjection,
47
+ Injection,
48
+ } from "./types";
49
+
50
+ export class ConversationStore {
51
+ // ========== STATIC METHODS (delegate to registry) ==========
52
+
53
+ static initialize(metadataPath: string, agentPubkeys?: Iterable<string>): void {
54
+ conversationRegistry.initialize(metadataPath, agentPubkeys);
55
+ }
56
+
57
+ /**
58
+ * Get or load a conversation store by ID.
59
+ * @param conversationId - Full 64-char conversation ID (FullEventId or string)
60
+ */
61
+ static getOrLoad(conversationId: ConversationIdInput): ConversationStore {
62
+ return conversationRegistry.getOrLoad(conversationId);
63
+ }
64
+
65
+ /**
66
+ * Get a conversation store if it exists.
67
+ * @param conversationId - Full 64-char conversation ID (FullEventId or string)
68
+ */
69
+ static get(conversationId: ConversationIdInput): ConversationStore | undefined {
70
+ return conversationRegistry.get(conversationId);
71
+ }
72
+
73
+ /**
74
+ * Check if a conversation exists.
75
+ * @param conversationId - Full 64-char conversation ID (FullEventId or string)
76
+ */
77
+ static has(conversationId: ConversationIdInput): boolean {
78
+ return conversationRegistry.has(conversationId);
79
+ }
80
+
81
+ static async create(event: NDKEvent): Promise<ConversationStore> {
82
+ return conversationRegistry.create(event);
83
+ }
84
+
85
+ static findByEventId(eventId: string): ConversationStore | undefined {
86
+ return conversationRegistry.findByEventId(eventId);
87
+ }
88
+
89
+ static getAll(): ConversationStore[] {
90
+ return conversationRegistry.getAll();
91
+ }
92
+
93
+ static cacheEvent(event: NDKEvent): void {
94
+ conversationRegistry.cacheEvent(event);
95
+ }
96
+
97
+ static getCachedEvent(eventId: string): NDKEvent | undefined {
98
+ return conversationRegistry.getCachedEvent(eventId);
99
+ }
100
+
101
+ static async addEvent(conversationId: string, event: NDKEvent): Promise<void> {
102
+ return conversationRegistry.addEvent(conversationId, event);
103
+ }
104
+
105
+ static setConversationTitle(conversationId: string, title: string): void {
106
+ conversationRegistry.setConversationTitle(conversationId, title);
107
+ }
108
+
109
+ static async updateConversationMetadata(
110
+ conversationId: string,
111
+ metadata: Partial<ConversationMetadata>
112
+ ): Promise<void> {
113
+ return conversationRegistry.updateConversationMetadata(conversationId, metadata);
114
+ }
115
+
116
+ static archive(conversationId: string): void {
117
+ conversationRegistry.archive(conversationId);
118
+ }
119
+
120
+ static async complete(conversationId: string): Promise<void> {
121
+ return conversationRegistry.complete(conversationId);
122
+ }
123
+
124
+ static async cleanup(): Promise<void> {
125
+ return conversationRegistry.cleanup();
126
+ }
127
+
128
+ static search(query: string): ConversationStore[] {
129
+ return conversationRegistry.search(query);
130
+ }
131
+
132
+ static getProjectId(): string | null {
133
+ return conversationRegistry.projectId;
134
+ }
135
+
136
+ static getBasePath(): string {
137
+ return conversationRegistry.basePath;
138
+ }
139
+
140
+ static getConversationsDir(): string | null {
141
+ return conversationRegistry.getConversationsDir();
142
+ }
143
+
144
+ static listConversationIdsFromDisk(): string[] {
145
+ return conversationRegistry.listConversationIdsFromDisk();
146
+ }
147
+
148
+ static listProjectIdsFromDisk(): string[] {
149
+ return conversationRegistry.listProjectIdsFromDisk();
150
+ }
151
+
152
+ static listConversationIdsFromDiskForProject(projectId: string): string[] {
153
+ return conversationRegistry.listConversationIdsFromDiskForProject(projectId);
154
+ }
155
+
156
+ static isAgentPubkey(pubkey: string): boolean {
157
+ return conversationRegistry.isAgentPubkey(pubkey);
158
+ }
159
+
160
+ static get agentPubkeys(): Set<string> {
161
+ return conversationRegistry.agentPubkeys;
162
+ }
163
+
164
+ static readLightweightMetadata(
165
+ conversationId: string
166
+ ): ReturnType<typeof conversationRegistry.readLightweightMetadata> {
167
+ return conversationRegistry.readLightweightMetadata(conversationId);
168
+ }
169
+
170
+ static readMessagesFromDisk(conversationId: string): ConversationEntry[] | null {
171
+ return conversationRegistry.readMessagesFromDisk(conversationId);
172
+ }
173
+
174
+ static readConversationPreview(
175
+ conversationId: string,
176
+ agentPubkey: string
177
+ ): ReturnType<typeof conversationRegistry.readConversationPreview> {
178
+ return conversationRegistry.readConversationPreview(conversationId, agentPubkey);
179
+ }
180
+
181
+ static readConversationPreviewForProject(
182
+ conversationId: string,
183
+ agentPubkey: string,
184
+ projectId: string
185
+ ): ReturnType<typeof conversationRegistry.readConversationPreviewForProject> {
186
+ return conversationRegistry.readConversationPreviewForProject(conversationId, agentPubkey, projectId);
187
+ }
188
+
189
+ static reset(): void {
190
+ conversationRegistry.reset();
191
+ }
192
+
193
+ // ========== INSTANCE MEMBERS ==========
194
+
195
+ private basePath: string;
196
+ private projectId: string | null = null;
197
+ private conversationId: string | null = null;
198
+ private state: ConversationState = {
199
+ activeRal: {},
200
+ nextRalNumber: {},
201
+ injections: [],
202
+ messages: [],
203
+ metadata: {},
204
+ agentTodos: {},
205
+ todoNudgedAgents: [],
206
+ blockedAgents: [],
207
+ executionTime: { totalSeconds: 0, isActive: false, lastUpdated: Date.now() },
208
+ };
209
+ private eventIdSet: Set<string> = new Set();
210
+ private blockedAgentsSet: Set<string> = new Set();
211
+
212
+ constructor(basePath: string) {
213
+ this.basePath = basePath;
214
+ }
215
+
216
+ private getFilePath(): string {
217
+ if (!this.projectId || !this.conversationId) {
218
+ throw new Error("Must call load() before accessing file");
219
+ }
220
+ return join(this.basePath, this.projectId, "conversations", `${this.conversationId}.json`);
221
+ }
222
+
223
+ private ensureDirectory(): void {
224
+ if (!this.projectId) {
225
+ throw new Error("Must call load() before accessing directory");
226
+ }
227
+ const dir = join(this.basePath, this.projectId, "conversations");
228
+ if (!existsSync(dir)) {
229
+ mkdirSync(dir, { recursive: true });
230
+ }
231
+ }
232
+
233
+ load(projectId: string, conversationId: string): void {
234
+ this.projectId = projectId;
235
+ this.conversationId = conversationId;
236
+
237
+ const filePath = this.getFilePath();
238
+ if (existsSync(filePath)) {
239
+ const content = readFileSync(filePath, "utf-8");
240
+ const loaded = JSON.parse(content);
241
+ this.state = {
242
+ activeRal: loaded.activeRal ?? {},
243
+ nextRalNumber: loaded.nextRalNumber ?? {},
244
+ injections: loaded.injections ?? [],
245
+ messages: loaded.messages ?? [],
246
+ metadata: loaded.metadata ?? {},
247
+ agentTodos: loaded.agentTodos ?? {},
248
+ todoNudgedAgents: loaded.todoNudgedAgents ?? [],
249
+ // Note: todoRemindedAgents removed in refactor - ignore if present in old files
250
+ blockedAgents: loaded.blockedAgents ?? [],
251
+ executionTime: loaded.executionTime ?? { totalSeconds: 0, isActive: false, lastUpdated: Date.now() },
252
+ metaModelVariantOverride: loaded.metaModelVariantOverride,
253
+ deferredInjections: loaded.deferredInjections ?? [],
254
+ };
255
+ this.eventIdSet = new Set(
256
+ this.state.messages.map((m) => m.eventId).filter((id): id is string => id !== undefined)
257
+ );
258
+ this.blockedAgentsSet = new Set(this.state.blockedAgents);
259
+ } else {
260
+ this.state = {
261
+ activeRal: {},
262
+ nextRalNumber: {},
263
+ injections: [],
264
+ messages: [],
265
+ metadata: {},
266
+ agentTodos: {},
267
+ todoNudgedAgents: [],
268
+ blockedAgents: [],
269
+ executionTime: { totalSeconds: 0, isActive: false, lastUpdated: Date.now() },
270
+ };
271
+ this.eventIdSet = new Set();
272
+ this.blockedAgentsSet = new Set();
273
+ }
274
+ }
275
+
276
+ getId(): string {
277
+ if (!this.conversationId) throw new Error("Must call load() before accessing ID");
278
+ return this.conversationId;
279
+ }
280
+
281
+ get id(): string {
282
+ return this.getId();
283
+ }
284
+
285
+ getProjectId(): string | null {
286
+ return this.projectId;
287
+ }
288
+
289
+ get title(): string | undefined {
290
+ return this.state.metadata.title;
291
+ }
292
+
293
+ get metadata(): ConversationMetadata {
294
+ return this.state.metadata;
295
+ }
296
+
297
+ get executionTime(): ExecutionTime {
298
+ return this.state.executionTime;
299
+ }
300
+
301
+ getMessageCount(): number {
302
+ return this.state.messages.length;
303
+ }
304
+
305
+ /**
306
+ * Get the message count after applying compression segments.
307
+ * This is the count in "compressed space" used for delta-mode cursors.
308
+ *
309
+ * CRITICAL: Delta-mode cursors must reference compressed space, not raw space.
310
+ * If cursor is in raw space, it can exceed compressed array length after compression,
311
+ * causing buildMessagesForRalAfterIndex to silently drop new messages.
312
+ */
313
+ getCompressedMessageCount(): number {
314
+ if (!this.conversationId) {
315
+ return this.state.messages.length;
316
+ }
317
+
318
+ const segments = this.loadCompressionLog(this.conversationId);
319
+ if (segments.length === 0) {
320
+ return this.state.messages.length;
321
+ }
322
+
323
+ const compressed = applySegmentsToEntries(this.state.messages, segments);
324
+ return compressed.length;
325
+ }
326
+
327
+ getLastActivityTime(): number {
328
+ const lastMessage = this.state.messages[this.state.messages.length - 1];
329
+ return lastMessage?.timestamp || 0;
330
+ }
331
+
332
+ getRootEventId(): string | undefined {
333
+ return this.state.messages[0]?.eventId;
334
+ }
335
+
336
+ getRootAuthorPubkey(): string | undefined {
337
+ return this.state.messages[0]?.pubkey;
338
+ }
339
+
340
+ async save(): Promise<void> {
341
+ this.ensureDirectory();
342
+ const filePath = this.getFilePath();
343
+ await writeFile(filePath, JSON.stringify(this.state, null, 2));
344
+ }
345
+
346
+ // RAL Lifecycle
347
+
348
+ createRal(agentPubkey: string): number {
349
+ const nextNum = (this.state.nextRalNumber[agentPubkey] || 0) + 1;
350
+ this.state.nextRalNumber[agentPubkey] = nextNum;
351
+ if (!this.state.activeRal[agentPubkey]) {
352
+ this.state.activeRal[agentPubkey] = [];
353
+ }
354
+ this.state.activeRal[agentPubkey].push({ id: nextNum });
355
+ return nextNum;
356
+ }
357
+
358
+ ensureRalActive(agentPubkey: string, ralNumber: number): void {
359
+ if (!this.state.activeRal[agentPubkey]) {
360
+ this.state.activeRal[agentPubkey] = [];
361
+ }
362
+ if (!this.isRalActive(agentPubkey, ralNumber)) {
363
+ this.state.activeRal[agentPubkey].push({ id: ralNumber });
364
+ const currentNext = this.state.nextRalNumber[agentPubkey] || 0;
365
+ if (ralNumber >= currentNext) {
366
+ this.state.nextRalNumber[agentPubkey] = ralNumber;
367
+ }
368
+ }
369
+ }
370
+
371
+ completeRal(agentPubkey: string, ralNumber: number): void {
372
+ const activeRals = this.state.activeRal[agentPubkey];
373
+ if (activeRals) {
374
+ this.state.activeRal[agentPubkey] = activeRals.filter((r) => r.id !== ralNumber);
375
+ }
376
+ }
377
+
378
+ isRalActive(agentPubkey: string, ralNumber: number): boolean {
379
+ const activeRals = this.state.activeRal[agentPubkey] || [];
380
+ return activeRals.some((r) => r.id === ralNumber);
381
+ }
382
+
383
+ getActiveRals(agentPubkey: string): number[] {
384
+ const activeRals = this.state.activeRal[agentPubkey] || [];
385
+ return activeRals.map((r) => r.id);
386
+ }
387
+
388
+ getAllActiveRals(): Map<string, number[]> {
389
+ const result = new Map<string, number[]>();
390
+ for (const [agentPubkey, rals] of Object.entries(this.state.activeRal)) {
391
+ if (rals.length > 0) {
392
+ result.set(agentPubkey, rals.map((r) => r.id));
393
+ }
394
+ }
395
+ return result;
396
+ }
397
+
398
+ // Message Operations
399
+
400
+ addMessage(entry: ConversationEntry): number {
401
+ if (entry.eventId && this.eventIdSet.has(entry.eventId)) {
402
+ return -1;
403
+ }
404
+ const index = this.state.messages.length;
405
+ this.state.messages.push(entry);
406
+ if (entry.eventId) {
407
+ this.eventIdSet.add(entry.eventId);
408
+ }
409
+ return index;
410
+ }
411
+
412
+ getAllMessages(): ConversationEntry[] {
413
+ return this.state.messages;
414
+ }
415
+
416
+ setEventId(messageIndex: number, eventId: string): void {
417
+ if (messageIndex >= 0 && messageIndex < this.state.messages.length) {
418
+ this.state.messages[messageIndex].eventId = eventId;
419
+ this.eventIdSet.add(eventId);
420
+ }
421
+ }
422
+
423
+ hasEventId(eventId: string): boolean {
424
+ return this.eventIdSet.has(eventId);
425
+ }
426
+
427
+ getFirstUserMessage(): (ConversationEntry & { id: number }) | undefined {
428
+ for (let i = 0; i < this.state.messages.length; i++) {
429
+ const msg = this.state.messages[i];
430
+ if (msg.messageType === "text" && !ConversationStore.agentPubkeys.has(msg.pubkey)) {
431
+ return { ...msg, id: i };
432
+ }
433
+ }
434
+ return undefined;
435
+ }
436
+
437
+ updateMessageContent(messageIndex: number, newContent: string): void {
438
+ if (messageIndex >= 0 && messageIndex < this.state.messages.length) {
439
+ this.state.messages[messageIndex].content = newContent;
440
+ }
441
+ }
442
+
443
+ getMetaModelVariantOverride(agentPubkey: string): string | undefined {
444
+ return this.state.metaModelVariantOverride?.[agentPubkey];
445
+ }
446
+
447
+ setMetaModelVariantOverride(agentPubkey: string, variantName: string): void {
448
+ if (!this.state.metaModelVariantOverride) {
449
+ this.state.metaModelVariantOverride = {};
450
+ }
451
+ this.state.metaModelVariantOverride[agentPubkey] = variantName;
452
+ }
453
+
454
+ clearMetaModelVariantOverride(agentPubkey: string): void {
455
+ if (this.state.metaModelVariantOverride) {
456
+ delete this.state.metaModelVariantOverride[agentPubkey];
457
+ }
458
+ }
459
+
460
+ hasToolCall(toolCallId: string): boolean {
461
+ return this.state.messages.some(
462
+ (m) =>
463
+ m.messageType === "tool-call" &&
464
+ (m.toolData as ToolCallPart[] | undefined)?.some((part) => part.toolCallId === toolCallId)
465
+ );
466
+ }
467
+
468
+ hasToolResult(toolCallId: string): boolean {
469
+ return this.state.messages.some(
470
+ (m) =>
471
+ m.messageType === "tool-result" &&
472
+ (m.toolData as ToolResultPart[] | undefined)?.some((part) => part.toolCallId === toolCallId)
473
+ );
474
+ }
475
+
476
+ /**
477
+ * Add a delegation marker to the conversation.
478
+ * Markers are lazily expanded when building messages - only direct child
479
+ * delegations are expanded, preventing exponential transcript bloat.
480
+ *
481
+ * @param marker - The delegation marker data
482
+ * @param agentPubkey - The pubkey of the agent this marker is for
483
+ * @param ralNumber - The RAL number for targeting
484
+ * @returns The index of the added message
485
+ */
486
+ addDelegationMarker(
487
+ marker: DelegationMarker,
488
+ agentPubkey: string,
489
+ ralNumber?: number
490
+ ): number {
491
+ const entry: ConversationEntry = {
492
+ pubkey: agentPubkey,
493
+ ral: ralNumber,
494
+ content: "", // Markers have no text content
495
+ messageType: "delegation-marker",
496
+ timestamp: marker.completedAt ?? marker.initiatedAt ?? Math.floor(Date.now() / 1000),
497
+ targetedPubkeys: [agentPubkey], // Target the delegator
498
+ delegationMarker: marker,
499
+ };
500
+ return this.addMessage(entry);
501
+ }
502
+
503
+ /**
504
+ * Update an existing delegation marker to mark it as completed or aborted.
505
+ * Returns true if marker was found and updated, false otherwise.
506
+ *
507
+ * Idempotent: Returns true if marker is already in the target state.
508
+ */
509
+ updateDelegationMarker(
510
+ delegationConversationId: string,
511
+ updates: {
512
+ status: "completed" | "aborted";
513
+ completedAt: number;
514
+ abortReason?: string;
515
+ }
516
+ ): boolean {
517
+ // Find the marker in messages (try pending first, then any status for idempotency)
518
+ let markerEntry = this.state.messages.find(
519
+ msg =>
520
+ msg.messageType === "delegation-marker" &&
521
+ msg.delegationMarker?.delegationConversationId === delegationConversationId &&
522
+ msg.delegationMarker?.status === "pending"
523
+ );
524
+
525
+ // If no pending marker found, check if it's already completed with the same status (idempotency)
526
+ if (!markerEntry) {
527
+ markerEntry = this.state.messages.find(
528
+ msg =>
529
+ msg.messageType === "delegation-marker" &&
530
+ msg.delegationMarker?.delegationConversationId === delegationConversationId &&
531
+ msg.delegationMarker?.status === updates.status
532
+ );
533
+
534
+ // If found and already in target state, return true (idempotent)
535
+ if (markerEntry) {
536
+ return true;
537
+ }
538
+
539
+ // Otherwise, no marker found at all
540
+ return false;
541
+ }
542
+
543
+ // Update the marker (delegationMarker is guaranteed present since messageType === "delegation-marker")
544
+ const dm = markerEntry.delegationMarker!;
545
+ dm.status = updates.status;
546
+ dm.completedAt = updates.completedAt;
547
+ if (updates.abortReason) {
548
+ dm.abortReason = updates.abortReason;
549
+ }
550
+ // DON'T update entry.timestamp - it should remain the initiation time
551
+ // Only the delegationMarker.completedAt changes
552
+
553
+ return true;
554
+ }
555
+
556
+ // Injection Operations
557
+
558
+ addInjection(injection: Injection): void {
559
+ this.state.injections.push(injection);
560
+ }
561
+
562
+ getPendingInjections(agentPubkey: string, ralNumber: number): Injection[] {
563
+ return this.state.injections.filter(
564
+ (i) => i.targetRal.pubkey === agentPubkey && i.targetRal.ral === ralNumber
565
+ );
566
+ }
567
+
568
+ consumeInjections(agentPubkey: string, ralNumber: number): Injection[] {
569
+ const toConsume = this.getPendingInjections(agentPubkey, ralNumber);
570
+ this.state.injections = this.state.injections.filter(
571
+ (i) => !(i.targetRal.pubkey === agentPubkey && i.targetRal.ral === ralNumber)
572
+ );
573
+ for (const injection of toConsume) {
574
+ this.addMessage({
575
+ pubkey: agentPubkey,
576
+ ral: ralNumber,
577
+ content: injection.content,
578
+ messageType: "text",
579
+ targetedPubkeys: injection.role === "user" ? [agentPubkey] : undefined,
580
+ });
581
+ }
582
+ return toConsume;
583
+ }
584
+
585
+ // Deferred Injection Operations (for next-turn messages)
586
+
587
+ /**
588
+ * Add a deferred injection for an agent's next turn.
589
+ *
590
+ * Unlike regular injections that target a specific RAL, deferred injections
591
+ * are consumed at the START of any future RAL for the target agent.
592
+ * This is used for supervision messages that should NOT block the current
593
+ * completion but should appear in the agent's next conversation turn.
594
+ */
595
+ addDeferredInjection(injection: DeferredInjection): void {
596
+ if (!this.state.deferredInjections) {
597
+ this.state.deferredInjections = [];
598
+ }
599
+ this.state.deferredInjections.push(injection);
600
+ }
601
+
602
+ /**
603
+ * Get pending deferred injections for an agent.
604
+ */
605
+ getPendingDeferredInjections(agentPubkey: string): DeferredInjection[] {
606
+ return (this.state.deferredInjections ?? []).filter(
607
+ (i) => i.targetPubkey === agentPubkey
608
+ );
609
+ }
610
+
611
+ /**
612
+ * Consume deferred injections for an agent, returning them and removing from queue.
613
+ *
614
+ * Unlike consumeInjections which adds messages to store, this just returns
615
+ * the injections for the caller to handle (typically as ephemeral messages
616
+ * in MessageCompiler).
617
+ */
618
+ consumeDeferredInjections(agentPubkey: string): DeferredInjection[] {
619
+ const toConsume = this.getPendingDeferredInjections(agentPubkey);
620
+ this.state.deferredInjections = (this.state.deferredInjections ?? []).filter(
621
+ (i) => i.targetPubkey !== agentPubkey
622
+ );
623
+ return toConsume;
624
+ }
625
+
626
+ // Message Building
627
+
628
+ async buildMessagesForRal(
629
+ agentPubkey: string,
630
+ ralNumber: number,
631
+ projectRoot?: string
632
+ ): Promise<ModelMessage[]> {
633
+ // INVARIANT: conversationId should always be set after load()
634
+ // If missing, delegation markers won't expand - log a warning for debugging
635
+ if (!this.conversationId) {
636
+ logger.warn("[ConversationStore.buildMessagesForRal] conversationId is null - delegation markers will not expand");
637
+ }
638
+
639
+ const activeRals = new Set(this.getActiveRals(agentPubkey));
640
+
641
+ // Apply compression segments if they exist
642
+ const segments = this.conversationId ? this.loadCompressionLog(this.conversationId) : [];
643
+ const entries = segments.length > 0
644
+ ? applySegmentsToEntries(this.state.messages, segments)
645
+ : this.state.messages;
646
+
647
+ // Callback to get delegation messages for marker expansion
648
+ const getDelegationMessages = (delegationConversationId: string) => {
649
+ const store = conversationRegistry.get(delegationConversationId);
650
+ return store?.getAllMessages();
651
+ };
652
+
653
+ return buildMessagesFromEntries(entries, {
654
+ viewingAgentPubkey: agentPubkey,
655
+ ralNumber,
656
+ activeRals,
657
+ totalMessages: entries.length,
658
+ agentPubkeys: ConversationStore.agentPubkeys,
659
+ projectRoot,
660
+ conversationId: this.conversationId ?? undefined,
661
+ getDelegationMessages,
662
+ });
663
+ }
664
+
665
+ async buildMessagesForRalAfterIndex(
666
+ agentPubkey: string,
667
+ ralNumber: number,
668
+ afterIndex: number,
669
+ projectRoot?: string
670
+ ): Promise<ModelMessage[]> {
671
+ // INVARIANT: conversationId should always be set after load()
672
+ // If missing, delegation markers won't expand - log a warning for debugging
673
+ if (!this.conversationId) {
674
+ logger.warn("[ConversationStore.buildMessagesForRalAfterIndex] conversationId is null - delegation markers will not expand");
675
+ }
676
+
677
+ const activeRals = new Set(this.getActiveRals(agentPubkey));
678
+ const startIndex = Math.max(afterIndex + 1, 0);
679
+
680
+ // Apply compression segments if they exist
681
+ const segments = this.conversationId ? this.loadCompressionLog(this.conversationId) : [];
682
+ const allEntries = segments.length > 0
683
+ ? applySegmentsToEntries(this.state.messages, segments)
684
+ : this.state.messages;
685
+
686
+ if (startIndex >= allEntries.length) return [];
687
+ const entries = allEntries.slice(startIndex);
688
+
689
+ // Callback to get delegation messages for marker expansion
690
+ const getDelegationMessages = (delegationConversationId: string) => {
691
+ const store = conversationRegistry.get(delegationConversationId);
692
+ return store?.getAllMessages();
693
+ };
694
+
695
+ return buildMessagesFromEntries(entries, {
696
+ viewingAgentPubkey: agentPubkey,
697
+ ralNumber,
698
+ activeRals,
699
+ indexOffset: startIndex,
700
+ totalMessages: allEntries.length,
701
+ agentPubkeys: ConversationStore.agentPubkeys,
702
+ projectRoot,
703
+ conversationId: this.conversationId ?? undefined,
704
+ getDelegationMessages,
705
+ });
706
+ }
707
+
708
+ // Metadata Operations
709
+
710
+ getMetadata(): ConversationMetadata {
711
+ return this.state.metadata;
712
+ }
713
+
714
+ updateMetadata(updates: Partial<ConversationMetadata>): void {
715
+ this.state.metadata = { ...this.state.metadata, ...updates };
716
+ }
717
+
718
+ getTitle(): string | undefined {
719
+ return this.state.metadata.title;
720
+ }
721
+
722
+ setTitle(title: string): void {
723
+ this.state.metadata.title = title;
724
+ }
725
+
726
+ // Todo Operations
727
+
728
+ getTodos(agentPubkey: string): TodoItem[] {
729
+ return this.state.agentTodos[agentPubkey] ?? [];
730
+ }
731
+
732
+ setTodos(agentPubkey: string, todos: TodoItem[]): void {
733
+ this.state.agentTodos[agentPubkey] = todos;
734
+ }
735
+
736
+ hasBeenNudgedAboutTodos(agentPubkey: string): boolean {
737
+ return this.state.todoNudgedAgents.includes(agentPubkey);
738
+ }
739
+
740
+ setNudgedAboutTodos(agentPubkey: string): void {
741
+ if (!this.state.todoNudgedAgents.includes(agentPubkey)) {
742
+ this.state.todoNudgedAgents.push(agentPubkey);
743
+ }
744
+ }
745
+
746
+ // Blocked Agents
747
+
748
+ isAgentBlocked(agentPubkey: string): boolean {
749
+ return this.blockedAgentsSet.has(agentPubkey);
750
+ }
751
+
752
+ blockAgent(agentPubkey: string): void {
753
+ this.blockedAgentsSet.add(agentPubkey);
754
+ this.state.blockedAgents = Array.from(this.blockedAgentsSet);
755
+ }
756
+
757
+ unblockAgent(agentPubkey: string): void {
758
+ this.blockedAgentsSet.delete(agentPubkey);
759
+ this.state.blockedAgents = Array.from(this.blockedAgentsSet);
760
+ }
761
+
762
+ getBlockedAgents(): Set<string> {
763
+ return this.blockedAgentsSet;
764
+ }
765
+
766
+ // Event Message Operations
767
+
768
+ private static extractTargetedPubkeys(event: NDKEvent): string[] {
769
+ const pTags = event.getMatchingTags("p");
770
+ if (pTags.length === 0) return [];
771
+ const targeted: string[] = [];
772
+ for (const pTag of pTags) {
773
+ const pubkey = pTag[1];
774
+ if (pubkey) targeted.push(pubkey);
775
+ }
776
+ return targeted;
777
+ }
778
+
779
+ addEventMessage(event: NDKEvent, isFromAgent: boolean): void {
780
+ if (!event.id) return;
781
+ if (event.kind !== 1) return;
782
+ if (event.tagValue("tool")) return;
783
+ if (this.hasEventId(event.id)) return;
784
+
785
+ const targetedPubkeys = ConversationStore.extractTargetedPubkeys(event);
786
+ this.addMessage({
787
+ pubkey: event.pubkey,
788
+ content: event.content,
789
+ messageType: "text",
790
+ eventId: event.id,
791
+ timestamp: event.created_at,
792
+ targetedPubkeys: targetedPubkeys.length > 0 ? targetedPubkeys : undefined,
793
+ });
794
+
795
+ if (!isFromAgent) {
796
+ this.state.metadata.last_user_message = event.content;
797
+ }
798
+ }
799
+
800
+ // Compression Operations
801
+
802
+ /**
803
+ * Load compression log for a conversation.
804
+ * Returns empty array if no compression log exists.
805
+ */
806
+ loadCompressionLog(conversationId: string): CompressionSegment[] {
807
+ if (!this.projectId) {
808
+ return [];
809
+ }
810
+
811
+ const conversationsDir = join(this.basePath, this.projectId, "conversations");
812
+ const compressionsDir = join(conversationsDir, "compressions");
813
+ if (!existsSync(compressionsDir)) {
814
+ return [];
815
+ }
816
+
817
+ const compressionPath = join(compressionsDir, `${conversationId}.json`);
818
+ if (!existsSync(compressionPath)) {
819
+ return [];
820
+ }
821
+
822
+ try {
823
+ const data = readFileSync(compressionPath, "utf-8");
824
+ const log = JSON.parse(data) as CompressionLog;
825
+ return log.segments || [];
826
+ } catch (error) {
827
+ // CRITICAL: Use imported logger, not this.logger (which doesn't exist)
828
+ logger.warn(`Failed to load compression log for ${conversationId}:`, error);
829
+ return [];
830
+ }
831
+ }
832
+
833
+ /**
834
+ * Append new compression segments to the log.
835
+ * Creates the compressions directory if needed.
836
+ */
837
+ async appendCompressionSegments(
838
+ conversationId: string,
839
+ segments: CompressionSegment[]
840
+ ): Promise<void> {
841
+ if (!this.projectId) {
842
+ throw new Error("Conversations directory not initialized");
843
+ }
844
+
845
+ const conversationsDir = join(this.basePath, this.projectId, "conversations");
846
+ const compressionsDir = join(conversationsDir, "compressions");
847
+ if (!existsSync(compressionsDir)) {
848
+ mkdirSync(compressionsDir, { recursive: true });
849
+ }
850
+
851
+ const compressionPath = join(compressionsDir, `${conversationId}.json`);
852
+
853
+ // Load existing segments
854
+ const existingSegments = this.loadCompressionLog(conversationId);
855
+
856
+ // Append new segments
857
+ const log: CompressionLog = {
858
+ conversationId,
859
+ segments: [...existingSegments, ...segments],
860
+ updatedAt: Date.now(),
861
+ };
862
+
863
+ await writeFile(compressionPath, JSON.stringify(log, null, 2), "utf-8");
864
+ }
865
+ }
866
+
867
+ // Register store class with registry to avoid circular imports.
868
+ conversationRegistry.setConversationStoreClass(ConversationStore);