@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,682 @@
1
+ import type { DocumentMetadata, LanceDBResult, LanceDBStoredDocument } from "@/services/rag/rag-utils";
2
+ import { calculateRelevanceScore, mapLanceResultToDocument } from "@/services/rag/rag-utils";
3
+ import { handleError } from "@/utils/error-handler";
4
+ import { logger } from "@/utils/logger";
5
+ import type { Table, VectorQuery } from "@lancedb/lancedb";
6
+ import type { EmbeddingProvider } from "@/services/embedding";
7
+ import type { RAGDatabaseService } from "./RAGDatabaseService";
8
+
9
+ /**
10
+ * Document structure for RAG operations
11
+ */
12
+ export interface RAGDocument {
13
+ id?: string;
14
+ content: string;
15
+ metadata?: DocumentMetadata;
16
+ vector?: Float32Array;
17
+ timestamp?: number;
18
+ source?: string;
19
+ }
20
+
21
+ /**
22
+ * Schema definition for LanceDB collection
23
+ */
24
+ export interface LanceDBSchema {
25
+ id: string;
26
+ content: string;
27
+ vector: string;
28
+ metadata: string;
29
+ timestamp: string;
30
+ source: string;
31
+ [key: string]: string;
32
+ }
33
+
34
+ /**
35
+ * Collection metadata structure
36
+ */
37
+ export interface RAGCollection {
38
+ name: string;
39
+ schema?: LanceDBSchema;
40
+ created_at: number;
41
+ updated_at: number;
42
+ }
43
+
44
+ /**
45
+ * Query result with relevance score
46
+ */
47
+ export interface RAGQueryResult {
48
+ document: RAGDocument;
49
+ score: number;
50
+ }
51
+
52
+ /**
53
+ * Result from bulkUpsert with per-chunk failure isolation.
54
+ * Successfully flushed documents are counted; failed document indices
55
+ * are reported so callers can decide which items to mark as complete.
56
+ */
57
+ export interface BulkUpsertResult {
58
+ /** Number of documents successfully upserted */
59
+ upsertedCount: number;
60
+ /** 0-based indices into the original input array that failed */
61
+ failedIndices: number[];
62
+ }
63
+
64
+ /**
65
+ * Custom errors for RAG operations
66
+ */
67
+ export class RAGValidationError extends Error {
68
+ constructor(message: string) {
69
+ super(message);
70
+ this.name = "RAGValidationError";
71
+ }
72
+ }
73
+
74
+ export class RAGOperationError extends Error {
75
+ constructor(
76
+ message: string,
77
+ public readonly cause?: Error
78
+ ) {
79
+ super(message);
80
+ this.name = "RAGOperationError";
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Handles RAG CRUD operations
86
+ * Single Responsibility: Business logic for document storage and retrieval
87
+ */
88
+ export class RAGOperations {
89
+ private static readonly BATCH_SIZE = 100;
90
+
91
+ constructor(
92
+ private readonly dbManager: RAGDatabaseService,
93
+ private readonly embeddingProvider: EmbeddingProvider
94
+ ) {}
95
+
96
+ /**
97
+ * Create a new collection with vector schema
98
+ */
99
+ async createCollection(
100
+ name: string,
101
+ customSchema?: Partial<LanceDBSchema>
102
+ ): Promise<RAGCollection> {
103
+ // Validate collection name
104
+ this.validateCollectionName(name);
105
+
106
+ // Check if already exists
107
+ const exists = await this.dbManager.tableExists(name);
108
+ if (exists) {
109
+ throw new RAGOperationError(`Collection '${name}' already exists`);
110
+ }
111
+
112
+ try {
113
+ const dimensions = await this.embeddingProvider.getDimensions();
114
+
115
+ // Build schema with vector column
116
+ const defaultSchema = {
117
+ id: "string",
118
+ content: "string",
119
+ vector: `vector(${dimensions})`,
120
+ metadata: "string", // JSON string
121
+ timestamp: "int64",
122
+ source: "string",
123
+ };
124
+
125
+ const finalSchema = { ...defaultSchema, ...customSchema };
126
+
127
+ // Create table with initial row (required by LanceDB)
128
+ // Use regular array for vector to match document insertion format
129
+ const initialRow = {
130
+ id: "initial",
131
+ content: "",
132
+ vector: Array(dimensions).fill(0),
133
+ metadata: "{}",
134
+ timestamp: Date.now(),
135
+ source: "system",
136
+ };
137
+
138
+ const table = await this.dbManager.createTable(name, [initialRow], {
139
+ mode: "overwrite",
140
+ });
141
+
142
+ // Delete the initial row
143
+ await table.delete("id = 'initial'");
144
+
145
+ logger.info(`Collection '${name}' created with schema`, { schema: finalSchema });
146
+
147
+ return {
148
+ name,
149
+ schema: finalSchema,
150
+ created_at: Date.now(),
151
+ updated_at: Date.now(),
152
+ };
153
+ } catch (error) {
154
+ return this.handleRAGError(error, `Failed to create collection '${name}'`);
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Add documents to a collection with batching
160
+ */
161
+ async addDocuments(collectionName: string, documents: RAGDocument[]): Promise<void> {
162
+ if (!documents || documents.length === 0) {
163
+ throw new RAGValidationError("Documents array cannot be empty");
164
+ }
165
+
166
+ const table = await this.dbManager.getTable(collectionName);
167
+
168
+ try {
169
+ // Process in batches for efficiency
170
+ for (let i = 0; i < documents.length; i += RAGOperations.BATCH_SIZE) {
171
+ const batch = documents.slice(i, i + RAGOperations.BATCH_SIZE);
172
+
173
+ const processedDocs = await this.processBatch(batch);
174
+ await table.add(processedDocs as unknown as Record<string, unknown>[]);
175
+
176
+ logger.debug(
177
+ `Added batch of ${processedDocs.length} documents to '${collectionName}'`
178
+ );
179
+ }
180
+
181
+ logger.info(
182
+ `Successfully added ${documents.length} documents to collection '${collectionName}'`
183
+ );
184
+ } catch (error) {
185
+ return this.handleRAGError(
186
+ error,
187
+ `Failed to add documents to collection '${collectionName}'`
188
+ );
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Process a batch of documents for insertion
194
+ */
195
+ private async processBatch(documents: RAGDocument[]): Promise<LanceDBStoredDocument[]> {
196
+ return Promise.all(
197
+ documents.map(async (doc) => {
198
+ // Validate document structure
199
+ this.validateDocument(doc);
200
+
201
+ const vector = doc.vector || (await this.embeddingProvider.embed(doc.content));
202
+
203
+ const storedDoc: LanceDBStoredDocument = {
204
+ id: doc.id || this.generateDocumentId(),
205
+ content: doc.content,
206
+ vector: Array.from(vector),
207
+ metadata: JSON.stringify(doc.metadata || {}),
208
+ timestamp: doc.timestamp || Date.now(),
209
+ source: doc.source || "user",
210
+ };
211
+
212
+ return storedDoc;
213
+ })
214
+ );
215
+ }
216
+
217
+ /**
218
+ * Perform semantic search on a collection
219
+ */
220
+ async performSemanticSearch(
221
+ collectionName: string,
222
+ queryText: string,
223
+ topK = 5
224
+ ): Promise<RAGQueryResult[]> {
225
+ return this.performSemanticSearchWithFilter(collectionName, queryText, topK, undefined);
226
+ }
227
+
228
+ /**
229
+ * Perform semantic search with optional SQL prefilter
230
+ * The filter is applied BEFORE vector search for proper isolation
231
+ * @param filter SQL-style filter string, applied as prefilter during vector search
232
+ */
233
+ async performSemanticSearchWithFilter(
234
+ collectionName: string,
235
+ queryText: string,
236
+ topK = 5,
237
+ filter?: string
238
+ ): Promise<RAGQueryResult[]> {
239
+ // Validate inputs early
240
+ this.validateSearchInputs(collectionName, queryText, topK);
241
+
242
+ const table = await this.dbManager.getTable(collectionName);
243
+
244
+ try {
245
+ // Generate query embedding
246
+ const queryVector = await this.embeddingProvider.embed(queryText);
247
+
248
+ // Perform vector search with optional filter
249
+ const results = await this.executeVectorSearch(table, queryVector, topK, filter);
250
+
251
+ logger.info(
252
+ `Semantic search completed on '${collectionName}': found ${results.length} results`,
253
+ { filter: filter || "none" }
254
+ );
255
+
256
+ return results;
257
+ } catch (error) {
258
+ return this.handleRAGError(
259
+ error,
260
+ `Failed to perform semantic search on collection '${collectionName}'`
261
+ );
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Delete a document by its ID
267
+ */
268
+ async deleteDocumentById(collectionName: string, documentId: string): Promise<void> {
269
+ const table = await this.dbManager.getTable(collectionName);
270
+
271
+ try {
272
+ // Escape single quotes in the ID for SQL safety
273
+ const escapedId = documentId.replace(/'/g, "''");
274
+ await table.delete(`id = '${escapedId}'`);
275
+ logger.debug(`Deleted document '${documentId}' from collection '${collectionName}'`);
276
+ } catch (error) {
277
+ // Log but don't throw - document might not exist
278
+ logger.debug(`Could not delete document '${documentId}': ${error}`);
279
+ }
280
+ }
281
+
282
+ /**
283
+ * Execute vector search with optional prefilter and transform results
284
+ */
285
+ private async executeVectorSearch(
286
+ table: Table,
287
+ queryVector: Float32Array,
288
+ topK: number,
289
+ filter?: string
290
+ ): Promise<RAGQueryResult[]> {
291
+ const searchQuery = this.createVectorSearchQuery(table, queryVector, topK, filter);
292
+ const results = await this.executeLanceDBQuery(searchQuery);
293
+ return this.transformSearchResults(results);
294
+ }
295
+
296
+ /**
297
+ * Create a vector search query with optional SQL prefilter
298
+ * The filter is applied BEFORE vector search (prefilter by default in LanceDB)
299
+ */
300
+ private createVectorSearchQuery(
301
+ table: Table,
302
+ queryVector: Float32Array,
303
+ topK: number,
304
+ filter?: string
305
+ ): VectorQuery {
306
+ logger.debug(`Creating vector search with topK=${topK}, vector_dims=${queryVector.length}, filter=${filter || "none"}`);
307
+
308
+ let query = table.search(Array.from(queryVector)).limit(topK) as VectorQuery;
309
+
310
+ // Apply prefilter if provided - this filters BEFORE vector search
311
+ if (filter) {
312
+ query = query.where(filter) as VectorQuery;
313
+ }
314
+
315
+ return query;
316
+ }
317
+
318
+ /**
319
+ * Execute LanceDB query with fallback approaches
320
+ */
321
+ private async executeLanceDBQuery(searchQuery: VectorQuery): Promise<LanceDBResult[]> {
322
+ return this.withQueryErrorHandling(async () => {
323
+ const results =
324
+ (await this.tryToArrayQuery(searchQuery)) ??
325
+ (await this.tryExecuteQuery(searchQuery)) ??
326
+ (await this.tryIterateQuery(searchQuery));
327
+
328
+ this.logQueryResults(results);
329
+ return results;
330
+ }, "Vector search execution failed");
331
+ }
332
+
333
+ /**
334
+ * Try executing query using toArray() method
335
+ */
336
+ private async tryToArrayQuery(searchQuery: VectorQuery): Promise<LanceDBResult[] | null> {
337
+ if (typeof searchQuery.toArray !== "function") return null;
338
+
339
+ const queryResults = await searchQuery.toArray();
340
+ logger.debug(`Query executed with toArray(), got ${queryResults.length} results`);
341
+ return queryResults;
342
+ }
343
+
344
+ /**
345
+ * Try executing query using execute() method
346
+ */
347
+ private async tryExecuteQuery(searchQuery: VectorQuery): Promise<LanceDBResult[] | null> {
348
+ const queryWithExecute = searchQuery as VectorQuery & { execute?: () => Promise<unknown> };
349
+ if (typeof queryWithExecute.execute !== "function") return null;
350
+
351
+ const queryResults = await queryWithExecute.execute();
352
+ logger.debug("Query executed with execute()");
353
+
354
+ if (Array.isArray(queryResults)) {
355
+ return queryResults;
356
+ }
357
+
358
+ if (queryResults) {
359
+ const results: LanceDBResult[] = [];
360
+ for await (const item of queryResults) {
361
+ results.push(item as unknown as LanceDBResult);
362
+ }
363
+ return results;
364
+ }
365
+
366
+ return null;
367
+ }
368
+
369
+ /**
370
+ * Try executing query using direct iteration
371
+ */
372
+ private async tryIterateQuery(searchQuery: VectorQuery): Promise<LanceDBResult[]> {
373
+ logger.debug("Trying direct iteration");
374
+ const results: LanceDBResult[] = [];
375
+
376
+ for await (const item of searchQuery) {
377
+ results.push(item as unknown as LanceDBResult);
378
+ }
379
+
380
+ return results;
381
+ }
382
+
383
+ /**
384
+ * Higher-order function for consistent error handling in query operations
385
+ */
386
+ private async withQueryErrorHandling<T>(
387
+ operation: () => Promise<T>,
388
+ errorMessage: string
389
+ ): Promise<T> {
390
+ try {
391
+ return await operation();
392
+ } catch (error) {
393
+ logger.error(errorMessage, { error });
394
+ throw new Error(`${errorMessage}: ${error}`, { cause: error });
395
+ }
396
+ }
397
+
398
+ /**
399
+ * Centralized error handling for RAG operations
400
+ * Preserves validation/operation errors, wraps others in RAGOperationError
401
+ */
402
+ private handleRAGError(error: unknown, message: string): never {
403
+ if (error instanceof RAGValidationError || error instanceof RAGOperationError) {
404
+ throw error;
405
+ }
406
+ handleError(error, message, { logLevel: "error" });
407
+ throw new RAGOperationError(message, error as Error);
408
+ }
409
+
410
+ /**
411
+ * Log query results for debugging
412
+ */
413
+ private logQueryResults(results: LanceDBResult[]): void {
414
+ logger.debug(`Vector search collected ${results.length} results`);
415
+
416
+ if (results.length > 0) {
417
+ logger.debug(`First result structure: ${JSON.stringify(Object.keys(results[0]))}`);
418
+ logger.debug("First result sample:", {
419
+ id: results[0].id,
420
+ content_preview: results[0].content?.substring(0, 50),
421
+ has_vector: !!results[0].vector,
422
+ distance: results[0]._distance,
423
+ });
424
+ }
425
+ }
426
+
427
+ /**
428
+ * Transform LanceDB results to RAGQueryResult format
429
+ */
430
+ private transformSearchResults(results: LanceDBResult[]): RAGQueryResult[] {
431
+ return results.map((result) => this.transformSingleResult(result));
432
+ }
433
+
434
+ /**
435
+ * Transform a single LanceDB result
436
+ */
437
+ private transformSingleResult(result: LanceDBResult): RAGQueryResult {
438
+ return {
439
+ document: mapLanceResultToDocument(result),
440
+ score: calculateRelevanceScore(result._distance),
441
+ };
442
+ }
443
+
444
+ /**
445
+ * Delete a collection
446
+ */
447
+ async deleteCollection(name: string): Promise<void> {
448
+ const exists = await this.dbManager.tableExists(name);
449
+ if (!exists) {
450
+ throw new RAGOperationError(`Collection '${name}' does not exist`);
451
+ }
452
+
453
+ await this.dbManager.dropTable(name);
454
+ logger.info(`Collection '${name}' deleted successfully`);
455
+ }
456
+
457
+ /**
458
+ * List all collections
459
+ */
460
+ async listCollections(): Promise<string[]> {
461
+ return this.dbManager.listTables();
462
+ }
463
+
464
+ /**
465
+ * Get collection statistics including document counts by agent.
466
+ * Uses LanceDB's countRows with SQL filtering for efficiency.
467
+ *
468
+ * @param collectionName Name of the collection to query
469
+ * @param agentPubkey Optional agent pubkey to count their contributions
470
+ * @returns Object with total count and optional agent-specific count
471
+ */
472
+ /**
473
+ * Escape a string for use in SQL LIKE pattern.
474
+ * Escapes: single quotes ('), double quotes ("), backslashes (\), and LIKE wildcards (%, _).
475
+ *
476
+ * IMPORTANT: DataFusion (used by LanceDB) has NO default escape character.
477
+ * The backslash escapes here only work when paired with ESCAPE '\\' clause.
478
+ * See: https://github.com/apache/datafusion/issues/13291
479
+ *
480
+ * Note: Agent pubkeys are hex strings (0-9, a-f) so most escaping isn't strictly needed,
481
+ * but we escape properly for defense-in-depth and to handle any future metadata fields.
482
+ */
483
+ private escapeSqlLikeValue(value: string): string {
484
+ return value
485
+ .replace(/\\/g, "\\\\") // Escape backslashes first
486
+ .replace(/'/g, "''") // SQL standard: escape single quote by doubling
487
+ .replace(/"/g, '\\"') // Escape double quotes
488
+ .replace(/%/g, "\\%") // Escape LIKE wildcard %
489
+ .replace(/_/g, "\\_"); // Escape LIKE wildcard _
490
+ }
491
+
492
+ async getCollectionStats(
493
+ collectionName: string,
494
+ agentPubkey?: string
495
+ ): Promise<{ totalCount: number; agentCount?: number }> {
496
+ const table = await this.dbManager.getTable(collectionName);
497
+
498
+ try {
499
+ // Get total document count
500
+ const totalCount = await table.countRows();
501
+
502
+ // If agentPubkey provided, count documents attributed to this agent
503
+ // The metadata field is stored as JSON string, so we use LIKE for matching
504
+ let agentCount: number | undefined;
505
+ if (agentPubkey) {
506
+ // SQL filter for JSON string field containing agent_pubkey
507
+ // Format: metadata LIKE '%"agent_pubkey":"<pubkey>"%' ESCAPE '\\'
508
+ // ESCAPE clause is required because DataFusion has no default escape character
509
+ const escapedPubkey = this.escapeSqlLikeValue(agentPubkey);
510
+ const filter = `metadata LIKE '%"agent_pubkey":"${escapedPubkey}"%' ESCAPE '\\\\'`;
511
+ agentCount = await table.countRows(filter);
512
+ }
513
+
514
+ return { totalCount, agentCount };
515
+ } catch (error) {
516
+ return this.handleRAGError(
517
+ error,
518
+ `Failed to get stats for collection '${collectionName}'`
519
+ );
520
+ }
521
+ }
522
+
523
+ /**
524
+ * Get statistics for all collections with agent attribution.
525
+ * Efficiently retrieves counts for all collections in parallel.
526
+ *
527
+ * @param agentPubkey Agent pubkey to count contributions for
528
+ * @returns Array of collection stats with agent and total counts
529
+ */
530
+ async getAllCollectionStats(
531
+ agentPubkey: string
532
+ ): Promise<Array<{ name: string; agentDocCount: number; totalDocCount: number }>> {
533
+ const collections = await this.listCollections();
534
+
535
+ const stats = await Promise.all(
536
+ collections.map(async (name) => {
537
+ try {
538
+ const { totalCount, agentCount } = await this.getCollectionStats(name, agentPubkey);
539
+ return {
540
+ name,
541
+ agentDocCount: agentCount ?? 0,
542
+ totalDocCount: totalCount,
543
+ };
544
+ } catch (error) {
545
+ // Log but don't fail - return zero counts for problematic collections
546
+ logger.warn(`Failed to get stats for collection '${name}':`, error);
547
+ return {
548
+ name,
549
+ agentDocCount: 0,
550
+ totalDocCount: 0,
551
+ };
552
+ }
553
+ })
554
+ );
555
+
556
+ return stats;
557
+ }
558
+
559
+ /**
560
+ * Bulk upsert documents into a collection using LanceDB mergeInsert.
561
+ *
562
+ * Uses the `id` column as the merge key:
563
+ * - Existing rows with matching `id` are updated in-place
564
+ * - New rows (no matching `id`) are inserted
565
+ *
566
+ * This creates one LanceDB version per chunk of BATCH_SIZE instead of
567
+ * 2N versions (1 delete + 1 insert per document) with the old approach.
568
+ *
569
+ * Failures are isolated per chunk: if one chunk throws, the remaining
570
+ * chunks still proceed. The returned `BulkUpsertResult.failedIndices`
571
+ * tells the caller exactly which input documents failed so only
572
+ * successful ones are marked as complete.
573
+ */
574
+ async bulkUpsert(collectionName: string, documents: RAGDocument[]): Promise<BulkUpsertResult> {
575
+ if (!documents || documents.length === 0) {
576
+ return { upsertedCount: 0, failedIndices: [] };
577
+ }
578
+
579
+ const table = await this.dbManager.getTable(collectionName);
580
+
581
+ let totalUpserted = 0;
582
+ const failedIndices: number[] = [];
583
+
584
+ // Process in batches for embedding generation
585
+ for (let i = 0; i < documents.length; i += RAGOperations.BATCH_SIZE) {
586
+ const chunkEnd = Math.min(i + RAGOperations.BATCH_SIZE, documents.length);
587
+ const batch = documents.slice(i, chunkEnd);
588
+
589
+ try {
590
+ const processedDocs = await this.processBatch(batch);
591
+
592
+ // Use mergeInsert with `id` as merge key for atomic upsert
593
+ const result = await table
594
+ .mergeInsert("id")
595
+ .whenMatchedUpdateAll()
596
+ .whenNotMatchedInsertAll()
597
+ .execute(processedDocs as unknown as Record<string, unknown>[]);
598
+
599
+ totalUpserted += result.numInsertedRows + result.numUpdatedRows;
600
+
601
+ logger.debug(
602
+ `Bulk upsert batch: ${processedDocs.length} docs → ${result.numInsertedRows} inserted, ${result.numUpdatedRows} updated in '${collectionName}'`
603
+ );
604
+ } catch (error) {
605
+ const message = error instanceof Error ? error.message : String(error);
606
+ logger.error(`Bulk upsert chunk failed (indices ${i}..${chunkEnd - 1})`, {
607
+ collectionName,
608
+ chunkSize: batch.length,
609
+ error: message,
610
+ });
611
+ // Record every index in this failed chunk
612
+ for (let idx = i; idx < chunkEnd; idx++) {
613
+ failedIndices.push(idx);
614
+ }
615
+ }
616
+ }
617
+
618
+ logger.info(
619
+ `Bulk upsert complete: ${totalUpserted} upserted, ${failedIndices.length} failed in '${collectionName}'`
620
+ );
621
+
622
+ return { upsertedCount: totalUpserted, failedIndices };
623
+ }
624
+
625
+ /**
626
+ * Generate a unique document ID
627
+ */
628
+ private generateDocumentId(): string {
629
+ return `doc_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
630
+ }
631
+
632
+ /**
633
+ * Validate collection name format
634
+ */
635
+ private validateCollectionName(name: string): void {
636
+ if (!name || typeof name !== "string") {
637
+ throw new RAGValidationError("Collection name must be a non-empty string");
638
+ }
639
+
640
+ if (!/^[a-zA-Z0-9_]+$/.test(name)) {
641
+ throw new RAGValidationError(
642
+ "Collection name must be alphanumeric with underscores only"
643
+ );
644
+ }
645
+
646
+ if (name.length > 64) {
647
+ throw new RAGValidationError("Collection name must be 64 characters or less");
648
+ }
649
+ }
650
+
651
+ /**
652
+ * Validate search input parameters
653
+ */
654
+ private validateSearchInputs(collectionName: string, queryText: string, topK: number): void {
655
+ this.validateCollectionName(collectionName);
656
+
657
+ if (!queryText || queryText.trim().length === 0) {
658
+ throw new RAGValidationError("Query text cannot be empty");
659
+ }
660
+
661
+ if (!Number.isInteger(topK) || topK < 1 || topK > 100) {
662
+ throw new RAGValidationError("topK must be an integer between 1 and 100");
663
+ }
664
+ }
665
+
666
+ /**
667
+ * Validate document structure
668
+ */
669
+ private validateDocument(doc: RAGDocument): void {
670
+ if (!doc.content || doc.content.trim().length === 0) {
671
+ throw new RAGValidationError("Document content cannot be empty");
672
+ }
673
+
674
+ if (doc.id && typeof doc.id !== "string") {
675
+ throw new RAGValidationError("Document ID must be a string");
676
+ }
677
+
678
+ if (doc.metadata && typeof doc.metadata !== "object") {
679
+ throw new RAGValidationError("Document metadata must be an object");
680
+ }
681
+ }
682
+ }