@johpaz/hive-sdk 0.1.6 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/CHANGELOG.md +441 -0
  2. package/README.md +21 -3
  3. package/package.json +27 -5
  4. package/packages/core/src/agent/acceptance-checks.ts +9 -9
  5. package/packages/core/src/agent/agent-catalog.ts +82 -25
  6. package/packages/core/src/agent/agent-loop.ts +115 -26
  7. package/packages/core/src/agent/capability-search.ts +2 -2
  8. package/packages/core/src/agent/catalog-selector.ts +4 -4
  9. package/packages/core/src/agent/compaction.ts +30 -10
  10. package/packages/core/src/agent/context-compiler.ts +63 -36
  11. package/packages/core/src/agent/conversation-store.ts +167 -9
  12. package/packages/core/src/agent/curator.ts +16 -7
  13. package/packages/core/src/agent/delegation-runtime.ts +5 -5
  14. package/packages/core/src/agent/goal-runner.ts +9 -9
  15. package/packages/core/src/agent/index.ts +1 -0
  16. package/packages/core/src/agent/llm-client.ts +98 -37
  17. package/packages/core/src/agent/llm-providers/anthropic.ts +4 -4
  18. package/packages/core/src/agent/llm-providers/deepseek.ts +1 -1
  19. package/packages/core/src/agent/llm-providers/gemini.ts +4 -4
  20. package/packages/core/src/agent/llm-providers/groq.ts +1 -1
  21. package/packages/core/src/agent/llm-providers/hiveagents.ts +3 -3
  22. package/packages/core/src/agent/llm-providers/interface.ts +2 -2
  23. package/packages/core/src/agent/llm-providers/kimi.ts +1 -1
  24. package/packages/core/src/agent/llm-providers/minimax.ts +1 -1
  25. package/packages/core/src/agent/llm-providers/mistral.ts +1 -1
  26. package/packages/core/src/agent/llm-providers/modelscope.ts +1 -1
  27. package/packages/core/src/agent/llm-providers/nvidia.ts +40 -1
  28. package/packages/core/src/agent/llm-providers/ollama.ts +4 -4
  29. package/packages/core/src/agent/llm-providers/openai-compat-base.ts +47 -7
  30. package/packages/core/src/agent/llm-providers/openai.ts +1 -1
  31. package/packages/core/src/agent/llm-providers/opencode-go.ts +1 -1
  32. package/packages/core/src/agent/llm-providers/openrouter.ts +1 -1
  33. package/packages/core/src/agent/llm-providers/qwen.ts +1 -1
  34. package/packages/core/src/agent/llm-providers/z-ai.ts +1 -1
  35. package/packages/core/src/agent/mcp-result-normalizer.ts +192 -0
  36. package/packages/core/src/agent/playbook-selector.ts +22 -7
  37. package/packages/core/src/agent/prompt-builder.ts +5 -5
  38. package/packages/core/src/agent/proof-packet.ts +5 -5
  39. package/packages/core/src/agent/providers/index.ts +39 -4
  40. package/packages/core/src/agent/realtime-providers/gemini-live.ts +238 -0
  41. package/packages/core/src/agent/realtime-providers/index.ts +29 -0
  42. package/packages/core/src/agent/realtime-providers/interface.ts +108 -0
  43. package/packages/core/src/agent/reflector.ts +38 -15
  44. package/packages/core/src/agent/run-store.ts +8 -8
  45. package/packages/core/src/agent/service.ts +10 -10
  46. package/packages/core/src/agent/skill-selector.ts +6 -6
  47. package/packages/core/src/agent/thread-id.ts +71 -0
  48. package/packages/core/src/agent/thread-store.ts +293 -0
  49. package/packages/core/src/agent/tool-selector.ts +9 -5
  50. package/packages/core/src/agent/tracer.ts +5 -5
  51. package/packages/core/src/api/createAgent.ts +67 -2
  52. package/packages/core/src/artifacts/index.ts +15 -0
  53. package/packages/core/src/artifacts/store.ts +161 -5
  54. package/packages/core/src/canvas/emitter.ts +2 -2
  55. package/packages/core/src/canvas/index.ts +9 -0
  56. package/packages/core/src/channels/telegram.ts +1 -1
  57. package/packages/core/src/channels/webchat.ts +1 -1
  58. package/packages/core/src/config/loader.ts +14 -5
  59. package/packages/core/src/ethics/EthicsGuard.ts +7 -1
  60. package/packages/core/src/events/agent-bus.ts +3 -3
  61. package/packages/core/src/events/channel-narration.ts +3 -3
  62. package/packages/core/src/events/event-bus.ts +1 -1
  63. package/packages/core/src/events/index.ts +18 -0
  64. package/packages/core/src/events/narration.ts +3 -3
  65. package/packages/core/src/events/tool-narration.ts +4 -0
  66. package/packages/core/src/gateway/channel-notify.ts +103 -6
  67. package/packages/core/src/gateway/delegation-groups.ts +4 -4
  68. package/packages/core/src/gateway/durable-queue.ts +18 -6
  69. package/packages/core/src/gateway/index.ts +3 -0
  70. package/packages/core/src/gateway/job-store.ts +11 -5
  71. package/packages/core/src/gateway/notification-inbox.ts +2 -2
  72. package/packages/core/src/gateway/server.ts +2 -2
  73. package/packages/core/src/harness/executors.ts +493 -0
  74. package/packages/core/src/harness/index.ts +12 -2
  75. package/packages/core/src/hooks/index.ts +203 -0
  76. package/packages/core/src/images/index.ts +161 -0
  77. package/packages/core/src/index.ts +1 -0
  78. package/packages/core/src/mcp/MCPClient.ts +3 -3
  79. package/packages/core/src/mcp/hot-reload.ts +5 -5
  80. package/packages/core/src/mcp/tool-sync.ts +5 -5
  81. package/packages/core/src/mcp/transports/index.ts +2 -2
  82. package/packages/core/src/mcp/transports/sse.ts +1 -1
  83. package/packages/core/src/models/index.ts +36 -0
  84. package/packages/core/src/multimodal/index.ts +2 -2
  85. package/packages/core/src/multimodal/vision-service.ts +51 -19
  86. package/packages/core/src/plugins/loader.ts +4 -1
  87. package/packages/core/src/resilience/circuit-breaker.ts +16 -5
  88. package/packages/core/src/resilience/index.ts +13 -0
  89. package/packages/core/src/resilience/retry.ts +1 -1
  90. package/packages/core/src/scheduler/CronScheduler.ts +54 -27
  91. package/packages/core/src/scheduler/cron/expression.ts +165 -0
  92. package/packages/core/src/scheduler/cron/index.ts +10 -0
  93. package/packages/core/src/scheduler/cron/job.ts +339 -0
  94. package/packages/core/src/scheduler/cron/next-run.ts +121 -0
  95. package/packages/core/src/scheduler/cron/zoned-time.ts +138 -0
  96. package/packages/core/src/scheduler/index.ts +21 -3
  97. package/packages/core/src/scheduler/integration.ts +25 -14
  98. package/packages/core/src/scheduler/types.ts +3 -18
  99. package/packages/core/src/services/agents.ts +268 -0
  100. package/packages/core/src/services/cron.ts +257 -0
  101. package/packages/core/src/services/endpoints.ts +289 -0
  102. package/packages/core/src/services/ethics.ts +107 -0
  103. package/packages/core/src/services/images.ts +212 -0
  104. package/packages/core/src/services/index.ts +112 -0
  105. package/packages/core/src/services/mcp.ts +201 -0
  106. package/packages/core/src/services/memory.ts +133 -0
  107. package/packages/core/src/services/models.ts +179 -0
  108. package/packages/core/src/services/providers.ts +152 -0
  109. package/packages/core/src/services/setup.ts +222 -0
  110. package/packages/core/src/services/skills.ts +241 -0
  111. package/packages/core/src/services/swarms.ts +307 -0
  112. package/packages/core/src/services/tools.ts +106 -0
  113. package/packages/core/src/sessions/index.ts +268 -0
  114. package/packages/core/src/sessions/resolve.ts +108 -0
  115. package/packages/core/src/skills/SkillLoader.ts +8 -1
  116. package/packages/core/src/skills/bundled/artifacts/artifact_reader/SKILL.md +105 -0
  117. package/packages/core/src/skills/bundled/cron_manager/SKILL.md +21 -11
  118. package/packages/core/src/skills/bundled/images/image_editor/SKILL.md +120 -0
  119. package/packages/core/src/skills/bundled/web/browser_automate/SKILL.md +12 -3
  120. package/packages/core/src/skills/bundled/web/browser_scrape/SKILL.md +22 -7
  121. package/packages/core/src/skills/bundled-data.generated.ts +110 -12
  122. package/packages/core/src/storage/bootstrap.ts +107 -12
  123. package/packages/core/src/storage/causal-events.ts +1 -1
  124. package/packages/core/src/storage/collections.ts +138 -2
  125. package/packages/core/src/storage/crypto.ts +35 -4
  126. package/packages/core/src/storage/hive.ts +1 -1
  127. package/packages/core/src/storage/hivedb.ts +10 -1
  128. package/packages/core/src/storage/index.ts +2 -1
  129. package/packages/core/src/storage/onboarding.ts +61 -45
  130. package/packages/core/src/storage/reconcile.ts +11 -6
  131. package/packages/core/src/storage/seed.ts +191 -23
  132. package/packages/core/src/storage/usage.ts +3 -3
  133. package/packages/core/src/swarm/AgentExecutor.ts +2 -2
  134. package/packages/core/src/swarm/Coordinator.ts +8 -8
  135. package/packages/core/src/swarm/EventBridge.ts +2 -2
  136. package/packages/core/src/swarm/RoleSwarm.ts +234 -0
  137. package/packages/core/src/swarm/TaskGraph.ts +2 -2
  138. package/packages/core/src/swarm/index.ts +7 -0
  139. package/packages/core/src/swarm/presets/HiveLearnPreset.ts +2 -2
  140. package/packages/core/src/swarm/presets/ResearchPreset.ts +2 -2
  141. package/packages/core/src/swarm/strategies/ParallelStrategy.ts +1 -1
  142. package/packages/core/src/swarm/strategies/PriorityStrategy.ts +3 -3
  143. package/packages/core/src/swarm/types.ts +3 -18
  144. package/packages/core/src/tool-runtime/embedded-worker.generated.ts +21 -0
  145. package/packages/core/src/tool-runtime/index.ts +129 -14
  146. package/packages/core/src/tools/ToolExecutor.ts +7 -3
  147. package/packages/core/src/tools/agents/index.ts +18 -60
  148. package/packages/core/src/tools/cli/index.ts +55 -0
  149. package/packages/core/src/tools/core/index.ts +52 -4
  150. package/packages/core/src/tools/cron/index.ts +8 -8
  151. package/packages/core/src/tools/images/index.ts +130 -0
  152. package/packages/core/src/tools/index.ts +14 -1
  153. package/packages/core/src/tools/office/office-escribir-xlsx.ts +2 -1
  154. package/packages/core/src/tools/office/office-leer-xlsx.ts +2 -1
  155. package/packages/core/src/tools/office/xlsx-loader.ts +19 -0
  156. package/packages/core/src/tools/web/artifact-inspect.ts +2 -2
  157. package/packages/core/src/tools/web/artifact-read.ts +162 -0
  158. package/packages/core/src/tools/web/browser-backend.ts +141 -44
  159. package/packages/core/src/tools/web/browser-click.ts +2 -2
  160. package/packages/core/src/tools/web/browser-extract.ts +2 -2
  161. package/packages/core/src/tools/web/browser-navigate.ts +2 -2
  162. package/packages/core/src/tools/web/browser-screenshot.ts +12 -5
  163. package/packages/core/src/tools/web/browser-script.ts +2 -2
  164. package/packages/core/src/tools/web/browser-service.ts +63 -384
  165. package/packages/core/src/tools/web/browser-session.ts +125 -0
  166. package/packages/core/src/tools/web/browser-type.ts +2 -2
  167. package/packages/core/src/tools/web/browser-wait.ts +2 -2
  168. package/packages/core/src/tools/web/computer-use.ts +553 -0
  169. package/packages/core/src/tools/web/index.ts +8 -1
  170. package/packages/core/src/tools/web/webview-backend.ts +460 -21
  171. package/packages/core/src/utils/index.ts +1 -0
  172. package/packages/core/src/utils/logger.ts +12 -4
  173. package/packages/core/src/utils/redact-binary.ts +17 -0
  174. package/packages/core/src/utils/toon.ts +1 -1
  175. package/packages/core/src/voice/index.ts +6 -6
  176. package/bun.lock +0 -859
  177. package/bunfig.toml +0 -9
  178. package/docs/API-AGENTS.md +0 -367
  179. package/docs/API-CONTEXT-COMPILER.md +0 -249
  180. package/docs/API-DAG-SCHEDULER.md +0 -273
  181. package/docs/API-TOOLS-SKILLS-CHANNELS.md +0 -446
  182. package/docs/API-WORKERS-EVENTS.md +0 -299
  183. package/docs/HIVE-HARNESS.md +0 -113
  184. package/docs/INDEX.md +0 -190
  185. package/docs/TEMPLATE-HIVE-APP.md +0 -360
  186. package/packages/cli/package.json +0 -17
  187. package/packages/cli/src/commands/create-app.test.ts +0 -180
  188. package/packages/core/package.json +0 -70
  189. package/packages/core/src/api/createAgent.test.ts +0 -160
  190. package/packages/core/src/canvas/canvas.test.ts +0 -36
  191. package/packages/core/src/channels/channels.test.ts +0 -18
  192. package/packages/core/src/ethics/EthicsGuard.test.ts +0 -108
  193. package/packages/core/src/gateway/gateway.test.ts +0 -38
  194. package/packages/core/src/memory/Scratchpad.test.ts +0 -68
  195. package/packages/core/src/scheduler/scheduler.test.ts +0 -15
  196. package/packages/core/src/skills/skills.test.ts +0 -62
  197. package/packages/core/src/swarm/swarm.test.ts +0 -24
  198. package/packages/core/src/tool-runtime/tool-runtime.test.ts +0 -99
  199. package/packages/core/src/tools/ToolRegistry.test.ts +0 -98
  200. package/packages/core/src/tools/api/api-request.test.ts +0 -164
  201. package/packages/core/src/tools/web/browser-service.test.ts +0 -83
  202. package/packages/core/src/workers/workers.test.ts +0 -41
  203. package/scripts/bump-version.ts +0 -248
  204. package/scripts/generate-skill-bundle.ts +0 -108
  205. package/test/acceptance-checks.test.ts +0 -403
  206. package/test/agent-loop-terminal-synthesis.test.ts +0 -32
  207. package/test/browser-backend.test.ts +0 -308
  208. package/test/catalog-agents-stay-enabled.test.ts +0 -117
  209. package/test/causal-events.test.ts +0 -117
  210. package/test/compaction.test.ts +0 -105
  211. package/test/context-compiler.test.ts +0 -269
  212. package/test/curator.test.ts +0 -130
  213. package/test/durable-queue.test.ts +0 -114
  214. package/test/harness-barrel.test.ts +0 -64
  215. package/test/hive-helpers.test.ts +0 -130
  216. package/test/hivedb-search.test.ts +0 -189
  217. package/test/internal-turns.test.ts +0 -166
  218. package/test/job-idempotency.test.ts +0 -68
  219. package/test/job-retry-backoff.test.ts +0 -184
  220. package/test/job-store.test.ts +0 -381
  221. package/test/llm-retry.test.ts +0 -97
  222. package/test/memory-perf.test.ts +0 -774
  223. package/test/minimal-loadout.test.ts +0 -78
  224. package/test/model-catalog.test.ts +0 -105
  225. package/test/preload.ts +0 -12
  226. package/test/reflector.test.ts +0 -320
  227. package/test/retention-cap.test.ts +0 -91
  228. package/test/retired-capabilities-pruned.test.ts +0 -192
  229. package/test/run-store.test.ts +0 -355
  230. package/test/scratchpad.test.ts +0 -74
  231. package/test/secrets-durability.test.ts +0 -119
  232. package/test/seed-model-reseed.test.ts +0 -155
  233. package/test/setup-agent-seed.test.ts +0 -264
  234. package/test/tool-inventory.test.ts +0 -65
  235. package/test/tool-runtime.test.ts +0 -258
  236. package/test/tool-selector-runtime-tools.test.ts +0 -117
  237. package/test/toon.test.ts +0 -429
  238. package/tsconfig.json +0 -42
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Enjambre por roles — orquestador y trabajadores, con estrategia declarada.
3
+ *
4
+ * Es la tercera forma de armar un enjambre en Hive, y convive con las otras dos
5
+ * a propósito:
6
+ *
7
+ * - **Delegación por catálogo** (`agent/delegation-runtime.ts`, `task_delegate`):
8
+ * el coordinador decide a quién delegar en tiempo real, con criterios de
9
+ * aceptación y proof packets. El modelo elige la forma del trabajo.
10
+ * - **DAG de tareas** (`Coordinator.ts`): el grafo se conoce de antemano y el
11
+ * scheduler resuelve dependencias y concurrencia.
12
+ * - **Roles (esto)**: el enjambre es *configuración persistida* — una lista de
13
+ * agentes con rol y orden, y una estrategia. Quien lo define es un usuario en
14
+ * una UI, no el modelo ni el programador.
15
+ *
16
+ * La tercera no se puede expresar con las otras dos: un DAG exige conocer las
17
+ * aristas, y acá la topología es la estrategia. Por eso existe.
18
+ *
19
+ * Este módulo no persiste nada: `onMessage` es el punto donde el consumidor
20
+ * guarda cada paso donde quiera (Postgres, HiveDB, un log).
21
+ */
22
+
23
+ import { runAgentIsolated } from "../agent/agent-loop.ts"
24
+ import type { ProviderCredentials } from "../agent/llm-client.ts"
25
+ import { logger } from "../utils/logger.ts"
26
+
27
+ const log = logger.child("role-swarm")
28
+
29
+ export type SwarmStrategy = "sequential" | "parallel" | "hierarchical"
30
+
31
+ export interface RoleAgent {
32
+ agentId: string
33
+ role: "orchestrator" | "worker"
34
+ /** Orden de ejecución en la estrategia secuencial. */
35
+ orderIndex?: number
36
+ }
37
+
38
+ /** Un paso del enjambre, tal como se lo entrega a `onMessage`. */
39
+ export interface SwarmMessage {
40
+ agentId: string
41
+ role: "user" | "assistant"
42
+ content: string
43
+ stepIndex: number
44
+ }
45
+
46
+ /** Cómo se invoca a un agente. Se puede reemplazar para tests o para envolverlo. */
47
+ export type AgentInvoker = (input: {
48
+ agentId: string
49
+ message: string
50
+ threadId: string
51
+ channel?: string
52
+ credentials?: ProviderCredentials
53
+ signal?: AbortSignal
54
+ }) => Promise<string>
55
+
56
+ export const defaultInvoker: AgentInvoker = async (input) =>
57
+ runAgentIsolated({
58
+ agentId: input.agentId,
59
+ taskDescription: input.message,
60
+ threadId: input.threadId,
61
+ channel: input.channel,
62
+ credentials: input.credentials,
63
+ signal: input.signal,
64
+ })
65
+
66
+ export interface RoleSwarmOptions {
67
+ /** Los agentes del enjambre, con su rol. */
68
+ agents: RoleAgent[]
69
+ strategy: SwarmStrategy
70
+ input: string
71
+ /** Identifica la corrida; también es el threadId del orquestador. */
72
+ runId: string
73
+ channel?: string
74
+ /** Requerido por la estrategia jerárquica si ningún agente tiene rol orchestrator. */
75
+ orchestratorAgentId?: string
76
+ /**
77
+ * Tope de delegaciones en la estrategia jerárquica.
78
+ *
79
+ * Sin esto, un orquestador que siga emitiendo `DELEGATE:` no termina nunca:
80
+ * cada vuelta es una llamada al modelo, así que un bucle no es sólo lento, es
81
+ * caro. Al agotarse se devuelve lo último que dijo el orquestador.
82
+ */
83
+ maxDelegations?: number
84
+ /** Credenciales del inquilino, propagadas a cada agente. */
85
+ credentials?: ProviderCredentials
86
+ signal?: AbortSignal
87
+ /** Se llama en cada paso; acá persiste el consumidor si quiere. */
88
+ onMessage?: (message: SwarmMessage) => void | Promise<void>
89
+ /** Reemplaza la invocación real (tests, instrumentación). */
90
+ invoke?: AgentInvoker
91
+ }
92
+
93
+ export interface RoleSwarmResult {
94
+ output: string
95
+ /** Llamadas al modelo, para contabilidad de uso. */
96
+ agentCalls: number
97
+ /** Delegaciones efectuadas (sólo jerárquica). */
98
+ delegations: number
99
+ /** true si la jerárquica se cortó por `maxDelegations`. */
100
+ truncated: boolean
101
+ }
102
+
103
+ const DEFAULT_MAX_DELEGATIONS = 10
104
+
105
+ /**
106
+ * `DELEGATE:<agente>:<subtarea>` y `FINAL:<respuesta>`.
107
+ *
108
+ * El `[\s\S]` en lugar de `.` es deliberado: una subtarea de varias líneas es lo
109
+ * normal, y con `.` se cortaba en el primer salto de línea y el worker recibía
110
+ * una instrucción truncada.
111
+ */
112
+ const DELEGATE_RE = /DELEGATE:\s*([^\s:]+)\s*:\s*([\s\S]+?)(?=\nDELEGATE:|\nFINAL:|$)/
113
+ const FINAL_RE = /FINAL:\s*([\s\S]+)/
114
+
115
+ function orderedWorkers(agents: RoleAgent[]): RoleAgent[] {
116
+ return [...agents].sort((a, b) => (a.orderIndex ?? 0) - (b.orderIndex ?? 0))
117
+ }
118
+
119
+ export async function runRoleSwarm(opts: RoleSwarmOptions): Promise<RoleSwarmResult> {
120
+ const invoke = opts.invoke ?? defaultInvoker
121
+ const emit = async (m: SwarmMessage) => { await opts.onMessage?.(m) }
122
+ let agentCalls = 0
123
+
124
+ const call = async (agentId: string, message: string, threadId: string) => {
125
+ agentCalls++
126
+ return invoke({
127
+ agentId,
128
+ message,
129
+ threadId,
130
+ channel: opts.channel,
131
+ credentials: opts.credentials,
132
+ signal: opts.signal,
133
+ })
134
+ }
135
+
136
+ if (opts.agents.length === 0) {
137
+ throw new Error("Un enjambre necesita al menos un agente")
138
+ }
139
+
140
+ // ── Secuencial: la salida de cada uno es la entrada del siguiente ──────────
141
+ if (opts.strategy === "sequential") {
142
+ const agents = orderedWorkers(opts.agents)
143
+ let context = opts.input
144
+ for (const [i, agent] of agents.entries()) {
145
+ await emit({ agentId: agent.agentId, role: "user", content: context, stepIndex: i })
146
+ context = await call(agent.agentId, context, opts.runId)
147
+ await emit({ agentId: agent.agentId, role: "assistant", content: context, stepIndex: i })
148
+ }
149
+ return { output: context, agentCalls, delegations: 0, truncated: false }
150
+ }
151
+
152
+ // ── Paralelo: todos ven la misma entrada; se concatenan las salidas ────────
153
+ if (opts.strategy === "parallel") {
154
+ const agents = orderedWorkers(opts.agents)
155
+ const outputs = await Promise.all(
156
+ agents.map(async (agent, i) => {
157
+ await emit({ agentId: agent.agentId, role: "user", content: opts.input, stepIndex: i })
158
+ // Hilo propio por agente: comparten la entrada, no la conversación.
159
+ const out = await call(agent.agentId, opts.input, `${opts.runId}-${agent.agentId}`)
160
+ await emit({ agentId: agent.agentId, role: "assistant", content: out, stepIndex: i })
161
+ return out
162
+ }),
163
+ )
164
+ return { output: outputs.join("\n\n---\n\n"), agentCalls, delegations: 0, truncated: false }
165
+ }
166
+
167
+ // ── Jerárquica: el orquestador delega hasta dar una respuesta final ────────
168
+ const orchestratorId =
169
+ opts.orchestratorAgentId ?? opts.agents.find((a) => a.role === "orchestrator")?.agentId
170
+ if (!orchestratorId) {
171
+ throw new Error("La estrategia jerárquica necesita un agente con rol orchestrator")
172
+ }
173
+
174
+ const workers = opts.agents.filter((a) => a.role === "worker")
175
+ const workerIds = new Set(workers.map((w) => w.agentId))
176
+ if (workerIds.size === 0) {
177
+ throw new Error("La estrategia jerárquica necesita al menos un agente con rol worker")
178
+ }
179
+
180
+ const protocol = [
181
+ `Eres el orquestador de un enjambre de agentes.`,
182
+ `Agentes disponibles: ${[...workerIds].join(", ")}.`,
183
+ `Para delegar usa el formato: DELEGATE:<agent_id>:<subtarea>`,
184
+ `Cuando hayas terminado responde con: FINAL:<respuesta>`,
185
+ ].join("\n")
186
+
187
+ const firstInput = `${protocol}\n\nTarea: ${opts.input}`
188
+ await emit({ agentId: orchestratorId, role: "user", content: firstInput, stepIndex: 0 })
189
+ let output = await call(orchestratorId, firstInput, opts.runId)
190
+ await emit({ agentId: orchestratorId, role: "assistant", content: output, stepIndex: 0 })
191
+
192
+ const maxDelegations = opts.maxDelegations ?? DEFAULT_MAX_DELEGATIONS
193
+ let delegations = 0
194
+ let step = 1
195
+ let truncated = false
196
+
197
+ while (true) {
198
+ const match = output.match(DELEGATE_RE)
199
+ if (!match) break
200
+
201
+ const delegateId = match[1]!.trim()
202
+ const subtask = match[2]!.trim()
203
+
204
+ if (!workerIds.has(delegateId)) {
205
+ log.warn(`el orquestador delegó a "${delegateId}", que no está en el enjambre — se corta`)
206
+ break
207
+ }
208
+ if (delegations >= maxDelegations) {
209
+ log.warn(`tope de ${maxDelegations} delegaciones alcanzado — se corta con lo último del orquestador`)
210
+ truncated = true
211
+ break
212
+ }
213
+
214
+ await emit({ agentId: delegateId, role: "user", content: subtask, stepIndex: step })
215
+ const workerOutput = await call(delegateId, subtask, `${opts.runId}-${delegateId}`)
216
+ await emit({ agentId: delegateId, role: "assistant", content: workerOutput, stepIndex: step })
217
+ delegations++
218
+ step++
219
+
220
+ const followUp = `Resultado de ${delegateId}: ${workerOutput}\n\nContinúa o responde con FINAL:<respuesta>`
221
+ await emit({ agentId: orchestratorId, role: "user", content: followUp, stepIndex: step })
222
+ output = await call(orchestratorId, followUp, opts.runId)
223
+ await emit({ agentId: orchestratorId, role: "assistant", content: output, stepIndex: step })
224
+ step++
225
+ }
226
+
227
+ const final = output.match(FINAL_RE)
228
+ return {
229
+ output: final ? final[1]!.trim() : output,
230
+ agentCalls,
231
+ delegations,
232
+ truncated,
233
+ }
234
+ }
@@ -8,8 +8,8 @@
8
8
  * 3. Provide runtime queries: ready nodes, overall progress
