@johpaz/hive-sdk 0.0.12 → 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.
- package/.github/CODEOWNERS +9 -0
- package/.github/workflows/publish.yml +89 -0
- package/.github/workflows/version-bump.yml +102 -0
- package/CHANGELOG.md +38 -0
- package/README.md +158 -0
- package/bun.lock +543 -0
- package/bunfig.toml +7 -0
- package/docs/API-AGENTS.md +316 -0
- package/docs/API-CONTEXT-COMPILER.md +252 -0
- package/docs/API-DAG-SCHEDULER.md +273 -0
- package/docs/API-TOOLS-SKILLS-CHANNELS.md +293 -0
- package/docs/API-WORKERS-EVENTS.md +152 -0
- package/docs/INDEX.md +141 -0
- package/docs/README.md +68 -0
- package/package.json +54 -105
- package/packages/cli/package.json +17 -0
- package/packages/cli/src/commands/init.ts +56 -0
- package/packages/cli/src/commands/run.ts +45 -0
- package/packages/cli/src/commands/test.ts +42 -0
- package/packages/cli/src/commands/trace.ts +55 -0
- package/packages/cli/src/index.ts +43 -0
- package/packages/core/package.json +58 -0
- package/packages/core/src/ace/Curator.ts +158 -0
- package/packages/core/src/ace/Reflector.ts +200 -0
- package/packages/core/src/ace/Tracer.ts +100 -0
- package/packages/core/src/ace/index.ts +4 -0
- package/packages/core/src/agent/AgentRunner.ts +699 -0
- package/packages/core/src/agent/Compaction.ts +221 -0
- package/packages/core/src/agent/ContextCompiler.ts +567 -0
- package/packages/core/src/agent/ContextGuard.ts +91 -0
- package/packages/core/src/agent/ConversationStore.ts +244 -0
- package/packages/core/src/agent/Hooks.ts +166 -0
- package/packages/core/src/agent/NativeTools.ts +31 -0
- package/packages/core/src/agent/PromptBuilder.ts +169 -0
- package/packages/core/src/agent/Service.ts +267 -0
- package/packages/core/src/agent/StuckLoop.ts +133 -0
- package/packages/core/src/agent/index.ts +12 -0
- package/packages/core/src/agent/providers/LLMClient.ts +149 -0
- package/packages/core/src/agent/providers/anthropic.ts +212 -0
- package/packages/core/src/agent/providers/gemini.ts +215 -0
- package/packages/core/src/agent/providers/index.ts +199 -0
- package/packages/core/src/agent/providers/interface.ts +195 -0
- package/packages/core/src/agent/providers/ollama.ts +175 -0
- package/packages/core/src/agent/providers/openai-compat.ts +231 -0
- package/packages/core/src/agent/providers.ts +1 -0
- package/packages/core/src/agent/selectors/PlaybookSelector.ts +147 -0
- package/packages/core/src/agent/selectors/SkillSelector.ts +478 -0
- package/packages/core/src/agent/selectors/ToolSelector.ts +577 -0
- package/packages/core/src/agent/selectors/index.ts +6 -0
- package/packages/core/src/api/createAgent.test.ts +48 -0
- package/packages/core/src/api/createAgent.ts +122 -0
- package/packages/core/src/api/index.ts +2 -0
- package/packages/core/src/canvas/CanvasManager.ts +390 -0
- package/packages/core/src/canvas/a2ui-tools.ts +255 -0
- package/packages/core/src/canvas/canvas-tools.ts +448 -0
- package/packages/core/src/canvas/emitter.ts +149 -0
- package/packages/core/src/canvas/index.ts +6 -0
- package/packages/core/src/config/index.ts +2 -0
- package/packages/core/src/config/loader.ts +554 -0
- package/packages/core/src/ethics/EthicsGuard.test.ts +54 -0
- package/packages/core/src/ethics/EthicsGuard.ts +66 -0
- package/packages/core/src/ethics/index.ts +2 -0
- package/packages/core/src/gateway/channel-notify.test.ts +14 -0
- package/packages/core/src/gateway/channel-notify.ts +12 -0
- package/packages/core/src/gateway/index.ts +1 -0
- package/packages/core/src/index.ts +37 -0
- package/packages/core/src/mcp/MCPClient.ts +439 -0
- package/packages/core/src/mcp/MCPToolAdapter.ts +176 -0
- package/packages/core/src/mcp/config.ts +13 -0
- package/packages/core/src/mcp/hot-reload.ts +147 -0
- package/packages/core/src/mcp/index.ts +11 -0
- package/packages/core/src/mcp/logger.ts +42 -0
- package/packages/core/src/mcp/singleton.ts +21 -0
- package/packages/core/src/mcp/transports/index.ts +67 -0
- package/packages/core/src/mcp/transports/sse.ts +241 -0
- package/packages/core/src/mcp/transports/websocket.ts +159 -0
- package/packages/core/src/memory/Scratchpad.test.ts +47 -0
- package/packages/core/src/memory/Scratchpad.ts +37 -0
- package/packages/core/src/memory/Storage.ts +6 -0
- package/packages/core/src/memory/index.ts +2 -0
- package/packages/core/src/multimodal/VisionService.ts +293 -0
- package/packages/core/src/multimodal/index.ts +2 -0
- package/packages/core/src/multimodal/types.ts +28 -0
- package/packages/core/src/security/Pairing.ts +250 -0
- package/packages/core/src/security/RateLimit.ts +270 -0
- package/packages/core/src/security/index.ts +4 -0
- package/packages/core/src/skills/SkillLoader.ts +388 -0
- package/packages/core/src/skills/bundled-data.generated.ts +3332 -0
- package/packages/core/src/skills/defineSkill.ts +18 -0
- package/packages/core/src/skills/index.ts +4 -0
- package/packages/core/src/state/index.ts +2 -0
- package/packages/core/src/state/store.ts +312 -0
- package/packages/core/src/storage/SQLiteStorage.ts +407 -0
- package/packages/core/src/storage/crypto.ts +101 -0
- package/packages/core/src/storage/index.ts +10 -0
- package/packages/core/src/storage/onboarding.ts +1603 -0
- package/packages/core/src/storage/schema.ts +689 -0
- package/packages/core/src/storage/seed.ts +740 -0
- package/packages/core/src/storage/usage.ts +374 -0
- package/packages/core/src/swarm/AgentBus.ts +460 -0
- package/packages/core/src/swarm/AgentExecutor.ts +53 -0
- package/packages/core/src/swarm/Coordinator.ts +251 -0
- package/packages/core/src/swarm/EventBridge.ts +122 -0
- package/packages/core/src/swarm/EventBus.ts +169 -0
- package/packages/core/src/swarm/TaskGraph.ts +192 -0
- package/packages/core/src/swarm/TaskNode.ts +97 -0
- package/packages/core/src/swarm/TaskResult.ts +22 -0
- package/packages/core/src/swarm/WorkerPool.ts +236 -0
- package/packages/core/src/swarm/errors.ts +37 -0
- package/packages/core/src/swarm/index.ts +30 -0
- package/packages/core/src/swarm/presets/HiveLearnPreset.ts +99 -0
- package/packages/core/src/swarm/presets/ResearchPreset.ts +97 -0
- package/packages/core/src/swarm/presets/index.ts +4 -0
- package/packages/core/src/swarm/strategies/ParallelStrategy.ts +21 -0
- package/packages/core/src/swarm/strategies/PriorityStrategy.ts +46 -0
- package/packages/core/src/swarm/strategies/index.ts +3 -0
- package/packages/core/src/swarm/types.ts +164 -0
- package/packages/core/src/tools/ToolExecutor.ts +58 -0
- package/packages/core/src/tools/ToolRegistry.test.ts +98 -0
- package/packages/core/src/tools/ToolRegistry.ts +61 -0
- package/packages/core/src/tools/agents/get-available-models.ts +118 -0
- package/packages/core/src/tools/agents/index.ts +715 -0
- package/packages/core/src/tools/bridge-events.ts +26 -0
- package/packages/core/src/tools/canvas/index.ts +375 -0
- package/packages/core/src/tools/cli/index.ts +142 -0
- package/packages/core/src/tools/codebridge/index.ts +342 -0
- package/packages/core/src/tools/core/index.ts +476 -0
- package/packages/core/src/tools/cron/index.ts +626 -0
- package/packages/core/src/tools/filesystem/fs-delete.ts +78 -0
- package/packages/core/src/tools/filesystem/fs-edit.ts +106 -0
- package/packages/core/src/tools/filesystem/fs-exists.ts +63 -0
- package/packages/core/src/tools/filesystem/fs-glob.ts +108 -0
- package/packages/core/src/tools/filesystem/fs-list.ts +129 -0
- package/packages/core/src/tools/filesystem/fs-read.ts +72 -0
- package/packages/core/src/tools/filesystem/fs-write.ts +67 -0
- package/packages/core/src/tools/filesystem/index.ts +34 -0
- package/packages/core/src/tools/filesystem/workspace-guard.ts +62 -0
- package/packages/core/src/tools/index.ts +231 -0
- package/packages/core/src/tools/meeting/index.ts +363 -0
- package/packages/core/src/tools/office/index.ts +47 -0
- package/packages/core/src/tools/office/office-escribir-docx.ts +192 -0
- package/packages/core/src/tools/office/office-escribir-pdf.ts +172 -0
- package/packages/core/src/tools/office/office-escribir-pptx.ts +174 -0
- package/packages/core/src/tools/office/office-escribir-xlsx.ts +116 -0
- package/packages/core/src/tools/office/office-leer-docx.ts +93 -0
- package/packages/core/src/tools/office/office-leer-pdf.ts +114 -0
- package/packages/core/src/tools/office/office-leer-pptx.ts +136 -0
- package/packages/core/src/tools/office/office-leer-xlsx.ts +124 -0
- package/packages/core/src/tools/projects/index.ts +37 -0
- package/packages/core/src/tools/projects/project-create.ts +94 -0
- package/packages/core/src/tools/projects/project-done.ts +66 -0
- package/packages/core/src/tools/projects/project-fail.ts +66 -0
- package/packages/core/src/tools/projects/project-list.ts +96 -0
- package/packages/core/src/tools/projects/project-update.ts +72 -0
- package/packages/core/src/tools/projects/task-create.ts +68 -0
- package/packages/core/src/tools/projects/task-evaluate.ts +93 -0
- package/packages/core/src/tools/projects/task-update.ts +93 -0
- package/packages/core/src/tools/types.ts +39 -0
- package/packages/core/src/tools/voice/index.ts +104 -0
- package/packages/core/src/tools/web/browser-click.ts +78 -0
- package/packages/core/src/tools/web/browser-extract.ts +139 -0
- package/packages/core/src/tools/web/browser-navigate.ts +106 -0
- package/packages/core/src/tools/web/browser-screenshot.ts +87 -0
- package/packages/core/src/tools/web/browser-script.ts +88 -0
- package/packages/core/src/tools/web/browser-service.ts +554 -0
- package/packages/core/src/tools/web/browser-type.ts +101 -0
- package/packages/core/src/tools/web/browser-wait.ts +136 -0
- package/packages/core/src/tools/web/index.ts +41 -0
- package/packages/core/src/tools/web/web-fetch.ts +78 -0
- package/packages/core/src/tools/web/web-search.ts +123 -0
- package/packages/core/src/utils/benchmark.ts +80 -0
- package/packages/core/src/utils/crypto.ts +73 -0
- package/packages/core/src/utils/date.ts +42 -0
- package/packages/core/src/utils/index.ts +10 -0
- package/packages/core/src/utils/logger.ts +389 -0
- package/packages/core/src/utils/retry.ts +70 -0
- package/packages/core/src/utils/toon.ts +253 -0
- package/packages/core/src/voice/index.ts +656 -0
- package/test/setup-db.ts +216 -0
- package/tsconfig.json +39 -0
- package/src/agents.ts +0 -1
- package/src/canvas.ts +0 -1
- package/src/channels.ts +0 -1
- package/src/config.ts +0 -1
- package/src/events.ts +0 -1
- package/src/gateway.ts +0 -1
- package/src/index.ts +0 -304
- package/src/mcp.ts +0 -1
- package/src/multimodal.ts +0 -1
- package/src/scheduler.ts +0 -1
- package/src/security.ts +0 -1
- package/src/skills.ts +0 -1
- package/src/state.ts +0 -1
- package/src/storage.ts +0 -1
- package/src/tools.ts +0 -1
- package/src/tts.ts +0 -1
- package/src/types.ts +0 -82
- package/src/utils.ts +0 -1
- package/src/voice.ts +0 -1
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FTS5-based Dynamic Tool Selector Module
|
|
3
|
+
*
|
|
4
|
+
* Context Compiler Level 3 - Intelligent Tool Selection
|
|
5
|
+
*
|
|
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
|
+
*
|
|
9
|
+
* DESIGN DECISIONS:
|
|
10
|
+
*
|
|
11
|
+
* 1. Stateless: No memory between turns - each message is evaluated independently.
|
|
12
|
+
* Rationale: Prevents cascade effects where a bad selection in one turn affects
|
|
13
|
+
* future turns. Forces fresh evaluation each time.
|
|
14
|
+
*
|
|
15
|
+
* 2. Maximum 4 tools per turn: Keeps token count low and prevents overwhelming
|
|
16
|
+
* 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
|
+
*
|
|
23
|
+
* 4. Atomic over orchestration: When ambiguous, prefer individual tools over
|
|
24
|
+
* compound/manager tools. Rationale: Atomic tools are more predictable and
|
|
25
|
+
* 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
|
+
*
|
|
30
|
+
* 6. Tool categorization: Tools are categorized by semantic domain:
|
|
31
|
+
* - scheduling (cron tools)
|
|
32
|
+
* - projects (project/task management)
|
|
33
|
+
* - filesystem (file operations)
|
|
34
|
+
* - web (search/fetch)
|
|
35
|
+
* - browser (browser automation)
|
|
36
|
+
* - memory (notes, memory operations)
|
|
37
|
+
* - code (exec, terminal)
|
|
38
|
+
* - canvas (UI rendering)
|
|
39
|
+
* - agents (agent creation/management)
|
|
40
|
+
* - core (notify, report_progress, save_note)
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
import { getDb } from "../../storage/SQLiteStorage.ts"
|
|
44
|
+
import { logger } from "../../utils/logger.ts"
|
|
45
|
+
|
|
46
|
+
const log = logger.child("tool-selector")
|
|
47
|
+
|
|
48
|
+
// ─── Types ───────────────────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
export interface ToolDescriptor {
|
|
51
|
+
name: string
|
|
52
|
+
description: string
|
|
53
|
+
category: string
|
|
54
|
+
/** Abstraction level: atomic (single operation) vs orchestration (manages multiple) */
|
|
55
|
+
abstractionLevel?: "atomic" | "orchestration"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface SelectedTool {
|
|
59
|
+
name: string
|
|
60
|
+
score: number
|
|
61
|
+
category: string
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface ToolSelectorResult {
|
|
65
|
+
tools: ToolDescriptor[]
|
|
66
|
+
selected: SelectedTool[]
|
|
67
|
+
reasoning: string
|
|
68
|
+
timingMs: number
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ─── Configuration ───────────────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
/** Maximum tools to return per message */
|
|
74
|
+
const MAX_TOOLS_PER_TURN = 12
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Minimum bm25 score threshold. Below this = conversational, no tools needed.
|
|
78
|
+
*
|
|
79
|
+
* CRITICAL: bm25() returns NEGATIVE scores where closer to 0 = more relevant.
|
|
80
|
+
* - Score of -5 is MORE relevant than -20
|
|
81
|
+
* - We use -30 as threshold to filter noise while allowing valid matches
|
|
82
|
+
*
|
|
83
|
+
* Previous values: -25 (too strict), -100 (too permissive)
|
|
84
|
+
* New value: -30 (balanced filtering, FTS5 MATCH handles the heavy lifting)
|
|
85
|
+
*/
|
|
86
|
+
export const MIN_RELEVANCE_THRESHOLD = -30
|
|
87
|
+
|
|
88
|
+
/** Stopwords to filter out before FTS5 query construction */
|
|
89
|
+
const STOPWORDS = new Set([
|
|
90
|
+
"que", "con", "para", "por", "una", "uno", "los", "las", "del",
|
|
91
|
+
"como", "esta", "esto", "ese", "eso", "the", "and", "for",
|
|
92
|
+
"with", "this", "that", "have", "will", "also", "de", "en",
|
|
93
|
+
"el", "la", "se", "su", "sus", "al", "es", "son", "pero",
|
|
94
|
+
"más", "mas", "ya", "yo", "tu", "te", "ti", "mi", "me",
|
|
95
|
+
"hola", "hi", "hello", "hey", "gracias", "thank", "please",
|
|
96
|
+
"ok", "okay", "yes", "si", "no", "bien", "good", "great",
|
|
97
|
+
])
|
|
98
|
+
|
|
99
|
+
/** Conversational patterns that should return empty tool list */
|
|
100
|
+
const CONVERSATIONAL_PATTERNS = [
|
|
101
|
+
/^(hola|hi|hello|hey|buenos? días?|buenas? noches?|qué tal|howdy)/i,
|
|
102
|
+
/^(gracias|thank you|thanks|muchas gracias|muchas thanks)/i,
|
|
103
|
+
/^(cómo estás?|how are you?|qué流水|you doing|qué cuentas)/i,
|
|
104
|
+
/^(sí|yes|ok|okay|de acuerdo|perfecto|claro|por supuesto)/i,
|
|
105
|
+
/^(adiós|bye|nos vemos|see you|later|chau)/i,
|
|
106
|
+
/^(entiendo|understand|i see|ya veo|got it)/i,
|
|
107
|
+
/^(bien|good|great|excelente|awesome|perfect)/i,
|
|
108
|
+
/^(?:\?|¿)$/, // Just a question mark
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
// ─── Tool Catalog ───────────────────────────────────────────────────────────
|
|
112
|
+
//
|
|
113
|
+
// These 47 tools are the core toolset. Each has:
|
|
114
|
+
// - name: unique identifier
|
|
115
|
+
// - description: what the tool does (used for FTS5 matching)
|
|
116
|
+
// - category: semantic domain for grouping
|
|
117
|
+
// - abstractionLevel: atomic (single operation) vs orchestration (manages multiple)
|
|
118
|
+
//
|
|
119
|
+
// The descriptions are enriched with Spanish/English keywords for better FTS5 matching.
|
|
120
|
+
|
|
121
|
+
export const CORE_TOOL_CATALOG: ToolDescriptor[] = [
|
|
122
|
+
// Cron tools (cron.*)
|
|
123
|
+
{ 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" },
|
|
124
|
+
{ 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" },
|
|
125
|
+
{ 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" },
|
|
126
|
+
{ 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" },
|
|
127
|
+
{ name: "cron.resume", description: "Resume a previously paused cron job. Spanish keywords: reanudar tarea, continuar tarea pausada, activar recordatorio", category: "scheduling", abstractionLevel: "atomic" },
|
|
128
|
+
{ name: "cron.delete", description: "Delete a cron job permanently. Spanish keywords: eliminar tarea programada, borrar recordatorio, cancelar tarea", category: "scheduling", abstractionLevel: "atomic" },
|
|
129
|
+
{ 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" },
|
|
130
|
+
{ 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" },
|
|
131
|
+
|
|
132
|
+
// Project management tools (high-level orchestration)
|
|
133
|
+
{ 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" },
|
|
134
|
+
{ 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" },
|
|
135
|
+
{ 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" },
|
|
136
|
+
{ 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" },
|
|
137
|
+
{ 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" },
|
|
138
|
+
|
|
139
|
+
// Task management (atomic)
|
|
140
|
+
{ 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" },
|
|
141
|
+
{ 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" },
|
|
142
|
+
{ 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" },
|
|
143
|
+
|
|
144
|
+
// Code execution
|
|
145
|
+
{ 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" },
|
|
146
|
+
|
|
147
|
+
// Web tools
|
|
148
|
+
{ 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" },
|
|
149
|
+
{ 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" },
|
|
150
|
+
|
|
151
|
+
// Memory tools
|
|
152
|
+
{ 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" },
|
|
153
|
+
{ name: "memory_read", description: "Retrieve from memory by title, fetch saved information using memory identifier. Spanish keywords: leer memoria, recuperar información, recordar, obtener dato, buscar memoria", category: "memory", abstractionLevel: "atomic" },
|
|
154
|
+
{ name: "memory_list", description: "List all memory entries, show all saved memories and stored knowledge. Spanish keywords: listar memorias, ver memorias guardadas, todas las memorias, lista de memorias", category: "memory", abstractionLevel: "atomic" },
|
|
155
|
+
{ name: "memory_search", description: "Search memory by content, find memories containing specific keywords. Spanish keywords: buscar en memoria, buscar información guardada, buscar en recuerdos", category: "memory", abstractionLevel: "atomic" },
|
|
156
|
+
{ 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" },
|
|
157
|
+
|
|
158
|
+
// Agent/worker management
|
|
159
|
+
{ 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" },
|
|
160
|
+
{ 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" },
|
|
161
|
+
{ 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" },
|
|
162
|
+
|
|
163
|
+
// Notes/persistence
|
|
164
|
+
{ name: "save_note", description: "Save persistent note to scratchpad, write quick notes and reminders. Spanish keywords: guardar nota, escribir nota, recordatorio rápido, nota rápida, apuntar", category: "core", abstractionLevel: "atomic" },
|
|
165
|
+
|
|
166
|
+
// Notifications/reporting
|
|
167
|
+
{ name: "notify", description: "Send system notification, alert user with message or alert. Spanish keywords: notificar, enviar notificación, alertar, aviso, alarma", category: "core", abstractionLevel: "atomic" },
|
|
168
|
+
{ name: "report_progress", description: "Report progress to user, inform user of current status and completion. Spanish keywords: reportar progreso, informar estado, actualizar,报告进度, progreso", category: "core", abstractionLevel: "atomic" },
|
|
169
|
+
|
|
170
|
+
// Browser automation
|
|
171
|
+
{ name: "browser_navigate", description: "Navigate to URL and get content, open web pages and extract information. Spanish keywords: navegar web, abrir página, ir a sitio, navegar, ir a página", category: "browser", abstractionLevel: "atomic" },
|
|
172
|
+
{ name: "browser_screenshot", description: "Take webpage screenshot, capture visual snapshot of web page. Spanish keywords: captura de pantalla, screenshot, fotografiar página, imagen de página", category: "browser", abstractionLevel: "atomic" },
|
|
173
|
+
{ name: "browser_click", description: "Click element on page, interact with buttons and links in browser. Spanish keywords: hacer clic, presionar botón, clickear, pulsar, botón", category: "browser", abstractionLevel: "atomic" },
|
|
174
|
+
{ name: "browser_type", description: "Type into input field, fill forms and text inputs in browser. Spanish keywords: escribir en página, llenar formulario, introducir texto, completar formulario", category: "browser", abstractionLevel: "atomic" },
|
|
175
|
+
{ name: "browser_extract", description: "Extract text, links, or structured data from page using CSS selectors or XPath. Spanish keywords: extraer datos, obtener información, scraping, selectores, xpath", category: "browser", abstractionLevel: "atomic" },
|
|
176
|
+
{ 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" },
|
|
177
|
+
{ 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" },
|
|
178
|
+
|
|
179
|
+
// Canvas/UI rendering tools
|
|
180
|
+
{ 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" },
|
|
181
|
+
{ 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" },
|
|
182
|
+
{ 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" },
|
|
183
|
+
{ 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" },
|
|
184
|
+
{ 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" },
|
|
185
|
+
{ 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" },
|
|
186
|
+
{ 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" },
|
|
187
|
+
|
|
188
|
+
// A2UI v0.9 rich interactive surfaces
|
|
189
|
+
{ 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" },
|
|
190
|
+
{ 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" },
|
|
191
|
+
{ 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" },
|
|
192
|
+
{ 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" },
|
|
193
|
+
|
|
194
|
+
// CodeBridge (subagent process management)
|
|
195
|
+
{ 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" },
|
|
196
|
+
{ 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" },
|
|
197
|
+
{ 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" },
|
|
198
|
+
|
|
199
|
+
// Voice tools
|
|
200
|
+
{ 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" },
|
|
201
|
+
{ 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" },
|
|
202
|
+
|
|
203
|
+
// Filesystem tools
|
|
204
|
+
{ 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" },
|
|
205
|
+
{ name: "fs_write", description: "Create or overwrite file in workspace. Spanish keywords: crear archivo, guardar archivo, escribir archivo, crear fichero, escribir fichero", category: "filesystem", abstractionLevel: "atomic" },
|
|
206
|
+
{ name: "fs_edit", description: "Edit specific lines or sections of a file. Spanish keywords: editar archivo, modificar líneas, actualizar contenido, cambiar archivo", category: "filesystem", abstractionLevel: "atomic" },
|
|
207
|
+
{ name: "fs_delete", description: "Delete file or directory. Spanish keywords: eliminar archivo, borrar archivo, borrar carpeta, eliminar fichero", category: "filesystem", abstractionLevel: "atomic" },
|
|
208
|
+
{ name: "fs_list", description: "List files and directories. Spanish keywords: listar archivos, ver carpeta, explorar directorio, listar ficheros", category: "filesystem", abstractionLevel: "atomic" },
|
|
209
|
+
{ name: "fs_glob", description: "Find files matching wildcard patterns. Spanish keywords: buscar archivos, patrón, encontrar archivos, buscar ficheros", category: "filesystem", abstractionLevel: "atomic" },
|
|
210
|
+
{ name: "fs_exists", description: "Check if a file or directory exists. Spanish keywords: verificar archivo, comprobar, existe archivo, comprobar fichero", category: "filesystem", abstractionLevel: "atomic" },
|
|
211
|
+
|
|
212
|
+
// Agent delegation and communication
|
|
213
|
+
{ 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" },
|
|
214
|
+
{ 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" },
|
|
215
|
+
{ 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" },
|
|
216
|
+
{ 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" },
|
|
217
|
+
{ 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" },
|
|
218
|
+
{ 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" },
|
|
219
|
+
]
|
|
220
|
+
|
|
221
|
+
// ─── Helper Functions ───────────────────────────────────────────────────────-
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Check if message is purely conversational (no tools needed)
|
|
225
|
+
*
|
|
226
|
+
* Uses pattern matching for common conversational phrases.
|
|
227
|
+
* Also checks for very short messages that are likely greetings.
|
|
228
|
+
*/
|
|
229
|
+
function isConversational(message: string): boolean {
|
|
230
|
+
log.info(`[tool-selector] Checking if message is conversational: "${message}"`)
|
|
231
|
+
const trimmed = message.trim()
|
|
232
|
+
|
|
233
|
+
// Empty or very short messages
|
|
234
|
+
if (trimmed.length < 2) return true
|
|
235
|
+
|
|
236
|
+
// Check conversational patterns
|
|
237
|
+
for (const pattern of CONVERSATIONAL_PATTERNS) {
|
|
238
|
+
if (pattern.test(trimmed)) {
|
|
239
|
+
log.debug(`[tool-selector] Message matched conversational pattern: ${pattern}`)
|
|
240
|
+
return true
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Check if all words are stopwords (likely conversational)
|
|
245
|
+
const words = trimmed.toLowerCase().split(/\s+/)
|
|
246
|
+
const meaningfulWords = words.filter(w => w.length > 2 && !STOPWORDS.has(w))
|
|
247
|
+
if (meaningfulWords.length === 0) {
|
|
248
|
+
log.debug(`[tool-selector] All words are stopwords - conversational`)
|
|
249
|
+
return true
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return false
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Build FTS5 query from user message
|
|
257
|
+
*
|
|
258
|
+
* Strips stopwords, special characters, and limits to 8 keywords.
|
|
259
|
+
* Uses OR operator for flexible matching.
|
|
260
|
+
*/
|
|
261
|
+
function buildFTSQuery(message: string): string {
|
|
262
|
+
log.info(`[tool-selector] Building FTS query from message: "${message}"`)
|
|
263
|
+
const words = message
|
|
264
|
+
.toLowerCase()
|
|
265
|
+
.replace(/[^\p{L}\p{N}\s]/gu, " ")
|
|
266
|
+
.split(/\s+/)
|
|
267
|
+
.filter((w) => w.length > 2 && !STOPWORDS.has(w))
|
|
268
|
+
.slice(0, 8)
|
|
269
|
+
|
|
270
|
+
if (words.length === 0) return ""
|
|
271
|
+
|
|
272
|
+
// Use prefix matching for better recall (e.g., "gener*" matches "generar", "generando", "generación")
|
|
273
|
+
return words.map(w => `${w}*`).join(" OR ")
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Determine abstraction level preference
|
|
278
|
+
*
|
|
279
|
+
* Returns 'atomic' to prefer individual tools, 'orchestration' to prefer
|
|
280
|
+
* manager tools. Currently always prefers atomic for better control.
|
|
281
|
+
*/
|
|
282
|
+
function getAbstractionPreference(): "atomic" | "orchestration" {
|
|
283
|
+
// Prefer atomic tools for more predictable behavior
|
|
284
|
+
return "atomic"
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ─── Main Selection Function ─────────────────────────────────────────────────
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Select tools for a given user message using FTS5 bm25() scoring
|
|
291
|
+
*
|
|
292
|
+
* @param userMessage - The raw user message
|
|
293
|
+
* @param fullToolList - Full list of available tools (for validation/filtering)
|
|
294
|
+
* @returns Array of 0-4 selected tools with scores
|
|
295
|
+
*
|
|
296
|
+
* ALGORITHM:
|
|
297
|
+
* 1. If conversational → return []
|
|
298
|
+
* 2. Build FTS5 query from message keywords
|
|
299
|
+
* 3. Query tools_fts with bm25() scoring
|
|
300
|
+
* 4. Filter results below MIN_RELEVANCE_THRESHOLD
|
|
301
|
+
* 5. If ambiguous → prefer atomic over orchestration
|
|
302
|
+
* 6. Return top maxTools results (default: MAX_TOOLS_PER_TURN)
|
|
303
|
+
*/
|
|
304
|
+
export function selectTools(
|
|
305
|
+
userMessage: string,
|
|
306
|
+
fullToolList: ToolDescriptor[] = CORE_TOOL_CATALOG,
|
|
307
|
+
maxTools: number = MAX_TOOLS_PER_TURN
|
|
308
|
+
): ToolDescriptor[] {
|
|
309
|
+
const startTime = performance.now()
|
|
310
|
+
|
|
311
|
+
// Log incoming user message for debugging/validation
|
|
312
|
+
log.debug(`[tool-selector] Processing user message: "${userMessage.substring(0, 100)}"`)
|
|
313
|
+
|
|
314
|
+
// Step 1: Check if conversational
|
|
315
|
+
if (isConversational(userMessage)) {
|
|
316
|
+
log.debug(`[tool-selector] Conversational message, returning empty array`)
|
|
317
|
+
return []
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Step 2: Build FTS5 query
|
|
321
|
+
const ftsQuery = buildFTSQuery(userMessage)
|
|
322
|
+
if (!ftsQuery) {
|
|
323
|
+
log.debug(`[tool-selector] No valid FTS query terms, returning empty array`)
|
|
324
|
+
return []
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
log.debug(`[tool-selector] FTS query: "${ftsQuery}"`)
|
|
328
|
+
|
|
329
|
+
// Step 3: Execute FTS5 query with bm25 scoring
|
|
330
|
+
const db = getDb()
|
|
331
|
+
|
|
332
|
+
// Use bm25() with column weights for relevance scoring
|
|
333
|
+
// FTS5 table columns: tool_name, name, description, category
|
|
334
|
+
// Weights: tool_name=1.0, name=5.0, description=3.0, category=1.0
|
|
335
|
+
// Higher weight on name (5.0) for exact tool name matching
|
|
336
|
+
// Get more initially (maxTools * 2) for filtering, then limit to maxTools
|
|
337
|
+
const ftsResults = db.query(`
|
|
338
|
+
SELECT tool_name, bm25(tools_fts, 1.0, 5.0, 3.0, 1.0) as bm25_score
|
|
339
|
+
FROM tools_fts
|
|
340
|
+
WHERE tools_fts MATCH ?
|
|
341
|
+
ORDER BY bm25_score ASC
|
|
342
|
+
LIMIT ?
|
|
343
|
+
`).all(ftsQuery, maxTools * 2) as { tool_name: string; bm25_score: number }[]
|
|
344
|
+
|
|
345
|
+
if (ftsResults.length === 0) {
|
|
346
|
+
log.debug(`[tool-selector] No FTS matches, returning empty array`)
|
|
347
|
+
return []
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Log raw scores for debugging
|
|
351
|
+
log.info(`[tool-selector] Raw FTS scores: ${ftsResults.slice(0, 10).map(r => `${r.tool_name}=${r.bm25_score.toFixed(2)}`).join(", ")}`)
|
|
352
|
+
|
|
353
|
+
// Step 4: Apply relevance threshold filter
|
|
354
|
+
// bm25() returns negative scores; threshold is -0.5 (loosened from typical -5)
|
|
355
|
+
const relevantResults = ftsResults.filter(r => r.bm25_score >= MIN_RELEVANCE_THRESHOLD)
|
|
356
|
+
|
|
357
|
+
if (relevantResults.length === 0) {
|
|
358
|
+
log.debug(`[tool-selector] All results below threshold ${MIN_RELEVANCE_THRESHOLD}, returning empty`)
|
|
359
|
+
return []
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Step 5: Map to tool descriptors with additional metadata
|
|
363
|
+
const toolMap = new Map(fullToolList.map(t => [t.name, t]))
|
|
364
|
+
|
|
365
|
+
const scoredTools: SelectedTool[] = []
|
|
366
|
+
|
|
367
|
+
for (const result of relevantResults) {
|
|
368
|
+
const tool = toolMap.get(result.tool_name)
|
|
369
|
+
if (tool) {
|
|
370
|
+
scoredTools.push({
|
|
371
|
+
name: tool.name,
|
|
372
|
+
score: result.bm25_score,
|
|
373
|
+
category: tool.category,
|
|
374
|
+
})
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Step 6: Prefer atomic over orchestration when ambiguous
|
|
379
|
+
// If we have more than MAX_TOOLS_PER_TURN, prioritize by abstraction level
|
|
380
|
+
const abstractionPref = getAbstractionPreference()
|
|
381
|
+
|
|
382
|
+
if (scoredTools.length > MAX_TOOLS_PER_TURN) {
|
|
383
|
+
// Sort by score first, then by abstraction level preference
|
|
384
|
+
// CRITICAL FIX: bm25() returns NEGATIVE scores where closer to 0 = more relevant
|
|
385
|
+
// So we sort ASCENDING (a.score - b.score) to put -8.02 before -5.11
|
|
386
|
+
scoredTools.sort((a, b) => {
|
|
387
|
+
// First by score (ascending for bm25 - closer to 0 is better)
|
|
388
|
+
if (Math.abs(a.score - b.score) > 0.1) {
|
|
389
|
+
return a.score - b.score // ✅ Fixed: ascending for negative bm25 scores
|
|
390
|
+
}
|
|
391
|
+
// Then by abstraction preference (preferred type first)
|
|
392
|
+
const aTool = toolMap.get(a.name)
|
|
393
|
+
const bTool = toolMap.get(b.name)
|
|
394
|
+
const aLevel = aTool?.abstractionLevel ?? "atomic"
|
|
395
|
+
const bLevel = bTool?.abstractionLevel ?? "atomic"
|
|
396
|
+
|
|
397
|
+
if (abstractionPref === "atomic") {
|
|
398
|
+
return (aLevel === "atomic" ? -1 : 1)
|
|
399
|
+
} else {
|
|
400
|
+
return (aLevel === "orchestration" ? -1 : 1)
|
|
401
|
+
}
|
|
402
|
+
})
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Step 7: Take top N tools
|
|
406
|
+
const topTools = scoredTools.slice(0, maxTools)
|
|
407
|
+
|
|
408
|
+
// Step 8: Return as ToolDescriptor array
|
|
409
|
+
const result = topTools.map(t => toolMap.get(t.name)!).filter(Boolean)
|
|
410
|
+
|
|
411
|
+
const timing = performance.now() - startTime
|
|
412
|
+
|
|
413
|
+
// Log final selected tools with info level (important for tracking tool selection process)
|
|
414
|
+
if (result.length > 0) {
|
|
415
|
+
log.info(`[tool-selector] Selected ${result.length} tools in ${timing.toFixed(2)}ms:`,
|
|
416
|
+
result.map(t => ({ name: t.name, category: t.category })))
|
|
417
|
+
} else {
|
|
418
|
+
log.debug(`[tool-selector] No tools selected, returning empty array in ${timing.toFixed(2)}ms`)
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return result
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// ─── Sync Tools to FTS5 ─────────────────────────────────────────────────────
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Sync tool catalog to FTS5 virtual table
|
|
428
|
+
*
|
|
429
|
+
* Called on initialization from gateway/initializer.ts to populate the FTS5 index.
|
|
430
|
+
* Assumes the tools_fts table already exists (created by CONTEXT_ENGINE_SCHEMA).
|
|
431
|
+
* Descriptions are enriched with bilingual keywords for better matching.
|
|
432
|
+
*
|
|
433
|
+
* @param tools - Optional array of tools to sync. If not provided, fetches from DB.
|
|
434
|
+
*/
|
|
435
|
+
export async function syncToolCatalogToFTS(tools?: ToolDescriptor[]): Promise<void> {
|
|
436
|
+
const db = getDb()
|
|
437
|
+
|
|
438
|
+
try {
|
|
439
|
+
// Step 1: Build full catalog = CORE_TOOL_CATALOG + any tools in DB not already covered
|
|
440
|
+
// CORE_TOOL_CATALOG has bilingual keywords; DB tools may be dynamically registered
|
|
441
|
+
const catalogByName = new Map<string, ToolDescriptor>(
|
|
442
|
+
CORE_TOOL_CATALOG.map(t => [t.name, t])
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
// Merge in any tools from the DB that are missing from the static catalog
|
|
446
|
+
const dbTools = db.query("SELECT name, description, category FROM tools").all() as Array<{ name: string; description: string | null; category: string | null }>
|
|
447
|
+
for (const row of dbTools) {
|
|
448
|
+
if (!catalogByName.has(row.name)) {
|
|
449
|
+
catalogByName.set(row.name, {
|
|
450
|
+
name: row.name,
|
|
451
|
+
description: row.description ?? row.name,
|
|
452
|
+
category: (row.category ?? "core") as any,
|
|
453
|
+
abstractionLevel: "atomic",
|
|
454
|
+
})
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Also merge any explicitly passed tools (e.g. from initializer)
|
|
459
|
+
for (const t of (tools || [])) {
|
|
460
|
+
if (!catalogByName.has(t.name)) {
|
|
461
|
+
catalogByName.set(t.name, t)
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const toolCatalog = Array.from(catalogByName.values())
|
|
466
|
+
|
|
467
|
+
// Step 2: Atomic transaction for FTS5 sync
|
|
468
|
+
// We use a transaction to ensure that if sync fails, we don't end up with an empty FTS table
|
|
469
|
+
const syncTransaction = db.transaction(() => {
|
|
470
|
+
// Verify table exists inside transaction (optional but safer)
|
|
471
|
+
const tableCheck = db.query("SELECT name FROM sqlite_master WHERE type='table' AND name='tools_fts'").get()
|
|
472
|
+
if (!tableCheck) {
|
|
473
|
+
throw new Error("tools_fts table does not exist!")
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// A: Clear existing data
|
|
477
|
+
db.run("DELETE FROM tools_fts")
|
|
478
|
+
|
|
479
|
+
// B: Prepare insertion
|
|
480
|
+
const insert = db.prepare(`
|
|
481
|
+
INSERT INTO tools_fts(tool_name, name, description, category)
|
|
482
|
+
VALUES (?, ?, ?, ?)
|
|
483
|
+
`)
|
|
484
|
+
|
|
485
|
+
// C: Re-populate
|
|
486
|
+
for (const tool of toolCatalog) {
|
|
487
|
+
const enriched = enrichToolDescription(tool)
|
|
488
|
+
insert.run(tool.name, tool.name, enriched, tool.category)
|
|
489
|
+
}
|
|
490
|
+
})
|
|
491
|
+
|
|
492
|
+
// Execute transaction
|
|
493
|
+
syncTransaction()
|
|
494
|
+
|
|
495
|
+
log.info(`[tool-selector] Atomic sync complete: ${toolCatalog.length} tools indexed in FTS5`)
|
|
496
|
+
|
|
497
|
+
} catch (err) {
|
|
498
|
+
log.error(`[tool-selector] Transactional sync failed:`, err)
|
|
499
|
+
throw err // Re-throw to inform initializer
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Enrich tool description with category-specific keywords
|
|
505
|
+
*
|
|
506
|
+
* This improves FTS5 matching for both English and Spanish queries.
|
|
507
|
+
*/
|
|
508
|
+
function enrichToolDescription(tool: ToolDescriptor): string {
|
|
509
|
+
const keywordsByCategory: Record<string, string> = {
|
|
510
|
+
scheduling: "programar recordatorio alarma cron schedule reminder task future tiempo",
|
|
511
|
+
projects: "proyecto tarea plan organizer milestone backlog sprint work",
|
|
512
|
+
filesystem: "archivo file leer escribir editar documento content source code",
|
|
513
|
+
web: "buscar internet google web search find information news research",
|
|
514
|
+
browser: "navegador browser click screenshot form automation web page UI",
|
|
515
|
+
memory: "recordar nota guardar memory store remember persist knowledge",
|
|
516
|
+
code: "code ejecutar run script bash shell terminal command devops",
|
|
517
|
+
canvas: "canvas diagram visualization graph node edge flow chart",
|
|
518
|
+
agents: "agente worker specialist create delegate hire team manager",
|
|
519
|
+
core: "notificar message alert notify communicate progress status",
|
|
520
|
+
voice: "voz audio transcribir speech speak sintetizar audio voice transcription",
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const extra = keywordsByCategory[tool.category] ?? ""
|
|
524
|
+
return `${tool.description} ${extra}`
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// ─── Initialization ─────────────────────────────────────────────────────────
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Sanitize an MCP tool name to comply with LLM function-name rules.
|
|
531
|
+
*
|
|
532
|
+
* Gemini (and OpenAI) require: start with letter/underscore, only [a-zA-Z0-9_.-:], max 64 chars.
|
|
533
|
+
* Server names from the UI can contain spaces and special chars (e.g. "X antes twiter").
|
|
534
|
+
*
|
|
535
|
+
* Canonical format: `{safeServer}__{safeTool}` (double underscore as separator)
|
|
536
|
+
*/
|
|
537
|
+
export function mcpToolFullName(serverName: string, toolName: string): string {
|
|
538
|
+
const safe = (s: string) => s.replace(/\s+/g, '_').replace(/[^a-zA-Z0-9_.\-:]/g, '_')
|
|
539
|
+
const full = `${safe(serverName)}__${safe(toolName)}`
|
|
540
|
+
// Ensure starts with letter/underscore and fits within 64 chars
|
|
541
|
+
const trimmed = full.length > 64 ? full.substring(0, 64) : full
|
|
542
|
+
return /^[a-zA-Z_]/.test(trimmed) ? trimmed : `_${trimmed}`.substring(0, 64)
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Initialize the tool selector
|
|
547
|
+
*
|
|
548
|
+
* DEPRECATED: syncToolCatalogToFTS() is now called from gateway/initializer.ts
|
|
549
|
+
* This function is kept for backward compatibility but is no longer needed
|
|
550
|
+
*/
|
|
551
|
+
export function initializeToolSelector(): void {
|
|
552
|
+
log.info(`[tool-selector] Initializing (deprecated - sync is done in gateway/initializer.ts)`)
|
|
553
|
+
// syncToolCatalogToFTS() - No longer needed here, done in gateway/initializer.ts
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// ─── Debug/Test Helpers ─────────────────────────────────────────────────────
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Get all tools (for debugging/testing)
|
|
560
|
+
*/
|
|
561
|
+
export function getAllTools(): ToolDescriptor[] {
|
|
562
|
+
return [...CORE_TOOL_CATALOG]
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Get tool by name
|
|
567
|
+
*/
|
|
568
|
+
export function getToolByName(name: string): ToolDescriptor | undefined {
|
|
569
|
+
return CORE_TOOL_CATALOG.find(t => t.name === name)
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Get tools by category
|
|
574
|
+
*/
|
|
575
|
+
export function getToolsByCategory(category: string): ToolDescriptor[] {
|
|
576
|
+
return CORE_TOOL_CATALOG.filter(t => t.category === category)
|
|
577
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { ToolDescriptor, SelectedTool, ToolSelectorResult } from "./ToolSelector.ts";
|
|
2
|
+
export { MIN_RELEVANCE_THRESHOLD, CORE_TOOL_CATALOG, selectTools, syncToolCatalogToFTS, mcpToolFullName, initializeToolSelector, getAllTools, getToolByName, getToolsByCategory } from "./ToolSelector.ts";
|
|
3
|
+
export type { SkillDescriptor, SelectedSkill, SkillSelectorResult } from "./SkillSelector.ts";
|
|
4
|
+
export { MINIMAL_SKILL_NAMES, selectSkills, getMinimalSkills, syncSkillsToFTS, initializeSkillSelector, getAllSkillsFromDB, getSkillByName, getSkillsByCategory } from "./SkillSelector.ts";
|
|
5
|
+
export type { PlaybookRule } from "./PlaybookSelector.ts";
|
|
6
|
+
export { selectPlaybookRules, syncPlaybookToFTS } from "./PlaybookSelector.ts";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from "bun:test";
|
|
2
|
+
import { getDb, initializeDatabase, dbService } from "../storage/SQLiteStorage.ts";
|
|
3
|
+
|
|
4
|
+
describe("createAgent API", () => {
|
|
5
|
+
beforeAll(async () => {
|
|
6
|
+
await initializeDatabase();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
afterAll(() => {
|
|
10
|
+
dbService.close();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("defineTool and defineSkill exports are available", async () => {
|
|
14
|
+
const { defineTool } = await import("../tools/ToolRegistry.ts");
|
|
15
|
+
const { defineSkill } = await import("../skills/defineSkill.ts");
|
|
16
|
+
|
|
17
|
+
const tool = defineTool({
|
|
18
|
+
name: "greet",
|
|
19
|
+
description: "Says hello",
|
|
20
|
+
execute: async (args: { name: string }) => `Hello ${args.name}`,
|
|
21
|
+
});
|
|
22
|
+
expect(tool.name).toBe("greet");
|
|
23
|
+
|
|
24
|
+
const skill = defineSkill({
|
|
25
|
+
name: "greeting-skill",
|
|
26
|
+
description: "Greeting skill",
|
|
27
|
+
steps: [{ action: "greet", instruction: "Say hello" }],
|
|
28
|
+
});
|
|
29
|
+
expect(skill.name).toBe("greeting-skill");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("createAgent builds an agent instance", async () => {
|
|
33
|
+
const { createAgent } = await import("./createAgent.ts");
|
|
34
|
+
const agent = await createAgent({
|
|
35
|
+
name: "test-agent",
|
|
36
|
+
provider: "openai",
|
|
37
|
+
model: "gpt-4o-mini",
|
|
38
|
+
});
|
|
39
|
+
expect(agent.name).toBe("test-agent");
|
|
40
|
+
expect(agent.config).toBeDefined();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("ToolRegistry can be used standalone", async () => {
|
|
44
|
+
const { ToolRegistry } = await import("../tools/ToolRegistry.ts");
|
|
45
|
+
const reg = new ToolRegistry();
|
|
46
|
+
expect(reg.size()).toBe(0);
|
|
47
|
+
});
|
|
48
|
+
});
|