@johpaz/hive-sdk 0.0.14 → 0.0.15
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/.github/CODEOWNERS +9 -0
- package/.github/workflows/publish.yml +89 -0
- package/.github/workflows/version-bump.yml +102 -0
- package/CHANGELOG.md +38 -0
- package/README.md +158 -0
- package/bun.lock +543 -0
- package/bunfig.toml +7 -0
- package/docs/API-AGENTS.md +316 -0
- package/docs/API-CONTEXT-COMPILER.md +252 -0
- package/docs/API-DAG-SCHEDULER.md +273 -0
- package/docs/API-TOOLS-SKILLS-CHANNELS.md +293 -0
- package/docs/API-WORKERS-EVENTS.md +152 -0
- package/docs/INDEX.md +141 -0
- package/docs/README.md +68 -0
- package/package.json +54 -105
- package/packages/cli/package.json +17 -0
- package/packages/cli/src/commands/init.ts +56 -0
- package/packages/cli/src/commands/run.ts +45 -0
- package/packages/cli/src/commands/test.ts +42 -0
- package/packages/cli/src/commands/trace.ts +55 -0
- package/packages/cli/src/index.ts +43 -0
- package/packages/core/package.json +58 -0
- package/packages/core/src/ace/Curator.ts +158 -0
- package/packages/core/src/ace/Reflector.ts +200 -0
- package/packages/core/src/ace/Tracer.ts +100 -0
- package/packages/core/src/ace/index.ts +4 -0
- package/packages/core/src/agent/AgentRunner.ts +699 -0
- package/packages/core/src/agent/Compaction.ts +221 -0
- package/packages/core/src/agent/ContextCompiler.ts +567 -0
- package/packages/core/src/agent/ContextGuard.ts +91 -0
- package/packages/core/src/agent/ConversationStore.ts +244 -0
- package/packages/core/src/agent/Hooks.ts +166 -0
- package/packages/core/src/agent/NativeTools.ts +31 -0
- package/packages/core/src/agent/PromptBuilder.ts +169 -0
- package/packages/core/src/agent/Service.ts +267 -0
- package/packages/core/src/agent/StuckLoop.ts +133 -0
- package/packages/core/src/agent/index.ts +12 -0
- package/packages/core/src/agent/providers/LLMClient.ts +149 -0
- package/packages/core/src/agent/providers/anthropic.ts +212 -0
- package/packages/core/src/agent/providers/gemini.ts +215 -0
- package/packages/core/src/agent/providers/index.ts +199 -0
- package/packages/core/src/agent/providers/interface.ts +195 -0
- package/packages/core/src/agent/providers/ollama.ts +175 -0
- package/packages/core/src/agent/providers/openai-compat.ts +231 -0
- package/packages/core/src/agent/providers.ts +1 -0
- package/packages/core/src/agent/selectors/PlaybookSelector.ts +147 -0
- package/packages/core/src/agent/selectors/SkillSelector.ts +478 -0
- package/packages/core/src/agent/selectors/ToolSelector.ts +577 -0
- package/packages/core/src/agent/selectors/index.ts +6 -0
- package/packages/core/src/api/createAgent.test.ts +48 -0
- package/packages/core/src/api/createAgent.ts +122 -0
- package/packages/core/src/api/index.ts +2 -0
- package/packages/core/src/canvas/CanvasManager.ts +390 -0
- package/packages/core/src/canvas/a2ui-tools.ts +255 -0
- package/packages/core/src/canvas/canvas-tools.ts +448 -0
- package/packages/core/src/canvas/emitter.ts +149 -0
- package/packages/core/src/canvas/index.ts +6 -0
- package/packages/core/src/config/index.ts +2 -0
- package/packages/core/src/config/loader.ts +554 -0
- package/packages/core/src/ethics/EthicsGuard.test.ts +54 -0
- package/packages/core/src/ethics/EthicsGuard.ts +66 -0
- package/packages/core/src/ethics/index.ts +2 -0
- package/packages/core/src/gateway/channel-notify.test.ts +14 -0
- package/packages/core/src/gateway/channel-notify.ts +12 -0
- package/packages/core/src/gateway/index.ts +1 -0
- package/packages/core/src/index.ts +37 -0
- package/packages/core/src/mcp/MCPClient.ts +439 -0
- package/packages/core/src/mcp/MCPToolAdapter.ts +176 -0
- package/packages/core/src/mcp/config.ts +13 -0
- package/packages/core/src/mcp/hot-reload.ts +147 -0
- package/packages/core/src/mcp/index.ts +11 -0
- package/packages/core/src/mcp/logger.ts +42 -0
- package/packages/core/src/mcp/singleton.ts +21 -0
- package/packages/core/src/mcp/transports/index.ts +67 -0
- package/packages/core/src/mcp/transports/sse.ts +241 -0
- package/packages/core/src/mcp/transports/websocket.ts +159 -0
- package/packages/core/src/memory/Scratchpad.test.ts +47 -0
- package/packages/core/src/memory/Scratchpad.ts +37 -0
- package/packages/core/src/memory/Storage.ts +6 -0
- package/packages/core/src/memory/index.ts +2 -0
- package/packages/core/src/multimodal/VisionService.ts +293 -0
- package/packages/core/src/multimodal/index.ts +2 -0
- package/packages/core/src/multimodal/types.ts +28 -0
- package/packages/core/src/security/Pairing.ts +250 -0
- package/packages/core/src/security/RateLimit.ts +270 -0
- package/packages/core/src/security/index.ts +4 -0
- package/packages/core/src/skills/SkillLoader.ts +388 -0
- package/packages/core/src/skills/bundled-data.generated.ts +3332 -0
- package/packages/core/src/skills/defineSkill.ts +18 -0
- package/packages/core/src/skills/index.ts +4 -0
- package/packages/core/src/state/index.ts +2 -0
- package/packages/core/src/state/store.ts +312 -0
- package/packages/core/src/storage/SQLiteStorage.ts +407 -0
- package/packages/core/src/storage/crypto.ts +101 -0
- package/packages/core/src/storage/index.ts +10 -0
- package/packages/core/src/storage/onboarding.ts +1603 -0
- package/packages/core/src/storage/schema.ts +689 -0
- package/packages/core/src/storage/seed.ts +740 -0
- package/packages/core/src/storage/usage.ts +374 -0
- package/packages/core/src/swarm/AgentBus.ts +460 -0
- package/packages/core/src/swarm/AgentExecutor.ts +53 -0
- package/packages/core/src/swarm/Coordinator.ts +251 -0
- package/packages/core/src/swarm/EventBridge.ts +122 -0
- package/packages/core/src/swarm/EventBus.ts +169 -0
- package/packages/core/src/swarm/TaskGraph.ts +192 -0
- package/packages/core/src/swarm/TaskNode.ts +97 -0
- package/packages/core/src/swarm/TaskResult.ts +22 -0
- package/packages/core/src/swarm/WorkerPool.ts +236 -0
- package/packages/core/src/swarm/errors.ts +37 -0
- package/packages/core/src/swarm/index.ts +30 -0
- package/packages/core/src/swarm/presets/HiveLearnPreset.ts +99 -0
- package/packages/core/src/swarm/presets/ResearchPreset.ts +97 -0
- package/packages/core/src/swarm/presets/index.ts +4 -0
- package/packages/core/src/swarm/strategies/ParallelStrategy.ts +21 -0
- package/packages/core/src/swarm/strategies/PriorityStrategy.ts +46 -0
- package/packages/core/src/swarm/strategies/index.ts +3 -0
- package/packages/core/src/swarm/types.ts +164 -0
- package/packages/core/src/tools/ToolExecutor.ts +58 -0
- package/packages/core/src/tools/ToolRegistry.test.ts +98 -0
- package/packages/core/src/tools/ToolRegistry.ts +61 -0
- package/packages/core/src/tools/agents/get-available-models.ts +118 -0
- package/packages/core/src/tools/agents/index.ts +715 -0
- package/packages/core/src/tools/bridge-events.ts +26 -0
- package/packages/core/src/tools/canvas/index.ts +375 -0
- package/packages/core/src/tools/cli/index.ts +142 -0
- package/packages/core/src/tools/codebridge/index.ts +342 -0
- package/packages/core/src/tools/core/index.ts +476 -0
- package/packages/core/src/tools/cron/index.ts +626 -0
- package/packages/core/src/tools/filesystem/fs-delete.ts +78 -0
- package/packages/core/src/tools/filesystem/fs-edit.ts +106 -0
- package/packages/core/src/tools/filesystem/fs-exists.ts +63 -0
- package/packages/core/src/tools/filesystem/fs-glob.ts +108 -0
- package/packages/core/src/tools/filesystem/fs-list.ts +129 -0
- package/packages/core/src/tools/filesystem/fs-read.ts +72 -0
- package/packages/core/src/tools/filesystem/fs-write.ts +67 -0
- package/packages/core/src/tools/filesystem/index.ts +34 -0
- package/packages/core/src/tools/filesystem/workspace-guard.ts +62 -0
- package/packages/core/src/tools/index.ts +231 -0
- package/packages/core/src/tools/meeting/index.ts +363 -0
- package/packages/core/src/tools/office/index.ts +47 -0
- package/packages/core/src/tools/office/office-escribir-docx.ts +192 -0
- package/packages/core/src/tools/office/office-escribir-pdf.ts +172 -0
- package/packages/core/src/tools/office/office-escribir-pptx.ts +174 -0
- package/packages/core/src/tools/office/office-escribir-xlsx.ts +116 -0
- package/packages/core/src/tools/office/office-leer-docx.ts +93 -0
- package/packages/core/src/tools/office/office-leer-pdf.ts +114 -0
- package/packages/core/src/tools/office/office-leer-pptx.ts +136 -0
- package/packages/core/src/tools/office/office-leer-xlsx.ts +124 -0
- package/packages/core/src/tools/projects/index.ts +37 -0
- package/packages/core/src/tools/projects/project-create.ts +94 -0
- package/packages/core/src/tools/projects/project-done.ts +66 -0
- package/packages/core/src/tools/projects/project-fail.ts +66 -0
- package/packages/core/src/tools/projects/project-list.ts +96 -0
- package/packages/core/src/tools/projects/project-update.ts +72 -0
- package/packages/core/src/tools/projects/task-create.ts +68 -0
- package/packages/core/src/tools/projects/task-evaluate.ts +93 -0
- package/packages/core/src/tools/projects/task-update.ts +93 -0
- package/packages/core/src/tools/types.ts +39 -0
- package/packages/core/src/tools/voice/index.ts +104 -0
- package/packages/core/src/tools/web/browser-click.ts +78 -0
- package/packages/core/src/tools/web/browser-extract.ts +139 -0
- package/packages/core/src/tools/web/browser-navigate.ts +106 -0
- package/packages/core/src/tools/web/browser-screenshot.ts +87 -0
- package/packages/core/src/tools/web/browser-script.ts +88 -0
- package/packages/core/src/tools/web/browser-service.ts +554 -0
- package/packages/core/src/tools/web/browser-type.ts +101 -0
- package/packages/core/src/tools/web/browser-wait.ts +136 -0
- package/packages/core/src/tools/web/index.ts +41 -0
- package/packages/core/src/tools/web/web-fetch.ts +78 -0
- package/packages/core/src/tools/web/web-search.ts +123 -0
- package/packages/core/src/utils/benchmark.ts +80 -0
- package/packages/core/src/utils/crypto.ts +73 -0
- package/packages/core/src/utils/date.ts +42 -0
- package/packages/core/src/utils/index.ts +10 -0
- package/packages/core/src/utils/logger.ts +389 -0
- package/packages/core/src/utils/retry.ts +70 -0
- package/packages/core/src/utils/toon.ts +253 -0
- package/packages/core/src/voice/index.ts +656 -0
- package/test/setup-db.ts +216 -0
- package/tsconfig.json +39 -0
- package/src/agents.ts +0 -1
- package/src/canvas.ts +0 -1
- package/src/channels.ts +0 -1
- package/src/config.ts +0 -1
- package/src/events.ts +0 -1
- package/src/gateway.ts +0 -1
- package/src/index.ts +0 -304
- package/src/mcp.ts +0 -1
- package/src/multimodal.ts +0 -1
- package/src/scheduler.ts +0 -1
- package/src/security.ts +0 -1
- package/src/skills.ts +0 -1
- package/src/state.ts +0 -1
- package/src/storage.ts +0 -1
- package/src/tools.ts +0 -1
- package/src/tts.ts +0 -1
- package/src/types.ts +0 -82
- package/src/utils.ts +0 -1
- package/src/voice.ts +0 -1
|
@@ -0,0 +1,567 @@
|
|
|
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 { getDb } from "../storage/SQLiteStorage.ts"
|
|
25
|
+
import { logger } from "../utils/logger.ts"
|
|
26
|
+
import type { LLMMessage, LLMToolDef, ContentPart } from "./providers/LLMClient"
|
|
27
|
+
import type { MCPClientManager } from "../mcp/index.ts"
|
|
28
|
+
import { syncToolCatalogToFTS, mcpToolFullName } from "./selectors/index.ts"
|
|
29
|
+
import { syncSkillsToFTS, getMinimalSkills, selectSkills, type SkillDescriptor } from "./selectors/index.ts"
|
|
30
|
+
import { syncPlaybookToFTS } from "./selectors/index.ts"
|
|
31
|
+
import { getRecentMessages, getSummary, getScratchpad, toAPIMessages } from "./ConversationStore"
|
|
32
|
+
import { formatContext, estimateTokens } from "../utils/toon.ts"
|
|
33
|
+
import { buildSystemPromptWithProjects } from "./PromptBuilder"
|
|
34
|
+
import { createAllTools } from "../tools/index.ts"
|
|
35
|
+
import { resolveUserId } from "../storage/onboarding.ts"
|
|
36
|
+
import { getMCPManager as getSingletonMCPManager } from "../mcp/singleton.ts"
|
|
37
|
+
import { syncMCPToolsToDB, syncMCPToolsToFTS } from "../mcp/MCPToolAdapter.ts"
|
|
38
|
+
import { getUserDate, getUserTime } from "../utils/date.ts"
|
|
39
|
+
|
|
40
|
+
const log = logger.child("context-compiler")
|
|
41
|
+
|
|
42
|
+
// Configuration constants
|
|
43
|
+
const KEEP_LAST_N_MESSAGES = 40 // Always keep last N messages (Strategy: SELECT) — increased because tool calls/results are now persisted
|
|
44
|
+
const TOKEN_COMPACT_THRESHOLD = 6000 // Compact when exceeds this (Strategy: COMPRESS)
|
|
45
|
+
|
|
46
|
+
// MINIMAL TOOL SET — fixed always-available tools
|
|
47
|
+
// The agent discovers the rest via search_knowledge
|
|
48
|
+
const MINIMAL_TOOLS = new Set([
|
|
49
|
+
"save_note",
|
|
50
|
+
"notify",
|
|
51
|
+
"report_progress",
|
|
52
|
+
"search_knowledge",
|
|
53
|
+
])
|
|
54
|
+
|
|
55
|
+
// MINIMAL SKILL SET — fixed always-available skills
|
|
56
|
+
// These skills are ALWAYS in context - the agent uses them to discover everything else
|
|
57
|
+
const MINIMAL_SKILL_NAMES = [
|
|
58
|
+
"busqueda_fts5", // Core: how to find tools, skills, MCP, playbook via search_knowledge
|
|
59
|
+
"canvas_report", // Display results to users with charts, tables, cards
|
|
60
|
+
"memory_manager", // Persistent notes that survive context compression
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
// ─── Types ─────────────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
// Simple tool interface for context compilation
|
|
66
|
+
export interface ContextTool {
|
|
67
|
+
name: string
|
|
68
|
+
description: string
|
|
69
|
+
parameters: Record<string, unknown>
|
|
70
|
+
execute?: (params: Record<string, unknown>) => Promise<unknown>
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface CompiledContext {
|
|
74
|
+
systemPrompt: string
|
|
75
|
+
messages: LLMMessage[]
|
|
76
|
+
tools: LLMToolDef[]
|
|
77
|
+
allTools: ContextTool[]
|
|
78
|
+
skills: SkillDescriptor[] // Skills loaded (minimal + discovered)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ─── Main compiler ─────────────────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Compile context for agent execution implementing 4 strategies:
|
|
85
|
+
* 1. WRITE - Load scratchpad notes
|
|
86
|
+
* 2. SELECT - Tool loadout, playbook rules, selective history
|
|
87
|
+
* 3. COMPRESS - Use summaries, clear old tool results
|
|
88
|
+
* 4. ISOLATE - Worker gets minimal context
|
|
89
|
+
*/
|
|
90
|
+
export async function compileContext(opts: {
|
|
91
|
+
agentId: string
|
|
92
|
+
threadId: string
|
|
93
|
+
userId?: string
|
|
94
|
+
userMessage: string | ContentPart[]
|
|
95
|
+
channel?: string
|
|
96
|
+
isolated?: boolean
|
|
97
|
+
taskContext?: string | ContentPart[]
|
|
98
|
+
mcpManager?: MCPClientManager | null
|
|
99
|
+
}): Promise<CompiledContext> {
|
|
100
|
+
const db = getDb()
|
|
101
|
+
const { agentId, threadId, mcpManager, userMessage, isolated, taskContext } = opts
|
|
102
|
+
|
|
103
|
+
// Fallback: Get MCP Manager from singleton if not provided
|
|
104
|
+
const effectiveMcpManager = mcpManager ?? (() => {
|
|
105
|
+
const singletonMcp = getSingletonMCPManager()
|
|
106
|
+
if (singletonMcp) {
|
|
107
|
+
log.info(`[context-compiler] Using MCP Manager from singleton`)
|
|
108
|
+
return singletonMcp
|
|
109
|
+
}
|
|
110
|
+
return null
|
|
111
|
+
})()
|
|
112
|
+
|
|
113
|
+
// Resolve userId from database with priority: explicit param → channel identity → single user
|
|
114
|
+
const userId = opts.userId || resolveUserId({
|
|
115
|
+
threadId,
|
|
116
|
+
channel: opts.channel,
|
|
117
|
+
channelUserId: threadId
|
|
118
|
+
}) || threadId || ""
|
|
119
|
+
|
|
120
|
+
// [STEP-1] Load agent config
|
|
121
|
+
log.info(`[context-compiler] [STEP-1] Loading agent config for id=${agentId}`)
|
|
122
|
+
let agent: any
|
|
123
|
+
try {
|
|
124
|
+
agent = db.query<any, [string]>(
|
|
125
|
+
"SELECT * FROM agents WHERE id = ?"
|
|
126
|
+
).get(agentId)
|
|
127
|
+
} catch (err) {
|
|
128
|
+
log.error(`[context-compiler] [STEP-1] ❌ FAILED loading agent: ${JSON.stringify(err)}`)
|
|
129
|
+
throw err
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!agent) {
|
|
133
|
+
throw new Error(`Agent not found: ${agentId}`)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const isWorker = agent.role === 'worker' || !!isolated
|
|
137
|
+
log.info(`[context-compiler] [STEP-1] ✅ Compiling for ${isWorker ? 'worker' : 'coordinator'} agent=${agent.name}`)
|
|
138
|
+
|
|
139
|
+
// [STEP-2] STRATEGY 1: WRITE — Load scratchpad (persistent notes)
|
|
140
|
+
log.info(`[context-compiler] [STEP-2] Loading scratchpad...`)
|
|
141
|
+
let scratchpadNotes: ReturnType<typeof getScratchpad> = []
|
|
142
|
+
try {
|
|
143
|
+
scratchpadNotes = getScratchpad(threadId)
|
|
144
|
+
log.info(`[context-compiler] [STEP-2] ✅ Loaded ${scratchpadNotes.length} scratchpad notes`)
|
|
145
|
+
} catch (err) {
|
|
146
|
+
log.error(`[context-compiler] [STEP-2] ❌ FAILED loading scratchpad: ${JSON.stringify(err)}`)
|
|
147
|
+
throw err
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// [STEP-3c] Load MCP tools (executors only — FTS sync happens here too)
|
|
151
|
+
log.info(`[context-compiler] [STEP-3c] Loading MCP tools...`)
|
|
152
|
+
const mcpToolExecutors: ContextTool[] = []
|
|
153
|
+
|
|
154
|
+
if (effectiveMcpManager) {
|
|
155
|
+
try {
|
|
156
|
+
const dbServers = db.query<any, []>(
|
|
157
|
+
"SELECT id, name, status FROM mcp_servers WHERE enabled = 1"
|
|
158
|
+
).all()
|
|
159
|
+
|
|
160
|
+
for (const server of dbServers) {
|
|
161
|
+
// Try ID first (normalized), then name
|
|
162
|
+
let serverTools = effectiveMcpManager.getServerTools(server.id)
|
|
163
|
+
if (!serverTools || serverTools.length === 0) {
|
|
164
|
+
serverTools = effectiveMcpManager.getServerTools(server.name)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (serverTools && serverTools.length > 0) {
|
|
168
|
+
log.info(`[context-compiler] [STEP-3c] Server ${server.name}: ${serverTools.length} tools`)
|
|
169
|
+
|
|
170
|
+
for (const mcpTool of serverTools) {
|
|
171
|
+
// Sanitized name valid for all LLM providers (no spaces, max 64 chars)
|
|
172
|
+
const fullName = mcpToolFullName(server.name, mcpTool.name)
|
|
173
|
+
|
|
174
|
+
// Executor for agent-loop (has the real call)
|
|
175
|
+
mcpToolExecutors.push({
|
|
176
|
+
name: fullName,
|
|
177
|
+
description: mcpTool.description || `Tool from ${server.name}`,
|
|
178
|
+
parameters: mcpTool.inputSchema || { type: "object", properties: {} },
|
|
179
|
+
execute: async (params: Record<string, unknown>) => {
|
|
180
|
+
// Return raw JS value — agent-loop will TOON-encode via formatToolResult.
|
|
181
|
+
// Never pre-stringify here: formatToolResult(string) double-encodes.
|
|
182
|
+
return await effectiveMcpManager.callTool(server.id, mcpTool.name, params)
|
|
183
|
+
},
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
log.warn(`[context-compiler] [STEP-3c] Server ${server.name} has no tools (not connected yet)`)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
log.info(`[context-compiler] [STEP-3c] ✅ Loaded ${mcpToolExecutors.length} MCP tools`)
|
|
193
|
+
|
|
194
|
+
// Persist MCP tool definitions to DB for search_knowledge and FTS5 search
|
|
195
|
+
if (mcpToolExecutors.length > 0) {
|
|
196
|
+
try {
|
|
197
|
+
for (const server of dbServers) {
|
|
198
|
+
let serverTools = effectiveMcpManager!.getServerTools(server.id)
|
|
199
|
+
if (!serverTools || serverTools.length === 0) {
|
|
200
|
+
serverTools = effectiveMcpManager!.getServerTools(server.name)
|
|
201
|
+
}
|
|
202
|
+
if (serverTools && serverTools.length > 0) {
|
|
203
|
+
syncMCPToolsToDB(server.id || server.name, server.name, serverTools)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
await syncMCPToolsToFTS();
|
|
207
|
+
log.info(`[context-compiler] [STEP-3c] ✅ Persisted MCP tools to DB + FTS5`)
|
|
208
|
+
} catch (syncErr) {
|
|
209
|
+
log.warn(`[context-compiler] [STEP-3c] ⚠️ Failed to persist MCP tools to DB: ${(syncErr as Error).message}`)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
} catch (err) {
|
|
213
|
+
log.error(`[context-compiler] [STEP-3c] ❌ Failed: ${(err as Error).message}`)
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
log.info(`[context-compiler] [STEP-3c] ⚠️ No MCP manager, skipping MCP tools`)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// [STEP-4] Minimal tool set — agent discovers the rest via search_knowledge
|
|
220
|
+
log.info(`[context-compiler] [STEP-4] Building minimal tool set`)
|
|
221
|
+
|
|
222
|
+
// [STEP-8] Combine native tools + MCP executors loaded in STEP-3c
|
|
223
|
+
const config = { tools: {} }
|
|
224
|
+
const allNativeTools = createAllTools(config)
|
|
225
|
+
const nativeTools: ContextTool[] = allNativeTools.map(t => ({
|
|
226
|
+
name: t.name,
|
|
227
|
+
description: t.description || "",
|
|
228
|
+
parameters: t.parameters as any,
|
|
229
|
+
execute: t.execute,
|
|
230
|
+
}))
|
|
231
|
+
|
|
232
|
+
const allTools = [...nativeTools, ...mcpToolExecutors]
|
|
233
|
+
|
|
234
|
+
// Only native minimal tools in LLM context
|
|
235
|
+
// MCP tools are discovered dynamically via search_knowledge(type="mcp")
|
|
236
|
+
const filteredNativeTools: ContextTool[] = nativeTools.filter(t => MINIMAL_TOOLS.has(t.name))
|
|
237
|
+
|
|
238
|
+
const nativeToolsForLLM: LLMToolDef[] = filteredNativeTools.map(t => ({
|
|
239
|
+
type: "function" as const,
|
|
240
|
+
function: {
|
|
241
|
+
name: t.name,
|
|
242
|
+
description: t.description,
|
|
243
|
+
parameters: t.parameters,
|
|
244
|
+
},
|
|
245
|
+
}))
|
|
246
|
+
|
|
247
|
+
const toolsForLLM: LLMToolDef[] = nativeToolsForLLM
|
|
248
|
+
|
|
249
|
+
log.info(`[context-compiler] [STEP-4] Minimal native tool set: ${filteredNativeTools.length} tools`)
|
|
250
|
+
log.info(`[context-compiler] [STEP-4b] MCP tools available via search_knowledge: ${mcpToolExecutors.length} (not injected)`)
|
|
251
|
+
log.info(`[context-compiler] [STEP-8] ✅ Combined tools: ${allTools.length} total executors, ${toolsForLLM.length} in LLM context`)
|
|
252
|
+
|
|
253
|
+
// [STEP-8b] STRATEGY 2: SELECT — Skill Loadout (minimal + discovered)
|
|
254
|
+
log.info(`[context-compiler] [STEP-8b] Building skill loadout...`)
|
|
255
|
+
let minimalSkills: SkillDescriptor[] = []
|
|
256
|
+
let discoveredSkills: SkillDescriptor[] = []
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
// Load minimal skills (always available)
|
|
260
|
+
minimalSkills = getMinimalSkills()
|
|
261
|
+
log.info(`[context-compiler] [STEP-8b] ✅ Loaded ${minimalSkills.length} minimal skills`)
|
|
262
|
+
|
|
263
|
+
// Discover additional skills via FTS5 (coordinator only)
|
|
264
|
+
if (!isWorker) {
|
|
265
|
+
const inputForSkills = taskContext || userMessage
|
|
266
|
+
const textMessage = typeof inputForSkills === "string"
|
|
267
|
+
? inputForSkills
|
|
268
|
+
: Array.isArray(inputForSkills)
|
|
269
|
+
? inputForSkills.filter(p => p.type === "text").map(p => (p as any).text).join("\n")
|
|
270
|
+
: String(inputForSkills)
|
|
271
|
+
discoveredSkills = selectSkills(textMessage)
|
|
272
|
+
log.info(`[context-compiler] [STEP-8b] ✅ Discovered ${discoveredSkills.length} additional skills via FTS5`)
|
|
273
|
+
}
|
|
274
|
+
} catch (err) {
|
|
275
|
+
log.warn(`[context-compiler] [STEP-8b] ⚠️ Skill loadout failed: ${(err as Error).message}`)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Combine skills (minimal + discovered, avoiding duplicates)
|
|
279
|
+
const skillMap = new Map<string, SkillDescriptor>()
|
|
280
|
+
for (const skill of minimalSkills) {
|
|
281
|
+
skillMap.set(skill.name, skill)
|
|
282
|
+
}
|
|
283
|
+
for (const skill of discoveredSkills) {
|
|
284
|
+
if (!skillMap.has(skill.name)) {
|
|
285
|
+
skillMap.set(skill.name, skill)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const allSkills = Array.from(skillMap.values())
|
|
289
|
+
|
|
290
|
+
// [STEP-9] STRATEGY 3: COMPRESS — Load history with compaction
|
|
291
|
+
log.info(`[context-compiler] [STEP-9] Loading conversation history...`)
|
|
292
|
+
let recentMessages: ReturnType<typeof getRecentMessages> = []
|
|
293
|
+
try {
|
|
294
|
+
recentMessages = getRecentMessages(threadId, KEEP_LAST_N_MESSAGES)
|
|
295
|
+
log.info(`[context-compiler] [STEP-9] ✅ Loaded ${recentMessages.length} recent messages`)
|
|
296
|
+
} catch (err) {
|
|
297
|
+
log.error(`[context-compiler] [STEP-9] ❌ FAILED loading history: ${JSON.stringify(err)}`)
|
|
298
|
+
throw err
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Check if we need to use summary (conversation is long)
|
|
302
|
+
let summary: ReturnType<typeof getSummary> = null
|
|
303
|
+
try {
|
|
304
|
+
summary = getSummary(threadId)
|
|
305
|
+
} catch (err) {
|
|
306
|
+
log.error(`[context-compiler] [STEP-9b] ❌ FAILED loading summary: ${JSON.stringify(err)}`)
|
|
307
|
+
throw err
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const totalTokens = recentMessages.reduce((sum, m) => sum + estimateTokens(m.content), 0)
|
|
311
|
+
|
|
312
|
+
let messages: LLMMessage[]
|
|
313
|
+
|
|
314
|
+
if (summary && totalTokens > TOKEN_COMPACT_THRESHOLD) {
|
|
315
|
+
// Use summary + recent messages (Strategy: COMPRESS)
|
|
316
|
+
messages = [
|
|
317
|
+
{ role: "system", content: `[Conversation Summary]: ${summary.summary}` },
|
|
318
|
+
...toAPIMessages(recentMessages),
|
|
319
|
+
]
|
|
320
|
+
log.info(`[context-compiler] [STEP-9c] Using summary (${summary.messages_covered} messages compressed)`)
|
|
321
|
+
} else {
|
|
322
|
+
// Conversation is short enough, use all recent messages
|
|
323
|
+
messages = toAPIMessages(recentMessages)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// [STEP-10] STRATEGY 4: ISOLATE — Build context based on agent role
|
|
327
|
+
log.info(`[context-compiler] [STEP-10] Building system prompt...`)
|
|
328
|
+
let systemPrompt: string
|
|
329
|
+
try {
|
|
330
|
+
systemPrompt = await buildSystemPromptWithProjects({ agentId, userId })
|
|
331
|
+
log.info(`[context-compiler] [STEP-10] ✅ System prompt built (${systemPrompt.length} chars)`)
|
|
332
|
+
} catch (err) {
|
|
333
|
+
log.error(`[context-compiler] [STEP-10] ❌ FAILED building system prompt: ${JSON.stringify(err)}`)
|
|
334
|
+
throw err
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// [STEP-10b] Inject current date/time (ENTORNO ACTUAL)
|
|
338
|
+
const userRow = db.query<any, [string]>(
|
|
339
|
+
"SELECT timezone FROM users WHERE id = ?"
|
|
340
|
+
).get(userId)
|
|
341
|
+
const userTimezone = userRow?.timezone || "UTC"
|
|
342
|
+
const now = new Date()
|
|
343
|
+
const fecha = getUserDate(userTimezone, now)
|
|
344
|
+
const hora = getUserTime(userTimezone, now)
|
|
345
|
+
const workspaceLine = agent.workspace ? `\n**Workspace**: ${agent.workspace} (usa SIEMPRE este path como basePath en herramientas de filesystem)` : ""
|
|
346
|
+
systemPrompt += `\n\n# ENTORNO ACTUAL\n**Fecha**: ${fecha}\n**Hora**: ${hora}\n**Zona horaria**: ${userTimezone}${workspaceLine}\n`
|
|
347
|
+
log.info(`[context-compiler] [STEP-10b] ✅ Injected current date/time: ${fecha} ${hora} (${userTimezone})`)
|
|
348
|
+
|
|
349
|
+
// Inject scratchpad (Strategy: WRITE) — usando TOON para ahorro de tokens
|
|
350
|
+
if (scratchpadNotes.length > 0) {
|
|
351
|
+
const scratchpadData: Record<string, string> = {}
|
|
352
|
+
for (const n of scratchpadNotes) {
|
|
353
|
+
scratchpadData[n.key] = n.value
|
|
354
|
+
}
|
|
355
|
+
// TOON comprime el formato clave-valor
|
|
356
|
+
const scratchpadContent = formatContext(scratchpadData)
|
|
357
|
+
systemPrompt += `\n\n# SCRATCHPAD (Persistent Notes)\n${scratchpadContent}\n`
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Inject active/recent project state from DB (coordinator only)
|
|
361
|
+
if (!isWorker) {
|
|
362
|
+
try {
|
|
363
|
+
const recentProjects = db.query<any, []>(`
|
|
364
|
+
SELECT p.id, p.name, p.status, p.progress, p.description,
|
|
365
|
+
COUNT(t.id) as total_tasks,
|
|
366
|
+
SUM(CASE WHEN t.status = 'completed' THEN 1 ELSE 0 END) as done_tasks
|
|
367
|
+
FROM projects p
|
|
368
|
+
LEFT JOIN tasks t ON t.project_id = p.id
|
|
369
|
+
WHERE p.status IN ('active', 'pending', 'paused')
|
|
370
|
+
GROUP BY p.id
|
|
371
|
+
ORDER BY p.updated_at DESC
|
|
372
|
+
LIMIT 10
|
|
373
|
+
`).all()
|
|
374
|
+
|
|
375
|
+
if (recentProjects.length > 0) {
|
|
376
|
+
let projectSection = `\n\n# ESTADO DE PROYECTOS\n`
|
|
377
|
+
for (const proj of recentProjects) {
|
|
378
|
+
projectSection += `\n## ${proj.name} [${proj.status.toUpperCase()}] (${proj.done_tasks}/${proj.total_tasks} tareas, ${proj.progress ?? 0}%)\n`
|
|
379
|
+
if (proj.description) projectSection += `> ${proj.description}\n`
|
|
380
|
+
|
|
381
|
+
// Load tasks for this project
|
|
382
|
+
const tasks = db.query<any, [string]>(
|
|
383
|
+
"SELECT name, status, progress, result FROM tasks WHERE project_id = ? ORDER BY id ASC"
|
|
384
|
+
).all(proj.id)
|
|
385
|
+
for (const task of tasks) {
|
|
386
|
+
const resultSummary = task.result
|
|
387
|
+
? ` → ${task.result.substring(0, 120)}${task.result.length > 120 ? "…" : ""}`
|
|
388
|
+
: ""
|
|
389
|
+
projectSection += ` - [${task.status}] ${task.name}${resultSummary}\n`
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
systemPrompt += projectSection
|
|
393
|
+
log.info(`[context-compiler] [STEP-10c] Injected ${recentProjects.length} projects into context`)
|
|
394
|
+
}
|
|
395
|
+
} catch (err) {
|
|
396
|
+
log.warn(`[context-compiler] [STEP-10c] Failed to inject projects: ${(err as Error).message}`)
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Dynamic tool discovery instruction (coordinator only)
|
|
401
|
+
// Note: MCP tools are already available directly, no search needed
|
|
402
|
+
if (!isWorker) {
|
|
403
|
+
// Build minimal tools documentation from filtered native tools
|
|
404
|
+
const minimalToolsDocs = filteredNativeTools
|
|
405
|
+
.filter(t => MINIMAL_TOOLS.has(t.name))
|
|
406
|
+
.map(t => `- **${t.name}**: ${t.description || "Herramienta nativa"}`)
|
|
407
|
+
.join("\n")
|
|
408
|
+
|
|
409
|
+
systemPrompt += `\n\n# HERRAMIENTAS NATIVAS BÁSICAS (SIEMPRE DISPONIBLES)\n` +
|
|
410
|
+
`Estas 4 herramientas nativas están SIEMPRE disponibles en tu contexto y tienen prioridad sobre MCP:\n\n` +
|
|
411
|
+
`${minimalToolsDocs}\n\n` +
|
|
412
|
+
`**REGLAS DE USO:**\n` +
|
|
413
|
+
`1. Si necesitas una herramienta que no esté en la lista arriba → USA \`search_knowledge\` para encontrarla:\n` +
|
|
414
|
+
` - Herramientas nativas: \`search_knowledge(type="tools", query="<qué necesitas>")\`\n` +
|
|
415
|
+
` - Herramientas MCP (externas): \`search_knowledge(type="mcp", query="<qué necesitas>")\`\n` +
|
|
416
|
+
` - Todo junto: \`search_knowledge(type="all", query="<qué necesitas>")\`\n` +
|
|
417
|
+
`2. NUNCA uses una herramienta MCP si existe una nativa equivalente en el catálogo\n` +
|
|
418
|
+
`3. Las herramientas MCP se activan dinámicamente vía search_knowledge — NO están en tu contexto por defecto\n\n` +
|
|
419
|
+
`# CATÁLOGO DE HERRAMIENTAS\n` +
|
|
420
|
+
`Usá \`search_knowledge\` para descubrir:\n` +
|
|
421
|
+
`- Skills (instrucciones de tareas complejas): type="skills"\n` +
|
|
422
|
+
`- Playbook (buenas prácticas): type="playbook"\n` +
|
|
423
|
+
`- Herramientas nativas: type="tools"\n` +
|
|
424
|
+
`- Herramientas MCP (externas): type="mcp"\n` +
|
|
425
|
+
`- Todo: type="all"\n` +
|
|
426
|
+
`\n## REGLA CRÍTICA — Delegación a workers\n` +
|
|
427
|
+
`Los workers arrancan con herramientas mínimas (save_note, notify, report_progress, search_knowledge).\n` +
|
|
428
|
+
`**ANTES de crear o delegar a un worker**, SIEMPRE debes:\n` +
|
|
429
|
+
`1. Usar \`search_knowledge(type="tools", query="<tarea del worker>")\` para identificar qué herramientas necesita.\n` +
|
|
430
|
+
`2. Incluir esas herramientas en el campo \`tools\` al crear el agente con \`create_agent\`, o\n` +
|
|
431
|
+
` en el campo \`task_description\` de \`task_delegate\` como instrucción explícita:\n` +
|
|
432
|
+
` "Usa las herramientas: web_search, fs_read, ... para completar esta tarea."\n` +
|
|
433
|
+
`3. El worker con esa instrucción usará \`search_knowledge\` para activar las tools por nombre.\n` +
|
|
434
|
+
`Ejemplo: si el worker debe investigar en internet → busca "web search herramienta internet, herramientas de navegacion, browser" → obtienes "web_search" → dile al worker que use web_search.\n` +
|
|
435
|
+
`4. Las herramientas se inyectan dinamicamente vía search_knowledge — NO están en tu contexto por defecto\n`
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
// Inject available skills (minimal + discovered)
|
|
439
|
+
if (allSkills.length > 0) {
|
|
440
|
+
let skillsSection = `\n\n# SKILLS ACTIVAS\n`
|
|
441
|
+
skillsSection += `Usá estas skills como guía cuando sea relevante:\n\n`
|
|
442
|
+
|
|
443
|
+
for (const skill of allSkills) {
|
|
444
|
+
const isMinimal = MINIMAL_SKILL_NAMES.includes(skill.name)
|
|
445
|
+
const badge = isMinimal ? "[SIEMPRE]" : "[DISCOVERED]"
|
|
446
|
+
const desc = skill.description ? ` — ${skill.description}` : ""
|
|
447
|
+
skillsSection += `- **${skill.name}** ${badge}${desc}\n`
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
systemPrompt += skillsSection
|
|
451
|
+
log.info(`[context-compiler] [STEP-10d] Injected ${allSkills.length} skills (${minimalSkills.length} minimal, ${discoveredSkills.length} discovered)`)
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Inject Canvas A2UI component documentation
|
|
455
|
+
systemPrompt += `\n\n# 🎨 CANVAS A2UI — Componentes disponibles para \`canvas_render\`\n` +
|
|
456
|
+
`**REGLA**: Usá \`canvas_render\` con el tipo específico en vez de siempre usar \`canvas_show_card\` + markdown.\n\n` +
|
|
457
|
+
`## Tipos de visualización:\n` +
|
|
458
|
+
`- **chart** — Gráficos. Props: \`{type:"bar"|"line"|"area"|"pie", data:[{name,...}], xKey:"name", keys:["valor"], colors:[], title}\`\n` +
|
|
459
|
+
`- **table** — Tablas de datos. Props: \`{title, columns:[{header,key}], data:[{...}]}\`\n` +
|
|
460
|
+
`- **progress** — Barras de progreso. Props: \`{bars:[{label,value:0-100}]}\`\n` +
|
|
461
|
+
`- **markdown** — Texto rich. Props: \`{content:"## título\\n..."}\`\n` +
|
|
462
|
+
`- **card** — Tarjeta con items. Props: \`{title, description, items:[{label,value}], footer}\`\n` +
|
|
463
|
+
`- **accordion** — Secciones colapsables. Props: \`{items:[{value,title,content}]}\`\n` +
|
|
464
|
+
`- **tabs** — Pestañas. Props: \`{tabs:[{value,label,content}]}\`\n` +
|
|
465
|
+
`- **badge** — Etiqueta. Props: \`{label, variant:"default"|"secondary"|"destructive"|"outline"}\`\n` +
|
|
466
|
+
`- **separator** — Línea divisora\n` +
|
|
467
|
+
`- **bee-loader** — Animación de carga. Props: \`{message}\`\n\n` +
|
|
468
|
+
`## Tipos interactivos (bloquean hasta respuesta del usuario):\n` +
|
|
469
|
+
`- **form** — Formulario. Props: \`{title, fields:[{name,label,type,placeholder,options}], submitLabel}\`\n` +
|
|
470
|
+
` → Tipos de campo: \`text\`, \`email\`, \`number\`, \`textarea\`, \`select\`, \`checkbox\`\n` +
|
|
471
|
+
` → Al Submit recibirás: \`{data:{campo:valor,...}}\`\n` +
|
|
472
|
+
`- **button** — Botón clickeable. Props: \`{label, variant:"default"|"outline"|"secondary"|"destructive"}\`\n` +
|
|
473
|
+
` → Al click recibirás: \`{action:"click", data:{label}}\`\n` +
|
|
474
|
+
`- **alert-dialog** — Confirmación. Props: \`{title, description, confirmLabel, cancelLabel}\`\n` +
|
|
475
|
+
` → Al confirmar recibirás: \`{data:{confirmed:true|false}}\`\n\n` +
|
|
476
|
+
`## Cuándo usar cada uno:\n` +
|
|
477
|
+
`- Estadísticas/datos numéricos → **chart** (bar/line/pie)\n` +
|
|
478
|
+
`- Listas de filas/columnas → **table**\n` +
|
|
479
|
+
`- Texto largo / análisis → **markdown**\n` +
|
|
480
|
+
`- Pedir datos al usuario → **canvas_ask** o **canvas_render con form**\n` +
|
|
481
|
+
`- Confirmar acción peligrosa → **canvas_confirm** o **canvas_render con alert-dialog**\n` +
|
|
482
|
+
`- Mostrar progreso de tarea → **canvas_show_progress**\n\n` +
|
|
483
|
+
`## Ejemplos:\n` +
|
|
484
|
+
`\`\`\`\n` +
|
|
485
|
+
`canvas_render(component:"chart", data:{type:"bar", data:[{mes:"Ene",ventas:1200},{mes:"Feb",ventas:1800}], xKey:"mes", keys:["ventas"], title:"Ventas por mes"})\n` +
|
|
486
|
+
`canvas_render(component:"table", data:{title:"Archivos", columns:[{header:"Nombre",key:"name"},{header:"Tamaño",key:"size"}], data:[{name:"app.ts",size:"12KB"}]})\n` +
|
|
487
|
+
`canvas_render(component:"form", data:{title:"Configuración", fields:[{name:"nombre",label:"Nombre",type:"text"},{name:"tipo",label:"Tipo",type:"select",options:[{value:"a",label:"A"},{value:"b",label:"B"}]}], submitLabel:"Guardar"})\n` +
|
|
488
|
+
`\`\`\`\n\n` +
|
|
489
|
+
`# 🎨🎨 CANVAS A2UI v0.9 — Superficies interactivas ricas\n` +
|
|
490
|
+
`Además de los componentes shadcn, podes crear superficies A2UI v0.9 (protocolo estándar de Google) para UIs ricas e interactivas.\n\n` +
|
|
491
|
+
`## Flujo A2UI:\n` +
|
|
492
|
+
`1. \`a2ui_create_surface\` — Crear la superficie (obligatorio primero)\n` +
|
|
493
|
+
`2. \`a2ui_update_components\` — Enviar componentes (puedes enviar múltiples veces)\n` +
|
|
494
|
+
`3. \`a2ui_update_data_model\` — Actualizar datos dinámicos\n` +
|
|
495
|
+
`4. \`a2ui_delete_surface\` — Eliminar la superficie\n\n` +
|
|
496
|
+
`## Componentes A2UI v0.9:\n` +
|
|
497
|
+
`- **Column** — Contenedor vertical. Props: children (array de IDs), distribution, alignment\n` +
|
|
498
|
+
`- **Row** — Contenedor horizontal. Props: children (array de IDs), distribution, alignment\n` +
|
|
499
|
+
`- **Card** — Tarjeta con child\n` +
|
|
500
|
+
`- **Text** — Texto con usageHint: h1-h5, body, caption, code, label\n` +
|
|
501
|
+
`- **Button** — Botón interactivo. Props: child, variant ("primary"|"borderless"), action\n` +
|
|
502
|
+
`- **TextField** — Campo de texto. Props: label, value (path), variant, placeholder, checks\n` +
|
|
503
|
+
`- **CheckBox** — Checkbox. Props: label, value (path)\n` +
|
|
504
|
+
`- **ChoicePicker** — Selección múltiple. Props: options, variant, maxAllowedSelections, selections\n` +
|
|
505
|
+
`- **Slider** — Slider numérico. Props: value (path), minValue, maxValue\n` +
|
|
506
|
+
`- **DateTimeInput** — Fecha/hora. Props: value (path), enableDate, enableTime\n` +
|
|
507
|
+
`- **List** — Lista scrolleable. Props: children, direction\n` +
|
|
508
|
+
`- **Tabs** — Pestañas. Props: tabItems\n` +
|
|
509
|
+
`- **Modal** — Diálogo. Props: entryPointChild, contentChild\n` +
|
|
510
|
+
`- **Divider** — Línea divisora. Props: axis\n` +
|
|
511
|
+
`- **Image** — Imagen. Props: url, fit, usageHint\n` +
|
|
512
|
+
`- **Icon** — Ícono. Props: name\n` +
|
|
513
|
+
`- **Video** — Video. Props: url\n` +
|
|
514
|
+
`- **AudioPlayer** — Reproductor de audio. Props: url, description\n\n` +
|
|
515
|
+
`## Data Binding (Dynamic Values):\n` +
|
|
516
|
+
`- Valor literal: \`"texto"\` o número directo\n` +
|
|
517
|
+
`- Path del data model: \`{"path": "/user/name"}\` — se resuelve contra el data model de la superficie\n` +
|
|
518
|
+
`- Function call: \`{"call": "formatDate", "args": {"value": {"path": "/date"}, "format": "yyyy-MM-dd"}}\`\n\n` +
|
|
519
|
+
`## Acciones:\n` +
|
|
520
|
+
`- Evento: \`{"event": {"name": "submit_form", "context": {"email": {"path": "/form/email"}}}}\`\n` +
|
|
521
|
+
`- El contexto se resuelve contra el data model antes de enviar\n\n` +
|
|
522
|
+
`## Ejemplo completo:\n` +
|
|
523
|
+
`\`\`\`\n` +
|
|
524
|
+
`// 1. Crear superficie\n` +
|
|
525
|
+
`a2ui_create_surface(surfaceId:"contact_form", catalogId:"https://a2ui.org/specification/v0_9/basic_catalog.json", theme:{primaryColor:"#3B82F6", agentDisplayName:"Asistente"})\n\n` +
|
|
526
|
+
`// 2. Enviar componentes\n` +
|
|
527
|
+
`a2ui_update_components(surfaceId:"contact_form", components:[\n` +
|
|
528
|
+
` {id:"root", component:"Column", children:{array:["header","name_field","email_field","submit_btn"]}},\n` +
|
|
529
|
+
` {id:"header", component:"Text", text:"Contacto", usageHint:"h2"},\n` +
|
|
530
|
+
` {id:"name_field", component:"TextField", label:"Nombre", value:{path:"/form/name"}, variant:"shortText"},\n` +
|
|
531
|
+
` {id:"email_field", component:"TextField", label:"Email", value:{path:"/form/email"}, variant:"shortText", checks:[{call:"required",args:{value:{path:"/form/email"}},message:"Email es obligatorio"},{call:"email",args:{value:{path:"/form/email"}},message:"Email inválido"}]},\n` +
|
|
532
|
+
` {id:"submit_text", component:"Text", text:"Enviar"},\n` +
|
|
533
|
+
` {id:"submit_btn", component:"Button", child:"submit_text", variant:"primary", action:{event:{name:"submit_contact",context:{name:{path:"/form/name"},email:{path:"/form/email"}}}}}\n` +
|
|
534
|
+
`])\n\n` +
|
|
535
|
+
`// 3. Poblar data model\n` +
|
|
536
|
+
`a2ui_update_data_model(surfaceId:"contact_form", path:"/form", value:{name:"",email:""})\n` +
|
|
537
|
+
`\`\`\`\n`
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// For isolated workers, add task context + tool discovery instruction
|
|
541
|
+
if (isWorker && opts.taskContext) {
|
|
542
|
+
systemPrompt += `\n\n# HERRAMIENTAS DISPONIBLES\n` +
|
|
543
|
+
`Arrancas con herramientas básicas. Si tu tarea requiere herramientas adicionales (web_search, fs_read, browser_navigate, etc.):\n` +
|
|
544
|
+
`1. Usá \`search_knowledge(type="tools", query="<herramienta o tarea>")\` para encontrarlas.\n` +
|
|
545
|
+
`2. Las herramientas que encuentres estarán disponibles para usar inmediatamente.\n` +
|
|
546
|
+
`Si el coordinador te indicó herramientas específicas, buscalas primero con search_knowledge antes de ejecutar tu tarea.\n` +
|
|
547
|
+
`\n# CURRENT TASK\n${opts.taskContext}\n\nFocus ONLY on this task. Do not deviate.`
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
log.info(
|
|
551
|
+
`[context-compiler] ✅ DONE: ${allTools.length} total tools, ` +
|
|
552
|
+
`${toolsForLLM.length} selected tools, ${messages.length} messages, ` +
|
|
553
|
+
`${allSkills.length} skills (${minimalSkills.length} minimal, ${discoveredSkills.length} discovered), ` +
|
|
554
|
+
`isolated=${isWorker}`
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
return {
|
|
558
|
+
systemPrompt,
|
|
559
|
+
messages,
|
|
560
|
+
tools: toolsForLLM,
|
|
561
|
+
allTools,
|
|
562
|
+
skills: allSkills,
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Re-export sync functions for gateway/initializer
|
|
567
|
+
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { Config } from "../config/loader.ts";
|
|
2
|
+
import { logger } from "../utils/logger.ts";
|
|
3
|
+
import type { LLMMessage as Message } from "../agent/providers/LLMClient.ts";
|
|
4
|
+
|
|
5
|
+
export interface ContextGuardResult {
|
|
6
|
+
canProceed: boolean;
|
|
7
|
+
currentTokens: number;
|
|
8
|
+
maxTokens: number;
|
|
9
|
+
utilizationPercent: number;
|
|
10
|
+
needsCompaction: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class ContextGuard {
|
|
14
|
+
private config: Config;
|
|
15
|
+
private log = logger.child("context-guard");
|
|
16
|
+
|
|
17
|
+
constructor(config: Config) {
|
|
18
|
+
this.config = config;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
estimateTokens(messages: Message[]): number {
|
|
22
|
+
let total = 0;
|
|
23
|
+
|
|
24
|
+
for (const msg of messages) {
|
|
25
|
+
total += Math.ceil(msg.content.length / 4);
|
|
26
|
+
|
|
27
|
+
if (msg.name) {
|
|
28
|
+
total += Math.ceil(msg.name.length / 4);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (msg.tool_calls) {
|
|
32
|
+
for (const tc of msg.tool_calls) {
|
|
33
|
+
total += Math.ceil(tc.function.name.length / 4);
|
|
34
|
+
total += Math.ceil(tc.function.arguments.length / 4);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return total;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
check(messages: Message[], systemPrompt?: string): ContextGuardResult {
|
|
43
|
+
const maxTokens = this.config.agent?.context?.maxTokens || 128000;
|
|
44
|
+
const threshold = this.config.agent?.context?.compactionThreshold || 0.8;
|
|
45
|
+
|
|
46
|
+
let currentTokens = this.estimateTokens(messages);
|
|
47
|
+
|
|
48
|
+
if (systemPrompt) {
|
|
49
|
+
currentTokens += Math.ceil(systemPrompt.length / 4);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
currentTokens += 500;
|
|
53
|
+
|
|
54
|
+
const utilizationPercent = currentTokens / maxTokens;
|
|
55
|
+
const needsCompaction = utilizationPercent >= threshold;
|
|
56
|
+
const canProceed = currentTokens < maxTokens * 0.95;
|
|
57
|
+
|
|
58
|
+
this.log.debug(`Context check: ${currentTokens}/${maxTokens} tokens (${(utilizationPercent * 100).toFixed(1)}%)`);
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
canProceed,
|
|
62
|
+
currentTokens,
|
|
63
|
+
maxTokens,
|
|
64
|
+
utilizationPercent,
|
|
65
|
+
needsCompaction,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
shouldCompact(messages: Message[], systemPrompt?: string): boolean {
|
|
70
|
+
const result = this.check(messages, systemPrompt);
|
|
71
|
+
return result.needsCompaction;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
getRecommendedAction(messages: Message[], systemPrompt?: string): "proceed" | "compact" | "error" {
|
|
75
|
+
const result = this.check(messages, systemPrompt);
|
|
76
|
+
|
|
77
|
+
if (result.utilizationPercent >= 0.95) {
|
|
78
|
+
return "error";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (result.needsCompaction) {
|
|
82
|
+
return "compact";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return "proceed";
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function createContextGuard(config: Config): ContextGuard {
|
|
90
|
+
return new ContextGuard(config);
|
|
91
|
+
}
|