@johpaz/hive-sdk 0.1.6 → 0.2.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 (185) hide show
  1. package/CHANGELOG.md +135 -0
  2. package/README.md +11 -1
  3. package/package.json +18 -2
  4. package/packages/core/src/agent/acceptance-checks.ts +9 -9
  5. package/packages/core/src/agent/agent-catalog.ts +3 -3
  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 +10 -9
  10. package/packages/core/src/agent/context-compiler.ts +58 -34
  11. package/packages/core/src/agent/conversation-store.ts +31 -7
  12. package/packages/core/src/agent/curator.ts +4 -4
  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 +1 -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 +9 -5
  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 +4 -4
  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 +4 -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 +6 -6
  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 +250 -0
  49. package/packages/core/src/agent/tool-selector.ts +7 -5
  50. package/packages/core/src/agent/tracer.ts +5 -5
  51. package/packages/core/src/api/createAgent.ts +1 -1
  52. package/packages/core/src/artifacts/store.ts +84 -3
  53. package/packages/core/src/canvas/emitter.ts +2 -2
  54. package/packages/core/src/channels/telegram.ts +1 -1
  55. package/packages/core/src/channels/webchat.ts +1 -1
  56. package/packages/core/src/config/loader.ts +14 -5
  57. package/packages/core/src/events/agent-bus.ts +3 -3
  58. package/packages/core/src/events/channel-narration.ts +3 -3
  59. package/packages/core/src/events/event-bus.ts +1 -1
  60. package/packages/core/src/events/narration.ts +3 -3
  61. package/packages/core/src/gateway/delegation-groups.ts +4 -4
  62. package/packages/core/src/gateway/durable-queue.ts +5 -5
  63. package/packages/core/src/gateway/job-store.ts +5 -5
  64. package/packages/core/src/gateway/notification-inbox.ts +2 -2
  65. package/packages/core/src/gateway/server.ts +2 -2
  66. package/packages/core/src/mcp/MCPClient.ts +3 -3
  67. package/packages/core/src/mcp/hot-reload.ts +5 -5
  68. package/packages/core/src/mcp/tool-sync.ts +5 -5
  69. package/packages/core/src/mcp/transports/index.ts +2 -2
  70. package/packages/core/src/mcp/transports/sse.ts +1 -1
  71. package/packages/core/src/models/index.ts +36 -0
  72. package/packages/core/src/multimodal/index.ts +2 -2
  73. package/packages/core/src/multimodal/vision-service.ts +6 -6
  74. package/packages/core/src/plugins/loader.ts +4 -1
  75. package/packages/core/src/resilience/circuit-breaker.ts +16 -5
  76. package/packages/core/src/resilience/retry.ts +1 -1
  77. package/packages/core/src/scheduler/CronScheduler.ts +6 -6
  78. package/packages/core/src/scheduler/integration.ts +9 -9
  79. package/packages/core/src/sessions/index.ts +266 -0
  80. package/packages/core/src/storage/bootstrap.ts +34 -8
  81. package/packages/core/src/storage/causal-events.ts +1 -1
  82. package/packages/core/src/storage/collections.ts +32 -1
  83. package/packages/core/src/storage/crypto.ts +16 -2
  84. package/packages/core/src/storage/hive.ts +1 -1
  85. package/packages/core/src/storage/hivedb.ts +10 -1
  86. package/packages/core/src/storage/onboarding.ts +6 -6
  87. package/packages/core/src/storage/reconcile.ts +5 -5
  88. package/packages/core/src/storage/seed.ts +103 -13
  89. package/packages/core/src/storage/usage.ts +3 -3
  90. package/packages/core/src/swarm/AgentExecutor.ts +2 -2
  91. package/packages/core/src/swarm/Coordinator.ts +8 -8
  92. package/packages/core/src/swarm/EventBridge.ts +2 -2
  93. package/packages/core/src/swarm/RoleSwarm.ts +234 -0
  94. package/packages/core/src/swarm/TaskGraph.ts +2 -2
  95. package/packages/core/src/swarm/index.ts +7 -0
  96. package/packages/core/src/swarm/presets/HiveLearnPreset.ts +2 -2
  97. package/packages/core/src/swarm/presets/ResearchPreset.ts +2 -2
  98. package/packages/core/src/swarm/strategies/ParallelStrategy.ts +1 -1
  99. package/packages/core/src/swarm/strategies/PriorityStrategy.ts +3 -3
  100. package/packages/core/src/tools/ToolExecutor.ts +7 -3
  101. package/packages/core/src/tools/core/index.ts +2 -2
  102. package/packages/core/src/tools/cron/index.ts +4 -4
  103. package/packages/core/src/tools/web/artifact-inspect.ts +2 -2
  104. package/packages/core/src/tools/web/artifact-read.ts +162 -0
  105. package/packages/core/src/tools/web/browser-backend.ts +141 -44
  106. package/packages/core/src/tools/web/browser-click.ts +2 -2
  107. package/packages/core/src/tools/web/browser-extract.ts +2 -2
  108. package/packages/core/src/tools/web/browser-navigate.ts +2 -2
  109. package/packages/core/src/tools/web/browser-screenshot.ts +12 -5
  110. package/packages/core/src/tools/web/browser-script.ts +2 -2
  111. package/packages/core/src/tools/web/browser-service.ts +63 -384
  112. package/packages/core/src/tools/web/browser-session.ts +125 -0
  113. package/packages/core/src/tools/web/browser-type.ts +2 -2
  114. package/packages/core/src/tools/web/browser-wait.ts +2 -2
  115. package/packages/core/src/tools/web/computer-use.ts +553 -0
  116. package/packages/core/src/tools/web/index.ts +8 -1
  117. package/packages/core/src/tools/web/webview-backend.ts +460 -21
  118. package/packages/core/src/utils/index.ts +1 -0
  119. package/packages/core/src/utils/logger.ts +12 -4
  120. package/packages/core/src/utils/redact-binary.ts +17 -0
  121. package/packages/core/src/utils/toon.ts +1 -1
  122. package/packages/core/src/voice/index.ts +6 -6
  123. package/bun.lock +0 -859
  124. package/bunfig.toml +0 -9
  125. package/docs/API-AGENTS.md +0 -367
  126. package/docs/API-CONTEXT-COMPILER.md +0 -249
  127. package/docs/API-DAG-SCHEDULER.md +0 -273
  128. package/docs/API-TOOLS-SKILLS-CHANNELS.md +0 -446
  129. package/docs/API-WORKERS-EVENTS.md +0 -299
  130. package/docs/HIVE-HARNESS.md +0 -113
  131. package/docs/INDEX.md +0 -190
  132. package/docs/TEMPLATE-HIVE-APP.md +0 -360
  133. package/packages/cli/package.json +0 -17
  134. package/packages/cli/src/commands/create-app.test.ts +0 -180
  135. package/packages/core/package.json +0 -70
  136. package/packages/core/src/api/createAgent.test.ts +0 -160
  137. package/packages/core/src/canvas/canvas.test.ts +0 -36
  138. package/packages/core/src/channels/channels.test.ts +0 -18
  139. package/packages/core/src/ethics/EthicsGuard.test.ts +0 -108
  140. package/packages/core/src/gateway/gateway.test.ts +0 -38
  141. package/packages/core/src/memory/Scratchpad.test.ts +0 -68
  142. package/packages/core/src/scheduler/scheduler.test.ts +0 -15
  143. package/packages/core/src/skills/skills.test.ts +0 -62
  144. package/packages/core/src/swarm/swarm.test.ts +0 -24
  145. package/packages/core/src/tool-runtime/tool-runtime.test.ts +0 -99
  146. package/packages/core/src/tools/ToolRegistry.test.ts +0 -98
  147. package/packages/core/src/tools/api/api-request.test.ts +0 -164
  148. package/packages/core/src/tools/web/browser-service.test.ts +0 -83
  149. package/packages/core/src/workers/workers.test.ts +0 -41
  150. package/scripts/bump-version.ts +0 -248
  151. package/scripts/generate-skill-bundle.ts +0 -108
  152. package/test/acceptance-checks.test.ts +0 -403
  153. package/test/agent-loop-terminal-synthesis.test.ts +0 -32
  154. package/test/browser-backend.test.ts +0 -308
  155. package/test/catalog-agents-stay-enabled.test.ts +0 -117
  156. package/test/causal-events.test.ts +0 -117
  157. package/test/compaction.test.ts +0 -105
  158. package/test/context-compiler.test.ts +0 -269
  159. package/test/curator.test.ts +0 -130
  160. package/test/durable-queue.test.ts +0 -114
  161. package/test/harness-barrel.test.ts +0 -64
  162. package/test/hive-helpers.test.ts +0 -130
  163. package/test/hivedb-search.test.ts +0 -189
  164. package/test/internal-turns.test.ts +0 -166
  165. package/test/job-idempotency.test.ts +0 -68
  166. package/test/job-retry-backoff.test.ts +0 -184
  167. package/test/job-store.test.ts +0 -381
  168. package/test/llm-retry.test.ts +0 -97
  169. package/test/memory-perf.test.ts +0 -774
  170. package/test/minimal-loadout.test.ts +0 -78
  171. package/test/model-catalog.test.ts +0 -105
  172. package/test/preload.ts +0 -12
  173. package/test/reflector.test.ts +0 -320
  174. package/test/retention-cap.test.ts +0 -91
  175. package/test/retired-capabilities-pruned.test.ts +0 -192
  176. package/test/run-store.test.ts +0 -355
  177. package/test/scratchpad.test.ts +0 -74
  178. package/test/secrets-durability.test.ts +0 -119
  179. package/test/seed-model-reseed.test.ts +0 -155
  180. package/test/setup-agent-seed.test.ts +0 -264
  181. package/test/tool-inventory.test.ts +0 -65
  182. package/test/tool-runtime.test.ts +0 -258
  183. package/test/tool-selector-runtime-tools.test.ts +0 -117
  184. package/test/toon.test.ts +0 -429
  185. package/tsconfig.json +0 -42
