@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.
- package/CHANGELOG.md +441 -0
- package/README.md +21 -3
- package/package.json +27 -5
- package/packages/core/src/agent/acceptance-checks.ts +9 -9
- package/packages/core/src/agent/agent-catalog.ts +82 -25
- 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 +30 -10
- package/packages/core/src/agent/context-compiler.ts +63 -36
- package/packages/core/src/agent/conversation-store.ts +167 -9
- package/packages/core/src/agent/curator.ts +16 -7
- 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 +40 -1
- package/packages/core/src/agent/llm-providers/ollama.ts +4 -4
- package/packages/core/src/agent/llm-providers/openai-compat-base.ts +47 -7
- 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 +22 -7
- 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 +39 -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 +38 -15
- 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 +293 -0
- package/packages/core/src/agent/tool-selector.ts +9 -5
- package/packages/core/src/agent/tracer.ts +5 -5
- package/packages/core/src/api/createAgent.ts +67 -2
- package/packages/core/src/artifacts/index.ts +15 -0
- package/packages/core/src/artifacts/store.ts +161 -5
- package/packages/core/src/canvas/emitter.ts +2 -2
- package/packages/core/src/canvas/index.ts +9 -0
- 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/ethics/EthicsGuard.ts +7 -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/index.ts +18 -0
- package/packages/core/src/events/narration.ts +3 -3
- package/packages/core/src/events/tool-narration.ts +4 -0
- package/packages/core/src/gateway/channel-notify.ts +103 -6
- package/packages/core/src/gateway/delegation-groups.ts +4 -4
- package/packages/core/src/gateway/durable-queue.ts +18 -6
- package/packages/core/src/gateway/index.ts +3 -0
- package/packages/core/src/gateway/job-store.ts +11 -5
- package/packages/core/src/gateway/notification-inbox.ts +2 -2
- package/packages/core/src/gateway/server.ts +2 -2
- package/packages/core/src/harness/executors.ts +493 -0
- package/packages/core/src/harness/index.ts +12 -2
- package/packages/core/src/hooks/index.ts +203 -0
- package/packages/core/src/images/index.ts +161 -0
- package/packages/core/src/index.ts +1 -0
- 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 +51 -19
- package/packages/core/src/plugins/loader.ts +4 -1
- package/packages/core/src/resilience/circuit-breaker.ts +16 -5
- package/packages/core/src/resilience/index.ts +13 -0
- package/packages/core/src/resilience/retry.ts +1 -1
- package/packages/core/src/scheduler/CronScheduler.ts +54 -27
- package/packages/core/src/scheduler/cron/expression.ts +165 -0
- package/packages/core/src/scheduler/cron/index.ts +10 -0
- package/packages/core/src/scheduler/cron/job.ts +339 -0
- package/packages/core/src/scheduler/cron/next-run.ts +121 -0
- package/packages/core/src/scheduler/cron/zoned-time.ts +138 -0
- package/packages/core/src/scheduler/index.ts +21 -3
- package/packages/core/src/scheduler/integration.ts +25 -14
- package/packages/core/src/scheduler/types.ts +3 -18
- package/packages/core/src/services/agents.ts +268 -0
- package/packages/core/src/services/cron.ts +257 -0
- package/packages/core/src/services/endpoints.ts +289 -0
- package/packages/core/src/services/ethics.ts +107 -0
- package/packages/core/src/services/images.ts +212 -0
- package/packages/core/src/services/index.ts +112 -0
- package/packages/core/src/services/mcp.ts +201 -0
- package/packages/core/src/services/memory.ts +133 -0
- package/packages/core/src/services/models.ts +179 -0
- package/packages/core/src/services/providers.ts +152 -0
- package/packages/core/src/services/setup.ts +222 -0
- package/packages/core/src/services/skills.ts +241 -0
- package/packages/core/src/services/swarms.ts +307 -0
- package/packages/core/src/services/tools.ts +106 -0
- package/packages/core/src/sessions/index.ts +268 -0
- package/packages/core/src/sessions/resolve.ts +108 -0
- package/packages/core/src/skills/SkillLoader.ts +8 -1
- package/packages/core/src/skills/bundled/artifacts/artifact_reader/SKILL.md +105 -0
- package/packages/core/src/skills/bundled/cron_manager/SKILL.md +21 -11
- package/packages/core/src/skills/bundled/images/image_editor/SKILL.md +120 -0
- package/packages/core/src/skills/bundled/web/browser_automate/SKILL.md +12 -3
- package/packages/core/src/skills/bundled/web/browser_scrape/SKILL.md +22 -7
- package/packages/core/src/skills/bundled-data.generated.ts +110 -12
- package/packages/core/src/storage/bootstrap.ts +107 -12
- package/packages/core/src/storage/causal-events.ts +1 -1
- package/packages/core/src/storage/collections.ts +138 -2
- package/packages/core/src/storage/crypto.ts +35 -4
- package/packages/core/src/storage/hive.ts +1 -1
- package/packages/core/src/storage/hivedb.ts +10 -1
- package/packages/core/src/storage/index.ts +2 -1
- package/packages/core/src/storage/onboarding.ts +61 -45
- package/packages/core/src/storage/reconcile.ts +11 -6
- package/packages/core/src/storage/seed.ts +191 -23
- 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/swarm/types.ts +3 -18
- package/packages/core/src/tool-runtime/embedded-worker.generated.ts +21 -0
- package/packages/core/src/tool-runtime/index.ts +129 -14
- package/packages/core/src/tools/ToolExecutor.ts +7 -3
- package/packages/core/src/tools/agents/index.ts +18 -60
- package/packages/core/src/tools/cli/index.ts +55 -0
- package/packages/core/src/tools/core/index.ts +52 -4
- package/packages/core/src/tools/cron/index.ts +8 -8
- package/packages/core/src/tools/images/index.ts +130 -0
- package/packages/core/src/tools/index.ts +14 -1
- package/packages/core/src/tools/office/office-escribir-xlsx.ts +2 -1
- package/packages/core/src/tools/office/office-leer-xlsx.ts +2 -1
- package/packages/core/src/tools/office/xlsx-loader.ts +19 -0
- 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,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,403 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* acceptance-checks — el reemplazo determinístico del agente acceptance_verifier.
|
|
3
|
-
*
|
|
4
|
-
* Es el punto donde se decide si la entrega de un worker se acepta, se rechaza o
|
|
5
|
-
* se manda a juicio del coordinador, y corre sin una sola llamada al LLM. El
|
|
6
|
-
* tri-estado importa: "unchecked" NO es un fallo, es "acá no aplicó nada
|
|
7
|
-
* determinístico, decidilo vos" — confundirlo con "failed" haría que el
|
|
8
|
-
* coordinador reabra tareas que estaban bien.
|
|
9
|
-
*
|
|
10
|
-
* Usa HIVE_DB_PATH=":memory:".
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
process.env.HIVE_DB_PATH = ":memory:";
|
|
14
|
-
|
|
15
|
-
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
|
16
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
17
|
-
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
18
|
-
import { tmpdir } from "node:os";
|
|
19
|
-
import { join } from "node:path";
|
|
20
|
-
import { closeHiveDb } from "../packages/core/src/storage/hivedb";
|
|
21
|
-
import { ensureHiveDb } from "../packages/core/src/storage/bootstrap";
|
|
22
|
-
import { col } from "../packages/core/src/storage/hive";
|
|
23
|
-
import type { AgentDoc, ArtifactDoc } from "../packages/core/src/storage/collections";
|
|
24
|
-
import {
|
|
25
|
-
runAcceptanceChecks,
|
|
26
|
-
recordAgentOutcome,
|
|
27
|
-
sanitizeDiagnostic,
|
|
28
|
-
} from "../packages/core/src/agent/acceptance-checks";
|
|
29
|
-
|
|
30
|
-
let scratch: string;
|
|
31
|
-
|
|
32
|
-
beforeEach(async () => {
|
|
33
|
-
closeHiveDb();
|
|
34
|
-
await ensureHiveDb();
|
|
35
|
-
scratch = mkdtempSync(join(tmpdir(), "hive-acceptance-"));
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
afterEach(() => {
|
|
39
|
-
closeHiveDb();
|
|
40
|
-
rmSync(scratch, { recursive: true, force: true });
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
/** Crea un artifact real en disco + su fila, tal que inspectArtifact lo valide. */
|
|
44
|
-
async function seedArtifact(overrides: Partial<ArtifactDoc> = {}): Promise<string> {
|
|
45
|
-
const id = randomUUID();
|
|
46
|
-
const bytes = Buffer.from(`contenido de ${id}`);
|
|
47
|
-
const path = join(scratch, `${id}.txt`);
|
|
48
|
-
writeFileSync(path, bytes);
|
|
49
|
-
|
|
50
|
-
const doc: ArtifactDoc = {
|
|
51
|
-
id,
|
|
52
|
-
run_id: null,
|
|
53
|
-
task_id: null,
|
|
54
|
-
user_id: "tester",
|
|
55
|
-
kind: "document",
|
|
56
|
-
path,
|
|
57
|
-
mime_type: "text/plain",
|
|
58
|
-
size: bytes.byteLength,
|
|
59
|
-
sha256: createHash("sha256").update(bytes).digest("hex"),
|
|
60
|
-
width: null,
|
|
61
|
-
height: null,
|
|
62
|
-
status: "active",
|
|
63
|
-
created_at: Date.now(),
|
|
64
|
-
expires_at: Date.now() + 3_600_000,
|
|
65
|
-
expired_at: null,
|
|
66
|
-
...overrides,
|
|
67
|
-
};
|
|
68
|
-
await (await col<ArtifactDoc>("artifacts")).put(id, doc);
|
|
69
|
-
return id;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async function seedAgent(id: string, helpful = 0, harmful = 0): Promise<void> {
|
|
73
|
-
const agents = await col<AgentDoc>("agents");
|
|
74
|
-
await agents.put(id, {
|
|
75
|
-
id,
|
|
76
|
-
name: id,
|
|
77
|
-
role: "worker",
|
|
78
|
-
helpful_count: helpful,
|
|
79
|
-
harmful_count: harmful,
|
|
80
|
-
updated_at: 0,
|
|
81
|
-
} as AgentDoc);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// ─── sanitizeDiagnostic ───────────────────────────────────────────────────────
|
|
85
|
-
|
|
86
|
-
describe("sanitizeDiagnostic", () => {
|
|
87
|
-
test("redacta credenciales antes de que el detalle llegue al prompt del coordinador", () => {
|
|
88
|
-
const out = sanitizeDiagnostic(
|
|
89
|
-
'api_key: sk-live-abcdef123 y {"token":"ghp_secreto"} y authorization: Bearer xyz',
|
|
90
|
-
);
|
|
91
|
-
expect(out).not.toContain("sk-live-abcdef123");
|
|
92
|
-
expect(out).not.toContain("ghp_secreto");
|
|
93
|
-
expect(out).not.toContain("xyz");
|
|
94
|
-
expect(out).toContain("[REDACTED]");
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
test("no deja el token detrás del esquema de auth", () => {
|
|
98
|
-
// Regresión: la versión anterior consumía sólo "Bearer" y publicaba el token.
|
|
99
|
-
expect(sanitizeDiagnostic("authorization: Bearer ghp_tokenreal")).toBe(
|
|
100
|
-
"authorization: Bearer [REDACTED]",
|
|
101
|
-
);
|
|
102
|
-
expect(sanitizeDiagnostic("Authorization: Basic dXNlcjpwYXNz")).toBe(
|
|
103
|
-
"Authorization: Basic [REDACTED]",
|
|
104
|
-
);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test("cubre las variantes de nombre de la credencial", () => {
|
|
108
|
-
for (const key of ["api_key", "api-key", "apikey", "TOKEN", "Secret", "authorization"]) {
|
|
109
|
-
expect(sanitizeDiagnostic(`${key}=valor_sensible`)).not.toContain("valor_sensible");
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
test("trunca al límite — un check tool verborrágico no puede inundar el contexto", () => {
|
|
114
|
-
expect(sanitizeDiagnostic("a".repeat(5000))).toHaveLength(1000);
|
|
115
|
-
expect(sanitizeDiagnostic("a".repeat(5000), 42)).toHaveLength(42);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test("deja pasar texto inocuo sin tocarlo", () => {
|
|
119
|
-
const inocuo = "El archivo informe.pdf tiene 12 páginas";
|
|
120
|
-
expect(sanitizeDiagnostic(inocuo)).toBe(inocuo);
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
// ─── delivery gate ────────────────────────────────────────────────────────────
|
|
125
|
-
|
|
126
|
-
describe("runAcceptanceChecks — delivery gate", () => {
|
|
127
|
-
test("una entrega vacía falla sin necesidad de criterios", async () => {
|
|
128
|
-
const checks = await runAcceptanceChecks({ objective: "algo", delivery: "" });
|
|
129
|
-
|
|
130
|
-
expect(checks.status).toBe("failed");
|
|
131
|
-
expect(checks.results).toHaveLength(1);
|
|
132
|
-
expect(checks.results[0]).toMatchObject({
|
|
133
|
-
criterion_id: "delivery",
|
|
134
|
-
check: "delivery_gate",
|
|
135
|
-
met: false,
|
|
136
|
-
detail: "Empty delivery",
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
test("una entrega de puro whitespace cuenta como vacía", async () => {
|
|
141
|
-
const checks = await runAcceptanceChecks({ objective: "algo", delivery: " \n\t " });
|
|
142
|
-
expect(checks.status).toBe("failed");
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
test("el worker que declara `status: failed` se rechaza solo", async () => {
|
|
146
|
-
const checks = await runAcceptanceChecks({
|
|
147
|
-
objective: "algo",
|
|
148
|
-
delivery: "Intenté descargarlo pero el sitio pide login.\n- status: failed",
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
expect(checks.status).toBe("failed");
|
|
152
|
-
expect(checks.results[0].detail).toContain("failed");
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
test("`status: completed` no dispara el gate", async () => {
|
|
156
|
-
const checks = await runAcceptanceChecks({
|
|
157
|
-
objective: "algo",
|
|
158
|
-
delivery: "Listo.\nstatus: completed",
|
|
159
|
-
});
|
|
160
|
-
expect(checks.results.some((r) => r.check === "delivery_gate")).toBe(false);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
test("la palabra 'failed' en prosa no rechaza una entrega buena", async () => {
|
|
164
|
-
// El gate se ancla a "status:" al principio de línea. Sin eso, cualquier
|
|
165
|
-
// worker que narre un intento fallido intermedio se auto-rechazaría.
|
|
166
|
-
const checks = await runAcceptanceChecks({
|
|
167
|
-
objective: "algo",
|
|
168
|
-
delivery: "El primer intento failed, reintenté y salió bien. Adjunto el resultado.",
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
expect(checks.status).toBe("unchecked");
|
|
172
|
-
expect(checks.results).toHaveLength(0);
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
// ─── tri-estado ───────────────────────────────────────────────────────────────
|
|
177
|
-
|
|
178
|
-
describe("runAcceptanceChecks — tri-estado", () => {
|
|
179
|
-
test("sin nada determinístico aplicable devuelve 'unchecked', no 'failed'", async () => {
|
|
180
|
-
const checks = await runAcceptanceChecks({
|
|
181
|
-
objective: "resumir el documento",
|
|
182
|
-
delivery: "Acá va el resumen del documento.",
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
expect(checks.status).toBe("unchecked");
|
|
186
|
-
expect(checks.results).toHaveLength(0);
|
|
187
|
-
expect(checks.summary).toContain("requiere juicio del coordinador");
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
test("un criterio sin checkTool no genera resultado — queda para el coordinador", async () => {
|
|
191
|
-
const checks = await runAcceptanceChecks({
|
|
192
|
-
objective: "resumir",
|
|
193
|
-
delivery: "resumen",
|
|
194
|
-
acceptance: [{ id: "c1", description: "el resumen debe ser fiel al original" }],
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
expect(checks.status).toBe("unchecked");
|
|
198
|
-
expect(checks.results).toHaveLength(0);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
test("un checkTool que no está en el registry es 'unchecked', no un fallo", async () => {
|
|
202
|
-
// Un nombre de tool mal escrito en el criterio no debe hacer fracasar una
|
|
203
|
-
// entrega correcta: no sabemos nada, y eso es distinto de saber que está mal.
|
|
204
|
-
const checks = await runAcceptanceChecks({
|
|
205
|
-
objective: "verificar",
|
|
206
|
-
delivery: "hecho",
|
|
207
|
-
acceptance: [{ id: "c1", description: "x", checkTool: "tool_que_no_existe" }],
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
expect(checks.results).toHaveLength(1);
|
|
211
|
-
expect(checks.results[0]).toMatchObject({
|
|
212
|
-
criterion_id: "c1",
|
|
213
|
-
check: "tool_que_no_existe",
|
|
214
|
-
met: null,
|
|
215
|
-
detail: "Check tool not found in registry",
|
|
216
|
-
});
|
|
217
|
-
expect(checks.status).toBe("unchecked");
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
test("un fallo pesa más que un acierto", async () => {
|
|
221
|
-
const good = await seedArtifact();
|
|
222
|
-
const checks = await runAcceptanceChecks({
|
|
223
|
-
objective: "entregar dos archivos",
|
|
224
|
-
delivery: "listo",
|
|
225
|
-
evidence: [
|
|
226
|
-
`artifact_id: ${good}`,
|
|
227
|
-
`artifact_id: ${randomUUID()}`, // no existe
|
|
228
|
-
],
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
expect(checks.results).toHaveLength(2);
|
|
232
|
-
expect(checks.results.some((r) => r.met === true)).toBe(true);
|
|
233
|
-
expect(checks.results.some((r) => r.met === false)).toBe(true);
|
|
234
|
-
expect(checks.status).toBe("failed");
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
// ─── artifacts ────────────────────────────────────────────────────────────────
|
|
239
|
-
|
|
240
|
-
describe("runAcceptanceChecks — artifacts", () => {
|
|
241
|
-
test("un artifact íntegro pasa", async () => {
|
|
242
|
-
const id = await seedArtifact();
|
|
243
|
-
const checks = await runAcceptanceChecks({
|
|
244
|
-
objective: "generar el informe",
|
|
245
|
-
delivery: "informe generado",
|
|
246
|
-
evidence: [`Adjunto el informe. artifact_id: ${id}`],
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
expect(checks.status).toBe("passed");
|
|
250
|
-
expect(checks.results[0]).toMatchObject({ check: "artifact_inspect", met: true });
|
|
251
|
-
expect(checks.results[0].detail).toContain(id);
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
test("un artifact_id inventado se detecta — es el modo de falla que motivó el check", async () => {
|
|
255
|
-
const fantasma = randomUUID();
|
|
256
|
-
const checks = await runAcceptanceChecks({
|
|
257
|
-
objective: "generar el informe",
|
|
258
|
-
delivery: "informe generado",
|
|
259
|
-
evidence: [`artifact_id: ${fantasma}`],
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
expect(checks.status).toBe("failed");
|
|
263
|
-
expect(checks.results[0].detail).toContain("Artifact not found");
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
test("un artifact cuyo binario cambió bajo los pies falla el hash", async () => {
|
|
267
|
-
const id = await seedArtifact();
|
|
268
|
-
const artifacts = await col<ArtifactDoc>("artifacts");
|
|
269
|
-
const entry = (await artifacts.get(id))!;
|
|
270
|
-
writeFileSync(entry.doc.path, "contenido manipulado");
|
|
271
|
-
|
|
272
|
-
const checks = await runAcceptanceChecks({
|
|
273
|
-
objective: "x",
|
|
274
|
-
delivery: "y",
|
|
275
|
-
evidence: [`artifact_id: ${id}`],
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
expect(checks.status).toBe("failed");
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
test("un artifact expirado no cuenta como entregado", async () => {
|
|
282
|
-
const id = await seedArtifact({ status: "expired", expired_at: Date.now() });
|
|
283
|
-
const checks = await runAcceptanceChecks({
|
|
284
|
-
objective: "x",
|
|
285
|
-
delivery: "y",
|
|
286
|
-
evidence: [`artifact_id: ${id}`],
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
expect(checks.status).toBe("failed");
|
|
290
|
-
expect(checks.results[0].detail).toContain("expired");
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
test("el mismo artifact citado varias veces se inspecciona una sola vez", async () => {
|
|
294
|
-
const id = await seedArtifact();
|
|
295
|
-
const checks = await runAcceptanceChecks({
|
|
296
|
-
objective: "x",
|
|
297
|
-
delivery: "y",
|
|
298
|
-
evidence: [`artifact_id: ${id}`, `de nuevo artifact_id: ${id}`],
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
expect(checks.results.filter((r) => r.check === "artifact_inspect")).toHaveLength(1);
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
test("evidencia sin artifact_id no inventa checks", async () => {
|
|
305
|
-
const checks = await runAcceptanceChecks({
|
|
306
|
-
objective: "x",
|
|
307
|
-
delivery: "y",
|
|
308
|
-
evidence: ["Busqué en la web y encontré tres fuentes."],
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
expect(checks.results).toHaveLength(0);
|
|
312
|
-
expect(checks.status).toBe("unchecked");
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
test("reconoce el artifact_id entrecomillado y en mayúsculas", async () => {
|
|
316
|
-
const id = await seedArtifact();
|
|
317
|
-
const checks = await runAcceptanceChecks({
|
|
318
|
-
objective: "x",
|
|
319
|
-
delivery: "y",
|
|
320
|
-
evidence: [`ARTIFACT_ID: "${id}"`],
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
expect(checks.results).toHaveLength(1);
|
|
324
|
-
expect(checks.results[0].met).toBe(true);
|
|
325
|
-
});
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
// ─── summary ──────────────────────────────────────────────────────────────────
|
|
329
|
-
|
|
330
|
-
describe("runAcceptanceChecks — summary", () => {
|
|
331
|
-
test("el summary marca cada resultado con su símbolo y redacta credenciales", async () => {
|
|
332
|
-
const ok = await seedArtifact();
|
|
333
|
-
const checks = await runAcceptanceChecks({
|
|
334
|
-
objective: "x",
|
|
335
|
-
delivery: "y",
|
|
336
|
-
evidence: [`artifact_id: ${ok}`, `artifact_id: ${randomUUID()}`],
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
expect(checks.summary).toContain("✅");
|
|
340
|
-
expect(checks.summary).toContain("❌");
|
|
341
|
-
expect(checks.summary).toContain("artifact_inspect");
|
|
342
|
-
});
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
// ─── recordAgentOutcome ───────────────────────────────────────────────────────
|
|
346
|
-
|
|
347
|
-
describe("recordAgentOutcome", () => {
|
|
348
|
-
test("suma al contador correcto y no toca el otro", async () => {
|
|
349
|
-
await seedAgent("worker_x", 2, 1);
|
|
350
|
-
|
|
351
|
-
await recordAgentOutcome("worker_x", "helpful");
|
|
352
|
-
let doc = (await (await col<AgentDoc>("agents")).get("worker_x"))!.doc;
|
|
353
|
-
expect(doc.helpful_count).toBe(3);
|
|
354
|
-
expect(doc.harmful_count).toBe(1);
|
|
355
|
-
|
|
356
|
-
await recordAgentOutcome("worker_x", "harmful");
|
|
357
|
-
doc = (await (await col<AgentDoc>("agents")).get("worker_x"))!.doc;
|
|
358
|
-
expect(doc.helpful_count).toBe(3);
|
|
359
|
-
expect(doc.harmful_count).toBe(2);
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
test("arranca desde 0 cuando el agente nunca tuvo contadores", async () => {
|
|
363
|
-
const agents = await col<AgentDoc>("agents");
|
|
364
|
-
await agents.put("worker_nuevo", { id: "worker_nuevo", name: "nuevo", role: "worker" } as AgentDoc);
|
|
365
|
-
|
|
366
|
-
await recordAgentOutcome("worker_nuevo", "helpful");
|
|
367
|
-
|
|
368
|
-
const doc = (await agents.get("worker_nuevo"))!.doc;
|
|
369
|
-
expect(doc.helpful_count).toBe(1);
|
|
370
|
-
expect(doc.harmful_count).toBe(0);
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
test("refresca updated_at", async () => {
|
|
374
|
-
await seedAgent("worker_ts");
|
|
375
|
-
await recordAgentOutcome("worker_ts", "helpful");
|
|
376
|
-
const doc = (await (await col<AgentDoc>("agents")).get("worker_ts"))!.doc;
|
|
377
|
-
expect(doc.updated_at).toBeGreaterThan(0);
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
test("un agentId ausente o desconocido es un no-op silencioso", async () => {
|
|
381
|
-
// El llamador no siempre sabe qué agente ejecutó (delegación anónima); esto
|
|
382
|
-
// corre en la ruta de cierre de una tarea y no puede tumbarla.
|
|
383
|
-
await recordAgentOutcome(null, "helpful");
|
|
384
|
-
await recordAgentOutcome(undefined, "harmful");
|
|
385
|
-
await recordAgentOutcome("", "helpful");
|
|
386
|
-
await recordAgentOutcome("no_existe", "harmful");
|
|
387
|
-
|
|
388
|
-
expect(await (await col<AgentDoc>("agents")).get("no_existe")).toBeUndefined();
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
test("escrituras concurrentes sobre el mismo agente no se pisan (OCC)", async () => {
|
|
392
|
-
await seedAgent("worker_race");
|
|
393
|
-
|
|
394
|
-
await Promise.all([
|
|
395
|
-
recordAgentOutcome("worker_race", "helpful"),
|
|
396
|
-
recordAgentOutcome("worker_race", "helpful"),
|
|
397
|
-
recordAgentOutcome("worker_race", "harmful"),
|
|
398
|
-
]);
|
|
399
|
-
|
|
400
|
-
const doc = (await (await col<AgentDoc>("agents")).get("worker_race"))!.doc;
|
|
401
|
-
expect(doc.helpful_count + doc.harmful_count).toBe(3);
|
|
402
|
-
});
|
|
403
|
-
});
|
|
@@ -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
|
-
});
|