@johpaz/hive-sdk 0.1.5 → 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 (182) hide show
  1. package/CHANGELOG.md +167 -0
  2. package/README.md +11 -1
  3. package/package.json +26 -10
  4. package/packages/core/src/agent/acceptance-checks.ts +16 -10
  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 +15 -1
  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 +226 -0
  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 +85 -366
  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 +851 -0
  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 -833
  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/agent-loop-terminal-synthesis.test.ts +0 -32
  153. package/test/catalog-agents-stay-enabled.test.ts +0 -117
  154. package/test/causal-events.test.ts +0 -117
  155. package/test/compaction.test.ts +0 -105
  156. package/test/context-compiler.test.ts +0 -269
  157. package/test/curator.test.ts +0 -130
  158. package/test/durable-queue.test.ts +0 -114
  159. package/test/harness-barrel.test.ts +0 -64
  160. package/test/hive-helpers.test.ts +0 -130
  161. package/test/hivedb-search.test.ts +0 -189
  162. package/test/internal-turns.test.ts +0 -166
  163. package/test/job-idempotency.test.ts +0 -68
  164. package/test/job-retry-backoff.test.ts +0 -184
  165. package/test/job-store.test.ts +0 -381
  166. package/test/llm-retry.test.ts +0 -97
  167. package/test/memory-perf.test.ts +0 -774
  168. package/test/minimal-loadout.test.ts +0 -78
  169. package/test/model-catalog.test.ts +0 -105
  170. package/test/preload.ts +0 -12
  171. package/test/reflector.test.ts +0 -320
  172. package/test/retention-cap.test.ts +0 -91
  173. package/test/retired-capabilities-pruned.test.ts +0 -192
  174. package/test/run-store.test.ts +0 -355
  175. package/test/scratchpad.test.ts +0 -74
  176. package/test/secrets-durability.test.ts +0 -119
  177. package/test/seed-model-reseed.test.ts +0 -155
  178. package/test/setup-agent-seed.test.ts +0 -264
  179. package/test/tool-inventory.test.ts +0 -65
  180. package/test/tool-runtime.test.ts +0 -258
  181. package/test/toon.test.ts +0 -429
  182. package/tsconfig.json +0 -42