package/CHANGELOG.md CHANGED
@@ -4,14 +4,149 @@
4
4
 
5
5
  ### Seguridad
6
6
 
7
+ - **La lista blanca de tools no se aplicaba al descubrimiento dinámico.**
8
+ `compileContext` sólo recortaba `allTools` cuando el agente era de catálogo
9
+ (`source === "catalog"`). Un agente creado por el usuario veía su loadout
10
+ inicial restringido, pero `search_knowledge` busca contra el índice completo y
11
+ el agent loop inyecta lo que encuentre resolviéndolo contra `allTools`: la
12
+ tool excluida terminaba siendo llamable igual. Ahora la restricción depende de
13
+ que el agente declare una lista, no de su origen. Cubierto por
14
+ `test/tool-allowlist-discovery.test.ts`.
15
+
16
+ - **Aislamiento de credenciales entre inquilinos.** `AgentLoopOptions` no tenía
17
+ forma de recibir la key del proveedor, así que la única fuente era el secret
18
+ store de HiveDB o `process.env[PROVIDER_API_KEY]`, ambos globales al proceso.
19
+ Un host multi-tenant que corriera dos workspaces en el mismo proceso les daba
20
+ la misma credencial. Se agregó `credentials` en `AgentLoopOptions`,
21
+ `IsolatedAgentOptions` y `resolveProviderConfig`; la credencial de la llamada
22
+ gana y corta ahí, sin consultar las fuentes globales ni mutar `process.env`.
23
+ Retrocompatible: sin `credentials` el comportamiento es el de siempre.
24
+ Cubierto por `test/tenant-isolation.test.ts`.
25
+
26
+
7
27
  - **`sanitizeDiagnostic` dejaba el token en claro detrás del esquema de auth.**
