@johpaz/hive-sdk 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 -20
  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 -17
  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,12 +1,14 @@
1
1
  /**
2
2
  * Tracer — ACE Generator output.
3
3
  *
4
- * Records every agent execution to the `traces` table.
5
- * Fire-and-forget (non-blocking). Also updates playbook helpful/harmful counts
6
- * based on execution outcome.
4
+ * Records every agent execution to the `traces` collection.
5
+ * Fire-and-forget (non-blocking). Also updates the denormalized
6
+ * `agents.lastTraceAt` field used by the Curator's stale-worker detection.
7
7
  */
8
8
 
9
- import { logger } from "../utils/logger.ts"
9
+ import { logger } from "../utils/logger"
10
+ import { col, nextId, updateDoc } from "../storage/hive"
11
+ import type { TraceDoc, AgentDoc } from "../storage/collections"
10
12
 
11
13
  const log = logger.child("tracer")
12
14
 
@@ -21,6 +23,9 @@ export interface TraceInput {
21
23
  errorMessage?: string | null
22
24
  durationMs?: number
23
25
  tokensUsed?: number
26
+ /** G9 causal stream id for this run, when the causal log is enabled. */
27
+ causalStreamId?: string | null
28
+ catalogAgentId?: string | null
24
29
  }
25
30
 
26
31
  /**
@@ -31,27 +36,32 @@ export function saveTrace(trace: TraceInput): void {
31
36
  // Run asynchronously so it never blocks the caller
32
37
  Promise.resolve().then(async () => {
33
38
  try {
34
- const { getDb } = await import("../storage/SQLiteStorage.ts")
35
- const db = getDb()
36
-
37
- db.query(`
38
- INSERT INTO traces
39
- (thread_id, agent_id, agent_name, tool_used, input_summary,
40
- output_summary, success, error_message, duration_ms, tokens_used)
41
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
42
- `).run(
43
- trace.threadId,
44
- trace.agentId,
45
- trace.agentName,
46
- trace.toolUsed ?? null,
47
- trace.inputSummary.substring(0, 500),
48
- trace.outputSummary.substring(0, 500),
49
- trace.success ? 1 : 0,
50
- trace.errorMessage ?? null,
51
- trace.durationMs ?? null,
52
- trace.tokensUsed ?? null,
53
- )
39
+ const tracesCol = await col<TraceDoc>("traces")
40
+ const id = await nextId("traces")
41
+ const now = Date.now()
42
+ const agentsCol = await col<AgentDoc>("agents")
43
+ const agentEntry = await agentsCol.get(trace.agentId)
44
+ const catalogAgentId = trace.catalogAgentId ?? (agentEntry?.doc.source === "catalog" ? agentEntry.doc.id : null)
45
+ await tracesCol.put(id, {
46
+ id,
47
+ thread_id: trace.threadId,
48
+ agent_id: trace.agentId,
49
+ agent_name: trace.agentName,
50
+ tool_used: trace.toolUsed ?? null,
51
+ input_summary: trace.inputSummary.substring(0, 500),
52
+ output_summary: trace.outputSummary.substring(0, 500),
53
+ success: trace.success,
54
+ error_message: trace.errorMessage ?? null,
55
+ duration_ms: trace.durationMs ?? null,
56
+ tokens_used: trace.tokensUsed ?? null,
57
+ created_at: now,
58
+ causal_stream_id: trace.causalStreamId ?? null,
59
+ catalog_agent_id: catalogAgentId ?? undefined,
60
+ })
54
61
 
62
+ // Denormalized field the Curator scans for stale-worker detection,
63
+ // avoiding a correlated MAX(created_at) query HiveDB has no primitive for.
64
+ await updateDoc<AgentDoc>("agents", trace.agentId, { lastTraceAt: now }).catch(() => { /* agent may not exist */ })
55
65
  // Trigger reflector check in background
56
66
  checkReflectorTrigger().catch(() => { /* ignore */ })
