@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.
- package/CHANGELOG.md +135 -0
- package/README.md +11 -1
- package/package.json +18 -2
- package/packages/core/src/agent/acceptance-checks.ts +9 -9
- package/packages/core/src/agent/agent-catalog.ts +3 -3
- package/packages/core/src/agent/agent-loop.ts +115 -26
- package/packages/core/src/agent/capability-search.ts +2 -2
- package/packages/core/src/agent/catalog-selector.ts +4 -4
- package/packages/core/src/agent/compaction.ts +10 -9
- package/packages/core/src/agent/context-compiler.ts +58 -34
- package/packages/core/src/agent/conversation-store.ts +31 -7
- package/packages/core/src/agent/curator.ts +4 -4
- package/packages/core/src/agent/delegation-runtime.ts +5 -5
- package/packages/core/src/agent/goal-runner.ts +9 -9
- package/packages/core/src/agent/index.ts +1 -0
- package/packages/core/src/agent/llm-client.ts +98 -37
- package/packages/core/src/agent/llm-providers/anthropic.ts +4 -4
- package/packages/core/src/agent/llm-providers/deepseek.ts +1 -1
- package/packages/core/src/agent/llm-providers/gemini.ts +4 -4
- package/packages/core/src/agent/llm-providers/groq.ts +1 -1
- package/packages/core/src/agent/llm-providers/hiveagents.ts +3 -3
- package/packages/core/src/agent/llm-providers/interface.ts +2 -2
- package/packages/core/src/agent/llm-providers/kimi.ts +1 -1
- package/packages/core/src/agent/llm-providers/minimax.ts +1 -1
- package/packages/core/src/agent/llm-providers/mistral.ts +1 -1
- package/packages/core/src/agent/llm-providers/modelscope.ts +1 -1
- package/packages/core/src/agent/llm-providers/nvidia.ts +1 -1
- package/packages/core/src/agent/llm-providers/ollama.ts +4 -4
- package/packages/core/src/agent/llm-providers/openai-compat-base.ts +9 -5
- package/packages/core/src/agent/llm-providers/openai.ts +1 -1
- package/packages/core/src/agent/llm-providers/opencode-go.ts +1 -1
- package/packages/core/src/agent/llm-providers/openrouter.ts +1 -1
- package/packages/core/src/agent/llm-providers/qwen.ts +1 -1
- package/packages/core/src/agent/llm-providers/z-ai.ts +1 -1
- package/packages/core/src/agent/mcp-result-normalizer.ts +192 -0
- package/packages/core/src/agent/playbook-selector.ts +4 -4
- package/packages/core/src/agent/prompt-builder.ts +5 -5
- package/packages/core/src/agent/proof-packet.ts +5 -5
- package/packages/core/src/agent/providers/index.ts +4 -4
- package/packages/core/src/agent/realtime-providers/gemini-live.ts +238 -0
- package/packages/core/src/agent/realtime-providers/index.ts +29 -0
- package/packages/core/src/agent/realtime-providers/interface.ts +108 -0
- package/packages/core/src/agent/reflector.ts +6 -6
- package/packages/core/src/agent/run-store.ts +8 -8
- package/packages/core/src/agent/service.ts +10 -10
- package/packages/core/src/agent/skill-selector.ts +6 -6
- package/packages/core/src/agent/thread-id.ts +71 -0
- package/packages/core/src/agent/thread-store.ts +250 -0
- package/packages/core/src/agent/tool-selector.ts +7 -5
- package/packages/core/src/agent/tracer.ts +5 -5
- package/packages/core/src/api/createAgent.ts +1 -1
- package/packages/core/src/artifacts/store.ts +84 -3
- package/packages/core/src/canvas/emitter.ts +2 -2
- package/packages/core/src/channels/telegram.ts +1 -1
- package/packages/core/src/channels/webchat.ts +1 -1
- package/packages/core/src/config/loader.ts +14 -5
- package/packages/core/src/events/agent-bus.ts +3 -3
- package/packages/core/src/events/channel-narration.ts +3 -3
- package/packages/core/src/events/event-bus.ts +1 -1
- package/packages/core/src/events/narration.ts +3 -3
- package/packages/core/src/gateway/delegation-groups.ts +4 -4
- package/packages/core/src/gateway/durable-queue.ts +5 -5
- package/packages/core/src/gateway/job-store.ts +5 -5
- package/packages/core/src/gateway/notification-inbox.ts +2 -2
- package/packages/core/src/gateway/server.ts +2 -2
- package/packages/core/src/mcp/MCPClient.ts +3 -3
- package/packages/core/src/mcp/hot-reload.ts +5 -5
- package/packages/core/src/mcp/tool-sync.ts +5 -5
- package/packages/core/src/mcp/transports/index.ts +2 -2
- package/packages/core/src/mcp/transports/sse.ts +1 -1
- package/packages/core/src/models/index.ts +36 -0
- package/packages/core/src/multimodal/index.ts +2 -2
- package/packages/core/src/multimodal/vision-service.ts +6 -6
- package/packages/core/src/plugins/loader.ts +4 -1
- package/packages/core/src/resilience/circuit-breaker.ts +16 -5
- package/packages/core/src/resilience/retry.ts +1 -1
- package/packages/core/src/scheduler/CronScheduler.ts +6 -6
- package/packages/core/src/scheduler/integration.ts +9 -9
- package/packages/core/src/sessions/index.ts +266 -0
- package/packages/core/src/storage/bootstrap.ts +34 -8
- package/packages/core/src/storage/causal-events.ts +1 -1
- package/packages/core/src/storage/collections.ts +32 -1
- package/packages/core/src/storage/crypto.ts +16 -2
- package/packages/core/src/storage/hive.ts +1 -1
- package/packages/core/src/storage/hivedb.ts +10 -1
- package/packages/core/src/storage/onboarding.ts +6 -6
- package/packages/core/src/storage/reconcile.ts +5 -5
- package/packages/core/src/storage/seed.ts +103 -13
- package/packages/core/src/storage/usage.ts +3 -3
- package/packages/core/src/swarm/AgentExecutor.ts +2 -2
- package/packages/core/src/swarm/Coordinator.ts +8 -8
- package/packages/core/src/swarm/EventBridge.ts +2 -2
- package/packages/core/src/swarm/RoleSwarm.ts +234 -0
- package/packages/core/src/swarm/TaskGraph.ts +2 -2
- package/packages/core/src/swarm/index.ts +7 -0
- package/packages/core/src/swarm/presets/HiveLearnPreset.ts +2 -2
- package/packages/core/src/swarm/presets/ResearchPreset.ts +2 -2
- package/packages/core/src/swarm/strategies/ParallelStrategy.ts +1 -1
- package/packages/core/src/swarm/strategies/PriorityStrategy.ts +3 -3
- package/packages/core/src/tools/ToolExecutor.ts +7 -3
- package/packages/core/src/tools/core/index.ts +2 -2
- package/packages/core/src/tools/cron/index.ts +4 -4
- package/packages/core/src/tools/web/artifact-inspect.ts +2 -2
- package/packages/core/src/tools/web/artifact-read.ts +162 -0
- package/packages/core/src/tools/web/browser-backend.ts +141 -44
- package/packages/core/src/tools/web/browser-click.ts +2 -2
- package/packages/core/src/tools/web/browser-extract.ts +2 -2
- package/packages/core/src/tools/web/browser-navigate.ts +2 -2
- package/packages/core/src/tools/web/browser-screenshot.ts +12 -5
- package/packages/core/src/tools/web/browser-script.ts +2 -2
- package/packages/core/src/tools/web/browser-service.ts +63 -384
- package/packages/core/src/tools/web/browser-session.ts +125 -0
- package/packages/core/src/tools/web/browser-type.ts +2 -2
- package/packages/core/src/tools/web/browser-wait.ts +2 -2
- package/packages/core/src/tools/web/computer-use.ts +553 -0
- package/packages/core/src/tools/web/index.ts +8 -1
- package/packages/core/src/tools/web/webview-backend.ts +460 -21
- package/packages/core/src/utils/index.ts +1 -0
- package/packages/core/src/utils/logger.ts +12 -4
- package/packages/core/src/utils/redact-binary.ts +17 -0
- package/packages/core/src/utils/toon.ts +1 -1
- package/packages/core/src/voice/index.ts +6 -6
- package/bun.lock +0 -859
- package/bunfig.toml +0 -9
- package/docs/API-AGENTS.md +0 -367
- package/docs/API-CONTEXT-COMPILER.md +0 -249
- package/docs/API-DAG-SCHEDULER.md +0 -273
- package/docs/API-TOOLS-SKILLS-CHANNELS.md +0 -446
- package/docs/API-WORKERS-EVENTS.md +0 -299
- package/docs/HIVE-HARNESS.md +0 -113
- package/docs/INDEX.md +0 -190
- package/docs/TEMPLATE-HIVE-APP.md +0 -360
- package/packages/cli/package.json +0 -17
- package/packages/cli/src/commands/create-app.test.ts +0 -180
- package/packages/core/package.json +0 -70
- package/packages/core/src/api/createAgent.test.ts +0 -160
- package/packages/core/src/canvas/canvas.test.ts +0 -36
- package/packages/core/src/channels/channels.test.ts +0 -18
- package/packages/core/src/ethics/EthicsGuard.test.ts +0 -108
- package/packages/core/src/gateway/gateway.test.ts +0 -38
- package/packages/core/src/memory/Scratchpad.test.ts +0 -68
- package/packages/core/src/scheduler/scheduler.test.ts +0 -15
- package/packages/core/src/skills/skills.test.ts +0 -62
- package/packages/core/src/swarm/swarm.test.ts +0 -24
- package/packages/core/src/tool-runtime/tool-runtime.test.ts +0 -99
- package/packages/core/src/tools/ToolRegistry.test.ts +0 -98
- package/packages/core/src/tools/api/api-request.test.ts +0 -164
- package/packages/core/src/tools/web/browser-service.test.ts +0 -83
- package/packages/core/src/workers/workers.test.ts +0 -41
- package/scripts/bump-version.ts +0 -248
- package/scripts/generate-skill-bundle.ts +0 -108
- package/test/acceptance-checks.test.ts +0 -403
- package/test/agent-loop-terminal-synthesis.test.ts +0 -32
- package/test/browser-backend.test.ts +0 -308
- package/test/catalog-agents-stay-enabled.test.ts +0 -117
- package/test/causal-events.test.ts +0 -117
- package/test/compaction.test.ts +0 -105
- package/test/context-compiler.test.ts +0 -269
- package/test/curator.test.ts +0 -130
- package/test/durable-queue.test.ts +0 -114
- package/test/harness-barrel.test.ts +0 -64
- package/test/hive-helpers.test.ts +0 -130
- package/test/hivedb-search.test.ts +0 -189
- package/test/internal-turns.test.ts +0 -166
- package/test/job-idempotency.test.ts +0 -68
- package/test/job-retry-backoff.test.ts +0 -184
- package/test/job-store.test.ts +0 -381
- package/test/llm-retry.test.ts +0 -97
- package/test/memory-perf.test.ts +0 -774
- package/test/minimal-loadout.test.ts +0 -78
- package/test/model-catalog.test.ts +0 -105
- package/test/preload.ts +0 -12
- package/test/reflector.test.ts +0 -320
- package/test/retention-cap.test.ts +0 -91
- package/test/retired-capabilities-pruned.test.ts +0 -192
- package/test/run-store.test.ts +0 -355
- package/test/scratchpad.test.ts +0 -74
- package/test/secrets-durability.test.ts +0 -119
- package/test/seed-model-reseed.test.ts +0 -155
- package/test/setup-agent-seed.test.ts +0 -264
- package/test/tool-inventory.test.ts +0 -65
- package/test/tool-runtime.test.ts +0 -258
- package/test/tool-selector-runtime-tools.test.ts +0 -117
- package/test/toon.test.ts +0 -429
- 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
|
-
});
|