8
28
  La regex consumía sólo la palabra `Bearer`, así que un diagnóstico con
9
29
  `authorization: Bearer <token>` quedaba como `authorization: [REDACTED] <token>`
10
30
  y la credencial viajaba al prompt del coordinador. Afecta a **0.1.5 y
11
31
  anteriores**: el archivo viaja en el tarball publicado.
12
32
 
33
+ ### Cambiado
34
+
35
+ - **Los tests que manejan un navegador real son opt-in (`BROWSER_TESTS=1`).**
36
+ Su guarda era `isWebViewSupported()`, que sólo comprueba que exista un binario
37
+ de Chromium — no que arranque. En un runner de CI (contenedor, a menudo root)
38
+ el binario está y Chromium muere igual sin `--no-sandbox`, así que ~90 tests
39
+ de integración fallaban por el entorno. Como los tests son condición para
40
+ publicar, eso bloqueaba el release. Los describe unitarios de esos mismos
41
+ archivos —`resolveBackendKind`, detección de motor, `normalizeCookies`,
42
+ `sessionPersistenceEnabled`— siguen corriendo siempre: son los que cubren el
43
+ contrato del backend.
44
+
45
+ - **Se quitó un `mock.module` que se filtraba entre archivos de test.** El test
46
+ de aislamiento multi-tenant sustituía el módulo `storage/crypto` para no
47
+ escribir en el keychain del SO. `mock.module` es global al proceso, no al
48
+ archivo: mientras estuviera activo, cualquier otro test que importara ese
49
+ módulo recibía el doble, y `loadProviderApiKey` devolvía la key del mock. Que
50
+ mordiera dependía del orden de ejecución — pasaba en local y fallaba en CI.
51
+ Ahora el test usa un id de proveedor propio (`test-tenant-isolation`) y limpia
52
+ sus secretos, sin tocar el módulo ni la credencial de nadie.
53
+
54
+ - **`resetKeychainProbe()`** en `storage/crypto.ts`. Si el keychain del SO no
55
+ responde, el resultado se cachea a nivel de módulo para no reintentar en cada
56
+ lectura — correcto en producción, pero significa que el primer sondeo vale
57
+ para todo el proceso. Un test que sustituya `Bun.secrets` por un doble queda
58
+ cortocircuitado si algo ya sondeó y falló antes, que es lo que pasa en CI
59
+ headless.
60
+
61
+
62
+ - **Automatización web: un solo backend, `Bun.WebView`.** Se retiró
63
+ `AgentBrowserBackend`, que hablaba con el CLI de agent-browser por
64
+ subproceso. El motivo no es de estilo: medido en Bun 1.4 el WebView **sí**
65
+ corre headless (Bun lanza Chromium con `--headless`), que era la única razón
66
+ por la que agent-browser seguía siendo el default. Lo que quedaba era su
67
+ costo — ~40 ms de `Bun.spawn` por operación contra ~0,3 ms, y ~88 MB con su
68
+ propia copia de Chrome.
69
+
70
+ Lo importante para quien consume el paquete: el backend viejo ejecutaba
71
+ **`bun add agent-browser@latest` en el entorno del consumidor**, al primer uso
72
+ de una browser tool. Una versión flotante bajada de npm en runtime, en
73
+ producción. Eso ya no existe.
74
+
75
+ Requisitos ahora: un Chromium instalado (o `BUN_CHROME_PATH`) y **Bun ≥ 1.4**,
76
+ declarado en `engines`. La clave de config `tools.browser.backend` sobrevive:
77
+ `"agent-browser"` se acepta, avisa una vez y usa el WebView, así que las
78
+ configuraciones viejas no se rompen.
79
+
80
+ - **Sesión de navegador persistente** (`tools/web/browser-session.ts`). El
81
+ perfil de Chrome que abre Bun es efímero —su ruta lleva un hash que cambia
82
+ entre procesos— así que las cookies se guardan y restauran a mano. Sin esto
83
+ cada reinicio empezaba sin logins. Se controla con `tools.browser.persistSession`
84
+ (activo por defecto).
85
+
86
+ - **Nueva tool `computer_use_task`**: operar el navegador mirando la pantalla
87
+ —clic por coordenadas, escribir, navegar— cuando no hay un selector CSS
88
+ estable (canvas, UIs generadas, visores embebidos).
89
+
90
+ - CI actualizado a **Bun 1.4.0**, alineado con `hive`.
91
+
92
+ ### Añadido
93
+
94
+ - **`@johpaz/hive-sdk/sessions`** — la conversación de un usuario como una sola
95
+ cosa. Hasta acá "sesión" estaba repartida entre `thread-store` (identidad),
96
+ `conversation-store` (mensajes), `run-store` (ejecución) y un `Map` en memoria
97
+ que moría con el proceso; no existía la consulta "qué sesiones tiene este
98
+ usuario". `Session` es una vista compuesta sobre las colecciones que ya
99
+ existían — no agrega una tercera persistencia — y `Session.id` ES el
100
+ `threadId`. Incluye `createSession`, `listSessions`, `appendMessage`,
101
+ `resumeSession`, `closeSession`/`reopenSession` y `deleteSession`.
102
+
103
+ - **`@johpaz/hive-sdk/models`** — el seed de modelos con nombre propio. El
104
+ catálogo (18 proveedores, 110 modelos), las claves de modelo y el cálculo de
105
+ costo seguían viviendo bajo `storage/`; esto les da un punto de entrada sin
106
+ mover la implementación.
107
+
108
+ - **Enjambre por roles** (`runRoleSwarm` en `@johpaz/hive-sdk/swarm`) —
109
+ orquestador/trabajadores con estrategias `sequential`, `parallel` y
110
+ `hierarchical`. Es la tercera forma de armar un enjambre, junto a la
111
+ delegación por catálogo y al DAG de tareas, y la única que expresa un enjambre
112
+ como *configuración persistida* en vez de un grafo conocido de antemano. No
113
+ persiste nada: `onMessage` es el punto de enganche del consumidor.
114
+
115
+ - **`bun run drift`** (`scripts/check-drift.ts`) — compara los módulos del
116
+ cerebro contra `hive` y reporta qué falta y qué difiere, indicando de qué lado
117
+ está el avance. El SDK es la fuente de verdad pero nada lo garantizaba
118
+ estructuralmente: la última vez la divergencia llegó a compartir sólo 87 de
119
+ 224 nombres de archivo.
120
+
121
+ - **`test/exports-contract.test.ts`** — importa de verdad cada subpath declarado
122
+ en `exports`. Los deep-imports se han roto entre versiones sin aviso, y el
123
+ consumidor se defendía pineando la versión exacta.
124
+
13
125
  ### Corregido
