@johpaz/hive-sdk 0.0.17 → 0.1.3

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 (63) hide show
  1. package/README.md +83 -203
  2. package/bun.lock +833 -0
  3. package/bunfig.toml +7 -0
  4. package/docs/API-TOOLS-SKILLS-CHANNELS.md +60 -0
  5. package/docs/HIVE-HARNESS.md +113 -0
  6. package/package.json +36 -2
  7. package/packages/cli/package.json +1 -1
  8. package/packages/core/package.json +13 -2
  9. package/packages/core/src/ace/Tracer.ts +1 -1
  10. package/packages/core/src/agent/AgentRunner.ts +12 -0
  11. package/packages/core/src/agent/ContextCompiler.ts +4 -4
  12. package/packages/core/src/agent/ConversationStore.ts +30 -20
  13. package/packages/core/src/agent/selectors/PlaybookSelector.ts +50 -76
  14. package/packages/core/src/agent/selectors/SkillSelector.ts +106 -262
  15. package/packages/core/src/agent/selectors/ToolSelector.ts +54 -89
  16. package/packages/core/src/api/createAgent.ts +10 -0
  17. package/packages/core/src/auth/auth.ts +36 -23
  18. package/packages/core/src/config/loader.ts +2 -2
  19. package/packages/core/src/harness/boot-id.ts +20 -0
  20. package/packages/core/src/harness/collections.ts +98 -0
  21. package/packages/core/src/harness/db-helpers.ts +87 -0
  22. package/packages/core/src/harness/durable-queue.ts +337 -0
  23. package/packages/core/src/harness/goal-verifier.ts +141 -0
  24. package/packages/core/src/harness/harness.test.ts +236 -0
  25. package/packages/core/src/harness/index.ts +34 -0
  26. package/packages/core/src/harness/job-store.ts +399 -0
  27. package/packages/core/src/harness/proof-packet.ts +69 -0
  28. package/packages/core/src/harness/reconcile.ts +149 -0
  29. package/packages/core/src/harness/run-epoch.ts +32 -0
  30. package/packages/core/src/harness/run-store.ts +334 -0
  31. package/packages/core/src/index.ts +19 -0
  32. package/packages/core/src/memory/Scratchpad.test.ts +23 -21
  33. package/packages/core/src/memory/Scratchpad.ts +41 -24
  34. package/packages/core/src/skills/bundled-data.generated.ts +50 -0
  35. package/packages/core/src/skills/skills.test.ts +21 -0
  36. package/packages/core/src/storage/HiveDBStorage.ts +64 -0
  37. package/packages/core/src/storage/SQLiteStorage.ts +7 -0
  38. package/packages/core/src/storage/hiveSeed.ts +308 -0
  39. package/packages/core/src/storage/hiveStorage.test.ts +38 -0
  40. package/packages/core/src/storage/index.ts +10 -0
  41. package/packages/core/src/storage/seed.ts +5 -1
  42. package/packages/core/src/storage/usage.ts +106 -167
  43. package/packages/core/src/tool-runtime/tool-runtime.test.ts +11 -3
  44. package/packages/core/src/tools/agents/get-available-models.ts +52 -56
  45. package/packages/core/src/tools/agents/index.ts +77 -60
  46. package/packages/core/src/tools/core/index.ts +106 -291
  47. package/packages/core/src/tools/index.ts +1 -0
  48. package/packages/core/src/tools/meeting/index.ts +83 -93
  49. package/packages/core/src/tools/web/api-request.test.ts +170 -0
  50. package/packages/core/src/tools/web/api-request.ts +239 -0
  51. package/packages/core/src/tools/web/browser-click.ts +2 -2
  52. package/packages/core/src/tools/web/browser-extract.ts +22 -6
  53. package/packages/core/src/tools/web/browser-navigate.ts +34 -18
  54. package/packages/core/src/tools/web/browser-screenshot.ts +40 -8
  55. package/packages/core/src/tools/web/browser-script.ts +2 -2
  56. package/packages/core/src/tools/web/browser-service.test.ts +83 -0
  57. package/packages/core/src/tools/web/browser-service.ts +290 -341
  58. package/packages/core/src/tools/web/browser-type.ts +2 -2
  59. package/packages/core/src/tools/web/browser-wait.ts +2 -2
  60. package/packages/core/src/tools/web/index.ts +3 -0
  61. package/packages/core/src/utils/toon.ts +4 -4
  62. package/CHANGELOG.md +0 -72
  63. package/docs/README.md +0 -161
