@johpaz/hive-sdk 0.1.4 → 0.1.6
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 +129 -0
- package/README.md +78 -23
- package/bun.lock +55 -29
- 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 +17 -12
- 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 +13 -12
- package/packages/core/src/agent/acceptance-checks.ts +172 -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 +76 -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 -27
- 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 -18
- 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-backend.ts +129 -0
- package/packages/core/src/tools/web/browser-screenshot.ts +26 -5
- package/packages/core/src/tools/web/browser-service.ts +80 -35
- 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/tools/web/webview-backend.ts +412 -0
- 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/acceptance-checks.test.ts +403 -0
- package/test/agent-loop-terminal-synthesis.test.ts +32 -0
- package/test/browser-backend.test.ts +308 -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/tool-selector-runtime-tools.test.ts +117 -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,689 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Compiler — Implementa las 4 estrategias de Context Engineering:
|
|
3
|
+
*
|
|
4
|
+
* 1. ESCRIBIR (Write) — Guardar información fuera del contexto:
|
|
5
|
+
* - Scratchpad: notas persistentes por conversación
|
|
6
|
+
* - Trazas de ejecución: registro en traces table
|
|
7
|
+
*
|
|
8
|
+
* 2. SELECCIONAR (Select) — Traer solo lo relevante:
|
|
9
|
+
* - Tool Loadout: máx 3-5 tools relevantes por turno
|
|
10
|
+
* - Playbook filtering: reglas ACE aplicables a esta tarea
|
|
11
|
+
* - Historial selectivo: resumen + mensajes recientes
|
|
12
|
+
*
|
|
13
|
+
* 3. COMPRIMIR (Compress) — Reducir tokens manteniendo información:
|
|
14
|
+
* - Compaction: resumir mensajes viejos
|
|
15
|
+
* - Tool result clearing: reemplazar resultados antiguos por resúmenes
|
|
16
|
+
*
|
|
17
|
+
* 4. AISLAR (Isolate) — Separar contextos por agente:
|
|
18
|
+
* - Cada worker recibe su propio contexto mínimo
|
|
19
|
+
* - El Coordinador ve el panorama completo
|
|
20
|
+
*
|
|
21
|
+
* TODOS los datos se formatean en TOON para ahorro de tokens.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { col, fromIndexable } from "../storage/hive"
|
|
25
|
+
import type { AgentDoc, ModelDoc } from "../storage/collections"
|
|
26
|
+
import { logger } from "../utils/logger"
|
|
27
|
+
import type { LLMMessage, LLMToolDef, ContentPart } from "./llm-client"
|
|
28
|
+
import type { MCPClientManager } from "../mcp/index.ts"
|
|
29
|
+
import { syncToolCatalogToIndex, mcpToolFullName } from "./tool-selector"
|
|
30
|
+
import { syncSkillsToIndex, getMinimalSkills, selectSkills, getSkillByName, type SkillDescriptor } from "./skill-selector"
|
|
31
|
+
import { syncPlaybookToIndex, selectPlaybookRules } from "./playbook-selector"
|
|
32
|
+
import { getRecentMessages, getSummary, getScratchpad, toAPIMessages } from "./conversation-store"
|
|
33
|
+
import { formatContext, estimateTokens } from "../utils/toon"
|
|
34
|
+
import { buildSystemPromptWithProjects } from "./prompt-builder"
|
|
35
|
+
import { createAllTools } from "../tools/index.ts"
|
|
36
|
+
import { resolveUserId } from "../storage/onboarding"
|
|
37
|
+
import { getMCPManager as getSingletonMCPManager } from "../mcp/singleton"
|
|
38
|
+
import { syncMCPToolsToDB, syncMCPToolsToIndex } from "../mcp/tool-sync"
|
|
39
|
+
import { getUserDate, getUserTime } from "../utils/date"
|
|
40
|
+
import { loadConfig } from "../config/loader"
|
|
41
|
+
import { getHiveDb } from "../storage/hivedb"
|
|
42
|
+
import { listCatalogAgents, renderAgentRoutingCatalog } from "./catalog-selector"
|
|
43
|
+
import { expandToolAllowlist } from "./delegation-runtime"
|
|
44
|
+
import { MINIMAL_TOOLS } from "./minimal-loadout"
|
|
45
|
+
|
|
46
|
+
const log = logger.child("context-compiler")
|
|
47
|
+
|
|
48
|
+
// Configuration constants
|
|
49
|
+
const KEEP_LAST_N_MESSAGES = 15 // Always keep last N messages (Strategy: SELECT) — only user+assistant text, no tool results
|
|
50
|
+
const DEFAULT_CONTEXT_WINDOW = 250000 // Default context window when model is unknown
|
|
51
|
+
const COMPACT_RATIO = 0.80 // Reserve budget: truncate system prompt when it would exceed 80% of context window
|
|
52
|
+
const MAX_SYSTEM_PROMPT_CHARS_CAP = 128000 // Hard cap for pathological prompts; normal budget is model-aware
|
|
53
|
+
const MCP_LAZY_CONNECT_TIMEOUT_MS = 8000 // Bound for on-demand connect of a dormant MCP server
|
|
54
|
+
const SUMMARY_MAX_CHARS = 4000 // Cap the compacted summary so it can't itself blow the system-prompt budget
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
/** Bounds a dormant MCP server's on-demand wake so one dead server can't stall a whole turn. */
|
|
59
|
+
async function withMcpConnectTimeout(op: () => Promise<void>, timeoutMs: number): Promise<void> {
|
|
60
|
+
let timer: ReturnType<typeof setTimeout>
|
|
61
|
+
const timeout = new Promise<never>((_, reject) => {
|
|
62
|
+
timer = setTimeout(() => reject(new Error(`MCP connect timed out after ${timeoutMs}ms`)), timeoutMs)
|
|
63
|
+
})
|
|
64
|
+
try {
|
|
65
|
+
await Promise.race([op(), timeout])
|
|
66
|
+
} finally {
|
|
67
|
+
clearTimeout(timer!)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ─── Types ─────────────────────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
// Simple tool interface for context compilation
|
|
74
|
+
export interface ContextTool {
|
|
75
|
+
name: string
|
|
76
|
+
description: string
|
|
77
|
+
parameters: Record<string, unknown>
|
|
78
|
+
execute?: (params: Record<string, unknown>) => Promise<unknown>
|
|
79
|
+
/** Per-tool timeout (ms) override from the Tool definition. */
|
|
80
|
+
timeoutMs?: number
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface CompiledContext {
|
|
84
|
+
systemPrompt: string
|
|
85
|
+
/** The `# RESUMEN DE LA CONVERSACIÓN` block already folded into `systemPrompt` — exposed so a caller-supplied `systemPromptOverride` can compose it in instead of discarding it (see agent-loop.ts). "" when compaction hasn't fired. */
|
|
86
|
+
conversationSummarySection: string
|
|
87
|
+
messages: LLMMessage[]
|
|
88
|
+
tools: LLMToolDef[]
|
|
89
|
+
allTools: ContextTool[]
|
|
90
|
+
skills: SkillDescriptor[] // Skills loaded (minimal + discovered)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ─── G9 causal context (buildAgentContext) ────────────────────────────────
|
|
94
|
+
|
|
95
|
+
interface AgentContextItemShape {
|
|
96
|
+
type: "decision" | "toolCall" | "anomaly" | "episode" | "phaseSummary"
|
|
97
|
+
seq?: number
|
|
98
|
+
phase?: string
|
|
99
|
+
text?: string
|
|
100
|
+
taskId?: string
|
|
101
|
+
summary?: string
|
|
102
|
+
keyDecisions?: number[]
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
interface AgentContextShape {
|
|
106
|
+
items: AgentContextItemShape[]
|
|
107
|
+
similarEpisodes: Array<{ taskId: string; summary: string }>
|
|
108
|
+
anomalies: AgentContextItemShape[]
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function formatCausalContextItem(item: AgentContextItemShape): string | null {
|
|
112
|
+
switch (item.type) {
|
|
113
|
+
case "decision":
|
|
114
|
+
case "toolCall":
|
|
115
|
+
case "phaseSummary":
|
|
116
|
+
return item.text ? `- ${item.text}` : null
|
|
117
|
+
case "anomaly":
|
|
118
|
+
return item.text ? `- ⚠ ${item.text}` : null
|
|
119
|
+
case "episode":
|
|
120
|
+
return item.summary ? `- (episodio previo) ${item.summary}` : null
|
|
121
|
+
default:
|
|
122
|
+
return null
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Maps the stored AgentDoc (sentinel-encoded FKs) to the shape context-compiler works with. */
|
|
127
|
+
function fromAgentDoc(doc: AgentDoc) {
|
|
128
|
+
return {
|
|
129
|
+
id: doc.id,
|
|
130
|
+
user_id: doc.user_id,
|
|
131
|
+
name: doc.name,
|
|
132
|
+
role: doc.role,
|
|
133
|
+
system_prompt: doc.system_prompt,
|
|
134
|
+
tone: doc.tone,
|
|
135
|
+
provider_id: fromIndexable(doc.provider_id),
|
|
136
|
+
model_id: fromIndexable(doc.model_id),
|
|
137
|
+
tools_json: doc.tools_json,
|
|
138
|
+
tool_allowlist_json: doc.tool_allowlist_json,
|
|
139
|
+
skills_json: doc.skills_json,
|
|
140
|
+
active_mcp_json: doc.active_mcp_json,
|
|
141
|
+
mcp_server_ids_json: doc.mcp_server_ids_json,
|
|
142
|
+
source: doc.source,
|
|
143
|
+
max_iterations: doc.max_iterations,
|
|
144
|
+
workspace: doc.workspace,
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ─── Main compiler ─────────────────────────────────────────────────────────
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Compile context for agent execution implementing 4 strategies:
|
|
152
|
+
* 1. WRITE - Load scratchpad notes
|
|
153
|
+
* 2. SELECT - Tool loadout, playbook rules, selective history
|
|
154
|
+
* 3. COMPRESS - Use summaries, clear old tool results
|
|
155
|
+
* 4. ISOLATE - Worker gets minimal context
|
|
156
|
+
*/
|
|
157
|
+
export async function compileContext(opts: {
|
|
158
|
+
agentId: string
|
|
159
|
+
threadId: string
|
|
160
|
+
userId?: string
|
|
161
|
+
userMessage: string | ContentPart[]
|
|
162
|
+
channel?: string
|
|
163
|
+
isolated?: boolean
|
|
164
|
+
taskContext?: string | ContentPart[]
|
|
165
|
+
mcpManager?: MCPClientManager | null
|
|
166
|
+
/** G9 causal stream id for this invocation (agent-loop.ts's causalStreamId). */
|
|
167
|
+
causalStreamId?: string
|
|
168
|
+
}): Promise<CompiledContext> {
|
|
169
|
+
const { agentId, threadId, mcpManager, userMessage, isolated, taskContext } = opts
|
|
170
|
+
|
|
171
|
+
// Fallback: Get MCP Manager from singleton if not provided
|
|
172
|
+
const effectiveMcpManager = mcpManager ?? (() => {
|
|
173
|
+
const singletonMcp = getSingletonMCPManager()
|
|
174
|
+
if (singletonMcp) {
|
|
175
|
+
log.info(`[context-compiler] Using MCP Manager from singleton`)
|
|
176
|
+
return singletonMcp
|
|
177
|
+
}
|
|
178
|
+
return null
|
|
179
|
+
})()
|
|
180
|
+
|
|
181
|
+
// Resolve userId from database with priority: explicit param → channel identity → single user
|
|
182
|
+
const userId = opts.userId || (await resolveUserId({
|
|
183
|
+
threadId,
|
|
184
|
+
channel: opts.channel,
|
|
185
|
+
channelUserId: threadId
|
|
186
|
+
})) || threadId || ""
|
|
187
|
+
|
|
188
|
+
// [STEP-1] Load agent config
|
|
189
|
+
log.info(`[context-compiler] [STEP-1] Loading agent config for id=${agentId}`)
|
|
190
|
+
let agent: ReturnType<typeof fromAgentDoc>
|
|
191
|
+
try {
|
|
192
|
+
const agentsCol = await col<AgentDoc>("agents")
|
|
193
|
+
const entry = await agentsCol.get(agentId)
|
|
194
|
+
agent = entry ? fromAgentDoc(entry.doc) : undefined
|
|
195
|
+
} catch (err) {
|
|
196
|
+
log.error(`[context-compiler] [STEP-1] ❌ FAILED loading agent: ${JSON.stringify(err)}`)
|
|
197
|
+
throw err
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (!agent) {
|
|
201
|
+
throw new Error(`Agent not found: ${agentId}`)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const isWorker = agent.role === 'worker' || !!isolated
|
|
205
|
+
const canDiscoverAllMcp = agent.role === "coordinator" && !isolated
|
|
206
|
+
// A catalog-seeded agent (agent-catalog.ts) has its loadout fully
|
|
207
|
+
// curated (tool_allowlist_json/skills_json/mcp scope) — plain agent_create
|
|
208
|
+
// workers and the coordinator get the open/minimal defaults below instead.
|
|
209
|
+
const isCatalogAgent = agent.source === "catalog"
|
|
210
|
+
log.info(`[context-compiler] [STEP-1] ✅ Compiling for ${isWorker ? 'worker' : 'coordinator'} agent=${agent.name}`)
|
|
211
|
+
|
|
212
|
+
// Load model's context window for compaction decisions
|
|
213
|
+
let modelContextWindow = DEFAULT_CONTEXT_WINDOW
|
|
214
|
+
if (agent.model_id) {
|
|
215
|
+
try {
|
|
216
|
+
const modelsCol = await col<ModelDoc>("models")
|
|
217
|
+
// Id completo: el recorte del primer segmento fallaba para todo modelo
|
|
218
|
+
// con barra en el nombre y dejaba el context window en el default.
|
|
219
|
+
const modelEntry = await modelsCol.get(agent.model_id)
|
|
220
|
+
if (modelEntry?.doc.context_window) modelContextWindow = modelEntry.doc.context_window
|
|
221
|
+
} catch { /* use default */ }
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// [STEP-2] STRATEGY 1: WRITE — Load scratchpad (persistent notes)
|
|
225
|
+
log.info(`[context-compiler] [STEP-2] Loading scratchpad...`)
|
|
226
|
+
let scratchpadNotes: Awaited<ReturnType<typeof getScratchpad>> = []
|
|
227
|
+
try {
|
|
228
|
+
scratchpadNotes = await getScratchpad(threadId)
|
|
229
|
+
log.info(`[context-compiler] [STEP-2] ✅ Loaded ${scratchpadNotes.length} scratchpad notes`)
|
|
230
|
+
} catch (err) {
|
|
231
|
+
log.error(`[context-compiler] [STEP-2] ❌ FAILED loading scratchpad: ${JSON.stringify(err)}`)
|
|
232
|
+
throw err
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// [STEP-3c] Load MCP tools (executors only — index sync happens here too)
|
|
236
|
+
log.info(`[context-compiler] [STEP-3c] Loading MCP tools...`)
|
|
237
|
+
const mcpToolExecutors: ContextTool[] = []
|
|
238
|
+
|
|
239
|
+
if (effectiveMcpManager) {
|
|
240
|
+
try {
|
|
241
|
+
const mcpServersCol = await col<import("../storage/collections").McpServerDoc>("mcpServers")
|
|
242
|
+
const assignedMcpIds = new Set<string>([
|
|
243
|
+
...(agent.mcp_server_ids_json ? JSON.parse(agent.mcp_server_ids_json) : []),
|
|
244
|
+
...(agent.active_mcp_json ? JSON.parse(agent.active_mcp_json) : []),
|
|
245
|
+
])
|
|
246
|
+
const dbServers = (await mcpServersCol.scan({}))
|
|
247
|
+
.map(e => e.doc)
|
|
248
|
+
.filter(s => s.enabled && (canDiscoverAllMcp || assignedMcpIds.has(s.id)))
|
|
249
|
+
|
|
250
|
+
for (const server of dbServers) {
|
|
251
|
+
// Try ID first (normalized), then name
|
|
252
|
+
let resolvedServerKey = server.id
|
|
253
|
+
let serverTools = effectiveMcpManager.getServerTools(server.id)
|
|
254
|
+
if (!serverTools || serverTools.length === 0) {
|
|
255
|
+
resolvedServerKey = server.name
|
|
256
|
+
serverTools = effectiveMcpManager.getServerTools(server.name)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Lazy wake: the server is registered but dormant (no agent has leased
|
|
260
|
+
// it yet). Connect on demand so the coordinator isn't permanently cut off
|
|
261
|
+
// from tools nothing else ever wakes.
|
|
262
|
+
if (!serverTools || serverTools.length === 0) {
|
|
263
|
+
for (const key of [server.id, server.name]) {
|
|
264
|
+
try {
|
|
265
|
+
await withMcpConnectTimeout(() => effectiveMcpManager!.connectServer(key), MCP_LAZY_CONNECT_TIMEOUT_MS)
|
|
266
|
+
const woken = effectiveMcpManager.getServerTools(key)
|
|
267
|
+
if (woken && woken.length > 0) {
|
|
268
|
+
resolvedServerKey = key
|
|
269
|
+
serverTools = woken
|
|
270
|
+
break
|
|
271
|
+
}
|
|
272
|
+
} catch (err) {
|
|
273
|
+
log.warn(`[context-compiler] [STEP-3c] Lazy connect failed for ${server.name} (${key}): ${(err as Error).message}`)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (serverTools && serverTools.length > 0) {
|
|
279
|
+
log.info(`[context-compiler] [STEP-3c] Server ${server.name}: ${serverTools.length} tools`)
|
|
280
|
+
|
|
281
|
+
for (const mcpTool of serverTools) {
|
|
282
|
+
// Sanitized name valid for all LLM providers (no spaces, max 64 chars)
|
|
283
|
+
const fullName = mcpToolFullName(server.name, mcpTool.name)
|
|
284
|
+
|
|
285
|
+
// Skip tools whose sanitized name is empty or fails provider validation
|
|
286
|
+
if (!fullName || !/^[a-zA-Z0-9_-]{1,64}$/.test(fullName)) {
|
|
287
|
+
log.warn(`[context-compiler] Skipping MCP tool with unsupported name: "${mcpTool.name}" (server: ${server.name}, sanitized: "${fullName}")`)
|
|
288
|
+
continue
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Executor for agent-loop (has the real call)
|
|
292
|
+
mcpToolExecutors.push({
|
|
293
|
+
name: fullName,
|
|
294
|
+
description: mcpTool.description || `Tool from ${server.name}`,
|
|
295
|
+
parameters: mcpTool.inputSchema || { type: "object", properties: {} },
|
|
296
|
+
execute: async (params: Record<string, unknown>) => {
|
|
297
|
+
// Return raw JS value — agent-loop will TOON-encode via formatToolResult.
|
|
298
|
+
// Never pre-stringify here: formatToolResult(string) double-encodes.
|
|
299
|
+
return await effectiveMcpManager.callTool(resolvedServerKey, mcpTool.name, params)
|
|
300
|
+
},
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
}
|
|
304
|
+
} else {
|
|
305
|
+
log.warn(`[context-compiler] [STEP-3c] Server ${server.name} has no tools (not connected yet)`)
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
log.info(`[context-compiler] [STEP-3c] ✅ Loaded ${mcpToolExecutors.length} MCP tools`)
|
|
310
|
+
|
|
311
|
+
// Persist MCP tool definitions to DB for search_knowledge (HiveDB index)
|
|
312
|
+
if (mcpToolExecutors.length > 0) {
|
|
313
|
+
try {
|
|
314
|
+
for (const server of dbServers) {
|
|
315
|
+
let serverTools = effectiveMcpManager!.getServerTools(server.id)
|
|
316
|
+
if (!serverTools || serverTools.length === 0) {
|
|
317
|
+
serverTools = effectiveMcpManager!.getServerTools(server.name)
|
|
318
|
+
}
|
|
319
|
+
if (serverTools && serverTools.length > 0) {
|
|
320
|
+
await syncMCPToolsToDB(server.id || server.name, server.name, serverTools)
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
await syncMCPToolsToIndex();
|
|
324
|
+
log.info(`[context-compiler] [STEP-3c] ✅ Persisted MCP tools to DB + HiveDB index`)
|
|
325
|
+
} catch (syncErr) {
|
|
326
|
+
log.warn(`[context-compiler] [STEP-3c] ⚠️ Failed to persist MCP tools to DB: ${(syncErr as Error).message}`)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
} catch (err) {
|
|
330
|
+
log.error(`[context-compiler] [STEP-3c] ❌ Failed: ${(err as Error).message}`)
|
|
331
|
+
}
|
|
332
|
+
} else {
|
|
333
|
+
log.info(`[context-compiler] [STEP-3c] ⚠️ No MCP manager, skipping MCP tools`)
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// [STEP-4] Minimal tool set — agent discovers the rest via search_knowledge
|
|
337
|
+
log.info(`[context-compiler] [STEP-4] Building minimal tool set`)
|
|
338
|
+
|
|
339
|
+
// [STEP-8] Combine native tools + MCP executors loaded in STEP-3c
|
|
340
|
+
const config = { tools: {} }
|
|
341
|
+
const allNativeTools = createAllTools(config)
|
|
342
|
+
const nativeTools: ContextTool[] = allNativeTools.map(t => ({
|
|
343
|
+
name: t.name,
|
|
344
|
+
description: t.description || "",
|
|
345
|
+
parameters: t.parameters as any,
|
|
346
|
+
execute: t.execute,
|
|
347
|
+
timeoutMs: t.timeoutMs,
|
|
348
|
+
}))
|
|
349
|
+
|
|
350
|
+
let allTools = [...nativeTools, ...mcpToolExecutors]
|
|
351
|
+
|
|
352
|
+
// Only native minimal tools in LLM context
|
|
353
|
+
// MCP tools are discovered dynamically via search_knowledge(type="mcp")
|
|
354
|
+
let filteredNativeTools: ContextTool[] = nativeTools.filter(t => MINIMAL_TOOLS.has(t.name))
|
|
355
|
+
if (isCatalogAgent) {
|
|
356
|
+
const allowedNames = new Set<string>(
|
|
357
|
+
agent.tool_allowlist_json
|
|
358
|
+
? expandToolAllowlist(
|
|
359
|
+
JSON.parse(agent.tool_allowlist_json),
|
|
360
|
+
nativeTools.map((tool) => tool.name),
|
|
361
|
+
)
|
|
362
|
+
: agent.tools_json
|
|
363
|
+
? JSON.parse(agent.tools_json)
|
|
364
|
+
: [],
|
|
365
|
+
)
|
|
366
|
+
filteredNativeTools = nativeTools.filter((tool) => allowedNames.has(tool.name))
|
|
367
|
+
allTools = [...filteredNativeTools, ...mcpToolExecutors]
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const nativeToolsForLLM: LLMToolDef[] = filteredNativeTools.map(t => ({
|
|
371
|
+
type: "function" as const,
|
|
372
|
+
function: {
|
|
373
|
+
name: t.name,
|
|
374
|
+
description: t.description,
|
|
375
|
+
parameters: t.parameters,
|
|
376
|
+
},
|
|
377
|
+
}))
|
|
378
|
+
|
|
379
|
+
let toolsForLLM: LLMToolDef[] = nativeToolsForLLM
|
|
380
|
+
// Workers receive MCP tools directly only when prepareDelegation has
|
|
381
|
+
// activated a persistent assignment (or the verifier's internal readback
|
|
382
|
+
// scope). The coordinator keeps every enabled MCP executor discoverable,
|
|
383
|
+
// but out of its initial prompt.
|
|
384
|
+
if (!canDiscoverAllMcp && mcpToolExecutors.length > 0) {
|
|
385
|
+
toolsForLLM = [
|
|
386
|
+
...toolsForLLM,
|
|
387
|
+
...mcpToolExecutors.map((tool) => ({
|
|
388
|
+
type: "function" as const,
|
|
389
|
+
function: {
|
|
390
|
+
name: tool.name,
|
|
391
|
+
description: tool.description,
|
|
392
|
+
parameters: tool.parameters,
|
|
393
|
+
},
|
|
394
|
+
})),
|
|
395
|
+
]
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
log.info(`[context-compiler] [STEP-4] Minimal native tool set: ${filteredNativeTools.length} tools`)
|
|
399
|
+
log.info(
|
|
400
|
+
canDiscoverAllMcp
|
|
401
|
+
? `[context-compiler] [STEP-4b] MCP tools discoverable by coordinator: ${mcpToolExecutors.length}`
|
|
402
|
+
: `[context-compiler] [STEP-4b] MCP tools assigned directly to worker: ${mcpToolExecutors.length}`,
|
|
403
|
+
)
|
|
404
|
+
log.info(`[context-compiler] [STEP-8] ✅ Combined tools: ${allTools.length} total executors, ${toolsForLLM.length} in LLM context`)
|
|
405
|
+
|
|
406
|
+
// [STEP-8b] STRATEGY 2: SELECT — Skill Loadout (minimal + discovered)
|
|
407
|
+
log.info(`[context-compiler] [STEP-8b] Building skill loadout...`)
|
|
408
|
+
let minimalSkills: SkillDescriptor[] = []
|
|
409
|
+
let discoveredSkills: SkillDescriptor[] = []
|
|
410
|
+
|
|
411
|
+
try {
|
|
412
|
+
// Load minimal skills (always available)
|
|
413
|
+
minimalSkills = await getMinimalSkills()
|
|
414
|
+
log.info(`[context-compiler] [STEP-8b] ✅ Loaded ${minimalSkills.length} minimal skills`)
|
|
415
|
+
|
|
416
|
+
// Discover additional skills via HiveDB search (coordinator only)
|
|
417
|
+
if (!isWorker) {
|
|
418
|
+
const inputForSkills = taskContext || userMessage
|
|
419
|
+
const textMessage = typeof inputForSkills === "string"
|
|
420
|
+
? inputForSkills
|
|
421
|
+
: Array.isArray(inputForSkills)
|
|
422
|
+
? inputForSkills.filter(p => p.type === "text").map(p => (p as any).text).join("\n")
|
|
423
|
+
: String(inputForSkills)
|
|
424
|
+
discoveredSkills = await selectSkills(textMessage)
|
|
425
|
+
log.info(`[context-compiler] [STEP-8b] ✅ Discovered ${discoveredSkills.length} additional skills via HiveDB`)
|
|
426
|
+
}
|
|
427
|
+
if (isCatalogAgent && agent.skills_json) {
|
|
428
|
+
for (const skillId of JSON.parse(agent.skills_json) as string[]) {
|
|
429
|
+
const forced = await getSkillByName(skillId)
|
|
430
|
+
if (forced) discoveredSkills.push(forced)
|
|
431
|
+
}
|
|
432
|
+
log.info(`[context-compiler] [STEP-8b] ✅ Loaded ${discoveredSkills.length} catalog agent skills`)
|
|
433
|
+
}
|
|
434
|
+
} catch (err) {
|
|
435
|
+
log.warn(`[context-compiler] [STEP-8b] ⚠️ Skill loadout failed: ${(err as Error).message}`)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Combine skills (minimal + discovered, avoiding duplicates)
|
|
439
|
+
const skillMap = new Map<string, SkillDescriptor>()
|
|
440
|
+
for (const skill of minimalSkills) {
|
|
441
|
+
skillMap.set(skill.name, skill)
|
|
442
|
+
}
|
|
443
|
+
for (const skill of discoveredSkills) {
|
|
444
|
+
if (!skillMap.has(skill.name)) {
|
|
445
|
+
skillMap.set(skill.name, skill)
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
const allSkills = Array.from(skillMap.values())
|
|
449
|
+
|
|
450
|
+
// [STEP-9] STRATEGY 3: COMPRESS — Load history with compaction
|
|
451
|
+
log.info(`[context-compiler] [STEP-9] Loading conversation history...`)
|
|
452
|
+
let recentMessages: Awaited<ReturnType<typeof getRecentMessages>> = []
|
|
453
|
+
try {
|
|
454
|
+
recentMessages = await getRecentMessages(threadId, KEEP_LAST_N_MESSAGES)
|
|
455
|
+
log.info(`[context-compiler] [STEP-9] ✅ Loaded ${recentMessages.length} recent messages`)
|
|
456
|
+
} catch (err) {
|
|
457
|
+
log.error(`[context-compiler] [STEP-9] ❌ FAILED loading history: ${JSON.stringify(err)}`)
|
|
458
|
+
throw err
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Check if we need to use summary (conversation is long)
|
|
462
|
+
let summary: Awaited<ReturnType<typeof getSummary>> = null
|
|
463
|
+
try {
|
|
464
|
+
summary = await getSummary(threadId)
|
|
465
|
+
} catch (err) {
|
|
466
|
+
log.error(`[context-compiler] [STEP-9b] ❌ FAILED loading summary: ${JSON.stringify(err)}`)
|
|
467
|
+
throw err
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// A summary applies when it covers messages that fell out of the current
|
|
471
|
+
// window — i.e. the window's earliest message is strictly after the last
|
|
472
|
+
// message the summary covers. (Comparing window tokens against a
|
|
473
|
+
// context-window-sized threshold, as before, almost never fires: the
|
|
474
|
+
// window is capped at KEEP_LAST_N_MESSAGES messages, which rarely
|
|
475
|
+
// approaches 80% of the context window on its own — so the summary was
|
|
476
|
+
// computed, stored, and the user notified, but never actually reached the
|
|
477
|
+
// model.)
|
|
478
|
+
const summaryApplies = !!(
|
|
479
|
+
summary && summary.last_message_id > 0 &&
|
|
480
|
+
(recentMessages.length === 0 || recentMessages[0].id > summary.last_message_id)
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
const conversationSummarySection = summaryApplies
|
|
484
|
+
? `\n\n# RESUMEN DE LA CONVERSACIÓN (turnos anteriores, compactados)\n${summary!.summary.slice(0, SUMMARY_MAX_CHARS)}\n`
|
|
485
|
+
: ""
|
|
486
|
+
|
|
487
|
+
if (summaryApplies) {
|
|
488
|
+
log.info(`[context-compiler] [STEP-9c] Summary applies (${summary!.messages_covered} messages compressed) — folded into system prompt`)
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Never a second "system" turn here — every provider (Gemini, Anthropic,
|
|
492
|
+
// OpenAI-compat) hoists ALL role:"system" messages into a single top-level
|
|
493
|
+
// system instruction, so a second one either gets silently merged (fine)
|
|
494
|
+
// or — on a context-overflow retry that keeps only the LAST system message
|
|
495
|
+
// (openai-compat-base.ts) — silently replaces the real prompt. The summary
|
|
496
|
+
// lives in `systemPrompt` instead (see conversationSummarySection below).
|
|
497
|
+
const messages: LLMMessage[] = toAPIMessages(recentMessages)
|
|
498
|
+
|
|
499
|
+
// [STEP-10] STRATEGY 4: ISOLATE — Build context based on agent role
|
|
500
|
+
log.info(`[context-compiler] [STEP-10] Building system prompt...`)
|
|
501
|
+
let systemPrompt: string
|
|
502
|
+
try {
|
|
503
|
+
systemPrompt = await buildSystemPromptWithProjects({ agentId, userId })
|
|
504
|
+
log.info(`[context-compiler] [STEP-10] ✅ System prompt built (${systemPrompt.length} chars)`)
|
|
505
|
+
} catch (err) {
|
|
506
|
+
log.error(`[context-compiler] [STEP-10] ❌ FAILED building system prompt: ${JSON.stringify(err)}`)
|
|
507
|
+
throw err
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// [STEP-10b] Inject current date/time (ENTORNO ACTUAL)
|
|
511
|
+
const usersCol = await col<import("../storage/collections").UserDoc>("users")
|
|
512
|
+
const userRow = await usersCol.get(userId)
|
|
513
|
+
const userTimezone = userRow?.doc.timezone || "UTC"
|
|
514
|
+
const now = new Date()
|
|
515
|
+
const fecha = getUserDate(userTimezone, now)
|
|
516
|
+
const hora = getUserTime(userTimezone, now)
|
|
517
|
+
const workspaceLine = agent.workspace ? `\n**Workspace**: ${agent.workspace} (usa SIEMPRE este path como basePath en herramientas de filesystem)` : ""
|
|
518
|
+
systemPrompt += `\n\n# ENTORNO ACTUAL\n**Fecha**: ${fecha}\n**Hora**: ${hora}\n**Zona horaria**: ${userTimezone}${workspaceLine}\n`
|
|
519
|
+
log.info(`[context-compiler] [STEP-10b] ✅ Injected current date/time: ${fecha} ${hora} (${userTimezone})`)
|
|
520
|
+
|
|
521
|
+
// Placed early (right after ENTORNO ACTUAL), not appended at the end: the
|
|
522
|
+
// truncation guard below cuts the system prompt's TAIL when it's over
|
|
523
|
+
// budget, so a summary appended last would be the first thing silently
|
|
524
|
+
// dropped on an oversized prompt.
|
|
525
|
+
systemPrompt += conversationSummarySection
|
|
526
|
+
|
|
527
|
+
// Only the live roster goes here — how to delegate, fan-out/fan-in and the
|
|
528
|
+
// execution-truth rules are static doctrine and live in the coordinator's
|
|
529
|
+
// stored prompt (storage/onboarding.ts), not duplicated per turn.
|
|
530
|
+
if (!isWorker) {
|
|
531
|
+
const routingCatalog = renderAgentRoutingCatalog(await listCatalogAgents())
|
|
532
|
+
systemPrompt += `\n\n# COLMENA DE AGENTES\nWorkers disponibles ahora mismo (globales del sistema, ya existen):\n\n${routingCatalog}\n`
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const playbookInput = taskContext || userMessage
|
|
536
|
+
const playbookText = typeof playbookInput === "string"
|
|
537
|
+
? playbookInput
|
|
538
|
+
: Array.isArray(playbookInput)
|
|
539
|
+
? playbookInput.filter((part) => part.type === "text").map((part) => (part as any).text).join("\n")
|
|
540
|
+
: String(playbookInput)
|
|
541
|
+
const playbookRules = (await selectPlaybookRules(playbookText)).filter((rule) => {
|
|
542
|
+
if (!rule.applicable_to || !rule.applicable_to.includes("agent:")) return true
|
|
543
|
+
return isCatalogAgent ? rule.applicable_to.includes(`agent:${agent.id}`) : false
|
|
544
|
+
})
|
|
545
|
+
if (playbookRules.length > 0) {
|
|
546
|
+
systemPrompt += `\n\n# PLAYBOOK APRENDIDO\n${playbookRules.map((rule) => `- ${rule.rule}`).join("\n")}\n`
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Inject scratchpad (Strategy: WRITE) — usando TOON para ahorro de tokens
|
|
550
|
+
if (scratchpadNotes.length > 0) {
|
|
551
|
+
const scratchpadData: Record<string, string> = {}
|
|
552
|
+
for (const n of scratchpadNotes) {
|
|
553
|
+
scratchpadData[n.key] = n.value
|
|
554
|
+
}
|
|
555
|
+
// TOON comprime el formato clave-valor
|
|
556
|
+
const scratchpadContent = formatContext(scratchpadData)
|
|
557
|
+
systemPrompt += `\n\n# SCRATCHPAD (Persistent Notes)\n${scratchpadContent}\n`
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// G9: causal context window (buildAgentContext) — only when the summary
|
|
561
|
+
// applies this turn (a real DB round-trip, not a per-turn cost) and
|
|
562
|
+
// there's a causal stream to build it from. episodicSimilarity is omitted:
|
|
563
|
+
// it requires embeddings hive doesn't generate anywhere yet.
|
|
564
|
+
if (summaryApplies && opts.causalStreamId && loadConfig().causalLog?.enabled) {
|
|
565
|
+
try {
|
|
566
|
+
const causalDb = await getHiveDb()
|
|
567
|
+
const objectiveSource = taskContext || userMessage
|
|
568
|
+
const currentObjective = typeof objectiveSource === "string"
|
|
569
|
+
? objectiveSource
|
|
570
|
+
: Array.isArray(objectiveSource)
|
|
571
|
+
? objectiveSource.filter((p) => p.type === "text").map((p) => (p as any).text).join("\n")
|
|
572
|
+
: String(objectiveSource)
|
|
573
|
+
const causalMaxTokens = Math.max(500, Math.min(4000, Math.floor(modelContextWindow * 0.05)))
|
|
574
|
+
|
|
575
|
+
const causalCtx = (await causalDb.buildAgentContext({
|
|
576
|
+
taskId: opts.causalStreamId,
|
|
577
|
+
currentPhase: "current",
|
|
578
|
+
currentObjective: currentObjective.slice(0, 2000),
|
|
579
|
+
maxTokens: causalMaxTokens,
|
|
580
|
+
strategy: { causalAnchors: true, compressCompletedPhases: true },
|
|
581
|
+
})) as AgentContextShape
|
|
582
|
+
|
|
583
|
+
const causalLines = [...(causalCtx.items ?? []), ...(causalCtx.anomalies ?? [])]
|
|
584
|
+
.map(formatCausalContextItem)
|
|
585
|
+
.filter((line): line is string => !!line)
|
|
586
|
+
|
|
587
|
+
if (causalLines.length > 0) {
|
|
588
|
+
systemPrompt += `\n\n# CAUSAL CONTEXT (decisiones y tool calls de este turno, previos a la compactación — priorizá la conversación actual; usalo solo para no repetir algo que ya funcionó o ya falló)\n${causalLines.join("\n")}\n`
|
|
589
|
+
log.info(`[context-compiler] [STEP-9d] ✅ Injected ${causalLines.length} causal context item(s)`)
|
|
590
|
+
}
|
|
591
|
+
} catch (err) {
|
|
592
|
+
log.warn(`[context-compiler] [STEP-9d] ⚠️ Causal context build failed: ${(err as Error).message}`)
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Coordinator only. Just the live loadout — how to use it is doctrine and
|
|
597
|
+
// lives in the stored prompt + the capability_discovery skill.
|
|
598
|
+
if (!isWorker) {
|
|
599
|
+
const minimalToolsDocs = filteredNativeTools
|
|
600
|
+
.filter(t => MINIMAL_TOOLS.has(t.name))
|
|
601
|
+
.map(t => `- **${t.name}**: ${t.description || "Herramienta nativa"}`)
|
|
602
|
+
.join("\n")
|
|
603
|
+
|
|
604
|
+
systemPrompt += `\n\n# HERRAMIENTAS SIEMPRE DISPONIBLES\n${minimalToolsDocs}\n`
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
// Inject available skills (minimal + discovered)
|
|
608
|
+
if (allSkills.length > 0) {
|
|
609
|
+
// Minimal skills: inject full body (always-loaded instructions)
|
|
610
|
+
const minimalNames = new Set(minimalSkills.map(s => s.name))
|
|
611
|
+
const minimalWithBody = allSkills.filter(s => minimalNames.has(s.name) && s.body)
|
|
612
|
+
if (minimalWithBody.length > 0) {
|
|
613
|
+
let minimalSection = `\n\n# SKILLS SIEMPRE ACTIVAS\n`
|
|
614
|
+
for (const skill of minimalWithBody) {
|
|
615
|
+
minimalSection += `\n## ${skill.name}\n${skill.body}\n`
|
|
616
|
+
}
|
|
617
|
+
systemPrompt += minimalSection
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Discovered skills: list only (body arrives via agent-loop when tools are injected)
|
|
621
|
+
const discoveredOnly = allSkills.filter(s => !minimalNames.has(s.name))
|
|
622
|
+
if (discoveredOnly.length > 0) {
|
|
623
|
+
let discoveredSection = `\n\n# SKILLS DESCUBIERTAS (relevantes para esta tarea)\n`
|
|
624
|
+
for (const skill of discoveredOnly) {
|
|
625
|
+
const desc = skill.description ? ` — ${skill.description}` : ""
|
|
626
|
+
discoveredSection += `- **${skill.name}**${desc}\n`
|
|
627
|
+
}
|
|
628
|
+
systemPrompt += discoveredSection
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
log.info(`[context-compiler] [STEP-10d] Injected ${minimalWithBody.length} minimal skill bodies + ${discoveredOnly.length} discovered skills`)
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// For isolated workers, add task context + tool discovery instruction
|
|
637
|
+
if (isWorker && opts.taskContext && !isCatalogAgent) {
|
|
638
|
+
systemPrompt += `\n\n# HERRAMIENTAS DISPONIBLES\n` +
|
|
639
|
+
`Arrancas con herramientas básicas. Si tu tarea requiere herramientas adicionales (web_search, fs_read, browser_navigate, etc.):\n` +
|
|
640
|
+
`1. Usá \`search_knowledge(type="tools", query="<herramienta o tarea>")\` para encontrarlas.\n` +
|
|
641
|
+
`2. Las herramientas que encuentres estarán disponibles para usar inmediatamente.\n` +
|
|
642
|
+
`Si el coordinador te indicó herramientas específicas, buscalas primero con search_knowledge antes de ejecutar tu tarea.\n` +
|
|
643
|
+
`\n# CURRENT TASK\n${opts.taskContext}\n\nFocus ONLY on this task. Do not deviate.`
|
|
644
|
+
} else if (isWorker && opts.taskContext) {
|
|
645
|
+
systemPrompt += `\n\n# CURRENT TASK\n${opts.taskContext}\n\nFocus ONLY on this task and return the required structured delivery.`
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// Truncate system prompt only when it exceeds a model-aware budget.
|
|
649
|
+
const maxSystemPromptChars = Math.min(
|
|
650
|
+
MAX_SYSTEM_PROMPT_CHARS_CAP,
|
|
651
|
+
Math.max(8000, Math.floor(modelContextWindow * COMPACT_RATIO * 4))
|
|
652
|
+
)
|
|
653
|
+
if (systemPrompt.length > maxSystemPromptChars) {
|
|
654
|
+
const originalLen = systemPrompt.length
|
|
655
|
+
systemPrompt = systemPrompt.substring(0, maxSystemPromptChars) +
|
|
656
|
+
`\n\n[... System prompt truncated (${originalLen} chars → ${maxSystemPromptChars} chars) ...]`
|
|
657
|
+
log.info(`[context-compiler] System prompt truncated: ${originalLen} → ${maxSystemPromptChars} chars`)
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
const estimatedSystemTokens = estimateTokens(systemPrompt)
|
|
661
|
+
const estimatedMsgTokens = messages.reduce((sum, m) => sum + estimateTokens(typeof m.content === 'string' ? m.content : JSON.stringify(m.content)), 0)
|
|
662
|
+
const estimatedToolTokens = toolsForLLM.reduce((sum, t) => sum + estimateTokens(JSON.stringify(t)), 0)
|
|
663
|
+
const estimatedTotal = estimatedSystemTokens + estimatedMsgTokens + estimatedToolTokens
|
|
664
|
+
const budgetPct = modelContextWindow > 0 ? Math.round((estimatedTotal / modelContextWindow) * 100) : 0
|
|
665
|
+
|
|
666
|
+
log.info(
|
|
667
|
+
`[context-compiler] ✅ DONE: ${allTools.length} total tools, ` +
|
|
668
|
+
`${toolsForLLM.length} selected tools, ${messages.length} messages, ` +
|
|
669
|
+
`${allSkills.length} skills, isolated=${isWorker}, ` +
|
|
670
|
+
`est.tokens: sys=${estimatedSystemTokens} msgs=${estimatedMsgTokens} tools=${estimatedToolTokens} ` +
|
|
671
|
+
`total=${estimatedTotal}/${modelContextWindow} (${budgetPct}%)`
|
|
672
|
+
)
|
|
673
|
+
|
|
674
|
+
return {
|
|
675
|
+
systemPrompt,
|
|
676
|
+
conversationSummarySection,
|
|
677
|
+
messages,
|
|
678
|
+
tools: toolsForLLM,
|
|
679
|
+
allTools,
|
|
680
|
+
skills: allSkills,
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// Re-export sync functions for gateway/initializer
|
|
685
|
+
export {
|
|
686
|
+
syncToolCatalogToIndex as syncToolsToIndex,
|
|
687
|
+
syncSkillsToIndex,
|
|
688
|
+
syncPlaybookToIndex,
|
|
689
|
+
}
|