@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,49 @@
1
+ export class TreeRenderer {
2
+ /**
3
+ * Get the connector character(s) for the current node
4
+ */
5
+ getConnector(style: "ascii" | "unicode" | "markdown", isLast: boolean): string {
6
+ switch (style) {
7
+ case "ascii":
8
+ return isLast ? "└── " : "├── ";
9
+ case "unicode":
10
+ return isLast ? "└── " : "├── ";
11
+ case "markdown":
12
+ return "- ";
13
+ default:
14
+ return "";
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Get the prefix for child nodes
20
+ */
21
+ getChildPrefix(style: "ascii" | "unicode" | "markdown", parentIsLast: boolean): string {
22
+ switch (style) {
23
+ case "ascii":
24
+ return parentIsLast ? " " : "│ ";
25
+ case "unicode":
26
+ return parentIsLast ? " " : "│ ";
27
+ case "markdown":
28
+ return " ";
29
+ default:
30
+ return "";
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Get a separator line between threads
36
+ */
37
+ getSeparator(style: "ascii" | "unicode" | "markdown", width = 60): string {
38
+ switch (style) {
39
+ case "ascii":
40
+ return "-".repeat(width);
41
+ case "unicode":
42
+ return "─".repeat(width);
43
+ case "markdown":
44
+ return "\n---\n";
45
+ default:
46
+ return "";
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,2 @@
1
+ export { ConversationStore } from "./ConversationStore";
2
+ export type { ConversationMetadata, ConversationEntry } from "./types";
@@ -0,0 +1,143 @@
1
+ import { promises as fs } from "node:fs";
2
+ import { config } from "@/services/ConfigService";
3
+ import * as path from "node:path";
4
+ import { formatAnyError } from "@/lib/error-formatter";
5
+ import { logger } from "@/utils/logger";
6
+ import type { ModelMessage } from "ai";
7
+
8
+ /**
9
+ * Storage interface for tool messages
10
+ * Single Responsibility: Persist and retrieve tool execution messages
11
+ */
12
+ export class ToolMessageStorage {
13
+ private readonly storageDir = config.getConfigPath("tool-messages");
14
+
15
+ /**
16
+ * Store tool messages for later reconstruction
17
+ */
18
+ async store(
19
+ eventId: string,
20
+ toolCall: {
21
+ toolCallId: string;
22
+ toolName: string;
23
+ input: unknown;
24
+ },
25
+ toolResult: {
26
+ toolCallId: string;
27
+ toolName: string;
28
+ output: unknown;
29
+ error?: boolean;
30
+ },
31
+ agentPubkey: string
32
+ ): Promise<void> {
33
+ try {
34
+ // Ensure input is always an object (AI SDK schema requires it, and JSON.stringify strips undefined)
35
+ const safeInput = toolCall.input !== undefined ? toolCall.input : {};
36
+
37
+ // Ensure output is always defined
38
+ const safeOutput = toolResult.output !== undefined
39
+ ? {
40
+ type: "text" as const,
41
+ value: typeof toolResult.output === "string"
42
+ ? toolResult.output
43
+ : JSON.stringify(toolResult.output),
44
+ }
45
+ : { type: "text" as const, value: "" };
46
+
47
+ const messages: ModelMessage[] = [
48
+ {
49
+ role: "assistant",
50
+ content: [
51
+ {
52
+ type: "tool-call" as const,
53
+ toolCallId: toolCall.toolCallId,
54
+ toolName: toolCall.toolName,
55
+ input: safeInput,
56
+ },
57
+ ],
58
+ },
59
+ {
60
+ role: "tool",
61
+ content: [
62
+ {
63
+ type: "tool-result" as const,
64
+ toolCallId: toolResult.toolCallId,
65
+ toolName: toolResult.toolName,
66
+ output: safeOutput,
67
+ },
68
+ ],
69
+ },
70
+ ];
71
+
72
+ await fs.mkdir(this.storageDir, { recursive: true });
73
+
74
+ const filePath = path.join(this.storageDir, `${eventId}.json`);
75
+ const data = {
76
+ eventId,
77
+ agentPubkey,
78
+ timestamp: Date.now(),
79
+ messages,
80
+ };
81
+
82
+ await fs.writeFile(filePath, JSON.stringify(data, null, 2));
83
+
84
+ logger.debug("[ToolMessageStorage] Stored tool messages", {
85
+ eventId: eventId.substring(0, 8),
86
+ filePath,
87
+ });
88
+ } catch (error) {
89
+ logger.error("[ToolMessageStorage] Failed to store tool messages", {
90
+ error: formatAnyError(error),
91
+ eventId,
92
+ });
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Load tool messages from storage by event ID
98
+ */
99
+ async load(eventId: string): Promise<ModelMessage[] | null> {
100
+ try {
101
+ const filePath = path.join(this.storageDir, `${eventId}.json`);
102
+ const data = await fs.readFile(filePath, "utf-8");
103
+ const parsed = JSON.parse(data);
104
+ // The stored messages are valid ModelMessage[] from the store() method
105
+ return parsed.messages as ModelMessage[];
106
+ } catch {
107
+ // File doesn't exist or can't be read
108
+ return null;
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Clean up old tool messages
114
+ */
115
+ async cleanup(olderThanMs: number = 24 * 60 * 60 * 1000): Promise<void> {
116
+ try {
117
+ const files = await fs.readdir(this.storageDir);
118
+ const now = Date.now();
119
+
120
+ for (const file of files) {
121
+ if (!file.endsWith(".json")) continue;
122
+
123
+ const filePath = path.join(this.storageDir, file);
124
+ const stats = await fs.stat(filePath);
125
+
126
+ if (now - stats.mtimeMs > olderThanMs) {
127
+ await fs.unlink(filePath);
128
+ logger.debug("[ToolMessageStorage] Cleaned up old tool message file", {
129
+ file,
130
+ ageMs: now - stats.mtimeMs,
131
+ });
132
+ }
133
+ }
134
+ } catch (error) {
135
+ logger.error("[ToolMessageStorage] Failed to cleanup", {
136
+ error: formatAnyError(error),
137
+ });
138
+ }
139
+ }
140
+ }
141
+
142
+ // Singleton instance
143
+ export const toolMessageStorage = new ToolMessageStorage();
@@ -0,0 +1,393 @@
1
+ /**
2
+ * ConversationIndexManager - Handles JSON index I/O with debounced updates.
3
+ *
4
+ * Responsibilities:
5
+ * - Load index from disk or rebuild if missing
6
+ * - Rebuild index by scanning all conversation files
7
+ * - Debounced incremental updates (30 seconds)
8
+ * - Atomic writes (temp file + rename)
9
+ */
10
+
11
+ import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "fs";
12
+ import { join, dirname } from "path";
13
+ import { logger } from "@/utils/logger";
14
+ import type {
15
+ ConversationIndex,
16
+ ConversationIndexEntry,
17
+ MessageIndexEntry,
18
+ } from "./types";
19
+ import {
20
+ listConversationIdsFromDiskForProject,
21
+ readLightweightMetadata,
22
+ readMessagesFromDisk,
23
+ } from "../ConversationDiskReader";
24
+ import type { ConversationEntry } from "../types";
25
+
26
+ /** Current index format version */
27
+ const INDEX_VERSION = "1.0";
28
+
29
+ /** Debounce delay for index updates (30 seconds) */
30
+ const DEBOUNCE_MS = 30_000;
31
+
32
+ /** Index filename */
33
+ const INDEX_FILENAME = "conversation-search-index.json";
34
+
35
+ /**
36
+ * State for debounced updates.
37
+ */
38
+ interface DebounceState {
39
+ timerId: NodeJS.Timeout | null;
40
+ pendingConversationIds: Set<string>;
41
+ }
42
+
43
+ /**
44
+ * Manages the conversation search index.
45
+ */
46
+ export class ConversationIndexManager {
47
+ private basePath: string;
48
+ private projectId: string;
49
+ private cachedIndex: ConversationIndex | null = null;
50
+ private debounceState: DebounceState = {
51
+ timerId: null,
52
+ pendingConversationIds: new Set(),
53
+ };
54
+
55
+ constructor(basePath: string, projectId: string) {
56
+ this.basePath = basePath;
57
+ this.projectId = projectId;
58
+ }
59
+
60
+ /**
61
+ * Get the path to the index file.
62
+ */
63
+ private getIndexPath(): string {
64
+ return join(this.basePath, this.projectId, ".tenex", INDEX_FILENAME);
65
+ }
66
+
67
+ /**
68
+ * Ensure the .tenex directory exists.
69
+ */
70
+ private ensureDirectory(): void {
71
+ const dir = dirname(this.getIndexPath());
72
+ if (!existsSync(dir)) {
73
+ mkdirSync(dir, { recursive: true });
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Load the index from disk.
79
+ * Returns null if the index doesn't exist or is invalid.
80
+ */
81
+ private loadFromDisk(): ConversationIndex | null {
82
+ const indexPath = this.getIndexPath();
83
+ try {
84
+ if (!existsSync(indexPath)) {
85
+ return null;
86
+ }
87
+ const content = readFileSync(indexPath, "utf-8");
88
+ const parsed = JSON.parse(content) as ConversationIndex;
89
+
90
+ // Version check
91
+ if (parsed.version !== INDEX_VERSION) {
92
+ logger.info("[ConversationIndexManager] Index version mismatch, will rebuild", {
93
+ found: parsed.version,
94
+ expected: INDEX_VERSION,
95
+ });
96
+ return null;
97
+ }
98
+
99
+ return parsed;
100
+ } catch (error) {
101
+ logger.warn("[ConversationIndexManager] Failed to load index", { error });
102
+ return null;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Save the index to disk atomically.
108
+ */
109
+ private saveToDisk(index: ConversationIndex): void {
110
+ this.ensureDirectory();
111
+ const indexPath = this.getIndexPath();
112
+ const tempPath = indexPath + ".tmp";
113
+
114
+ try {
115
+ // Write to temp file
116
+ writeFileSync(tempPath, JSON.stringify(index, null, 2));
117
+
118
+ // Atomic rename
119
+ renameSync(tempPath, indexPath);
120
+
121
+ logger.debug("[ConversationIndexManager] Index saved", {
122
+ conversationCount: index.conversations.length,
123
+ });
124
+ } catch (error) {
125
+ logger.error("[ConversationIndexManager] Failed to save index", { error });
126
+ // Clean up temp file if it exists
127
+ try {
128
+ if (existsSync(tempPath)) {
129
+ unlinkSync(tempPath);
130
+ }
131
+ } catch {
132
+ // Ignore cleanup errors
133
+ }
134
+ throw error;
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Index a single conversation from disk.
140
+ */
141
+ private indexConversation(conversationId: string): ConversationIndexEntry | null {
142
+ try {
143
+ const metadata = readLightweightMetadata(this.basePath, this.projectId, conversationId);
144
+ const messages = readMessagesFromDisk(this.basePath, this.projectId, conversationId);
145
+
146
+ if (!messages || messages.length === 0) {
147
+ return null;
148
+ }
149
+
150
+ // Extract agents (unique pubkeys)
151
+ const agentSet = new Set<string>();
152
+ for (const msg of messages) {
153
+ if (msg.pubkey) {
154
+ agentSet.add(msg.pubkey);
155
+ }
156
+ }
157
+
158
+ // Index messages (only text messages with content)
159
+ const indexedMessages: MessageIndexEntry[] = [];
160
+ for (let i = 0; i < messages.length; i++) {
161
+ const msg = messages[i] as ConversationEntry;
162
+ if (msg.messageType === "text" && msg.content && msg.content.trim().length > 0) {
163
+ indexedMessages.push({
164
+ messageId: `msg-${i}`,
165
+ content: msg.content,
166
+ timestamp: msg.timestamp,
167
+ from: msg.pubkey,
168
+ to: msg.targetedPubkeys?.[0],
169
+ });
170
+ }
171
+ }
172
+
173
+ // Calculate lastMessageAt from the last message with a timestamp
174
+ let lastMessageAt: number | undefined;
175
+ for (let i = messages.length - 1; i >= 0; i--) {
176
+ if (messages[i].timestamp) {
177
+ lastMessageAt = messages[i].timestamp;
178
+ break;
179
+ }
180
+ }
181
+
182
+ return {
183
+ conversationId,
184
+ slug: this.projectId,
185
+ title: metadata?.title,
186
+ messageCount: messages.length,
187
+ lastMessageAt,
188
+ agents: Array.from(agentSet),
189
+ messages: indexedMessages,
190
+ };
191
+ } catch (error) {
192
+ logger.warn("[ConversationIndexManager] Failed to index conversation", {
193
+ conversationId: conversationId.substring(0, 8),
194
+ error,
195
+ });
196
+ return null;
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Rebuild the entire index from scratch.
202
+ */
203
+ rebuildIndex(): ConversationIndex {
204
+ logger.info("[ConversationIndexManager] Rebuilding index", { projectId: this.projectId });
205
+
206
+ const conversationIds = listConversationIdsFromDiskForProject(this.basePath, this.projectId);
207
+ const conversations: ConversationIndexEntry[] = [];
208
+
209
+ for (const convId of conversationIds) {
210
+ const entry = this.indexConversation(convId);
211
+ if (entry) {
212
+ conversations.push(entry);
213
+ }
214
+ }
215
+
216
+ const index: ConversationIndex = {
217
+ version: INDEX_VERSION,
218
+ lastUpdated: new Date().toISOString(),
219
+ conversations,
220
+ };
221
+
222
+ // Save to disk
223
+ this.saveToDisk(index);
224
+ this.cachedIndex = index;
225
+
226
+ logger.info("[ConversationIndexManager] Index rebuilt", {
227
+ conversationCount: conversations.length,
228
+ projectId: this.projectId,
229
+ });
230
+
231
+ return index;
232
+ }
233
+
234
+ /**
235
+ * Get the current index, loading from disk or rebuilding if necessary.
236
+ */
237
+ getIndex(): ConversationIndex {
238
+ // Return cached index if available
239
+ if (this.cachedIndex) {
240
+ return this.cachedIndex;
241
+ }
242
+
243
+ // Try to load from disk
244
+ const loaded = this.loadFromDisk();
245
+ if (loaded) {
246
+ this.cachedIndex = loaded;
247
+ return loaded;
248
+ }
249
+
250
+ // Rebuild index
251
+ return this.rebuildIndex();
252
+ }
253
+
254
+ /**
255
+ * Trigger a debounced update for a specific conversation.
256
+ * Updates will be batched and applied after 30 seconds.
257
+ */
258
+ triggerUpdate(conversationId: string): void {
259
+ this.debounceState.pendingConversationIds.add(conversationId);
260
+
261
+ // If timer already running, let it handle the update
262
+ if (this.debounceState.timerId) {
263
+ return;
264
+ }
265
+
266
+ // Schedule debounced update
267
+ this.debounceState.timerId = setTimeout(() => {
268
+ this.flushPendingUpdates();
269
+ }, DEBOUNCE_MS);
270
+ }
271
+
272
+ /**
273
+ * Flush all pending updates immediately.
274
+ */
275
+ private flushPendingUpdates(): void {
276
+ if (this.debounceState.timerId) {
277
+ clearTimeout(this.debounceState.timerId);
278
+ this.debounceState.timerId = null;
279
+ }
280
+
281
+ const pendingIds = Array.from(this.debounceState.pendingConversationIds);
282
+ this.debounceState.pendingConversationIds.clear();
283
+
284
+ if (pendingIds.length === 0) {
285
+ return;
286
+ }
287
+
288
+ logger.debug("[ConversationIndexManager] Flushing pending updates", {
289
+ count: pendingIds.length,
290
+ });
291
+
292
+ // Get current index
293
+ const index = this.getIndex();
294
+
295
+ // Update each conversation
296
+ for (const convId of pendingIds) {
297
+ const newEntry = this.indexConversation(convId);
298
+
299
+ // Find existing entry index
300
+ const existingIndex = index.conversations.findIndex(
301
+ (c) => c.conversationId === convId
302
+ );
303
+
304
+ if (newEntry) {
305
+ if (existingIndex >= 0) {
306
+ // Update existing
307
+ index.conversations[existingIndex] = newEntry;
308
+ } else {
309
+ // Add new
310
+ index.conversations.push(newEntry);
311
+ }
312
+ } else if (existingIndex >= 0) {
313
+ // Conversation no longer exists, remove it
314
+ index.conversations.splice(existingIndex, 1);
315
+ }
316
+ }
317
+
318
+ // Update timestamp and save
319
+ index.lastUpdated = new Date().toISOString();
320
+ this.saveToDisk(index);
321
+ this.cachedIndex = index;
322
+ }
323
+
324
+ /**
325
+ * Force an immediate update for a conversation (no debouncing).
326
+ */
327
+ updateConversationNow(conversationId: string): void {
328
+ const index = this.getIndex();
329
+ const newEntry = this.indexConversation(conversationId);
330
+
331
+ const existingIndex = index.conversations.findIndex(
332
+ (c) => c.conversationId === conversationId
333
+ );
334
+
335
+ if (newEntry) {
336
+ if (existingIndex >= 0) {
337
+ index.conversations[existingIndex] = newEntry;
338
+ } else {
339
+ index.conversations.push(newEntry);
340
+ }
341
+ } else if (existingIndex >= 0) {
342
+ index.conversations.splice(existingIndex, 1);
343
+ }
344
+
345
+ index.lastUpdated = new Date().toISOString();
346
+ this.saveToDisk(index);
347
+ this.cachedIndex = index;
348
+ }
349
+
350
+ /**
351
+ * Invalidate the cached index, forcing a reload on next access.
352
+ */
353
+ invalidateCache(): void {
354
+ this.cachedIndex = null;
355
+ }
356
+
357
+ /**
358
+ * Clean up resources (timers, etc.).
359
+ */
360
+ cleanup(): void {
361
+ if (this.debounceState.timerId) {
362
+ clearTimeout(this.debounceState.timerId);
363
+ this.debounceState.timerId = null;
364
+ }
365
+ this.debounceState.pendingConversationIds.clear();
366
+ }
367
+ }
368
+
369
+ /** Singleton instances per project */
370
+ const instances = new Map<string, ConversationIndexManager>();
371
+
372
+ /**
373
+ * Get or create an index manager for a project.
374
+ */
375
+ export function getIndexManager(basePath: string, projectId: string): ConversationIndexManager {
376
+ const key = `${basePath}:${projectId}`;
377
+ let manager = instances.get(key);
378
+ if (!manager) {
379
+ manager = new ConversationIndexManager(basePath, projectId);
380
+ instances.set(key, manager);
381
+ }
382
+ return manager;
383
+ }
384
+
385
+ /**
386
+ * Clear all cached instances (for testing).
387
+ */
388
+ export function clearIndexManagerInstances(): void {
389
+ for (const manager of instances.values()) {
390
+ manager.cleanup();
391
+ }
392
+ instances.clear();
393
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * QueryParser - Pure function for parsing and validating search queries.
3
+ *
4
+ * Handles:
5
+ * - Text query validation (required, non-empty)
6
+ * - Agent filter parsing (string array)
7
+ * - Date filter parsing (Unix timestamps or ISO 8601 strings)
8
+ * - 'after' is a pure alias for 'since' (since takes precedence if both provided)
9
+ */
10
+
11
+ import type { SearchFilters, SearchQuery } from "./types";
12
+
13
+ /**
14
+ * Input format from the tool schema.
15
+ */
16
+ export interface RawSearchInput {
17
+ query: string;
18
+ filters?: {
19
+ agents?: string[];
20
+ since?: string | number;
21
+ after?: string | number;
22
+ };
23
+ }
24
+
25
+ /**
26
+ * Parse a date input to Unix timestamp in seconds.
27
+ * Accepts:
28
+ * - Unix timestamp (number or numeric string) in seconds
29
+ * - ISO 8601 date strings (e.g., "2026-01-26T10:00:00Z")
30
+ * - Date-only strings (e.g., "2026-01-26")
31
+ *
32
+ * @throws Error if the date cannot be parsed
33
+ */
34
+ export function parseTimestamp(value: string | number): number {
35
+ if (typeof value === "number") {
36
+ // Assume it's already Unix seconds
37
+ return value;
38
+ }
39
+
40
+ // Try parsing as numeric string (Unix timestamp)
41
+ const numericValue = Number(value);
42
+ if (!isNaN(numericValue) && value.trim() === String(numericValue)) {
43
+ return numericValue;
44
+ }
45
+
46
+ // Try parsing as date string
47
+ const date = new Date(value);
48
+ if (isNaN(date.getTime())) {
49
+ throw new Error(`Invalid date format: "${value}". Expected Unix timestamp or ISO 8601 date.`);
50
+ }
51
+
52
+ // Convert to Unix seconds
53
+ return Math.floor(date.getTime() / 1000);
54
+ }
55
+
56
+ /**
57
+ * Parse and validate a raw search input into a SearchQuery.
58
+ *
59
+ * @throws Error if validation fails
60
+ */
61
+ export function parseQuery(input: RawSearchInput): SearchQuery {
62
+ // Validate query text
63
+ if (!input.query || typeof input.query !== "string") {
64
+ throw new Error("Search query is required and must be a non-empty string.");
65
+ }
66
+
67
+ const trimmedQuery = input.query.trim();
68
+ if (trimmedQuery.length === 0) {
69
+ throw new Error("Search query cannot be empty.");
70
+ }
71
+
72
+ // Parse filters
73
+ const filters: SearchFilters = {};
74
+
75
+ if (input.filters) {
76
+ // Parse agents filter
77
+ if (input.filters.agents !== undefined) {
78
+ if (!Array.isArray(input.filters.agents)) {
79
+ throw new Error("filters.agents must be an array of strings.");
80
+ }
81
+ const validAgents = input.filters.agents.filter(
82
+ (a): a is string => typeof a === "string" && a.trim().length > 0
83
+ );
84
+ if (validAgents.length > 0) {
85
+ filters.agents = validAgents.map((a) => a.trim());
86
+ }
87
+ }
88
+
89
+ // Parse since filter (primary)
90
+ if (input.filters.since !== undefined) {
91
+ filters.since = parseTimestamp(input.filters.since);
92
+ }
93
+
94
+ // Parse after filter as pure alias for since
95
+ // If since is already set, ignore after (since takes precedence)
96
+ if (input.filters.after !== undefined && filters.since === undefined) {
97
+ filters.since = parseTimestamp(input.filters.after);
98
+ }
99
+ // Note: We don't store 'after' separately - it's just an alias for 'since'
100
+ }
101
+
102
+ return {
103
+ text: trimmedQuery,
104
+ filters,
105
+ };
106
+ }
107
+
108
+ /**
109
+ * Get the "since" timestamp from filters.
110
+ * Note: 'after' is already resolved to 'since' during parsing.
111
+ */
112
+ export function getEffectiveSinceTimestamp(filters: SearchFilters): number | undefined {
113
+ return filters.since;
114
+ }