@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
@@ -4,8 +4,8 @@ import type {
4
4
  AgentModelOverride,
5
5
  AgentWorkspaceScope,
6
6
  } from "../storage/collections";
7
- import { col, toIndexable, fromIndexable } from "../storage/hive";
8
- import { expandToolAllowlist } from "./delegation-runtime";
7
+ import { col, toIndexable, fromIndexable } from "../storage/hive.ts";
8
+ import { expandToolAllowlist } from "./delegation-runtime.ts";
9
9
 
10
10
  interface CatalogPersona {
11
11
  id: string;
@@ -37,7 +37,7 @@ ${s.workflow.map((step, index) => `${index + 1}. ${step}`).join("\n")}
37
37
 
38
38
  # QUÉ NO HACES
39
39
  ${s.prohibitions.map((rule) => `- ${rule}`).join("\n")}
40
- - No hablás con el usuario, no pedís confirmaciones directas y no delegás a otros agentes.
40
+ - No hablas con el usuario, no pedís confirmaciones directas y no delegas a otros agentes.
41
41
  - No ampliás el alcance ni usás tools fuera del loadout autorizado.
42
42
  - No declarás éxito sin evidencia comprobable para cada criterio.
43
43
 
@@ -72,10 +72,10 @@ const CATALOG_PERSONAS: CatalogPersona[] = [
72
72
  role: "Tu dominio es investigación web, contraste de fuentes y síntesis basada en evidencia.",
73
73
  receives: "Una pregunta acotada, contexto relevante, restricciones de actualidad y criterios de aceptación.",
74
74
  workflow: [
75
- "Convertí la pregunta en consultas concretas y buscá fuentes primarias o autorizadas.",
76
- "Leé las fuentes relevantes y separá hechos, inferencias y datos no confirmados.",
75
+ "Convertí la pregunta en consultas concretas y busca fuentes primarias o autorizadas.",
76
+ "Lee las fuentes relevantes y separa hechos, inferencias y datos no confirmados.",
77
77
  "Contrastá afirmaciones sensibles o discutidas con más de una fuente.",
78
- "Entregá una síntesis concisa con referencias y fechas cuando sean relevantes.",
78
+ "Entrega una síntesis concisa con referencias y fechas cuando sean relevantes.",
79
79
  ],
80
80
  prohibitions: [...COMMON_PROHIBITIONS, "No automatizás formularios ni realizás acciones en sitios."],
81
81
  quality: "Cada afirmación material debe poder rastrearse a una fuente accesible; los desacuerdos se presentan explícitamente.",
@@ -92,10 +92,10 @@ const CATALOG_PERSONAS: CatalogPersona[] = [
92
92
  role: "Tu dominio es navegación y automatización web renderizada.",
93
93
  receives: "Una acción web autorizada, URL inicial, datos permitidos, estado final esperado y límites de seguridad.",
94
94
  workflow: [
95
- "Abrí el sitio y verificá que corresponda al objetivo.",
96
- "Inspeccioná el estado antes de interactuar y usá selectores estables.",
97
- "Ejecutá solamente los clicks, escritura y esperas necesarios.",
98
- "Verificá el estado final mediante extracción y captura de pantalla.",
95
+ "Abre el sitio y verifica que corresponda al objetivo.",
96
+ "Inspeccioná el estado antes de interactuar y usa selectores estables.",
97
+ "Ejecuta solamente los clicks, escritura y esperas necesarios.",
98
+ "Verifica el estado final mediante extracción y captura de pantalla.",
99
99
  ],
100
100
  prohibitions: [...COMMON_PROHIBITIONS, "No confirmás compras, envíos, borrados o publicaciones si el principal no autorizó explícitamente ese efecto."],
101
101
  quality: "La entrega incluye URL final, estado observado y evidencia visual o estructurada posterior a la acción.",
@@ -112,10 +112,10 @@ const CATALOG_PERSONAS: CatalogPersona[] = [
112
112
  role: "Tu dominio es operaciones seguras sobre archivos y carpetas del workspace.",
113
113
  receives: "Paths relativos o autorizados, contenido solicitado, operación exacta y estado final esperado.",
114
114
  workflow: [
115
- "Resolvé todos los paths contra el workspace y comprobá su estado inicial.",
115
+ "Resuelve todos los paths contra el workspace y comprueba su estado inicial.",
116
116
  "Aplicá la mínima operación necesaria sin tocar paths ajenos.",
117
117
  "Volvé a leer o listar el resultado para comprobarlo.",
118
- "Reportá paths exactos, cambios y evidencia de readback.",
118
+ "Reporta paths exactos, cambios y evidencia de readback.",
119
119
  ],
120
120
  prohibitions: [...COMMON_PROHIBITIONS, "No ejecutás comandos shell ni modificás repositorios fuera de la operación de archivos pedida."],
121
121
  quality: "Todos los paths permanecen dentro del workspace y su contenido o ausencia final se comprueba después de la operación.",
@@ -134,10 +134,10 @@ const CATALOG_PERSONAS: CatalogPersona[] = [
134
134
  workflow: [
135
135
  "Inspeccioná el repositorio, convenciones y estado antes de editar.",
136
136
  "Determiná la causa o el diseño mínimo y modificá solo archivos pertinentes.",
137
- "Ejecutá checks, tests o builds proporcionales al riesgo.",
138
- "Entregá archivos cambiados, evidencia de validación y riesgos restantes.",
137
+ "Ejecuta checks, tests o builds proporcionales al riesgo.",
138
+ "Entrega archivos cambiados, evidencia de validación y riesgos restantes.",
139
139
  ],
140
- prohibitions: [...COMMON_PROHIBITIONS, "No sobrescribís cambios ajenos, no publicás y no delegás a subagentes CLI."],
140
+ prohibitions: [...COMMON_PROHIBITIONS, "No sobrescribís cambios ajenos, no publicás y no delegas a subagentes CLI."],
141
141
  quality: "El cambio satisface el comportamiento pedido, preserva compatibilidad y pasa las validaciones relevantes.",
142
142
  routingExamples: ["implementar una función", "arreglar un bug", "ejecutar tests de un proyecto"],
143
143
  tools: ["fs_*", "cli_exec"],
@@ -153,10 +153,10 @@ const CATALOG_PERSONAS: CatalogPersona[] = [
153
153
  role: "Tu dominio es lectura y generación de archivos Office estructurados.",
154
154
  receives: "Archivo de entrada o especificación del documento, formato final, contenido y path autorizado.",
155
155
  workflow: [
156
- "Inspeccioná entradas y confirmá el formato solicitado.",
156
+ "Inspeccioná entradas y confirma el formato solicitado.",
157
157
  "Generá o extraé contenido preservando estructura y datos.",
158
- "Comprobá que el archivo existe, no está vacío y puede reabrirse.",
159
- "Entregá el path final, resumen de contenido y prueba de reapertura.",
158
+ "Comprueba que el archivo existe, no está vacío y puede reabrirse.",
159
+ "Entrega el path final, resumen de contenido y prueba de reapertura.",
160
160
  ],
161
161
  prohibitions: [...COMMON_PROHIBITIONS, "No editás formatos binarios con tools genéricas de filesystem."],
162
162
  quality: "El artefacto debe abrir sin error con la tool lectora correspondiente y contener la estructura solicitada.",
@@ -174,9 +174,9 @@ const CATALOG_PERSONAS: CatalogPersona[] = [
174
174
  receives: "Sesión, surfaceId, flujo solicitado, datos, acciones permitidas y criterios visuales.",
175
175
  workflow: [
176
176
  "Diseñá una jerarquía pequeña con IDs únicos y un root explícito.",
177
- "Creá la superficie antes de enviar componentes.",
177
+ "Crea la superficie antes de enviar componentes.",
178
178
  "Enviá componentes válidos y después el data model enlazado.",
179
- "Comprobá acknowledgements, IDs y paths; liberá la superficie al cancelar.",
179
+ "Comprueba acknowledgements, IDs y paths; liberá la superficie al cancelar.",
180
180
  ],
181
181
  prohibitions: [...COMMON_PROHIBITIONS, "No interpretás acciones del usuario ni conversás; los eventos vuelven al principal."],
182
182
  quality: "La superficie usa el catálogo v0.9, no tiene referencias rotas y sus bindings apuntan a paths válidos.",
@@ -194,9 +194,9 @@ const CATALOG_PERSONAS: CatalogPersona[] = [
194
194
  receives: "Una automatización que Hive debe ejecutar después, su horario o recurrencia, timezone, canal y comportamiento esperado.",
195
195
  workflow: [
196
196
  "Normalizá fecha, recurrencia y timezone sin cambiar la intención.",
197
- "Creá o modificá únicamente el job solicitado.",
197
+ "Crea o modificá únicamente el job solicitado.",
198
198
  "Consultá el job persistido y su próxima ejecución.",
199
- "Entregá ID, estado, timezone y next_run_at.",
199
+ "Entrega ID, estado, timezone y next_run_at.",
200
200
  ],
201
201
  prohibitions: [
202
202
  ...COMMON_PROHIBITIONS,
@@ -223,9 +223,9 @@ const CATALOG_PERSONAS: CatalogPersona[] = [
223
223
  receives: "Endpoint autorizado, método, headers permitidos, payload, status esperado y esquema relevante.",
224
224
  workflow: [
225
225
  "Validá método, host, payload y alcance antes del request.",
226
- "Ejecutá una sola operación idempotente o explícitamente autorizada.",
227
- "Comprobá status, headers y forma de la respuesta.",
228
- "Entregá evidencia saneada sin secretos.",
226
+ "Ejecuta una sola operación idempotente o explícitamente autorizada.",
227
+ "Comprueba status, headers y forma de la respuesta.",
228
+ "Entrega evidencia saneada sin secretos.",
229
229
  ],
230
230
  prohibitions: [...COMMON_PROHIBITIONS, "No repetís mutaciones automáticamente ni cambiás método, host o payload para forzar éxito."],
231
231
  quality: "El status y contrato observados coinciden con los criterios y la evidencia no contiene credenciales.",
@@ -284,6 +284,63 @@ export function createSeedCatalogAgents(now = Date.now()): AgentDoc[] {
284
284
 
285
285
  export const CATALOG_AGENT_IDS = CATALOG_PERSONAS.map((s) => s.id);
286
286
 
287
+ /**
288
+ * Qué tools y skills necesita un subconjunto de agentes de catálogo.
289
+ *
290
+ * Devuelve la **unión**, no la lista de cada uno, y ahí está todo el asunto: las
291
+ * tools se comparten. `web_fetch` lo declaran `web_researcher` y
292
+ * `browser_operator`; `fs_*` lo declaran `workspace_file_operator` y
293
+ * `software_engineer`. Sembrar "sólo lo de este agente" dejaría a los demás sin
294
+ * capacidades que sí necesitan, y el fallo aparecería recién cuando el modelo
295
+ * descubra por BM25 una tool que no puede ejecutar.
296
+ *
297
+ * Los patrones se devuelven **sin expandir** a propósito: `expandToolAllowlist`
298
+ * necesita el registro vivo de tools, que este módulo no debe conocer. Quien
299
+ * siembre expande.
300
+ *
301
+ * `MINIMAL_TOOLS` no se incluye acá: son del coordinador, existan o no estos
302
+ * agentes, y quien siembra las añade siempre.
303
+ */
304
+ export function requiredCapabilitiesFor(agentIds: string[]): {
305
+ toolPatterns: string[];
306
+ skills: string[];
307
+ } {
308
+ const conocidos = new Set(CATALOG_AGENT_IDS);
309
+ const desconocidos = agentIds.filter((id) => !conocidos.has(id));
310
+ if (desconocidos.length > 0) {
311
+ throw new Error(`No existen en el catálogo: ${desconocidos.join(", ")}`);
312
+ }
313
+
314
+ const pedidos = new Set(agentIds);
315
+ const toolPatterns = new Set<string>();
316
+ const skills = new Set<string>();
317
+
318
+ for (const persona of CATALOG_PERSONAS) {
319
+ if (!pedidos.has(persona.id)) continue;
320
+ for (const t of persona.tools) toolPatterns.add(t);
321
+ for (const s of persona.skills) skills.add(s);
322
+ }
323
+
324
+ return { toolPatterns: [...toolPatterns], skills: [...skills] };
325
+ }
326
+
327
+ /** Las personas del catálogo, para que una UI pueda ofrecerlas al configurar. */
328
+ export function listCatalogPersonas(): Array<{
329
+ id: string;
330
+ name: string;
331
+ description: string;
332
+ tools: string[];
333
+ skills: string[];
334
+ }> {
335
+ return CATALOG_PERSONAS.map((p) => ({
336
+ id: p.id,
337
+ name: p.name,
338
+ description: p.description,
339
+ tools: p.tools,
340
+ skills: p.skills,
341
+ }));
342
+ }
343
+
287
344
  /**
288
345
  * Writes the coordinator's provider/model onto every other agent row, so the
289
346
  * whole hive is explicitly configured instead of relying on the runtime
@@ -13,24 +13,25 @@
13
13
  * Also used directly by runAgentIsolated() for worker tasks.
14
14
  */
15
15
 
16
- import { logger } from "../utils/logger"
17
- import { col, fromIndexable } from "../storage/hive"
18
- import { getHiveDb } from "../storage/hivedb"
16
+ import { logger } from "../utils/logger.ts"
17
+ import { col, fromIndexable } from "../storage/hive.ts"
18
+ import { getHiveDb } from "../storage/hivedb.ts"
19
19
  import type { HiveDB, EventInput } from "@johpaz/hive-db"
20
- import type { AgentDoc, TurnSource } from "../storage/collections"
21
- import { callLLM, resolveProviderConfig, getDefaultLLM, type LLMMessage } from "./llm-client"
22
- import { addMessage } from "./conversation-store"
23
- import { saveTrace, recordLLMUsage } from "./tracer"
24
- import { maybeCompact, clearOldToolResults } from "./compaction"
25
- import { emitCanvas } from "../canvas/emitter"
20
+ import type { AgentDoc, TurnSource } from "../storage/collections.ts"
21
+ import { callLLM, resolveProviderConfig, getDefaultLLM, type LLMMessage, type ProviderCredentials } from "./llm-client.ts"
22
+ import { addMessage } from "./conversation-store.ts"
23
+ import { saveTrace, recordLLMUsage } from "./tracer.ts"
24
+ import { maybeCompact, clearOldToolResults } from "./compaction.ts"
25
+ import { emitCanvas } from "../canvas/emitter.ts"
26
26
  import type { MCPClientManager } from "../mcp/index.ts"
27
- import { compileContext } from "./context-compiler"
28
- import { formatToolResult } from "../utils/toon"
29
- import { resolveUserId, resolveAgentId } from "../storage/onboarding"
30
- import type { ContentPart } from "../multimodal/types"
31
- import { loadConfig } from "../config/loader"
32
- import { executeToolBatch } from "../tool-runtime"
33
- import { createStuckLoopDetector, getInterventionMessage, type StuckLoopState } from "./stuck-loop"
27
+ import { compileContext } from "./context-compiler.ts"
28
+ import { formatToolResult } from "../utils/toon.ts"
29
+ import { redactBinaryStrings } from "../utils/redact-binary.ts"
30
+ import { resolveUserId, resolveAgentId } from "../storage/onboarding.ts"
31
+ import type { ContentPart } from "../multimodal/types.ts"
32
+ import { loadConfig } from "../config/loader.ts"
33
+ import { executeToolBatch } from "../tool-runtime/index.ts"
34
+ import { createStuckLoopDetector, getInterventionMessage, type StuckLoopState } from "./stuck-loop.ts"
34
35
  import {
35
36
  createRun as createAgentRun,
36
37
  checkpoint as checkpointRun,
@@ -44,9 +45,9 @@ import {
44
45
  startLeaseRenewal,
45
46
  stopLeaseRenewal,
46
47
  type RunCheckpointState,
47
- } from "./run-store"
48
- import { publishNarration } from "../events/narration"
49
- import { getNarration } from "../events/tool-narration"
48
+ } from "./run-store.ts"
49
+ import { publishNarration } from "../events/narration.ts"
50
+ import { getNarration } from "../events/tool-narration.ts"
50
51
 
51
52
  const log = logger.child("agent-loop")
52
53
 
@@ -94,6 +95,50 @@ export async function synthesizeFinalResponse(
94
95
  )
95
96
  }
96
97
 
98
+ type LoadoutContext = {
99
+ tools: Array<{ type: string; function?: { name?: string } }>
100
+ allTools: Array<{ name: string }>
101
+ }
102
+
103
+ /**
104
+ * Adds artifact_read to the loadout the moment a tool result hands the model an
105
+ * `artifact_ref` it will need to open.
106
+ *
107
+ * mcp-result-normalizer.ts moves oversized MCP text results out of the context
108
+ * window and leaves a reference behind. Discovery (search_knowledge) can find
109
+ * the reader, but that costs an iteration and assumes the model thinks to look:
110
+ * in the incident this comes from, it reached for artifact_inspect instead, got
111
+ * metadata, and burned the rest of its budget on `find` and `env` before the
112
+ * turn died on an empty synthesis. Image refs are excluded — those are carried
113
+ * to the UI, not read back by the model.
114
+ *
115
+ * Returns true when the loadout changed.
116
+ */
117
+ export function injectArtifactReadIfNeeded(toolResult: unknown, ctx: LoadoutContext): boolean {
118
+ if (!Array.isArray(toolResult)) return false
119
+
120
+ const needsReader = toolResult.some((block) =>
121
+ !!block && typeof block === "object" &&
122
+ (block as { type?: unknown }).type === "artifact_ref" &&
123
+ !String((block as { mime_type?: unknown }).mime_type ?? "").startsWith("image/")
124
+ )
125
+ if (!needsReader) return false
126
+ if (ctx.tools.some((t) => t.function?.name === "artifact_read")) return false
127
+
128
+ const reader = ctx.allTools.find((t) => t.name === "artifact_read")
129
+ if (!reader) return false
130
+
131
+ ctx.tools.push({
132
+ type: "function",
133
+ function: {
134
+ name: reader.name,
135
+ description: (reader as any).description ?? "",
136
+ parameters: (reader as any).parameters ?? { type: "object", properties: {} },
137
+ },
138
+ } as LoadoutContext["tools"][number])
139
+ return true
140
+ }
141
+
97
142
  /** Bounds a single async operation to its own timeout window, independent of any caller. */
98
143
  export async function withTimeout<T>(op: () => Promise<T>, timeoutMs: number): Promise<T> {
99
144
  let timer: ReturnType<typeof setTimeout>
@@ -171,6 +216,14 @@ export interface AgentLoopOptions {
171
216
  taskId?: string
172
217
  /** External routing/session id for progress delivery. */
173
218
  sessionId?: string
219
+ /**
220
+ * Credenciales del proveedor para ESTA llamada, en vez de las del proceso.
221
+ *
222
+ * Un host multi-tenant resuelve la key de su inquilino y la pasa acá. Sin esto
223
+ * la única fuente era el secret store de HiveDB o `process.env`, ambos globales:
224
+ * dos inquilinos concurrentes en el mismo proceso compartían credencial.
225
+ */
226
+ credentials?: ProviderCredentials
174
227
  /** Whether to resume from a previously saved checkpoint */
175
228
  resume?: boolean
176
229
  /** Run budget — overrides agent.max_iterations when set */
@@ -215,6 +268,8 @@ export interface StreamChunk {
215
268
  agent?: { messages: any[]; streamed?: boolean }
216
269
  tools?: { messages: any[] }
217
270
  usage?: { input_tokens: number; output_tokens: number }
271
+ /** Image artifacts (mcp-result-normalizer.ts) produced by tools this turn. */
272
+ artifacts?: { images: Array<{ artifactId: string; mimeType: string }> }
218
273
  }
219
274
 
220
275
  // ─── Main agent loop ──────────────────────────────────────────────────────────
@@ -280,7 +335,7 @@ export async function* runAgent(
280
335
  agentProvider = agentProvider || defaultLLM.provider
281
336
  agentModel = agentModel || defaultLLM.model
282
337
  }
283
- const providerCfg = await resolveProviderConfig(agentProvider, agentModel)
338
+ const providerCfg = await resolveProviderConfig(agentProvider, agentModel, opts.credentials)
284
339
 
285
340
  const cleanModel = providerCfg.model.replace(new RegExp(`^${providerCfg.provider}\\/`), "")
286
341
  log.info(`[agent-loop] Starting: agent=${agentName} thread=${opts.threadId} provider=${providerCfg.provider}/${cleanModel}`)
@@ -360,6 +415,10 @@ export async function* runAgent(
360
415
  let iterations = 0
361
416
  let totalInputTokens = 0
362
417
  let totalOutputTokens = 0
418
+ // Image artifacts produced by tool results this turn (post mcp-result-normalizer.ts) —
419
+ // surfaced in the final chunk so callers (webchat-turn.ts) can attach them to the
420
+ // outbound message instead of the model having to describe them in text.
421
+ const turnImageArtifacts: Array<{ artifactId: string; mimeType: string }> = []
363
422
  let lastToolSignature = ""
364
423
  let consecutiveRepeat = 0
365
424
  let idleIterations = 0
@@ -452,7 +511,7 @@ export async function* runAgent(
452
511
  iterations++
453
512
 
454
513
  const delegationGroupAtCall = opts.turnId && !opts.isolated
455
- ? await import("../gateway/delegation-groups").then((mod) => mod.getDelegationGroup(opts.turnId!))
514
+ ? await import("../gateway/delegation-groups.ts").then((mod) => mod.getDelegationGroup(opts.turnId!))
456
515
  : null
457
516
  let streamedThisCall = false
458
517
  let response: Awaited<ReturnType<typeof callLLM>>
@@ -648,6 +707,27 @@ export async function* runAgent(
648
707
  const toolResultJS = batchResult.result
649
708
  const toolMs = batchResult.durationMs
650
709
 
710
+ // Surface image artifacts (see mcp-result-normalizer.ts) so the final
711
+ // response can carry them to the UI/channel — before TOON-encoding,
712
+ // while toolResultJS is still structured.
713
+ if (Array.isArray(toolResultJS)) {
714
+ for (const block of toolResultJS) {
715
+ if (
716
+ block && typeof block === "object" &&
717
+ (block as { type?: unknown }).type === "artifact_ref" &&
718
+ typeof (block as { mime_type?: unknown }).mime_type === "string" &&
719
+ (block as { mime_type: string }).mime_type.startsWith("image/")
720
+ ) {
721
+ const ref = block as { artifact_id: string; mime_type: string }
722
+ turnImageArtifacts.push({ artifactId: ref.artifact_id, mimeType: ref.mime_type })
723
+ }
724
+ }
725
+ }
726
+
727
+ if (injectArtifactReadIfNeeded(toolResultJS, ctx)) {
728
+ log.info("[agent-loop] Tool result carries an artifact_ref — injected artifact_read into loadout")
729
+ }
730
+
651
731
  // Encode TOON only for LLM consumption (with cost calculation)
652
732
  const toolResultLLM = formatToolResult(toolResultJS, cleanModel)
653
733
 
@@ -812,7 +892,7 @@ export async function* runAgent(
812
892
  // Inject skills associated with the injected tools
813
893
  if (injectedTools.length > 0) {
814
894
  try {
815
- const skillsCol = await col<import("../storage/collections").SkillDoc>("skills")
895
+ const skillsCol = await col<import("../storage/collections.ts").SkillDoc>("skills")
816
896
  // Find skills that use any of the injected tools
817
897
  const activeSkills = (await skillsCol.scan({})).filter(e => e.doc.active)
818
898
  const skillsWithTools = activeSkills
@@ -1032,7 +1112,7 @@ export async function* runAgent(
1032
1112
  // Make one extra call without tools so it summarizes what it did.
1033
1113
  if (!finalContent) {
1034
1114
  const pendingDelegation = opts.turnId && !opts.isolated
1035
- ? await import("../gateway/delegation-groups").then((mod) => mod.getDelegationGroup(opts.turnId!))
1115
+ ? await import("../gateway/delegation-groups.ts").then((mod) => mod.getDelegationGroup(opts.turnId!))
1036
1116
  : null
1037
1117
  if (pendingDelegation) {
1038
1118
  log.info(`[agent-loop] Suppressing terminal synthesis while delegation group ${opts.turnId} is pending`)
@@ -1085,6 +1165,10 @@ export async function* runAgent(
1085
1165
  yield { usage: { input_tokens: totalInputTokens, output_tokens: totalOutputTokens } }
1086
1166
  }
1087
1167
 
1168
+ if (turnImageArtifacts.length > 0) {
1169
+ yield { artifacts: { images: turnImageArtifacts } }
1170
+ }
1171
+
1088
1172
  // ── Post-loop ────────────────────────────────────────────────────────────
1089
1173
  const durationMs = Math.round(performance.now() - t0)
1090
1174
 
@@ -1203,6 +1287,12 @@ export interface IsolatedAgentOptions {
1203
1287
  userId?: string
1204
1288
  channel?: string
1205
1289
  sessionId?: string
1290
+ /**
1291
+ * Se hereda del turno que delegó. Sin propagarla, un worker delegado volvía a
1292
+ * caer en la credencial global del proceso y la fuga entre inquilinos se
1293
+ * reabría justo en el camino de delegación.
1294
+ */
1295
+ credentials?: ProviderCredentials
1206
1296
  }
1207
1297
 
1208
1298
  export async function runAgentIsolatedDetailed(
@@ -1226,15 +1316,14 @@ export async function runAgentIsolatedDetailed(
1226
1316
  userId: opts.userId,
1227
1317
  channel: opts.channel,
1228
1318
  sessionId: opts.sessionId,
1319
+ credentials: opts.credentials,
1229
1320
  })) {
1230
1321
  if (chunk.agent?.messages?.[0]?.content) {
1231
1322
  lastContent = chunk.agent.messages[0].content
1232
1323
  }
1233
1324
  for (const message of chunk.tools?.messages ?? []) {
1234
1325
  const raw = typeof message.content === "string" ? message.content : JSON.stringify(message.content)
1235
- const safe = raw
1236
- .replace(/data:[a-z0-9.+-]+\/[a-z0-9.+-]+;base64,[a-z0-9+/=\s]+/gi, "[REDACTED_BINARY]")
1237
- .replace(/[A-Za-z0-9+/]{1000,}={0,2}/g, "[REDACTED_BINARY]")
1326
+ const safe = redactBinaryStrings(raw)
1238
1327
  toolEvidence.push(`${message.name ?? "tool"}: ${safe.slice(0, 4000)}`)
1239
1328
  if (toolEvidence.length > 8) toolEvidence.shift()
1240
1329
  }
@@ -19,8 +19,8 @@
19
19
  */
20
20
 
21
21
  import type { IndexDoc } from "@johpaz/hive-db";
22
- import { getHiveDb } from "../storage/hivedb";
23
- import { logger } from "../utils/logger";
22
+ import { getHiveDb } from "../storage/hivedb.ts";
23
+ import { logger } from "../utils/logger.ts";
24
24
 
25
25
  const log = logger.child("capability-search");
26
26
 
@@ -1,12 +1,12 @@
1
- import { col } from "../storage/hive";
2
- import type { AgentDoc } from "../storage/collections";
1
+ import { col } from "../storage/hive.ts";
2
+ import type { AgentDoc } from "../storage/collections.ts";
3
3
  import {
4
4
  applyRelativeCutoff,
5
5
  replaceCapabilityDocs,
6
6
  searchCapabilities,
7
7
  type CapabilityDoc,
8
- } from "./capability-search";
9
- import { logger } from "../utils/logger";
8
+ } from "./capability-search.ts";
9
+ import { logger } from "../utils/logger.ts";
10
10
 
11
11
  const log = logger.child("catalog-selector");
12
12
  const RELEVANCE_RATIO = 0.35;
@@ -15,7 +15,7 @@
15
15
  * short summaries in the in-memory message array before model calls.
16
16
  */
17
17
 
18
- import { logger } from "../utils/logger"
18
+ import { logger } from "../utils/logger.ts"
19
19
  import {
20
20
  getTotalTokens,
21
21
  getHistory,
@@ -24,11 +24,13 @@ import {
24
24
  getMessageCount,
25
25
  isInternalSource,
26
26
  type StoredMessage,
27
- } from "./conversation-store"
28
- import { estimateTokens } from "../utils/toon"
29
- import { callLLM, resolveProviderConfig, getDefaultLLM, type ContentPart } from "./llm-client"
30
- import { col, fromIndexable } from "../storage/hive"
31
- import type { AgentDoc, ModelDoc } from "../storage/collections"
27
+ } from "./conversation-store.ts"
28
+ import { estimateTokens } from "../utils/toon.ts"
29
+ import { callLLM, resolveProviderConfig, getDefaultLLM, type ContentPart } from "./llm-client.ts"
30
+ import { col, fromIndexable } from "../storage/hive.ts"
31
+ import type { AgentDoc, ModelDoc } from "../storage/collections.ts"
32
+ import { loadConfig } from "../config/loader.ts"
33
+ import { runBeforeCompaction } from "../hooks/index.ts"
32
34
 
33
35
  const log = logger.child("compaction")
34
36
 
@@ -51,8 +53,15 @@ export async function maybeCompact(
51
53
  try {
52
54
  const totalTokens = await getTotalTokens(threadId)
53
55
 
54
- // Use model's context window if available, otherwise use default
56
+ // Orden de precedencia: lo que el usuario configuró gana sobre lo que se
57
+ // deduce del modelo, y eso gana sobre la constante.
58
+ //
59
+ // `agent.context.compactionThreshold` estaba en el esquema de configuración
60
+ // y **no lo leía nadie**: alguien podía ajustarlo y no pasaba nada. Una
61
+ // opción que no hace nada es peor que no tenerla, porque el usuario cree
62
+ // que cambió algo.
55
63
  let effectiveThreshold = COMPACT_TOKEN_THRESHOLD
64
+ const configurado = loadConfig().agent?.context?.compactionThreshold
56
65
  try {
57
66
  const agentsCol = await col<AgentDoc>("agents")
58
67
  const coordinators = await agentsCol.findBy("role", "coordinator", { limit: 1 })
@@ -69,8 +78,18 @@ export async function maybeCompact(
69
78
  }
70
79
  } catch { /* use default threshold */ }
71
80
 
81
+ if (configurado && configurado > 0) effectiveThreshold = configurado
82
+
72
83
  if (totalTokens < effectiveThreshold) return
73
84
 
85
+ // Avisar antes de comprimir: es la última oportunidad de que alguien
86
+ // conserve algo del historial que está por resumirse.
87
+ await runBeforeCompaction({
88
+ threadId,
89
+ messageCount: await getMessageCount(threadId),
90
+ totalTokens,
91
+ }).catch(() => {})
92
+
74
93
  const summary = await getSummary(threadId)
75
94
  const totalMessages = await getMessageCount(threadId)
76
95
 
@@ -118,7 +137,7 @@ export function renderTranscript(rows: StoredMessage[], maxMsgChars = MAX_MSG_CH
118
137
  /**
119
138
  * Compress a thread's history into a summary.
120
139
  */
121
- async function compactThread(
140
+ export async function compactThread(
122
141
  threadId: string,
123
142
  notify?: { channel: string; userId: string }
124
143
  ): Promise<void> {
@@ -187,11 +206,12 @@ async function compactThread(
187
206
  // Notify user in their active channel (non-critical)
188
207
  if (notify?.channel && notify?.userId) {
189
208
  try {
190
- const { sendToUserChannel } = await import("../gateway/channel-notify")
209
+ const { sendToUserChannel } = await import("../gateway/channel-notify.ts")
191
210
  await sendToUserChannel(
192
211
  notify.channel,
193
212
  notify.userId,
194
- `🗜️ Resumí ${toSummarize.length} mensajes anteriores para mantener el contexto limpio.`
213
+ `🗜️ Resumí ${toSummarize.length} mensajes anteriores para mantener el contexto limpio.`,
214
+ { threadId }
195
215
  )
196
216
  } catch {
197
217
  // Non-critical — don't break the flow if notification fails