@johpaz/hive-sdk 0.1.3 → 0.1.5
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 +97 -0
- package/README.md +78 -23
- package/bunfig.toml +4 -2
- package/docs/API-AGENTS.md +78 -27
- package/docs/API-CONTEXT-COMPILER.md +31 -34
- package/docs/API-TOOLS-SKILLS-CHANNELS.md +58 -22
- package/docs/HIVE-HARNESS.md +1 -1
- package/docs/INDEX.md +4 -4
- package/docs/TEMPLATE-HIVE-APP.md +10 -10
- package/package.json +9 -4
- package/packages/cli/package.json +2 -2
- package/packages/cli/src/commands/create-app.test.ts +36 -7
- package/packages/cli/src/commands/init.ts +3 -3
- package/packages/cli/src/commands/run.ts +1 -1
- package/packages/cli/src/commands/test.ts +37 -25
- package/packages/cli/src/commands/trace.ts +30 -28
- package/packages/cli/templates/hive-app/.env.example +10 -2
- package/packages/cli/templates/hive-app/README.md +103 -0
- package/packages/cli/templates/hive-app/hive.config.ts +9 -3
- package/packages/cli/templates/hive-app/src/agents/coordinator.ts +8 -1
- package/packages/cli/templates/hive-app/src/main.ts +12 -19
- package/packages/core/package.json +5 -4
- package/packages/core/src/agent/acceptance-checks.ts +166 -0
- package/packages/core/src/agent/agent-catalog.ts +348 -0
- package/packages/core/src/agent/agent-loop.ts +1373 -0
- package/packages/core/src/agent/capability-search.ts +186 -0
- package/packages/core/src/agent/catalog-selector.ts +103 -0
- package/packages/core/src/agent/{Compaction.ts → compaction.ts} +86 -63
- package/packages/core/src/agent/context-compiler.ts +689 -0
- package/packages/core/src/agent/conversation-store.ts +381 -0
- package/packages/core/src/agent/curator.ts +276 -0
- package/packages/core/src/agent/delegation-runtime.ts +241 -0
- package/packages/core/src/agent/goal-runner.ts +323 -0
- package/packages/core/src/agent/index.ts +17 -12
- package/packages/core/src/agent/llm-client.ts +266 -0
- package/packages/core/src/agent/llm-providers/anthropic.ts +264 -0
- package/packages/core/src/agent/llm-providers/deepseek.ts +8 -0
- package/packages/core/src/agent/{providers → llm-providers}/gemini.ts +98 -60
- package/packages/core/src/agent/llm-providers/groq.ts +5 -0
- package/packages/core/src/agent/llm-providers/hiveagents.ts +253 -0
- package/packages/core/src/agent/{providers → llm-providers}/interface.ts +73 -13
- package/packages/core/src/agent/llm-providers/kimi.ts +8 -0
- package/packages/core/src/agent/llm-providers/minimax.ts +13 -0
- package/packages/core/src/agent/llm-providers/mistral.ts +5 -0
- package/packages/core/src/agent/llm-providers/modelscope.ts +5 -0
- package/packages/core/src/agent/llm-providers/nvidia.ts +5 -0
- package/packages/core/src/agent/{providers → llm-providers}/ollama.ts +31 -5
- package/packages/core/src/agent/llm-providers/openai-compat-base.ts +418 -0
- package/packages/core/src/agent/llm-providers/openai.ts +5 -0
- package/packages/core/src/agent/llm-providers/opencode-go.ts +9 -0
- package/packages/core/src/agent/llm-providers/openrouter.ts +5 -0
- package/packages/core/src/agent/llm-providers/qwen.ts +5 -0
- package/packages/core/src/agent/llm-providers/z-ai.ts +5 -0
- package/packages/core/src/agent/minimal-loadout.ts +47 -0
- package/packages/core/src/agent/playbook-selector.ts +119 -0
- package/packages/core/src/agent/{PromptBuilder.ts → prompt-builder.ts} +21 -22
- package/packages/core/src/{harness → agent}/proof-packet.ts +16 -21
- package/packages/core/src/agent/providers/index.ts +35 -16
- package/packages/core/src/agent/reflector.ts +320 -0
- package/packages/core/src/agent/routing-intent.ts +22 -0
- package/packages/core/src/{harness → agent}/run-epoch.ts +4 -3
- package/packages/core/src/{harness → agent}/run-store.ts +142 -81
- package/packages/core/src/agent/{Service.ts → service.ts} +37 -26
- package/packages/core/src/agent/skill-selector.ts +374 -0
- package/packages/core/src/agent/stuck-loop.ts +209 -0
- package/packages/core/src/agent/{selectors/ToolSelector.ts → tool-selector.ts} +188 -178
- package/packages/core/src/{ace/Tracer.ts → agent/tracer.ts} +37 -27
- package/packages/core/src/api/createAgent.test.ts +139 -27
- package/packages/core/src/api/createAgent.ts +232 -44
- package/packages/core/src/artifacts/store.ts +162 -0
- package/packages/core/src/canvas/canvas-manager.ts +161 -0
- package/packages/core/src/canvas/canvas.test.ts +8 -4
- package/packages/core/src/canvas/emitter.ts +131 -80
- package/packages/core/src/canvas/index.ts +1 -3
- package/packages/core/src/channels/base.ts +9 -1
- package/packages/core/src/channels/discord.ts +5 -4
- package/packages/core/src/channels/manager.ts +122 -30
- package/packages/core/src/channels/slack.ts +5 -4
- package/packages/core/src/channels/telegram.ts +36 -6
- package/packages/core/src/channels/webchat.ts +11 -10
- package/packages/core/src/channels/whatsapp.ts +23 -7
- package/packages/core/src/config/index.ts +13 -2
- package/packages/core/src/config/loader.ts +71 -29
- package/packages/core/src/ethics/EthicsGuard.test.ts +90 -36
- package/packages/core/src/ethics/EthicsGuard.ts +51 -47
- package/packages/core/src/events/agent-bus.ts +44 -68
- package/packages/core/src/events/channel-narration.ts +150 -0
- package/packages/core/src/events/narration.ts +82 -0
- package/packages/core/src/events/tool-narration.ts +62 -0
- package/packages/core/src/gateway/delegation-groups.ts +258 -0
- package/packages/core/src/{harness → gateway}/durable-queue.ts +102 -42
- package/packages/core/src/{harness → gateway}/job-store.ts +85 -48
- package/packages/core/src/gateway/lane-queue.ts +173 -0
- package/packages/core/src/gateway/notification-inbox.ts +57 -0
- package/packages/core/src/gateway/server.ts +1 -1
- package/packages/core/src/harness/index.ts +46 -27
- package/packages/core/src/index.ts +33 -20
- package/packages/core/src/mcp/hot-reload.ts +32 -23
- package/packages/core/src/mcp/index.ts +6 -3
- package/packages/core/src/mcp/singleton.ts +1 -4
- package/packages/core/src/mcp/tool-sync.ts +138 -0
- package/packages/core/src/memory/Scratchpad.test.ts +39 -20
- package/packages/core/src/memory/Scratchpad.ts +27 -34
- package/packages/core/src/multimodal/vision-service.ts +44 -38
- package/packages/core/src/resilience/retry.ts +95 -0
- package/packages/core/src/scheduler/CronScheduler.ts +334 -287
- package/packages/core/src/scheduler/index.ts +9 -7
- package/packages/core/src/scheduler/integration.ts +46 -26
- package/packages/core/src/scheduler/scheduler.test.ts +9 -13
- package/packages/core/src/scheduler/types.ts +7 -2
- package/packages/core/src/security/Pairing.ts +1 -1
- package/packages/core/src/skills/bundled/a2ui/a2ui_dashboard/SKILL.md +176 -0
- package/packages/core/src/skills/bundled/a2ui/a2ui_form/SKILL.md +202 -0
- package/packages/core/src/skills/bundled/a2ui/a2ui_interactive/SKILL.md +206 -0
- package/packages/core/src/skills/bundled/agents/agent_spawner/SKILL.md +173 -0
- package/packages/core/src/skills/bundled/agents/memory_manager/SKILL.md +143 -0
- package/packages/core/src/skills/bundled/agents/research_and_remember/SKILL.md +139 -0
- package/packages/core/src/skills/bundled/agents/task_orchestrator/SKILL.md +98 -0
- package/packages/core/src/skills/bundled/api/api_client/SKILL.md +132 -0
- package/packages/core/src/skills/bundled/cli/cli_pipeline/SKILL.md +135 -0
- package/packages/core/src/skills/bundled/cli/cli_safe_exec/SKILL.md +125 -0
- package/packages/core/src/skills/bundled/cli/software_engineering/SKILL.md +23 -0
- package/packages/core/src/skills/bundled/cron_manager/SKILL.md +188 -0
- package/packages/core/src/skills/bundled/cron_reminder/SKILL.md +112 -0
- package/packages/core/src/skills/bundled/filesystem/file_manager/SKILL.md +118 -0
- package/packages/core/src/skills/bundled/filesystem/file_read_and_summarize/SKILL.md +109 -0
- package/packages/core/src/skills/bundled/filesystem/file_writer/SKILL.md +129 -0
- package/packages/core/src/skills/bundled/filesystem/workspace_file_operator/SKILL.md +22 -0
- package/packages/core/src/skills/bundled/office/office_document_manager/SKILL.md +262 -0
- package/packages/core/src/skills/bundled/search_knowledge/capability_discovery/SKILL.md +75 -0
- package/packages/core/src/skills/bundled/web/browser_automate/SKILL.md +120 -0
- package/packages/core/src/skills/bundled/web/browser_scrape/SKILL.md +109 -0
- package/packages/core/src/skills/bundled/web/web_monitor/SKILL.md +127 -0
- package/packages/core/src/skills/bundled/web/web_research/SKILL.md +119 -0
- package/packages/core/src/skills/bundled-data.generated.ts +731 -2678
- package/packages/core/src/skills/skills.test.ts +52 -11
- package/packages/core/src/{harness → storage}/boot-id.ts +5 -2
- package/packages/core/src/storage/bootstrap.ts +151 -0
- package/packages/core/src/storage/causal-events.ts +84 -0
- package/packages/core/src/storage/collections.ts +680 -0
- package/packages/core/src/storage/crypto.ts +205 -74
- package/packages/core/src/{harness/db-helpers.ts → storage/hive.ts} +63 -7
- package/packages/core/src/storage/hivedb.ts +61 -0
- package/packages/core/src/storage/index.ts +111 -17
- package/packages/core/src/storage/model-id.ts +53 -0
- package/packages/core/src/storage/onboarding.ts +540 -972
- package/packages/core/src/storage/reconcile.ts +238 -0
- package/packages/core/src/storage/seed.ts +572 -406
- package/packages/core/src/storage/usage.ts +285 -225
- package/packages/core/src/storage/user-email.ts +11 -0
- package/packages/core/src/swarm/AgentExecutor.ts +1 -1
- package/packages/core/src/swarm/EventBridge.ts +1 -1
- package/packages/core/src/swarm/index.ts +12 -9
- package/packages/core/src/tool-runtime/index.ts +146 -23
- package/packages/core/src/tool-runtime/tool-worker.ts +2 -2
- package/packages/core/src/tool-runtime/worker-tools.ts +27 -0
- package/packages/core/src/{canvas/a2ui-tools.ts → tools/a2ui/index.ts} +17 -8
- package/packages/core/src/tools/agents/get-available-models.ts +36 -54
- package/packages/core/src/tools/agents/index.ts +784 -292
- package/packages/core/src/tools/api/api-request.test.ts +164 -0
- package/packages/core/src/tools/api/api-request.ts +174 -0
- package/packages/core/src/tools/api/index.ts +16 -0
- package/packages/core/src/tools/cli/index.ts +4 -0
- package/packages/core/src/tools/core/index.ts +281 -112
- package/packages/core/src/tools/cron/index.ts +121 -124
- package/packages/core/src/tools/index.ts +63 -78
- package/packages/core/src/tools/office/office-escribir-xlsx.ts +3 -1
- package/packages/core/src/tools/types.ts +3 -1
- package/packages/core/src/tools/web/artifact-inspect.ts +23 -0
- package/packages/core/src/tools/web/browser-screenshot.ts +26 -5
- package/packages/core/src/tools/web/browser-service.ts +5 -0
- package/packages/core/src/tools/web/browser-type.ts +3 -8
- package/packages/core/src/tools/web/index.ts +4 -4
- package/packages/core/src/voice/index.ts +89 -63
- package/packages/core/src/workers/agent.worker.ts +2 -2
- package/packages/core/src/workers/workers.test.ts +3 -10
- package/scripts/bump-version.ts +248 -0
- package/scripts/generate-skill-bundle.ts +108 -0
- package/test/agent-loop-terminal-synthesis.test.ts +32 -0
- package/test/catalog-agents-stay-enabled.test.ts +117 -0
- package/test/causal-events.test.ts +117 -0
- package/test/compaction.test.ts +105 -0
- package/test/context-compiler.test.ts +269 -0
- package/test/curator.test.ts +130 -0
- package/test/durable-queue.test.ts +114 -0
- package/test/harness-barrel.test.ts +64 -0
- package/test/hive-helpers.test.ts +130 -0
- package/test/hivedb-search.test.ts +189 -0
- package/test/internal-turns.test.ts +166 -0
- package/test/job-idempotency.test.ts +68 -0
- package/test/job-retry-backoff.test.ts +184 -0
- package/test/job-store.test.ts +381 -0
- package/test/llm-retry.test.ts +97 -0
- package/test/memory-perf.test.ts +774 -0
- package/test/minimal-loadout.test.ts +78 -0
- package/test/model-catalog.test.ts +105 -0
- package/test/preload.ts +12 -0
- package/test/reflector.test.ts +320 -0
- package/test/retention-cap.test.ts +91 -0
- package/test/retired-capabilities-pruned.test.ts +192 -0
- package/test/run-store.test.ts +355 -0
- package/test/scratchpad.test.ts +74 -0
- package/test/secrets-durability.test.ts +119 -0
- package/test/seed-model-reseed.test.ts +155 -0
- package/test/setup-agent-seed.test.ts +264 -0
- package/test/tool-inventory.test.ts +65 -0
- package/test/tool-runtime.test.ts +258 -0
- package/test/toon.test.ts +429 -0
- package/tsconfig.json +2 -0
- package/packages/core/src/ace/Curator.ts +0 -158
- package/packages/core/src/ace/Reflector.ts +0 -200
- package/packages/core/src/ace/index.ts +0 -4
- package/packages/core/src/agent/AgentRunner.ts +0 -711
- package/packages/core/src/agent/ContextCompiler.ts +0 -567
- package/packages/core/src/agent/ContextGuard.ts +0 -91
- package/packages/core/src/agent/ConversationStore.ts +0 -254
- package/packages/core/src/agent/Hooks.ts +0 -166
- package/packages/core/src/agent/StuckLoop.ts +0 -133
- package/packages/core/src/agent/providers/LLMClient.ts +0 -149
- package/packages/core/src/agent/providers/anthropic.ts +0 -212
- package/packages/core/src/agent/providers/openai-compat.ts +0 -231
- package/packages/core/src/agent/selectors/PlaybookSelector.ts +0 -121
- package/packages/core/src/agent/selectors/SkillSelector.ts +0 -322
- package/packages/core/src/agent/selectors/index.ts +0 -6
- package/packages/core/src/auth/auth.ts +0 -121
- package/packages/core/src/auth/index.ts +0 -1
- package/packages/core/src/canvas/CanvasManager.ts +0 -390
- package/packages/core/src/canvas/canvas-tools.ts +0 -448
- package/packages/core/src/harness/collections.ts +0 -98
- package/packages/core/src/harness/goal-verifier.ts +0 -141
- package/packages/core/src/harness/harness.test.ts +0 -236
- package/packages/core/src/harness/reconcile.ts +0 -149
- package/packages/core/src/mcp/MCPToolAdapter.ts +0 -176
- package/packages/core/src/multimodal/VisionService.ts +0 -293
- package/packages/core/src/scheduler/dag/AgentExecutor.ts +0 -53
- package/packages/core/src/scheduler/dag/DAGScheduler.ts +0 -250
- package/packages/core/src/scheduler/dag/EventBridge.ts +0 -122
- package/packages/core/src/scheduler/dag/TaskGraph.ts +0 -192
- package/packages/core/src/scheduler/dag/TaskNode.ts +0 -97
- package/packages/core/src/scheduler/dag/TaskResult.ts +0 -22
- package/packages/core/src/scheduler/dag/errors.ts +0 -37
- package/packages/core/src/scheduler/dag/index.ts +0 -26
- package/packages/core/src/scheduler/dag/presets/ResearchPreset.ts +0 -97
- package/packages/core/src/scheduler/dag/strategies/ParallelStrategy.ts +0 -21
- package/packages/core/src/scheduler/dag/strategies/PriorityStrategy.ts +0 -46
- package/packages/core/src/storage/HiveDBStorage.ts +0 -64
- package/packages/core/src/storage/SQLiteStorage.ts +0 -414
- package/packages/core/src/storage/hiveSeed.ts +0 -308
- package/packages/core/src/storage/hiveStorage.test.ts +0 -38
- package/packages/core/src/storage/schema.ts +0 -689
- package/packages/core/src/storage/storage.test.ts +0 -37
- package/packages/core/src/swarm/AgentBus.ts +0 -460
- package/packages/core/src/swarm/EventBus.ts +0 -169
- package/packages/core/src/swarm/WorkerPool.ts +0 -236
- package/packages/core/src/tools/bridge-events.ts +0 -26
- package/packages/core/src/tools/canvas/index.ts +0 -375
- package/packages/core/src/tools/codebridge/index.ts +0 -342
- package/packages/core/src/tools/meeting/index.ts +0 -353
- package/packages/core/src/tools/projects/index.ts +0 -37
- package/packages/core/src/tools/projects/project-create.ts +0 -94
- package/packages/core/src/tools/projects/project-done.ts +0 -66
- package/packages/core/src/tools/projects/project-fail.ts +0 -66
- package/packages/core/src/tools/projects/project-list.ts +0 -96
- package/packages/core/src/tools/projects/project-update.ts +0 -72
- package/packages/core/src/tools/projects/task-create.ts +0 -68
- package/packages/core/src/tools/projects/task-evaluate.ts +0 -93
- package/packages/core/src/tools/projects/task-update.ts +0 -93
- package/packages/core/src/tools/voice/index.ts +0 -104
- package/packages/core/src/tools/web/api-request.test.ts +0 -170
- package/packages/core/src/tools/web/api-request.ts +0 -239
- package/test/setup-db.ts +0 -216
- /package/packages/core/src/agent/{NativeTools.ts → native-tools.ts} +0 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capability Search — shared HiveDB search layer
|
|
3
|
+
*
|
|
4
|
+
* Single entry point for searching Hive's capability catalogs (native tools,
|
|
5
|
+
* skills, playbook rules, MCP tools): one HiveDB index for all four, with the
|
|
6
|
+
* catalog discriminated by the `type` field rather than by separate tables.
|
|
7
|
+
*
|
|
8
|
+
* Document convention (one index, type discrimination via filters):
|
|
9
|
+
* - id: `tool:${name}` | `skill:${id}` | `playbook:${rowid}` | `mcp:${id}` | `agent:${id}`
|
|
10
|
+
* - name: tool/skill name or rule head (BM25 boost 4.0)
|
|
11
|
+
* - tags: category + triggers + keywords (BM25 boost 3.0)
|
|
12
|
+
* - body: description / rule text / content (BM25 boost 2.0)
|
|
13
|
+
* - filters: { type: "tool"|"skill"|"playbook"|"mcp" }
|
|
14
|
+
* plus { server_id } on MCP docs for per-server hot-reload.
|
|
15
|
+
*
|
|
16
|
+
* Score semantics: text-only queries return raw BM25 (positive, higher is
|
|
17
|
+
* better). Never compare against an absolute floor — BM25 magnitude depends
|
|
18
|
+
* on corpus and document length. Use applyRelativeCutoff() instead.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { IndexDoc } from "@johpaz/hive-db";
|
|
22
|
+
import { getHiveDb } from "../storage/hivedb";
|
|
23
|
+
import { logger } from "../utils/logger";
|
|
24
|
+
|
|
25
|
+
const log = logger.child("capability-search");
|
|
26
|
+
|
|
27
|
+
export type CapabilityType = "tool" | "skill" | "playbook" | "mcp" | "agent";
|
|
28
|
+
|
|
29
|
+
export interface CapabilityHit {
|
|
30
|
+
/** Namespaced id, e.g. "tool:web_search" */
|
|
31
|
+
id: string;
|
|
32
|
+
type: CapabilityType;
|
|
33
|
+
/** Id without the type prefix, e.g. "web_search" */
|
|
34
|
+
rawId: string;
|
|
35
|
+
/** Raw BM25 score: positive, higher = more relevant */
|
|
36
|
+
score: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface CapabilityDoc {
|
|
40
|
+
type: CapabilityType;
|
|
41
|
+
/** Id within the type namespace (tool name, skill id, playbook rowid, mcp id) */
|
|
42
|
+
rawId: string;
|
|
43
|
+
name?: string;
|
|
44
|
+
body?: string;
|
|
45
|
+
tags?: string;
|
|
46
|
+
/** Extra filters besides `type` (e.g. { server_id } for MCP docs) */
|
|
47
|
+
extraFilters?: Array<{ field: string; value: string }>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const TYPE_PREFIXES: CapabilityType[] = ["tool", "skill", "playbook", "mcp", "agent"];
|
|
51
|
+
|
|
52
|
+
function splitId(id: string): { type: CapabilityType; rawId: string } | null {
|
|
53
|
+
const sep = id.indexOf(":");
|
|
54
|
+
if (sep === -1) return null;
|
|
55
|
+
const type = id.slice(0, sep) as CapabilityType;
|
|
56
|
+
if (!TYPE_PREFIXES.includes(type)) return null;
|
|
57
|
+
return { type, rawId: id.slice(sep + 1) };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ─── Search ──────────────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
export interface SearchCapabilitiesOptions {
|
|
63
|
+
/** Restrict to these types. Omit or empty = all types. */
|
|
64
|
+
types?: CapabilityType[];
|
|
65
|
+
/** Maximum hits to return (default 10). */
|
|
66
|
+
k?: number;
|
|
67
|
+
/** Per-field BM25 boosts (engine defaults: name 4, tags 3, body 2). */
|
|
68
|
+
boosts?: { name?: number; body?: number; tags?: number };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Search the capability index with raw user text. The engine parses leniently
|
|
73
|
+
* (accents, quotes, operators and punctuation never throw) and applies
|
|
74
|
+
* Spanish stemming + accent folding, so callers must NOT pre-sanitize.
|
|
75
|
+
*/
|
|
76
|
+
export async function searchCapabilities(
|
|
77
|
+
query: string,
|
|
78
|
+
opts: SearchCapabilitiesOptions = {}
|
|
79
|
+
): Promise<CapabilityHit[]> {
|
|
80
|
+
const k = opts.k ?? 10;
|
|
81
|
+
const types = opts.types?.length ? opts.types : undefined;
|
|
82
|
+
const trimmed = query.trim();
|
|
83
|
+
if (!trimmed) return [];
|
|
84
|
+
|
|
85
|
+
const startTime = performance.now();
|
|
86
|
+
const db = await getHiveDb();
|
|
87
|
+
|
|
88
|
+
// Filters are AND-ed by the engine, so multi-type search runs one query per
|
|
89
|
+
// type; the single-type and all-types cases are one engine call.
|
|
90
|
+
const queries = types
|
|
91
|
+
? types.map((t) => ({
|
|
92
|
+
type: t,
|
|
93
|
+
filters: [{ field: "type", value: t }],
|
|
94
|
+
}))
|
|
95
|
+
: [{ type: undefined, filters: undefined }];
|
|
96
|
+
|
|
97
|
+
const merged = new Map<string, CapabilityHit>();
|
|
98
|
+
for (const q of queries) {
|
|
99
|
+
const hits = await db.queryHybrid({
|
|
100
|
+
text: trimmed,
|
|
101
|
+
k,
|
|
102
|
+
filters: q.filters,
|
|
103
|
+
boosts: opts.boosts,
|
|
104
|
+
});
|
|
105
|
+
for (const hit of hits) {
|
|
106
|
+
const parsed = splitId(hit.id);
|
|
107
|
+
if (!parsed) continue;
|
|
108
|
+
const existing = merged.get(hit.id);
|
|
109
|
+
if (!existing || hit.score > existing.score) {
|
|
110
|
+
merged.set(hit.id, {
|
|
111
|
+
id: hit.id,
|
|
112
|
+
type: parsed.type,
|
|
113
|
+
rawId: parsed.rawId,
|
|
114
|
+
score: hit.score,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const results = Array.from(merged.values())
|
|
121
|
+
.sort((a, b) => b.score - a.score)
|
|
122
|
+
.slice(0, k);
|
|
123
|
+
|
|
124
|
+
const timing = performance.now() - startTime;
|
|
125
|
+
log.debug(
|
|
126
|
+
`[capability-search] "${trimmed.substring(0, 60)}" → ${results.length} hits in ${timing.toFixed(1)}ms`
|
|
127
|
+
);
|
|
128
|
+
return results;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Keep only hits scoring at least `ratio` of the top hit. This replaces the
|
|
133
|
+
* old absolute negative-bm25 thresholds: relevance is relative to the best
|
|
134
|
+
* match, never an absolute floor.
|
|
135
|
+
*/
|
|
136
|
+
export function applyRelativeCutoff(
|
|
137
|
+
hits: CapabilityHit[],
|
|
138
|
+
ratio = 0.3
|
|
139
|
+
): CapabilityHit[] {
|
|
140
|
+
if (hits.length === 0) return hits;
|
|
141
|
+
const top = hits[0].score;
|
|
142
|
+
if (top <= 0) return [];
|
|
143
|
+
return hits.filter((h) => h.score >= ratio * top);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ─── Sync helpers ────────────────────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Replace all documents of a type: deletes existing docs carrying the type
|
|
150
|
+
* filter, then batch-upserts the new set under a single index commit.
|
|
151
|
+
*/
|
|
152
|
+
export async function replaceCapabilityDocs(
|
|
153
|
+
type: CapabilityType,
|
|
154
|
+
docs: CapabilityDoc[]
|
|
155
|
+
): Promise<void> {
|
|
156
|
+
const db = await getHiveDb();
|
|
157
|
+
await db.deleteByFilter({ field: "type", value: type });
|
|
158
|
+
if (docs.length === 0) return;
|
|
159
|
+
await db.upsertBatch(docs.map(toIndexDoc));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Upsert documents without clearing the rest of their type. */
|
|
163
|
+
export async function upsertCapabilityDocs(docs: CapabilityDoc[]): Promise<void> {
|
|
164
|
+
if (docs.length === 0) return;
|
|
165
|
+
const db = await getHiveDb();
|
|
166
|
+
await db.upsertBatch(docs.map(toIndexDoc));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Delete every MCP doc belonging to a server (hot-reload/disconnect). */
|
|
170
|
+
export async function deleteCapabilitiesByServer(serverId: string): Promise<void> {
|
|
171
|
+
const db = await getHiveDb();
|
|
172
|
+
await db.deleteByFilter({ field: "server_id", value: serverId });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function toIndexDoc(doc: CapabilityDoc): IndexDoc {
|
|
176
|
+
return {
|
|
177
|
+
id: `${doc.type}:${doc.rawId}`,
|
|
178
|
+
name: doc.name,
|
|
179
|
+
body: doc.body,
|
|
180
|
+
tags: doc.tags,
|
|
181
|
+
filters: [
|
|
182
|
+
{ field: "type", value: doc.type },
|
|
183
|
+
...(doc.extraFilters ?? []),
|
|
184
|
+
],
|
|
185
|
+
};
|
|
186
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { col } from "../storage/hive";
|
|
2
|
+
import type { AgentDoc } from "../storage/collections";
|
|
3
|
+
import {
|
|
4
|
+
applyRelativeCutoff,
|
|
5
|
+
replaceCapabilityDocs,
|
|
6
|
+
searchCapabilities,
|
|
7
|
+
type CapabilityDoc,
|
|
8
|
+
} from "./capability-search";
|
|
9
|
+
import { logger } from "../utils/logger";
|
|
10
|
+
|
|
11
|
+
const log = logger.child("catalog-selector");
|
|
12
|
+
const RELEVANCE_RATIO = 0.35;
|
|
13
|
+
const ROUTING_STOP_WORDS = new Set([
|
|
14
|
+
"a", "al", "de", "del", "el", "en", "la", "las", "lo", "los",
|
|
15
|
+
"o", "otro", "otra", "un", "una", "y",
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
function routingTokens(value: string): Set<string> {
|
|
19
|
+
const normalized = value
|
|
20
|
+
.normalize("NFD")
|
|
21
|
+
.replace(/\p{Diacritic}/gu, "")
|
|
22
|
+
.toLowerCase()
|
|
23
|
+
.split(/[^\p{Letter}\p{Number}_]+/u)
|
|
24
|
+
.filter(Boolean)
|
|
25
|
+
.map((token) => token.length > 4 && token.endsWith("s") ? token.slice(0, -1) : token)
|
|
26
|
+
.filter((token) => !ROUTING_STOP_WORDS.has(token));
|
|
27
|
+
return new Set(normalized);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function matchesRoutingExclusion(query: string, agent: AgentDoc): boolean {
|
|
31
|
+
if (!agent.routing_exclusions_json) return false;
|
|
32
|
+
const queryTokens = routingTokens(query);
|
|
33
|
+
const exclusions = JSON.parse(agent.routing_exclusions_json) as string[];
|
|
34
|
+
|
|
35
|
+
return exclusions.some((exclusion) => {
|
|
36
|
+
const excludedTokens = routingTokens(exclusion);
|
|
37
|
+
const shared = [...excludedTokens].filter((token) => queryTokens.has(token)).length;
|
|
38
|
+
return shared >= 2 && shared / Math.max(excludedTokens.size, 1) >= 0.35;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function getCatalogAgent(id: string): Promise<AgentDoc | null> {
|
|
43
|
+
const entry = await (await col<AgentDoc>("agents")).get(id);
|
|
44
|
+
return entry?.doc.source === "catalog" && entry.doc.enabled ? entry.doc : null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function listCatalogAgents(): Promise<AgentDoc[]> {
|
|
48
|
+
const c = await col<AgentDoc>("agents");
|
|
49
|
+
return (await c.findBy("source", "catalog")).map((entry) => entry.doc).filter((doc) => doc.enabled);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function searchCatalogAgents(query: string, k = 5): Promise<Array<{ agent: AgentDoc; score: number }>> {
|
|
53
|
+
const hits = applyRelativeCutoff(
|
|
54
|
+
await searchCapabilities(query, { types: ["agent"], k }),
|
|
55
|
+
RELEVANCE_RATIO,
|
|
56
|
+
);
|
|
57
|
+
const c = await col<AgentDoc>("agents");
|
|
58
|
+
const results: Array<{ agent: AgentDoc; score: number }> = [];
|
|
59
|
+
for (const hit of hits) {
|
|
60
|
+
const entry = await c.get(hit.rawId);
|
|
61
|
+
if (
|
|
62
|
+
entry?.doc.source === "catalog" &&
|
|
63
|
+
entry.doc.enabled &&
|
|
64
|
+
!matchesRoutingExclusion(query, entry.doc)
|
|
65
|
+
) {
|
|
66
|
+
results.push({ agent: entry.doc, score: hit.score });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return results;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function syncCatalogAgentsToIndex(): Promise<void> {
|
|
73
|
+
const agents = await listCatalogAgents();
|
|
74
|
+
const docs: CapabilityDoc[] = agents.map((agent) => ({
|
|
75
|
+
type: "agent",
|
|
76
|
+
rawId: agent.id,
|
|
77
|
+
name: `${agent.name} ${agent.id}`,
|
|
78
|
+
tags: [
|
|
79
|
+
...(agent.routing_examples_json ? JSON.parse(agent.routing_examples_json) : []),
|
|
80
|
+
...(agent.tool_allowlist_json ? JSON.parse(agent.tool_allowlist_json) : []),
|
|
81
|
+
...(agent.skills_json ? JSON.parse(agent.skills_json) : []),
|
|
82
|
+
].join(" "),
|
|
83
|
+
// The complete system prompt is intentionally excluded: it adds routing
|
|
84
|
+
// noise and may contain operational policy not meant for retrieval.
|
|
85
|
+
body: agent.description ?? "",
|
|
86
|
+
}));
|
|
87
|
+
await replaceCapabilityDocs("agent", docs);
|
|
88
|
+
log.info(`[catalog-selector] Synced ${docs.length} catalog agents to HiveDB index`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function renderAgentRoutingCatalog(agents: AgentDoc[]): string {
|
|
92
|
+
return agents
|
|
93
|
+
.map((a) => {
|
|
94
|
+
const exclusions = a.routing_exclusions_json
|
|
95
|
+
? JSON.parse(a.routing_exclusions_json) as string[]
|
|
96
|
+
: [];
|
|
97
|
+
const exclusionText = exclusions.length > 0
|
|
98
|
+
? ` NO usar para: ${exclusions.join("; ")}.`
|
|
99
|
+
: "";
|
|
100
|
+
return `- ${a.id}: ${a.description ?? ""}${exclusionText}`;
|
|
101
|
+
})
|
|
102
|
+
.join("\n");
|
|
103
|
+
}
|
|
@@ -15,23 +15,26 @@
|
|
|
15
15
|
* short summaries in the in-memory message array before model calls.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import { logger } from "../utils/logger
|
|
18
|
+
import { logger } from "../utils/logger"
|
|
19
19
|
import {
|
|
20
20
|
getTotalTokens,
|
|
21
21
|
getHistory,
|
|
22
22
|
getSummary,
|
|
23
23
|
saveSummary,
|
|
24
|
-
toAPIMessages,
|
|
25
24
|
getMessageCount,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
import {
|
|
25
|
+
isInternalSource,
|
|
26
|
+
type StoredMessage,
|
|
27
|
+
} from "./conversation-store"
|
|
28
|
+
import { estimateTokens } from "../utils/toon"
|
|
29
|
+
import { callLLM, resolveProviderConfig, getDefaultLLM, type ContentPart } from "./llm-client"
|
|
30
|
+
import { col, fromIndexable } from "../storage/hive"
|
|
31
|
+
import type { AgentDoc, ModelDoc } from "../storage/collections"
|
|
30
32
|
|
|
31
33
|
const log = logger.child("compaction")
|
|
32
34
|
|
|
33
35
|
// Token budget: compress when stored tokens exceed this threshold
|
|
34
|
-
|
|
36
|
+
// Will be overridden by model's context_window at runtime if available
|
|
37
|
+
const COMPACT_TOKEN_THRESHOLD = 32000 // ~25% of 128K default context window
|
|
35
38
|
const KEEP_LAST_N_MESSAGES = 5 // always keep most recent N messages
|
|
36
39
|
const TOOL_RESULT_MAX_CHARS = 200 // max chars for old tool results after clearing
|
|
37
40
|
const MAX_TRANSCRIPT_MSGS = 30 // cap messages sent to summarizer (avoids OOM on small models)
|
|
@@ -46,11 +49,30 @@ export async function maybeCompact(
|
|
|
46
49
|
notify?: { channel: string; userId: string }
|
|
47
50
|
): Promise<void> {
|
|
48
51
|
try {
|
|
49
|
-
const totalTokens = getTotalTokens(threadId)
|
|
50
|
-
if (totalTokens < COMPACT_TOKEN_THRESHOLD) return
|
|
52
|
+
const totalTokens = await getTotalTokens(threadId)
|
|
51
53
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
// Use model's context window if available, otherwise use default
|
|
55
|
+
let effectiveThreshold = COMPACT_TOKEN_THRESHOLD
|
|
56
|
+
try {
|
|
57
|
+
const agentsCol = await col<AgentDoc>("agents")
|
|
58
|
+
const coordinators = await agentsCol.findBy("role", "coordinator", { limit: 1 })
|
|
59
|
+
const modelId = fromIndexable(coordinators[0]?.doc.model_id ?? null)
|
|
60
|
+
if (modelId) {
|
|
61
|
+
const modelsCol = await col<ModelDoc>("models")
|
|
62
|
+
// El id se busca completo: recortar el primer segmento rompía cualquier
|
|
63
|
+
// modelo cuyo nombre lleve barra (meta/llama-3.3-70b-instruct buscaba
|
|
64
|
+
// "llama-3.3-70b-instruct", no encontraba nada y caía al default).
|
|
65
|
+
const modelEntry = await modelsCol.get(modelId)
|
|
66
|
+
if (modelEntry?.doc.context_window) {
|
|
67
|
+
effectiveThreshold = Math.floor(modelEntry.doc.context_window * 0.25)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} catch { /* use default threshold */ }
|
|
71
|
+
|
|
72
|
+
if (totalTokens < effectiveThreshold) return
|
|
73
|
+
|
|
74
|
+
const summary = await getSummary(threadId)
|
|
75
|
+
const totalMessages = await getMessageCount(threadId)
|
|
54
76
|
|
|
55
77
|
// Already summarized up to near the current state
|
|
56
78
|
if (summary && summary.last_message_id > totalMessages - KEEP_LAST_N_MESSAGES) return
|
|
@@ -62,22 +84,48 @@ export async function maybeCompact(
|
|
|
62
84
|
}
|
|
63
85
|
}
|
|
64
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Find a clean cut point: the "keep" side must begin with a user turn so we
|
|
89
|
+
* never leave orphaned tool messages at the start of the visible window.
|
|
90
|
+
* Internal events (delegation fan-in) are persisted as role:"user", so they
|
|
91
|
+
* are valid boundaries here same as human turns.
|
|
92
|
+
* Returns 0 when no clean boundary exists (caller should skip compaction).
|
|
93
|
+
*/
|
|
94
|
+
export function findCompactionCutIndex(rows: StoredMessage[], keepLastN = KEEP_LAST_N_MESSAGES): number {
|
|
95
|
+
let cutIndex = rows.length - keepLastN
|
|
96
|
+
while (cutIndex > 0 && rows[cutIndex]?.role !== "user") {
|
|
97
|
+
cutIndex--
|
|
98
|
+
}
|
|
99
|
+
return cutIndex
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Render a transcript for the summarizer LLM. Internal events (delegation
|
|
104
|
+
* fan-in notices) are labeled distinctly — they are persisted as
|
|
105
|
+
* role:"user" so the LLM treats them as input, but labeling them [USER] here
|
|
106
|
+
* would make the summarizer attribute system-generated task outcomes to the
|
|
107
|
+
* human, baking that misattribution into the durable summary.
|
|
108
|
+
*/
|
|
109
|
+
export function renderTranscript(rows: StoredMessage[], maxMsgChars = MAX_MSG_CHARS): string {
|
|
110
|
+
return rows
|
|
111
|
+
.map((r) => {
|
|
112
|
+
const label = isInternalSource(r.source) ? "EVENTO INTERNO" : r.role.toUpperCase()
|
|
113
|
+
return `[${label}]: ${r.content.substring(0, maxMsgChars)}`
|
|
114
|
+
})
|
|
115
|
+
.join("\n\n")
|
|
116
|
+
}
|
|
117
|
+
|
|
65
118
|
/**
|
|
66
119
|
* Compress a thread's history into a summary.
|
|
67
120
|
*/
|
|
68
|
-
|
|
121
|
+
async function compactThread(
|
|
69
122
|
threadId: string,
|
|
70
123
|
notify?: { channel: string; userId: string }
|
|
71
124
|
): Promise<void> {
|
|
72
|
-
const allMessages = getHistory(threadId)
|
|
125
|
+
const allMessages = await getHistory(threadId)
|
|
73
126
|
if (allMessages.length <= KEEP_LAST_N_MESSAGES) return
|
|
74
127
|
|
|
75
|
-
|
|
76
|
-
// we never leave orphaned tool messages at the start of the visible window.
|
|
77
|
-
let cutIndex = allMessages.length - KEEP_LAST_N_MESSAGES
|
|
78
|
-
while (cutIndex > 0 && allMessages[cutIndex]?.role !== "user") {
|
|
79
|
-
cutIndex--
|
|
80
|
-
}
|
|
128
|
+
const cutIndex = findCompactionCutIndex(allMessages)
|
|
81
129
|
if (cutIndex <= 0) {
|
|
82
130
|
log.info(`[compaction] No clean user-turn boundary found — skipping`)
|
|
83
131
|
return
|
|
@@ -88,32 +136,17 @@ export async function compactThread(
|
|
|
88
136
|
|
|
89
137
|
const lastSummarizedId = toSummarize[toSummarize.length - 1].id
|
|
90
138
|
|
|
91
|
-
const existingSummary = getSummary(threadId)
|
|
139
|
+
const existingSummary = await getSummary(threadId)
|
|
92
140
|
if (existingSummary && existingSummary.last_message_id >= lastSummarizedId) return
|
|
93
141
|
|
|
94
142
|
// Cap transcript to avoid overflowing small model contexts
|
|
95
143
|
const capped = toSummarize.slice(-MAX_TRANSCRIPT_MSGS)
|
|
96
|
-
const
|
|
97
|
-
const transcript = apiMessages
|
|
98
|
-
.map((m) => {
|
|
99
|
-
const text = typeof m.content === "string"
|
|
100
|
-
? m.content
|
|
101
|
-
: Array.isArray(m.content)
|
|
102
|
-
? m.content.filter(p => p.type === "text").map(p => (p as any).text).join("\n")
|
|
103
|
-
: ""
|
|
104
|
-
return `[${m.role.toUpperCase()}]: ${text.substring(0, MAX_MSG_CHARS)}`
|
|
105
|
-
})
|
|
106
|
-
.join("\n\n")
|
|
144
|
+
const transcript = renderTranscript(capped)
|
|
107
145
|
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
"SELECT provider_id, model_id FROM agents WHERE role = 'coordinator' LIMIT 1"
|
|
111
|
-
).get()
|
|
146
|
+
const defaultLLM = await getDefaultLLM()
|
|
147
|
+
if (!defaultLLM) throw new Error("No active LLM providers/models configured in the database")
|
|
112
148
|
|
|
113
|
-
const providerCfg = await resolveProviderConfig(
|
|
114
|
-
coordinator?.provider_id || "openai",
|
|
115
|
-
coordinator?.model_id || "gpt-4o-mini"
|
|
116
|
-
)
|
|
149
|
+
const providerCfg = await resolveProviderConfig(defaultLLM.provider, defaultLLM.model)
|
|
117
150
|
|
|
118
151
|
const summaryResponse = await callLLM({
|
|
119
152
|
...providerCfg,
|
|
@@ -131,10 +164,22 @@ export async function compactThread(
|
|
|
131
164
|
],
|
|
132
165
|
})
|
|
133
166
|
|
|
167
|
+
// A failed provider call still returns a populated `content` (the error text),
|
|
168
|
+
// so this has to gate on stop_reason — otherwise the summary that permanently
|
|
169
|
+
// replaces N messages of history becomes "[LLM Error] ...". Skipping leaves the
|
|
170
|
+
// thread uncompacted, which is recoverable; saving is not.
|
|
171
|
+
if (summaryResponse.stop_reason === "error") {
|
|
172
|
+
log.warn(
|
|
173
|
+
`[compaction] Summarizer call failed (${summaryResponse.error?.message ?? "unknown error"}) — `
|
|
174
|
+
+ `keeping thread ${threadId} uncompacted rather than saving the error as its summary`
|
|
175
|
+
)
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
|
|
134
179
|
const summary = summaryResponse.content.trim()
|
|
135
180
|
if (!summary) return
|
|
136
181
|
|
|
137
|
-
saveSummary(threadId, summary, toSummarize.length, lastSummarizedId)
|
|
182
|
+
await saveSummary(threadId, summary, toSummarize.length, lastSummarizedId)
|
|
138
183
|
log.info(
|
|
139
184
|
`[compaction] Thread ${threadId} compacted: ${toSummarize.length} msgs → ${estimateTokens(summary)} tokens`
|
|
140
185
|
)
|
|
@@ -142,7 +187,7 @@ export async function compactThread(
|
|
|
142
187
|
// Notify user in their active channel (non-critical)
|
|
143
188
|
if (notify?.channel && notify?.userId) {
|
|
144
189
|
try {
|
|
145
|
-
const { sendToUserChannel } = await import("../gateway/channel-notify
|
|
190
|
+
const { sendToUserChannel } = await import("../gateway/channel-notify")
|
|
146
191
|
await sendToUserChannel(
|
|
147
192
|
notify.channel,
|
|
148
193
|
notify.userId,
|
|
@@ -197,25 +242,3 @@ export function clearOldToolResults<T extends { role: string; content: string |
|
|
|
197
242
|
})
|
|
198
243
|
}
|
|
199
244
|
|
|
200
|
-
/**
|
|
201
|
-
* Summarize a tool result to a single line
|
|
202
|
-
* Used for very old tool results (> 10 turns)
|
|
203
|
-
*/
|
|
204
|
-
export function summarizeToolResult(content: string, toolName?: string): string {
|
|
205
|
-
// Try to extract success/failure status
|
|
206
|
-
const isError = content.includes('error') || content.includes('failed') || content.startsWith('[Tool Error]')
|
|
207
|
-
const isSuccess = content.includes('ok') || content.includes('success') || content.includes('true')
|
|
208
|
-
|
|
209
|
-
// Try to extract key result field from JSON/TOON
|
|
210
|
-
let keyInfo = ""
|
|
211
|
-
try {
|
|
212
|
-
// Simple extraction of first key value
|
|
213
|
-
const firstLine = content.split('\n')[0].substring(0, 80)
|
|
214
|
-
keyInfo = firstLine
|
|
215
|
-
} catch {
|
|
216
|
-
keyInfo = content.substring(0, 80)
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const status = isError ? "failed" : isSuccess ? "success" : "completed"
|
|
220
|
-
return `[${toolName || 'Tool'} ${status}: ${keyInfo}...]`
|
|
221
|
-
}
|