@johpaz/hive-sdk 0.2.0 → 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.
- package/CHANGELOG.md +306 -0
- package/README.md +11 -3
- package/package.json +10 -4
- package/packages/core/src/agent/agent-catalog.ts +81 -24
- package/packages/core/src/agent/compaction.ts +20 -1
- package/packages/core/src/agent/context-compiler.ts +6 -3
- package/packages/core/src/agent/conversation-store.ts +136 -2
- package/packages/core/src/agent/curator.ts +12 -3
- package/packages/core/src/agent/llm-providers/nvidia.ts +39 -0
- package/packages/core/src/agent/llm-providers/openai-compat-base.ts +38 -2
- package/packages/core/src/agent/playbook-selector.ts +18 -3
- package/packages/core/src/agent/prompt-builder.ts +2 -2
- package/packages/core/src/agent/providers/index.ts +36 -1
- package/packages/core/src/agent/reflector.ts +32 -9
- package/packages/core/src/agent/skill-selector.ts +2 -2
- package/packages/core/src/agent/thread-store.ts +43 -0
- package/packages/core/src/agent/tool-selector.ts +2 -0
- package/packages/core/src/api/createAgent.ts +66 -1
- package/packages/core/src/artifacts/index.ts +15 -0
- package/packages/core/src/artifacts/store.ts +77 -2
- package/packages/core/src/canvas/index.ts +9 -0
- package/packages/core/src/ethics/EthicsGuard.ts +7 -1
- package/packages/core/src/events/index.ts +18 -0
- package/packages/core/src/events/tool-narration.ts +4 -0
- package/packages/core/src/gateway/channel-notify.ts +103 -6
- package/packages/core/src/gateway/durable-queue.ts +13 -1
- package/packages/core/src/gateway/index.ts +3 -0
- package/packages/core/src/gateway/job-store.ts +6 -0
- package/packages/core/src/harness/executors.ts +493 -0
- package/packages/core/src/harness/index.ts +12 -2
- package/packages/core/src/hooks/index.ts +203 -0
- package/packages/core/src/images/index.ts +161 -0
- package/packages/core/src/index.ts +1 -0
- package/packages/core/src/multimodal/vision-service.ts +45 -13
- package/packages/core/src/resilience/index.ts +13 -0
- package/packages/core/src/scheduler/CronScheduler.ts +48 -21
- package/packages/core/src/scheduler/cron/expression.ts +165 -0
- package/packages/core/src/scheduler/cron/index.ts +10 -0
- package/packages/core/src/scheduler/cron/job.ts +339 -0
- package/packages/core/src/scheduler/cron/next-run.ts +121 -0
- package/packages/core/src/scheduler/cron/zoned-time.ts +138 -0
- package/packages/core/src/scheduler/index.ts +21 -3
- package/packages/core/src/scheduler/integration.ts +16 -5
- package/packages/core/src/scheduler/types.ts +3 -18
- package/packages/core/src/services/agents.ts +268 -0
- package/packages/core/src/services/cron.ts +257 -0
- package/packages/core/src/services/endpoints.ts +289 -0
- package/packages/core/src/services/ethics.ts +107 -0
- package/packages/core/src/services/images.ts +212 -0
- package/packages/core/src/services/index.ts +112 -0
- package/packages/core/src/services/mcp.ts +201 -0
- package/packages/core/src/services/memory.ts +133 -0
- package/packages/core/src/services/models.ts +179 -0
- package/packages/core/src/services/providers.ts +152 -0
- package/packages/core/src/services/setup.ts +222 -0
- package/packages/core/src/services/skills.ts +241 -0
- package/packages/core/src/services/swarms.ts +307 -0
- package/packages/core/src/services/tools.ts +106 -0
- package/packages/core/src/sessions/index.ts +5 -3
- package/packages/core/src/sessions/resolve.ts +108 -0
- package/packages/core/src/skills/SkillLoader.ts +8 -1
- package/packages/core/src/skills/bundled/artifacts/artifact_reader/SKILL.md +105 -0
- package/packages/core/src/skills/bundled/cron_manager/SKILL.md +21 -11
- package/packages/core/src/skills/bundled/images/image_editor/SKILL.md +120 -0
- package/packages/core/src/skills/bundled/web/browser_automate/SKILL.md +12 -3
- package/packages/core/src/skills/bundled/web/browser_scrape/SKILL.md +22 -7
- package/packages/core/src/skills/bundled-data.generated.ts +110 -12
- package/packages/core/src/storage/bootstrap.ts +74 -5
- package/packages/core/src/storage/collections.ts +106 -1
- package/packages/core/src/storage/crypto.ts +24 -7
- package/packages/core/src/storage/index.ts +2 -1
- package/packages/core/src/storage/onboarding.ts +59 -43
- package/packages/core/src/storage/reconcile.ts +6 -1
- package/packages/core/src/storage/seed.ts +89 -11
- package/packages/core/src/swarm/types.ts +3 -18
- package/packages/core/src/tool-runtime/embedded-worker.generated.ts +21 -0
- package/packages/core/src/tool-runtime/index.ts +129 -14
- package/packages/core/src/tools/agents/index.ts +18 -60
- package/packages/core/src/tools/cli/index.ts +55 -0
- package/packages/core/src/tools/core/index.ts +50 -2
- package/packages/core/src/tools/cron/index.ts +4 -4
- package/packages/core/src/tools/images/index.ts +130 -0
- package/packages/core/src/tools/index.ts +14 -1
- package/packages/core/src/tools/office/office-escribir-xlsx.ts +2 -1
- package/packages/core/src/tools/office/office-leer-xlsx.ts +2 -1
- package/packages/core/src/tools/office/xlsx-loader.ts +19 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,8 +2,154 @@
|
|
|
2
2
|
|
|
3
3
|
## Sin publicar
|
|
4
4
|
|
|
5
|
+
### Corregido
|
|
6
|
+
|
|
7
|
+
- **`browser_scrape` extraía con una tool que no ve lo que el navegador
|
|
8
|
+
renderizó.** La skill existe para sitios dinámicos, y su paso de extracción
|
|
9
|
+
usaba `web_fetch`, que vuelve a pedir la URL al servidor y recibe el HTML sin
|
|
10
|
+
JavaScript ejecutado: en un SPA, una cáscara vacía. Pasa a usar
|
|
11
|
+
`browser_extract`, que lee el DOM ya renderizado, con un `browser_wait` previo.
|
|
12
|
+
(Uno de sus ejemplos citaba además `browser_fetch`, que no existe.)
|
|
13
|
+
|
|
14
|
+
- **`browser_automate` no esperaba a los elementos.** Sus pasos iban de navegar a
|
|
15
|
+
hacer clic sin `browser_wait` en el medio, que es la falla más común de la
|
|
16
|
+
automatización web y falla en silencio. Se agregó el paso y el orden de
|
|
17
|
+
escalada: selector → `browser_script` → `computer_use_task`.
|
|
18
|
+
|
|
19
|
+
- **La tabla de campos de `cron_manager` omitía la mitad de lo que acepta la
|
|
20
|
+
tool**: `max_runs`, `payload`, `agent_id` y `tool_name`. También documentaba
|
|
21
|
+
expresiones de 5 campos cuando el motor acepta 6, y no aclaraba que la zona
|
|
22
|
+
horaria sale del perfil del usuario y no se pasa en la llamada.
|
|
23
|
+
|
|
24
|
+
- **Los contadores del scheduler perdían actualizaciones.** `run_count` y
|
|
25
|
+
`error_count` se calculaban desde una lectura hecha antes del bucle de
|
|
26
|
+
reintento de `updateJob`, así que al reintentar por conflicto de versión se
|
|
27
|
+
reescribía el valor viejo. Con dos corridas solapadas del mismo job —normal en
|
|
28
|
+
uno que tarda más que su intervalo y no declara `protect`, y garantizado en la
|
|
29
|
+
puesta al día por misfire, que llama a `execute()` en paralelo con el job ya
|
|
30
|
+
activado— ambas leían `error_count: 4` y ambas escribían 5. La consecuencia no
|
|
31
|
+
era el número: es que el umbral de auto-pausa (5 errores seguidos) no se
|
|
32
|
+
alcanzaba nunca y un job que fallaba siempre se quedaba reintentando para
|
|
33
|
+
siempre. `updateJob` ahora acepta un parche en forma de función, que se evalúa
|
|
34
|
+
contra la lectura fresca de cada intento. Es el mismo error que ya se corrigió
|
|
35
|
+
en `touchThread`. Cubierto por `packages/core/src/scheduler/scheduler.test.ts`.
|
|
36
|
+
|
|
37
|
+
### Agregado
|
|
38
|
+
|
|
39
|
+
- **El seed inicial de especialistas es una elección.** `ensureHiveDb()` y
|
|
40
|
+
`seedAllData()` aceptan `specialists: "all" | "none" | string[]`. Con `"none"`
|
|
41
|
+
la colmena arranca sin ningún especialista y con sólo las `MINIMAL_TOOLS`
|
|
42
|
+
activas —la competencia del coordinador—, y son los enjambres los que traen
|
|
43
|
+
consigo a los suyos. `"all"` sigue siendo el default, así que nada cambia para
|
|
44
|
+
quien no lo pida.
|
|
45
|
+
|
|
46
|
+
La elección alcanza también a las **capacidades**, no sólo a los agentes: una
|
|
47
|
+
fila de tool o skill que nace en un arranque `"none"` nace inactiva. Antes
|
|
48
|
+
`active` defaulteaba a `true` para toda fila nueva, así que un arranque sin
|
|
49
|
+
especialistas dejaba igual las 62 tools encendidas — el usuario terminaba
|
|
50
|
+
apagando a mano lo que nunca pidió.
|
|
51
|
+
|
|
52
|
+
**Nunca borra.** La elección gobierna qué se crea, no qué se conserva: una
|
|
53
|
+
base que ya tiene sus ocho agentes no pierde ninguno por arrancar con
|
|
54
|
+
`"none"`, y se siguen reconciliando en cada arranque.
|
|
55
|
+
|
|
56
|
+
`createSwarm` acepta ahora miembros del catálogo que todavía no tienen fila:
|
|
57
|
+
con el seed en `"none"` un enjambre es el **pedido de instalación**, no una
|
|
58
|
+
referencia a algo que ya debería existir. Un id que no es del catálogo y no
|
|
59
|
+
existe sigue siendo un error.
|
|
60
|
+
|
|
61
|
+
- **Crear un enjambre ahora siembra sus especialistas.** El seed selectivo
|
|
62
|
+
(`applySeedPlan`) dejaba elegir qué personas del catálogo instalar, pero
|
|
63
|
+
`createSwarm` no lo miraba: guardaba el enjambre **sin una queja** con
|
|
64
|
+
miembros apagados y sus tools inactivas. La validación de "el agente existe"
|
|
65
|
+
pasaba igual, porque el seed crea las 8 filas siempre y sólo cambia `enabled`
|
|
66
|
+
— el enjambre quedaba definido y sin poder trabajar.
|
|
67
|
+
|
|
68
|
+
`createSwarm` y `updateSwarm` aceptan `activateMembers`, **`false` por
|
|
69
|
+
defecto**: crear un enjambre no debería cambiar en silencio qué capacidades
|
|
70
|
+
tiene la instalación entera, así que sin él el enjambre se crea igual y el
|
|
71
|
+
faltante vuelve en `pendingActivation` para que la UI lo muestre y el usuario
|
|
72
|
+
decida. Con `true` se activa la unión con lo que ya estaba, de modo que
|
|
73
|
+
encender los especialistas de un enjambre nunca apaga los de otro.
|
|
74
|
+
|
|
75
|
+
Se agregó `planActivationFor(agentIds)`, que devuelve el faltante **sin
|
|
76
|
+
encender nada** —para el "esto se va a activar" antes de confirmar— y
|
|
77
|
+
`enableCatalogAgents(ids)` en plural, porque activarlos de a uno reescribía el
|
|
78
|
+
catálogo entero una vez por agente. Cubierto por `test/swarm-seed.test.ts`.
|
|
79
|
+
|
|
80
|
+
- **Skills para las capacidades que no tenían ninguna.** `image_editor`
|
|
81
|
+
(`image_metadata`, `image_transform`, `artifact_inspect`) y `artifact_reader`
|
|
82
|
+
(`artifact_read`, `artifact_inspect`). Las tools existían pero ninguna skill
|
|
83
|
+
las enseñaba, así que el modelo sólo podía dar con ellas de casualidad vía
|
|
84
|
+
`search_knowledge` — y en el caso de los artefactos eso deja inerte todo el
|
|
85
|
+
mecanismo de `artifact_ref`, que existe justamente para que los archivos
|
|
86
|
+
grandes no entren en la ventana de contexto.
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
- **`sessionStart` y `sessionEnd` ya se disparan.** Eran registrables desde que
|
|
90
|
+
se implementaron los hooks, pero nada los invocaba. Van enganchados a las
|
|
91
|
+
cuatro transiciones del ciclo de vida del hilo (crear, reabrir, archivar,
|
|
92
|
+
borrar), todas en `agent/thread-store.ts`. `sessionStart` cuelga del `put` que
|
|
93
|
+
crea la fila y no de `createSession`, que es idempotente y se llama en cada
|
|
94
|
+
turno: enganchado ahí habría contado mensajes en vez de conversaciones.
|
|
95
|
+
`closeSession`/`reopenSession` pasan a delegar en los nuevos `archiveThread`
|
|
96
|
+
y `unarchiveThread` para que las cuatro transiciones vivan en un solo archivo.
|
|
97
|
+
Cubierto por `test/hooks.test.ts`.
|
|
98
|
+
|
|
99
|
+
### Quitado
|
|
100
|
+
|
|
101
|
+
- **Cero dependencias para el cron: fuera `croner` y `cron-parser`.** El motor
|
|
102
|
+
ahora es propio (`scheduler/cron/`) y usa sólo `setTimeout` e `Intl` del
|
|
103
|
+
runtime. `cron-parser` además ni siquiera se importaba: estaba declarada en
|
|
104
|
+
los dos `package.json` y se la bajaba todo el que instalara el SDK.
|
|
105
|
+
|
|
106
|
+
`Bun.cron()` **no** sirve como reemplazo —evaluado contra el runtime 1.4.0—:
|
|
107
|
+
acepta sólo 5 campos, rechaza una fecha ISO como patrón (que es como se
|
|
108
|
+
agendan los jobs `one_shot`), ignora la zona horaria en `parse()`, y su handle
|
|
109
|
+
no expone la próxima corrida, de donde sale `next_run_at` y con lo que se
|
|
110
|
+
detectan las corridas perdidas al arrancar. Tampoco tiene equivalente de
|
|
111
|
+
`protect`, `maxRuns`, `interval`, `startAt`/`stopAt` ni `domAndDow`, todos
|
|
112
|
+
campos persistidos de `CronJobDoc`.
|
|
113
|
+
|
|
114
|
+
El motor propio conserva la superficie entera, así que `CronScheduler` no
|
|
115
|
+
cambió de comportamiento, e implementa además los dos casos de horario de
|
|
116
|
+
verano que se rompen callados: la hora que **no existe** al adelantar el
|
|
117
|
+
reloj (se saltea ese día en vez de correr a una hora inventada) y la que
|
|
118
|
+
**ocurre dos veces** al atrasarlo (corre en la primera, una sola vez). El
|
|
119
|
+
motor se exporta suelto desde `./scheduler` —`Cron`, `parseCronExpression`,
|
|
120
|
+
`isValidCronExpression`, `nextOccurrence`— para validar o previsualizar sin
|
|
121
|
+
montar un scheduler. Documentado en `docs/API-CRON.md`. Cubierto por
|
|
122
|
+
`packages/core/src/scheduler/cron/cron-engine.test.ts` (28 tests).
|
|
123
|
+
|
|
124
|
+
- **`CronerOptions` (tipo público).** Estaba declarado dos veces —en
|
|
125
|
+
`scheduler/types.ts` y en `swarm/types.ts`— y no tipaba nada en ninguna parte:
|
|
126
|
+
un tipo muerto con el nombre de una librería que ya no se usa. Su forma es la
|
|
127
|
+
de `CronOptions`, que ahora exporta el motor desde `./scheduler`.
|
|
128
|
+
|
|
129
|
+
- **Menciones a Croner en lo que lee el modelo.** Las descripciones de
|
|
130
|
+
`cron.create` (`start_at`, `stop_at`, `dom_and_dow`) y la skill `cron_manager`
|
|
131
|
+
citaban opciones "de Croner". Eso entra en el prompt: nombrarle al modelo una
|
|
132
|
+
librería que el código ya no usa lo manda a buscar documentación que no
|
|
133
|
+
aplica. Quedan sólo las referencias históricas que explican por qué el motor
|
|
134
|
+
es propio.
|
|
135
|
+
|
|
5
136
|
### Seguridad
|
|
6
137
|
|
|
138
|
+
- **El playbook ACE no distinguía de quién era lo aprendido.** `PlaybookDoc` y
|
|
139
|
+
`ReflectionDoc` no tenían `user_id`, así que la cadena entera —trazas →
|
|
140
|
+
reflexión → regla → inyección en el system prompt— era global: lo que el
|
|
141
|
+
agente aprendía interactuando con una persona se le aplicaba a todas las demás
|
|
142
|
+
del mismo proceso. Es el mismo supuesto de "un solo usuario" que ya se había
|
|
143
|
+
cerrado en `memory`. Ahora el reflector agrupa las trazas por usuario
|
|
144
|
+
(derivado del `thread_id`), el curador propaga el dueño a la regla y deduplica
|
|
145
|
+
dentro del usuario, y las tres puertas de lectura filtran a global + propio:
|
|
146
|
+
`selectPlaybookRules(texto, userId)`, `EthicsGuard.getRules(rol, userId)` y la
|
|
147
|
+
tool `search_knowledge`. Las reglas sembradas siguen siendo globales a
|
|
148
|
+
propósito (`user_id: ""`): son conocimiento del producto, no de nadie.
|
|
149
|
+
`ensureHiveDb()` migra las filas anteriores asignándolas al primer usuario de
|
|
150
|
+
la base — dejarlas sin dueño las volvería globales, que es justo lo que se
|
|
151
|
+
viene a cerrar. Cubierto por `test/playbook-isolation.test.ts`.
|
|
152
|
+
|
|
7
153
|
- **La lista blanca de tools no se aplicaba al descubrimiento dinámico.**
|
|
8
154
|
`compileContext` sólo recortaba `allTools` cuando el agente era de catálogo
|
|
9
155
|
(`source === "catalog"`). Un agente creado por el usuario veía su loadout
|
|
@@ -51,6 +197,15 @@
|
|
|
51
197
|
Ahora el test usa un id de proveedor propio (`test-tenant-isolation`) y limpia
|
|
52
198
|
sus secretos, sin tocar el módulo ni la credencial de nadie.
|
|
53
199
|
|
|
200
|
+
- **El caché de disponibilidad del keychain se envenenaba para todo el proceso.**
|
|
201
|
+
`_keychainOk` recuerda si `Bun.secrets` respondió, para no reintentar en cada
|
|
202
|
+
lectura en un servidor sin libsecret. El problema es que ese resultado valía
|
|
203
|
+
para siempre: una vez marcado como no disponible, sustituir `Bun.secrets` por
|
|
204
|
+
otro backend —o por un doble de test— no servía de nada, porque la lectura
|
|
205
|
+
cortaba antes de tocarlo. Ahora se detecta que el objeto cambió de identidad y
|
|
206
|
+
el sondeo se invalida solo. Era la causa de que el test de compatibilidad con
|
|
207
|
+
keychain fallara en CI headless (y sólo ahí).
|
|
208
|
+
|
|
54
209
|
- **`resetKeychainProbe()`** en `storage/crypto.ts`. Si el keychain del SO no
|
|
55
210
|
responde, el resultado se cachea a nivel de módulo para no reintentar en cada
|
|
56
211
|
lectura — correcto en producción, pero significa que el primer sondeo vale
|
|
@@ -89,8 +244,82 @@
|
|
|
89
244
|
|
|
90
245
|
- CI actualizado a **Bun 1.4.0**, alineado con `hive`.
|
|
91
246
|
|
|
247
|
+
### Quitado
|
|
248
|
+
|
|
249
|
+
- **`AgentRunner`** (`agent/providers/index.ts`). Era una capa de compatibilidad
|
|
250
|
+
con la firma de LangGraph anterior a que el runtime pasara a `agent-loop.ts`, y
|
|
251
|
+
**nunca llegó a instanciarse**: los cuatro puntos de entrada reales —el
|
|
252
|
+
gateway, `createAgent`, el worker y los ejecutores del harness— llaman
|
|
253
|
+
`runAgent()` directo. 158 líneas de código muerto. El subpath
|
|
254
|
+
`@johpaz/hive-sdk/agent/providers` sigue existiendo con sus tipos (`Provider`,
|
|
255
|
+
`ModelResponse`), que sí son parte del contrato público.
|
|
256
|
+
|
|
92
257
|
### Añadido
|
|
93
258
|
|
|
259
|
+
- **Streaming por token en la API pública.** `chat(mensaje, { stream: true })`
|
|
260
|
+
emite eventos `token` con los deltas del proveedor a medida que llegan. El
|
|
261
|
+
mecanismo ya existía —los proveedores llamaban `onToken` por cada delta— pero
|
|
262
|
+
**ningún punto de entrada lo pasaba**, así que nunca llegaba a nadie: la
|
|
263
|
+
respuesta aparecía de golpe al terminar el turno.
|
|
264
|
+
|
|
265
|
+
- **`@johpaz/hive-sdk/services/images`** — imágenes como servicio para el usuario
|
|
266
|
+
final, no para el agente: entra y sale por bytes, se persiste por id. Incluye
|
|
267
|
+
galería (`listImages`), presets y control de retención.
|
|
268
|
+
|
|
269
|
+
- **`@johpaz/hive-sdk/services` — la superficie que maneja una interfaz.** El SDK
|
|
270
|
+
estaba construido para que lo condujera el modelo: casi todo el CRUD vivía
|
|
271
|
+
dentro de las tools (`cronCreateTool`, `memoryWriteTool`, `agentCreateTool`),
|
|
272
|
+
con argumentos con forma de LLM y respuestas escritas para un prompt. Montar
|
|
273
|
+
una UI encima obligaba a llamar `tool.execute({...})` y parsear prosa, o a
|
|
274
|
+
escribir consultas crudas contra HiveDB conociendo un esquema privado.
|
|
275
|
+
|
|
276
|
+
Ahora la implementación vive en `services/` y las tools la envuelven — una
|
|
277
|
+
implementación, dos consumidores. Diez dominios: `agents`, `swarms`, `skills`,
|
|
278
|
+
`tools`, `providers`, `models`, `mcp`, `cron`, `memory`, `ethics`. Es
|
|
279
|
+
deliberadamente agnóstico del framework (funciones, no rutas HTTP): una app
|
|
280
|
+
móvil o de escritorio que embeba el runtime no quiere un servidor.
|
|
281
|
+
|
|
282
|
+
Añade tres cosas que hive no hace: **valida que las referencias existan** al
|
|
283
|
+
asignar tools/skills/MCP a un agente (hive las guarda sin comprobar, y el
|
|
284
|
+
error aparece cuando el agente intenta usarlas); **`testMcpServer()`**, que
|
|
285
|
+
allí es "guardá y esperá a que el hot-reload conecte"; y el **rename de modelo
|
|
286
|
+
transaccional**, que re-apunta a cada agente en el mismo `batch()`.
|
|
287
|
+
|
|
288
|
+
- **`SwarmDoc` — los enjambres se pueden guardar.** Hasta acá un enjambre existía
|
|
289
|
+
sólo mientras corría: `runRoleSwarm()` recibe los agentes en la llamada y no
|
|
290
|
+
persiste nada, así que quien armara uno desde una interfaz lo perdía al cerrar
|
|
291
|
+
la ventana. Era el bloqueador real para poner una UI encima del SDK, y explica
|
|
292
|
+
por qué hive-cloud creó sus propias tablas en Postgres.
|
|
293
|
+
|
|
294
|
+
La validación ocurre **al guardar, no al correr**: un enjambre jerárquico sin
|
|
295
|
+
orquestador, o con un agente que ya no existe, es un error de configuración —
|
|
296
|
+
descubrirlo semanas después, cuando alguien lo ejecuta, es descubrirlo tarde.
|
|
297
|
+
|
|
298
|
+
- **El harness trae ejecutores listos** (`initHarnessExecutors()`). La cola
|
|
299
|
+
durable sabía encolar, reintentar y recuperar tras un crash, pero no ejecutar:
|
|
300
|
+
registrar los ejecutores quedaba en manos de quien usara el SDK, y eso son
|
|
301
|
+
~420 líneas de cableado —epoch, proof packets, criterios de aceptación,
|
|
302
|
+
fan-in de delegaciones— antes de correr un solo enjambre durable. Ahora vienen
|
|
303
|
+
`worker_task` (worker delegado en contexto aislado, con verificación de sus
|
|
304
|
+
criterios) y `goal_run` (varios turnos contra un objetivo hasta verificarlo o
|
|
305
|
+
agotar el presupuesto).
|
|
306
|
+
|
|
307
|
+
`chat_turn` no está a propósito: qué es un canal y cómo se transmite un token
|
|
308
|
+
lo define la aplicación. Se registra desde fuera con `registerExecutor()`.
|
|
309
|
+
Registrar sigue siendo opt-in — `initHarnessExecutors()` no se llama sola.
|
|
310
|
+
|
|
311
|
+
- **`getRegisteredExecutorTypes()`** — el registro era privado, así que no había
|
|
312
|
+
forma de comprobar si un tipo quedó cableado. Un job encolado sin ejecutor no
|
|
313
|
+
falla al encolarse sino al tomarse, lejos de donde está el error.
|
|
314
|
+
|
|
315
|
+
- **Superficie pública completa**: 33 subpaths (antes 28). `events/` y
|
|
316
|
+
`resilience/` no tenían barril, `canvas/` no exportaba su emitter, `artifacts/`
|
|
317
|
+
no existía como módulo, y `./events` apuntaba a un solo archivo — el
|
|
318
|
+
**agent-bus**, que es la mensajería entre workers de un enjambre, era
|
|
319
|
+
inalcanzable desde fuera. También se exponen `./tool-runtime`, `./channels`,
|
|
320
|
+
`./voice`, y `initializeBrowserService`/`activateBrowserTools`, sin los cuales
|
|
321
|
+
las browser tools estaban en el catálogo pero nadie podía arrancarlas.
|
|
322
|
+
|
|
94
323
|
- **`@johpaz/hive-sdk/sessions`** — la conversación de un usuario como una sola
|
|
95
324
|
cosa. Hasta acá "sesión" estaba repartida entre `thread-store` (identidad),
|
|
96
325
|
`conversation-store` (mensajes), `run-store` (ejecución) y un `Map` en memoria
|
|
@@ -124,6 +353,83 @@
|
|
|
124
353
|
|
|
125
354
|
### Corregido
|
|
126
355
|
|
|
356
|
+
- **Las notificaciones no llegaban a ningún lado.** `notifyChannel` era un stub
|
|
357
|
+
que sólo hacía `console.log`, y está en el camino real: la tool `notify`, los
|
|
358
|
+
reportes de progreso, el aviso de que una tarea programada terminó, el de un
|
|
359
|
+
turno interrumpido por un crash. Un agente sobre el SDK **no podía hablarle al
|
|
360
|
+
usuario por ningún canal**, mientras `channels/manager.ts` tenía adaptadores
|
|
361
|
+
funcionales de Slack, Discord, Telegram y WhatsApp sin nada que los conectara.
|
|
362
|
+
Ahora la app registra el suyo con `setChannelManager()`; sin registro se
|
|
363
|
+
conserva el comportamiento anterior, pero avisando.
|
|
364
|
+
|
|
365
|
+
- **Las imágenes se reenviaban al modelo en cada turno.** `content_multimodal`
|
|
366
|
+
guardaba el base64 completo y `toAPIMessages` lo restauraba una y otra vez:
|
|
367
|
+
cinco fotos en una conversación eran cinco fotos viajando en cada turno
|
|
368
|
+
siguiente. Ahora se guardan como artefacto y en el historial queda una
|
|
369
|
+
referencia; las de los últimos mensajes se vuelven a poner en línea, porque un
|
|
370
|
+
modelo de visión no ve una foto desde un id. Mismo criterio que
|
|
371
|
+
`clearOldToolResults`.
|
|
372
|
+
|
|
373
|
+
- **`token_count` no contaba las imágenes**, así que la compactación creía que un
|
|
374
|
+
hilo lleno de fotos ocupaba lo que ocupa su texto y no se disparaba hasta que
|
|
375
|
+
el proveedor rechazaba el turno. Ahora se estiman por área, como cobran los
|
|
376
|
+
proveedores.
|
|
377
|
+
|
|
378
|
+
- **`agent.context.compactionThreshold` no lo leía nadie.** Estaba en el esquema
|
|
379
|
+
de configuración y ajustarlo no hacía nada. Una opción que no hace nada es
|
|
380
|
+
peor que no tenerla, porque el usuario cree que cambió algo.
|
|
381
|
+
|
|
382
|
+
- **`search_knowledge` filtraba en el lugar equivocado.** Mostraba tools fuera de
|
|
383
|
+
la lista blanca del agente. La ejecución sí estaba protegida, pero además de
|
|
384
|
+
contarle qué existe fuera de su alcance, ofrecerle algo que no puede ejecutar
|
|
385
|
+
es hacerle perder un turno.
|
|
386
|
+
|
|
387
|
+
- **El seed selectivo no habría sobrevivido a un reinicio.** `reseedToolsAndSkills()`
|
|
388
|
+
escribía `active: true` para todas las tools y skills en cada arranque, así que
|
|
389
|
+
la elección del usuario sobre qué capacidades quiere en su colmena duraba hasta
|
|
390
|
+
el próximo reinicio: apagaba lo que no usaba y volvía todo. Ahora el reseed
|
|
391
|
+
preserva `active` —la descripción y la categoría siguen viniendo del código,
|
|
392
|
+
que es su fuente de verdad—, igual que ya hacía con los modelos.
|
|
393
|
+
|
|
394
|
+
- **La memoria era global al proceso.** El id de `MemoryDoc` era sólo el título y
|
|
395
|
+
no había `user_id`: dos usuarios no podían tener una memoria con el mismo
|
|
396
|
+
nombre —la segunda pisaba la primera— y cualquiera veía la del otro. Coherente
|
|
397
|
+
con hive, que es mono-usuario; inservible para un runtime donde cada quien arma
|
|
398
|
+
su colmena. El id pasa a ser `${userId}:${title}` y toda lectura filtra por
|
|
399
|
+
dueño. Las filas anteriores se migran al arrancar.
|
|
400
|
+
|
|
401
|
+
- **Los ids no manejaban acentos.** "Efímero" quedaba como `ef_mero` y "Diseño"
|
|
402
|
+
como `dise_o`, porque la í y la ñ no son `[a-z0-9]`. Para un producto en
|
|
403
|
+
español eso no es cosmético. `slugify()` normaliza los diacríticos antes de
|
|
404
|
+
filtrar, y se aplica también a skills y servidores MCP.
|
|
405
|
+
|
|
406
|
+
- **Documentación que describía un backend retirado.** `API-TOOLS-SKILLS-CHANNELS.md`
|
|
407
|
+
seguía explicando cómo `agent-browser` se instalaba solo en `~/.hive/` al
|
|
408
|
+
primer uso — un backend que ya no existe. Reescrita para `Bun.WebView`, con la
|
|
409
|
+
nota de por qué se retiró. También se corrigieron los conteos (58→60 tools,
|
|
410
|
+
106→110 modelos) y los pies de página congelados en `v0.0.17`.
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
- **Un job que moría por expiración de lease no disparaba su terminal hook.** La
|
|
414
|
+
ruta normal de fallo sí lo hacía; la de recuperación tras un crash, no. El
|
|
415
|
+
aviso al usuario y el fan-in de delegaciones se perdían en silencio justo
|
|
416
|
+
cuando más importaban.
|
|
417
|
+
|
|
418
|
+
- **Los artefactos de imagen no llegaban al consumidor.** El agent loop ya los
|
|
419
|
+
emitía (`chunk.artifacts.images`, vía mcp-result-normalizer), pero el wrapper
|
|
420
|
+
`AgentRunner` no los propagaba, así que una imagen producida por una tool MCP
|
|
421
|
+
se perdía antes de salir del SDK.
|
|
422
|
+
|
|
423
|
+
- **NVIDIA no emitía razonamiento.** NIM lo mantiene apagado por defecto y el
|
|
424
|
+
interruptor no es `reasoning_effort` sino `chat_template_kwargs`, con una
|
|
425
|
+
clave distinta por familia de modelo. Se añade el reintento sin esos extras
|
|
426
|
+
cuando el proveedor responde 400/422: perder el razonamiento es mejor que
|
|
427
|
+
perder el turno.
|
|
428
|
+
|
|
429
|
+
- **Un turno con más de una tool call podía morir por un hueco de empaquetado.**
|
|
430
|
+
`resolveWorkerEntry()` lanzaba si no encontraba el worker; ahora devuelve null
|
|
431
|
+
y degrada a hilo principal.
|
|
432
|
+
|
|
127
433
|
- **`touchThread` perdía mensajes en el contador.** El incremento se calculaba
|
|
128
434
|
fuera del reintento de `updateDoc`, así que ante un conflicto de versión el
|
|
129
435
|
reintento volvía a escribir el valor viejo. Como `addMessage` la llama sin
|
package/README.md
CHANGED
|
@@ -17,17 +17,25 @@ bun add @johpaz/hive-sdk
|
|
|
17
17
|
**Hive SDK es un Agent Harness**: un marco de trabajo completo para construir, desplegar y escalar aplicaciones de agentes de IA. A diferencia de un simple wrapper de LLM, un *harness* provee todo lo necesario para que un agente opere en producción:
|
|
18
18
|
|
|
19
19
|
- **Agentes**: ciclo ReAct nativo con checkpoint durable, 16 providers LLM y descubrimiento de tools/skills por búsqueda BM25.
|
|
20
|
-
- **Catálogo**: 18 providers y
|
|
21
|
-
- **Tools**:
|
|
20
|
+
- **Catálogo**: 18 providers y 110 modelos sembrados, cada uno con su precio por millón de tokens — una sola fuente de verdad para el costo.
|
|
21
|
+
- **Tools**: 60 tools incluidas — filesystem, web search, browser automation (`Bun.WebView`), APIs (`api_request`), a2ui, office, cron, delegación.
|
|
22
22
|
- **Skills**: 23 workflows bundled, más los tuyos con `defineSkill` y `SkillLoader`.
|
|
23
23
|
- **Canales**: Telegram, Discord, WhatsApp, Slack y WebChat con `ChannelManager`.
|
|
24
24
|
- **Swarm**: orquestación multi-agente con `DAGScheduler`, `TaskGraph` y `WorkerPool`.
|
|
25
25
|
- **Runtime**: ejecución paralela de tools vía Bun Workers.
|
|
26
26
|
- **Gateway**: servidor HTTP/WebSocket para exponer agentes como API.
|
|
27
27
|
- **Memoria y estado**: HiveDB (colecciones + índice BM25), scratchpad, context compiler con compactación.
|
|
28
|
+
- **Servicios**: CRUD tipado de agentes, enjambres, skills, modelos, MCP y cron para montarle **la interfaz que quieras** — móvil, web o escritorio. Ver [API-SERVICES.md](./docs/API-SERVICES.md).
|
|
29
|
+
- **Sesiones**: un hilo por canal y por contacto, con historial, resumen y reanudación tras un corte.
|
|
30
|
+
- **Imágenes**: redimensionar y convertir con `Bun.Image`, sin dependencias nativas. Las imágenes entrantes se normalizan antes de llegar al modelo — una foto de cámara pasa de 217 KB a 4 KB.
|
|
31
|
+
- **Harness**: cola durable con leases y recuperación tras crash, y ejecutores listos (`initHarnessExecutors`).
|
|
28
32
|
|
|
29
33
|
Con Hive SDK no montas un agente desde cero: **enganchas tu lógica de negocio en un harness ya armado**.
|
|
30
34
|
|
|
35
|
+
Y si además querés ponerle interfaz, no tenés que hablarle a la base de datos ni
|
|
36
|
+
imitar el formato que espera el modelo: `@johpaz/hive-sdk/services` expone el
|
|
37
|
+
mismo CRUD que usan las tools, en funciones tipadas.
|
|
38
|
+
|
|
31
39
|
## Instalación
|
|
32
40
|
|
|
33
41
|
> **Requiere Bun.** El paquete se publica como TypeScript y usa APIs de Bun
|
|
@@ -222,4 +230,4 @@ npm view @johpaz/hive-sdk dist-tags # verificar después del release
|
|
|
222
230
|
|
|
223
231
|
---
|
|
224
232
|
|
|
225
|
-
*Hive SDK v0.
|
|
233
|
+
*Hive SDK v0.3.0 — MIT*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@johpaz/hive-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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,9 @@
|
|
|
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
|
+
"./services": "./packages/core/src/services/index.ts",
|
|
39
|
+
"./hooks": "./packages/core/src/hooks/index.ts",
|
|
40
|
+
"./images": "./packages/core/src/images/index.ts",
|
|
38
41
|
"./sessions": "./packages/core/src/sessions/index.ts",
|
|
39
42
|
"./models": "./packages/core/src/models/index.ts",
|
|
40
43
|
"./swarm": "./packages/core/src/swarm/index.ts",
|
|
@@ -42,7 +45,12 @@
|
|
|
42
45
|
"./swarm/presets": "./packages/core/src/swarm/presets/index.ts",
|
|
43
46
|
"./scheduler": "./packages/core/src/scheduler/index.ts",
|
|
44
47
|
"./workers": "./packages/core/src/workers/index.ts",
|
|
45
|
-
"./events": "./packages/core/src/events/
|
|
48
|
+
"./events": "./packages/core/src/events/index.ts",
|
|
49
|
+
"./resilience": "./packages/core/src/resilience/index.ts",
|
|
50
|
+
"./tool-runtime": "./packages/core/src/tool-runtime/index.ts",
|
|
51
|
+
"./channels": "./packages/core/src/channels/index.ts",
|
|
52
|
+
"./voice": "./packages/core/src/voice/index.ts",
|
|
53
|
+
"./artifacts": "./packages/core/src/artifacts/index.ts",
|
|
46
54
|
"./ethics": "./packages/core/src/ethics/index.ts",
|
|
47
55
|
"./canvas": "./packages/core/src/canvas/index.ts",
|
|
48
56
|
"./config": "./packages/core/src/config/index.ts",
|
|
@@ -97,8 +105,6 @@
|
|
|
97
105
|
"@slack/bolt": "^4.7.2",
|
|
98
106
|
"@whiskeysockets/baileys": "7.0.0-rc11",
|
|
99
107
|
"async-mutex": "^0.5.0",
|
|
100
|
-
"cron-parser": "^5.5.0",
|
|
101
|
-
"croner": "^10.0.1",
|
|
102
108
|
"discord.js": "^14.26.4",
|
|
103
109
|
"docx": "^9.6.1",
|
|
104
110
|
"grammy": "^1.42.0",
|
|
@@ -3,7 +3,7 @@ import type {
|
|
|
3
3
|
AgentAcceptanceCriterion,
|
|
4
4
|
AgentModelOverride,
|
|
5
5
|
AgentWorkspaceScope,
|
|
6
|
-
} from "../storage/collections
|
|
6
|
+
} from "../storage/collections";
|
|
7
7
|
import { col, toIndexable, fromIndexable } from "../storage/hive.ts";
|
|
8
8
|
import { expandToolAllowlist } from "./delegation-runtime.ts";
|
|
9
9
|
|
|
@@ -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
|
|
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
|
|
76
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
96
|
-
"Inspeccioná el estado antes de interactuar y
|
|
97
|
-
"
|
|
98
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
138
|
-
"
|
|
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
|
|
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
|
|
156
|
+
"Inspeccioná entradas y confirma el formato solicitado.",
|
|
157
157
|
"Generá o extraé contenido preservando estructura y datos.",
|
|
158
|
-
"
|
|
159
|
-
"
|
|
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
|
-
"
|
|
177
|
+
"Crea la superficie antes de enviar componentes.",
|
|
178
178
|
"Enviá componentes válidos y después el data model enlazado.",
|
|
179
|
-
"
|
|
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
|
-
"
|
|
197
|
+
"Crea o modificá únicamente el job solicitado.",
|
|
198
198
|
"Consultá el job persistido y su próxima ejecución.",
|
|
199
|
-
"
|
|
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
|
-
"
|
|
227
|
-
"
|
|
228
|
-
"
|
|
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
|
|
@@ -29,6 +29,8 @@ import { estimateTokens } from "../utils/toon.ts"
|
|
|
29
29
|
import { callLLM, resolveProviderConfig, getDefaultLLM, type ContentPart } from "./llm-client.ts"
|
|
30
30
|
import { col, fromIndexable } from "../storage/hive.ts"
|
|
31
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
|
-
//
|
|
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
|
|