@johpaz/hive-sdk 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (272) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/README.md +78 -23
  3. package/bunfig.toml +4 -2
  4. package/docs/API-AGENTS.md +78 -27
  5. package/docs/API-CONTEXT-COMPILER.md +31 -34
  6. package/docs/API-TOOLS-SKILLS-CHANNELS.md +58 -22
  7. package/docs/HIVE-HARNESS.md +1 -1
  8. package/docs/INDEX.md +4 -4
  9. package/docs/TEMPLATE-HIVE-APP.md +10 -10
  10. package/package.json +9 -4
  11. package/packages/cli/package.json +2 -2
  12. package/packages/cli/src/commands/create-app.test.ts +36 -7
  13. package/packages/cli/src/commands/init.ts +3 -3
  14. package/packages/cli/src/commands/run.ts +1 -1
  15. package/packages/cli/src/commands/test.ts +37 -25
  16. package/packages/cli/src/commands/trace.ts +30 -28
  17. package/packages/cli/templates/hive-app/.env.example +10 -2
  18. package/packages/cli/templates/hive-app/README.md +103 -0
  19. package/packages/cli/templates/hive-app/hive.config.ts +9 -3
  20. package/packages/cli/templates/hive-app/src/agents/coordinator.ts +8 -1
  21. package/packages/cli/templates/hive-app/src/main.ts +12 -19
  22. package/packages/core/package.json +5 -4
  23. package/packages/core/src/agent/acceptance-checks.ts +166 -0
  24. package/packages/core/src/agent/agent-catalog.ts +348 -0
  25. package/packages/core/src/agent/agent-loop.ts +1373 -0
  26. package/packages/core/src/agent/capability-search.ts +186 -0
  27. package/packages/core/src/agent/catalog-selector.ts +103 -0
  28. package/packages/core/src/agent/{Compaction.ts → compaction.ts} +86 -63
  29. package/packages/core/src/agent/context-compiler.ts +689 -0
  30. package/packages/core/src/agent/conversation-store.ts +381 -0
  31. package/packages/core/src/agent/curator.ts +276 -0
  32. package/packages/core/src/agent/delegation-runtime.ts +241 -0
  33. package/packages/core/src/agent/goal-runner.ts +323 -0
  34. package/packages/core/src/agent/index.ts +17 -12
  35. package/packages/core/src/agent/llm-client.ts +266 -0
  36. package/packages/core/src/agent/llm-providers/anthropic.ts +264 -0
  37. package/packages/core/src/agent/llm-providers/deepseek.ts +8 -0
  38. package/packages/core/src/agent/{providers → llm-providers}/gemini.ts +98 -60
  39. package/packages/core/src/agent/llm-providers/groq.ts +5 -0
  40. package/packages/core/src/agent/llm-providers/hiveagents.ts +253 -0
  41. package/packages/core/src/agent/{providers → llm-providers}/interface.ts +73 -13
  42. package/packages/core/src/agent/llm-providers/kimi.ts +8 -0
  43. package/packages/core/src/agent/llm-providers/minimax.ts +13 -0
  44. package/packages/core/src/agent/llm-providers/mistral.ts +5 -0
  45. package/packages/core/src/agent/llm-providers/modelscope.ts +5 -0
  46. package/packages/core/src/agent/llm-providers/nvidia.ts +5 -0
  47. package/packages/core/src/agent/{providers → llm-providers}/ollama.ts +31 -5
  48. package/packages/core/src/agent/llm-providers/openai-compat-base.ts +418 -0
  49. package/packages/core/src/agent/llm-providers/openai.ts +5 -0
  50. package/packages/core/src/agent/llm-providers/opencode-go.ts +9 -0
  51. package/packages/core/src/agent/llm-providers/openrouter.ts +5 -0
  52. package/packages/core/src/agent/llm-providers/qwen.ts +5 -0
  53. package/packages/core/src/agent/llm-providers/z-ai.ts +5 -0
  54. package/packages/core/src/agent/minimal-loadout.ts +47 -0
  55. package/packages/core/src/agent/playbook-selector.ts +119 -0
  56. package/packages/core/src/agent/{PromptBuilder.ts → prompt-builder.ts} +21 -22
  57. package/packages/core/src/{harness → agent}/proof-packet.ts +16 -21
  58. package/packages/core/src/agent/providers/index.ts +35 -16
  59. package/packages/core/src/agent/reflector.ts +320 -0
  60. package/packages/core/src/agent/routing-intent.ts +22 -0
  61. package/packages/core/src/{harness → agent}/run-epoch.ts +4 -3
  62. package/packages/core/src/{harness → agent}/run-store.ts +142 -81
  63. package/packages/core/src/agent/{Service.ts → service.ts} +37 -26
  64. package/packages/core/src/agent/skill-selector.ts +374 -0
  65. package/packages/core/src/agent/stuck-loop.ts +209 -0
  66. package/packages/core/src/agent/{selectors/ToolSelector.ts → tool-selector.ts} +188 -178
  67. package/packages/core/src/{ace/Tracer.ts → agent/tracer.ts} +37 -27
  68. package/packages/core/src/api/createAgent.test.ts +139 -27
  69. package/packages/core/src/api/createAgent.ts +232 -44
  70. package/packages/core/src/artifacts/store.ts +162 -0
  71. package/packages/core/src/canvas/canvas-manager.ts +161 -0
  72. package/packages/core/src/canvas/canvas.test.ts +8 -4
  73. package/packages/core/src/canvas/emitter.ts +131 -80
  74. package/packages/core/src/canvas/index.ts +1 -3
  75. package/packages/core/src/channels/base.ts +9 -1
  76. package/packages/core/src/channels/discord.ts +5 -4
  77. package/packages/core/src/channels/manager.ts +122 -30
  78. package/packages/core/src/channels/slack.ts +5 -4
  79. package/packages/core/src/channels/telegram.ts +36 -6
  80. package/packages/core/src/channels/webchat.ts +11 -10
  81. package/packages/core/src/channels/whatsapp.ts +23 -7
  82. package/packages/core/src/config/index.ts +13 -2
  83. package/packages/core/src/config/loader.ts +71 -29
  84. package/packages/core/src/ethics/EthicsGuard.test.ts +90 -36
  85. package/packages/core/src/ethics/EthicsGuard.ts +51 -47
  86. package/packages/core/src/events/agent-bus.ts +44 -68
  87. package/packages/core/src/events/channel-narration.ts +150 -0
  88. package/packages/core/src/events/narration.ts +82 -0
  89. package/packages/core/src/events/tool-narration.ts +62 -0
  90. package/packages/core/src/gateway/delegation-groups.ts +258 -0
  91. package/packages/core/src/{harness → gateway}/durable-queue.ts +102 -42
  92. package/packages/core/src/{harness → gateway}/job-store.ts +85 -48
  93. package/packages/core/src/gateway/lane-queue.ts +173 -0
  94. package/packages/core/src/gateway/notification-inbox.ts +57 -0
  95. package/packages/core/src/gateway/server.ts +1 -1
  96. package/packages/core/src/harness/index.ts +46 -27
  97. package/packages/core/src/index.ts +33 -27
  98. package/packages/core/src/mcp/hot-reload.ts +32 -23
  99. package/packages/core/src/mcp/index.ts +6 -3
  100. package/packages/core/src/mcp/singleton.ts +1 -4
  101. package/packages/core/src/mcp/tool-sync.ts +138 -0
  102. package/packages/core/src/memory/Scratchpad.test.ts +39 -20
  103. package/packages/core/src/memory/Scratchpad.ts +27 -34
  104. package/packages/core/src/multimodal/vision-service.ts +44 -38
  105. package/packages/core/src/resilience/retry.ts +95 -0
  106. package/packages/core/src/scheduler/CronScheduler.ts +334 -287
  107. package/packages/core/src/scheduler/index.ts +9 -7
  108. package/packages/core/src/scheduler/integration.ts +46 -26
  109. package/packages/core/src/scheduler/scheduler.test.ts +9 -13
  110. package/packages/core/src/scheduler/types.ts +7 -2
  111. package/packages/core/src/security/Pairing.ts +1 -1
  112. package/packages/core/src/skills/bundled/a2ui/a2ui_dashboard/SKILL.md +176 -0
  113. package/packages/core/src/skills/bundled/a2ui/a2ui_form/SKILL.md +202 -0
  114. package/packages/core/src/skills/bundled/a2ui/a2ui_interactive/SKILL.md +206 -0
  115. package/packages/core/src/skills/bundled/agents/agent_spawner/SKILL.md +173 -0
  116. package/packages/core/src/skills/bundled/agents/memory_manager/SKILL.md +143 -0
  117. package/packages/core/src/skills/bundled/agents/research_and_remember/SKILL.md +139 -0
  118. package/packages/core/src/skills/bundled/agents/task_orchestrator/SKILL.md +98 -0
  119. package/packages/core/src/skills/bundled/api/api_client/SKILL.md +132 -0
  120. package/packages/core/src/skills/bundled/cli/cli_pipeline/SKILL.md +135 -0
  121. package/packages/core/src/skills/bundled/cli/cli_safe_exec/SKILL.md +125 -0
  122. package/packages/core/src/skills/bundled/cli/software_engineering/SKILL.md +23 -0
  123. package/packages/core/src/skills/bundled/cron_manager/SKILL.md +188 -0
  124. package/packages/core/src/skills/bundled/cron_reminder/SKILL.md +112 -0
  125. package/packages/core/src/skills/bundled/filesystem/file_manager/SKILL.md +118 -0
  126. package/packages/core/src/skills/bundled/filesystem/file_read_and_summarize/SKILL.md +109 -0
  127. package/packages/core/src/skills/bundled/filesystem/file_writer/SKILL.md +129 -0
  128. package/packages/core/src/skills/bundled/filesystem/workspace_file_operator/SKILL.md +22 -0
  129. package/packages/core/src/skills/bundled/office/office_document_manager/SKILL.md +262 -0
  130. package/packages/core/src/skills/bundled/search_knowledge/capability_discovery/SKILL.md +75 -0
  131. package/packages/core/src/skills/bundled/web/browser_automate/SKILL.md +120 -0
  132. package/packages/core/src/skills/bundled/web/browser_scrape/SKILL.md +109 -0
  133. package/packages/core/src/skills/bundled/web/web_monitor/SKILL.md +127 -0
  134. package/packages/core/src/skills/bundled/web/web_research/SKILL.md +119 -0
  135. package/packages/core/src/skills/bundled-data.generated.ts +731 -2678
  136. package/packages/core/src/skills/skills.test.ts +52 -11
  137. package/packages/core/src/{harness → storage}/boot-id.ts +5 -2
  138. package/packages/core/src/storage/bootstrap.ts +151 -0
  139. package/packages/core/src/storage/causal-events.ts +84 -0
  140. package/packages/core/src/storage/collections.ts +680 -0
  141. package/packages/core/src/storage/crypto.ts +205 -74
  142. package/packages/core/src/{harness/db-helpers.ts → storage/hive.ts} +63 -7
  143. package/packages/core/src/storage/hivedb.ts +61 -0
  144. package/packages/core/src/storage/index.ts +111 -18
  145. package/packages/core/src/storage/model-id.ts +53 -0
  146. package/packages/core/src/storage/onboarding.ts +540 -972
  147. package/packages/core/src/storage/reconcile.ts +238 -0
  148. package/packages/core/src/storage/seed.ts +572 -406
  149. package/packages/core/src/storage/usage.ts +285 -225
  150. package/packages/core/src/storage/user-email.ts +11 -0
  151. package/packages/core/src/swarm/AgentExecutor.ts +1 -1
  152. package/packages/core/src/swarm/EventBridge.ts +1 -1
  153. package/packages/core/src/swarm/index.ts +12 -9
  154. package/packages/core/src/tool-runtime/index.ts +146 -23
  155. package/packages/core/src/tool-runtime/tool-worker.ts +2 -2
  156. package/packages/core/src/tool-runtime/worker-tools.ts +27 -0
  157. package/packages/core/src/{canvas/a2ui-tools.ts → tools/a2ui/index.ts} +17 -8
  158. package/packages/core/src/tools/agents/get-available-models.ts +36 -54
  159. package/packages/core/src/tools/agents/index.ts +784 -292
  160. package/packages/core/src/tools/api/api-request.test.ts +164 -0
  161. package/packages/core/src/tools/api/api-request.ts +174 -0
  162. package/packages/core/src/tools/api/index.ts +16 -0
  163. package/packages/core/src/tools/cli/index.ts +4 -0
  164. package/packages/core/src/tools/core/index.ts +281 -112
  165. package/packages/core/src/tools/cron/index.ts +121 -124
  166. package/packages/core/src/tools/index.ts +63 -78
  167. package/packages/core/src/tools/office/office-escribir-xlsx.ts +3 -1
  168. package/packages/core/src/tools/types.ts +3 -1
  169. package/packages/core/src/tools/web/artifact-inspect.ts +23 -0
  170. package/packages/core/src/tools/web/browser-screenshot.ts +26 -5
  171. package/packages/core/src/tools/web/browser-service.ts +5 -0
  172. package/packages/core/src/tools/web/browser-type.ts +3 -8
  173. package/packages/core/src/tools/web/index.ts +4 -4
  174. package/packages/core/src/voice/index.ts +89 -63
  175. package/packages/core/src/workers/agent.worker.ts +2 -2
  176. package/packages/core/src/workers/workers.test.ts +3 -10
  177. package/scripts/bump-version.ts +248 -0
  178. package/scripts/generate-skill-bundle.ts +108 -0
  179. package/test/agent-loop-terminal-synthesis.test.ts +32 -0
  180. package/test/catalog-agents-stay-enabled.test.ts +117 -0
  181. package/test/causal-events.test.ts +117 -0
  182. package/test/compaction.test.ts +105 -0
  183. package/test/context-compiler.test.ts +269 -0
  184. package/test/curator.test.ts +130 -0
  185. package/test/durable-queue.test.ts +114 -0
  186. package/test/harness-barrel.test.ts +64 -0
  187. package/test/hive-helpers.test.ts +130 -0
  188. package/test/hivedb-search.test.ts +189 -0
  189. package/test/internal-turns.test.ts +166 -0
  190. package/test/job-idempotency.test.ts +68 -0
  191. package/test/job-retry-backoff.test.ts +184 -0
  192. package/test/job-store.test.ts +381 -0
  193. package/test/llm-retry.test.ts +97 -0
  194. package/test/memory-perf.test.ts +774 -0
  195. package/test/minimal-loadout.test.ts +78 -0
  196. package/test/model-catalog.test.ts +105 -0
  197. package/test/preload.ts +12 -0
  198. package/test/reflector.test.ts +320 -0
  199. package/test/retention-cap.test.ts +91 -0
  200. package/test/retired-capabilities-pruned.test.ts +192 -0
  201. package/test/run-store.test.ts +355 -0
  202. package/test/scratchpad.test.ts +74 -0
  203. package/test/secrets-durability.test.ts +119 -0
  204. package/test/seed-model-reseed.test.ts +155 -0
  205. package/test/setup-agent-seed.test.ts +264 -0
  206. package/test/tool-inventory.test.ts +65 -0
  207. package/test/tool-runtime.test.ts +258 -0
  208. package/test/toon.test.ts +429 -0
  209. package/tsconfig.json +2 -0
  210. package/packages/core/src/ace/Curator.ts +0 -158
  211. package/packages/core/src/ace/Reflector.ts +0 -200
  212. package/packages/core/src/ace/index.ts +0 -4
  213. package/packages/core/src/agent/AgentRunner.ts +0 -711
  214. package/packages/core/src/agent/ContextCompiler.ts +0 -567
  215. package/packages/core/src/agent/ContextGuard.ts +0 -91
  216. package/packages/core/src/agent/ConversationStore.ts +0 -254
  217. package/packages/core/src/agent/Hooks.ts +0 -166
  218. package/packages/core/src/agent/StuckLoop.ts +0 -133
  219. package/packages/core/src/agent/providers/LLMClient.ts +0 -149
  220. package/packages/core/src/agent/providers/anthropic.ts +0 -212
  221. package/packages/core/src/agent/providers/openai-compat.ts +0 -231
  222. package/packages/core/src/agent/selectors/PlaybookSelector.ts +0 -121
  223. package/packages/core/src/agent/selectors/SkillSelector.ts +0 -322
  224. package/packages/core/src/agent/selectors/index.ts +0 -6
  225. package/packages/core/src/auth/auth.ts +0 -121
  226. package/packages/core/src/auth/index.ts +0 -1
  227. package/packages/core/src/canvas/CanvasManager.ts +0 -390
  228. package/packages/core/src/canvas/canvas-tools.ts +0 -448
  229. package/packages/core/src/harness/collections.ts +0 -98
  230. package/packages/core/src/harness/goal-verifier.ts +0 -141
  231. package/packages/core/src/harness/harness.test.ts +0 -236
  232. package/packages/core/src/harness/reconcile.ts +0 -149
  233. package/packages/core/src/mcp/MCPToolAdapter.ts +0 -176
  234. package/packages/core/src/multimodal/VisionService.ts +0 -293
  235. package/packages/core/src/scheduler/dag/AgentExecutor.ts +0 -53
  236. package/packages/core/src/scheduler/dag/DAGScheduler.ts +0 -250
  237. package/packages/core/src/scheduler/dag/EventBridge.ts +0 -122
  238. package/packages/core/src/scheduler/dag/TaskGraph.ts +0 -192
  239. package/packages/core/src/scheduler/dag/TaskNode.ts +0 -97
  240. package/packages/core/src/scheduler/dag/TaskResult.ts +0 -22
  241. package/packages/core/src/scheduler/dag/errors.ts +0 -37
  242. package/packages/core/src/scheduler/dag/index.ts +0 -26
  243. package/packages/core/src/scheduler/dag/presets/ResearchPreset.ts +0 -97
  244. package/packages/core/src/scheduler/dag/strategies/ParallelStrategy.ts +0 -21
  245. package/packages/core/src/scheduler/dag/strategies/PriorityStrategy.ts +0 -46
  246. package/packages/core/src/storage/HiveDBStorage.ts +0 -64
  247. package/packages/core/src/storage/SQLiteStorage.ts +0 -414
  248. package/packages/core/src/storage/hiveSeed.ts +0 -308
  249. package/packages/core/src/storage/hiveStorage.test.ts +0 -38
  250. package/packages/core/src/storage/schema.ts +0 -689
  251. package/packages/core/src/storage/storage.test.ts +0 -37
  252. package/packages/core/src/swarm/AgentBus.ts +0 -460
  253. package/packages/core/src/swarm/EventBus.ts +0 -169
  254. package/packages/core/src/swarm/WorkerPool.ts +0 -236
  255. package/packages/core/src/tools/bridge-events.ts +0 -26
  256. package/packages/core/src/tools/canvas/index.ts +0 -375
  257. package/packages/core/src/tools/codebridge/index.ts +0 -342
  258. package/packages/core/src/tools/meeting/index.ts +0 -353
  259. package/packages/core/src/tools/projects/index.ts +0 -37
  260. package/packages/core/src/tools/projects/project-create.ts +0 -94
  261. package/packages/core/src/tools/projects/project-done.ts +0 -66
  262. package/packages/core/src/tools/projects/project-fail.ts +0 -66
  263. package/packages/core/src/tools/projects/project-list.ts +0 -96
  264. package/packages/core/src/tools/projects/project-update.ts +0 -72
  265. package/packages/core/src/tools/projects/task-create.ts +0 -68
  266. package/packages/core/src/tools/projects/task-evaluate.ts +0 -93
  267. package/packages/core/src/tools/projects/task-update.ts +0 -93
  268. package/packages/core/src/tools/voice/index.ts +0 -104
  269. package/packages/core/src/tools/web/api-request.test.ts +0 -170
  270. package/packages/core/src/tools/web/api-request.ts +0 -239
  271. package/test/setup-db.ts +0 -216
  272. /package/packages/core/src/agent/{NativeTools.ts → native-tools.ts} +0 -0