@@ -6,148 +6,32 @@
6
6
 
7
7
  import type { Tool } from "../types.ts";
8
8
  import { getDb } from "../../storage/SQLiteStorage.ts";
9
+ import { getHiveDB } from "../../storage/HiveDBStorage.ts";
9
10
  import { logger } from "../../utils/logger.ts";
10
11
 
11
12
  const log = logger.child("core");
12
13
 
13
- // ─── Bilingual dictionary: Spanish → English ────────────────────────────────
14
-
15
- const ES_EN_DICT: Record<string, string[]> = {
16
- // Acciones
17
- "buscar": ["search", "find", "list", "get", "query"],
18
- "listar": ["list", "get", "fetch", "retrieve"],
19
- "crear": ["create", "add", "insert", "new", "make"],
20
- "actualizar": ["update", "edit", "modify", "change"],
21
- "eliminar": ["delete", "remove", "destroy"],
22
- "obtener": ["get", "fetch", "retrieve", "read"],
23
- "enviar": ["send", "post", "submit", "push"],
24
- "leer": ["read", "get", "fetch"],
25
- "escribir": ["write", "create", "save"],
26
- "modificar": ["update", "modify", "edit", "change"],
27
- "ejecutar": ["execute", "run", "invoke"],
28
- "conectar": ["connect", "link"],
29
- "desconectar": ["disconnect", "remove"],
30
- "descargar": ["download", "export", "fetch"],
31
- "subir": ["upload", "import", "create"],
32
- "analizar": ["analyze", "review", "examine"],
33
- "generar": ["generate", "create", "produce"],
34
- "convertir": ["convert", "transform", "translate"],
35
- "validar": ["validate", "verify", "check"],
36
- "importar": ["import", "load", "ingest"],
37
- "exportar": ["export", "download", "extract"],
38
- "comprimir": ["compress", "zip", "archive"],
39
- "extraer": ["extract", "get", "retrieve", "parse"],
40
- "reemplazar": ["replace", "update", "swap"],
41
- "cargar": ["load", "import", "upload"],
42
- "guardar": ["save", "store", "create"],
43
- "consultar": ["query", "search", "get", "list"],
44
- "registrar": ["register", "create", "log", "record"],
45
- "programar": ["schedule", "plan", "cron"],
46
- "notificar": ["notify", "alert", "send"],
47
- "reiniciar": ["restart", "reset", "reboot"],
48
- "configurar": ["configure", "setup", "set"],
49
- "autenticar": ["authenticate", "login", "auth"],
50
- "publicar": ["publish", "deploy", "release"],
51
- "desplegar": ["deploy", "publish", "release"],
52
- "copiar": ["copy", "clone", "duplicate"],
53
- "mover": ["move", "transfer", "migrate"],
54
- "comparar": ["compare", "diff", "match"],
55
- "fusionar": ["merge", "combine", "join"],
56
- "dividir": ["split", "divide", "partition"],
57
- "filtrar": ["filter", "search", "query"],
58
- "ordenar": ["sort", "order", "arrange"],
59
- "traducir": ["translate", "convert"],
60
-
61
- // Entidades
62
- "base": ["base", "database", "db"],
63
- "bases": ["bases", "databases"],
64
- "datos": ["data", "records", "rows", "entries"],
65
- "registro": ["record", "entry", "row", "item"],
66
- "registros": ["records", "entries", "rows", "items"],
67
- "tabla": ["table", "schema", "collection"],
68
- "tablas": ["tables", "schemas"],
69
- "campo": ["field", "column", "property"],
70
- "campos": ["fields", "columns", "properties"],
71
- "usuario": ["user", "account"],
72
- "usuarios": ["users", "accounts"],
73
- "proyecto": ["project", "repo", "workspace"],
74
- "proyectos": ["projects", "repos", "workspaces"],
75
- "archivo": ["file", "document"],
76
- "archivos": ["files", "documents"],
77
- "correo": ["email", "mail", "message"],
78
- "correos": ["emails", "mails", "messages"],
79
- "noticia": ["news", "article", "post"],
80
- "noticias": ["news", "articles", "posts"],
81
- "contenido": ["content", "data", "text"],
82
- "tarea": ["task", "job", "issue", "ticket"],
83
- "tareas": ["tasks", "jobs", "issues", "tickets"],
84
- "pagina": ["page", "site", "web"],
85
- "enlace": ["link", "url", "reference"],
86
- "imagen": ["image", "picture", "photo"],
87
- "video": ["video", "media"],
88
- "audio": ["audio", "sound", "media"],
89
- "categoria": ["category", "tag", "label"],
90
- "estado": ["status", "state", "condition"],
91
- "error": ["error", "exception", "fault"],
92
- "fuente": ["source", "origin", "reference"],
93
- "esquema": ["schema", "structure", "model"],
94
- "respuesta": ["response", "reply", "answer"],
95
- "solicitud": ["request", "query", "call"],
96
- "repositorio": ["repository", "repo"],
97
- "seguridad": ["security", "auth", "permission"],
98
- "permiso": ["permission", "role", "access"],
99
- "acceso": ["access", "login", "entry"],
100
- "servidor": ["server", "host", "service"],
101
- "conexion": ["connection", "link", "integration"],
102
- "integracion": ["integration", "connector", "plugin"],
103
- "herramienta": ["tool", "utility", "function"],
104
- "informacion": ["info", "information", "details"],
105
- "lista": ["list", "collection", "array"],
106
- "reporte": ["report", "summary", "analytics"],
107
- "metrica": ["metric", "stat", "analytics"],
108
- "contacto": ["contact", "lead", "person"],
109
- };
110
-
111
- /**
112
- * Translate a Spanish query to English equivalents for FTS5 fallback.
113
- * Returns an array of English keyword tokens.
114
- */
115
- function translateQueryToEnglish(query: string): string {
116
- const words = query.toLowerCase().replace(/_/g, " ").split(/\s+/).filter(w => w.length > 1);
117
- const translated: string[] = [];
118
-
119
- for (const word of words) {
120
- const equivalents = ES_EN_DICT[word];
121
- if (equivalents) {
122
- translated.push(...equivalents);
123
- }
124
- }
125
-
126
- return [...new Set(translated)].join(" ");
127
- }
14
+ // ─── search_knowledge ────────────────────────────────────────────────────────
128
15
 
