@kolisachint/hoocode-agent 0.4.9 → 0.4.11
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 +12 -0
- package/dist/core/dispatch-evaluator.d.ts +31 -0
- package/dist/core/dispatch-evaluator.d.ts.map +1 -0
- package/dist/core/dispatch-evaluator.js +300 -0
- package/dist/core/dispatch-evaluator.js.map +1 -0
- package/dist/core/lifeguard.d.ts +1 -0
- package/dist/core/lifeguard.d.ts.map +1 -1
- package/dist/core/lifeguard.js +6 -0
- package/dist/core/lifeguard.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/subagent-pool.d.ts +35 -0
- package/dist/core/subagent-pool.d.ts.map +1 -1
- package/dist/core/subagent-pool.js +152 -11
- package/dist/core/subagent-pool.js.map +1 -1
- package/dist/core/subagent.d.ts +9 -1
- package/dist/core/subagent.d.ts.map +1 -1
- package/dist/core/subagent.js +23 -2
- package/dist/core/subagent.js.map +1 -1
- package/dist/core/task-store.d.ts +12 -3
- package/dist/core/task-store.d.ts.map +1 -1
- package/dist/core/task-store.js +5 -2
- package/dist/core/task-store.js.map +1 -1
- package/dist/core/tools/subagent.d.ts +5 -1
- package/dist/core/tools/subagent.d.ts.map +1 -1
- package/dist/core/tools/subagent.js +64 -13
- package/dist/core/tools/subagent.js.map +1 -1
- package/dist/init-templates.generated.d.ts +1 -0
- package/dist/init-templates.generated.d.ts.map +1 -1
- package/dist/init-templates.generated.js +8 -0
- package/dist/init-templates.generated.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +2 -2
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +9 -14
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector.js +1 -6
- package/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +1 -5
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/task-panel.d.ts +5 -2
- package/dist/modes/interactive/components/task-panel.d.ts.map +1 -1
- package/dist/modes/interactive/components/task-panel.js +103 -9
- package/dist/modes/interactive/components/task-panel.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +12 -10
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +64 -3
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/dark.json +5 -5
- package/dist/modes/interactive/theme/light.json +5 -5
- package/docs/routing.md +57 -0
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
- package/templates/agents/doc.md +35 -0
- package/templates/agents/edit.md +37 -0
- package/templates/agents/explore.md +35 -0
- package/templates/agents/review.md +36 -0
- package/templates/agents/test.md +35 -0
- package/templates/subagent/doc.md +16 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { EventEmitter } from "node:events";
|
|
3
|
+
import { type AgentType } from "./dispatch-evaluator.js";
|
|
3
4
|
import { type SubagentMode } from "./subagent.js";
|
|
4
5
|
export interface SubagentPoolTask {
|
|
5
6
|
task_id: string;
|
|
@@ -33,6 +34,18 @@ export interface SubagentResult {
|
|
|
33
34
|
/** Parsed result.json content when available (e.g. on partial completion). */
|
|
34
35
|
result_data?: Record<string, unknown>;
|
|
35
36
|
}
|
|
37
|
+
export interface TaskResult {
|
|
38
|
+
/** True when the evaluator decided the task is simple enough for inline handling. */
|
|
39
|
+
handled_inline: boolean;
|
|
40
|
+
/** Present when the task was delegated. */
|
|
41
|
+
task_id?: string;
|
|
42
|
+
agent_type?: string;
|
|
43
|
+
reason?: string;
|
|
44
|
+
/** Subagent result when delegated. */
|
|
45
|
+
result?: SubagentResult;
|
|
46
|
+
/** Duration in milliseconds when delegated. */
|
|
47
|
+
duration?: number;
|
|
48
|
+
}
|
|
36
49
|
export interface SubagentPoolOptions {
|
|
37
50
|
/** Path to the hoocode executable. */
|
|
38
51
|
executable: string;
|
|
@@ -87,6 +100,28 @@ export declare class SubagentPool extends EventEmitter {
|
|
|
87
100
|
running_count(): number;
|
|
88
101
|
/** Number of tasks waiting in the queue. */
|
|
89
102
|
queued_count(): number;
|
|
103
|
+
/**
|
|
104
|
+
* Dispatch a task through the evaluator.
|
|
105
|
+
*
|
|
106
|
+
* - If `forceAgent` is provided, skip evaluation and spawn directly.
|
|
107
|
+
* - Otherwise evaluate the task. If it should be handled inline, return
|
|
108
|
+
* `{ handled_inline: true }` immediately.
|
|
109
|
+
* - If delegating, spawn the subagent, wait for completion, write
|
|
110
|
+
* `output.json`, and return the result.
|
|
111
|
+
*/
|
|
112
|
+
dispatch(task: string, forceAgent?: AgentType): Promise<TaskResult>;
|
|
113
|
+
/**
|
|
114
|
+
* Dispatch a batch of subtasks concurrently.
|
|
115
|
+
*
|
|
116
|
+
* Spawns up to `maxConcurrency` at once; overflow is queued with FIFO.
|
|
117
|
+
* Returns aggregated results in the same order as the input.
|
|
118
|
+
*/
|
|
119
|
+
dispatchBatch(tasks: Array<{
|
|
120
|
+
agent_type: AgentType;
|
|
121
|
+
prompt: string;
|
|
122
|
+
}>): Promise<TaskResult[]>;
|
|
123
|
+
private writeDispatchLog;
|
|
124
|
+
private writeOutputJson;
|
|
90
125
|
/** Kill all running processes, clear the queue, and reject pending waiters. */
|
|
91
126
|
dispose(): void;
|
|
92
127
|
/** Pull tasks from the queue while slots are available. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subagent-pool.d.ts","sourceRoot":"","sources":["../../src/core/subagent-pool.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { EventEmitter } from \"node:events\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { CONFIG_DIR_NAME } from \"../config.js\";\nimport { waitForChildProcess } from \"../utils/child-process.js\";\nimport { SubagentLifeguard } from \"./lifeguard.js\";\nimport { OutputVerifier } from \"./output-verifier.js\";\nimport { getSubagentSystemPrompt, type SubagentMode } from \"./subagent.js\";\nimport { TokenBudget } from \"./token-budget.js\";\n\nexport interface SubagentPoolTask {\n\ttask_id: string;\n\tagent_type: SubagentMode | string;\n\ttask: string;\n\tcontext?: string;\n\ttoken_budget?: number;\n\tcwd?: string;\n\tmodel?: string;\n\tprovider?: string;\n}\n\nexport interface SubagentSlot {\n\tpid: number;\n\tagent_type: string;\n\ttask_id: string;\n\tspawned_at: number;\n\ttoken_budget: number;\n\tprocess: ReturnType<typeof spawn>;\n}\n\nexport interface SubagentResult {\n\ttask_id: string;\n\tok: boolean;\n\tstdout: string;\n\tstderr: string;\n\texit_code: number | null;\n\terror?: string;\n\t/** True when the task exceeded its token budget and was hard-stopped. */\n\tbudget_exceeded?: boolean;\n\t/** Terminal status derived from how the task finished. */\n\tstatus?: \"complete\" | \"partial\" | \"failed\" | \"stalled\" | \"timeout\";\n\t/** Parsed result.json content when available (e.g. on partial completion). */\n\tresult_data?: Record<string, unknown>;\n}\n\nexport interface SubagentPoolOptions {\n\t/** Path to the hoocode executable. */\n\texecutable: string;\n\t/** Maximum concurrent child processes. Defaults to 5. */\n\tmaxConcurrency?: number;\n\t/** Working directory for spawned processes. Defaults to process.cwd(). */\n\tcwd?: string;\n\t/** Environment variables. Defaults to process.env. */\n\tenv?: NodeJS.ProcessEnv;\n\t/** Default token budget per task. Defaults to 0. */\n\tdefaultTokenBudget?: number;\n}\n\n/**\n * Pool for running hoocode subagents as child processes with bounded concurrency,\n * FIFO queuing with priority support, and automatic slot refill.\n *\n * Events:\n * - \"task_done\" – task completed successfully and output was verified\n * - \"task_failed\" – task failed (spawn error, bad exit code, verification failure)\n * - \"task_stalled\" – heartbeat missed for 60s, process was SIGKILLed\n * - \"task_timeout\" – hard timeout exceeded, process was SIGKILLed\n * - \"budget_warning\" – token usage crossed 80% threshold\n */\nexport class SubagentPool extends EventEmitter {\n\tprivate readonly maxConcurrency: number;\n\tprivate readonly executable: string;\n\tprivate readonly cwd: string;\n\tprivate readonly env: NodeJS.ProcessEnv;\n\tprivate readonly defaultTokenBudget: number;\n\n\tprivate slots = new Map<string, SubagentSlot>();\n\tprivate queue: SubagentPoolTask[] = [];\n\tprivate completed = new Map<string, SubagentResult>();\n\tprivate waiters = new Map<string, { resolve: (result: SubagentResult) => void; reject: (err: Error) => void }>();\n\tprivate budgets = new Map<string, TokenBudget>();\n\tprivate verifier = new OutputVerifier();\n\tprivate lifeguard: SubagentLifeguard;\n\tprivate disposed = false;\n\t/** Tracks why a task was killed (stalled / timeout) before exit handler fires. */\n\tprivate killReasons = new Map<string, \"stalled\" | \"timeout\">();\n\t/** Persistent terminal status map, survives wait_for consumption. */\n\tprivate taskStatus = new Map<string, \"done\" | \"failed\" | \"stalled\" | \"timeout\">();\n\n\tconstructor(options: SubagentPoolOptions) {\n\t\tsuper();\n\t\tthis.maxConcurrency = options.maxConcurrency ?? 5;\n\t\tthis.executable = options.executable;\n\t\tthis.cwd = options.cwd ?? process.cwd();\n\t\tthis.env = options.env ?? process.env;\n\t\tthis.defaultTokenBudget = options.defaultTokenBudget ?? 0;\n\t\tthis.verifier = new OutputVerifier(this.cwd);\n\t\tthis.lifeguard = new SubagentLifeguard(this.cwd);\n\t\tthis.lifeguard.on(\"stalled\", (data: { task_id: string; pid: number }) => {\n\t\t\tthis.killReasons.set(data.task_id, \"stalled\");\n\t\t\tthis.emit(\"task_stalled\", data);\n\t\t});\n\t\tthis.lifeguard.on(\"timeout\", (data: { task_id: string; pid: number }) => {\n\t\t\tthis.killReasons.set(data.task_id, \"timeout\");\n\t\t\tthis.emit(\"task_timeout\", data);\n\t\t});\n\t}\n\n\t/** Priority value: higher numbers run first. */\n\tprivate priorityOf(agent_type: string): number {\n\t\tswitch (agent_type) {\n\t\t\tcase \"explore\":\n\t\t\t\treturn 2;\n\t\t\tcase \"doc\":\n\t\t\t\treturn 0;\n\t\t\tdefault:\n\t\t\t\treturn 1;\n\t\t}\n\t}\n\n\t/** Queue a task. It will run when a slot is free. */\n\tspawn(task: SubagentPoolTask): void {\n\t\tif (this.disposed) {\n\t\t\tthrow new Error(\"SubagentPool has been disposed\");\n\t\t}\n\t\tif (\n\t\t\tthis.slots.has(task.task_id) ||\n\t\t\tthis.queue.some((t) => t.task_id === task.task_id) ||\n\t\t\tthis.completed.has(task.task_id)\n\t\t) {\n\t\t\tthrow new Error(`Duplicate task_id: ${task.task_id}`);\n\t\t}\n\n\t\tconst p = this.priorityOf(task.agent_type);\n\t\tconst idx = this.queue.findIndex((t) => this.priorityOf(t.agent_type) < p);\n\t\tif (idx === -1) {\n\t\t\tthis.queue.push(task);\n\t\t} else {\n\t\t\tthis.queue.splice(idx, 0, task);\n\t\t}\n\t\tthis.pull();\n\t}\n\n\t/** Current status of a task. */\n\tget_status(task_id: string): \"running\" | \"queued\" | \"done\" | \"failed\" | \"stalled\" | \"timeout\" {\n\t\tif (this.slots.has(task_id)) return \"running\";\n\t\tif (this.queue.some((t) => t.task_id === task_id)) return \"queued\";\n\t\tconst persisted = this.taskStatus.get(task_id);\n\t\tif (persisted) return persisted;\n\t\tconst result = this.completed.get(task_id);\n\t\tif (result) {\n\t\t\tif (result.status === \"stalled\") return \"stalled\";\n\t\t\tif (result.status === \"timeout\") return \"timeout\";\n\t\t\tif (result.ok) return \"done\";\n\t\t\treturn \"failed\";\n\t\t}\n\t\treturn \"failed\";\n\t}\n\n\t/** Wait for a task to complete and return its result. */\n\twait_for(task_id: string): Promise<SubagentResult> {\n\t\tif (this.disposed) {\n\t\t\treturn Promise.reject(new Error(\"SubagentPool has been disposed\"));\n\t\t}\n\n\t\tconst existing = this.completed.get(task_id);\n\t\tif (existing) {\n\t\t\tthis.completed.delete(task_id);\n\t\t\treturn Promise.resolve(existing);\n\t\t}\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tthis.waiters.set(task_id, { resolve, reject });\n\t\t});\n\t}\n\n\t/** Number of currently running subagents. */\n\trunning_count(): number {\n\t\treturn this.slots.size;\n\t}\n\n\t/** Number of tasks waiting in the queue. */\n\tqueued_count(): number {\n\t\treturn this.queue.length;\n\t}\n\n\t/** Kill all running processes, clear the queue, and reject pending waiters. */\n\tdispose(): void {\n\t\tif (this.disposed) return;\n\t\tthis.disposed = true;\n\n\t\tfor (const slot of this.slots.values()) {\n\t\t\tif (!slot.process.killed) {\n\t\t\t\tslot.process.kill(\"SIGTERM\");\n\t\t\t}\n\t\t}\n\t\tthis.slots.clear();\n\t\tthis.queue = [];\n\n\t\tfor (const [task_id, waiter] of this.waiters) {\n\t\t\twaiter.reject(new Error(\"SubagentPool disposed\"));\n\t\t\tthis.waiters.delete(task_id);\n\t\t}\n\t\tthis.completed.clear();\n\t\tthis.budgets.clear();\n\t\tthis.killReasons.clear();\n\t\tthis.taskStatus.clear();\n\t\tthis.lifeguard.dispose();\n\t\tthis.removeAllListeners();\n\t}\n\n\t/** Pull tasks from the queue while slots are available. */\n\tprivate pull(): void {\n\t\twhile (this.slots.size < this.maxConcurrency && this.queue.length > 0) {\n\t\t\tconst task = this.queue.shift()!;\n\t\t\tthis.startTask(task, false);\n\t\t}\n\t}\n\n\t/** Build CLI arguments for a task. */\n\tprivate buildArgs(task: SubagentPoolTask): string[] {\n\t\tconst args: string[] = [\"--mode\", \"json\", \"--no-session\"];\n\n\t\tif (task.agent_type) {\n\t\t\ttry {\n\t\t\t\tconst systemPrompt = getSubagentSystemPrompt(task.agent_type as SubagentMode);\n\t\t\t\targs.push(\"--system-prompt\", systemPrompt);\n\t\t\t} catch {\n\t\t\t\t// Unknown mode, skip custom system prompt\n\t\t\t}\n\t\t}\n\n\t\tif (task.model) {\n\t\t\targs.push(\"--model\", task.model);\n\t\t}\n\t\tif (task.provider) {\n\t\t\targs.push(\"--provider\", task.provider);\n\t\t}\n\n\t\tconst prompt = task.context?.trim()\n\t\t\t? `Context from the calling agent:\\n\\n${task.context.trim()}\\n\\nTask: ${task.task.trim()}`\n\t\t\t: `Task: ${task.task.trim()}`;\n\t\targs.push(prompt);\n\n\t\treturn args;\n\t}\n\n\t/** Start a task in a child process, with one retry on failure. */\n\tprivate startTask(task: SubagentPoolTask, isRetry: boolean): void {\n\t\t// Get or create a TokenBudget tracker. On retry, reuse the existing one\n\t\t// so cumulative usage persists across retries.\n\t\tlet budget = this.budgets.get(task.task_id);\n\t\tif (!budget) {\n\t\t\tbudget = new TokenBudget(task.task_id, task.agent_type, {\n\t\t\t\tlimit: task.token_budget,\n\t\t\t\tcwd: task.cwd ?? this.cwd,\n\t\t\t});\n\t\t\tbudget.on(\"budget_warning\", (data: { task_id: string; message: string; used: number; limit: number }) => {\n\t\t\t\tthis.emit(\"budget_warning\", data);\n\t\t\t});\n\t\t\tbudget.on(\"budget_exceeded\", () => {\n\t\t\t\tconst slot = this.slots.get(task.task_id);\n\t\t\t\tif (slot && !slot.process.killed) {\n\t\t\t\t\tslot.process.kill(\"SIGTERM\");\n\t\t\t\t}\n\t\t\t});\n\t\t\tthis.budgets.set(task.task_id, budget);\n\t\t}\n\n\t\tlet proc: ReturnType<typeof spawn>;\n\t\ttry {\n\t\t\tproc = spawn(this.executable, this.buildArgs(task), {\n\t\t\t\tcwd: task.cwd ?? this.cwd,\n\t\t\t\tenv: this.env,\n\t\t\t\tshell: false,\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t});\n\t\t} catch {\n\t\t\tif (!isRetry) {\n\t\t\t\tthis.startTask(task, true);\n\t\t\t} else {\n\t\t\t\tthis.emit(\"task_failed\", {\n\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\terror: \"Spawn failed synchronously\",\n\t\t\t\t});\n\t\t\t\tthis.resolveWaiter(task.task_id, {\n\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\tok: false,\n\t\t\t\t\tstdout: \"\",\n\t\t\t\t\tstderr: \"\",\n\t\t\t\t\texit_code: null,\n\t\t\t\t\terror: \"Spawn failed synchronously\",\n\t\t\t\t\tstatus: \"failed\",\n\t\t\t\t});\n\t\t\t\tthis.pull();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst slot: SubagentSlot = {\n\t\t\tpid: proc.pid ?? 0,\n\t\t\tagent_type: task.agent_type,\n\t\t\ttask_id: task.task_id,\n\t\t\tspawned_at: Date.now(),\n\t\t\ttoken_budget: task.token_budget ?? this.defaultTokenBudget,\n\t\t\tprocess: proc,\n\t\t};\n\n\t\tthis.slots.set(task.task_id, slot);\n\t\tthis.lifeguard.monitor(task.task_id, task.agent_type, proc);\n\n\t\tlet stdout = \"\";\n\t\tlet stderr = \"\";\n\t\tlet budgetExceeded = false;\n\n\t\tproc.stdout?.on(\"data\", (data: Buffer) => {\n\t\t\tconst chunk = data.toString();\n\t\t\tstdout += chunk;\n\t\t\tbudget.processStdout(chunk);\n\n\t\t\t// Heartbeat detection: look for {\"ping\":true} JSON lines\n\t\t\tfor (const raw of chunk.split(\"\\n\")) {\n\t\t\t\tconst line = raw.trim();\n\t\t\t\tif (!line.startsWith(\"{\")) continue;\n\t\t\t\ttry {\n\t\t\t\t\tconst parsed = JSON.parse(line) as Record<string, unknown>;\n\t\t\t\t\tif (parsed.ping === true) {\n\t\t\t\t\t\tthis.lifeguard.recordHeartbeat(task.task_id);\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// Not a ping line, ignore\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tproc.stderr?.on(\"data\", (data: Buffer) => {\n\t\t\tstderr += data.toString();\n\t\t});\n\n\t\tbudget.once(\"budget_exceeded\", () => {\n\t\t\tbudgetExceeded = true;\n\t\t});\n\n\t\twaitForChildProcess(proc)\n\t\t\t.then((code) => {\n\t\t\t\tthis.slots.delete(task.task_id);\n\t\t\t\tbudget.flush();\n\n\t\t\t\tconst killReason = this.killReasons.get(task.task_id);\n\t\t\t\tthis.killReasons.delete(task.task_id);\n\n\t\t\t\t// If killed by lifeguard, override exit handling\n\t\t\t\tif (killReason === \"stalled\" || killReason === \"timeout\") {\n\t\t\t\t\tconst result: SubagentResult = {\n\t\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\t\tok: false,\n\t\t\t\t\t\tstdout,\n\t\t\t\t\t\tstderr,\n\t\t\t\t\t\texit_code: code,\n\t\t\t\t\t\tstatus: killReason,\n\t\t\t\t\t};\n\t\t\t\t\tthis.emit(`task_${killReason}`, { task_id: task.task_id });\n\t\t\t\t\tthis.resolveWaiter(task.task_id, result);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst result: SubagentResult = {\n\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\tok: code === 0 && !budgetExceeded,\n\t\t\t\t\tstdout,\n\t\t\t\t\tstderr,\n\t\t\t\t\texit_code: code,\n\t\t\t\t\tbudget_exceeded: budgetExceeded,\n\t\t\t\t\tstatus: code === 0 && !budgetExceeded ? \"complete\" : \"failed\",\n\t\t\t\t};\n\n\t\t\t\tif (budgetExceeded) {\n\t\t\t\t\t// Force-return whatever exists in result.json, mark partial\n\t\t\t\t\tconst resultData = this.tryReadResultJson(task.task_id, task.cwd ?? this.cwd);\n\t\t\t\t\tresult.status = resultData ? \"partial\" : \"failed\";\n\t\t\t\t\tresult.result_data = resultData;\n\t\t\t\t\tif (resultData) {\n\t\t\t\t\t\tresult.ok = true; // partial is considered success with data\n\t\t\t\t\t}\n\t\t\t\t\tthis.emit(\"task_done\", { task_id: task.task_id, status: \"partial\" });\n\t\t\t\t\tthis.resolveWaiter(task.task_id, result);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (result.ok) {\n\t\t\t\t\tconst verification = this.verifier.verify(task.task_id, task.cwd ?? this.cwd);\n\t\t\t\t\tif (!verification.valid) {\n\t\t\t\t\t\tresult.ok = false;\n\t\t\t\t\t\tresult.error = verification.reason;\n\t\t\t\t\t\tresult.status = \"failed\";\n\t\t\t\t\t\tthis.emit(\"task_failed\", {\n\t\t\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\t\t\terror: verification.reason,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tthis.resolveWaiter(task.task_id, result);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (result.ok) {\n\t\t\t\t\tthis.emit(\"task_done\", { task_id: task.task_id, status: \"complete\" });\n\t\t\t\t} else {\n\t\t\t\t\tthis.emit(\"task_failed\", {\n\t\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\t\terror: result.error ?? `Exited with code ${code}`,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tthis.resolveWaiter(task.task_id, result);\n\t\t\t})\n\t\t\t.catch((err) => {\n\t\t\t\tthis.slots.delete(task.task_id);\n\t\t\t\tbudget.flush();\n\t\t\t\tif (!isRetry) {\n\t\t\t\t\tthis.startTask(task, true);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst error = err instanceof Error ? err.message : String(err);\n\t\t\t\tthis.emit(\"task_failed\", { task_id: task.task_id, error });\n\t\t\t\tthis.resolveWaiter(task.task_id, {\n\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\tok: false,\n\t\t\t\t\tstdout,\n\t\t\t\t\tstderr,\n\t\t\t\t\texit_code: null,\n\t\t\t\t\terror,\n\t\t\t\t\tstatus: \"failed\",\n\t\t\t\t});\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\tthis.budgets.delete(task.task_id);\n\t\t\t\tthis.pull();\n\t\t\t});\n\t}\n\n\tprivate tryReadResultJson(task_id: string, cwd: string): Record<string, unknown> | undefined {\n\t\tconst path = join(cwd, CONFIG_DIR_NAME, \"agents\", task_id, \"result.json\");\n\t\tif (!existsSync(path)) return undefined;\n\t\ttry {\n\t\t\tconst raw = readFileSync(path, \"utf-8\");\n\t\t\treturn JSON.parse(raw) as Record<string, unknown>;\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tprivate resolveWaiter(task_id: string, result: SubagentResult): void {\n\t\t// Persist terminal status for get_status() even after wait_for consumes the result\n\t\tif (result.status === \"stalled\") this.taskStatus.set(task_id, \"stalled\");\n\t\telse if (result.status === \"timeout\") this.taskStatus.set(task_id, \"timeout\");\n\t\telse if (result.ok) this.taskStatus.set(task_id, \"done\");\n\t\telse this.taskStatus.set(task_id, \"failed\");\n\n\t\tconst waiter = this.waiters.get(task_id);\n\t\tif (waiter) {\n\t\t\twaiter.resolve(result);\n\t\t\tthis.waiters.delete(task_id);\n\t\t\treturn;\n\t\t}\n\t\tthis.completed.set(task_id, result);\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"subagent-pool.d.ts","sourceRoot":"","sources":["../../src/core/subagent-pool.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { EventEmitter } from \"node:events\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { CONFIG_DIR_NAME } from \"../config.js\";\nimport { waitForChildProcess } from \"../utils/child-process.js\";\nimport { type AgentType, DispatchEvaluator } from \"./dispatch-evaluator.js\";\nimport { SubagentLifeguard } from \"./lifeguard.js\";\nimport { OutputVerifier } from \"./output-verifier.js\";\nimport { getSubagentSystemPrompt, type SubagentMode } from \"./subagent.js\";\nimport { TokenBudget } from \"./token-budget.js\";\n\nexport interface SubagentPoolTask {\n\ttask_id: string;\n\tagent_type: SubagentMode | string;\n\ttask: string;\n\tcontext?: string;\n\ttoken_budget?: number;\n\tcwd?: string;\n\tmodel?: string;\n\tprovider?: string;\n}\n\nexport interface SubagentSlot {\n\tpid: number;\n\tagent_type: string;\n\ttask_id: string;\n\tspawned_at: number;\n\ttoken_budget: number;\n\tprocess: ReturnType<typeof spawn>;\n}\n\nexport interface SubagentResult {\n\ttask_id: string;\n\tok: boolean;\n\tstdout: string;\n\tstderr: string;\n\texit_code: number | null;\n\terror?: string;\n\t/** True when the task exceeded its token budget and was hard-stopped. */\n\tbudget_exceeded?: boolean;\n\t/** Terminal status derived from how the task finished. */\n\tstatus?: \"complete\" | \"partial\" | \"failed\" | \"stalled\" | \"timeout\";\n\t/** Parsed result.json content when available (e.g. on partial completion). */\n\tresult_data?: Record<string, unknown>;\n}\n\nexport interface TaskResult {\n\t/** True when the evaluator decided the task is simple enough for inline handling. */\n\thandled_inline: boolean;\n\t/** Present when the task was delegated. */\n\ttask_id?: string;\n\tagent_type?: string;\n\treason?: string;\n\t/** Subagent result when delegated. */\n\tresult?: SubagentResult;\n\t/** Duration in milliseconds when delegated. */\n\tduration?: number;\n}\n\nexport interface SubagentPoolOptions {\n\t/** Path to the hoocode executable. */\n\texecutable: string;\n\t/** Maximum concurrent child processes. Defaults to 5. */\n\tmaxConcurrency?: number;\n\t/** Working directory for spawned processes. Defaults to process.cwd(). */\n\tcwd?: string;\n\t/** Environment variables. Defaults to process.env. */\n\tenv?: NodeJS.ProcessEnv;\n\t/** Default token budget per task. Defaults to 0. */\n\tdefaultTokenBudget?: number;\n}\n\n/**\n * Pool for running hoocode subagents as child processes with bounded concurrency,\n * FIFO queuing with priority support, and automatic slot refill.\n *\n * Events:\n * - \"task_done\" – task completed successfully and output was verified\n * - \"task_failed\" – task failed (spawn error, bad exit code, verification failure)\n * - \"task_stalled\" – heartbeat missed for 60s, process was SIGKILLed\n * - \"task_timeout\" – hard timeout exceeded, process was SIGKILLed\n * - \"budget_warning\" – token usage crossed 80% threshold\n */\nexport class SubagentPool extends EventEmitter {\n\tprivate readonly maxConcurrency: number;\n\tprivate readonly executable: string;\n\tprivate readonly cwd: string;\n\tprivate readonly env: NodeJS.ProcessEnv;\n\tprivate readonly defaultTokenBudget: number;\n\n\tprivate slots = new Map<string, SubagentSlot>();\n\tprivate queue: SubagentPoolTask[] = [];\n\tprivate completed = new Map<string, SubagentResult>();\n\tprivate waiters = new Map<string, { resolve: (result: SubagentResult) => void; reject: (err: Error) => void }>();\n\tprivate budgets = new Map<string, TokenBudget>();\n\tprivate verifier = new OutputVerifier();\n\tprivate lifeguard: SubagentLifeguard;\n\tprivate disposed = false;\n\t/** Tracks why a task was killed (stalled / timeout) before exit handler fires. */\n\tprivate killReasons = new Map<string, \"stalled\" | \"timeout\">();\n\t/** Persistent terminal status map, survives wait_for consumption. */\n\tprivate taskStatus = new Map<string, \"done\" | \"failed\" | \"stalled\" | \"timeout\">();\n\n\tconstructor(options: SubagentPoolOptions) {\n\t\tsuper();\n\t\tthis.maxConcurrency = options.maxConcurrency ?? 5;\n\t\tthis.executable = options.executable;\n\t\tthis.cwd = options.cwd ?? process.cwd();\n\t\tthis.env = options.env ?? process.env;\n\t\tthis.defaultTokenBudget = options.defaultTokenBudget ?? 0;\n\t\tthis.verifier = new OutputVerifier(this.cwd);\n\t\tthis.lifeguard = new SubagentLifeguard(this.cwd);\n\t\tthis.lifeguard.on(\"stalled\", (data: { task_id: string; pid: number }) => {\n\t\t\tthis.killReasons.set(data.task_id, \"stalled\");\n\t\t\tthis.emit(\"task_stalled\", data);\n\t\t});\n\t\tthis.lifeguard.on(\"timeout\", (data: { task_id: string; pid: number }) => {\n\t\t\tthis.killReasons.set(data.task_id, \"timeout\");\n\t\t\tthis.emit(\"task_timeout\", data);\n\t\t});\n\t}\n\n\t/** Priority value: higher numbers run first. */\n\tprivate priorityOf(agent_type: string): number {\n\t\tswitch (agent_type) {\n\t\t\tcase \"explore\":\n\t\t\tcase \"review\":\n\t\t\t\treturn 2;\n\t\t\tcase \"doc\":\n\t\t\t\treturn 0;\n\t\t\tdefault:\n\t\t\t\treturn 1;\n\t\t}\n\t}\n\n\t/** Queue a task. It will run when a slot is free. */\n\tspawn(task: SubagentPoolTask): void {\n\t\tif (this.disposed) {\n\t\t\tthrow new Error(\"SubagentPool has been disposed\");\n\t\t}\n\t\tif (\n\t\t\tthis.slots.has(task.task_id) ||\n\t\t\tthis.queue.some((t) => t.task_id === task.task_id) ||\n\t\t\tthis.completed.has(task.task_id)\n\t\t) {\n\t\t\tthrow new Error(`Duplicate task_id: ${task.task_id}`);\n\t\t}\n\n\t\tconst p = this.priorityOf(task.agent_type);\n\t\tconst idx = this.queue.findIndex((t) => this.priorityOf(t.agent_type) < p);\n\t\tif (idx === -1) {\n\t\t\tthis.queue.push(task);\n\t\t} else {\n\t\t\tthis.queue.splice(idx, 0, task);\n\t\t}\n\t\tthis.pull();\n\t}\n\n\t/** Current status of a task. */\n\tget_status(task_id: string): \"running\" | \"queued\" | \"done\" | \"failed\" | \"stalled\" | \"timeout\" {\n\t\tif (this.slots.has(task_id)) return \"running\";\n\t\tif (this.queue.some((t) => t.task_id === task_id)) return \"queued\";\n\t\tconst persisted = this.taskStatus.get(task_id);\n\t\tif (persisted) return persisted;\n\t\tconst result = this.completed.get(task_id);\n\t\tif (result) {\n\t\t\tif (result.status === \"stalled\") return \"stalled\";\n\t\t\tif (result.status === \"timeout\") return \"timeout\";\n\t\t\tif (result.ok) return \"done\";\n\t\t\treturn \"failed\";\n\t\t}\n\t\treturn \"failed\";\n\t}\n\n\t/** Wait for a task to complete and return its result. */\n\twait_for(task_id: string): Promise<SubagentResult> {\n\t\tif (this.disposed) {\n\t\t\treturn Promise.reject(new Error(\"SubagentPool has been disposed\"));\n\t\t}\n\n\t\tconst existing = this.completed.get(task_id);\n\t\tif (existing) {\n\t\t\tthis.completed.delete(task_id);\n\t\t\treturn Promise.resolve(existing);\n\t\t}\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tthis.waiters.set(task_id, { resolve, reject });\n\t\t});\n\t}\n\n\t/** Number of currently running subagents. */\n\trunning_count(): number {\n\t\treturn this.slots.size;\n\t}\n\n\t/** Number of tasks waiting in the queue. */\n\tqueued_count(): number {\n\t\treturn this.queue.length;\n\t}\n\n\t/**\n\t * Dispatch a task through the evaluator.\n\t *\n\t * - If `forceAgent` is provided, skip evaluation and spawn directly.\n\t * - Otherwise evaluate the task. If it should be handled inline, return\n\t * `{ handled_inline: true }` immediately.\n\t * - If delegating, spawn the subagent, wait for completion, write\n\t * `output.json`, and return the result.\n\t */\n\tasync dispatch(task: string, forceAgent?: AgentType): Promise<TaskResult> {\n\t\tif (this.disposed) {\n\t\t\treturn Promise.reject(new Error(\"SubagentPool has been disposed\"));\n\t\t}\n\n\t\tconst evaluator = new DispatchEvaluator();\n\t\tconst analysis = evaluator.evaluate(task);\n\n\t\tif (!forceAgent && !analysis.should_delegate) {\n\t\t\treturn { handled_inline: true, reason: analysis.reason };\n\t\t}\n\n\t\tconst agent_type: AgentType = forceAgent ?? (analysis.agent_type as AgentType) ?? \"explore\";\n\t\tconst task_id = `dispatch-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n\t\tconst reason = forceAgent ? \"user_override\" : analysis.reason;\n\t\tconst complexity = analysis.estimated_complexity;\n\n\t\t// Pre-dispatch logging\n\t\tconst logLine = `[DISPATCH] agent=${agent_type} reason=${reason} complexity=${complexity} task_id=${task_id}`;\n\t\tconsole.log(logLine);\n\t\tthis.writeDispatchLog(task_id, agent_type, reason, complexity, task);\n\n\t\tconst poolTask: SubagentPoolTask = {\n\t\t\ttask_id,\n\t\t\tagent_type,\n\t\t\ttask,\n\t\t\tcwd: this.cwd,\n\t\t};\n\n\t\tconst startTime = Date.now();\n\t\tthis.spawn(poolTask);\n\t\tconst result = await this.wait_for(task_id);\n\t\tconst duration = Date.now() - startTime;\n\n\t\treturn {\n\t\t\thandled_inline: false,\n\t\t\ttask_id,\n\t\t\tagent_type,\n\t\t\treason,\n\t\t\tresult,\n\t\t\tduration,\n\t\t};\n\t}\n\n\t/**\n\t * Dispatch a batch of subtasks concurrently.\n\t *\n\t * Spawns up to `maxConcurrency` at once; overflow is queued with FIFO.\n\t * Returns aggregated results in the same order as the input.\n\t */\n\tasync dispatchBatch(tasks: Array<{ agent_type: AgentType; prompt: string }>): Promise<TaskResult[]> {\n\t\tif (this.disposed) {\n\t\t\treturn Promise.reject(new Error(\"SubagentPool has been disposed\"));\n\t\t}\n\n\t\tconst promises = tasks.map(async ({ agent_type, prompt }) => {\n\t\t\treturn this.dispatch(prompt, agent_type);\n\t\t});\n\n\t\treturn Promise.all(promises);\n\t}\n\n\tprivate writeDispatchLog(\n\t\ttask_id: string,\n\t\tagent_type: string,\n\t\treason: string,\n\t\tcomplexity: string,\n\t\ttask: string,\n\t): void {\n\t\tconst log = {\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\ttask_id,\n\t\t\tagent_type,\n\t\t\treason,\n\t\t\tcomplexity,\n\t\t\ttask,\n\t\t};\n\t\tconst path = join(this.cwd, CONFIG_DIR_NAME, \"agents\", task_id, \"dispatch-log.json\");\n\t\ttry {\n\t\t\tmkdirSync(dirname(path), { recursive: true });\n\t\t\twriteFileSync(path, JSON.stringify(log, null, 2));\n\t\t} catch {\n\t\t\t// Best-effort persistence\n\t\t}\n\t}\n\n\tprivate writeOutputJson(task_id: string, result: SubagentResult): void {\n\t\tconst output = {\n\t\t\ttask_id: result.task_id,\n\t\t\tok: result.ok,\n\t\t\texit_code: result.exit_code,\n\t\t\tstatus: result.status,\n\t\t\tstdout: result.stdout,\n\t\t\tstderr: result.stderr,\n\t\t\terror: result.error,\n\t\t\tbudget_exceeded: result.budget_exceeded,\n\t\t\tresult_data: result.result_data,\n\t\t};\n\t\tconst path = join(this.cwd, CONFIG_DIR_NAME, \"agents\", task_id, \"output.json\");\n\t\ttry {\n\t\t\tmkdirSync(dirname(path), { recursive: true });\n\t\t\twriteFileSync(path, JSON.stringify(output, null, 2));\n\t\t} catch {\n\t\t\t// Best-effort persistence\n\t\t}\n\t}\n\n\t/** Kill all running processes, clear the queue, and reject pending waiters. */\n\tdispose(): void {\n\t\tif (this.disposed) return;\n\t\tthis.disposed = true;\n\n\t\tfor (const slot of this.slots.values()) {\n\t\t\tif (!slot.process.killed) {\n\t\t\t\tslot.process.kill(\"SIGTERM\");\n\t\t\t}\n\t\t}\n\t\tthis.slots.clear();\n\t\tthis.queue = [];\n\n\t\tfor (const [task_id, waiter] of this.waiters) {\n\t\t\twaiter.reject(new Error(\"SubagentPool disposed\"));\n\t\t\tthis.waiters.delete(task_id);\n\t\t}\n\t\tthis.completed.clear();\n\t\tfor (const budget of this.budgets.values()) {\n\t\t\tbudget.removeAllListeners();\n\t\t}\n\t\tthis.budgets.clear();\n\t\tthis.killReasons.clear();\n\t\tthis.taskStatus.clear();\n\t\tthis.lifeguard.dispose();\n\t\tthis.removeAllListeners();\n\t}\n\n\t/** Pull tasks from the queue while slots are available. */\n\tprivate pull(): void {\n\t\twhile (this.slots.size < this.maxConcurrency && this.queue.length > 0) {\n\t\t\tconst task = this.queue.shift()!;\n\t\t\tthis.startTask(task, false);\n\t\t}\n\t}\n\n\t/** Build CLI arguments for a task. */\n\tprivate buildArgs(task: SubagentPoolTask): string[] {\n\t\tconst args: string[] = [\"--mode\", \"json\", \"--no-session\"];\n\n\t\tif (task.agent_type) {\n\t\t\ttry {\n\t\t\t\tconst systemPrompt = getSubagentSystemPrompt(task.agent_type as SubagentMode);\n\t\t\t\targs.push(\"--system-prompt\", systemPrompt);\n\t\t\t} catch {\n\t\t\t\t// Unknown mode, skip custom system prompt\n\t\t\t}\n\t\t}\n\n\t\tif (task.model) {\n\t\t\targs.push(\"--model\", task.model);\n\t\t}\n\t\tif (task.provider) {\n\t\t\targs.push(\"--provider\", task.provider);\n\t\t}\n\n\t\tconst prompt = task.context?.trim()\n\t\t\t? `Context from the calling agent:\\n\\n${task.context.trim()}\\n\\nTask: ${task.task.trim()}`\n\t\t\t: `Task: ${task.task.trim()}`;\n\t\targs.push(prompt);\n\n\t\treturn args;\n\t}\n\n\t/** Start a task in a child process, with one retry on failure. */\n\tprivate startTask(task: SubagentPoolTask, isRetry: boolean): void {\n\t\t// Get or create a TokenBudget tracker. On retry, reuse the existing one\n\t\t// so cumulative usage persists across retries.\n\t\tlet budget = this.budgets.get(task.task_id);\n\t\tif (!budget) {\n\t\t\tbudget = new TokenBudget(task.task_id, task.agent_type, {\n\t\t\t\tlimit: task.token_budget,\n\t\t\t\tcwd: task.cwd ?? this.cwd,\n\t\t\t});\n\t\t\tbudget.on(\"budget_warning\", (data: { task_id: string; message: string; used: number; limit: number }) => {\n\t\t\t\tthis.emit(\"budget_warning\", data);\n\t\t\t});\n\t\t\tbudget.on(\"budget_exceeded\", () => {\n\t\t\t\tconst slot = this.slots.get(task.task_id);\n\t\t\t\tif (slot && !slot.process.killed) {\n\t\t\t\t\tslot.process.kill(\"SIGTERM\");\n\t\t\t\t}\n\t\t\t});\n\t\t\tthis.budgets.set(task.task_id, budget);\n\t\t}\n\n\t\tlet proc: ReturnType<typeof spawn>;\n\t\ttry {\n\t\t\tproc = spawn(this.executable, this.buildArgs(task), {\n\t\t\t\tcwd: task.cwd ?? this.cwd,\n\t\t\t\tenv: this.env,\n\t\t\t\tshell: false,\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t});\n\t\t} catch {\n\t\t\tif (!isRetry) {\n\t\t\t\tthis.startTask(task, true);\n\t\t\t} else {\n\t\t\t\tthis.emit(\"task_failed\", {\n\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\terror: \"Spawn failed synchronously\",\n\t\t\t\t});\n\t\t\t\tthis.resolveWaiter(task.task_id, {\n\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\tok: false,\n\t\t\t\t\tstdout: \"\",\n\t\t\t\t\tstderr: \"\",\n\t\t\t\t\texit_code: null,\n\t\t\t\t\terror: \"Spawn failed synchronously\",\n\t\t\t\t\tstatus: \"failed\",\n\t\t\t\t});\n\t\t\t\tthis.pull();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst slot: SubagentSlot = {\n\t\t\tpid: proc.pid ?? 0,\n\t\t\tagent_type: task.agent_type,\n\t\t\ttask_id: task.task_id,\n\t\t\tspawned_at: Date.now(),\n\t\t\ttoken_budget: task.token_budget ?? this.defaultTokenBudget,\n\t\t\tprocess: proc,\n\t\t};\n\n\t\tthis.slots.set(task.task_id, slot);\n\t\tthis.lifeguard.monitor(task.task_id, task.agent_type, proc);\n\n\t\tlet stdout = \"\";\n\t\tlet stderr = \"\";\n\n\t\tproc.stdout?.on(\"data\", (data: Buffer) => {\n\t\t\tconst chunk = data.toString();\n\t\t\tstdout += chunk;\n\t\t\tbudget.processStdout(chunk);\n\n\t\t\t// Heartbeat detection: look for {\"ping\":true} JSON lines\n\t\t\tfor (const raw of chunk.split(\"\\n\")) {\n\t\t\t\tconst line = raw.trim();\n\t\t\t\tif (!line.startsWith(\"{\")) continue;\n\t\t\t\ttry {\n\t\t\t\t\tconst parsed = JSON.parse(line) as Record<string, unknown>;\n\t\t\t\t\tif (parsed.ping === true) {\n\t\t\t\t\t\tthis.lifeguard.recordHeartbeat(task.task_id);\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// Not a ping line, ignore\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tproc.stderr?.on(\"data\", (data: Buffer) => {\n\t\t\tstderr += data.toString();\n\t\t});\n\n\t\twaitForChildProcess(proc)\n\t\t\t.then((code) => {\n\t\t\t\tthis.slots.delete(task.task_id);\n\t\t\t\tbudget.flush();\n\n\t\t\t\tconst killReason = this.killReasons.get(task.task_id);\n\t\t\t\tthis.killReasons.delete(task.task_id);\n\n\t\t\t\tconst duration = Date.now() - slot.spawned_at;\n\t\t\t\tconst tokens_used = budget.getUsed();\n\t\t\t\tconst budgetExceeded = budget.isExceeded();\n\n\t\t\t\t// If killed by lifeguard, override exit handling\n\t\t\t\tif (killReason === \"stalled\" || killReason === \"timeout\") {\n\t\t\t\t\tconst result: SubagentResult = {\n\t\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\t\tok: false,\n\t\t\t\t\t\tstdout,\n\t\t\t\t\t\tstderr,\n\t\t\t\t\t\texit_code: code,\n\t\t\t\t\t\tstatus: killReason,\n\t\t\t\t\t};\n\t\t\t\t\tthis.writeOutputJson(task.task_id, result);\n\t\t\t\t\tthis.emit(`task_${killReason}`, {\n\t\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\t\tagent_type: task.agent_type,\n\t\t\t\t\t\tduration,\n\t\t\t\t\t\ttokens_used,\n\t\t\t\t\t});\n\t\t\t\t\tthis.resolveWaiter(task.task_id, result);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst result: SubagentResult = {\n\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\tok: code === 0 && !budgetExceeded,\n\t\t\t\t\tstdout,\n\t\t\t\t\tstderr,\n\t\t\t\t\texit_code: code,\n\t\t\t\t\tbudget_exceeded: budgetExceeded,\n\t\t\t\t\tstatus: code === 0 && !budgetExceeded ? \"complete\" : \"failed\",\n\t\t\t\t};\n\n\t\t\t\tif (budgetExceeded) {\n\t\t\t\t\t// Force-return whatever exists in result.json, mark partial\n\t\t\t\t\tconst resultData = this.tryReadResultJson(task.task_id, task.cwd ?? this.cwd);\n\t\t\t\t\tresult.status = resultData ? \"partial\" : \"failed\";\n\t\t\t\t\tresult.result_data = resultData;\n\t\t\t\t\tif (resultData) {\n\t\t\t\t\t\tresult.ok = true; // partial is considered success with data\n\t\t\t\t\t}\n\t\t\t\t\tthis.writeOutputJson(task.task_id, result);\n\t\t\t\t\tthis.emit(\"task_done\", {\n\t\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\t\tagent_type: task.agent_type,\n\t\t\t\t\t\tduration,\n\t\t\t\t\t\ttokens_used,\n\t\t\t\t\t\tstatus: \"partial\",\n\t\t\t\t\t});\n\t\t\t\t\tthis.resolveWaiter(task.task_id, result);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (result.ok) {\n\t\t\t\t\tconst verification = this.verifier.verify(task.task_id, task.cwd ?? this.cwd);\n\t\t\t\t\tif (!verification.valid) {\n\t\t\t\t\t\tresult.ok = false;\n\t\t\t\t\t\tresult.error = verification.reason;\n\t\t\t\t\t\tresult.status = \"failed\";\n\t\t\t\t\t\tthis.writeOutputJson(task.task_id, result);\n\t\t\t\t\t\tthis.emit(\"task_failed\", {\n\t\t\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\t\t\tagent_type: task.agent_type,\n\t\t\t\t\t\t\tduration,\n\t\t\t\t\t\t\ttokens_used,\n\t\t\t\t\t\t\terror: verification.reason,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tthis.resolveWaiter(task.task_id, result);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tthis.writeOutputJson(task.task_id, result);\n\n\t\t\t\tif (result.ok) {\n\t\t\t\t\tthis.emit(\"task_done\", {\n\t\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\t\tagent_type: task.agent_type,\n\t\t\t\t\t\tduration,\n\t\t\t\t\t\ttokens_used,\n\t\t\t\t\t\tstatus: \"complete\",\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tthis.emit(\"task_failed\", {\n\t\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\t\tagent_type: task.agent_type,\n\t\t\t\t\t\tduration,\n\t\t\t\t\t\ttokens_used,\n\t\t\t\t\t\terror: result.error ?? `Exited with code ${code}`,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tthis.resolveWaiter(task.task_id, result);\n\t\t\t})\n\t\t\t.catch((err) => {\n\t\t\t\tthis.slots.delete(task.task_id);\n\t\t\t\tbudget.flush();\n\t\t\t\tconst duration = Date.now() - slot.spawned_at;\n\t\t\t\tconst tokens_used = budget.getUsed();\n\t\t\t\tif (!isRetry) {\n\t\t\t\t\tthis.startTask(task, true);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst error = err instanceof Error ? err.message : String(err);\n\t\t\t\tconst result: SubagentResult = {\n\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\tok: false,\n\t\t\t\t\tstdout,\n\t\t\t\t\tstderr,\n\t\t\t\t\texit_code: null,\n\t\t\t\t\terror,\n\t\t\t\t\tstatus: \"failed\",\n\t\t\t\t};\n\t\t\t\tthis.writeOutputJson(task.task_id, result);\n\t\t\t\tthis.emit(\"task_failed\", {\n\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\tagent_type: task.agent_type,\n\t\t\t\t\tduration,\n\t\t\t\t\ttokens_used,\n\t\t\t\t\terror,\n\t\t\t\t});\n\t\t\t\tthis.resolveWaiter(task.task_id, result);\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\tbudget.removeAllListeners();\n\t\t\t\tthis.budgets.delete(task.task_id);\n\t\t\t\tthis.pull();\n\t\t\t});\n\t}\n\n\tprivate tryReadResultJson(task_id: string, cwd: string): Record<string, unknown> | undefined {\n\t\tconst path = join(cwd, CONFIG_DIR_NAME, \"agents\", task_id, \"result.json\");\n\t\tif (!existsSync(path)) return undefined;\n\t\ttry {\n\t\t\tconst raw = readFileSync(path, \"utf-8\");\n\t\t\treturn JSON.parse(raw) as Record<string, unknown>;\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tprivate resolveWaiter(task_id: string, result: SubagentResult): void {\n\t\t// Persist terminal status for get_status() even after wait_for consumes the result\n\t\tif (result.status === \"stalled\") this.taskStatus.set(task_id, \"stalled\");\n\t\telse if (result.status === \"timeout\") this.taskStatus.set(task_id, \"timeout\");\n\t\telse if (result.ok) this.taskStatus.set(task_id, \"done\");\n\t\telse this.taskStatus.set(task_id, \"failed\");\n\n\t\tconst waiter = this.waiters.get(task_id);\n\t\tif (waiter) {\n\t\t\twaiter.resolve(result);\n\t\t\tthis.waiters.delete(task_id);\n\t\t\treturn;\n\t\t}\n\t\tthis.completed.set(task_id, result);\n\t}\n}\n"]}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { EventEmitter } from "node:events";
|
|
3
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
-
import { join } from "node:path";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
5
|
import { CONFIG_DIR_NAME } from "../config.js";
|
|
6
6
|
import { waitForChildProcess } from "../utils/child-process.js";
|
|
7
|
+
import { DispatchEvaluator } from "./dispatch-evaluator.js";
|
|
7
8
|
import { SubagentLifeguard } from "./lifeguard.js";
|
|
8
9
|
import { OutputVerifier } from "./output-verifier.js";
|
|
9
10
|
import { getSubagentSystemPrompt } from "./subagent.js";
|
|
@@ -59,6 +60,7 @@ export class SubagentPool extends EventEmitter {
|
|
|
59
60
|
priorityOf(agent_type) {
|
|
60
61
|
switch (agent_type) {
|
|
61
62
|
case "explore":
|
|
63
|
+
case "review":
|
|
62
64
|
return 2;
|
|
63
65
|
case "doc":
|
|
64
66
|
return 0;
|
|
@@ -129,6 +131,105 @@ export class SubagentPool extends EventEmitter {
|
|
|
129
131
|
queued_count() {
|
|
130
132
|
return this.queue.length;
|
|
131
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Dispatch a task through the evaluator.
|
|
136
|
+
*
|
|
137
|
+
* - If `forceAgent` is provided, skip evaluation and spawn directly.
|
|
138
|
+
* - Otherwise evaluate the task. If it should be handled inline, return
|
|
139
|
+
* `{ handled_inline: true }` immediately.
|
|
140
|
+
* - If delegating, spawn the subagent, wait for completion, write
|
|
141
|
+
* `output.json`, and return the result.
|
|
142
|
+
*/
|
|
143
|
+
async dispatch(task, forceAgent) {
|
|
144
|
+
if (this.disposed) {
|
|
145
|
+
return Promise.reject(new Error("SubagentPool has been disposed"));
|
|
146
|
+
}
|
|
147
|
+
const evaluator = new DispatchEvaluator();
|
|
148
|
+
const analysis = evaluator.evaluate(task);
|
|
149
|
+
if (!forceAgent && !analysis.should_delegate) {
|
|
150
|
+
return { handled_inline: true, reason: analysis.reason };
|
|
151
|
+
}
|
|
152
|
+
const agent_type = forceAgent ?? analysis.agent_type ?? "explore";
|
|
153
|
+
const task_id = `dispatch-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
154
|
+
const reason = forceAgent ? "user_override" : analysis.reason;
|
|
155
|
+
const complexity = analysis.estimated_complexity;
|
|
156
|
+
// Pre-dispatch logging
|
|
157
|
+
const logLine = `[DISPATCH] agent=${agent_type} reason=${reason} complexity=${complexity} task_id=${task_id}`;
|
|
158
|
+
console.log(logLine);
|
|
159
|
+
this.writeDispatchLog(task_id, agent_type, reason, complexity, task);
|
|
160
|
+
const poolTask = {
|
|
161
|
+
task_id,
|
|
162
|
+
agent_type,
|
|
163
|
+
task,
|
|
164
|
+
cwd: this.cwd,
|
|
165
|
+
};
|
|
166
|
+
const startTime = Date.now();
|
|
167
|
+
this.spawn(poolTask);
|
|
168
|
+
const result = await this.wait_for(task_id);
|
|
169
|
+
const duration = Date.now() - startTime;
|
|
170
|
+
return {
|
|
171
|
+
handled_inline: false,
|
|
172
|
+
task_id,
|
|
173
|
+
agent_type,
|
|
174
|
+
reason,
|
|
175
|
+
result,
|
|
176
|
+
duration,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Dispatch a batch of subtasks concurrently.
|
|
181
|
+
*
|
|
182
|
+
* Spawns up to `maxConcurrency` at once; overflow is queued with FIFO.
|
|
183
|
+
* Returns aggregated results in the same order as the input.
|
|
184
|
+
*/
|
|
185
|
+
async dispatchBatch(tasks) {
|
|
186
|
+
if (this.disposed) {
|
|
187
|
+
return Promise.reject(new Error("SubagentPool has been disposed"));
|
|
188
|
+
}
|
|
189
|
+
const promises = tasks.map(async ({ agent_type, prompt }) => {
|
|
190
|
+
return this.dispatch(prompt, agent_type);
|
|
191
|
+
});
|
|
192
|
+
return Promise.all(promises);
|
|
193
|
+
}
|
|
194
|
+
writeDispatchLog(task_id, agent_type, reason, complexity, task) {
|
|
195
|
+
const log = {
|
|
196
|
+
timestamp: new Date().toISOString(),
|
|
197
|
+
task_id,
|
|
198
|
+
agent_type,
|
|
199
|
+
reason,
|
|
200
|
+
complexity,
|
|
201
|
+
task,
|
|
202
|
+
};
|
|
203
|
+
const path = join(this.cwd, CONFIG_DIR_NAME, "agents", task_id, "dispatch-log.json");
|
|
204
|
+
try {
|
|
205
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
206
|
+
writeFileSync(path, JSON.stringify(log, null, 2));
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
// Best-effort persistence
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
writeOutputJson(task_id, result) {
|
|
213
|
+
const output = {
|
|
214
|
+
task_id: result.task_id,
|
|
215
|
+
ok: result.ok,
|
|
216
|
+
exit_code: result.exit_code,
|
|
217
|
+
status: result.status,
|
|
218
|
+
stdout: result.stdout,
|
|
219
|
+
stderr: result.stderr,
|
|
220
|
+
error: result.error,
|
|
221
|
+
budget_exceeded: result.budget_exceeded,
|
|
222
|
+
result_data: result.result_data,
|
|
223
|
+
};
|
|
224
|
+
const path = join(this.cwd, CONFIG_DIR_NAME, "agents", task_id, "output.json");
|
|
225
|
+
try {
|
|
226
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
227
|
+
writeFileSync(path, JSON.stringify(output, null, 2));
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
// Best-effort persistence
|
|
231
|
+
}
|
|
232
|
+
}
|
|
132
233
|
/** Kill all running processes, clear the queue, and reject pending waiters. */
|
|
133
234
|
dispose() {
|
|
134
235
|
if (this.disposed)
|
|
@@ -146,6 +247,9 @@ export class SubagentPool extends EventEmitter {
|
|
|
146
247
|
this.waiters.delete(task_id);
|
|
147
248
|
}
|
|
148
249
|
this.completed.clear();
|
|
250
|
+
for (const budget of this.budgets.values()) {
|
|
251
|
+
budget.removeAllListeners();
|
|
252
|
+
}
|
|
149
253
|
this.budgets.clear();
|
|
150
254
|
this.killReasons.clear();
|
|
151
255
|
this.taskStatus.clear();
|
|
@@ -247,7 +351,6 @@ export class SubagentPool extends EventEmitter {
|
|
|
247
351
|
this.lifeguard.monitor(task.task_id, task.agent_type, proc);
|
|
248
352
|
let stdout = "";
|
|
249
353
|
let stderr = "";
|
|
250
|
-
let budgetExceeded = false;
|
|
251
354
|
proc.stdout?.on("data", (data) => {
|
|
252
355
|
const chunk = data.toString();
|
|
253
356
|
stdout += chunk;
|
|
@@ -271,15 +374,15 @@ export class SubagentPool extends EventEmitter {
|
|
|
271
374
|
proc.stderr?.on("data", (data) => {
|
|
272
375
|
stderr += data.toString();
|
|
273
376
|
});
|
|
274
|
-
budget.once("budget_exceeded", () => {
|
|
275
|
-
budgetExceeded = true;
|
|
276
|
-
});
|
|
277
377
|
waitForChildProcess(proc)
|
|
278
378
|
.then((code) => {
|
|
279
379
|
this.slots.delete(task.task_id);
|
|
280
380
|
budget.flush();
|
|
281
381
|
const killReason = this.killReasons.get(task.task_id);
|
|
282
382
|
this.killReasons.delete(task.task_id);
|
|
383
|
+
const duration = Date.now() - slot.spawned_at;
|
|
384
|
+
const tokens_used = budget.getUsed();
|
|
385
|
+
const budgetExceeded = budget.isExceeded();
|
|
283
386
|
// If killed by lifeguard, override exit handling
|
|
284
387
|
if (killReason === "stalled" || killReason === "timeout") {
|
|
285
388
|
const result = {
|
|
@@ -290,7 +393,13 @@ export class SubagentPool extends EventEmitter {
|
|
|
290
393
|
exit_code: code,
|
|
291
394
|
status: killReason,
|
|
292
395
|
};
|
|
293
|
-
this.
|
|
396
|
+
this.writeOutputJson(task.task_id, result);
|
|
397
|
+
this.emit(`task_${killReason}`, {
|
|
398
|
+
task_id: task.task_id,
|
|
399
|
+
agent_type: task.agent_type,
|
|
400
|
+
duration,
|
|
401
|
+
tokens_used,
|
|
402
|
+
});
|
|
294
403
|
this.resolveWaiter(task.task_id, result);
|
|
295
404
|
return;
|
|
296
405
|
}
|
|
@@ -311,7 +420,14 @@ export class SubagentPool extends EventEmitter {
|
|
|
311
420
|
if (resultData) {
|
|
312
421
|
result.ok = true; // partial is considered success with data
|
|
313
422
|
}
|
|
314
|
-
this.
|
|
423
|
+
this.writeOutputJson(task.task_id, result);
|
|
424
|
+
this.emit("task_done", {
|
|
425
|
+
task_id: task.task_id,
|
|
426
|
+
agent_type: task.agent_type,
|
|
427
|
+
duration,
|
|
428
|
+
tokens_used,
|
|
429
|
+
status: "partial",
|
|
430
|
+
});
|
|
315
431
|
this.resolveWaiter(task.task_id, result);
|
|
316
432
|
return;
|
|
317
433
|
}
|
|
@@ -321,20 +437,34 @@ export class SubagentPool extends EventEmitter {
|
|
|
321
437
|
result.ok = false;
|
|
322
438
|
result.error = verification.reason;
|
|
323
439
|
result.status = "failed";
|
|
440
|
+
this.writeOutputJson(task.task_id, result);
|
|
324
441
|
this.emit("task_failed", {
|
|
325
442
|
task_id: task.task_id,
|
|
443
|
+
agent_type: task.agent_type,
|
|
444
|
+
duration,
|
|
445
|
+
tokens_used,
|
|
326
446
|
error: verification.reason,
|
|
327
447
|
});
|
|
328
448
|
this.resolveWaiter(task.task_id, result);
|
|
329
449
|
return;
|
|
330
450
|
}
|
|
331
451
|
}
|
|
452
|
+
this.writeOutputJson(task.task_id, result);
|
|
332
453
|
if (result.ok) {
|
|
333
|
-
this.emit("task_done", {
|
|
454
|
+
this.emit("task_done", {
|
|
455
|
+
task_id: task.task_id,
|
|
456
|
+
agent_type: task.agent_type,
|
|
457
|
+
duration,
|
|
458
|
+
tokens_used,
|
|
459
|
+
status: "complete",
|
|
460
|
+
});
|
|
334
461
|
}
|
|
335
462
|
else {
|
|
336
463
|
this.emit("task_failed", {
|
|
337
464
|
task_id: task.task_id,
|
|
465
|
+
agent_type: task.agent_type,
|
|
466
|
+
duration,
|
|
467
|
+
tokens_used,
|
|
338
468
|
error: result.error ?? `Exited with code ${code}`,
|
|
339
469
|
});
|
|
340
470
|
}
|
|
@@ -343,13 +473,14 @@ export class SubagentPool extends EventEmitter {
|
|
|
343
473
|
.catch((err) => {
|
|
344
474
|
this.slots.delete(task.task_id);
|
|
345
475
|
budget.flush();
|
|
476
|
+
const duration = Date.now() - slot.spawned_at;
|
|
477
|
+
const tokens_used = budget.getUsed();
|
|
346
478
|
if (!isRetry) {
|
|
347
479
|
this.startTask(task, true);
|
|
348
480
|
return;
|
|
349
481
|
}
|
|
350
482
|
const error = err instanceof Error ? err.message : String(err);
|
|
351
|
-
|
|
352
|
-
this.resolveWaiter(task.task_id, {
|
|
483
|
+
const result = {
|
|
353
484
|
task_id: task.task_id,
|
|
354
485
|
ok: false,
|
|
355
486
|
stdout,
|
|
@@ -357,9 +488,19 @@ export class SubagentPool extends EventEmitter {
|
|
|
357
488
|
exit_code: null,
|
|
358
489
|
error,
|
|
359
490
|
status: "failed",
|
|
491
|
+
};
|
|
492
|
+
this.writeOutputJson(task.task_id, result);
|
|
493
|
+
this.emit("task_failed", {
|
|
494
|
+
task_id: task.task_id,
|
|
495
|
+
agent_type: task.agent_type,
|
|
496
|
+
duration,
|
|
497
|
+
tokens_used,
|
|
498
|
+
error,
|
|
360
499
|
});
|
|
500
|
+
this.resolveWaiter(task.task_id, result);
|
|
361
501
|
})
|
|
362
502
|
.finally(() => {
|
|
503
|
+
budget.removeAllListeners();
|
|
363
504
|
this.budgets.delete(task.task_id);
|
|
364
505
|
this.pull();
|
|
365
506
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subagent-pool.js","sourceRoot":"","sources":["../../src/core/subagent-pool.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { EventEmitter } from \"node:events\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { CONFIG_DIR_NAME } from \"../config.js\";\nimport { waitForChildProcess } from \"../utils/child-process.js\";\nimport { SubagentLifeguard } from \"./lifeguard.js\";\nimport { OutputVerifier } from \"./output-verifier.js\";\nimport { getSubagentSystemPrompt, type SubagentMode } from \"./subagent.js\";\nimport { TokenBudget } from \"./token-budget.js\";\n\nexport interface SubagentPoolTask {\n\ttask_id: string;\n\tagent_type: SubagentMode | string;\n\ttask: string;\n\tcontext?: string;\n\ttoken_budget?: number;\n\tcwd?: string;\n\tmodel?: string;\n\tprovider?: string;\n}\n\nexport interface SubagentSlot {\n\tpid: number;\n\tagent_type: string;\n\ttask_id: string;\n\tspawned_at: number;\n\ttoken_budget: number;\n\tprocess: ReturnType<typeof spawn>;\n}\n\nexport interface SubagentResult {\n\ttask_id: string;\n\tok: boolean;\n\tstdout: string;\n\tstderr: string;\n\texit_code: number | null;\n\terror?: string;\n\t/** True when the task exceeded its token budget and was hard-stopped. */\n\tbudget_exceeded?: boolean;\n\t/** Terminal status derived from how the task finished. */\n\tstatus?: \"complete\" | \"partial\" | \"failed\" | \"stalled\" | \"timeout\";\n\t/** Parsed result.json content when available (e.g. on partial completion). */\n\tresult_data?: Record<string, unknown>;\n}\n\nexport interface SubagentPoolOptions {\n\t/** Path to the hoocode executable. */\n\texecutable: string;\n\t/** Maximum concurrent child processes. Defaults to 5. */\n\tmaxConcurrency?: number;\n\t/** Working directory for spawned processes. Defaults to process.cwd(). */\n\tcwd?: string;\n\t/** Environment variables. Defaults to process.env. */\n\tenv?: NodeJS.ProcessEnv;\n\t/** Default token budget per task. Defaults to 0. */\n\tdefaultTokenBudget?: number;\n}\n\n/**\n * Pool for running hoocode subagents as child processes with bounded concurrency,\n * FIFO queuing with priority support, and automatic slot refill.\n *\n * Events:\n * - \"task_done\" – task completed successfully and output was verified\n * - \"task_failed\" – task failed (spawn error, bad exit code, verification failure)\n * - \"task_stalled\" – heartbeat missed for 60s, process was SIGKILLed\n * - \"task_timeout\" – hard timeout exceeded, process was SIGKILLed\n * - \"budget_warning\" – token usage crossed 80% threshold\n */\nexport class SubagentPool extends EventEmitter {\n\tprivate readonly maxConcurrency: number;\n\tprivate readonly executable: string;\n\tprivate readonly cwd: string;\n\tprivate readonly env: NodeJS.ProcessEnv;\n\tprivate readonly defaultTokenBudget: number;\n\n\tprivate slots = new Map<string, SubagentSlot>();\n\tprivate queue: SubagentPoolTask[] = [];\n\tprivate completed = new Map<string, SubagentResult>();\n\tprivate waiters = new Map<string, { resolve: (result: SubagentResult) => void; reject: (err: Error) => void }>();\n\tprivate budgets = new Map<string, TokenBudget>();\n\tprivate verifier = new OutputVerifier();\n\tprivate lifeguard: SubagentLifeguard;\n\tprivate disposed = false;\n\t/** Tracks why a task was killed (stalled / timeout) before exit handler fires. */\n\tprivate killReasons = new Map<string, \"stalled\" | \"timeout\">();\n\t/** Persistent terminal status map, survives wait_for consumption. */\n\tprivate taskStatus = new Map<string, \"done\" | \"failed\" | \"stalled\" | \"timeout\">();\n\n\tconstructor(options: SubagentPoolOptions) {\n\t\tsuper();\n\t\tthis.maxConcurrency = options.maxConcurrency ?? 5;\n\t\tthis.executable = options.executable;\n\t\tthis.cwd = options.cwd ?? process.cwd();\n\t\tthis.env = options.env ?? process.env;\n\t\tthis.defaultTokenBudget = options.defaultTokenBudget ?? 0;\n\t\tthis.verifier = new OutputVerifier(this.cwd);\n\t\tthis.lifeguard = new SubagentLifeguard(this.cwd);\n\t\tthis.lifeguard.on(\"stalled\", (data: { task_id: string; pid: number }) => {\n\t\t\tthis.killReasons.set(data.task_id, \"stalled\");\n\t\t\tthis.emit(\"task_stalled\", data);\n\t\t});\n\t\tthis.lifeguard.on(\"timeout\", (data: { task_id: string; pid: number }) => {\n\t\t\tthis.killReasons.set(data.task_id, \"timeout\");\n\t\t\tthis.emit(\"task_timeout\", data);\n\t\t});\n\t}\n\n\t/** Priority value: higher numbers run first. */\n\tprivate priorityOf(agent_type: string): number {\n\t\tswitch (agent_type) {\n\t\t\tcase \"explore\":\n\t\t\t\treturn 2;\n\t\t\tcase \"doc\":\n\t\t\t\treturn 0;\n\t\t\tdefault:\n\t\t\t\treturn 1;\n\t\t}\n\t}\n\n\t/** Queue a task. It will run when a slot is free. */\n\tspawn(task: SubagentPoolTask): void {\n\t\tif (this.disposed) {\n\t\t\tthrow new Error(\"SubagentPool has been disposed\");\n\t\t}\n\t\tif (\n\t\t\tthis.slots.has(task.task_id) ||\n\t\t\tthis.queue.some((t) => t.task_id === task.task_id) ||\n\t\t\tthis.completed.has(task.task_id)\n\t\t) {\n\t\t\tthrow new Error(`Duplicate task_id: ${task.task_id}`);\n\t\t}\n\n\t\tconst p = this.priorityOf(task.agent_type);\n\t\tconst idx = this.queue.findIndex((t) => this.priorityOf(t.agent_type) < p);\n\t\tif (idx === -1) {\n\t\t\tthis.queue.push(task);\n\t\t} else {\n\t\t\tthis.queue.splice(idx, 0, task);\n\t\t}\n\t\tthis.pull();\n\t}\n\n\t/** Current status of a task. */\n\tget_status(task_id: string): \"running\" | \"queued\" | \"done\" | \"failed\" | \"stalled\" | \"timeout\" {\n\t\tif (this.slots.has(task_id)) return \"running\";\n\t\tif (this.queue.some((t) => t.task_id === task_id)) return \"queued\";\n\t\tconst persisted = this.taskStatus.get(task_id);\n\t\tif (persisted) return persisted;\n\t\tconst result = this.completed.get(task_id);\n\t\tif (result) {\n\t\t\tif (result.status === \"stalled\") return \"stalled\";\n\t\t\tif (result.status === \"timeout\") return \"timeout\";\n\t\t\tif (result.ok) return \"done\";\n\t\t\treturn \"failed\";\n\t\t}\n\t\treturn \"failed\";\n\t}\n\n\t/** Wait for a task to complete and return its result. */\n\twait_for(task_id: string): Promise<SubagentResult> {\n\t\tif (this.disposed) {\n\t\t\treturn Promise.reject(new Error(\"SubagentPool has been disposed\"));\n\t\t}\n\n\t\tconst existing = this.completed.get(task_id);\n\t\tif (existing) {\n\t\t\tthis.completed.delete(task_id);\n\t\t\treturn Promise.resolve(existing);\n\t\t}\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tthis.waiters.set(task_id, { resolve, reject });\n\t\t});\n\t}\n\n\t/** Number of currently running subagents. */\n\trunning_count(): number {\n\t\treturn this.slots.size;\n\t}\n\n\t/** Number of tasks waiting in the queue. */\n\tqueued_count(): number {\n\t\treturn this.queue.length;\n\t}\n\n\t/** Kill all running processes, clear the queue, and reject pending waiters. */\n\tdispose(): void {\n\t\tif (this.disposed) return;\n\t\tthis.disposed = true;\n\n\t\tfor (const slot of this.slots.values()) {\n\t\t\tif (!slot.process.killed) {\n\t\t\t\tslot.process.kill(\"SIGTERM\");\n\t\t\t}\n\t\t}\n\t\tthis.slots.clear();\n\t\tthis.queue = [];\n\n\t\tfor (const [task_id, waiter] of this.waiters) {\n\t\t\twaiter.reject(new Error(\"SubagentPool disposed\"));\n\t\t\tthis.waiters.delete(task_id);\n\t\t}\n\t\tthis.completed.clear();\n\t\tthis.budgets.clear();\n\t\tthis.killReasons.clear();\n\t\tthis.taskStatus.clear();\n\t\tthis.lifeguard.dispose();\n\t\tthis.removeAllListeners();\n\t}\n\n\t/** Pull tasks from the queue while slots are available. */\n\tprivate pull(): void {\n\t\twhile (this.slots.size < this.maxConcurrency && this.queue.length > 0) {\n\t\t\tconst task = this.queue.shift()!;\n\t\t\tthis.startTask(task, false);\n\t\t}\n\t}\n\n\t/** Build CLI arguments for a task. */\n\tprivate buildArgs(task: SubagentPoolTask): string[] {\n\t\tconst args: string[] = [\"--mode\", \"json\", \"--no-session\"];\n\n\t\tif (task.agent_type) {\n\t\t\ttry {\n\t\t\t\tconst systemPrompt = getSubagentSystemPrompt(task.agent_type as SubagentMode);\n\t\t\t\targs.push(\"--system-prompt\", systemPrompt);\n\t\t\t} catch {\n\t\t\t\t// Unknown mode, skip custom system prompt\n\t\t\t}\n\t\t}\n\n\t\tif (task.model) {\n\t\t\targs.push(\"--model\", task.model);\n\t\t}\n\t\tif (task.provider) {\n\t\t\targs.push(\"--provider\", task.provider);\n\t\t}\n\n\t\tconst prompt = task.context?.trim()\n\t\t\t? `Context from the calling agent:\\n\\n${task.context.trim()}\\n\\nTask: ${task.task.trim()}`\n\t\t\t: `Task: ${task.task.trim()}`;\n\t\targs.push(prompt);\n\n\t\treturn args;\n\t}\n\n\t/** Start a task in a child process, with one retry on failure. */\n\tprivate startTask(task: SubagentPoolTask, isRetry: boolean): void {\n\t\t// Get or create a TokenBudget tracker. On retry, reuse the existing one\n\t\t// so cumulative usage persists across retries.\n\t\tlet budget = this.budgets.get(task.task_id);\n\t\tif (!budget) {\n\t\t\tbudget = new TokenBudget(task.task_id, task.agent_type, {\n\t\t\t\tlimit: task.token_budget,\n\t\t\t\tcwd: task.cwd ?? this.cwd,\n\t\t\t});\n\t\t\tbudget.on(\"budget_warning\", (data: { task_id: string; message: string; used: number; limit: number }) => {\n\t\t\t\tthis.emit(\"budget_warning\", data);\n\t\t\t});\n\t\t\tbudget.on(\"budget_exceeded\", () => {\n\t\t\t\tconst slot = this.slots.get(task.task_id);\n\t\t\t\tif (slot && !slot.process.killed) {\n\t\t\t\t\tslot.process.kill(\"SIGTERM\");\n\t\t\t\t}\n\t\t\t});\n\t\t\tthis.budgets.set(task.task_id, budget);\n\t\t}\n\n\t\tlet proc: ReturnType<typeof spawn>;\n\t\ttry {\n\t\t\tproc = spawn(this.executable, this.buildArgs(task), {\n\t\t\t\tcwd: task.cwd ?? this.cwd,\n\t\t\t\tenv: this.env,\n\t\t\t\tshell: false,\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t});\n\t\t} catch {\n\t\t\tif (!isRetry) {\n\t\t\t\tthis.startTask(task, true);\n\t\t\t} else {\n\t\t\t\tthis.emit(\"task_failed\", {\n\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\terror: \"Spawn failed synchronously\",\n\t\t\t\t});\n\t\t\t\tthis.resolveWaiter(task.task_id, {\n\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\tok: false,\n\t\t\t\t\tstdout: \"\",\n\t\t\t\t\tstderr: \"\",\n\t\t\t\t\texit_code: null,\n\t\t\t\t\terror: \"Spawn failed synchronously\",\n\t\t\t\t\tstatus: \"failed\",\n\t\t\t\t});\n\t\t\t\tthis.pull();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst slot: SubagentSlot = {\n\t\t\tpid: proc.pid ?? 0,\n\t\t\tagent_type: task.agent_type,\n\t\t\ttask_id: task.task_id,\n\t\t\tspawned_at: Date.now(),\n\t\t\ttoken_budget: task.token_budget ?? this.defaultTokenBudget,\n\t\t\tprocess: proc,\n\t\t};\n\n\t\tthis.slots.set(task.task_id, slot);\n\t\tthis.lifeguard.monitor(task.task_id, task.agent_type, proc);\n\n\t\tlet stdout = \"\";\n\t\tlet stderr = \"\";\n\t\tlet budgetExceeded = false;\n\n\t\tproc.stdout?.on(\"data\", (data: Buffer) => {\n\t\t\tconst chunk = data.toString();\n\t\t\tstdout += chunk;\n\t\t\tbudget.processStdout(chunk);\n\n\t\t\t// Heartbeat detection: look for {\"ping\":true} JSON lines\n\t\t\tfor (const raw of chunk.split(\"\\n\")) {\n\t\t\t\tconst line = raw.trim();\n\t\t\t\tif (!line.startsWith(\"{\")) continue;\n\t\t\t\ttry {\n\t\t\t\t\tconst parsed = JSON.parse(line) as Record<string, unknown>;\n\t\t\t\t\tif (parsed.ping === true) {\n\t\t\t\t\t\tthis.lifeguard.recordHeartbeat(task.task_id);\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// Not a ping line, ignore\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tproc.stderr?.on(\"data\", (data: Buffer) => {\n\t\t\tstderr += data.toString();\n\t\t});\n\n\t\tbudget.once(\"budget_exceeded\", () => {\n\t\t\tbudgetExceeded = true;\n\t\t});\n\n\t\twaitForChildProcess(proc)\n\t\t\t.then((code) => {\n\t\t\t\tthis.slots.delete(task.task_id);\n\t\t\t\tbudget.flush();\n\n\t\t\t\tconst killReason = this.killReasons.get(task.task_id);\n\t\t\t\tthis.killReasons.delete(task.task_id);\n\n\t\t\t\t// If killed by lifeguard, override exit handling\n\t\t\t\tif (killReason === \"stalled\" || killReason === \"timeout\") {\n\t\t\t\t\tconst result: SubagentResult = {\n\t\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\t\tok: false,\n\t\t\t\t\t\tstdout,\n\t\t\t\t\t\tstderr,\n\t\t\t\t\t\texit_code: code,\n\t\t\t\t\t\tstatus: killReason,\n\t\t\t\t\t};\n\t\t\t\t\tthis.emit(`task_${killReason}`, { task_id: task.task_id });\n\t\t\t\t\tthis.resolveWaiter(task.task_id, result);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst result: SubagentResult = {\n\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\tok: code === 0 && !budgetExceeded,\n\t\t\t\t\tstdout,\n\t\t\t\t\tstderr,\n\t\t\t\t\texit_code: code,\n\t\t\t\t\tbudget_exceeded: budgetExceeded,\n\t\t\t\t\tstatus: code === 0 && !budgetExceeded ? \"complete\" : \"failed\",\n\t\t\t\t};\n\n\t\t\t\tif (budgetExceeded) {\n\t\t\t\t\t// Force-return whatever exists in result.json, mark partial\n\t\t\t\t\tconst resultData = this.tryReadResultJson(task.task_id, task.cwd ?? this.cwd);\n\t\t\t\t\tresult.status = resultData ? \"partial\" : \"failed\";\n\t\t\t\t\tresult.result_data = resultData;\n\t\t\t\t\tif (resultData) {\n\t\t\t\t\t\tresult.ok = true; // partial is considered success with data\n\t\t\t\t\t}\n\t\t\t\t\tthis.emit(\"task_done\", { task_id: task.task_id, status: \"partial\" });\n\t\t\t\t\tthis.resolveWaiter(task.task_id, result);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (result.ok) {\n\t\t\t\t\tconst verification = this.verifier.verify(task.task_id, task.cwd ?? this.cwd);\n\t\t\t\t\tif (!verification.valid) {\n\t\t\t\t\t\tresult.ok = false;\n\t\t\t\t\t\tresult.error = verification.reason;\n\t\t\t\t\t\tresult.status = \"failed\";\n\t\t\t\t\t\tthis.emit(\"task_failed\", {\n\t\t\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\t\t\terror: verification.reason,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tthis.resolveWaiter(task.task_id, result);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (result.ok) {\n\t\t\t\t\tthis.emit(\"task_done\", { task_id: task.task_id, status: \"complete\" });\n\t\t\t\t} else {\n\t\t\t\t\tthis.emit(\"task_failed\", {\n\t\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\t\terror: result.error ?? `Exited with code ${code}`,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tthis.resolveWaiter(task.task_id, result);\n\t\t\t})\n\t\t\t.catch((err) => {\n\t\t\t\tthis.slots.delete(task.task_id);\n\t\t\t\tbudget.flush();\n\t\t\t\tif (!isRetry) {\n\t\t\t\t\tthis.startTask(task, true);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst error = err instanceof Error ? err.message : String(err);\n\t\t\t\tthis.emit(\"task_failed\", { task_id: task.task_id, error });\n\t\t\t\tthis.resolveWaiter(task.task_id, {\n\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\tok: false,\n\t\t\t\t\tstdout,\n\t\t\t\t\tstderr,\n\t\t\t\t\texit_code: null,\n\t\t\t\t\terror,\n\t\t\t\t\tstatus: \"failed\",\n\t\t\t\t});\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\tthis.budgets.delete(task.task_id);\n\t\t\t\tthis.pull();\n\t\t\t});\n\t}\n\n\tprivate tryReadResultJson(task_id: string, cwd: string): Record<string, unknown> | undefined {\n\t\tconst path = join(cwd, CONFIG_DIR_NAME, \"agents\", task_id, \"result.json\");\n\t\tif (!existsSync(path)) return undefined;\n\t\ttry {\n\t\t\tconst raw = readFileSync(path, \"utf-8\");\n\t\t\treturn JSON.parse(raw) as Record<string, unknown>;\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tprivate resolveWaiter(task_id: string, result: SubagentResult): void {\n\t\t// Persist terminal status for get_status() even after wait_for consumes the result\n\t\tif (result.status === \"stalled\") this.taskStatus.set(task_id, \"stalled\");\n\t\telse if (result.status === \"timeout\") this.taskStatus.set(task_id, \"timeout\");\n\t\telse if (result.ok) this.taskStatus.set(task_id, \"done\");\n\t\telse this.taskStatus.set(task_id, \"failed\");\n\n\t\tconst waiter = this.waiters.get(task_id);\n\t\tif (waiter) {\n\t\t\twaiter.resolve(result);\n\t\t\tthis.waiters.delete(task_id);\n\t\t\treturn;\n\t\t}\n\t\tthis.completed.set(task_id, result);\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"subagent-pool.js","sourceRoot":"","sources":["../../src/core/subagent-pool.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { EventEmitter } from \"node:events\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { CONFIG_DIR_NAME } from \"../config.js\";\nimport { waitForChildProcess } from \"../utils/child-process.js\";\nimport { type AgentType, DispatchEvaluator } from \"./dispatch-evaluator.js\";\nimport { SubagentLifeguard } from \"./lifeguard.js\";\nimport { OutputVerifier } from \"./output-verifier.js\";\nimport { getSubagentSystemPrompt, type SubagentMode } from \"./subagent.js\";\nimport { TokenBudget } from \"./token-budget.js\";\n\nexport interface SubagentPoolTask {\n\ttask_id: string;\n\tagent_type: SubagentMode | string;\n\ttask: string;\n\tcontext?: string;\n\ttoken_budget?: number;\n\tcwd?: string;\n\tmodel?: string;\n\tprovider?: string;\n}\n\nexport interface SubagentSlot {\n\tpid: number;\n\tagent_type: string;\n\ttask_id: string;\n\tspawned_at: number;\n\ttoken_budget: number;\n\tprocess: ReturnType<typeof spawn>;\n}\n\nexport interface SubagentResult {\n\ttask_id: string;\n\tok: boolean;\n\tstdout: string;\n\tstderr: string;\n\texit_code: number | null;\n\terror?: string;\n\t/** True when the task exceeded its token budget and was hard-stopped. */\n\tbudget_exceeded?: boolean;\n\t/** Terminal status derived from how the task finished. */\n\tstatus?: \"complete\" | \"partial\" | \"failed\" | \"stalled\" | \"timeout\";\n\t/** Parsed result.json content when available (e.g. on partial completion). */\n\tresult_data?: Record<string, unknown>;\n}\n\nexport interface TaskResult {\n\t/** True when the evaluator decided the task is simple enough for inline handling. */\n\thandled_inline: boolean;\n\t/** Present when the task was delegated. */\n\ttask_id?: string;\n\tagent_type?: string;\n\treason?: string;\n\t/** Subagent result when delegated. */\n\tresult?: SubagentResult;\n\t/** Duration in milliseconds when delegated. */\n\tduration?: number;\n}\n\nexport interface SubagentPoolOptions {\n\t/** Path to the hoocode executable. */\n\texecutable: string;\n\t/** Maximum concurrent child processes. Defaults to 5. */\n\tmaxConcurrency?: number;\n\t/** Working directory for spawned processes. Defaults to process.cwd(). */\n\tcwd?: string;\n\t/** Environment variables. Defaults to process.env. */\n\tenv?: NodeJS.ProcessEnv;\n\t/** Default token budget per task. Defaults to 0. */\n\tdefaultTokenBudget?: number;\n}\n\n/**\n * Pool for running hoocode subagents as child processes with bounded concurrency,\n * FIFO queuing with priority support, and automatic slot refill.\n *\n * Events:\n * - \"task_done\" – task completed successfully and output was verified\n * - \"task_failed\" – task failed (spawn error, bad exit code, verification failure)\n * - \"task_stalled\" – heartbeat missed for 60s, process was SIGKILLed\n * - \"task_timeout\" – hard timeout exceeded, process was SIGKILLed\n * - \"budget_warning\" – token usage crossed 80% threshold\n */\nexport class SubagentPool extends EventEmitter {\n\tprivate readonly maxConcurrency: number;\n\tprivate readonly executable: string;\n\tprivate readonly cwd: string;\n\tprivate readonly env: NodeJS.ProcessEnv;\n\tprivate readonly defaultTokenBudget: number;\n\n\tprivate slots = new Map<string, SubagentSlot>();\n\tprivate queue: SubagentPoolTask[] = [];\n\tprivate completed = new Map<string, SubagentResult>();\n\tprivate waiters = new Map<string, { resolve: (result: SubagentResult) => void; reject: (err: Error) => void }>();\n\tprivate budgets = new Map<string, TokenBudget>();\n\tprivate verifier = new OutputVerifier();\n\tprivate lifeguard: SubagentLifeguard;\n\tprivate disposed = false;\n\t/** Tracks why a task was killed (stalled / timeout) before exit handler fires. */\n\tprivate killReasons = new Map<string, \"stalled\" | \"timeout\">();\n\t/** Persistent terminal status map, survives wait_for consumption. */\n\tprivate taskStatus = new Map<string, \"done\" | \"failed\" | \"stalled\" | \"timeout\">();\n\n\tconstructor(options: SubagentPoolOptions) {\n\t\tsuper();\n\t\tthis.maxConcurrency = options.maxConcurrency ?? 5;\n\t\tthis.executable = options.executable;\n\t\tthis.cwd = options.cwd ?? process.cwd();\n\t\tthis.env = options.env ?? process.env;\n\t\tthis.defaultTokenBudget = options.defaultTokenBudget ?? 0;\n\t\tthis.verifier = new OutputVerifier(this.cwd);\n\t\tthis.lifeguard = new SubagentLifeguard(this.cwd);\n\t\tthis.lifeguard.on(\"stalled\", (data: { task_id: string; pid: number }) => {\n\t\t\tthis.killReasons.set(data.task_id, \"stalled\");\n\t\t\tthis.emit(\"task_stalled\", data);\n\t\t});\n\t\tthis.lifeguard.on(\"timeout\", (data: { task_id: string; pid: number }) => {\n\t\t\tthis.killReasons.set(data.task_id, \"timeout\");\n\t\t\tthis.emit(\"task_timeout\", data);\n\t\t});\n\t}\n\n\t/** Priority value: higher numbers run first. */\n\tprivate priorityOf(agent_type: string): number {\n\t\tswitch (agent_type) {\n\t\t\tcase \"explore\":\n\t\t\tcase \"review\":\n\t\t\t\treturn 2;\n\t\t\tcase \"doc\":\n\t\t\t\treturn 0;\n\t\t\tdefault:\n\t\t\t\treturn 1;\n\t\t}\n\t}\n\n\t/** Queue a task. It will run when a slot is free. */\n\tspawn(task: SubagentPoolTask): void {\n\t\tif (this.disposed) {\n\t\t\tthrow new Error(\"SubagentPool has been disposed\");\n\t\t}\n\t\tif (\n\t\t\tthis.slots.has(task.task_id) ||\n\t\t\tthis.queue.some((t) => t.task_id === task.task_id) ||\n\t\t\tthis.completed.has(task.task_id)\n\t\t) {\n\t\t\tthrow new Error(`Duplicate task_id: ${task.task_id}`);\n\t\t}\n\n\t\tconst p = this.priorityOf(task.agent_type);\n\t\tconst idx = this.queue.findIndex((t) => this.priorityOf(t.agent_type) < p);\n\t\tif (idx === -1) {\n\t\t\tthis.queue.push(task);\n\t\t} else {\n\t\t\tthis.queue.splice(idx, 0, task);\n\t\t}\n\t\tthis.pull();\n\t}\n\n\t/** Current status of a task. */\n\tget_status(task_id: string): \"running\" | \"queued\" | \"done\" | \"failed\" | \"stalled\" | \"timeout\" {\n\t\tif (this.slots.has(task_id)) return \"running\";\n\t\tif (this.queue.some((t) => t.task_id === task_id)) return \"queued\";\n\t\tconst persisted = this.taskStatus.get(task_id);\n\t\tif (persisted) return persisted;\n\t\tconst result = this.completed.get(task_id);\n\t\tif (result) {\n\t\t\tif (result.status === \"stalled\") return \"stalled\";\n\t\t\tif (result.status === \"timeout\") return \"timeout\";\n\t\t\tif (result.ok) return \"done\";\n\t\t\treturn \"failed\";\n\t\t}\n\t\treturn \"failed\";\n\t}\n\n\t/** Wait for a task to complete and return its result. */\n\twait_for(task_id: string): Promise<SubagentResult> {\n\t\tif (this.disposed) {\n\t\t\treturn Promise.reject(new Error(\"SubagentPool has been disposed\"));\n\t\t}\n\n\t\tconst existing = this.completed.get(task_id);\n\t\tif (existing) {\n\t\t\tthis.completed.delete(task_id);\n\t\t\treturn Promise.resolve(existing);\n\t\t}\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tthis.waiters.set(task_id, { resolve, reject });\n\t\t});\n\t}\n\n\t/** Number of currently running subagents. */\n\trunning_count(): number {\n\t\treturn this.slots.size;\n\t}\n\n\t/** Number of tasks waiting in the queue. */\n\tqueued_count(): number {\n\t\treturn this.queue.length;\n\t}\n\n\t/**\n\t * Dispatch a task through the evaluator.\n\t *\n\t * - If `forceAgent` is provided, skip evaluation and spawn directly.\n\t * - Otherwise evaluate the task. If it should be handled inline, return\n\t * `{ handled_inline: true }` immediately.\n\t * - If delegating, spawn the subagent, wait for completion, write\n\t * `output.json`, and return the result.\n\t */\n\tasync dispatch(task: string, forceAgent?: AgentType): Promise<TaskResult> {\n\t\tif (this.disposed) {\n\t\t\treturn Promise.reject(new Error(\"SubagentPool has been disposed\"));\n\t\t}\n\n\t\tconst evaluator = new DispatchEvaluator();\n\t\tconst analysis = evaluator.evaluate(task);\n\n\t\tif (!forceAgent && !analysis.should_delegate) {\n\t\t\treturn { handled_inline: true, reason: analysis.reason };\n\t\t}\n\n\t\tconst agent_type: AgentType = forceAgent ?? (analysis.agent_type as AgentType) ?? \"explore\";\n\t\tconst task_id = `dispatch-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n\t\tconst reason = forceAgent ? \"user_override\" : analysis.reason;\n\t\tconst complexity = analysis.estimated_complexity;\n\n\t\t// Pre-dispatch logging\n\t\tconst logLine = `[DISPATCH] agent=${agent_type} reason=${reason} complexity=${complexity} task_id=${task_id}`;\n\t\tconsole.log(logLine);\n\t\tthis.writeDispatchLog(task_id, agent_type, reason, complexity, task);\n\n\t\tconst poolTask: SubagentPoolTask = {\n\t\t\ttask_id,\n\t\t\tagent_type,\n\t\t\ttask,\n\t\t\tcwd: this.cwd,\n\t\t};\n\n\t\tconst startTime = Date.now();\n\t\tthis.spawn(poolTask);\n\t\tconst result = await this.wait_for(task_id);\n\t\tconst duration = Date.now() - startTime;\n\n\t\treturn {\n\t\t\thandled_inline: false,\n\t\t\ttask_id,\n\t\t\tagent_type,\n\t\t\treason,\n\t\t\tresult,\n\t\t\tduration,\n\t\t};\n\t}\n\n\t/**\n\t * Dispatch a batch of subtasks concurrently.\n\t *\n\t * Spawns up to `maxConcurrency` at once; overflow is queued with FIFO.\n\t * Returns aggregated results in the same order as the input.\n\t */\n\tasync dispatchBatch(tasks: Array<{ agent_type: AgentType; prompt: string }>): Promise<TaskResult[]> {\n\t\tif (this.disposed) {\n\t\t\treturn Promise.reject(new Error(\"SubagentPool has been disposed\"));\n\t\t}\n\n\t\tconst promises = tasks.map(async ({ agent_type, prompt }) => {\n\t\t\treturn this.dispatch(prompt, agent_type);\n\t\t});\n\n\t\treturn Promise.all(promises);\n\t}\n\n\tprivate writeDispatchLog(\n\t\ttask_id: string,\n\t\tagent_type: string,\n\t\treason: string,\n\t\tcomplexity: string,\n\t\ttask: string,\n\t): void {\n\t\tconst log = {\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\ttask_id,\n\t\t\tagent_type,\n\t\t\treason,\n\t\t\tcomplexity,\n\t\t\ttask,\n\t\t};\n\t\tconst path = join(this.cwd, CONFIG_DIR_NAME, \"agents\", task_id, \"dispatch-log.json\");\n\t\ttry {\n\t\t\tmkdirSync(dirname(path), { recursive: true });\n\t\t\twriteFileSync(path, JSON.stringify(log, null, 2));\n\t\t} catch {\n\t\t\t// Best-effort persistence\n\t\t}\n\t}\n\n\tprivate writeOutputJson(task_id: string, result: SubagentResult): void {\n\t\tconst output = {\n\t\t\ttask_id: result.task_id,\n\t\t\tok: result.ok,\n\t\t\texit_code: result.exit_code,\n\t\t\tstatus: result.status,\n\t\t\tstdout: result.stdout,\n\t\t\tstderr: result.stderr,\n\t\t\terror: result.error,\n\t\t\tbudget_exceeded: result.budget_exceeded,\n\t\t\tresult_data: result.result_data,\n\t\t};\n\t\tconst path = join(this.cwd, CONFIG_DIR_NAME, \"agents\", task_id, \"output.json\");\n\t\ttry {\n\t\t\tmkdirSync(dirname(path), { recursive: true });\n\t\t\twriteFileSync(path, JSON.stringify(output, null, 2));\n\t\t} catch {\n\t\t\t// Best-effort persistence\n\t\t}\n\t}\n\n\t/** Kill all running processes, clear the queue, and reject pending waiters. */\n\tdispose(): void {\n\t\tif (this.disposed) return;\n\t\tthis.disposed = true;\n\n\t\tfor (const slot of this.slots.values()) {\n\t\t\tif (!slot.process.killed) {\n\t\t\t\tslot.process.kill(\"SIGTERM\");\n\t\t\t}\n\t\t}\n\t\tthis.slots.clear();\n\t\tthis.queue = [];\n\n\t\tfor (const [task_id, waiter] of this.waiters) {\n\t\t\twaiter.reject(new Error(\"SubagentPool disposed\"));\n\t\t\tthis.waiters.delete(task_id);\n\t\t}\n\t\tthis.completed.clear();\n\t\tfor (const budget of this.budgets.values()) {\n\t\t\tbudget.removeAllListeners();\n\t\t}\n\t\tthis.budgets.clear();\n\t\tthis.killReasons.clear();\n\t\tthis.taskStatus.clear();\n\t\tthis.lifeguard.dispose();\n\t\tthis.removeAllListeners();\n\t}\n\n\t/** Pull tasks from the queue while slots are available. */\n\tprivate pull(): void {\n\t\twhile (this.slots.size < this.maxConcurrency && this.queue.length > 0) {\n\t\t\tconst task = this.queue.shift()!;\n\t\t\tthis.startTask(task, false);\n\t\t}\n\t}\n\n\t/** Build CLI arguments for a task. */\n\tprivate buildArgs(task: SubagentPoolTask): string[] {\n\t\tconst args: string[] = [\"--mode\", \"json\", \"--no-session\"];\n\n\t\tif (task.agent_type) {\n\t\t\ttry {\n\t\t\t\tconst systemPrompt = getSubagentSystemPrompt(task.agent_type as SubagentMode);\n\t\t\t\targs.push(\"--system-prompt\", systemPrompt);\n\t\t\t} catch {\n\t\t\t\t// Unknown mode, skip custom system prompt\n\t\t\t}\n\t\t}\n\n\t\tif (task.model) {\n\t\t\targs.push(\"--model\", task.model);\n\t\t}\n\t\tif (task.provider) {\n\t\t\targs.push(\"--provider\", task.provider);\n\t\t}\n\n\t\tconst prompt = task.context?.trim()\n\t\t\t? `Context from the calling agent:\\n\\n${task.context.trim()}\\n\\nTask: ${task.task.trim()}`\n\t\t\t: `Task: ${task.task.trim()}`;\n\t\targs.push(prompt);\n\n\t\treturn args;\n\t}\n\n\t/** Start a task in a child process, with one retry on failure. */\n\tprivate startTask(task: SubagentPoolTask, isRetry: boolean): void {\n\t\t// Get or create a TokenBudget tracker. On retry, reuse the existing one\n\t\t// so cumulative usage persists across retries.\n\t\tlet budget = this.budgets.get(task.task_id);\n\t\tif (!budget) {\n\t\t\tbudget = new TokenBudget(task.task_id, task.agent_type, {\n\t\t\t\tlimit: task.token_budget,\n\t\t\t\tcwd: task.cwd ?? this.cwd,\n\t\t\t});\n\t\t\tbudget.on(\"budget_warning\", (data: { task_id: string; message: string; used: number; limit: number }) => {\n\t\t\t\tthis.emit(\"budget_warning\", data);\n\t\t\t});\n\t\t\tbudget.on(\"budget_exceeded\", () => {\n\t\t\t\tconst slot = this.slots.get(task.task_id);\n\t\t\t\tif (slot && !slot.process.killed) {\n\t\t\t\t\tslot.process.kill(\"SIGTERM\");\n\t\t\t\t}\n\t\t\t});\n\t\t\tthis.budgets.set(task.task_id, budget);\n\t\t}\n\n\t\tlet proc: ReturnType<typeof spawn>;\n\t\ttry {\n\t\t\tproc = spawn(this.executable, this.buildArgs(task), {\n\t\t\t\tcwd: task.cwd ?? this.cwd,\n\t\t\t\tenv: this.env,\n\t\t\t\tshell: false,\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t});\n\t\t} catch {\n\t\t\tif (!isRetry) {\n\t\t\t\tthis.startTask(task, true);\n\t\t\t} else {\n\t\t\t\tthis.emit(\"task_failed\", {\n\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\terror: \"Spawn failed synchronously\",\n\t\t\t\t});\n\t\t\t\tthis.resolveWaiter(task.task_id, {\n\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\tok: false,\n\t\t\t\t\tstdout: \"\",\n\t\t\t\t\tstderr: \"\",\n\t\t\t\t\texit_code: null,\n\t\t\t\t\terror: \"Spawn failed synchronously\",\n\t\t\t\t\tstatus: \"failed\",\n\t\t\t\t});\n\t\t\t\tthis.pull();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst slot: SubagentSlot = {\n\t\t\tpid: proc.pid ?? 0,\n\t\t\tagent_type: task.agent_type,\n\t\t\ttask_id: task.task_id,\n\t\t\tspawned_at: Date.now(),\n\t\t\ttoken_budget: task.token_budget ?? this.defaultTokenBudget,\n\t\t\tprocess: proc,\n\t\t};\n\n\t\tthis.slots.set(task.task_id, slot);\n\t\tthis.lifeguard.monitor(task.task_id, task.agent_type, proc);\n\n\t\tlet stdout = \"\";\n\t\tlet stderr = \"\";\n\n\t\tproc.stdout?.on(\"data\", (data: Buffer) => {\n\t\t\tconst chunk = data.toString();\n\t\t\tstdout += chunk;\n\t\t\tbudget.processStdout(chunk);\n\n\t\t\t// Heartbeat detection: look for {\"ping\":true} JSON lines\n\t\t\tfor (const raw of chunk.split(\"\\n\")) {\n\t\t\t\tconst line = raw.trim();\n\t\t\t\tif (!line.startsWith(\"{\")) continue;\n\t\t\t\ttry {\n\t\t\t\t\tconst parsed = JSON.parse(line) as Record<string, unknown>;\n\t\t\t\t\tif (parsed.ping === true) {\n\t\t\t\t\t\tthis.lifeguard.recordHeartbeat(task.task_id);\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// Not a ping line, ignore\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tproc.stderr?.on(\"data\", (data: Buffer) => {\n\t\t\tstderr += data.toString();\n\t\t});\n\n\t\twaitForChildProcess(proc)\n\t\t\t.then((code) => {\n\t\t\t\tthis.slots.delete(task.task_id);\n\t\t\t\tbudget.flush();\n\n\t\t\t\tconst killReason = this.killReasons.get(task.task_id);\n\t\t\t\tthis.killReasons.delete(task.task_id);\n\n\t\t\t\tconst duration = Date.now() - slot.spawned_at;\n\t\t\t\tconst tokens_used = budget.getUsed();\n\t\t\t\tconst budgetExceeded = budget.isExceeded();\n\n\t\t\t\t// If killed by lifeguard, override exit handling\n\t\t\t\tif (killReason === \"stalled\" || killReason === \"timeout\") {\n\t\t\t\t\tconst result: SubagentResult = {\n\t\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\t\tok: false,\n\t\t\t\t\t\tstdout,\n\t\t\t\t\t\tstderr,\n\t\t\t\t\t\texit_code: code,\n\t\t\t\t\t\tstatus: killReason,\n\t\t\t\t\t};\n\t\t\t\t\tthis.writeOutputJson(task.task_id, result);\n\t\t\t\t\tthis.emit(`task_${killReason}`, {\n\t\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\t\tagent_type: task.agent_type,\n\t\t\t\t\t\tduration,\n\t\t\t\t\t\ttokens_used,\n\t\t\t\t\t});\n\t\t\t\t\tthis.resolveWaiter(task.task_id, result);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst result: SubagentResult = {\n\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\tok: code === 0 && !budgetExceeded,\n\t\t\t\t\tstdout,\n\t\t\t\t\tstderr,\n\t\t\t\t\texit_code: code,\n\t\t\t\t\tbudget_exceeded: budgetExceeded,\n\t\t\t\t\tstatus: code === 0 && !budgetExceeded ? \"complete\" : \"failed\",\n\t\t\t\t};\n\n\t\t\t\tif (budgetExceeded) {\n\t\t\t\t\t// Force-return whatever exists in result.json, mark partial\n\t\t\t\t\tconst resultData = this.tryReadResultJson(task.task_id, task.cwd ?? this.cwd);\n\t\t\t\t\tresult.status = resultData ? \"partial\" : \"failed\";\n\t\t\t\t\tresult.result_data = resultData;\n\t\t\t\t\tif (resultData) {\n\t\t\t\t\t\tresult.ok = true; // partial is considered success with data\n\t\t\t\t\t}\n\t\t\t\t\tthis.writeOutputJson(task.task_id, result);\n\t\t\t\t\tthis.emit(\"task_done\", {\n\t\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\t\tagent_type: task.agent_type,\n\t\t\t\t\t\tduration,\n\t\t\t\t\t\ttokens_used,\n\t\t\t\t\t\tstatus: \"partial\",\n\t\t\t\t\t});\n\t\t\t\t\tthis.resolveWaiter(task.task_id, result);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (result.ok) {\n\t\t\t\t\tconst verification = this.verifier.verify(task.task_id, task.cwd ?? this.cwd);\n\t\t\t\t\tif (!verification.valid) {\n\t\t\t\t\t\tresult.ok = false;\n\t\t\t\t\t\tresult.error = verification.reason;\n\t\t\t\t\t\tresult.status = \"failed\";\n\t\t\t\t\t\tthis.writeOutputJson(task.task_id, result);\n\t\t\t\t\t\tthis.emit(\"task_failed\", {\n\t\t\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\t\t\tagent_type: task.agent_type,\n\t\t\t\t\t\t\tduration,\n\t\t\t\t\t\t\ttokens_used,\n\t\t\t\t\t\t\terror: verification.reason,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tthis.resolveWaiter(task.task_id, result);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tthis.writeOutputJson(task.task_id, result);\n\n\t\t\t\tif (result.ok) {\n\t\t\t\t\tthis.emit(\"task_done\", {\n\t\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\t\tagent_type: task.agent_type,\n\t\t\t\t\t\tduration,\n\t\t\t\t\t\ttokens_used,\n\t\t\t\t\t\tstatus: \"complete\",\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tthis.emit(\"task_failed\", {\n\t\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\t\tagent_type: task.agent_type,\n\t\t\t\t\t\tduration,\n\t\t\t\t\t\ttokens_used,\n\t\t\t\t\t\terror: result.error ?? `Exited with code ${code}`,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tthis.resolveWaiter(task.task_id, result);\n\t\t\t})\n\t\t\t.catch((err) => {\n\t\t\t\tthis.slots.delete(task.task_id);\n\t\t\t\tbudget.flush();\n\t\t\t\tconst duration = Date.now() - slot.spawned_at;\n\t\t\t\tconst tokens_used = budget.getUsed();\n\t\t\t\tif (!isRetry) {\n\t\t\t\t\tthis.startTask(task, true);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst error = err instanceof Error ? err.message : String(err);\n\t\t\t\tconst result: SubagentResult = {\n\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\tok: false,\n\t\t\t\t\tstdout,\n\t\t\t\t\tstderr,\n\t\t\t\t\texit_code: null,\n\t\t\t\t\terror,\n\t\t\t\t\tstatus: \"failed\",\n\t\t\t\t};\n\t\t\t\tthis.writeOutputJson(task.task_id, result);\n\t\t\t\tthis.emit(\"task_failed\", {\n\t\t\t\t\ttask_id: task.task_id,\n\t\t\t\t\tagent_type: task.agent_type,\n\t\t\t\t\tduration,\n\t\t\t\t\ttokens_used,\n\t\t\t\t\terror,\n\t\t\t\t});\n\t\t\t\tthis.resolveWaiter(task.task_id, result);\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\tbudget.removeAllListeners();\n\t\t\t\tthis.budgets.delete(task.task_id);\n\t\t\t\tthis.pull();\n\t\t\t});\n\t}\n\n\tprivate tryReadResultJson(task_id: string, cwd: string): Record<string, unknown> | undefined {\n\t\tconst path = join(cwd, CONFIG_DIR_NAME, \"agents\", task_id, \"result.json\");\n\t\tif (!existsSync(path)) return undefined;\n\t\ttry {\n\t\t\tconst raw = readFileSync(path, \"utf-8\");\n\t\t\treturn JSON.parse(raw) as Record<string, unknown>;\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tprivate resolveWaiter(task_id: string, result: SubagentResult): void {\n\t\t// Persist terminal status for get_status() even after wait_for consumes the result\n\t\tif (result.status === \"stalled\") this.taskStatus.set(task_id, \"stalled\");\n\t\telse if (result.status === \"timeout\") this.taskStatus.set(task_id, \"timeout\");\n\t\telse if (result.ok) this.taskStatus.set(task_id, \"done\");\n\t\telse this.taskStatus.set(task_id, \"failed\");\n\n\t\tconst waiter = this.waiters.get(task_id);\n\t\tif (waiter) {\n\t\t\twaiter.resolve(result);\n\t\t\tthis.waiters.delete(task_id);\n\t\t\treturn;\n\t\t}\n\t\tthis.completed.set(task_id, result);\n\t}\n}\n"]}
|
package/dist/core/subagent.d.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import type { Model } from "@kolisachint/hoocode-ai";
|
|
13
13
|
import type { ModelRegistry } from "./model-registry.js";
|
|
14
|
-
export type SubagentMode = "explore" | "edit" | "test" | "fix" | "review";
|
|
14
|
+
export type SubagentMode = "explore" | "edit" | "test" | "fix" | "review" | "doc";
|
|
15
15
|
export declare const SUBAGENT_MODES: readonly SubagentMode[];
|
|
16
16
|
export interface RunSubagentOptions {
|
|
17
17
|
/** The task to delegate. */
|
|
@@ -36,6 +36,14 @@ export interface SubagentResult {
|
|
|
36
36
|
ok: boolean;
|
|
37
37
|
/** Populated when ok is false. */
|
|
38
38
|
error?: string;
|
|
39
|
+
/** Token and cost usage from the subagent session, if available. */
|
|
40
|
+
usage?: {
|
|
41
|
+
input: number;
|
|
42
|
+
output: number;
|
|
43
|
+
cacheRead: number;
|
|
44
|
+
cacheWrite: number;
|
|
45
|
+
cost: number;
|
|
46
|
+
};
|
|
39
47
|
}
|
|
40
48
|
/** Return the clean, minimal system prompt for a subagent mode. */
|
|
41
49
|
export declare function getSubagentSystemPrompt(mode: SubagentMode): string;
|