@johpaz/hive-sdk 0.4.3 → 0.4.5
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 +20 -4
- package/README.md +48 -7
- package/SECURITY.md +17 -0
- package/docs/API-AGENTS.md +430 -0
- package/docs/API-ARTIFACTS.md +55 -0
- package/docs/API-CONTEXT-COMPILER.md +285 -0
- package/docs/API-CRON.md +188 -0
- package/docs/API-DAG-SCHEDULER.md +291 -0
- package/docs/API-HOOKS.md +147 -0
- package/docs/API-RESILIENCE.md +45 -0
- package/docs/API-SERVICES.md +458 -0
- package/docs/API-SESSIONS.md +146 -0
- package/docs/API-TOOLS-SKILLS-CHANNELS.md +499 -0
- package/docs/API-WORKERS-EVENTS.md +311 -0
- package/docs/HIVE-HARNESS.md +232 -0
- package/docs/INDEX.md +198 -0
- package/docs/SECURITY-GUARDRAILS.md +87 -0
- package/docs/TEMPLATE-HIVE-APP.md +360 -0
- package/docs/UPGRADING.md +65 -0
- package/docs/assets/logoblack.png +0 -0
- package/docs/assets/logocolor-dark.png +0 -0
- package/docs/assets/logocolorbg.png +0 -0
- package/docs/plans/2026-09-05-office-dependency-hardening-design.md +28 -0
- package/docs/plans/2026-09-06-dependency-audit-remediation-design.md +25 -0
- package/docs/plans/2026-09-06-pptx-image-size-remediation-design.md +54 -0
- package/docs/plans/2026-09-06-typescript7-bun142-documentation-design.md +48 -0
- package/package.json +9 -8
- package/packages/cli/templates/hive-app/package.json +3 -0
- package/packages/core/src/agent/llm-providers/hiveagents.ts +2 -2
- package/packages/core/src/agent/providers/index.ts +17 -1
- package/packages/core/src/api/createAgent.ts +4 -2
- package/packages/core/src/config/loader.ts +2 -1
- package/packages/core/src/gateway/server.ts +1 -1
- package/packages/core/src/mcp/transports/sse.ts +22 -8
- package/packages/core/src/mcp/transports/websocket.ts +11 -9
- package/packages/core/src/scheduler/CronScheduler.ts +4 -2
- package/packages/core/src/scheduler/cron/job.ts +2 -1
- package/packages/core/src/scheduler/cron/zoned-time.ts +2 -1
- package/packages/core/src/tool-runtime/tool-worker.ts +3 -1
- package/packages/core/src/tools/office/office-escribir-pptx.ts +3 -1
- package/packages/core/src/tools/office/office-leer-pdf.ts +93 -44
- package/packages/core/src/tools/office/office-leer-xlsx.ts +36 -10
- package/packages/core/src/tools/office/security-limits.ts +28 -0
- package/packages/core/src/utils/port.ts +33 -0
- package/packages/core/src/vendor/pptxgenjs/LICENSE +21 -0
- package/packages/core/src/vendor/pptxgenjs/README.md +17 -0
- package/packages/core/src/vendor/pptxgenjs/pptxgen.es.d.ts +17 -0
- package/packages/core/src/vendor/pptxgenjs/pptxgen.es.js +7368 -0
- package/packages/core/src/voice/index.ts +6 -5
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# API Reference — DAG Scheduler
|
|
2
|
+
|
|
3
|
+
## Índice
|
|
4
|
+
|
|
5
|
+
1. [Arquitectura](#arquitectura)
|
|
6
|
+
2. [TaskNode](#tasknode)
|
|
7
|
+
3. [TaskGraph](#taskgraph)
|
|
8
|
+
4. [DAGScheduler](#dagscheduler)
|
|
9
|
+
5. [Estrategias](#estrategias)
|
|
10
|
+
6. [Presets](#presets)
|
|
11
|
+
7. [EventBridge](#eventbridge)
|
|
12
|
+
8. [Errores Comunes](#errores-comunes)
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Arquitectura
|
|
17
|
+
|
|
18
|
+
El DAG Scheduler orquesta la ejecución de grafos acíclicos dirigidos (DAG) de tareas.
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
TaskNode → TaskGraph → DAGScheduler
|
|
22
|
+
│
|
|
23
|
+
┌──────────────┼──────────────┐
|
|
24
|
+
▼ ▼ ▼
|
|
25
|
+
Worker 1 Worker 2 Worker 3
|
|
26
|
+
(agente) (agente) (agente)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## TaskNode
|
|
32
|
+
|
|
33
|
+
Representa una tarea individual en el grafo.
|
|
34
|
+
|
|
35
|
+
### TaskNodeConfig
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { TaskNode } from "@johpaz/hive-sdk";
|
|
39
|
+
|
|
40
|
+
interface TaskNodeConfig {
|
|
41
|
+
id: string;
|
|
42
|
+
agentId: string;
|
|
43
|
+
name?: string;
|
|
44
|
+
taskDescription: string;
|
|
45
|
+
deps: string[];
|
|
46
|
+
priority?: number;
|
|
47
|
+
timeout?: number;
|
|
48
|
+
maxRetries?: number;
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Estados
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
type NodeStatus = "PENDING" | "READY" | "RUNNING" | "COMPLETED" | "FAILED";
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Ejemplo
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
const node = new TaskNode({
|
|
62
|
+
id: "fetch-data",
|
|
63
|
+
agentId: "fetcher",
|
|
64
|
+
taskDescription: "Obtener datos de API",
|
|
65
|
+
deps: [],
|
|
66
|
+
});
|
|
67
|
+
node.markRunning();
|
|
68
|
+
// ... ejecutar ...
|
|
69
|
+
node.markCompleted("datos obtenidos");
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## TaskGraph
|
|
75
|
+
|
|
76
|
+
Grafo acíclico dirigido de tareas.
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { TaskGraph } from "@johpaz/hive-sdk";
|
|
80
|
+
|
|
81
|
+
const graph = new TaskGraph([
|
|
82
|
+
{ id: "a", agentId: "worker", taskDescription: "Tarea A", deps: [] },
|
|
83
|
+
{ id: "b", agentId: "worker", taskDescription: "Tarea B", deps: ["a"] },
|
|
84
|
+
{ id: "c", agentId: "worker", taskDescription: "Tarea C", deps: ["a"] },
|
|
85
|
+
{ id: "d", agentId: "worker", taskDescription: "Tarea D", deps: ["b", "c"] },
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
// Nodos listos para ejecutar (sin deps pendientes)
|
|
89
|
+
const ready = graph.getReadyNodes();
|
|
90
|
+
|
|
91
|
+
// IDs de nodos completados
|
|
92
|
+
const completed = graph.getCompletedIds();
|
|
93
|
+
|
|
94
|
+
// Nodos recién listos tras completar uno
|
|
95
|
+
const newlyReady = graph.getNewlyReadyIds(new Set(["a"]));
|
|
96
|
+
|
|
97
|
+
// Resultados de dependencias
|
|
98
|
+
const depResults = graph.getDepResults("d");
|
|
99
|
+
|
|
100
|
+
// Progreso
|
|
101
|
+
const progress = graph.getProgress();
|
|
102
|
+
// { total: 4, completed: 1, running: 0, failed: 0, pending: 3, percentComplete: 25 }
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## DAGScheduler
|
|
108
|
+
|
|
109
|
+
Orquestador principal de la ejecución.
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { DAGScheduler, TaskGraph } from "@johpaz/hive-sdk";
|
|
113
|
+
|
|
114
|
+
const graph = new TaskGraph([
|
|
115
|
+
{ id: "fetch", agentId: "fetcher", taskDescription: "Fetch data", deps: [] },
|
|
116
|
+
{ id: "process", agentId: "processor", taskDescription: "Process", deps: ["fetch"] },
|
|
117
|
+
{ id: "save", agentId: "saver", taskDescription: "Save", deps: ["process"] },
|
|
118
|
+
]);
|
|
119
|
+
|
|
120
|
+
const scheduler = new DAGScheduler({
|
|
121
|
+
maxConcurrentWorkers: 2,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const result = await scheduler.execute(graph);
|
|
125
|
+
|
|
126
|
+
console.log(`Success: ${result.success}`);
|
|
127
|
+
console.log(`Duration: ${result.totalDurationMs}ms`);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### DAGResult
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
interface DAGResult {
|
|
134
|
+
swarmId: string;
|
|
135
|
+
totalDurationMs: number;
|
|
136
|
+
completed: NodeSummary[];
|
|
137
|
+
failed: NodeSummary[];
|
|
138
|
+
success: boolean;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
interface NodeSummary {
|
|
142
|
+
id: string;
|
|
143
|
+
name: string;
|
|
144
|
+
status: "COMPLETED" | "FAILED";
|
|
145
|
+
durationMs: number;
|
|
146
|
+
result?: string;
|
|
147
|
+
error?: string;
|
|
148
|
+
retries: number;
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Control
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
scheduler.abort(); // Abortar ejecución en curso
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Estrategias
|
|
161
|
+
|
|
162
|
+
### ParallelStrategy (FIFO)
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
import { ParallelStrategy } from "@johpaz/hive-sdk";
|
|
166
|
+
|
|
167
|
+
const strategy = new ParallelStrategy(); // Orden de llegada
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### PriorityStrategy
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
import { PriorityStrategy } from "@johpaz/hive-sdk";
|
|
174
|
+
|
|
175
|
+
const strategy = new PriorityStrategy(); // Por prioridad + path crítico
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Custom Strategy
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import type { ExecutionStrategy } from "@johpaz/hive-sdk";
|
|
182
|
+
|
|
183
|
+
const myStrategy: ExecutionStrategy = {
|
|
184
|
+
initialize(nodes) { /* precomputar */ },
|
|
185
|
+
pick(nodes) { return nodes[0]; }, // FIFO
|
|
186
|
+
};
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Presets
|
|
192
|
+
|
|
193
|
+
Grafos predefinidos.
|
|
194
|
+
|
|
195
|
+
### ResearchPreset
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
import { createResearchGraph } from "@hive/core/swarm/presets";
|
|
199
|
+
|
|
200
|
+
const graph = createResearchGraph({
|
|
201
|
+
agents: { researcher: "researcher-id", writer: "writer-id" },
|
|
202
|
+
query: "Análisis de mercado",
|
|
203
|
+
});
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### HiveLearnPreset
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import { createHiveLearnGraph } from "@hive/core/swarm/presets";
|
|
210
|
+
|
|
211
|
+
const graph = createHiveLearnGraph({
|
|
212
|
+
agents: { teacher: "teacher-id", student: "student-id" },
|
|
213
|
+
topic: "Machine Learning",
|
|
214
|
+
});
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## EventBridge
|
|
220
|
+
|
|
221
|
+
Puente de eventos entre el scheduler y el resto del sistema.
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
import { EventBridge } from "@johpaz/hive-sdk";
|
|
225
|
+
|
|
226
|
+
const bridge = new EventBridge("swarm-123", "project-1", "coordinator-1");
|
|
227
|
+
|
|
228
|
+
bridge.onTaskCompleted = (node, progress) => {
|
|
229
|
+
console.log(`${node.name}: ${progress.percentComplete}%`);
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
bridge.onSwarmCompleted = (result) => {
|
|
233
|
+
console.log(`Swarm completed. Success: ${result.success}`);
|
|
234
|
+
};
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## IAgentExecutor
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
import type { IAgentExecutor } from "@johpaz/hive-sdk";
|
|
243
|
+
|
|
244
|
+
const myExecutor: IAgentExecutor = {
|
|
245
|
+
async execute(node, depResults, threadId) {
|
|
246
|
+
const context = Object.values(depResults).join("\n");
|
|
247
|
+
return await myCustomFunction(node.taskDescription, context);
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const result = await scheduler.execute(graph, { executor: myExecutor });
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Errores Comunes
|
|
257
|
+
|
|
258
|
+
### Cyclic dependency detected
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
// ❌ Ciclo: a→b→a
|
|
262
|
+
[{ id: "a", deps: ["b"] }, { id: "b", deps: ["a"] }];
|
|
263
|
+
|
|
264
|
+
// ✅ Sin ciclo
|
|
265
|
+
[{ id: "a", deps: [] }, { id: "b", deps: ["a"] }];
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Task timeout
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
// Configurar timeout razonable
|
|
272
|
+
const config = { id: "task", agentId: "w", taskDescription: "...", deps: [], timeout: 30000 };
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Errores y utilidades
|
|
276
|
+
|
|
277
|
+
| | |
|
|
278
|
+
|---|---|
|
|
279
|
+
| `CyclicDependencyError` | El grafo tiene un ciclo: no hay orden posible. Se detecta al construirlo, no al ejecutarlo. |
|
|
280
|
+
| `TaskTimeoutError` | Un nodo agotó su ventana. Lleva el id del nodo. |
|
|
281
|
+
| `TaskFailureError` | Un nodo falló; sus dependientes se marcan sin llegar a correr. |
|
|
282
|
+
|
|
283
|
+
`defaultInvoker` es cómo se invoca a un agente por defecto en `runRoleSwarm`
|
|
284
|
+
(`runAgentIsolated`). Se puede reemplazar para instrumentar o para tests, sin
|
|
285
|
+
tocar la topología.
|
|
286
|
+
|
|
287
|
+
`createTaskHandler` · `notifyTaskCompletion` · `setSchedulerForCleanup` conectan
|
|
288
|
+
el enjambre con el scheduler: son lo que hace que una tarea programada pueda
|
|
289
|
+
disparar un grafo y que su finalización se notifique.
|
|
290
|
+
|
|
291
|
+
*Documentación Hive SDK — ver `version` en package.json*
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# API-HOOKS — engancharse al ciclo de vida
|
|
2
|
+
|
|
3
|
+
## Por qué existe
|
|
4
|
+
|
|
5
|
+
`HooksConfigSchema` declaraba 14 hooks en la configuración y **ninguno se
|
|
6
|
+
invocaba**: era un esquema sin implementación. Quien lo encontrara asumiría que
|
|
7
|
+
funciona, que es peor que no tenerlo.
|
|
8
|
+
|
|
9
|
+
Ahora están conectados, con dos formas:
|
|
10
|
+
|
|
11
|
+
- **Callbacks en proceso** (`registerHook`) — tipados, sin costo de arranque, y
|
|
12
|
+
pueden **devolver una decisión**. Es el primitivo: quien consume el SDK ya está
|
|
13
|
+
en el mismo proceso.
|
|
14
|
+
- **Scripts externos** (`hooks.scripts` en la configuración) — para quien no
|
|
15
|
+
escribe TypeScript. Se montan encima del primitivo. Cuestan un `Bun.spawn` por
|
|
16
|
+
invocación, así que conviene reservarlos para lo que no ocurre en cada tool call.
|
|
17
|
+
|
|
18
|
+
Sólo están los cinco con un uso claro. Los otros nueve del esquema original
|
|
19
|
+
quedaron fuera a propósito: cada hook es una promesa que después hay que
|
|
20
|
+
sostener, y uno que nadie usa es superficie que envejece mal.
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { registerHook } from "@johpaz/hive-sdk/hooks";
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Los cinco
|
|
27
|
+
|
|
28
|
+
| Hook | Cuándo | Puede bloquear |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| `beforeToolCall` | Antes de ejecutar una tool | **sí** |
|
|
31
|
+
| `afterToolCall` | Con el resultado, haya salido bien o mal | no |
|
|
32
|
+
| `beforeCompaction` | Antes de comprimir el historial | no |
|
|
33
|
+
| `sessionStart` | Al abrir una conversación (crearla o reabrirla) | no |
|
|
34
|
+
| `sessionEnd` | Al cerrarla (archivarla o borrarla) | no |
|
|
35
|
+
|
|
36
|
+
## Bloquear una tool
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
registerHook("beforeToolCall", (ctx) => {
|
|
40
|
+
if (ctx.toolName === "cli_exec" && String(ctx.args.command).includes("curl")) {
|
|
41
|
+
return { block: true, reason: "las llamadas de red salen por api_request" };
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
La tool **no se ejecuta**, y el motivo le llega al modelo como resultado, para
|
|
47
|
+
que sepa por qué no se hizo en vez de reintentar a ciegas.
|
|
48
|
+
|
|
49
|
+
Detalles que importan:
|
|
50
|
+
|
|
51
|
+
- **El primero que bloquea gana.** No tiene sentido seguir preguntando cuando ya
|
|
52
|
+
hay una negativa.
|
|
53
|
+
- **Un hook que lanza no bloquea.** Un observador roto no debería frenar el
|
|
54
|
+
trabajo; se registra el error y se sigue.
|
|
55
|
+
- **Se aplica a las tools que corren en un worker también.** El enganche está en
|
|
56
|
+
`executeToolBatch`, no en la ejecución del hilo principal: engancharlo abajo
|
|
57
|
+
dejaría la mitad de las llamadas sin revisar, que en un hook de política es
|
|
58
|
+
peor que no tenerlo.
|
|
59
|
+
- **Bloquear una no impide las demás**, y el orden se conserva: el modelo espera
|
|
60
|
+
una respuesta por cada llamada que hizo, en el orden en que las hizo.
|
|
61
|
+
|
|
62
|
+
## Auditar
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
registerHook("afterToolCall", (ctx) => {
|
|
66
|
+
auditoria.registrar({
|
|
67
|
+
tool: ctx.toolName, ok: ctx.ok, ms: ctx.durationMs,
|
|
68
|
+
agente: ctx.agentId, usuario: ctx.userId,
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`afterToolCall` **también ve las bloqueadas**: auditar incluye lo que no pasó.
|
|
74
|
+
|
|
75
|
+
## Sesiones
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
registerHook("sessionStart", async ({ threadId, userId, channel }) => {
|
|
79
|
+
await miPanel.registrarConversacion(userId, threadId, channel);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
registerHook("sessionEnd", async ({ threadId, userId }) => {
|
|
83
|
+
await miPanel.cerrarConversacion(userId, threadId);
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Se disparan en las cuatro transiciones del ciclo de vida de un hilo, todas en
|
|
88
|
+
`agent/thread-store.ts`:
|
|
89
|
+
|
|
90
|
+
| Transición | Hook | API pública |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| Se crea el hilo | `sessionStart` | `createSession` · `createWebSession` |
|
|
93
|
+
| Se reabre | `sessionStart` | `reopenSession` |
|
|
94
|
+
| Se archiva | `sessionEnd` | `closeSession` |
|
|
95
|
+
| Se borra | `sessionEnd` | `deleteSession` |
|
|
96
|
+
|
|
97
|
+
Detalles que importan:
|
|
98
|
+
|
|
99
|
+
- **`sessionStart` no se dispara en cada turno.** `createSession` es idempotente
|
|
100
|
+
y se llama con cada mensaje entrante; el enganche está en el `put` que crea la
|
|
101
|
+
fila, no en la función. Quien use el hook para inicializar estado o para
|
|
102
|
+
cobrar por conversación cuenta conversaciones, no mensajes.
|
|
103
|
+
- **Dos turnos concurrentes lo disparan una sola vez.** El `put` con
|
|
104
|
+
`expectedVersion: 0` es el punto de serialización: el que pierde la carrera no
|
|
105
|
+
dispara nada.
|
|
106
|
+
- **Archivar o reabrir dos veces seguidas dispara una sola vez**: el estado se
|
|
107
|
+
compara antes de escribir, y un no-op no anuncia nada.
|
|
108
|
+
- **`sessionEnd` por borrado lleva `userId` y `channel`.** La fila se lee antes
|
|
109
|
+
de borrarla; si no, el hook recibiría un `threadId` y nada más, y quien lo use
|
|
110
|
+
para limpiar no sabría de quién era lo que se fue.
|
|
111
|
+
- **Cubre a los canales que llaman `ensureThread` directo**, sin pasar por el
|
|
112
|
+
módulo `sessions`.
|
|
113
|
+
|
|
114
|
+
## Scripts
|
|
115
|
+
|
|
116
|
+
```jsonc
|
|
117
|
+
// hive.config.json
|
|
118
|
+
{ "hooks": { "scripts": { "before_tool_call": "./hooks/politica.ts" } } }
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
loadConfiguredHookScripts(); // opt-in: ejecutar procesos externos no debería
|
|
123
|
+
// pasar por el solo hecho de importar el SDK
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
El contexto llega por stdin como JSON. Para `before_tool_call`, **salir con
|
|
127
|
+
código distinto de 0 bloquea** y lo que el script escriba en stdout es el motivo
|
|
128
|
+
— el equivalente en procesos a devolver `{ block }`.
|
|
129
|
+
|
|
130
|
+
## Utilidades
|
|
131
|
+
|
|
132
|
+
`hasHooks(nombre)` para saltarse trabajo cuando no hay ninguno registrado;
|
|
133
|
+
`clearHooks(nombre?)` para tests. `registerHook` devuelve la función que lo quita.
|
|
134
|
+
|
|
135
|
+
## Los disparadores
|
|
136
|
+
|
|
137
|
+
`runBeforeToolCall` · `runAfterToolCall` · `runBeforeCompaction` ·
|
|
138
|
+
`runSessionStart` · `runSessionEnd`
|
|
139
|
+
|
|
140
|
+
Los llama el SDK en cada punto del ciclo; están exportados para quien integre los
|
|
141
|
+
hooks en su propio flujo —un gateway con su propia noción de sesión, por ejemplo—
|
|
142
|
+
pero **no hay que llamarlos** para que los hooks funcionen en un turno normal.
|
|
143
|
+
|
|
144
|
+
`runBeforeToolCall` devuelve el motivo del bloqueo o `null`; los demás no
|
|
145
|
+
devuelven nada, porque son observadores.
|
|
146
|
+
|
|
147
|
+
*Documentación Hive SDK — ver `version` en package.json*
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# API-RESILIENCE — reintentos y circuit breakers
|
|
2
|
+
|
|
3
|
+
```typescript
|
|
4
|
+
import { withRetry, isRetryableError, CircuitBreaker } from "@johpaz/hive-sdk/resilience";
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
## Reintentos
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
const res = await withRetry(
|
|
11
|
+
() => llamarAlProveedor(),
|
|
12
|
+
{ maxAttempts: 3, initialDelayMs: 1000, backoffMultiplier: 2, maxDelayMs: 30000 },
|
|
13
|
+
(err) => isRetryableError(err),
|
|
14
|
+
);
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`isRetryableError` distingue lo que mejora reintentando —429, 5xx, timeouts,
|
|
18
|
+
cortes de red— de lo que no. **Un 400 no mejora reintentando**: el cuerpo está
|
|
19
|
+
mal y volver a mandarlo igual sólo gasta una llamada. Lo mismo con 401 y 403.
|
|
20
|
+
|
|
21
|
+
El backoff es exponencial con jitter, y respeta el `Retry-After` cuando el
|
|
22
|
+
proveedor lo manda: si te dicen cuánto esperar, discutirlo es contraproducente.
|
|
23
|
+
|
|
24
|
+
`computeRetryDelay(intento, policy)` calcula la espera si necesitas programarla
|
|
25
|
+
por tu cuenta.
|
|
26
|
+
|
|
27
|
+
## Circuit breakers
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
const breaker = new CircuitBreaker("proveedor-x", { failureThreshold: 5, resetTimeoutMs: 30000 });
|
|
31
|
+
const r = await breaker.execute(() => llamar());
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Cuando un servicio ya viene fallando, seguir intentando le agrega carga y te
|
|
35
|
+
gasta el presupuesto de reintentos. El breaker corta: tras N fallos se abre y
|
|
36
|
+
rechaza sin llamar, hasta que pasa el tiempo de reposo y deja pasar una de
|
|
37
|
+
prueba.
|
|
38
|
+
|
|
39
|
+
`CircuitBreakerOpenError` lleva `retryAfterMs`, que es lo que necesita quien
|
|
40
|
+
quiera decirle al usuario cuándo volver a intentar.
|
|
41
|
+
|
|
42
|
+
`circuitBreakerRegistry` mantiene uno por nombre, para no crear un breaker nuevo
|
|
43
|
+
en cada llamada y perder el estado que le da sentido.
|
|
44
|
+
|
|
45
|
+
*Documentación Hive SDK — ver `version` en package.json*
|