@johpaz/hive 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/CONTRIBUTING.md +44 -0
  2. package/README.md +310 -0
  3. package/package.json +96 -0
  4. package/packages/cli/package.json +28 -0
  5. package/packages/cli/src/commands/agent-run.ts +168 -0
  6. package/packages/cli/src/commands/agents.ts +398 -0
  7. package/packages/cli/src/commands/chat.ts +142 -0
  8. package/packages/cli/src/commands/config.ts +50 -0
  9. package/packages/cli/src/commands/cron.ts +161 -0
  10. package/packages/cli/src/commands/dev.ts +95 -0
  11. package/packages/cli/src/commands/doctor.ts +133 -0
  12. package/packages/cli/src/commands/gateway.ts +443 -0
  13. package/packages/cli/src/commands/logs.ts +57 -0
  14. package/packages/cli/src/commands/mcp.ts +175 -0
  15. package/packages/cli/src/commands/message.ts +77 -0
  16. package/packages/cli/src/commands/onboard.ts +1868 -0
  17. package/packages/cli/src/commands/security.ts +144 -0
  18. package/packages/cli/src/commands/service.ts +50 -0
  19. package/packages/cli/src/commands/sessions.ts +116 -0
  20. package/packages/cli/src/commands/skills.ts +187 -0
  21. package/packages/cli/src/commands/update.ts +25 -0
  22. package/packages/cli/src/index.ts +185 -0
  23. package/packages/cli/src/utils/token.ts +6 -0
  24. package/packages/code-bridge/README.md +78 -0
  25. package/packages/code-bridge/package.json +18 -0
  26. package/packages/code-bridge/src/index.ts +95 -0
  27. package/packages/code-bridge/src/process-manager.ts +212 -0
  28. package/packages/code-bridge/src/schemas.ts +133 -0
  29. package/packages/core/package.json +46 -0
  30. package/packages/core/src/agent/agent-loop.ts +369 -0
  31. package/packages/core/src/agent/compaction.ts +140 -0
  32. package/packages/core/src/agent/context-compiler.ts +378 -0
  33. package/packages/core/src/agent/context-guard.ts +91 -0
  34. package/packages/core/src/agent/context.ts +138 -0
  35. package/packages/core/src/agent/conversation-store.ts +198 -0
  36. package/packages/core/src/agent/curator.ts +158 -0
  37. package/packages/core/src/agent/hooks.ts +166 -0
  38. package/packages/core/src/agent/index.ts +116 -0
  39. package/packages/core/src/agent/llm-client.ts +503 -0
  40. package/packages/core/src/agent/native-tools.ts +505 -0
  41. package/packages/core/src/agent/prompt-builder.ts +532 -0
  42. package/packages/core/src/agent/providers/index.ts +167 -0
  43. package/packages/core/src/agent/providers.ts +1 -0
  44. package/packages/core/src/agent/reflector.ts +170 -0
  45. package/packages/core/src/agent/service.ts +64 -0
  46. package/packages/core/src/agent/stuck-loop.ts +133 -0
  47. package/packages/core/src/agent/supervisor.ts +39 -0
  48. package/packages/core/src/agent/tracer.ts +102 -0
  49. package/packages/core/src/agent/workspace.ts +110 -0
  50. package/packages/core/src/canvas/canvas-manager.test.ts +161 -0
  51. package/packages/core/src/canvas/canvas-manager.ts +319 -0
  52. package/packages/core/src/canvas/canvas-tools.ts +420 -0
  53. package/packages/core/src/canvas/emitter.ts +115 -0
  54. package/packages/core/src/canvas/index.ts +2 -0
  55. package/packages/core/src/channels/base.ts +138 -0
  56. package/packages/core/src/channels/discord.ts +260 -0
  57. package/packages/core/src/channels/index.ts +7 -0
  58. package/packages/core/src/channels/manager.ts +383 -0
  59. package/packages/core/src/channels/slack.ts +287 -0
  60. package/packages/core/src/channels/telegram.ts +502 -0
  61. package/packages/core/src/channels/webchat.ts +128 -0
  62. package/packages/core/src/channels/whatsapp.ts +375 -0
  63. package/packages/core/src/config/index.ts +12 -0
  64. package/packages/core/src/config/loader.ts +529 -0
  65. package/packages/core/src/events/event-bus.ts +169 -0
  66. package/packages/core/src/gateway/index.ts +5 -0
  67. package/packages/core/src/gateway/initializer.ts +290 -0
  68. package/packages/core/src/gateway/lane-queue.ts +169 -0
  69. package/packages/core/src/gateway/resolver.ts +108 -0
  70. package/packages/core/src/gateway/router.ts +124 -0
  71. package/packages/core/src/gateway/server.ts +3317 -0
  72. package/packages/core/src/gateway/session.ts +95 -0
  73. package/packages/core/src/gateway/slash-commands.ts +192 -0
  74. package/packages/core/src/heartbeat/index.ts +157 -0
  75. package/packages/core/src/index.ts +19 -0
  76. package/packages/core/src/integrations/catalog.ts +286 -0
  77. package/packages/core/src/integrations/env.ts +64 -0
  78. package/packages/core/src/integrations/index.ts +2 -0
  79. package/packages/core/src/memory/index.ts +1 -0
  80. package/packages/core/src/memory/notes.ts +68 -0
  81. package/packages/core/src/plugins/api.ts +128 -0
  82. package/packages/core/src/plugins/index.ts +2 -0
  83. package/packages/core/src/plugins/loader.ts +365 -0
  84. package/packages/core/src/resilience/circuit-breaker.ts +225 -0
  85. package/packages/core/src/security/google-chat.ts +269 -0
  86. package/packages/core/src/security/index.ts +192 -0
  87. package/packages/core/src/security/pairing.ts +250 -0
  88. package/packages/core/src/security/rate-limit.ts +270 -0
  89. package/packages/core/src/security/signal.ts +321 -0
  90. package/packages/core/src/state/store.ts +312 -0
  91. package/packages/core/src/storage/bun-sqlite-store.ts +188 -0
  92. package/packages/core/src/storage/crypto.ts +101 -0
  93. package/packages/core/src/storage/db-context.ts +333 -0
  94. package/packages/core/src/storage/onboarding.ts +1087 -0
  95. package/packages/core/src/storage/schema.ts +541 -0
  96. package/packages/core/src/storage/seed.ts +571 -0
  97. package/packages/core/src/storage/sqlite.ts +387 -0
  98. package/packages/core/src/storage/usage.ts +212 -0
  99. package/packages/core/src/tools/bridge-events.ts +74 -0
  100. package/packages/core/src/tools/browser.ts +275 -0
  101. package/packages/core/src/tools/codebridge.ts +421 -0
  102. package/packages/core/src/tools/coordinator-tools.ts +179 -0
  103. package/packages/core/src/tools/cron.ts +611 -0
  104. package/packages/core/src/tools/exec.ts +140 -0
  105. package/packages/core/src/tools/fs.ts +364 -0
  106. package/packages/core/src/tools/index.ts +12 -0
  107. package/packages/core/src/tools/memory.ts +176 -0
  108. package/packages/core/src/tools/notify.ts +113 -0
  109. package/packages/core/src/tools/project-management.ts +376 -0
  110. package/packages/core/src/tools/project.ts +375 -0
  111. package/packages/core/src/tools/read.ts +158 -0
  112. package/packages/core/src/tools/web.ts +436 -0
  113. package/packages/core/src/tools/workspace.ts +171 -0
  114. package/packages/core/src/utils/benchmark.ts +80 -0
  115. package/packages/core/src/utils/crypto.ts +73 -0
  116. package/packages/core/src/utils/date.ts +42 -0
  117. package/packages/core/src/utils/index.ts +4 -0
  118. package/packages/core/src/utils/logger.ts +388 -0
  119. package/packages/core/src/utils/retry.ts +70 -0
  120. package/packages/core/src/voice/index.ts +583 -0
  121. package/packages/core/tsconfig.json +9 -0
  122. package/packages/mcp/package.json +26 -0
  123. package/packages/mcp/src/config.ts +13 -0
  124. package/packages/mcp/src/index.ts +1 -0
  125. package/packages/mcp/src/logger.ts +42 -0
  126. package/packages/mcp/src/manager.ts +434 -0
  127. package/packages/mcp/src/transports/index.ts +67 -0
  128. package/packages/mcp/src/transports/sse.ts +241 -0
  129. package/packages/mcp/src/transports/websocket.ts +159 -0
  130. package/packages/skills/package.json +21 -0
  131. package/packages/skills/src/bundled/agent_management/SKILL.md +24 -0
  132. package/packages/skills/src/bundled/browser_automation/SKILL.md +30 -0
  133. package/packages/skills/src/bundled/context_compact/SKILL.md +35 -0
  134. package/packages/skills/src/bundled/cron_manager/SKILL.md +52 -0
  135. package/packages/skills/src/bundled/file_manager/SKILL.md +76 -0
  136. package/packages/skills/src/bundled/http_client/SKILL.md +24 -0
  137. package/packages/skills/src/bundled/memory/SKILL.md +42 -0
  138. package/packages/skills/src/bundled/project_management/SKILL.md +26 -0
  139. package/packages/skills/src/bundled/shell/SKILL.md +43 -0
  140. package/packages/skills/src/bundled/system_notify/SKILL.md +52 -0
  141. package/packages/skills/src/bundled/voice/SKILL.md +25 -0
  142. package/packages/skills/src/bundled/web_search/SKILL.md +29 -0
  143. package/packages/skills/src/index.ts +1 -0
  144. package/packages/skills/src/loader.ts +282 -0
  145. package/packages/tools/package.json +43 -0
  146. package/packages/tools/src/browser/browser.test.ts +111 -0
  147. package/packages/tools/src/browser/index.ts +272 -0
  148. package/packages/tools/src/canvas/index.ts +220 -0
  149. package/packages/tools/src/cron/cron.test.ts +164 -0
  150. package/packages/tools/src/cron/index.ts +304 -0
  151. package/packages/tools/src/filesystem/filesystem.test.ts +240 -0
  152. package/packages/tools/src/filesystem/index.ts +379 -0
  153. package/packages/tools/src/git/index.ts +239 -0
  154. package/packages/tools/src/index.ts +4 -0
  155. package/packages/tools/src/shell/detect-env.ts +70 -0
  156. package/packages/tools/tsconfig.json +9 -0