@@ -1,711 +0,0 @@
1
- /**
2
- * Agent Loop — native implementation, no LangGraph.
3
- *
4
- * Replaces supervisor.ts + graph.ts.
5
- *
6
- * Pattern:
7
- * user message → context compiler → model call → [tool call → model call]* → response
8
- *
9
- * Exposes an async generator compatible with the existing providers/index.ts stream API:
10
- * yield { agent: { messages: [AIMessage] } }
11
- * yield { tools: { messages: [ToolMessage] } }
12
- *
13
- * Also used directly by runAgentIsolated() for worker tasks.
14
- */
15
-
16
- import { logger } from "../utils/logger.ts"
17
- import { getDb } from "../storage/SQLiteStorage.ts"
18
- import { callLLM, resolveProviderConfig, type LLMMessage } from "./providers/LLMClient"
19
- import { addMessage } from "./ConversationStore"
20
- import { saveTrace, recordLLMUsage } from "../ace/Tracer"
21
- import { maybeCompact, clearOldToolResults } from "./Compaction"
22
- import { emitCanvas } from "../canvas/emitter.ts"
23
- import type { MCPClientManager } from "../mcp/index.ts"
24
- import { compileContext } from "./ContextCompiler"
25
- import { formatToolResult } from "../utils/toon.ts"
26
- import { getAverageTokenCost } from "../storage/usage.ts"
27
- import { resolveUserId, resolveAgentId } from "../storage/onboarding.ts"
28
- import type { ContentPart } from "../multimodal/types.ts"
29
-
30
- /**
31
- * Execute a tool by name from the available tools list
32
- * This is a local helper function since executeTool is not exported elsewhere
33
- *
34
- * Returns: JS object normal (se encodea solo al enviar al LLM)
35
- */
36
- async function executeTool(
37
- allTools: Array<{ name: string; execute?: (params: Record<string, unknown>, config?: any) => Promise<unknown> }>,
38
- toolName: string,
39
- args: unknown,
40
- config: { user_id?: string; thread_id?: string; channel?: string; workspace?: string | null }
41
- ): Promise<unknown> {
42
- const tool = allTools.find(t => t.name === toolName)
43
- if (!tool?.execute) {
44
- return { error: true, message: `Tool '${toolName}' not found or not executable` }
45
- }
46
- try {
47
- const parsedArgs = typeof args === 'string' ? JSON.parse(args) : args
48
- return await tool.execute(parsedArgs as Record<string, unknown>, { configurable: config })
49
- } catch (err) {
50
- return {
51
- error: true,
52
- tool: toolName,
53
- message: (err as Error).message,
54
- timestamp: new Date().toISOString(),
55
- }
56
- }
57
- }
58
-
59
- const log = logger.child("agent-loop")
60
-
61
- // ─── Types ────────────────────────────────────────────────────────────────────
62
-
63
- export interface AgentLoopOptions {
64
- agentId: string
65
- userMessage: string | ContentPart[]
66
- threadId: string
67
- channel?: string
68
- mcpManager?: MCPClientManager | null
69
- /** System prompt override (from server.ts config) */
70
- systemPromptOverride?: string
71
- /** Worker mode: isolated context + single-task execution */
72
- isolated?: boolean
73
- taskContext?: string | ContentPart[]
74
- onStep?: (step: StepEvent) => Promise<void>
75
- /** User ID for context propagation */
76
- userId?: string
77
- /** Abort signal to stop generation mid-execution */
78
- signal?: AbortSignal
79
- /** Clean text for FTS5 and tracing (extracted from userMessage if multimodal) */
80
- rawUserMessage?: string
81
- /**
82
- * Per-call provider API key override, taking precedence over the DB-stored
83
- * key resolved by `resolveProviderConfig`. Required for safe multi-tenant
84
- * hosting (e.g. hive-cloud): without it, concurrent calls for different
85
- * tenants using the same provider would race on `process.env[...]`, since
86
- * that env var is process-global.
87
- */
88
- apiKey?: string
89
- /** Per-call provider base URL override (self-hosted/proxy endpoints), same rationale as `apiKey`. */
90
- baseUrl?: string
91
- }
92
-
93
- export interface StepEvent {
94
- type: "text" | "tool_call" | "tool_result"
95
- message: string
96
- toolName?: string
97
- isError?: boolean
98
- }
99
-
100
- // ─── Stream chunk types (compatible with providers/index.ts) ─────────────────
101
-
102
- export interface StreamChunk {
103
- agent?: { messages: any[] }
104
- tools?: { messages: any[] }
105
- usage?: { input_tokens: number; output_tokens: number }
106
- }
107
-
108
- // ─── Main agent loop ──────────────────────────────────────────────────────────
109
-
110
- export async function* runAgent(
111
- opts: AgentLoopOptions
112
- ): AsyncGenerator<StreamChunk> {
113
- const t0 = performance.now()
114
- const db = getDb()
115
-
116
- // Load agent config from DB
117
- const agent = db.query<any, [string]>("SELECT * FROM agents WHERE id = ?").get(opts.agentId)
118
- if (!agent) throw new Error(`Agent not found: ${opts.agentId}`)
119
-
120
- const agentName = agent.name || opts.agentId
121
- const maxIterations = agent.max_iterations || 10
122
-
123
- // Resolve LLM provider config
124
- const providerCfg = await resolveProviderConfig(
125
- agent.provider_id || "openai",
126
- agent.model_id || "gpt-4o-mini"
127
- )
128
- if (opts.apiKey) providerCfg.apiKey = opts.apiKey
129
- if (opts.baseUrl) providerCfg.baseUrl = opts.baseUrl
130
-
131
- const cleanModel = providerCfg.model.replace(new RegExp(`^${providerCfg.provider}\\/`), "")
132
- log.info(`[agent-loop] Starting: agent=${agentName} thread=${opts.threadId} provider=${providerCfg.provider}/${cleanModel}`)
133
-
134
- emitCanvas("canvas:node_update", {
135
- nodeId: opts.agentId,
136
- changes: { status: "thinking" },
137
- })
138
-
139
- // Store the user message in conversation history
140
- if (!opts.isolated) {
141
- // If userMessage is multimodal, addMessage extracts text for history storage
142
- addMessage(opts.threadId, "user", opts.userMessage, { channel: opts.channel })
143
- // Run compaction if conversation history is getting large
144
- await maybeCompact(
145
- opts.threadId,
146
- opts.channel && opts.userId
147
- ? { channel: opts.channel, userId: opts.userId }
148
- : undefined
149
- )
150
- }
151
-
152
- // Compile context (system prompt + history + tools)
153
- const ctx = await compileContext({
154
- agentId: opts.agentId,
155
- threadId: opts.threadId,
156
- userMessage: opts.userMessage,
157
- channel: opts.channel,
158
- mcpManager: opts.mcpManager,
159
- isolated: opts.isolated,
160
- taskContext: opts.taskContext,
161
- userId: opts.userId,
162
- })
163
-
164
- const systemPrompt = opts.systemPromptOverride || ctx.systemPrompt
165
-
166
- // Build initial messages array for the model
167
- let messages: LLMMessage[] = [
168
- { role: "system", content: systemPrompt },
169
- ...ctx.messages,
170
- ]
171
-
172
- // For isolated workers the user message is the task context, not from history
173
- if (opts.isolated) {
174
- messages.push({ role: "user", content: opts.userMessage })
175
- }
176
-
177
- let iterations = 0
178
- let totalInputTokens = 0
179
- let totalOutputTokens = 0
180
- let finalContent = ""
181
- // Loop detection: track last tool call signature to break identical consecutive calls
182
- let lastToolSignature = ""
183
- let consecutiveRepeat = 0
184
- let loopDetected = false
185
-
186
- // ── The loop ────────────────────────────────────────────────────────────
187
- while (iterations < maxIterations) {
188
- if (opts.signal?.aborted) {
189
- log.info(`[agent-loop] Aborted by signal at iteration ${iterations}`)
190
- finalContent = "Generación detenida."
191
- break
192
- }
193
-
194
- iterations++
195
-
196
- const response = await callLLM({
197
- ...providerCfg,
198
- messages: clearOldToolResults(messages) as LLMMessage[],
199
- tools: ctx.tools.length > 0 ? ctx.tools : undefined,
200
- })
201
-
202
- // Accumulate usage
203
- if (response.usage) {
204
- totalInputTokens += response.usage.input_tokens
205
- totalOutputTokens += response.usage.output_tokens
206
- }
207
-
208
- // Emit agent chunk (compatible with providers/index.ts)
209
- const agentMsg: any = { content: response.content }
210
- if (response.tool_calls?.length) agentMsg.tool_calls = response.tool_calls
211
- yield { agent: { messages: [agentMsg] } }
212
-
213
- // Notify onStep for narration text
214
- if (opts.onStep && response.content) {
215
- await opts.onStep({ type: "text", message: response.content })
216
- }
217
-
218
- // ── No tool calls → final response ──────────────────────────────────
219
- if (!response.tool_calls?.length || response.stop_reason !== "tool_calls") {
220
- finalContent = response.content?.trim() || ""
221
- // Only save to history if we have real content; empty → synthesis block will handle it
222
- if (finalContent && !opts.isolated) {
223
- addMessage(opts.threadId, "assistant", finalContent)
224
- }
225
- break
226
- }
227
-
228
- // ── Tool calls → execute each tool ──────────────────────────────────
229
- // Add assistant message with tool_calls to local messages array AND persist
230
- messages.push({
231
- role: "assistant",
232
- content: response.content,
233
- tool_calls: response.tool_calls,
234
- reasoning_content: response.reasoning_content,
235
- })
236
- if (!opts.isolated) {
237
- addMessage(opts.threadId, "assistant", response.content || "", {
238
- channel: opts.channel,
239
- tool_calls: response.tool_calls,
240
- reasoning_content: response.reasoning_content,
241
- })
242
- }
243
-
244
- for (const tc of response.tool_calls) {
245
- const toolName = tc.function.name
246
-
247
- emitCanvas("canvas:node_update", {
248
- nodeId: opts.agentId,
249
- changes: { status: "tool_call", currentTool: toolName },
250
- })
251
-
252
- if (opts.onStep) {
253
- if (response.content) {
254
- await opts.onStep({ type: "text", message: response.content })
255
- }
256
- await opts.onStep({
257
- type: "tool_call",
258
- toolName,
259
- message: `Calling tool: \`${toolName}\``,
260
- })
261
- }
262
-
263
- const tTool = performance.now()
264
- const toolResultJS = await executeTool(
265
- ctx.allTools,
266
- toolName,
267
- tc.function.arguments,
268
- {
269
- user_id: opts.userId,
270
- thread_id: opts.threadId,
271
- channel: opts.channel,
272
- workspace: agent.workspace ?? null,
273
- }
274
- )
275
- const toolMs = Math.round(performance.now() - tTool)
276
-
277
- // Encode TOON only for LLM consumption (with cost calculation)
278
- const toolResultLLM = formatToolResult(toolResultJS, cleanModel)
279
-
280
- log.info(`[agent-loop] Tool ${toolName} completed in ${toolMs}ms`)
281
-
282
- // Log tool result preview (truncated to avoid flooding logs)
283
- const resultPreview = toolResultLLM.length > 500
284
- ? toolResultLLM.substring(0, 500) + `… (+${toolResultLLM.length - 500} chars)`
285
- : toolResultLLM
286
- log.info(`[agent-loop] Tool result [${toolName}]: ${resultPreview}`)
287
-
288
- // Extract text for trace summary
289
- const textMessage = typeof opts.userMessage === "string"
290
- ? opts.userMessage
291
- : Array.isArray(opts.userMessage)
292
- ? opts.userMessage.filter(p => p.type === "text").map(p => (p as any).text).join("\n")
293
- : String(opts.userMessage)
294
-
295
- // Clean timestamp from message for trace
296
- const cleanMessage = textMessage.replace(/^\[Timestamp:.*?\]\n/, "")
297
-
298
- // Save tool call trace
299
- saveTrace({
300
- threadId: opts.threadId,
301
- agentId: opts.agentId,
302
- agentName,
303
- toolUsed: toolName,
304
- inputSummary: `${cleanMessage.substring(0, 200)} → ${toolName}`,
305
- outputSummary: toolResultLLM.substring(0, 300),
306
- success: !toolResultLLM.startsWith("[Tool Error]"),
307
- errorMessage: toolResultLLM.startsWith("[Tool Error]") ? toolResultLLM : null,
308
- durationMs: toolMs,
309
- })
310
-
311
- // Emit tool chunk (TOON encoded for LLM)
312
- yield { tools: { messages: [{ content: toolResultLLM, tool_call_id: tc.id }] } }
313
-
314
- if (opts.onStep) {
315
- await opts.onStep({ type: "tool_result", message: toolResultLLM })
316
- }
317
-
318
- // Add tool result to messages for next model call AND persist (TOON encoded)
319
- messages.push({
320
- role: "tool",
321
- content: toolResultLLM,
322
- tool_call_id: tc.id,
323
- })
324
- if (!opts.isolated) {
325
- addMessage(opts.threadId, "tool", toolResultLLM, {
326
- channel: opts.channel,
327
- tool_call_id: tc.id,
328
- })
329
- }
330
-
331
- // Dynamic tool injection: when search_knowledge finds tools (native or MCP), add them to ctx.tools
332
- if (toolName === "search_knowledge") {
333
- // Use JS object directly (no parse needed)
334
- try {
335
- const result = toolResultJS as any
336
- const foundTools: Array<{ name: string }> = result?.tools ?? []
337
- const foundMcpTools: Array<{ tool_name: string; full_name?: string; id?: string }> = result?.toolsmcp ?? []
338
- const currentToolNames = new Set(ctx.tools.map((t: any) => t.function?.name))
339
-
340
- // Track which tools were injected for skill lookup
341
- const injectedTools: string[] = []
342
-
343
- // Inject native tools
344
- for (const found of foundTools) {
345
- if (!currentToolNames.has(found.name)) {
346
- const nativeTool = ctx.allTools.find(t => t.name === found.name)
347
- if (nativeTool) {
348
- ctx.tools.push({
349
- type: "function",
350
- function: {
351
- name: nativeTool.name,
352
- description: (nativeTool as any).description ?? "",
353
- parameters: (nativeTool as any).parameters ?? { type: "object", properties: {} },
354
- },
355
- })
356
- log.info(`[agent-loop] Injected discovered native tool into loadout: ${nativeTool.name}`)
357
- currentToolNames.add(found.name)
358
- injectedTools.push(nativeTool.name)
359
- }
360
- }
361
- }
362
-
363
- // Inject MCP tools discovered via search_knowledge(type="mcp")
364
- for (const found of foundMcpTools) {
365
- // Use full_name (sanitized compound id) because ctx.allTools stores MCP tools
366
- // under the sanitized name (e.g. "Instagram__mis_estadisticas_de_instagram"),
367
- // NOT the original tool_name (e.g. "mis estadisticas de instagram").
368
- const mcpFullName = found.full_name || found.id
369
- log.debug(`[agent-loop] MCP discovery candidate: tool_name="${found.tool_name}", full_name="${found.full_name}", id="${found.id}", resolved="${mcpFullName}"`)
370
- if (!currentToolNames.has(mcpFullName)) {
371
- const mcpTool = ctx.allTools.find(t => t.name === mcpFullName)
372
- if (mcpTool) {
373
- ctx.tools.push({
374
- type: "function",
375
- function: {
376
- name: mcpTool.name,
377
- description: (mcpTool as any).description ?? "",
378
- parameters: (mcpTool as any).parameters ?? { type: "object", properties: {} },
379
- },
380
- })
381
- log.info(`[agent-loop] Injected discovered MCP tool into loadout: ${mcpTool.name}`)
382
- currentToolNames.add(mcpFullName)
383
- } else {
384
- log.warn(`[agent-loop] MCP tool "${mcpFullName}" not found in allTools (available MCP: ${ctx.allTools.filter(t => t.name.includes('__')).map(t => t.name).join(', ')})`)
385
- }
386
- }
387
- }
388
-
389
- // Inject skills associated with the injected tools
390
- if (injectedTools.length > 0) {
391
- try {
392
- const db = getDb()
393
- // Find skills that use any of the injected tools
394
- const placeholders = injectedTools.map(() => "?").join(",")
395
- const skillsWithTools = db.query(`
396
- SELECT DISTINCT s.name, s.body, s.tools
397
- FROM skills s
398
- WHERE s.active = 1
399
- AND (
400
- ${injectedTools.map(() => `s.tools LIKE ?`).join(" OR ")}
401
- )
402
- `).all(...injectedTools.map(t => `%${t}%`)) as Array<{ name: string; body: string; tools: string }>
403
-
404
- // Filter to only skills that actually contain the tools (not partial matches)
405
- const matchingSkills = skillsWithTools.filter(s => {
406
- const skillTools = s.tools?.split(",").map(t => t.trim()) ?? []
407
- return injectedTools.some(injected => skillTools.includes(injected))
408
- })
409
-
410
- if (matchingSkills.length > 0) {
411
- const skillSection = matchingSkills
412
- .map(s => `## Skill: ${s.name}\n${s.body}`)
413
- .join("\n\n")
414
-
415
- // Add skill instructions to system prompt (first message)
416
- const systemMsg = messages.find(m => m.role === "system")
417
- if (systemMsg && typeof systemMsg.content === "string") {
418
- // Check if we already added this skill
419
- const existingSkillNames = new Set(
420
- (systemMsg.content.match(/## Skill: ([^\n]+)/g) || [])
421
- .map(m => m.replace("## Skill: ", "").trim())
422
- )
423
-
424
- const newSkills = matchingSkills.filter(s => !existingSkillNames.has(s.name))
425
- if (newSkills.length > 0) {
426
- const newSkillSection = newSkills
427
- .map(s => `## Skill: ${s.name}\n${s.body}`)
428
- .join("\n\n")
429
-
430
- systemMsg.content += `\n\n--- SKILL INSTRUCTIONS (Auto-loaded) ---\n${newSkillSection}`
431
- log.info(`[agent-loop] Injected ${newSkills.length} skill(s) for tools: ${newSkills.map(s => s.name).join(", ")}`)
432
- }
433
- }
434
- }
435
- } catch (skillErr) {
436
- log.warn(`[agent-loop] Failed to inject skills for tools: ${(skillErr as Error).message}`)
437
- }
438
- }
439
- } catch (err) {
440
- log.warn(`[agent-loop] search_knowledge tool injection failed: ${(err as Error).message}`)
441
- }
442
-
443
- // Enrich the tool result with skill instructions and playbook rules
444
- try {
445
- const result = toolResultJS as any
446
- const foundSkills: Array<{ name: string; body?: string }> = result?.skills ?? []
447
- const foundPlaybook: Array<{ rule: string; category?: string }> = result?.playbook ?? []
448
-
449
- if (foundSkills.length > 0 || foundPlaybook.length > 0) {
450
- const extras: string[] = []
451
-
452
- if (foundSkills.some((s: any) => s.body)) {
453
- const section = foundSkills
454
- .filter((s: any) => s.body)
455
- .map((s: any) => `## Skill: ${s.name}\n${s.body}`)
456
- .join("\n\n")
457
- extras.push(`\n\n--- SKILL INSTRUCTIONS ---\n${section}`)
458
- }
459
-
460
- if (foundPlaybook.length > 0) {
461
- const section = foundPlaybook.map((p: any) => `- [${p.category ?? "general"}] ${p.rule}`).join("\n")
462
- extras.push(`\n\n--- PLAYBOOK RULES ---\n${section}`)
463
- }
464
-
465
- if (extras.length > 0) {
466
- const lastMsg = messages[messages.length - 1]
467
- if (lastMsg?.role === "tool") {
468
- lastMsg.content += extras.join("")
469
- log.info(`[agent-loop] Enriched search_knowledge result with ${foundSkills.length} skill(s) and ${foundPlaybook.length} rule(s)`)
470
- }
471
- }
472
- }
473
- } catch (err) {
474
- log.warn(`[agent-loop] search_knowledge enrichment failed: ${(err as Error).message}`)
475
- }
476
- }
477
-
478
- // Loop detection: same tool + same args called consecutively → break
479
- const sig = `${toolName}:${JSON.stringify(tc.function.arguments)}`
480
- if (sig === lastToolSignature) {
481
- consecutiveRepeat++
482
- if (consecutiveRepeat >= 2) {
483
- log.warn(`[agent-loop] Loop detected: "${toolName}" x${consecutiveRepeat + 1} with same args. Breaking.`)
484
- finalContent = "No pude completar la tarea porque no encontré las herramientas necesarias para ello."
485
- loopDetected = true
486
- break
487
- }
488
- } else {
489
- lastToolSignature = sig
490
- consecutiveRepeat = 0
491
- }
492
- }
493
-
494
- if (loopDetected) break
495
-
496
- emitCanvas("canvas:node_update", {
497
- nodeId: opts.agentId,
498
- changes: { status: "thinking", currentTool: null },
499
- })
500
- }
501
-
502
- // ── Synthesis call when max iterations hit without a text response ────────
503
- // The agent spent all iterations on tool calls and never produced a final message.
504
- // Make one extra call without tools so it summarizes what it did.
505
- if (!finalContent) {
506
- log.info(`[agent-loop] Max iterations hit with no text response — requesting synthesis (isolated=${!!opts.isolated})`)
507
- try {
508
- messages.push({
509
- role: "user",
510
- content: "Basándote en lo que hiciste hasta ahora, responde al usuario con un resumen claro de lo que completaste o del estado actual. Sé conciso.",
511
- })
512
- const synthesis = await callLLM({
513
- ...providerCfg,
514
- messages: clearOldToolResults(messages) as LLMMessage[],
515
- tools: undefined, // no tools — force text response
516
- })
517
- if (synthesis.usage) {
518
- totalInputTokens += synthesis.usage.input_tokens
519
- totalOutputTokens += synthesis.usage.output_tokens
520
- }
521
- finalContent = synthesis.content?.trim() || "He completado las tareas solicitadas."
522
- if (!opts.isolated) {
523
- addMessage(opts.threadId, "assistant", finalContent)
524
- }
525
- yield { agent: { messages: [{ content: finalContent }] } }
526
- } catch (err) {
527
- log.warn(`[agent-loop] Synthesis call failed: ${(err as Error).message}`)
528
- finalContent = "He completado las tareas solicitadas."
529
- if (!opts.isolated) {
530
- addMessage(opts.threadId, "assistant", finalContent)
531
- }
532
- yield { agent: { messages: [{ content: finalContent }] } }
533
- }
534
- }
535
-
536
- // Emit final usage so consumers (e.g. AgentRunner) can surface real token counts
537
- if (totalInputTokens > 0 || totalOutputTokens > 0) {
538
- yield { usage: { input_tokens: totalInputTokens, output_tokens: totalOutputTokens } }
539
- }
540
-
541
- // ── Post-loop ────────────────────────────────────────────────────────────
542
- const durationMs = Math.round(performance.now() - t0)
543
-
544
- emitCanvas("canvas:node_update", {
545
- nodeId: opts.agentId,
546
- changes: { status: "idle", currentTool: null },
547
- })
548
-
549
- // Record usage
550
- recordLLMUsage({
551
- provider: providerCfg.provider,
552
- model: providerCfg.model,
553
- inputTokens: totalInputTokens,
554
- outputTokens: totalOutputTokens,
555
- })
556
-
557
- // Extract text for trace summary
558
- const textMessageFinal = opts.rawUserMessage || (typeof opts.userMessage === "string"
559
- ? opts.userMessage
560
- : Array.isArray(opts.userMessage)
561
- ? opts.userMessage.filter(p => p.type === "text").map(p => (p as any).text).join("\n")
562
- : String(opts.userMessage))
563
-
564
- // Save overall trace
565
- const cleanMessageFinal = textMessageFinal.replace(/^\[Timestamp:.*?\]\n/, "")
566
- saveTrace({
567
- threadId: opts.threadId,
568
- agentId: opts.agentId,
569
- agentName,
570
- inputSummary: cleanMessageFinal.substring(0, 300),
571
- outputSummary: finalContent.substring(0, 300),
572
- success: true,
573
- durationMs,
574
- tokensUsed: totalInputTokens + totalOutputTokens,
575
- })
576
-
577
- log.info(
578
- `[agent-loop] Done: agent=${agentName} iterations=${iterations} ` +
579
- `tokens=${totalInputTokens + totalOutputTokens} elapsed=${durationMs}ms`
580
- )
581
- }
582
-
583
- // ─── Isolated worker execution (Fase 4.4) ───────────────────────────────────
584
-
585
- /**
586
- * Run a worker agent in an isolated context.
587
- * Returns the final response string.
588
- */
589
- export async function runAgentIsolated(opts: {
590
- agentId: string
591
- taskDescription: string | ContentPart[]
592
- threadId: string
593
- mcpManager?: MCPClientManager | null
594
- }): Promise<string> {
595
- let lastContent = ""
596
- for await (const chunk of runAgent({
597
- agentId: opts.agentId,
598
- userMessage: opts.taskDescription,
599
- threadId: opts.threadId,
600
- isolated: true,
601
- taskContext: opts.taskDescription,
602
- mcpManager: opts.mcpManager,
603
- })) {
604
- if (chunk.agent?.messages?.[0]?.content) {
605
- lastContent = chunk.agent.messages[0].content
606
- }
607
- }
608
- return lastContent
609
- }
610
-
611
- // ─── Shim: AgentLoop class with stream() compatible with providers/index.ts ──
612
-
613
- export class AgentLoop {
614
- private mcpManager: MCPClientManager | null = null
615
-
616
- setMCPManager(m: MCPClientManager) {
617
- this.mcpManager = m
618
- }
619
-
620
- /**
621
- * Returns an async iterable that emits chunks compatible with
622
- * the existing providers/index.ts stream consumer.
623
- */
624
- stream(
625
- input: { messages: Array<{ role: string; content: string | ContentPart[] }> },
626
- config: {
627
- configurable?: {
628
- thread_id?: string
629
- agent_id?: string
630
- user_id?: string
631
- system_prompt?: string
632
- channel?: string
633
- raw_user_message?: string
634
- }
635
- signal?: AbortSignal
636
- }
637
- ): AsyncIterable<StreamChunk> {
638
- // Resolve from database with priority: explicit param → DB lookup → single user/agent
639
- const threadId = config.configurable?.thread_id || resolveUserId({}) || "default"
640
- const agentId = config.configurable?.agent_id || resolveAgentId(config.configurable?.agent_id) || this._resolveCoordinatorId() || "main"
641
- const systemPromptOverride = config.configurable?.system_prompt
642
- const channel = config.configurable?.channel
643
- const userId = config.configurable?.user_id || resolveUserId({
644
- channel: config.configurable?.channel ? (config.configurable?.channel as string).split(':')[0] : null,
645
- channelUserId: config.configurable?.thread_id
646
- })
647
-
648
- // Log MCP Manager status
649
- log.info(`[AgentLoop.stream] MCP Manager available: ${this.mcpManager !== null}`)
650
- if (this.mcpManager) {
651
- try {
652
- const servers = this.mcpManager.listServers?.() || []
653
- log.info(`[AgentLoop.stream] MCP servers: ${servers.length} registered`)
654
- for (const s of servers) {
655
- log.info(` - ${s.name}: ${s.status} (${s.tools?.length || 0} tools)`)
656
- }
657
- } catch (e) {
658
- log.warn(`[AgentLoop.stream] Failed to list MCP servers: ${(e as Error).message}`)
659
- }
660
- }
661
-
662
- // Extract the last user message from the input
663
- const lastUserMsg = [...input.messages].reverse().find((m) => m.role === "user")
664
- const userMessage = lastUserMsg?.content || ""
665
-
666
- // Use clean message (without timestamp) for FTS5 selectors
667
- const rawUserMessage = config.configurable?.raw_user_message ||
668
- (typeof userMessage === "string" ? userMessage : userMessage.filter(p => p.type === "text").map(p => (p as any).text).join("\n"))
669
-
670
- return runAgent({
671
- agentId,
672
- userMessage, // FULL MULTIMODAL MESSAGE
673
- rawUserMessage, // CLEAN TEXT for FTS5
674
- threadId,
675
- channel,
676
- systemPromptOverride,
677
- mcpManager: this.mcpManager,
678
- userId,
679
- signal: config.signal,
680
- })
681
- }
682
-
683
- private _resolveCoordinatorId(): string {
684
- // Use the storage helper to get coordinator agent ID from database
685
- const coordinatorId = resolveAgentId(null);
686
- return coordinatorId || "main";
687
- }
688
- }
689
-
690
- // Singleton
691
- let _agentLoop: AgentLoop | null = null
692
-
693
- export function getAgentLoop(): AgentLoop | null {
694
- return _agentLoop
695
- }
696
-
697
- export function buildAgentLoop(opts: { mcpManager?: MCPClientManager | null } = {}): AgentLoop {
698
- _agentLoop = new AgentLoop()
699
- if (opts.mcpManager) {
700
- _agentLoop.setMCPManager(opts.mcpManager)
701
- log.info("[buildAgentLoop] MCP Manager set successfully")
702
- } else {
703
- log.warn("[buildAgentLoop] No MCP Manager provided, agent will not have MCP tools")
704
- }
705
- return _agentLoop
706
- }
707
-
708
- export async function rebuildAgentLoop(opts: { mcpManager?: MCPClientManager | null } = {}): Promise<AgentLoop> {
709
- _agentLoop = null
710
- return buildAgentLoop(opts)
711
- }