@johpaz/hive-sdk 0.0.12 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/.github/CODEOWNERS +9 -0
  2. package/.github/workflows/publish.yml +89 -0
  3. package/.github/workflows/version-bump.yml +102 -0
  4. package/CHANGELOG.md +38 -0
  5. package/README.md +158 -0
  6. package/bun.lock +543 -0
  7. package/bunfig.toml +7 -0
  8. package/docs/API-AGENTS.md +316 -0
  9. package/docs/API-CONTEXT-COMPILER.md +252 -0
  10. package/docs/API-DAG-SCHEDULER.md +273 -0
  11. package/docs/API-TOOLS-SKILLS-CHANNELS.md +293 -0
  12. package/docs/API-WORKERS-EVENTS.md +152 -0
  13. package/docs/INDEX.md +141 -0
  14. package/docs/README.md +68 -0
  15. package/package.json +54 -105
  16. package/packages/cli/package.json +17 -0
  17. package/packages/cli/src/commands/init.ts +56 -0
  18. package/packages/cli/src/commands/run.ts +45 -0
  19. package/packages/cli/src/commands/test.ts +42 -0
  20. package/packages/cli/src/commands/trace.ts +55 -0
  21. package/packages/cli/src/index.ts +43 -0
  22. package/packages/core/package.json +58 -0
  23. package/packages/core/src/ace/Curator.ts +158 -0
  24. package/packages/core/src/ace/Reflector.ts +200 -0
  25. package/packages/core/src/ace/Tracer.ts +100 -0
  26. package/packages/core/src/ace/index.ts +4 -0
  27. package/packages/core/src/agent/AgentRunner.ts +699 -0
  28. package/packages/core/src/agent/Compaction.ts +221 -0
  29. package/packages/core/src/agent/ContextCompiler.ts +567 -0
  30. package/packages/core/src/agent/ContextGuard.ts +91 -0
  31. package/packages/core/src/agent/ConversationStore.ts +244 -0
  32. package/packages/core/src/agent/Hooks.ts +166 -0
  33. package/packages/core/src/agent/NativeTools.ts +31 -0
  34. package/packages/core/src/agent/PromptBuilder.ts +169 -0
  35. package/packages/core/src/agent/Service.ts +267 -0
  36. package/packages/core/src/agent/StuckLoop.ts +133 -0
  37. package/packages/core/src/agent/index.ts +12 -0
  38. package/packages/core/src/agent/providers/LLMClient.ts +149 -0
  39. package/packages/core/src/agent/providers/anthropic.ts +212 -0
  40. package/packages/core/src/agent/providers/gemini.ts +215 -0
  41. package/packages/core/src/agent/providers/index.ts +199 -0
  42. package/packages/core/src/agent/providers/interface.ts +195 -0
  43. package/packages/core/src/agent/providers/ollama.ts +175 -0
  44. package/packages/core/src/agent/providers/openai-compat.ts +231 -0
  45. package/packages/core/src/agent/providers.ts +1 -0
  46. package/packages/core/src/agent/selectors/PlaybookSelector.ts +147 -0
  47. package/packages/core/src/agent/selectors/SkillSelector.ts +478 -0
  48. package/packages/core/src/agent/selectors/ToolSelector.ts +577 -0
  49. package/packages/core/src/agent/selectors/index.ts +6 -0
  50. package/packages/core/src/api/createAgent.test.ts +48 -0
  51. package/packages/core/src/api/createAgent.ts +122 -0
  52. package/packages/core/src/api/index.ts +2 -0
  53. package/packages/core/src/canvas/CanvasManager.ts +390 -0
  54. package/packages/core/src/canvas/a2ui-tools.ts +255 -0
  55. package/packages/core/src/canvas/canvas-tools.ts +448 -0
  56. package/packages/core/src/canvas/emitter.ts +149 -0
  57. package/packages/core/src/canvas/index.ts +6 -0
  58. package/packages/core/src/config/index.ts +2 -0
  59. package/packages/core/src/config/loader.ts +554 -0
  60. package/packages/core/src/ethics/EthicsGuard.test.ts +54 -0
  61. package/packages/core/src/ethics/EthicsGuard.ts +66 -0
  62. package/packages/core/src/ethics/index.ts +2 -0
  63. package/packages/core/src/gateway/channel-notify.test.ts +14 -0
  64. package/packages/core/src/gateway/channel-notify.ts +12 -0
  65. package/packages/core/src/gateway/index.ts +1 -0
  66. package/packages/core/src/index.ts +37 -0
  67. package/packages/core/src/mcp/MCPClient.ts +439 -0
  68. package/packages/core/src/mcp/MCPToolAdapter.ts +176 -0
  69. package/packages/core/src/mcp/config.ts +13 -0
  70. package/packages/core/src/mcp/hot-reload.ts +147 -0
  71. package/packages/core/src/mcp/index.ts +11 -0
  72. package/packages/core/src/mcp/logger.ts +42 -0
  73. package/packages/core/src/mcp/singleton.ts +21 -0
  74. package/packages/core/src/mcp/transports/index.ts +67 -0
  75. package/packages/core/src/mcp/transports/sse.ts +241 -0
  76. package/packages/core/src/mcp/transports/websocket.ts +159 -0
  77. package/packages/core/src/memory/Scratchpad.test.ts +47 -0
  78. package/packages/core/src/memory/Scratchpad.ts +37 -0
  79. package/packages/core/src/memory/Storage.ts +6 -0
  80. package/packages/core/src/memory/index.ts +2 -0
  81. package/packages/core/src/multimodal/VisionService.ts +293 -0
  82. package/packages/core/src/multimodal/index.ts +2 -0
  83. package/packages/core/src/multimodal/types.ts +28 -0
  84. package/packages/core/src/security/Pairing.ts +250 -0
  85. package/packages/core/src/security/RateLimit.ts +270 -0
  86. package/packages/core/src/security/index.ts +4 -0
  87. package/packages/core/src/skills/SkillLoader.ts +388 -0
  88. package/packages/core/src/skills/bundled-data.generated.ts +3332 -0
  89. package/packages/core/src/skills/defineSkill.ts +18 -0
  90. package/packages/core/src/skills/index.ts +4 -0
  91. package/packages/core/src/state/index.ts +2 -0
  92. package/packages/core/src/state/store.ts +312 -0
  93. package/packages/core/src/storage/SQLiteStorage.ts +407 -0
  94. package/packages/core/src/storage/crypto.ts +101 -0
  95. package/packages/core/src/storage/index.ts +10 -0
  96. package/packages/core/src/storage/onboarding.ts +1603 -0
  97. package/packages/core/src/storage/schema.ts +689 -0
  98. package/packages/core/src/storage/seed.ts +740 -0
  99. package/packages/core/src/storage/usage.ts +374 -0
  100. package/packages/core/src/swarm/AgentBus.ts +460 -0
  101. package/packages/core/src/swarm/AgentExecutor.ts +53 -0
  102. package/packages/core/src/swarm/Coordinator.ts +251 -0
  103. package/packages/core/src/swarm/EventBridge.ts +122 -0
  104. package/packages/core/src/swarm/EventBus.ts +169 -0
  105. package/packages/core/src/swarm/TaskGraph.ts +192 -0
  106. package/packages/core/src/swarm/TaskNode.ts +97 -0
  107. package/packages/core/src/swarm/TaskResult.ts +22 -0
  108. package/packages/core/src/swarm/WorkerPool.ts +236 -0
  109. package/packages/core/src/swarm/errors.ts +37 -0
  110. package/packages/core/src/swarm/index.ts +30 -0
  111. package/packages/core/src/swarm/presets/HiveLearnPreset.ts +99 -0
  112. package/packages/core/src/swarm/presets/ResearchPreset.ts +97 -0
  113. package/packages/core/src/swarm/presets/index.ts +4 -0
  114. package/packages/core/src/swarm/strategies/ParallelStrategy.ts +21 -0
  115. package/packages/core/src/swarm/strategies/PriorityStrategy.ts +46 -0
  116. package/packages/core/src/swarm/strategies/index.ts +3 -0
  117. package/packages/core/src/swarm/types.ts +164 -0
  118. package/packages/core/src/tools/ToolExecutor.ts +58 -0
  119. package/packages/core/src/tools/ToolRegistry.test.ts +98 -0
  120. package/packages/core/src/tools/ToolRegistry.ts +61 -0
  121. package/packages/core/src/tools/agents/get-available-models.ts +118 -0
  122. package/packages/core/src/tools/agents/index.ts +715 -0
  123. package/packages/core/src/tools/bridge-events.ts +26 -0
  124. package/packages/core/src/tools/canvas/index.ts +375 -0
  125. package/packages/core/src/tools/cli/index.ts +142 -0
  126. package/packages/core/src/tools/codebridge/index.ts +342 -0
  127. package/packages/core/src/tools/core/index.ts +476 -0
  128. package/packages/core/src/tools/cron/index.ts +626 -0
  129. package/packages/core/src/tools/filesystem/fs-delete.ts +78 -0
  130. package/packages/core/src/tools/filesystem/fs-edit.ts +106 -0
  131. package/packages/core/src/tools/filesystem/fs-exists.ts +63 -0
  132. package/packages/core/src/tools/filesystem/fs-glob.ts +108 -0
  133. package/packages/core/src/tools/filesystem/fs-list.ts +129 -0
  134. package/packages/core/src/tools/filesystem/fs-read.ts +72 -0
  135. package/packages/core/src/tools/filesystem/fs-write.ts +67 -0
  136. package/packages/core/src/tools/filesystem/index.ts +34 -0
  137. package/packages/core/src/tools/filesystem/workspace-guard.ts +62 -0
  138. package/packages/core/src/tools/index.ts +231 -0
  139. package/packages/core/src/tools/meeting/index.ts +363 -0
  140. package/packages/core/src/tools/office/index.ts +47 -0
  141. package/packages/core/src/tools/office/office-escribir-docx.ts +192 -0
  142. package/packages/core/src/tools/office/office-escribir-pdf.ts +172 -0
  143. package/packages/core/src/tools/office/office-escribir-pptx.ts +174 -0
  144. package/packages/core/src/tools/office/office-escribir-xlsx.ts +116 -0
  145. package/packages/core/src/tools/office/office-leer-docx.ts +93 -0
  146. package/packages/core/src/tools/office/office-leer-pdf.ts +114 -0
  147. package/packages/core/src/tools/office/office-leer-pptx.ts +136 -0
  148. package/packages/core/src/tools/office/office-leer-xlsx.ts +124 -0
  149. package/packages/core/src/tools/projects/index.ts +37 -0
  150. package/packages/core/src/tools/projects/project-create.ts +94 -0
  151. package/packages/core/src/tools/projects/project-done.ts +66 -0
  152. package/packages/core/src/tools/projects/project-fail.ts +66 -0
  153. package/packages/core/src/tools/projects/project-list.ts +96 -0
  154. package/packages/core/src/tools/projects/project-update.ts +72 -0
  155. package/packages/core/src/tools/projects/task-create.ts +68 -0
  156. package/packages/core/src/tools/projects/task-evaluate.ts +93 -0
  157. package/packages/core/src/tools/projects/task-update.ts +93 -0
  158. package/packages/core/src/tools/types.ts +39 -0
  159. package/packages/core/src/tools/voice/index.ts +104 -0
  160. package/packages/core/src/tools/web/browser-click.ts +78 -0
  161. package/packages/core/src/tools/web/browser-extract.ts +139 -0
  162. package/packages/core/src/tools/web/browser-navigate.ts +106 -0
  163. package/packages/core/src/tools/web/browser-screenshot.ts +87 -0
  164. package/packages/core/src/tools/web/browser-script.ts +88 -0
  165. package/packages/core/src/tools/web/browser-service.ts +554 -0
  166. package/packages/core/src/tools/web/browser-type.ts +101 -0
  167. package/packages/core/src/tools/web/browser-wait.ts +136 -0
  168. package/packages/core/src/tools/web/index.ts +41 -0
  169. package/packages/core/src/tools/web/web-fetch.ts +78 -0
  170. package/packages/core/src/tools/web/web-search.ts +123 -0
  171. package/packages/core/src/utils/benchmark.ts +80 -0
  172. package/packages/core/src/utils/crypto.ts +73 -0
  173. package/packages/core/src/utils/date.ts +42 -0
  174. package/packages/core/src/utils/index.ts +10 -0
  175. package/packages/core/src/utils/logger.ts +389 -0
  176. package/packages/core/src/utils/retry.ts +70 -0
  177. package/packages/core/src/utils/toon.ts +253 -0
  178. package/packages/core/src/voice/index.ts +656 -0
  179. package/test/setup-db.ts +216 -0
  180. package/tsconfig.json +39 -0
  181. package/src/agents.ts +0 -1
  182. package/src/canvas.ts +0 -1
  183. package/src/channels.ts +0 -1
  184. package/src/config.ts +0 -1
  185. package/src/events.ts +0 -1
  186. package/src/gateway.ts +0 -1
  187. package/src/index.ts +0 -304
  188. package/src/mcp.ts +0 -1
  189. package/src/multimodal.ts +0 -1
  190. package/src/scheduler.ts +0 -1
  191. package/src/security.ts +0 -1
  192. package/src/skills.ts +0 -1
  193. package/src/state.ts +0 -1
  194. package/src/storage.ts +0 -1
  195. package/src/tools.ts +0 -1
  196. package/src/tts.ts +0 -1
  197. package/src/types.ts +0 -82
  198. package/src/utils.ts +0 -1
  199. package/src/voice.ts +0 -1
