@johpaz/hive-sdk 0.1.4 → 0.1.5

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 (272) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/README.md +78 -23
  3. package/bunfig.toml +4 -2
  4. package/docs/API-AGENTS.md +78 -27
  5. package/docs/API-CONTEXT-COMPILER.md +31 -34
  6. package/docs/API-TOOLS-SKILLS-CHANNELS.md +58 -22
  7. package/docs/HIVE-HARNESS.md +1 -1
  8. package/docs/INDEX.md +4 -4
  9. package/docs/TEMPLATE-HIVE-APP.md +10 -10
  10. package/package.json +9 -4
  11. package/packages/cli/package.json +2 -2
  12. package/packages/cli/src/commands/create-app.test.ts +36 -7
  13. package/packages/cli/src/commands/init.ts +3 -3
  14. package/packages/cli/src/commands/run.ts +1 -1
  15. package/packages/cli/src/commands/test.ts +37 -25
  16. package/packages/cli/src/commands/trace.ts +30 -28
  17. package/packages/cli/templates/hive-app/.env.example +10 -2
  18. package/packages/cli/templates/hive-app/README.md +103 -0
  19. package/packages/cli/templates/hive-app/hive.config.ts +9 -3
  20. package/packages/cli/templates/hive-app/src/agents/coordinator.ts +8 -1
  21. package/packages/cli/templates/hive-app/src/main.ts +12 -19
  22. package/packages/core/package.json +5 -4
  23. package/packages/core/src/agent/acceptance-checks.ts +166 -0
  24. package/packages/core/src/agent/agent-catalog.ts +348 -0
  25. package/packages/core/src/agent/agent-loop.ts +1373 -0
  26. package/packages/core/src/agent/capability-search.ts +186 -0
  27. package/packages/core/src/agent/catalog-selector.ts +103 -0
  28. package/packages/core/src/agent/{Compaction.ts → compaction.ts} +86 -63
  29. package/packages/core/src/agent/context-compiler.ts +689 -0
  30. package/packages/core/src/agent/conversation-store.ts +381 -0
  31. package/packages/core/src/agent/curator.ts +276 -0
  32. package/packages/core/src/agent/delegation-runtime.ts +241 -0
  33. package/packages/core/src/agent/goal-runner.ts +323 -0
  34. package/packages/core/src/agent/index.ts +17 -12
  35. package/packages/core/src/agent/llm-client.ts +266 -0
  36. package/packages/core/src/agent/llm-providers/anthropic.ts +264 -0
  37. package/packages/core/src/agent/llm-providers/deepseek.ts +8 -0
  38. package/packages/core/src/agent/{providers → llm-providers}/gemini.ts +98 -60
  39. package/packages/core/src/agent/llm-providers/groq.ts +5 -0
  40. package/packages/core/src/agent/llm-providers/hiveagents.ts +253 -0
  41. package/packages/core/src/agent/{providers → llm-providers}/interface.ts +73 -13
  42. package/packages/core/src/agent/llm-providers/kimi.ts +8 -0
  43. package/packages/core/src/agent/llm-providers/minimax.ts +13 -0
  44. package/packages/core/src/agent/llm-providers/mistral.ts +5 -0
  45. package/packages/core/src/agent/llm-providers/modelscope.ts +5 -0
  46. package/packages/core/src/agent/llm-providers/nvidia.ts +5 -0
  47. package/packages/core/src/agent/{providers → llm-providers}/ollama.ts +31 -5
  48. package/packages/core/src/agent/llm-providers/openai-compat-base.ts +418 -0
  49. package/packages/core/src/agent/llm-providers/openai.ts +5 -0
  50. package/packages/core/src/agent/llm-providers/opencode-go.ts +9 -0
  51. package/packages/core/src/agent/llm-providers/openrouter.ts +5 -0
  52. package/packages/core/src/agent/llm-providers/qwen.ts +5 -0
  53. package/packages/core/src/agent/llm-providers/z-ai.ts +5 -0
  54. package/packages/core/src/agent/minimal-loadout.ts +47 -0
  55. package/packages/core/src/agent/playbook-selector.ts +119 -0
  56. package/packages/core/src/agent/{PromptBuilder.ts → prompt-builder.ts} +21 -22
  57. package/packages/core/src/{harness → agent}/proof-packet.ts +16 -21
  58. package/packages/core/src/agent/providers/index.ts +35 -16
  59. package/packages/core/src/agent/reflector.ts +320 -0
  60. package/packages/core/src/agent/routing-intent.ts +22 -0
  61. package/packages/core/src/{harness → agent}/run-epoch.ts +4 -3
  62. package/packages/core/src/{harness → agent}/run-store.ts +142 -81
  63. package/packages/core/src/agent/{Service.ts → service.ts} +37 -26
  64. package/packages/core/src/agent/skill-selector.ts +374 -0
  65. package/packages/core/src/agent/stuck-loop.ts +209 -0
  66. package/packages/core/src/agent/{selectors/ToolSelector.ts → tool-selector.ts} +188 -178
  67. package/packages/core/src/{ace/Tracer.ts → agent/tracer.ts} +37 -27
  68. package/packages/core/src/api/createAgent.test.ts +139 -27
  69. package/packages/core/src/api/createAgent.ts +232 -44
  70. package/packages/core/src/artifacts/store.ts +162 -0
  71. package/packages/core/src/canvas/canvas-manager.ts +161 -0
  72. package/packages/core/src/canvas/canvas.test.ts +8 -4
  73. package/packages/core/src/canvas/emitter.ts +131 -80
  74. package/packages/core/src/canvas/index.ts +1 -3
  75. package/packages/core/src/channels/base.ts +9 -1
  76. package/packages/core/src/channels/discord.ts +5 -4
  77. package/packages/core/src/channels/manager.ts +122 -30
  78. package/packages/core/src/channels/slack.ts +5 -4
  79. package/packages/core/src/channels/telegram.ts +36 -6
  80. package/packages/core/src/channels/webchat.ts +11 -10
  81. package/packages/core/src/channels/whatsapp.ts +23 -7
  82. package/packages/core/src/config/index.ts +13 -2
  83. package/packages/core/src/config/loader.ts +71 -29
  84. package/packages/core/src/ethics/EthicsGuard.test.ts +90 -36
  85. package/packages/core/src/ethics/EthicsGuard.ts +51 -47
  86. package/packages/core/src/events/agent-bus.ts +44 -68
  87. package/packages/core/src/events/channel-narration.ts +150 -0
  88. package/packages/core/src/events/narration.ts +82 -0
  89. package/packages/core/src/events/tool-narration.ts +62 -0
  90. package/packages/core/src/gateway/delegation-groups.ts +258 -0
  91. package/packages/core/src/{harness → gateway}/durable-queue.ts +102 -42
  92. package/packages/core/src/{harness → gateway}/job-store.ts +85 -48
  93. package/packages/core/src/gateway/lane-queue.ts +173 -0
  94. package/packages/core/src/gateway/notification-inbox.ts +57 -0
  95. package/packages/core/src/gateway/server.ts +1 -1
  96. package/packages/core/src/harness/index.ts +46 -27
  97. package/packages/core/src/index.ts +33 -27
  98. package/packages/core/src/mcp/hot-reload.ts +32 -23
  99. package/packages/core/src/mcp/index.ts +6 -3
  100. package/packages/core/src/mcp/singleton.ts +1 -4
  101. package/packages/core/src/mcp/tool-sync.ts +138 -0
  102. package/packages/core/src/memory/Scratchpad.test.ts +39 -20
  103. package/packages/core/src/memory/Scratchpad.ts +27 -34
  104. package/packages/core/src/multimodal/vision-service.ts +44 -38
  105. package/packages/core/src/resilience/retry.ts +95 -0
  106. package/packages/core/src/scheduler/CronScheduler.ts +334 -287
  107. package/packages/core/src/scheduler/index.ts +9 -7
  108. package/packages/core/src/scheduler/integration.ts +46 -26
  109. package/packages/core/src/scheduler/scheduler.test.ts +9 -13
  110. package/packages/core/src/scheduler/types.ts +7 -2
  111. package/packages/core/src/security/Pairing.ts +1 -1
  112. package/packages/core/src/skills/bundled/a2ui/a2ui_dashboard/SKILL.md +176 -0
  113. package/packages/core/src/skills/bundled/a2ui/a2ui_form/SKILL.md +202 -0
  114. package/packages/core/src/skills/bundled/a2ui/a2ui_interactive/SKILL.md +206 -0
  115. package/packages/core/src/skills/bundled/agents/agent_spawner/SKILL.md +173 -0
  116. package/packages/core/src/skills/bundled/agents/memory_manager/SKILL.md +143 -0
  117. package/packages/core/src/skills/bundled/agents/research_and_remember/SKILL.md +139 -0
  118. package/packages/core/src/skills/bundled/agents/task_orchestrator/SKILL.md +98 -0
  119. package/packages/core/src/skills/bundled/api/api_client/SKILL.md +132 -0
  120. package/packages/core/src/skills/bundled/cli/cli_pipeline/SKILL.md +135 -0
  121. package/packages/core/src/skills/bundled/cli/cli_safe_exec/SKILL.md +125 -0
  122. package/packages/core/src/skills/bundled/cli/software_engineering/SKILL.md +23 -0
  123. package/packages/core/src/skills/bundled/cron_manager/SKILL.md +188 -0
  124. package/packages/core/src/skills/bundled/cron_reminder/SKILL.md +112 -0
  125. package/packages/core/src/skills/bundled/filesystem/file_manager/SKILL.md +118 -0
  126. package/packages/core/src/skills/bundled/filesystem/file_read_and_summarize/SKILL.md +109 -0
  127. package/packages/core/src/skills/bundled/filesystem/file_writer/SKILL.md +129 -0
  128. package/packages/core/src/skills/bundled/filesystem/workspace_file_operator/SKILL.md +22 -0
  129. package/packages/core/src/skills/bundled/office/office_document_manager/SKILL.md +262 -0
  130. package/packages/core/src/skills/bundled/search_knowledge/capability_discovery/SKILL.md +75 -0
  131. package/packages/core/src/skills/bundled/web/browser_automate/SKILL.md +120 -0
  132. package/packages/core/src/skills/bundled/web/browser_scrape/SKILL.md +109 -0
  133. package/packages/core/src/skills/bundled/web/web_monitor/SKILL.md +127 -0
  134. package/packages/core/src/skills/bundled/web/web_research/SKILL.md +119 -0
  135. package/packages/core/src/skills/bundled-data.generated.ts +731 -2678
  136. package/packages/core/src/skills/skills.test.ts +52 -11
  137. package/packages/core/src/{harness → storage}/boot-id.ts +5 -2
  138. package/packages/core/src/storage/bootstrap.ts +151 -0
  139. package/packages/core/src/storage/causal-events.ts +84 -0
  140. package/packages/core/src/storage/collections.ts +680 -0
  141. package/packages/core/src/storage/crypto.ts +205 -74
  142. package/packages/core/src/{harness/db-helpers.ts → storage/hive.ts} +63 -7
  143. package/packages/core/src/storage/hivedb.ts +61 -0
  144. package/packages/core/src/storage/index.ts +111 -18
  145. package/packages/core/src/storage/model-id.ts +53 -0
  146. package/packages/core/src/storage/onboarding.ts +540 -972
  147. package/packages/core/src/storage/reconcile.ts +238 -0
  148. package/packages/core/src/storage/seed.ts +572 -406
  149. package/packages/core/src/storage/usage.ts +285 -225
  150. package/packages/core/src/storage/user-email.ts +11 -0
  151. package/packages/core/src/swarm/AgentExecutor.ts +1 -1
  152. package/packages/core/src/swarm/EventBridge.ts +1 -1
  153. package/packages/core/src/swarm/index.ts +12 -9
  154. package/packages/core/src/tool-runtime/index.ts +146 -23
  155. package/packages/core/src/tool-runtime/tool-worker.ts +2 -2
  156. package/packages/core/src/tool-runtime/worker-tools.ts +27 -0
  157. package/packages/core/src/{canvas/a2ui-tools.ts → tools/a2ui/index.ts} +17 -8
  158. package/packages/core/src/tools/agents/get-available-models.ts +36 -54
  159. package/packages/core/src/tools/agents/index.ts +784 -292
  160. package/packages/core/src/tools/api/api-request.test.ts +164 -0
  161. package/packages/core/src/tools/api/api-request.ts +174 -0
  162. package/packages/core/src/tools/api/index.ts +16 -0
  163. package/packages/core/src/tools/cli/index.ts +4 -0
  164. package/packages/core/src/tools/core/index.ts +281 -112
  165. package/packages/core/src/tools/cron/index.ts +121 -124
  166. package/packages/core/src/tools/index.ts +63 -78
  167. package/packages/core/src/tools/office/office-escribir-xlsx.ts +3 -1
  168. package/packages/core/src/tools/types.ts +3 -1
  169. package/packages/core/src/tools/web/artifact-inspect.ts +23 -0
  170. package/packages/core/src/tools/web/browser-screenshot.ts +26 -5
  171. package/packages/core/src/tools/web/browser-service.ts +5 -0
  172. package/packages/core/src/tools/web/browser-type.ts +3 -8
  173. package/packages/core/src/tools/web/index.ts +4 -4
  174. package/packages/core/src/voice/index.ts +89 -63
  175. package/packages/core/src/workers/agent.worker.ts +2 -2
  176. package/packages/core/src/workers/workers.test.ts +3 -10
  177. package/scripts/bump-version.ts +248 -0
  178. package/scripts/generate-skill-bundle.ts +108 -0
  179. package/test/agent-loop-terminal-synthesis.test.ts +32 -0
  180. package/test/catalog-agents-stay-enabled.test.ts +117 -0
  181. package/test/causal-events.test.ts +117 -0
  182. package/test/compaction.test.ts +105 -0
  183. package/test/context-compiler.test.ts +269 -0
  184. package/test/curator.test.ts +130 -0
  185. package/test/durable-queue.test.ts +114 -0
  186. package/test/harness-barrel.test.ts +64 -0
  187. package/test/hive-helpers.test.ts +130 -0
  188. package/test/hivedb-search.test.ts +189 -0
  189. package/test/internal-turns.test.ts +166 -0
  190. package/test/job-idempotency.test.ts +68 -0
  191. package/test/job-retry-backoff.test.ts +184 -0
  192. package/test/job-store.test.ts +381 -0
  193. package/test/llm-retry.test.ts +97 -0
  194. package/test/memory-perf.test.ts +774 -0
  195. package/test/minimal-loadout.test.ts +78 -0
  196. package/test/model-catalog.test.ts +105 -0
  197. package/test/preload.ts +12 -0
  198. package/test/reflector.test.ts +320 -0
  199. package/test/retention-cap.test.ts +91 -0
  200. package/test/retired-capabilities-pruned.test.ts +192 -0
  201. package/test/run-store.test.ts +355 -0
  202. package/test/scratchpad.test.ts +74 -0
  203. package/test/secrets-durability.test.ts +119 -0
  204. package/test/seed-model-reseed.test.ts +155 -0
  205. package/test/setup-agent-seed.test.ts +264 -0
  206. package/test/tool-inventory.test.ts +65 -0
  207. package/test/tool-runtime.test.ts +258 -0
  208. package/test/toon.test.ts +429 -0
  209. package/tsconfig.json +2 -0
  210. package/packages/core/src/ace/Curator.ts +0 -158
  211. package/packages/core/src/ace/Reflector.ts +0 -200
  212. package/packages/core/src/ace/index.ts +0 -4
  213. package/packages/core/src/agent/AgentRunner.ts +0 -711
  214. package/packages/core/src/agent/ContextCompiler.ts +0 -567
  215. package/packages/core/src/agent/ContextGuard.ts +0 -91
  216. package/packages/core/src/agent/ConversationStore.ts +0 -254
  217. package/packages/core/src/agent/Hooks.ts +0 -166
  218. package/packages/core/src/agent/StuckLoop.ts +0 -133
  219. package/packages/core/src/agent/providers/LLMClient.ts +0 -149
  220. package/packages/core/src/agent/providers/anthropic.ts +0 -212
  221. package/packages/core/src/agent/providers/openai-compat.ts +0 -231
  222. package/packages/core/src/agent/selectors/PlaybookSelector.ts +0 -121
  223. package/packages/core/src/agent/selectors/SkillSelector.ts +0 -322
  224. package/packages/core/src/agent/selectors/index.ts +0 -6
  225. package/packages/core/src/auth/auth.ts +0 -121
  226. package/packages/core/src/auth/index.ts +0 -1
  227. package/packages/core/src/canvas/CanvasManager.ts +0 -390
  228. package/packages/core/src/canvas/canvas-tools.ts +0 -448
  229. package/packages/core/src/harness/collections.ts +0 -98
  230. package/packages/core/src/harness/goal-verifier.ts +0 -141
  231. package/packages/core/src/harness/harness.test.ts +0 -236
  232. package/packages/core/src/harness/reconcile.ts +0 -149
  233. package/packages/core/src/mcp/MCPToolAdapter.ts +0 -176
  234. package/packages/core/src/multimodal/VisionService.ts +0 -293
  235. package/packages/core/src/scheduler/dag/AgentExecutor.ts +0 -53
  236. package/packages/core/src/scheduler/dag/DAGScheduler.ts +0 -250
  237. package/packages/core/src/scheduler/dag/EventBridge.ts +0 -122
  238. package/packages/core/src/scheduler/dag/TaskGraph.ts +0 -192
  239. package/packages/core/src/scheduler/dag/TaskNode.ts +0 -97
  240. package/packages/core/src/scheduler/dag/TaskResult.ts +0 -22
  241. package/packages/core/src/scheduler/dag/errors.ts +0 -37
  242. package/packages/core/src/scheduler/dag/index.ts +0 -26
  243. package/packages/core/src/scheduler/dag/presets/ResearchPreset.ts +0 -97
  244. package/packages/core/src/scheduler/dag/strategies/ParallelStrategy.ts +0 -21
  245. package/packages/core/src/scheduler/dag/strategies/PriorityStrategy.ts +0 -46
  246. package/packages/core/src/storage/HiveDBStorage.ts +0 -64
  247. package/packages/core/src/storage/SQLiteStorage.ts +0 -414
  248. package/packages/core/src/storage/hiveSeed.ts +0 -308
  249. package/packages/core/src/storage/hiveStorage.test.ts +0 -38
  250. package/packages/core/src/storage/schema.ts +0 -689
  251. package/packages/core/src/storage/storage.test.ts +0 -37
  252. package/packages/core/src/swarm/AgentBus.ts +0 -460
  253. package/packages/core/src/swarm/EventBus.ts +0 -169
  254. package/packages/core/src/swarm/WorkerPool.ts +0 -236
  255. package/packages/core/src/tools/bridge-events.ts +0 -26
  256. package/packages/core/src/tools/canvas/index.ts +0 -375
  257. package/packages/core/src/tools/codebridge/index.ts +0 -342
  258. package/packages/core/src/tools/meeting/index.ts +0 -353
  259. package/packages/core/src/tools/projects/index.ts +0 -37
  260. package/packages/core/src/tools/projects/project-create.ts +0 -94
  261. package/packages/core/src/tools/projects/project-done.ts +0 -66
  262. package/packages/core/src/tools/projects/project-fail.ts +0 -66
  263. package/packages/core/src/tools/projects/project-list.ts +0 -96
  264. package/packages/core/src/tools/projects/project-update.ts +0 -72
  265. package/packages/core/src/tools/projects/task-create.ts +0 -68
  266. package/packages/core/src/tools/projects/task-evaluate.ts +0 -93
  267. package/packages/core/src/tools/projects/task-update.ts +0 -93
  268. package/packages/core/src/tools/voice/index.ts +0 -104
  269. package/packages/core/src/tools/web/api-request.test.ts +0 -170
  270. package/packages/core/src/tools/web/api-request.ts +0 -239
  271. package/test/setup-db.ts +0 -216
  272. /package/packages/core/src/agent/{NativeTools.ts → native-tools.ts} +0 -0
