@johpaz/hive-sdk 0.1.6 → 0.3.0

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 (238) hide show
  1. package/CHANGELOG.md +441 -0
  2. package/README.md +21 -3
  3. package/package.json +27 -5
  4. package/packages/core/src/agent/acceptance-checks.ts +9 -9
  5. package/packages/core/src/agent/agent-catalog.ts +82 -25
  6. package/packages/core/src/agent/agent-loop.ts +115 -26
  7. package/packages/core/src/agent/capability-search.ts +2 -2
  8. package/packages/core/src/agent/catalog-selector.ts +4 -4
  9. package/packages/core/src/agent/compaction.ts +30 -10
  10. package/packages/core/src/agent/context-compiler.ts +63 -36
  11. package/packages/core/src/agent/conversation-store.ts +167 -9
  12. package/packages/core/src/agent/curator.ts +16 -7
  13. package/packages/core/src/agent/delegation-runtime.ts +5 -5
  14. package/packages/core/src/agent/goal-runner.ts +9 -9
  15. package/packages/core/src/agent/index.ts +1 -0
  16. package/packages/core/src/agent/llm-client.ts +98 -37
  17. package/packages/core/src/agent/llm-providers/anthropic.ts +4 -4
  18. package/packages/core/src/agent/llm-providers/deepseek.ts +1 -1
  19. package/packages/core/src/agent/llm-providers/gemini.ts +4 -4
  20. package/packages/core/src/agent/llm-providers/groq.ts +1 -1
  21. package/packages/core/src/agent/llm-providers/hiveagents.ts +3 -3
  22. package/packages/core/src/agent/llm-providers/interface.ts +2 -2
  23. package/packages/core/src/agent/llm-providers/kimi.ts +1 -1
  24. package/packages/core/src/agent/llm-providers/minimax.ts +1 -1
  25. package/packages/core/src/agent/llm-providers/mistral.ts +1 -1
  26. package/packages/core/src/agent/llm-providers/modelscope.ts +1 -1
  27. package/packages/core/src/agent/llm-providers/nvidia.ts +40 -1
  28. package/packages/core/src/agent/llm-providers/ollama.ts +4 -4
  29. package/packages/core/src/agent/llm-providers/openai-compat-base.ts +47 -7
  30. package/packages/core/src/agent/llm-providers/openai.ts +1 -1
  31. package/packages/core/src/agent/llm-providers/opencode-go.ts +1 -1
  32. package/packages/core/src/agent/llm-providers/openrouter.ts +1 -1
  33. package/packages/core/src/agent/llm-providers/qwen.ts +1 -1
  34. package/packages/core/src/agent/llm-providers/z-ai.ts +1 -1
  35. package/packages/core/src/agent/mcp-result-normalizer.ts +192 -0
  36. package/packages/core/src/agent/playbook-selector.ts +22 -7
  37. package/packages/core/src/agent/prompt-builder.ts +5 -5
  38. package/packages/core/src/agent/proof-packet.ts +5 -5
  39. package/packages/core/src/agent/providers/index.ts +39 -4
  40. package/packages/core/src/agent/realtime-providers/gemini-live.ts +238 -0
  41. package/packages/core/src/agent/realtime-providers/index.ts +29 -0
  42. package/packages/core/src/agent/realtime-providers/interface.ts +108 -0
  43. package/packages/core/src/agent/reflector.ts +38 -15
  44. package/packages/core/src/agent/run-store.ts +8 -8
  45. package/packages/core/src/agent/service.ts +10 -10
  46. package/packages/core/src/agent/skill-selector.ts +6 -6
  47. package/packages/core/src/agent/thread-id.ts +71 -0
  48. package/packages/core/src/agent/thread-store.ts +293 -0
  49. package/packages/core/src/agent/tool-selector.ts +9 -5
  50. package/packages/core/src/agent/tracer.ts +5 -5
  51. package/packages/core/src/api/createAgent.ts +67 -2
  52. package/packages/core/src/artifacts/index.ts +15 -0
  53. package/packages/core/src/artifacts/store.ts +161 -5
  54. package/packages/core/src/canvas/emitter.ts +2 -2
  55. package/packages/core/src/canvas/index.ts +9 -0
  56. package/packages/core/src/channels/telegram.ts +1 -1
  57. package/packages/core/src/channels/webchat.ts +1 -1
  58. package/packages/core/src/config/loader.ts +14 -5
  59. package/packages/core/src/ethics/EthicsGuard.ts +7 -1
  60. package/packages/core/src/events/agent-bus.ts +3 -3
  61. package/packages/core/src/events/channel-narration.ts +3 -3
  62. package/packages/core/src/events/event-bus.ts +1 -1
  63. package/packages/core/src/events/index.ts +18 -0
  64. package/packages/core/src/events/narration.ts +3 -3
  65. package/packages/core/src/events/tool-narration.ts +4 -0
  66. package/packages/core/src/gateway/channel-notify.ts +103 -6
  67. package/packages/core/src/gateway/delegation-groups.ts +4 -4
  68. package/packages/core/src/gateway/durable-queue.ts +18 -6
  69. package/packages/core/src/gateway/index.ts +3 -0
  70. package/packages/core/src/gateway/job-store.ts +11 -5
  71. package/packages/core/src/gateway/notification-inbox.ts +2 -2
  72. package/packages/core/src/gateway/server.ts +2 -2
  73. package/packages/core/src/harness/executors.ts +493 -0
  74. package/packages/core/src/harness/index.ts +12 -2
  75. package/packages/core/src/hooks/index.ts +203 -0
  76. package/packages/core/src/images/index.ts +161 -0
  77. package/packages/core/src/index.ts +1 -0
  78. package/packages/core/src/mcp/MCPClient.ts +3 -3
  79. package/packages/core/src/mcp/hot-reload.ts +5 -5
  80. package/packages/core/src/mcp/tool-sync.ts +5 -5
  81. package/packages/core/src/mcp/transports/index.ts +2 -2
  82. package/packages/core/src/mcp/transports/sse.ts +1 -1
  83. package/packages/core/src/models/index.ts +36 -0
  84. package/packages/core/src/multimodal/index.ts +2 -2
  85. package/packages/core/src/multimodal/vision-service.ts +51 -19
  86. package/packages/core/src/plugins/loader.ts +4 -1
  87. package/packages/core/src/resilience/circuit-breaker.ts +16 -5
  88. package/packages/core/src/resilience/index.ts +13 -0
  89. package/packages/core/src/resilience/retry.ts +1 -1
  90. package/packages/core/src/scheduler/CronScheduler.ts +54 -27
  91. package/packages/core/src/scheduler/cron/expression.ts +165 -0
  92. package/packages/core/src/scheduler/cron/index.ts +10 -0
  93. package/packages/core/src/scheduler/cron/job.ts +339 -0
  94. package/packages/core/src/scheduler/cron/next-run.ts +121 -0
  95. package/packages/core/src/scheduler/cron/zoned-time.ts +138 -0
  96. package/packages/core/src/scheduler/index.ts +21 -3
  97. package/packages/core/src/scheduler/integration.ts +25 -14
  98. package/packages/core/src/scheduler/types.ts +3 -18
  99. package/packages/core/src/services/agents.ts +268 -0
  100. package/packages/core/src/services/cron.ts +257 -0
  101. package/packages/core/src/services/endpoints.ts +289 -0
  102. package/packages/core/src/services/ethics.ts +107 -0
  103. package/packages/core/src/services/images.ts +212 -0
  104. package/packages/core/src/services/index.ts +112 -0
  105. package/packages/core/src/services/mcp.ts +201 -0
  106. package/packages/core/src/services/memory.ts +133 -0
  107. package/packages/core/src/services/models.ts +179 -0
  108. package/packages/core/src/services/providers.ts +152 -0
  109. package/packages/core/src/services/setup.ts +222 -0
  110. package/packages/core/src/services/skills.ts +241 -0
  111. package/packages/core/src/services/swarms.ts +307 -0
  112. package/packages/core/src/services/tools.ts +106 -0
  113. package/packages/core/src/sessions/index.ts +268 -0
  114. package/packages/core/src/sessions/resolve.ts +108 -0
  115. package/packages/core/src/skills/SkillLoader.ts +8 -1
  116. package/packages/core/src/skills/bundled/artifacts/artifact_reader/SKILL.md +105 -0
  117. package/packages/core/src/skills/bundled/cron_manager/SKILL.md +21 -11
  118. package/packages/core/src/skills/bundled/images/image_editor/SKILL.md +120 -0
  119. package/packages/core/src/skills/bundled/web/browser_automate/SKILL.md +12 -3
  120. package/packages/core/src/skills/bundled/web/browser_scrape/SKILL.md +22 -7
  121. package/packages/core/src/skills/bundled-data.generated.ts +110 -12
  122. package/packages/core/src/storage/bootstrap.ts +107 -12
  123. package/packages/core/src/storage/causal-events.ts +1 -1
  124. package/packages/core/src/storage/collections.ts +138 -2
  125. package/packages/core/src/storage/crypto.ts +35 -4
  126. package/packages/core/src/storage/hive.ts +1 -1
  127. package/packages/core/src/storage/hivedb.ts +10 -1
  128. package/packages/core/src/storage/index.ts +2 -1
  129. package/packages/core/src/storage/onboarding.ts +61 -45
  130. package/packages/core/src/storage/reconcile.ts +11 -6
  131. package/packages/core/src/storage/seed.ts +191 -23
  132. package/packages/core/src/storage/usage.ts +3 -3
  133. package/packages/core/src/swarm/AgentExecutor.ts +2 -2
  134. package/packages/core/src/swarm/Coordinator.ts +8 -8
  135. package/packages/core/src/swarm/EventBridge.ts +2 -2
  136. package/packages/core/src/swarm/RoleSwarm.ts +234 -0
  137. package/packages/core/src/swarm/TaskGraph.ts +2 -2
  138. package/packages/core/src/swarm/index.ts +7 -0
  139. package/packages/core/src/swarm/presets/HiveLearnPreset.ts +2 -2
  140. package/packages/core/src/swarm/presets/ResearchPreset.ts +2 -2
  141. package/packages/core/src/swarm/strategies/ParallelStrategy.ts +1 -1
  142. package/packages/core/src/swarm/strategies/PriorityStrategy.ts +3 -3
  143. package/packages/core/src/swarm/types.ts +3 -18
  144. package/packages/core/src/tool-runtime/embedded-worker.generated.ts +21 -0
  145. package/packages/core/src/tool-runtime/index.ts +129 -14
  146. package/packages/core/src/tools/ToolExecutor.ts +7 -3
  147. package/packages/core/src/tools/agents/index.ts +18 -60
  148. package/packages/core/src/tools/cli/index.ts +55 -0
  149. package/packages/core/src/tools/core/index.ts +52 -4
  150. package/packages/core/src/tools/cron/index.ts +8 -8
  151. package/packages/core/src/tools/images/index.ts +130 -0
  152. package/packages/core/src/tools/index.ts +14 -1
  153. package/packages/core/src/tools/office/office-escribir-xlsx.ts +2 -1
  154. package/packages/core/src/tools/office/office-leer-xlsx.ts +2 -1
  155. package/packages/core/src/tools/office/xlsx-loader.ts +19 -0
  156. package/packages/core/src/tools/web/artifact-inspect.ts +2 -2
  157. package/packages/core/src/tools/web/artifact-read.ts +162 -0
  158. package/packages/core/src/tools/web/browser-backend.ts +141 -44
  159. package/packages/core/src/tools/web/browser-click.ts +2 -2
  160. package/packages/core/src/tools/web/browser-extract.ts +2 -2
  161. package/packages/core/src/tools/web/browser-navigate.ts +2 -2
  162. package/packages/core/src/tools/web/browser-screenshot.ts +12 -5
  163. package/packages/core/src/tools/web/browser-script.ts +2 -2
  164. package/packages/core/src/tools/web/browser-service.ts +63 -384
  165. package/packages/core/src/tools/web/browser-session.ts +125 -0
  166. package/packages/core/src/tools/web/browser-type.ts +2 -2
  167. package/packages/core/src/tools/web/browser-wait.ts +2 -2
  168. package/packages/core/src/tools/web/computer-use.ts +553 -0
  169. package/packages/core/src/tools/web/index.ts +8 -1
  170. package/packages/core/src/tools/web/webview-backend.ts +460 -21
  171. package/packages/core/src/utils/index.ts +1 -0
  172. package/packages/core/src/utils/logger.ts +12 -4
  173. package/packages/core/src/utils/redact-binary.ts +17 -0
  174. package/packages/core/src/utils/toon.ts +1 -1
  175. package/packages/core/src/voice/index.ts +6 -6
  176. package/bun.lock +0 -859
  177. package/bunfig.toml +0 -9
  178. package/docs/API-AGENTS.md +0 -367
  179. package/docs/API-CONTEXT-COMPILER.md +0 -249
  180. package/docs/API-DAG-SCHEDULER.md +0 -273
  181. package/docs/API-TOOLS-SKILLS-CHANNELS.md +0 -446
  182. package/docs/API-WORKERS-EVENTS.md +0 -299
  183. package/docs/HIVE-HARNESS.md +0 -113
  184. package/docs/INDEX.md +0 -190
  185. package/docs/TEMPLATE-HIVE-APP.md +0 -360
  186. package/packages/cli/package.json +0 -17
  187. package/packages/cli/src/commands/create-app.test.ts +0 -180
  188. package/packages/core/package.json +0 -70
  189. package/packages/core/src/api/createAgent.test.ts +0 -160
  190. package/packages/core/src/canvas/canvas.test.ts +0 -36
  191. package/packages/core/src/channels/channels.test.ts +0 -18
  192. package/packages/core/src/ethics/EthicsGuard.test.ts +0 -108
  193. package/packages/core/src/gateway/gateway.test.ts +0 -38
  194. package/packages/core/src/memory/Scratchpad.test.ts +0 -68
  195. package/packages/core/src/scheduler/scheduler.test.ts +0 -15
  196. package/packages/core/src/skills/skills.test.ts +0 -62
  197. package/packages/core/src/swarm/swarm.test.ts +0 -24
  198. package/packages/core/src/tool-runtime/tool-runtime.test.ts +0 -99
  199. package/packages/core/src/tools/ToolRegistry.test.ts +0 -98
  200. package/packages/core/src/tools/api/api-request.test.ts +0 -164
  201. package/packages/core/src/tools/web/browser-service.test.ts +0 -83
  202. package/packages/core/src/workers/workers.test.ts +0 -41
  203. package/scripts/bump-version.ts +0 -248
  204. package/scripts/generate-skill-bundle.ts +0 -108
  205. package/test/acceptance-checks.test.ts +0 -403
  206. package/test/agent-loop-terminal-synthesis.test.ts +0 -32
  207. package/test/browser-backend.test.ts +0 -308
  208. package/test/catalog-agents-stay-enabled.test.ts +0 -117
  209. package/test/causal-events.test.ts +0 -117
  210. package/test/compaction.test.ts +0 -105
  211. package/test/context-compiler.test.ts +0 -269
  212. package/test/curator.test.ts +0 -130
  213. package/test/durable-queue.test.ts +0 -114
  214. package/test/harness-barrel.test.ts +0 -64
  215. package/test/hive-helpers.test.ts +0 -130
  216. package/test/hivedb-search.test.ts +0 -189
  217. package/test/internal-turns.test.ts +0 -166
  218. package/test/job-idempotency.test.ts +0 -68
  219. package/test/job-retry-backoff.test.ts +0 -184
  220. package/test/job-store.test.ts +0 -381
  221. package/test/llm-retry.test.ts +0 -97
  222. package/test/memory-perf.test.ts +0 -774
  223. package/test/minimal-loadout.test.ts +0 -78
  224. package/test/model-catalog.test.ts +0 -105
  225. package/test/preload.ts +0 -12
  226. package/test/reflector.test.ts +0 -320
  227. package/test/retention-cap.test.ts +0 -91
  228. package/test/retired-capabilities-pruned.test.ts +0 -192
  229. package/test/run-store.test.ts +0 -355
  230. package/test/scratchpad.test.ts +0 -74
  231. package/test/secrets-durability.test.ts +0 -119
  232. package/test/seed-model-reseed.test.ts +0 -155
  233. package/test/setup-agent-seed.test.ts +0 -264
  234. package/test/tool-inventory.test.ts +0 -65
  235. package/test/tool-runtime.test.ts +0 -258
  236. package/test/tool-selector-runtime-tools.test.ts +0 -117
  237. package/test/toon.test.ts +0 -429
  238. package/tsconfig.json +0 -42