@@ -0,0 +1,407 @@
1
+ import { Database } from "bun:sqlite";
2
+ import { logger } from "../utils/logger.ts";
3
+ import * as path from "node:path";
4
+ import { existsSync, mkdirSync } from "node:fs";
5
+ import { getHiveDir } from "../config/loader.ts";
6
+ import { SCHEMA, PROJECTS_SCHEMA, CONTEXT_ENGINE_SCHEMA, MEETING_SCHEMA } from "./schema.ts";
7
+
8
+ function getDbPath(): string {
9
+ return path.join(getHiveDir(), "data", "hive.db");
10
+ }
11
+
12
+ export function getDbPathLazy(): string {
13
+ return getDbPath();
14
+ }
15
+
16
+ let _db: Database | null = null;
17
+
18
+ export function getDb(): Database {
19
+ if (!_db) throw new Error("DB no inicializada. Llama initializeDatabase() primero.");
20
+ return _db;
21
+ }
22
+
23
+
24
+ export function initializeDatabase(): Database {
25
+ const hiveDir = getHiveDir();
26
+ const dir = path.join(hiveDir, "data");
27
+ if (!existsSync(dir)) {
28
+ mkdirSync(dir, { recursive: true });
29
+ }
30
+
31
+ const dbPath = getDbPath();
32
+ const dbFileExists = existsSync(dbPath);
33
+
34
+ _db = new Database(dbPath, { create: true });
35
+
36
+ // ── Pre-schema migration: drop legacy cron tables before SCHEMA exec ──
37
+ // This must happen BEFORE SCHEMA runs, because CREATE TABLE IF NOT EXISTS
38
+ // will skip if the old table exists, leaving us with the wrong schema.
39
+ try {
40
+ const cronJobsCols = _db.query(`PRAGMA table_info(cron_jobs)`).all() as any[];
41
+ if (cronJobsCols.length > 0) {
42
+ const cronColNames = cronJobsCols.map((c: any) => c.name);
43
+ const needsMigration = cronColNames.includes("enabled")
44
+ || cronColNames.includes("user_id")
45
+ || !cronColNames.includes("status")
46
+ || !cronColNames.includes("task");
47
+
48
+ if (needsMigration) {
49
+ logger.info("🛠️ Dropping legacy cron_jobs table for schema rebuild...");
50
+ // Drop FK references first
51
+ try { _db.run(`DROP TRIGGER IF EXISTS update_cron_jobs_updated_at`); } catch {}
52
+ try { _db.run(`DROP TRIGGER IF EXISTS update_scheduled_tasks_updated_at`); } catch {}
53
+ try { _db.run(`DROP TABLE IF EXISTS task_runs`); } catch {}
54
+ _db.run(`DROP TABLE cron_jobs`);
55
+ logger.info("✅ Legacy cron_jobs table dropped — will be recreated by SCHEMA");
56
+ }
57
+ }
58
+
59
+ // Also drop scheduled_tasks if it exists (will be recreated with new name)
60
+ const stCheck = _db.query(`SELECT name FROM sqlite_master WHERE type='table' AND name='scheduled_tasks'`).all() as any[];
61
+ if (stCheck.length > 0) {
62
+ logger.info("🛠️ Dropping legacy scheduled_tasks table...");
63
+ try { _db.run(`DROP TRIGGER IF EXISTS update_scheduled_tasks_updated_at`); } catch {}
64
+ _db.run(`DROP TABLE scheduled_tasks`);
65
+ logger.info("✅ Legacy scheduled_tasks table dropped");
66
+ }
67
+
68
+ // Drop old indexes that reference legacy columns
69
+ try { _db.run(`DROP INDEX IF EXISTS idx_cron_jobs_user`); } catch {}
70
+ try { _db.run(`DROP INDEX IF EXISTS idx_cron_jobs_enabled`); } catch {}
71
+ try { _db.run(`DROP INDEX IF EXISTS idx_cron_jobs_next_run`); } catch {}
72
+ try { _db.run(`DROP INDEX IF EXISTS idx_scheduled_tasks_status`); } catch {}
73
+ try { _db.run(`DROP INDEX IF EXISTS idx_scheduled_tasks_type`); } catch {}
74
+ try { _db.run(`DROP INDEX IF EXISTS idx_scheduled_tasks_next_run`); } catch {}
75
+ try { _db.run(`DROP INDEX IF EXISTS idx_scheduled_tasks_agent`); } catch {}
76
+ } catch (preSchemaErr) {
77
+ logger.warn("⚠️ Pre-schema migration check failed:", { error: (preSchemaErr as Error).message });
78
+ }
79
+
80
+ _db.run(SCHEMA);
81
+ _db.run(PROJECTS_SCHEMA);
82
+ _db.run(CONTEXT_ENGINE_SCHEMA);
83
+ _db.run(MEETING_SCHEMA);
84
+
85
+ ensureSchemaSync();
86
+
87
+ return _db;
88
+ }
89
+
90
+ function ensureColumnExists(tableName: string, columnName: string, columnDefinition: string): void {
91
+ if (!_db) return;
92
+ try {
93
+ const info = _db.query(`PRAGMA table_info(${tableName})`).all() as any[];
94
+ const exists = info.some((col: any) => col.name === columnName);
95
+
96
+ if (!exists) {
97
+ logger.info(`🛠️ Añadiendo columna faltante '${columnName}' a la tabla '${tableName}'`);
98
+ _db.run(`ALTER TABLE ${tableName} ADD COLUMN ${columnName} ${columnDefinition}`);
99
+ }
100
+ } catch (err) {
101
+ logger.warn(`⚠️ No se pudo verificar/añadir la columna '${columnName}' en '${tableName}':`, { error: (err as Error).message });
102
+ }
103
+ }
104
+
105
+ function ensureSchemaSync(): void {
106
+ if (!_db) return;
107
+
108
+ // Sync users (auth columns)
109
+ ensureColumnExists("users", "email", "TEXT");
110
+ ensureColumnExists("users", "password_hash", "TEXT");
111
+
112
+ // Sync mcp_servers
113
+ ensureColumnExists("mcp_servers", "tools_count", "INTEGER DEFAULT 0");
114
+ ensureColumnExists("mcp_servers", "status", "TEXT NOT NULL DEFAULT 'disconnected'");
115
+ ensureColumnExists("mcp_servers", "env_encrypted", "TEXT");
116
+ ensureColumnExists("mcp_servers", "env_iv", "TEXT");
117
+ ensureColumnExists("mcp_servers", "headers_encrypted", "TEXT");
118
+ ensureColumnExists("mcp_servers", "headers_iv", "TEXT");
119
+
120
+ // Sync providers
121
+ ensureColumnExists("providers", "api_key_encrypted", "TEXT");
122
+ ensureColumnExists("providers", "api_key_iv", "TEXT");
123
+ ensureColumnExists("providers", "headers_encrypted", "TEXT");
124
+ ensureColumnExists("providers", "headers_iv", "TEXT");
125
+ ensureColumnExists("providers", "num_ctx", "INTEGER");
126
+ ensureColumnExists("providers", "num_gpu", "INTEGER DEFAULT -1");
127
+
128
+ // Sync agents (new Context Engine columns — safe no-ops if already present)
129
+ ensureColumnExists("agents", "headers_encrypted", "TEXT");
130
+ ensureColumnExists("agents", "headers_iv", "TEXT");
131
+ ensureColumnExists("agents", "system_prompt", "TEXT");
132
+ ensureColumnExists("agents", "role", "TEXT NOT NULL DEFAULT 'coordinator'");
133
+ ensureColumnExists("agents", "tools_json", "TEXT");
134
+ ensureColumnExists("agents", "skills_json", "TEXT");
135
+ ensureColumnExists("agents", "parent_id", "TEXT");
136
+ ensureColumnExists("agents", "max_iterations", "INTEGER NOT NULL DEFAULT 10");
137
+ ensureColumnExists("agents", "updated_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
138
+ ensureColumnExists("agents", "workspace", "TEXT");
139
+
140
+ // Sync tasks (new Context Engine columns)
141
+ ensureColumnExists("tasks", "priority", "INTEGER NOT NULL DEFAULT 0");
142
+ ensureColumnExists("tasks", "depends_on", "TEXT");
143
+ ensureColumnExists("tasks", "error", "TEXT");
144
+ ensureColumnExists("tasks", "completed_at", "INTEGER");
145
+
146
+ // Sync tools (new columns)
147
+ ensureColumnExists("tools", "created_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
148
+ ensureColumnExists("tools", "updated_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
149
+
150
+ // Sync skills (new columns)
151
+ ensureColumnExists("skills", "created_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
152
+ ensureColumnExists("skills", "updated_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
153
+
154
+ // ── Cron Jobs: ensure triggers and columns are correct ──
155
+ // Triggers: clean up old references and recreate
156
+ try {
157
+ _db.run(`DROP TRIGGER IF EXISTS update_scheduled_tasks_updated_at`);
158
+ _db.run(`DROP TRIGGER IF EXISTS update_cron_jobs_updated_at`);
159
+ _db.run(`CREATE TRIGGER IF NOT EXISTS update_cron_jobs_updated_at
160
+ AFTER UPDATE ON cron_jobs
161
+ BEGIN
162
+ UPDATE cron_jobs SET updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now') WHERE id = NEW.id;
163
+ END`);
164
+ } catch (triggerErr) {
165
+ logger.warn("⚠️ Failed to recreate trigger:", { error: (triggerErr as Error).message });
166
+ }
167
+
168
+ // Ensure new columns exist (for incremental upgrades)
169
+ ensureColumnExists("cron_jobs", "task", "TEXT NOT NULL DEFAULT ''");
170
+ ensureColumnExists("cron_jobs", "status", "TEXT NOT NULL DEFAULT 'active'");
171
+ ensureColumnExists("cron_jobs", "task_type", "TEXT NOT NULL DEFAULT 'recurring'");
172
+ ensureColumnExists("cron_jobs", "start_at", "TEXT");
173
+ ensureColumnExists("cron_jobs", "stop_at", "TEXT");
174
+ ensureColumnExists("cron_jobs", "dom_and_dow", "INTEGER NOT NULL DEFAULT 0");
175
+ ensureColumnExists("cron_jobs", "fire_at", "TEXT");
176
+ ensureColumnExists("cron_jobs", "protect", "INTEGER NOT NULL DEFAULT 1");
177
+ ensureColumnExists("cron_jobs", "interval_sec", "INTEGER");
178
+ ensureColumnExists("cron_jobs", "agent_id", "TEXT");
179
+ ensureColumnExists("cron_jobs", "completed_at", "TEXT");
180
+
181
+ // Context Engine tables — ensure created_at/updated_at columns exist
182
+ ensureColumnExists("conversations", "created_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
183
+ ensureColumnExists("conversations", "updated_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
184
+ ensureColumnExists("conversations", "content_multimodal", "TEXT");
185
+ ensureColumnExists("conversations", "reasoning_content", "TEXT"); // Kimi K2 thinking round-trip
186
+ ensureColumnExists("summaries", "created_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
187
+ ensureColumnExists("summaries", "updated_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
188
+ ensureColumnExists("scratchpad", "created_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
189
+ ensureColumnExists("scratchpad", "updated_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
190
+ ensureColumnExists("traces", "created_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
191
+ ensureColumnExists("reflections", "created_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
192
+ ensureColumnExists("playbook", "created_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
193
+ ensureColumnExists("playbook", "updated_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
194
+ ensureColumnExists("tool_cache", "created_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
195
+
196
+ // hive_capabilities: create if not exists (applied via CONTEXT_ENGINE_SCHEMA IF NOT EXISTS)
197
+ // No column migrations needed — table is seeded fresh each startup via INSERT OR REPLACE
198
+
199
+ // Data migrations: fix known bad base_url values from old seeds
200
+ if (_db) {
201
+ // Sync channels — vision columns for multimodal support
202
+ ensureColumnExists("channels", "vision_enabled", "INTEGER NOT NULL DEFAULT 0");
203
+ ensureColumnExists("channels", "ocr_provider", "TEXT");
204
+ ensureColumnExists("channels", "vision_provider", "TEXT");
205
+ ensureColumnExists("channels", "vision_model_id", "TEXT");
206
+
207
+ _db.query(`UPDATE providers SET base_url = 'https://api.groq.com/openai/v1' WHERE id = 'groq' AND base_url = 'https://api.groq.com/v1'`).run();
208
+ _db.query(`UPDATE providers SET base_url = 'https://api.openai.com/v1' WHERE id = 'openai' AND base_url = 'https://api.openai.com'`).run();
209
+ // Fix Gemini base_url: the @google/genai SDK already knows the correct URL internally.
210
+ // Passing /v1beta as baseUrl causes it to double-append the path → 404.
211
+ _db.query(`UPDATE providers SET base_url = NULL WHERE id = 'gemini' AND base_url = 'https://generativelanguage.googleapis.com/v1beta'`).run();
212
+ }
213
+ }
214
+
215
+
216
+ export class DatabaseService {
217
+ private log = logger.child("sqlite");
218
+
219
+ private get db(): Database {
220
+ if (!_db) {
221
+ initializeDatabase();
222
+ }
223
+ return _db!;
224
+ }
225
+
226
+ public close(): void {
227
+ if (_db) {
228
+ _db.close();
229
+ _db = null;
230
+ }
231
+ }
232
+
233
+ public updateMCPServer(id: string, updates: any): void {
234
+ const fields = [];
235
+ const values: any = { $id: id };
236
+
237
+ if (updates.enabled !== undefined) {
238
+ fields.push("enabled = $enabled");
239
+ values.$enabled = updates.enabled ? 1 : 0;
240
+ }
241
+ if (updates.active !== undefined) {
242
+ fields.push("active = $active");
243
+ values.$active = updates.active ? 1 : 0;
244
+ }
245
+ if (updates.status !== undefined) {
246
+ fields.push("status = $status");
247
+ values.$status = updates.status;
248
+ }
249
+ if (updates.tools_count !== undefined) {
250
+ fields.push("tools_count = $tools_count");
251
+ values.$tools_count = updates.tools_count;
252
+ }
253
+ if (updates.transport !== undefined) {
254
+ fields.push("transport = $transport");
255
+ values.$transport = updates.transport;
256
+ }
257
+ if (updates.command !== undefined) {
258
+ fields.push("command = $command");
259
+ values.$command = updates.command;
260
+ }
261
+ if (updates.args !== undefined) {
262
+ fields.push("args = $args");
263
+ values.$args = JSON.stringify(updates.args);
264
+ }
265
+ if (updates.url !== undefined) {
266
+ fields.push("url = $url");
267
+ values.$url = updates.url;
268
+ }
269
+ if (updates.env_encrypted !== undefined) {
270
+ fields.push("env_encrypted = $env_encrypted");
271
+ values.$env_encrypted = updates.env_encrypted;
272
+ }
273
+ if (updates.env_iv !== undefined) {
274
+ fields.push("env_iv = $env_iv");
275
+ values.$env_iv = updates.env_iv;
276
+ }
277
+ if (updates.headers_encrypted !== undefined) {
278
+ fields.push("headers_encrypted = $headers_encrypted");
279
+ values.$headers_encrypted = updates.headers_encrypted;
280
+ }
281
+ if (updates.headers_iv !== undefined) {
282
+ fields.push("headers_iv = $headers_iv");
283
+ values.$headers_iv = updates.headers_iv;
284
+ }
285
+
286
+ if (fields.length === 0) return;
287
+
288
+ const query = `UPDATE mcp_servers SET ${fields.join(", ")} WHERE id = $id`;
289
+ try {
290
+ this.db.query(query).run(values);
291
+ this.log.debug(`MCP server ${id} updated in DB`);
292
+ } catch (error: any) {
293
+ this.log.error(`Failed to update MCP server ${id}: ${error.message}`);
294
+ }
295
+ }
296
+
297
+ public getActiveAgentWorkspace(): string | null {
298
+ try {
299
+ const row = this.db.query(
300
+ "SELECT workspace FROM agents WHERE role = 'coordinator' LIMIT 1"
301
+ ).get() as { workspace: string } | null;
302
+ const ws = row?.workspace;
303
+ return ws && ws !== "null" ? ws : null;
304
+ } catch {
305
+ return null;
306
+ }
307
+ }
308
+
309
+ public listMCPServers(): any[] {
310
+ try {
311
+ return this.db.query("SELECT * FROM mcp_servers").all();
312
+ } catch (error: any) {
313
+ this.log.error(`Failed to list MCP servers: ${error.message}`);
314
+ return [];
315
+ }
316
+ }
317
+
318
+ public createTask(task: {
319
+ project_id: string;
320
+ agent_id?: string | null;
321
+ parent_task_id?: number | null;
322
+ name: string;
323
+ description?: string | null;
324
+ }): number {
325
+ const result = this.db.query(`
326
+ INSERT INTO tasks (project_id, agent_id, parent_task_id, name, description)
327
+ VALUES (?, ?, ?, ?, ?)
328
+ `).run(
329
+ task.project_id,
330
+ task.agent_id ?? null,
331
+ task.parent_task_id ?? null,
332
+ task.name,
333
+ task.description ?? null
334
+ );
335
+ return Number(result.lastInsertRowid);
336
+ }
337
+
338
+ public updateTask(taskId: number, updates: {
339
+ status?: string;
340
+ progress?: number;
341
+ result?: string;
342
+ agent_id?: string | null;
343
+ }): boolean {
344
+ const fields: string[] = ["updated_at = unixepoch()"];
345
+ const values: any[] = [];
346
+
347
+ if (updates.status !== undefined) { fields.push("status = ?"); values.push(updates.status); }
348
+ if (updates.progress !== undefined) { fields.push("progress = ?"); values.push(updates.progress); }
349
+ if (updates.result !== undefined) { fields.push("result = ?"); values.push(updates.result); }
350
+ if (updates.agent_id !== undefined) { fields.push("agent_id = ?"); values.push(updates.agent_id); }
351
+
352
+ values.push(taskId);
353
+ const res = this.db.query(`UPDATE tasks SET ${fields.join(", ")} WHERE id = ?`).run(...values);
354
+ return res.changes > 0;
355
+ }
356
+
357
+ public getTasksByProject(projectId: string): any[] {
358
+ return this.db.query(
359
+ "SELECT * FROM tasks WHERE project_id = ? ORDER BY id ASC"
360
+ ).all(projectId) as any[];
361
+ }
362
+
363
+ public getProjectWithTasks(projectId: string): any | null {
364
+ const project = this.db.query("SELECT * FROM projects WHERE id = ?").get(projectId) as any;
365
+ if (!project) return null;
366
+ project.tasks = this.getTasksByProject(projectId);
367
+ return project;
368
+ }
369
+
370
+ public recalcProjectProgress(projectId: string): number {
371
+ const row = this.db.query(
372
+ "SELECT AVG(progress) as avg_progress FROM tasks WHERE project_id = ?"
373
+ ).get(projectId) as any;
374
+ const avg = Math.round(row?.avg_progress ?? 0);
375
+ this.db.query("UPDATE projects SET progress = ?, updated_at = unixepoch() WHERE id = ?").run(avg, projectId);
376
+ return avg;
377
+ }
378
+
379
+ public saveMCPServer(server: any): void {
380
+ try {
381
+ this.db.query(`
382
+ INSERT OR REPLACE INTO mcp_servers (id, name, transport, command, args, url, env_encrypted, env_iv, headers_encrypted, headers_iv, enabled, active, builtin, tools_count, status)
383
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
384
+ `).run(
385
+ server.id || server.name,
386
+ server.name,
387
+ server.transport,
388
+ server.command || null,
389
+ JSON.stringify(server.args || []),
390
+ server.url || null,
391
+ server.env_encrypted || null,
392
+ server.env_iv || null,
393
+ server.headers_encrypted || null,
394
+ server.headers_iv || null,
395
+ server.enabled ? 1 : 0,
396
+ server.active ? 1 : 0,
397
+ server.builtin ? 1 : 0,
398
+ server.tools_count || 0,
399
+ server.status || "disconnected"
400
+ );
401
+ } catch (error: any) {
402
+ this.log.error(`Failed to save MCP server ${server.name}: ${error.message}`);
403
+ }
404
+ }
405
+ }
406
+
407
+ export const dbService = new DatabaseService();
@@ -0,0 +1,101 @@
1
+ import { randomBytes, createCipheriv, createDecipheriv, createHash } from "node:crypto";
2
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
3
+ import * as path from "node:path";
4
+ import { homedir } from "node:os";
5
+
6
+ let _encryptionKey: Buffer | null = null;
7
+
8
+ function getEncryptionKey(): Buffer {
9
+ if (_encryptionKey) return _encryptionKey;
10
+
11
+ const masterKey = process.env.HIVE_MASTER_KEY;
12
+
13
+ if (masterKey) {
14
+ _encryptionKey = Buffer.from(masterKey.slice(0, 32).padEnd(32, "0"), "utf8");
15
+ } else {
16
+ const hiveDir = process.env.HIVE_HOME || path.join(homedir(), ".hive");
17
+ if (!existsSync(hiveDir)) {
18
+ mkdirSync(hiveDir, { recursive: true });
19
+ }
20
+ const keyPath = path.join(hiveDir, ".master.key");
21
+
22
+ if (existsSync(keyPath)) {
23
+ const storedKey = readFileSync(keyPath, "utf-8").trim();
24
+ _encryptionKey = Buffer.from(storedKey, "hex");
25
+ } else {
26
+ _encryptionKey = randomBytes(32);
27
+ writeFileSync(keyPath, _encryptionKey.toString("hex"), { mode: 0o600 });
28
+ }
29
+ }
30
+
31
+ return _encryptionKey;
32
+ }
33
+
34
+ export interface EncryptedData {
35
+ encrypted: string;
36
+ iv: string;
37
+ }
38
+
39
+ export function encrypt(text: string): EncryptedData {
40
+ const key = getEncryptionKey();
41
+ const iv = randomBytes(16);
42
+
43
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
44
+
45
+ let encrypted = cipher.update(text, "utf8", "hex");
46
+ encrypted += cipher.final("hex");
47
+ const authTag = cipher.getAuthTag().toString("hex");
48
+
49
+ return {
50
+ encrypted: encrypted + ":" + authTag,
51
+ iv: iv.toString("hex"),
52
+ };
53
+ }
54
+
55
+ export function decrypt(data: EncryptedData): string {
56
+ const key = getEncryptionKey();
57
+ const iv = Buffer.from(data.iv, "hex");
58
+ const [encrypted, authTag] = data.encrypted.split(":");
59
+
60
+ const decipher = createDecipheriv("aes-256-gcm", key, iv);
61
+ decipher.setAuthTag(Buffer.from(authTag, "hex"));
62
+
63
+ let decrypted = decipher.update(encrypted, "hex", "utf8");
64
+ decrypted += decipher.final("utf8");
65
+
66
+ return decrypted;
67
+ }
68
+
69
+ export function encryptApiKey(apiKey: string): { encrypted: string; iv: string } {
70
+ return encrypt(apiKey);
71
+ }
72
+
73
+ export function decryptApiKey(encrypted: string, iv: string): string {
74
+ return decrypt({ encrypted, iv });
75
+ }
76
+
77
+ export function encryptConfig(config: Record<string, unknown>): { encrypted: string; iv: string } {
78
+ return encrypt(JSON.stringify(config));
79
+ }
80
+
81
+ export function decryptConfig(encrypted: string, iv: string): Record<string, unknown> {
82
+ const decrypted = decrypt({ encrypted, iv });
83
+ return JSON.parse(decrypted);
84
+ }
85
+
86
+ export function hashPassword(password: string): string {
87
+ const hasher = new Bun.CryptoHasher("sha256");
88
+ hasher.update(password);
89
+ return hasher.digest("hex");
90
+ }
91
+
92
+ export function verifyPassword(password: string, hash: string): boolean {
93
+ const hasher = new Bun.CryptoHasher("sha256");
94
+ hasher.update(password);
95
+ return hasher.digest("hex") === hash;
96
+ }
97
+
98
+ export function maskApiKey(apiKey: string): string {
99
+ if (!apiKey || apiKey.length < 8) return "••••••••";
100
+ return apiKey.slice(0, 4) + "••••••••" + apiKey.slice(-4);
101
+ }
@@ -0,0 +1,10 @@
1
+ export { getDbPathLazy, getDb, initializeDatabase, DatabaseService, dbService } from "./SQLiteStorage.ts";
2
+ export { SCHEMA, PROJECTS_SCHEMA, CONTEXT_ENGINE_SCHEMA, MEETING_SCHEMA } from "./schema.ts";
3
+ export { recordUsage, getUsageStats, getProviderPricing, estimateCostForTokens, getAverageTokenCost, recordToonSavings } from "./usage.ts";
4
+ export type { UsageRecord, UsageSummary } from "./usage.ts";
5
+ export type { OnboardingSection } from "./onboarding.ts";
6
+ export { resolveUserId, resolveAgentId, initOnboardingDb } from "./onboarding.ts";
7
+ export type { EncryptedData } from "./crypto.ts";
8
+ export { encrypt, decrypt, encryptApiKey, decryptApiKey, encryptConfig, decryptConfig } from "./crypto.ts";
9
+ export type { SeedData } from "./seed.ts";
10
+ export { SEED_DATA, seedAllData } from "./seed.ts";