@johpaz/hive-sdk 0.1.6 → 0.3.0

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