9
9
  */
10
10
 
11
- import { TaskNode, type TaskNodeConfig } from "./TaskNode"
12
- import { CyclicDependencyError } from "./errors"
11
+ import { TaskNode, type TaskNodeConfig } from "./TaskNode.ts"
12
+ import { CyclicDependencyError } from "./errors.ts"
13
13
 
14
14
  export class TaskGraph {
15
15
  readonly nodes: Map<string, TaskNode>
@@ -31,3 +31,10 @@ export { createHiveLearnGraph } from "./presets/index.ts";
31
31
  export type { HiveLearnAgentIds, HiveLearnInput } from "./presets/index.ts";
32
32
  export { createResearchGraph } from "./presets/index.ts";
33
33
  export type { ResearchAgentIds } from "./presets/index.ts";
34
+
35
+ // ─── Enjambre por roles (orquestador/trabajadores) ───────────────────────────
36
+ export { runRoleSwarm, defaultInvoker } from "./RoleSwarm.ts";
37
+ export type {
38
+ SwarmStrategy, RoleAgent, SwarmMessage, AgentInvoker,
39
+ RoleSwarmOptions, RoleSwarmResult,
40
+ } from "./RoleSwarm.ts";
@@ -16,8 +16,8 @@
16
16
  * Savings vs sequential: ~38% (~75s)
17
17
  */
18
18
 
19
- import { TaskGraph } from "../TaskGraph"
20
- import type { TaskNodeConfig } from "../TaskNode"
19
+ import { TaskGraph } from "../TaskGraph.ts"
20
+ import type { TaskNodeConfig } from "../TaskNode.ts"
21
21
 
22
22
  export interface HiveLearnAgentIds {
23
23
  curriculum: string
@@ -18,8 +18,8 @@
18
18
  * await scheduler.execute(graph, { projectId, coordinatorId })
19
19
  */
20
20
 
21
- import { TaskGraph } from "../TaskGraph"
22
- import type { TaskNodeConfig } from "../TaskNode"
21
+ import { TaskGraph } from "../TaskGraph.ts"
22
+ import type { TaskNodeConfig } from "../TaskNode.ts"
23
23
 
24
24
  export interface ResearchAgentIds {
25
25
  research: string
@@ -6,7 +6,7 @@
6
6
  * otherwise they wait in the queue.
7
7
  */
8
8
 
9
- import { TaskNode } from "../TaskNode"
9
+ import { TaskNode } from "../TaskNode.ts"
10
10
 
11
11
  export interface ExecutionStrategy {
12
12
  pick(queue: TaskNode[]): TaskNode | undefined
@@ -5,9 +5,9 @@
5
5
  * when slots are limited. Within same effective priority, FIFO order applies.
6
6
  */
7
7
 
8
- import { TaskNode } from "../TaskNode"
9
- import { TaskGraph } from "../TaskGraph"
10
- import type { ExecutionStrategy } from "./ParallelStrategy"
8
+ import { TaskNode } from "../TaskNode.ts"
9
+ import { TaskGraph } from "../TaskGraph.ts"
10
+ import type { ExecutionStrategy } from "./ParallelStrategy.ts"
11
11
 
12
12
  export class PriorityStrategy implements ExecutionStrategy {
13
13
  private criticalPathSet = new Set<string>()
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * Hive Scheduler - Type Definitions
3
3
  *
4
- * Type interfaces for the Croner-based scheduling system.
4
+ * Type interfaces for the scheduling system.
5
5
  * All names use "CronJob" terminology (formerly ScheduledTask).
6
6
  */
7
7
 
8
8
  import type { Database } from "bun:sqlite";
9
- import type { Cron } from "croner";
9
+ import type { Cron } from "../scheduler/cron/index.ts";
10
10
 
11
11
  /**
12
12
  * Task type: recurring uses cron expression, one_shot uses fire_at
@@ -141,24 +141,9 @@ export interface CronJobExecutionResult {
141
141
  }
142
142
 
143
143
  /**
144
- * Internal job wrapper holding Croner instance and metadata
144
+ * Internal job wrapper holding the scheduled job and its metadata
145
145
  */
146
146
  export interface CronJobEntry {
147
147
  job: CronJob;
148
148
  cron: Cron;
149
149
  }
150
-
151
- /**
152
- * Options for Croner job creation
153
- */
154
- export interface CronerOptions {
155
- timezone: string;
156
- protect: boolean;
157
- catch: boolean | ((error: Error) => void);
158
- name: string;
159
- maxRuns?: number;
160
- interval?: number;
161
- startAt?: string;
162
- stopAt?: string;
163
- domAndDow?: boolean;
164
- }
@@ -0,0 +1,21 @@
1
+ // Auto-generado — NO EDITAR.
2
+ //
3
+ // En este paquete el valor es SIEMPRE null, y así debe quedarse: el SDK se
4
+ // publica como fuente, no como ejecutable standalone, así que el worker se
5
+ // resuelve desde disco (tool-worker.ts al lado de este archivo, o
6
+ // dist/tool-worker.js junto al bundle).
7
+ //
8
+ // El archivo existe porque el gateway de hive sí compila un binario, y ahí su
9
+ // `scripts/build-gateway.ts` reescribe este stub antes de compilar con:
10
+ //
11
+ // import workerFile from "./tool-worker.generated.js" with { type: "file" }
12
+ // export const embeddedToolWorkerPath: string | null = workerFile
13
+ //
14
+ // Ese `with { type: "file" }` es lo que mete el bundle del worker dentro del
15
+ // ejecutable, porque `new Worker(new URL("./tool-worker.ts", import.meta.url))`
16
+ // NO se embebe solo: el path se resuelve en runtime y el bundler no lo ve.
17
+ // Sin eso la app de escritorio se instalaba sin worker y cualquier turno con
18
+ // más de una tool call moría con "Tool worker entry not found" (v1.0.3 y
19
+ // anteriores). Mantener el símbolo acá deja que `tool-runtime/index.ts` sea el
20
+ // mismo archivo en los dos repos.
21
+ export const embeddedToolWorkerPath: string | null = null
@@ -4,6 +4,9 @@ import { dirname, join } from "node:path"
4
4
  import { availableParallelism } from "node:os"
5
5
  import type { Config } from "../config/loader.ts"
6
6
  import { loadConfig } from "../config/loader.ts"
7
+ import { logger } from "../utils/logger.ts"
8
+ import { embeddedToolWorkerPath } from "./embedded-worker.generated.ts"
9
+ import { hasHooks, runBeforeToolCall, runAfterToolCall } from "../hooks/index.ts"
7
10
 
8
11
  export type ToolCallLike = {
9
12
  id: string
@@ -105,7 +108,19 @@ type WorkerSlot = {
105
108
  job?: QueuedJob
106
109
  }
107
110
 
108
- function resolveWorkerEntry(): string {
111
+ let cachedWorkerEntry: string | null | undefined
112
+
113
+ /**
114
+ * Locate the tool worker entry, or null when this build ships without one.
115
+ *
116
+ * Returning null instead of throwing is deliberate: workers are an
117
+ * optimization, and a packaging gap must never take a whole turn down with it
118
+ * (it did until v1.0.3 — every desktop install failed each multi-tool turn
119
+ * with "Tool worker entry not found"). The caller degrades to the main thread.
120
+ */
121
+ function resolveWorkerEntry(): string | null {
122
+ if (cachedWorkerEntry !== undefined) return cachedWorkerEntry
123
+
109
124
  const candidates = [
110
125
  new URL("./tool-worker.js", import.meta.url),
111
126
  new URL("./tool-worker.ts", import.meta.url),
@@ -115,7 +130,8 @@ function resolveWorkerEntry(): string {
115
130
 
116
131
  for (const candidate of candidates) {
117
132
  if (existsSync(fileURLToPath(candidate))) {
118
- return candidate.href
133
+ cachedWorkerEntry = candidate.href
134
+ return cachedWorkerEntry
119
135
  }
120
136
  }
121
137
 
@@ -135,13 +151,25 @@ function resolveWorkerEntry(): string {
135
151
 
136
152
  for (const filePath of fallbacks) {
137
153
  if (existsSync(filePath)) {
138
- return filePath
154
+ cachedWorkerEntry = filePath
155
+ return cachedWorkerEntry
139
156
  }
140
157
  }
141
158
 
142
- throw new Error(
143
- `Tool worker entry not found. Tried: ${[...candidates.map((candidate) => fileURLToPath(candidate)), ...fallbacks].join(", ")}`
159
+ // Standalone executable: the worker was embedded at compile time and lives in
160
+ // the virtual bunfs, where existsSync() reports false so it is taken on
161
+ // trust. It is only ever set by scripts/build-gateway.ts.
162
+ if (embeddedToolWorkerPath) {
163
+ cachedWorkerEntry = embeddedToolWorkerPath
164
+ return cachedWorkerEntry
165
+ }
166
+
167
+ logger.warn(
168
+ "[tool-runtime] No tool worker entry found — running tool batches on the main thread (sequentially)",
169
+ { tried: [...candidates.map((candidate) => fileURLToPath(candidate)), ...fallbacks] }
144
170
  )
171
+ cachedWorkerEntry = null
172
+ return null
145
173
  }
146
174
 
147
175
  function serializeError(error: unknown): SerializedError {
@@ -187,6 +215,7 @@ const DEFAULT_MAIN_THREAD_TOOL_NAMES = new Set([
187
215
  "browser_navigate",
188
216
  "browser_screenshot",
189
217
  "artifact_inspect",
218
+ "artifact_read",
190
219
  "browser_click",
191
220
  "browser_type",
192
221
  "browser_extract",
@@ -255,10 +284,12 @@ class ToolWorkerPool {
255
284
  private workers: WorkerSlot[] = []
256
285
  private queue: QueuedJob[] = []
257
286
  private readonly maxWorkers: number
287
+ private readonly workerEntry: string
258
288
  private disposed = false
259
289
 
260
- constructor(maxWorkers: number) {
290
+ constructor(maxWorkers: number, workerEntry: string) {
261
291
  this.maxWorkers = Math.max(1, maxWorkers)
292
+ this.workerEntry = workerEntry
262
293
  }
263
294
 
264
295
  execute(job: Omit<QueuedJob, "resolve" | "settled" | "startedAt">): Promise<ToolBatchResult> {
@@ -385,7 +416,7 @@ class ToolWorkerPool {
385
416
  }
386
417
 
387
418
  private createSlot(): WorkerSlot {
388
- const worker = new Worker(resolveWorkerEntry(), { type: "module" })
419
+ const worker = new Worker(this.workerEntry, { type: "module" })
389
420
  const slot: WorkerSlot = { worker, busy: false }
390
421
 
391
422
  worker.onmessage = (event: MessageEvent<WorkerMessage>) => {
@@ -500,11 +531,15 @@ class ToolWorkerPool {
500
531
  job.resolve(result)
501
532
 
502
533
  if (restart) {
534
+ // Un worker que abortó o murió se descarta, y el reemplazo lo crea
535
+ // `drain()` solo si queda trabajo. Antes se levantaba uno nuevo en el
536
+ // acto: sobre un aborto —el caso típico al terminar un turno o una
537
+ // suite— eso deja un worker recién arrancado que nadie va a usar y que
538
+ // hay que terminar a mitad del arranque. Bun se cae con SIGSEGV al
539
+ // cerrar el proceso en ese estado (CI: workers_spawned 13, terminated 11).
503
540
  slot.worker.terminate()
504
541
  const index = this.workers.indexOf(slot)
505
- if (index >= 0) {
506
- this.workers[index] = this.createSlot()
507
- }
542
+ if (index >= 0) this.workers.splice(index, 1)
508
543
  } else {
509
544
  slot.busy = false
510
545
  slot.job = undefined
@@ -550,15 +585,90 @@ function resolveToolTimeout(
550
585
  return baseTimeoutMs
551
586
  }
552
587
 
553
- function getPool(maxWorkers: number): ToolWorkerPool {
588
+ function getPool(maxWorkers: number): ToolWorkerPool | null {
589
+ const workerEntry = resolveWorkerEntry()
590
+ if (!workerEntry) return null
591
+
554
592
  if (!sharedPool || sharedPoolSize !== maxWorkers) {
555
- sharedPool = new ToolWorkerPool(maxWorkers)
593
+ sharedPool = new ToolWorkerPool(maxWorkers, workerEntry)
556
594
  sharedPoolSize = maxWorkers
557
595
  }
558
596
  return sharedPool
559
597
  }
560
598
 
599
+ /**
600
+ * Ejecuta un lote de tool calls, aplicando los hooks alrededor.
601
+ *
602
+ * Los hooks se resuelven acá y no dentro del despacho porque hay dos caminos
603
+ * —hilo principal y pool de workers— con puntos de retorno distintos.
604
+ * Engancharlos abajo dejaría la mitad de las llamadas sin revisar, que en un
605
+ * hook de política es peor que no tenerlo.
606
+ *
607
+ * Una tool bloqueada **no se ejecuta**: se saca del lote y su lugar se rellena
608
+ * con el motivo, para que el modelo sepa por qué no se hizo en vez de
609
+ * reintentar a ciegas.
610
+ */
561
611
  export async function executeToolBatch(options: ExecuteToolBatchOptions): Promise<ToolBatchResult[]> {
612
+ const bloqueadas = new Map<string, string>()
613
+
614
+ if (hasHooks("beforeToolCall")) {
615
+ for (const toolCall of options.toolCalls) {
616
+ const args = typeof toolCall.function.arguments === "string"
617
+ ? (() => { try { return JSON.parse(toolCall.function.arguments) } catch { return {} } })()
618
+ : (toolCall.function.arguments ?? {})
619
+
620
+ const motivo = await runBeforeToolCall({
621
+ toolName: toolCall.function.name,
622
+ args: args as Record<string, unknown>,
623
+ agentId: options.toolConfig?.agent_id as string | undefined,
624
+ userId: options.toolConfig?.user_id as string | undefined,
625
+ threadId: options.toolConfig?.thread_id as string | undefined,
626
+ })
627
+ if (motivo) bloqueadas.set(toolCall.id, motivo)
628
+ }
629
+ }
630
+
631
+ const permitidas = options.toolCalls.filter((tc) => !bloqueadas.has(tc.id))
632
+ const ejecutadas = permitidas.length > 0
633
+ ? await executeToolBatchInner({ ...options, toolCalls: permitidas })
634
+ : []
635
+
636
+ // Se reconstruye el orden original: el modelo espera una respuesta por cada
637
+ // llamada que hizo, en el orden en que las hizo.
638
+ const porId = new Map(ejecutadas.map((r) => [r.toolCall.id, r]))
639
+ const resultados = options.toolCalls.map((toolCall) => {
640
+ const bloqueo = bloqueadas.get(toolCall.id)
641
+ if (bloqueo) {
642
+ return {
643
+ toolCall,
644
+ toolName: toolCall.function.name,
645
+ result: toolErrorResult(toolCall.function.name, bloqueo),
646
+ ok: false,
647
+ durationMs: 0,
648
+ } as ToolBatchResult
649
+ }
650
+ return porId.get(toolCall.id)!
651
+ })
652
+
653
+ if (hasHooks("afterToolCall")) {
654
+ for (const r of resultados) {
655
+ await runAfterToolCall({
656
+ toolName: r.toolName,
657
+ args: {},
658
+ result: r.result,
659
+ ok: r.ok,
660
+ durationMs: r.durationMs,
661
+ agentId: options.toolConfig?.agent_id as string | undefined,
662
+ userId: options.toolConfig?.user_id as string | undefined,
663
+ threadId: options.toolConfig?.thread_id as string | undefined,
664
+ })
665
+ }
666
+ }
667
+
668
+ return resultados
669
+ }
670
+
671
+ async function executeToolBatchInner(options: ExecuteToolBatchOptions): Promise<ToolBatchResult[]> {
562
672
  const runtimeConfig = resolveRuntimeConfig(options.workerPool)
563
673
  const hiveConfig = options.hiveConfig ?? loadConfig()
564
674
  const mainThreadToolNames = [
@@ -578,7 +688,13 @@ export async function executeToolBatch(options: ExecuteToolBatchOptions): Promis
578
688
  }))
579
689
  }
580
690
 
581
- if (!runtimeConfig.enabled || !runtimeConfig.parallelToolCalls || options.toolCalls.length <= 1) {
691
+ // A null pool means workers are disabled, unnecessary (single call), or
692
+ // unavailable in this build — all three degrade to the main thread.
693
+ const pool = runtimeConfig.enabled && runtimeConfig.parallelToolCalls && options.toolCalls.length > 1
694
+ ? getPool(runtimeConfig.maxWorkers)
695
+ : null
696
+
697
+ if (!pool) {
582
698
  const results: ToolBatchResult[] = []
583
699
  for (const toolCall of options.toolCalls) {
584
700
  const startedAt = performance.now()
@@ -605,7 +721,6 @@ export async function executeToolBatch(options: ExecuteToolBatchOptions): Promis
605
721
  return results
606
722
  }
607
723
 
608
- const pool = getPool(runtimeConfig.maxWorkers)
609
724
  const batchId = crypto.randomUUID()
610
725
  const abortHandler = () => pool.abortBatch(batchId, "Tool execution aborted")
611
726
  options.signal?.addEventListener("abort", abortHandler, { once: true })
@@ -1,5 +1,5 @@
1
- import type { ToolDefinition } from "./ToolRegistry";
2
- import type { ToolRegistry } from "./ToolRegistry";
1
+ import type { ToolDefinition } from "./ToolRegistry.ts";
2
+ import type { ToolRegistry } from "./ToolRegistry.ts";
3
3
 
4
4
  export interface ToolExecutionResult {
5
5
  toolName: string;
@@ -10,7 +10,11 @@ export interface ToolExecutionResult {
10
10
  }
11
11
 
12
12
  export class ToolExecutor {
13
- constructor(private registry: ToolRegistry) {}
13
+ private registry: ToolRegistry;
14
+
15
+ constructor(registry: ToolRegistry) {
16
+ this.registry = registry;
17
+ }
14
18
 
15
19
  async execute(
16
20
  name: string,