@johpaz/hive-sdk 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +97 -0
- package/README.md +78 -23
- package/bunfig.toml +4 -2
- package/docs/API-AGENTS.md +78 -27
- package/docs/API-CONTEXT-COMPILER.md +31 -34
- package/docs/API-TOOLS-SKILLS-CHANNELS.md +58 -22
- package/docs/HIVE-HARNESS.md +1 -1
- package/docs/INDEX.md +4 -4
- package/docs/TEMPLATE-HIVE-APP.md +10 -10
- package/package.json +9 -4
- package/packages/cli/package.json +2 -2
- package/packages/cli/src/commands/create-app.test.ts +36 -7
- package/packages/cli/src/commands/init.ts +3 -3
- package/packages/cli/src/commands/run.ts +1 -1
- package/packages/cli/src/commands/test.ts +37 -25
- package/packages/cli/src/commands/trace.ts +30 -28
- package/packages/cli/templates/hive-app/.env.example +10 -2
- package/packages/cli/templates/hive-app/README.md +103 -0
- package/packages/cli/templates/hive-app/hive.config.ts +9 -3
- package/packages/cli/templates/hive-app/src/agents/coordinator.ts +8 -1
- package/packages/cli/templates/hive-app/src/main.ts +12 -19
- package/packages/core/package.json +5 -4
- package/packages/core/src/agent/acceptance-checks.ts +166 -0
- package/packages/core/src/agent/agent-catalog.ts +348 -0
- package/packages/core/src/agent/agent-loop.ts +1373 -0
- package/packages/core/src/agent/capability-search.ts +186 -0
- package/packages/core/src/agent/catalog-selector.ts +103 -0
- package/packages/core/src/agent/{Compaction.ts → compaction.ts} +86 -63
- package/packages/core/src/agent/context-compiler.ts +689 -0
- package/packages/core/src/agent/conversation-store.ts +381 -0
- package/packages/core/src/agent/curator.ts +276 -0
- package/packages/core/src/agent/delegation-runtime.ts +241 -0
- package/packages/core/src/agent/goal-runner.ts +323 -0
- package/packages/core/src/agent/index.ts +17 -12
- package/packages/core/src/agent/llm-client.ts +266 -0
- package/packages/core/src/agent/llm-providers/anthropic.ts +264 -0
- package/packages/core/src/agent/llm-providers/deepseek.ts +8 -0
- package/packages/core/src/agent/{providers → llm-providers}/gemini.ts +98 -60
- package/packages/core/src/agent/llm-providers/groq.ts +5 -0
- package/packages/core/src/agent/llm-providers/hiveagents.ts +253 -0
- package/packages/core/src/agent/{providers → llm-providers}/interface.ts +73 -13
- package/packages/core/src/agent/llm-providers/kimi.ts +8 -0
- package/packages/core/src/agent/llm-providers/minimax.ts +13 -0
- package/packages/core/src/agent/llm-providers/mistral.ts +5 -0
- package/packages/core/src/agent/llm-providers/modelscope.ts +5 -0
- package/packages/core/src/agent/llm-providers/nvidia.ts +5 -0
- package/packages/core/src/agent/{providers → llm-providers}/ollama.ts +31 -5
- package/packages/core/src/agent/llm-providers/openai-compat-base.ts +418 -0
- package/packages/core/src/agent/llm-providers/openai.ts +5 -0
- package/packages/core/src/agent/llm-providers/opencode-go.ts +9 -0
- package/packages/core/src/agent/llm-providers/openrouter.ts +5 -0
- package/packages/core/src/agent/llm-providers/qwen.ts +5 -0
- package/packages/core/src/agent/llm-providers/z-ai.ts +5 -0
- package/packages/core/src/agent/minimal-loadout.ts +47 -0
- package/packages/core/src/agent/playbook-selector.ts +119 -0
- package/packages/core/src/agent/{PromptBuilder.ts → prompt-builder.ts} +21 -22
- package/packages/core/src/{harness → agent}/proof-packet.ts +16 -21
- package/packages/core/src/agent/providers/index.ts +35 -16
- package/packages/core/src/agent/reflector.ts +320 -0
- package/packages/core/src/agent/routing-intent.ts +22 -0
- package/packages/core/src/{harness → agent}/run-epoch.ts +4 -3
- package/packages/core/src/{harness → agent}/run-store.ts +142 -81
- package/packages/core/src/agent/{Service.ts → service.ts} +37 -26
- package/packages/core/src/agent/skill-selector.ts +374 -0
- package/packages/core/src/agent/stuck-loop.ts +209 -0
- package/packages/core/src/agent/{selectors/ToolSelector.ts → tool-selector.ts} +188 -178
- package/packages/core/src/{ace/Tracer.ts → agent/tracer.ts} +37 -27
- package/packages/core/src/api/createAgent.test.ts +139 -27
- package/packages/core/src/api/createAgent.ts +232 -44
- package/packages/core/src/artifacts/store.ts +162 -0
- package/packages/core/src/canvas/canvas-manager.ts +161 -0
- package/packages/core/src/canvas/canvas.test.ts +8 -4
- package/packages/core/src/canvas/emitter.ts +131 -80
- package/packages/core/src/canvas/index.ts +1 -3
- package/packages/core/src/channels/base.ts +9 -1
- package/packages/core/src/channels/discord.ts +5 -4
- package/packages/core/src/channels/manager.ts +122 -30
- package/packages/core/src/channels/slack.ts +5 -4
- package/packages/core/src/channels/telegram.ts +36 -6
- package/packages/core/src/channels/webchat.ts +11 -10
- package/packages/core/src/channels/whatsapp.ts +23 -7
- package/packages/core/src/config/index.ts +13 -2
- package/packages/core/src/config/loader.ts +71 -29
- package/packages/core/src/ethics/EthicsGuard.test.ts +90 -36
- package/packages/core/src/ethics/EthicsGuard.ts +51 -47
- package/packages/core/src/events/agent-bus.ts +44 -68
- package/packages/core/src/events/channel-narration.ts +150 -0
- package/packages/core/src/events/narration.ts +82 -0
- package/packages/core/src/events/tool-narration.ts +62 -0
- package/packages/core/src/gateway/delegation-groups.ts +258 -0
- package/packages/core/src/{harness → gateway}/durable-queue.ts +102 -42
- package/packages/core/src/{harness → gateway}/job-store.ts +85 -48
- package/packages/core/src/gateway/lane-queue.ts +173 -0
- package/packages/core/src/gateway/notification-inbox.ts +57 -0
- package/packages/core/src/gateway/server.ts +1 -1
- package/packages/core/src/harness/index.ts +46 -27
- package/packages/core/src/index.ts +33 -27
- package/packages/core/src/mcp/hot-reload.ts +32 -23
- package/packages/core/src/mcp/index.ts +6 -3
- package/packages/core/src/mcp/singleton.ts +1 -4
- package/packages/core/src/mcp/tool-sync.ts +138 -0
- package/packages/core/src/memory/Scratchpad.test.ts +39 -20
- package/packages/core/src/memory/Scratchpad.ts +27 -34
- package/packages/core/src/multimodal/vision-service.ts +44 -38
- package/packages/core/src/resilience/retry.ts +95 -0
- package/packages/core/src/scheduler/CronScheduler.ts +334 -287
- package/packages/core/src/scheduler/index.ts +9 -7
- package/packages/core/src/scheduler/integration.ts +46 -26
- package/packages/core/src/scheduler/scheduler.test.ts +9 -13
- package/packages/core/src/scheduler/types.ts +7 -2
- package/packages/core/src/security/Pairing.ts +1 -1
- package/packages/core/src/skills/bundled/a2ui/a2ui_dashboard/SKILL.md +176 -0
- package/packages/core/src/skills/bundled/a2ui/a2ui_form/SKILL.md +202 -0
- package/packages/core/src/skills/bundled/a2ui/a2ui_interactive/SKILL.md +206 -0
- package/packages/core/src/skills/bundled/agents/agent_spawner/SKILL.md +173 -0
- package/packages/core/src/skills/bundled/agents/memory_manager/SKILL.md +143 -0
- package/packages/core/src/skills/bundled/agents/research_and_remember/SKILL.md +139 -0
- package/packages/core/src/skills/bundled/agents/task_orchestrator/SKILL.md +98 -0
- package/packages/core/src/skills/bundled/api/api_client/SKILL.md +132 -0
- package/packages/core/src/skills/bundled/cli/cli_pipeline/SKILL.md +135 -0
- package/packages/core/src/skills/bundled/cli/cli_safe_exec/SKILL.md +125 -0
- package/packages/core/src/skills/bundled/cli/software_engineering/SKILL.md +23 -0
- package/packages/core/src/skills/bundled/cron_manager/SKILL.md +188 -0
- package/packages/core/src/skills/bundled/cron_reminder/SKILL.md +112 -0
- package/packages/core/src/skills/bundled/filesystem/file_manager/SKILL.md +118 -0
- package/packages/core/src/skills/bundled/filesystem/file_read_and_summarize/SKILL.md +109 -0
- package/packages/core/src/skills/bundled/filesystem/file_writer/SKILL.md +129 -0
- package/packages/core/src/skills/bundled/filesystem/workspace_file_operator/SKILL.md +22 -0
- package/packages/core/src/skills/bundled/office/office_document_manager/SKILL.md +262 -0
- package/packages/core/src/skills/bundled/search_knowledge/capability_discovery/SKILL.md +75 -0
- package/packages/core/src/skills/bundled/web/browser_automate/SKILL.md +120 -0
- package/packages/core/src/skills/bundled/web/browser_scrape/SKILL.md +109 -0
- package/packages/core/src/skills/bundled/web/web_monitor/SKILL.md +127 -0
- package/packages/core/src/skills/bundled/web/web_research/SKILL.md +119 -0
- package/packages/core/src/skills/bundled-data.generated.ts +731 -2678
- package/packages/core/src/skills/skills.test.ts +52 -11
- package/packages/core/src/{harness → storage}/boot-id.ts +5 -2
- package/packages/core/src/storage/bootstrap.ts +151 -0
- package/packages/core/src/storage/causal-events.ts +84 -0
- package/packages/core/src/storage/collections.ts +680 -0
- package/packages/core/src/storage/crypto.ts +205 -74
- package/packages/core/src/{harness/db-helpers.ts → storage/hive.ts} +63 -7
- package/packages/core/src/storage/hivedb.ts +61 -0
- package/packages/core/src/storage/index.ts +111 -18
- package/packages/core/src/storage/model-id.ts +53 -0
- package/packages/core/src/storage/onboarding.ts +540 -972
- package/packages/core/src/storage/reconcile.ts +238 -0
- package/packages/core/src/storage/seed.ts +572 -406
- package/packages/core/src/storage/usage.ts +285 -225
- package/packages/core/src/storage/user-email.ts +11 -0
- package/packages/core/src/swarm/AgentExecutor.ts +1 -1
- package/packages/core/src/swarm/EventBridge.ts +1 -1
- package/packages/core/src/swarm/index.ts +12 -9
- package/packages/core/src/tool-runtime/index.ts +146 -23
- package/packages/core/src/tool-runtime/tool-worker.ts +2 -2
- package/packages/core/src/tool-runtime/worker-tools.ts +27 -0
- package/packages/core/src/{canvas/a2ui-tools.ts → tools/a2ui/index.ts} +17 -8
- package/packages/core/src/tools/agents/get-available-models.ts +36 -54
- package/packages/core/src/tools/agents/index.ts +784 -292
- package/packages/core/src/tools/api/api-request.test.ts +164 -0
- package/packages/core/src/tools/api/api-request.ts +174 -0
- package/packages/core/src/tools/api/index.ts +16 -0
- package/packages/core/src/tools/cli/index.ts +4 -0
- package/packages/core/src/tools/core/index.ts +281 -112
- package/packages/core/src/tools/cron/index.ts +121 -124
- package/packages/core/src/tools/index.ts +63 -78
- package/packages/core/src/tools/office/office-escribir-xlsx.ts +3 -1
- package/packages/core/src/tools/types.ts +3 -1
- package/packages/core/src/tools/web/artifact-inspect.ts +23 -0
- package/packages/core/src/tools/web/browser-screenshot.ts +26 -5
- package/packages/core/src/tools/web/browser-service.ts +5 -0
- package/packages/core/src/tools/web/browser-type.ts +3 -8
- package/packages/core/src/tools/web/index.ts +4 -4
- package/packages/core/src/voice/index.ts +89 -63
- package/packages/core/src/workers/agent.worker.ts +2 -2
- package/packages/core/src/workers/workers.test.ts +3 -10
- package/scripts/bump-version.ts +248 -0
- package/scripts/generate-skill-bundle.ts +108 -0
- package/test/agent-loop-terminal-synthesis.test.ts +32 -0
- package/test/catalog-agents-stay-enabled.test.ts +117 -0
- package/test/causal-events.test.ts +117 -0
- package/test/compaction.test.ts +105 -0
- package/test/context-compiler.test.ts +269 -0
- package/test/curator.test.ts +130 -0
- package/test/durable-queue.test.ts +114 -0
- package/test/harness-barrel.test.ts +64 -0
- package/test/hive-helpers.test.ts +130 -0
- package/test/hivedb-search.test.ts +189 -0
- package/test/internal-turns.test.ts +166 -0
- package/test/job-idempotency.test.ts +68 -0
- package/test/job-retry-backoff.test.ts +184 -0
- package/test/job-store.test.ts +381 -0
- package/test/llm-retry.test.ts +97 -0
- package/test/memory-perf.test.ts +774 -0
- package/test/minimal-loadout.test.ts +78 -0
- package/test/model-catalog.test.ts +105 -0
- package/test/preload.ts +12 -0
- package/test/reflector.test.ts +320 -0
- package/test/retention-cap.test.ts +91 -0
- package/test/retired-capabilities-pruned.test.ts +192 -0
- package/test/run-store.test.ts +355 -0
- package/test/scratchpad.test.ts +74 -0
- package/test/secrets-durability.test.ts +119 -0
- package/test/seed-model-reseed.test.ts +155 -0
- package/test/setup-agent-seed.test.ts +264 -0
- package/test/tool-inventory.test.ts +65 -0
- package/test/tool-runtime.test.ts +258 -0
- package/test/toon.test.ts +429 -0
- package/tsconfig.json +2 -0
- package/packages/core/src/ace/Curator.ts +0 -158
- package/packages/core/src/ace/Reflector.ts +0 -200
- package/packages/core/src/ace/index.ts +0 -4
- package/packages/core/src/agent/AgentRunner.ts +0 -711
- package/packages/core/src/agent/ContextCompiler.ts +0 -567
- package/packages/core/src/agent/ContextGuard.ts +0 -91
- package/packages/core/src/agent/ConversationStore.ts +0 -254
- package/packages/core/src/agent/Hooks.ts +0 -166
- package/packages/core/src/agent/StuckLoop.ts +0 -133
- package/packages/core/src/agent/providers/LLMClient.ts +0 -149
- package/packages/core/src/agent/providers/anthropic.ts +0 -212
- package/packages/core/src/agent/providers/openai-compat.ts +0 -231
- package/packages/core/src/agent/selectors/PlaybookSelector.ts +0 -121
- package/packages/core/src/agent/selectors/SkillSelector.ts +0 -322
- package/packages/core/src/agent/selectors/index.ts +0 -6
- package/packages/core/src/auth/auth.ts +0 -121
- package/packages/core/src/auth/index.ts +0 -1
- package/packages/core/src/canvas/CanvasManager.ts +0 -390
- package/packages/core/src/canvas/canvas-tools.ts +0 -448
- package/packages/core/src/harness/collections.ts +0 -98
- package/packages/core/src/harness/goal-verifier.ts +0 -141
- package/packages/core/src/harness/harness.test.ts +0 -236
- package/packages/core/src/harness/reconcile.ts +0 -149
- package/packages/core/src/mcp/MCPToolAdapter.ts +0 -176
- package/packages/core/src/multimodal/VisionService.ts +0 -293
- package/packages/core/src/scheduler/dag/AgentExecutor.ts +0 -53
- package/packages/core/src/scheduler/dag/DAGScheduler.ts +0 -250
- package/packages/core/src/scheduler/dag/EventBridge.ts +0 -122
- package/packages/core/src/scheduler/dag/TaskGraph.ts +0 -192
- package/packages/core/src/scheduler/dag/TaskNode.ts +0 -97
- package/packages/core/src/scheduler/dag/TaskResult.ts +0 -22
- package/packages/core/src/scheduler/dag/errors.ts +0 -37
- package/packages/core/src/scheduler/dag/index.ts +0 -26
- package/packages/core/src/scheduler/dag/presets/ResearchPreset.ts +0 -97
- package/packages/core/src/scheduler/dag/strategies/ParallelStrategy.ts +0 -21
- package/packages/core/src/scheduler/dag/strategies/PriorityStrategy.ts +0 -46
- package/packages/core/src/storage/HiveDBStorage.ts +0 -64
- package/packages/core/src/storage/SQLiteStorage.ts +0 -414
- package/packages/core/src/storage/hiveSeed.ts +0 -308
- package/packages/core/src/storage/hiveStorage.test.ts +0 -38
- package/packages/core/src/storage/schema.ts +0 -689
- package/packages/core/src/storage/storage.test.ts +0 -37
- package/packages/core/src/swarm/AgentBus.ts +0 -460
- package/packages/core/src/swarm/EventBus.ts +0 -169
- package/packages/core/src/swarm/WorkerPool.ts +0 -236
- package/packages/core/src/tools/bridge-events.ts +0 -26
- package/packages/core/src/tools/canvas/index.ts +0 -375
- package/packages/core/src/tools/codebridge/index.ts +0 -342
- package/packages/core/src/tools/meeting/index.ts +0 -353
- package/packages/core/src/tools/projects/index.ts +0 -37
- package/packages/core/src/tools/projects/project-create.ts +0 -94
- package/packages/core/src/tools/projects/project-done.ts +0 -66
- package/packages/core/src/tools/projects/project-fail.ts +0 -66
- package/packages/core/src/tools/projects/project-list.ts +0 -96
- package/packages/core/src/tools/projects/project-update.ts +0 -72
- package/packages/core/src/tools/projects/task-create.ts +0 -68
- package/packages/core/src/tools/projects/task-evaluate.ts +0 -93
- package/packages/core/src/tools/projects/task-update.ts +0 -93
- package/packages/core/src/tools/voice/index.ts +0 -104
- package/packages/core/src/tools/web/api-request.test.ts +0 -170
- package/packages/core/src/tools/web/api-request.ts +0 -239
- package/test/setup-db.ts +0 -216
- /package/packages/core/src/agent/{NativeTools.ts → native-tools.ts} +0 -0
|
@@ -1,96 +1,186 @@
|
|
|
1
1
|
import { logger } from "../utils/logger.ts";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { ensureHiveDb } from "./bootstrap";
|
|
3
|
+
import { col, toIndexable, fromIndexable } from "./hive";
|
|
4
|
+
import {
|
|
5
|
+
storeProviderApiKey,
|
|
6
|
+
storeChannelConfig,
|
|
7
|
+
storeMcpEnv,
|
|
8
|
+
loadProviderApiKey,
|
|
9
|
+
loadChannelConfig,
|
|
10
|
+
} from "./crypto";
|
|
5
11
|
import { SkillLoader } from "../skills/index.ts";
|
|
12
|
+
import type {
|
|
13
|
+
UserDoc, ProviderDoc, ModelDoc, AgentDoc, ChannelDoc, McpServerDoc,
|
|
14
|
+
UserIdentityDoc, OnboardingProgressDoc, EthicsDoc, SkillDoc, ToolDoc,
|
|
15
|
+
} from "./collections";
|
|
16
|
+
import { normalizeUserEmail } from "./user-email";
|
|
6
17
|
|
|
7
18
|
export interface OnboardingSection {
|
|
8
|
-
step: "user" | "skills" | "ethics" | "tools" | "provider" | "model" | "channel" | "
|
|
19
|
+
step: "user" | "skills" | "ethics" | "tools" | "provider" | "model" | "channel" | "mcp" | "agent" | "complete";
|
|
9
20
|
userId: string;
|
|
10
21
|
data: Record<string, unknown>;
|
|
11
22
|
completedAt?: number;
|
|
12
23
|
}
|
|
13
24
|
|
|
14
25
|
const log = logger.child("onboarding");
|
|
15
|
-
// 9️⃣ Hive System Prompt
|
|
16
26
|
|
|
17
27
|
const HIVE_SYSTEM_PROMPT = `
|
|
18
28
|
# HIVE — Agente Coordinador
|
|
19
29
|
|
|
20
|
-
Sos Bee, coordinador de Hive.
|
|
30
|
+
Sos Bee, coordinador de Hive. Sos el único agente que conversa con el usuario, y no trabajás solo: dirigís una colmena de workers especializados que corren en paralelo.
|
|
21
31
|
|
|
22
|
-
|
|
32
|
+
**Tu oficio es repartir trabajo, no hacerlo todo vos.** Ante cada pedido buscás primero quién puede resolverlo; solo lo hacés con tus propias manos cuando no hay nadie que lo cubra.
|
|
23
33
|
|
|
24
|
-
1.
|
|
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.
|
|
34
|
+
## 1. ANTES DE ACTUAR
|
|
30
35
|
|
|
31
|
-
|
|
36
|
+
Leé el pedido completo y mirá lo que ya sabés: la sección SCRATCHPAD trae tus notas de esta conversación y \`memory_read\` / \`memory_search\` lo guardado en conversaciones anteriores. No rehagas trabajo ya hecho ni vuelvas a preguntar algo que ya te dijeron.
|
|
32
37
|
|
|
33
|
-
|
|
38
|
+
**Si es un saludo, una charla o una pregunta que respondés de memoria: respondé y terminá.** Eso no se delega nunca ni necesita herramientas.
|
|
34
39
|
|
|
35
|
-
|
|
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
|
+
## 2. DESCOMPONER
|
|
40
41
|
|
|
41
|
-
|
|
42
|
+
Separá el pedido en partes y clasificá cada una:
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
| Tipo de parte | Qué hacés |
|
|
45
|
+
|---|---|
|
|
46
|
+
| Independientes entre sí | Van juntas, en paralelo, en este mismo turno |
|
|
47
|
+
| Una necesita el resultado de otra | Va en una fase posterior |
|
|
48
|
+
| Trivial o conversacional | La resolvés vos, sin herramientas |
|
|
44
49
|
|
|
45
|
-
##
|
|
50
|
+
## 3. POR CADA PARTE: ¿HAY UN AGENTE QUE LA HAGA?
|
|
46
51
|
|
|
47
|
-
**
|
|
52
|
+
**Esta es la pregunta central de tu rol, y contestarla es gratis:** el roster está en la sección COLMENA DE AGENTES de este mismo prompt, no hace falta ninguna llamada para consultarlo.
|
|
48
53
|
|
|
49
|
-
|
|
54
|
+
1. **¿Encaja un agente de la colmena?** → \`task_delegate\`. Este es el camino por defecto.
|
|
55
|
+
2. **¿Ninguno encaja?** → \`agent_find\` por si existe un worker propio para esa especialidad.
|
|
56
|
+
3. **¿Tampoco hay?** → recién ahí \`search_knowledge\` para encontrar las herramientas. Preferí siempre herramientas nativas sobre MCP.
|
|
57
|
+
4. **Si encontraste una tool nativa** → resolvelo vos directamente.
|
|
58
|
+
5. **Si al menos una parte requiere MCP**:
|
|
59
|
+
- Agrupá las tools por \`server_id\` y usá \`agent_find\` para buscar un especialista del usuario que ya tenga ese servidor.
|
|
60
|
+
- Si existe y está habilitado, delegale la parte correspondiente. No preguntes ni crees otro.
|
|
61
|
+
- Si no existe, **antes de ejecutar cualquier tool de ese servidor**, preguntale al usuario si quiere crear un agente persistente para esa integración.
|
|
62
|
+
- Si acepta: usá \`get_available_models\`, descubrí \`agent_create\`, creá un worker con \`mcp_server_id\` y delegale la tarea actual. El agente recibe todas las tools actuales y futuras de ese servidor.
|
|
63
|
+
- Si rechaza: ejecutá vos directamente las tools MCP necesarias solo para esta solicitud.
|
|
64
|
+
- Si intervienen varios servidores sin especialista, tratá cada servidor por separado: un agente por servidor, nunca uno combinado.
|
|
50
65
|
|
|
51
|
-
|
|
66
|
+
### CALENDARIO NO ES CRON
|
|
52
67
|
|
|
53
|
-
|
|
68
|
+
- \`schedule_automation_agent\` administra jobs que Hive ejecutará después: tareas recurrentes, reportes automáticos, monitoreos y recordatorios de una sola ejecución.
|
|
69
|
+
- Crear, consultar o modificar eventos, citas o reuniones; invitar asistentes; o revisar disponibilidad pertenece al servidor de calendario y a su especialista MCP.
|
|
70
|
+
- Una frase como “agenda una reunión” significa calendario, no \`cron.create\`. Solo usá cron cuando el usuario quiere que Hive ejecute una instrucción en el futuro.
|
|
54
71
|
|
|
55
|
-
**
|
|
72
|
+
Si \`search_knowledge\` no devuelve nada y el pedido es corto o ambiguo, **preguntale al usuario** en vez de adivinar y encadenar más búsquedas. Una pregunta cuesta un turno; adivinar mal cuesta varios.
|
|
56
73
|
|
|
57
|
-
|
|
74
|
+
## 4. DELEGAR EN PARALELO
|
|
58
75
|
|
|
59
|
-
|
|
76
|
+
Las partes independientes se lanzan **todas en el mismo turno**: una \`task_delegate\` por parte, con \`mode="async"\`. Hive las agrupa por turno y los workers corren simultáneamente.
|
|
60
77
|
|
|
61
|
-
|
|
62
|
-
- \`memory_write\` / \`memory_read\` — Memoria cross-conversación por clave
|
|
63
|
-
- Playbook — Reglas aprendidas inyectadas automáticamente
|
|
78
|
+
Si el usuario pide tres cosas que no dependen entre sí, son tres \`task_delegate\` en la misma respuesta — no una, esperar, y después la siguiente. **Paralelizar es el caso normal, no la excepción.**
|
|
64
79
|
|
|
65
|
-
|
|
80
|
+
Cada delegación lleva: \`worker_id\`, una subtarea acotada, contexto mínimo y \`acceptance\` verificable. Antes de delegar, si el worker va a necesitar herramientas puntuales, buscalas con \`search_knowledge\` e incluilas en la instrucción. Reservá \`mode="sync"\` solo para un lookup cuyo resultado esperás en segundos.
|
|
66
81
|
|
|
67
|
-
|
|
68
|
-
|
|
82
|
+
Si más adelante una entrega no cumple sus criterios, \`task_revise\` reencola al mismo worker sobre el mismo hilo (ver sección 6) — no crees una delegación nueva para corregir algo ya delegado.
|
|
83
|
+
|
|
84
|
+
## 5. ESPERAR: NO ESPERÁS
|
|
85
|
+
|
|
86
|
+
Después de delegar, contale al usuario en una línea qué pusiste a correr y **terminá tu turno**.
|
|
87
|
+
|
|
88
|
+
Cuando todas las tareas del turno alcanzan estado terminal, Hive te reinvoca automáticamente con un mensaje \`[Sistema]\` que trae el resultado de cada una.
|
|
89
|
+
|
|
90
|
+
- **No hagas polling** con \`task_status\` en loop. Usalo solo si el usuario pide el estado antes de tiempo.
|
|
91
|
+
- No anuncies resultados que todavía no tenés ni declares éxito antes del \`[Sistema]\`.
|
|
92
|
+
- No re-delegues una tarea porque "no contestó": ya está encolada.
|
|
93
|
+
|
|
94
|
+
## 6. CERRAR
|
|
95
|
+
|
|
96
|
+
Al recibir el \`[Sistema]\`, cada entrega trae sus \`acceptance\` (criterios) y sus \`checks\` (resultado determinístico, sin LLM, ya calculado):
|
|
97
|
+
|
|
98
|
+
- \`checks.status="passed"\` → un check automático ya lo confirmó. Aceptalo.
|
|
99
|
+
- \`checks.status="failed"\` (implica \`ok=false\`) → no cumplió. Nunca lo reportes como éxito.
|
|
100
|
+
- \`checks.status="unchecked"\` o ausente → no hay check automático para ese criterio: **vos sos quien juzga**, con el contenido y la evidencia que trae la entrega.
|
|
101
|
+
|
|
102
|
+
Si una entrega no cumple sus criterios: usá \`task_revise\` con el \`task_id\` y un feedback concreto y accionable — el worker retoma con su contexto, no hace falta repetirle todo el pedido. Si el problema es trivial y tenés las tools, corregilo vos directamente en vez de re-delegar. No inventes trabajo ni evidencia.
|
|
103
|
+
|
|
104
|
+
Cuando todo lo delegado en esta ronda cumple, escribí **una sola** respuesta final integrando todo. Las entradas con \`ok=false\` se reportan con su motivo real, nunca como éxito.
|
|
105
|
+
|
|
106
|
+
Guardá lo que vaya a servir después: \`save_note\` para esta conversación, \`memory_write\` para lo que deba sobrevivir a ella. Confirmá con el usuario antes de persistir datos suyos.
|
|
107
|
+
|
|
108
|
+
## REGLAS PERMANENTES
|
|
109
|
+
|
|
110
|
+
1. **Ética primero** — Operás bajo un Código de Ética obligatorio que no podés ignorar.
|
|
111
|
+
2. **Verdad de ejecución** — \`TaskDoc\`/\`JobDoc\` son la fuente de verdad. \`agent_find\` solo descubre workers; nunca prueba si algo está corriendo: para eso están \`task_list\` y \`task_status\`. Si \`task_delegate\` devuelve \`ok=true\` con \`task_id\`, \`job_id\` y \`run_id\`, la tarea se persistió de verdad y no es una simulación. Si una herramienta falla, reportá su resultado exacto: no inventes IDs, estados ni ejecuciones.
|
|
112
|
+
3. **Vos aceptás las entregas** — cada entrega vuelve con sus criterios, su evidencia y el resultado de los checks determinísticos (ver sección 6). Si cumple, la integrás; si no, \`task_revise\` con feedback concreto, o la corregís vos si es trivial. Si un worker devuelve \`needs_input\`, vos formulás la pregunta al usuario con contexto.
|
|
113
|
+
4. **Buscá antes de crear** — nunca crees un worker si el catálogo ya cubre la tarea.
|
|
114
|
+
5. **Mínimo privilegio** — solo las herramientas necesarias a cada worker. La única excepción explícita es un especialista MCP aprobado por el usuario: recibe el servidor completo que figura en \`mcp_server_ids_json\`, nunca otros servidores.
|
|
115
|
+
6. **Nunca \`cli_exec\` para cron** — usá \`cron.create\`, y preguntá al usuario cada cuánto ejecutar.
|
|
116
|
+
7. **Calendario ≠ cron** — los eventos y reuniones van al especialista MCP de calendario; cron solo programa futuras ejecuciones de Hive.
|
|
117
|
+
|
|
118
|
+
## QUÉ HAY EN TU CONTEXTO
|
|
119
|
+
|
|
120
|
+
- **COLMENA DE AGENTES** — los workers disponibles ahora mismo. Consultalo antes de decidir nada.
|
|
121
|
+
- **HERRAMIENTAS SIEMPRE DISPONIBLES** — con las que arrancás cada turno. El resto se descubre con \`search_knowledge\` y queda usable de inmediato.
|
|
122
|
+
- **SCRATCHPAD** — tus notas de esta conversación; sobreviven a la compresión del historial.
|
|
123
|
+
- **PLAYBOOK APRENDIDO** — reglas aprendidas de turnos anteriores, ya filtradas por relevancia. Aplicalas.
|
|
124
|
+
- **SKILLS DESCUBIERTAS** — nombres de skills que el sistema considera relevantes para este pedido. Son una pista, no instrucciones: su contenido llega cuando descubrís sus herramientas con \`search_knowledge\`.
|
|
125
|
+
|
|
126
|
+
## CANALES
|
|
127
|
+
|
|
128
|
+
webchat (siempre activo) · telegram · discord · slack · whatsapp. Preferencia para cron: telegram > discord > webchat.
|
|
69
129
|
`
|
|
70
|
-
export function initOnboardingDb(): void {
|
|
71
|
-
try {
|
|
72
|
-
initializeDatabase();
|
|
73
130
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
131
|
+
/**
|
|
132
|
+
* First line of every stock coordinator prompt ever shipped. Used to tell a
|
|
133
|
+
* stock prompt (safe to upgrade in place) from one the user rewrote through
|
|
134
|
+
* the agents API, which must never be clobbered.
|
|
135
|
+
*/
|
|
136
|
+
const HIVE_PROMPT_STOCK_HEADER = "# HIVE — Agente Coordinador";
|
|
77
137
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Upgrades coordinators still carrying an older stock prompt.
|
|
140
|
+
*
|
|
141
|
+
* HIVE_SYSTEM_PROMPT is only written during onboarding/setup, so an install
|
|
142
|
+
* created before a prompt change keeps the old text in `agents.system_prompt`
|
|
143
|
+
* forever — new coordinator behavior would never reach existing users. This
|
|
144
|
+
* runs on every boot and rewrites only rows whose prompt is verbatim stock
|
|
145
|
+
* (starts with the stock header and differs from the current text). A prompt
|
|
146
|
+
* the user customized doesn't match and is left untouched.
|
|
147
|
+
*/
|
|
148
|
+
export async function refreshCoordinatorPrompts(): Promise<number> {
|
|
149
|
+
const agentsCol = await col<AgentDoc>("agents");
|
|
150
|
+
let updated = 0;
|
|
151
|
+
for (const entry of await agentsCol.findBy("role", "coordinator")) {
|
|
152
|
+
const current = entry.doc.system_prompt ?? "";
|
|
153
|
+
if (current === HIVE_SYSTEM_PROMPT) continue;
|
|
154
|
+
if (!current.trimStart().startsWith(HIVE_PROMPT_STOCK_HEADER)) continue; // user-authored
|
|
155
|
+
await agentsCol.put(entry.id, {
|
|
156
|
+
...entry.doc,
|
|
157
|
+
system_prompt: HIVE_SYSTEM_PROMPT,
|
|
158
|
+
updated_at: Date.now(),
|
|
159
|
+
}, { expectedVersion: entry.version });
|
|
160
|
+
updated++;
|
|
161
|
+
}
|
|
162
|
+
return updated;
|
|
163
|
+
}
|
|
82
164
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
165
|
+
/** Generates a 32-char lowercase hex id, matching the old `lower(hex(randomblob(16)))` scheme. */
|
|
166
|
+
function genId(): string {
|
|
167
|
+
return crypto.randomUUID().replace(/-/g, "");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export async function initOnboardingDb(): Promise<void> {
|
|
171
|
+
try {
|
|
172
|
+
// ensureHiveDb() already reseeds the static catalogs unconditionally on
|
|
173
|
+
// every call — no separate userCount gate/seedAllData() call needed here.
|
|
174
|
+
await ensureHiveDb();
|
|
86
175
|
} catch (e) {
|
|
87
176
|
log.error("⚠️ Fallo al inicializar/poblar la DB:", { error: (e as Error).message });
|
|
88
177
|
}
|
|
89
178
|
}
|
|
90
179
|
|
|
91
|
-
export function saveUserProfile(data: {
|
|
180
|
+
export async function saveUserProfile(data: {
|
|
92
181
|
userId?: string;
|
|
93
182
|
userName?: string;
|
|
183
|
+
userEmail?: string;
|
|
94
184
|
userLanguage?: string;
|
|
95
185
|
userTimezone?: string;
|
|
96
186
|
userOccupation?: string;
|
|
@@ -100,77 +190,82 @@ export function saveUserProfile(data: {
|
|
|
100
190
|
agentDescription?: string;
|
|
101
191
|
agentTone?: string;
|
|
102
192
|
channelUserId?: string;
|
|
103
|
-
}): string {
|
|
193
|
+
}): Promise<string> {
|
|
104
194
|
try {
|
|
105
|
-
const
|
|
195
|
+
const usersCol = await col<UserDoc>("users");
|
|
106
196
|
let finalUserId = data.userId;
|
|
197
|
+
const normalizedEmail = data.userEmail !== undefined
|
|
198
|
+
? normalizeUserEmail(data.userEmail)
|
|
199
|
+
: undefined;
|
|
107
200
|
|
|
108
201
|
if (!finalUserId) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
data.
|
|
115
|
-
data.
|
|
116
|
-
data.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
202
|
+
finalUserId = genId();
|
|
203
|
+
await usersCol.put(finalUserId, {
|
|
204
|
+
id: finalUserId,
|
|
205
|
+
name: data.userName || null,
|
|
206
|
+
language: data.userLanguage || null,
|
|
207
|
+
timezone: data.userTimezone || null,
|
|
208
|
+
occupation: data.userOccupation || null,
|
|
209
|
+
notes: data.userNotes || null,
|
|
210
|
+
master_key_hash: null,
|
|
211
|
+
email: normalizedEmail ?? null,
|
|
212
|
+
password_hash: null,
|
|
213
|
+
preferred_cron_channel: "auto",
|
|
214
|
+
created_at: Date.now(),
|
|
215
|
+
});
|
|
121
216
|
log.info("✅ User created with auto-generated ID", { userId: finalUserId });
|
|
122
217
|
} else {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
data.userTimezone || null,
|
|
138
|
-
data.userOccupation || null,
|
|
139
|
-
data.userNotes || null
|
|
140
|
-
);
|
|
218
|
+
const existing = await usersCol.get(finalUserId);
|
|
219
|
+
await usersCol.put(finalUserId, {
|
|
220
|
+
id: finalUserId,
|
|
221
|
+
name: data.userName ?? existing?.doc.name ?? null,
|
|
222
|
+
language: data.userLanguage ?? existing?.doc.language ?? null,
|
|
223
|
+
timezone: data.userTimezone ?? existing?.doc.timezone ?? null,
|
|
224
|
+
occupation: data.userOccupation ?? existing?.doc.occupation ?? null,
|
|
225
|
+
notes: data.userNotes ?? existing?.doc.notes ?? null,
|
|
226
|
+
master_key_hash: existing?.doc.master_key_hash ?? null,
|
|
227
|
+
email: normalizedEmail ?? existing?.doc.email ?? null,
|
|
228
|
+
password_hash: existing?.doc.password_hash ?? null,
|
|
229
|
+
preferred_cron_channel: existing?.doc.preferred_cron_channel ?? "auto",
|
|
230
|
+
created_at: existing?.doc.created_at ?? Date.now(),
|
|
231
|
+
}, existing ? { expectedVersion: existing.version } : undefined);
|
|
141
232
|
}
|
|
142
233
|
|
|
143
234
|
// 2️⃣ Crear identidad base para webchat (sesión única)
|
|
144
235
|
if (data.channelUserId) {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
236
|
+
const identitiesCol = await col<UserIdentityDoc>("userIdentities");
|
|
237
|
+
await identitiesCol.put(`${finalUserId}:webchat`, {
|
|
238
|
+
user_id: finalUserId, channel: "webchat", channel_user_id: data.channelUserId, linked_at: Date.now(),
|
|
239
|
+
});
|
|
149
240
|
log.info("✅ User identity created for webchat", { userId: finalUserId });
|
|
150
241
|
}
|
|
151
242
|
|
|
152
243
|
// 3️⃣ Crear o actualizar agente
|
|
153
244
|
if (data.agentId && data.agentName) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
245
|
+
const agentsCol = await col<AgentDoc>("agents");
|
|
246
|
+
const existingAgent = await agentsCol.get(data.agentId);
|
|
247
|
+
const now = Date.now();
|
|
248
|
+
await agentsCol.put(data.agentId, {
|
|
249
|
+
id: data.agentId,
|
|
250
|
+
user_id: finalUserId,
|
|
251
|
+
name: data.agentName,
|
|
252
|
+
description: data.agentDescription ?? existingAgent?.doc.description ?? null,
|
|
253
|
+
system_prompt: HIVE_SYSTEM_PROMPT,
|
|
254
|
+
tone: data.agentTone ?? existingAgent?.doc.tone ?? null,
|
|
255
|
+
role: "coordinator",
|
|
256
|
+
status: existingAgent?.doc.status ?? "idle",
|
|
257
|
+
enabled: existingAgent?.doc.enabled ?? true,
|
|
258
|
+
provider_id: existingAgent?.doc.provider_id ?? toIndexable(null),
|
|
259
|
+
model_id: existingAgent?.doc.model_id ?? toIndexable(null),
|
|
260
|
+
tools_json: existingAgent?.doc.tools_json ?? null,
|
|
261
|
+
skills_json: existingAgent?.doc.skills_json ?? null,
|
|
262
|
+
parent_id: existingAgent?.doc.parent_id ?? toIndexable(null),
|
|
263
|
+
max_iterations: existingAgent?.doc.max_iterations ?? 10,
|
|
264
|
+
workspace: existingAgent?.doc.workspace ?? null,
|
|
265
|
+
lastTraceAt: existingAgent?.doc.lastTraceAt ?? null,
|
|
266
|
+
created_at: existingAgent?.doc.created_at ?? now,
|
|
267
|
+
updated_at: now,
|
|
268
|
+
}, existingAgent ? { expectedVersion: existingAgent.version } : undefined);
|
|
174
269
|
}
|
|
175
270
|
|
|
176
271
|
return finalUserId;
|
|
@@ -180,12 +275,12 @@ user_id = COALESCE(excluded.user_id, user_id),
|
|
|
180
275
|
}
|
|
181
276
|
}
|
|
182
277
|
|
|
183
|
-
export function activateSkills(userId: string, skillIds: string[]): void {
|
|
278
|
+
export async function activateSkills(userId: string, skillIds: string[]): Promise<void> {
|
|
184
279
|
try {
|
|
185
|
-
const
|
|
186
|
-
// Activar skills seleccionadas
|
|
280
|
+
const skillsCol = await col<SkillDoc>("skills");
|
|
187
281
|
for (const skillId of skillIds) {
|
|
188
|
-
|
|
282
|
+
const existing = await skillsCol.get(skillId);
|
|
283
|
+
if (existing) await skillsCol.put(skillId, { ...existing.doc, active: true }, { expectedVersion: existing.version });
|
|
189
284
|
}
|
|
190
285
|
log.info("✅ Skills activadas:", { skillIds: skillIds.join(", ") });
|
|
191
286
|
} catch (e) {
|
|
@@ -193,25 +288,28 @@ export function activateSkills(userId: string, skillIds: string[]): void {
|
|
|
193
288
|
}
|
|
194
289
|
}
|
|
195
290
|
|
|
196
|
-
export function activateEthics(userId: string, ethicsId: string): void {
|
|
291
|
+
export async function activateEthics(userId: string, ethicsId: string): Promise<void> {
|
|
197
292
|
try {
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
293
|
+
const ethicsCol = await col<EthicsDoc>("ethics");
|
|
294
|
+
const all = await ethicsCol.scan({});
|
|
295
|
+
for (const e of all) {
|
|
296
|
+
const shouldBeActive = e.id === ethicsId;
|
|
297
|
+
if (e.doc.active !== shouldBeActive) {
|
|
298
|
+
await ethicsCol.put(e.id, { ...e.doc, active: shouldBeActive }, { expectedVersion: e.version });
|
|
299
|
+
}
|
|
300
|
+
}
|
|
203
301
|
log.info("✅ Ethics activado:", { ethicsId });
|
|
204
302
|
} catch (e) {
|
|
205
303
|
log.error("⚠️ Error activating ethics:", { error: (e as Error).message });
|
|
206
304
|
}
|
|
207
305
|
}
|
|
208
306
|
|
|
209
|
-
export function activateTools(userId: string, toolIds: string[]): void {
|
|
307
|
+
export async function activateTools(userId: string, toolIds: string[]): Promise<void> {
|
|
210
308
|
try {
|
|
211
|
-
const
|
|
212
|
-
// Activar tools seleccionadas
|
|
309
|
+
const toolsCol = await col<ToolDoc>("tools");
|
|
213
310
|
for (const toolId of toolIds) {
|
|
214
|
-
|
|
311
|
+
const existing = await toolsCol.get(toolId);
|
|
312
|
+
if (existing) await toolsCol.put(toolId, { ...existing.doc, active: true, enabled: true }, { expectedVersion: existing.version });
|
|
215
313
|
}
|
|
216
314
|
log.info("✅ Tools activadas:", { toolIds: toolIds.join(", ") });
|
|
217
315
|
} catch (e) {
|
|
@@ -223,9 +321,9 @@ export function activateTools(userId: string, toolIds: string[]): void {
|
|
|
223
321
|
* Activate all browser tools when Chromium is available
|
|
224
322
|
* Called from gateway initializer when browser service connects successfully
|
|
225
323
|
*/
|
|
226
|
-
export function activateBrowserTools(): void {
|
|
324
|
+
export async function activateBrowserTools(): Promise<void> {
|
|
227
325
|
try {
|
|
228
|
-
const
|
|
326
|
+
const toolsCol = await col<ToolDoc>("tools");
|
|
229
327
|
const browserToolIds = [
|
|
230
328
|
"browser_navigate",
|
|
231
329
|
"browser_screenshot",
|
|
@@ -237,7 +335,8 @@ export function activateBrowserTools(): void {
|
|
|
237
335
|
];
|
|
238
336
|
|
|
239
337
|
for (const toolId of browserToolIds) {
|
|
240
|
-
|
|
338
|
+
const existing = await toolsCol.get(toolId);
|
|
339
|
+
if (existing) await toolsCol.put(toolId, { ...existing.doc, active: true, enabled: true }, { expectedVersion: existing.version });
|
|
241
340
|
}
|
|
242
341
|
log.info("✅ Browser tools activated (Chromium available)");
|
|
243
342
|
} catch (e) {
|
|
@@ -253,43 +352,40 @@ export async function saveProviderConfig(data: {
|
|
|
253
352
|
baseUrl?: string;
|
|
254
353
|
}): Promise<void> {
|
|
255
354
|
try {
|
|
256
|
-
const
|
|
355
|
+
const providersCol = await col<ProviderDoc>("providers");
|
|
356
|
+
const modelsCol = await col<ModelDoc>("models");
|
|
257
357
|
|
|
258
|
-
|
|
259
|
-
|
|
358
|
+
// 1️⃣ Primero: Actualizar provider global con API key del usuario
|
|
359
|
+
const existingProvider = await providersCol.get(data.provider);
|
|
360
|
+
if (existingProvider) {
|
|
361
|
+
await providersCol.put(data.provider, {
|
|
362
|
+
...existingProvider.doc, base_url: data.baseUrl || null, enabled: true, active: true,
|
|
363
|
+
}, { expectedVersion: existingProvider.version });
|
|
364
|
+
}
|
|
260
365
|
|
|
261
366
|
if (data.apiKey) {
|
|
262
|
-
|
|
263
|
-
apiKeyEncrypted = encrypted.encrypted;
|
|
264
|
-
apiKeyIv = encrypted.iv;
|
|
367
|
+
await storeProviderApiKey(data.provider, data.apiKey);
|
|
265
368
|
}
|
|
266
369
|
|
|
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
370
|
log.info("✅ Provider actualizado:", { provider: data.provider });
|
|
279
371
|
|
|
280
372
|
// 2️⃣ Segundo: Activar el modelo seleccionado
|
|
281
373
|
// For Ollama, models are inserted dynamically (not seeded), ensure row exists first
|
|
282
374
|
if (data.provider === "ollama" && data.model) {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
375
|
+
const existingModel = await modelsCol.get(data.model);
|
|
376
|
+
if (!existingModel) {
|
|
377
|
+
await modelsCol.put(data.model, {
|
|
378
|
+
id: data.model, provider_id: "ollama", name: data.model, model_type: "llm",
|
|
379
|
+
context_window: 0, capabilities: null, enabled: true, active: true,
|
|
380
|
+
source: "discovered",
|
|
381
|
+
});
|
|
382
|
+
}
|
|
287
383
|
}
|
|
288
384
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
385
|
+
const modelToActivate = await modelsCol.get(data.model);
|
|
386
|
+
if (modelToActivate) {
|
|
387
|
+
await modelsCol.put(data.model, { ...modelToActivate.doc, enabled: true, active: true }, { expectedVersion: modelToActivate.version });
|
|
388
|
+
}
|
|
293
389
|
|
|
294
390
|
log.info("✅ Model activado:", { model: data.model });
|
|
295
391
|
} catch (e) {
|
|
@@ -298,28 +394,12 @@ VALUES(?, ?, 'ollama', 'llm', 1, 1)
|
|
|
298
394
|
}
|
|
299
395
|
}
|
|
300
396
|
|
|
301
|
-
export function
|
|
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 {
|
|
397
|
+
export async function activateMcpServers(userId: string, mcpIds: string[]): Promise<void> {
|
|
318
398
|
try {
|
|
319
|
-
const
|
|
320
|
-
// Activar MCP servers seleccionados
|
|
399
|
+
const mcpCol = await col<McpServerDoc>("mcpServers");
|
|
321
400
|
for (const mcpId of mcpIds) {
|
|
322
|
-
|
|
401
|
+
const existing = await mcpCol.get(mcpId);
|
|
402
|
+
if (existing) await mcpCol.put(mcpId, { ...existing.doc, active: true, enabled: true }, { expectedVersion: existing.version });
|
|
323
403
|
}
|
|
324
404
|
log.info("✅ MCP servers activados:", { mcpIds: mcpIds.join(", ") });
|
|
325
405
|
} catch (e) {
|
|
@@ -327,8 +407,31 @@ export function activateMcpServers(userId: string, mcpIds: string[]): void {
|
|
|
327
407
|
}
|
|
328
408
|
}
|
|
329
409
|
|
|
410
|
+
/**
|
|
411
|
+
* Seeds every non-coordinator agent with the provider/model the user just
|
|
412
|
+
* configured for the coordinator. Onboarding is the one moment where
|
|
413
|
+
* overwriting is right: the user is (re)choosing the main model, so the whole
|
|
414
|
+
* hive follows it. The boot-time counterpart (`ensureAgentsConfigured`) only
|
|
415
|
+
* fills the blanks.
|
|
416
|
+
*
|
|
417
|
+
* Note: an agent with `model_override_json` (the acceptance verifier asks for
|
|
418
|
+
* a different model family) stops consulting that override once its row has
|
|
419
|
+
* an explicit pair — `resolveAgentModel()` returns the row first.
|
|
420
|
+
*/
|
|
421
|
+
export async function propagateCoordinatorModel(
|
|
422
|
+
userId: string,
|
|
423
|
+
providerId: string,
|
|
424
|
+
modelId: string,
|
|
425
|
+
): Promise<number> {
|
|
426
|
+
const { applyCoordinatorModel } = await import("../agent/agent-catalog");
|
|
427
|
+
const updated = await applyCoordinatorModel({ userId, providerId, modelId, overwrite: true });
|
|
428
|
+
if (updated > 0) {
|
|
429
|
+
log.info(`✅ ${updated} agente(s) sincronizados con el modelo del coordinador`, { providerId, modelId });
|
|
430
|
+
}
|
|
431
|
+
return updated;
|
|
432
|
+
}
|
|
330
433
|
|
|
331
|
-
export function saveAgentConfig(data: {
|
|
434
|
+
export async function saveAgentConfig(data: {
|
|
332
435
|
userId: string;
|
|
333
436
|
agentId?: string;
|
|
334
437
|
agentName: string;
|
|
@@ -336,63 +439,65 @@ export function saveAgentConfig(data: {
|
|
|
336
439
|
modelId: string;
|
|
337
440
|
tone: string;
|
|
338
441
|
description?: string;
|
|
339
|
-
}): string {
|
|
442
|
+
}): Promise<string> {
|
|
340
443
|
try {
|
|
341
|
-
const
|
|
342
|
-
|
|
444
|
+
const providersCol = await col<ProviderDoc>("providers");
|
|
445
|
+
const modelsCol = await col<ModelDoc>("models");
|
|
446
|
+
const agentsCol = await col<AgentDoc>("agents");
|
|
343
447
|
|
|
344
448
|
// Validate FK references — use null if the referenced row doesn't exist
|
|
345
449
|
// (e.g. custom Ollama model IDs are not in the seed models table)
|
|
346
450
|
const rawProviderId = data.providerId || null;
|
|
347
451
|
const rawModelId = data.modelId || null;
|
|
348
|
-
const safeProviderId = rawProviderId &&
|
|
349
|
-
const safeModelId = rawModelId &&
|
|
452
|
+
const safeProviderId = rawProviderId && (await providersCol.get(rawProviderId)) ? rawProviderId : null;
|
|
453
|
+
const safeModelId = rawModelId && (await modelsCol.get(rawModelId)) ? rawModelId : null;
|
|
454
|
+
|
|
455
|
+
let finalAgentId = data.agentId;
|
|
456
|
+
const now = Date.now();
|
|
350
457
|
|
|
351
|
-
// Si no se pasa agentId, dejar que SQLite lo genere automáticamente
|
|
352
458
|
if (!finalAgentId) {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
HIVE_SYSTEM_PROMPT,
|
|
364
|
-
safeProviderId,
|
|
365
|
-
safeModelId
|
|
366
|
-
) as { id: string };
|
|
367
|
-
finalAgentId = result.id;
|
|
459
|
+
finalAgentId = genId();
|
|
460
|
+
await agentsCol.put(finalAgentId, {
|
|
461
|
+
id: finalAgentId, user_id: data.userId, name: data.agentName,
|
|
462
|
+
description: data.description || null, system_prompt: HIVE_SYSTEM_PROMPT,
|
|
463
|
+
tone: data.tone, role: "coordinator", status: "idle", enabled: true,
|
|
464
|
+
provider_id: toIndexable(safeProviderId), model_id: toIndexable(safeModelId),
|
|
465
|
+
tools_json: null, skills_json: null, parent_id: toIndexable(null),
|
|
466
|
+
max_iterations: 10, workspace: null, lastTraceAt: null,
|
|
467
|
+
created_at: now, updated_at: now,
|
|
468
|
+
});
|
|
368
469
|
log.info("✅ Agent created with auto-generated ID", { agentId: finalAgentId });
|
|
369
470
|
} else {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
471
|
+
const existing = await agentsCol.get(finalAgentId);
|
|
472
|
+
await agentsCol.put(finalAgentId, {
|
|
473
|
+
id: finalAgentId,
|
|
474
|
+
user_id: data.userId ?? existing?.doc.user_id,
|
|
475
|
+
name: data.agentName ?? existing?.doc.name,
|
|
476
|
+
description: data.description ?? existing?.doc.description ?? null,
|
|
477
|
+
system_prompt: HIVE_SYSTEM_PROMPT,
|
|
478
|
+
tone: data.tone ?? existing?.doc.tone,
|
|
479
|
+
role: "coordinator",
|
|
480
|
+
status: "idle",
|
|
481
|
+
enabled: true,
|
|
482
|
+
provider_id: toIndexable(safeProviderId) !== "__none__" ? toIndexable(safeProviderId) : (existing?.doc.provider_id ?? toIndexable(null)),
|
|
483
|
+
model_id: toIndexable(safeModelId) !== "__none__" ? toIndexable(safeModelId) : (existing?.doc.model_id ?? toIndexable(null)),
|
|
484
|
+
tools_json: existing?.doc.tools_json ?? null,
|
|
485
|
+
skills_json: existing?.doc.skills_json ?? null,
|
|
486
|
+
parent_id: existing?.doc.parent_id ?? toIndexable(null),
|
|
487
|
+
max_iterations: existing?.doc.max_iterations ?? 10,
|
|
488
|
+
workspace: existing?.doc.workspace ?? null,
|
|
489
|
+
lastTraceAt: existing?.doc.lastTraceAt ?? null,
|
|
490
|
+
created_at: existing?.doc.created_at ?? now,
|
|
491
|
+
updated_at: now,
|
|
492
|
+
}, existing ? { expectedVersion: existing.version } : undefined);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Seed the rest of the hive (catalog personas + any worker) with the
|
|
496
|
+
// coordinator's pair. Skipped while the pair is still incomplete — the CLI
|
|
497
|
+
// onboarding creates the coordinator first and only picks provider/model a
|
|
498
|
+
// few steps later, and that later call re-enters here with both set.
|
|
499
|
+
if (safeProviderId && safeModelId) {
|
|
500
|
+
await propagateCoordinatorModel(data.userId, safeProviderId, safeModelId);
|
|
396
501
|
}
|
|
397
502
|
|
|
398
503
|
return finalAgentId;
|
|
@@ -408,31 +513,25 @@ export async function activateChannel(userId: string, data: {
|
|
|
408
513
|
config?: Record<string, unknown>;
|
|
409
514
|
}): Promise<void> {
|
|
410
515
|
try {
|
|
411
|
-
const
|
|
516
|
+
const channelsCol = await col<ChannelDoc>("channels");
|
|
517
|
+
const existing = await channelsCol.get(data.channelId);
|
|
518
|
+
if (existing) {
|
|
519
|
+
await channelsCol.put(data.channelId, {
|
|
520
|
+
...existing.doc, user_id: userId, active: true, enabled: true, status: "connected",
|
|
521
|
+
}, { expectedVersion: existing.version });
|
|
522
|
+
}
|
|
412
523
|
|
|
413
524
|
if (data.config && Object.keys(data.config).length > 0) {
|
|
414
|
-
|
|
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);
|
|
525
|
+
await storeChannelConfig(data.channelId, data.config);
|
|
427
526
|
}
|
|
428
527
|
|
|
429
528
|
// Create user_identity for the channel if channelUserId provided
|
|
430
529
|
if (data.channelUserId) {
|
|
431
530
|
const channelType = data.channelId; // webchat, telegram, discord, etc.
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
531
|
+
const identitiesCol = await col<UserIdentityDoc>("userIdentities");
|
|
532
|
+
await identitiesCol.put(`${userId}:${channelType}`, {
|
|
533
|
+
user_id: userId, channel: channelType, channel_user_id: data.channelUserId, linked_at: Date.now(),
|
|
534
|
+
});
|
|
436
535
|
log.info("✅ User identity created", { userId, channel: channelType });
|
|
437
536
|
}
|
|
438
537
|
|
|
@@ -452,66 +551,52 @@ export async function saveVoiceConfig(data: {
|
|
|
452
551
|
ttsApiKey?: string;
|
|
453
552
|
}): Promise<void> {
|
|
454
553
|
try {
|
|
455
|
-
const
|
|
554
|
+
const modelsCol = await col<ModelDoc>("models");
|
|
555
|
+
const providersCol = await col<ProviderDoc>("providers");
|
|
556
|
+
const channelsCol = await col<ChannelDoc>("channels");
|
|
456
557
|
|
|
457
558
|
// Activate STT and TTS models
|
|
458
|
-
|
|
459
|
-
|
|
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";
|
|
559
|
+
for (const modelId of [data.sttProvider, data.ttsProvider]) {
|
|
560
|
+
const existing = await modelsCol.get(modelId);
|
|
561
|
+
if (existing) await modelsCol.put(modelId, { ...existing.doc, active: true, enabled: true }, { expectedVersion: existing.version });
|
|
469
562
|
}
|
|
470
563
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}
|
|
478
|
-
ttsProviderId = "qwen";
|
|
479
|
-
}
|
|
564
|
+
// Resolve provider IDs from the DB: the value can be a model id or a provider id
|
|
565
|
+
const resolveVoiceProviderId = async (id: string): Promise<string> => {
|
|
566
|
+
const model = await modelsCol.get(id);
|
|
567
|
+
if (model?.doc.provider_id) return model.doc.provider_id;
|
|
568
|
+
const provider = await providersCol.get(id);
|
|
569
|
+
return provider?.doc.id || "";
|
|
570
|
+
};
|
|
480
571
|
|
|
481
|
-
|
|
572
|
+
const sttProviderId = await resolveVoiceProviderId(data.sttProvider);
|
|
573
|
+
const ttsProviderId = await resolveVoiceProviderId(data.ttsProvider);
|
|
574
|
+
|
|
575
|
+
// Save STT API key to provider if provided.
|
|
576
|
+
// Note: this deliberately does NOT flip the provider's enabled/active flags —
|
|
577
|
+
// groq/openai/gemini/qwen are shared rows between voice (STT/TTS) and LLM chat,
|
|
578
|
+
// and voice's own "configured" check only looks at whether a key is stored, so
|
|
579
|
+
// touching those flags here would silently surface a voice-only key as a fully
|
|
580
|
+
// active LLM chat provider.
|
|
482
581
|
if (data.sttApiKey && sttProviderId) {
|
|
483
|
-
|
|
484
|
-
|
|
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 });
|
|
582
|
+
await storeProviderApiKey(sttProviderId, data.sttApiKey);
|
|
583
|
+
log.info("✅ STT API key guardada en keychain", { provider: sttProviderId });
|
|
493
584
|
}
|
|
494
585
|
|
|
495
|
-
// Save TTS API key to provider if provided
|
|
586
|
+
// Save TTS API key to provider if provided (see note above).
|
|
496
587
|
if (data.ttsApiKey && ttsProviderId) {
|
|
497
|
-
|
|
498
|
-
|
|
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 });
|
|
588
|
+
await storeProviderApiKey(ttsProviderId, data.ttsApiKey);
|
|
589
|
+
log.info("✅ TTS API key guardada en keychain", { provider: ttsProviderId });
|
|
507
590
|
}
|
|
508
591
|
|
|
509
592
|
// Update channel with voice config
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
593
|
+
const existingChannel = await channelsCol.get(data.channelId);
|
|
594
|
+
if (existingChannel) {
|
|
595
|
+
await channelsCol.put(data.channelId, {
|
|
596
|
+
...existingChannel.doc, user_id: data.userId, voice_enabled: data.voiceEnabled,
|
|
597
|
+
stt_provider: data.sttProvider, tts_provider: data.ttsProvider,
|
|
598
|
+
}, { expectedVersion: existingChannel.version });
|
|
599
|
+
}
|
|
515
600
|
|
|
516
601
|
log.info("✅ Voice config saved:", {
|
|
517
602
|
channelId: data.channelId,
|
|
@@ -537,179 +622,121 @@ export async function saveMcpServer(data: {
|
|
|
537
622
|
enabled?: boolean;
|
|
538
623
|
}): Promise<void> {
|
|
539
624
|
try {
|
|
540
|
-
const
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
625
|
+
const mcpId = `${data.userId}:${data.name}`;
|
|
626
|
+
const mcpCol = await col<McpServerDoc>("mcpServers");
|
|
627
|
+
|
|
628
|
+
await mcpCol.put(mcpId, {
|
|
629
|
+
id: mcpId, user_id: data.userId, name: data.name, transport: data.transport,
|
|
630
|
+
command: data.command || null, args: JSON.stringify(data.args || []),
|
|
631
|
+
url: data.url || null, enabled: !!data.enabled, active: !!data.enabled,
|
|
632
|
+
builtin: false, status: "disconnected", tools_count: 0,
|
|
633
|
+
});
|
|
546
634
|
|
|
547
635
|
if (data.env && Object.keys(data.env).length > 0) {
|
|
548
|
-
|
|
549
|
-
envEncrypted = encrypted.encrypted;
|
|
550
|
-
envIv = encrypted.iv;
|
|
636
|
+
await storeMcpEnv(mcpId, data.env);
|
|
551
637
|
}
|
|
552
638
|
|
|
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
639
|
log.info("✅ MCP server saved:", { name: data.name });
|
|
571
640
|
} catch (e) {
|
|
572
641
|
log.error("⚠️ Error saving MCP server:", { error: (e as Error).message });
|
|
573
642
|
}
|
|
574
643
|
}
|
|
575
644
|
|
|
576
|
-
export function saveToolSelection(userId: string, tools: string[]): void {
|
|
645
|
+
export async function saveToolSelection(userId: string, tools: string[]): Promise<void> {
|
|
577
646
|
try {
|
|
578
|
-
const
|
|
579
|
-
|
|
647
|
+
const toolsCol = await col<ToolDoc>("tools");
|
|
580
648
|
for (const tool of tools) {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
UPDATE tools SET active = 1, enabled = 1
|
|
584
|
-
WHERE id = ?
|
|
585
|
-
`).run(tool);
|
|
649
|
+
const existing = await toolsCol.get(tool);
|
|
650
|
+
if (existing) await toolsCol.put(tool, { ...existing.doc, active: true, enabled: true }, { expectedVersion: existing.version });
|
|
586
651
|
}
|
|
587
|
-
|
|
588
652
|
log.info("✅ Tools activadas:", { tools: tools.join(", ") });
|
|
589
653
|
} catch (e) {
|
|
590
654
|
log.error("⚠️ Error saving tools:", { error: (e as Error).message });
|
|
591
655
|
}
|
|
592
656
|
}
|
|
593
657
|
|
|
594
|
-
|
|
658
|
+
async function setActiveEnabled(collection: string, id: string, value: boolean): Promise<void> {
|
|
659
|
+
const c = await col<{ active: boolean; enabled: boolean }>(collection);
|
|
660
|
+
const existing = await c.get(id);
|
|
661
|
+
if (existing) await c.put(id, { ...existing.doc, active: value, enabled: value }, { expectedVersion: existing.version });
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
export async function activateProvider(providerId: string): Promise<void> {
|
|
595
665
|
try {
|
|
596
|
-
|
|
597
|
-
db.query(`
|
|
598
|
-
UPDATE providers SET active = 1, enabled = 1
|
|
599
|
-
WHERE id = ?
|
|
600
|
-
`).run(providerId);
|
|
666
|
+
await setActiveEnabled("providers", providerId, true);
|
|
601
667
|
log.info("✅ Provider activado:", { providerId });
|
|
602
668
|
} catch (e) {
|
|
603
669
|
log.error("⚠️ Error activating provider:", { error: (e as Error).message });
|
|
604
670
|
}
|
|
605
671
|
}
|
|
606
672
|
|
|
607
|
-
export function activateModel(modelId: string): void {
|
|
673
|
+
export async function activateModel(modelId: string): Promise<void> {
|
|
608
674
|
try {
|
|
609
|
-
|
|
610
|
-
db.query(`
|
|
611
|
-
UPDATE models SET active = 1, enabled = 1
|
|
612
|
-
WHERE id = ?
|
|
613
|
-
`).run(modelId);
|
|
675
|
+
await setActiveEnabled("models", modelId, true);
|
|
614
676
|
log.info("✅ Model activado:", { modelId });
|
|
615
677
|
} catch (e) {
|
|
616
678
|
log.error("⚠️ Error activating model:", { error: (e as Error).message });
|
|
617
679
|
}
|
|
618
680
|
}
|
|
619
681
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
export function activateMcpServer(mcpName: string): void {
|
|
682
|
+
export async function activateMcpServer(mcpName: string): Promise<void> {
|
|
623
683
|
try {
|
|
624
|
-
|
|
625
|
-
db.query(`
|
|
626
|
-
UPDATE mcp_servers SET active = 1, enabled = 1
|
|
627
|
-
WHERE id = ?
|
|
628
|
-
`).run(mcpName);
|
|
684
|
+
await setActiveEnabled("mcpServers", mcpName, true);
|
|
629
685
|
log.info("✅ MCP server activado:", { mcpName });
|
|
630
686
|
} catch (e) {
|
|
631
687
|
log.error("⚠️ Error activating MCP server:", { error: (e as Error).message });
|
|
632
688
|
}
|
|
633
689
|
}
|
|
634
690
|
|
|
635
|
-
export function deactivateProvider(providerId: string): void {
|
|
691
|
+
export async function deactivateProvider(providerId: string): Promise<void> {
|
|
636
692
|
try {
|
|
637
|
-
|
|
638
|
-
db.query(`
|
|
639
|
-
UPDATE providers SET active = 0, enabled = 0
|
|
640
|
-
WHERE id = ?
|
|
641
|
-
`).run(providerId);
|
|
693
|
+
await setActiveEnabled("providers", providerId, false);
|
|
642
694
|
log.warn("⚠️ Provider desactivado:", { providerId });
|
|
643
695
|
} catch (e) {
|
|
644
696
|
log.error("⚠️ Error deactivating provider:", { error: (e as Error).message });
|
|
645
697
|
}
|
|
646
698
|
}
|
|
647
699
|
|
|
648
|
-
export function deactivateModel(modelId: string): void {
|
|
700
|
+
export async function deactivateModel(modelId: string): Promise<void> {
|
|
649
701
|
try {
|
|
650
|
-
|
|
651
|
-
db.query(`
|
|
652
|
-
UPDATE models SET active = 0, enabled = 0
|
|
653
|
-
WHERE id = ?
|
|
654
|
-
`).run(modelId);
|
|
702
|
+
await setActiveEnabled("models", modelId, false);
|
|
655
703
|
log.warn("⚠️ Model desactivado:", { modelId });
|
|
656
704
|
} catch (e) {
|
|
657
705
|
log.error("⚠️ Error deactivating model:", { error: (e as Error).message });
|
|
658
706
|
}
|
|
659
707
|
}
|
|
660
708
|
|
|
661
|
-
export function deactivateChannel(channelType: string): void {
|
|
709
|
+
export async function deactivateChannel(channelType: string): Promise<void> {
|
|
662
710
|
try {
|
|
663
|
-
|
|
664
|
-
db.query(`
|
|
665
|
-
UPDATE channels SET active = 0, enabled = 0
|
|
666
|
-
WHERE id = ?
|
|
667
|
-
`).run(channelType);
|
|
711
|
+
await setActiveEnabled("channels", channelType, false);
|
|
668
712
|
log.warn("⚠️ Channel desactivado:", { channelType });
|
|
669
713
|
} catch (e) {
|
|
670
714
|
log.error("⚠️ Error deactivating channel:", { error: (e as Error).message });
|
|
671
715
|
}
|
|
672
716
|
}
|
|
673
717
|
|
|
674
|
-
export function deactivateMcpServer(mcpName: string): void {
|
|
718
|
+
export async function deactivateMcpServer(mcpName: string): Promise<void> {
|
|
675
719
|
try {
|
|
676
|
-
|
|
677
|
-
db.query(`
|
|
678
|
-
UPDATE mcp_servers SET active = 0, enabled = 0
|
|
679
|
-
WHERE id = ?
|
|
680
|
-
`).run(mcpName);
|
|
720
|
+
await setActiveEnabled("mcpServers", mcpName, false);
|
|
681
721
|
log.warn("⚠️ MCP server desactivado:", { mcpName });
|
|
682
722
|
} catch (e) {
|
|
683
723
|
log.error("⚠️ Error deactivating MCP server:", { error: (e as Error).message });
|
|
684
724
|
}
|
|
685
725
|
}
|
|
686
726
|
|
|
687
|
-
export function getAllProviders(): Array<{
|
|
727
|
+
export async function getAllProviders(): Promise<Array<{
|
|
688
728
|
id: string;
|
|
689
729
|
name: string;
|
|
690
730
|
baseUrl: string | null;
|
|
691
731
|
enabled: boolean;
|
|
692
732
|
active: boolean;
|
|
693
|
-
}
|
|
733
|
+
}>> {
|
|
694
734
|
try {
|
|
695
|
-
const
|
|
696
|
-
const
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
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,
|
|
735
|
+
const providersCol = await col<ProviderDoc>("providers");
|
|
736
|
+
const entries = await providersCol.scan({});
|
|
737
|
+
return entries.map((e) => ({
|
|
738
|
+
id: e.doc.id, name: e.doc.name, baseUrl: e.doc.base_url,
|
|
739
|
+
enabled: e.doc.enabled, active: e.doc.active,
|
|
713
740
|
}));
|
|
714
741
|
} catch (e) {
|
|
715
742
|
log.warn("[onboarding] ⚠️ Error getting providers:", (e as Error).message);
|
|
@@ -717,7 +744,7 @@ export function getAllProviders(): Array<{
|
|
|
717
744
|
}
|
|
718
745
|
}
|
|
719
746
|
|
|
720
|
-
export function getAllModels(): Array<{
|
|
747
|
+
export async function getAllModels(): Promise<Array<{
|
|
721
748
|
id: string;
|
|
722
749
|
name: string;
|
|
723
750
|
providerId: string;
|
|
@@ -725,30 +752,14 @@ export function getAllModels(): Array<{
|
|
|
725
752
|
capabilities: string | null;
|
|
726
753
|
enabled: boolean;
|
|
727
754
|
active: boolean;
|
|
728
|
-
}
|
|
755
|
+
}>> {
|
|
729
756
|
try {
|
|
730
|
-
const
|
|
731
|
-
const
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
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,
|
|
757
|
+
const modelsCol = await col<ModelDoc>("models");
|
|
758
|
+
const entries = await modelsCol.scan({});
|
|
759
|
+
return entries.map((e) => ({
|
|
760
|
+
id: e.doc.id, name: e.doc.name, providerId: e.doc.provider_id,
|
|
761
|
+
contextWindow: e.doc.context_window, capabilities: e.doc.capabilities,
|
|
762
|
+
enabled: e.doc.enabled, active: e.doc.active,
|
|
752
763
|
}));
|
|
753
764
|
} catch (e) {
|
|
754
765
|
log.error("⚠️ Error getting models:", { error: (e as Error).message });
|
|
@@ -756,35 +767,20 @@ export function getAllModels(): Array<{
|
|
|
756
767
|
}
|
|
757
768
|
}
|
|
758
769
|
|
|
759
|
-
export function getAllEthics(): Array<{
|
|
770
|
+
export async function getAllEthics(): Promise<Array<{
|
|
760
771
|
id: string;
|
|
761
772
|
name: string;
|
|
762
773
|
description: string | null;
|
|
763
774
|
content: string;
|
|
764
775
|
isDefault: boolean;
|
|
765
776
|
active: boolean;
|
|
766
|
-
}
|
|
777
|
+
}>> {
|
|
767
778
|
try {
|
|
768
|
-
const
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
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,
|
|
779
|
+
const ethicsCol = await col<EthicsDoc>("ethics");
|
|
780
|
+
const entries = await ethicsCol.scan({});
|
|
781
|
+
return entries.map((e) => ({
|
|
782
|
+
id: e.doc.id, name: e.doc.name, description: e.doc.description, content: e.doc.content,
|
|
783
|
+
isDefault: e.doc.is_default, active: e.doc.active,
|
|
788
784
|
}));
|
|
789
785
|
} catch (e) {
|
|
790
786
|
log.error("⚠️ Error getting ethics:", { error: (e as Error).message });
|
|
@@ -792,73 +788,19 @@ export function getAllEthics(): Array<{
|
|
|
792
788
|
}
|
|
793
789
|
}
|
|
794
790
|
|
|
795
|
-
export function
|
|
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<{
|
|
791
|
+
export async function getAllSkills(): Promise<Array<{
|
|
832
792
|
id: string;
|
|
833
793
|
name: string;
|
|
834
794
|
description: string | null;
|
|
835
|
-
source: string;
|
|
836
|
-
isGlobal: boolean;
|
|
837
795
|
enabled: boolean;
|
|
838
796
|
active: boolean;
|
|
839
|
-
}
|
|
797
|
+
}>> {
|
|
840
798
|
try {
|
|
841
|
-
const
|
|
842
|
-
const
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
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,
|
|
799
|
+
const skillsCol = await col<SkillDoc>("skills");
|
|
800
|
+
const entries = await skillsCol.scan({});
|
|
801
|
+
return entries.map((e) => ({
|
|
802
|
+
id: e.doc.id, name: e.doc.name, description: e.doc.description,
|
|
803
|
+
enabled: true, active: e.doc.active,
|
|
862
804
|
}));
|
|
863
805
|
} catch (e) {
|
|
864
806
|
log.error("⚠️ Error getting skills:", { error: (e as Error).message });
|
|
@@ -866,35 +808,20 @@ export function getAllSkills(): Array<{
|
|
|
866
808
|
}
|
|
867
809
|
}
|
|
868
810
|
|
|
869
|
-
export function getAllDbTools(): Array<{
|
|
811
|
+
export async function getAllDbTools(): Promise<Array<{
|
|
870
812
|
id: string;
|
|
871
813
|
name: string;
|
|
872
814
|
description: string | null;
|
|
873
815
|
category: string | null;
|
|
874
816
|
enabled: boolean;
|
|
875
817
|
active: boolean;
|
|
876
|
-
}
|
|
818
|
+
}>> {
|
|
877
819
|
try {
|
|
878
|
-
const
|
|
879
|
-
const
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
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,
|
|
820
|
+
const toolsCol = await col<ToolDoc>("tools");
|
|
821
|
+
const entries = await toolsCol.scan({});
|
|
822
|
+
return entries.map((e) => ({
|
|
823
|
+
id: e.doc.id, name: e.doc.name, description: e.doc.description, category: e.doc.category,
|
|
824
|
+
enabled: e.doc.enabled, active: e.doc.active,
|
|
898
825
|
}));
|
|
899
826
|
} catch (e) {
|
|
900
827
|
log.error("⚠️ Error getting tools:", { error: (e as Error).message });
|
|
@@ -902,7 +829,7 @@ export function getAllDbTools(): Array<{
|
|
|
902
829
|
}
|
|
903
830
|
}
|
|
904
831
|
|
|
905
|
-
export function getAllMcpServers(): Array<{
|
|
832
|
+
export async function getAllMcpServers(): Promise<Array<{
|
|
906
833
|
id: string;
|
|
907
834
|
name: string;
|
|
908
835
|
transport: string;
|
|
@@ -912,34 +839,14 @@ export function getAllMcpServers(): Array<{
|
|
|
912
839
|
builtin: boolean;
|
|
913
840
|
enabled: boolean;
|
|
914
841
|
active: boolean;
|
|
915
|
-
}
|
|
842
|
+
}>> {
|
|
916
843
|
try {
|
|
917
|
-
const
|
|
918
|
-
const
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
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,
|
|
844
|
+
const mcpCol = await col<McpServerDoc>("mcpServers");
|
|
845
|
+
const entries = await mcpCol.scan({});
|
|
846
|
+
return entries.map((e) => ({
|
|
847
|
+
id: e.doc.id, name: e.doc.name, transport: e.doc.transport, command: e.doc.command,
|
|
848
|
+
args: e.doc.args, url: e.doc.url, builtin: e.doc.builtin,
|
|
849
|
+
enabled: e.doc.enabled, active: e.doc.active,
|
|
943
850
|
}));
|
|
944
851
|
} catch (e) {
|
|
945
852
|
log.error("⚠️ Error getting MCP servers:", { error: (e as Error).message });
|
|
@@ -947,35 +854,20 @@ export function getAllMcpServers(): Array<{
|
|
|
947
854
|
}
|
|
948
855
|
}
|
|
949
856
|
|
|
950
|
-
export function getAllChannels(): Array<{
|
|
857
|
+
export async function getAllChannels(): Promise<Array<{
|
|
951
858
|
id: string;
|
|
952
859
|
type: string;
|
|
953
860
|
accountId: string;
|
|
954
861
|
status: string;
|
|
955
862
|
enabled: boolean;
|
|
956
863
|
active: boolean;
|
|
957
|
-
}
|
|
864
|
+
}>> {
|
|
958
865
|
try {
|
|
959
|
-
const
|
|
960
|
-
const
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
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,
|
|
866
|
+
const channelsCol = await col<ChannelDoc>("channels");
|
|
867
|
+
const entries = await channelsCol.scan({});
|
|
868
|
+
return entries.map((e) => ({
|
|
869
|
+
id: e.doc.id, type: e.doc.type, accountId: e.doc.id,
|
|
870
|
+
status: e.doc.status, enabled: e.doc.enabled, active: e.doc.active,
|
|
979
871
|
}));
|
|
980
872
|
} catch (e) {
|
|
981
873
|
log.warn("[onboarding] ⚠️ Error getting channels:", (e as Error).message);
|
|
@@ -983,29 +875,17 @@ export function getAllChannels(): Array<{
|
|
|
983
875
|
}
|
|
984
876
|
}
|
|
985
877
|
|
|
986
|
-
export function getActiveTools(): Array<{
|
|
878
|
+
export async function getActiveTools(): Promise<Array<{
|
|
987
879
|
id: string;
|
|
988
880
|
name: string;
|
|
989
881
|
description: string | null;
|
|
990
882
|
category: string | null;
|
|
991
|
-
}
|
|
883
|
+
}>> {
|
|
992
884
|
try {
|
|
993
|
-
const
|
|
994
|
-
const
|
|
995
|
-
|
|
996
|
-
|
|
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,
|
|
885
|
+
const toolsCol = await col<ToolDoc>("tools");
|
|
886
|
+
const entries = await toolsCol.scan({});
|
|
887
|
+
return entries.filter((e) => e.doc.active).map((e) => ({
|
|
888
|
+
id: e.doc.id, name: e.doc.name, description: e.doc.description, category: e.doc.category,
|
|
1009
889
|
}));
|
|
1010
890
|
} catch (e) {
|
|
1011
891
|
log.error("⚠️ Error getting active tools:", { error: (e as Error).message });
|
|
@@ -1013,18 +893,15 @@ export function getActiveTools(): Array<{
|
|
|
1013
893
|
}
|
|
1014
894
|
}
|
|
1015
895
|
|
|
1016
|
-
export function getOnboardingProgress(userId: string): OnboardingSection | null {
|
|
896
|
+
export async function getOnboardingProgress(userId: string): Promise<OnboardingSection | null> {
|
|
1017
897
|
try {
|
|
1018
|
-
const
|
|
1019
|
-
const
|
|
1020
|
-
|
|
1021
|
-
).get(userId);
|
|
1022
|
-
|
|
1023
|
-
if (result) {
|
|
898
|
+
const progressCol = await col<OnboardingProgressDoc>("onboardingProgress");
|
|
899
|
+
const entry = await progressCol.get(userId);
|
|
900
|
+
if (entry) {
|
|
1024
901
|
return {
|
|
1025
|
-
step:
|
|
902
|
+
step: entry.doc.step as OnboardingSection["step"],
|
|
1026
903
|
userId,
|
|
1027
|
-
data: JSON.parse(
|
|
904
|
+
data: JSON.parse(entry.doc.data),
|
|
1028
905
|
completedAt: Date.now(),
|
|
1029
906
|
};
|
|
1030
907
|
}
|
|
@@ -1034,13 +911,12 @@ export function getOnboardingProgress(userId: string): OnboardingSection | null
|
|
|
1034
911
|
}
|
|
1035
912
|
}
|
|
1036
913
|
|
|
1037
|
-
export function saveOnboardingProgress(section: OnboardingSection): void {
|
|
914
|
+
export async function saveOnboardingProgress(section: OnboardingSection): Promise<void> {
|
|
1038
915
|
try {
|
|
1039
|
-
const
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
`).run(section.userId, section.userId, section.step, JSON.stringify(section.data));
|
|
916
|
+
const progressCol = await col<OnboardingProgressDoc>("onboardingProgress");
|
|
917
|
+
await progressCol.put(section.userId, {
|
|
918
|
+
user_id: section.userId, step: section.step, data: JSON.stringify(section.data),
|
|
919
|
+
});
|
|
1044
920
|
} catch (e) {
|
|
1045
921
|
log.error("⚠️ Error saving progress:", { error: (e as Error).message });
|
|
1046
922
|
}
|
|
@@ -1054,27 +930,15 @@ export async function getUserProviders(userId: string): Promise<Array<{
|
|
|
1054
930
|
enabled: boolean;
|
|
1055
931
|
}>> {
|
|
1056
932
|
try {
|
|
1057
|
-
const
|
|
1058
|
-
const
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
id
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
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,
|
|
933
|
+
const providersCol = await col<ProviderDoc>("providers");
|
|
934
|
+
const entries = await providersCol.scan({});
|
|
935
|
+
return Promise.all(entries.map(async (e) => ({
|
|
936
|
+
id: e.doc.id,
|
|
937
|
+
name: e.doc.name,
|
|
938
|
+
// Secrets are keyed by provider id ("openai"), never by display name ("OpenAI")
|
|
939
|
+
apiKey: (await loadProviderApiKey(e.doc.id)) || null,
|
|
940
|
+
baseUrl: e.doc.base_url,
|
|
941
|
+
enabled: e.doc.enabled,
|
|
1078
942
|
})));
|
|
1079
943
|
} catch (e) {
|
|
1080
944
|
log.warn("[onboarding] ⚠️ Error getting providers:", (e as Error).message);
|
|
@@ -1090,27 +954,14 @@ export async function getUserChannels(userId: string): Promise<Array<{
|
|
|
1090
954
|
enabled: boolean;
|
|
1091
955
|
}>> {
|
|
1092
956
|
try {
|
|
1093
|
-
const
|
|
1094
|
-
const
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
enabled:
|
|
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,
|
|
957
|
+
const channelsCol = await col<ChannelDoc>("channels");
|
|
958
|
+
const entries = await channelsCol.findBy("user_id", userId);
|
|
959
|
+
return Promise.all(entries.map(async (e) => ({
|
|
960
|
+
id: e.doc.type,
|
|
961
|
+
type: e.doc.type,
|
|
962
|
+
accountId: e.doc.id,
|
|
963
|
+
config: await loadChannelConfig(e.doc.id),
|
|
964
|
+
enabled: e.doc.enabled,
|
|
1114
965
|
})));
|
|
1115
966
|
} catch (e) {
|
|
1116
967
|
log.warn("[onboarding] ⚠️ Error getting channels:", (e as Error).message);
|
|
@@ -1118,32 +969,20 @@ export async function getUserChannels(userId: string): Promise<Array<{
|
|
|
1118
969
|
}
|
|
1119
970
|
}
|
|
1120
971
|
|
|
1121
|
-
export function getUserAgents(userId: string): Array<{
|
|
972
|
+
export async function getUserAgents(userId: string): Promise<Array<{
|
|
1122
973
|
id: string;
|
|
1123
974
|
name: string;
|
|
1124
975
|
providerId: string | null;
|
|
1125
976
|
modelId: string | null;
|
|
1126
977
|
tone: string;
|
|
1127
|
-
}
|
|
978
|
+
}>> {
|
|
1128
979
|
try {
|
|
1129
|
-
const
|
|
1130
|
-
const
|
|
1131
|
-
|
|
1132
|
-
name:
|
|
1133
|
-
|
|
1134
|
-
|
|
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",
|
|
980
|
+
const agentsCol = await col<AgentDoc>("agents");
|
|
981
|
+
const entries = await agentsCol.findBy("user_id", userId);
|
|
982
|
+
return entries.map((e) => ({
|
|
983
|
+
id: e.doc.id, name: e.doc.name,
|
|
984
|
+
providerId: fromIndexable(e.doc.provider_id), modelId: fromIndexable(e.doc.model_id),
|
|
985
|
+
tone: e.doc.tone || "friendly",
|
|
1147
986
|
}));
|
|
1148
987
|
} catch (e) {
|
|
1149
988
|
log.error("⚠️ Error getting agents:", { error: (e as Error).message });
|
|
@@ -1159,11 +998,11 @@ export function getUserAgents(userId: string): Array<{
|
|
|
1159
998
|
* Hive is designed around a single-user model, so this returns the first user found.
|
|
1160
999
|
* @returns The user ID or null if no users exist
|
|
1161
1000
|
*/
|
|
1162
|
-
export function getSingleUserId(): string | null {
|
|
1001
|
+
export async function getSingleUserId(): Promise<string | null> {
|
|
1163
1002
|
try {
|
|
1164
|
-
const
|
|
1165
|
-
const
|
|
1166
|
-
return
|
|
1003
|
+
const usersCol = await col<UserDoc>("users");
|
|
1004
|
+
const entries = await usersCol.scan({ limit: 1 });
|
|
1005
|
+
return entries[0]?.id || null;
|
|
1167
1006
|
} catch (e) {
|
|
1168
1007
|
log.warn("[getSingleUserId] ⚠️ Error getting user ID:", (e as Error).message);
|
|
1169
1008
|
return null;
|
|
@@ -1175,11 +1014,11 @@ export function getSingleUserId(): string | null {
|
|
|
1175
1014
|
* The coordinator is the agent with role = 'coordinator'.
|
|
1176
1015
|
* @returns The coordinator agent ID or null if not found
|
|
1177
1016
|
*/
|
|
1178
|
-
export function getCoordinatorAgentId(): string | null {
|
|
1017
|
+
export async function getCoordinatorAgentId(): Promise<string | null> {
|
|
1179
1018
|
try {
|
|
1180
|
-
const
|
|
1181
|
-
const
|
|
1182
|
-
return
|
|
1019
|
+
const agentsCol = await col<AgentDoc>("agents");
|
|
1020
|
+
const entries = await agentsCol.findBy("role", "coordinator", { limit: 1 });
|
|
1021
|
+
return entries[0]?.id || null;
|
|
1183
1022
|
} catch (e) {
|
|
1184
1023
|
log.warn("[getCoordinatorAgentId] ⚠️ Error getting coordinator agent ID:", (e as Error).message);
|
|
1185
1024
|
return null;
|
|
@@ -1192,13 +1031,12 @@ export function getCoordinatorAgentId(): string | null {
|
|
|
1192
1031
|
* @param channelUserId The channel-specific user ID (e.g., Telegram chat_id)
|
|
1193
1032
|
* @returns The Hive user ID or null if not found
|
|
1194
1033
|
*/
|
|
1195
|
-
export function getUserIdFromChannelIdentity(channel: string, channelUserId: string): string | null {
|
|
1034
|
+
export async function getUserIdFromChannelIdentity(channel: string, channelUserId: string): Promise<string | null> {
|
|
1196
1035
|
try {
|
|
1197
|
-
const
|
|
1198
|
-
const
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
return result?.user_id || null;
|
|
1036
|
+
const identitiesCol = await col<UserIdentityDoc>("userIdentities");
|
|
1037
|
+
const all = await identitiesCol.scan({});
|
|
1038
|
+
const match = all.find((e) => e.doc.channel === channel && e.doc.channel_user_id === channelUserId);
|
|
1039
|
+
return match?.doc.user_id || null;
|
|
1202
1040
|
} catch (e) {
|
|
1203
1041
|
log.warn("[getUserIdFromChannelIdentity] ⚠️ Error getting user ID from channel identity:", (e as Error).message);
|
|
1204
1042
|
return null;
|
|
@@ -1208,18 +1046,18 @@ export function getUserIdFromChannelIdentity(channel: string, channelUserId: str
|
|
|
1208
1046
|
/**
|
|
1209
1047
|
* Resolve the user ID from various sources with priority:
|
|
1210
1048
|
* 1. Explicit userId parameter
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
export function resolveUserId(
|
|
1049
|
+
* 2. Channel identity lookup (if channel and channelUserId provided)
|
|
1050
|
+
* 3. Single user from database
|
|
1051
|
+
* 4. Null (no user found)
|
|
1052
|
+
*/
|
|
1053
|
+
export async function resolveUserId(
|
|
1216
1054
|
opts: {
|
|
1217
1055
|
userId?: string | null;
|
|
1218
1056
|
threadId?: string | null;
|
|
1219
1057
|
channel?: string | null;
|
|
1220
1058
|
channelUserId?: string | null;
|
|
1221
1059
|
}
|
|
1222
|
-
): string | null {
|
|
1060
|
+
): Promise<string | null> {
|
|
1223
1061
|
// Priority 1: Explicit userId
|
|
1224
1062
|
if (opts.userId) {
|
|
1225
1063
|
return opts.userId;
|
|
@@ -1227,14 +1065,14 @@ export function resolveUserId(
|
|
|
1227
1065
|
|
|
1228
1066
|
// Priority 2: Channel identity lookup
|
|
1229
1067
|
if (opts.channel && opts.channelUserId) {
|
|
1230
|
-
const userId = getUserIdFromChannelIdentity(opts.channel, opts.channelUserId);
|
|
1068
|
+
const userId = await getUserIdFromChannelIdentity(opts.channel, opts.channelUserId);
|
|
1231
1069
|
if (userId) {
|
|
1232
1070
|
return userId;
|
|
1233
1071
|
}
|
|
1234
1072
|
}
|
|
1235
1073
|
|
|
1236
1074
|
// Priority 3: Single user from database
|
|
1237
|
-
const singleUserId = getSingleUserId();
|
|
1075
|
+
const singleUserId = await getSingleUserId();
|
|
1238
1076
|
if (singleUserId) {
|
|
1239
1077
|
return singleUserId;
|
|
1240
1078
|
}
|
|
@@ -1249,25 +1087,19 @@ export function resolveUserId(
|
|
|
1249
1087
|
* 2. First enabled agent
|
|
1250
1088
|
* 3. Null (no agent found)
|
|
1251
1089
|
*/
|
|
1252
|
-
export function getDefaultAgentId(): string | null {
|
|
1090
|
+
export async function getDefaultAgentId(): Promise<string | null> {
|
|
1253
1091
|
try {
|
|
1254
|
-
const
|
|
1092
|
+
const agentsCol = await col<AgentDoc>("agents");
|
|
1255
1093
|
|
|
1256
1094
|
// Try coordinator first
|
|
1257
|
-
const
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
if (coordinator?.id) {
|
|
1262
|
-
return coordinator.id;
|
|
1263
|
-
}
|
|
1095
|
+
const coordinators = await agentsCol.findBy("role", "coordinator");
|
|
1096
|
+
const enabledCoordinator = coordinators.find((e) => e.doc.enabled);
|
|
1097
|
+
if (enabledCoordinator) return enabledCoordinator.id;
|
|
1264
1098
|
|
|
1265
1099
|
// Fallback to first enabled agent
|
|
1266
|
-
const
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
return firstAgent?.id || null;
|
|
1100
|
+
const all = await agentsCol.scan({});
|
|
1101
|
+
const firstEnabled = all.find((e) => e.doc.enabled);
|
|
1102
|
+
return firstEnabled?.id || null;
|
|
1271
1103
|
} catch (e) {
|
|
1272
1104
|
log.warn("[getDefaultAgentId] ⚠️ Error getting default agent ID:", (e as Error).message);
|
|
1273
1105
|
return null;
|
|
@@ -1281,7 +1113,7 @@ export function getDefaultAgentId(): string | null {
|
|
|
1281
1113
|
* 3. First enabled agent from database
|
|
1282
1114
|
* 4. Null (no agent found)
|
|
1283
1115
|
*/
|
|
1284
|
-
export function resolveAgentId(agentId?: string | null): string | null {
|
|
1116
|
+
export async function resolveAgentId(agentId?: string | null): Promise<string | null> {
|
|
1285
1117
|
// Priority 1: Explicit agentId
|
|
1286
1118
|
if (agentId) {
|
|
1287
1119
|
return agentId;
|
|
@@ -1294,11 +1126,11 @@ export function resolveAgentId(agentId?: string | null): string | null {
|
|
|
1294
1126
|
/**
|
|
1295
1127
|
* Get user preferences (notes) for a given user ID
|
|
1296
1128
|
*/
|
|
1297
|
-
export function getUserPreferences(userId: string): string | null {
|
|
1129
|
+
export async function getUserPreferences(userId: string): Promise<string | null> {
|
|
1298
1130
|
try {
|
|
1299
|
-
const
|
|
1300
|
-
const
|
|
1301
|
-
return
|
|
1131
|
+
const usersCol = await col<UserDoc>("users");
|
|
1132
|
+
const entry = await usersCol.get(userId);
|
|
1133
|
+
return entry?.doc.notes || null;
|
|
1302
1134
|
} catch (e) {
|
|
1303
1135
|
log.warn("[getUserPreferences] ⚠️ Error getting user preferences:", (e as Error).message);
|
|
1304
1136
|
return null;
|
|
@@ -1308,7 +1140,7 @@ export function getUserPreferences(userId: string): string | null {
|
|
|
1308
1140
|
/**
|
|
1309
1141
|
* Get agent configuration by ID
|
|
1310
1142
|
*/
|
|
1311
|
-
export function getAgentConfig(agentId: string): {
|
|
1143
|
+
export async function getAgentConfig(agentId: string): Promise<{
|
|
1312
1144
|
id: string;
|
|
1313
1145
|
user_id: string;
|
|
1314
1146
|
name: string;
|
|
@@ -1320,284 +1152,20 @@ export function getAgentConfig(agentId: string): {
|
|
|
1320
1152
|
tools_json: string | null;
|
|
1321
1153
|
skills_json: string | null;
|
|
1322
1154
|
max_iterations: number;
|
|
1323
|
-
} | null {
|
|
1155
|
+
} | null> {
|
|
1324
1156
|
try {
|
|
1325
|
-
const
|
|
1326
|
-
const
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
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;
|
|
1157
|
+
const agentsCol = await col<AgentDoc>("agents");
|
|
1158
|
+
const entry = await agentsCol.get(agentId);
|
|
1159
|
+
if (!entry) return null;
|
|
1160
|
+
return {
|
|
1161
|
+
id: entry.doc.id, user_id: entry.doc.user_id, name: entry.doc.name,
|
|
1162
|
+
description: entry.doc.description, system_prompt: entry.doc.system_prompt, tone: entry.doc.tone,
|
|
1163
|
+
provider_id: fromIndexable(entry.doc.provider_id), model_id: fromIndexable(entry.doc.model_id),
|
|
1164
|
+
tools_json: entry.doc.tools_json, skills_json: entry.doc.skills_json,
|
|
1165
|
+
max_iterations: entry.doc.max_iterations,
|
|
1166
|
+
};
|
|
1345
1167
|
} catch (e) {
|
|
1346
1168
|
log.warn("[getAgentConfig] ⚠️ Error getting agent config:", (e as Error).message);
|
|
1347
1169
|
return null;
|
|
1348
1170
|
}
|
|
1349
1171
|
}
|
|
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
|
-
}
|