@@ -0,0 +1,532 @@
1
+ import { getDb } from "../storage/sqlite"
2
+ import { getGlobalStore } from "../storage/bun-sqlite-store"
3
+ import { logger } from "../utils/logger"
4
+ import { getUserDate, getUserTime } from "../utils/date"
5
+
6
+ const log = logger.child("prompt-builder")
7
+
8
+ export interface BuildPromptOptions {
9
+ agentId: string
10
+ userId: string
11
+ }
12
+
13
+ export async function buildSystemPrompt(options: BuildPromptOptions): Promise<string> {
14
+ const agentId = options.agentId
15
+ const userId = options.userId
16
+ if (!userId) {
17
+ throw new Error("No userId provided. Please set HIVE_USER_ID or complete onboarding.");
18
+ }
19
+ const db = getDb()
20
+ const store = getGlobalStore(db)
21
+ const parts: string[] = []
22
+
23
+ const t0 = performance.now()
24
+ log.info(`\n[CONTEXT] Building system prompt for Agent: ${agentId}, User: ${userId}`)
25
+
26
+ const agentRow = db
27
+ .query<any, [string]>("SELECT * FROM agents WHERE id = ?")
28
+ .get(agentId)
29
+
30
+ const t1 = performance.now()
31
+ if (agentRow) {
32
+ log.info(`[CONTEXT] ✓ Layer 1: Agent identity loaded (${(t1 - t0).toFixed(2)}ms) - ID: ${agentId}`)
33
+ parts.push(`# AGENT IDENTITY
34
+ ## Internal ID: ${agentRow.id}
35
+ ## Name: ${agentRow.name}
36
+ ## Description: ${agentRow.description || "Personal AI assistant"}
37
+ ## Tone: ${agentRow.tone || "friendly"}
38
+ ## Expertise: ${agentRow.expertise || "general assistance"}
39
+
40
+ You are ${agentRow.name}, ${agentRow.description || "a helpful AI assistant"}.
41
+ - Be ${agentRow.tone || "friendly and helpful"} in your responses
42
+ - Focus on your areas of expertise
43
+ - Never expose your internal agent_id to the user
44
+ `)
45
+ } else {
46
+ parts.push(`# AGENT IDENTITY
47
+ ## Internal ID: ${agentId}
48
+ You are Hive, a helpful AI assistant.
49
+ `)
50
+ }
51
+
52
+ const userRow = db
53
+ .query<any, [string]>("SELECT * FROM users WHERE id = ?")
54
+ .get(userId)
55
+
56
+ const t2 = performance.now()
57
+ if (userRow) {
58
+ log.info(`[CONTEXT] ✓ Layer 1: User profile loaded (${(t2 - t1).toFixed(2)}ms) - User/Thread ID: ${userId}`)
59
+ const userInfo: string[] = []
60
+ if (userRow.name) userInfo.push(`Name: ${userRow.name}`)
61
+ if (userRow.occupation) userInfo.push(`Occupation: ${userRow.occupation}`)
62
+ if (userRow.language) userInfo.push(`Language: ${userRow.language}`)
63
+ if (userRow.timezone) userInfo.push(`Timezone: ${userRow.timezone}`)
64
+ if (userRow.notes) userInfo.push(`Notes: ${userRow.notes}`)
65
+
66
+ if (userInfo.length > 0) {
67
+ parts.push(`# USER PROFILE
68
+ ${userInfo.join("\n")}
69
+ `)
70
+ }
71
+ }
72
+
73
+ try {
74
+ const tTools = performance.now()
75
+ const toolsData = await store.get("tools:active")
76
+ if (toolsData && toolsData.tools && Array.isArray(toolsData.tools)) {
77
+ log.info(`[CONTEXT] ✓ Layer 2: Tools loaded (${(performance.now() - tTools).toFixed(2)}ms) - ${toolsData.tools.length} active`)
78
+ const toolsList = toolsData.tools
79
+ .filter((t: any) => t && t.name)
80
+ .map((t: any) => `- **${t.name}**: ${t.description || "No description"} (${t.category || "general"})`)
81
+ .join("\n")
82
+
83
+ if (toolsList) {
84
+ parts.push(`# AVAILABLE TOOLS
85
+ ${toolsList}
86
+ `)
87
+ }
88
+ }
89
+ } catch (e) {
90
+ log.warn("[prompt-builder] Failed to load tools from store:", e)
91
+ }
92
+
93
+ try {
94
+ const tSkills = performance.now()
95
+ const skillsData = await store.get("skills:active")
96
+ if (skillsData && skillsData.skills && Array.isArray(skillsData.skills)) {
97
+ log.info(`[CONTEXT] ✓ Layer 2: Skills loaded (${(performance.now() - tSkills).toFixed(2)}ms) - ${skillsData.skills.length} active`)
98
+ const skillsList = skillsData.skills
99
+ .filter((s: any) => s && s.name)
100
+ .map((s: any) => `- **${s.name}**: ${s.description || "No description"}`)
101
+ .join("\n")
102
+
103
+ if (skillsList) {
104
+ parts.push(`# AVAILABLE SKILLS
105
+ ${skillsList}
106
+ `)
107
+ }
108
+ }
109
+ } catch (e) {
110
+ log.warn("[prompt-builder] Failed to load skills from store:", e)
111
+ }
112
+
113
+ try {
114
+ const mcpData = await store.get("mcp:connected")
115
+ if (mcpData && mcpData.servers && Array.isArray(mcpData.servers)) {
116
+ const mcpList = mcpData.servers
117
+ .filter((s: any) => s && s.name)
118
+ .map((s: any) => `- **${s.name}**: ${s.transport} (${s.command || s.url || "local"})`)
119
+ .join("\n")
120
+
121
+ if (mcpList) {
122
+ parts.push(`# CONNECTED MCP SERVERS
123
+ ${mcpList}
124
+ `)
125
+ }
126
+ }
127
+ } catch (e) {
128
+ log.warn("[prompt-builder] Failed to load MCP from store:", e)
129
+ }
130
+
131
+ try {
132
+ const tEthics = performance.now()
133
+ const ethicsData = await store.get("ethics:rules")
134
+ if (ethicsData && ethicsData.rules && Array.isArray(ethicsData.rules)) {
135
+ log.info(`[CONTEXT] ✓ Layer 2: Ethics loaded (${(performance.now() - tEthics).toFixed(2)}ms) - ${ethicsData.rules.length} active rules`)
136
+ const alwaysRules = ethicsData.rules.filter((r: any) => r.content?.startsWith("ALWAYS:"))
137
+ const neverRules = ethicsData.rules.filter((r: any) => r.content?.startsWith("NEVER:"))
138
+ const confirmRules = ethicsData.rules.filter((r: any) => r.content?.startsWith("CONFIRM:"))
139
+
140
+ const ethicsParts: string[] = []
141
+ if (alwaysRules.length > 0) {
142
+ ethicsParts.push("## Always\n" + alwaysRules.map((r: any) => `- ${r.content.replace("ALWAYS:", "").trim()}`).join("\n"))
143
+ }
144
+ if (neverRules.length > 0) {
145
+ ethicsParts.push("## Never\n" + neverRules.map((r: any) => `- ${r.content.replace("NEVER:", "").trim()}`).join("\n"))
146
+ }
147
+ if (confirmRules.length > 0) {
148
+ ethicsParts.push("## Confirm Before\n" + confirmRules.map((r: any) => `- ${r.content.replace("CONFIRM:", "").trim()}`).join("\n"))
149
+ }
150
+
151
+ if (ethicsParts.length > 0) {
152
+ parts.push(`# ETHICS GUIDELINES\n${ethicsParts.join("\n\n")}\n`)
153
+ }
154
+ }
155
+ } catch (e) {
156
+ log.warn("[prompt-builder] Failed to load ethics from store:", e)
157
+ }
158
+
159
+ // Active providers (with API key OR local like Ollama) and their active models
160
+ try {
161
+ const activeProviders = db.query<any, []>(`
162
+ SELECT p.id, p.name, p.base_url,
163
+ CASE WHEN p.api_key_encrypted IS NOT NULL THEN 1 ELSE 0 END as has_api_key,
164
+ GROUP_CONCAT(m.name, ', ') as model_names
165
+ FROM providers p
166
+ LEFT JOIN models m ON m.provider_id = p.id AND (m.active = 1 OR m.enabled = 1)
167
+ WHERE p.active = 1
168
+ AND (
169
+ p.api_key_encrypted IS NOT NULL
170
+ OR p.base_url LIKE '%localhost%'
171
+ OR p.base_url LIKE '%127.0.0.1%'
172
+ OR p.id = 'ollama'
173
+ )
174
+ GROUP BY p.id
175
+ `).all()
176
+
177
+ if (activeProviders.length > 0) {
178
+ const providerList = activeProviders
179
+ .map((p: any) => {
180
+ const authInfo = p.has_api_key ? "API key configurada" : "local (sin API key)"
181
+ const base = p.base_url ? ` — ${p.base_url}` : ""
182
+ const modelList = p.model_names ? `\n Modelos: ${p.model_names}` : ""
183
+ return `- **${p.name}** (${authInfo}${base})${modelList}`
184
+ })
185
+ .join("\n")
186
+
187
+ parts.push(`# PROVIDERS Y MODELOS DISPONIBLES
188
+ ${providerList}
189
+ `)
190
+ }
191
+ } catch (e) {
192
+ log.warn("[prompt-builder] Failed to load active providers:", e)
193
+ }
194
+
195
+ // Check if this is the main agent (coordinator)
196
+ const isCoordinator = agentRow?.is_coordinator === 1
197
+ if (isCoordinator) {
198
+ const subagents = db
199
+ .query<any, []>("SELECT id, name, description FROM agents WHERE is_coordinator = 0 AND enabled = 1")
200
+ .all()
201
+
202
+ if (subagents.length > 0) {
203
+ const subagentList = subagents
204
+ .map((a: any) => `- **${a.name}** (${a.id}): ${a.description || "No description"}`)
205
+ .join("\n")
206
+
207
+ parts.push(`# AVAILABLE SUBAGENTS FOR DELEGATION
208
+ You can delegate tasks to the following specialized agents:
209
+
210
+ ${subagentList}
211
+
212
+ Use the create_agent tool when you need a new specialized agent.
213
+ `)
214
+ }
215
+ }
216
+
217
+ const now = new Date()
218
+ const userTimezone = userRow?.timezone || "UTC"
219
+ let userLocalTime = "Not configured"
220
+ try {
221
+ userLocalTime = now.toLocaleString("en-US", {
222
+ timeZone: userTimezone,
223
+ dateStyle: "full",
224
+ timeStyle: "long",
225
+ })
226
+ } catch (e) {
227
+ log.warn(`[prompt-builder] Invalid timezone: ${userTimezone}`)
228
+ }
229
+
230
+ parts.push(`# TECHNICAL INFO
231
+ - agent_id: ${agentId} (internal use only, never expose to user)
232
+ - user_id: ${userId}
233
+ - thread_id: Use this for memory/context
234
+ - canvas_session: canvas:${userId} (Target this session for canvas tools)
235
+
236
+ # ENVIRONMENT
237
+ - Server Time (UTC): ${now.toISOString()}
238
+ - User Local Time: ${userLocalTime}
239
+ - User Timezone: ${userTimezone}
240
+ - fecha_usuario: ${getUserDate(userTimezone, now)}
241
+ - hora_usuario: ${getUserTime(userTimezone, now)}
242
+ - You are running on an environment where the "User Local Time" is the source of truth for the user. Use this to answer any temporal queries like "today", "tomorrow", "at 9 AM", etc.
243
+
244
+ # TAREA PROGRAMADA (CRON)
245
+
246
+ ## Conversión de lenguaje natural a cron
247
+
248
+ Cuando el usuario pida crear una tarea programada (ej: "recuérdame esto cada día a las 8am"),
249
+ DEBES interpretar el schedule en lenguaje natural y convertirlo a expresión cron de 5 campos:
250
+ minuto, hora, día del mes, mes, día de la semana.
251
+
252
+ NUNCA le pidas al usuario que escriba cron syntax — hazlo tú.
253
+
254
+ ### Ejemplos de conversión:
255
+
256
+ | Lenguaje natural | Expresión cron |
257
+ |-----------------|----------------|
258
+ | "todos los días a las 8am" | 0 8 * * * |
259
+ | "cada lunes a las 9am" | 0 9 * * 1 |
260
+ | "cada hora" | 0 * * * * |
261
+ | "cada 30 minutos" | */30 * * * * |
262
+ | "los viernes a las 6pm" | 0 18 * * 5 |
263
+ | "el primer día de cada mes" | 0 9 1 * * |
264
+ | "cada día de semana a las 7am" | 0 7 * * 1-5 |
265
+ | "cada 15 minutos" | */15 * * * * |
266
+ | "a medianoche" | 0 0 * * * |
267
+
268
+ ## Mostrar fechas al usuario
269
+
270
+ Cuando inmueves fechas o horas de tareas programadas al usuario, SIEMPRE:
271
+ 1. Convierte la fecha/hora UTC a la zona horaria del usuario (${userTimezone})
272
+ 2. Exprésala en lenguaje natural, nunca en formato técnico
273
+
274
+ MALO: "La próxima ejecución es 1738344000"
275
+ BUENO: "La próxima ejecución es mañana a las 8:00am"
276
+
277
+ MALO: "Schedule: 0 8 * * *"
278
+ BUENO: "Se ejecutará todos los días a las 8:00am"
279
+
280
+ ## Cambio de timezone del usuario
281
+
282
+ Si el usuario cambia su zona horaria (ej: "ahora estoy en Ciudad de México"),
283
+ DEBES recalcular el next_run de todas sus tareas programadas existentes:
284
+
285
+ 1. Obtén la nueva zona horaria del usuario
286
+ 2. Para cada tarea programada, usa croner con la nueva timezone para recalcular
287
+ 3. Actualiza cada tarea en la base de datos con el nuevo next_run
288
+
289
+ Ejemplo de cómo recalcular:
290
+ - Nueva timezone: America/Mexico_City
291
+ - Expresión cron original: 0 8 * * *
292
+ - Nue Calcula el próximo run usando croner con la nueva timezone
293
+ - Actualiza el campo next_run en la base de datos
294
+ `)
295
+
296
+
297
+ // Instruir al agente para reportar progreso y narrar acciones
298
+ parts.push(`# COMMUNICATION GUIDELINES
299
+
300
+ ## Narración Obligatoria
301
+
302
+ Durante tareas que toman tiempo en completarse, DEBES narrar tus acciones en lenguaje natural
303
+ ANTES y DESPUÉS de usar cada herramienta. Esto mantiene al usuario informado en tiempo real.
304
+
305
+ ### Antes de usar una herramienta:
306
+
307
+ Escribe UNA frase corta explicando qué vas a hacer. Esta frase llegará al usuario inmediatamente.
308
+
309
+ Ejemplos de narración correcta:
310
+ - Antes de buscar en internet: "Buscando en internet información sobre..."
311
+ - Antes de revisar código: "Revisando el archivo auth.ts..."
312
+ - Antes de activar un subagente: "Activando bee_test para ejecutar los tests..."
313
+ - Antes de analizar documentos: "Analizando los documentos de la carpeta..."
314
+ - Antes de hacer un commit: "Voy a hacer commit con los cambios corregidos..."
315
+ - Antes de ejecutar tests: "Ejecutando los tests unitarios..."
316
+
317
+ ### Después de usar una herramienta:
318
+
319
+ Escribe UNA frase corta resumiendo qué encontraste o qué hiciste. NUNCA incluyas datos técnicos
320
+ ni JSON en la narración. Solo el resumen humano.
321
+
322
+ Ejemplos de narración correcta:
323
+ - Después de encontrar errores: "Encontré 3 problemas en el código, voy a corregirlos uno por uno"
324
+ - Después de una búsqueda: "Encontré información relevante, analizándola..."
325
+ - Después de ejecutar tests: "Los tests pasaron correctamente, no hay errores"
326
+ - Después de analizar archivos: "Hay 5 archivos modificados en el último commit"
327
+
328
+ ### Reglas importantes:
329
+
330
+ 1. El resultado CRUDO de las herramientas (JSON, arrays, datos técnicos) NUNCA debe llegar al usuario
331
+ 2. Solo tu narración en lenguaje natural llega al usuario
332
+ 3. Si una tarea tiene múltiples pasos, menciona brevemente al inicio qué vas a hacer
333
+ 4. Usa la herramienta "report_progress" SOLO para estados formales (started, thinking, searching, processing, writing, completed, error, info)
334
+
335
+ El usuario no debe esperar a ciegas - manténgase informado de cada paso importante.
336
+ `)
337
+
338
+ parts.push(`
339
+ # TOON FORMAT
340
+
341
+ Tool and MCP responses use TOON (Token-Oriented Object Notation), a compact JSON alternative:
342
+
343
+ ## Arrays (tabular format):
344
+ \`\`\`
345
+ field1,field2,field3
346
+ value1,value2,value3
347
+ value4,value5,value6
348
+ \`\`\`
349
+
350
+ ## Simple objects:
351
+ \`\`\`
352
+ name: Alice
353
+ age: 30
354
+ active: true
355
+ \`\`\`
356
+
357
+ ## Nested objects:
358
+ \`\`\`
359
+ user:
360
+ name: John
361
+ profile:
362
+ age: 30
363
+ \`\`\`
364
+
365
+ Parse all tool/MCP responses as TOON format, not JSON.
366
+ `)
367
+
368
+ log.info(`[CONTEXT] System prompt built in ${(performance.now() - t0).toFixed(2)}ms\n`)
369
+ return parts.join("\n\n")
370
+ }
371
+
372
+ async function loadCodeBridgeContext(userId: string): Promise<string> {
373
+ try {
374
+ const db = getDb()
375
+ const bridges = db
376
+ .query<any, [string]>(
377
+ "SELECT name, cli_command FROM code_bridge WHERE user_id = ? AND enabled = 1 AND active = 1"
378
+ )
379
+ .all(userId)
380
+
381
+ if (!bridges || bridges.length === 0) return ""
382
+
383
+ const list = bridges
384
+ .map((b: any) => `- **${b.name}**: \`${b.cli_command}\``)
385
+ .join("\n")
386
+
387
+ return `
388
+ # HERRAMIENTAS CLI DISPONIBLES PARA PROYECTOS DE CÓDIGO
389
+
390
+ Tienes acceso a las siguientes herramientas de desarrollo activadas en tu entorno:
391
+
392
+ ${list}
393
+
394
+ Úsalas con la herramienta de ejecución de comandos cuando trabajes en proyectos de tipo "code".
395
+ Casos de uso habituales: linting, tests, build, git, deploy, formateo.
396
+ `
397
+ } catch {
398
+ return ""
399
+ }
400
+ }
401
+
402
+ export async function buildSystemPromptWithProjects(options: BuildPromptOptions): Promise<string> {
403
+ const basePrompt = await buildSystemPrompt(options)
404
+
405
+ const codeBridgeSection = await loadCodeBridgeContext(options.userId)
406
+
407
+ const projectInstructions = `
408
+ # GESTIÓN DE PROYECTOS Y TAREAS COMPLEJAS
409
+
410
+ Cuando recibas una solicitud que involucre código, múltiples pasos interdependientes,
411
+ investigación estructurada, creación de contenido extenso, análisis de datos, o cualquier
412
+ trabajo que requiera más de 2 herramientas o pasos — SIEMPRE debes:
413
+
414
+ 1. Llamar \`project_create\` ANTES de comenzar el trabajo
415
+ 2. Descomponer el trabajo en tareas atómicas dentro del mismo \`project_create\`
416
+ 3. Llamar \`task_update\` al iniciar y al completar cada tarea
417
+ 4. Llamar \`project_done\` o \`project_fail\` al finalizar
418
+
419
+ ## SEÑALES DE QUE NECESITAS UN PROYECTO
420
+
421
+ - La solicitud usa verbos como: crear, construir, desarrollar, analizar, investigar, escribir, refactorizar, optimizar
422
+ - Tu plan mental tiene más de 2 pasos
423
+ - El resultado final tiene múltiples entregables (archivos, reportes, módulos)
424
+ - Necesitas usar más de 2 herramientas diferentes
425
+ - La tarea tomará varios turnos de conversación
426
+
427
+ ## TIPOS DE PROYECTO
428
+
429
+ | Tipo | Cuándo usarlo |
430
+ |------|---------------|
431
+ | \`code\` | Desarrollo de software, scripts, APIs, debugging, refactoring |
432
+ | \`research\` | Investigación, análisis de mercado, síntesis de información |
433
+ | \`content\` | Escritura, documentación, presentaciones, emails extensos |
434
+ | \`data\` | Análisis de datos, reportes, visualizaciones |
435
+ | \`general\` | Cualquier otra tarea estructurada de múltiples pasos |
436
+
437
+ ## HERRAMIENTAS DISPONIBLES
438
+
439
+ ### \`project_create\` — Crear proyecto con tareas
440
+ Parámetros:
441
+ - \`name\`: Nombre descriptivo del proyecto
442
+ - \`description\`: Objetivo del proyecto
443
+ - \`type\`: "code" | "research" | "content" | "data" | "general"
444
+ - \`tasks\`: Lista de tareas con \`name\`, \`description\`, y opcionalmente \`agent_id\`
445
+ - Retorna: \`{ projectId, taskIds[] }\` — guarda estos IDs para las llamadas posteriores
446
+
447
+ ### \`task_update\` — Actualizar estado de una tarea
448
+ Parámetros:
449
+ - \`task_id\`: ID numérico de la tarea (de taskIds[])
450
+ - \`status\`: "pending" | "in_progress" | "completed" | "failed" | "blocked"
451
+ - \`progress\`: 0–100
452
+ - \`result\`: Resultado o resumen al completar
453
+ - \`agent_id\`: Reasignar a otro agente (opcional)
454
+
455
+ ### \`task_create\` — Agregar tarea extra a un proyecto existente
456
+ ### \`project_update\` — Actualizar progreso global del proyecto
457
+ ### \`project_done\` — Marcar proyecto completado (progress=100)
458
+ ### \`project_fail\` — Marcar proyecto fallido con motivo
459
+
460
+ ## EJEMPLO 1 — Proyecto de código
461
+
462
+ **Usuario:** "Crea una API REST en Node.js con autenticación JWT y base de datos SQLite"
463
+
464
+ \`\`\`
465
+ 1. project_create({
466
+ name: "API REST Node.js con JWT",
467
+ description: "API con autenticación JWT y persistencia SQLite",
468
+ type: "code",
469
+ tasks: [
470
+ { name: "Estructura y dependencias", description: "Inicializar proyecto, package.json, instalar deps" },
471
+ { name: "Configurar SQLite", description: "Schema de base de datos, migrations, conexión" },
472
+ { name: "Autenticación JWT", description: "Rutas /login y /register, middleware de verificación" },
473
+ { name: "Endpoints CRUD", description: "Rutas protegidas con JWT" },
474
+ { name: "Tests y documentación", description: "Tests básicos y README" }
475
+ ]
476
+ })
477
+ → Recibe: { projectId: "abc123", taskIds: [1, 2, 3, 4, 5] }
478
+
479
+ 2. task_update({ task_id: 1, status: "in_progress", progress: 0 })
480
+ [realiza trabajo de la tarea 1]
481
+ task_update({ task_id: 1, status: "completed", progress: 100, result: "package.json y deps instalados" })
482
+
483
+ 3. task_update({ task_id: 2, status: "in_progress", progress: 0 })
484
+ [realiza trabajo de la tarea 2]
485
+ task_update({ task_id: 2, status: "completed", progress: 100, result: "Schema SQL: users(id, email, password_hash)" })
486
+
487
+ 4. [... continúa con tareas 3, 4, 5 ...]
488
+
489
+ 5. project_done({ projectId: "abc123", summary: "API REST completa. 5 endpoints. Tests OK." })
490
+ \`\`\`
491
+
492
+ ## EJEMPLO 2 — Proyecto con delegación a sub-agentes
493
+
494
+ **Usuario:** "Investiga competidores, crea una landing page y un plan de marketing"
495
+
496
+ \`\`\`
497
+ 1. project_create({
498
+ name: "Lanzamiento de producto",
499
+ type: "general",
500
+ tasks: [
501
+ { name: "Análisis de competidores", description: "Investigar 5 competidores principales" },
502
+ { name: "Diseño de landing page", description: "Estructura y copy de la página" },
503
+ { name: "Plan de marketing", description: "Estrategia de canales y mensajes" }
504
+ ]
505
+ })
506
+
507
+ 2. create_agent({ name: "researcher", description: "Especialista en análisis de mercado" })
508
+ create_agent({ name: "copywriter", description: "Especialista en contenido web y marketing" })
509
+
510
+ 3. task_update({ task_id: [id tarea 1], status: "in_progress", agent_id: [id researcher] })
511
+ [researcher investiga]
512
+ task_update({ task_id: [id], status: "completed", result: "Reporte de 5 competidores completo" })
513
+
514
+ 4. task_update({ task_id: [id tarea 2], status: "in_progress", agent_id: [id copywriter] })
515
+ [copywriter crea landing]
516
+ task_update({ task_id: [id], status: "completed", result: "Landing page con 5 secciones" })
517
+
518
+ 5. project_done({ projectId: "...", summary: "Landing, análisis y plan de marketing completados" })
519
+ \`\`\`
520
+
521
+ ## REGLAS CRÍTICAS
522
+
523
+ 1. **SIEMPRE** crea el proyecto ANTES de empezar cualquier trabajo complejo
524
+ 2. **NUNCA** dejes una tarea en "in_progress" sin actualizarla al terminar
525
+ 3. Si una tarea falla, márcala como "failed" con el error en "result"
526
+ 4. El progreso del proyecto se calcula automáticamente como promedio de las tareas
527
+ 5. Usa \`task_create\` para agregar tareas imprevistas que surjan durante el trabajo
528
+ 6. Narra siempre al usuario qué tarea estás ejecutando antes de hacerlo
529
+ `
530
+
531
+ return basePrompt + "\n\n" + projectInstructions + codeBridgeSection
532
+ }
@@ -0,0 +1,167 @@
1
+ /**
2
+ * AgentRunner — thin wrapper over the native AgentLoop.
3
+ *
4
+ * Keeps the same public API (generate()) so server.ts doesn't need changes.
5
+ * Internally uses agent-loop.ts instead of LangGraph.
6
+ */
7
+
8
+ import type { Config } from "../../config/loader.ts"
9
+ import { logger } from "../../utils/logger.ts"
10
+ import { getDb } from "../../storage/sqlite.ts"
11
+ import { getSupervisorGraph } from "../supervisor.ts"
12
+
13
+ export type Provider = "openai" | "anthropic" | "gemini" | "mistral" | "kimi" | "ollama" | "openrouter" | "deepseek"
14
+
15
+ export interface StepEvent {
16
+ type: "text" | "plan" | "tool_call" | "tool_result"
17
+ message: string
18
+ toolName?: string
19
+ isError?: boolean
20
+ }
21
+
22
+ export interface ModelOptions {
23
+ provider?: Provider
24
+ model?: string
25
+ maxTokens?: number
26
+ temperature?: number
27
+ system?: string
28
+ messages: Array<{ role: string; content: string }>
29
+ tools?: Record<string, any>
30
+ maxSteps?: number
31
+ onToken?: (token: string) => void
32
+ onStep?: (step: StepEvent) => Promise<void>
33
+ threadId?: string
34
+ userId?: string
35
+ channel?: string
36
+ }
37
+
38
+ export interface ModelResponse {
39
+ content: string
40
+ toolCalls?: Array<{
41
+ id: string
42
+ name: string
43
+ args: Record<string, unknown>
44
+ }>
45
+ usage?: {
46
+ promptTokens: number
47
+ completionTokens: number
48
+ totalTokens: number
49
+ }
50
+ finishReason?: string
51
+ }
52
+
53
+ export class AgentRunner {
54
+ private config: Config
55
+
56
+ constructor(config: Config) {
57
+ this.config = config
58
+ }
59
+
60
+ async generate(options: ModelOptions): Promise<ModelResponse> {
61
+ const db = getDb()
62
+ const coordinatorAgent = db
63
+ .query<any, []>("SELECT id FROM agents WHERE is_coordinator = 1 LIMIT 1")
64
+ .get()
65
+ const agentId = process.env.HIVE_AGENT_ID || coordinatorAgent?.id || "main"
66
+
67
+ const userId = options.userId || process.env.HIVE_USER_ID
68
+ if (!userId) {
69
+ throw new Error("No userId provided. Please set HIVE_USER_ID or complete onboarding.")
70
+ }
71
+ const threadId = options.threadId || userId
72
+
73
+ const agentLoop = getSupervisorGraph()
74
+ if (!agentLoop) {
75
+ throw new Error("AgentLoop not initialized")
76
+ }
77
+
78
+ let lastAgentContent = ""
79
+ let toolCalls: ModelResponse["toolCalls"] = []
80
+
81
+ try {
82
+ const stream = agentLoop.stream(
83
+ { messages: options.messages },
84
+ {
85
+ configurable: {
86
+ thread_id: threadId,
87
+ agent_id: agentId,
88
+ user_id: userId,
89
+ // system_prompt intentionally omitted — context-compiler builds it
90
+ channel: options.channel,
91
+ },
92
+ }
93
+ )
94
+
95
+ let chunkCount = 0
96
+ for await (const chunk of stream) {
97
+ chunkCount++
98
+
99
+ if (chunk.agent?.messages) {
100
+ const lastMsg = chunk.agent.messages[chunk.agent.messages.length - 1]
101
+ const hasToolCalls = (lastMsg as any)?.tool_calls?.length > 0
102
+ logger.info(
103
+ `[STREAM] chunk#${chunkCount} agent: contentLen=${lastMsg?.content?.length ?? 0} hasToolCalls=${hasToolCalls}`
104
+ )
105
+
106
+ if (lastMsg?.content) {
107
+ const content = typeof lastMsg.content === "string" ? lastMsg.content : ""
108
+ lastAgentContent = content
109
+ if (options.onToken) options.onToken(content)
110
+ }
111
+
112
+ if (hasToolCalls) {
113
+ toolCalls = (lastMsg as any).tool_calls.map((tc: any) => ({
114
+ id: tc.id || tc.function?.name,
115
+ name: tc.function?.name || tc.name,
116
+ args: tc.function?.arguments
117
+ ? (typeof tc.function.arguments === "string"
118
+ ? JSON.parse(tc.function.arguments)
119
+ : tc.function.arguments)
120
+ : {},
121
+ }))
122
+
123
+ const narration = lastMsg?.content || ""
124
+ if (options.onStep && narration) {
125
+ await options.onStep({ type: "text", message: narration })
126
+ }
127
+ if (options.onStep) {
128
+ for (const tc of toolCalls) {
129
+ await options.onStep({
130
+ type: "tool_call",
131
+ toolName: tc.name,
132
+ message: `Calling tool: \`${tc.name}\``,
133
+ })
134
+ }
135
+ }
136
+ }
137
+ }
138
+
139
+ if (chunk.tools?.messages) {
140
+ const lastMsg = chunk.tools.messages[chunk.tools.messages.length - 1]
141
+ if (lastMsg?.content && options.onStep) {
142
+ await options.onStep({
143
+ type: "tool_result",
144
+ message: typeof lastMsg.content === "string" ? lastMsg.content : "",
145
+ })
146
+ }
147
+ }
148
+ }
149
+
150
+ logger.debug(`[STREAM] done. totalChunks=${chunkCount} lastAgentContent length=${lastAgentContent.length}`)
151
+
152
+ return {
153
+ content: lastAgentContent,
154
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
155
+ usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
156
+ finishReason: "stop",
157
+ }
158
+ } catch (error) {
159
+ logger.error("AgentRunner error:", error)
160
+ throw error
161
+ }
162
+ }
163
+ }
164
+
165
+ export function createAgentRunner(config: Config): AgentRunner {
166
+ return new AgentRunner(config)
167
+ }
@@ -0,0 +1 @@
1
+ export * from "./providers/index.ts";