@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,119 +0,0 @@
1
- /**
2
- * Secrets must survive a server restart.
3
- *
4
- * Regression test for provider API keys (and channel tokens / MCP headers)
5
- * vanishing on every restart in production: they were written to the OS
6
- * keychain only, which throws on headless Linux/Docker, leaving the value in
7
- * a per-process in-memory map that dies with the process.
8
- *
9
- * The durability half runs in real subprocesses against a real on-disk
10
- * HIVE_HOME — a restart is the whole point, so it cannot be faked in-process.
11
- */
12
-
13
- import { describe, test, expect, afterAll } from "bun:test";
14
- import { mkdtempSync, rmSync, statSync } from "node:fs";
15
- import { tmpdir } from "node:os";
16
- import * as path from "node:path";
17
-
18
- const CORE = path.join(import.meta.dir, "..", "packages", "core", "src");
19
- const hiveHome = mkdtempSync(path.join(tmpdir(), "hive-secrets-"));
20
-
21
- // Set before anything imports storage/*: the in-process half of this file
22
- // mints a master key, and it must land in the temp dir, never in the real
23
- // ~/.hive of whoever runs the suite.
24
- process.env.HIVE_HOME = hiveHome;
25
- process.env.HIVE_DB_PATH = ":memory:";
26
-
27
- afterAll(() => {
28
- rmSync(hiveHome, { recursive: true, force: true });
29
- });
30
-
31
- /** Run `body` in a fresh process with no OS keychain available. */
32
- async function inRestartedProcess(body: string): Promise<string> {
33
- const script = `
34
- process.env.HIVE_HOME = ${JSON.stringify(hiveHome)};
35
- // Headless Linux / Docker: Bun.secrets has no libsecret to talk to.
36
- (Bun as any).secrets = {
37
- get: async () => { throw new Error("keychain unavailable") },
38
- set: async () => { throw new Error("keychain unavailable") },
39
- delete: async () => { throw new Error("keychain unavailable") },
40
- };
41
- const crypto = await import(${JSON.stringify(path.join(CORE, "storage", "crypto.ts"))});
42
- const out = await (async () => { ${body} })();
43
- console.log("__RESULT__" + JSON.stringify(out));
44
- (await import(${JSON.stringify(path.join(CORE, "storage", "hivedb.ts"))})).closeHiveDb();
45
- `;
46
- const proc = Bun.spawn(["bun", "-e", script], { stdout: "pipe", stderr: "pipe" });
47
- const stdout = await new Response(proc.stdout).text();
48
- await proc.exited;
49
- const line = stdout.split("\n").find((l) => l.startsWith("__RESULT__"));
50
- if (!line) throw new Error(`subprocess produced no result:\n${stdout}\n${await new Response(proc.stderr).text()}`);
51
- return line.slice("__RESULT__".length);
52
- }
53
-
54
- describe("secret durability without an OS keychain", () => {
55
- test("provider API keys and channel tokens survive a restart", async () => {
56
- const written = JSON.parse(
57
- await inRestartedProcess(`
58
- return {
59
- provider: await crypto.storeProviderApiKey("openai", "sk-durable-1234567890"),
60
- channel: await crypto.storeChannelConfig("telegram", { botToken: "123:ABC" }),
61
- };
62
- `)
63
- );
64
- // Reported as durable at write time — callers can trust the return value.
65
- expect(written).toEqual({ provider: true, channel: true });
66
-
67
- const read = JSON.parse(
68
- await inRestartedProcess(`
69
- return {
70
- provider: await crypto.loadProviderApiKey("openai"),
71
- channel: await crypto.loadChannelConfig("telegram"),
72
- };
73
- `)
74
- );
75
- expect(read.provider).toBe("sk-durable-1234567890");
76
- expect(read.channel).toEqual({ botToken: "123:ABC" });
77
- }, 30_000);
78
-
79
- test("the master key is generated once, readable only by its owner", async () => {
80
- const keyPath = path.join(hiveHome, ".master.key");
81
- const mode = statSync(keyPath).mode & 0o777;
82
- expect(mode).toBe(0o600);
83
-
84
- const before = await Bun.file(keyPath).text();
85
- await inRestartedProcess(`return await crypto.loadProviderApiKey("openai")`);
86
- // A second boot must reuse the key — regenerating it would orphan every
87
- // secret already encrypted with the old one.
88
- expect(await Bun.file(keyPath).text()).toBe(before);
89
- }, 30_000);
90
-
91
- test("deleting a secret removes it from the durable store too", async () => {
92
- await inRestartedProcess(`return await crypto.deleteProviderSecrets("openai")`);
93
- const read = await inRestartedProcess(`return await crypto.loadProviderApiKey("openai")`);
94
- expect(JSON.parse(read)).toBe("");
95
- }, 30_000);
96
- });
97
-
98
- describe("keychain compatibility", () => {
99
- test("a secret that only exists in the OS keychain is still readable", async () => {
100
- const keychain = new Map<string, string>([["provider:legacy:api_key", "sk-from-keychain"]]);
101
- const original = (Bun as any).secrets;
102
- (Bun as any).secrets = {
103
- get: async ({ name }: { name: string }) => keychain.get(name) ?? null,
104
- set: async ({ name, value }: { name: string; value: string }) => void keychain.set(name, value),
105
- delete: async ({ name }: { name: string }) => void keychain.delete(name),
106
- };
107
- try {
108
- const { loadProviderApiKey, storeProviderApiKey } = await import("../packages/core/src/storage/crypto");
109
- expect(await loadProviderApiKey("legacy")).toBe("sk-from-keychain");
110
-
111
- // New writes are mirrored to the keychain as well, so a downgrade or a
112
- // desktop install keeps working the way it did before.
113
- await storeProviderApiKey("mirrored", "sk-mirrored");
114
- expect(keychain.get("provider:mirrored:api_key")).toBe("sk-mirrored");
115
- } finally {
116
- (Bun as any).secrets = original;
117
- }
118
- });
119
- });
@@ -1,155 +0,0 @@
1
- /**
2
- * Semántica del re-seed de modelos: wipe & recreate.
3
- *
4
- * Actualizar el catálogo debe ser editar SEED_DATA.models y arrancar. Eso exige
5
- * que las filas de catálogo se borren enteras (para que ningún campo viejo
6
- * sobreviva a una entrada corregida) sin llevarse por delante ni la elección del
7
- * usuario, ni los modelos descubiertos en runtime, ni los providers/claves.
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 { col } from "../packages/core/src/storage/hive";
16
- import type { ModelDoc, ProviderDoc } from "../packages/core/src/storage/collections";
17
- import { seedAllData, SEED_DATA } from "../packages/core/src/storage/seed";
18
- import { catalogModelKey } from "../packages/core/src/storage/model-id";
19
-
20
- beforeEach(async () => {
21
- closeHiveDb();
22
- await ensureHiveDb();
23
- });
24
-
25
- afterEach(() => {
26
- closeHiveDb();
27
- });
28
-
29
- const A_SEEDED_MODEL = () => {
30
- const m = SEED_DATA.models.find((x) => x.modelType === "llm" && x.providerId === "anthropic")!;
31
- return catalogModelKey(m.providerId, m.id);
32
- };
33
-
34
- describe("re-seed de modelos", () => {
35
- test("preserva enabled/active de los ids que sobreviven", async () => {
36
- const modelsCol = await col<ModelDoc>("models");
37
- const id = A_SEEDED_MODEL();
38
-
39
- const before = await modelsCol.get(id);
40
- expect(before).toBeTruthy();
41
- await modelsCol.put(id, { ...before!.doc, active: true, enabled: true }, { expectedVersion: before!.version });
42
-
43
- await seedAllData();
44
-
45
- const after = await modelsCol.get(id);
46
- expect(after!.doc.active).toBe(true);
47
- expect(after!.doc.enabled).toBe(true);
48
- });
49
-
50
- test("borra las filas de catálogo que salieron del seed", async () => {
51
- const modelsCol = await col<ModelDoc>("models");
52
- // Una fila de catálogo que ya no está en SEED_DATA (modelo retirado, como
53
- // los 8 modelos NVIDIA que devolvían 410).
54
- await modelsCol.put("provider-x/modelo-retirado", {
55
- id: "provider-x/modelo-retirado", provider_id: "nvidia", name: "Retirado",
56
- model_type: "llm", context_window: 1000, capabilities: null,
57
- enabled: true, active: true, source: "catalog",
58
- input_per_1m: 1, output_per_1m: 1,
59
- });
60
-
61
- await seedAllData();
62
-
63
- expect(await modelsCol.get("provider-x/modelo-retirado")).toBeFalsy();
64
- });
65
-
66
- test("conserva los modelos descubiertos en runtime", async () => {
67
- const modelsCol = await col<ModelDoc>("models");
68
- await modelsCol.put("llama3.3:8b", {
69
- id: "llama3.3:8b", provider_id: "ollama", name: "llama3.3:8b",
70
- model_type: "llm", context_window: 0, capabilities: null,
71
- enabled: true, active: true, source: "discovered",
72
- input_per_1m: 0, output_per_1m: 0,
73
- });
74
-
75
- await seedAllData();
76
-
77
- const survivor = await modelsCol.get("llama3.3:8b");
78
- expect(survivor).toBeTruthy();
79
- expect(survivor!.doc.active).toBe(true);
80
- });
81
-
82
- test("recrea los campos de catálogo desde cero", async () => {
83
- const modelsCol = await col<ModelDoc>("models");
84
- const id = A_SEEDED_MODEL();
85
- const original = (await modelsCol.get(id))!;
86
-
87
- // Un valor corrupto/desactualizado no debe sobrevivir al re-seed.
88
- await modelsCol.put(id, { ...original.doc, context_window: 42, input_per_1m: 999 }, { expectedVersion: original.version });
89
-
90
- await seedAllData();
91
-
92
- const after = (await modelsCol.get(id))!.doc;
93
- expect(after.context_window).toBe(original.doc.context_window);
94
- expect(after.input_per_1m).toBe(original.doc.input_per_1m ?? null);
95
- });
96
-
97
- test("cada modelo llm queda con su precio en la BD", async () => {
98
- const modelsCol = await col<ModelDoc>("models");
99
- const rows = (await modelsCol.scan({})).filter((r) => r.doc.model_type === "llm" && r.doc.source === "catalog");
100
-
101
- expect(rows.length).toBeGreaterThan(0);
102
- const sinPrecio = rows
103
- .filter((r) => typeof r.doc.input_per_1m !== "number" || typeof r.doc.output_per_1m !== "number")
104
- .map((r) => r.id);
105
- expect(sinPrecio).toEqual([]);
106
- });
107
-
108
- test("los ids de revendedor quedan prefijados y no colisionan", async () => {
109
- const modelsCol = await col<ModelDoc>("models");
110
-
111
- // z-ai/glm-5.2 se sirve desde NVIDIA y desde OpenRouter: antes una fila
112
- // pisaba a la otra porque la colección se indexaba sólo por model id.
113
- const nvidia = await modelsCol.get("nvidia/z-ai/glm-5.2");
114
- const openrouter = await modelsCol.get("openrouter/z-ai/glm-5.2");
115
-
116
- expect(nvidia?.doc.provider_id).toBe("nvidia");
117
- expect(openrouter?.doc.provider_id).toBe("openrouter");
118
- // Endpoint gratuito de NVIDIA vs. OpenRouter de pago — el costo ya no se confunde.
119
- expect(nvidia!.doc.input_per_1m).toBe(0);
120
- expect(openrouter!.doc.input_per_1m).toBeGreaterThan(0);
121
- });
122
- });
123
-
124
- describe("re-seed de providers", () => {
125
- test("es sólo aditivo: no borra ni reescribe los existentes", async () => {
126
- const providersCol = await col<ProviderDoc>("providers");
127
-
128
- // El usuario activó un provider y le puso un base_url propio.
129
- const anthropic = (await providersCol.get("anthropic"))!;
130
- await providersCol.put("anthropic", { ...anthropic.doc, active: true, enabled: true, base_url: "https://proxy.interno" }, { expectedVersion: anthropic.version });
131
-
132
- // Y tiene un provider que ya no está en el catálogo.
133
- await providersCol.put("provider-propio", {
134
- id: "provider-propio", name: "Propio", base_url: null, category: "llm",
135
- num_ctx: null, num_gpu: -1, enabled: true, active: true, created_at: Date.now(),
136
- });
137
-
138
- await seedAllData();
139
-
140
- const after = (await providersCol.get("anthropic"))!.doc;
141
- expect(after.base_url).toBe("https://proxy.interno");
142
- expect(after.active).toBe(true);
143
- expect(await providersCol.get("provider-propio")).toBeTruthy();
144
- });
145
-
146
- test("crea los providers del catálogo que faltaban", async () => {
147
- const providersCol = await col<ProviderDoc>("providers");
148
- await providersCol.delete("z-ai");
149
- expect(await providersCol.get("z-ai")).toBeFalsy();
150
-
151
- await seedAllData();
152
-
153
- expect(await providersCol.get("z-ai")).toBeTruthy();
154
- });
155
- });
@@ -1,264 +0,0 @@
1
- /**
2
- * Tests for the setup/onboarding step that seeds every agent row with the
3
- * coordinator's provider/model. Uses HIVE_DB_PATH=":memory:" so no state
4
- * persists between runs.
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, fromIndexable, toIndexable } from "../packages/core/src/storage/hive";
13
- import type { AgentDoc, ModelDoc } from "../packages/core/src/storage/collections";
14
- import { seedAllData } from "../packages/core/src/storage/seed";
15
- import { saveAgentConfig, propagateCoordinatorModel } from "../packages/core/src/storage/onboarding";
16
- import { CATALOG_AGENT_IDS } from "../packages/core/src/agent/agent-catalog";
17
-
18
- beforeEach(async () => {
19
- closeHiveDb();
20
- await ensureHiveDb();
21
- });
22
-
23
- afterEach(() => {
24
- closeHiveDb();
25
- });
26
-
27
- async function agent(id: string): Promise<AgentDoc> {
28
- const entry = await (await col<AgentDoc>("agents")).get(id);
29
- if (!entry) throw new Error(`agent not found: ${id}`);
30
- return entry.doc;
31
- }
32
-
33
- describe("catalog agents after boot", () => {
34
- test("are seeded without a provider/model pair", async () => {
35
- for (const id of CATALOG_AGENT_IDS) {
36
- const doc = await agent(id);
37
- expect(fromIndexable(doc.provider_id)).toBeNull();
38
- expect(fromIndexable(doc.model_id)).toBeNull();
39
- }
40
- });
41
- });
42
-
43
- describe("saveAgentConfig", () => {
44
- test("seeds every catalog agent with the coordinator's provider/model", async () => {
45
- const agentId = await saveAgentConfig({
46
- userId: "user-1",
47
- agentName: "Bee",
48
- tone: "friendly",
49
- providerId: "gemini",
50
- modelId: "gemini-3.6-flash",
51
- });
52
-
53
- const coordinator = await agent(agentId);
54
- expect(coordinator.role).toBe("coordinator");
55
- expect(fromIndexable(coordinator.provider_id)).toBe("gemini");
56
-
57
- for (const id of CATALOG_AGENT_IDS) {
58
- const doc = await agent(id);
59
- expect(fromIndexable(doc.provider_id)).toBe("gemini");
60
- expect(fromIndexable(doc.model_id)).toBe("gemini-3.6-flash");
61
- expect(doc.user_id).toBe("user-1");
62
- expect(doc.role).toBe("worker");
63
- // The rest of the persona survives the rewrite untouched
64
- expect(doc.enabled).toBe(true);
65
- expect(doc.status).toBe("idle");
66
- expect(doc.source).toBe("catalog");
67
- expect(doc.tool_allowlist_json).toBeTruthy();
68
- expect(doc.system_prompt.length).toBeGreaterThan(0);
69
- }
70
- });
71
-
72
- test("leaves the workers untouched while the coordinator has no model yet", async () => {
73
- await saveAgentConfig({
74
- userId: "user-1",
75
- agentName: "Bee",
76
- tone: "friendly",
77
- providerId: "",
78
- modelId: "",
79
- });
80
-
81
- const doc = await agent(CATALOG_AGENT_IDS[0]);
82
- expect(fromIndexable(doc.provider_id)).toBeNull();
83
- expect(fromIndexable(doc.model_id)).toBeNull();
84
- });
85
-
86
- test("ignores a provider/model that is not in the catalog tables", async () => {
87
- await saveAgentConfig({
88
- userId: "user-1",
89
- agentName: "Bee",
90
- tone: "friendly",
91
- providerId: "not-a-provider",
92
- modelId: "not-a-model",
93
- });
94
-
95
- const doc = await agent(CATALOG_AGENT_IDS[0]);
96
- expect(fromIndexable(doc.provider_id)).toBeNull();
97
- });
98
- });
99
-
100
- describe("reseed on an already-configured install (boot, no setup)", () => {
101
- async function configuredInstall(): Promise<string> {
102
- return saveAgentConfig({
103
- userId: "user-1",
104
- agentName: "Bee",
105
- tone: "friendly",
106
- providerId: "gemini",
107
- modelId: "gemini-3.6-flash",
108
- });
109
- }
110
-
111
- test("keeps every agent enabled and configured across a reseed", async () => {
112
- const coordinatorId = await configuredInstall();
113
- const agentsCol = await col<AgentDoc>("agents");
114
- // A worker the user pointed at a different model from the UI
115
- const custom = { ...(await agent(CATALOG_AGENT_IDS[0])), id: "custom-worker" };
116
- await agentsCol.put(custom.id, { ...custom, provider_id: toIndexable("openai"), model_id: toIndexable("gpt-5.6-luna") });
117
- // A persona the user disabled
118
- const off = await agent(CATALOG_AGENT_IDS[1]);
119
- await agentsCol.put(off.id, { ...off, enabled: false }, { expectedVersion: (await agentsCol.get(off.id))!.version });
120
-
121
- await seedAllData();
122
-
123
- expect(fromIndexable((await agent(coordinatorId)).model_id)).toBe("gemini-3.6-flash");
124
- for (const id of CATALOG_AGENT_IDS) {
125
- const doc = await agent(id);
126
- expect(fromIndexable(doc.provider_id)).toBe("gemini");
127
- expect(fromIndexable(doc.model_id)).toBe("gemini-3.6-flash");
128
- }
129
- // Per-agent choices and toggles survive the reseed
130
- expect(fromIndexable((await agent("custom-worker")).model_id)).toBe("gpt-5.6-luna");
131
- expect((await agent(CATALOG_AGENT_IDS[1])).enabled).toBe(false);
132
- });
133
-
134
- test("migrates the legacy cron persona stored in the database", async () => {
135
- await configuredInstall();
136
- const agentsCol = await col<AgentDoc>("agents");
137
- const cronId = "schedule_automation_agent";
138
- const current = (await agentsCol.get(cronId))!;
139
- await agentsCol.put(cronId, {
140
- ...current.doc,
141
- name: "Operador de agenda",
142
- description: "Crea y administra recordatorios y automatizaciones temporales con zona horaria correcta.",
143
- system_prompt: current.doc.system_prompt
144
- .replace(
145
- "Tu dominio son los jobs técnicos programados de Hive, su recurrencia, ventanas temporales y zonas horarias.",
146
- "Tu dominio es recordatorios, cron recurrente, ventanas temporales y zonas horarias.",
147
- )
148
- .replace(
149
- "Una automatización que Hive debe ejecutar después, su horario o recurrencia, timezone, canal y comportamiento esperado.",
150
- "Acción temporal, horario expresado por el usuario, timezone, canal y comportamiento esperado.",
151
- ),
152
- enabled: false,
153
- helpful_count: 7,
154
- }, { expectedVersion: current.version });
155
-
156
- await seedAllData();
157
-
158
- const migrated = await agent(cronId);
159
- expect(migrated.name).toBe("Operador de cron");
160
- expect(migrated.description).toContain("jobs programados");
161
- expect(migrated.system_prompt).toContain("jobs técnicos programados");
162
- expect(migrated.system_prompt).not.toContain("Tu dominio es recordatorios");
163
- expect(migrated.enabled).toBe(false);
164
- expect(migrated.helpful_count).toBe(7);
165
- expect(fromIndexable(migrated.model_id)).toBe("gemini-3.6-flash");
166
- });
167
-
168
- test("configures a persona added by an upgrade", async () => {
169
- await configuredInstall();
170
- const agentsCol = await col<AgentDoc>("agents");
171
- // Simulates a new catalog row landing blank, the way createSeedCatalogAgents seeds it
172
- const fresh = {
173
- ...(await agent(CATALOG_AGENT_IDS[0])),
174
- id: "new_persona",
175
- user_id: "",
176
- provider_id: toIndexable(null),
177
- model_id: toIndexable(null),
178
- };
179
- await agentsCol.put(fresh.id, fresh);
180
-
181
- await seedAllData();
182
-
183
- const doc = await agent("new_persona");
184
- expect(fromIndexable(doc.provider_id)).toBe("gemini");
185
- expect(fromIndexable(doc.model_id)).toBe("gemini-3.6-flash");
186
- expect(doc.user_id).toBe("user-1");
187
- expect(doc.enabled).toBe(true);
188
- });
189
-
190
- test("keeps a model discovered from a provider, and the agents pointing at it", async () => {
191
- const modelsCol = await col<ModelDoc>("models");
192
- await modelsCol.put("llama3.2:latest", {
193
- id: "llama3.2:latest", provider_id: "ollama", name: "llama3.2:latest", model_type: "llm",
194
- context_window: 0, capabilities: null, enabled: true, active: true, source: "discovered",
195
- });
196
- const coordinatorId = await saveAgentConfig({
197
- userId: "user-1",
198
- agentName: "Bee",
199
- tone: "friendly",
200
- providerId: "ollama",
201
- modelId: "llama3.2:latest",
202
- });
203
-
204
- await seedAllData();
205
-
206
- expect(await modelsCol.get("llama3.2:latest")).toBeTruthy();
207
- expect(fromIndexable((await agent(coordinatorId)).model_id)).toBe("llama3.2:latest");
208
- expect(fromIndexable((await agent(CATALOG_AGENT_IDS[0])).model_id)).toBe("llama3.2:latest");
209
- });
210
-
211
- test("still unlinks agents from a catalog model that was dropped", async () => {
212
- const modelsCol = await col<ModelDoc>("models");
213
- await modelsCol.put("retired-model", {
214
- id: "retired-model", provider_id: "gemini", name: "retired", model_type: "llm",
215
- context_window: 0, capabilities: null, enabled: true, active: true, source: "catalog",
216
- });
217
- const agentsCol = await col<AgentDoc>("agents");
218
- const orphan = { ...(await agent(CATALOG_AGENT_IDS[0])), id: "orphan-worker", model_id: toIndexable("retired-model") };
219
- await agentsCol.put(orphan.id, orphan);
220
-
221
- await seedAllData();
222
-
223
- expect(await modelsCol.get("retired-model")).toBeUndefined();
224
- // No coordinator configured here, so nothing refills it
225
- expect(fromIndexable((await agent("orphan-worker")).model_id)).toBeNull();
226
- });
227
- });
228
-
229
- describe("propagateCoordinatorModel", () => {
230
- test("covers workers created outside the catalog and skips coordinators", async () => {
231
- const agentsCol = await col<AgentDoc>("agents");
232
- const now = Date.now();
233
- const worker = { ...(await agent(CATALOG_AGENT_IDS[0])), id: "custom-worker", source: "user" as const };
234
- await agentsCol.put(worker.id, worker);
235
- const coordinatorId = await saveAgentConfig({
236
- userId: "user-1",
237
- agentName: "Bee",
238
- tone: "friendly",
239
- providerId: "gemini",
240
- modelId: "gemini-3.6-flash",
241
- });
242
-
243
- const updated = await propagateCoordinatorModel("user-1", "openai", "gpt-5.6-luna");
244
- expect(updated).toBe(CATALOG_AGENT_IDS.length + 1);
245
- expect(fromIndexable((await agent("custom-worker")).provider_id)).toBe("openai");
246
- // The coordinator keeps what saveAgentConfig persisted for it
247
- expect(fromIndexable((await agent(coordinatorId)).model_id)).toBe("gemini-3.6-flash");
248
- expect((await agent(CATALOG_AGENT_IDS[0])).updated_at).toBeGreaterThanOrEqual(now);
249
-
250
- // Idempotent: a second run with the same pair rewrites nothing
251
- expect(await propagateCoordinatorModel("user-1", "openai", "gpt-5.6-luna")).toBe(0);
252
- });
253
-
254
- test("keeps a user_id that is already set", async () => {
255
- const agentsCol = await col<AgentDoc>("agents");
256
- const owned = { ...(await agent(CATALOG_AGENT_IDS[0])), id: "owned-worker", user_id: "other-user" };
257
- await agentsCol.put(owned.id, owned);
258
-
259
- await propagateCoordinatorModel("user-1", "openai", "gpt-5.6-luna");
260
-
261
- expect((await agent("owned-worker")).user_id).toBe("other-user");
262
- expect((await agent("owned-worker")).provider_id).toBe(toIndexable("openai"));
263
- });
264
- });
@@ -1,65 +0,0 @@
1
- /**
2
- * The tool inventory has three sources that must agree:
3
- *
4
- * - `createAllTools()` — the executors that actually exist
5
- * - `CORE_TOOL_CATALOG` — bilingual descriptions (drives BM25 ranking)
6
- * - `SEED_DATA.tools` — the DB rows (drive the tools UI)
7
- *
8
- * The search index is built as `CORE_TOOL_CATALOG ∪ seed rows`
9
- * (tool-selector.ts). Two failure modes follow, and both have shipped before:
10
- *
11
- * - indexed without an executor → the agent discovers a tool and it blows up
12
- * - executor without indexing → working capability nobody can ever find
13
- *
14
- * Uses HIVE_DB_PATH=":memory:".
15
- */
16
-
17
- process.env.HIVE_DB_PATH = ":memory:";
18
-
19
- import { describe, test, expect } from "bun:test";
20
- import { createAllTools } from "../packages/core/src/tools/index";
21
- import { CORE_TOOL_CATALOG } from "../packages/core/src/agent/tool-selector";
22
- import { SEED_DATA } from "../packages/core/src/storage/seed";
23
-
24
- const executors = new Set(createAllTools({ tools: {} } as never).map((t) => t.name));
25
- const catalog = new Set(CORE_TOOL_CATALOG.map((t) => t.name));
26
- const seeded = new Set(SEED_DATA.tools.map((t) => t.id));
27
- const indexed = new Set([...catalog, ...seeded]);
28
-
29
- describe("tool inventory", () => {
30
- test("nothing is indexed without a working executor", () => {
31
- const ghosts = [...indexed].filter((name) => !executors.has(name)).sort();
32
- expect(ghosts).toEqual([]);
33
- });
34
-
35
- test("every executor is reachable through search_knowledge", () => {
36
- const invisible = [...executors].filter((name) => !indexed.has(name)).sort();
37
- expect(invisible).toEqual([]);
38
- });
39
-
40
- test("every tool carries bilingual keywords for BM25 ranking", () => {
41
- // A seed row alone gets indexed, but with only its own description; the
42
- // catalog entry is what supplies the ES/EN synonyms that make it findable.
43
- const withoutKeywords = [...executors].filter((name) => !catalog.has(name)).sort();
44
- expect(withoutKeywords).toEqual([]);
45
- });
46
-
47
- test("seed rows are internally consistent", () => {
48
- const ids = SEED_DATA.tools.map((t) => t.id);
49
- expect(ids.length).toBe(new Set(ids).size); // no duplicates
50
- expect(SEED_DATA.tools.filter((t) => t.id !== t.name)).toEqual([]);
51
- });
52
-
53
- test("the catalog has no duplicate entries", () => {
54
- const names = CORE_TOOL_CATALOG.map((t) => t.name);
55
- const dupes = names.filter((n, i) => names.indexOf(n) !== i).sort();
56
- expect(dupes).toEqual([]);
57
- });
58
-
59
- test("the retired projects/DAG tools are gone for good", () => {
60
- for (const name of ["project_create", "project_status", "task_create", "task_complete"]) {
61
- expect(executors.has(name)).toBe(false);
62
- expect(indexed.has(name)).toBe(false);
63
- }
64
- });
65
- });