@@ -1,78 +0,0 @@
1
- /**
2
- * The coordinator starts every turn with MINIMAL_TOOLS and the skills derived
3
- * from it. Everything else is discovered, and discovery injects a tool together
4
- * with the skills that document it (agent-loop.ts).
5
- *
6
- * So an always-injected skill that teaches a tool outside the loadout is pure
7
- * cost: the agent reads instructions for something it cannot call, and gets the
8
- * same skill again later when it discovers the tool. That is exactly what had
9
- * drifted — 3 of the 4 hand-listed "minimal" skills were in that state — hence
10
- * the derivation in minimal-loadout.ts and this test.
11
- *
12
- * Uses HIVE_DB_PATH=":memory:".
13
- */
14
-
15
- process.env.HIVE_DB_PATH = ":memory:";
16
-
17
- import { describe, test, expect, beforeAll, afterAll } from "bun:test";
18
- import { closeHiveDb } from "../packages/core/src/storage/hivedb";
19
- import { ensureHiveDb } from "../packages/core/src/storage/bootstrap";
20
- import { createAllTools } from "../packages/core/src/tools/index";
21
- import { getMinimalSkills } from "../packages/core/src/agent/skill-selector";
22
- import {
23
- MINIMAL_TOOLS,
24
- isMinimalSkill,
25
- parseSkillTools,
26
- } from "../packages/core/src/agent/minimal-loadout";
27
-
28
- beforeAll(async () => {
29
- closeHiveDb();
30
- await ensureHiveDb();
31
- });
32
-
33
- afterAll(() => {
34
- closeHiveDb();
35
- });
36
-
37
- describe("minimal loadout", () => {
38
- test("every minimal tool is a real executor", () => {
39
- const executors = new Set(createAllTools({ tools: {} } as never).map((t) => t.name));
40
- const missing = [...MINIMAL_TOOLS].filter((name) => !executors.has(name)).sort();
41
- expect(missing).toEqual([]);
42
- });
43
-
44
- test("no minimal skill teaches a tool outside the loadout", async () => {
45
- const offenders = (await getMinimalSkills())
46
- .map((skill) => ({
47
- name: skill.name,
48
- outside: parseSkillTools(skill.tools).filter((t) => !MINIMAL_TOOLS.has(t)),
49
- }))
50
- .filter((s) => s.outside.length > 0);
51
- expect(offenders).toEqual([]);
52
- });
53
-
54
- test("the derivation actually selects something", async () => {
55
- const names = (await getMinimalSkills()).map((s) => s.name);
56
- // capability_discovery documents search_knowledge and nothing else, so it
57
- // qualifies by construction. If this ever empties out, the loadout and the
58
- // bundled skills have drifted apart again.
59
- expect(names).toContain("capability_discovery");
60
- });
61
-
62
- test("the rule rejects skills that reach outside the loadout", () => {
63
- expect(isMinimalSkill("search_knowledge")).toBe(true);
64
- expect(isMinimalSkill("task_delegate, agent_find, task_status")).toBe(true);
65
- expect(isMinimalSkill("search_knowledge, memory_write")).toBe(false);
66
- expect(isMinimalSkill("a2ui_create_surface")).toBe(false);
67
- expect(isMinimalSkill("")).toBe(false); // no anchor to the loadout
68
- });
69
-
70
- test("skills left out still reach the agent through discovery", async () => {
71
- // They are not lost, just not always-on: agent-loop injects a skill when
72
- // search_knowledge surfaces any tool the skill declares.
73
- const minimal = new Set((await getMinimalSkills()).map((s) => s.name));
74
- expect(minimal.has("memory_manager")).toBe(false);
75
- expect(minimal.has("a2ui_dashboard")).toBe(false);
76
- expect(minimal.has("task_orchestrator")).toBe(false);
77
- });
78
- });
@@ -1,105 +0,0 @@
1
- // Importar el seed arrastra los módulos de storage, que resuelven la ruta de la
2
- // BD al cargarse. Sin esto el archivo podía dejar apuntando la BD real.
3
- process.env.HIVE_DB_PATH = ":memory:";
4
-
5
- import { describe, expect, test } from "bun:test";
6
- import { SEED_DATA } from "../packages/core/src/storage/seed";
7
- import { catalogModelKey, wireModelId, isResellerProvider } from "../packages/core/src/storage/model-id";
8
-
9
- /**
10
- * Guardas del catálogo de modelos.
11
- *
12
- * La colección `models` se indexa por una sola clave, así que dos entradas que
13
- * colapsen a la misma la pisan entre sí — pasó de verdad con `z-ai/glm-5.2`,
14
- * sembrado a la vez en NVIDIA NIM y en OpenRouter, donde una fila desaparecía en
15
- * silencio. Y un modelo LLM sin precio se reporta como $0 en el dashboard, que es
16
- * indistinguible de un endpoint realmente gratuito.
17
- */
18
- describe("catálogo de modelos (SEED_DATA.models)", () => {
19
- test("ninguna entrada colapsa a la misma clave de BD", () => {
20
- const seen = new Map<string, string>();
21
- const collisions: string[] = [];
22
-
23
- for (const model of SEED_DATA.models) {
24
- const key = catalogModelKey(model.providerId, model.id);
25
- const previous = seen.get(key);
26
- if (previous) {
27
- collisions.push(`${key} ← ${previous} y ${model.providerId}/${model.id}`);
28
- }
29
- seen.set(key, `${model.providerId}/${model.id}`);
30
- }
31
-
32
- expect(collisions).toEqual([]);
33
- });
34
-
35
- test("todo modelo llm trae precio de entrada y de salida", () => {
36
- const unpriced = SEED_DATA.models
37
- .filter((m) => m.modelType === "llm")
38
- .filter((m) => m.inputPer1M === undefined || m.outputPer1M === undefined)
39
- .map((m) => `${m.providerId}/${m.id}`);
40
-
41
- expect(unpriced).toEqual([]);
42
- });
43
-
44
- test("los precios son números no negativos", () => {
45
- const invalid = SEED_DATA.models
46
- .filter((m) => m.modelType === "llm")
47
- .filter((m) => !(typeof m.inputPer1M === "number" && m.inputPer1M >= 0)
48
- || !(typeof m.outputPer1M === "number" && m.outputPer1M >= 0))
49
- .map((m) => `${m.providerId}/${m.id}`);
50
-
51
- expect(invalid).toEqual([]);
52
- });
53
-
54
- test("todo modelo apunta a un provider sembrado", () => {
55
- const providerIds = new Set(SEED_DATA.providers.map((p) => p.id));
56
- const orphans = SEED_DATA.models
57
- .filter((m) => !providerIds.has(m.providerId))
58
- .map((m) => `${m.providerId}/${m.id}`);
59
-
60
- expect(orphans).toEqual([]);
61
- });
62
- });
63
-
64
- describe("catalogModelKey / wireModelId", () => {
65
- test("los revendedores prefijan y los propietarios no", () => {
66
- expect(catalogModelKey("nvidia", "z-ai/glm-5.2")).toBe("nvidia/z-ai/glm-5.2");
67
- expect(catalogModelKey("openrouter", "z-ai/glm-5.2")).toBe("openrouter/z-ai/glm-5.2");
68
- expect(catalogModelKey("anthropic", "claude-opus-5")).toBe("claude-opus-5");
69
- expect(catalogModelKey("z-ai", "glm-5.2")).toBe("glm-5.2");
70
- // ModelScope revende modelos de terceros (Qwen/, deepseek-ai/, zai-org/),
71
- // que se solapan con los de NVIDIA y OpenRouter.
72
- expect(catalogModelKey("modelscope", "Qwen/Qwen3.5-397B-A17B")).toBe("modelscope/Qwen/Qwen3.5-397B-A17B");
73
- expect(wireModelId("modelscope", "modelscope/Qwen-Ambassador/Qwen3.8-Max")).toBe("Qwen-Ambassador/Qwen3.8-Max");
74
- });
75
-
76
- test("`id` en el catálogo es siempre el nombre del cable, no la clave ya prefijada", () => {
77
- // No es idempotente a propósito: aplicarlo dos veces prefija dos veces. Un
78
- // atajo `startsWith` para evitarlo rompía los modelos cuyo namespace propio
79
- // coincide con el id del provider (ver el caso de abajo).
80
- expect(catalogModelKey("opencode-go", "glm-5.1")).toBe("opencode-go/glm-5.1");
81
- for (const model of SEED_DATA.models) {
82
- expect(model.id.startsWith(`${model.providerId}/${model.providerId}/`)).toBe(false);
83
- }
84
- });
85
-
86
- test("un modelo propio del revendedor conserva su namespace tras el strip", () => {
87
- // El nombre de cable de NVIDIA para su Nemotron ya empieza con "nvidia/",
88
- // así que la clave lleva el prefijo dos veces a propósito.
89
- const key = catalogModelKey("nvidia", "nvidia/nemotron-3-super-120b-a12b");
90
- expect(key).toBe("nvidia/nvidia/nemotron-3-super-120b-a12b");
91
- expect(wireModelId("nvidia", key)).toBe("nvidia/nemotron-3-super-120b-a12b");
92
- });
93
-
94
- test("wireModelId revierte catalogModelKey para todo el catálogo", () => {
95
- for (const model of SEED_DATA.models) {
96
- const key = catalogModelKey(model.providerId, model.id);
97
- expect(wireModelId(model.providerId, key)).toBe(model.id);
98
- }
99
- });
100
-
101
- test("wireModelId no toca a los providers propietarios", () => {
102
- expect(isResellerProvider("anthropic")).toBe(false);
103
- expect(wireModelId("anthropic", "claude-opus-5")).toBe("claude-opus-5");
104
- });
105
- });
package/test/preload.ts DELETED
@@ -1,12 +0,0 @@
1
- /**
2
- * Preload de la suite de tests.
3
- *
4
- * Cada archivo de test pone `process.env.HIVE_DB_PATH = ":memory:"` en su primera
5
- * línea, pero los `import` de ESM se evalúan ANTES que cualquier sentencia del
6
- * módulo: si algo de la cadena de imports resuelve la ruta de la BD al cargarse,
7
- * la asignación llega tarde y la suite termina abriendo la base real del usuario
8
- * (~/.hive/data/hivedb), escribiéndole contadores y filtrando estado entre
9
- * archivos. Un preload corre antes que todo, así que acá sí gana.
10
- */
11
-
12
- process.env.HIVE_DB_PATH ||= ":memory:";
@@ -1,320 +0,0 @@
1
- /**
2
- * Reflector tests: local trace-batch analysis, plus the G9 causalLog path
3
- * where per-tool insights (failure count, avg latency) come from HiveDB's
4
- * whole-history toolStats() instead of just the current batch of traces.
5
- */
6
-
7
- process.env.HIVE_DB_PATH = ":memory:";
8
-
9
- import { describe, test, expect, beforeEach, afterEach } from "bun:test";
10
- import { closeHiveDb, getHiveDb } from "../packages/core/src/storage/hivedb";
11
- import { ensureHiveDb } from "../packages/core/src/storage/bootstrap";
12
- import { resetBootId } from "../packages/core/src/storage/boot-id";
13
- import { col, nextId } from "../packages/core/src/storage/hive";
14
- import type { TraceDoc, ReflectionDoc } from "../packages/core/src/storage/collections";
15
- import { runReflector } from "../packages/core/src/agent/reflector";
16
-
17
- async function seedTrace(overrides: Partial<TraceDoc>) {
18
- const tracesCol = await col<TraceDoc>("traces");
19
- const id = await nextId("traces");
20
- await tracesCol.put(id, {
21
- id,
22
- thread_id: "thread-1",
23
- agent_id: "agent-1",
24
- agent_name: "Agent 1",
25
- tool_used: null,
26
- input_summary: "input",
27
- output_summary: "output",
28
- success: true,
29
- error_message: null,
30
- duration_ms: null,
31
- tokens_used: null,
32
- created_at: Date.now(),
33
- ...overrides,
34
- });
35
- return id;
36
- }
37
-
38
- beforeEach(async () => {
39
- closeHiveDb();
40
- resetBootId();
41
- await ensureHiveDb();
42
- });
43
-
44
- afterEach(() => {
45
- closeHiveDb();
46
- delete process.env.HIVE_CAUSAL_LOG;
47
- });
48
-
49
- describe("reflector: local trace-batch analysis (causalLog disabled)", () => {
50
- test("failure_pattern insight uses the batch-local failure count", async () => {
51
- for (let i = 0; i < 4; i++) {
52
- await seedTrace({ tool_used: "flaky_tool", success: false });
53
- }
54
- for (let i = 0; i < 6; i++) {
55
- await seedTrace({ tool_used: "ok_tool", success: true });
56
- }
57
-
58
- await runReflector();
59
-
60
- const reflectionsCol = await col<ReflectionDoc>("reflections");
61
- const all = await reflectionsCol.scan({});
62
- const failureInsight = all.find((e) => e.doc.insight_type === "failure_pattern");
63
- expect(failureInsight).toBeDefined();
64
- expect(failureInsight!.doc.description).toContain("flaky_tool");
65
- expect(failureInsight!.doc.description).toContain("4 times");
66
- });
67
- });
68
-
69
- describe("reflector: G9 causal event log (causalLog enabled)", () => {
70
- test("failure_pattern insight uses toolStats().errors from the whole event log, not just this batch", async () => {
71
- process.env.HIVE_CAUSAL_LOG = "true";
72
- const db = await getHiveDb();
73
-
74
- // 6 historical ToolCall errors for "flaky_tool" — more than the 4 failures
75
- // present in this batch of traces.
76
- for (let i = 0; i < 6; i++) {
77
- await db.append({
78
- agentId: "agent-1",
79
- streamId: "stream-history",
80
- kind: "ToolCall",
81
- payload: JSON.stringify({ tool: "flaky_tool", outcome: { Err: `boom-${i}` } }),
82
- });
83
- }
84
-
85
- for (let i = 0; i < 4; i++) {
86
- await seedTrace({ tool_used: "flaky_tool", success: false });
87
- }
88
- for (let i = 0; i < 6; i++) {
89
- await seedTrace({ tool_used: "ok_tool", success: true });
90
- }
91
-
92
- await runReflector();
93
-
94
- const reflectionsCol = await col<ReflectionDoc>("reflections");
95
- const all = await reflectionsCol.scan({});
96
- const failureInsight = all.find((e) => e.doc.insight_type === "failure_pattern");
97
- expect(failureInsight).toBeDefined();
98
- // 6 from the event log's full history, not 4 from this batch alone
99
- expect(failureInsight!.doc.description).toContain("6 times");
100
- });
101
-
102
- test("optimization (slow tool) insight uses toolStats().totalLatencyMs/invocations", async () => {
103
- process.env.HIVE_CAUSAL_LOG = "true";
104
- const db = await getHiveDb();
105
-
106
- // 3 historical ToolCall events for "slow_tool" with a high average latency
107
- // (12000ms), distinct from the batch's own (lower) durations.
108
- for (let i = 0; i < 3; i++) {
109
- await db.append({
110
- agentId: "agent-1",
111
- streamId: "stream-history",
112
- kind: "ToolCall",
113
- payload: JSON.stringify({ tool: "slow_tool", latency_ms: 12000, outcome: "Ok" }),
114
- });
115
- }
116
-
117
- for (let i = 0; i < 3; i++) {
118
- await seedTrace({ tool_used: "slow_tool", success: true, duration_ms: 6000 });
119
- }
120
- for (let i = 0; i < 7; i++) {
121
- await seedTrace({ tool_used: "ok_tool", success: true });
122
- }
123
-
124
- await runReflector();
125
-
126
- const reflectionsCol = await col<ReflectionDoc>("reflections");
127
- const all = await reflectionsCol.scan({});
128
- const slowInsight = all.find(
129
- (e) => e.doc.insight_type === "optimization" && e.doc.description.includes("slow_tool")
130
- );
131
- expect(slowInsight).toBeDefined();
132
- // avg from toolStats (12000ms), not from the batch's own durations (6000ms)
133
- expect(slowInsight!.doc.description).toContain("12000ms");
134
- });
135
- });
136
-
137
- describe("reflector: G9 causal-thread evaluation (evaluateHarness)", () => {
138
- test("a stream with a repeated tool-call failure produces root_cause and learning_proposal insights", async () => {
139
- process.env.HIVE_CAUSAL_LOG = "true";
140
- const db = await getHiveDb();
141
-
142
- // Build a realistic causal thread: Intent -> Decision -> 3x ToolCall Err
143
- // (same tool + message), mirroring exactly what agent-loop.ts emits and
144
- // matching the 3-repetition threshold hiveBD's errorLoop detector requires.
145
- const streamId = "stream-rootcause-1";
146
- const intentSeq = await db.append({
147
- agentId: "agent-1",
148
- streamId,
149
- kind: "IntentLogged",
150
- payload: JSON.stringify({ actor: "agent-1", intent: "deploy the checkout service" }),
151
- });
152
- const decisionSeq = await db.append({
153
- agentId: "agent-1",
154
- streamId,
155
- kind: "StateTransition",
156
- payload: JSON.stringify({ description: "Calling flaky_tool" }),
157
- causation: intentSeq,
158
- });
159
- for (let i = 0; i < 3; i++) {
160
- await db.append({
161
- agentId: "agent-1",
162
- streamId,
163
- kind: "ToolCall",
164
- payload: JSON.stringify({ tool: "flaky_tool", outcome: { Err: "boom" } }),
165
- causation: decisionSeq,
166
- });
167
- }
168
-
169
- await seedTrace({
170
- tool_used: "flaky_tool",
171
- success: false,
172
- causal_stream_id: streamId,
173
- input_summary: "deploy the checkout service",
174
- });
175
- for (let i = 0; i < 9; i++) {
176
- await seedTrace({ tool_used: "ok_tool", success: true });
177
- }
178
-
179
- await runReflector();
180
-
181
- const reflectionsCol = await col<ReflectionDoc>("reflections");
182
- const all = await reflectionsCol.scan({});
183
-
184
- const rootCause = all.find((e) => e.doc.insight_type === "root_cause");
185
- expect(rootCause).toBeDefined();
186
- expect(rootCause!.doc.description).toContain("flaky_tool");
187
-
188
- const proposal = all.find((e) => e.doc.insight_type === "learning_proposal");
189
- expect(proposal).toBeDefined();
190
- expect(proposal!.doc.description.toLowerCase()).toContain("flaky_tool");
191
- expect(proposal!.doc.confidence).toBeGreaterThanOrEqual(0.5);
192
-
193
- // The mapped playbook categories from curator.ts (root_cause -> error_avoidance,
194
- // learning_proposal -> response_quality) confirm the insights flowed all the
195
- // way through to the playbook, not just into the reflections collection.
196
- const playbookCol = await col<import("../packages/core/src/storage/collections").PlaybookDoc>("playbook");
197
- const playbook = await playbookCol.scan({});
198
- expect(playbook.some((e) => e.doc.source_reflection_id === rootCause!.id && e.doc.category === "error_avoidance")).toBe(true);
199
- expect(playbook.some((e) => e.doc.source_reflection_id === proposal!.id && e.doc.category === "response_quality")).toBe(true);
200
- });
201
-
202
- test("a stream with no failures produces no root_cause or learning_proposal insights", async () => {
203
- process.env.HIVE_CAUSAL_LOG = "true";
204
- const db = await getHiveDb();
205
-
206
- const streamId = "stream-clean-1";
207
- const intentSeq = await db.append({
208
- agentId: "agent-1",
209
- streamId,
210
- kind: "IntentLogged",
211
- payload: JSON.stringify({ actor: "agent-1", intent: "read a file" }),
212
- });
213
- await db.append({
214
- agentId: "agent-1",
215
- streamId,
216
- kind: "StateTransition",
217
- payload: JSON.stringify({ description: "Calling read_file" }),
218
- causation: intentSeq,
219
- });
220
-
221
- await seedTrace({ tool_used: "read_file", success: true, causal_stream_id: streamId });
222
- for (let i = 0; i < 9; i++) {
223
- await seedTrace({ tool_used: "ok_tool", success: true });
224
- }
225
-
226
- await runReflector();
227
-
228
- const reflectionsCol = await col<ReflectionDoc>("reflections");
229
- const all = await reflectionsCol.scan({});
230
- expect(all.some((e) => e.doc.insight_type === "root_cause")).toBe(false);
231
- expect(all.some((e) => e.doc.insight_type === "learning_proposal")).toBe(false);
232
- });
233
-
234
- test("a single tool-call failure produces exactly one root_cause insight, not two", async () => {
235
- // Regression guard: harness.rs's find_root_cause() emits both
236
- // evaluation.rootCause AND an equivalent finding{kind:"rootCause"} from the
237
- // same resolution — analyzeCausalThreads() must only turn that into one
238
- // insight, not two, per failure.
239
- process.env.HIVE_CAUSAL_LOG = "true";
240
- const db = await getHiveDb();
241
-
242
- const streamId = "stream-single-failure";
243
- const intentSeq = await db.append({
244
- agentId: "agent-1",
245
- streamId,
246
- kind: "IntentLogged",
247
- payload: JSON.stringify({ actor: "agent-1", intent: "deploy the checkout service" }),
248
- });
249
- const decisionSeq = await db.append({
250
- agentId: "agent-1",
251
- streamId,
252
- kind: "StateTransition",
253
- payload: JSON.stringify({ description: "Calling deploy_service" }),
254
- causation: intentSeq,
255
- });
256
- await db.append({
257
- agentId: "agent-1",
258
- streamId,
259
- kind: "ToolCall",
260
- payload: JSON.stringify({ tool: "deploy_service", outcome: { Err: "boom" } }),
261
- causation: decisionSeq,
262
- });
263
-
264
- await seedTrace({ tool_used: "deploy_service", success: false, causal_stream_id: streamId });
265
- for (let i = 0; i < 9; i++) {
266
- await seedTrace({ tool_used: "ok_tool", success: true });
267
- }
268
-
269
- await runReflector();
270
-
271
- const reflectionsCol = await col<ReflectionDoc>("reflections");
272
- const all = await reflectionsCol.scan({});
273
- const rootCauseInsights = all.filter((e) => e.doc.insight_type === "root_cause");
274
- expect(rootCauseInsights.length).toBe(1);
275
- });
276
-
277
- test("the same underlying root cause across two different streams reinforces one playbook rule instead of duplicating it", async () => {
278
- process.env.HIVE_CAUSAL_LOG = "true";
279
- const db = await getHiveDb();
280
-
281
- async function seedFailingStream(streamId: string) {
282
- const intentSeq = await db.append({
283
- agentId: "agent-1",
284
- streamId,
285
- kind: "IntentLogged",
286
- payload: JSON.stringify({ actor: "agent-1", intent: "deploy the checkout service" }),
287
- });
288
- const decisionSeq = await db.append({
289
- agentId: "agent-1",
290
- streamId,
291
- kind: "StateTransition",
292
- payload: JSON.stringify({ description: "Calling deploy_service" }),
293
- causation: intentSeq,
294
- });
295
- await db.append({
296
- agentId: "agent-1",
297
- streamId,
298
- kind: "ToolCall",
299
- payload: JSON.stringify({ tool: "deploy_service", outcome: { Err: "boom" } }),
300
- causation: decisionSeq,
301
- });
302
- await seedTrace({ tool_used: "deploy_service", success: false, causal_stream_id: streamId });
303
- }
304
-
305
- await seedFailingStream("stream-reinforce-a");
306
- await seedFailingStream("stream-reinforce-b");
307
- for (let i = 0; i < 8; i++) {
308
- await seedTrace({ tool_used: "ok_tool", success: true });
309
- }
310
-
311
- await runReflector();
312
-
313
- const playbookCol = await col<import("../packages/core/src/storage/collections").PlaybookDoc>("playbook");
314
- const rootCauseRules = (await playbookCol.scan({})).filter((e) =>
315
- e.doc.rule.includes('decision "Calling deploy_service"')
316
- );
317
- expect(rootCauseRules.length).toBe(1);
318
- expect(rootCauseRules[0].doc.helpful_count).toBe(2);
319
- });
320
- });
@@ -1,91 +0,0 @@
1
- import { describe, it, expect, beforeEach } from "bun:test";
2
- process.env.HIVE_DB_PATH = ":memory:";
3
-
4
- import { closeHiveDb } from "../packages/core/src/storage/hivedb";
5
- import { ensureHiveDb } from "../packages/core/src/storage/bootstrap";
6
- import { col } from "../packages/core/src/storage/hive";
7
- import { reconcileOnBoot } from "../packages/core/src/storage/reconcile";
8
-
9
- beforeEach(async () => {
10
- await closeHiveDb();
11
- await ensureHiveDb();
12
- });
13
-
14
- describe("retention cap in reconcileOnBoot", () => {
15
- it("caps agentRuns to 500 per thread", async () => {
16
- const agentRunsCol = await col<any>("agentRuns");
17
- // Insert 510 runs for the same thread
18
- for (let i = 0; i < 510; i++) {
19
- await agentRunsCol.put(`run-${i}`, {
20
- agent_id: "agent-1",
21
- thread_id: "thread-1",
22
- kind: "chat",
23
- status: "completed",
24
- created_at: i, // ascending so higher = newer
25
- lease_expires_at: Date.now() + 60000,
26
- });
27
- }
28
- const beforeCount = (await agentRunsCol.scan({})).length;
29
- expect(beforeCount).toBe(510);
30
-
31
- await reconcileOnBoot("test-boot");
32
-
33
- const afterCount = (await agentRunsCol.scan({})).length;
34
- expect(afterCount).toBe(500);
35
-
36
- // Verify we kept the newest (highest created_at)
37
- const remaining = await agentRunsCol.scan({});
38
- const ids = remaining.map((e: any) => parseInt(e.id.split("-")[1]));
39
- expect(Math.min(...ids)).toBe(10); // first 10 pruned, 10..509 kept
40
- });
41
-
42
- it("caps jobs to 500 per run_id", async () => {
43
- const jobsCol = await col<any>("jobQueue");
44
- for (let i = 0; i < 520; i++) {
45
- await jobsCol.put(`job-${i}`, {
46
- lane: "default",
47
- type: "chat_turn",
48
- status: "completed",
49
- run_id: "run-1",
50
- attempts: 1,
51
- max_attempts: 3,
52
- lease_expires_at: null,
53
- created_at: i,
54
- payload_json: "{}",
55
- priority: 0,
56
- not_before: 0,
57
- boot_id: null,
58
- result_json: null,
59
- error: null,
60
- started_at: null,
61
- finished_at: null,
62
- });
63
- }
64
- const beforeCount = (await jobsCol.scan({})).length;
65
- expect(beforeCount).toBe(520);
66
-
67
- await reconcileOnBoot("test-boot");
68
-
69
- const afterCount = (await jobsCol.scan({})).length;
70
- expect(afterCount).toBe(500);
71
- });
72
-
73
- it("does not prune when under the cap", async () => {
74
- const agentRunsCol = await col<any>("agentRuns");
75
- for (let i = 0; i < 100; i++) {
76
- await agentRunsCol.put(`run-${i}`, {
77
- agent_id: "agent-1",
78
- thread_id: "thread-1",
79
- kind: "chat",
80
- status: "completed",
81
- created_at: i,
82
- lease_expires_at: Date.now() + 60000,
83
- });
84
- }
85
-
86
- await reconcileOnBoot("test-boot");
87
-
88
- const afterCount = (await agentRunsCol.scan({})).length;
89
- expect(afterCount).toBe(100);
90
- });
91
- });