14
126
 
127
+ - **`touchThread` perdía mensajes en el contador.** El incremento se calculaba
128
+ fuera del reintento de `updateDoc`, así que ante un conflicto de versión el
129
+ reintento volvía a escribir el valor viejo. Como `addMessage` la llama sin
130
+ esperarla, dos mensajes seguidos del mismo hilo bastaban para que el conteo se
131
+ quedara corto de forma permanente. Ahora el valor se recalcula dentro del
132
+ bucle.
133
+
134
+ - **El paquete publicaba su propia suite de tests.** Sin campo `files`, el
135
+ tarball llevaba 329 archivos y 2.3 MB, incluidos `test/`, `docs/`, `scripts/`
136
+ y los `*.test.ts` que conviven con el código. Ahora son 260 archivos y 448 kB.
137
+
138
+ - **`prepublish` no verificaba nada** (era un `echo`), y además es el hook
139
+ deprecado. Se reemplazó por `prepublishOnly` con typecheck + tests.
140
+
141
+ - **Sintaxis TypeScript que ningún runtime salvo Bun puede procesar.** Las 5
142
+ *parameter properties* (`constructor(private x)`) rompían incluso el
143
+ type-stripping nativo de Node, y como las clases se re-exportan desde el barrel
144
+ raíz tumbaban cualquier import del paquete. Se reescribieron a mano, sin
145
+ cambiar la API, y se normalizaron 355 imports relativos a extensión `.ts`
146
+ explícita. El paquete sigue requiriendo Bun por el uso de `Bun.*` en 18
147
+ archivos del core — ahora documentado en el README.
148
+
149
+
15
150
  - Se fijaron las 8 dependencias que estaban en `latest` (`zod`, `discord.js`,
16
151
  `grammy`, `@slack/bolt`, `@whiskeysockets/baileys`, `@modelcontextprotocol/sdk`,
17
152
  `qrcode-terminal`, `@sapphire/snowflake`). Como `bun.lock` no se publica, cada
