@johpaz/hive-sdk 0.0.14 → 0.0.15

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 (199) hide show
  1. package/.github/CODEOWNERS +9 -0
  2. package/.github/workflows/publish.yml +89 -0
  3. package/.github/workflows/version-bump.yml +102 -0
  4. package/CHANGELOG.md +38 -0
  5. package/README.md +158 -0
  6. package/bun.lock +543 -0
  7. package/bunfig.toml +7 -0
  8. package/docs/API-AGENTS.md +316 -0
  9. package/docs/API-CONTEXT-COMPILER.md +252 -0
  10. package/docs/API-DAG-SCHEDULER.md +273 -0
  11. package/docs/API-TOOLS-SKILLS-CHANNELS.md +293 -0
  12. package/docs/API-WORKERS-EVENTS.md +152 -0
  13. package/docs/INDEX.md +141 -0
  14. package/docs/README.md +68 -0
  15. package/package.json +54 -105
  16. package/packages/cli/package.json +17 -0
  17. package/packages/cli/src/commands/init.ts +56 -0
  18. package/packages/cli/src/commands/run.ts +45 -0
  19. package/packages/cli/src/commands/test.ts +42 -0
  20. package/packages/cli/src/commands/trace.ts +55 -0
  21. package/packages/cli/src/index.ts +43 -0
  22. package/packages/core/package.json +58 -0
  23. package/packages/core/src/ace/Curator.ts +158 -0
  24. package/packages/core/src/ace/Reflector.ts +200 -0
  25. package/packages/core/src/ace/Tracer.ts +100 -0
  26. package/packages/core/src/ace/index.ts +4 -0
  27. package/packages/core/src/agent/AgentRunner.ts +699 -0
  28. package/packages/core/src/agent/Compaction.ts +221 -0
  29. package/packages/core/src/agent/ContextCompiler.ts +567 -0
  30. package/packages/core/src/agent/ContextGuard.ts +91 -0
  31. package/packages/core/src/agent/ConversationStore.ts +244 -0
  32. package/packages/core/src/agent/Hooks.ts +166 -0
  33. package/packages/core/src/agent/NativeTools.ts +31 -0
  34. package/packages/core/src/agent/PromptBuilder.ts +169 -0
  35. package/packages/core/src/agent/Service.ts +267 -0
  36. package/packages/core/src/agent/StuckLoop.ts +133 -0
  37. package/packages/core/src/agent/index.ts +12 -0
  38. package/packages/core/src/agent/providers/LLMClient.ts +149 -0
  39. package/packages/core/src/agent/providers/anthropic.ts +212 -0
  40. package/packages/core/src/agent/providers/gemini.ts +215 -0
  41. package/packages/core/src/agent/providers/index.ts +199 -0
  42. package/packages/core/src/agent/providers/interface.ts +195 -0
  43. package/packages/core/src/agent/providers/ollama.ts +175 -0
  44. package/packages/core/src/agent/providers/openai-compat.ts +231 -0
  45. package/packages/core/src/agent/providers.ts +1 -0
  46. package/packages/core/src/agent/selectors/PlaybookSelector.ts +147 -0
  47. package/packages/core/src/agent/selectors/SkillSelector.ts +478 -0
  48. package/packages/core/src/agent/selectors/ToolSelector.ts +577 -0
  49. package/packages/core/src/agent/selectors/index.ts +6 -0
  50. package/packages/core/src/api/createAgent.test.ts +48 -0
  51. package/packages/core/src/api/createAgent.ts +122 -0
  52. package/packages/core/src/api/index.ts +2 -0
  53. package/packages/core/src/canvas/CanvasManager.ts +390 -0
  54. package/packages/core/src/canvas/a2ui-tools.ts +255 -0
  55. package/packages/core/src/canvas/canvas-tools.ts +448 -0
  56. package/packages/core/src/canvas/emitter.ts +149 -0
  57. package/packages/core/src/canvas/index.ts +6 -0
  58. package/packages/core/src/config/index.ts +2 -0
  59. package/packages/core/src/config/loader.ts +554 -0
  60. package/packages/core/src/ethics/EthicsGuard.test.ts +54 -0
  61. package/packages/core/src/ethics/EthicsGuard.ts +66 -0
  62. package/packages/core/src/ethics/index.ts +2 -0
  63. package/packages/core/src/gateway/channel-notify.test.ts +14 -0
  64. package/packages/core/src/gateway/channel-notify.ts +12 -0
  65. package/packages/core/src/gateway/index.ts +1 -0
  66. package/packages/core/src/index.ts +37 -0
  67. package/packages/core/src/mcp/MCPClient.ts +439 -0
  68. package/packages/core/src/mcp/MCPToolAdapter.ts +176 -0
  69. package/packages/core/src/mcp/config.ts +13 -0
  70. package/packages/core/src/mcp/hot-reload.ts +147 -0
  71. package/packages/core/src/mcp/index.ts +11 -0
  72. package/packages/core/src/mcp/logger.ts +42 -0
  73. package/packages/core/src/mcp/singleton.ts +21 -0
  74. package/packages/core/src/mcp/transports/index.ts +67 -0
  75. package/packages/core/src/mcp/transports/sse.ts +241 -0
  76. package/packages/core/src/mcp/transports/websocket.ts +159 -0
  77. package/packages/core/src/memory/Scratchpad.test.ts +47 -0
  78. package/packages/core/src/memory/Scratchpad.ts +37 -0
  79. package/packages/core/src/memory/Storage.ts +6 -0
  80. package/packages/core/src/memory/index.ts +2 -0
  81. package/packages/core/src/multimodal/VisionService.ts +293 -0
  82. package/packages/core/src/multimodal/index.ts +2 -0
  83. package/packages/core/src/multimodal/types.ts +28 -0
  84. package/packages/core/src/security/Pairing.ts +250 -0
  85. package/packages/core/src/security/RateLimit.ts +270 -0
  86. package/packages/core/src/security/index.ts +4 -0
  87. package/packages/core/src/skills/SkillLoader.ts +388 -0
  88. package/packages/core/src/skills/bundled-data.generated.ts +3332 -0
  89. package/packages/core/src/skills/defineSkill.ts +18 -0
  90. package/packages/core/src/skills/index.ts +4 -0
  91. package/packages/core/src/state/index.ts +2 -0
  92. package/packages/core/src/state/store.ts +312 -0
  93. package/packages/core/src/storage/SQLiteStorage.ts +407 -0
  94. package/packages/core/src/storage/crypto.ts +101 -0
  95. package/packages/core/src/storage/index.ts +10 -0
  96. package/packages/core/src/storage/onboarding.ts +1603 -0
  97. package/packages/core/src/storage/schema.ts +689 -0
  98. package/packages/core/src/storage/seed.ts +740 -0
  99. package/packages/core/src/storage/usage.ts +374 -0
  100. package/packages/core/src/swarm/AgentBus.ts +460 -0
  101. package/packages/core/src/swarm/AgentExecutor.ts +53 -0
  102. package/packages/core/src/swarm/Coordinator.ts +251 -0
  103. package/packages/core/src/swarm/EventBridge.ts +122 -0
  104. package/packages/core/src/swarm/EventBus.ts +169 -0
  105. package/packages/core/src/swarm/TaskGraph.ts +192 -0
  106. package/packages/core/src/swarm/TaskNode.ts +97 -0
  107. package/packages/core/src/swarm/TaskResult.ts +22 -0
  108. package/packages/core/src/swarm/WorkerPool.ts +236 -0
  109. package/packages/core/src/swarm/errors.ts +37 -0
  110. package/packages/core/src/swarm/index.ts +30 -0
  111. package/packages/core/src/swarm/presets/HiveLearnPreset.ts +99 -0
  112. package/packages/core/src/swarm/presets/ResearchPreset.ts +97 -0
  113. package/packages/core/src/swarm/presets/index.ts +4 -0
  114. package/packages/core/src/swarm/strategies/ParallelStrategy.ts +21 -0
  115. package/packages/core/src/swarm/strategies/PriorityStrategy.ts +46 -0
  116. package/packages/core/src/swarm/strategies/index.ts +3 -0
  117. package/packages/core/src/swarm/types.ts +164 -0
  118. package/packages/core/src/tools/ToolExecutor.ts +58 -0
  119. package/packages/core/src/tools/ToolRegistry.test.ts +98 -0
  120. package/packages/core/src/tools/ToolRegistry.ts +61 -0
  121. package/packages/core/src/tools/agents/get-available-models.ts +118 -0
  122. package/packages/core/src/tools/agents/index.ts +715 -0
  123. package/packages/core/src/tools/bridge-events.ts +26 -0
  124. package/packages/core/src/tools/canvas/index.ts +375 -0
  125. package/packages/core/src/tools/cli/index.ts +142 -0
  126. package/packages/core/src/tools/codebridge/index.ts +342 -0
  127. package/packages/core/src/tools/core/index.ts +476 -0
  128. package/packages/core/src/tools/cron/index.ts +626 -0
  129. package/packages/core/src/tools/filesystem/fs-delete.ts +78 -0
  130. package/packages/core/src/tools/filesystem/fs-edit.ts +106 -0
  131. package/packages/core/src/tools/filesystem/fs-exists.ts +63 -0
  132. package/packages/core/src/tools/filesystem/fs-glob.ts +108 -0
  133. package/packages/core/src/tools/filesystem/fs-list.ts +129 -0
  134. package/packages/core/src/tools/filesystem/fs-read.ts +72 -0
  135. package/packages/core/src/tools/filesystem/fs-write.ts +67 -0
  136. package/packages/core/src/tools/filesystem/index.ts +34 -0
  137. package/packages/core/src/tools/filesystem/workspace-guard.ts +62 -0
  138. package/packages/core/src/tools/index.ts +231 -0
  139. package/packages/core/src/tools/meeting/index.ts +363 -0
  140. package/packages/core/src/tools/office/index.ts +47 -0
  141. package/packages/core/src/tools/office/office-escribir-docx.ts +192 -0
  142. package/packages/core/src/tools/office/office-escribir-pdf.ts +172 -0
  143. package/packages/core/src/tools/office/office-escribir-pptx.ts +174 -0
  144. package/packages/core/src/tools/office/office-escribir-xlsx.ts +116 -0
  145. package/packages/core/src/tools/office/office-leer-docx.ts +93 -0
  146. package/packages/core/src/tools/office/office-leer-pdf.ts +114 -0
  147. package/packages/core/src/tools/office/office-leer-pptx.ts +136 -0
  148. package/packages/core/src/tools/office/office-leer-xlsx.ts +124 -0
  149. package/packages/core/src/tools/projects/index.ts +37 -0
  150. package/packages/core/src/tools/projects/project-create.ts +94 -0
  151. package/packages/core/src/tools/projects/project-done.ts +66 -0
  152. package/packages/core/src/tools/projects/project-fail.ts +66 -0
  153. package/packages/core/src/tools/projects/project-list.ts +96 -0
  154. package/packages/core/src/tools/projects/project-update.ts +72 -0
  155. package/packages/core/src/tools/projects/task-create.ts +68 -0
  156. package/packages/core/src/tools/projects/task-evaluate.ts +93 -0
  157. package/packages/core/src/tools/projects/task-update.ts +93 -0
  158. package/packages/core/src/tools/types.ts +39 -0
  159. package/packages/core/src/tools/voice/index.ts +104 -0
  160. package/packages/core/src/tools/web/browser-click.ts +78 -0
  161. package/packages/core/src/tools/web/browser-extract.ts +139 -0
  162. package/packages/core/src/tools/web/browser-navigate.ts +106 -0
  163. package/packages/core/src/tools/web/browser-screenshot.ts +87 -0
  164. package/packages/core/src/tools/web/browser-script.ts +88 -0
  165. package/packages/core/src/tools/web/browser-service.ts +554 -0
  166. package/packages/core/src/tools/web/browser-type.ts +101 -0
  167. package/packages/core/src/tools/web/browser-wait.ts +136 -0
  168. package/packages/core/src/tools/web/index.ts +41 -0
  169. package/packages/core/src/tools/web/web-fetch.ts +78 -0
  170. package/packages/core/src/tools/web/web-search.ts +123 -0
  171. package/packages/core/src/utils/benchmark.ts +80 -0
  172. package/packages/core/src/utils/crypto.ts +73 -0
  173. package/packages/core/src/utils/date.ts +42 -0
  174. package/packages/core/src/utils/index.ts +10 -0
  175. package/packages/core/src/utils/logger.ts +389 -0
  176. package/packages/core/src/utils/retry.ts +70 -0
  177. package/packages/core/src/utils/toon.ts +253 -0
  178. package/packages/core/src/voice/index.ts +656 -0
  179. package/test/setup-db.ts +216 -0
  180. package/tsconfig.json +39 -0
  181. package/src/agents.ts +0 -1
  182. package/src/canvas.ts +0 -1
  183. package/src/channels.ts +0 -1
  184. package/src/config.ts +0 -1
  185. package/src/events.ts +0 -1
  186. package/src/gateway.ts +0 -1
  187. package/src/index.ts +0 -304
  188. package/src/mcp.ts +0 -1
  189. package/src/multimodal.ts +0 -1
  190. package/src/scheduler.ts +0 -1
  191. package/src/security.ts +0 -1
  192. package/src/skills.ts +0 -1
  193. package/src/state.ts +0 -1
  194. package/src/storage.ts +0 -1
  195. package/src/tools.ts +0 -1
  196. package/src/tts.ts +0 -1
  197. package/src/types.ts +0 -82
  198. package/src/utils.ts +0 -1
  199. package/src/voice.ts +0 -1