57
67
  } catch (err) {
@@ -72,7 +82,7 @@ async function checkReflectorTrigger(): Promise<void> {
72
82
  _tracesSinceLastReflection = 0
73
83
 
74
84
  // Lazy import to avoid circular deps
75
- const { runReflector } = await import("./Reflector.ts")
85
+ const { runReflector } = await import("./reflector")
76
86
  runReflector().catch((err) => {
77
87
  log.warn("[tracer] Reflector run failed:", err)
78
88
  })
@@ -88,8 +98,8 @@ export function recordLLMUsage(opts: {
88
98
  }): void {
89
99
  Promise.resolve().then(async () => {
90
100
  try {
91
- const { recordUsage } = await import("../storage/usage.ts")
92
- await recordUsage({
101
+ const { recordUsage } = await import("../storage/usage")
102
+ recordUsage({
93
103
  provider: opts.provider,
94
104
  model: opts.model,
95
105
  inputTokens: opts.inputTokens,
@@ -1,19 +1,148 @@
1
- import { describe, it, expect, beforeAll, afterAll } from "bun:test";
2
- import { getDb, initializeDatabase, dbService } from "../storage/SQLiteStorage.ts";
1
+ /**
2
+ * `createAgent` persiste su configuración en la fila del agente.
3
+ *
4
+ * Hasta 0.1.5 aceptaba `provider`, `model`, `maxIterations`, `workspace` y
5
+ * `systemPrompt` y los descartaba: construía el loop global y corría con lo que
6
+ * hubiera en la base. Estos tests fijan que ahora sí lleguen.
7
+ */
3
8
 
4
- describe("createAgent API", () => {
5
- beforeAll(async () => {
6
- await initializeDatabase();
9
+ process.env.HIVE_DB_PATH = ":memory:";
10
+
11
+ import { describe, it, expect, beforeEach, afterEach } from "bun:test";
12
+ import { z } from "zod";
13
+ import { closeHiveDb } from "../storage/hivedb.ts";
14
+ import { ensureHiveDb } from "../storage/bootstrap.ts";
15
+ import { col, fromIndexable } from "../storage/hive.ts";
16
+ import { clearAppTools, createAllTools } from "../tools/index.ts";
17
+ import { selectTools } from "../agent/tool-selector.ts";
18
+ import { loadConfig } from "../config/loader.ts";
19
+ import type { AgentDoc, ModelDoc } from "../storage/collections.ts";
20
+ import { createAgent } from "./createAgent.ts";
21
+ import { defineTool } from "../tools/ToolRegistry.ts";
22
+ import { defineSkill } from "../skills/defineSkill.ts";
23
+
24
+ beforeEach(async () => {
25
+ closeHiveDb();
26
+ clearAppTools();
27
+ await ensureHiveDb();
28
+ });
29
+
30
+ afterEach(() => {
31
+ clearAppTools();
32
+ closeHiveDb();
33
+ });
34
+
35
+ describe("createAgent", () => {
36
+ it("persiste provider, modelo, prompt e iteraciones en la fila del agente", async () => {
37
+ const agent = await createAgent({
38
+ name: "Test Agent",
39
+ provider: "anthropic",
40
+ model: "claude-opus-5",
41
+ systemPrompt: "Sos un asistente de prueba.",
42
+ maxIterations: 7,
43
+ workspace: "/tmp/ws",
44
+ });
45
+
46
+ expect(agent.name).toBe("Test Agent");
47
+ expect(agent.id).toBe("test_agent");
48
+
49
+ const agentsCol = await col<AgentDoc>("agents");
50
+ const row = (await agentsCol.get("test_agent"))!.doc;
51
+
52
+ expect(fromIndexable(row.provider_id)).toBe("anthropic");
53
+ expect(fromIndexable(row.model_id)).toBe("claude-opus-5");
54
+ expect(row.system_prompt).toBe("Sos un asistente de prueba.");
55
+ expect(row.max_iterations).toBe(7);
56
+ expect(row.workspace).toBe("/tmp/ws");
57
+ });
58
+
59
+ it("prefija el id del modelo cuando el provider es revendedor", async () => {
60
+ await createAgent({
61
+ name: "reseller",
62
+ provider: "modelscope",
63
+ model: "Qwen-Ambassador/Qwen3.8-Max",
64
+ });
65
+
66
+ const agentsCol = await col<AgentDoc>("agents");
67
+ const row = (await agentsCol.get("reseller"))!.doc;
68
+
69
+ expect(fromIndexable(row.model_id)).toBe("modelscope/Qwen-Ambassador/Qwen3.8-Max");
70
+ });
71
+
72
+ it("activa el modelo elegido, o resolveProviderConfig no encuentra su base_url", async () => {
73
+ await createAgent({ name: "activador", provider: "anthropic", model: "claude-opus-5" });
74
+
75
+ const modelsCol = await col<ModelDoc>("models");
76
+ const model = (await modelsCol.get("claude-opus-5"))!.doc;
77
+
78
+ expect(model.active).toBe(true);
79
+ expect(model.enabled).toBe(true);
80
+ });
81
+
82
+ it("rechaza un modelo que no está en el catálogo", async () => {
83
+ await expect(
84
+ createAgent({ name: "fantasma", provider: "anthropic", model: "claude-que-no-existe" })
85
+ ).rejects.toThrow(/no está en el catálogo/);
86
+ });
87
+
88
+ it("pide provider cuando se especifica un modelo", async () => {
89
+ // El mismo modelo lo sirven varios providers y la clave del catálogo
90
+ // depende de cuál, así que adivinarlo sería elegir por el usuario.
91
+ await expect(createAgent({ name: "sin-provider", model: "claude-opus-5" })).rejects.toThrow(
92
+ /necesita también `provider`/
93
+ );
7
94
  });
8
95
 
9
- afterAll(() => {
10
- dbService.close();
96
+ it("registra las tools de la app con su ejecutor, no sólo la declaración", async () => {
97
+ const greet = defineTool({
98
+ name: "greet_person",
99
+ description: "Saluda a alguien",
100
+ schema: z.object({ name: z.string().describe("a quién saludar") }),
101
+ execute: async (args: { name: string }) => `Hola ${args.name}`,
102
+ });
103
+
104
+ await createAgent({ name: "con-tools", tools: [greet] });
105
+
106
+ const registered = createAllTools(await loadConfig()).find((t) => t.name === "greet_person");
107
+ expect(registered, "la tool no llegó al registry que lee el context compiler").toBeDefined();
108
+ expect(registered!.parameters.properties).toHaveProperty("name");
109
+ expect(registered!.parameters.required).toEqual(["name"]);
110
+ expect(await registered!.execute({ name: "Ana" })).toBe("Hola Ana");
111
+ });
112
+
113
+ it("deja la tool de la app descubrible por búsqueda de capacidad", async () => {
114
+ // El loadout inicial es mínimo a propósito; el resto se encuentra vía
115
+ // `search_knowledge`, que corre sobre el índice BM25. Si createAgent no
116
+ // siembra la fila en `tools` y no reindexa, la tool tiene ejecutor pero el
117
+ // modelo no se entera de que existe.
118
+ const weather = defineTool({
119
+ name: "get_weather",
120
+ description: "Consulta el clima actual de una ciudad. Sinónimos: tiempo, temperatura, pronóstico",
121
+ schema: z.object({ city: z.string() }),
122
+ execute: async (a: { city: string }) => `Soleado en ${a.city}`,
123
+ });
124
+
125
+ await createAgent({ name: "descubrible", tools: [weather] });
126
+
127
+ const found = await selectTools("clima de una ciudad", undefined, 5);
128
+ expect(found.map((t) => t.name)).toContain("get_weather");
11
129
  });
12
130
 
13
- it("defineTool and defineSkill exports are available", async () => {
14
- const { defineTool } = await import("../tools/ToolRegistry.ts");
15
- const { defineSkill } = await import("../skills/defineSkill.ts");
131
+ it("reusa la fila al recrear un agente con el mismo nombre", async () => {
132
+ const first = await createAgent({ name: "estable", provider: "anthropic", model: "claude-opus-5" });
133
+ const second = await createAgent({ name: "estable", provider: "anthropic", model: "claude-sonnet-5" });
16
134
 
135
+ expect(second.id).toBe(first.id);
136
+
137
+ const agentsCol = await col<AgentDoc>("agents");
138
+ const all = (await agentsCol.scan({})).filter((e) => e.id === "estable");
139
+ expect(all).toHaveLength(1);
140
+ expect(fromIndexable(all[0].doc.model_id)).toBe("claude-sonnet-5");
141
+ });
142
+ });
143
+
144
+ describe("defineTool / defineSkill", () => {
145
+ it("siguen siendo funciones de declaración puras", () => {
17
146
  const tool = defineTool({
18
147
  name: "greet",
19
148
  description: "Says hello",
@@ -28,21 +157,4 @@ describe("createAgent API", () => {
28
157
  });
29
158
  expect(skill.name).toBe("greeting-skill");
30
159
  });
31
-
32
- it("createAgent builds an agent instance", async () => {
33
- const { createAgent } = await import("./createAgent.ts");
34
- const agent = await createAgent({
35
- name: "test-agent",
36
- provider: "openai",
37
- model: "gpt-4o-mini",
38
- });
39
- expect(agent.name).toBe("test-agent");
40
- expect(agent.config).toBeDefined();
41
- });
42
-
43
- it("ToolRegistry can be used standalone", async () => {
44
- const { ToolRegistry } = await import("../tools/ToolRegistry.ts");
45
- const reg = new ToolRegistry();
46
- expect(reg.size()).toBe(0);
47
- });
48
160
  });
@@ -1,13 +1,28 @@
1
+ /**
2
+ * createAgent — la puerta de entrada del SDK.
3
+ *
4
+ * Hasta 0.1.5 esta función aceptaba `provider`, `model`, `maxIterations`,
5
+ * `skills` y `workspace` y los descartaba: construía el agent loop global y
6
+ * corría con lo que hubiera en la base. El agente terminaba respondiendo con un
7
+ * modelo que el llamador nunca eligió. Ahora la config se persiste en la fila
8
+ * del agente, que es de donde el loop resuelve provider y modelo.
9
+ */
10
+
11
+ import { z } from "zod";
1
12
  import type { ToolDefinition } from "../tools/ToolRegistry.ts";
2
13
  import type { SkillDefinition } from "../skills/defineSkill.ts";
14
+ import type { Tool, ToolParameter } from "../tools/types.ts";
15
+ import type { Provider } from "../agent/providers/index.ts";
3
16
  import { logger } from "../utils/logger.ts";
4
17
 
5
18
  const log = logger.child("api");
6
19
 
7
20
  export interface AgentConfig {
8
21
  name: string;
22
+ /** Id del modelo tal como lo nombra su dueño (ej. "claude-opus-5", "Qwen-Ambassador/Qwen3.8-Max"). */
9
23
  model?: string;
10
- provider?: "openai" | "anthropic" | "gemini" | "ollama";
24
+ /** Cualquiera de los 16 providers del catálogo. */
25
+ provider?: Provider;
11
26
  systemPrompt?: string;
12
27
  tools?: ToolDefinition[];
13
28
  skills?: SkillDefinition[];
@@ -18,6 +33,7 @@ export interface AgentConfig {
18
33
 
19
34
  export interface Agent {
20
35
  readonly name: string;
36
+ readonly id: string;
21
37
  readonly config: AgentConfig;
22
38
  chat(message: string, opts?: { threadId?: string; channel?: string }): AsyncGenerator<AgentEvent>;
23
39
  run(task: string, opts?: { threadId?: string; channel?: string }): Promise<string>;
@@ -29,16 +45,54 @@ export type AgentEvent =
29
45
  | { type: "tool_result"; name: string; result: unknown }
30
46
  | { type: "done"; response: string };
31
47
 
48
+ /** Id estable derivado del nombre, para que dos `createAgent` con el mismo nombre compartan historial. */
49
+ function agentIdFrom(name: string): string {
50
+ const slug = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
51
+ return slug || "agent";
52
+ }
53
+
54
+ /**
55
+ * Convierte una tool declarada con `defineTool` (schema de Zod) al shape que
56
+ * espera el runtime (JSON Schema). Sin `schema` queda sin parámetros.
57
+ */
58
+ function toRuntimeTool(def: ToolDefinition): Tool {
59
+ let properties: Record<string, ToolParameter> = {};
60
+ let required: string[] = [];
61
+
62
+ if (def.schema) {
63
+ const json = z.toJSONSchema(def.schema) as {
64
+ properties?: Record<string, ToolParameter>;
65
+ required?: string[];
66
+ };
67
+ properties = json.properties ?? {};
68
+ required = json.required ?? [];
69
+ }
70
+
71
+ return {
72
+ name: def.name,
73
+ description: def.description,
74
+ parameters: { type: "object", properties, required },
75
+ execute: async (params, config) => def.execute(params, config),
76
+ };
77
+ }
78
+
32
79
  export async function createAgent(config: AgentConfig): Promise<Agent> {
33
- const { initializeDatabase } = await import("../storage/SQLiteStorage.ts");
34
- const { createAllTools } = await import("../tools/index.ts");
80
+ const { ensureHiveDb } = await import("../storage/bootstrap.ts");
81
+ const { col, toIndexable } = await import("../storage/hive.ts");
82
+ const { catalogModelKey } = await import("../storage/model-id.ts");
83
+ const { registerAppTool } = await import("../tools/index.ts");
35
84
  const { loadConfig } = await import("../config/loader.ts");
85
+ const { resolveUserId } = await import("../storage/onboarding.ts");
86
+ type AgentDoc = import("../storage/collections.ts").AgentDoc;
87
+ type ModelDoc = import("../storage/collections.ts").ModelDoc;
88
+ type ProviderDoc = import("../storage/collections.ts").ProviderDoc;
36
89
 
37
- await initializeDatabase();
90
+ // Abre HiveDB, crea los índices y siembra el catálogo de providers/modelos.
91
+ await ensureHiveDb();
38
92
 
39
93
  const coreConfig = await loadConfig();
40
94
 
41
- // Initialize browser automation (agent-browser) if enabled
95
+ // Browser automation (agent-browser) si está habilitado.
42
96
  try {
43
97
  const { initializeBrowserService } = await import("../tools/web/browser-service.ts");
44
98
  const browserService = initializeBrowserService(coreConfig);
@@ -47,16 +101,114 @@ export async function createAgent(config: AgentConfig): Promise<Agent> {
47
101
  log.warn(`Browser service initialization skipped: ${(err as Error).message}`);
48
102
  }
49
103
 
50
- const allBuiltInTools = createAllTools(coreConfig);
104
+ // Las tools de la app necesitan dos cosas para ser usables, y hasta 0.1.5 no
105
+ // tenían ninguna:
106
+ // 1. un ejecutor en el registry que arma el context compiler, y
107
+ // 2. una fila en la colección `tools`, que es sobre la que corre la
108
+ // búsqueda por capacidad.
109
+ // Sin (2) la tool existe pero el modelo no la descubre nunca: el loadout
110
+ // inicial es mínimo a propósito y el resto se encuentra vía `search_knowledge`.
111
+ if (config.tools?.length) {
112
+ type ToolDoc = import("../storage/collections.ts").ToolDoc;
113
+ const toolsCol = await col<ToolDoc>("tools");
114
+ const ts = Date.now();
115
+
116
+ for (const tool of config.tools) {
117
+ registerAppTool(toRuntimeTool(tool));
118
+ await toolsCol.put(tool.name, {
119
+ id: tool.name,
120
+ name: tool.name,
121
+ description: tool.description,
122
+ category: tool.category ?? "app",
123
+ enabled: true,
124
+ active: true,
125
+ created_at: ts,
126
+ updated_at: ts,
127
+ });
128
+ }
129
+ }
130
+
131
+ // ─── Provider y modelo ────────────────────────────────────────────────────
132
+ // El loop los lee de la fila del agente, así que hay que dejarlos escritos.
133
+ const providerId = config.provider ?? "";
134
+ let modelKey = "";
135
+
136
+ if (config.model) {
137
+ if (!providerId) {
138
+ throw new Error(
139
+ `createAgent({ model: "${config.model}" }) necesita también \`provider\`: el mismo modelo lo sirven varios providers y la clave del catálogo depende de cuál.`
140
+ );
141
+ }
142
+ modelKey = catalogModelKey(providerId, config.model);
143
+
144
+ const modelsCol = await col<ModelDoc>("models");
145
+ const modelEntry = await modelsCol.get(modelKey);
146
+ if (!modelEntry) {
147
+ throw new Error(
148
+ `El modelo "${config.model}" no está en el catálogo de ${providerId}. `
149
+ + `Agregalo a SEED_DATA.models o elegí uno de los sembrados.`
150
+ );
151
+ }
152
+ // Activarlo, o `resolveProviderConfig` no encuentra base_url ni la key.
153
+ await modelsCol.put(
154
+ modelKey,
155
+ { ...modelEntry.doc, enabled: true, active: true },
156
+ { expectedVersion: modelEntry.version }
157
+ );
51
158
 
52
- const customTools = (config.tools ?? []).map(t => ({
53
- name: t.name,
54
- description: t.description,
55
- execute: t.execute,
56
- }));
159
+ const providersCol = await col<ProviderDoc>("providers");
160
+ const providerEntry = await providersCol.get(providerId);
161
+ if (providerEntry) {
162
+ await providersCol.put(
163
+ providerId,
164
+ { ...providerEntry.doc, enabled: true, active: true },
165
+ { expectedVersion: providerEntry.version }
166
+ );
167
+ }
168
+ }
57
169
 
58
- const mergedTools = [...allBuiltInTools, ...customTools];
170
+ // ─── Fila del agente ──────────────────────────────────────────────────────
171
+ const agentId = agentIdFrom(config.name);
172
+ const agentsCol = await col<AgentDoc>("agents");
173
+ const existing = await agentsCol.get(agentId);
174
+ const now = Date.now();
175
+ const userId = (await resolveUserId({})) || "default";
59
176
 
177
+ await agentsCol.put(
178
+ agentId,
179
+ {
180
+ ...(existing?.doc ?? {}),
181
+ id: agentId,
182
+ user_id: existing?.doc.user_id ?? userId,
183
+ name: config.name,
184
+ description: existing?.doc.description ?? null,
185
+ system_prompt: config.systemPrompt ?? existing?.doc.system_prompt ?? null,
186
+ tone: existing?.doc.tone ?? null,
187
+ role: existing?.doc.role ?? "coordinator",
188
+ status: "idle",
189
+ enabled: true,
190
+ provider_id: toIndexable(providerId || null),
191
+ model_id: toIndexable(modelKey || null),
192
+ tools_json: existing?.doc.tools_json ?? null,
193
+ skills_json: existing?.doc.skills_json ?? null,
194
+ parent_id: toIndexable(null),
195
+ max_iterations: config.maxIterations ?? existing?.doc.max_iterations ?? 25,
196
+ workspace: config.workspace ?? existing?.doc.workspace ?? null,
197
+ lastTraceAt: existing?.doc.lastTraceAt ?? null,
198
+ created_at: existing?.doc.created_at ?? now,
199
+ updated_at: now,
200
+ } as AgentDoc,
201
+ existing ? { expectedVersion: existing.version } : undefined
202
+ );
203
+
204
+ // ─── Índice de capacidades ────────────────────────────────────────────────
205
+ // Sin esto el agente queda limitado al loadout mínimo para siempre: el resto
206
+ // de las tools y skills se descubren con `search_knowledge`, que corre sobre
207
+ // el índice BM25, y ese índice no se llena solo. En hive lo hace
208
+ // `gateway/initializer.ts` al arrancar; por la vía del SDK nadie lo llamaba.
209
+ await syncCapabilityIndexes();
210
+
211
+ // ─── MCP ──────────────────────────────────────────────────────────────────
60
212
  let mcpManager = null;
61
213
  if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
62
214
  const { MCPClientManager } = await import("../mcp/index.ts");
@@ -79,54 +231,90 @@ export async function createAgent(config: AgentConfig): Promise<Agent> {
79
231
  await mcpManager.initialize();
80
232
  }
81
233
 
82
- const loop = await (async () => {
83
- const { buildAgentLoop } = await import("../agent/AgentRunner.ts");
84
- const agentLoop = buildAgentLoop({ mcpManager });
85
- return agentLoop;
86
- })();
234
+ const { runAgent } = await import("../agent/agent-loop.ts");
87
235
 
88
236
  return {
89
237
  name: config.name,
238
+ id: agentId,
90
239
  config,
91
240
  async *chat(message, opts) {
92
241
  const threadId = opts?.threadId ?? crypto.randomUUID();
93
- const iter = loop.stream(
94
- { messages: [{ role: "user", content: message }] },
95
- {
96
- configurable: {
97
- thread_id: threadId,
98
- channel: opts?.channel ?? "cli",
99
- system_prompt: config.systemPrompt,
100
- },
101
- }
102
- );
103
- for await (const chunk of iter) {
104
- if (chunk.agent?.messages) {
105
- for (const msg of chunk.agent.messages) {
106
- if (msg.content && typeof msg.content === "string") {
107
- yield { type: "text" as const, content: msg.content };
108
- }
109
- if (msg.tool_calls) {
110
- for (const tc of msg.tool_calls) {
111
- yield { type: "tool_call" as const, name: tc.function?.name ?? tc.name ?? "unknown", args: typeof tc.function?.arguments === "string" ? JSON.parse(tc.function.arguments) : tc.function?.arguments ?? {} };
112
- }
113
- }
242
+ let response = "";
243
+
244
+ for await (const chunk of runAgent({
245
+ agentId,
246
+ userMessage: message,
247
+ threadId,
248
+ channel: opts?.channel ?? "cli",
249
+ mcpManager,
250
+ userId,
251
+ })) {
252
+ for (const msg of chunk.agent?.messages ?? []) {
253
+ if (typeof msg.content === "string" && msg.content) {
254
+ response = msg.content;
255
+ yield { type: "text" as const, content: msg.content };
114
256
  }
115
- }
116
- if (chunk.tools?.messages) {
117
- for (const msg of chunk.tools.messages) {
118
- yield { type: "tool_result" as const, name: msg.name ?? msg.tool_name ?? "unknown", result: msg.content };
257
+ for (const tc of msg.tool_calls ?? []) {
258
+ const raw = tc.function?.arguments;
259
+ yield {
260
+ type: "tool_call" as const,
261
+ name: tc.function?.name ?? "unknown",
262
+ args: typeof raw === "string" ? safeParse(raw) : (raw ?? {}),
263
+ };
119
264
  }
120
265
  }
266
+ for (const msg of chunk.tools?.messages ?? []) {
267
+ yield { type: "tool_result" as const, name: msg.name ?? "unknown", result: msg.content };
268
+ }
121
269
  }
122
- yield { type: "done" as const, response: "" };
270
+
271
+ yield { type: "done" as const, response };
123
272
  },
124
273
  async run(task, opts) {
274
+ // El loop emite el texto acumulado del turno, no deltas: quedarse con el
275
+ // último evento evita duplicar la respuesta al concatenar.
125
276
  let response = "";
126
277
  for await (const event of this.chat(task, opts)) {
127
- if (event.type === "text") response += event.content;
278
+ if (event.type === "text") response = event.content;
279
+ if (event.type === "done" && event.response) response = event.response;
128
280
  }
129
281
  return response;
130
282
  },
131
283
  };
132
284
  }
285
+
286
+ function safeParse(raw: string): Record<string, unknown> {
287
+ try {
288
+ return JSON.parse(raw);
289
+ } catch {
290
+ return {};
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Llena el índice BM25 de capacidades (tools, skills, reglas de playbook,
296
+ * agentes del catálogo) sobre el que corre `search_knowledge`.
297
+ *
298
+ * Es el equivalente de lo que hace `gateway/initializer.ts` en hive al arrancar.
299
+ * Los errores no se propagan: sin índice el agente sigue funcionando con el
300
+ * loadout mínimo, y romper `createAgent` por una reindexación sería peor.
301
+ */
302
+ export async function syncCapabilityIndexes(): Promise<void> {
303
+ const { syncToolsToIndex, syncSkillsToIndex, syncPlaybookToIndex } = await import(
304
+ "../agent/context-compiler.ts"
305
+ );
306
+ const { syncCatalogAgentsToIndex } = await import("../agent/catalog-selector.ts");
307
+
308
+ const results = await Promise.allSettled([
309
+ syncToolsToIndex(),
310
+ syncSkillsToIndex(),
311
+ syncPlaybookToIndex(),
312
+ syncCatalogAgentsToIndex(),
313
+ ]);
314
+
315
+ for (const result of results) {
316
+ if (result.status === "rejected") {
317
+ log.warn(`Capability index sync failed: ${result.reason}`);
318
+ }
319
+ }
320
+ }