@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,130 +0,0 @@
1
- /**
2
- * Unit tests for the HiveDB collection helpers (storage/hive.ts) and the
3
- * bootstrap entry point (storage/bootstrap.ts). Uses an in-memory database
4
- * (HIVE_DB_PATH=":memory:") so no state persists between runs.
5
- */
6
-
7
- process.env.HIVE_DB_PATH = ":memory:";
8
-
9
- import { describe, test, expect, beforeEach } from "bun:test";
10
- import { closeHiveDb } from "../packages/core/src/storage/hivedb";
11
- import { col, nextId, updateDoc, updateManyByIndex, findByAny, bumpRollup } from "../packages/core/src/storage/hive";
12
- import { ensureHiveDb, isBootstrapped } from "../packages/core/src/storage/bootstrap";
13
- import {
14
- acknowledgeNotification,
15
- createNotification,
16
- listPendingNotifications,
17
- } from "../packages/core/src/gateway/notification-inbox";
18
-
19
- beforeEach(() => {
20
- closeHiveDb();
21
- });
22
-
23
- describe("storage/hive.ts helpers", () => {
24
- test("nextId produces a monotonically increasing, lexicographically sortable sequence", async () => {
25
- const a = await nextId("test-counter");
26
- const b = await nextId("test-counter");
27
- const c = await nextId("test-counter");
28
- expect(a < b).toBe(true);
29
- expect(b < c).toBe(true);
30
- expect(a).toBe("000000000000001");
31
- expect(c).toBe("000000000000003");
32
- });
33
-
34
- test("nextId keeps separate counters independent", async () => {
35
- const a1 = await nextId("counter-a");
36
- const b1 = await nextId("counter-b");
37
- const a2 = await nextId("counter-a");
38
- expect(a1).toBe("000000000000001");
39
- expect(b1).toBe("000000000000001");
40
- expect(a2).toBe("000000000000002");
41
- });
42
-
43
- test("updateDoc merges a patch into an existing document", async () => {
44
- const c = await col<{ name: string; active: boolean }>("widgets");
45
- await c.put("w1", { name: "Widget", active: false });
46
- const updated = await updateDoc<{ name: string; active: boolean }>("widgets", "w1", { active: true });
47
- expect(updated).toEqual({ name: "Widget", active: true });
48
- const fromDb = await c.get("w1");
49
- expect(fromDb?.doc).toEqual({ name: "Widget", active: true });
50
- });
51
-
52
- test("updateDoc throws for a missing document", async () => {
53
- await expect(updateDoc("widgets", "missing", { active: true })).rejects.toThrow("not found");
54
- });
55
-
56
- test("updateManyByIndex patches every doc matching an indexed field", async () => {
57
- const c = await col<{ provider_id: string; active: boolean }>("models_test");
58
- await c.createIndex("provider_id");
59
- await c.put("m1", { provider_id: "openai", active: true });
60
- await c.put("m2", { provider_id: "openai", active: true });
61
- await c.put("m3", { provider_id: "anthropic", active: true });
62
-
63
- const count = await updateManyByIndex<{ provider_id: string; active: boolean }>(
64
- "models_test", "provider_id", "openai", { active: false }
65
- );
66
- expect(count).toBe(2);
67
- expect((await c.get("m1"))?.doc.active).toBe(false);
68
- expect((await c.get("m2"))?.doc.active).toBe(false);
69
- expect((await c.get("m3"))?.doc.active).toBe(true);
70
- });
71
-
72
- test("findByAny emulates WHERE field IN (...)", async () => {
73
- const c = await col<{ agent_id: string }>("bus_test");
74
- await c.createIndex("agent_id");
75
- await c.put("msg1", { agent_id: "a1" });
76
- await c.put("msg2", { agent_id: "a2" });
77
- await c.put("msg3", { agent_id: "a3" });
78
-
79
- const rows = await findByAny<{ agent_id: string }>("bus_test", "agent_id", ["a1", "a3", "a3"]);
80
- expect(rows.length).toBe(2);
81
- expect(rows.map((r) => r.id).sort()).toEqual(["msg1", "msg3"]);
82
- });
83
-
84
- test("bumpRollup accumulates deltas and nested breakdowns across calls", async () => {
85
- await bumpRollup("usageRollups_test", "hour:2026-07-08T10", { inputTokens: 100, costUsd: 0.5 }, { field: "byProvider", key: "openai" });
86
- await bumpRollup("usageRollups_test", "hour:2026-07-08T10", { inputTokens: 50, costUsd: 0.2 }, { field: "byProvider", key: "openai" });
87
- await bumpRollup("usageRollups_test", "hour:2026-07-08T10", { inputTokens: 10, costUsd: 0.1 }, { field: "byProvider", key: "anthropic" });
88
-
89
- const c = await col<any>("usageRollups_test");
90
- const doc = (await c.get("hour:2026-07-08T10"))?.doc;
91
- expect(doc.inputTokens).toBe(160);
92
- expect(doc.costUsd).toBeCloseTo(0.8);
93
- expect(doc.byProvider.openai.inputTokens).toBe(150);
94
- expect(doc.byProvider.anthropic.inputTokens).toBe(10);
95
- });
96
- });
97
-
98
- describe("durable WebChat notification inbox", () => {
99
- test("keeps a notification pending until its owner acknowledges it", async () => {
100
- const notifications = await col("notifications");
101
- await notifications.createIndex("user_id");
102
- const notification = await createNotification({
103
- userId: "user-a",
104
- channel: "webchat",
105
- message: "Trabajo terminado",
106
- });
107
-
108
- expect(await listPendingNotifications("user-a", "webchat")).toHaveLength(1);
109
- expect(await acknowledgeNotification(notification.id, "user-b")).toBe(false);
110
- expect(await listPendingNotifications("user-a", "webchat")).toHaveLength(1);
111
-
112
- expect(await acknowledgeNotification(notification.id, "user-a")).toBe(true);
113
- expect(await listPendingNotifications("user-a", "webchat")).toHaveLength(0);
114
- });
115
- });
116
-
117
- describe("storage/bootstrap.ts", () => {
118
- test("ensureHiveDb is idempotent and marks bootstrapped", async () => {
119
- // Sin aserción sobre el estado inicial: `bootstrapped` es un flag de módulo
120
- // que `closeHiveDb()` no resetea, así que su valor de entrada depende de si
121
- // otro archivo de test ya llamó a ensureHiveDb en este mismo proceso.
122
- await ensureHiveDb();
123
- expect(isBootstrapped()).toBe(true);
124
- await ensureHiveDb(); // second call must not throw (idempotent indexes + schemaVersion put)
125
-
126
- const meta = await col<{ value: number }>("meta");
127
- const version = await meta.get("schemaVersion");
128
- expect(version?.doc.value).toBe(1);
129
- });
130
- });
@@ -1,189 +0,0 @@
1
- /**
2
- * HiveDB capability search — integration tests
3
- *
4
- * Covers the capability search layer:
5
- * packages/core/src/agent/capability-search.ts on top of @johpaz/hive-db.
6
- *
7
- * Uses an in-memory index (HIVE_DB_PATH=":memory:") so no state
8
- * touches ~/.hive.
9
- */
10
-
11
- process.env.HIVE_DB_PATH = ":memory:";
12
-
13
- import { describe, test, expect, beforeAll, afterAll } from "bun:test";
14
- import {
15
- searchCapabilities,
16
- applyRelativeCutoff,
17
- replaceCapabilityDocs,
18
- upsertCapabilityDocs,
19
- deleteCapabilitiesByServer,
20
- } from "../packages/core/src/agent/capability-search";
21
- import { closeHiveDb } from "../packages/core/src/storage/hivedb";
22
-
23
- beforeAll(async () => {
24
- await replaceCapabilityDocs("tool", [
25
- {
26
- type: "tool",
27
- rawId: "web_search",
28
- name: "web_search",
29
- body: "Busca en internet información actualizada, noticias e investigación",
30
- tags: "web",
31
- },
32
- {
33
- type: "tool",
34
- rawId: "cron.create",
35
- name: "cron.create",
36
- body: "Programar tareas recurrentes, crear recordatorios y automatizar horarios",
37
- tags: "scheduling",
38
- },
39
- ]);
40
- await replaceCapabilityDocs("skill", [
41
- {
42
- type: "skill",
43
- rawId: "reportes",
44
- name: "generación de reportes",
45
- body: "Genera reportes mensuales de transacciones y métricas",
46
- tags: "reportes análisis",
47
- },
48
- ]);
49
- await replaceCapabilityDocs("mcp", [
50
- {
51
- type: "mcp",
52
- rawId: "gmail__send_email",
53
- name: "send_email",
54
- body: "Send an email to a recipient",
55
- tags: "mcp gmail",
56
- extraFilters: [{ field: "server_id", value: "gmail" }],
57
- },
58
- {
59
- type: "mcp",
60
- rawId: "github__create_issue",
61
- name: "create_issue",
62
- body: "Create a GitHub issue",
63
- tags: "mcp github",
64
- extraFilters: [{ field: "server_id", value: "github" }],
65
- },
66
- ]);
67
- });
68
-
69
- afterAll(() => {
70
- closeHiveDb();
71
- });
72
-
73
- describe("HiveDB capability search", () => {
74
- test("Spanish stemming: 'reporte' finds 'reportes'", async () => {
75
- const hits = await searchCapabilities("reporte", { types: ["skill"] });
76
- expect(hits.length).toBe(1);
77
- expect(hits[0].rawId).toBe("reportes");
78
- expect(hits[0].score).toBeGreaterThan(0);
79
- });
80
-
81
- test("accent folding: 'transaccion' finds 'transacciones'", async () => {
82
- const hits = await searchCapabilities("transaccion", { types: ["skill"] });
83
- expect(hits.length).toBe(1);
84
- expect(hits[0].rawId).toBe("reportes");
85
- });
86
-
87
- test("raw user input with operators and punctuation never throws", async () => {
88
- for (const q of [
89
- '"comillas sin cerrar',
90
- "¿puedes buscar (noticias) OR NOT?",
91
- "a:b* ~^",
92
- "***",
93
- ]) {
94
- const hits = await searchCapabilities(q, {});
95
- expect(Array.isArray(hits)).toBe(true);
96
- }
97
- // And the natural-language version still matches
98
- const hits = await searchCapabilities("¿puedes buscar noticias en internet?", {
99
- types: ["tool"],
100
- });
101
- expect(hits.map((h) => h.rawId)).toContain("web_search");
102
- });
103
-
104
- test("type filter isolates result sets", async () => {
105
- // "email" only exists in MCP docs
106
- const mcpHits = await searchCapabilities("email", { types: ["mcp"] });
107
- expect(mcpHits.length).toBe(1);
108
- expect(mcpHits[0].type).toBe("mcp");
109
-
110
- const toolHits = await searchCapabilities("email", { types: ["tool"] });
111
- expect(toolHits.length).toBe(0);
112
- });
113
-
114
- test("all-types search returns namespaced hits", async () => {
115
- const hits = await searchCapabilities("reportes internet email");
116
- const types = new Set(hits.map((h) => h.type));
117
- expect(types.has("tool")).toBe(true);
118
- expect(types.has("skill")).toBe(true);
119
- expect(types.has("mcp")).toBe(true);
120
- for (const hit of hits) {
121
- expect(hit.id).toBe(`${hit.type}:${hit.rawId}`);
122
- }
123
- });
124
-
125
- test("replaceCapabilityDocs does not duplicate on re-sync", async () => {
126
- // Simulate the startup sync running twice
127
- await replaceCapabilityDocs("tool", [
128
- {
129
- type: "tool",
130
- rawId: "web_search",
131
- name: "web_search",
132
- body: "Busca en internet información actualizada, noticias e investigación",
133
- tags: "web",
134
- },
135
- {
136
- type: "tool",
137
- rawId: "cron.create",
138
- name: "cron.create",
139
- body: "Programar tareas recurrentes, crear recordatorios y automatizar horarios",
140
- tags: "scheduling",
141
- },
142
- ]);
143
- const hits = await searchCapabilities("internet noticias", { types: ["tool"], k: 20 });
144
- const ids = hits.map((h) => h.rawId);
145
- expect(new Set(ids).size).toBe(ids.length);
146
- expect(ids.filter((id) => id === "web_search").length).toBe(1);
147
- });
148
-
149
- test("MCP server disconnect removes only that server's docs", async () => {
150
- await deleteCapabilitiesByServer("gmail");
151
-
152
- const emailHits = await searchCapabilities("email", { types: ["mcp"] });
153
- expect(emailHits.length).toBe(0);
154
-
155
- const githubHits = await searchCapabilities("github issue", { types: ["mcp"] });
156
- expect(githubHits.map((h) => h.rawId)).toContain("github__create_issue");
157
-
158
- // Restore for other tests
159
- await upsertCapabilityDocs([
160
- {
161
- type: "mcp",
162
- rawId: "gmail__send_email",
163
- name: "send_email",
164
- body: "Send an email to a recipient",
165
- tags: "mcp gmail",
166
- extraFilters: [{ field: "server_id", value: "gmail" }],
167
- },
168
- ]);
169
- });
170
-
171
- test("applyRelativeCutoff drops weak hits, keeps strong ones", () => {
172
- const hits = [
173
- { id: "tool:a", type: "tool" as const, rawId: "a", score: 10 },
174
- { id: "tool:b", type: "tool" as const, rawId: "b", score: 5 },
175
- { id: "tool:c", type: "tool" as const, rawId: "c", score: 1 },
176
- ];
177
- const kept = applyRelativeCutoff(hits, 0.3);
178
- expect(kept.map((h) => h.rawId)).toEqual(["a", "b"]);
179
- expect(applyRelativeCutoff([], 0.3)).toEqual([]);
180
- });
181
-
182
- test("selector-path latency stays under 50ms warm", async () => {
183
- await searchCapabilities("warmup", { types: ["tool"] });
184
- const start = performance.now();
185
- await searchCapabilities("programar recordatorio diario", { types: ["tool"] });
186
- const elapsed = performance.now() - start;
187
- expect(elapsed).toBeLessThan(50);
188
- });
189
- });
@@ -1,166 +0,0 @@
1
- /**
2
- * Internal-event turns (delegation fan-in) must persist as role:"user" + a
3
- * `source` tag — never role:"system" — so every LLM provider that hoists
4
- * ALL role:"system" messages into a single system instruction (Gemini,
5
- * Anthropic) can't accidentally promote a transient delegation notice into a
6
- * permanent standing instruction. See conversation-store.ts's
7
- * isInternalSource/formatInternalEvent and routes/chat.ts's history filter.
8
- */
9
-
10
- process.env.HIVE_DB_PATH = ":memory:";
11
-
12
- import { describe, test, expect, beforeEach, afterEach } from "bun:test";
13
- import { closeHiveDb } 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 { col } from "../packages/core/src/storage/hive";
17
- import {
18
- addMessage,
19
- getRecentMessages,
20
- toAPIMessages,
21
- isInternalSource,
22
- formatInternalEvent,
23
- } from "../packages/core/src/agent/conversation-store";
24
- import type { ConversationDoc } from "../packages/core/src/storage/collections";
25
-
26
- beforeEach(async () => {
27
- closeHiveDb();
28
- resetBootId();
29
- await ensureHiveDb();
30
- });
31
-
32
- afterEach(() => {
33
- closeHiveDb();
34
- });
35
-
36
- describe("internal turns: persistence", () => {
37
- test("addMessage with source persists role:user + the given source", async () => {
38
- await addMessage("thread-a", "user", "El agente completó la tarea X.", {
39
- source: "task_complete",
40
- });
41
-
42
- const conversationsCol = await col<ConversationDoc>("conversations");
43
- const entries = await conversationsCol.scan({ prefix: "thread-a:" });
44
- expect(entries).toHaveLength(1);
45
- expect(entries[0].doc.role).toBe("user");
46
- expect(entries[0].doc.source).toBe("task_complete");
47
- });
48
-
49
- test("normal messages persist source:message by default", async () => {
50
- await addMessage("thread-b", "user", "hola");
51
-
52
- const conversationsCol = await col<ConversationDoc>("conversations");
53
- const entries = await conversationsCol.scan({ prefix: "thread-b:" });
54
- expect(entries[0].doc.source).toBe("message");
55
- });
56
- });
57
-
58
- describe("internal turns: serialization (toAPIMessages)", () => {
59
- test("wraps internal-source content in the <hive:internal_event> marker", async () => {
60
- await addMessage("thread-c", "user", "El agente completó la tarea X.", {
61
- source: "task_complete",
62
- });
63
-
64
- const rows = await getRecentMessages("thread-c", 10);
65
- const apiMessages = toAPIMessages(rows);
66
-
67
- expect(apiMessages).toHaveLength(1);
68
- expect(apiMessages[0].role).toBe("user");
69
- expect(apiMessages[0].content).toContain('<hive:internal_event source="task_complete">');
70
- expect(apiMessages[0].content).toContain("El agente completó la tarea X.");
71
- });
72
-
73
- test("leaves normal user/assistant content untouched", async () => {
74
- await addMessage("thread-d", "user", "hola");
75
- await addMessage("thread-d", "assistant", "¿en qué te ayudo?");
76
-
77
- const rows = await getRecentMessages("thread-d", 10);
78
- const apiMessages = toAPIMessages(rows);
79
-
80
- expect(apiMessages[0].content).toBe("hola");
81
- expect(apiMessages[1].content).toBe("¿en qué te ayudo?");
82
- });
83
-
84
- test("regression guard: a mix of user/assistant/internal rows never yields role:system", async () => {
85
- await addMessage("thread-e", "user", "Delegá esto");
86
- await addMessage("thread-e", "assistant", "Listo, delegado.");
87
- await addMessage("thread-e", "user", "El agente completó la tarea X.", {
88
- source: "task_complete",
89
- });
90
- await addMessage("thread-e", "assistant", "El worker terminó la tarea X.");
91
-
92
- const rows = await getRecentMessages("thread-e", 10);
93
- const apiMessages = toAPIMessages(rows);
94
-
95
- expect(apiMessages).toHaveLength(4);
96
- expect(apiMessages.every((m) => m.role !== "system")).toBe(true);
97
- });
98
- });
99
-
100
- // El bloque "public history endpoint" de hive quedó fuera: cubre
101
- // `handleGetChatHistory` de gateway/routes/chat.ts, y el gateway del SDK no
102
- // incluye esas rutas. Lo que sí viaja es el filtro que consume la ruta —
103
- // `isInternalSource` / `getRecentMessages` — que se verifica más abajo.
104
-
105
- describe("internal turns: legacy role:system compat", () => {
106
- async function writeLegacySystemRow(threadId: string, content: string) {
107
- // Pre-`source` rows used role:"system" as the sole marker for internal
108
- // events; source didn't exist yet (persisted as absent/null).
109
- await addMessage(threadId, "user", content); // reserves the id/seq
110
- const conversationsCol = await col<ConversationDoc>("conversations");
111
- const entries = await conversationsCol.scan({ prefix: `${threadId}:` });
112
- // Target the row just added, not the thread's first row — a thread may
113
- // already have earlier messages when this helper runs.
114
- const { id, version, doc } = entries[entries.length - 1];
115
- await conversationsCol.put(id, { ...doc, role: "system", source: null }, { expectedVersion: version });
116
- }
117
-
118
- test("getRecentMessages normalizes legacy role:system rows to user/legacy_internal", async () => {
119
- await writeLegacySystemRow("thread-g", "[Sistema] aviso viejo");
120
-
121
- const rows = await getRecentMessages("thread-g", 10);
122
- expect(rows).toHaveLength(1);
123
- expect(rows[0].role).toBe("user");
124
- expect(rows[0].source).toBe("legacy_internal");
125
- });
126
-
127
- test("toAPIMessages wraps legacy rows the same as current internal events", async () => {
128
- await writeLegacySystemRow("thread-h", "[Sistema] aviso viejo");
129
-
130
- const rows = await getRecentMessages("thread-h", 10);
131
- const apiMessages = toAPIMessages(rows);
132
-
133
- expect(apiMessages[0].role).toBe("user");
134
- expect(apiMessages[0].content).toContain('<hive:internal_event source="legacy_internal">');
135
- });
136
-
137
- test("una fila legacy queda marcada como interna, así el historial público puede filtrarla", async () => {
138
- await addMessage("thread-i", "user", "hola");
139
- await writeLegacySystemRow("thread-i", "[Sistema] aviso viejo");
140
-
141
- const rows = await getRecentMessages("thread-i", 10);
142
- const visible = rows.filter((r) => !isInternalSource(r.source));
143
-
144
- expect(visible).toHaveLength(1);
145
- expect(visible[0].content).toBe("hola");
146
- });
147
- });
148
-
149
- describe("isInternalSource / formatInternalEvent", () => {
150
- test("recognizes task_complete, delegation_summary, legacy_internal — nothing else", () => {
151
- expect(isInternalSource("task_complete")).toBe(true);
152
- expect(isInternalSource("delegation_summary")).toBe(true);
153
- expect(isInternalSource("legacy_internal")).toBe(true);
154
- expect(isInternalSource("message")).toBe(false);
155
- expect(isInternalSource("api")).toBe(false);
156
- expect(isInternalSource(null)).toBe(false);
157
- expect(isInternalSource(undefined)).toBe(false);
158
- });
159
-
160
- test("formatInternalEvent frames content with the source and a non-citation instruction", () => {
161
- const wrapped = formatInternalEvent("task_complete", "resultado X");
162
- expect(wrapped).toContain('source="task_complete"');
163
- expect(wrapped).toContain("resultado X");
164
- expect(wrapped).toContain("NO es un mensaje del usuario");
165
- });
166
- });
@@ -1,68 +0,0 @@
1
- /**
2
- * Tests for gateway/job-store.ts — idempotency_key dedup on createJob:
3
- * a repeated key returns the existing job (whatever its status) instead of
4
- * creating a duplicate.
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 { resetBootId } from "../packages/core/src/storage/boot-id";
13
- import { createJob, claimJob, completeJob, findByIdempotencyKey } from "../packages/core/src/gateway/job-store";
14
- import { col } from "../packages/core/src/storage/hive";
15
- import type { JobDoc } from "../packages/core/src/storage/collections";
16
-
17
- beforeEach(async () => {
18
- closeHiveDb();
19
- resetBootId();
20
- await ensureHiveDb();
21
- });
22
-
23
- afterEach(() => {
24
- closeHiveDb();
25
- });
26
-
27
- describe("job-store: idempotency_key", () => {
28
- test("two createJob calls with the same key return the same job", async () => {
29
- const first = await createJob({ lane: "s1", type: "worker_task", payload: { n: 1 }, run_id: "r1", idempotency_key: "dedupe-me" });
30
- const second = await createJob({ lane: "s1", type: "worker_task", payload: { n: 2 }, run_id: "r1", idempotency_key: "dedupe-me" });
31
-
32
- expect(second.id).toBe(first.id);
33
- // Second call's payload never gets persisted — the original wins.
34
- expect(JSON.parse(second.payload_json).n).toBe(1);
35
-
36
- const c = await col<JobDoc>("jobQueue");
37
- const all = await c.scan();
38
- expect(all.filter((e) => e.doc.type === "worker_task").length).toBe(1);
39
- });
40
-
41
- test("returns the cached result once the original job has completed", async () => {
42
- const job = await createJob({ lane: "s1", type: "worker_task", payload: {}, run_id: "r1", idempotency_key: "k1" });
43
- await claimJob(job.id);
44
- await completeJob(job.id, { done: true });
45
-
46
- const repeat = await createJob({ lane: "s1", type: "worker_task", payload: {}, run_id: "r1", idempotency_key: "k1" });
47
- expect(repeat.id).toBe(job.id);
48
- expect(repeat.status).toBe("completed");
49
- expect(JSON.parse(repeat.result_json!)).toEqual({ done: true });
50
- });
51
-
52
- test("jobs without an idempotency_key are never deduped against each other", async () => {
53
- const a = await createJob({ lane: "s1", type: "worker_task", payload: {}, run_id: "r1" });
54
- const b = await createJob({ lane: "s1", type: "worker_task", payload: {}, run_id: "r2" });
55
- expect(a.id).not.toBe(b.id);
56
- });
57
-
58
- test("findByIdempotencyKey returns null for an unknown key", async () => {
59
- const result = await findByIdempotencyKey("does-not-exist");
60
- expect(result).toBeNull();
61
- });
62
-
63
- test("different keys create distinct jobs", async () => {
64
- const a = await createJob({ lane: "s1", type: "worker_task", payload: {}, run_id: "r1", idempotency_key: "key-a" });
65
- const b = await createJob({ lane: "s1", type: "worker_task", payload: {}, run_id: "r2", idempotency_key: "key-b" });
66
- expect(a.id).not.toBe(b.id);
67
- });
68
- });