@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,13 +1,13 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
3
  import {
4
- createAgent,
5
4
  startGateway,
6
- initializeDatabase,
5
+ ensureHiveDb,
7
6
  ChannelManager,
8
- logger,
9
7
  loadConfig,
8
+ logger,
10
9
  } from "@johpaz/hive-sdk";
10
+ import { coordinatorAgent } from "./agents/coordinator.ts";
11
11
  import config from "../hive.config.ts";
12
12
 
13
13
  const log = logger.child("app");
@@ -15,29 +15,22 @@ const log = logger.child("app");
15
15
  async function main() {
16
16
  log.info(`Starting {{APP_NAME}}...`);
17
17
 
18
- // Initialize database
19
- await initializeDatabase();
20
-
21
- // Create the main agent
22
- const agent = await createAgent({
23
- name: "coordinator",
24
- provider: "openai",
25
- model: "gpt-4o-mini",
26
- systemPrompt:
27
- "You are a helpful AI assistant running in a Hive harness. You can use tools, manage tasks, and communicate across channels.",
28
- });
18
+ // Abre HiveDB, crea los índices y siembra el catálogo de providers y modelos.
19
+ // Es idempotente: correrlo en cada arranque es cómo se actualiza el catálogo.
20
+ await ensureHiveDb();
29
21
 
30
- log.info(`Agent ready: ${agent.name}`);
22
+ log.info(`Agent ready: ${coordinatorAgent.name}`);
31
23
 
32
- // Initialize channels
33
- const channelManager = new ChannelManager();
34
- // TODO: configure channels from hive.config.ts
24
+ // Initialize channels. `loadConfig()` mezcla los defaults del SDK con
25
+ // hive.config.ts y el entorno; `config` sólo tiene lo que declaraste vos.
26
+ const channelManager = new ChannelManager(await loadConfig());
27
+ await channelManager.initialize();
35
28
 
36
29
  // Start the gateway
37
30
  const gateway = await startGateway({
38
31
  host: config.gateway?.host,
39
32
  port: config.gateway?.port,
40
- agentId: "coordinator",
33
+ agentId: coordinatorAgent.id,
41
34
  });
42
35
 
43
36
  log.info(`{{APP_NAME}} is running at http://${gateway.hostname}:${gateway.port}`);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hive/core",
3
- "version": "0.1.4",
4
- "description": "Hive Core — Agentes AI con Context Engineering, FTS5, ACE, Swarm",
3
+ "version": "0.1.5",
4
+ "description": "Hive Core — runtime de agentes con context engineering, catálogo de modelos y HiveDB",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "main": "./src/index.ts",
@@ -10,14 +10,15 @@
10
10
  ".": "./src/index.ts",
11
11
  "./agent": "./src/agent/index.ts",
12
12
  "./agent/providers": "./src/agent/providers/index.ts",
13
- "./agent/selectors": "./src/agent/selectors/index.ts",
14
13
  "./tools": "./src/tools/index.ts",
15
14
  "./skills": "./src/skills/index.ts",
16
15
  "./storage": "./src/storage/index.ts",
17
16
  "./swarm": "./src/swarm/index.ts",
18
17
  "./swarm/strategies": "./src/swarm/strategies/index.ts",
19
18
  "./swarm/presets": "./src/swarm/presets/index.ts",
20
- "./ace": "./src/ace/index.ts",
19
+ "./scheduler": "./src/scheduler/index.ts",
20
+ "./workers": "./src/workers/index.ts",
21
+ "./events": "./src/events/event-bus.ts",
21
22
  "./ethics": "./src/ethics/index.ts",
22
23
  "./canvas": "./src/canvas/index.ts",
23
24
  "./config": "./src/config/index.ts",
@@ -0,0 +1,166 @@
1
+ /**
2
+ * acceptance-checks — deterministic acceptance checking, no LLM.
3
+ *
4
+ * Replaces the independent acceptance_verifier agent loop (which ran a full
5
+ * agent-loop, up to 3 times, just to re-derive a verdict the worker's own
6
+ * structured output already implies). This runs whatever deterministic
7
+ * checkTool each criterion declares, inspects any artifact_id found in the
8
+ * evidence, and applies cheap delivery gates — zero LLM calls.
9
+ *
10
+ * When nothing deterministic applies to a criterion, the result is
11
+ * "unchecked", not a failure: that's exactly the case the coordinator's
12
+ * closing turn (already paid for, via the delegation-group fan-in) is meant
13
+ * to judge using this checks result plus the raw evidence.
14
+ */
15
+
16
+ import { col } from "../storage/hive";
17
+ import type { AgentDoc } from "../storage/collections";
18
+ import type { AcceptanceCriterion } from "./run-store";
19
+ import { interpretCheckResult } from "./goal-runner";
20
+ import { inspectArtifact } from "../artifacts/store";
21
+ import { loadConfig } from "../config/loader";
22
+ import { logger } from "../utils/logger";
23
+
24
+ const log = logger.child("acceptance-checks");
25
+
26
+ export type CheckStatus = "passed" | "failed" | "unchecked";
27
+
28
+ export interface AcceptanceCheckResult {
29
+ criterion_id: string;
30
+ /** checkTool name, "artifact_inspect", "delivery_gate", or "none". */
31
+ check: string;
32
+ /** null when no deterministic check applied to this criterion. */
33
+ met: boolean | null;
34
+ detail: string;
35
+ }
36
+
37
+ export interface AcceptanceChecks {
38
+ status: CheckStatus;
39
+ results: AcceptanceCheckResult[];
40
+ summary: string;
41
+ }
42
+
43
+ export function sanitizeDiagnostic(value: string, limit = 1000): string {
44
+ return value
45
+ .replace(/(api[_-]?key|token|authorization|secret)(["'=:\s]+)[^\s,}"']+/gi, "$1$2[REDACTED]")
46
+ .slice(0, limit);
47
+ }
48
+
49
+ async function runCheckTool(criterion: AcceptanceCriterion, objective: string): Promise<AcceptanceCheckResult | null> {
50
+ if (!criterion.checkTool) return null;
51
+ try {
52
+ const { executeToolBatch } = await import("../tool-runtime");
53
+ const { createAllTools } = await import("../tools/index");
54
+ const allTools = createAllTools(loadConfig());
55
+ const toolDef = allTools.find((t) => t.name === criterion.checkTool);
56
+ if (!toolDef) {
57
+ log.warn(`[acceptance-checks] Check tool "${criterion.checkTool}" not found in the tool registry`);
58
+ return { criterion_id: criterion.id, check: criterion.checkTool, met: null, detail: "Check tool not found in registry" };
59
+ }
60
+ const toolResults = await executeToolBatch({
61
+ toolCalls: [{
62
+ id: `check-${criterion.id}`,
63
+ function: { name: criterion.checkTool, arguments: JSON.stringify({ goal: criterion.description || objective }) },
64
+ }],
65
+ allTools,
66
+ toolConfig: {},
67
+ });
68
+ const result = toolResults[0];
69
+ if (!result?.ok) {
70
+ return { criterion_id: criterion.id, check: criterion.checkTool, met: false, detail: `Check tool failed: ${result?.error?.message ?? "unknown"}` };
71
+ }
72
+ const interpreted = interpretCheckResult(result.result);
73
+ return { criterion_id: criterion.id, check: criterion.checkTool, met: interpreted.met, detail: interpreted.reason };
74
+ } catch (err) {
75
+ log.warn(`[acceptance-checks] Check tool "${criterion.checkTool}" errored: ${(err as Error).message}`);
76
+ return { criterion_id: criterion.id, check: criterion.checkTool, met: false, detail: `Check tool error: ${(err as Error).message}` };
77
+ }
78
+ }
79
+
80
+ async function artifactChecks(evidence: string[]): Promise<AcceptanceCheckResult[]> {
81
+ const artifactIds = new Set<string>();
82
+ for (const item of evidence) {
83
+ for (const match of String(item).matchAll(/artifact_id:\s*["']?([0-9a-f-]{36})["']?/gi)) {
84
+ if (match[1]) artifactIds.add(match[1]);
85
+ }
86
+ }
87
+ const results: AcceptanceCheckResult[] = [];
88
+ for (const artifactId of artifactIds) {
89
+ const inspection = await inspectArtifact(artifactId);
90
+ const ok = inspection.ok === true;
91
+ results.push({
92
+ criterion_id: "artifact",
93
+ check: "artifact_inspect",
94
+ met: ok,
95
+ detail: ok ? `Artifact ${artifactId} verified` : `Artifact ${artifactId} inspection failed: ${JSON.stringify(inspection)}`,
96
+ });
97
+ }
98
+ return results;
99
+ }
100
+
101
+ /** Matches the worker's own documented output contract (agent-catalog.ts buildSystemPrompt): "status: completed|needs_input|partial|failed". */
102
+ function selfDeclaredFailure(delivery: string): AcceptanceCheckResult | null {
103
+ const match = delivery.match(/(?:^|\n)\s*-?\s*status\s*:\s*(completed|needs_input|partial|failed)/i);
104
+ if (!match || match[1].toLowerCase() !== "failed") return null;
105
+ return { criterion_id: "delivery", check: "delivery_gate", met: false, detail: "El worker declaró status: failed" };
106
+ }
107
+
108
+ export async function runAcceptanceChecks(input: {
109
+ objective: string;
110
+ acceptance?: AcceptanceCriterion[] | null;
111
+ delivery: string;
112
+ evidence?: string[];
113
+ }): Promise<AcceptanceChecks> {
114
+ const criteria = input.acceptance?.length ? input.acceptance : [];
115
+ const evidence = input.evidence ?? [];
116
+ const results: AcceptanceCheckResult[] = [];
117
+
118
+ if (!input.delivery || !input.delivery.trim()) {
119
+ results.push({ criterion_id: "delivery", check: "delivery_gate", met: false, detail: "Empty delivery" });
120
+ } else {
121
+ const declared = selfDeclaredFailure(input.delivery);
122
+ if (declared) results.push(declared);
123
+ }
124
+
125
+ for (const criterion of criteria) {
126
+ const checked = await runCheckTool(criterion, input.objective);
127
+ if (checked) results.push(checked);
128
+ }
129
+
130
+ results.push(...(await artifactChecks(evidence)));
131
+
132
+ const anyFailed = results.some((r) => r.met === false);
133
+ const anyPassed = results.some((r) => r.met === true);
134
+ const status: CheckStatus = anyFailed ? "failed" : anyPassed ? "passed" : "unchecked";
135
+
136
+ const summary = results.length
137
+ ? results.map((r) => `${r.met === true ? "✅" : r.met === false ? "❌" : "◌"} ${r.check}: ${sanitizeDiagnostic(r.detail, 300)}`).join("\n")
138
+ : "Sin checks determinísticos aplicables — requiere juicio del coordinador.";
139
+
140
+ return { status, results, summary };
141
+ }
142
+
143
+ /** ACE counters live directly on the executor's AgentDoc row. */
144
+ export async function recordAgentOutcome(
145
+ agentId: string | null | undefined,
146
+ outcome: "helpful" | "harmful",
147
+ ): Promise<void> {
148
+ if (!agentId) return;
149
+ const agents = await col<AgentDoc>("agents");
150
+ for (let attempt = 0; attempt < 3; attempt++) {
151
+ const current = await agents.get(agentId);
152
+ if (!current) return;
153
+ try {
154
+ await agents.put(agentId, {
155
+ ...current.doc,
156
+ helpful_count: (current.doc.helpful_count ?? 0) + (outcome === "helpful" ? 1 : 0),
157
+ harmful_count: (current.doc.harmful_count ?? 0) + (outcome === "harmful" ? 1 : 0),
158
+ updated_at: Date.now(),
159
+ }, { expectedVersion: current.version });
160
+ return;
161
+ } catch {
162
+ // Retry OCC conflicts; another outcome may have updated the same agent concurrently.
163
+ }
164
+ }
165
+ log.warn(`[acceptance-checks] Could not update ACE counters for ${agentId}`);
166
+ }
@@ -0,0 +1,348 @@
1
+ import type {
2
+ AgentDoc,
3
+ AgentAcceptanceCriterion,
4
+ AgentModelOverride,
5
+ AgentWorkspaceScope,
6
+ } from "../storage/collections";
7
+ import { col, toIndexable, fromIndexable } from "../storage/hive";
8
+ import { expandToolAllowlist } from "./delegation-runtime";
9
+
10
+ interface CatalogPersona {
11
+ id: string;
12
+ name: string;
13
+ description: string;
14
+ role: string;
15
+ receives: string;
16
+ workflow: string[];
17
+ prohibitions: string[];
18
+ quality: string;
19
+ routingExamples: string[];
20
+ routingExclusions?: string[];
21
+ tools: string[];
22
+ skills: string[];
23
+ workspaceScope: AgentWorkspaceScope;
24
+ acceptance: AgentAcceptanceCriterion[];
25
+ modelOverride?: AgentModelOverride | null;
26
+ }
27
+
28
+ function buildSystemPrompt(s: CatalogPersona): string {
29
+ return `# ROL
30
+ ${s.role}
31
+
32
+ # QUÉ RECIBES
33
+ ${s.receives}
34
+
35
+ # CÓMO TRABAJAS
36
+ ${s.workflow.map((step, index) => `${index + 1}. ${step}`).join("\n")}
37
+
38
+ # QUÉ NO HACES
39
+ ${s.prohibitions.map((rule) => `- ${rule}`).join("\n")}
40
+ - No hablás con el usuario, no pedís confirmaciones directas y no delegás a otros agentes.
41
+ - No ampliás el alcance ni usás tools fuera del loadout autorizado.
42
+ - No declarás éxito sin evidencia comprobable para cada criterio.
43
+
44
+ # FORMATO DE ENTREGA
45
+ Devolvé exclusivamente un objeto estructurado con:
46
+ - status: completed | needs_input | partial | failed
47
+ - what_was_done: resumen factual
48
+ - artifacts: paths, URLs, surface IDs, job IDs u otros artefactos
49
+ - evidence: evidencia vinculada a cada criterio de aceptación
50
+ - risks: límites, efectos secundarios o incertidumbres
51
+ - question: solo cuando status=needs_input; dirigida al agente principal, nunca al usuario
52
+
53
+ # CRITERIO DE CALIDAD
54
+ ${s.quality}`;
55
+ }
56
+
57
+ const COMMON_PROHIBITIONS = [
58
+ "No inventás resultados, archivos, status HTTP, capturas ni efectos externos.",
59
+ "No exponés credenciales, tokens, cookies, secretos ni datos privados en la entrega.",
60
+ ];
61
+
62
+ const CODE_MODEL: AgentModelOverride = {
63
+ required_capabilities: ["code", "function_calling"],
64
+ fallback: "general",
65
+ };
66
+
67
+ const CATALOG_PERSONAS: CatalogPersona[] = [
68
+ {
69
+ id: "web_researcher",
70
+ name: "Investigador web",
71
+ description: "Investiga preguntas actuales en fuentes web y entrega conclusiones verificables con referencias.",
72
+ role: "Tu dominio es investigación web, contraste de fuentes y síntesis basada en evidencia.",
73
+ receives: "Una pregunta acotada, contexto relevante, restricciones de actualidad y criterios de aceptación.",
74
+ workflow: [
75
+ "Convertí la pregunta en consultas concretas y buscá fuentes primarias o autorizadas.",
76
+ "Leé las fuentes relevantes y separá hechos, inferencias y datos no confirmados.",
77
+ "Contrastá afirmaciones sensibles o discutidas con más de una fuente.",
78
+ "Entregá una síntesis concisa con referencias y fechas cuando sean relevantes.",
79
+ ],
80
+ prohibitions: [...COMMON_PROHIBITIONS, "No automatizás formularios ni realizás acciones en sitios."],
81
+ quality: "Cada afirmación material debe poder rastrearse a una fuente accesible; los desacuerdos se presentan explícitamente.",
82
+ routingExamples: ["investigar una noticia", "comparar información actual", "buscar fuentes para un informe"],
83
+ tools: ["web_search", "web_fetch"],
84
+ skills: ["web_research"],
85
+ workspaceScope: { kind: "none" },
86
+ acceptance: [{ id: "sources", description: "Las conclusiones incluyen fuentes accesibles y evidencia trazable." }],
87
+ },
88
+ {
89
+ id: "browser_operator",
90
+ name: "Operador de navegador",
91
+ description: "Navega sitios, completa formularios y verifica visualmente el estado final de una operación web.",
92
+ role: "Tu dominio es navegación y automatización web renderizada.",
93
+ receives: "Una acción web autorizada, URL inicial, datos permitidos, estado final esperado y límites de seguridad.",
94
+ workflow: [
95
+ "Abrí el sitio y verificá que corresponda al objetivo.",
96
+ "Inspeccioná el estado antes de interactuar y usá selectores estables.",
97
+ "Ejecutá solamente los clicks, escritura y esperas necesarios.",
98
+ "Verificá el estado final mediante extracción y captura de pantalla.",
99
+ ],
100
+ prohibitions: [...COMMON_PROHIBITIONS, "No confirmás compras, envíos, borrados o publicaciones si el principal no autorizó explícitamente ese efecto."],
101
+ quality: "La entrega incluye URL final, estado observado y evidencia visual o estructurada posterior a la acción.",
102
+ routingExamples: ["llenar un formulario", "iniciar sesión", "hacer clic y verificar una página"],
103
+ tools: ["browser_*", "web_fetch"],
104
+ skills: ["browser_automate", "browser_scrape"],
105
+ workspaceScope: { kind: "none" },
106
+ acceptance: [{ id: "final_state", description: "El estado final del sitio fue comprobado visual o estructuralmente." }],
107
+ },
108
+ {
109
+ id: "workspace_file_operator",
110
+ name: "Operador de archivos",
111
+ description: "Crea, lee, edita, organiza y elimina archivos o carpetas dentro del workspace autorizado.",
112
+ role: "Tu dominio es operaciones seguras sobre archivos y carpetas del workspace.",
113
+ receives: "Paths relativos o autorizados, contenido solicitado, operación exacta y estado final esperado.",
114
+ workflow: [
115
+ "Resolvé todos los paths contra el workspace y comprobá su estado inicial.",
116
+ "Aplicá la mínima operación necesaria sin tocar paths ajenos.",
117
+ "Volvé a leer o listar el resultado para comprobarlo.",
118
+ "Reportá paths exactos, cambios y evidencia de readback.",
119
+ ],
120
+ prohibitions: [...COMMON_PROHIBITIONS, "No ejecutás comandos shell ni modificás repositorios fuera de la operación de archivos pedida."],
121
+ quality: "Todos los paths permanecen dentro del workspace y su contenido o ausencia final se comprueba después de la operación.",
122
+ routingExamples: ["crear una carpeta", "editar un archivo de texto", "organizar archivos"],
123
+ tools: ["fs_*"],
124
+ skills: ["workspace_file_operator", "file_manager"],
125
+ workspaceScope: { kind: "workspace", read_globs: ["**/*"], write_globs: ["**/*"] },
126
+ acceptance: [{ id: "readback", description: "El estado final de cada path se verificó mediante lectura, listado o existencia." }],
127
+ },
128
+ {
129
+ id: "software_engineer",
130
+ name: "Ingeniero de software",
131
+ description: "Implementa, depura y prueba software dentro de un repositorio o workspace existente.",
132
+ role: "Tu dominio es ingeniería de software, diagnóstico, cambios mínimos y validación automatizada.",
133
+ receives: "Objetivo técnico, repositorio, restricciones, comportamiento esperado y comandos de validación disponibles.",
134
+ workflow: [
135
+ "Inspeccioná el repositorio, convenciones y estado antes de editar.",
136
+ "Determiná la causa o el diseño mínimo y modificá solo archivos pertinentes.",
137
+ "Ejecutá checks, tests o builds proporcionales al riesgo.",
138
+ "Entregá archivos cambiados, evidencia de validación y riesgos restantes.",
139
+ ],
140
+ prohibitions: [...COMMON_PROHIBITIONS, "No sobrescribís cambios ajenos, no publicás y no delegás a subagentes CLI."],
141
+ quality: "El cambio satisface el comportamiento pedido, preserva compatibilidad y pasa las validaciones relevantes.",
142
+ routingExamples: ["implementar una función", "arreglar un bug", "ejecutar tests de un proyecto"],
143
+ tools: ["fs_*", "cli_exec"],
144
+ skills: ["software_engineering", "cli_safe_exec", "cli_pipeline"],
145
+ workspaceScope: { kind: "workspace", read_globs: ["**/*"], write_globs: ["**/*"] },
146
+ acceptance: [{ id: "checks", description: "Las pruebas o checks relevantes pasan y los cambios están acotados al objetivo." }],
147
+ modelOverride: CODE_MODEL,
148
+ },
149
+ {
150
+ id: "office_document_agent",
151
+ name: "Operador de Office",
152
+ description: "Lee y genera documentos PDF, Word, Excel y PowerPoint dentro del workspace.",
153
+ role: "Tu dominio es lectura y generación de archivos Office estructurados.",
154
+ receives: "Archivo de entrada o especificación del documento, formato final, contenido y path autorizado.",
155
+ workflow: [
156
+ "Inspeccioná entradas y confirmá el formato solicitado.",
157
+ "Generá o extraé contenido preservando estructura y datos.",
158
+ "Comprobá que el archivo existe, no está vacío y puede reabrirse.",
159
+ "Entregá el path final, resumen de contenido y prueba de reapertura.",
160
+ ],
161
+ prohibitions: [...COMMON_PROHIBITIONS, "No editás formatos binarios con tools genéricas de filesystem."],
162
+ quality: "El artefacto debe abrir sin error con la tool lectora correspondiente y contener la estructura solicitada.",
163
+ routingExamples: ["crear un Excel", "leer un PDF", "generar una presentación"],
164
+ tools: ["office_*", "fs_exists"],
165
+ skills: ["office_document_manager"],
166
+ workspaceScope: { kind: "workspace", read_globs: ["**/*.pdf", "**/*.docx", "**/*.xlsx", "**/*.pptx"], write_globs: ["**/*.pdf", "**/*.docx", "**/*.xlsx", "**/*.pptx"] },
167
+ acceptance: [{ id: "opens", description: "El archivo generado existe, no está vacío y se reabre sin error." }],
168
+ },
169
+ {
170
+ id: "a2ui_builder",
171
+ name: "Constructor A2UI",
172
+ description: "Construye formularios, dashboards y flujos interactivos compatibles con A2UI v0.9.",
173
+ role: "Tu dominio es superficies A2UI v0.9, componentes planos y data binding.",
174
+ receives: "Sesión, surfaceId, flujo solicitado, datos, acciones permitidas y criterios visuales.",
175
+ workflow: [
176
+ "Diseñá una jerarquía pequeña con IDs únicos y un root explícito.",
177
+ "Creá la superficie antes de enviar componentes.",
178
+ "Enviá componentes válidos y después el data model enlazado.",
179
+ "Comprobá acknowledgements, IDs y paths; liberá la superficie al cancelar.",
180
+ ],
181
+ prohibitions: [...COMMON_PROHIBITIONS, "No interpretás acciones del usuario ni conversás; los eventos vuelven al principal."],
182
+ quality: "La superficie usa el catálogo v0.9, no tiene referencias rotas y sus bindings apuntan a paths válidos.",
183
+ routingExamples: ["crear formulario A2UI", "dashboard interactivo", "wizard de varios pasos"],
184
+ tools: ["a2ui_*"],
185
+ skills: ["a2ui_form", "a2ui_dashboard", "a2ui_interactive"],
186
+ workspaceScope: { kind: "resource", resource_types: ["a2ui_surface"] },
187
+ acceptance: [{ id: "surface", description: "La superficie fue creada con componentes y bindings válidos." }],
188
+ },
189
+ {
190
+ id: "schedule_automation_agent",
191
+ name: "Operador de cron",
192
+ description: "Crea y administra jobs programados de Hive: automatizaciones recurrentes o ejecuciones únicas mediante cron.*.",
193
+ role: "Tu dominio son los jobs técnicos programados de Hive, su recurrencia, ventanas temporales y zonas horarias.",
194
+ receives: "Una automatización que Hive debe ejecutar después, su horario o recurrencia, timezone, canal y comportamiento esperado.",
195
+ workflow: [
196
+ "Normalizá fecha, recurrencia y timezone sin cambiar la intención.",
197
+ "Creá o modificá únicamente el job solicitado.",
198
+ "Consultá el job persistido y su próxima ejecución.",
199
+ "Entregá ID, estado, timezone y next_run_at.",
200
+ ],
201
+ prohibitions: [
202
+ ...COMMON_PROHIBITIONS,
203
+ "No inventás una hora cuando la ambigüedad cambia materialmente el resultado.",
204
+ "No creás, consultás ni modificás eventos, citas, reuniones, asistentes o disponibilidad de un calendario externo.",
205
+ ],
206
+ quality: "La definición persistida representa la intención temporal y su próxima ejecución es comprobable.",
207
+ routingExamples: ["ejecutar una tarea cada hora", "programar un reporte semanal", "pausar un cron job"],
208
+ routingExclusions: [
209
+ "crear, consultar o modificar eventos de calendario",
210
+ "agendar citas o reuniones e invitar asistentes",
211
+ "consultar disponibilidad en Google Calendar u otro calendario externo",
212
+ ],
213
+ tools: ["cron.*"],
214
+ skills: ["cron_manager", "cron_reminder"],
215
+ workspaceScope: { kind: "resource", resource_types: ["cron_job"] },
216
+ acceptance: [{ id: "scheduled", description: "El job persistido tiene estado, timezone y próxima ejecución correctos." }],
217
+ },
218
+ {
219
+ id: "api_operator",
220
+ name: "Operador de APIs",
221
+ description: "Ejecuta y verifica operaciones contra APIs REST expresamente autorizadas.",
222
+ role: "Tu dominio es requests REST, contratos HTTP y validación de respuestas.",
223
+ receives: "Endpoint autorizado, método, headers permitidos, payload, status esperado y esquema relevante.",
224
+ workflow: [
225
+ "Validá método, host, payload y alcance antes del request.",
226
+ "Ejecutá una sola operación idempotente o explícitamente autorizada.",
227
+ "Comprobá status, headers y forma de la respuesta.",
228
+ "Entregá evidencia saneada sin secretos.",
229
+ ],
230
+ prohibitions: [...COMMON_PROHIBITIONS, "No repetís mutaciones automáticamente ni cambiás método, host o payload para forzar éxito."],
231
+ quality: "El status y contrato observados coinciden con los criterios y la evidencia no contiene credenciales.",
232
+ routingExamples: ["hacer un GET a una API", "enviar un POST autorizado", "validar respuesta REST"],
233
+ tools: ["api_request"],
234
+ skills: ["api_client"],
235
+ workspaceScope: { kind: "resource", resource_types: ["http_endpoint"] },
236
+ acceptance: [{ id: "http", description: "El status y el contrato de respuesta coinciden con lo esperado." }],
237
+ },
238
+ ];
239
+
240
+ /**
241
+ * Seeds the catalog personas directly as `agents` rows (role:"worker",
242
+ * source:"catalog") — no separate template collection, no materialization
243
+ * step. `tools_json` is pre-expanded here for display purposes only; the
244
+ * authoritative expansion happens again at delegation time against the live
245
+ * tool registry (task_delegate re-expands `tool_allowlist_json` fresh, so a
246
+ * tool registered after this seed ran is still picked up without needing a
247
+ * reseed).
248
+ */
249
+ export function createSeedCatalogAgents(now = Date.now()): AgentDoc[] {
250
+ return CATALOG_PERSONAS.map((s) => ({
251
+ id: s.id,
252
+ user_id: "",
253
+ name: s.name,
254
+ description: s.description,
255
+ system_prompt: buildSystemPrompt(s),
256
+ tone: "professional",
257
+ role: "worker",
258
+ status: "idle",
259
+ enabled: true,
260
+ provider_id: toIndexable(null),
261
+ model_id: toIndexable(null),
262
+ tools_json: JSON.stringify(expandToolAllowlist(s.tools)),
263
+ skills_json: JSON.stringify(s.skills),
264
+ active_mcp_json: null,
265
+ parent_id: toIndexable(null),
266
+ max_iterations: 20,
267
+ workspace: null,
268
+ lastTraceAt: null,
269
+ created_at: now,
270
+ updated_at: now,
271
+ source: "catalog",
272
+ routing_examples_json: JSON.stringify(s.routingExamples),
273
+ routing_exclusions_json: JSON.stringify(s.routingExclusions ?? []),
274
+ tool_allowlist_json: JSON.stringify(s.tools),
275
+ mcp_server_ids_json: null,
276
+ workspace_scope_json: JSON.stringify(s.workspaceScope),
277
+ model_override_json: s.modelOverride ? JSON.stringify(s.modelOverride) : null,
278
+ default_acceptance_json: JSON.stringify(s.acceptance),
279
+ helpful_count: 0,
280
+ harmful_count: 0,
281
+ seed_version: 1,
282
+ }));
283
+ }
284
+
285
+ export const CATALOG_AGENT_IDS = CATALOG_PERSONAS.map((s) => s.id);
286
+
287
+ /**
288
+ * Writes the coordinator's provider/model onto every other agent row, so the
289
+ * whole hive is explicitly configured instead of relying on the runtime
290
+ * parent-inheritance fallback in `resolveAgentModel()` (which leaves the rows
291
+ * blank in the UI and unusable for scheduler/cron paths that read them).
292
+ *
293
+ * `overwrite: true` — onboarding/setup: the user just (re)chose the main
294
+ * model, so every worker follows it.
295
+ * `overwrite: false` — every boot: only fills rows with an incomplete pair,
296
+ * which is what picks up personas added by an upgrade and installs configured
297
+ * before this existed, without touching a per-agent model set from the UI.
298
+ */
299
+ export async function applyCoordinatorModel(opts: {
300
+ userId: string;
301
+ providerId: string;
302
+ modelId: string;
303
+ overwrite: boolean;
304
+ }): Promise<number> {
305
+ const agentsCol = await col<AgentDoc>("agents");
306
+ let updated = 0;
307
+ for (const entry of await agentsCol.scan({})) {
308
+ if (entry.doc.role === "coordinator") continue;
309
+ const hasPair = fromIndexable(entry.doc.provider_id) && fromIndexable(entry.doc.model_id);
310
+ if (hasPair && !opts.overwrite) continue;
311
+ const nextUserId = entry.doc.user_id || opts.userId;
312
+ if (
313
+ entry.doc.provider_id === toIndexable(opts.providerId) &&
314
+ entry.doc.model_id === toIndexable(opts.modelId) &&
315
+ entry.doc.user_id === nextUserId
316
+ ) continue;
317
+ await agentsCol.put(entry.id, {
318
+ ...entry.doc,
319
+ user_id: nextUserId,
320
+ provider_id: toIndexable(opts.providerId),
321
+ model_id: toIndexable(opts.modelId),
322
+ updated_at: Date.now(),
323
+ }, { expectedVersion: entry.version });
324
+ updated++;
325
+ }
326
+ return updated;
327
+ }
328
+
329
+ /**
330
+ * Boot-time half of the above: reads the configured coordinator from the DB
331
+ * and fills in any agent left without a provider/model pair. No-op on a fresh
332
+ * install (no coordinator yet) — setup does the seeding there.
333
+ */
334
+ export async function ensureAgentsConfigured(): Promise<number> {
335
+ const agentsCol = await col<AgentDoc>("agents");
336
+ const coordinators = (await agentsCol.findBy("role", "coordinator")).map((e) => e.doc);
337
+ const coordinator = coordinators
338
+ .filter((doc) => fromIndexable(doc.provider_id) && fromIndexable(doc.model_id))
339
+ .sort((a, b) => a.created_at - b.created_at)[0];
340
+ if (!coordinator) return 0;
341
+
342
+ return applyCoordinatorModel({
343
+ userId: coordinator.user_id,
344
+ providerId: fromIndexable(coordinator.provider_id)!,
345
+ modelId: fromIndexable(coordinator.model_id)!,
346
+ overwrite: false,
347
+ });
348
+ }