@@ -0,0 +1,476 @@
1
+ /**
2
+ * Core Tools - 4 tools
3
+ *
4
+ * @category core
5
+ */
6
+
7
+ import type { Tool } from "../types.ts";
8
+ import { getDb } from "../../storage/SQLiteStorage.ts";
9
+ import { logger } from "../../utils/logger.ts";
10
+
11
+ const log = logger.child("core");
12
+
13
+ // ─── Bilingual dictionary: Spanish → English ────────────────────────────────
14
+
15
+ const ES_EN_DICT: Record<string, string[]> = {
16
+ // Acciones
17
+ "buscar": ["search", "find", "list", "get", "query"],
18
+ "listar": ["list", "get", "fetch", "retrieve"],
19
+ "crear": ["create", "add", "insert", "new", "make"],
20
+ "actualizar": ["update", "edit", "modify", "change"],
21
+ "eliminar": ["delete", "remove", "destroy"],
22
+ "obtener": ["get", "fetch", "retrieve", "read"],
23
+ "enviar": ["send", "post", "submit", "push"],
24
+ "leer": ["read", "get", "fetch"],
25
+ "escribir": ["write", "create", "save"],
26
+ "modificar": ["update", "modify", "edit", "change"],
27
+ "ejecutar": ["execute", "run", "invoke"],
28
+ "conectar": ["connect", "link"],
29
+ "desconectar": ["disconnect", "remove"],
30
+ "descargar": ["download", "export", "fetch"],
31
+ "subir": ["upload", "import", "create"],
32
+ "analizar": ["analyze", "review", "examine"],
33
+ "generar": ["generate", "create", "produce"],
34
+ "convertir": ["convert", "transform", "translate"],
35
+ "validar": ["validate", "verify", "check"],
36
+ "importar": ["import", "load", "ingest"],
37
+ "exportar": ["export", "download", "extract"],
38
+ "comprimir": ["compress", "zip", "archive"],
39
+ "extraer": ["extract", "get", "retrieve", "parse"],
40
+ "reemplazar": ["replace", "update", "swap"],
41
+ "cargar": ["load", "import", "upload"],
42
+ "guardar": ["save", "store", "create"],
43
+ "consultar": ["query", "search", "get", "list"],
44
+ "registrar": ["register", "create", "log", "record"],
45
+ "programar": ["schedule", "plan", "cron"],
46
+ "notificar": ["notify", "alert", "send"],
47
+ "reiniciar": ["restart", "reset", "reboot"],
48
+ "configurar": ["configure", "setup", "set"],
49
+ "autenticar": ["authenticate", "login", "auth"],
50
+ "publicar": ["publish", "deploy", "release"],
51
+ "desplegar": ["deploy", "publish", "release"],
52
+ "copiar": ["copy", "clone", "duplicate"],
53
+ "mover": ["move", "transfer", "migrate"],
54
+ "comparar": ["compare", "diff", "match"],
55
+ "fusionar": ["merge", "combine", "join"],
56
+ "dividir": ["split", "divide", "partition"],
57
+ "filtrar": ["filter", "search", "query"],
58
+ "ordenar": ["sort", "order", "arrange"],
59
+ "traducir": ["translate", "convert"],
60
+
61
+ // Entidades
62
+ "base": ["base", "database", "db"],
63
+ "bases": ["bases", "databases"],
64
+ "datos": ["data", "records", "rows", "entries"],
65
+ "registro": ["record", "entry", "row", "item"],
66
+ "registros": ["records", "entries", "rows", "items"],
67
+ "tabla": ["table", "schema", "collection"],
68
+ "tablas": ["tables", "schemas"],
69
+ "campo": ["field", "column", "property"],
70
+ "campos": ["fields", "columns", "properties"],
71
+ "usuario": ["user", "account"],
72
+ "usuarios": ["users", "accounts"],
73
+ "proyecto": ["project", "repo", "workspace"],
74
+ "proyectos": ["projects", "repos", "workspaces"],
75
+ "archivo": ["file", "document"],
76
+ "archivos": ["files", "documents"],
77
+ "correo": ["email", "mail", "message"],
78
+ "correos": ["emails", "mails", "messages"],
79
+ "noticia": ["news", "article", "post"],
80
+ "noticias": ["news", "articles", "posts"],
81
+ "contenido": ["content", "data", "text"],
82
+ "tarea": ["task", "job", "issue", "ticket"],
83
+ "tareas": ["tasks", "jobs", "issues", "tickets"],
84
+ "pagina": ["page", "site", "web"],
85
+ "enlace": ["link", "url", "reference"],
86
+ "imagen": ["image", "picture", "photo"],
87
+ "video": ["video", "media"],
88
+ "audio": ["audio", "sound", "media"],
89
+ "categoria": ["category", "tag", "label"],
90
+ "estado": ["status", "state", "condition"],
91
+ "error": ["error", "exception", "fault"],
92
+ "fuente": ["source", "origin", "reference"],
93
+ "esquema": ["schema", "structure", "model"],
94
+ "respuesta": ["response", "reply", "answer"],
95
+ "solicitud": ["request", "query", "call"],
96
+ "repositorio": ["repository", "repo"],
97
+ "seguridad": ["security", "auth", "permission"],
98
+ "permiso": ["permission", "role", "access"],
99
+ "acceso": ["access", "login", "entry"],
100
+ "servidor": ["server", "host", "service"],
101
+ "conexion": ["connection", "link", "integration"],
102
+ "integracion": ["integration", "connector", "plugin"],
103
+ "herramienta": ["tool", "utility", "function"],
104
+ "informacion": ["info", "information", "details"],
105
+ "lista": ["list", "collection", "array"],
106
+ "reporte": ["report", "summary", "analytics"],
107
+ "metrica": ["metric", "stat", "analytics"],
108
+ "contacto": ["contact", "lead", "person"],
109
+ };
110
+
111
+ /**
112
+ * Translate a Spanish query to English equivalents for FTS5 fallback.
113
+ * Returns an array of English keyword tokens.
114
+ */
115
+ function translateQueryToEnglish(query: string): string {
116
+ const words = query.toLowerCase().replace(/_/g, " ").split(/\s+/).filter(w => w.length > 1);
117
+ const translated: string[] = [];
118
+
119
+ for (const word of words) {
120
+ const equivalents = ES_EN_DICT[word];
121
+ if (equivalents) {
122
+ translated.push(...equivalents);
123
+ }
124
+ }
125
+
126
+ return [...new Set(translated)].join(" ");
127
+ }
128
+
129
+ /**
130
+ * Build an FTS5 MATCH expression from a list of words.
131
+ * Multi-word: AND with prefix wildcard. Single: exact OR prefix.
132
+ */
133
+ function buildFtsMatch(words: string[]): string {
134
+ if (words.length > 1) {
135
+ return words.map(w => `${w}*`).join(' AND ');
136
+ }
137
+ return `"${words.join(' ')}" OR ${words[0]}*`;
138
+ }
139
+
140
+ // ─── search_knowledge ────────────────────────────────────────────────────────
141
+
142
+ export const searchKnowledgeTool: Tool = {
143
+ name: "search_knowledge",
144
+ description: "Busca herramientas NATIVAS (tools), MCP (tools externas), habilidades (skills) o reglas del playbook en la base de conocimientos. Usa búsqueda full-text (FTS5) con fallback bilingüe español→inglés. type='mcp' para herramientas MCP, type='all' para buscar en todo.",
145
+ parameters: {
146
+ type: "object",
147
+ properties: {
148
+ query: {
149
+ type: "string",
150
+ description: "Término de búsqueda (nombre, descripción, categoría). Se busca primero en español, luego en inglés si hay pocos resultados.",
151
+ },
152
+ type: {
153
+ type: "string",
154
+ enum: ["all", "tools", "skills", "playbook", "mcp"],
155
+ description: "Tipo de conocimiento a buscar",
156
+ },
157
+ limit: {
158
+ type: "number",
159
+ description: "Máximo de resultados (default: 10)",
160
+ },
161
+ },
162
+ required: ["query"],
163
+ },
164
+ execute: async (params: Record<string, unknown>) => {
165
+ const db = getDb();
166
+ const query = params.query as string;
167
+ const type = (params.type as string) ?? "all";
168
+ const limit = (params.limit as number) ?? 10;
169
+ const MIN_RESULTS_FOR_BILINGUAL = 2;
170
+
171
+ try {
172
+ const escapedQuery = query.replace(/'/g, "''");
173
+ const normalizedQuery = escapedQuery.replace(/_/g, " ").trim();
174
+ const words = normalizedQuery.split(/\s+/).filter(w => w.length > 0);
175
+ const ftsMatch = buildFtsMatch(words);
176
+
177
+ const result: any = { query, type, tools: [], skills: [], playbook: [], toolsmcp: [] };
178
+
179
+ // ─── Search functions (reusable for bilingual fallback) ───────────
180
+
181
+ function searchTools(matchExpr: string): any[] {
182
+ if (type !== "all" && type !== "tools") return [];
183
+ try {
184
+ return db.query(`
185
+ SELECT
186
+ COALESCE(t.id, tools_fts.tool_name) as id,
187
+ COALESCE(t.name, tools_fts.tool_name) as name,
188
+ COALESCE(t.description, tools_fts.description) as description,
189
+ COALESCE(t.category, tools_fts.category) as category,
190
+ COALESCE(t.enabled, 1) as enabled,
191
+ COALESCE(t.active, 1) as active,
192
+ bm25(tools_fts) as rank
193
+ FROM tools_fts
194
+ LEFT JOIN tools t ON t.name = tools_fts.tool_name
195
+ WHERE tools_fts MATCH ?
196
+ ORDER BY rank
197
+ LIMIT ?
198
+ `).all(matchExpr, limit) as any[];
199
+ } catch { return []; }
200
+ }
201
+
202
+ function searchSkills(matchExpr: string): any[] {
203
+ if (type !== "all" && type !== "skills") return [];
204
+ try {
205
+ return db.query(`
206
+ SELECT s.id, s.name, s.description, s.category, s.tools, s.triggers, s.preferred_agents, s.body, s.active, bm25(skills_fts) as rank
207
+ FROM skills_fts
208
+ JOIN skills s ON s.id = skills_fts.id
209
+ WHERE skills_fts MATCH ?
210
+ ORDER BY rank
211
+ LIMIT ?
212
+ `).all(matchExpr, limit) as any[];
213
+ } catch { return []; }
214
+ }
215
+
216
+ function searchPlaybook(matchExpr: string): any[] {
217
+ if (type !== "all" && type !== "playbook") return [];
218
+ try {
219
+ return db.query(`
220
+ SELECT p.id, p.rule, p.category, p.applicable_to, p.helpful_count, p.harmful_count, p.active, bm25(playbook_fts) as rank
221
+ FROM playbook_fts
222
+ JOIN playbook p ON p.id = playbook_fts.rowid
223
+ WHERE playbook_fts MATCH ?
224
+ ORDER BY rank
225
+ LIMIT ?
226
+ `).all(matchExpr, limit) as any[];
227
+ } catch { return []; }
228
+ }
229
+
230
+ function searchMcpTools(matchExpr: string): any[] {
231
+ if (type !== "all" && type !== "mcp") return [];
232
+ try {
233
+ return db.query(`
234
+ SELECT m.id, m.server_name, m.tool_name, m.description, m.category, m.active, bm25(mcp_tools_fts) as rank
235
+ FROM mcp_tools_fts
236
+ JOIN mcp_tools m ON m.id = mcp_tools_fts.id
237
+ WHERE mcp_tools_fts MATCH ?
238
+ ORDER BY rank
239
+ LIMIT ?
240
+ `).all(matchExpr, limit) as any[];
241
+ } catch { return []; }
242
+ }
243
+
244
+ // ─── Pass 1: Search with original query ─────────────────────────
245
+
246
+ const tools1 = searchTools(ftsMatch);
247
+ const skills1 = searchSkills(ftsMatch);
248
+ const playbook1 = searchPlaybook(ftsMatch);
249
+ const mcp1 = searchMcpTools(ftsMatch);
250
+
251
+ const totalFirst = tools1.length + skills1.length + playbook1.length + mcp1.length;
252
+
253
+ // Map results
254
+ result.tools = tools1.map((t: any) => ({
255
+ id: t.id, name: t.name, description: t.description, category: t.category,
256
+ enabled: t.enabled === 1, active: t.active === 1, rank: t.rank,
257
+ }));
258
+ result.skills = skills1.map((s: any) => ({
259
+ id: s.id, name: s.name, description: s.description, category: s.category,
260
+ tools: s.tools, triggers: s.triggers,
261
+ preferred_agents: s.preferred_agents ? JSON.parse(s.preferred_agents) : [],
262
+ body: s.body ? (s.body.length > 400 ? s.body.substring(0, 400) + "…" : s.body) : undefined,
263
+ active: s.active === 1, rank: s.rank,
264
+ }));
265
+ result.playbook = playbook1.map((p: any) => ({
266
+ id: p.id, rule: p.rule, category: p.category,
267
+ applicable_to: p.applicable_to ? JSON.parse(p.applicable_to) : null,
268
+ helpful_count: p.helpful_count, harmful_count: p.harmful_count,
269
+ active: p.active === 1, rank: p.rank,
270
+ }));
271
+ result.toolsmcp = mcp1.map((t: any) => ({
272
+ id: t.id, full_name: t.id, server_name: t.server_name, tool_name: t.tool_name,
273
+ description: t.description, category: t.category,
274
+ active: t.active === 1, rank: t.rank,
275
+ }));
276
+
277
+ // ─── Pass 2: Bilingual fallback (ES → EN) ──────────────────────
278
+
279
+ if (totalFirst < MIN_RESULTS_FOR_BILINGUAL) {
280
+ const englishQuery = translateQueryToEnglish(normalizedQuery);
281
+ if (englishQuery.length > 0) {
282
+ const enWords = englishQuery.split(/\s+/).filter(w => w.length > 0);
283
+ const enMatch = buildFtsMatch(enWords);
284
+
285
+ log.info(`[search_knowledge] Bilingual fallback: "${normalizedQuery}" → "${englishQuery}" (first pass: ${totalFirst} results)`);
286
+
287
+ const existingIds = new Set([
288
+ ...result.tools.map((t: any) => t.name),
289
+ ...result.skills.map((s: any) => s.id),
290
+ ...result.playbook.map((p: any) => p.id),
291
+ ...result.toolsmcp.map((t: any) => t.id),
292
+ ]);
293
+
294
+ // Merge English results (dedup by id)
295
+ for (const t of searchTools(enMatch)) {
296
+ if (!existingIds.has(t.name || t.id)) {
297
+ result.tools.push({
298
+ id: t.id, name: t.name, description: t.description, category: t.category,
299
+ enabled: t.enabled === 1, active: t.active === 1, rank: t.rank,
300
+ });
301
+ existingIds.add(t.name || t.id);
302
+ }
303
+ }
304
+ for (const s of searchSkills(enMatch)) {
305
+ if (!existingIds.has(s.id)) {
306
+ result.skills.push({
307
+ id: s.id, name: s.name, description: s.description, category: s.category,
308
+ tools: s.tools, triggers: s.triggers,
309
+ preferred_agents: s.preferred_agents ? JSON.parse(s.preferred_agents) : [],
310
+ body: s.body ? (s.body.length > 400 ? s.body.substring(0, 400) + "…" : s.body) : undefined,
311
+ active: s.active === 1, rank: s.rank,
312
+ });
313
+ existingIds.add(s.id);
314
+ }
315
+ }
316
+ for (const p of searchPlaybook(enMatch)) {
317
+ if (!existingIds.has(p.id)) {
318
+ result.playbook.push({
319
+ id: p.id, rule: p.rule, category: p.category,
320
+ applicable_to: p.applicable_to ? JSON.parse(p.applicable_to) : null,
321
+ helpful_count: p.helpful_count, harmful_count: p.harmful_count,
322
+ active: p.active === 1, rank: p.rank,
323
+ });
324
+ existingIds.add(p.id);
325
+ }
326
+ }
327
+ for (const t of searchMcpTools(enMatch)) {
328
+ if (!existingIds.has(t.id)) {
329
+ result.toolsmcp.push({
330
+ id: t.id, full_name: t.id, server_name: t.server_name, tool_name: t.tool_name,
331
+ description: t.description, category: t.category,
332
+ active: t.active === 1, rank: t.rank,
333
+ });
334
+ existingIds.add(t.id);
335
+ }
336
+ }
337
+ }
338
+ }
339
+
340
+ result.totalResults = result.tools.length + result.skills.length + result.playbook.length + result.toolsmcp.length;
341
+
342
+ return { ok: true, ...result };
343
+ } catch (error) {
344
+ return {
345
+ ok: false,
346
+ error: `Search failed: ${(error as Error).message}`,
347
+ };
348
+ }
349
+ },
350
+ };
351
+
352
+ // ─── notify ──────────────────────────────────────────────────────────────────
353
+
354
+ export const notifyTool: Tool = {
355
+ name: "notify",
356
+ description: "Send a notification or progress update to the user's active channel. Use this to keep the user informed while working on long tasks.",
357
+ parameters: {
358
+ type: "object",
359
+ properties: {
360
+ message: {
361
+ type: "string",
362
+ description: "Notification message to send to the user",
363
+ },
364
+ },
365
+ required: ["message"],
366
+ },
367
+ execute: async (params: Record<string, unknown>, config?: any) => {
368
+ const { sendToUserChannel } = await import("../../gateway/channel-notify.ts");
369
+ const message = params.message as string;
370
+ const channel = (config?.configurable?.channel as string) ?? "webchat";
371
+ const userId = (config?.configurable?.user_id as string) ?? "";
372
+
373
+ log.info(`[notify] Sending to ${channel}/${userId}: ${message.substring(0, 80)}`);
374
+
375
+ const result = await sendToUserChannel(channel, userId, message)
376
+ if (!result.ok) throw new Error(`Channel send failed: ${result.error}`)
377
+ return result
378
+ },
379
+ };
380
+
381
+ // ─── save_note (scratchpad) ──────────────────────────────────────────────────
382
+
383
+ export const saveNoteTool: Tool = {
384
+ name: "save_note",
385
+ description: "Save a note to the scratchpad (survives context compression).",
386
+ parameters: {
387
+ type: "object",
388
+ properties: {
389
+ key: {
390
+ type: "string",
391
+ description: "Unique key for the note",
392
+ },
393
+ value: {
394
+ type: "string",
395
+ description: "Note content",
396
+ },
397
+ thread_id: {
398
+ type: "string",
399
+ description: "Thread ID (optional, uses current thread if not specified)",
400
+ },
401
+ },
402
+ required: ["key", "value"],
403
+ },
404
+ execute: async (params: Record<string, unknown>, config?: any) => {
405
+ const db = getDb();
406
+ const key = params.key as string;
407
+ const value = params.value as string;
408
+ const threadId = (params.thread_id as string) ?? config?.configurable?.thread_id ?? "default";
409
+
410
+ try {
411
+ db.query(`
412
+ INSERT OR REPLACE INTO scratchpad (thread_id, key, value, source, updated_at)
413
+ VALUES (?, ?, ?, 'agent', unixepoch())
414
+ `).run(threadId, key, value);
415
+
416
+ return { ok: true, key, message: "Note saved." };
417
+ } catch (error) {
418
+ return {
419
+ ok: false,
420
+ error: `Failed to save note: ${(error as Error).message}`,
421
+ };
422
+ }
423
+ },
424
+ };
425
+
426
+ // ─── report_progress ─────────────────────────────────────────────────────────
427
+
428
+ export const reportProgressTool: Tool = {
429
+ name: "report_progress",
430
+ description: "Report progress of an ongoing task to the user. Sends a real-time update to the active channel. Use frequently during long operations so the user knows what's happening.",
431
+ parameters: {
432
+ type: "object",
433
+ properties: {
434
+ progress: {
435
+ type: "number",
436
+ description: "Progress percentage (0-100)",
437
+ },
438
+ message: {
439
+ type: "string",
440
+ description: "Progress message describing what you are currently doing",
441
+ },
442
+ task_id: {
443
+ type: "string",
444
+ description: "Task or project ID (optional)",
445
+ },
446
+ },
447
+ required: ["progress", "message"],
448
+ },
449
+ execute: async (params: Record<string, unknown>, config?: any) => {
450
+ const { sendToUserChannel } = await import("../../gateway/channel-notify.ts");
451
+ const progress = params.progress as number;
452
+ const message = params.message as string;
453
+ const taskId = (params.task_id as string) ?? null;
454
+ const channel = (config?.configurable?.channel as string) ?? "webchat";
455
+ const userId = (config?.configurable?.user_id as string) ?? "";
456
+
457
+ log.info(`[report_progress] ${progress}% — ${message}`);
458
+
459
+ // Update task progress in DB if task_id provided
460
+ if (taskId) {
461
+ const db = getDb();
462
+ db.query(`UPDATE tasks SET progress = ?, updated_at = unixepoch() WHERE id = ?`).run(progress, taskId);
463
+ }
464
+
465
+ // Send real-time update to the user's channel
466
+ const progressEmoji = progress >= 100 ? "✅" : progress >= 50 ? "⚙️" : "🔄";
467
+ const result = await sendToUserChannel(channel, userId, `${progressEmoji} ${progress}% — ${message}`)
468
+ if (!result.ok) throw new Error(`Channel send failed: ${result.error}`)
469
+
470
+ return { ok: true, progress, message, task_id: taskId };
471
+ },
472
+ };
473
+
474
+ export function createTools(): Tool[] {
475
+ return [searchKnowledgeTool, notifyTool, saveNoteTool, reportProgressTool];
476
+ }