@@ -33,22 +33,9 @@ export const memoryWriteTool: Tool = {
33
33
  required: ["title", "content"],
34
34
  },
35
35
  execute: async (params: Record<string, unknown>) => {
36
- const title = params.title as string;
37
- const content = params.content as string;
38
-
39
36
  try {
40
- const memoryCol = await col<MemoryDoc>("memory");
41
- const existing = await memoryCol.get(title);
42
- const now = Date.now();
43
- await memoryCol.put(title, {
44
- id: title,
45
- title,
46
- content,
47
- created_at: existing?.doc.created_at ?? now,
48
- updated_at: now,
49
- }, existing ? { expectedVersion: existing.version } : { expectedVersion: 0 });
50
-
51
- return { ok: true, title, message: "Memory saved." };
37
+ const entry = await writeMemory(params.title as string, params.content as string);
38
+ return { ok: true, title: entry.title, message: "Memory saved." };
52
39
  } catch (error) {
53
40
  return { ok: false, error: `Failed to save memory: ${(error as Error).message}` };
54
41
  }
@@ -69,21 +56,15 @@ export const memoryReadTool: Tool = {
69
56
  },
70
57
  execute: async (params: Record<string, unknown>) => {
71
58
  const title = params.title as string;
72
-
73
59
  try {
74
- const memoryCol = await col<MemoryDoc>("memory");
75
- const entry = await memoryCol.get(title);
76
-
77
- if (!entry) {
78
- return { ok: false, error: `Memory not found: ${title}` };
79
- }
80
-
60
+ const entry = await readMemory(title);
61
+ if (!entry) return { ok: false, error: `Memory not found: ${title}` };
81
62
  return {
82
63
  ok: true,
83
- title: entry.doc.title,
84
- content: entry.doc.content,
85
- createdAt: new Date(entry.doc.created_at).toISOString(),
86
- updatedAt: new Date(entry.doc.updated_at).toISOString(),
64
+ title: entry.title,
65
+ content: entry.content,
66
+ createdAt: new Date(entry.createdAt).toISOString(),
67
+ updatedAt: new Date(entry.updatedAt).toISOString(),
87
68
  };
88
69
  } catch (error) {
89
70
  return { ok: false, error: `Failed to read memory: ${(error as Error).message}` };
@@ -102,15 +83,11 @@ export const memoryListTool: Tool = {
102
83
  },
103
84
  execute: async () => {
104
85
  try {
105
- const memoryCol = await col<MemoryDoc>("memory");
106
- const notes = (await memoryCol.scan({}))
107
- .map(e => e.doc)
108
- .sort((a, b) => b.updated_at - a.updated_at);
109
-
86
+ const entries = await listMemories();
110
87
  return {
111
88
  ok: true,
112
- count: notes.length,
113
- entries: notes.map((n) => ({ title: n.title, createdAt: new Date(n.created_at).toISOString() })),
89
+ count: entries.length,
90
+ entries: entries.map((n) => ({ title: n.title, createdAt: new Date(n.createdAt).toISOString() })),
114
91
  };
115
92
  } catch (error) {
116
93
  return { ok: false, error: `Failed to list memories: ${(error as Error).message}` };
@@ -132,23 +109,9 @@ export const memorySearchTool: Tool = {
132
109
  },
133
110
  execute: async (params: Record<string, unknown>) => {
134
111
  const query = params.query as string;
135
- const needle = query.toLowerCase();
136
-
137
112
  try {
138
- const memoryCol = await col<MemoryDoc>("memory");
139
- const notes = (await memoryCol.scan({}))
140
- .map(e => e.doc)
141
- .filter(n => n.content.toLowerCase().includes(needle) || n.title.toLowerCase().includes(needle));
142
-
143
- return {
144
- ok: true,
145
- query,
146
- count: notes.length,
147
- results: notes.map((n) => ({
148
- title: n.title,
149
- snippet: n.content.slice(0, 200) + (n.content.length > 200 ? "..." : ""),
150
- })),
151
- };
113
+ const results = await searchMemories(query);
114
+ return { ok: true, query, count: results.length, results };
152
115
  } catch (error) {
153
116
  return { ok: false, error: `Failed to search memories: ${(error as Error).message}` };
154
117
  }
@@ -169,17 +132,9 @@ export const memoryDeleteTool: Tool = {
169
132
  },
170
133
  execute: async (params: Record<string, unknown>) => {
171
134
  const title = params.title as string;
172
-
173
135
  try {
174
- const memoryCol = await col<MemoryDoc>("memory");
175
- const existing = await memoryCol.get(title);
176
-
177
- if (!existing) {
178
- return { ok: false, error: `Memory not found: ${title}` };
179
- }
180
-
181
- await memoryCol.delete(title);
182
-
136
+ const borrada = await deleteMemory(title);
137
+ if (!borrada) return { ok: false, error: `Memory not found: ${title}` };
183
138
  return { ok: true, title, message: "Memory deleted." };
184
139
  } catch (error) {
185
140
  return { ok: false, error: `Failed to delete memory: ${(error as Error).message}` };
@@ -1202,6 +1157,9 @@ export const busReadTool: Tool = {
1202
1157
 
1203
1158
  import crypto from "crypto";
1204
1159
  import { getAvailableModelsTool } from "./get-available-models.ts";
1160
+ // Las tools de memoria son envoltorios: la implementación vive en services/memory.ts,
1161
+ // para que una UI pueda usarla sin pasar por el formato que espera el modelo.
1162
+ import { writeMemory, readMemory, listMemories, searchMemories, deleteMemory } from "../../services/memory.ts";
1205
1163
 
1206
1164
  export function createTools(): Tool[] {
1207
1165
  return [
@@ -10,6 +10,7 @@ import type { Tool } from "../types.ts";
10
10
  import { logger } from "../../utils/logger.ts";
11
11
  import { resolveInWorkspace, getWorkspace, expandPath } from "../filesystem/workspace-guard.ts";
12
12
  import * as fs from "node:fs";
13
+ import { loadConfig } from "../../config/loader.ts";
13
14
 
14
15
  const log = logger.child("cli-exec");
15
16
 
@@ -28,6 +29,53 @@ const BLOCKED_PATTERNS: Array<{ pattern: RegExp; reason: string }> = [
28
29
  { pattern: /format\s+[a-z]:/i, reason: "disk format (Windows)" },
29
30
  ];
30
31
 
32
+ /** El primer token del comando: `git status` → `git`. Es lo que se compara. */
33
+ function commandName(command: string): string {
34
+ const limpio = command.trim().replace(/^\s*(sudo|env|nohup)\s+/i, "");
35
+ return (limpio.split(/[\s;|&<>]/)[0] ?? "").split("/").pop() ?? "";
36
+ }
37
+
38
+ /**
39
+ * Aplica la política de comandos que el usuario configuró.
40
+ *
41
+ * `tools.exec.allowlist` y `.denylist` existían en el esquema de configuración y
42
+ * **no las leía nadie**: alguien podía escribir `denylist: ["curl", "rm"]`
43
+ * creyendo que restringía a sus agentes, y no restringía nada. En una opción de
44
+ * seguridad eso es peor que no tenerla, porque da confianza falsa.
45
+ *
46
+ * `BLOCKED_PATTERNS` sigue siendo incondicional: lo catastrófico se bloquea
47
+ * haya o no configuración. Esta capa es la que el usuario controla.
48
+ *
49
+ * Precedencia: si hay allowlist, sólo eso se permite (es la postura más
50
+ * restrictiva y la que alguien espera al escribirla). La denylist se aplica
51
+ * después, para poder tener una allowlist amplia con excepciones puntuales.
52
+ *
53
+ * Devuelve el motivo del rechazo, o `null` si no hay objeción.
54
+ */
55
+ function checkExecPolicy(command: string): string | null {
56
+ const exec = loadConfig().tools?.exec;
57
+ if (!exec) return null;
58
+
59
+ if (exec.enabled === false) {
60
+ return "La ejecución de comandos está deshabilitada en la configuración";
61
+ }
62
+
63
+ const nombre = commandName(command).toLowerCase();
64
+ if (!nombre) return null;
65
+
66
+ const permitidos = exec.allowlist?.map((c) => c.toLowerCase());
67
+ if (permitidos?.length && !permitidos.includes(nombre)) {
68
+ return `Comando no permitido: "${nombre}". Permitidos: ${permitidos.join(", ")}`;
69
+ }
70
+
71
+ const denegados = exec.denylist?.map((c) => c.toLowerCase());
72
+ if (denegados?.includes(nombre)) {
73
+ return `Comando denegado por la configuración: "${nombre}"`;
74
+ }
75
+
76
+ return null; // sin objeción
77
+ }
78
+
31
79
  export const cliExecTool: Tool = {
32
80
  name: "cli_exec",
33
81
  // Long-running commands need a generous runtime ceiling (the tool allows
@@ -75,6 +123,13 @@ export const cliExecTool: Tool = {
75
123
  return { ok: false, error: `Working directory not found: ${cwd}` };
76
124
  }
77
125
 
126
+ // ── Política configurable por el usuario ──────────────────────────────────
127
+ const objecion = checkExecPolicy(command);
128
+ if (objecion) {
129
+ log.warn(`bloqueado por configuración: ${command}`);
130
+ return { ok: false, error: objecion };
131
+ }
132
+
78
133
  // ── Dangerous pattern check ────────────────────────────────────────────────
79
134
  for (const { pattern, reason } of BLOCKED_PATTERNS) {
80
135
  if (pattern.test(command)) {
@@ -136,6 +136,40 @@ function translateQueryToEnglish(query: string): string {
136
136
 
137
137
  // ─── search_knowledge ────────────────────────────────────────────────────────
138
138
 
139
+ /**
140
+ * Las tools que el agente que pregunta puede realmente usar.
141
+ *
142
+ * El descubrimiento buscaba contra el índice global y devolvía cualquier
143
+ * coincidencia, sin mirar la lista blanca de quien preguntaba. La ejecución sí
144
+ * estaba protegida —`context-compiler.ts` recorta `allTools`, así que una tool
145
+ * fuera de la lista no se puede resolver ni llamar— pero el agente igual veía su
146
+ * nombre y su descripción. Filtrar acá cierra esa fuga y, de paso, deja de
147
+ * ofrecerle al modelo capacidades que no va a poder usar, que es una forma
148
+ * segura de hacerle perder un turno.
149
+ *
150
+ * Sin agente en el contexto (una llamada suelta, un test) no se filtra nada.
151
+ */
152
+ async function allowedToolNames(agentId?: string): Promise<Set<string> | null> {
153
+ if (!agentId) return null;
154
+ try {
155
+ const agents = await col<AgentDoc>("agents");
156
+ const entry = await agents.get(agentId);
157
+ if (!entry) return null;
158
+
159
+ const raw = entry.doc.tool_allowlist_json ?? entry.doc.tools_json;
160
+ if (!raw) return null; // sin lista declarada, descubrimiento abierto
161
+
162
+ const { expandToolAllowlist } = await import("../../agent/delegation-runtime.ts");
163
+ const patrones = JSON.parse(raw) as string[];
164
+ if (!Array.isArray(patrones) || patrones.length === 0) return new Set();
165
+
166
+ const { MINIMAL_TOOLS } = await import("../../agent/minimal-loadout.ts");
167
+ return new Set([...MINIMAL_TOOLS, ...expandToolAllowlist(patrones)]);
168
+ } catch {
169
+ return null;
170
+ }
171
+ }
172
+
139
173
  export const searchKnowledgeTool: Tool = {
140
174
  name: "search_knowledge",
141
175
  description: "Busca en TODO el conocimiento de Hive: tools nativas, MCP, skills, agentes de catálogo y playbook.",
@@ -158,10 +192,11 @@ export const searchKnowledgeTool: Tool = {
158
192
  },
159
193
  required: ["query"],
160
194
  },
161
- execute: async (params: Record<string, unknown>) => {
195
+ execute: async (params: Record<string, unknown>, config?: any) => {
162
196
  const query = params.query as string;
163
197
  const type = (params.type as string) ?? "all";
164
198
  const limit = (params.limit as number) ?? 10;
199
+ const usuarioActual = (config?.configurable?.user_id as string) ?? "";
165
200
  const MIN_RESULTS_FOR_BILINGUAL = 2;
166
201
 
167
202
  // Map the tool's `type` param onto capability types
@@ -187,6 +222,8 @@ export const searchKnowledgeTool: Tool = {
187
222
 
188
223
  const result: any = { query, type, tools: [], skills: [], playbook: [], toolsmcp: [], agents: [] };
189
224
 
225
+ const permitidas = await allowedToolNames(config?.configurable?.agent_id);
226
+
190
227
  // ─── Hydration from HiveDB collections (index stores only ids + search text) ──
191
228
 
192
229
  const coreCatalog = new Map(CORE_TOOL_CATALOG.map(t => [t.name, t]));
@@ -198,6 +235,10 @@ export const searchKnowledgeTool: Tool = {
198
235
  const agentsCol = await col<AgentDoc>("agents");
199
236
 
200
237
  async function hydrateTool(hit: CapabilityHit): Promise<any | null> {
238
+ // Ofrecerle al modelo una tool que no puede ejecutar es hacerle perder
239
+ // un turno, además de contarle qué existe fuera de su alcance.
240
+ if (permitidas && !permitidas.has(hit.rawId)) return null;
241
+
201
242
  const entry = await toolsCol.get(hit.rawId);
202
243
  if (entry) {
203
244
  const row = entry.doc;
@@ -232,6 +273,10 @@ export const searchKnowledgeTool: Tool = {
232
273
  const entry = await playbookCol.get(hit.rawId);
233
274
  const p = entry?.doc;
234
275
  if (!p || !p.active) return null;
276
+ // Mismo alcance que la inyección en el prompt: lo global más lo propio.
277
+ // Sin esto, el agente puede buscar en el playbook y leerle a un usuario
278
+ // lo que aprendió de otro, por la puerta de al lado.
279
+ if (p.user_id !== "" && p.user_id !== usuarioActual) return null;
235
280
  return {
236
281
  id: p.id, rule: p.rule, category: p.category,
237
282
  applicable_to: p.applicable_to ? JSON.parse(p.applicable_to) : null,
@@ -346,14 +391,17 @@ export const notifyTool: Tool = {
346
391
  required: ["message"],
347
392
  },
348
393
  execute: async (params: Record<string, unknown>, config?: any) => {
349
- const { sendToUserChannel } = await import("../../gateway/channel-notify");
394
+ const { sendToUserChannel } = await import("../../gateway/channel-notify.ts");
350
395
  const message = params.message as string;
351
396
  const channel = (config?.configurable?.channel as string) ?? "webchat";
352
397
  const userId = (config?.configurable?.user_id as string) ?? "";
353
398
 
354
399
  log.info(`[notify] Sending to ${channel}/${userId}: ${message.substring(0, 80)}`);
355
400
 
356
- const result = await sendToUserChannel(channel, userId, message)
401
+ // El aviso tiene que volver al hilo del que salió: sin el threadId,
402
+ // `notifyChannel` no sabe a qué conversación del canal responder.
403
+ const threadId = config?.configurable?.thread_id as string | undefined;
404
+ const result = await sendToUserChannel(channel, userId, message, { threadId })
357
405
  if (!result.ok) throw new Error(`Channel send failed: ${result.error}`)
358
406
  return result
359
407
  },
@@ -423,7 +471,7 @@ export const reportProgressTool: Tool = {
423
471
  required: ["progress", "message"],
424
472
  },
425
473
  execute: async (params: Record<string, unknown>, config?: any) => {
426
- const { sendToUserChannel } = await import("../../gateway/channel-notify");
474
+ const { sendToUserChannel } = await import("../../gateway/channel-notify.ts");
427
475
  const progress = params.progress as number;
428
476
  const message = params.message as string;
429
477
  const taskId = (params.task_id as string) ?? null;
@@ -8,11 +8,11 @@
8
8
  * @category cron
9
9
  */
10
10
 
11
- import type { Tool } from "../types";
12
- import { col, toIndexable } from "../../storage/hive";
13
- import type { UserDoc, UserIdentityDoc, ChannelDoc, CronJobDoc, TaskRunDoc } from "../../storage/collections";
14
- import { logger } from "../../utils/logger";
15
- import { Cron } from "croner";
11
+ import type { Tool } from "../types.ts";
12
+ import { col, toIndexable } from "../../storage/hive.ts";
13
+ import type { UserDoc, UserIdentityDoc, ChannelDoc, CronJobDoc, TaskRunDoc } from "../../storage/collections.ts";
14
+ import { logger } from "../../utils/logger.ts";
15
+ import { Cron } from "../../scheduler/cron/index.ts";
16
16
 
17
17
  const log = logger.child("CronTools");
18
18
 
@@ -114,9 +114,9 @@ export const cronCreateTool: Tool = {
114
114
  tool_name: { type: "string", description: "Specific tool to execute (optional)" },
115
115
  max_runs: { type: "number", description: "Maximum executions (optional, null = unlimited)" },
116
116
  channel: { type: "string", description: "Notification channel (system, telegram, discord, whatsapp, cli)" },
117
- start_at: { type: "string", description: "ISO 8601 datetime: start of execution window (Croner startAt). Optional." },
118
- stop_at: { type: "string", description: "ISO 8601 datetime: end of execution window (Croner stopAt). Optional." },
119
- dom_and_dow: { type: "boolean", description: "If true, both day-of-month AND day-of-week must match (Croner domAndDow). Default: false (OR logic)" },
117
+ start_at: { type: "string", description: "ISO 8601 datetime: start of execution window. Optional." },
118
+ stop_at: { type: "string", description: "ISO 8601 datetime: end of execution window. Optional." },
119
+ dom_and_dow: { type: "boolean", description: "If true, both day-of-month AND day-of-week must match. Default: false (OR logic)" },
120
120
  },
121
121
  required: ["name", "task", "task_type"],
122
122
  },
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Tools de imagen — redimensionar, convertir y medir.
3
+ *
4
+ * Se apoyan en `Bun.Image` (ver `images/`), así que no agregan dependencias: el
5
+ * SDK ya exige Bun ≥ 1.4.
6
+ *
7
+ * Trabajan sobre **artefactos**, no sobre base64 suelto, y eso es deliberado.
8
+ * Devolver una imagen en base64 al modelo es exactamente lo que llena la
9
+ * ventana de contexto —el problema que `mcp-result-normalizer.ts` existe para
10
+ * evitar—. Acá la entrada es un `artifact_id` y la salida es otro: el modelo
11
+ * maneja referencias y sólo mira la imagen si de verdad la necesita.
12
+ *
13
+ * Como cualquier otra tool, se activan o desactivan desde el catálogo.
14
+ */
15
+
16
+ import type { Tool } from "../types.ts";
17
+ import { createArtifact, readArtifactBytes } from "../../artifacts/store.ts";
18
+ import { measureImage, transformImage, imagesSupported, type ImageFormat } from "../../images/index.ts";
19
+ import { resolveUserId } from "../../storage/onboarding.ts";
20
+ import { logger } from "../../utils/logger.ts";
21
+
22
+ const log = logger.child("tools/images");
23
+
24
+ const FORMATOS: ImageFormat[] = ["jpeg", "png", "webp", "avif", "heic"];
25
+
26
+ /** Mensaje común: sin Bun 1.4 estas tools no pueden hacer nada. */
27
+ function sinSoporte() {
28
+ return { ok: false, error: "El procesamiento de imágenes necesita Bun >= 1.4 (Bun.Image no está disponible)" };
29
+ }
30
+
31
+ async function leerArtefacto(artifactId: string) {
32
+ const datos = await readArtifactBytes(artifactId);
33
+ if (!datos) return null;
34
+ return datos;
35
+ }
36
+
37
+ export const imageMetadataTool: Tool = {
38
+ name: "image_metadata",
39
+ description:
40
+ "Lee las dimensiones y el formato de una imagen guardada, sin abrirla ni cargarla al contexto. " +
41
+ "Spanish: medir imagen, dimensiones de la imagen, tamaño de la foto, formato de imagen",
42
+ parameters: {
43
+ type: "object",
44
+ properties: {
45
+ artifact_id: { type: "string", description: "Id del artefacto que contiene la imagen" },
46
+ },
47
+ required: ["artifact_id"],
48
+ },
49
+ execute: async (params: Record<string, unknown>) => {
50
+ if (!imagesSupported()) return sinSoporte();
51
+ const id = params.artifact_id as string;
52
+ try {
53
+ const datos = await leerArtefacto(id);
54
+ if (!datos) return { ok: false, error: `No encontré el artefacto ${id}` };
55
+ const meta = await measureImage(datos.bytes);
56
+ return { ok: true, artifact_id: id, ...meta, bytes: datos.bytes.length };
57
+ } catch (error) {
58
+ return { ok: false, error: `No pude leer la imagen: ${(error as Error).message}` };
59
+ }
60
+ },
61
+ };
62
+
63
+ export const imageTransformTool: Tool = {
64
+ name: "image_transform",
65
+ description:
66
+ "Redimensiona, rota o convierte de formato una imagen guardada y devuelve un artefacto nuevo. " +
67
+ "El original no se toca. Spanish: redimensionar imagen, cambiar tamaño, convertir a webp, " +
68
+ "comprimir imagen, rotar foto, achicar imagen",
69
+ parameters: {
70
+ type: "object",
71
+ properties: {
72
+ artifact_id: { type: "string", description: "Id del artefacto de origen" },
73
+ width: { type: "number", description: "Ancho en píxeles. Si sólo se da uno, se mantiene la proporción" },
74
+ height: { type: "number", description: "Alto en píxeles" },
75
+ format: { type: "string", description: "Formato de salida", enum: FORMATOS },
76
+ quality: { type: "number", description: "1–100, sólo para formatos con pérdida", minimum: 1, maximum: 100 },
77
+ rotate: { type: "number", description: "Grados: 90, 180 o 270" },
78
+ },
79
+ required: ["artifact_id"],
80
+ },
81
+ execute: async (params: Record<string, unknown>) => {
82
+ if (!imagesSupported()) return sinSoporte();
83
+ const id = params.artifact_id as string;
84
+
85
+ const formato = params.format as ImageFormat | undefined;
86
+ if (formato && !FORMATOS.includes(formato)) {
87
+ return { ok: false, error: `Formato no soportado: ${formato}. Disponibles: ${FORMATOS.join(", ")}` };
88
+ }
89
+
90
+ try {
91
+ const datos = await leerArtefacto(id);
92
+ if (!datos) return { ok: false, error: `No encontré el artefacto ${id}` };
93
+
94
+ const { bytes, metadata } = await transformImage(datos.bytes, {
95
+ width: params.width as number | undefined,
96
+ height: params.height as number | undefined,
97
+ format: formato,
98
+ quality: params.quality as number | undefined,
99
+ rotate: params.rotate as number | undefined,
100
+ });
101
+
102
+ const userId = (await resolveUserId({}).catch(() => null)) ?? "";
103
+ const artefacto = await createArtifact({
104
+ bytes,
105
+ mimeType: `image/${metadata.format}`,
106
+ kind: "image",
107
+ userId,
108
+ });
109
+
110
+ log.info(`imagen ${id} → ${artefacto.id} (${metadata.width}x${metadata.height} ${metadata.format})`);
111
+ // Se devuelve la referencia, nunca los bytes: el base64 en el contexto es
112
+ // justo lo que se quiere evitar.
113
+ return {
114
+ ok: true,
115
+ artifact_id: artefacto.id,
116
+ width: metadata.width,
117
+ height: metadata.height,
118
+ format: metadata.format,
119
+ bytes: bytes.length,
120
+ original_bytes: datos.bytes.length,
121
+ };
122
+ } catch (error) {
123
+ return { ok: false, error: `No pude transformar la imagen: ${(error as Error).message}` };
124
+ }
125
+ },
126
+ };
127
+
128
+ export function createTools(): Tool[] {
129
+ return [imageMetadataTool, imageTransformTool];
130
+ }
@@ -13,8 +13,9 @@ import * as filesystem from "./filesystem/index.ts";
13
13
 
14
14
  // Web (10)
15
15
  import * as web from "./web/index.ts";
16
+ import * as images from "./images/index.ts";
16
17
 
17
- // Cron (8) - Croner-based scheduler tools
18
+ // Cron (8) - scheduler tools
18
19
  import * as cron from "./cron/index.ts";
19
20
 
20
21
  // CLI (1)
@@ -70,6 +71,7 @@ export function createAllTools(config: Config): Tool[] {
70
71
 
71
72
  // WEB (10)
72
73
  ...web.createTools(),
74
+ ...images.createTools(),
73
75
 
74
76
  // CRON (8)
75
77
  ...cron.createTools(),
@@ -215,3 +217,14 @@ export {
215
217
  export {
216
218
  apiRequestTool,
217
219
  } from "./api/index.ts";
220
+
221
+ // Arranque del navegador. Las browser tools existen en el catálogo desde el
222
+ // seed, pero no operan hasta que alguien levanta el servicio: quien construya
223
+ // sobre el SDK necesita poder hacerlo sin importar rutas internas.
224
+ export {
225
+ initializeBrowserService,
226
+ getBrowserService,
227
+ shutdownBrowser,
228
+ } from "./web/browser-service.ts";
229
+
230
+ export * from "./images/index.ts";
@@ -10,6 +10,7 @@ import type { Tool } from "../types.ts";
10
10
  import { logger } from "../../utils/logger.ts";
11
11
  import * as path from "node:path";
12
12
  import * as fs from "node:fs";
13
+ import { cargarXlsx } from "./xlsx-loader.ts";
13
14
 
14
15
  const log = logger.child("office-escribir-xlsx");
15
16
 
@@ -55,7 +56,7 @@ export const officeEscribirXlsxTool: Tool = {
55
56
  log.debug(`Generando XLSX: ${ruta}`);
56
57
 
57
58
  try {
58
- const XLSX = await import("xlsx");
59
+ const XLSX = await cargarXlsx();
59
60
 
60
61
  const workbook = XLSX.utils.book_new();
61
62
 
@@ -10,6 +10,7 @@ import type { Tool } from "../types.ts";
10
10
  import { logger } from "../../utils/logger.ts";
11
11
  import * as fs from "node:fs";
12
12
  import * as path from "node:path";
13
+ import { cargarXlsx } from "./xlsx-loader.ts";
13
14
 
14
15
  const log = logger.child("office-leer-xlsx");
15
16
 
@@ -55,7 +56,7 @@ export const officeLeerXlsxTool: Tool = {
55
56
  return { ok: false, error: `Archivo no encontrado: ${rutaAbsoluta}` };
56
57
  }
57
58
 
58
- const XLSX = await import("xlsx");
59
+ const XLSX = await cargarXlsx();
59
60
  const buffer = fs.readFileSync(rutaAbsoluta);
60
61
  const workbook = XLSX.read(buffer, { type: "buffer" });
61
62
 
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Carga el lector de hojas de cálculo, o explica por qué no puede.
3
+ *
4
+ * `xlsx` se importa en caliente para no pagarlo en cada arranque, y eso deja el
5
+ * fallo a merced del momento: si la dependencia no llegó a instalarse —su
6
+ * tarball se cayó una vez en mitad de un release— el usuario recibía un
7
+ * "Cannot find module" crudo en medio de una respuesta.
8
+ */
9
+ export async function cargarXlsx(): Promise<typeof import("xlsx")> {
10
+ try {
11
+ return await import("xlsx");
12
+ } catch (error) {
13
+ throw new Error(
14
+ "El soporte de hojas de cálculo no está disponible en esta instalación: " +
15
+ `falta la dependencia «xlsx» (${(error as Error).message}). ` +
16
+ "Reinstala las dependencias con `bun install` para recuperarlo.",
17
+ );
18
+ }
19
+ }
@@ -1,5 +1,5 @@
1
- import type { Tool } from "../types";
2
- import { inspectArtifact } from "../../artifacts/store";
1
+ import type { Tool } from "../types.ts";
2
+ import { inspectArtifact } from "../../artifacts/store.ts";
3
3
 
4
4
  export const artifactInspectTool: Tool = {
5
5
  name: "artifact_inspect",