package/README.md CHANGED
@@ -1,3 +1,7 @@
1
+ <p align="center">
2
+ <img src="docs/assets/logoblack.png" alt="Hive SDK" width="180" />
3
+ </p>
4
+
1
5
  # @johpaz/hive-sdk
2
6
 
3
7
  > **Hive Agent Harness SDK** — construí, desplegá y escalá aplicaciones de agentes de IA, con soporte multi-canal, Bun Workers y orquestación en swarm.
@@ -26,6 +30,12 @@ Con Hive SDK no montas un agente desde cero: **enganchas tu lógica de negocio e
26
30
 
27
31
  ## Instalación
28
32
 
33
+ > **Requiere Bun.** El paquete se publica como TypeScript y usa APIs de Bun
34
+ > (`Bun.secrets`, `Bun.spawn`, Workers) en 18 archivos del core, así que no
35
+ > corre sobre Node aunque se le apliquen los flags de type-stripping. Si tu
36
+ > backend es Node, hoy la vía es un proceso Bun aparte; el build a JS que
37
+ > levantaría esa restricción todavía no existe.
38
+
29
39
  ```bash
30
40
  # Instalar globalmente para el CLI
31
41
  bun install -g @johpaz/hive-sdk
@@ -212,4 +222,4 @@ npm view @johpaz/hive-sdk dist-tags # verificar después del release
212
222
 
213
223
  ---
214
224
 
215
- *Hive SDK v0.1.6 — MIT*
225
+ *Hive SDK v0.2.0 — MIT*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@johpaz/hive-sdk",
3
- "version": "0.1.6",
3
+ "version": "0.2.0",
4
4
  "private": false,
5
5
  "description": "Hive SDK — The Agent Harness SDK. Build, deploy, and scale AI agent applications with multi-channel support, context engineering, and swarm orchestration.",
6
6
  "license": "MIT",
@@ -35,6 +35,8 @@
35
35
  "./tools": "./packages/core/src/tools/index.ts",
36
36
  "./skills": "./packages/core/src/skills/index.ts",
37
37
  "./storage": "./packages/core/src/storage/index.ts",
38
+ "./sessions": "./packages/core/src/sessions/index.ts",
39
+ "./models": "./packages/core/src/models/index.ts",
38
40
  "./swarm": "./packages/core/src/swarm/index.ts",
39
41
  "./swarm/strategies": "./packages/core/src/swarm/strategies/index.ts",
40
42
  "./swarm/presets": "./packages/core/src/swarm/presets/index.ts",
@@ -59,6 +61,19 @@
59
61
  "bin": {
60
62
  "hives": "./packages/cli/bin/hives"
61
63
  },
64
+ "files": [
65
+ "packages/core/src",
66
+ "packages/cli/bin",
67
+ "packages/cli/src",
68
+ "packages/cli/templates",
69
+ "!packages/**/*.test.ts",
70
+ "README.md",
71
+ "CHANGELOG.md",
72
+ "LICENSE"
73
+ ],
74
+ "engines": {
75
+ "bun": ">=1.4.0"
76
+ },
62
77
  "workspaces": [
63
78
  "packages/core",
64
79
  "packages/cli"
@@ -70,7 +85,8 @@
70
85
  "skills:bundle": "bun scripts/generate-skill-bundle.ts",
71
86
  "version:set": "bun scripts/bump-version.ts",
72
87
  "release": "bun scripts/bump-version.ts --push",
73
- "prepublish": "echo 'No build needed - Bun runs TypeScript directly'"
88
+ "prepublishOnly": "bun run typecheck && bun test",
89
+ "drift": "bun scripts/check-drift.ts"
74
90
  },
75
91
  "dependencies": {
76
92
  "@johpaz/hive-db": "^0.4.0",
@@ -13,13 +13,13 @@
13
13
  * to judge using this checks result plus the raw evidence.
14
14
  */
15
15
 
16
- import { col } from "../storage/hive";
17
- import type { AgentDoc } from "../storage/collections";
18
- import type { AcceptanceCriterion } from "./run-store";
19
- import { interpretCheckResult } from "./goal-runner";
20
- import { inspectArtifact } from "../artifacts/store";
21
- import { loadConfig } from "../config/loader";
22
- import { logger } from "../utils/logger";
16
+ import { col } from "../storage/hive.ts";
17
+ import type { AgentDoc } from "../storage/collections.ts";
18
+ import type { AcceptanceCriterion } from "./run-store.ts";
19
+ import { interpretCheckResult } from "./goal-runner.ts";
20
+ import { inspectArtifact } from "../artifacts/store.ts";
21
+ import { loadConfig } from "../config/loader.ts";
22
+ import { logger } from "../utils/logger.ts";
23
23
 
24
24
  const log = logger.child("acceptance-checks");
25
25
 
@@ -55,8 +55,8 @@ export function sanitizeDiagnostic(value: string, limit = 1000): string {
55
55
  async function runCheckTool(criterion: AcceptanceCriterion, objective: string): Promise<AcceptanceCheckResult | null> {
56
56
  if (!criterion.checkTool) return null;
57
57
  try {
58
- const { executeToolBatch } = await import("../tool-runtime");
59
- const { createAllTools } = await import("../tools/index");
58
+ const { executeToolBatch } = await import("../tool-runtime/index.ts");
59
+ const { createAllTools } = await import("../tools/index.ts");
60
60
  const allTools = createAllTools(loadConfig());
61
61
  const toolDef = allTools.find((t) => t.name === criterion.checkTool);
62
62
  if (!toolDef) {
@@ -3,9 +3,9 @@ import type {
3
3
  AgentAcceptanceCriterion,
4
4
  AgentModelOverride,
5
5
  AgentWorkspaceScope,
6
- } from "../storage/collections";
7
- import { col, toIndexable, fromIndexable } from "../storage/hive";
8
- import { expandToolAllowlist } from "./delegation-runtime";
6
+ } from "../storage/collections.ts";
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;
@@ -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,11 @@ 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
32
 
33
33
  const log = logger.child("compaction")
34
34
 
@@ -118,7 +118,7 @@ export function renderTranscript(rows: StoredMessage[], maxMsgChars = MAX_MSG_CH
118
118
  /**
119
119
  * Compress a thread's history into a summary.
120
120
  */
121
- async function compactThread(
121
+ export async function compactThread(
122
122
  threadId: string,
123
123
  notify?: { channel: string; userId: string }
124
124
  ): Promise<void> {
@@ -187,11 +187,12 @@ async function compactThread(
187
187
  // Notify user in their active channel (non-critical)
188
188
  if (notify?.channel && notify?.userId) {
189
189
  try {
190
- const { sendToUserChannel } = await import("../gateway/channel-notify")
190
+ const { sendToUserChannel } = await import("../gateway/channel-notify.ts")
191
191
  await sendToUserChannel(
192
192
  notify.channel,
193
193
  notify.userId,
194
- `🗜️ Resumí ${toSummarize.length} mensajes anteriores para mantener el contexto limpio.`
194
+ `🗜️ Resumí ${toSummarize.length} mensajes anteriores para mantener el contexto limpio.`,
195
+ { threadId }
195
196
  )
196
197
  } catch {
197
198
  // Non-critical — don't break the flow if notification fails