@@ -1,48 +1,55 @@
1
1
  /**
2
- * FTS5-based Dynamic Tool Selector Module
3
- *
2
+ * HiveDB-based Dynamic Tool Selector Module
3
+ *
4
4
  * Context Compiler Level 3 - Intelligent Tool Selection
5
- *
5
+ *
6
6
  * This module intercepts each message BEFORE calling the LLM and uses
7
- * SQLite FTS5 bm25() scoring to select the most relevant tools (0-4).
8
- *
7
+ * HiveDB BM25 scoring (Spanish stemming + accent folding, per-field boosts)
8
+ * to select the most relevant tools.
9
+ *
9
10
  * DESIGN DECISIONS:
10
- *
11
+ *
11
12
  * 1. Stateless: No memory between turns - each message is evaluated independently.
12
13
  * Rationale: Prevents cascade effects where a bad selection in one turn affects
13
14
  * future turns. Forces fresh evaluation each time.
14
- *
15
- * 2. Maximum 4 tools per turn: Keeps token count low and prevents overwhelming
15
+ *
16
+ * 2. Maximum tools per turn: Keeps token count low and prevents overwhelming
16
17
  * the LLM with irrelevant tools. Forces prioritization.
17
- *
18
- * 3. Relevance threshold: If highest bm25 score < MIN_RELEVANCE_THRESHOLD,
19
- * the message is considered conversational and returns empty array.
20
- * Rationale: Prevents false positives on generic messages like "hola" or
21
- * "cómo estás?" which should not trigger any tools.
22
- *
18
+ *
19
+ * 3. Relative relevance cutoff: BM25 scores are positive (higher = better) but
20
+ * their magnitude depends on corpus and document length, so hits are kept
21
+ * only if they score at least RELEVANCE_RATIO of the top hit. Conversational
22
+ * messages are short-circuited by pattern matching before any search.
23
+ *
23
24
  * 4. Atomic over orchestration: When ambiguous, prefer individual tools over
24
25
  * compound/manager tools. Rationale: Atomic tools are more predictable and
25
26
  * the LLM can combine them as needed.
26
- *
27
- * 5. Performance: Must complete in under 50ms. FTS5 queries are typically
28
- * <5ms for small tool catalogs (<100 tools).
29
- *
27
+ *
28
+ * 5. Performance: Must complete in under 50ms. HiveDB (tantivy) queries are
29
+ * sub-millisecond for small tool catalogs (<100 tools).
30
+ *
30
31
  * 6. Tool categorization: Tools are categorized by semantic domain:
31
32
  * - scheduling (cron tools)
32
- * - projects (project/task management)
33
33
  * - filesystem (file operations)
34
34
  * - web (search/fetch)
35
35
  * - browser (browser automation)
36
36
  * - memory (notes, memory operations)
37
37
  * - code (exec, terminal)
38
- * - canvas (UI rendering)
38
+ * - a2ui (interactive panel rendering)
39
39
  * - agents (agent creation/management)
40
40
  * - core (notify, report_progress, save_note)
41
41
  */
