@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.
- package/CHANGELOG.md +441 -0
- package/README.md +21 -3
- package/package.json +27 -5
- package/packages/core/src/agent/acceptance-checks.ts +9 -9
- package/packages/core/src/agent/agent-catalog.ts +82 -25
- package/packages/core/src/agent/agent-loop.ts +115 -26
- package/packages/core/src/agent/capability-search.ts +2 -2
- package/packages/core/src/agent/catalog-selector.ts +4 -4
- package/packages/core/src/agent/compaction.ts +30 -10
- package/packages/core/src/agent/context-compiler.ts +63 -36
- package/packages/core/src/agent/conversation-store.ts +167 -9
- package/packages/core/src/agent/curator.ts +16 -7
- package/packages/core/src/agent/delegation-runtime.ts +5 -5
- package/packages/core/src/agent/goal-runner.ts +9 -9
- package/packages/core/src/agent/index.ts +1 -0
- package/packages/core/src/agent/llm-client.ts +98 -37
- package/packages/core/src/agent/llm-providers/anthropic.ts +4 -4
- package/packages/core/src/agent/llm-providers/deepseek.ts +1 -1
- package/packages/core/src/agent/llm-providers/gemini.ts +4 -4
- package/packages/core/src/agent/llm-providers/groq.ts +1 -1
- package/packages/core/src/agent/llm-providers/hiveagents.ts +3 -3
- package/packages/core/src/agent/llm-providers/interface.ts +2 -2
- package/packages/core/src/agent/llm-providers/kimi.ts +1 -1
- package/packages/core/src/agent/llm-providers/minimax.ts +1 -1
- package/packages/core/src/agent/llm-providers/mistral.ts +1 -1
- package/packages/core/src/agent/llm-providers/modelscope.ts +1 -1
- package/packages/core/src/agent/llm-providers/nvidia.ts +40 -1
- package/packages/core/src/agent/llm-providers/ollama.ts +4 -4
- package/packages/core/src/agent/llm-providers/openai-compat-base.ts +47 -7
- package/packages/core/src/agent/llm-providers/openai.ts +1 -1
- package/packages/core/src/agent/llm-providers/opencode-go.ts +1 -1
- package/packages/core/src/agent/llm-providers/openrouter.ts +1 -1
- package/packages/core/src/agent/llm-providers/qwen.ts +1 -1
- package/packages/core/src/agent/llm-providers/z-ai.ts +1 -1
- package/packages/core/src/agent/mcp-result-normalizer.ts +192 -0
- package/packages/core/src/agent/playbook-selector.ts +22 -7
- package/packages/core/src/agent/prompt-builder.ts +5 -5
- package/packages/core/src/agent/proof-packet.ts +5 -5
- package/packages/core/src/agent/providers/index.ts +39 -4
- package/packages/core/src/agent/realtime-providers/gemini-live.ts +238 -0
- package/packages/core/src/agent/realtime-providers/index.ts +29 -0
- package/packages/core/src/agent/realtime-providers/interface.ts +108 -0
- package/packages/core/src/agent/reflector.ts +38 -15
- package/packages/core/src/agent/run-store.ts +8 -8
- package/packages/core/src/agent/service.ts +10 -10
- package/packages/core/src/agent/skill-selector.ts +6 -6
- package/packages/core/src/agent/thread-id.ts +71 -0
- package/packages/core/src/agent/thread-store.ts +293 -0
- package/packages/core/src/agent/tool-selector.ts +9 -5
- package/packages/core/src/agent/tracer.ts +5 -5
- package/packages/core/src/api/createAgent.ts +67 -2
- package/packages/core/src/artifacts/index.ts +15 -0
- package/packages/core/src/artifacts/store.ts +161 -5
- package/packages/core/src/canvas/emitter.ts +2 -2
- package/packages/core/src/canvas/index.ts +9 -0
- package/packages/core/src/channels/telegram.ts +1 -1
- package/packages/core/src/channels/webchat.ts +1 -1
- package/packages/core/src/config/loader.ts +14 -5
- package/packages/core/src/ethics/EthicsGuard.ts +7 -1
- package/packages/core/src/events/agent-bus.ts +3 -3
- package/packages/core/src/events/channel-narration.ts +3 -3
- package/packages/core/src/events/event-bus.ts +1 -1
- package/packages/core/src/events/index.ts +18 -0
- package/packages/core/src/events/narration.ts +3 -3
- package/packages/core/src/events/tool-narration.ts +4 -0
- package/packages/core/src/gateway/channel-notify.ts +103 -6
- package/packages/core/src/gateway/delegation-groups.ts +4 -4
- package/packages/core/src/gateway/durable-queue.ts +18 -6
- package/packages/core/src/gateway/index.ts +3 -0
- package/packages/core/src/gateway/job-store.ts +11 -5
- package/packages/core/src/gateway/notification-inbox.ts +2 -2
- package/packages/core/src/gateway/server.ts +2 -2
- package/packages/core/src/harness/executors.ts +493 -0
- package/packages/core/src/harness/index.ts +12 -2
- package/packages/core/src/hooks/index.ts +203 -0
- package/packages/core/src/images/index.ts +161 -0
- package/packages/core/src/index.ts +1 -0
- package/packages/core/src/mcp/MCPClient.ts +3 -3
- package/packages/core/src/mcp/hot-reload.ts +5 -5
- package/packages/core/src/mcp/tool-sync.ts +5 -5
- package/packages/core/src/mcp/transports/index.ts +2 -2
- package/packages/core/src/mcp/transports/sse.ts +1 -1
- package/packages/core/src/models/index.ts +36 -0
- package/packages/core/src/multimodal/index.ts +2 -2
- package/packages/core/src/multimodal/vision-service.ts +51 -19
- package/packages/core/src/plugins/loader.ts +4 -1
- package/packages/core/src/resilience/circuit-breaker.ts +16 -5
- package/packages/core/src/resilience/index.ts +13 -0
- package/packages/core/src/resilience/retry.ts +1 -1
- package/packages/core/src/scheduler/CronScheduler.ts +54 -27
- package/packages/core/src/scheduler/cron/expression.ts +165 -0
- package/packages/core/src/scheduler/cron/index.ts +10 -0
- package/packages/core/src/scheduler/cron/job.ts +339 -0
- package/packages/core/src/scheduler/cron/next-run.ts +121 -0
- package/packages/core/src/scheduler/cron/zoned-time.ts +138 -0
- package/packages/core/src/scheduler/index.ts +21 -3
- package/packages/core/src/scheduler/integration.ts +25 -14
- package/packages/core/src/scheduler/types.ts +3 -18
- package/packages/core/src/services/agents.ts +268 -0
- package/packages/core/src/services/cron.ts +257 -0
- package/packages/core/src/services/endpoints.ts +289 -0
- package/packages/core/src/services/ethics.ts +107 -0
- package/packages/core/src/services/images.ts +212 -0
- package/packages/core/src/services/index.ts +112 -0
- package/packages/core/src/services/mcp.ts +201 -0
- package/packages/core/src/services/memory.ts +133 -0
- package/packages/core/src/services/models.ts +179 -0
- package/packages/core/src/services/providers.ts +152 -0
- package/packages/core/src/services/setup.ts +222 -0
- package/packages/core/src/services/skills.ts +241 -0
- package/packages/core/src/services/swarms.ts +307 -0
- package/packages/core/src/services/tools.ts +106 -0
- package/packages/core/src/sessions/index.ts +268 -0
- package/packages/core/src/sessions/resolve.ts +108 -0
- package/packages/core/src/skills/SkillLoader.ts +8 -1
- package/packages/core/src/skills/bundled/artifacts/artifact_reader/SKILL.md +105 -0
- package/packages/core/src/skills/bundled/cron_manager/SKILL.md +21 -11
- package/packages/core/src/skills/bundled/images/image_editor/SKILL.md +120 -0
- package/packages/core/src/skills/bundled/web/browser_automate/SKILL.md +12 -3
- package/packages/core/src/skills/bundled/web/browser_scrape/SKILL.md +22 -7
- package/packages/core/src/skills/bundled-data.generated.ts +110 -12
- package/packages/core/src/storage/bootstrap.ts +107 -12
- package/packages/core/src/storage/causal-events.ts +1 -1
- package/packages/core/src/storage/collections.ts +138 -2
- package/packages/core/src/storage/crypto.ts +35 -4
- package/packages/core/src/storage/hive.ts +1 -1
- package/packages/core/src/storage/hivedb.ts +10 -1
- package/packages/core/src/storage/index.ts +2 -1
- package/packages/core/src/storage/onboarding.ts +61 -45
- package/packages/core/src/storage/reconcile.ts +11 -6
- package/packages/core/src/storage/seed.ts +191 -23
- package/packages/core/src/storage/usage.ts +3 -3
- package/packages/core/src/swarm/AgentExecutor.ts +2 -2
- package/packages/core/src/swarm/Coordinator.ts +8 -8
- package/packages/core/src/swarm/EventBridge.ts +2 -2
- package/packages/core/src/swarm/RoleSwarm.ts +234 -0
- package/packages/core/src/swarm/TaskGraph.ts +2 -2
- package/packages/core/src/swarm/index.ts +7 -0
- package/packages/core/src/swarm/presets/HiveLearnPreset.ts +2 -2
- package/packages/core/src/swarm/presets/ResearchPreset.ts +2 -2
- package/packages/core/src/swarm/strategies/ParallelStrategy.ts +1 -1
- package/packages/core/src/swarm/strategies/PriorityStrategy.ts +3 -3
- package/packages/core/src/swarm/types.ts +3 -18
- package/packages/core/src/tool-runtime/embedded-worker.generated.ts +21 -0
- package/packages/core/src/tool-runtime/index.ts +129 -14
- package/packages/core/src/tools/ToolExecutor.ts +7 -3
- package/packages/core/src/tools/agents/index.ts +18 -60
- package/packages/core/src/tools/cli/index.ts +55 -0
- package/packages/core/src/tools/core/index.ts +52 -4
- package/packages/core/src/tools/cron/index.ts +8 -8
- package/packages/core/src/tools/images/index.ts +130 -0
- package/packages/core/src/tools/index.ts +14 -1
- package/packages/core/src/tools/office/office-escribir-xlsx.ts +2 -1
- package/packages/core/src/tools/office/office-leer-xlsx.ts +2 -1
- package/packages/core/src/tools/office/xlsx-loader.ts +19 -0
- package/packages/core/src/tools/web/artifact-inspect.ts +2 -2
- package/packages/core/src/tools/web/artifact-read.ts +162 -0
- package/packages/core/src/tools/web/browser-backend.ts +141 -44
- package/packages/core/src/tools/web/browser-click.ts +2 -2
- package/packages/core/src/tools/web/browser-extract.ts +2 -2
- package/packages/core/src/tools/web/browser-navigate.ts +2 -2
- package/packages/core/src/tools/web/browser-screenshot.ts +12 -5
- package/packages/core/src/tools/web/browser-script.ts +2 -2
- package/packages/core/src/tools/web/browser-service.ts +63 -384
- package/packages/core/src/tools/web/browser-session.ts +125 -0
- package/packages/core/src/tools/web/browser-type.ts +2 -2
- package/packages/core/src/tools/web/browser-wait.ts +2 -2
- package/packages/core/src/tools/web/computer-use.ts +553 -0
- package/packages/core/src/tools/web/index.ts +8 -1
- package/packages/core/src/tools/web/webview-backend.ts +460 -21
- package/packages/core/src/utils/index.ts +1 -0
- package/packages/core/src/utils/logger.ts +12 -4
- package/packages/core/src/utils/redact-binary.ts +17 -0
- package/packages/core/src/utils/toon.ts +1 -1
- package/packages/core/src/voice/index.ts +6 -6
- package/bun.lock +0 -859
- package/bunfig.toml +0 -9
- package/docs/API-AGENTS.md +0 -367
- package/docs/API-CONTEXT-COMPILER.md +0 -249
- package/docs/API-DAG-SCHEDULER.md +0 -273
- package/docs/API-TOOLS-SKILLS-CHANNELS.md +0 -446
- package/docs/API-WORKERS-EVENTS.md +0 -299
- package/docs/HIVE-HARNESS.md +0 -113
- package/docs/INDEX.md +0 -190
- package/docs/TEMPLATE-HIVE-APP.md +0 -360
- package/packages/cli/package.json +0 -17
- package/packages/cli/src/commands/create-app.test.ts +0 -180
- package/packages/core/package.json +0 -70
- package/packages/core/src/api/createAgent.test.ts +0 -160
- package/packages/core/src/canvas/canvas.test.ts +0 -36
- package/packages/core/src/channels/channels.test.ts +0 -18
- package/packages/core/src/ethics/EthicsGuard.test.ts +0 -108
- package/packages/core/src/gateway/gateway.test.ts +0 -38
- package/packages/core/src/memory/Scratchpad.test.ts +0 -68
- package/packages/core/src/scheduler/scheduler.test.ts +0 -15
- package/packages/core/src/skills/skills.test.ts +0 -62
- package/packages/core/src/swarm/swarm.test.ts +0 -24
- package/packages/core/src/tool-runtime/tool-runtime.test.ts +0 -99
- package/packages/core/src/tools/ToolRegistry.test.ts +0 -98
- package/packages/core/src/tools/api/api-request.test.ts +0 -164
- package/packages/core/src/tools/web/browser-service.test.ts +0 -83
- package/packages/core/src/workers/workers.test.ts +0 -41
- package/scripts/bump-version.ts +0 -248
- package/scripts/generate-skill-bundle.ts +0 -108
- package/test/acceptance-checks.test.ts +0 -403
- package/test/agent-loop-terminal-synthesis.test.ts +0 -32
- package/test/browser-backend.test.ts +0 -308
- package/test/catalog-agents-stay-enabled.test.ts +0 -117
- package/test/causal-events.test.ts +0 -117
- package/test/compaction.test.ts +0 -105
- package/test/context-compiler.test.ts +0 -269
- package/test/curator.test.ts +0 -130
- package/test/durable-queue.test.ts +0 -114
- package/test/harness-barrel.test.ts +0 -64
- package/test/hive-helpers.test.ts +0 -130
- package/test/hivedb-search.test.ts +0 -189
- package/test/internal-turns.test.ts +0 -166
- package/test/job-idempotency.test.ts +0 -68
- package/test/job-retry-backoff.test.ts +0 -184
- package/test/job-store.test.ts +0 -381
- package/test/llm-retry.test.ts +0 -97
- package/test/memory-perf.test.ts +0 -774
- package/test/minimal-loadout.test.ts +0 -78
- package/test/model-catalog.test.ts +0 -105
- package/test/preload.ts +0 -12
- package/test/reflector.test.ts +0 -320
- package/test/retention-cap.test.ts +0 -91
- package/test/retired-capabilities-pruned.test.ts +0 -192
- package/test/run-store.test.ts +0 -355
- package/test/scratchpad.test.ts +0 -74
- package/test/secrets-durability.test.ts +0 -119
- package/test/seed-model-reseed.test.ts +0 -155
- package/test/setup-agent-seed.test.ts +0 -264
- package/test/tool-inventory.test.ts +0 -65
- package/test/tool-runtime.test.ts +0 -258
- package/test/tool-selector-runtime-tools.test.ts +0 -117
- package/test/toon.test.ts +0 -429
- 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
|
|
@@ -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
|
|
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 "
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
154
|
+
cachedWorkerEntry = filePath
|
|
155
|
+
return cachedWorkerEntry
|
|
139
156
|
}
|
|
140
157
|
}
|
|
141
158
|
|
|
142
|
-
|
|
143
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
13
|
+
private registry: ToolRegistry;
|
|
14
|
+
|
|
15
|
+
constructor(registry: ToolRegistry) {
|
|
16
|
+
this.registry = registry;
|
|
17
|
+
}
|
|
14
18
|
|
|
15
19
|
async execute(
|
|
16
20
|
name: string,
|