@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,1603 @@
|
|
|
1
|
+
import { logger } from "../utils/logger.ts";
|
|
2
|
+
import { getDb, initializeDatabase } from "./SQLiteStorage.ts";
|
|
3
|
+
import { encryptApiKey, encryptConfig, decryptApiKey, decryptConfig } from "./crypto";
|
|
4
|
+
import { seedAllData, SEED_DATA } from "./seed";
|
|
5
|
+
import { SkillLoader } from "../skills/index.ts";
|
|
6
|
+
|
|
7
|
+
export interface OnboardingSection {
|
|
8
|
+
step: "user" | "skills" | "ethics" | "tools" | "provider" | "model" | "channel" | "codebridge" | "mcp" | "agent" | "complete";
|
|
9
|
+
userId: string;
|
|
10
|
+
data: Record<string, unknown>;
|
|
11
|
+
completedAt?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const log = logger.child("onboarding");
|
|
15
|
+
// 9️⃣ Hive System Prompt
|
|
16
|
+
|
|
17
|
+
const HIVE_SYSTEM_PROMPT = `
|
|
18
|
+
# HIVE — Agente Coordinador
|
|
19
|
+
|
|
20
|
+
Sos Bee, coordinador de Hive. Resolvés tareas del usuario directamente o delegando a workers especializados. Tu rol es "coordinator".
|
|
21
|
+
|
|
22
|
+
## ⚡ REGLAS CRÍTICAS
|
|
23
|
+
|
|
24
|
+
1. **Ética primero** — Operás bajo un Código de Ética obligatorio. No podés ignorarlo.
|
|
25
|
+
2. **Confirmá antes de guardar** — Siempre verificá con el usuario antes de persistir datos en la BD.
|
|
26
|
+
3. **Buscá antes de crear** — Usá search_knowledge para capacidades, find_agent para workers.
|
|
27
|
+
4. **Mínimo privilegio** — Asigná solo las tools necesarias a cada worker.
|
|
28
|
+
5. **Nunca cli_exec para cron** — Usá siempre cron.create para tareas programadas.
|
|
29
|
+
6. **Nunca codebridge_launch directo** — Creá un worker code_developer primero.
|
|
30
|
+
|
|
31
|
+
## 🔍 DISCOVERY — CÓMO ENCONTRAR MÁS CAPACIDADES
|
|
32
|
+
|
|
33
|
+
Arrancás con solo 4 herramientas. Para descubrir más, usá **search_knowledge**:
|
|
34
|
+
|
|
35
|
+
- \`search_knowledge(type="tools", query="leer archivos")\` → herramientas nativas
|
|
36
|
+
- \`search_knowledge(type="mcp", query="listar bases datos")\` → herramientas MCP externas
|
|
37
|
+
- \`search_knowledge(type="skills", query="debuggear código")\` → skills (instrucciones de tareas)
|
|
38
|
+
- \`search_knowledge(type="playbook", query="seguridad")\` → playbook (buenas prácticas)
|
|
39
|
+
- \`search_knowledge(type="all", query="buscar web internet")\` → busca en todo
|
|
40
|
+
|
|
41
|
+
La búsqueda es bilingüe: buscá en español y si hay pocos resultados se re-intenta con equivalentes en inglés.
|
|
42
|
+
|
|
43
|
+
**Prioridad:** SIEMPRE preferí herramientas nativas sobre MCP cuando ambas resuelven la tarea.
|
|
44
|
+
|
|
45
|
+
## 📋 FLUJO DE TRABAJO
|
|
46
|
+
|
|
47
|
+
**Tarea simple (1-2 pasos):** Ejecutala directo con tus tools.
|
|
48
|
+
|
|
49
|
+
**Tarea repetitiva:** Usá cron.create. Preguntá al usuario cada cuánto ejecutarla.
|
|
50
|
+
|
|
51
|
+
**Tarea compleja (múltiples workers):** Creá un proyecto con project_create, descomponé en tareas, delegá con delegate_task.
|
|
52
|
+
|
|
53
|
+
**Worker:** find_agent → ¿existe? → reutilizalo. Si no → create_agent con system_prompt claro y tools_json mínimo. **delegate_task** lo activa.
|
|
54
|
+
|
|
55
|
+
**Proyectos:** Solo creá proyecto cuando hay múltiples workers coordinando. NO para tareas unitarias.
|
|
56
|
+
|
|
57
|
+
**Cierre:** task_update(status, result) → task_evaluate(criteria) → project_done(summary).
|
|
58
|
+
|
|
59
|
+
## 🧠 MEMORIA
|
|
60
|
+
|
|
61
|
+
- \`save_note\` — Persiste notas por conversación (sobrevive compresión)
|
|
62
|
+
- \`memory_write\` / \`memory_read\` — Memoria cross-conversación por clave
|
|
63
|
+
- Playbook — Reglas aprendidas inyectadas automáticamente
|
|
64
|
+
|
|
65
|
+
## 📡 CANALES
|
|
66
|
+
|
|
67
|
+
webchat (siempre activo) · telegram · discord · slack · whatsapp
|
|
68
|
+
Canal preferido para cron: telegram > discord > webchat
|
|
69
|
+
`
|
|
70
|
+
export function initOnboardingDb(): void {
|
|
71
|
+
try {
|
|
72
|
+
initializeDatabase();
|
|
73
|
+
|
|
74
|
+
// Verificar si la DB ya tiene datos antes de hacer seed
|
|
75
|
+
const db = getDb();
|
|
76
|
+
const userCount = db.query("SELECT COUNT(*) as count FROM users").get() as { count: number };
|
|
77
|
+
|
|
78
|
+
if (userCount.count > 0) {
|
|
79
|
+
log.info("✅ DB ya inicializada con " + userCount.count + " usuario(s). Saltando seed.");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
log.info("🌱 Ejecutando seed de datos...");
|
|
84
|
+
seedAllData();
|
|
85
|
+
log.info("✅ Seed completado correctamente.");
|
|
86
|
+
} catch (e) {
|
|
87
|
+
log.error("⚠️ Fallo al inicializar/poblar la DB:", { error: (e as Error).message });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function saveUserProfile(data: {
|
|
92
|
+
userId?: string;
|
|
93
|
+
userName?: string;
|
|
94
|
+
userLanguage?: string;
|
|
95
|
+
userTimezone?: string;
|
|
96
|
+
userOccupation?: string;
|
|
97
|
+
userNotes?: string;
|
|
98
|
+
agentName?: string;
|
|
99
|
+
agentId?: string;
|
|
100
|
+
agentDescription?: string;
|
|
101
|
+
agentTone?: string;
|
|
102
|
+
channelUserId?: string;
|
|
103
|
+
}): string {
|
|
104
|
+
try {
|
|
105
|
+
const db = getDb();
|
|
106
|
+
let finalUserId = data.userId;
|
|
107
|
+
|
|
108
|
+
if (!finalUserId) {
|
|
109
|
+
// 1️⃣ Dejar que SQLite genere el ID automáticamente con randomblob(16)
|
|
110
|
+
const result = db.query(`
|
|
111
|
+
INSERT INTO users(name, language, timezone, occupation, notes)
|
|
112
|
+
VALUES(?, ?, ?, ?, ?) RETURNING id
|
|
113
|
+
`).get(
|
|
114
|
+
data.userName || null,
|
|
115
|
+
data.userLanguage || null,
|
|
116
|
+
data.userTimezone || null,
|
|
117
|
+
data.userOccupation || null,
|
|
118
|
+
data.userNotes || null
|
|
119
|
+
) as { id: string };
|
|
120
|
+
finalUserId = result.id;
|
|
121
|
+
log.info("✅ User created with auto-generated ID", { userId: finalUserId });
|
|
122
|
+
} else {
|
|
123
|
+
// 1️⃣ Upsert con ID explícito (flujo web o actualización)
|
|
124
|
+
db.query(`
|
|
125
|
+
INSERT INTO users(id, name, language, timezone, occupation, notes)
|
|
126
|
+
VALUES(?, ?, ?, ?, ?, ?)
|
|
127
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
128
|
+
name = COALESCE(excluded.name, name),
|
|
129
|
+
language = COALESCE(excluded.language, language),
|
|
130
|
+
timezone = COALESCE(excluded.timezone, timezone),
|
|
131
|
+
occupation = COALESCE(excluded.occupation, occupation),
|
|
132
|
+
notes = COALESCE(excluded.notes, notes)
|
|
133
|
+
`).run(
|
|
134
|
+
finalUserId,
|
|
135
|
+
data.userName || null,
|
|
136
|
+
data.userLanguage || null,
|
|
137
|
+
data.userTimezone || null,
|
|
138
|
+
data.userOccupation || null,
|
|
139
|
+
data.userNotes || null
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 2️⃣ Crear identidad base para webchat (sesión única)
|
|
144
|
+
if (data.channelUserId) {
|
|
145
|
+
db.query(`
|
|
146
|
+
INSERT OR REPLACE INTO user_identities(user_id, channel, channel_user_id)
|
|
147
|
+
VALUES(?, 'webchat', ?)
|
|
148
|
+
`).run(finalUserId, data.channelUserId);
|
|
149
|
+
log.info("✅ User identity created for webchat", { userId: finalUserId });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 3️⃣ Crear o actualizar agente
|
|
153
|
+
if (data.agentId && data.agentName) {
|
|
154
|
+
|
|
155
|
+
db.query(`
|
|
156
|
+
INSERT INTO agents
|
|
157
|
+
(id, user_id, name, description, tone, system_prompt, status, role)
|
|
158
|
+
VALUES(?, ?, ?, ?, ?, ?, 'idle', 'coordinator')
|
|
159
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
160
|
+
user_id = COALESCE(excluded.user_id, user_id),
|
|
161
|
+
name = COALESCE(excluded.name, name),
|
|
162
|
+
description = COALESCE(excluded.description, description),
|
|
163
|
+
tone = COALESCE(excluded.tone, tone),
|
|
164
|
+
system_prompt = excluded.system_prompt,
|
|
165
|
+
role = 'coordinator'
|
|
166
|
+
`).run(
|
|
167
|
+
data.agentId,
|
|
168
|
+
finalUserId,
|
|
169
|
+
data.agentName,
|
|
170
|
+
data.agentDescription || null,
|
|
171
|
+
data.agentTone || null,
|
|
172
|
+
HIVE_SYSTEM_PROMPT,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return finalUserId;
|
|
177
|
+
} catch (e) {
|
|
178
|
+
log.error("⚠️ Error saving user profile:", { error: (e as Error).message });
|
|
179
|
+
throw e;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function activateSkills(userId: string, skillIds: string[]): void {
|
|
184
|
+
try {
|
|
185
|
+
const db = getDb();
|
|
186
|
+
// Activar skills seleccionadas
|
|
187
|
+
for (const skillId of skillIds) {
|
|
188
|
+
db.query(`UPDATE skills SET active = 1 WHERE id = ? `).run(skillId);
|
|
189
|
+
}
|
|
190
|
+
log.info("✅ Skills activadas:", { skillIds: skillIds.join(", ") });
|
|
191
|
+
} catch (e) {
|
|
192
|
+
log.error("⚠️ Error activating skills:", { error: (e as Error).message });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function activateEthics(userId: string, ethicsId: string): void {
|
|
197
|
+
try {
|
|
198
|
+
const db = getDb();
|
|
199
|
+
// Activar el ethics seleccionado
|
|
200
|
+
db.query(`UPDATE ethics SET active = 1 WHERE id = ? `).run(ethicsId);
|
|
201
|
+
// Desactivar los demás
|
|
202
|
+
db.query(`UPDATE ethics SET active = 0 WHERE id != ? `).run(ethicsId);
|
|
203
|
+
log.info("✅ Ethics activado:", { ethicsId });
|
|
204
|
+
} catch (e) {
|
|
205
|
+
log.error("⚠️ Error activating ethics:", { error: (e as Error).message });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function activateTools(userId: string, toolIds: string[]): void {
|
|
210
|
+
try {
|
|
211
|
+
const db = getDb();
|
|
212
|
+
// Activar tools seleccionadas
|
|
213
|
+
for (const toolId of toolIds) {
|
|
214
|
+
db.query(`UPDATE tools SET active = 1, enabled = 1 WHERE id = ? `).run(toolId);
|
|
215
|
+
}
|
|
216
|
+
log.info("✅ Tools activadas:", { toolIds: toolIds.join(", ") });
|
|
217
|
+
} catch (e) {
|
|
218
|
+
log.error("⚠️ Error activating tools:", { error: (e as Error).message });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Activate all browser tools when Chromium is available
|
|
224
|
+
* Called from gateway initializer when browser service connects successfully
|
|
225
|
+
*/
|
|
226
|
+
export function activateBrowserTools(): void {
|
|
227
|
+
try {
|
|
228
|
+
const db = getDb();
|
|
229
|
+
const browserToolIds = [
|
|
230
|
+
"browser_navigate",
|
|
231
|
+
"browser_screenshot",
|
|
232
|
+
"browser_click",
|
|
233
|
+
"browser_type",
|
|
234
|
+
"browser_extract",
|
|
235
|
+
"browser_script",
|
|
236
|
+
"browser_wait",
|
|
237
|
+
];
|
|
238
|
+
|
|
239
|
+
for (const toolId of browserToolIds) {
|
|
240
|
+
db.query(`UPDATE tools SET active = 1, enabled = 1 WHERE id = ? `).run(toolId);
|
|
241
|
+
}
|
|
242
|
+
log.info("✅ Browser tools activated (Chromium available)");
|
|
243
|
+
} catch (e) {
|
|
244
|
+
log.error("⚠️ Error activating browser tools:", { error: (e as Error).message });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export async function saveProviderConfig(data: {
|
|
249
|
+
userId: string;
|
|
250
|
+
provider: string;
|
|
251
|
+
model: string;
|
|
252
|
+
apiKey?: string;
|
|
253
|
+
baseUrl?: string;
|
|
254
|
+
}): Promise<void> {
|
|
255
|
+
try {
|
|
256
|
+
const db = getDb();
|
|
257
|
+
|
|
258
|
+
let apiKeyEncrypted = null;
|
|
259
|
+
let apiKeyIv = null;
|
|
260
|
+
|
|
261
|
+
if (data.apiKey) {
|
|
262
|
+
const encrypted = await encryptApiKey(data.apiKey);
|
|
263
|
+
apiKeyEncrypted = encrypted.encrypted;
|
|
264
|
+
apiKeyIv = encrypted.iv;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// 1️⃣ Primero: Actualizar provider global con API key del usuario
|
|
268
|
+
db.query(`
|
|
269
|
+
UPDATE providers SET
|
|
270
|
+
api_key_encrypted = ?,
|
|
271
|
+
api_key_iv = ?,
|
|
272
|
+
base_url = ?,
|
|
273
|
+
enabled = 1,
|
|
274
|
+
active = 1
|
|
275
|
+
WHERE id = ?
|
|
276
|
+
`).run(apiKeyEncrypted, apiKeyIv, data.baseUrl || null, data.provider);
|
|
277
|
+
|
|
278
|
+
log.info("✅ Provider actualizado:", { provider: data.provider });
|
|
279
|
+
|
|
280
|
+
// 2️⃣ Segundo: Activar el modelo seleccionado
|
|
281
|
+
// For Ollama, models are inserted dynamically (not seeded), ensure row exists first
|
|
282
|
+
if (data.provider === "ollama" && data.model) {
|
|
283
|
+
db.query(`
|
|
284
|
+
INSERT OR IGNORE INTO models(id, name, provider_id, model_type, enabled, active)
|
|
285
|
+
VALUES(?, ?, 'ollama', 'llm', 1, 1)
|
|
286
|
+
`).run(data.model, data.model);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
db.query(`
|
|
290
|
+
UPDATE models SET enabled = 1, active = 1
|
|
291
|
+
WHERE id = ?
|
|
292
|
+
`).run(data.model);
|
|
293
|
+
|
|
294
|
+
log.info("✅ Model activado:", { model: data.model });
|
|
295
|
+
} catch (e) {
|
|
296
|
+
log.error("⚠️ Error saving provider:", { error: (e as Error).message });
|
|
297
|
+
throw e;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export function activateCodeBridge(userId: string, codeBridgeConfig: { id: string; enabled: boolean; port?: number }[]): void {
|
|
302
|
+
try {
|
|
303
|
+
const db = getDb();
|
|
304
|
+
// 7️⃣ Séptimo: Configurar Code Bridge CLIs seleccionados
|
|
305
|
+
for (const cb of codeBridgeConfig) {
|
|
306
|
+
db.query(`
|
|
307
|
+
UPDATE code_bridge SET enabled = ?, active = ?, port = ?, user_id = ?
|
|
308
|
+
WHERE id = ?
|
|
309
|
+
`).run(cb.enabled ? 1 : 0, cb.enabled ? 1 : 0, cb.port || 18791, userId, cb.id);
|
|
310
|
+
}
|
|
311
|
+
log.info("✅ Code Bridge configurado:", { codeBridgeIds: codeBridgeConfig.map(c => c.id).join(", ") });
|
|
312
|
+
} catch (e) {
|
|
313
|
+
log.error("⚠️ Error configuring code bridge:", { error: (e as Error).message });
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function activateMcpServers(userId: string, mcpIds: string[]): void {
|
|
318
|
+
try {
|
|
319
|
+
const db = getDb();
|
|
320
|
+
// Activar MCP servers seleccionados
|
|
321
|
+
for (const mcpId of mcpIds) {
|
|
322
|
+
db.query(`UPDATE mcp_servers SET active = 1, enabled = 1 WHERE id = ? `).run(mcpId);
|
|
323
|
+
}
|
|
324
|
+
log.info("✅ MCP servers activados:", { mcpIds: mcpIds.join(", ") });
|
|
325
|
+
} catch (e) {
|
|
326
|
+
log.error("⚠️ Error activating MCP servers:", { error: (e as Error).message });
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
export function saveAgentConfig(data: {
|
|
332
|
+
userId: string;
|
|
333
|
+
agentId?: string;
|
|
334
|
+
agentName: string;
|
|
335
|
+
providerId: string;
|
|
336
|
+
modelId: string;
|
|
337
|
+
tone: string;
|
|
338
|
+
description?: string;
|
|
339
|
+
}): string {
|
|
340
|
+
try {
|
|
341
|
+
const db = getDb();
|
|
342
|
+
let finalAgentId = data.agentId;
|
|
343
|
+
|
|
344
|
+
// Validate FK references — use null if the referenced row doesn't exist
|
|
345
|
+
// (e.g. custom Ollama model IDs are not in the seed models table)
|
|
346
|
+
const rawProviderId = data.providerId || null;
|
|
347
|
+
const rawModelId = data.modelId || null;
|
|
348
|
+
const safeProviderId = rawProviderId && db.query("SELECT id FROM providers WHERE id = ?").get(rawProviderId) ? rawProviderId : null;
|
|
349
|
+
const safeModelId = rawModelId && db.query("SELECT id FROM models WHERE id = ?").get(rawModelId) ? rawModelId : null;
|
|
350
|
+
|
|
351
|
+
// Si no se pasa agentId, dejar que SQLite lo genere automáticamente
|
|
352
|
+
if (!finalAgentId) {
|
|
353
|
+
const result = db.query(`
|
|
354
|
+
INSERT INTO agents
|
|
355
|
+
(user_id, name, description, tone, system_prompt, provider_id, model_id, status, role, enabled)
|
|
356
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, 'idle', 'coordinator', 1)
|
|
357
|
+
RETURNING id
|
|
358
|
+
`).get(
|
|
359
|
+
data.userId,
|
|
360
|
+
data.agentName,
|
|
361
|
+
data.description || null,
|
|
362
|
+
data.tone,
|
|
363
|
+
HIVE_SYSTEM_PROMPT,
|
|
364
|
+
safeProviderId,
|
|
365
|
+
safeModelId
|
|
366
|
+
) as { id: string };
|
|
367
|
+
finalAgentId = result.id;
|
|
368
|
+
log.info("✅ Agent created with auto-generated ID", { agentId: finalAgentId });
|
|
369
|
+
} else {
|
|
370
|
+
// INSERT or UPDATE agent (crea nuevo o actualiza existente)
|
|
371
|
+
db.query(`
|
|
372
|
+
INSERT INTO agents
|
|
373
|
+
(id, user_id, name, description, tone, system_prompt, provider_id, model_id, status, role, enabled)
|
|
374
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, 'idle', 'coordinator', 1)
|
|
375
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
376
|
+
user_id = COALESCE(excluded.user_id, user_id),
|
|
377
|
+
name = COALESCE(excluded.name, name),
|
|
378
|
+
description = COALESCE(excluded.description, description),
|
|
379
|
+
tone = COALESCE(excluded.tone, tone),
|
|
380
|
+
system_prompt = excluded.system_prompt,
|
|
381
|
+
provider_id = COALESCE(excluded.provider_id, provider_id),
|
|
382
|
+
model_id = COALESCE(excluded.model_id, model_id),
|
|
383
|
+
status = 'idle',
|
|
384
|
+
enabled = 1,
|
|
385
|
+
role = 'coordinator'
|
|
386
|
+
`).run(
|
|
387
|
+
data.agentId,
|
|
388
|
+
data.userId,
|
|
389
|
+
data.agentName,
|
|
390
|
+
data.description || null,
|
|
391
|
+
data.tone,
|
|
392
|
+
HIVE_SYSTEM_PROMPT,
|
|
393
|
+
safeProviderId,
|
|
394
|
+
safeModelId
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return finalAgentId;
|
|
399
|
+
} catch (e) {
|
|
400
|
+
log.error("⚠️ Error saving agent:", { error: (e as Error).message });
|
|
401
|
+
throw e;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export async function activateChannel(userId: string, data: {
|
|
406
|
+
channelId: string;
|
|
407
|
+
channelUserId?: string; // For creating user_identity
|
|
408
|
+
config?: Record<string, unknown>;
|
|
409
|
+
}): Promise<void> {
|
|
410
|
+
try {
|
|
411
|
+
const db = getDb();
|
|
412
|
+
|
|
413
|
+
if (data.config && Object.keys(data.config).length > 0) {
|
|
414
|
+
const encrypted = await encryptConfig(data.config);
|
|
415
|
+
db.query(`
|
|
416
|
+
UPDATE channels
|
|
417
|
+
SET user_id = ?, active = 1, enabled = 1, status = 'connected',
|
|
418
|
+
config_encrypted = ?, config_iv = ?
|
|
419
|
+
WHERE id = ?
|
|
420
|
+
`).run(userId, encrypted.encrypted, encrypted.iv, data.channelId);
|
|
421
|
+
} else {
|
|
422
|
+
db.query(`
|
|
423
|
+
UPDATE channels
|
|
424
|
+
SET user_id = ?, active = 1, enabled = 1, status = 'connected'
|
|
425
|
+
WHERE id = ?
|
|
426
|
+
`).run(userId, data.channelId);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Create user_identity for the channel if channelUserId provided
|
|
430
|
+
if (data.channelUserId) {
|
|
431
|
+
const channelType = data.channelId; // webchat, telegram, discord, etc.
|
|
432
|
+
db.query(`
|
|
433
|
+
INSERT OR REPLACE INTO user_identities(user_id, channel, channel_user_id)
|
|
434
|
+
VALUES(?, ?, ?)
|
|
435
|
+
`).run(userId, channelType, data.channelUserId);
|
|
436
|
+
log.info("✅ User identity created", { userId, channel: channelType });
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
log.info("✅ Channel activated:", { channelId: data.channelId, userId });
|
|
440
|
+
} catch (e) {
|
|
441
|
+
log.error("⚠️ Error activating channel:", { error: (e as Error).message });
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export async function saveVoiceConfig(data: {
|
|
446
|
+
userId: string;
|
|
447
|
+
channelId: string;
|
|
448
|
+
voiceEnabled: boolean;
|
|
449
|
+
sttProvider: string;
|
|
450
|
+
ttsProvider: string;
|
|
451
|
+
sttApiKey?: string;
|
|
452
|
+
ttsApiKey?: string;
|
|
453
|
+
}): Promise<void> {
|
|
454
|
+
try {
|
|
455
|
+
const db = getDb();
|
|
456
|
+
|
|
457
|
+
// Activate STT and TTS models
|
|
458
|
+
db.query(`UPDATE models SET active = 1, enabled = 1 WHERE id = ? `).run(data.sttProvider);
|
|
459
|
+
db.query(`UPDATE models SET active = 1, enabled = 1 WHERE id = ? `).run(data.ttsProvider);
|
|
460
|
+
|
|
461
|
+
// Determine provider IDs based on model IDs
|
|
462
|
+
let sttProviderId = "";
|
|
463
|
+
let ttsProviderId = "";
|
|
464
|
+
|
|
465
|
+
if (data.sttProvider.startsWith("whisper") || data.sttProvider === "distil-whisper-large-v3-en") {
|
|
466
|
+
sttProviderId = "groq";
|
|
467
|
+
} else if (data.sttProvider === "whisper-1") {
|
|
468
|
+
sttProviderId = "openai";
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (data.ttsProvider.startsWith("eleven")) {
|
|
472
|
+
ttsProviderId = "elevenlabs";
|
|
473
|
+
} else if (data.ttsProvider.startsWith("tts-") || data.ttsProvider.startsWith("gpt-")) {
|
|
474
|
+
ttsProviderId = "openai";
|
|
475
|
+
} else if (data.ttsProvider.startsWith("gemini")) {
|
|
476
|
+
ttsProviderId = "gemini";
|
|
477
|
+
} else if (data.ttsProvider.startsWith("qwen")) {
|
|
478
|
+
ttsProviderId = "qwen";
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Save STT API key to provider if provided
|
|
482
|
+
if (data.sttApiKey && sttProviderId) {
|
|
483
|
+
const encrypted = await encryptApiKey(data.sttApiKey);
|
|
484
|
+
db.query(`
|
|
485
|
+
UPDATE providers SET
|
|
486
|
+
api_key_encrypted = ?,
|
|
487
|
+
api_key_iv = ?,
|
|
488
|
+
enabled = 1,
|
|
489
|
+
active = 1
|
|
490
|
+
WHERE id = ?
|
|
491
|
+
`).run(encrypted.encrypted, encrypted.iv, sttProviderId);
|
|
492
|
+
log.info("✅ STT API key guardada en BD (encriptada)", { provider: sttProviderId });
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Save TTS API key to provider if provided
|
|
496
|
+
if (data.ttsApiKey && ttsProviderId) {
|
|
497
|
+
const encrypted = await encryptApiKey(data.ttsApiKey);
|
|
498
|
+
db.query(`
|
|
499
|
+
UPDATE providers SET
|
|
500
|
+
api_key_encrypted = ?,
|
|
501
|
+
api_key_iv = ?,
|
|
502
|
+
enabled = 1,
|
|
503
|
+
active = 1
|
|
504
|
+
WHERE id = ?
|
|
505
|
+
`).run(encrypted.encrypted, encrypted.iv, ttsProviderId);
|
|
506
|
+
log.info("✅ TTS API key guardada en BD (encriptada)", { provider: ttsProviderId });
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Update channel with voice config
|
|
510
|
+
db.query(`
|
|
511
|
+
UPDATE channels
|
|
512
|
+
SET user_id = ?, voice_enabled = ?, stt_provider = ?, tts_provider = ?
|
|
513
|
+
WHERE id = ?
|
|
514
|
+
`).run(data.userId, data.voiceEnabled ? 1 : 0, data.sttProvider, data.ttsProvider, data.channelId);
|
|
515
|
+
|
|
516
|
+
log.info("✅ Voice config saved:", {
|
|
517
|
+
channelId: data.channelId,
|
|
518
|
+
userId: data.userId,
|
|
519
|
+
sttProvider: data.sttProvider,
|
|
520
|
+
ttsProvider: data.ttsProvider,
|
|
521
|
+
sttProviderId,
|
|
522
|
+
ttsProviderId
|
|
523
|
+
});
|
|
524
|
+
} catch (e) {
|
|
525
|
+
log.error("⚠️ Error saving voice config:", { error: (e as Error).message });
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
export async function saveMcpServer(data: {
|
|
530
|
+
userId: string;
|
|
531
|
+
name: string;
|
|
532
|
+
transport: string;
|
|
533
|
+
command?: string;
|
|
534
|
+
args?: string[];
|
|
535
|
+
env?: Record<string, string>;
|
|
536
|
+
url?: string;
|
|
537
|
+
enabled?: boolean;
|
|
538
|
+
}): Promise<void> {
|
|
539
|
+
try {
|
|
540
|
+
const db = getDb();
|
|
541
|
+
|
|
542
|
+
const mcpId = `${data.userId}:${data.name} `;
|
|
543
|
+
|
|
544
|
+
let envEncrypted = null;
|
|
545
|
+
let envIv = null;
|
|
546
|
+
|
|
547
|
+
if (data.env && Object.keys(data.env).length > 0) {
|
|
548
|
+
const encrypted = await encryptConfig(data.env as Record<string, unknown>);
|
|
549
|
+
envEncrypted = encrypted.encrypted;
|
|
550
|
+
envIv = encrypted.iv;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
db.query(`
|
|
554
|
+
INSERT OR REPLACE INTO mcp_servers
|
|
555
|
+
(id, user_id, name, transport, command, args, env_encrypted, env_iv, url, enabled, builtin)
|
|
556
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0)
|
|
557
|
+
`).run(
|
|
558
|
+
mcpId,
|
|
559
|
+
data.userId,
|
|
560
|
+
data.name,
|
|
561
|
+
data.transport,
|
|
562
|
+
data.command || null,
|
|
563
|
+
JSON.stringify(data.args || []),
|
|
564
|
+
envEncrypted,
|
|
565
|
+
envIv,
|
|
566
|
+
data.url || null,
|
|
567
|
+
data.enabled ? 1 : 0
|
|
568
|
+
);
|
|
569
|
+
|
|
570
|
+
log.info("✅ MCP server saved:", { name: data.name });
|
|
571
|
+
} catch (e) {
|
|
572
|
+
log.error("⚠️ Error saving MCP server:", { error: (e as Error).message });
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
export function saveToolSelection(userId: string, tools: string[]): void {
|
|
577
|
+
try {
|
|
578
|
+
const db = getDb();
|
|
579
|
+
|
|
580
|
+
for (const tool of tools) {
|
|
581
|
+
// Activar la herramienta (ya existe del seed)
|
|
582
|
+
db.query(`
|
|
583
|
+
UPDATE tools SET active = 1, enabled = 1
|
|
584
|
+
WHERE id = ?
|
|
585
|
+
`).run(tool);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
log.info("✅ Tools activadas:", { tools: tools.join(", ") });
|
|
589
|
+
} catch (e) {
|
|
590
|
+
log.error("⚠️ Error saving tools:", { error: (e as Error).message });
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
export function activateProvider(providerId: string): void {
|
|
595
|
+
try {
|
|
596
|
+
const db = getDb();
|
|
597
|
+
db.query(`
|
|
598
|
+
UPDATE providers SET active = 1, enabled = 1
|
|
599
|
+
WHERE id = ?
|
|
600
|
+
`).run(providerId);
|
|
601
|
+
log.info("✅ Provider activado:", { providerId });
|
|
602
|
+
} catch (e) {
|
|
603
|
+
log.error("⚠️ Error activating provider:", { error: (e as Error).message });
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
export function activateModel(modelId: string): void {
|
|
608
|
+
try {
|
|
609
|
+
const db = getDb();
|
|
610
|
+
db.query(`
|
|
611
|
+
UPDATE models SET active = 1, enabled = 1
|
|
612
|
+
WHERE id = ?
|
|
613
|
+
`).run(modelId);
|
|
614
|
+
log.info("✅ Model activado:", { modelId });
|
|
615
|
+
} catch (e) {
|
|
616
|
+
log.error("⚠️ Error activating model:", { error: (e as Error).message });
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
export function activateMcpServer(mcpName: string): void {
|
|
623
|
+
try {
|
|
624
|
+
const db = getDb();
|
|
625
|
+
db.query(`
|
|
626
|
+
UPDATE mcp_servers SET active = 1, enabled = 1
|
|
627
|
+
WHERE id = ?
|
|
628
|
+
`).run(mcpName);
|
|
629
|
+
log.info("✅ MCP server activado:", { mcpName });
|
|
630
|
+
} catch (e) {
|
|
631
|
+
log.error("⚠️ Error activating MCP server:", { error: (e as Error).message });
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
export function deactivateProvider(providerId: string): void {
|
|
636
|
+
try {
|
|
637
|
+
const db = getDb();
|
|
638
|
+
db.query(`
|
|
639
|
+
UPDATE providers SET active = 0, enabled = 0
|
|
640
|
+
WHERE id = ?
|
|
641
|
+
`).run(providerId);
|
|
642
|
+
log.warn("⚠️ Provider desactivado:", { providerId });
|
|
643
|
+
} catch (e) {
|
|
644
|
+
log.error("⚠️ Error deactivating provider:", { error: (e as Error).message });
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
export function deactivateModel(modelId: string): void {
|
|
649
|
+
try {
|
|
650
|
+
const db = getDb();
|
|
651
|
+
db.query(`
|
|
652
|
+
UPDATE models SET active = 0, enabled = 0
|
|
653
|
+
WHERE id = ?
|
|
654
|
+
`).run(modelId);
|
|
655
|
+
log.warn("⚠️ Model desactivado:", { modelId });
|
|
656
|
+
} catch (e) {
|
|
657
|
+
log.error("⚠️ Error deactivating model:", { error: (e as Error).message });
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
export function deactivateChannel(channelType: string): void {
|
|
662
|
+
try {
|
|
663
|
+
const db = getDb();
|
|
664
|
+
db.query(`
|
|
665
|
+
UPDATE channels SET active = 0, enabled = 0
|
|
666
|
+
WHERE id = ?
|
|
667
|
+
`).run(channelType);
|
|
668
|
+
log.warn("⚠️ Channel desactivado:", { channelType });
|
|
669
|
+
} catch (e) {
|
|
670
|
+
log.error("⚠️ Error deactivating channel:", { error: (e as Error).message });
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
export function deactivateMcpServer(mcpName: string): void {
|
|
675
|
+
try {
|
|
676
|
+
const db = getDb();
|
|
677
|
+
db.query(`
|
|
678
|
+
UPDATE mcp_servers SET active = 0, enabled = 0
|
|
679
|
+
WHERE id = ?
|
|
680
|
+
`).run(mcpName);
|
|
681
|
+
log.warn("⚠️ MCP server desactivado:", { mcpName });
|
|
682
|
+
} catch (e) {
|
|
683
|
+
log.error("⚠️ Error deactivating MCP server:", { error: (e as Error).message });
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
export function getAllProviders(): Array<{
|
|
688
|
+
id: string;
|
|
689
|
+
name: string;
|
|
690
|
+
baseUrl: string | null;
|
|
691
|
+
enabled: boolean;
|
|
692
|
+
active: boolean;
|
|
693
|
+
}> {
|
|
694
|
+
try {
|
|
695
|
+
const db = getDb();
|
|
696
|
+
const results = db.query(`
|
|
697
|
+
SELECT id, name, base_url, enabled, active
|
|
698
|
+
FROM providers
|
|
699
|
+
`).all() as Array<{
|
|
700
|
+
id: string;
|
|
701
|
+
name: string;
|
|
702
|
+
base_url: string | null;
|
|
703
|
+
enabled: number;
|
|
704
|
+
active: number;
|
|
705
|
+
}>;
|
|
706
|
+
|
|
707
|
+
return results.map(r => ({
|
|
708
|
+
id: r.id,
|
|
709
|
+
name: r.name,
|
|
710
|
+
baseUrl: r.base_url,
|
|
711
|
+
enabled: r.enabled === 1,
|
|
712
|
+
active: r.active === 1,
|
|
713
|
+
}));
|
|
714
|
+
} catch (e) {
|
|
715
|
+
log.warn("[onboarding] ⚠️ Error getting providers:", (e as Error).message);
|
|
716
|
+
return [];
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
export function getAllModels(): Array<{
|
|
721
|
+
id: string;
|
|
722
|
+
name: string;
|
|
723
|
+
providerId: string;
|
|
724
|
+
contextWindow: number | null;
|
|
725
|
+
capabilities: string | null;
|
|
726
|
+
enabled: boolean;
|
|
727
|
+
active: boolean;
|
|
728
|
+
}> {
|
|
729
|
+
try {
|
|
730
|
+
const db = getDb();
|
|
731
|
+
const results = db.query(`
|
|
732
|
+
SELECT id, name, provider_id, context_window, capabilities, enabled, active
|
|
733
|
+
FROM models
|
|
734
|
+
`).all() as Array<{
|
|
735
|
+
id: string;
|
|
736
|
+
name: string;
|
|
737
|
+
provider_id: string;
|
|
738
|
+
context_window: number | null;
|
|
739
|
+
capabilities: string | null;
|
|
740
|
+
enabled: number;
|
|
741
|
+
active: number;
|
|
742
|
+
}>;
|
|
743
|
+
|
|
744
|
+
return results.map(r => ({
|
|
745
|
+
id: r.id,
|
|
746
|
+
name: r.name,
|
|
747
|
+
providerId: r.provider_id,
|
|
748
|
+
contextWindow: r.context_window,
|
|
749
|
+
capabilities: r.capabilities,
|
|
750
|
+
enabled: r.enabled === 1,
|
|
751
|
+
active: r.active === 1,
|
|
752
|
+
}));
|
|
753
|
+
} catch (e) {
|
|
754
|
+
log.error("⚠️ Error getting models:", { error: (e as Error).message });
|
|
755
|
+
return [];
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
export function getAllEthics(): Array<{
|
|
760
|
+
id: string;
|
|
761
|
+
name: string;
|
|
762
|
+
description: string | null;
|
|
763
|
+
content: string;
|
|
764
|
+
isDefault: boolean;
|
|
765
|
+
active: boolean;
|
|
766
|
+
}> {
|
|
767
|
+
try {
|
|
768
|
+
const db = getDb();
|
|
769
|
+
const results = db.query(`
|
|
770
|
+
SELECT id, name, description, content, is_default, active
|
|
771
|
+
FROM ethics
|
|
772
|
+
`).all() as Array<{
|
|
773
|
+
id: string;
|
|
774
|
+
name: string;
|
|
775
|
+
description: string | null;
|
|
776
|
+
content: string;
|
|
777
|
+
is_default: number;
|
|
778
|
+
active: number;
|
|
779
|
+
}>;
|
|
780
|
+
|
|
781
|
+
return results.map(r => ({
|
|
782
|
+
id: r.id,
|
|
783
|
+
name: r.name,
|
|
784
|
+
description: r.description,
|
|
785
|
+
content: r.content,
|
|
786
|
+
isDefault: r.is_default === 1,
|
|
787
|
+
active: r.active === 1,
|
|
788
|
+
}));
|
|
789
|
+
} catch (e) {
|
|
790
|
+
log.error("⚠️ Error getting ethics:", { error: (e as Error).message });
|
|
791
|
+
return [];
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
export function getAllCodeBridge(): Array<{
|
|
796
|
+
id: string;
|
|
797
|
+
name: string;
|
|
798
|
+
cliCommand: string;
|
|
799
|
+
port: number;
|
|
800
|
+
enabled: boolean;
|
|
801
|
+
active: boolean;
|
|
802
|
+
}> {
|
|
803
|
+
try {
|
|
804
|
+
const db = getDb();
|
|
805
|
+
const results = db.query(`
|
|
806
|
+
SELECT id, name, cli_command, port, enabled, active
|
|
807
|
+
FROM code_bridge
|
|
808
|
+
`).all() as Array<{
|
|
809
|
+
id: string;
|
|
810
|
+
name: string;
|
|
811
|
+
cli_command: string;
|
|
812
|
+
port: number;
|
|
813
|
+
enabled: number;
|
|
814
|
+
active: number;
|
|
815
|
+
}>;
|
|
816
|
+
|
|
817
|
+
return results.map(r => ({
|
|
818
|
+
id: r.id,
|
|
819
|
+
name: r.name,
|
|
820
|
+
cliCommand: r.cli_command,
|
|
821
|
+
port: r.port,
|
|
822
|
+
enabled: r.enabled === 1,
|
|
823
|
+
active: r.active === 1,
|
|
824
|
+
}));
|
|
825
|
+
} catch (e) {
|
|
826
|
+
log.error("⚠️ Error getting code bridge:", { error: (e as Error).message });
|
|
827
|
+
return [];
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
export function getAllSkills(): Array<{
|
|
832
|
+
id: string;
|
|
833
|
+
name: string;
|
|
834
|
+
description: string | null;
|
|
835
|
+
source: string;
|
|
836
|
+
isGlobal: boolean;
|
|
837
|
+
enabled: boolean;
|
|
838
|
+
active: boolean;
|
|
839
|
+
}> {
|
|
840
|
+
try {
|
|
841
|
+
const db = getDb();
|
|
842
|
+
const results = db.query(`
|
|
843
|
+
SELECT id, name, api_key_encrypted, api_key_iv, base_url, enabled
|
|
844
|
+
FROM providers
|
|
845
|
+
`).all() as Array<{
|
|
846
|
+
id: string;
|
|
847
|
+
name: string;
|
|
848
|
+
description: string | null;
|
|
849
|
+
source: string;
|
|
850
|
+
enabled: number;
|
|
851
|
+
active: number;
|
|
852
|
+
}>;
|
|
853
|
+
|
|
854
|
+
return results.map(r => ({
|
|
855
|
+
id: r.id,
|
|
856
|
+
name: r.name,
|
|
857
|
+
description: r.description,
|
|
858
|
+
source: r.source,
|
|
859
|
+
isGlobal: false,
|
|
860
|
+
enabled: r.enabled === 1,
|
|
861
|
+
active: r.active === 1,
|
|
862
|
+
}));
|
|
863
|
+
} catch (e) {
|
|
864
|
+
log.error("⚠️ Error getting skills:", { error: (e as Error).message });
|
|
865
|
+
return [];
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
export function getAllDbTools(): Array<{
|
|
870
|
+
id: string;
|
|
871
|
+
name: string;
|
|
872
|
+
description: string | null;
|
|
873
|
+
category: string | null;
|
|
874
|
+
enabled: boolean;
|
|
875
|
+
active: boolean;
|
|
876
|
+
}> {
|
|
877
|
+
try {
|
|
878
|
+
const db = getDb();
|
|
879
|
+
const results = db.query(`
|
|
880
|
+
SELECT id, name, description, category, enabled, active
|
|
881
|
+
FROM tools
|
|
882
|
+
`).all() as Array<{
|
|
883
|
+
id: string;
|
|
884
|
+
name: string;
|
|
885
|
+
description: string | null;
|
|
886
|
+
category: string | null;
|
|
887
|
+
enabled: number;
|
|
888
|
+
active: number;
|
|
889
|
+
}>;
|
|
890
|
+
|
|
891
|
+
return results.map(r => ({
|
|
892
|
+
id: r.id,
|
|
893
|
+
name: r.name,
|
|
894
|
+
description: r.description,
|
|
895
|
+
category: r.category,
|
|
896
|
+
enabled: r.enabled === 1,
|
|
897
|
+
active: r.active === 1,
|
|
898
|
+
}));
|
|
899
|
+
} catch (e) {
|
|
900
|
+
log.error("⚠️ Error getting tools:", { error: (e as Error).message });
|
|
901
|
+
return [];
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
export function getAllMcpServers(): Array<{
|
|
906
|
+
id: string;
|
|
907
|
+
name: string;
|
|
908
|
+
transport: string;
|
|
909
|
+
command: string | null;
|
|
910
|
+
args: string | null;
|
|
911
|
+
url: string | null;
|
|
912
|
+
builtin: boolean;
|
|
913
|
+
enabled: boolean;
|
|
914
|
+
active: boolean;
|
|
915
|
+
}> {
|
|
916
|
+
try {
|
|
917
|
+
const db = getDb();
|
|
918
|
+
const results = db.query(`
|
|
919
|
+
SELECT id, name, transport, command, args, url, builtin, enabled, active
|
|
920
|
+
FROM mcp_servers
|
|
921
|
+
`).all() as Array<{
|
|
922
|
+
id: string;
|
|
923
|
+
name: string;
|
|
924
|
+
transport: string;
|
|
925
|
+
command: string | null;
|
|
926
|
+
args: string | null;
|
|
927
|
+
url: string | null;
|
|
928
|
+
builtin: number;
|
|
929
|
+
enabled: number;
|
|
930
|
+
active: number;
|
|
931
|
+
}>;
|
|
932
|
+
|
|
933
|
+
return results.map(r => ({
|
|
934
|
+
id: r.id,
|
|
935
|
+
name: r.name,
|
|
936
|
+
transport: r.transport,
|
|
937
|
+
command: r.command,
|
|
938
|
+
args: r.args,
|
|
939
|
+
url: r.url,
|
|
940
|
+
builtin: r.builtin === 1,
|
|
941
|
+
enabled: r.enabled === 1,
|
|
942
|
+
active: r.active === 1,
|
|
943
|
+
}));
|
|
944
|
+
} catch (e) {
|
|
945
|
+
log.error("⚠️ Error getting MCP servers:", { error: (e as Error).message });
|
|
946
|
+
return [];
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
export function getAllChannels(): Array<{
|
|
951
|
+
id: string;
|
|
952
|
+
type: string;
|
|
953
|
+
accountId: string;
|
|
954
|
+
status: string;
|
|
955
|
+
enabled: boolean;
|
|
956
|
+
active: boolean;
|
|
957
|
+
}> {
|
|
958
|
+
try {
|
|
959
|
+
const db = getDb();
|
|
960
|
+
const results = db.query(`
|
|
961
|
+
SELECT id, type, id as account_id, status, enabled, active
|
|
962
|
+
FROM channels
|
|
963
|
+
`).all() as Array<{
|
|
964
|
+
id: string;
|
|
965
|
+
type: string;
|
|
966
|
+
account_id: string;
|
|
967
|
+
status: string;
|
|
968
|
+
enabled: number;
|
|
969
|
+
active: number;
|
|
970
|
+
}>;
|
|
971
|
+
|
|
972
|
+
return results.map(r => ({
|
|
973
|
+
id: r.id,
|
|
974
|
+
type: r.type,
|
|
975
|
+
accountId: r.id,
|
|
976
|
+
status: r.status,
|
|
977
|
+
enabled: r.enabled === 1,
|
|
978
|
+
active: r.active === 1,
|
|
979
|
+
}));
|
|
980
|
+
} catch (e) {
|
|
981
|
+
log.warn("[onboarding] ⚠️ Error getting channels:", (e as Error).message);
|
|
982
|
+
return [];
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
export function getActiveTools(): Array<{
|
|
987
|
+
id: string;
|
|
988
|
+
name: string;
|
|
989
|
+
description: string | null;
|
|
990
|
+
category: string | null;
|
|
991
|
+
}> {
|
|
992
|
+
try {
|
|
993
|
+
const db = getDb();
|
|
994
|
+
const results = db.query(`
|
|
995
|
+
SELECT id, name, description, category
|
|
996
|
+
FROM tools WHERE active = 1
|
|
997
|
+
`).all() as Array<{
|
|
998
|
+
id: string;
|
|
999
|
+
name: string;
|
|
1000
|
+
description: string | null;
|
|
1001
|
+
category: string | null;
|
|
1002
|
+
}>;
|
|
1003
|
+
|
|
1004
|
+
return results.map(r => ({
|
|
1005
|
+
id: r.id,
|
|
1006
|
+
name: r.name,
|
|
1007
|
+
description: r.description,
|
|
1008
|
+
category: r.category,
|
|
1009
|
+
}));
|
|
1010
|
+
} catch (e) {
|
|
1011
|
+
log.error("⚠️ Error getting active tools:", { error: (e as Error).message });
|
|
1012
|
+
return [];
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
export function getOnboardingProgress(userId: string): OnboardingSection | null {
|
|
1017
|
+
try {
|
|
1018
|
+
const db = getDb();
|
|
1019
|
+
const result = db.query<{ step: string; data: string }, [string]>(
|
|
1020
|
+
"SELECT step, data FROM onboarding_progress WHERE user_id = ? LIMIT 1"
|
|
1021
|
+
).get(userId);
|
|
1022
|
+
|
|
1023
|
+
if (result) {
|
|
1024
|
+
return {
|
|
1025
|
+
step: result.step as OnboardingSection["step"],
|
|
1026
|
+
userId,
|
|
1027
|
+
data: JSON.parse(result.data),
|
|
1028
|
+
completedAt: Date.now(),
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
return null;
|
|
1032
|
+
} catch {
|
|
1033
|
+
return null;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
export function saveOnboardingProgress(section: OnboardingSection): void {
|
|
1038
|
+
try {
|
|
1039
|
+
const db = getDb();
|
|
1040
|
+
db.query(`
|
|
1041
|
+
INSERT OR REPLACE INTO onboarding_progress(id, user_id, step, data)
|
|
1042
|
+
VALUES(?, ?, ?, ?)
|
|
1043
|
+
`).run(section.userId, section.userId, section.step, JSON.stringify(section.data));
|
|
1044
|
+
} catch (e) {
|
|
1045
|
+
log.error("⚠️ Error saving progress:", { error: (e as Error).message });
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
export async function getUserProviders(userId: string): Promise<Array<{
|
|
1050
|
+
id: string;
|
|
1051
|
+
name: string;
|
|
1052
|
+
apiKey: string | null;
|
|
1053
|
+
baseUrl: string | null;
|
|
1054
|
+
enabled: boolean;
|
|
1055
|
+
}>> {
|
|
1056
|
+
try {
|
|
1057
|
+
const db = getDb();
|
|
1058
|
+
const results = db.query(`
|
|
1059
|
+
SELECT id, name, api_key_encrypted, api_key_iv, base_url, enabled
|
|
1060
|
+
FROM providers
|
|
1061
|
+
`).all() as Array<{
|
|
1062
|
+
id: string;
|
|
1063
|
+
name: string;
|
|
1064
|
+
api_key_encrypted: string | null;
|
|
1065
|
+
api_key_iv: string | null;
|
|
1066
|
+
base_url: string | null;
|
|
1067
|
+
enabled: number;
|
|
1068
|
+
}>;
|
|
1069
|
+
|
|
1070
|
+
return Promise.all(results.map(async r => ({
|
|
1071
|
+
id: r.name,
|
|
1072
|
+
name: r.name,
|
|
1073
|
+
apiKey: r.api_key_encrypted && r.api_key_iv
|
|
1074
|
+
? await decryptApiKey(r.api_key_encrypted, r.api_key_iv)
|
|
1075
|
+
: null,
|
|
1076
|
+
baseUrl: r.base_url,
|
|
1077
|
+
enabled: r.enabled === 1,
|
|
1078
|
+
})));
|
|
1079
|
+
} catch (e) {
|
|
1080
|
+
log.warn("[onboarding] ⚠️ Error getting providers:", (e as Error).message);
|
|
1081
|
+
return [];
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
export async function getUserChannels(userId: string): Promise<Array<{
|
|
1086
|
+
id: string;
|
|
1087
|
+
type: string;
|
|
1088
|
+
accountId: string;
|
|
1089
|
+
config: Record<string, unknown>;
|
|
1090
|
+
enabled: boolean;
|
|
1091
|
+
}>> {
|
|
1092
|
+
try {
|
|
1093
|
+
const db = getDb();
|
|
1094
|
+
const results = db.query<{
|
|
1095
|
+
id: string;
|
|
1096
|
+
type: string;
|
|
1097
|
+
account_id: string;
|
|
1098
|
+
config_encrypted: string | null;
|
|
1099
|
+
config_iv: string | null;
|
|
1100
|
+
enabled: number;
|
|
1101
|
+
}, [string]>(`
|
|
1102
|
+
SELECT id, type, id as account_id, config_encrypted, config_iv, enabled
|
|
1103
|
+
FROM channels WHERE user_id = ?
|
|
1104
|
+
`).all(userId);
|
|
1105
|
+
|
|
1106
|
+
return Promise.all(results.map(async r => ({
|
|
1107
|
+
id: r.type,
|
|
1108
|
+
type: r.type,
|
|
1109
|
+
accountId: r.id,
|
|
1110
|
+
config: r.config_encrypted && r.config_iv
|
|
1111
|
+
? await decryptConfig(r.config_encrypted, r.config_iv)
|
|
1112
|
+
: {},
|
|
1113
|
+
enabled: r.enabled === 1,
|
|
1114
|
+
})));
|
|
1115
|
+
} catch (e) {
|
|
1116
|
+
log.warn("[onboarding] ⚠️ Error getting channels:", (e as Error).message);
|
|
1117
|
+
return [];
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
export function getUserAgents(userId: string): Array<{
|
|
1122
|
+
id: string;
|
|
1123
|
+
name: string;
|
|
1124
|
+
providerId: string | null;
|
|
1125
|
+
modelId: string | null;
|
|
1126
|
+
tone: string;
|
|
1127
|
+
}> {
|
|
1128
|
+
try {
|
|
1129
|
+
const db = getDb();
|
|
1130
|
+
const results = db.query<{
|
|
1131
|
+
id: string;
|
|
1132
|
+
name: string;
|
|
1133
|
+
provider_id: string | null;
|
|
1134
|
+
model_id: string | null;
|
|
1135
|
+
tone: string;
|
|
1136
|
+
}, [string]>(`
|
|
1137
|
+
SELECT id, name, provider_id, model_id, tone
|
|
1138
|
+
FROM agents WHERE user_id = ?
|
|
1139
|
+
`).all(userId);
|
|
1140
|
+
|
|
1141
|
+
return results.map(r => ({
|
|
1142
|
+
id: r.id,
|
|
1143
|
+
name: r.name,
|
|
1144
|
+
providerId: r.provider_id,
|
|
1145
|
+
modelId: r.model_id,
|
|
1146
|
+
tone: r.tone || "friendly",
|
|
1147
|
+
}));
|
|
1148
|
+
} catch (e) {
|
|
1149
|
+
log.error("⚠️ Error getting agents:", { error: (e as Error).message });
|
|
1150
|
+
return [];
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// ─── Identity Resolution Helpers ──────────────────────────────────────────────
|
|
1155
|
+
// These functions resolve userId and agentId from the database instead of environment variables
|
|
1156
|
+
|
|
1157
|
+
/**
|
|
1158
|
+
* Get the single user ID from the database.
|
|
1159
|
+
* Hive is designed around a single-user model, so this returns the first user found.
|
|
1160
|
+
* @returns The user ID or null if no users exist
|
|
1161
|
+
*/
|
|
1162
|
+
export function getSingleUserId(): string | null {
|
|
1163
|
+
try {
|
|
1164
|
+
const db = getDb();
|
|
1165
|
+
const result = db.query("SELECT id FROM users LIMIT 1").get() as { id: string } | undefined;
|
|
1166
|
+
return result?.id || null;
|
|
1167
|
+
} catch (e) {
|
|
1168
|
+
log.warn("[getSingleUserId] ⚠️ Error getting user ID:", (e as Error).message);
|
|
1169
|
+
return null;
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
/**
|
|
1174
|
+
* Get the coordinator agent ID from the database.
|
|
1175
|
+
* The coordinator is the agent with role = 'coordinator'.
|
|
1176
|
+
* @returns The coordinator agent ID or null if not found
|
|
1177
|
+
*/
|
|
1178
|
+
export function getCoordinatorAgentId(): string | null {
|
|
1179
|
+
try {
|
|
1180
|
+
const db = getDb();
|
|
1181
|
+
const result = db.query("SELECT id FROM agents WHERE role = 'coordinator' LIMIT 1").get() as { id: string } | undefined;
|
|
1182
|
+
return result?.id || null;
|
|
1183
|
+
} catch (e) {
|
|
1184
|
+
log.warn("[getCoordinatorAgentId] ⚠️ Error getting coordinator agent ID:", (e as Error).message);
|
|
1185
|
+
return null;
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
/**
|
|
1190
|
+
* Get the user ID associated with a specific channel identity.
|
|
1191
|
+
* @param channel The channel type (e.g., 'webchat', 'telegram', 'discord')
|
|
1192
|
+
* @param channelUserId The channel-specific user ID (e.g., Telegram chat_id)
|
|
1193
|
+
* @returns The Hive user ID or null if not found
|
|
1194
|
+
*/
|
|
1195
|
+
export function getUserIdFromChannelIdentity(channel: string, channelUserId: string): string | null {
|
|
1196
|
+
try {
|
|
1197
|
+
const db = getDb();
|
|
1198
|
+
const result = db.query(
|
|
1199
|
+
"SELECT user_id FROM user_identities WHERE channel = ? AND channel_user_id = ? LIMIT 1"
|
|
1200
|
+
).get(channel, channelUserId) as { user_id: string } | undefined;
|
|
1201
|
+
return result?.user_id || null;
|
|
1202
|
+
} catch (e) {
|
|
1203
|
+
log.warn("[getUserIdFromChannelIdentity] ⚠️ Error getting user ID from channel identity:", (e as Error).message);
|
|
1204
|
+
return null;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
/**
|
|
1209
|
+
* Resolve the user ID from various sources with priority:
|
|
1210
|
+
* 1. Explicit userId parameter
|
|
1211
|
+
* 2. Channel identity lookup (if channel and channelUserId provided)
|
|
1212
|
+
* 3. Single user from database
|
|
1213
|
+
* 4. Null (no user found)
|
|
1214
|
+
*/
|
|
1215
|
+
export function resolveUserId(
|
|
1216
|
+
opts: {
|
|
1217
|
+
userId?: string | null;
|
|
1218
|
+
threadId?: string | null;
|
|
1219
|
+
channel?: string | null;
|
|
1220
|
+
channelUserId?: string | null;
|
|
1221
|
+
}
|
|
1222
|
+
): string | null {
|
|
1223
|
+
// Priority 1: Explicit userId
|
|
1224
|
+
if (opts.userId) {
|
|
1225
|
+
return opts.userId;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
// Priority 2: Channel identity lookup
|
|
1229
|
+
if (opts.channel && opts.channelUserId) {
|
|
1230
|
+
const userId = getUserIdFromChannelIdentity(opts.channel, opts.channelUserId);
|
|
1231
|
+
if (userId) {
|
|
1232
|
+
return userId;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// Priority 3: Single user from database
|
|
1237
|
+
const singleUserId = getSingleUserId();
|
|
1238
|
+
if (singleUserId) {
|
|
1239
|
+
return singleUserId;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
// Priority 4: No user found
|
|
1243
|
+
return null;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
/**
|
|
1247
|
+
* Get the default agent ID with priority:
|
|
1248
|
+
* 1. Coordinator agent (role = 'coordinator')
|
|
1249
|
+
* 2. First enabled agent
|
|
1250
|
+
* 3. Null (no agent found)
|
|
1251
|
+
*/
|
|
1252
|
+
export function getDefaultAgentId(): string | null {
|
|
1253
|
+
try {
|
|
1254
|
+
const db = getDb();
|
|
1255
|
+
|
|
1256
|
+
// Try coordinator first
|
|
1257
|
+
const coordinator = db.query(
|
|
1258
|
+
"SELECT id FROM agents WHERE role = 'coordinator' AND enabled = 1 LIMIT 1"
|
|
1259
|
+
).get() as { id: string } | undefined;
|
|
1260
|
+
|
|
1261
|
+
if (coordinator?.id) {
|
|
1262
|
+
return coordinator.id;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
// Fallback to first enabled agent
|
|
1266
|
+
const firstAgent = db.query(
|
|
1267
|
+
"SELECT id FROM agents WHERE enabled = 1 LIMIT 1"
|
|
1268
|
+
).get() as { id: string } | undefined;
|
|
1269
|
+
|
|
1270
|
+
return firstAgent?.id || null;
|
|
1271
|
+
} catch (e) {
|
|
1272
|
+
log.warn("[getDefaultAgentId] ⚠️ Error getting default agent ID:", (e as Error).message);
|
|
1273
|
+
return null;
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
/**
|
|
1278
|
+
* Resolve the agent ID with priority:
|
|
1279
|
+
* 1. Explicit agentId parameter
|
|
1280
|
+
* 2. Coordinator agent from database
|
|
1281
|
+
* 3. First enabled agent from database
|
|
1282
|
+
* 4. Null (no agent found)
|
|
1283
|
+
*/
|
|
1284
|
+
export function resolveAgentId(agentId?: string | null): string | null {
|
|
1285
|
+
// Priority 1: Explicit agentId
|
|
1286
|
+
if (agentId) {
|
|
1287
|
+
return agentId;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
// Priority 2: Default from database (coordinator or first enabled)
|
|
1291
|
+
return getDefaultAgentId();
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
/**
|
|
1295
|
+
* Get user preferences (notes) for a given user ID
|
|
1296
|
+
*/
|
|
1297
|
+
export function getUserPreferences(userId: string): string | null {
|
|
1298
|
+
try {
|
|
1299
|
+
const db = getDb();
|
|
1300
|
+
const result = db.query("SELECT notes FROM users WHERE id = ?").get(userId) as { notes: string | null } | undefined;
|
|
1301
|
+
return result?.notes || null;
|
|
1302
|
+
} catch (e) {
|
|
1303
|
+
log.warn("[getUserPreferences] ⚠️ Error getting user preferences:", (e as Error).message);
|
|
1304
|
+
return null;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
/**
|
|
1309
|
+
* Get agent configuration by ID
|
|
1310
|
+
*/
|
|
1311
|
+
export function getAgentConfig(agentId: string): {
|
|
1312
|
+
id: string;
|
|
1313
|
+
user_id: string;
|
|
1314
|
+
name: string;
|
|
1315
|
+
description: string | null;
|
|
1316
|
+
system_prompt: string | null;
|
|
1317
|
+
tone: string | null;
|
|
1318
|
+
provider_id: string | null;
|
|
1319
|
+
model_id: string | null;
|
|
1320
|
+
tools_json: string | null;
|
|
1321
|
+
skills_json: string | null;
|
|
1322
|
+
max_iterations: number;
|
|
1323
|
+
} | null {
|
|
1324
|
+
try {
|
|
1325
|
+
const db = getDb();
|
|
1326
|
+
const result = db.query(`
|
|
1327
|
+
SELECT id, user_id, name, description, system_prompt, tone,
|
|
1328
|
+
provider_id, model_id, tools_json, skills_json, max_iterations
|
|
1329
|
+
FROM agents WHERE id = ?
|
|
1330
|
+
`).get(agentId) as {
|
|
1331
|
+
id: string;
|
|
1332
|
+
user_id: string;
|
|
1333
|
+
name: string;
|
|
1334
|
+
description: string | null;
|
|
1335
|
+
system_prompt: string | null;
|
|
1336
|
+
tone: string | null;
|
|
1337
|
+
provider_id: string | null;
|
|
1338
|
+
model_id: string | null;
|
|
1339
|
+
tools_json: string | null;
|
|
1340
|
+
skills_json: string | null;
|
|
1341
|
+
max_iterations: number;
|
|
1342
|
+
} | undefined;
|
|
1343
|
+
|
|
1344
|
+
return result || null;
|
|
1345
|
+
} catch (e) {
|
|
1346
|
+
log.warn("[getAgentConfig] ⚠️ Error getting agent config:", (e as Error).message);
|
|
1347
|
+
return null;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
/**
|
|
1352
|
+
* Idempotent startup migrations. Runs on every gateway start.
|
|
1353
|
+
* Each migration is guarded by the schema_migrations table — once applied, it never re-runs.
|
|
1354
|
+
*/
|
|
1355
|
+
export function runStartupMigrations(): void {
|
|
1356
|
+
try {
|
|
1357
|
+
const db = getDb();
|
|
1358
|
+
|
|
1359
|
+
const applied = (v: string) =>
|
|
1360
|
+
!!db.query("SELECT 1 FROM schema_migrations WHERE version = ?").get(v);
|
|
1361
|
+
const markApplied = (v: string) =>
|
|
1362
|
+
db.query("INSERT OR IGNORE INTO schema_migrations(version) VALUES(?)").run(v);
|
|
1363
|
+
|
|
1364
|
+
// v0.0.29 — consolidate tools + skills: drop and recreate tables with current schema, reseed
|
|
1365
|
+
if (!applied("v0.0.29")) {
|
|
1366
|
+
const db = getDb();
|
|
1367
|
+
log.info("[migration v0.0.29] Dropping and recreating tools + skills tables...");
|
|
1368
|
+
|
|
1369
|
+
db.run("DROP TABLE IF EXISTS skills_fts");
|
|
1370
|
+
db.run("DROP TABLE IF EXISTS skills");
|
|
1371
|
+
db.run("DROP TABLE IF EXISTS tools_fts");
|
|
1372
|
+
db.run("DROP TABLE IF EXISTS tools");
|
|
1373
|
+
|
|
1374
|
+
db.run(`CREATE TABLE tools (
|
|
1375
|
+
id TEXT PRIMARY KEY,
|
|
1376
|
+
name TEXT NOT NULL UNIQUE,
|
|
1377
|
+
description TEXT,
|
|
1378
|
+
category TEXT,
|
|
1379
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
1380
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
1381
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
1382
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
1383
|
+
)`);
|
|
1384
|
+
|
|
1385
|
+
db.run(`CREATE VIRTUAL TABLE tools_fts USING fts5(tool_name, name, description, category)`);
|
|
1386
|
+
|
|
1387
|
+
db.run(`CREATE TABLE skills (
|
|
1388
|
+
id TEXT PRIMARY KEY,
|
|
1389
|
+
name TEXT NOT NULL,
|
|
1390
|
+
description TEXT,
|
|
1391
|
+
version TEXT DEFAULT '0.0.1',
|
|
1392
|
+
author TEXT DEFAULT 'Anonymous',
|
|
1393
|
+
icon TEXT DEFAULT '🧩',
|
|
1394
|
+
category TEXT NOT NULL,
|
|
1395
|
+
permissions TEXT,
|
|
1396
|
+
dependencies TEXT,
|
|
1397
|
+
tools TEXT NOT NULL,
|
|
1398
|
+
triggers TEXT NOT NULL,
|
|
1399
|
+
preferred_agents TEXT,
|
|
1400
|
+
body TEXT NOT NULL,
|
|
1401
|
+
version_num INTEGER DEFAULT 1,
|
|
1402
|
+
active INTEGER DEFAULT 1,
|
|
1403
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
1404
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
1405
|
+
)`);
|
|
1406
|
+
|
|
1407
|
+
db.run(`CREATE VIRTUAL TABLE skills_fts USING fts5(id, name, description, category, tools, triggers, body)`);
|
|
1408
|
+
|
|
1409
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_skills_category ON skills(category)");
|
|
1410
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_skills_active ON skills(active)");
|
|
1411
|
+
|
|
1412
|
+
db.run(`DROP TRIGGER IF EXISTS skills_ai`);
|
|
1413
|
+
db.run(`DROP TRIGGER IF EXISTS skills_au`);
|
|
1414
|
+
db.run(`DROP TRIGGER IF EXISTS skills_ad`);
|
|
1415
|
+
db.run(`CREATE TRIGGER skills_ai AFTER INSERT ON skills BEGIN
|
|
1416
|
+
INSERT INTO skills_fts(id, name, description, category, tools, triggers, body)
|
|
1417
|
+
VALUES (new.id, new.name, new.description, new.category, new.tools, new.triggers, new.body);
|
|
1418
|
+
END`);
|
|
1419
|
+
db.run(`CREATE TRIGGER skills_au AFTER UPDATE ON skills BEGIN
|
|
1420
|
+
DELETE FROM skills_fts WHERE id = old.id;
|
|
1421
|
+
INSERT INTO skills_fts(id, name, description, category, tools, triggers, body)
|
|
1422
|
+
VALUES (new.id, new.name, new.description, new.category, new.tools, new.triggers, new.body);
|
|
1423
|
+
END`);
|
|
1424
|
+
db.run(`CREATE TRIGGER skills_ad AFTER DELETE ON skills BEGIN
|
|
1425
|
+
DELETE FROM skills_fts WHERE id = old.id;
|
|
1426
|
+
END`);
|
|
1427
|
+
|
|
1428
|
+
// Reseed tools
|
|
1429
|
+
const insertToolFts = db.prepare(`INSERT OR REPLACE INTO tools_fts(tool_name, name, description, category) VALUES (?, ?, ?, ?)`);
|
|
1430
|
+
let toolCount = 0;
|
|
1431
|
+
for (const tool of SEED_DATA.tools) {
|
|
1432
|
+
db.query(`INSERT INTO tools (id, name, description, category, enabled, active, created_at, updated_at) VALUES (?, ?, ?, ?, 1, 1, (unixepoch()), (unixepoch()))`)
|
|
1433
|
+
.run(tool.id, tool.name, tool.description, tool.category);
|
|
1434
|
+
insertToolFts.run(tool.name, tool.name, tool.description, tool.category);
|
|
1435
|
+
toolCount++;
|
|
1436
|
+
}
|
|
1437
|
+
log.info(`[migration v0.0.29] ✅ ${toolCount} tools re-seeded`);
|
|
1438
|
+
|
|
1439
|
+
// Reseed skills from SkillLoader
|
|
1440
|
+
const skillLoader = new SkillLoader({ workspacePath: process.env.HIVE_HOME || process.cwd() });
|
|
1441
|
+
const bundledSkills = skillLoader.loadBundledSkills();
|
|
1442
|
+
log.info(`[migration v0.0.29] 📚 SkillLoader loaded ${bundledSkills.length} bundled skills`);
|
|
1443
|
+
let skillCount = 0;
|
|
1444
|
+
for (const s of bundledSkills) {
|
|
1445
|
+
db.query(`
|
|
1446
|
+
INSERT OR REPLACE INTO skills (
|
|
1447
|
+
id, name, description, version, author, icon, category,
|
|
1448
|
+
permissions, dependencies, tools, triggers, preferred_agents,
|
|
1449
|
+
body, version_num, active, created_at, updated_at
|
|
1450
|
+
)
|
|
1451
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, (unixepoch()), (unixepoch()))
|
|
1452
|
+
`).run(
|
|
1453
|
+
s.name, s.name, s.description || "",
|
|
1454
|
+
typeof s.version === "string" ? s.version : String(s.version || "0.0.1"),
|
|
1455
|
+
s.author || "Anonymous",
|
|
1456
|
+
s.icon || "🧩",
|
|
1457
|
+
s.category || "general",
|
|
1458
|
+
JSON.stringify(s.permissions || []),
|
|
1459
|
+
JSON.stringify(s.dependencies || []),
|
|
1460
|
+
(s.tools || []).join(","),
|
|
1461
|
+
(s.triggers || []).join(","),
|
|
1462
|
+
JSON.stringify(s.preferred_agents || []),
|
|
1463
|
+
s.content || "",
|
|
1464
|
+
parseInt(String(s.version || "0.0.1").split(".")[0]) || 1
|
|
1465
|
+
);
|
|
1466
|
+
skillCount++;
|
|
1467
|
+
}
|
|
1468
|
+
log.info(`[migration v0.0.29] ✅ ${skillCount} skills re-seeded (FTS5 auto-synced via triggers)`);
|
|
1469
|
+
|
|
1470
|
+
markApplied("v0.0.29");
|
|
1471
|
+
log.info("✅ Migration v0.0.29: tools + skills consolidated, dropped and recreated");
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
// v0.0.30 — add NVIDIA NIM provider + 12 free models (without dropping existing data)
|
|
1475
|
+
if (!applied("v0.0.30")) {
|
|
1476
|
+
const db = getDb();
|
|
1477
|
+
log.info("[migration v0.0.30] Ensuring providers table exists...");
|
|
1478
|
+
db.run(`CREATE TABLE IF NOT EXISTS providers (
|
|
1479
|
+
id TEXT PRIMARY KEY,
|
|
1480
|
+
name TEXT NOT NULL UNIQUE,
|
|
1481
|
+
api_key_encrypted TEXT,
|
|
1482
|
+
api_key_iv TEXT,
|
|
1483
|
+
headers_encrypted TEXT,
|
|
1484
|
+
headers_iv TEXT,
|
|
1485
|
+
base_url TEXT,
|
|
1486
|
+
category TEXT NOT NULL DEFAULT 'llm',
|
|
1487
|
+
num_ctx INTEGER,
|
|
1488
|
+
num_gpu INTEGER DEFAULT -1,
|
|
1489
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
1490
|
+
active INTEGER NOT NULL DEFAULT 0,
|
|
1491
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
1492
|
+
)`);
|
|
1493
|
+
log.info("[migration v0.0.30] Ensuring models table exists...");
|
|
1494
|
+
db.run(`CREATE TABLE IF NOT EXISTS models (
|
|
1495
|
+
id TEXT PRIMARY KEY,
|
|
1496
|
+
provider_id TEXT REFERENCES providers(id) ON DELETE CASCADE,
|
|
1497
|
+
name TEXT NOT NULL,
|
|
1498
|
+
model_type TEXT NOT NULL DEFAULT 'llm',
|
|
1499
|
+
context_window INTEGER NOT NULL DEFAULT 20000,
|
|
1500
|
+
capabilities TEXT,
|
|
1501
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
1502
|
+
active INTEGER NOT NULL DEFAULT 0
|
|
1503
|
+
)`);
|
|
1504
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_models_provider ON models(provider_id)");
|
|
1505
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_models_type ON models(model_type)");
|
|
1506
|
+
log.info("[migration v0.0.30] Adding new providers and models...");
|
|
1507
|
+
for (const provider of SEED_DATA.providers) {
|
|
1508
|
+
db.query(`
|
|
1509
|
+
INSERT OR IGNORE INTO providers (id, name, base_url, category, enabled, active)
|
|
1510
|
+
VALUES (?, ?, ?, ?, 1, 0)
|
|
1511
|
+
`).run(provider.id, provider.name, provider.baseUrl || null, provider.category || 'llm');
|
|
1512
|
+
}
|
|
1513
|
+
const ollamaHost = process.env.OLLAMA_HOST;
|
|
1514
|
+
if (ollamaHost) {
|
|
1515
|
+
db.query(`UPDATE providers SET base_url = ? WHERE id = 'ollama'`).run(ollamaHost);
|
|
1516
|
+
log.info(`[migration v0.0.30] ✅ Ollama base_url set to ${ollamaHost} (from OLLAMA_HOST env)`);
|
|
1517
|
+
}
|
|
1518
|
+
let modelCount = 0;
|
|
1519
|
+
for (const model of SEED_DATA.models) {
|
|
1520
|
+
db.query(`
|
|
1521
|
+
INSERT OR IGNORE INTO models (id, provider_id, name, model_type, context_window, capabilities, enabled, active)
|
|
1522
|
+
VALUES (?, ?, ?, ?, ?, ?, 1, 0)
|
|
1523
|
+
`).run(model.id, model.providerId, model.name, model.modelType, model.contextWindow || null, model.capabilities || null);
|
|
1524
|
+
modelCount++;
|
|
1525
|
+
}
|
|
1526
|
+
log.info(`[migration v0.0.30] ✅ Added ${SEED_DATA.providers.length} providers and ${modelCount} models`);
|
|
1527
|
+
markApplied("v0.0.30");
|
|
1528
|
+
log.info("✅ Migration v0.0.30: NVIDIA NIM provider + 12 free models added");
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
// v0.0.31 — Update coordinator system_prompt to reduced version + sync bundled skills
|
|
1532
|
+
if (!applied("v0.0.31")) {
|
|
1533
|
+
const db = getDb();
|
|
1534
|
+
log.info("[migration v0.0.31] Updating coordinator system_prompt...");
|
|
1535
|
+
|
|
1536
|
+
// Update coordinator system_prompt with new concise version
|
|
1537
|
+
db.run(`UPDATE agents SET system_prompt = ? WHERE role = 'coordinator'`, [HIVE_SYSTEM_PROMPT]);
|
|
1538
|
+
const updated = db.query("SELECT name FROM agents WHERE role = 'coordinator' AND system_prompt = ?").get(HIVE_SYSTEM_PROMPT);
|
|
1539
|
+
if (updated) {
|
|
1540
|
+
log.info("[migration v0.0.31] ✅ Coordinator system_prompt updated");
|
|
1541
|
+
} else {
|
|
1542
|
+
log.warn("[migration v0.0.31] ⚠️ Coordinator update may have failed - checking length...");
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
// Add/update skills from bundled data (busqueda_fts5, canvas_report, memory_manager minimal set)
|
|
1546
|
+
log.info("[migration v0.0.31] Verifying minimal skills exist...");
|
|
1547
|
+
const skillLoader = new SkillLoader({ workspacePath: process.env.HIVE_HOME || process.cwd() });
|
|
1548
|
+
const bundledSkills = skillLoader.loadBundledSkills();
|
|
1549
|
+
|
|
1550
|
+
let skillsAdded = 0;
|
|
1551
|
+
for (const s of bundledSkills) {
|
|
1552
|
+
db.query(`
|
|
1553
|
+
INSERT OR IGNORE INTO skills (
|
|
1554
|
+
id, name, description, version, author, icon, category,
|
|
1555
|
+
permissions, dependencies, tools, triggers, preferred_agents,
|
|
1556
|
+
body, version_num, active, created_at, updated_at
|
|
1557
|
+
)
|
|
1558
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, (unixepoch()), (unixepoch()))
|
|
1559
|
+
`).run(
|
|
1560
|
+
s.name, s.name, s.description || "", String(s.version || "1.0.0"),
|
|
1561
|
+
s.author || "Hive", s.icon || "🧩", s.category || "general",
|
|
1562
|
+
JSON.stringify(s.permissions || []), JSON.stringify(s.dependencies || []),
|
|
1563
|
+
(s.tools || []).join(","), (s.triggers || []).join(","), "[]",
|
|
1564
|
+
s.content || "", 100
|
|
1565
|
+
);
|
|
1566
|
+
skillsAdded++;
|
|
1567
|
+
}
|
|
1568
|
+
log.info(`[migration v0.0.31] ✅ ${skillsAdded} skills synced from bundle`);
|
|
1569
|
+
|
|
1570
|
+
// Sync skills_fts (FTS5 index)
|
|
1571
|
+
log.info("[migration v0.0.31] Syncing skills_fts index...");
|
|
1572
|
+
db.run("DELETE FROM skills_fts");
|
|
1573
|
+
const ftsInsert = db.prepare("INSERT INTO skills_fts(id, name, description, category, tools, triggers, body) VALUES(?, ?, ?, ?, ?, ?, ?)");
|
|
1574
|
+
const activeSkills = db.query("SELECT * FROM skills WHERE active = 1").all() as any[];
|
|
1575
|
+
for (const s of activeSkills) {
|
|
1576
|
+
ftsInsert.run(s.id, s.name, s.description || "", s.category || "", s.tools || "", s.triggers || "", s.body || "");
|
|
1577
|
+
}
|
|
1578
|
+
log.info(`[migration v0.0.31] ✅ ${activeSkills.length} skills indexed in FTS5`);
|
|
1579
|
+
|
|
1580
|
+
markApplied("v0.0.31");
|
|
1581
|
+
log.info("✅ Migration v0.0.31: Reduced system_prompt + skills sync");
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
// v0.0.32 — add vision/multimodal columns to channels table
|
|
1585
|
+
if (!applied("v0.0.32")) {
|
|
1586
|
+
const db = getDb();
|
|
1587
|
+
log.info("[migration v0.0.32] Adding vision columns to channels table...");
|
|
1588
|
+
|
|
1589
|
+
const addCol = (col: string, def: string) => {
|
|
1590
|
+
try { db.run(`ALTER TABLE channels ADD COLUMN ${col} ${def}`); } catch { /* already exists */ }
|
|
1591
|
+
};
|
|
1592
|
+
addCol("vision_enabled", "INTEGER NOT NULL DEFAULT 0");
|
|
1593
|
+
addCol("ocr_provider", "TEXT");
|
|
1594
|
+
addCol("vision_provider", "TEXT");
|
|
1595
|
+
addCol("vision_model_id", "TEXT");
|
|
1596
|
+
|
|
1597
|
+
markApplied("v0.0.32");
|
|
1598
|
+
log.info("✅ Migration v0.0.32: vision columns added to channels");
|
|
1599
|
+
}
|
|
1600
|
+
} catch (e) {
|
|
1601
|
+
log.error("⚠️ runStartupMigrations failed:", { error: (e as Error).message });
|
|
1602
|
+
}
|
|
1603
|
+
}
|