@johpaz/hive-sdk 0.1.6 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/CHANGELOG.md +441 -0
  2. package/README.md +21 -3
  3. package/package.json +27 -5
  4. package/packages/core/src/agent/acceptance-checks.ts +9 -9
  5. package/packages/core/src/agent/agent-catalog.ts +82 -25
  6. package/packages/core/src/agent/agent-loop.ts +115 -26
  7. package/packages/core/src/agent/capability-search.ts +2 -2
  8. package/packages/core/src/agent/catalog-selector.ts +4 -4
  9. package/packages/core/src/agent/compaction.ts +30 -10
  10. package/packages/core/src/agent/context-compiler.ts +63 -36
  11. package/packages/core/src/agent/conversation-store.ts +167 -9
  12. package/packages/core/src/agent/curator.ts +16 -7
  13. package/packages/core/src/agent/delegation-runtime.ts +5 -5
  14. package/packages/core/src/agent/goal-runner.ts +9 -9
  15. package/packages/core/src/agent/index.ts +1 -0
  16. package/packages/core/src/agent/llm-client.ts +98 -37
  17. package/packages/core/src/agent/llm-providers/anthropic.ts +4 -4
  18. package/packages/core/src/agent/llm-providers/deepseek.ts +1 -1
  19. package/packages/core/src/agent/llm-providers/gemini.ts +4 -4
  20. package/packages/core/src/agent/llm-providers/groq.ts +1 -1
  21. package/packages/core/src/agent/llm-providers/hiveagents.ts +3 -3
  22. package/packages/core/src/agent/llm-providers/interface.ts +2 -2
  23. package/packages/core/src/agent/llm-providers/kimi.ts +1 -1
  24. package/packages/core/src/agent/llm-providers/minimax.ts +1 -1
  25. package/packages/core/src/agent/llm-providers/mistral.ts +1 -1
  26. package/packages/core/src/agent/llm-providers/modelscope.ts +1 -1
  27. package/packages/core/src/agent/llm-providers/nvidia.ts +40 -1
  28. package/packages/core/src/agent/llm-providers/ollama.ts +4 -4
  29. package/packages/core/src/agent/llm-providers/openai-compat-base.ts +47 -7
  30. package/packages/core/src/agent/llm-providers/openai.ts +1 -1
  31. package/packages/core/src/agent/llm-providers/opencode-go.ts +1 -1
  32. package/packages/core/src/agent/llm-providers/openrouter.ts +1 -1
  33. package/packages/core/src/agent/llm-providers/qwen.ts +1 -1
  34. package/packages/core/src/agent/llm-providers/z-ai.ts +1 -1
  35. package/packages/core/src/agent/mcp-result-normalizer.ts +192 -0
  36. package/packages/core/src/agent/playbook-selector.ts +22 -7
  37. package/packages/core/src/agent/prompt-builder.ts +5 -5
  38. package/packages/core/src/agent/proof-packet.ts +5 -5
  39. package/packages/core/src/agent/providers/index.ts +39 -4
  40. package/packages/core/src/agent/realtime-providers/gemini-live.ts +238 -0
  41. package/packages/core/src/agent/realtime-providers/index.ts +29 -0
  42. package/packages/core/src/agent/realtime-providers/interface.ts +108 -0
  43. package/packages/core/src/agent/reflector.ts +38 -15
  44. package/packages/core/src/agent/run-store.ts +8 -8
  45. package/packages/core/src/agent/service.ts +10 -10
  46. package/packages/core/src/agent/skill-selector.ts +6 -6
  47. package/packages/core/src/agent/thread-id.ts +71 -0
  48. package/packages/core/src/agent/thread-store.ts +293 -0
  49. package/packages/core/src/agent/tool-selector.ts +9 -5
  50. package/packages/core/src/agent/tracer.ts +5 -5
  51. package/packages/core/src/api/createAgent.ts +67 -2
  52. package/packages/core/src/artifacts/index.ts +15 -0
  53. package/packages/core/src/artifacts/store.ts +161 -5
  54. package/packages/core/src/canvas/emitter.ts +2 -2
  55. package/packages/core/src/canvas/index.ts +9 -0
  56. package/packages/core/src/channels/telegram.ts +1 -1
  57. package/packages/core/src/channels/webchat.ts +1 -1
  58. package/packages/core/src/config/loader.ts +14 -5
  59. package/packages/core/src/ethics/EthicsGuard.ts +7 -1
  60. package/packages/core/src/events/agent-bus.ts +3 -3
  61. package/packages/core/src/events/channel-narration.ts +3 -3
  62. package/packages/core/src/events/event-bus.ts +1 -1
  63. package/packages/core/src/events/index.ts +18 -0
  64. package/packages/core/src/events/narration.ts +3 -3
  65. package/packages/core/src/events/tool-narration.ts +4 -0
  66. package/packages/core/src/gateway/channel-notify.ts +103 -6
  67. package/packages/core/src/gateway/delegation-groups.ts +4 -4
  68. package/packages/core/src/gateway/durable-queue.ts +18 -6
  69. package/packages/core/src/gateway/index.ts +3 -0
  70. package/packages/core/src/gateway/job-store.ts +11 -5
  71. package/packages/core/src/gateway/notification-inbox.ts +2 -2
  72. package/packages/core/src/gateway/server.ts +2 -2
  73. package/packages/core/src/harness/executors.ts +493 -0
  74. package/packages/core/src/harness/index.ts +12 -2
  75. package/packages/core/src/hooks/index.ts +203 -0
  76. package/packages/core/src/images/index.ts +161 -0
  77. package/packages/core/src/index.ts +1 -0
  78. package/packages/core/src/mcp/MCPClient.ts +3 -3
  79. package/packages/core/src/mcp/hot-reload.ts +5 -5
  80. package/packages/core/src/mcp/tool-sync.ts +5 -5
  81. package/packages/core/src/mcp/transports/index.ts +2 -2
  82. package/packages/core/src/mcp/transports/sse.ts +1 -1
  83. package/packages/core/src/models/index.ts +36 -0
  84. package/packages/core/src/multimodal/index.ts +2 -2
  85. package/packages/core/src/multimodal/vision-service.ts +51 -19
  86. package/packages/core/src/plugins/loader.ts +4 -1
  87. package/packages/core/src/resilience/circuit-breaker.ts +16 -5
  88. package/packages/core/src/resilience/index.ts +13 -0
  89. package/packages/core/src/resilience/retry.ts +1 -1
  90. package/packages/core/src/scheduler/CronScheduler.ts +54 -27
  91. package/packages/core/src/scheduler/cron/expression.ts +165 -0
  92. package/packages/core/src/scheduler/cron/index.ts +10 -0
  93. package/packages/core/src/scheduler/cron/job.ts +339 -0
  94. package/packages/core/src/scheduler/cron/next-run.ts +121 -0
  95. package/packages/core/src/scheduler/cron/zoned-time.ts +138 -0
  96. package/packages/core/src/scheduler/index.ts +21 -3
  97. package/packages/core/src/scheduler/integration.ts +25 -14
  98. package/packages/core/src/scheduler/types.ts +3 -18
  99. package/packages/core/src/services/agents.ts +268 -0
  100. package/packages/core/src/services/cron.ts +257 -0
  101. package/packages/core/src/services/endpoints.ts +289 -0
  102. package/packages/core/src/services/ethics.ts +107 -0
  103. package/packages/core/src/services/images.ts +212 -0
  104. package/packages/core/src/services/index.ts +112 -0
  105. package/packages/core/src/services/mcp.ts +201 -0
  106. package/packages/core/src/services/memory.ts +133 -0
  107. package/packages/core/src/services/models.ts +179 -0
  108. package/packages/core/src/services/providers.ts +152 -0
  109. package/packages/core/src/services/setup.ts +222 -0
  110. package/packages/core/src/services/skills.ts +241 -0
  111. package/packages/core/src/services/swarms.ts +307 -0
  112. package/packages/core/src/services/tools.ts +106 -0
  113. package/packages/core/src/sessions/index.ts +268 -0
  114. package/packages/core/src/sessions/resolve.ts +108 -0
  115. package/packages/core/src/skills/SkillLoader.ts +8 -1
  116. package/packages/core/src/skills/bundled/artifacts/artifact_reader/SKILL.md +105 -0
  117. package/packages/core/src/skills/bundled/cron_manager/SKILL.md +21 -11
  118. package/packages/core/src/skills/bundled/images/image_editor/SKILL.md +120 -0
  119. package/packages/core/src/skills/bundled/web/browser_automate/SKILL.md +12 -3
  120. package/packages/core/src/skills/bundled/web/browser_scrape/SKILL.md +22 -7
  121. package/packages/core/src/skills/bundled-data.generated.ts +110 -12
  122. package/packages/core/src/storage/bootstrap.ts +107 -12
  123. package/packages/core/src/storage/causal-events.ts +1 -1
  124. package/packages/core/src/storage/collections.ts +138 -2
  125. package/packages/core/src/storage/crypto.ts +35 -4
  126. package/packages/core/src/storage/hive.ts +1 -1
  127. package/packages/core/src/storage/hivedb.ts +10 -1
  128. package/packages/core/src/storage/index.ts +2 -1
  129. package/packages/core/src/storage/onboarding.ts +61 -45
  130. package/packages/core/src/storage/reconcile.ts +11 -6
  131. package/packages/core/src/storage/seed.ts +191 -23
  132. package/packages/core/src/storage/usage.ts +3 -3
  133. package/packages/core/src/swarm/AgentExecutor.ts +2 -2
  134. package/packages/core/src/swarm/Coordinator.ts +8 -8
  135. package/packages/core/src/swarm/EventBridge.ts +2 -2
  136. package/packages/core/src/swarm/RoleSwarm.ts +234 -0
  137. package/packages/core/src/swarm/TaskGraph.ts +2 -2
  138. package/packages/core/src/swarm/index.ts +7 -0
  139. package/packages/core/src/swarm/presets/HiveLearnPreset.ts +2 -2
  140. package/packages/core/src/swarm/presets/ResearchPreset.ts +2 -2
  141. package/packages/core/src/swarm/strategies/ParallelStrategy.ts +1 -1
  142. package/packages/core/src/swarm/strategies/PriorityStrategy.ts +3 -3
  143. package/packages/core/src/swarm/types.ts +3 -18
  144. package/packages/core/src/tool-runtime/embedded-worker.generated.ts +21 -0
  145. package/packages/core/src/tool-runtime/index.ts +129 -14
  146. package/packages/core/src/tools/ToolExecutor.ts +7 -3
  147. package/packages/core/src/tools/agents/index.ts +18 -60
  148. package/packages/core/src/tools/cli/index.ts +55 -0
  149. package/packages/core/src/tools/core/index.ts +52 -4
  150. package/packages/core/src/tools/cron/index.ts +8 -8
  151. package/packages/core/src/tools/images/index.ts +130 -0
  152. package/packages/core/src/tools/index.ts +14 -1
  153. package/packages/core/src/tools/office/office-escribir-xlsx.ts +2 -1
  154. package/packages/core/src/tools/office/office-leer-xlsx.ts +2 -1
  155. package/packages/core/src/tools/office/xlsx-loader.ts +19 -0
  156. package/packages/core/src/tools/web/artifact-inspect.ts +2 -2
  157. package/packages/core/src/tools/web/artifact-read.ts +162 -0
  158. package/packages/core/src/tools/web/browser-backend.ts +141 -44
  159. package/packages/core/src/tools/web/browser-click.ts +2 -2
  160. package/packages/core/src/tools/web/browser-extract.ts +2 -2
  161. package/packages/core/src/tools/web/browser-navigate.ts +2 -2
  162. package/packages/core/src/tools/web/browser-screenshot.ts +12 -5
  163. package/packages/core/src/tools/web/browser-script.ts +2 -2
  164. package/packages/core/src/tools/web/browser-service.ts +63 -384
  165. package/packages/core/src/tools/web/browser-session.ts +125 -0
  166. package/packages/core/src/tools/web/browser-type.ts +2 -2
  167. package/packages/core/src/tools/web/browser-wait.ts +2 -2
  168. package/packages/core/src/tools/web/computer-use.ts +553 -0
  169. package/packages/core/src/tools/web/index.ts +8 -1
  170. package/packages/core/src/tools/web/webview-backend.ts +460 -21
  171. package/packages/core/src/utils/index.ts +1 -0
  172. package/packages/core/src/utils/logger.ts +12 -4
  173. package/packages/core/src/utils/redact-binary.ts +17 -0
  174. package/packages/core/src/utils/toon.ts +1 -1
  175. package/packages/core/src/voice/index.ts +6 -6
  176. package/bun.lock +0 -859
  177. package/bunfig.toml +0 -9
  178. package/docs/API-AGENTS.md +0 -367
  179. package/docs/API-CONTEXT-COMPILER.md +0 -249
  180. package/docs/API-DAG-SCHEDULER.md +0 -273
  181. package/docs/API-TOOLS-SKILLS-CHANNELS.md +0 -446
  182. package/docs/API-WORKERS-EVENTS.md +0 -299
  183. package/docs/HIVE-HARNESS.md +0 -113
  184. package/docs/INDEX.md +0 -190
  185. package/docs/TEMPLATE-HIVE-APP.md +0 -360
  186. package/packages/cli/package.json +0 -17
  187. package/packages/cli/src/commands/create-app.test.ts +0 -180
  188. package/packages/core/package.json +0 -70
  189. package/packages/core/src/api/createAgent.test.ts +0 -160
  190. package/packages/core/src/canvas/canvas.test.ts +0 -36
  191. package/packages/core/src/channels/channels.test.ts +0 -18
  192. package/packages/core/src/ethics/EthicsGuard.test.ts +0 -108
  193. package/packages/core/src/gateway/gateway.test.ts +0 -38
  194. package/packages/core/src/memory/Scratchpad.test.ts +0 -68
  195. package/packages/core/src/scheduler/scheduler.test.ts +0 -15
  196. package/packages/core/src/skills/skills.test.ts +0 -62
  197. package/packages/core/src/swarm/swarm.test.ts +0 -24
  198. package/packages/core/src/tool-runtime/tool-runtime.test.ts +0 -99
  199. package/packages/core/src/tools/ToolRegistry.test.ts +0 -98
  200. package/packages/core/src/tools/api/api-request.test.ts +0 -164
  201. package/packages/core/src/tools/web/browser-service.test.ts +0 -83
  202. package/packages/core/src/workers/workers.test.ts +0 -41
  203. package/scripts/bump-version.ts +0 -248
  204. package/scripts/generate-skill-bundle.ts +0 -108
  205. package/test/acceptance-checks.test.ts +0 -403
  206. package/test/agent-loop-terminal-synthesis.test.ts +0 -32
  207. package/test/browser-backend.test.ts +0 -308
  208. package/test/catalog-agents-stay-enabled.test.ts +0 -117
  209. package/test/causal-events.test.ts +0 -117
  210. package/test/compaction.test.ts +0 -105
  211. package/test/context-compiler.test.ts +0 -269
  212. package/test/curator.test.ts +0 -130
  213. package/test/durable-queue.test.ts +0 -114
  214. package/test/harness-barrel.test.ts +0 -64
  215. package/test/hive-helpers.test.ts +0 -130
  216. package/test/hivedb-search.test.ts +0 -189
  217. package/test/internal-turns.test.ts +0 -166
  218. package/test/job-idempotency.test.ts +0 -68
  219. package/test/job-retry-backoff.test.ts +0 -184
  220. package/test/job-store.test.ts +0 -381
  221. package/test/llm-retry.test.ts +0 -97
  222. package/test/memory-perf.test.ts +0 -774
  223. package/test/minimal-loadout.test.ts +0 -78
  224. package/test/model-catalog.test.ts +0 -105
  225. package/test/preload.ts +0 -12
  226. package/test/reflector.test.ts +0 -320
  227. package/test/retention-cap.test.ts +0 -91
  228. package/test/retired-capabilities-pruned.test.ts +0 -192
  229. package/test/run-store.test.ts +0 -355
  230. package/test/scratchpad.test.ts +0 -74
  231. package/test/secrets-durability.test.ts +0 -119
  232. package/test/seed-model-reseed.test.ts +0 -155
  233. package/test/setup-agent-seed.test.ts +0 -264
  234. package/test/tool-inventory.test.ts +0 -65
  235. package/test/tool-runtime.test.ts +0 -258
  236. package/test/tool-selector-runtime-tools.test.ts +0 -117
  237. package/test/toon.test.ts +0 -429
  238. package/tsconfig.json +0 -42
@@ -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
- });