@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
@@ -1,308 +0,0 @@
1
- /**
2
- * BrowserBackend — selección de backend y el WebViewBackend real.
3
- *
4
- * Los tests marcados como "vivos" abren un navegador de verdad y se saltan solos
5
- * donde no hay motor (sin Chrome y sin macOS). Los de selección son puros y
6
- * corren siempre: son los que evitan que un cambio de default mande el gateway
7
- * headless a un backend que necesita display.
8
- */
9
-
10
- process.env.HIVE_DB_PATH = ":memory:";
11
-
12
- import { describe, test, expect, beforeEach, afterEach, afterAll } from "bun:test";
13
- import {
14
- resolveBackendKind,
15
- isWebViewSupported,
16
- resolveWebViewEngine,
17
- findChrome,
18
- } from "../packages/core/src/tools/web/browser-backend.ts";
19
- import { WebViewBackend } from "../packages/core/src/tools/web/webview-backend.ts";
20
-
21
- const ORIGINAL_ENV = process.env.HIVE_BROWSER_BACKEND;
22
-
23
- afterEach(() => {
24
- if (ORIGINAL_ENV === undefined) delete process.env.HIVE_BROWSER_BACKEND;
25
- else process.env.HIVE_BROWSER_BACKEND = ORIGINAL_ENV;
26
- });
27
-
28
- // Una sola vez, al final del archivo: `closeAll()` mata TODOS los subprocesos de
29
- // navegador del proceso, así que dentro de un `afterAll` por bloque le arranca
30
- // Chrome de abajo al bloque siguiente ("Chrome killed by signal 9").
31
- afterAll(() => {
32
- (globalThis as { Bun?: { WebView?: { closeAll?: () => void } } }).Bun?.WebView?.closeAll?.();
33
- });
34
-
35
- // ─── selección de backend ─────────────────────────────────────────────────────
36
-
37
- describe("resolveBackendKind", () => {
38
- test("el default es agent-browser — es el único que corre headless sin Chrome", () => {
39
- delete process.env.HIVE_BROWSER_BACKEND;
40
- expect(resolveBackendKind(undefined)).toBe("agent-browser");
41
- });
42
-
43
- test("respeta la preferencia de config", () => {
44
- delete process.env.HIVE_BROWSER_BACKEND;
45
- expect(resolveBackendKind("webview")).toBe("webview");
46
- expect(resolveBackendKind("agent-browser")).toBe("agent-browser");
47
- });
48
-
49
- test("HIVE_BROWSER_BACKEND pisa la config", () => {
50
- process.env.HIVE_BROWSER_BACKEND = "webview";
51
- expect(resolveBackendKind("agent-browser")).toBe("webview");
52
-
53
- process.env.HIVE_BROWSER_BACKEND = "agent-browser";
54
- expect(resolveBackendKind("webview")).toBe("agent-browser");
55
- });
56
-
57
- test("'auto' nunca elige webview si no hay motor disponible", () => {
58
- delete process.env.HIVE_BROWSER_BACKEND;
59
- const kind = resolveBackendKind("auto");
60
- expect(kind).toBe(isWebViewSupported() ? "webview" : "agent-browser");
61
- });
62
-
63
- test("un valor desconocido cae al default en vez de romper", () => {
64
- process.env.HIVE_BROWSER_BACKEND = "netscape";
65
- expect(resolveBackendKind("webview")).toBe("agent-browser");
66
- });
67
- });
68
-
69
- describe("detección de motor", () => {
70
- test("resolveWebViewEngine devuelve webkit en macOS y chrome sólo si hay binario", () => {
71
- const engine = resolveWebViewEngine();
72
- if (process.platform === "darwin") {
73
- expect(engine).toBe("webkit");
74
- } else {
75
- expect(engine).toBe(findChrome() ? "chrome" : null);
76
- }
77
- });
78
-
79
- test("BUN_CHROME_PATH gana sobre la búsqueda en rutas estándar", () => {
80
- const previous = process.env.BUN_CHROME_PATH;
81
- process.env.BUN_CHROME_PATH = "/ruta/propia/chrome";
82
- try {
83
- expect(findChrome()).toBe("/ruta/propia/chrome");
84
- } finally {
85
- if (previous === undefined) delete process.env.BUN_CHROME_PATH;
86
- else process.env.BUN_CHROME_PATH = previous;
87
- }
88
- });
89
- });
90
-
91
- // ─── WebViewBackend vivo ──────────────────────────────────────────────────────
92
-
93
- const LIVE = isWebViewSupported();
94
- // El charset va explícito: sin él el motor asume latin-1 y "botón" llega como
95
- // "botón". Una página real lo declara; una data: URL no.
96
- const page = (html: string) => `data:text/html;charset=utf-8,${encodeURIComponent(html)}`;
97
-
98
- describe.skipIf(!LIVE)("WebViewBackend (navegador real)", () => {
99
- let backend: WebViewBackend;
100
-
101
- beforeEach(() => {
102
- backend = new WebViewBackend();
103
- });
104
-
105
- afterEach(() => {
106
- backend.close();
107
- });
108
-
109
- test("navega y evalúa en la página", async () => {
110
- await backend.navigate(page("<h1>hola mundo</h1>"));
111
- expect(await backend.evaluate<string>("document.querySelector('h1').textContent")).toBe("hola mundo");
112
- });
113
-
114
- test("serializa operaciones concurrentes — WebView rechaza las solapadas", async () => {
115
- // Sin la cola interna esto falla con
116
- // "ERR_INVALID_STATE: a simple operation is already pending".
117
- await backend.navigate(page("<p id=x>1</p>"));
118
-
119
- const results = await Promise.all([
120
- backend.evaluate("1 + 1"),
121
- backend.evaluate("2 + 2"),
122
- backend.evaluate("3 + 3"),
123
- backend.evaluate("document.getElementById('x').textContent"),
124
- ]);
125
-
126
- expect(results).toEqual([2, 4, 6, "1"]);
127
- });
128
-
129
- test("una operación fallida no rompe la cola para las siguientes", async () => {
130
- await backend.navigate(page("<h1>sigo viva</h1>"));
131
-
132
- await expect(backend.evaluate("esto.no.existe()")).rejects.toThrow();
133
- expect(await backend.evaluate<string>("document.querySelector('h1').textContent")).toBe("sigo viva");
134
- });
135
-
136
- test("click y fill operan por selector CSS", async () => {
137
- await backend.navigate(
138
- page(`<input id="i"><button id="b" onclick="document.getElementById('i').dataset.hit='1'">Go</button>`),
139
- );
140
-
141
- await backend.fill("#i", "texto nuevo");
142
- expect(await backend.evaluate<string>("document.getElementById('i').value")).toBe("texto nuevo");
143
-
144
- await backend.click("#b");
145
- expect(await backend.evaluate<string>("document.getElementById('i').dataset.hit")).toBe("1");
146
- });
147
-
148
- test("fill reemplaza el contenido en vez de agregarlo", async () => {
149
- await backend.navigate(page(`<input id="i" value="viejo">`));
150
- await backend.fill("#i", "nuevo");
151
- expect(await backend.evaluate<string>("document.getElementById('i').value")).toBe("nuevo");
152
- });
153
-
154
- test("typeIn y fill fallan claro cuando el selector no existe", async () => {
155
- await backend.navigate(page("<h1>x</h1>"));
156
- await expect(backend.typeIn("#no-existe", "x")).rejects.toThrow(/no encontrado/);
157
- await expect(backend.fill("#no-existe", "x")).rejects.toThrow(/no encontrado/);
158
- });
159
-
160
- test("la navegación hacia atrás usa goBack — los tipos de bun-types dicen back()", async () => {
161
- await backend.navigate(page("<h1>primera</h1>"));
162
- await backend.navigate(page("<h1>segunda</h1>"));
163
- expect(await backend.evaluate<string>("document.querySelector('h1').textContent")).toBe("segunda");
164
-
165
- await backend.back();
166
- expect(await backend.evaluate<string>("document.querySelector('h1').textContent")).toBe("primera");
167
- });
168
-
169
- test("screenshot devuelve un PNG en base64", async () => {
170
- await backend.navigate(page("<h1>foto</h1>"));
171
- const shot = await backend.screenshot();
172
- expect(shot.length).toBeGreaterThan(100);
173
- // Cabecera PNG (\x89PNG) en base64.
174
- expect(shot.startsWith("iVBORw0KGgo")).toBe(true);
175
- });
176
-
177
- test("screenshotElement recorta al elemento pedido", async () => {
178
- await backend.navigate(
179
- page(`<div id="chico" style="width:80px;height:40px;background:red"></div>`),
180
- );
181
- const shot = await backend.screenshotElement("#chico");
182
- expect(shot.startsWith("iVBORw0KGgo")).toBe(true);
183
- // El recorte tiene que pesar menos que la captura del viewport entero.
184
- expect(shot.length).toBeLessThan((await backend.screenshot()).length);
185
- });
186
-
187
- test("screenshotElement falla claro si el elemento no existe", async () => {
188
- await backend.navigate(page("<h1>x</h1>"));
189
- await expect(backend.screenshotElement("#fantasma")).rejects.toThrow(/no visible o inexistente/);
190
- });
191
- });
192
-
193
- // ─── snapshot sintetizado ─────────────────────────────────────────────────────
194
-
195
- describe.skipIf(!LIVE)("WebViewBackend.snapshot", () => {
196
- let backend: WebViewBackend;
197
-
198
- beforeEach(() => {
199
- backend = new WebViewBackend();
200
- });
201
-
202
- afterEach(() => {
203
- backend.close();
204
- });
205
-
206
- test("reproduce el formato de agent-browser: rol, nombre y ref", async () => {
207
- await backend.navigate(page(`<h1>Example Domain</h1><p><a href="/x">Learn more</a></p>`));
208
- const snapshot = await backend.snapshot({ compact: true, depth: 3 });
209
-
210
- expect(snapshot).toContain('- heading "Example Domain" [level=1, ref=e1]');
211
- expect(snapshot).toContain('link "Learn more"');
212
- expect(snapshot).toMatch(/ref=e\d+/);
213
- });
214
-
215
- test("anida los hijos bajo el padre que emitió línea", async () => {
216
- await backend.navigate(page(`<p>Intro <a href="/x">un link</a></p>`));
217
- const snapshot = await backend.snapshot();
218
-
219
- const linea = snapshot.split("\n").find((l) => l.includes("link"));
220
- expect(linea?.startsWith(" ")).toBe(true);
221
- });
222
-
223
- test("los divs de maquetado no generan sangría — el árbol no se hunde", async () => {
224
- await backend.navigate(
225
- page(`<div><div><div><div><button>Enviar</button></div></div></div></div>`),
226
- );
227
- const snapshot = await backend.snapshot({ depth: 3 });
228
-
229
- // Cuatro divs de por medio y el botón sigue en el nivel raíz.
230
- expect(snapshot).toBe('- button "Enviar" [ref=e1]');
231
- });
232
-
233
- test("ignora script, style y lo oculto", async () => {
234
- await backend.navigate(
235
- page(`<script>var secreto=1</script><style>.x{}</style>
236
- <div hidden><a href="/h">oculto</a></div>
237
- <div style="display:none"><a href="/d">tampoco</a></div>
238
- <div aria-hidden="true"><a href="/a">ni este</a></div>
239
- <a href="/v">visible</a>`),
240
- );
241
- const snapshot = await backend.snapshot();
242
-
243
- expect(snapshot).toContain("visible");
244
- expect(snapshot).not.toContain("secreto");
245
- expect(snapshot).not.toContain("oculto");
246
- expect(snapshot).not.toContain("tampoco");
247
- expect(snapshot).not.toContain("ni este");
248
- });
249
-
250
- test("toma el nombre accesible de aria-label, alt y placeholder", async () => {
251
- await backend.navigate(
252
- page(`<button aria-label="Cerrar ventana"></button>
253
- <img src="x.png" alt="Un gato">
254
- <input placeholder="Tu correo">`),
255
- );
256
- const snapshot = await backend.snapshot();
257
-
258
- expect(snapshot).toContain("Cerrar ventana");
259
- expect(snapshot).toContain("Un gato");
260
- expect(snapshot).toContain("Tu correo");
261
- });
262
-
263
- test("interactiveOnly deja sólo lo accionable", async () => {
264
- await backend.navigate(
265
- page(`<h1>Un título</h1><p>Un párrafo largo</p><a href="/x">Un link</a><button>Un botón</button>`),
266
- );
267
- const snapshot = await backend.snapshot({ interactiveOnly: true });
268
-
269
- expect(snapshot).toContain("Un link");
270
- expect(snapshot).toContain("Un botón");
271
- expect(snapshot).not.toContain("Un título");
272
- expect(snapshot).not.toContain("Un párrafo largo");
273
- });
274
-
275
- test("depth corta la profundidad de lo que sí anida", async () => {
276
- // La profundidad cuenta niveles *emitidos*, no nodos del DOM: por eso hace
277
- // falta un ancestro con nombre propio (el nav) para que el link baje un nivel.
278
- await backend.navigate(page(`<nav aria-label="Menú"><a href="/x">hondo</a></nav>`));
279
-
280
- expect(await backend.snapshot({ depth: 1 })).not.toContain("hondo");
281
- expect(await backend.snapshot({ depth: 5 })).toContain("hondo");
282
- });
283
-
284
- test("marca los atributos de estado del control", async () => {
285
- await backend.navigate(
286
- page(`<input type="checkbox" aria-label="Acepto" checked>
287
- <button disabled aria-label="Enviar">x</button>`),
288
- );
289
- const snapshot = await backend.snapshot();
290
-
291
- expect(snapshot).toContain("checked");
292
- expect(snapshot).toContain("disabled");
293
- });
294
-
295
- test("un DOM enorme se trunca en vez de comerse el contexto", async () => {
296
- const filas = Array.from({ length: 4000 }, (_, i) => `<p>fila número ${i} con texto de relleno</p>`).join("");
297
- await backend.navigate(page(`<div>${filas}</div>`));
298
-
299
- const snapshot = await backend.snapshot();
300
- expect(snapshot.length).toBeLessThan(21_000);
301
- expect(snapshot).toContain("snapshot truncado");
302
- });
303
-
304
- test("una página vacía devuelve string vacío, no una excepción", async () => {
305
- await backend.navigate(page("<body></body>"));
306
- expect(await backend.snapshot()).toBe("");
307
- });
308
- });
@@ -1,117 +0,0 @@
1
- /**
2
- * A catalog persona is a capability of the whole hive, not a disposable
3
- * worker: nothing automatic may switch one off. Disabling is the user's call,
4
- * from the Agents UI (PUT /api/agents/:id). Uses HIVE_DB_PATH=":memory:".
5
- */
6
-
7
- process.env.HIVE_DB_PATH = ":memory:";
8
-
9
- import { describe, test, expect, beforeEach, afterEach } from "bun:test";
10
- import { closeHiveDb } from "../packages/core/src/storage/hivedb";
11
- import { ensureHiveDb } from "../packages/core/src/storage/bootstrap";
12
- import { col } from "../packages/core/src/storage/hive";
13
- import type { AgentDoc, AgentProposalDoc } from "../packages/core/src/storage/collections";
14
- import { runCurator } from "../packages/core/src/agent/curator";
15
- import { agentArchiveTool } from "../packages/core/src/tools/agents";
16
- import { CATALOG_AGENT_IDS } from "../packages/core/src/agent/agent-catalog";
17
-
18
- const VICTIM = "workspace_file_operator";
19
-
20
- beforeEach(async () => {
21
- closeHiveDb();
22
- await ensureHiveDb();
23
- });
24
-
25
- afterEach(() => {
26
- closeHiveDb();
27
- });
28
-
29
- async function agent(id: string): Promise<AgentDoc> {
30
- const entry = await (await col<AgentDoc>("agents")).get(id);
31
- if (!entry) throw new Error(`agent not found: ${id}`);
32
- return entry.doc;
33
- }
34
-
35
- async function setCounters(id: string, helpful: number, harmful: number): Promise<void> {
36
- const agentsCol = await col<AgentDoc>("agents");
37
- const entry = (await agentsCol.get(id))!;
38
- await agentsCol.put(id, { ...entry.doc, helpful_count: helpful, harmful_count: harmful }, { expectedVersion: entry.version });
39
- }
40
-
41
- describe("curator", () => {
42
- test("never disables a catalog agent, however bad its ACE counters are", async () => {
43
- await setCounters(VICTIM, 0, 25);
44
-
45
- await runCurator();
46
-
47
- expect((await agent(VICTIM)).enabled).toBe(true);
48
- });
49
-
50
- test("raises a reviewable proposal instead of applying the change", async () => {
51
- await setCounters(VICTIM, 0, 5);
52
-
53
- await runCurator();
54
-
55
- const proposals = (await (await col<AgentProposalDoc>("agentProposals")).findBy("agent_id", VICTIM)).map(e => e.doc);
56
- expect(proposals).toHaveLength(1);
57
- expect(proposals[0].type).toBe("disable_agent");
58
- expect(proposals[0].status).toBe("proposed");
59
- });
60
-
61
- test("does not archive catalog agents that were never used", async () => {
62
- // Seeded with lastTraceAt=null, which the inactivity cutoff reads as epoch 0
63
- expect((await agent(VICTIM)).lastTraceAt).toBeNull();
64
-
65
- await runCurator();
66
-
67
- for (const id of CATALOG_AGENT_IDS) {
68
- expect((await agent(id)).status).not.toBe("archived");
69
- }
70
- });
71
-
72
- test("repairs a catalog agent archived by an older release on boot", async () => {
73
- const agentsCol = await col<AgentDoc>("agents");
74
- const existing = (await agentsCol.get(VICTIM))!;
75
- await agentsCol.put(
76
- VICTIM,
77
- { ...existing.doc, status: "archived" },
78
- { expectedVersion: existing.version },
79
- );
80
-
81
- await ensureHiveDb();
82
-
83
- expect((await agent(VICTIM)).status).toBe("idle");
84
- });
85
-
86
- test("does not archive an inactive worker automatically", async () => {
87
- const agentsCol = await col<AgentDoc>("agents");
88
- const stale = { ...(await agent(VICTIM)), id: "stale-worker", source: "user" as const, lastTraceAt: 1 };
89
- await agentsCol.put(stale.id, stale);
90
-
91
- await runCurator();
92
-
93
- expect((await agent("stale-worker")).status).not.toBe("archived");
94
- expect((await agent("stale-worker")).enabled).toBe(true);
95
- });
96
- });
97
-
98
- describe("agent_archive tool", () => {
99
- test("refuses to archive a catalog agent", async () => {
100
- const result = await agentArchiveTool.execute({ agentId: VICTIM }) as { ok: boolean; error?: string };
101
-
102
- expect(result.ok).toBe(false);
103
- expect(result.error).toContain("catalog agent");
104
- expect((await agent(VICTIM)).enabled).toBe(true);
105
- });
106
-
107
- test("still archives a worker created by the coordinator", async () => {
108
- const agentsCol = await col<AgentDoc>("agents");
109
- const worker = { ...(await agent(VICTIM)), id: "spawned-worker", source: "user" as const };
110
- await agentsCol.put(worker.id, worker);
111
-
112
- const result = await agentArchiveTool.execute({ agentId: "spawned-worker" }) as { ok: boolean };
113
-
114
- expect(result.ok).toBe(true);
115
- expect((await agent("spawned-worker")).enabled).toBe(false);
116
- });
117
- });
@@ -1,117 +0,0 @@
1
- /**
2
- * Track B feasibility proof: HiveDB's subscribe()/events() (wrapped by
3
- * watchCausalEvents()) actually works for hive's use case — forward
4
- * delivery, pattern filtering, and clean shutdown — and explicitly proves
5
- * the "no historical replay" constraint documented in causal-events.ts
6
- * (subscribe()/events() are a pure in-process pub/sub, not a durable read
7
- * of the log — see hiveBD's reactive.rs).
8
- */
9
-
10
- process.env.HIVE_DB_PATH = ":memory:";
11
-
12
- import { describe, test, expect, beforeEach, afterEach } from "bun:test";
13
- import { closeHiveDb, getHiveDb } from "../packages/core/src/storage/hivedb";
14
- import { ensureHiveDb } from "../packages/core/src/storage/bootstrap";
15
- import { resetBootId } from "../packages/core/src/storage/boot-id";
16
- import { watchCausalEvents } from "../packages/core/src/storage/causal-events";
17
- import type { CausalEvent } from "../packages/core/src/storage/causal-events";
18
-
19
- beforeEach(async () => {
20
- closeHiveDb();
21
- resetBootId();
22
- await ensureHiveDb();
23
- });
24
-
25
- afterEach(() => {
26
- closeHiveDb();
27
- });
28
-
29
- describe("causal-events: watchCausalEvents()", () => {
30
- test("delivers only events matching the pattern, forward, in order", async () => {
31
- const stream = await watchCausalEvents({ agentId: "agent-x" });
32
- const db = await getHiveDb();
33
-
34
- await db.append({
35
- agentId: "agent-x",
36
- streamId: "s1",
37
- kind: "IntentLogged",
38
- payload: JSON.stringify({ actor: "agent-x", intent: "one" }),
39
- });
40
- await db.append({
41
- agentId: "agent-other",
42
- streamId: "s2",
43
- kind: "IntentLogged",
44
- payload: JSON.stringify({ actor: "agent-other", intent: "should be filtered out" }),
45
- });
46
- await db.append({
47
- agentId: "agent-x",
48
- streamId: "s1",
49
- kind: "StateTransition",
50
- payload: JSON.stringify({ description: "three" }),
51
- });
52
-
53
- const received: CausalEvent[] = [];
54
- const iterator = stream[Symbol.asyncIterator]();
55
- received.push((await iterator.next()).value!);
56
- received.push((await iterator.next()).value!);
57
- stream.close();
58
-
59
- expect(received.length).toBe(2);
60
- expect(received.every((e) => e.agentId === "agent-x")).toBe(true);
61
- expect(received.map((e) => e.kindTag)).toEqual(["IntentLogged", "StateTransition"]);
62
- });
63
-
64
- test("has no historical replay: events appended before subscribing are never delivered", async () => {
65
- const db = await getHiveDb();
66
- await db.append({
67
- agentId: "agent-y",
68
- streamId: "s1",
69
- kind: "IntentLogged",
70
- payload: JSON.stringify({ actor: "agent-y", intent: "pre-existing, must not be delivered" }),
71
- });
72
-
73
- // Subscribe AFTER the append above.
74
- const stream = await watchCausalEvents({ agentId: "agent-y" });
75
- const iterator = stream[Symbol.asyncIterator]();
76
-
77
- const raceResult = await Promise.race([
78
- iterator.next().then((r) => ({ type: "event" as const, value: r.value })),
79
- new Promise<{ type: "timeout" }>((resolve) => setTimeout(() => resolve({ type: "timeout" }), 200)),
80
- ]);
81
- expect(raceResult.type).toBe("timeout");
82
-
83
- // Prove the stream itself still works for a genuinely new event —
84
- // distinguishes "no replay" from "the stream is just broken".
85
- await db.append({
86
- agentId: "agent-y",
87
- streamId: "s1",
88
- kind: "StateTransition",
89
- payload: JSON.stringify({ description: "post-subscribe, must be delivered" }),
90
- });
91
- const { value: event } = await iterator.next();
92
- expect(event?.kindTag).toBe("StateTransition");
93
-
94
- stream.close();
95
- });
96
-
97
- test("close() ends the for-await loop cleanly and doesn't block a subsequent closeHiveDb()", async () => {
98
- const stream = await watchCausalEvents({ agentId: "agent-z" });
99
- const db = await getHiveDb();
100
- await db.append({
101
- agentId: "agent-z",
102
- streamId: "s1",
103
- kind: "IntentLogged",
104
- payload: JSON.stringify({ actor: "agent-z", intent: "one" }),
105
- });
106
-
107
- const collected: CausalEvent[] = [];
108
- for await (const event of stream) {
109
- collected.push(event);
110
- stream.close();
111
- }
112
-
113
- expect(collected.length).toBe(1);
114
- // afterEach's closeHiveDb() must not hang/throw with this subscription
115
- // closed-but-not-drained — implicitly verified by the test completing.
116
- });
117
- });
@@ -1,105 +0,0 @@
1
- /**
2
- * Pure-function tests for compaction.ts's cut-point and transcript-rendering
3
- * helpers — extracted so they're testable without a real LLM call
4
- * (compactThread() itself calls callLLM and needs network).
5
- */
6
-
7
- import { describe, test, expect } from "bun:test";
8
- import { findCompactionCutIndex, renderTranscript } from "../packages/core/src/agent/compaction";
9
- import type { StoredMessage } from "../packages/core/src/agent/conversation-store";
10
-
11
- function msg(partial: Partial<StoredMessage> & Pick<StoredMessage, "id" | "role" | "content">): StoredMessage {
12
- return {
13
- thread_id: "t",
14
- channel: "webchat",
15
- source: "message",
16
- tool_calls_json: null,
17
- tool_call_id: null,
18
- reasoning_content: null,
19
- content_multimodal: null,
20
- token_count: 1,
21
- created_at: Date.now(),
22
- ...partial,
23
- };
24
- }
25
-
26
- describe("findCompactionCutIndex", () => {
27
- test("finds a clean user-turn boundary near the end of the history", () => {
28
- const rows: StoredMessage[] = [
29
- msg({ id: 1, role: "user", content: "a" }),
30
- msg({ id: 2, role: "assistant", content: "b" }),
31
- msg({ id: 3, role: "user", content: "c" }),
32
- msg({ id: 4, role: "assistant", content: "d" }),
33
- msg({ id: 5, role: "user", content: "e" }),
34
- msg({ id: 6, role: "assistant", content: "f" }),
35
- msg({ id: 7, role: "user", content: "g" }),
36
- ];
37
-
38
- // keepLastN=3 -> naive cut at index 4 (rows[4].role === "user") — already clean.
39
- expect(findCompactionCutIndex(rows, 3)).toBe(4);
40
- });
41
-
42
- test("walks back to the nearest user turn when the naive cut lands on assistant", () => {
43
- const rows: StoredMessage[] = [
44
- msg({ id: 1, role: "user", content: "a" }),
45
- msg({ id: 2, role: "user", content: "b" }), // internal event, still role:user
46
- msg({ id: 3, role: "assistant", content: "c" }),
47
- msg({ id: 4, role: "assistant", content: "d" }),
48
- ];
49
-
50
- // keepLastN=1 -> naive cut at index 3 (assistant) -> walk back to index 1 (user).
51
- expect(findCompactionCutIndex(rows, 1)).toBe(1);
52
- });
53
-
54
- test("a tail of internal events (role:user) does not block the cut point", () => {
55
- const rows: StoredMessage[] = [
56
- msg({ id: 1, role: "user", content: "a" }),
57
- msg({ id: 2, role: "assistant", content: "b" }),
58
- msg({ id: 3, role: "user", content: "El agente completó la tarea X.", source: "task_complete" }),
59
- msg({ id: 4, role: "user", content: "El agente completó la tarea Y.", source: "delegation_summary" }),
60
- ];
61
-
62
- // keepLastN=2 -> naive cut at index 2, which IS a user turn (the internal
63
- // event) — before source-based framing existed, this was persisted as
64
- // role:"system" and would have blocked the cut entirely.
65
- expect(findCompactionCutIndex(rows, 2)).toBe(2);
66
- });
67
-
68
- test("returns 0 when no clean boundary exists (caller should skip compaction)", () => {
69
- const rows: StoredMessage[] = [
70
- msg({ id: 1, role: "assistant", content: "a" }),
71
- msg({ id: 2, role: "assistant", content: "b" }),
72
- ];
73
-
74
- expect(findCompactionCutIndex(rows, 1)).toBe(0);
75
- });
76
- });
77
-
78
- describe("renderTranscript", () => {
79
- test("labels normal rows by role", () => {
80
- const rows: StoredMessage[] = [
81
- msg({ id: 1, role: "user", content: "hola" }),
82
- msg({ id: 2, role: "assistant", content: "¿en qué te ayudo?" }),
83
- ];
84
-
85
- const transcript = renderTranscript(rows);
86
- expect(transcript).toContain("[USER]: hola");
87
- expect(transcript).toContain("[ASSISTANT]: ¿en qué te ayudo?");
88
- });
89
-
90
- test("labels internal-source rows [EVENTO INTERNO], never [USER]", () => {
91
- const rows: StoredMessage[] = [
92
- msg({ id: 1, role: "user", content: "El agente completó la tarea X.", source: "task_complete" }),
93
- ];
94
-
95
- const transcript = renderTranscript(rows);
96
- expect(transcript).toContain("[EVENTO INTERNO]: El agente completó la tarea X.");
97
- expect(transcript).not.toContain("[USER]");
98
- });
99
-
100
- test("truncates each message to maxMsgChars", () => {
101
- const rows: StoredMessage[] = [msg({ id: 1, role: "user", content: "x".repeat(500) })];
102
- const transcript = renderTranscript(rows, 10);
103
- expect(transcript).toBe(`[USER]: ${"x".repeat(10)}`);
104
- });
105
- });