@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,16 +1,21 @@
1
1
  /**
2
- * Agents Tools - 14 tools
3
- *
2
+ * Agents Tools - 15 tools
3
+ *
4
4
  * @category agents
5
5
  */
6
6
 
7
7
  import type { Tool } from "../types.ts";
8
- import { getDb } from "../../storage/SQLiteStorage.ts";
9
- import { getHiveDB } from "../../storage/HiveDBStorage.ts";
10
- import type { HiveModelDoc, HiveProviderDoc, HiveAgentDoc } from "../../storage/hiveSeed.ts";
8
+ import { col, toIndexable, fromIndexable, BROADCAST } from "../../storage/hive.ts";
9
+ import type { MemoryDoc, AgentDoc, ProviderDoc, ModelDoc, McpServerDoc, TaskDoc, AgentBusMessageDoc, AgentAcceptanceCriterion } from "../../storage/collections.ts";
10
+ import type { AcceptanceCriterion } from "../../agent/run-store.ts";
11
+ import type { PreparedDelegation } from "../../agent/delegation-runtime.ts";
11
12
  import { logger } from "../../utils/logger.ts";
12
- import { agentBus } from "../../swarm/AgentBus.ts";
13
- import { emitCanvas } from "../../canvas/emitter.ts";
13
+ import { agentBus } from "../../events/agent-bus.ts";
14
+ import {
15
+ emitDelegationStarted,
16
+ emitDelegationFinished,
17
+ emitWorkEvent,
18
+ } from "../../canvas/emitter.ts";
14
19
 
15
20
  const log = logger.child("agents");
16
21
 
@@ -28,15 +33,20 @@ export const memoryWriteTool: Tool = {
28
33
  required: ["title", "content"],
29
34
  },
