@johpaz/hive-sdk 0.1.5 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +167 -0
- package/README.md +11 -1
- package/package.json +26 -10
- package/packages/core/src/agent/acceptance-checks.ts +16 -10
- 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 +15 -1
- 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 +226 -0
- 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 +85 -366
- 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 +851 -0
- 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 -833
- 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/agent-loop-terminal-synthesis.test.ts +0 -32
- 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/toon.test.ts +0 -429
- package/tsconfig.json +0 -42
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* generate-bundle.ts
|
|
3
|
-
* Scans all SKILL.md files from src/bundled/ and generates
|
|
4
|
-
* src/bundled-data.generated.ts with all skill data inlined as TypeScript.
|
|
5
|
-
* This file is then bundled into dist/hive.js so skills load without FS access.
|
|
6
|
-
*
|
|
7
|
-
* Usage: bun scripts/generate-skill-bundle.ts
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import * as fs from "node:fs";
|
|
11
|
-
import * as path from "node:path";
|
|
12
|
-
import { YAML } from "bun";
|
|
13
|
-
|
|
14
|
-
const BUNDLED_DIR = path.join(import.meta.dir, "../packages/core/src/skills/bundled");
|
|
15
|
-
const OUTPUT_FILE = path.join(import.meta.dir, "../packages/core/src/skills/bundled-data.generated.ts");
|
|
16
|
-
|
|
17
|
-
interface SkillEntry {
|
|
18
|
-
name: string;
|
|
19
|
-
description: string;
|
|
20
|
-
category: string;
|
|
21
|
-
version: string;
|
|
22
|
-
tools: string[];
|
|
23
|
-
triggers: string[];
|
|
24
|
-
body: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function parseFrontmatter(content: string): { frontmatter: Record<string, unknown>; body: string } {
|
|
28
|
-
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
29
|
-
if (!match) return { frontmatter: {}, body: content };
|
|
30
|
-
try {
|
|
31
|
-
const frontmatter = YAML.parse(match[1]!) as Record<string, unknown>;
|
|
32
|
-
return { frontmatter, body: match[2]! };
|
|
33
|
-
} catch {
|
|
34
|
-
return { frontmatter: {}, body: content };
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function scanDir(dir: string): SkillEntry[] {
|
|
39
|
-
const skills: SkillEntry[] = [];
|
|
40
|
-
|
|
41
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
42
|
-
if (!entry.isDirectory()) continue;
|
|
43
|
-
const entryPath = path.join(dir, entry.name);
|
|
44
|
-
const skillMdPath = path.join(entryPath, "SKILL.md");
|
|
45
|
-
|
|
46
|
-
if (fs.existsSync(skillMdPath)) {
|
|
47
|
-
const content = fs.readFileSync(skillMdPath, "utf-8");
|
|
48
|
-
const { frontmatter, body } = parseFrontmatter(content);
|
|
49
|
-
|
|
50
|
-
skills.push({
|
|
51
|
-
name: (frontmatter.name as string) ?? entry.name,
|
|
52
|
-
description: (frontmatter.description as string) ?? "",
|
|
53
|
-
category: (frontmatter.category as string) ?? "general",
|
|
54
|
-
version: String(frontmatter.version ?? "1.0.0"),
|
|
55
|
-
tools: (frontmatter.tools as string[]) ?? [],
|
|
56
|
-
triggers: (frontmatter.triggers as string[]) ?? [],
|
|
57
|
-
body,
|
|
58
|
-
});
|
|
59
|
-
} else {
|
|
60
|
-
// Category directory — recurse one level
|
|
61
|
-
skills.push(...scanDir(entryPath));
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return skills;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const skills = scanDir(BUNDLED_DIR);
|
|
69
|
-
|
|
70
|
-
const lines: string[] = [
|
|
71
|
-
"// AUTO-GENERATED by scripts/generate-skill-bundle.ts",
|
|
72
|
-
"// Do NOT edit manually — run `bun scripts/generate-skill-bundle.ts` to regenerate",
|
|
73
|
-
"",
|
|
74
|
-
"export interface BundledSkillEntry {",
|
|
75
|
-
" name: string;",
|
|
76
|
-
" description: string;",
|
|
77
|
-
" category: string;",
|
|
78
|
-
" version: string;",
|
|
79
|
-
" tools: string[];",
|
|
80
|
-
" triggers: string[];",
|
|
81
|
-
" body: string;",
|
|
82
|
-
"}",
|
|
83
|
-
"",
|
|
84
|
-
`export const BUNDLED_SKILLS_DATA: BundledSkillEntry[] = [`,
|
|
85
|
-
];
|
|
86
|
-
|
|
87
|
-
for (const skill of skills) {
|
|
88
|
-
// Escape backticks and template literal delimiters in body content
|
|
89
|
-
const escapedBody = skill.body.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
|
|
90
|
-
const escapedDescription = skill.description.replace(/\\/g, "\\\\").replace(/`/g, "\\`");
|
|
91
|
-
|
|
92
|
-
lines.push(` {`);
|
|
93
|
-
lines.push(` name: ${JSON.stringify(skill.name)},`);
|
|
94
|
-
lines.push(` description: \`${escapedDescription}\`,`);
|
|
95
|
-
lines.push(` category: ${JSON.stringify(skill.category)},`);
|
|
96
|
-
lines.push(` version: ${JSON.stringify(skill.version)},`);
|
|
97
|
-
lines.push(` tools: ${JSON.stringify(skill.tools)},`);
|
|
98
|
-
lines.push(` triggers: ${JSON.stringify(skill.triggers)},`);
|
|
99
|
-
lines.push(` body: \`${escapedBody}\`,`);
|
|
100
|
-
lines.push(` },`);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
lines.push(`];`);
|
|
104
|
-
lines.push(``);
|
|
105
|
-
|
|
106
|
-
fs.writeFileSync(OUTPUT_FILE, lines.join("\n"), "utf-8");
|
|
107
|
-
|
|
108
|
-
console.info(`[generate-bundle] ✅ Generated ${skills.length} skills → ${path.relative(process.cwd(), OUTPUT_FILE)}`);
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
AgentSynthesisError,
|
|
4
|
-
synthesizeFinalResponse,
|
|
5
|
-
} from "../packages/core/src/agent/agent-loop";
|
|
6
|
-
|
|
7
|
-
describe("agent loop terminal synthesis", () => {
|
|
8
|
-
test("retries once and returns the real model response", async () => {
|
|
9
|
-
let calls = 0;
|
|
10
|
-
const result = await synthesizeFinalResponse(async () => {
|
|
11
|
-
calls++;
|
|
12
|
-
if (calls === 1) throw new Error("temporary provider failure");
|
|
13
|
-
return "Estado real de la tarea";
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
expect(result).toBe("Estado real de la tarea");
|
|
17
|
-
expect(calls).toBe(2);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
test("fails closed after two empty or failed responses", async () => {
|
|
21
|
-
let calls = 0;
|
|
22
|
-
const operation = synthesizeFinalResponse(async () => {
|
|
23
|
-
calls++;
|
|
24
|
-
if (calls === 1) return " ";
|
|
25
|
-
throw new Error("provider unavailable");
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
await expect(operation).rejects.toBeInstanceOf(AgentSynthesisError);
|
|
29
|
-
await expect(operation).rejects.toThrow("provider unavailable");
|
|
30
|
-
expect(calls).toBe(2);
|
|
31
|
-
});
|
|
32
|
-
});
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A catalog persona is a capability of the whole hive, not a disposable
|
|
3
|
-
* worker: nothing automatic may switch one off. Disabling is the user's call,
|
|
4
|
-
* from the Agents UI (PUT /api/agents/:id). Uses HIVE_DB_PATH=":memory:".
|
|
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 } from "../packages/core/src/storage/hive";
|
|
13
|
-
import type { AgentDoc, AgentProposalDoc } from "../packages/core/src/storage/collections";
|
|
14
|
-
import { runCurator } from "../packages/core/src/agent/curator";
|
|
15
|
-
import { agentArchiveTool } from "../packages/core/src/tools/agents";
|
|
16
|
-
import { CATALOG_AGENT_IDS } from "../packages/core/src/agent/agent-catalog";
|
|
17
|
-
|
|
18
|
-
const VICTIM = "workspace_file_operator";
|
|
19
|
-
|
|
20
|
-
beforeEach(async () => {
|
|
21
|
-
closeHiveDb();
|
|
22
|
-
await ensureHiveDb();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
afterEach(() => {
|
|
26
|
-
closeHiveDb();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
async function agent(id: string): Promise<AgentDoc> {
|
|
30
|
-
const entry = await (await col<AgentDoc>("agents")).get(id);
|
|
31
|
-
if (!entry) throw new Error(`agent not found: ${id}`);
|
|
32
|
-
return entry.doc;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async function setCounters(id: string, helpful: number, harmful: number): Promise<void> {
|
|
36
|
-
const agentsCol = await col<AgentDoc>("agents");
|
|
37
|
-
const entry = (await agentsCol.get(id))!;
|
|
38
|
-
await agentsCol.put(id, { ...entry.doc, helpful_count: helpful, harmful_count: harmful }, { expectedVersion: entry.version });
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
describe("curator", () => {
|
|
42
|
-
test("never disables a catalog agent, however bad its ACE counters are", async () => {
|
|
43
|
-
await setCounters(VICTIM, 0, 25);
|
|
44
|
-
|
|
45
|
-
await runCurator();
|
|
46
|
-
|
|
47
|
-
expect((await agent(VICTIM)).enabled).toBe(true);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("raises a reviewable proposal instead of applying the change", async () => {
|
|
51
|
-
await setCounters(VICTIM, 0, 5);
|
|
52
|
-
|
|
53
|
-
await runCurator();
|
|
54
|
-
|
|
55
|
-
const proposals = (await (await col<AgentProposalDoc>("agentProposals")).findBy("agent_id", VICTIM)).map(e => e.doc);
|
|
56
|
-
expect(proposals).toHaveLength(1);
|
|
57
|
-
expect(proposals[0].type).toBe("disable_agent");
|
|
58
|
-
expect(proposals[0].status).toBe("proposed");
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test("does not archive catalog agents that were never used", async () => {
|
|
62
|
-
// Seeded with lastTraceAt=null, which the inactivity cutoff reads as epoch 0
|
|
63
|
-
expect((await agent(VICTIM)).lastTraceAt).toBeNull();
|
|
64
|
-
|
|
65
|
-
await runCurator();
|
|
66
|
-
|
|
67
|
-
for (const id of CATALOG_AGENT_IDS) {
|
|
68
|
-
expect((await agent(id)).status).not.toBe("archived");
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test("repairs a catalog agent archived by an older release on boot", async () => {
|
|
73
|
-
const agentsCol = await col<AgentDoc>("agents");
|
|
74
|
-
const existing = (await agentsCol.get(VICTIM))!;
|
|
75
|
-
await agentsCol.put(
|
|
76
|
-
VICTIM,
|
|
77
|
-
{ ...existing.doc, status: "archived" },
|
|
78
|
-
{ expectedVersion: existing.version },
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
await ensureHiveDb();
|
|
82
|
-
|
|
83
|
-
expect((await agent(VICTIM)).status).toBe("idle");
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
test("does not archive an inactive worker automatically", async () => {
|
|
87
|
-
const agentsCol = await col<AgentDoc>("agents");
|
|
88
|
-
const stale = { ...(await agent(VICTIM)), id: "stale-worker", source: "user" as const, lastTraceAt: 1 };
|
|
89
|
-
await agentsCol.put(stale.id, stale);
|
|
90
|
-
|
|
91
|
-
await runCurator();
|
|
92
|
-
|
|
93
|
-
expect((await agent("stale-worker")).status).not.toBe("archived");
|
|
94
|
-
expect((await agent("stale-worker")).enabled).toBe(true);
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
describe("agent_archive tool", () => {
|
|
99
|
-
test("refuses to archive a catalog agent", async () => {
|
|
100
|
-
const result = await agentArchiveTool.execute({ agentId: VICTIM }) as { ok: boolean; error?: string };
|
|
101
|
-
|
|
102
|
-
expect(result.ok).toBe(false);
|
|
103
|
-
expect(result.error).toContain("catalog agent");
|
|
104
|
-
expect((await agent(VICTIM)).enabled).toBe(true);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test("still archives a worker created by the coordinator", async () => {
|
|
108
|
-
const agentsCol = await col<AgentDoc>("agents");
|
|
109
|
-
const worker = { ...(await agent(VICTIM)), id: "spawned-worker", source: "user" as const };
|
|
110
|
-
await agentsCol.put(worker.id, worker);
|
|
111
|
-
|
|
112
|
-
const result = await agentArchiveTool.execute({ agentId: "spawned-worker" }) as { ok: boolean };
|
|
113
|
-
|
|
114
|
-
expect(result.ok).toBe(true);
|
|
115
|
-
expect((await agent("spawned-worker")).enabled).toBe(false);
|
|
116
|
-
});
|
|
117
|
-
});
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Track B feasibility proof: HiveDB's subscribe()/events() (wrapped by
|
|
3
|
-
* watchCausalEvents()) actually works for hive's use case — forward
|
|
4
|
-
* delivery, pattern filtering, and clean shutdown — and explicitly proves
|
|
5
|
-
* the "no historical replay" constraint documented in causal-events.ts
|
|
6
|
-
* (subscribe()/events() are a pure in-process pub/sub, not a durable read
|
|
7
|
-
* of the log — see hiveBD's reactive.rs).
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
process.env.HIVE_DB_PATH = ":memory:";
|
|
11
|
-
|
|
12
|
-
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
|
13
|
-
import { closeHiveDb, getHiveDb } from "../packages/core/src/storage/hivedb";
|
|
14
|
-
import { ensureHiveDb } from "../packages/core/src/storage/bootstrap";
|
|
15
|
-
import { resetBootId } from "../packages/core/src/storage/boot-id";
|
|
16
|
-
import { watchCausalEvents } from "../packages/core/src/storage/causal-events";
|
|
17
|
-
import type { CausalEvent } from "../packages/core/src/storage/causal-events";
|
|
18
|
-
|
|
19
|
-
beforeEach(async () => {
|
|
20
|
-
closeHiveDb();
|
|
21
|
-
resetBootId();
|
|
22
|
-
await ensureHiveDb();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
afterEach(() => {
|
|
26
|
-
closeHiveDb();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
describe("causal-events: watchCausalEvents()", () => {
|
|
30
|
-
test("delivers only events matching the pattern, forward, in order", async () => {
|
|
31
|
-
const stream = await watchCausalEvents({ agentId: "agent-x" });
|
|
32
|
-
const db = await getHiveDb();
|
|
33
|
-
|
|
34
|
-
await db.append({
|
|
35
|
-
agentId: "agent-x",
|
|
36
|
-
streamId: "s1",
|
|
37
|
-
kind: "IntentLogged",
|
|
38
|
-
payload: JSON.stringify({ actor: "agent-x", intent: "one" }),
|
|
39
|
-
});
|
|
40
|
-
await db.append({
|
|
41
|
-
agentId: "agent-other",
|
|
42
|
-
streamId: "s2",
|
|
43
|
-
kind: "IntentLogged",
|
|
44
|
-
payload: JSON.stringify({ actor: "agent-other", intent: "should be filtered out" }),
|
|
45
|
-
});
|
|
46
|
-
await db.append({
|
|
47
|
-
agentId: "agent-x",
|
|
48
|
-
streamId: "s1",
|
|
49
|
-
kind: "StateTransition",
|
|
50
|
-
payload: JSON.stringify({ description: "three" }),
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
const received: CausalEvent[] = [];
|
|
54
|
-
const iterator = stream[Symbol.asyncIterator]();
|
|
55
|
-
received.push((await iterator.next()).value!);
|
|
56
|
-
received.push((await iterator.next()).value!);
|
|
57
|
-
stream.close();
|
|
58
|
-
|
|
59
|
-
expect(received.length).toBe(2);
|
|
60
|
-
expect(received.every((e) => e.agentId === "agent-x")).toBe(true);
|
|
61
|
-
expect(received.map((e) => e.kindTag)).toEqual(["IntentLogged", "StateTransition"]);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
test("has no historical replay: events appended before subscribing are never delivered", async () => {
|
|
65
|
-
const db = await getHiveDb();
|
|
66
|
-
await db.append({
|
|
67
|
-
agentId: "agent-y",
|
|
68
|
-
streamId: "s1",
|
|
69
|
-
kind: "IntentLogged",
|
|
70
|
-
payload: JSON.stringify({ actor: "agent-y", intent: "pre-existing, must not be delivered" }),
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
// Subscribe AFTER the append above.
|
|
74
|
-
const stream = await watchCausalEvents({ agentId: "agent-y" });
|
|
75
|
-
const iterator = stream[Symbol.asyncIterator]();
|
|
76
|
-
|
|
77
|
-
const raceResult = await Promise.race([
|
|
78
|
-
iterator.next().then((r) => ({ type: "event" as const, value: r.value })),
|
|
79
|
-
new Promise<{ type: "timeout" }>((resolve) => setTimeout(() => resolve({ type: "timeout" }), 200)),
|
|
80
|
-
]);
|
|
81
|
-
expect(raceResult.type).toBe("timeout");
|
|
82
|
-
|
|
83
|
-
// Prove the stream itself still works for a genuinely new event —
|
|
84
|
-
// distinguishes "no replay" from "the stream is just broken".
|
|
85
|
-
await db.append({
|
|
86
|
-
agentId: "agent-y",
|
|
87
|
-
streamId: "s1",
|
|
88
|
-
kind: "StateTransition",
|
|
89
|
-
payload: JSON.stringify({ description: "post-subscribe, must be delivered" }),
|
|
90
|
-
});
|
|
91
|
-
const { value: event } = await iterator.next();
|
|
92
|
-
expect(event?.kindTag).toBe("StateTransition");
|
|
93
|
-
|
|
94
|
-
stream.close();
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
test("close() ends the for-await loop cleanly and doesn't block a subsequent closeHiveDb()", async () => {
|
|
98
|
-
const stream = await watchCausalEvents({ agentId: "agent-z" });
|
|
99
|
-
const db = await getHiveDb();
|
|
100
|
-
await db.append({
|
|
101
|
-
agentId: "agent-z",
|
|
102
|
-
streamId: "s1",
|
|
103
|
-
kind: "IntentLogged",
|
|
104
|
-
payload: JSON.stringify({ actor: "agent-z", intent: "one" }),
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
const collected: CausalEvent[] = [];
|
|
108
|
-
for await (const event of stream) {
|
|
109
|
-
collected.push(event);
|
|
110
|
-
stream.close();
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
expect(collected.length).toBe(1);
|
|
114
|
-
// afterEach's closeHiveDb() must not hang/throw with this subscription
|
|
115
|
-
// closed-but-not-drained — implicitly verified by the test completing.
|
|
116
|
-
});
|
|
117
|
-
});
|
package/test/compaction.test.ts
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure-function tests for compaction.ts's cut-point and transcript-rendering
|
|
3
|
-
* helpers — extracted so they're testable without a real LLM call
|
|
4
|
-
* (compactThread() itself calls callLLM and needs network).
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { describe, test, expect } from "bun:test";
|
|
8
|
-
import { findCompactionCutIndex, renderTranscript } from "../packages/core/src/agent/compaction";
|
|
9
|
-
import type { StoredMessage } from "../packages/core/src/agent/conversation-store";
|
|
10
|
-
|
|
11
|
-
function msg(partial: Partial<StoredMessage> & Pick<StoredMessage, "id" | "role" | "content">): StoredMessage {
|
|
12
|
-
return {
|
|
13
|
-
thread_id: "t",
|
|
14
|
-
channel: "webchat",
|
|
15
|
-
source: "message",
|
|
16
|
-
tool_calls_json: null,
|
|
17
|
-
tool_call_id: null,
|
|
18
|
-
reasoning_content: null,
|
|
19
|
-
content_multimodal: null,
|
|
20
|
-
token_count: 1,
|
|
21
|
-
created_at: Date.now(),
|
|
22
|
-
...partial,
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
describe("findCompactionCutIndex", () => {
|
|
27
|
-
test("finds a clean user-turn boundary near the end of the history", () => {
|
|
28
|
-
const rows: StoredMessage[] = [
|
|
29
|
-
msg({ id: 1, role: "user", content: "a" }),
|
|
30
|
-
msg({ id: 2, role: "assistant", content: "b" }),
|
|
31
|
-
msg({ id: 3, role: "user", content: "c" }),
|
|
32
|
-
msg({ id: 4, role: "assistant", content: "d" }),
|
|
33
|
-
msg({ id: 5, role: "user", content: "e" }),
|
|
34
|
-
msg({ id: 6, role: "assistant", content: "f" }),
|
|
35
|
-
msg({ id: 7, role: "user", content: "g" }),
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
// keepLastN=3 -> naive cut at index 4 (rows[4].role === "user") — already clean.
|
|
39
|
-
expect(findCompactionCutIndex(rows, 3)).toBe(4);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test("walks back to the nearest user turn when the naive cut lands on assistant", () => {
|
|
43
|
-
const rows: StoredMessage[] = [
|
|
44
|
-
msg({ id: 1, role: "user", content: "a" }),
|
|
45
|
-
msg({ id: 2, role: "user", content: "b" }), // internal event, still role:user
|
|
46
|
-
msg({ id: 3, role: "assistant", content: "c" }),
|
|
47
|
-
msg({ id: 4, role: "assistant", content: "d" }),
|
|
48
|
-
];
|
|
49
|
-
|
|
50
|
-
// keepLastN=1 -> naive cut at index 3 (assistant) -> walk back to index 1 (user).
|
|
51
|
-
expect(findCompactionCutIndex(rows, 1)).toBe(1);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test("a tail of internal events (role:user) does not block the cut point", () => {
|
|
55
|
-
const rows: StoredMessage[] = [
|
|
56
|
-
msg({ id: 1, role: "user", content: "a" }),
|
|
57
|
-
msg({ id: 2, role: "assistant", content: "b" }),
|
|
58
|
-
msg({ id: 3, role: "user", content: "El agente completó la tarea X.", source: "task_complete" }),
|
|
59
|
-
msg({ id: 4, role: "user", content: "El agente completó la tarea Y.", source: "delegation_summary" }),
|
|
60
|
-
];
|
|
61
|
-
|
|
62
|
-
// keepLastN=2 -> naive cut at index 2, which IS a user turn (the internal
|
|
63
|
-
// event) — before source-based framing existed, this was persisted as
|
|
64
|
-
// role:"system" and would have blocked the cut entirely.
|
|
65
|
-
expect(findCompactionCutIndex(rows, 2)).toBe(2);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("returns 0 when no clean boundary exists (caller should skip compaction)", () => {
|
|
69
|
-
const rows: StoredMessage[] = [
|
|
70
|
-
msg({ id: 1, role: "assistant", content: "a" }),
|
|
71
|
-
msg({ id: 2, role: "assistant", content: "b" }),
|
|
72
|
-
];
|
|
73
|
-
|
|
74
|
-
expect(findCompactionCutIndex(rows, 1)).toBe(0);
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
describe("renderTranscript", () => {
|
|
79
|
-
test("labels normal rows by role", () => {
|
|
80
|
-
const rows: StoredMessage[] = [
|
|
81
|
-
msg({ id: 1, role: "user", content: "hola" }),
|
|
82
|
-
msg({ id: 2, role: "assistant", content: "¿en qué te ayudo?" }),
|
|
83
|
-
];
|
|
84
|
-
|
|
85
|
-
const transcript = renderTranscript(rows);
|
|
86
|
-
expect(transcript).toContain("[USER]: hola");
|
|
87
|
-
expect(transcript).toContain("[ASSISTANT]: ¿en qué te ayudo?");
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test("labels internal-source rows [EVENTO INTERNO], never [USER]", () => {
|
|
91
|
-
const rows: StoredMessage[] = [
|
|
92
|
-
msg({ id: 1, role: "user", content: "El agente completó la tarea X.", source: "task_complete" }),
|
|
93
|
-
];
|
|
94
|
-
|
|
95
|
-
const transcript = renderTranscript(rows);
|
|
96
|
-
expect(transcript).toContain("[EVENTO INTERNO]: El agente completó la tarea X.");
|
|
97
|
-
expect(transcript).not.toContain("[USER]");
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test("truncates each message to maxMsgChars", () => {
|
|
101
|
-
const rows: StoredMessage[] = [msg({ id: 1, role: "user", content: "x".repeat(500) })];
|
|
102
|
-
const transcript = renderTranscript(rows, 10);
|
|
103
|
-
expect(transcript).toBe(`[USER]: ${"x".repeat(10)}`);
|
|
104
|
-
});
|
|
105
|
-
});
|