42
42
 
43
- import { getHiveDB } from "../../storage/HiveDBStorage.ts"
44
- import { logger } from "../../utils/logger.ts"
45
- import type { HiveDB, IndexDoc } from "@johpaz/hive-db";
43
+ import { col } from "../storage/hive"
44
+ import type { ToolDoc } from "../storage/collections"
45
+ import { logger } from "../utils/logger"
46
+ import {
47
+ searchCapabilities,
48
+ applyRelativeCutoff,
49
+ replaceCapabilityDocs,
50
+ type CapabilityDoc,
51
+ } from "./capability-search"
52
+ import { isCalendarOperation } from "./routing-intent"
46
53
 
47
54
  const log = logger.child("tool-selector")
48
55
 
@@ -75,18 +82,13 @@ export interface ToolSelectorResult {
75
82
  const MAX_TOOLS_PER_TURN = 12
76
83
 
77
84
  /**
78
- * Minimum bm25 score threshold. Below this = conversational, no tools needed.
79
- *
80
- * CRITICAL: bm25() returns NEGATIVE scores where closer to 0 = more relevant.
81
- * - Score of -5 is MORE relevant than -20
82
- * - We use -30 as threshold to filter noise while allowing valid matches
83
- *
84
- * Previous values: -25 (too strict), -100 (too permissive)
85
- * New value: -30 (balanced filtering, FTS5 MATCH handles the heavy lifting)
85
+ * Relative relevance cutoff: keep a hit only if it scores at least this
86
+ * fraction of the top hit. HiveDB BM25 scores are positive (higher = better)
87
+ * but corpus-dependent, so absolute thresholds don't transfer a ratio does.
86
88
  */
87
- export const MIN_RELEVANCE_THRESHOLD = -30
89
+ const RELEVANCE_RATIO = 0.3
88
90
 
89
- /** Stopwords to filter out before FTS5 query construction */
91
+ /** Stopwords to filter out before BM25 query construction */
90
92
  const STOPWORDS = new Set([
91
93
  "que", "con", "para", "por", "una", "uno", "los", "las", "del",
92
94
  "como", "esta", "esto", "ese", "eso", "the", "and", "for",
@@ -113,15 +115,15 @@ const CONVERSATIONAL_PATTERNS = [
113
115
  //
114
116
  // These 47 tools are the core toolset. Each has:
115
117
  // - name: unique identifier
116
- // - description: what the tool does (used for FTS5 matching)
118
+ // - description: what the tool does (used for BM25 matching)
117
119
  // - category: semantic domain for grouping
118
120
  // - abstractionLevel: atomic (single operation) vs orchestration (manages multiple)
119
121
  //
120
- // The descriptions are enriched with Spanish/English keywords for better FTS5 matching.
122
+ // The descriptions are enriched with Spanish/English keywords for better BM25 matching.
121
123
 
122
124
  export const CORE_TOOL_CATALOG: ToolDescriptor[] = [
123
125
  // Cron tools (cron.*)
124
- { name: "cron.create", description: "Create new cron job: recurring (cron expression) or one-shot (fire_at). Requires 'task' field with instruction for the agent. Spanish keywords: programar tarea, crear recordatorio, agendar, automatizar horario, tarea recurrente, recordatorio diario, una vez", category: "scheduling", abstractionLevel: "atomic" },
126
+ { name: "cron.create", description: "Create a Hive scheduled automation: recurring (cron expression) or one-shot (fire_at). Requires a task instruction. Spanish keywords: programar tarea, crear automatización, ejecutar después, tarea recurrente, recordatorio automático, una vez", category: "scheduling", abstractionLevel: "atomic" },
125
127
  { name: "cron.list", description: "List all cron jobs with next execution times and status. Spanish keywords: ver tareas programadas, listar cronograma, próximas ejecuciones, tareas activas, recordatorios pendientes", category: "scheduling", abstractionLevel: "atomic" },
126
128
  { name: "cron.update", description: "Update an existing cron job: change expression, task instruction, channel, time window, etc. Use cron.list first to get task_id. Spanish keywords: actualizar tarea, modificar cron, editar recordatorio, cambiar horario, actualizar programación", category: "scheduling", abstractionLevel: "atomic" },
127
129
  { name: "cron.pause", description: "Pause a cron job temporarily without deleting it. Spanish keywords: pausar tarea programada, detener temporalmente, suspender recordatorio", category: "scheduling", abstractionLevel: "atomic" },
@@ -130,25 +132,12 @@ export const CORE_TOOL_CATALOG: ToolDescriptor[] = [
130
132
  { name: "cron.trigger", description: "Manually trigger immediate execution of a cron job now. Spanish keywords: ejecutar tarea ahora, forzar ejecución, disparar manualmente", category: "scheduling", abstractionLevel: "atomic" },
131
133
  { name: "cron.history", description: "Get execution history and run logs for a cron job. Spanish keywords: historial ejecuciones, logs tarea, cuándo corrió, registro de ejecuciones", category: "scheduling", abstractionLevel: "atomic" },
132
134
 
133
- // Project management tools (high-level orchestration)
134
- { name: "project_create", description: "Create project with tasks, start new project for complex multi-step work. Spanish keywords: crear proyecto, nuevo proyecto, iniciar trabajo, proyecto nuevo, comenzar proyecto", category: "projects", abstractionLevel: "orchestration" },
135
- { name: "project_list", description: "List all projects with their status. Spanish keywords: listar proyectos, ver proyectos, historial proyectos, todos los proyectos", category: "projects", abstractionLevel: "atomic" },
136
- { name: "project_update", description: "Update project progress, mark progress percentage and status changes. Spanish keywords: actualizar progreso, marcar avance, estado del proyecto, porcentaje completado", category: "projects", abstractionLevel: "atomic" },
137
- { name: "project_done", description: "Mark project complete, close finished projects and archive results. Spanish keywords: proyecto terminado, cerrar proyecto, finalizar, proyecto completado, marcar como hecho", category: "projects", abstractionLevel: "atomic" },
138
- { name: "project_fail", description: "Mark project failed, record failure reason and lessons learned. Spanish keywords: proyecto fallido, error, marcar como fallido, proyecto fracasado, fracaso", category: "projects", abstractionLevel: "atomic" },
139
-
140
- // Task management (atomic)
141
- { name: "task_create", description: "Add task to project, create subtasks and action items within projects. Spanish keywords: crear tarea, nueva tarea, agregar pendiente, agregar tarea, crear subtarea", category: "projects", abstractionLevel: "atomic" },
142
- { name: "task_update", description: "Update task status, mark tasks as complete or in progress. Spanish keywords: actualizar tarea, cambiar estado, marcar completa, tarea completada, tarea en progreso", category: "projects", abstractionLevel: "atomic" },
143
- { name: "task_evaluate", description: "Evaluate task result against acceptance criteria. Spanish keywords: evaluar tarea, validar resultado, criterios aceptación, verificar calidad", category: "projects", abstractionLevel: "atomic" },
144
-
145
135
  // Code execution
146
136
  { name: "cli_exec", description: "Execute shell commands, run bash scripts and system commands. Spanish keywords: ejecutar comando, terminal, línea de comandos, bash, script, comando del sistema", category: "cli", abstractionLevel: "atomic" },
147
137
 
148
138
  // Web tools
149
139
  { name: "web_search", description: "Search web for current information, find up-to-date news facts and research. Spanish keywords: buscar en internet, buscar web, información, noticias, investigación, buscar", category: "web", abstractionLevel: "atomic" },
150
140
  { name: "web_fetch", description: "Fetch content from URL, download and extract content from web pages. Spanish keywords: obtener página, descargar web, extraer contenido, obtener contenido, página web", category: "web", abstractionLevel: "atomic" },
151
- { name: "api_request", description: "Connect to REST APIs, make HTTP requests with authentication and custom headers. Spanish keywords: conectar api, peticion http, llamada api, rest api, endpoint, bearer token, api key, basic auth", category: "web", abstractionLevel: "atomic" },
152
141
 
153
142
  // Memory tools
154
143
  { name: "memory_write", description: "Store in long-term memory, save information to persistent memory for later retrieval. Spanish keywords: guardar memoria, guardar información, recordar, guardar dato, memoria", category: "memory", abstractionLevel: "atomic" },
@@ -158,8 +147,8 @@ export const CORE_TOOL_CATALOG: ToolDescriptor[] = [
158
147
  { name: "memory_delete", description: "Delete memory entry, remove saved memory from long-term storage. Spanish keywords: borrar memoria, eliminar información guardada, borrar dato, eliminar memoria", category: "memory", abstractionLevel: "atomic" },
159
148
 
160
149
  // Agent/worker management
161
- { name: "agent_create", description: "Create specialized worker agent, spawn new agent for specific task execution. Spanish keywords: crear agente, nuevo agente, trabajador, crear worker, nuevo trabajador", category: "agents", abstractionLevel: "orchestration" },
162
- { name: "agent_find", description: "Find existing worker agents, locate running or idle worker agents. Spanish keywords: buscar agente, encontrar trabajador, localizar, buscar worker, encontrar agente", category: "agents", abstractionLevel: "atomic" },
150
+ { name: "agent_create", description: "Create a specialized worker, including one persistent MCP server after user confirmation. Spanish keywords: crear agente, nuevo agente, trabajador, crear worker, especialista MCP", category: "agents", abstractionLevel: "orchestration" },
151
+ { name: "agent_find", description: "Discover available system catalog agents and user-owned workers. Not an execution monitor. Spanish keywords: buscar agente, encontrar trabajador, localizar, buscar worker, encontrar agente", category: "agents", abstractionLevel: "atomic" },
163
152
  { name: "agent_archive", description: "Archive unnecessary worker, terminate and archive idle or completed agents. Spanish keywords: archivar agente, terminar agente, borrar trabajador, desactivar agente", category: "agents", abstractionLevel: "atomic" },
164
153
 
165
154
  // Notes/persistence
@@ -178,29 +167,11 @@ export const CORE_TOOL_CATALOG: ToolDescriptor[] = [
178
167
  { name: "browser_script", description: "Execute arbitrary JavaScript in the browser page context and get the result. Spanish keywords: ejecutar javascript, script, código, función, evaluar, js en página", category: "browser", abstractionLevel: "atomic" },
179
168
  { name: "browser_wait", description: "Wait for an element to appear or condition to be met on the page. Spanish keywords: esperar, wait, condición, elemento, selector, aguardar carga", category: "browser", abstractionLevel: "atomic" },
180
169
 
181
- // Canvas/UI rendering tools
182
- { name: "canvas_render", description: "Render component on canvas, display UI components and data visualizations. Spanish keywords: renderizar, mostrar en canvas, visualizar, mostrar componente, dibujar", category: "canvas", abstractionLevel: "atomic" },
183
- { name: "canvas_ask", description: "Display form and wait for response, show interactive form and collect user input. Spanish keywords: mostrar formulario, pedir datos, solicitar información, formulario interactivo", category: "canvas", abstractionLevel: "atomic" },
184
- { name: "canvas_clear", description: "Clear canvas for session, reset canvas display and start fresh. Spanish keywords: limpiar canvas, borrar pantalla, reiniciar, limpiar, borrar", category: "canvas", abstractionLevel: "atomic" },
185
- { name: "canvas_show_card", description: "Display card with labeled items, show structured data in card format. Spanish keywords: mostrar tarjeta, visualizar datos, tarjeta de información, mostrar datos", category: "canvas", abstractionLevel: "atomic" },
186
- { name: "canvas_show_progress", description: "Display progress bars, show progress indicators and completion status. Spanish keywords: mostrar progreso, barra de progreso, indicador de progreso, avance", category: "canvas", abstractionLevel: "atomic" },
187
- { name: "canvas_show_list", description: "Display key-value list, show information in structured list format. Spanish keywords: mostrar lista, listar elementos, lista de valores, mostrar elementos", category: "canvas", abstractionLevel: "atomic" },
188
- { name: "canvas_confirm", description: "Show confirmation dialog, request user confirmation for actions. Spanish keywords: confirmar, diálogo de confirmación, confirmar acción, validación", category: "canvas", abstractionLevel: "atomic" },
189
-
190
170
  // A2UI v0.9 rich interactive surfaces
191
171
  { name: "a2ui_create_surface", description: "Create A2UI v0.9 surface for rich interactive UIs with forms, dashboards, and workflows. Spanish keywords: crear superficie A2UI, iniciar UI interactiva, crear formulario rico, interfaz A2UI, crear surface", category: "a2ui", abstractionLevel: "orchestration" },
192
172
  { name: "a2ui_update_components", description: "Send A2UI v0.9 components to an existing surface (Text, Button, TextField, Row, Column, Card, etc.). Spanish keywords: enviar componentes A2UI, actualizar UI, renderizar componentes, A2UI componentes, update components", category: "a2ui", abstractionLevel: "atomic" },
193
173
  { name: "a2ui_update_data_model", description: "Update A2UI v0.9 surface data model with JSON Pointer for dynamic data binding. Spanish keywords: actualizar datos A2UI, poblar formulario, cambiar valores, data model A2UI, actualizar modelo de datos", category: "a2ui", abstractionLevel: "atomic" },
194
- { name: "a2ui_delete_surface", description: "Delete A2UI v0.9 surface and remove it from the user's canvas. Spanish keywords: eliminar superficie A2UI, borrar UI, limpiar superficie A2UI, cerrar formulario, delete surface", category: "a2ui", abstractionLevel: "atomic" },
195
-
196
- // CodeBridge (subagent process management)
197
- { name: "codebridge_launch", description: "Launch subagent process, spawn new code bridge agent process. Spanish keywords: lanzar proceso, iniciar subagente, ejecutar código, nuevo proceso", category: "code", abstractionLevel: "orchestration" },
198
- { name: "codebridge_status", description: "Get status of running subagents, check code bridge agent status. Spanish keywords: estado del proceso, verificar subagente, estado del worker, estado", category: "code", abstractionLevel: "atomic" },
199
- { name: "codebridge_cancel", description: "Cancel running subagent, terminate code bridge agent process. Spanish keywords: cancelar proceso, terminar subagente, detener proceso, parar", category: "code", abstractionLevel: "atomic" },
200
-
201
- // Voice tools
202
- { name: "voice_transcribe", description: "Transcribe audio to text, convert speech to written text from audio files. Spanish keywords: transcribir audio, voz a texto, convertir audio, transcripción", category: "voice", abstractionLevel: "atomic" },
203
- { name: "voice_speak", description: "Convert text to audio and play, synthesize speech from text. Spanish keywords: hablar, sintetizar voz, texto a voz, reproducir audio, voz", category: "voice", abstractionLevel: "atomic" },
174
+ { name: "a2ui_delete_surface", description: "Delete A2UI v0.9 surface from the user's interactive panel. Spanish keywords: eliminar superficie A2UI, borrar UI, limpiar superficie A2UI, cerrar formulario, delete surface", category: "a2ui", abstractionLevel: "atomic" },
204
175
 
205
176
  // Filesystem tools
206
177
  { name: "fs_read", description: "Read file content from workspace. Spanish keywords: leer archivo, ver contenido, abrir archivo, leer fichero, mostrar archivo", category: "filesystem", abstractionLevel: "atomic" },
@@ -213,11 +184,35 @@ export const CORE_TOOL_CATALOG: ToolDescriptor[] = [
213
184
 
214
185
  // Agent delegation and communication
215
186
  { name: "task_delegate", description: "Delegate general task to worker agent. Spanish keywords: delegar tarea, asignar worker, ejecutar por agente, encomendar tarea", category: "agents", abstractionLevel: "orchestration" },
216
- { name: "task_delegate_code", description: "Delegate coding task to CLI subagent (Qwen, Claude Code, Gemini CLI). Spanish keywords: delegar código, subagente CLI, programación, codificar", category: "agents", abstractionLevel: "orchestration" },
187
+ { name: "task_revise", description: "Send a delegated task back to its worker with feedback when it does not meet its acceptance criteria. Spanish keywords: corregir tarea, devolver al worker, pedir correccion, reencolar tarea", category: "agents", abstractionLevel: "orchestration" },
188
+ { name: "task_list", description: "List real delegated task executions for the current user from persisted tasks and jobs. Spanish keywords: listar tareas activas, subagentes trabajando, ejecuciones reales", category: "agents", abstractionLevel: "atomic" },
217
189
  { name: "task_status", description: "Get execution status of delegated tasks. Spanish keywords: estado tarea delegada, verificar progreso, consultar tarea, progreso delegado", category: "agents", abstractionLevel: "atomic" },
218
190
  { name: "bus_publish", description: "Publish message to Agent Bus for worker-to-worker communication. Spanish keywords: publicar mensaje, comunicar workers, enviar bus, mensaje bus", category: "agents", abstractionLevel: "atomic" },
219
191
  { name: "bus_read", description: "Read unread messages from Agent Bus. Spanish keywords: leer mensajes bus, recibir mensajes, verificar bus, mensajes workers", category: "agents", abstractionLevel: "atomic" },
220
- { name: "project_updates", description: "Get recent status updates from workers in project. Spanish keywords: actualizaciones proyecto, estado workers, progreso equipo, noticias proyecto", category: "agents", abstractionLevel: "atomic" },
192
+
193
+ // Model discovery
194
+ { name: "get_available_models", description: "List configured providers and models with their capabilities and context windows. Spanish keywords: modelos disponibles, proveedores activos, qué modelos hay, listar modelos", category: "agents", abstractionLevel: "atomic" },
195
+
196
+ // Capability discovery
197
+ { name: "search_knowledge", description: "Search everything Hive knows: native tools, MCP tools, skills, catalog agents and playbook rules. Spanish keywords: buscar herramienta, descubrir capacidades, qué puedo hacer, buscar skill, buscar conocimiento", category: "core", abstractionLevel: "atomic" },
198
+
199
+ // HTTP / REST
200
+ { name: "api_request", description: "Perform an authorized HTTP request against a REST endpoint and validate the response. Spanish keywords: llamar api, request rest, consumir endpoint, petición http, hacer get, hacer post", category: "api", abstractionLevel: "atomic" },
201
+
202
+ // Artifacts
203
+ { name: "artifact_inspect", description: "Inspect a managed artifact's integrity and metadata without modifying it. Spanish keywords: inspeccionar artefacto, verificar archivo generado, metadatos artefacto, comprobar entrega", category: "web", abstractionLevel: "atomic" },
204
+
205
+ // Office documents — read
206
+ { name: "office_leer_pdf", description: "Read and extract text from a PDF document. Spanish keywords: leer pdf, extraer texto pdf, abrir pdf, contenido pdf", category: "office", abstractionLevel: "atomic" },
207
+ { name: "office_leer_docx", description: "Read and extract text from a Word document. Spanish keywords: leer word, leer docx, abrir documento word, contenido word", category: "office", abstractionLevel: "atomic" },
208
+ { name: "office_leer_xlsx", description: "Read rows and sheets from an Excel spreadsheet. Spanish keywords: leer excel, leer xlsx, abrir hoja de cálculo, contenido excel, leer planilla", category: "office", abstractionLevel: "atomic" },
209
+ { name: "office_leer_pptx", description: "Read slides and text from a PowerPoint presentation. Spanish keywords: leer powerpoint, leer pptx, abrir presentación, contenido diapositivas", category: "office", abstractionLevel: "atomic" },
210
+
211
+ // Office documents — write
212
+ { name: "office_escribir_pdf", description: "Generate a PDF document. Spanish keywords: crear pdf, generar pdf, escribir pdf, exportar a pdf", category: "office", abstractionLevel: "atomic" },
213
+ { name: "office_escribir_docx", description: "Generate a Word document. Spanish keywords: crear word, generar docx, escribir documento word, exportar a word", category: "office", abstractionLevel: "atomic" },
214
+ { name: "office_escribir_xlsx", description: "Generate an Excel spreadsheet. Spanish keywords: crear excel, generar xlsx, escribir hoja de cálculo, exportar a excel, armar planilla", category: "office", abstractionLevel: "atomic" },
215
+ { name: "office_escribir_pptx", description: "Generate a PowerPoint presentation. Spanish keywords: crear powerpoint, generar pptx, escribir presentación, armar diapositivas", category: "office", abstractionLevel: "atomic" },
221
216
  ]
222
217
 
223
218
  // ─── Helper Functions ───────────────────────────────────────────────────────-
@@ -254,27 +249,6 @@ function isConversational(message: string): boolean {
254
249
  return false
255
250
  }
256
251
 
257
- /**
258
- * Build FTS5 query from user message
259
- *
260
- * Strips stopwords, special characters, and limits to 8 keywords.
261
- * Uses OR operator for flexible matching.
262
- */
263
- function buildFTSQuery(message: string): string {
264
- log.info(`[tool-selector] Building FTS query from message: "${message}"`)
265
- const words = message
266
- .toLowerCase()
267
- .replace(/[^\p{L}\p{N}\s]/gu, " ")
268
- .split(/\s+/)
269
- .filter((w) => w.length > 2 && !STOPWORDS.has(w))
270
- .slice(0, 8)
271
-
272
- if (words.length === 0) return ""
273
-
274
- // Use prefix matching for better recall (e.g., "gener*" matches "generar", "generando", "generación")
275
- return words.map(w => `${w}*`).join(" OR ")
276
- }
277
-
278
252
  /**
279
253
  * Determine abstraction level preference
280
254
  *
@@ -289,19 +263,19 @@ function getAbstractionPreference(): "atomic" | "orchestration" {
289
263
  // ─── Main Selection Function ─────────────────────────────────────────────────
290
264
 
291
265
  /**
292
- * Select tools for a given user message using FTS5 bm25() scoring
293
- *
266
+ * Select tools for a given user message using HiveDB BM25 scoring
267
+ *
294
268
  * @param userMessage - The raw user message
295
269
  * @param fullToolList - Full list of available tools (for validation/filtering)
296
- * @returns Array of 0-4 selected tools with scores
297
- *
270
+ * @returns Array of selected tools
271
+ *
298
272
  * ALGORITHM:
299
273
  * 1. If conversational → return []
300
- * 2. Build FTS5 query from message keywords
301
- * 3. Query tools_fts with bm25() scoring
302
- * 4. Filter results below MIN_RELEVANCE_THRESHOLD
303
- * 5. If ambiguous → prefer atomic over orchestration
304
- * 6. Return top maxTools results (default: MAX_TOOLS_PER_TURN)
274
+ * 2. Query the HiveDB capability index with the raw message (the engine
275
+ * handles Spanish stemming, accent folding and malformed input)
276
+ * 3. Keep hits scoring at least RELEVANCE_RATIO of the top hit
277
+ * 4. If ambiguous → prefer atomic over orchestration
278
+ * 5. Return top maxTools results (default: MAX_TOOLS_PER_TURN)
305
279
  */
306
280
  export async function selectTools(
307
281
  userMessage: string,
@@ -319,41 +293,64 @@ export async function selectTools(
319
293
  return []
320
294
  }
321
295
 
322
- // Step 2: Build search query
323
- const searchQuery = buildFTSQuery(userMessage)
324
- if (!searchQuery) {
325
- log.debug(`[tool-selector] No valid query terms, returning empty array`)
296
+ // Step 2: Query the capability index with the raw message.
297
+ // Get more initially (maxTools * 2) for filtering, then limit to maxTools.
298
+ let hits
299
+ try {
300
+ hits = await searchCapabilities(userMessage, {
301
+ types: ["tool"],
302
+ k: maxTools * 2,
303
+ })
304
+ } catch (err) {
305
+ log.error(`[tool-selector] Capability search failed:`, err)
326
306
  return []
327
307
  }
328
308
 
329
- log.debug(`[tool-selector] Search query: "${searchQuery}"`)
330
-
331
- // Step 3: Execute hybrid search over the HiveDB semantic index
332
- const db = await getHiveDB()
333
-
334
- // HiveDB text-only BM25 returns positive scores where higher is better.
335
- const hits = await db.queryHybrid({
336
- text: searchQuery,
337
- k: maxTools * 2,
338
- boosts: { name: 5.0, body: 3.0, tags: 1.0 },
339
- })
340
-
341
309
  if (hits.length === 0) {
342
- log.debug(`[tool-selector] No index matches, returning empty array`)
310
+ log.debug(`[tool-selector] No matches, returning empty array`)
343
311
  return []
344
312
  }
345
313
 
346
314
  // Log raw scores for debugging
347
- log.info(`[tool-selector] Raw scores: ${hits.slice(0, 10).map(r => `${r.id}=${r.score.toFixed(2)}`).join(", ")}`)
315
+ log.info(`[tool-selector] Raw scores: ${hits.slice(0, 10).map(h => `${h.rawId}=${h.score.toFixed(2)}`).join(", ")}`)
316
+
317
+ // Step 3: Keep only hits close enough to the best match
318
+ const relevantHits = applyRelativeCutoff(hits, RELEVANCE_RATIO)
319
+ if (relevantHits.length === 0) {
320
+ log.debug(`[tool-selector] All results below ratio cutoff, returning empty`)
321
+ return []
322
+ }
348
323
 
349
- // Step 4: Map to tool descriptors with additional metadata
324
+ // Step 4: Map to tool descriptors with additional metadata.
325
+ //
326
+ // El mapa tiene que cubrir lo mismo que se indexó, o el hit se descarta en
327
+ // silencio. `syncToolCatalogToIndex` indexa CORE_TOOL_CATALOG **más** las
328
+ // filas de la colección `tools`; resolver sólo contra `fullToolList` (que
329
+ // por defecto es CORE_TOOL_CATALOG) hacía que una tool registrada en runtime
330
+ // —las que la app declara con `defineTool`— puntuara primero en BM25 y aun
331
+ // así nunca se le ofreciera al modelo.
350
332
  const toolMap = new Map(fullToolList.map(t => [t.name, t]))
333
+ const unresolved = relevantHits.filter(h => !toolMap.has(h.rawId))
334
+ if (unresolved.length > 0) {
335
+ const toolsCol = await col<ToolDoc>("tools")
336
+ for (const hit of unresolved) {
337
+ const entry = await toolsCol.get(hit.rawId)
338
+ if (!entry?.doc.enabled || !entry.doc.active) continue
339
+ toolMap.set(entry.doc.name, {
340
+ name: entry.doc.name,
341
+ description: entry.doc.description ?? entry.doc.name,
342
+ category: (entry.doc.category ?? "core") as any,
343
+ abstractionLevel: "atomic",
344
+ })
345
+ }
346
+ }
351
347
 
352
- const scoredTools: SelectedTool[] = []
348
+ const calendarOperation = isCalendarOperation(userMessage)
353
349
 
354
- for (const hit of hits) {
355
- const tool = toolMap.get(hit.id)
356
- if (tool) {
350
+ const scoredTools: SelectedTool[] = []
351
+ for (const hit of relevantHits) {
352
+ const tool = toolMap.get(hit.rawId)
353
+ if (tool && !(calendarOperation && tool.category === "scheduling")) {
357
354
  scoredTools.push({
358
355
  name: tool.name,
359
356
  score: hit.score,
@@ -366,11 +363,12 @@ export async function selectTools(
366
363
  const abstractionPref = getAbstractionPreference()
367
364
 
368
365
  if (scoredTools.length > MAX_TOOLS_PER_TURN) {
369
- // Sort by score descending (higher HiveDB score = more relevant)
370
366
  scoredTools.sort((a, b) => {
367
+ // First by score (descending: HiveDB scores are positive, higher = better)
371
368
  if (Math.abs(a.score - b.score) > 0.1) {
372
369
  return b.score - a.score
373
370
  }
371
+ // Then by abstraction preference (preferred type first)
374
372
  const aTool = toolMap.get(a.name)
375
373
  const bTool = toolMap.get(b.name)
376
374
  const aLevel = aTool?.abstractionLevel ?? "atomic"
@@ -392,6 +390,7 @@ export async function selectTools(
392
390
 
393
391
  const timing = performance.now() - startTime
394
392
 
393
+ // Log final selected tools with info level (important for tracking tool selection process)
395
394
  if (result.length > 0) {
396
395
  log.info(`[tool-selector] Selected ${result.length} tools in ${timing.toFixed(2)}ms:`,
397
396
  result.map(t => ({ name: t.name, category: t.category })))
@@ -402,41 +401,52 @@ export async function selectTools(
402
401
  return result
403
402
  }
404
403
 
405
- // ─── Sync Tools to FTS5 ─────────────────────────────────────────────────────
404
+ // ─── Sync Tools to HiveDB ────────────────────────────────────────────────────
406
405
 
407
406
  /**
408
- * Sync tool catalog to FTS5 virtual table
407
+ * Sync tool catalog to the HiveDB capability index.
409
408
  *
410
- * Called on initialization from gateway/initializer.ts to populate the FTS5 index.
411
- * Assumes the tools_fts table already exists (created by CONTEXT_ENGINE_SCHEMA).
412
- * Descriptions are enriched with bilingual keywords for better matching.
409
+ * Called on initialization from gateway/initializer.ts to populate the index.
410
+ * Replaces all `type=tool` documents atomically (delete-by-filter + one
411
+ * batch commit). Descriptions are enriched with bilingual keywords.
413
412
  *
414
413
  * @param tools - Optional array of tools to sync. If not provided, fetches from DB.
415
414
  */
416
- export async function syncToolCatalogToFTS(tools?: ToolDescriptor[]): Promise<void> {
417
- const db = await getHiveDB()
418
-
415
+ export async function syncToolCatalogToIndex(tools?: ToolDescriptor[]): Promise<void> {
419
416
  try {
420
- // Step 1: Build full catalog = CORE_TOOL_CATALOG + any explicitly passed tools
417
+ // Step 1: Build full catalog = CORE_TOOL_CATALOG + any tools in DB not already covered
418
+ // CORE_TOOL_CATALOG has bilingual keywords; DB tools may be dynamically registered
421
419
  const catalogByName = new Map<string, ToolDescriptor>(
422
420
  CORE_TOOL_CATALOG.map(t => [t.name, t])
423
421
  )
424
422
 
425
- // Merge in any tools persisted in HiveDB that are missing from the static catalog
426
- const toolsCol = db.collection<ToolDescriptor>("tools")
427
- const dbTools = await toolsCol.scan()
428
- for (const entry of dbTools) {
429
- const row = entry.doc
430
- if (!catalogByName.has(row.name)) {
431
- catalogByName.set(row.name, {
432
- name: row.name,
433
- description: row.description ?? row.name,
434
- category: row.category ?? "core",
435
- abstractionLevel: row.abstractionLevel ?? "atomic",
436
- })
423
+ // Merge in any tools from the DB that are missing from the static catalog
424
+ const toolsCol = await col<ToolDoc>("tools")
425
+ const dbTools = (await toolsCol.scan({})).map(e => e.doc)
426
+ for (const row of dbTools) {
427
+ if (catalogByName.has(row.name)) continue
428
+
429
+ // Check if this is a legacy name with different separator
430
+ // (e.g. "cron_create" vs "cron.create" — seed was wrong, tools use dots)
431
+ const altName = row.name.includes("_")
432
+ ? row.name.replace(/_/g, ".")
433
+ : row.name.includes(".")
434
+ ? row.name.replace(/\./g, "_")
435
+ : ""
436
+ if (altName && catalogByName.has(altName)) {
437
+ log.info(`[tool-selector] Skipping legacy tool name "${row.name}" — canonical "${altName}" already in catalog`)
438
+ continue
437
439
  }
440
+
441
+ catalogByName.set(row.name, {
442
+ name: row.name,
443
+ description: row.description ?? row.name,
444
+ category: (row.category ?? "core") as any,
445
+ abstractionLevel: "atomic",
446
+ })
438
447
  }
439
448
 
449
+ // Also merge any explicitly passed tools (e.g. from initializer)
440
450
  for (const t of (tools || [])) {
441
451
  if (!catalogByName.has(t.name)) {
442
452
  catalogByName.set(t.name, t)
@@ -445,44 +455,41 @@ export async function syncToolCatalogToFTS(tools?: ToolDescriptor[]): Promise<vo
445
455
 
446
456
  const toolCatalog = Array.from(catalogByName.values())
447
457
 
448
- // Step 2: Build index documents and upsert atomically via batch
449
- const docs: IndexDoc[] = toolCatalog.map(tool => ({
450
- id: tool.name,
458
+ // Step 2: Replace all tool documents in the HiveDB capability index
459
+ const docs: CapabilityDoc[] = toolCatalog.map(tool => ({
460
+ type: "tool" as const,
461
+ rawId: tool.name,
451
462
  name: tool.name,
452
463
  body: enrichToolDescription(tool),
453
464
  tags: tool.category,
454
- filters: [{ field: "type", value: "tool" }],
455
465
  }))
456
466
 
457
- await db.clearIndex()
458
- await db.upsertBatch(docs)
467
+ await replaceCapabilityDocs("tool", docs)
459
468
 
460
- log.info(`[tool-selector] Atomic sync complete: ${toolCatalog.length} tools indexed in HiveDB`)
469
+ log.info(`[tool-selector] Sync complete: ${toolCatalog.length} tools indexed in HiveDB`)
461
470
 
462
471
  } catch (err) {
463
- log.error(`[tool-selector] Transactional sync failed:`, err)
464
- throw err
472
+ log.error(`[tool-selector] Tool index sync failed:`, err)
473
+ throw err // Re-throw to inform initializer
465
474
  }
466
475
  }
467
476
 
468
477
  /**
469
478
  * Enrich tool description with category-specific keywords
470
479
  *
471
- * This improves FTS5 matching for both English and Spanish queries.
480
+ * This improves BM25 matching for both English and Spanish queries.
472
481
  */
473
- export function enrichToolDescription(tool: ToolDescriptor): string {
482
+ function enrichToolDescription(tool: ToolDescriptor): string {
474
483
  const keywordsByCategory: Record<string, string> = {
475
484
  scheduling: "programar recordatorio alarma cron schedule reminder task future tiempo",
476
- projects: "proyecto tarea plan organizer milestone backlog sprint work",
477
485
  filesystem: "archivo file leer escribir editar documento content source code",
478
486
  web: "buscar internet google web search find information news research",
479
487
  browser: "navegador browser click screenshot form automation web page UI",
480
488
  memory: "recordar nota guardar memory store remember persist knowledge",
481
489
  code: "code ejecutar run script bash shell terminal command devops",
482
- canvas: "canvas diagram visualization graph node edge flow chart",
483
- agents: "agente worker specialist create delegate hire team manager",
490
+ a2ui: "A2UI interactive panel interface form dashboard visualization",
491
+ agents: "agente worker catalog create delegate hire team manager",
484
492
  core: "notificar message alert notify communicate progress status",
485
- voice: "voz audio transcribir speech speak sintetizar audio voice transcription",
486
493
  }
487
494
 
488
495
  const extra = keywordsByCategory[tool.category] ?? ""
@@ -497,25 +504,28 @@ export function enrichToolDescription(tool: ToolDescriptor): string {
497
504
  * Gemini (and OpenAI) require: start with letter/underscore, only [a-zA-Z0-9_.-:], max 64 chars.
498
505
  * Server names from the UI can contain spaces and special chars (e.g. "X antes twiter").
499
506
  *
500
- * Canonical format: `{safeServer}__{safeTool}` (double underscore as separator)
507
+ * Canonical format: `{safeServer}__{safeTool}` (double underscore as separator).
508
+ * The server prefix exists only to disambiguate tools with the same name
509
+ * across servers — when the 64-char budget is exceeded, the SERVER part is
510
+ * shortened first so the distinctive tool name survives intact (long server
511
+ * names must never truncate the tool name).
501
512
  */
502
513
  export function mcpToolFullName(serverName: string, toolName: string): string {
503
- const safe = (s: string) => s.replace(/\s+/g, '_').replace(/[^a-zA-Z0-9_.\-:]/g, '_')
504
- const full = `${safe(serverName)}__${safe(toolName)}`
505
- // Ensure starts with letter/underscore and fits within 64 chars
506
- const trimmed = full.length > 64 ? full.substring(0, 64) : full
507
- return /^[a-zA-Z_]/.test(trimmed) ? trimmed : `_${trimmed}`.substring(0, 64)
508
- }
514
+ const MAX = 64
515
+ const MIN_SERVER = 8
516
+ const safe = (s: string) => s.replace(/\s+/g, '_').replace(/[^a-zA-Z0-9_\-]/g, '_')
509
517
 
510
- /**
511
- * Initialize the tool selector
512
- *
513
- * DEPRECATED: syncToolCatalogToFTS() is now called from gateway/initializer.ts
514
- * This function is kept for backward compatibility but is no longer needed
515
- */
516
- export function initializeToolSelector(): void {
517
- log.info(`[tool-selector] Initializing (deprecated - sync is done in gateway/initializer.ts)`)
518
- // syncToolCatalogToFTS() - No longer needed here, done in gateway/initializer.ts
518
+ let server = safe(serverName)
519
+ const tool = safe(toolName)
520
+
521
+ const room = MAX - 2 - tool.length
522
+ if (server.length > room) {
523
+ server = server.substring(0, Math.max(room, MIN_SERVER))
524
+ }
525
+
526
+ let full = `${server}__${tool}`
527
+ if (full.length > MAX) full = full.substring(0, MAX)
528
+ return /^[a-zA-Z_]/.test(full) ? full : `_${full}`.substring(0, MAX)
519
529
  }
520
530
 
521
531
  // ─── Debug/Test Helpers ─────────────────────────────────────────────────────