30
35
  execute: async (params: Record<string, unknown>) => {
31
- const db = await getHiveDB();
32
36
  const title = params.title as string;
33
37
  const content = params.content as string;
34
38
 
35
39
  try {
36
- const notesCol = db.collection<{ title: string; content: string; createdAt: number; updatedAt: number }>("notes");
37
- const id = crypto.randomUUID();
40
+ const memoryCol = await col<MemoryDoc>("memory");
41
+ const existing = await memoryCol.get(title);
38
42
  const now = Date.now();
39
- await notesCol.put(id, { title, content, createdAt: now, updatedAt: now });
43
+ await memoryCol.put(title, {
44
+ id: title,
45
+ title,
46
+ content,
47
+ created_at: existing?.doc.created_at ?? now,
48
+ updated_at: now,
49
+ }, existing ? { expectedVersion: existing.version } : { expectedVersion: 0 });
40
50
 
41
51
  return { ok: true, title, message: "Memory saved." };
42
52
  } catch (error) {
@@ -58,24 +68,22 @@ export const memoryReadTool: Tool = {
58
68
  required: ["title"],
59
69
  },
60
70
  execute: async (params: Record<string, unknown>) => {
61
- const db = await getHiveDB();
62
71
  const title = params.title as string;
63
72
 
64
73
  try {
65
- const notesCol = db.collection<{ title: string; content: string; createdAt: number; updatedAt: number }>("notes");
66
- const entries = await notesCol.scan();
67
- const note = entries.find(e => e.doc.title === title)?.doc;
74
+ const memoryCol = await col<MemoryDoc>("memory");
75
+ const entry = await memoryCol.get(title);
68
76
 
69
- if (!note) {
77
+ if (!entry) {
70
78
  return { ok: false, error: `Memory not found: ${title}` };
71
79
  }
72
80
 
73
81
  return {
74
82
  ok: true,
75
- title: note.title,
76
- content: note.content,
77
- createdAt: new Date(note.createdAt).toISOString(),
78
- updatedAt: new Date(note.updatedAt).toISOString(),
83
+ title: entry.doc.title,
84
+ content: entry.doc.content,
85
+ createdAt: new Date(entry.doc.created_at).toISOString(),
86
+ updatedAt: new Date(entry.doc.updated_at).toISOString(),
79
87
  };
80
88
  } catch (error) {
81
89
  return { ok: false, error: `Failed to read memory: ${(error as Error).message}` };
@@ -93,19 +101,16 @@ export const memoryListTool: Tool = {
93
101
  properties: {},
94
102
  },
95
103
  execute: async () => {
96
- const db = await getHiveDB();
97
-
98
104
  try {
99
- const notesCol = db.collection<{ title: string; content: string; createdAt: number; updatedAt: number }>("notes");
100
- const entries = await notesCol.scan();
101
- const notes = entries
105
+ const memoryCol = await col<MemoryDoc>("memory");
106
+ const notes = (await memoryCol.scan({}))
102
107
  .map(e => e.doc)
103
- .sort((a, b) => b.updatedAt - a.updatedAt);
108
+ .sort((a, b) => b.updated_at - a.updated_at);
104
109
 
105
110
  return {
106
111
  ok: true,
107
112
  count: notes.length,
108
- entries: notes.map((n) => ({ title: n.title, createdAt: new Date(n.createdAt).toISOString() })),
113
+ entries: notes.map((n) => ({ title: n.title, createdAt: new Date(n.created_at).toISOString() })),
109
114
  };
110
115
  } catch (error) {
111
116
  return { ok: false, error: `Failed to list memories: ${(error as Error).message}` };
@@ -126,15 +131,14 @@ export const memorySearchTool: Tool = {
126
131
  required: ["query"],
127
132
  },
128
133
  execute: async (params: Record<string, unknown>) => {
129
- const db = await getHiveDB();
130
- const query = (params.query as string).toLowerCase();
134
+ const query = params.query as string;
135
+ const needle = query.toLowerCase();
131
136
 
132
137
  try {
133
- const notesCol = db.collection<{ title: string; content: string; createdAt: number; updatedAt: number }>("notes");
134
- const entries = await notesCol.scan();
135
- const notes = entries
138
+ const memoryCol = await col<MemoryDoc>("memory");
139
+ const notes = (await memoryCol.scan({}))
136
140
  .map(e => e.doc)
137
- .filter(n => n.title.toLowerCase().includes(query) || n.content.toLowerCase().includes(query));
141
+ .filter(n => n.content.toLowerCase().includes(needle) || n.title.toLowerCase().includes(needle));
138
142
 
139
143
  return {
140
144
  ok: true,
@@ -164,19 +168,18 @@ export const memoryDeleteTool: Tool = {
164
168
  required: ["title"],
165
169
  },
166
170
  execute: async (params: Record<string, unknown>) => {
167
- const db = await getHiveDB();
168
171
  const title = params.title as string;
169
172
 
170
173
  try {
171
- const notesCol = db.collection<{ title: string; content: string; createdAt: number; updatedAt: number }>("notes");
172
- const entries = await notesCol.scan();
173
- const target = entries.find(e => e.doc.title === title);
174
+ const memoryCol = await col<MemoryDoc>("memory");
175
+ const existing = await memoryCol.get(title);
174
176
 
175
- if (!target) {
177
+ if (!existing) {
176
178
  return { ok: false, error: `Memory not found: ${title}` };
177
179
  }
178
180
 
179
- await notesCol.delete(target.id);
181
+ await memoryCol.delete(title);
182
+
180
183
  return { ok: true, title, message: "Memory deleted." };
181
184
  } catch (error) {
182
185
  return { ok: false, error: `Failed to delete memory: ${(error as Error).message}` };
@@ -188,7 +191,7 @@ export const memoryDeleteTool: Tool = {
188
191
 
189
192
  export const agentCreateTool: Tool = {
190
193
  name: "agent_create",
191
- description: "Crear un nuevo agente worker especializado. Requiere consultar get_available_models primero para seleccionar provider/model óptimos. Sinónimos: crear agente, nuevo worker, nuevo trabajador",
194
+ description: "Crear un nuevo agente worker especializado. Requiere consultar get_available_models; para un especialista MCP confirmado por el usuario, acepta mcp_server_id. Sinónimos: crear agente, nuevo worker, nuevo trabajador",
192
195
  parameters: {
193
196
  type: "object",
194
197
  properties: {
@@ -198,100 +201,157 @@ export const agentCreateTool: Tool = {
198
201
  tools_json: { type: "array", description: "Lista de IDs de herramientas", items: { type: "string" } },
199
202
  providerId: { type: "string", description: "ID del provider (openai, anthropic, ollama, etc.) - Obtener de get_available_models" },
200
203
  modelId: { type: "string", description: "ID del modelo (gpt-4o, claude-sonnet, etc.) - Obtener de get_available_models" },
204
+ mcp_server_id: {
205
+ type: "string",
206
+ description: "Servidor MCP persistente para un especialista. Requiere confirmación previa del usuario y asigna todas las tools actuales y futuras de ese servidor.",
207
+ },
201
208
  tone: { type: "string", description: "Tono del agente (friendly, professional, direct, etc.)" },
202
209
  max_iterations: { type: "number", description: "Límite de iteraciones del agente (default: 10)" },
203
210
  },
204
211
  required: ["name", "providerId", "modelId"],
205
212
  },
206
213
  execute: async (params: Record<string, unknown>, config?: any) => {
207
- const db = await getHiveDB();
208
214
  const userId = config?.configurable?.user_id;
209
- const parentId = config?.configurable?.agent_id ?? undefined;
215
+ const parentId = config?.configurable?.agent_id ?? null;
210
216
  const name = params.name as string;
211
217
  const description = (params.description as string) ?? "";
212
218
  const systemPrompt = (params.system_prompt as string) ?? "";
213
- const toolsJson = params.tools_json ? JSON.stringify(params.tools_json) : undefined;
219
+ const toolsJson = params.tools_json ? JSON.stringify(params.tools_json) : null;
214
220
  const providerId = params.providerId as string;
215
221
  const modelId = params.modelId as string;
222
+ const mcpServerId = params.mcp_server_id as string | undefined;
216
223
  const tone = (params.tone as string) ?? "friendly";
217
224
  const maxIterations = (params.max_iterations as number) ?? 10;
218
- const parentWorkspace = config?.configurable?.workspace ?? undefined;
225
+ const parentWorkspace = config?.configurable?.workspace ?? null;
219
226
 
227
+ // Validar que providerId y modelId sean obligatorios
220
228
  if (!providerId || !modelId) {
221
- return {
222
- ok: false,
223
- error: "providerId y modelId son obligatorios. Usá get_available_models para consultar los modelos disponibles antes de crear el agente."
229
+ return {
230
+ ok: false,
231
+ error: "providerId y modelId son obligatorios. Usá get_available_models para consultar los modelos disponibles antes de crear el agente."
224
232
  };
225
233
  }
226
234
 
227
- const providersCol = db.collection<HiveProviderDoc>("providers");
228
- const modelsCol = db.collection<HiveModelDoc>("models");
229
-
230
- const [providerEntry, modelEntry] = await Promise.all([
231
- providersCol.get(providerId),
232
- modelsCol.get(modelId),
233
- ]);
235
+ // Validar que el provider existe y está activo
236
+ const providersCol = await col<ProviderDoc>("providers");
237
+ const providerEntry = await providersCol.get(providerId);
234
238
 
235
239
  if (!providerEntry) {
236
- return {
237
- ok: false,
238
- error: `Provider '${providerId}' no existe. Usá get_available_models para ver providers disponibles.`
240
+ return {
241
+ ok: false,
242
+ error: `Provider '${providerId}' no existe. Usá get_available_models para ver providers disponibles.`
239
243
  };
240
244
  }
245
+
241
246
  if (!providerEntry.doc.enabled || !providerEntry.doc.active) {
242
- return {
243
- ok: false,
244
- error: `Provider '${providerId}' no está activo. Usá get_available_models para ver providers activos.`
247
+ return {
248
+ ok: false,
249
+ error: `Provider '${providerId}' no está activo. Usá get_available_models para ver providers activos.`
245
250
  };
246
251
  }
247
252
 
253
+ // Validar que el modelo existe y pertenece al provider ya validado. `active` en un
254
+ // ModelDoc solo marca el modelo por defecto del usuario (elegido en onboarding), no
255
+ // si el modelo es utilizable — cualquier modelo del provider configurado sirve.
256
+ const modelsCol = await col<ModelDoc>("models");
257
+ const modelEntry = await modelsCol.get(modelId);
258
+
248
259
  if (!modelEntry) {
249
- return {
250
- ok: false,
251
- error: `Modelo '${modelId}' no existe. Usá get_available_models para ver modelos disponibles.`
260
+ return {
261
+ ok: false,
262
+ error: `Modelo '${modelId}' no existe. Usá get_available_models para ver modelos disponibles.`
252
263
  };
253
264
  }
254
- if (!modelEntry.doc.enabled || !modelEntry.doc.active) {
255
- return {
256
- ok: false,
257
- error: `Modelo '${modelId}' no está activo. Usá get_available_models para ver modelos activos.`
265
+
266
+ if (!modelEntry.doc.enabled) {
267
+ return {
268
+ ok: false,
269
+ error: `Modelo '${modelId}' no está habilitado. Usá get_available_models para ver modelos disponibles.`
258
270
  };
259
271
  }
260
272
 
273
+ if (modelEntry.doc.provider_id !== providerId) {
274
+ return {
275
+ ok: false,
276
+ error: `Modelo '${modelId}' pertenece al provider '${modelEntry.doc.provider_id}', no a '${providerId}'.`
277
+ };
278
+ }
279
+
280
+ if (mcpServerId) {
281
+ if (!userId) {
282
+ return { ok: false, error: "No se puede asignar un servidor MCP sin contexto de usuario." };
283
+ }
284
+ const serverEntry = await (await col<McpServerDoc>("mcpServers")).get(mcpServerId);
285
+ if (!serverEntry?.doc.enabled) {
286
+ return { ok: false, error: `El servidor MCP '${mcpServerId}' no existe o está deshabilitado.` };
287
+ }
288
+ if (serverEntry.doc.user_id && serverEntry.doc.user_id !== userId) {
289
+ return { ok: false, error: `El servidor MCP '${mcpServerId}' pertenece a otro usuario.` };
290
+ }
291
+
292
+ const existingSpecialist = (await (await col<AgentDoc>("agents")).scan({}))
293
+ .map((entry) => entry.doc)
294
+ .find((agent) => {
295
+ if (agent.role !== "worker" || agent.user_id !== userId) return false;
296
+ try {
297
+ return agent.mcp_server_ids_json
298
+ ? (JSON.parse(agent.mcp_server_ids_json) as string[]).includes(mcpServerId)
299
+ : false;
300
+ } catch {
301
+ return false;
302
+ }
303
+ });
304
+ if (existingSpecialist) {
305
+ const action = existingSpecialist.enabled
306
+ ? "Reutilizalo con task_delegate."
307
+ : "Está deshabilitado; el usuario debe reactivarlo desde Agentes.";
308
+ return {
309
+ ok: false,
310
+ existingAgentId: existingSpecialist.id,
311
+ error: `Ya existe el especialista '${existingSpecialist.name}' para ese servidor. ${action}`,
312
+ };
313
+ }
314
+ }
315
+
261
316
  try {
262
317
  const agentId = crypto.randomUUID().replace(/-/g, "").slice(0, 16);
263
- const agentsCol = db.collection<HiveAgentDoc>("agents");
264
318
  const now = Date.now();
265
319
 
320
+ const agentsCol = await col<AgentDoc>("agents");
266
321
  await agentsCol.put(agentId, {
267
322
  id: agentId,
268
- userId,
323
+ user_id: userId ?? "",
269
324
  name,
270
325
  description,
271
- systemPrompt,
272
- toolsJson,
326
+ system_prompt: systemPrompt,
327
+ tone,
273
328
  role: "worker",
274
329
  status: "idle",
275
- parentId,
276
- providerId,
277
- modelId,
278
- tone,
279
- maxIterations,
280
- workspace: parentWorkspace,
281
330
  enabled: true,
282
- active: true,
283
- createdAt: now,
284
- updatedAt: now,
285
- });
331
+ provider_id: toIndexable(providerId),
332
+ model_id: toIndexable(modelId),
333
+ tools_json: toolsJson,
334
+ skills_json: null,
335
+ active_mcp_json: null,
336
+ parent_id: toIndexable(parentId),
337
+ max_iterations: maxIterations,
338
+ workspace: parentWorkspace,
339
+ lastTraceAt: null,
340
+ created_at: now,
341
+ updated_at: now,
342
+ source: "user",
343
+ mcp_server_ids_json: mcpServerId ? JSON.stringify([mcpServerId]) : null,
344
+ }, { expectedVersion: 0 });
286
345
 
287
- return {
288
- ok: true,
289
- agentId,
290
- name,
291
- providerId,
346
+ return {
347
+ ok: true,
348
+ agentId,
349
+ name,
350
+ providerId,
292
351
  modelId,
352
+ mcpServerId: mcpServerId ?? null,
293
353
  workspace: parentWorkspace,
294
- message: "Agente creado exitosamente."
354
+ message: "Agente creado exitosamente."
295
355
  };
296
356
  } catch (error) {
297
357
  return { ok: false, error: `Failed to create agent: ${(error as Error).message}` };
@@ -303,49 +363,81 @@ export const agentCreateTool: Tool = {
303
363
 
304
364
  export const agentFindTool: Tool = {
305
365
  name: "agent_find",
306
- description: "Find existing running or idle worker agents. Spanish: buscar agente, encontrar worker, localizar agente",
366
+ description: "Discover available worker agents. Includes global system catalog agents plus private workers owned by the current user. This tool does not report task execution; use task_list/task_status for that. Spanish: buscar agente, encontrar worker, localizar agente",
307
367
  parameters: {
308
368
  type: "object",
309
369
  properties: {
310
370
  search: { type: "string", description: "Search term for agent name or description" },
311
- status: { type: "string", enum: ["idle", "active", "any"], description: "Filter by status" },
371
+ availability: { type: "string", enum: ["enabled", "disabled", "any"], description: "Filter by whether the worker can accept tasks" },
372
+ status: { type: "string", enum: ["idle", "active", "any"], description: "Deprecated compatibility alias. It does not represent task execution; use task_list." },
312
373
  },
313
374
  },
314
375
  execute: async (params: Record<string, unknown>, config?: any) => {
315
- const db = await getHiveDB();
316
376
  const userId = config?.configurable?.user_id;
317
377
  const search = params.search as string | undefined;
318
- const status = params.status as string | undefined;
378
+ const legacyStatus = params.status as string | undefined;
379
+ const availability = (params.availability as string | undefined)
380
+ ?? (legacyStatus === "any" ? "any" : legacyStatus ? "enabled" : "any");
319
381
 
320
382
  try {
321
- const agentsCol = db.collection<HiveAgentDoc>("agents");
322
- const entries = await agentsCol.scan();
323
-
324
- let agents = entries
383
+ const agentsCol = await col<AgentDoc>("agents");
384
+ const mcpServersCol = await col<McpServerDoc>("mcpServers");
385
+ const serverNames = new Map(
386
+ (await mcpServersCol.scan({})).map((entry) => [entry.doc.id, entry.doc.name]),
387
+ );
388
+ let agents = (await agentsCol.scan({}))
325
389
  .map(e => e.doc)
326
- .filter(a => a.userId === userId && a.role === "worker");
390
+ .filter(a =>
391
+ a.role === "worker"
392
+ && (a.source === "catalog" || (!!userId && a.user_id === userId))
393
+ );
327
394
 
328
395
  if (search) {
329
- const s = search.toLowerCase();
330
- agents = agents.filter(a =>
331
- a.name.toLowerCase().includes(s) || a.description.toLowerCase().includes(s)
332
- );
396
+ const needle = search.toLowerCase();
397
+ agents = agents.filter((a) => {
398
+ let assignedServerIds: string[] = [];
399
+ try {
400
+ assignedServerIds = a.mcp_server_ids_json ? JSON.parse(a.mcp_server_ids_json) : [];
401
+ } catch {
402
+ assignedServerIds = [];
403
+ }
404
+ return a.name.toLowerCase().includes(needle)
405
+ || (a.description ?? "").toLowerCase().includes(needle)
406
+ || assignedServerIds.some((id) =>
407
+ id.toLowerCase().includes(needle)
408
+ || (serverNames.get(id) ?? "").toLowerCase().includes(needle),
409
+ );
410
+ });
333
411
  }
334
412
 
335
- if (status && status !== "any") {
336
- agents = agents.filter(a => a.status === status);
413
+ if (availability !== "any") {
414
+ agents = agents.filter(a => availability === "enabled" ? a.enabled : !a.enabled);
337
415
  }
338
416
 
339
417
  return {
340
418
  ok: true,
341
419
  count: agents.length,
342
- agents: agents.map((a) => ({
343
- id: a.id,
344
- name: a.name,
345
- description: a.description,
346
- role: a.role,
347
- status: a.status,
348
- })),
420
+ execution_source: "Use task_list or task_status for real execution state.",
421
+ ...(legacyStatus ? { warning: "The status filter is deprecated and cannot prove whether a task is running." } : {}),
422
+ agents: agents.map((a) => {
423
+ let mcpServerIds: string[] = [];
424
+ try {
425
+ mcpServerIds = a.mcp_server_ids_json ? JSON.parse(a.mcp_server_ids_json) : [];
426
+ } catch {
427
+ mcpServerIds = [];
428
+ }
429
+ return {
430
+ id: a.id,
431
+ name: a.name,
432
+ description: a.description,
433
+ role: a.role,
434
+ source: a.source ?? "user",
435
+ enabled: a.enabled,
436
+ availability: a.enabled ? "enabled" : "disabled",
437
+ mcpServerIds,
438
+ mcpServers: mcpServerIds.map((id) => ({ id, name: serverNames.get(id) ?? id })),
439
+ };
440
+ }),
349
441
  };
350
442
  } catch (error) {
351
443
  return { ok: false, error: `Failed to find agents: ${(error as Error).message}` };
@@ -357,27 +449,36 @@ export const agentFindTool: Tool = {
357
449
 
358
450
  export const agentArchiveTool: Tool = {
359
451
  name: "agent_archive",
360
- description: "Archive or terminate a worker agent. Spanish: archivar agente, terminar worker, desactivar agente",
452
+ description: "Archive or terminate a worker you created. Catalog agents cannot be archived — only the user can disable those from the UI. Spanish: archivar agente, terminar worker",
361
453
  parameters: {
362
454
  type: "object",
363
455
  properties: {
364
- agentId: { type: "string", description: "ID of the agent to archive" },
456
+ agentId: { type: "string", description: "ID of the worker to archive (not a catalog agent)" },
365
457
  },
366
458
  required: ["agentId"],
367
459
  },
368
460
  execute: async (params: Record<string, unknown>) => {
369
- const db = await getHiveDB();
370
461
  const agentId = params.agentId as string;
371
462
 
372
463
  try {
373
- const agentsCol = db.collection<HiveAgentDoc>("agents");
374
- const entry = await agentsCol.get(agentId);
464
+ const agentsCol = await col<AgentDoc>("agents");
465
+ const existing = await agentsCol.get(agentId);
375
466
 
376
- if (!entry) {
467
+ if (!existing) {
377
468
  return { ok: false, error: `Agent not found: ${agentId}` };
378
469
  }
379
470
 
380
- await agentsCol.put(agentId, { ...entry.doc, enabled: false, updatedAt: Date.now() });
471
+ // Catalog personas are shared capabilities of the whole hive — turning
472
+ // one off is the user's call, from the UI, never an agent's.
473
+ if (existing.doc.source === "catalog") {
474
+ return {
475
+ ok: false,
476
+ error: `'${existing.doc.name}' is a catalog agent and stays available. Only the user can disable it from the Agents UI.`,
477
+ };
478
+ }
479
+
480
+ await agentsCol.put(agentId, { ...existing.doc, enabled: false, status: "archived", updated_at: Date.now() }, { expectedVersion: existing.version });
481
+
381
482
  return { ok: true, agentId, message: "Agent archived." };
382
483
  } catch (error) {
383
484
  return { ok: false, error: `Failed to archive agent: ${(error as Error).message}` };
@@ -389,144 +490,563 @@ export const agentArchiveTool: Tool = {
389
490
 
390
491
  export const taskDelegateTool: Tool = {
391
492
  name: "task_delegate",
392
- description: "Delegate a task to a worker agent and execute it immediately (blocking). Spanish: delegar tarea, asignar worker, ejecutar por agente, delegate_task",
493
+ description: "Delegate a bounded task to an existing worker_id (any `agents` row: catalog-seeded or agent_create-made). The delivery goes through deterministic acceptance checks (no LLM); you judge anything they don't cover in your closing turn, and use task_revise to send it back with feedback if it doesn't meet its criteria. mode=sync blocks the conversation until done; mode=async enqueues and frees the conversation immediately — the user is notified automatically in this same chat when the worker finishes. Prefer async unless you expect the result in a few seconds.",
393
494
  parameters: {
394
495
  type: "object",
395
496
  properties: {
396
- worker_id: { type: "string", description: "ID of the worker agent" },
497
+ worker_id: { type: "string", description: "Target agent ID from agent_find or the catalog agent list in the system prompt." },
397
498
  task_description: { type: "string", description: "Clear, detailed instructions for the worker" },
398
- task_id: { type: "number", description: "Optional task DB ID to update status automatically" },
399
- project_id: { type: "string", description: "Optional project ID for progress tracking" },
499
+ acceptance: {
500
+ type: "array",
501
+ description: "Verifiable acceptance criteria. The worker's default criteria are used when omitted.",
502
+ items: {
503
+ type: "object",
504
+ properties: {
505
+ id: { type: "string" },
506
+ description: { type: "string" },
507
+ checkTool: { type: "string" },
508
+ },
509
+ required: ["id", "description"],
510
+ },
511
+ },
512
+ mode: { type: "string", enum: ["sync", "async"], description: "sync (default, blocking, 2min timeout — only for very short delegations) or async (enqueued, frees the conversation instantly; outcome is relayed back to the user automatically). Prefer async for anything non-trivial." },
400
513
  },
401
- required: ["worker_id", "task_description"],
514
+ required: ["task_description"],
402
515
  },
403
516
  execute: async (params: Record<string, unknown>, config?: any) => {
404
- const db = getDb();
405
- const workerId = params.worker_id as string;
517
+ const agentId = params.worker_id as string | undefined;
406
518
  const taskDescription = params.task_description as string;
407
- const taskId = params.task_id as number | undefined;
408
- const projectId = params.project_id as string | undefined;
519
+ const mode = (params.mode as string) ?? "sync";
520
+ const turnId = config?.configurable?.turn_id as string | undefined;
409
521
 
410
- // Verify worker exists and is enabled
411
- const worker = db.query<any, [string]>(
412
- "SELECT id, name, enabled FROM agents WHERE id = ?"
413
- ).get(workerId);
522
+ if (!agentId) return { ok: false, error: "Provide worker_id." };
414
523
 
415
- if (!worker) {
416
- return { ok: false, error: `Worker not found: ${workerId}` };
524
+ const agentsCol = await col<AgentDoc>("agents");
525
+ const parentAgentId = config?.configurable?.agent_id ?? "";
526
+ if (!parentAgentId) {
527
+ return { ok: false, error: "Delegation caller identity is missing (config.configurable.agent_id). The coordinator may exist, but its ID was not propagated to task_delegate." };
417
528
  }
418
- if (!worker.enabled) {
419
- return { ok: false, error: `Worker is disabled: ${worker.name}` };
420
- }
421
-
422
- // Fetch task info for bus notifications
423
- const taskRow = taskId
424
- ? db.query<any, [number]>("SELECT name, project_id FROM tasks WHERE id = ?").get(taskId)
529
+ const parentEntry = await agentsCol.get(parentAgentId);
530
+ if (!parentEntry) return { ok: false, error: `Delegation caller agent not found: ${parentAgentId}` };
531
+ if (!parentEntry.doc.enabled) return { ok: false, error: `Delegation caller agent is disabled: ${parentAgentId}` };
532
+ const parent = parentEntry.doc;
533
+ const parentProviderId = fromIndexable(parent.provider_id);
534
+ const parentModelId = fromIndexable(parent.model_id);
535
+
536
+ const workerEntry = await agentsCol.get(agentId);
537
+ if (!workerEntry) return { ok: false, error: `Agent not found: ${agentId}` };
538
+ const worker = workerEntry.doc;
539
+ if (!worker.enabled) return { ok: false, error: `Agent is disabled: ${worker.name}` };
540
+
541
+ const taskName = taskDescription.slice(0, 60);
542
+ const defaultAcceptance: AgentAcceptanceCriterion[] | null = worker.default_acceptance_json
543
+ ? JSON.parse(worker.default_acceptance_json)
425
544
  : null;
426
- const taskName = taskRow?.name ?? taskDescription.slice(0, 60);
427
- const resolvedProjectId = projectId ?? taskRow?.project_id ?? "";
428
-
429
- // Mark task in_progress if task_id provided
430
- if (taskId) {
431
- db.query("UPDATE tasks SET status='in_progress', agent_id=?, updated_at=unixepoch() WHERE id=?")
432
- .run(workerId, taskId);
433
- emitCanvas("canvas:node_update", { id: taskId.toString(), type: "task", data: { status: "in_progress", agent_id: workerId } });
434
- }
545
+ const acceptance = ((params.acceptance as AcceptanceCriterion[] | undefined)
546
+ ?? defaultAcceptance?.map((criterion) => ({
547
+ id: criterion.id,
548
+ description: criterion.description,
549
+ checkTool: criterion.check_tool,
550
+ }))
551
+ ?? [{ id: "objective", description: taskDescription }]);
552
+
553
+ // ── Async mode: create TaskDoc + enqueue worker_task in durable queue ──
554
+ if (mode === "async") {
555
+ try {
556
+ const { nextId, toIndexable, updateDoc } = await import("../../storage/hive.ts");
557
+ const { createRun } = await import("../../agent/run-store.ts");
558
+ const { getDurableQueue } = await import("../../gateway/durable-queue.ts");
559
+
560
+ const taskId = await nextId("tasks");
561
+ const now = Date.now();
562
+ const tasksCol = await col<TaskDoc>("tasks");
563
+ await tasksCol.put(taskId, {
564
+ id: taskId,
565
+ agent_id: toIndexable(agentId),
566
+ name: taskName,
567
+ description: taskDescription,
568
+ status: "pending",
569
+ progress: 0,
570
+ result: null,
571
+ error: null,
572
+ metadata: null,
573
+ job_id: null,
574
+ run_id: null,
575
+ thread_id: null,
576
+ delegation_group_id: turnId ?? null,
577
+ catalog_agent_id: toIndexable(worker.source === "catalog" ? agentId : null),
578
+ started_at: null,
579
+ attempts: 0,
580
+ created_at: now,
581
+ updated_at: now,
582
+ completed_at: null,
583
+ }, { expectedVersion: 0 });
584
+
585
+ if (turnId) {
586
+ const { registerDelegatedTask } = await import("../../gateway/delegation-groups.ts");
587
+ await registerDelegatedTask({
588
+ turnId,
589
+ taskId,
590
+ threadId: config?.configurable?.thread_id ?? "",
591
+ channel: config?.configurable?.channel,
592
+ userId: config?.configurable?.user_id,
593
+ sessionId: config?.configurable?.session_id,
594
+ coordinatorAgentId: parentAgentId,
595
+ });
596
+ }
597
+
598
+ const run = await createRun({
599
+ thread_id: `task-${taskId}-${agentId}`,
600
+ agent_id: agentId,
601
+ user_id: config?.configurable?.user_id ?? "",
602
+ channel: config?.configurable?.channel ?? null,
603
+ kind: "worker",
604
+ max_iterations: worker.max_iterations || 10,
605
+ resume_policy: "resume",
606
+ acceptance,
607
+ catalog_agent_id: worker.source === "catalog" ? agentId : undefined,
608
+ });
609
+
610
+ const queue = getDurableQueue();
611
+ const job = await queue.enqueue({
612
+ lane: `task:${taskId}`,
613
+ type: "worker_task",
614
+ run_id: run.id,
615
+ payload: {
616
+ workerId: agentId,
617
+ taskDescription,
618
+ taskName,
619
+ taskId,
620
+ acceptance,
621
+ parentAgentId,
622
+ parentProviderId,
623
+ parentModelId,
624
+ userId: config?.configurable?.user_id ?? "",
625
+ workspace: config?.configurable?.workspace ?? null,
626
+ // Delegating conversation's thread — lets delegation-notify.ts relay
627
+ // the outcome back to the user once this job reaches a terminal state.
628
+ originThreadId: config?.configurable?.thread_id ?? null,
629
+ originChannel: config?.configurable?.channel ?? null,
630
+ originSessionId: config?.configurable?.session_id ?? null,
631
+ turnId: turnId ?? null,
632
+ },
633
+ });
634
+
635
+ await updateDoc<TaskDoc>("tasks", taskId, {
636
+ job_id: job.id,
637
+ run_id: run.id,
638
+ thread_id: `task-${taskId}-${agentId}`,
639
+ updated_at: Date.now(),
640
+ } as Partial<TaskDoc>);
641
+
642
+ agentBus.notifyTaskStarted(agentId, worker.name, 0, taskName, "");
643
+ if (turnId) {
644
+ const { publishNarration } = await import("../../events/narration.ts");
645
+ await publishNarration({
646
+ turnId,
647
+ threadId: config?.configurable?.thread_id ?? "",
648
+ channel: config?.configurable?.channel,
649
+ userId: config?.configurable?.user_id,
650
+ sessionId: config?.configurable?.session_id,
651
+ agentId,
652
+ agentName: worker.name,
653
+ kind: "delegated",
654
+ status: "queued",
655
+ label: `Delegué “${taskName}” a ${worker.name}`,
656
+ dedupeKey: `delegated:${taskId}`,
657
+ });
658
+ }
435
659
 
436
- // Notify Agent Bus: task started
437
- agentBus.notifyTaskStarted(workerId, worker.name, taskId ?? 0, taskName, resolvedProjectId);
660
+ return {
661
+ ok: true,
662
+ task_id: taskId,
663
+ job_id: job.id,
664
+ run_id: run.id,
665
+ worker_id: agentId,
666
+ worker_name: worker.name,
667
+ status: "queued",
668
+ message: `Task enqueued (async). Use task_list for the queue or task_status with task_id="${taskId}" for this task.`,
669
+ };
670
+ } catch (err) {
671
+ return { ok: false, error: `Async delegation failed: ${(err as Error).message}` };
672
+ }
673
+ }
438
674
 
439
- log.info(`[task_delegate] Delegating to ${worker.name} (${workerId})`);
675
+ // ── Sync mode: blocking execution with 2min timeout ──
676
+ const { prepareDelegation } = await import("../../agent/delegation-runtime.ts");
677
+ const { getMCPManager } = await import("../../mcp/singleton.ts");
678
+ const mcpManager = getMCPManager();
440
679
 
680
+ let prepared: PreparedDelegation;
441
681
  try {
442
- // Dynamic import to avoid circular dependency (agent-loop → tools → agent-loop)
443
- const { runAgentIsolated } = await import("../../agent/AgentRunner.ts");
444
-
445
- const threadId = `task-${taskId ?? Date.now()}-${workerId}`;
446
- const result = await runAgentIsolated({
447
- agentId: workerId,
448
- taskDescription,
449
- threadId,
682
+ prepared = await prepareDelegation(agentId, {
683
+ workspace: config?.configurable?.workspace ?? null,
684
+ parentProviderId,
685
+ parentModelId,
686
+ mcpManager,
450
687
  });
688
+ } catch (err) {
689
+ return { ok: false, error: (err as Error).message };
690
+ }
451
691
 
452
- // Update task to completed if task_id provided
453
- if (taskId) {
454
- db.query(
455
- "UPDATE tasks SET status='completed', progress=100, result=?, updated_at=unixepoch() WHERE id=?"
456
- ).run(result, taskId);
457
- emitCanvas("canvas:node_update", { id: taskId.toString(), type: "task", data: { status: "completed", progress: 100 } });
458
-
459
- // Recalculate project progress if project_id provided
460
- if (resolvedProjectId) {
461
- const rows = db.query<any, [string]>(
462
- "SELECT AVG(progress) as avg FROM tasks WHERE project_id=?"
463
- ).get(resolvedProjectId);
464
- const avg = Math.round(rows?.avg ?? 0);
465
- db.query("UPDATE projects SET progress=?, updated_at=unixepoch() WHERE id=?")
466
- .run(avg, resolvedProjectId);
467
- emitCanvas("canvas:node_update", { id: resolvedProjectId, type: "project", data: { progress: avg } });
468
- }
469
- }
470
-
471
- // Notify Agent Bus: task completed
472
- agentBus.notifyTaskCompleted(workerId, worker.name, taskId ?? 0, taskName, resolvedProjectId, result);
473
-
474
- const finalProgress = resolvedProjectId
475
- ? (db.query<any, [string]>("SELECT progress FROM projects WHERE id=?").get(resolvedProjectId)?.progress ?? null)
476
- : null;
692
+ agentBus.notifyTaskStarted(agentId, worker.name, 0, taskName, "");
693
+ log.info(`[task_delegate] Delegating (sync) to ${worker.name} (${agentId})`);
694
+ const syncDelegationRef = `sync-${Date.now()}-${agentId}`;
695
+ emitDelegationStarted({ workerId: agentId, parentAgentId, taskRef: syncDelegationRef, taskName });
477
696
 
697
+ try {
698
+ const { runAgentIsolated, withTimeout } = await import("../../agent/agent-loop.ts");
699
+
700
+ const threadId = `task-${Date.now()}-${agentId}`;
701
+ const SYNC_TIMEOUT_MS = 2 * 60 * 1000;
702
+
703
+ // Real cancellation (e.g. the user's "stop" button): the job's AbortSignal
704
+ // reaches us via config.signal (tool-runtime/index.ts's executeToolBatch),
705
+ // and runAgentIsolated/runAgent already honor it mid-run. withTimeout stays
706
+ // as a hard ceiling in case the signal path doesn't stop things in time.
707
+ const signal = config?.signal as AbortSignal | undefined;
708
+ const result = await withTimeout(
709
+ () => runAgentIsolated({
710
+ agentId,
711
+ taskDescription,
712
+ threadId,
713
+ mcpManager,
714
+ signal,
715
+ }),
716
+ SYNC_TIMEOUT_MS,
717
+ );
718
+
719
+ agentBus.notifyTaskCompleted(agentId, worker.name, 0, taskName, "", result);
720
+
721
+ // Deterministic acceptance checks now always run, closing the old gap
722
+ // where a plain worker_id delegation (agent_create) skipped them
723
+ // entirely in sync mode. No LLM call: the calling agent (usually the
724
+ // coordinator) judges the delivery itself in this same tool response.
725
+ const { runAcceptanceChecks, recordAgentOutcome } = await import("../../agent/acceptance-checks.ts");
726
+ const checks = await runAcceptanceChecks({
727
+ objective: taskDescription,
728
+ acceptance,
729
+ delivery: result,
730
+ evidence: [result],
731
+ });
732
+ if (checks.status === "failed") {
733
+ await recordAgentOutcome(agentId, "harmful");
734
+ emitWorkEvent({
735
+ phase: "review_failed",
736
+ taskRef: syncDelegationRef,
737
+ taskName,
738
+ actorId: parentAgentId || agentId,
739
+ targetId: agentId,
740
+ detail: checks.summary,
741
+ });
742
+ return {
743
+ ok: false,
744
+ status: checks.status,
745
+ error: checks.summary,
746
+ };
747
+ }
748
+ await recordAgentOutcome(agentId, "helpful");
749
+ emitWorkEvent({
750
+ phase: "review_passed",
751
+ taskRef: syncDelegationRef,
752
+ taskName,
753
+ actorId: parentAgentId || agentId,
754
+ targetId: agentId,
755
+ });
756
+ emitWorkEvent({
757
+ phase: "completed",
758
+ taskRef: syncDelegationRef,
759
+ taskName,
760
+ actorId: agentId,
761
+ targetId: parentAgentId || null,
762
+ });
478
763
  return {
479
764
  ok: true,
480
- worker_id: workerId,
765
+ worker_id: agentId,
481
766
  worker_name: worker.name,
482
- task_id: taskId,
767
+ acceptance,
768
+ checks,
483
769
  result,
484
- project_progress: finalProgress,
485
770
  };
486
771
  } catch (err) {
487
- // Mark task failed if task_id provided
488
- if (taskId) {
489
- db.query(
490
- "UPDATE tasks SET status='failed', result=?, updated_at=unixepoch() WHERE id=?"
491
- ).run((err as Error).message, taskId);
492
- emitCanvas("canvas:node_update", { id: taskId.toString(), type: "task", data: { status: "failed" } });
772
+ const errorMessage = (err as Error).message;
773
+ const wasAborted = (config?.signal as AbortSignal | undefined)?.aborted === true;
774
+ agentBus.notifyTaskFailed(agentId, worker.name, 0, taskName, "", errorMessage);
775
+ emitWorkEvent({
776
+ phase: wasAborted ? "aborted" : "failed",
777
+ taskRef: syncDelegationRef,
778
+ taskName,
779
+ actorId: agentId,
780
+ targetId: parentAgentId || null,
781
+ detail: wasAborted ? "Trabajo interrumpido" : errorMessage,
782
+ });
783
+
784
+ return {
785
+ ok: false,
786
+ worker_id: agentId,
787
+ error: errorMessage,
788
+ };
789
+ } finally {
790
+ emitDelegationFinished({ workerId: agentId, taskRef: syncDelegationRef });
791
+ await prepared.release();
792
+ }
793
+ },
794
+ };
795
+
796
+ // ─── task_revise ─────────────────────────────────────────────────────────────
797
+
798
+ const MAX_TASK_REVISIONS = 2;
799
+
800
+ export const taskReviseTool: Tool = {
801
+ name: "task_revise",
802
+ description: "Send a completed or blocked delegated task back to its worker with concrete feedback, instead of reporting it as done. The worker resumes on the SAME thread — it keeps its prior context, so the feedback only needs to describe what's missing. Use this when a delivery doesn't meet its acceptance criteria and you can't fix it yourself.",
803
+ parameters: {
804
+ type: "object",
805
+ properties: {
806
+ task_id: { type: "string", description: "The task_id from task_delegate's result." },
807
+ feedback: { type: "string", description: "Concrete, actionable feedback: what's wrong and what the worker still needs to do." },
808
+ acceptance: {
809
+ type: "array",
810
+ description: "Updated acceptance criteria, if they need to change. Defaults to the original task's criteria.",
811
+ items: {
812
+ type: "object",
813
+ properties: {
814
+ id: { type: "string" },
815
+ description: { type: "string" },
816
+ checkTool: { type: "string" },
817
+ },
818
+ required: ["id", "description"],
819
+ },
820
+ },
821
+ },
822
+ required: ["task_id", "feedback"],
823
+ },
824
+ execute: async (params: Record<string, unknown>, config?: any) => {
825
+ const taskId = params.task_id as string | undefined;
826
+ const feedback = params.feedback as string | undefined;
827
+ if (!taskId) return { ok: false, error: "Provide task_id." };
828
+ if (!feedback || !feedback.trim()) return { ok: false, error: "Provide feedback describing what's missing." };
829
+
830
+ const parentAgentId = config?.configurable?.agent_id ?? "";
831
+ if (!parentAgentId) {
832
+ return { ok: false, error: "Caller identity is missing (config.configurable.agent_id)." };
833
+ }
834
+ const agentsCol = await col<AgentDoc>("agents");
835
+ const parentEntry = await agentsCol.get(parentAgentId);
836
+ if (!parentEntry) return { ok: false, error: `Caller agent not found: ${parentAgentId}` };
837
+ const parentProviderId = fromIndexable(parentEntry.doc.provider_id);
838
+ const parentModelId = fromIndexable(parentEntry.doc.model_id);
839
+
840
+ const tasksCol = await col<TaskDoc>("tasks");
841
+ const taskEntry = await tasksCol.get(taskId);
842
+ if (!taskEntry) return { ok: false, error: `Task not found: ${taskId}` };
843
+ const task = taskEntry.doc;
844
+ if (task.status !== "completed" && task.status !== "blocked") {
845
+ return { ok: false, error: `Task ${taskId} is "${task.status}" — only completed or blocked tasks can be revised.` };
846
+ }
847
+ if ((task.attempts ?? 0) >= MAX_TASK_REVISIONS) {
848
+ return { ok: false, error: `Task ${taskId} already used its ${MAX_TASK_REVISIONS} revision attempt(s). Fix it yourself or report the limit to the user.` };
849
+ }
850
+ if (!task.thread_id) {
851
+ return { ok: false, error: `Task ${taskId} has no thread_id — cannot resume its worker.` };
852
+ }
853
+
854
+ const agentId = fromIndexable(task.agent_id);
855
+ const workerEntry = agentId ? await agentsCol.get(agentId) : null;
856
+ if (!workerEntry) return { ok: false, error: `Worker not found for task ${taskId}: ${agentId}` };
857
+ const worker = workerEntry.doc;
858
+ if (!worker.enabled) return { ok: false, error: `Worker is disabled: ${worker.name}` };
859
+
860
+ const { createRun, getRun, deserializeAcceptance } = await import("../../agent/run-store.ts");
861
+ const { getDurableQueue } = await import("../../gateway/durable-queue.ts");
862
+ const { updateDoc } = await import("../../storage/hive.ts");
863
+
864
+ const previousRun = task.run_id ? await getRun(task.run_id) : null;
865
+ const previousAcceptance = previousRun ? deserializeAcceptance(previousRun) : null;
866
+ const acceptance = (params.acceptance as AcceptanceCriterion[] | undefined) ?? previousAcceptance ?? [{ id: "objective", description: task.description ?? feedback }];
867
+
868
+ try {
869
+ const run = await createRun({
870
+ thread_id: task.thread_id,
871
+ agent_id: agentId!,
872
+ user_id: config?.configurable?.user_id ?? "",
873
+ channel: config?.configurable?.channel ?? null,
874
+ kind: "worker",
875
+ max_iterations: worker.max_iterations || 10,
876
+ resume_policy: "resume",
877
+ acceptance,
878
+ catalog_agent_id: worker.source === "catalog" ? agentId! : undefined,
879
+ });
880
+
881
+ const turnId = config?.configurable?.turn_id as string | undefined;
882
+ if (turnId) {
883
+ const { registerDelegatedTask } = await import("../../gateway/delegation-groups.ts");
884
+ await registerDelegatedTask({
885
+ turnId,
886
+ taskId,
887
+ threadId: config?.configurable?.thread_id ?? "",
888
+ channel: config?.configurable?.channel,
889
+ userId: config?.configurable?.user_id,
890
+ sessionId: config?.configurable?.session_id,
891
+ coordinatorAgentId: parentAgentId,
892
+ });
493
893
  }
494
894
 
495
- // Notify Agent Bus: task failed
496
- agentBus.notifyTaskFailed(workerId, worker.name, taskId ?? 0, taskName, resolvedProjectId, (err as Error).message);
895
+ const queue = getDurableQueue();
896
+ const job = await queue.enqueue({
897
+ lane: `task:${taskId}`,
898
+ type: "worker_task",
899
+ run_id: run.id,
900
+ payload: {
901
+ workerId: agentId,
902
+ taskDescription: `${task.description ?? ""}\n\nCORRECCIÓN SOLICITADA: ${feedback}`,
903
+ taskName: task.name,
904
+ taskId,
905
+ acceptance,
906
+ parentAgentId,
907
+ parentProviderId,
908
+ parentModelId,
909
+ userId: config?.configurable?.user_id ?? "",
910
+ workspace: config?.configurable?.workspace ?? null,
911
+ originThreadId: config?.configurable?.thread_id ?? null,
912
+ originChannel: config?.configurable?.channel ?? null,
913
+ originSessionId: config?.configurable?.session_id ?? null,
914
+ turnId: turnId ?? null,
915
+ revision: true,
916
+ },
917
+ });
918
+
919
+ await updateDoc<TaskDoc>("tasks", taskId, {
920
+ status: "pending",
921
+ error: null,
922
+ job_id: job.id,
923
+ run_id: run.id,
924
+ attempts: (task.attempts ?? 0) + 1,
925
+ delegation_group_id: turnId ?? task.delegation_group_id ?? null,
926
+ updated_at: Date.now(),
927
+ } as Partial<TaskDoc>);
928
+
929
+ const { recordAgentOutcome } = await import("../../agent/acceptance-checks.ts");
930
+ await recordAgentOutcome(agentId, "harmful");
931
+
932
+ emitWorkEvent({
933
+ phase: "review_failed",
934
+ taskRef: taskId,
935
+ taskName: task.name,
936
+ actorId: parentAgentId,
937
+ targetId: agentId!,
938
+ detail: feedback,
939
+ });
940
+
941
+ if (turnId) {
942
+ const { publishNarration } = await import("../../events/narration.ts");
943
+ await publishNarration({
944
+ turnId,
945
+ threadId: config?.configurable?.thread_id ?? "",
946
+ channel: config?.configurable?.channel,
947
+ userId: config?.configurable?.user_id,
948
+ sessionId: config?.configurable?.session_id,
949
+ agentId: agentId!,
950
+ agentName: worker.name,
951
+ kind: "delegated",
952
+ status: "queued",
953
+ label: `Devolví “${task.name}” a ${worker.name} con correcciones`,
954
+ dedupeKey: `revised:${taskId}:${(task.attempts ?? 0) + 1}`,
955
+ });
956
+ }
497
957
 
498
958
  return {
499
- ok: false,
500
- worker_id: workerId,
959
+ ok: true,
501
960
  task_id: taskId,
502
- error: (err as Error).message,
961
+ job_id: job.id,
962
+ run_id: run.id,
963
+ worker_id: agentId,
964
+ attempts: (task.attempts ?? 0) + 1,
965
+ status: "queued",
966
+ message: `Revision enqueued (async). Use task_status with task_id="${taskId}" to check it.`,
503
967
  };
968
+ } catch (err) {
969
+ return { ok: false, error: `Task revision failed: ${(err as Error).message}` };
504
970
  }
505
971
  },
506
972
  };
507
973
 
508
- // ─── task_delegate_code ──────────────────────────────────────────────────────
974
+ // ─── task_list ──────────────────────────────────────────────────────────────
509
975
 
510
- export const taskDelegateCodeTool: Tool = {
511
- name: "task_delegate_code",
512
- description: "Delegate a coding task to a CLI subagent (Qwen, Claude, etc.) via Code Bridge. Spanish: delegar código, subagente CLI, programación, Qwen",
976
+ export const taskListTool: Tool = {
977
+ name: "task_list",
978
+ description: "List real delegated task executions for the current user. TaskDoc and JobDoc are the source of truth. Use this instead of agent_find to determine whether work is pending, running, completed, failed, or blocked.",
513
979
  parameters: {
514
980
  type: "object",
515
981
  properties: {
516
- cli: { type: "string", enum: ["qwen", "claude", "opencode", "gemini"], description: "CLI tool to use" },
517
- task_instructions: { type: "string", description: "Coding task instructions" },
982
+ status: {
983
+ type: "string",
984
+ enum: ["pending", "running", "completed", "failed", "blocked", "all"],
985
+ description: "Execution-state filter (default: all)",
986
+ },
987
+ worker_id: { type: "string", description: "Optional worker agent ID" },
988
+ limit: { type: "number", description: "Maximum tasks to return (default 20, maximum 100)" },
518
989
  },
519
- required: ["cli", "task_instructions"],
520
990
  },
521
- execute: async (params: Record<string, unknown>) => {
522
- const cli = params.cli as string;
523
- const taskInstructions = params.task_instructions as string;
524
-
525
- return {
526
- ok: true,
527
- cli,
528
- message: `Code task delegated to ${cli}: ${taskInstructions.substring(0, 100)}...`,
529
- };
991
+ execute: async (params: Record<string, unknown>, config?: any) => {
992
+ const userId = config?.configurable?.user_id as string | undefined;
993
+ if (!userId) return { ok: false, error: "Missing user context for task_list." };
994
+
995
+ const status = (params.status as string | undefined) ?? "all";
996
+ const workerId = params.worker_id as string | undefined;
997
+ const limit = Math.max(1, Math.min(100, Number(params.limit) || 20));
998
+
999
+ try {
1000
+ const tasksCol = await col<TaskDoc>("tasks");
1001
+ const runsCol = await col<import("../../storage/collections.ts").AgentRunDoc>("agentRuns");
1002
+ const { getJob } = await import("../../gateway/job-store.ts");
1003
+ const allTasks = (await tasksCol.scan({}))
1004
+ .map((entry) => entry.doc)
1005
+ .sort((a, b) => b.updated_at - a.updated_at);
1006
+
1007
+ const result: Array<Record<string, unknown>> = [];
1008
+ for (const task of allTasks) {
1009
+ if (result.length >= limit) break;
1010
+ if (workerId && fromIndexable(task.agent_id) !== workerId) continue;
1011
+
1012
+ const run = task.run_id ? await runsCol.get(task.run_id) : null;
1013
+ if (!run || run.doc.user_id !== userId) continue;
1014
+
1015
+ const job = task.job_id ? await getJob(task.job_id) : null;
1016
+ const executionState =
1017
+ task.status === "pending"
1018
+ ? "pending"
1019
+ : task.status === "in_progress"
1020
+ ? "running"
1021
+ : task.status;
1022
+ if (status !== "all" && executionState !== status) continue;
1023
+
1024
+ result.push({
1025
+ id: task.id,
1026
+ name: task.name,
1027
+ description: task.description,
1028
+ worker_id: fromIndexable(task.agent_id),
1029
+ status: task.status,
1030
+ execution_state: executionState,
1031
+ progress: task.progress,
1032
+ result: task.result,
1033
+ error: task.error,
1034
+ job_id: task.job_id,
1035
+ job_status: job?.status ?? null,
1036
+ run_id: task.run_id,
1037
+ run_status: run.doc.status,
1038
+ attempts: task.attempts ?? 0,
1039
+ created_at: task.created_at,
1040
+ started_at: task.started_at,
1041
+ updated_at: task.updated_at,
1042
+ completed_at: task.completed_at,
1043
+ });
1044
+ }
1045
+
1046
+ return { ok: true, task_count: result.length, tasks: result };
1047
+ } catch (error) {
1048
+ return { ok: false, error: `Failed to list delegated tasks: ${(error as Error).message}` };
1049
+ }
530
1050
  },
531
1051
  };
532
1052
 
@@ -534,34 +1054,58 @@ export const taskDelegateCodeTool: Tool = {
534
1054
 
535
1055
  export const taskStatusTool: Tool = {
536
1056
  name: "task_status",
537
- description: "Get execution status of one or more delegated tasks. Spanish: estado tarea delegada, verificar progreso, consultar tarea",
1057
+ description: "Get execution status of one or more delegated tasks. Accepts string or numeric IDs. Spanish: estado tarea delegada, verificar progreso, consultar tarea",
538
1058
  parameters: {
539
1059
  type: "object",
540
1060
  properties: {
541
- task_ids: { type: "array", description: "List of task IDs", items: { type: "number" } },
1061
+ task_ids: {
1062
+ type: "array",
1063
+ description: "List of task IDs (strings or numbers)",
1064
+ items: { type: "string" },
1065
+ },
542
1066
  },
543
1067
  required: ["task_ids"],
544
1068
  },
545
1069
  execute: async (params: Record<string, unknown>) => {
546
- const db = getDb();
547
- const taskIds = params.task_ids as number[];
1070
+ const taskIds = params.task_ids as Array<string | number>;
548
1071
 
549
1072
  try {
550
- const placeholders = taskIds.map(() => "?").join(",");
551
- const tasks = db.query<any, any[]>(
552
- `SELECT id, name, status, progress, result FROM tasks WHERE id IN (${placeholders})`
553
- ).all(...taskIds) as any[];
554
-
555
- return {
556
- ok: true,
557
- task_count: tasks.length,
558
- tasks: tasks.map((t) => ({
1073
+ const tasksCol = await col<TaskDoc>("tasks");
1074
+ const ids = taskIds.map((id) => String(id).padStart(15, "0"));
1075
+ const entries = await Promise.all(ids.map((id) => tasksCol.get(id)));
1076
+ const tasks = entries.filter((e): e is NonNullable<typeof e> => !!e).map((e) => e.doc);
1077
+
1078
+ const result = await Promise.all(tasks.map(async (t) => {
1079
+ let jobStatus: string | null = null;
1080
+ if (t.job_id) {
1081
+ try {
1082
+ const { getJob } = await import("../../gateway/job-store.ts");
1083
+ const job = await getJob(t.job_id);
1084
+ if (job) {
1085
+ jobStatus = job.status;
1086
+ }
1087
+ } catch { /* non-critical */ }
1088
+ }
1089
+ return {
559
1090
  id: t.id,
560
1091
  name: t.name,
561
1092
  status: t.status,
562
1093
  progress: t.progress,
563
1094
  result: t.result,
564
- })),
1095
+ error: t.error,
1096
+ job_id: t.job_id,
1097
+ run_id: t.run_id,
1098
+ job_status: jobStatus,
1099
+ attempts: t.attempts ?? 0,
1100
+ started_at: t.started_at,
1101
+ completed_at: t.completed_at,
1102
+ };
1103
+ }));
1104
+
1105
+ return {
1106
+ ok: true,
1107
+ task_count: result.length,
1108
+ tasks: result,
565
1109
  };
566
1110
  } catch (error) {
567
1111
  return { ok: false, error: `Failed to get task status: ${(error as Error).message}` };
@@ -619,39 +1163,34 @@ export const busReadTool: Tool = {
619
1163
  },
620
1164
  },
621
1165
  execute: async (params: Record<string, unknown>) => {
622
- const db = getDb();
623
1166
  const workerId = params.worker_id as string | undefined;
624
1167
  const limit = (params.limit as number) ?? 10;
625
1168
 
626
1169
  try {
627
- let query = "SELECT * FROM agent_bus_messages WHERE read = 0";
628
- const args: any[] = [];
1170
+ const messagesCol = await col<AgentBusMessageDoc>("agentBusMessages");
1171
+ let entries = (await messagesCol.scan({})).filter(e => !e.doc.read);
629
1172
 
630
1173
  if (workerId) {
631
- query += " AND (to_worker_id = ? OR to_worker_id IS NULL)";
632
- args.push(workerId);
1174
+ entries = entries.filter(e => e.doc.to_worker_id === workerId || e.doc.to_worker_id === BROADCAST);
633
1175
  }
634
1176
 
635
- query += " ORDER BY created_at ASC LIMIT ?";
636
- args.push(limit);
637
-
638
- const messages = db.query(query).all(...args) as any[];
1177
+ entries.sort((a, b) => a.doc.created_at - b.doc.created_at);
1178
+ entries = entries.slice(0, limit);
639
1179
 
640
1180
  // Mark as read
641
- if (messages.length > 0) {
642
- const ids = messages.map((m) => m.id).join(",");
643
- db.query(`UPDATE agent_bus_messages SET read = 1 WHERE id IN (${ids})`).run();
1181
+ for (const entry of entries) {
1182
+ await messagesCol.put(entry.id, { ...entry.doc, read: true }, { expectedVersion: entry.version });
644
1183
  }
645
1184
 
646
1185
  return {
647
1186
  ok: true,
648
- count: messages.length,
649
- messages: messages.map((m) => ({
1187
+ count: entries.length,
1188
+ messages: entries.map(({ doc: m }) => ({
650
1189
  id: m.id,
651
1190
  event_type: m.event_type,
652
1191
  content: m.content,
653
- from_worker_id: m.from_worker_id,
654
- created_at: new Date(m.created_at * 1000).toISOString(),
1192
+ from_worker_id: fromIndexable(m.from_worker_id),
1193
+ created_at: new Date(m.created_at).toISOString(),
655
1194
  })),
656
1195
  };
657
1196
  } catch (error) {
@@ -660,53 +1199,6 @@ export const busReadTool: Tool = {
660
1199
  },
661
1200
  };
662
1201
 
663
- // ─── project_updates ─────────────────────────────────────────────────────────
664
-
665
- export const projectUpdatesTool: Tool = {
666
- name: "project_updates",
667
- description: "Get recent status updates from workers in the same project. Spanish: actualizaciones proyecto, estado workers, progreso equipo",
668
- parameters: {
669
- type: "object",
670
- properties: {
671
- project_id: { type: "string", description: "Project ID to get updates from" },
672
- limit: { type: "number", description: "Maximum updates to return (default: 10)" },
673
- },
674
- required: ["project_id"],
675
- },
676
- execute: async (params: Record<string, unknown>) => {
677
- const db = getDb();
678
- const projectId = params.project_id as string;
679
- const limit = (params.limit as number) ?? 10;
680
-
681
- try {
682
- const tasks = db.query<any, [string, number]>(
683
- `SELECT t.id, t.name, t.status, t.progress, t.result, t.updated_at, a.name as agent_name
684
- FROM tasks t
685
- LEFT JOIN agents a ON t.agent_id = a.id
686
- WHERE t.project_id = ?
687
- ORDER BY t.updated_at DESC
688
- LIMIT ?`
689
- ).all(projectId, limit) as any[];
690
-
691
- return {
692
- ok: true,
693
- project_id: projectId,
694
- count: tasks.length,
695
- updates: tasks.map((t) => ({
696
- task_id: t.id,
697
- task_name: t.name,
698
- agent_name: t.agent_name,
699
- status: t.status,
700
- progress: t.progress,
701
- result: t.result,
702
- updated_at: new Date(t.updated_at * 1000).toISOString(),
703
- })),
704
- };
705
- } catch (error) {
706
- return { ok: false, error: `Failed to get updates: ${(error as Error).message}` };
707
- }
708
- },
709
- };
710
1202
 
711
1203
  import crypto from "crypto";
712
1204
  import { getAvailableModelsTool } from "./get-available-models.ts";
@@ -723,10 +1215,10 @@ export function createTools(): Tool[] {
723
1215
  agentFindTool,
724
1216
  agentArchiveTool,
725
1217
  taskDelegateTool,
726
- taskDelegateCodeTool,
1218
+ taskReviseTool,
1219
+ taskListTool,
727
1220
  taskStatusTool,
728
1221
  busPublishTool,
729
1222
  busReadTool,
730
- projectUpdatesTool,
731
1223
  ];
732
1224
  }