129
- /**
130
- * Build an FTS5 MATCH expression from a list of words.
131
- * Multi-word: AND with prefix wildcard. Single: exact OR prefix.
132
- */
133
- function buildFtsMatch(words: string[]): string {
134
- if (words.length > 1) {
135
- return words.map(w => `${w}*`).join(' AND ');
136
- }
137
- return `"${words.join(' ')}" OR ${words[0]}*`;
16
+ function buildSearchQuery(query: string): string {
17
+ return query
18
+ .toLowerCase()
19
+ .replace(/[^\p{L}\p{N}\s]/gu, " ")
20
+ .split(/\s+/)
21
+ .filter(w => w.length > 2)
22
+ .slice(0, 8)
23
+ .join(" ");
138
24
  }
139
25
 
140
- // ─── search_knowledge ────────────────────────────────────────────────────────
141
-
142
26
  export const searchKnowledgeTool: Tool = {
143
27
  name: "search_knowledge",
144
- description: "Busca herramientas NATIVAS (tools), MCP (tools externas), habilidades (skills) o reglas del playbook en la base de conocimientos. Usa búsqueda full-text (FTS5) con fallback bilingüe español→inglés. type='mcp' para herramientas MCP, type='all' para buscar en todo.",
28
+ description: "Busca herramientas NATIVAS (tools), MCP (tools externas), habilidades (skills) o reglas del playbook en la base de conocimientos. Usa búsqueda híbrida (BM25 + vector). type='mcp' para herramientas MCP, type='all' para buscar en todo.",
145
29
  parameters: {
146
30
  type: "object",
147
31
  properties: {
148
32
  query: {
149
33
  type: "string",
150
- description: "Término de búsqueda (nombre, descripción, categoría). Se busca primero en español, luego en inglés si hay pocos resultados.",
34
+ description: "Término de búsqueda (nombre, descripción, categoría).",
151
35
  },
152
36
  type: {
153
37
  type: "string",
@@ -162,179 +46,105 @@ export const searchKnowledgeTool: Tool = {
162
46
  required: ["query"],
163
47
  },
164
48
  execute: async (params: Record<string, unknown>) => {
165
- const db = getDb();
49
+ const db = await getHiveDB();
166
50
  const query = params.query as string;
167
51
  const type = (params.type as string) ?? "all";
168
52
  const limit = (params.limit as number) ?? 10;
169
- const MIN_RESULTS_FOR_BILINGUAL = 2;
170
53
 
171
54
  try {
172
- const escapedQuery = query.replace(/'/g, "''");
173
- const normalizedQuery = escapedQuery.replace(/_/g, " ").trim();
174
- const words = normalizedQuery.split(/\s+/).filter(w => w.length > 0);
175
- const ftsMatch = buildFtsMatch(words);
55
+ const searchQuery = buildSearchQuery(query);
56
+ if (!searchQuery) {
57
+ return { ok: true, query, type, tools: [], skills: [], playbook: [], toolsmcp: [], totalResults: 0 };
58
+ }
176
59
 
177
60
  const result: any = { query, type, tools: [], skills: [], playbook: [], toolsmcp: [] };
178
61
 
179
- // ─── Search functions (reusable for bilingual fallback) ───────────
180
-
181
- function searchTools(matchExpr: string): any[] {
182
- if (type !== "all" && type !== "tools") return [];
183
- try {
184
- return db.query(`
185
- SELECT
186
- COALESCE(t.id, tools_fts.tool_name) as id,
187
- COALESCE(t.name, tools_fts.tool_name) as name,
188
- COALESCE(t.description, tools_fts.description) as description,
189
- COALESCE(t.category, tools_fts.category) as category,
190
- COALESCE(t.enabled, 1) as enabled,
191
- COALESCE(t.active, 1) as active,
192
- bm25(tools_fts) as rank
193
- FROM tools_fts
194
- LEFT JOIN tools t ON t.name = tools_fts.tool_name
195
- WHERE tools_fts MATCH ?
196
- ORDER BY rank
197
- LIMIT ?
198
- `).all(matchExpr, limit) as any[];
199
- } catch { return []; }
62
+ async function searchByType(docType: string) {
63
+ return db.queryHybrid({
64
+ text: searchQuery,
65
+ k: limit,
66
+ filters: [{ field: "type", value: docType }],
67
+ boosts: { name: 5.0, body: 3.0, tags: 2.0 },
68
+ });
200
69
  }
201
70
 
202
- function searchSkills(matchExpr: string): any[] {
203
- if (type !== "all" && type !== "skills") return [];
204
- try {
205
- return db.query(`
206
- SELECT s.id, s.name, s.description, s.category, s.tools, s.triggers, s.preferred_agents, s.body, s.active, bm25(skills_fts) as rank
207
- FROM skills_fts
208
- JOIN skills s ON s.id = skills_fts.id
209
- WHERE skills_fts MATCH ?
210
- ORDER BY rank
211
- LIMIT ?
212
- `).all(matchExpr, limit) as any[];
213
- } catch { return []; }
71
+ if (type === "all" || type === "tools") {
72
+ const hits = await searchByType("tool");
73
+ const toolsCol = db.collection<{ name: string; description: string; category: string; enabled: boolean; active: boolean }>("tools");
74
+ for (const hit of hits) {
75
+ const entry = await toolsCol.get(hit.id);
76
+ const doc = entry?.doc;
77
+ result.tools.push({
78
+ id: hit.id,
79
+ name: doc?.name ?? hit.id,
80
+ description: doc?.description ?? "",
81
+ category: doc?.category ?? "core",
82
+ enabled: doc?.enabled ?? true,
83
+ active: doc?.active ?? true,
84
+ rank: hit.score,
85
+ });
86
+ }
214
87
  }
215
88
 
216
- function searchPlaybook(matchExpr: string): any[] {
217
- if (type !== "all" && type !== "playbook") return [];
218
- try {
219
- return db.query(`
220
- SELECT p.id, p.rule, p.category, p.applicable_to, p.helpful_count, p.harmful_count, p.active, bm25(playbook_fts) as rank
221
- FROM playbook_fts
222
- JOIN playbook p ON p.id = playbook_fts.rowid
223
- WHERE playbook_fts MATCH ?
224
- ORDER BY rank
225
- LIMIT ?
226
- `).all(matchExpr, limit) as any[];
227
- } catch { return []; }
89
+ if (type === "all" || type === "skills") {
90
+ const hits = await searchByType("skill");
91
+ const skillsCol = db.collection<{ name: string; description: string; category: string; body: string; tools: string[]; triggers: string[]; active: boolean }>("skills");
92
+ for (const hit of hits) {
93
+ const entry = await skillsCol.get(hit.id);
94
+ const doc = entry?.doc;
95
+ result.skills.push({
96
+ id: hit.id,
97
+ name: doc?.name ?? hit.id,
98
+ description: doc?.description ?? "",
99
+ category: doc?.category ?? "general",
100
+ tools: doc?.tools ?? [],
101
+ triggers: doc?.triggers ?? [],
102
+ body: doc?.body ? (doc.body.length > 400 ? doc.body.substring(0, 400) + "…" : doc.body) : undefined,
103
+ active: doc?.active ?? true,
104
+ rank: hit.score,
105
+ });
106
+ }
228
107
  }
229
108
 
230
- function searchMcpTools(matchExpr: string): any[] {
231
- if (type !== "all" && type !== "mcp") return [];
232
- try {
233
- return db.query(`
234
- SELECT m.id, m.server_name, m.tool_name, m.description, m.category, m.active, bm25(mcp_tools_fts) as rank
235
- FROM mcp_tools_fts
236
- JOIN mcp_tools m ON m.id = mcp_tools_fts.id
237
- WHERE mcp_tools_fts MATCH ?
238
- ORDER BY rank
239
- LIMIT ?
240
- `).all(matchExpr, limit) as any[];
241
- } catch { return []; }
109
+ if (type === "all" || type === "playbook") {
110
+ const hits = await searchByType("playbook");
111
+ const playbookCol = db.collection<{ rule: string; category: string; applicableTo?: string[]; active: boolean }>("playbook");
112
+ for (const hit of hits) {
113
+ const entry = await playbookCol.get(hit.id);
114
+ const doc = entry?.doc;
115
+ result.playbook.push({
116
+ id: hit.id,
117
+ rule: doc?.rule ?? "",
118
+ category: doc?.category ?? "",
119
+ applicable_to: doc?.applicableTo ?? null,
120
+ active: doc?.active ?? true,
121
+ rank: hit.score,
122
+ });
123
+ }
242
124
  }
243
125
 
244
- // ─── Pass 1: Search with original query ─────────────────────────
245
-
246
- const tools1 = searchTools(ftsMatch);
247
- const skills1 = searchSkills(ftsMatch);
248
- const playbook1 = searchPlaybook(ftsMatch);
249
- const mcp1 = searchMcpTools(ftsMatch);
250
-
251
- const totalFirst = tools1.length + skills1.length + playbook1.length + mcp1.length;
252
-
253
- // Map results
254
- result.tools = tools1.map((t: any) => ({
255
- id: t.id, name: t.name, description: t.description, category: t.category,
256
- enabled: t.enabled === 1, active: t.active === 1, rank: t.rank,
257
- }));
258
- result.skills = skills1.map((s: any) => ({
259
- id: s.id, name: s.name, description: s.description, category: s.category,
260
- tools: s.tools, triggers: s.triggers,
261
- preferred_agents: s.preferred_agents ? JSON.parse(s.preferred_agents) : [],
262
- body: s.body ? (s.body.length > 400 ? s.body.substring(0, 400) + "…" : s.body) : undefined,
263
- active: s.active === 1, rank: s.rank,
264
- }));
265
- result.playbook = playbook1.map((p: any) => ({
266
- id: p.id, rule: p.rule, category: p.category,
267
- applicable_to: p.applicable_to ? JSON.parse(p.applicable_to) : null,
268
- helpful_count: p.helpful_count, harmful_count: p.harmful_count,
269
- active: p.active === 1, rank: p.rank,
270
- }));
271
- result.toolsmcp = mcp1.map((t: any) => ({
272
- id: t.id, full_name: t.id, server_name: t.server_name, tool_name: t.tool_name,
273
- description: t.description, category: t.category,
274
- active: t.active === 1, rank: t.rank,
275
- }));
276
-
277
- // ─── Pass 2: Bilingual fallback (ES → EN) ──────────────────────
278
-
279
- if (totalFirst < MIN_RESULTS_FOR_BILINGUAL) {
280
- const englishQuery = translateQueryToEnglish(normalizedQuery);
281
- if (englishQuery.length > 0) {
282
- const enWords = englishQuery.split(/\s+/).filter(w => w.length > 0);
283
- const enMatch = buildFtsMatch(enWords);
284
-
285
- log.info(`[search_knowledge] Bilingual fallback: "${normalizedQuery}" → "${englishQuery}" (first pass: ${totalFirst} results)`);
286
-
287
- const existingIds = new Set([
288
- ...result.tools.map((t: any) => t.name),
289
- ...result.skills.map((s: any) => s.id),
290
- ...result.playbook.map((p: any) => p.id),
291
- ...result.toolsmcp.map((t: any) => t.id),
292
- ]);
293
-
294
- // Merge English results (dedup by id)
295
- for (const t of searchTools(enMatch)) {
296
- if (!existingIds.has(t.name || t.id)) {
297
- result.tools.push({
298
- id: t.id, name: t.name, description: t.description, category: t.category,
299
- enabled: t.enabled === 1, active: t.active === 1, rank: t.rank,
300
- });
301
- existingIds.add(t.name || t.id);
302
- }
303
- }
304
- for (const s of searchSkills(enMatch)) {
305
- if (!existingIds.has(s.id)) {
306
- result.skills.push({
307
- id: s.id, name: s.name, description: s.description, category: s.category,
308
- tools: s.tools, triggers: s.triggers,
309
- preferred_agents: s.preferred_agents ? JSON.parse(s.preferred_agents) : [],
310
- body: s.body ? (s.body.length > 400 ? s.body.substring(0, 400) + "…" : s.body) : undefined,
311
- active: s.active === 1, rank: s.rank,
312
- });
313
- existingIds.add(s.id);
314
- }
315
- }
316
- for (const p of searchPlaybook(enMatch)) {
317
- if (!existingIds.has(p.id)) {
318
- result.playbook.push({
319
- id: p.id, rule: p.rule, category: p.category,
320
- applicable_to: p.applicable_to ? JSON.parse(p.applicable_to) : null,
321
- helpful_count: p.helpful_count, harmful_count: p.harmful_count,
322
- active: p.active === 1, rank: p.rank,
323
- });
324
- existingIds.add(p.id);
325
- }
326
- }
327
- for (const t of searchMcpTools(enMatch)) {
328
- if (!existingIds.has(t.id)) {
329
- result.toolsmcp.push({
330
- id: t.id, full_name: t.id, server_name: t.server_name, tool_name: t.tool_name,
331
- description: t.description, category: t.category,
332
- active: t.active === 1, rank: t.rank,
333
- });
334
- existingIds.add(t.id);
335
- }
336
- }
337
- }
126
+ if (type === "all" || type === "mcp") {
127
+ const mcpCol = db.collection<{ serverName: string; toolName: string; description: string; category: string; active: boolean }>("mcp_tools");
128
+ const entries = await mcpCol.scan();
129
+ const q = searchQuery.toLowerCase();
130
+ const matches = entries
131
+ .map(e => e.doc)
132
+ .filter(m => m.active && (
133
+ m.toolName.toLowerCase().includes(q) ||
134
+ m.description.toLowerCase().includes(q) ||
135
+ m.category.toLowerCase().includes(q)
136
+ ))
137
+ .slice(0, limit);
138
+ result.toolsmcp = matches.map(m => ({
139
+ id: `${m.serverName}__${m.toolName}`,
140
+ full_name: `${m.serverName}__${m.toolName}`,
141
+ server_name: m.serverName,
142
+ tool_name: m.toolName,
143
+ description: m.description,
144
+ category: m.category,
145
+ active: true,
146
+ rank: 0,
147
+ }));
338
148
  }
339
149
 
340
150
  result.totalResults = result.tools.length + result.skills.length + result.playbook.length + result.toolsmcp.length;
@@ -380,6 +190,13 @@ export const notifyTool: Tool = {
380
190
 
381
191
  // ─── save_note (scratchpad) ──────────────────────────────────────────────────
382
192
 
193
+ interface ScratchpadDoc {
194
+ threadId: string;
195
+ key: string;
196
+ value: string;
197
+ updatedAt: number;
198
+ }
199
+
383
200
  export const saveNoteTool: Tool = {
384
201
  name: "save_note",
385
202
  description: "Save a note to the scratchpad (survives context compression).",
@@ -402,16 +219,14 @@ export const saveNoteTool: Tool = {
402
219
  required: ["key", "value"],
403
220
  },
404
221
  execute: async (params: Record<string, unknown>, config?: any) => {
405
- const db = getDb();
222
+ const db = await getHiveDB();
406
223
  const key = params.key as string;
407
224
  const value = params.value as string;
408
225
  const threadId = (params.thread_id as string) ?? config?.configurable?.thread_id ?? "default";
409
226
 
410
227
  try {
411
- db.query(`
412
- INSERT OR REPLACE INTO scratchpad (thread_id, key, value, source, updated_at)
413
- VALUES (?, ?, ?, 'agent', unixepoch())
414
- `).run(threadId, key, value);
228
+ const col = db.collection<ScratchpadDoc>("scratchpad");
229
+ await col.put(`${threadId}:${key}`, { threadId, key, value, updatedAt: Date.now() });
415
230
 
416
231
  return { ok: true, key, message: "Note saved." };
417
232
  } catch (error) {
@@ -473,4 +288,4 @@ export const reportProgressTool: Tool = {
473
288
 
474
289
  export function createTools(): Tool[] {
475
290
  return [searchKnowledgeTool, notifyTool, saveNoteTool, reportProgressTool];
476
- }
291
+ }
@@ -139,6 +139,7 @@ export {
139
139
  export {
140
140
  webSearchTool,
141
141
  webFetchTool,
142
+ apiRequestTool,
142
143
  browserNavigateTool,
143
144
  browserScreenshotTool,
144
145
  browserClickTool,